/* NAME: IRTransparent.c DESCRIPTION: Quesa interactive renderer transparency support. COPYRIGHT: Copyright (c) 1999-2005, Quesa Developers. All rights reserved. For the current release of Quesa, please see: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: o Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. o Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. o Neither the name of Quesa nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ___________________________________________________________________________ */ //============================================================================= // Include files //----------------------------------------------------------------------------- #include "IRPrefix.h" #include "IRTransparent.h" #include "IRUpdate.h" #include //============================================================================= // Internal constants //----------------------------------------------------------------------------- const float kQ3LargeZero = -1.0e-5f; const TQ3Point3D kFrustumOrigin = { 0.0f, 0.0f, 0.0f }; // In lieu of glext.h #ifndef GL_EXT_blend_minmax #define GL_FUNC_ADD_EXT 0x8006 #define GL_MIN_EXT 0x8007 #define GL_MAX_EXT 0x8008 #define GL_BLEND_EQUATION_EXT 0x8009 #endif //============================================================================= // Internal functions //----------------------------------------------------------------------------- //============================================================================= // ir_geom_calc_z_sum : Compute sum of z coordinates of a primitive. //----------------------------------------------------------------------------- static float ir_geom_calc_z_sum( const TQ3TransparentPrim* prim ) { float zSum = prim->frustumSpaceVerts[0].z; if (prim->numVerts > 1) { zSum += prim->frustumSpaceVerts[1].z; } if (prim->numVerts > 2) { zSum += prim->frustumSpaceVerts[2].z; } return zSum; } //============================================================================= // ir_geom_centroid_compare : Compare depth by average z coordinate. //----------------------------------------------------------------------------- // We actually use a sum rather than an average to avoid division. This // makes the comparison incorrect when comparing primitives with // different numbers of vertices, but it is probably most common for all // vertices to have the same number of vertices. // // This method of depth comparison is clearly incorrect in many cases, // but has the advantage of speed and simplicity. Since it is a true // linear order on vertices, we can use a standard sorting algorithm such // as qsort. static int ir_geom_centroid_compare(const void *item1, const void *item2) { TQ3TransparentPrim *prim1, *prim2; int sortResult; float sum1, sum2; // Grab our parameters prim1 = *(TQ3TransparentPrim **) item1; prim2 = *(TQ3TransparentPrim **) item2; sum1 = ir_geom_calc_z_sum( prim1 ); sum2 = ir_geom_calc_z_sum( prim2 ); if (sum1 < sum2) sortResult = -1; else sortResult = 1; return sortResult; } //============================================================================= // ir_geom_transparent_needs_specular : Test whether there may be specular highlights. //----------------------------------------------------------------------------- static TQ3Boolean ir_geom_transparent_needs_specular( const TQ3TransparentPrim *thePrim ) { return (TQ3Boolean)( (thePrim->numVerts == 3) && (thePrim->illumination == kQ3IlluminationTypePhong) && (thePrim->fillStyle == kQ3FillStyleFilled) ); } //============================================================================= // ir_geom_transparent_specular_render : Render a cached primitive for specular highlights. //----------------------------------------------------------------------------- static void ir_geom_transparent_specular_render(const TQ3TransparentPrim *thePrim) { const TQ3FVertex3D *theVertex; TQ3Uns32 n; // Validate our parameters Q3_ASSERT(thePrim->numVerts == 3); // Begin the primitive glBegin(GL_TRIANGLES); // Draw the primitive theVertex = thePrim->theVertices; for (n = 0; n < 3; ++n) { if (E3Bit_IsSet(theVertex->theFlags, kQ3FVertexHaveNormal)) glNormal3fv((const GLfloat *) &theVertex->theNormal); glVertex3fv((const GLfloat *) &theVertex->thePoint); theVertex++; } // Finish the primitive glEnd(); } //============================================================================= // ir_geom_transparent_render : Render a cached primitive. //----------------------------------------------------------------------------- static void ir_geom_transparent_render(const TQ3TransparentPrim *thePrim) { const TQ3FVertex3D *theVertex; TQ3FVertexFlags vertFlags; float vertAlpha; TQ3Uns32 n; // Validate our parameters Q3_ASSERT(thePrim->numVerts >= 1 && thePrim->numVerts <= 3); // Enable the texture // // The OpenGL texture object will still exist in the texture cache, and so // we can bind to it immediately without needing the original QD3D object. if (thePrim->theTexture != 0) { glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, thePrim->theTexture); } // Begin the primitive switch (thePrim->numVerts) { case 3: glBegin(GL_TRIANGLES); break; case 2: glBegin(GL_LINES); break; case 1: glBegin(GL_POINTS); break; } // Draw the primitive theVertex = thePrim->theVertices; vertFlags = theVertex->theFlags; for (n = 0; n < thePrim->numVerts; n++) { if (E3Bit_IsSet(vertFlags, kQ3FVertexHaveNormal)) glNormal3fv((const GLfloat *) &theVertex->theNormal); if (E3Bit_IsSet(vertFlags, kQ3FVertexHaveUV)) glTexCoord2fv((const GLfloat *) &theVertex->theUV); vertAlpha = (theVertex->colourTransparency.r + theVertex->colourTransparency.g + theVertex->colourTransparency.b) * 0.33333333f; glColor4f(theVertex->colourDiffuse.r, theVertex->colourDiffuse.g, theVertex->colourDiffuse.b, vertAlpha); glVertex3fv((const GLfloat *) &theVertex->thePoint); theVertex++; } // Finish the primitive glEnd(); // Turn off the texture if (thePrim->theTexture != 0) { glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); } } //============================================================================= // ir_geom_transparent_add : Add a transparent primitive. //----------------------------------------------------------------------------- static TQ3Status ir_geom_transparent_add(TQ3ViewObject theView, TQ3InteractiveData *instanceData, TQ3Uns32 numVerts, const TQ3FVertex3D *theVertices) { #pragma unused( theView ) TQ3Matrix4x4 localToFrustum; TQ3Point3D theCameraPoints[3]; const TQ3FVertex3D *theVertex; TQ3FVertexFlags vertFlags; TQ3TransparentPrim *thePrim; TQ3Uns32 n; TQ3Boolean isAllowed; // Validate our parameters for (n = 0; n < numVerts; n++) Q3_ASSERT(theVertices[0].theFlags == theVertices[n].theFlags); Q3_ASSERT(E3Bit_IsSet(theVertices[0].theFlags, kQ3FVertexHaveTransparency)); // Transform the vertices to camera space. // If all z values are positive, reject the primitive. theVertex = theVertices; isAllowed = kQ3False; for (n = 0; n < numVerts; n++) { Q3Point3D_Transform(&theVertex->thePoint, &instanceData->stateMatrixLocalToCamera, &theCameraPoints[n]); if ( theCameraPoints[n].z <= 0.0f ) { isAllowed = kQ3True; } theVertex++; } if (isAllowed == kQ3False) { return kQ3Success; } // Allocate another primitive thePrim = (TQ3TransparentPrim *) Q3SlabMemory_AppendData(instanceData->transBufferSlab, 1, NULL); if (thePrim == NULL) return(kQ3Failure); // Calculate the local->frustum matrix Q3Matrix4x4_Multiply(&instanceData->stateMatrixLocalToCamera, &instanceData->stateMatrixCameraToFrustum, &localToFrustum); // Set up the primitive vertices // // Points are transformed to frustum space, so that we can sort // in a fixed coordinate system. However we use camera coordinates for rendering. thePrim->numVerts = numVerts; Q3Memory_Copy(theVertices, thePrim->theVertices, numVerts * sizeof(TQ3FVertex3D)); vertFlags = thePrim->theVertices[0].theFlags; for (n = 0; n < numVerts; n++) { Q3Point3D_Transform(&thePrim->theVertices[n].thePoint, &localToFrustum, &thePrim->frustumSpaceVerts[n]); thePrim->theVertices[n].thePoint = theCameraPoints[n]; if (E3Bit_IsSet(vertFlags, kQ3FVertexHaveNormal)) { Q3Vector3D_Transform(&thePrim->theVertices[n].theNormal, &instanceData->stateMatrixLocalToCamera, &thePrim->theVertices[n].theNormal); Q3Vector3D_Normalize(&thePrim->theVertices[n].theNormal, &thePrim->theVertices[n].theNormal); } } // Set up the primitive state thePrim->theTexture = instanceData->stateTextureObject; thePrim->textureIsTransparent = instanceData->stateTextureIsTransparent; thePrim->orientationStyle = instanceData->stateOrientation; thePrim->fillStyle = instanceData->stateFill; thePrim->backfacingStyle = instanceData->stateBackfacing; thePrim->specularColor = *instanceData->stateGeomSpecularColour; thePrim->specularControl = instanceData->stateGeomSpecularControl; thePrim->illumination = instanceData->stateViewIllumination; thePrim->needsSpecular = ir_geom_transparent_needs_specular(thePrim); thePrim->cameraToFrustum = instanceData->stateMatrixCameraToFrustum; thePrim->fogStyleIndex = instanceData->curFogStyleIndex; return(kQ3Success); } //============================================================================= // ir_geom_transparent_update_specular : Update specularity for a transparent primitive. //----------------------------------------------------------------------------- static void ir_geom_transparent_update_specular( const TQ3TransparentPrim* inPrim, GLfloat* ioSpecularColor, float* ioSpecularControl ) { if ( (inPrim->specularColor.r != ioSpecularColor[0]) || (inPrim->specularColor.g != ioSpecularColor[1]) || (inPrim->specularColor.b != ioSpecularColor[2]) ) { ioSpecularColor[0] = inPrim->specularColor.r; ioSpecularColor[1] = inPrim->specularColor.g; ioSpecularColor[2] = inPrim->specularColor.b; glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, ioSpecularColor ); } if (inPrim->specularControl != *ioSpecularControl) { float specularControl; GLfloat shininess; specularControl = *ioSpecularControl = inPrim->specularControl; shininess = IRRenderer_SpecularControl_to_GLshininess( specularControl ); glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, shininess ); } } //============================================================================= // ir_geom_transparent_update_fog : Update fog state if needed. //----------------------------------------------------------------------------- static void ir_geom_transparent_update_fog( const TQ3TransparentPrim* inPrim, TQ3ViewObject theView, TQ3InteractiveData *instanceData ) { if (inPrim->fogStyleIndex != instanceData->curFogStyleIndex) { IRRenderer_Update_Style_Fog( theView, instanceData, &instanceData->fogStyles[ inPrim->fogStyleIndex ] ); } } //============================================================================= // ir_geom_transparent_equal_matrix4x4 : Test 4x4 matrices for equality. //----------------------------------------------------------------------------- static TQ3Boolean ir_geom_transparent_equal_matrix4x4( const TQ3Matrix4x4* inOne, const TQ3Matrix4x4* inTwo ) { TQ3Boolean isSame = kQ3True; const float* vals1 = &inOne->value[0][0]; const float* vals2 = &inTwo->value[0][0]; int i; for (i = 0; i < 16; ++i) { if (fabsf( vals1[i] - vals2[i] ) > kQ3RealZero) { isSame = kQ3False; break; } } return isSame; } //============================================================================= // Public functions //----------------------------------------------------------------------------- // IRTransBuffer_Initialize : Initialise the transparency buffer. //----------------------------------------------------------------------------- #pragma mark - TQ3Status IRTransBuffer_Initialize(TQ3InteractiveData *instanceData) { // Initialise our state instanceData->transBufferSlab = Q3SlabMemory_New(sizeof(TQ3TransparentPrim ), 0, NULL); instanceData->transPtrSlab = Q3SlabMemory_New(sizeof(TQ3TransparentPrim*), 0, NULL); if (instanceData->transBufferSlab == NULL || instanceData->transPtrSlab == NULL) { Q3Object_CleanDispose(&instanceData->transBufferSlab); Q3Object_CleanDispose(&instanceData->transPtrSlab); return(kQ3Failure); } return(kQ3Success); } //============================================================================= // IRTransBuffer_Terminate : Terminate the transparency buffer. //----------------------------------------------------------------------------- void IRTransBuffer_Terminate(TQ3InteractiveData *instanceData) { // Release our state Q3Object_CleanDispose(&instanceData->transBufferSlab); Q3Object_CleanDispose(&instanceData->transPtrSlab); } //============================================================================= // IRTransBuffer_Draw : Draw the transparent primitives. //----------------------------------------------------------------------------- void IRTransBuffer_Draw(TQ3ViewObject theView, TQ3InteractiveData *instanceData) { TQ3CameraTransformData cameraTransformData; TQ3Uns32 n, numPrims; TQ3TransparentPrim *thePrims; TQ3TransparentPrim **ptrs; GLfloat specularColor[4] = { -1.0f, -1.0f, -1.0f, 1.0f }; float specularControl = -1.0f; const GLfloat kBlackColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; TQ3Boolean shouldLightingBeEnabled, isLightingEnabled; TQ3FillStyle curFillStyle; TQ3OrientationStyle curOrientation; // Draw the transparent primitives numPrims = Q3SlabMemory_GetCount(instanceData->transBufferSlab); if (numPrims != 0) { // Get the primitives, and sort them thePrims = (TQ3TransparentPrim *) Q3SlabMemory_GetData( instanceData->transBufferSlab, 0); if (kQ3Success != Q3SlabMemory_SetCount( instanceData->transPtrSlab, numPrims )) return; ptrs = (TQ3TransparentPrim **) Q3SlabMemory_GetData( instanceData->transPtrSlab, 0); for (n = 0; n < numPrims; ++n) ptrs[n] = &thePrims[n]; qsort( ptrs, numPrims, sizeof(TQ3TransparentPrim*), ir_geom_centroid_compare ); // Save some OpenGL state glPushAttrib( GL_DEPTH_BUFFER_BIT | GL_LIGHTING_BIT | GL_COLOR_BUFFER_BIT ); // Update the OpenGL state // // We need to clear any transforms which are active when the rendering loop ended, // since transparent primitives are already transformed into camera coordinates, // and the camera to frustum matrix will be updated later. // // We also enable blending and turn off writes to the depth buffer. Q3Matrix4x4_SetIdentity(&cameraTransformData.localToWorld); Q3Matrix4x4_SetIdentity(&cameraTransformData.worldToCamera); Q3Matrix4x4_SetIdentity(&cameraTransformData.cameraToFrustum); Q3CameraTransform_Submit(&cameraTransformData, theView); glEnable(GL_BLEND); // The transparent pass does not need to write to the depth buffer, since it // is done after opaque stuff and is depth-sorted, but we will do depth testing. // Most likely the current depth comparison function is GL_LESS, and we need not // change that. Note that since we are not writing to the depth buffer, we // can continue to use the same depth comparison function when adding // specular highlights. glDepthMask(GL_FALSE); isLightingEnabled = kQ3True; glEnable(GL_LIGHTING); curFillStyle = kQ3FillStyleFilled; glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); curOrientation = kQ3OrientationStyleCounterClockwise; glFrontFace(GL_CCW); // The first pass will not include specularity, so we set the specular color black. glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, kBlackColor ); // Draw the primitives for (n = 0; n < numPrims; n++) { // Select the appropriate blending function // // Primitives can be transparent due to a texture or their vertex alpha. if (ptrs[n]->textureIsTransparent) glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); else glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Update the camera to frustum matrix if it has changed. if (kQ3False == ir_geom_transparent_equal_matrix4x4( &cameraTransformData.cameraToFrustum, &ptrs[n]->cameraToFrustum )) { cameraTransformData.cameraToFrustum = ptrs[n]->cameraToFrustum; Q3CameraTransform_Submit(&cameraTransformData, theView); } // Update lighting shouldLightingBeEnabled = (TQ3Boolean) (ptrs[n]->illumination != kQ3IlluminationTypeNULL); if (shouldLightingBeEnabled != isLightingEnabled) { if ( shouldLightingBeEnabled ) { glEnable(GL_LIGHTING); } else { glDisable(GL_LIGHTING); } isLightingEnabled = shouldLightingBeEnabled; } // Update fill style if (ptrs[n]->fillStyle != curFillStyle) { curFillStyle = ptrs[n]->fillStyle; switch (curFillStyle) { case kQ3FillStylePoints: glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); break; case kQ3FillStyleEdges: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); break; case kQ3FillStyleFilled: default: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); break; } } // Update orientation if ( (ptrs[n]->numVerts == 3) && (ptrs[n]->orientationStyle != curOrientation) ) { curOrientation = ptrs[n]->orientationStyle; if (curOrientation == kQ3OrientationStyleClockwise) glFrontFace(GL_CW); else glFrontFace(GL_CCW); } // Update fog ir_geom_transparent_update_fog( ptrs[n], theView, instanceData ); // Render the primitive ir_geom_transparent_render(ptrs[n]); // Add specular highlights if appropriate if (ptrs[n]->needsSpecular) { // Add, not alpha-blend, but use max rather than addition if possible // so that color components do not get too big. glBlendFunc( GL_ONE, GL_ONE ); if (instanceData->glBlendEqProc != NULL) (*instanceData->glBlendEqProc)( GL_MAX_EXT ); // black ambient and diffuse so we get only specular glDisable( GL_COLOR_MATERIAL ); glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, kBlackColor ); ir_geom_transparent_update_specular( ptrs[n], specularColor, &specularControl ); ir_geom_transparent_specular_render( ptrs[n] ); glEnable( GL_COLOR_MATERIAL ); if (instanceData->glBlendEqProc != NULL) (*instanceData->glBlendEqProc)( GL_FUNC_ADD_EXT ); } } // Reset the OpenGL state glPopAttrib(); // Empty the cache Q3SlabMemory_SetCount(instanceData->transBufferSlab, 0); Q3SlabMemory_SetCount(instanceData->transPtrSlab, 0); } } //============================================================================= // IRTransBuffer_AddTriangle : Add a transparent triangle. //----------------------------------------------------------------------------- TQ3Status IRTransBuffer_AddTriangle(TQ3ViewObject theView, TQ3InteractiveData *instanceData, const TQ3FVertex3D *theVertices) { TQ3Status qd3dStatus; // Add the triangle qd3dStatus = ir_geom_transparent_add(theView, instanceData, 3, theVertices); return(qd3dStatus); } //============================================================================= // IRTransBuffer_AddLine : Add a transparent line. //----------------------------------------------------------------------------- TQ3Status IRTransBuffer_AddLine(TQ3ViewObject theView, TQ3InteractiveData *instanceData, const TQ3FVertex3D *theVertices) { TQ3Status qd3dStatus; // Add the line qd3dStatus = ir_geom_transparent_add(theView, instanceData, 2, theVertices); return(qd3dStatus); } //============================================================================= // IRTransBuffer_AddPoint : Add a transparent point. //----------------------------------------------------------------------------- TQ3Status IRTransBuffer_AddPoint(TQ3ViewObject theView, TQ3InteractiveData *instanceData, const TQ3FVertex3D *theVertex) { TQ3Status qd3dStatus; // Add the point qd3dStatus = ir_geom_transparent_add(theView, instanceData, 1, theVertex); return(qd3dStatus); }