/* ** Command & Conquer Generals(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ //////////////////////////////////////////////////////////////////////////////// // // // (c) 2001-2003 Electronic Arts Inc. // // // //////////////////////////////////////////////////////////////////////////////// // FILE: W3DVolumetricShadow.cpp /////////////////////////////////////////////////////////// // // Real time shadow volume representations // // Author: Colin Day, January 2001 // Adapted for W3D: Mark Wilczynski October 2001 // // /////////////////////////////////////////////////////////////////////////////// ///@todo: Must cap shadow volumes if we ever allow camera inside the volumes. ///@todo: Find better way to determine when shadow volumes need updating - lights move, objects move. // SYSTEM INCLUDES //////////////////////////////////////////////////////////// #include // USER INCLUDES ////////////////////////////////////////////////////////////// #include "always.h" #include "GameClient/View.h" #include "WW3D2/Camera.h" #include "WW3D2/Light.h" #include "WW3D2/DX8Wrapper.h" #include "WW3D2/HLod.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" #include "Lib/BaseType.h" #include "W3DDevice/GameClient/W3DGranny.h" #include "W3DDevice/GameClient/Heightmap.h" #include "D3dx8math.h" #include "common/GlobalData.h" #include "common/drawmodule.h" #include "W3DDevice/GameClient/W3DVolumetricShadow.h" #include "W3DDevice/GameClient/W3DShadow.h" #include "WW3D2/statistics.h" #include "GameLogic/TerrainLogic.h" #include "WW3D2/DX8Caps.h" #include "GameClient/Drawable.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif // Global Variables and Functions ///////////////////////////////////////////// W3DVolumetricShadowManager *TheW3DVolumetricShadowManager=NULL; extern const FrustumClass *shadowCameraFrustum; //defined in W3DShadow. /////////////////////////////////////////////////////////////////////////////// // DEFINITIONS //////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // when the change in angle from the object to the light source // (represented in degrees in the first number below) // is large enough the shadow info will be reconstructed const Real cosAngleToCare = cos ((0.2 * PI) / 180.0); //1.5 degree difference #define MAX_SILHOUETTE_EDGES 1024 //maximum number of shadov volume sides or edges in silhoutte #define SHADOW_EXTRUSION_BUFFER 0.1f //amount to extend shadow volume beyond what's required to hit ground. #define AIRBORNE_UNIT_GROUND_DELTA 2.0f #define MAX_SHADOW_LENGTH_SCALE_FACTOR 1.0f //amount shadow can extend beyond the objects normal bounding sphere #define MAX_SHADOW_LENGTH_EXTRA_AIRBORNE_SCALE_FACTOR 1.5f //scales MAX_SHADOW_LENGTH_SCALE_FACTOR a little more for flying units #define MAX_EXTRUSION_LENGTH (512.0f*MAP_XY_FACTOR) //maximum length of a shadow extrusion - assumed as 512x512 cell map for now. #define MAX_SHADOW_EXTRUSION_UNDER_OBJECT_BEFORE_CLAMP 5.0f //maximum amount that shadow can reach below object base (z-position) before we clamp it's length to reduce artifacts. #define SHADOW_SAMPLING_INTERVAL (MAP_XY_FACTOR * 2.0f) //stepsize along ray used to find lowest point on terrain within shadow's reach. #define OVERHANGING_OBJECT_CLAMP_ANGLE (80.0f/180.0f*PI) //for objects that are right on a cliff edge, clamp light angle to cast a nearly vertical shadow. //#define SV_DEBUG //#define SV_DEBUG_BOUNDS struct SHADOW_STATIC_VOLUME_VERTEX //vertex structure passed to D3D { float x,y,z; }; #define SHADOW_STATIC_VOLUME_FVF D3DFVF_XYZ #ifdef SV_DEBUG //in debug mode, dynamic shadows are rendered with random diffuse color struct SHADOW_DYNAMIC_VOLUME_VERTEX //vertex structure passed to D3D { float x,y,z; DWORD diffuse; }; #define SHADOW_DYNAMIC_VOLUME_FVF D3DFVF_XYZ|D3DFVF_DIFFUSE #else typedef struct SHADOW_STATIC_VOLUME_VERTEX SHADOW_DYNAMIC_VOLUME_VERTEX; #define SHADOW_DYNAMIC_VOLUME_FVF D3DFVF_XYZ #endif LPDIRECT3DVERTEXBUFFER8 shadowVertexBufferD3D=NULL; ///Normalize(); #else Vector3::Normalized_Cross_Product(edge2,edge1, pvNorm); #endif return pvNorm; } int GetNumPolygon (void) const {return m_numPolygons;} /// given loaded geometry this builds the polygon neighbor information void buildPolygonNeighbors( void ); void buildPolygonNormals(void) { if (!m_polygonNormals) { //need to allocate storage Vector3 *tempVec = NEW Vector3[m_numPolygons]; for (int i=0; iI]; *psIndexList++ = m_parentVerts[polyi->J]; *psIndexList++ = m_parentVerts[polyi->K]; return 3; } virtual Vector3 *GetVertex (int dwVertId, Vector3 *pvVertex) { *pvVertex=m_verts[dwVertId]; return pvVertex; } MeshClass *m_mesh; ///< W3D mesh for this geometry Int m_meshRobjIndex; ///getMap(); if (!map) return 0; Int row=dwPolyId/((m_width-1)<<1); Int column=(dwPolyId>>1)-row*((m_width-1)); #ifdef FLIP_TRIANGLES UnsignedByte alpha[4]; float UA[4], VA[4]; Bool flipForBlend; map->getAlphaUVData(column+m_patchOriginX, row+m_patchOriginY, UA, VA, alpha, &flipForBlend, false); if (flipForBlend) { if (dwPolyId &1) { psIndexList[0]=row*m_width+column+1; psIndexList[1]=(row+1)*m_width+column+1; psIndexList[2]=(row+1)*m_width+column; } else { psIndexList[0]=row*m_width+column; psIndexList[1]=row*m_width+column+1; psIndexList[2]=(row+1)*m_width+column; } } else #endif { if (dwPolyId &1) { psIndexList[0]=row*m_width+column; psIndexList[1]=row*m_width+column+1; psIndexList[2]=(row+1)*m_width+column+1; } else { psIndexList[0]=row*m_width+column; psIndexList[1]=(row+1)*m_width+column+1; psIndexList[2]=(row+1)*m_width+column; } } return 3; } Vector3 *W3DShadowGeometryHeightmapMesh::GetVertex (int dwVertId, Vector3 *pvVertex) { WorldHeightMap *map=NULL; if (TheTerrainRenderObject) map=TheTerrainRenderObject->getMap(); if (!map) return NULL; Int row=dwVertId/m_width; Int column=dwVertId-row*m_width; UnsignedByte *data=map->getDataPtr(); pvVertex->X=(m_patchOriginX+column)*MAP_XY_FACTOR; pvVertex->Y=(m_patchOriginY+row)*MAP_XY_FACTOR; pvVertex->Z=(Real)data[(m_patchOriginX+column)+(m_patchOriginY+row)*map->getXExtent()]*MAP_HEIGHT_SCALE; return pvVertex; } Bool isPatchShadowed(W3DShadowGeometryHeightmapMesh *hm_mesh) { WorldHeightMap *map=NULL; Short poly[ 3 ]; Vector3 vertex; Vector3 normal,lightVector; Int firstVisible=0; Int testVisible; if (TheTerrainRenderObject) map=TheTerrainRenderObject->getMap(); if (!map) return NULL; hm_mesh->GetPolygonNormal( 0, &normal ); // get the vertex indices at this polygon hm_mesh->GetPolygonIndex( 0, poly, 3 ); // // find out "lightVector" to this polygon // // since our light source could be very close to the object and that // would change the shadow we are going to say that the light vector // is from the light position to one of the vertices in the polygon. // To be more correct we should use the center of the polygon but // this is a good approximation ... an ever broader approximation that // we could use would be the object center // hm_mesh->GetVertex( poly[ 0 ], &vertex ); lightVector= vertex - LightPosWorld[0]; // // dot the light vector with the normal of the polygon to see if the // poly is visible from this location // if( Vector3::Dot_Product( lightVector, normal ) < 0.0f ) firstVisible=1; for (Int i=1; iGetNumPolygon(); i++) { hm_mesh->GetPolygonNormal( i, &normal ); // get the vertex indices at this polygon hm_mesh->GetPolygonIndex( i, poly, 3 ); // // find out "lightVector" to this polygon // // since our light source could be very close to the object and that // would change the shadow we are going to say that the light vector // is from the light position to one of the vertices in the polygon. // To be more correct we should use the center of the polygon but // this is a good approximation ... an ever broader approximation that // we could use would be the object center // hm_mesh->GetVertex( poly[ 0 ], &vertex ); lightVector= vertex - LightPosWorld[0]; // // dot the light vector with the normal of the polygon to see if the // poly is visible from this location // testVisible=0; if( Vector3::Dot_Product( lightVector, normal ) < 0.0f ) testVisible=1; // if (testVisible ^ firstVisible) // return TRUE; //found polys facing different directions to sun, will cast shadow if (!testVisible) return TRUE; //some part of mesh not facing light, so it could cast a shadow } return FALSE; } #define SV_MAX_TERRAIN_MESHES 16 static W3DShadowGeometryHeightmapMesh terrainMeshes[SV_MAX_TERRAIN_MESHES]; static Int numTerrainMeshes=0; void W3DVolumetricShadowManager::loadTerrainShadows(void) { WorldHeightMap *map=NULL; Int patchSize=3; if (TheTerrainRenderObject) map=TheTerrainRenderObject->getMap(); if (!map) return; for (Int y=0; ygetYExtent(); y += patchSize-1) { for (Int x=0; xgetXExtent(); x += patchSize-1) { W3DShadowGeometryHeightmapMesh *hm_mesh=&terrainMeshes[numTerrainMeshes]; hm_mesh->setPatchOrigin(x,y); hm_mesh->setPatchSize(patchSize); if(isPatchShadowed(hm_mesh)) { //some polygons in this patch cast shadows, need to generate a mesh hm_mesh->buildPolygonNeighbors(); numTerrainMeshes++; } } } /* W3DShadowGeometryHeightmapMesh hm_mesh; short indexList[3]; Vector3 vertexList[3]; */ /* W3DShadow *shadow = NEW W3DShadow; // add to our shadow list through the shadow next links shadow->m_next = m_shadowList; m_shadowList = shadow; */ } #endif //DO_TERRAIN_SHADOW_VOLUMES /** This class will wrap any shadow casting geometry with additional data needed for efficient shadow volume generation. The W3DVolumetricShadowManager will allocate these structures and hash them for quick re-use on other models sharing the same geometry.*/ class W3DShadowGeometry : public RefCountClass, public HashableClass { public: W3DShadowGeometry( void ) { }; ~W3DShadowGeometry( void ) { }; virtual const char * Get_Key( void ) { return m_namebuf; } Int init (RenderObjClass *robj); Int initFromHLOD (RenderObjClass *robj); ///Get_LOD_Count()-1; W3DShadowGeometryMesh *geomMesh=&m_meshList[m_meshCount]; m_numTotalsVerts=0; for (i = 0; i < hlod->Get_Lod_Model_Count(top); i++) { if (hlod->Peek_Lod_Model(top,i) && hlod->Peek_Lod_Model(top,i)->Class_ID() == RenderObjClass::CLASSID_MESH) { DEBUG_ASSERTCRASH(m_meshCount < MAX_SHADOW_CASTER_MESHES, ("Too many shadow sub-meshes")); geomMesh->m_mesh = (MeshClass *)hlod->Peek_Lod_Model(top,i); geomMesh->m_meshRobjIndex=i; if ((geomMesh->m_mesh->Is_Alpha() || geomMesh->m_mesh->Is_Translucent()) && !geomMesh->m_mesh->Peek_Model()->Get_Flag(MeshGeometryClass::CAST_SHADOW)) continue; //transparent meshes that don't have forced shadows will not cast volumetric shadows MeshModelClass *mm = geomMesh->m_mesh->Peek_Model(); geomMesh->m_numVerts=mm->Get_Vertex_Count(); geomMesh->m_verts=mm->Get_Vertex_Array(); geomMesh->m_numPolygons=mm->Get_Polygon_Count(); geomMesh->m_polygons=mm->Get_Polygon_Array(); if (geomMesh->m_numVerts > MAX_SHADOW_VOLUME_VERTS) return FALSE; //too many vertices to process //reset index of all vertices memset(vertParent,0xffffffff,sizeof(vertParent)); newVertexCount=geomMesh->m_numVerts; //Find all duplicated vertices. for (j=0; jm_numVerts; j++) { if (vertParent[j] != 0xffff) continue; //this vertex has already been processed const Vector3 *v_curr=&geomMesh->m_verts[j]; for (k=j+1; km_numVerts; k++) { Vector3 len(*v_curr - geomMesh->m_verts[k]); if (len.Length2() == 0) { //found duplicate vertex vertParent[k]=j; newVertexCount--; //decrease total vertices since duplicate found. } } vertParent[j]=j; //first instance of new vertex } geomMesh->m_parentVerts = NEW UnsignedShort[geomMesh->m_numVerts]; memcpy(geomMesh->m_parentVerts,vertParent,sizeof(UnsignedShort)*geomMesh->m_numVerts); geomMesh->m_numVerts=newVertexCount; //adjust actual vertex count to ignore duplicates m_numTotalsVerts += newVertexCount; geomMesh->m_parentGeometry = this; // build our neighboring polygon information geomMesh->buildPolygonNeighbors(); geomMesh++; m_meshCount++; } } // for (i = 0; i < AdditionalModels.Count(); i++) { // res |= AdditionalModels[i].Model->Cast_Ray(raytest); // } return m_meshCount != 0; } Int W3DShadowGeometry::initFromMesh(RenderObjClass *robj) { //locations of parent vertices inside the vertex array after duplicate //vertices are removed. UnsignedShort vertParent[MAX_SHADOW_VOLUME_VERTS]; Int j,k,newVertexCount; W3DShadowGeometryMesh *geomMesh=&m_meshList[m_meshCount]; assert (m_meshCount < MAX_SHADOW_CASTER_MESHES); geomMesh->m_mesh = (MeshClass *)robj; geomMesh->m_meshRobjIndex = -1; //robj is the mesh so no index needed. if (((geomMesh->m_mesh->Is_Alpha() || geomMesh->m_mesh->Is_Translucent()) && !geomMesh->m_mesh->Peek_Model()->Get_Flag(MeshGeometryClass::CAST_SHADOW))) return FALSE; //transparent meshes that don't have forced shadows will not cast volumetric shadows MeshModelClass *mm = geomMesh->m_mesh->Peek_Model(); geomMesh->m_numVerts=mm->Get_Vertex_Count(); geomMesh->m_verts=mm->Get_Vertex_Array(); geomMesh->m_numPolygons=mm->Get_Polygon_Count(); geomMesh->m_polygons=mm->Get_Polygon_Array(); if (geomMesh->m_numVerts > MAX_SHADOW_VOLUME_VERTS) return FALSE; //too many vertices to process //reset index of all vertices memset(vertParent,0xffffffff,sizeof(vertParent)); newVertexCount=geomMesh->m_numVerts; //Find all duplicated vertices. for (j=0; jm_numVerts; j++) { if (vertParent[j] != 0xffff) continue; //this vertex has already been processed const Vector3 *v_curr=&geomMesh->m_verts[j]; for (k=j+1; km_numVerts; k++) { Vector3 len(*v_curr - geomMesh->m_verts[k]); if (len.Length2() == 0) { //found duplicate vertex vertParent[k]=j; newVertexCount--; //decrease total vertices since duplicate found. } } vertParent[j]=j; //first instance of new vertex } geomMesh->m_parentVerts = NEW UnsignedShort[geomMesh->m_numVerts]; memcpy(geomMesh->m_parentVerts,vertParent,sizeof(UnsignedShort)*geomMesh->m_numVerts); geomMesh->m_numVerts=newVertexCount; //adjust actual vertex count to ignore duplicates geomMesh->m_parentGeometry = this; m_meshCount=1; m_numTotalsVerts=newVertexCount; // build our neighboring polygon information geomMesh->buildPolygonNeighbors(); return TRUE; } Int W3DShadowGeometry::init(RenderObjClass *robj) { return TRUE; // m_robj=robj; /* //code to deal with granny - don't think we'll use shadow volumes on these!? granny_file *fileInfo=robj->getPrototype().m_file; for (Int modelIndex=0; modelIndexModelCount; modelIndex++) { granny_model *sourceModel = fileInfo->Models[modelIndex]; if (stricmp(sourceModel->Name,"AABOX") == 0) { //found a collision box, copy out data int MeshCount = sourceModel->MeshBindingCount; if (MeshCount==1) { granny_mesh *sourceMesh = sourceModel->MeshBindings[0].Mesh; granny_pn33_vertex *Vertices = (granny_pn33_vertex *)sourceMesh->PrimaryVertexData->Vertices; Vector3 points[24]; assert (sourceMesh->PrimaryVertexData->VertexCount <= 24); for (Int boxVertex=0; boxVertexPrimaryVertexData->VertexCount; boxVertex++) { points[boxVertex].Set(Vertices[boxVertex].Position[0], Vertices[boxVertex].Position[1], Vertices[boxVertex].Position[2]); } box.Init(points,sourceMesh->PrimaryVertexData->VertexCount); } } else { //mesh is part of model int meshCount = sourceModel->MeshBindingCount; for (Int meshIndex=0; meshIndexMeshBindings[meshIndex].Mesh; if (sourceMesh->PrimaryVertexData) vertexCount+=sourceMesh->PrimaryVertexData->VertexCount; } } }*/ } /////////////////////////////////////////////////////////////////////////////// // MEMBER DEFINITIONS ///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // W3DShadowGeometry ============================================================= // ============================================================================ W3DShadowGeometryMesh::W3DShadowGeometryMesh( void ) { // init polygon neighbor information m_polyNeighbors = NULL; m_numPolyNeighbors = 0; m_parentVerts = NULL; m_polygonNormals = NULL; } // end W3DShadowGeometry // ~W3DShadowGeometry ============================================================ // ============================================================================ W3DShadowGeometryMesh::~W3DShadowGeometryMesh( void ) { // remove our neighbor list information allocated deleteNeighbors(); if (m_parentVerts) { delete [] m_parentVerts; } if (m_polygonNormals) delete [] m_polygonNormals; } // end ~W3DShadowGeometry // GetPolyNeighbor ============================================================ // Return the poly neighbor structure at the given index // ============================================================================ PolyNeighbor *W3DShadowGeometryMesh::GetPolyNeighbor( Int polyIndex ) { // sanity if( polyIndex < 0 || polyIndex >= m_numPolyNeighbors ) { // DBGPRINTF(( "Invalid neighbor index '%d'\n", polyIndex )); assert( 0 ); return NULL; } // en dif return &m_polyNeighbors[ polyIndex ]; } // end GetPolyNeighbor // buildPolygonNeighbors ====================================================== // Whenever we set a new geometry we want to build some information about // the faces in the new geometry so that we can efficienty traverse across // the surface to neighboring polygons // ============================================================================ void W3DShadowGeometryMesh::buildPolygonNeighbors( void ) { Int numPolys; Int i, j; // how many polygons are in our geometry numPolys = GetNumPolygon(); // // if there are no polygons for this geometry then we should have no // neighbor information // if( numPolys == 0 ) { // // if our geometry has somehow deformed and we used to have polygon // neighbor information we should delete it before we bail // if( m_numPolyNeighbors != 0 ) deleteNeighbors(); return; // nothing to see here people, move along } // end if // // in the event that this geometry can deform on the fly or we are // building our neighbor information for the very first time ... // if our current geometry has a different number of polygons than // we had previously calculated we need to delete and reallocate a // new storate space for the neighbor information // if( numPolys != m_numPolyNeighbors ) { // delete the old neighbor storage deleteNeighbors(); // allocate a new pool for neighbor information if( allocateNeighbors( numPolys ) == FALSE ) return; } // end if // // initialize all polygon neighbor information to none and assign our // own reference to myIndex so we know who we are // for( i = 0; i < m_numPolyNeighbors; i++ ) { // assign our own identification m_polyNeighbors[ i ].myIndex = i; // assign our neighbors to none for( j = 0; j < MAX_POLYGON_NEIGHBORS; j++ ) m_polyNeighbors[ i ].neighbor[ j ].neighborIndex = NO_NEIGHBOR; } // end for i // assign polygon data for each of our polygons for( i = 0; i < m_numPolyNeighbors; i++ ) { Short poly[ 3 ]; // vertex indices for this polygon Short otherPoly[ 3 ]; // vertex indices for other polygon Vector3 vNorm; // get the indices of the three triangle points for this polygon GetPolygonIndex( i, poly, 3 ); GetPolygonNormal(i,&vNorm); // find the neighbors of this polygon for( j = 0; j < m_numPolyNeighbors; j++ ) { Int a, b; Int index1, index2; Int index1Pos[2]; //positions of shared edge vertices in triangle list. (0,1 or 2) Int diff1,diff2; // ignore our own polygon if( i == j ) continue; // get the vertex index information for this other polygon GetPolygonIndex( j, otherPoly, 3 ); // // if 2 of the 3 vertex indices are the same then these polygons // are neighbors // //Also check if winding order of vertices on edge is opposite. //If vertices are in same order as our polygon, then it's //not a valid edge because the neighbor is flipped. index1 = -1; index2 = -1; for( a = 0; a < 3; a++ ) for( b = 0; b < 3; b++ ) if( poly[ a ] == otherPoly[ b ] ) { if( index1 == -1 ) { index1 = poly[ a ]; // record matching index1 index1Pos[0]=a; index1Pos[1]=b; } else if( index2 == -1 ) { //Check direction of edge in each polygon. If they are same direction skip it. diff1 = a-index1Pos[0]; diff2 = b-index1Pos[1]; if ( ((diff1&0x80000000)^((abs(diff1)&2)<<30)) != ((diff2&0x80000000)^((abs(diff2)&2)<<30))) { Vector3 vOtherNorm; GetPolygonNormal(j,&vOtherNorm); //Check if the 2 polygons face in exactly opposite directions - don't allow this type of neighbor. if (fabs(Vector3::Dot_Product(vOtherNorm,vNorm) + 1.0f) <= 0.01f) continue; index2 = poly[ a ]; // record matching index2 } else continue; } else {//This is the same poly facing opposite direction. //assert( 0 ); // should never match 3 vertices! index1=index2=-1; continue; } } // end if if( index1 != -1 && index2 != -1 ) { // // the polygon j is a neighbor of our polygon i, put the j // index into the first free neighbor slot for polygon i // for( a = 0; a < MAX_POLYGON_NEIGHBORS; a++ ) if( m_polyNeighbors[ i ].neighbor[ a ].neighborIndex == NO_NEIGHBOR ) { // record the neighbor index m_polyNeighbors[ i ].neighbor[ a ].neighborIndex = j; // record the sharded edge vertex indices m_polyNeighbors[ i ].neighbor[ a ].neighborEdgeIndex[ 0 ] = index1; m_polyNeighbors[ i ].neighbor[ a ].neighborEdgeIndex[ 1 ] = index2; break; // exit for a } // end if // // error condition, if our counter a is at the max number // of neighbors, which is 3 for a triangle mesh, we did something // wrong here because that would mean we found a 4th match! // if (a == MAX_POLYGON_NEIGHBORS) { Vector3 pv[3]; // char errorText[255]; GetVertex (poly[0], &pv[0]); GetVertex (poly[1], &pv[1]); GetVertex (poly[2], &pv[2]); pv[0] = pv[0] + pv[1] + pv[2]; pv[0] /= 3.0f; //find center of polygon // sprintf(errorText,"%s: Shadow Polygon with too many neighbors at %f,%f,%f",m_parentGeometry->Get_Name(),pv[0].X,pv[0].Y,pv[0].Z); // DEBUG_LOG(("****%s Shadow Polygon with too many neighbors at %f,%f,%f\n",m_parentGeometry->Get_Name(),pv[0].X,pv[0].Y,pv[0].Z)); // DEBUG_ASSERTCRASH(a != MAX_POLYGON_NEIGHBORS,(errorText)); } } // end if } // end for j } // end for i } // end buildPolygonNeighbors // allocateNeighbors ========================================================== // Allocate storage for the polygon neighbors and record its size // ============================================================================ Bool W3DShadowGeometryMesh::allocateNeighbors( Int numPolys ) { // assure we're not re-allocating without deleting assert( m_numPolyNeighbors == 0 ); assert( m_polyNeighbors == NULL ); // allocate the list m_polyNeighbors = NEW PolyNeighbor[ numPolys ]; if( m_polyNeighbors == NULL ) { // DBGPRINTF(( "Unable to allocate polygon neighbors\n" )); assert( 0 ); return FALSE; } // end if // list is now acutally allocated m_numPolyNeighbors = numPolys; return TRUE; // success! } // end allocateNeighbors // deleteNeighbors ============================================================ // Delete all polygon neighbor storage and information // ============================================================================ void W3DShadowGeometryMesh::deleteNeighbors( void ) { // delete list if( m_polyNeighbors ) { delete [] m_polyNeighbors; m_polyNeighbors = NULL; m_numPolyNeighbors = 0; } // end if // sanity error checking assert( m_numPolyNeighbors == 0 ); assert( m_polyNeighbors == NULL ); } // end deleteNeighbors //#include "Common/ThingTemplate.h" // updateOptimalExtrusionPadding ============================================== // Use raycasting to figure out a shadow extrusion length that guarantees that // the highest point of the object is extruded long enough to hit some ground. // This is a very slow operation so only do once for static non-moving objects. // ============================================================================ void W3DVolumetricShadow::updateOptimalExtrusionPadding(void) { if (m_robj) { // DrawableInfo *drawInfo=(DrawableInfo *)m_robj->Get_User_Data(); // Drawable *draw = drawInfo->m_drawable; // if (strstr(draw->getTemplate()->getName().str(),"Right02") != 0) // draw = draw; //debug code for China06 wacky bridge shadow // get the light Vector3 lightPosWorld=TheW3DShadowManager->getLightPosWorld(0); // check if object has a limit/clamp on shadow length and adjust light // position of necessary. if (m_shadowLengthScale) { //Find light's distance from origin in xy plane Real lightXYDistance = sqrt(lightPosWorld.X*lightPosWorld.X + lightPosWorld.Y * lightPosWorld.Y); Real newZ=lightXYDistance*m_shadowLengthScale; if (newZ > lightPosWorld.Z) { //clamped z component is higher than actual light position allows so adjust it. lightPosWorld.Z = newZ; } } // find maximum shadow length which will not cause any corners of the object's bounding box // to cast shadows that drop significantly below the object's base. This will help avoid // artifacts when we have an object on a cliff/hill casting shadows onto the ground below. // We need this hack because the terrain does not cast shadows and looks weird when objects // sitting on an incline cast shadows down below. Vector3 objPos=m_robj->Get_Position(); Vector3 lastValidTerrainPoint = objPos; Real baseGroundHeight=objPos.Z; const AABoxClass &box=m_robj->Get_Bounding_Box(); Vector3 lightRay,shadowRay; LineSegClass lineseg; CastResultStruct result; Vector3 Corners[4]; RayCollisionTestClass raytest(lineseg,&result); //Get vertices of top of bounding box since they will generate the longest shadow Corners[0]=box.Center+box.Extent; //top right corner Corners[1]=Corners[0]; Corners[1].X -= 2.0f*box.Extent.X; //top left corner Corners[2]=Corners[1]; Corners[2].Y -= 2.0f*box.Extent.Y; //bottom left corner Corners[3]=Corners[2]; Corners[3].X += 2.0f*box.Extent.X; //bottom right corner //find the corner that causes the longest shadow projection //and clamp light position to make sure it falls on even ground about //the same height as the object's base. for (Int i=0; i<4; i++) { //Cast ray from top volume corners onto ground plane lightRay = Corners[i] - lightPosWorld; //vector light to corner lightRay.Normalize(); raytest.Ray.Set(Corners[i],Corners[i]+lightRay*MAX_EXTRUSION_LENGTH); result.Reset(); //find out where this ray intersects terrain. if (TheTerrainRenderObject->Cast_Ray(raytest) && !raytest.Result->StartBad) { //Found intersection point where shadow has its maximum length. Do a quick //search to see if terrain falls significantly below the height of the object //anywhere between the base and the intersection point. If so, we either need //to extend shadow extrusion or make the light angle more vertical. shadowRay.Set(result.ContactPoint-Corners[i]); //vector from object corner to terrain intersection. shadowRay.Z = 0; //remove z-component since we'll be sampling along the xy plane. //walk along the shadow/light direction vector looking for large dips - indicating object //is on hill or cliff. Real len=shadowRay.Length(); Int numSteps=REAL_TO_INT_CEIL(len/SHADOW_SAMPLING_INTERVAL); Real stepSize = 1.0f/(Real)numSteps; Vector3 terrainPoint; Real t=stepSize; for (Int j=0; jgetHeightMapHeight(terrainPoint.X,terrainPoint.Y,NULL); if (terrainHeight < (objPos.Z - MAX_SHADOW_EXTRUSION_UNDER_OBJECT_BEFORE_CLAMP)) //check if terrain dips more than 10 units under object. { if (j == 0) //this is the initial point so object must be right on the edge of a cliff. { baseGroundHeight = terrainHeight; //force extrusion all the way down cliff. Real tanAngle=tan(OVERHANGING_OBJECT_CLAMP_ANGLE); //clamp to about 89 degrees or close to vertical lightpos. setShadowLengthScale(tanAngle); //update the clamp angle to shorted shadow enough so this corner on flat ground. break; } //Find ray from last valid terrain contact point to object box corner. Vector3 clampRay(Corners[i]-lastValidTerrainPoint); Real clampAngle=asin(clampRay.Z/clampRay.Length()); if (clampAngle >= (PI/2.0f) || clampAngle <= 0) clampAngle = OVERHANGING_OBJECT_CLAMP_ANGLE; //clamp to about 89 degrees or close to vertical lightpos. Real tanAngle=tan(clampAngle); if (tanAngle > m_shadowLengthScale) setShadowLengthScale(tanAngle); //update the clamp angle to shorted shadow enough so this corner on flat ground. break; } if (terrainHeight < baseGroundHeight) { baseGroundHeight = terrainHeight; //point was below object but within safety margin so record it's position. lastValidTerrainPoint = terrainPoint; lastValidTerrainPoint.Z = baseGroundHeight; } t+=stepSize; } } } m_extraExtrusionPadding = objPos.Z - baseGroundHeight + SHADOW_EXTRUSION_BUFFER; DEBUG_ASSERTCRASH(m_extraExtrusionPadding <= (255.0f*MAP_HEIGHT_SCALE),("Warning: Volumetric Shadow UpdateOptimalExtrusionPadding too large")); } } // getRenderCost ============================================================ // Returns number of draw calls for this shadow. // ============================================================================ #if defined(_DEBUG) || defined(_INTERNAL) void W3DVolumetricShadow::getRenderCost(RenderCost & rc) const { Int drawCount = 0; if (m_geometry && m_isEnabled && !m_isInvisibleEnabled && TheGlobalData->m_useShadowVolumes) { Int i,j; HLodClass *hlod=(HLodClass *)m_robj; MeshClass *mesh; Int meshIndex; for( i = 0; i < MAX_SHADOW_LIGHTS; i++ ) { for (j=0; jgetMeshCount(); j++) { meshIndex=m_geometry->getMesh(j)->m_meshRobjIndex; if (meshIndex >= 0) mesh = (MeshClass *)hlod->Peek_Lod_Model(0,meshIndex); else mesh = (MeshClass *)m_robj; if (mesh && mesh->Is_Not_Hidden_At_All()) drawCount++; } } } rc.addShadowDrawCalls(drawCount*2); } #endif /************************************ New Buffered Rendering Code ************************/ void W3DVolumetricShadow::RenderVolume(Int meshIndex, Int lightIndex) { HLodClass *hlod=(HLodClass *)m_robj; MeshClass *mesh=NULL; Int meshRobjIndex=m_geometry->getMesh(meshIndex)->m_meshRobjIndex; if (meshRobjIndex >= 0) mesh = (MeshClass *)hlod->Peek_Lod_Model(0,meshRobjIndex); else mesh = (MeshClass *)m_robj; if (mesh) { #ifdef SV_DEBUG_BOUNDS RenderMeshVolumeBounds(meshIndex,lightIndex, &mesh->Get_Transform()); #endif if (m_shadowVolume[0][ meshIndex ]->GetFlags() & SHADOW_DYNAMIC) RenderDynamicMeshVolume(meshIndex,lightIndex,&mesh->Get_Transform()); else RenderMeshVolume(meshIndex,lightIndex,&mesh->Get_Transform()); } } void W3DVolumetricShadow::RenderMeshVolume(Int meshIndex, Int lightIndex, const Matrix3D *meshXform) { Geometry *geometry; Int numVerts, numPolys, numIndex; //Get D3D Device used by W3D for quicker access. LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); if (!m_pDev) return; geometry = m_shadowVolume[lightIndex][ meshIndex ]; // // if our count is out of sync with our geometry data something // is wrong here // assert( geometry ); // get geometry requirements numVerts = geometry->GetNumActiveVertex(); numPolys = geometry->GetNumActivePolygon(); numIndex = numPolys * 3; // reject shadows with no data if( numVerts == 0 || numPolys == 0 ) return; Matrix4 mWorld(*meshXform); ///@todo: W3D always does transpose on all of matrix sets. Slow??? Better to hack view matrix. m_pDev->SetTransform(D3DTS_WORLD,(_D3DMATRIX *)&mWorld.Transpose()); W3DBufferManager::W3DVertexBufferSlot *vbSlot=m_shadowVolumeVB[lightIndex][ meshIndex ]; if (!vbSlot) return; if (vbSlot->m_VB->m_DX8VertexBuffer->Get_DX8_Vertex_Buffer() != lastActiveVertexBuffer) { lastActiveVertexBuffer=vbSlot->m_VB->m_DX8VertexBuffer->Get_DX8_Vertex_Buffer(); m_pDev->SetStreamSource(0,lastActiveVertexBuffer, vbSlot->m_VB->m_DX8VertexBuffer->FVF_Info().Get_FVF_Size()); //12 bytes per vertex. } DEBUG_ASSERTCRASH(vbSlot->m_size >= numVerts,("Overflowing Shadow Vertex Buffer Slot")); W3DBufferManager::W3DIndexBufferSlot *ibSlot=m_shadowVolumeIB[lightIndex][ meshIndex ]; if (!ibSlot) return; DEBUG_ASSERTCRASH(ibSlot->m_size >= numIndex,("Overflowing Shadow Index Buffer Slot")); m_pDev->SetIndices(ibSlot->m_IB->m_DX8IndexBuffer->Get_DX8_Index_Buffer(),vbSlot->m_start); if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) { Debug_Statistics::Record_DX8_Polys_And_Vertices(numPolys,numVerts,ShaderClass::_PresetOpaqueShader); m_pDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,numVerts,ibSlot->m_start,numPolys); } } void W3DVolumetricShadow::RenderDynamicMeshVolume(Int meshIndex, Int lightIndex, const Matrix3D *meshXform) { Geometry *geometry; Int numVerts, numPolys, numIndex; SHADOW_DYNAMIC_VOLUME_VERTEX* pvVertices; UnsignedShort *pvIndices; //Get D3D Device used by W3D for quicker access. LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); if (!m_pDev) return; geometry = m_shadowVolume[lightIndex][ meshIndex ]; // // if our count is out of sync with our geometry data something // is wrong here // assert( geometry ); // get geometry requirements numVerts = geometry->GetNumActiveVertex(); numPolys = geometry->GetNumActivePolygon(); numIndex = numPolys * 3; // reject shadows with no data if( numVerts == 0 || numPolys == 0 ) return; if (nShadowVertsInBuf > (SHADOW_VERTEX_SIZE-numVerts)) //check if room for model verts { //flush the buffer by drawing the contents and re-locking again if (shadowVertexBufferD3D->Lock(0,numVerts*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX),(unsigned char**)&pvVertices,D3DLOCK_DISCARD) != D3D_OK) return; nShadowVertsInBuf=0; nShadowStartBatchVertex=0; } else { if (shadowVertexBufferD3D->Lock(nShadowVertsInBuf*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX),numVerts*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX), (unsigned char**)&pvVertices,D3DLOCK_NOOVERWRITE) != D3D_OK) return; } #ifdef SV_DEBUG srand(0x1345465); #endif if(pvVertices) { #ifdef SV_DEBUG for (Int i=0; iGetVertex(i); //cast is valid since both start with xyz pvVertices->diffuse=(rand()%255) | ((rand()%255)<<8) | ((rand()%255)<<16); pvVertices++; } #else memcpy(pvVertices,geometry->GetVertex(0),numVerts*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX)); #endif } shadowVertexBufferD3D->Unlock(); if (nShadowIndicesInBuf > (SHADOW_INDEX_SIZE-numIndex)) //check if room for model verts { //flush the buffer by drawing the contents and re-locking again if (shadowIndexBufferD3D->Lock(0,numIndex*sizeof(short),(unsigned char**)&pvIndices,D3DLOCK_DISCARD) != D3D_OK) return; nShadowIndicesInBuf=0; nShadowStartBatchIndex=0; } else { if (shadowIndexBufferD3D->Lock(nShadowIndicesInBuf*sizeof(short),numIndex*sizeof(short), (unsigned char**)&pvIndices,D3DLOCK_NOOVERWRITE) != D3D_OK) return; } if(pvIndices) { memcpy(pvIndices,geometry->GetPolygonIndex(0,(short *)pvIndices,3),numPolys*3*sizeof(short)); } shadowIndexBufferD3D->Unlock(); m_pDev->SetIndices(shadowIndexBufferD3D,nShadowStartBatchVertex); Matrix4 mWorld(*meshXform); m_pDev->SetTransform(D3DTS_WORLD,(_D3DMATRIX *)&mWorld.Transpose()); if (shadowVertexBufferD3D != lastActiveVertexBuffer) { m_pDev->SetStreamSource(0,shadowVertexBufferD3D,sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX)); lastActiveVertexBuffer = shadowVertexBufferD3D; } if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) { Debug_Statistics::Record_DX8_Polys_And_Vertices(numPolys,numVerts,ShaderClass::_PresetOpaqueShader); m_pDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,numVerts,nShadowStartBatchIndex,numPolys); } nShadowVertsInBuf += numVerts; nShadowStartBatchVertex=nShadowVertsInBuf; nShadowIndicesInBuf += numIndex; nShadowStartBatchIndex=nShadowIndicesInBuf; } /** Debug function to draw bounding boxes around shadow volumes */ void W3DVolumetricShadow::RenderMeshVolumeBounds(Int meshIndex, Int lightIndex, const Matrix3D *meshXform) { Geometry *geometry; Int numVerts, numPolys, numIndex; SHADOW_DYNAMIC_VOLUME_VERTEX* pvVertices; UnsignedShort *pvIndices; // Vertex Positions as a function of the box extents static Vector3 _BoxVerts[8] = { Vector3( 1.0f, 1.0f, 1.0f ), // +z ring of 4 verts Vector3( -1.0f, 1.0f, 1.0f ), Vector3( -1.0f,-1.0f, 1.0f ), Vector3( 1.0f,-1.0f, 1.0f ), Vector3( 1.0f, 1.0f,-1.0f ), // -z ring of 4 verts; Vector3( -1.0f, 1.0f,-1.0f ), Vector3( -1.0f,-1.0f,-1.0f ), Vector3( 1.0f,-1.0f,-1.0f ), }; // Face Connectivity static Vector3i _BoxFaces[12] = { Vector3i( 0,1,2 ), // +z faces Vector3i( 0,2,3 ), Vector3i( 4,7,6 ), // -z faces Vector3i( 4,6,5 ), Vector3i( 0,3,7 ), // +x faces Vector3i( 0,7,4 ), Vector3i( 1,5,6 ), // -x faces Vector3i( 1,6,2 ), Vector3i( 4,5,1 ), // +y faces Vector3i( 4,1,0 ), Vector3i( 3,2,6 ), // -y faces Vector3i( 3,6,7 ) }; static Vector3 verts[8]; //Get D3D Device used by W3D for quicker access. LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); if (!m_pDev) return; Vector3 meshPosition; meshXform->Get_Translation(&meshPosition); //current mesh position geometry = m_shadowVolume[lightIndex][ meshIndex ]; AABoxClass &aab=geometry->getBoundingBox(); // compute the vertex positions meshPosition += aab.Center; //get world space position of bounding box for (int ivert=0; ivert<8; ivert++) { verts[ivert].X = meshPosition.X + _BoxVerts[ivert][0] * aab.Extent.X; verts[ivert].Y = meshPosition.Y + _BoxVerts[ivert][1] * aab.Extent.Y; verts[ivert].Z = meshPosition.Z + _BoxVerts[ivert][2] * aab.Extent.Z; } // // if our count is out of sync with our geometry data something // is wrong here // assert( geometry ); // get geometry requirements numVerts = 8; numPolys = 12; numIndex = numPolys * 3; // reject shadows with no data if( numVerts == 0 || numPolys == 0 ) return; if (nShadowVertsInBuf > (SHADOW_VERTEX_SIZE-numVerts)) //check if room for model verts { //flush the buffer by drawing the contents and re-locking again if (shadowVertexBufferD3D->Lock(0,numVerts*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX),(unsigned char**)&pvVertices,D3DLOCK_DISCARD) != D3D_OK) return; nShadowVertsInBuf=0; nShadowStartBatchVertex=0; } else { if (shadowVertexBufferD3D->Lock(nShadowVertsInBuf*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX),numVerts*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX), (unsigned char**)&pvVertices,D3DLOCK_NOOVERWRITE) != D3D_OK) return; } srand(0x1345465); if(pvVertices) { for (Int i=0; i<8; i++) { pvVertices->x=verts[i][0]; pvVertices->y=verts[i][1]; pvVertices->z=verts[i][2]; #ifdef SV_DEBUG pvVertices->diffuse=(rand()%255) | ((rand()%255)<<8) | ((rand()%255)<<16); #endif pvVertices++; } } shadowVertexBufferD3D->Unlock(); if (nShadowIndicesInBuf > (SHADOW_INDEX_SIZE-numIndex)) //check if room for model verts { //flush the buffer by drawing the contents and re-locking again if (shadowIndexBufferD3D->Lock(0,numIndex*sizeof(short),(unsigned char**)&pvIndices,D3DLOCK_DISCARD) != D3D_OK) return;; nShadowIndicesInBuf=0; nShadowStartBatchIndex=0; } else { if (shadowIndexBufferD3D->Lock(nShadowIndicesInBuf*sizeof(short),numIndex*sizeof(short), (unsigned char**)&pvIndices,D3DLOCK_NOOVERWRITE) != D3D_OK) return; } if(pvIndices) { for (Int i=0; iUnlock(); m_pDev->SetIndices(shadowIndexBufferD3D,nShadowStartBatchVertex); //todo: replace this with mesh transform Matrix4 mWorld(1); //identity since boxes are pre-transformed to world space. m_pDev->SetTransform(D3DTS_WORLD,(_D3DMATRIX *)&mWorld.Transpose()); m_pDev->SetStreamSource(0,shadowVertexBufferD3D,sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX)); m_pDev->SetVertexShader(SHADOW_DYNAMIC_VOLUME_FVF); m_pDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,numVerts,nShadowStartBatchIndex,numPolys); nShadowVertsInBuf += numVerts; nShadowStartBatchVertex=nShadowVertsInBuf; nShadowIndicesInBuf += numIndex; nShadowStartBatchIndex=nShadowIndicesInBuf; } // Shadow ===================================================================== // Shadow default constructor // ============================================================================ W3DVolumetricShadow::W3DVolumetricShadow( void ) { Int i,j; m_next = NULL; m_geometry = NULL; m_shadowLengthScale = 0.0f; m_extraExtrusionPadding = 0.0f; m_robj = NULL; m_isEnabled = TRUE; m_isInvisibleEnabled = FALSE; for (j=0; j < MAX_SHADOW_CASTER_MESHES; j++) { m_numSilhouetteIndices[j] = 0; m_maxSilhouetteEntries[j] = 0; m_silhouetteIndex[j] = NULL; m_shadowVolumeCount[j] = 0; } for( i = 0; i < MAX_SHADOW_LIGHTS; i++ ) { for (j=0; j < MAX_SHADOW_CASTER_MESHES; j++) { m_shadowVolume[ i ][j] = NULL; m_shadowVolumeVB[i][j] = NULL; m_shadowVolumeIB[i][j] = NULL; m_shadowVolumeRenderTask[i][j].m_parentShadow = this; m_shadowVolumeRenderTask[i][j].m_meshIndex = (UnsignedByte)j; m_shadowVolumeRenderTask[i][j].m_lightIndex = (UnsignedByte)i; m_objectXformHistory[ i ][j].Make_Identity(); m_lightPosHistory[ i ][j] = Vector3(0,0,0); } } // end for i } // end W3DVolumetricShadow // ~W3DVolumetricShadow ==================================================================== // W3DVolumetricShadow destructor // ============================================================================ W3DVolumetricShadow::~W3DVolumetricShadow( void ) { Int i,j; // we must free any silhouette data allocated for (j = 0; j < MAX_SHADOW_CASTER_MESHES; j++) deleteSilhouette(j); // free any shadow volume data for( i = 0; i < MAX_SHADOW_LIGHTS; i++ ) { for (j = 0; j < MAX_SHADOW_CASTER_MESHES; j++) { if( m_shadowVolume[ i ][j] ) delete m_shadowVolume[ i ][j]; if( m_shadowVolumeVB[i][j]) TheW3DBufferManager->releaseSlot(m_shadowVolumeVB[i][j]); if( m_shadowVolumeIB[i][j]) TheW3DBufferManager->releaseSlot(m_shadowVolumeIB[i][j]); } } if (m_geometry) REF_PTR_RELEASE(m_geometry); m_geometry=NULL; m_robj=NULL; } // end ~W3DVolumetricShadow void W3DVolumetricShadow::SetGeometry( W3DShadowGeometry *geometry ) { Short numPrevVertices = 0; Short numNewVertices = 0; // // our geometry has changed, we need to allocate enough memory for the // silhouette data. If silhouette data is present it must be reallocated // to accomoddate the new size if smaller // // if we had previous geometry how many vertices did it have for (Int i=0; igetMesh(i)->GetNumVertex(); // now many vertices does our new geometry have if( geometry ) numNewVertices = geometry->getMesh(i)->GetNumVertex(); // // TODO: Colin, may want to change this in the future // if our new geometry requires more memory allocate it, if it requires // less we'll leave it around for future switches in geometry // if( numNewVertices > numPrevVertices ) { deleteSilhouette(i); if( allocateSilhouette(i, numNewVertices ) == FALSE ) return; } // end if } // assign the new geometry, possible over an old geometry m_geometry = geometry; } // end SetGeometry /**Called once per frame for each object, when necessary it will reconstruct the shadow volume for this shadow from the silhouette of the geometry and any light sources */ void W3DVolumetricShadow::Update() { static Int currentTime, lastTime, delay = 0; // OBJECT_PILE // static Vector3 originCompareVector(0,0,9999); static Vector3 originCompareVector(0,0,0); Vector3 pos; // sanity if( m_geometry == NULL) return; // // for now we will just rebuild a shadow volume every so often, this // should be changed to be built only when the light angle sufficiently // changes or when the object rotation sufficiently changes // // currentTime = timeGetTime(); // if( currentTime - lastTime >= delay ) { pos=m_robj->Get_Position(); if (pos == originCompareVector) { //the transform on this object was never set so we can't make any determination //if it's visible or what the shadow looks like. return; } //Check if this is a "flying" unit. Flying units are defined as anything that moves more than //AIRBORNE_UNIT_GROUND_DELTA world units above ground. This will allow "jumping" units like rocket //buggies from being flagged as "flying". We minimize the number of flying units because their shadow //volumes are extruded longer to reach the lowest point on the map. Regular shadows only extend far //enough to reach the base of the model (presumes base is already touching the ground). Real groundHeight; if (TheTerrainLogic) groundHeight=TheTerrainLogic->getGroundHeight(pos.X,pos.Y); //logic knows about bridges so use if available. else groundHeight=TheTerrainRenderObject->getHeightMapHeight(pos.X,pos.Y, NULL); if (fabs(pos.Z - groundHeight) >= AIRBORNE_UNIT_GROUND_DELTA) { Real extent = MAX_SHADOW_LENGTH_EXTRA_AIRBORNE_SCALE_FACTOR * m_robjExtent; if (WWMath::Fabs(pos.X - bcX) > (beX + extent) || WWMath::Fabs(pos.Y - bcY) > (beY + extent) || WWMath::Fabs(pos.Z - bcZ) > (beZ + extent)) return; //shadow can't be visible so no point in updating. //this unit is above ground, extend shadow volume to reach lowest point on the terrain plus extra bit to make //sure shadow goes under ground. updateVolumes(fabs(pos.Z - TheTerrainRenderObject->getMinHeight()) + SHADOW_EXTRUSION_BUFFER); } else { //normal object that is not floating above ground so we don't need to extend the shadow lower than the object's //base since it should be sitting directly at ground level. if (WWMath::Fabs(pos.X - bcX) > (beX + m_robjExtent) || WWMath::Fabs(pos.Y - bcY) > (beY + m_robjExtent) || WWMath::Fabs(pos.Z - bcZ) > (beZ + m_robjExtent)) return; //shadow can't be visible so no point in updating. //check if this object has never had it's extrusion length updated. Will only be true for //immobile objects because finding an optimal extrusion length is expensive. if (!m_extraExtrusionPadding) updateOptimalExtrusionPadding(); updateVolumes(m_extraExtrusionPadding); } // floorZ = 2.0f; //lower slightly so shadows go under ground. // update delay time lastTime = currentTime; } // end if } // end Update /** Update shadow volumes belonging to all meshes of this shadow caster. * Use zoffset to extend shadows below object's base by given amount. */ void W3DVolumetricShadow::updateVolumes(Real zoffset) { Int i,j; HLodClass *hlod=(HLodClass *)m_robj; MeshClass *mesh; static AABoxClass aaBox; static SphereClass sphere; Int meshIndex; DEBUG_ASSERTCRASH(hlod != NULL,("updateVolumes : hlod is NULL!")); Bool parentVis=m_robj->Is_Really_Visible(); for( i = 0; i < MAX_SHADOW_LIGHTS; i++ ) { for (j=0; jgetMeshCount(); j++) { meshIndex=m_geometry->getMesh(j)->m_meshRobjIndex; if (meshIndex >= 0) mesh = (MeshClass *)hlod->Peek_Lod_Model(0,meshIndex); else mesh = (MeshClass *)m_robj; if (mesh) { if (!mesh->Is_Not_Hidden_At_All()) continue; /**@todo: Getting the transform of the mesh may be forcing a full hierarchy evaluation. Expensive for off-screen models... do we really need this? */ //Extend floor of model by 'zoffset' to compensate for flying units. updateMeshVolume(j, i, &mesh->Get_Transform(), mesh->Get_Bounding_Box(),m_robj->Get_Position().Z - zoffset); //update visibility if not set yet if (m_shadowVolume[i][j]) { if(m_shadowVolume[i][j]->getVisibleState() == Geometry::STATE_UNKNOWN) { //Updating the mesh volume didn't update the visibility, must do it here. //First check against bounding sphere if (parentVis) { //parent is visible so most likely all sub_objects are also visible so probably all shadows also visible. //skip additional visibility tests m_shadowVolume[i][j]->setVisibleState(Geometry::STATE_VISIBLE); } else { sphere=m_shadowVolume[i][j]->getBoundingSphere(); sphere.Center += mesh->Get_Transform().Get_Translation(); CollisionMath::OverlapType result=CollisionMath::Overlap_Test(*shadowCameraFrustum,sphere); if (result == CollisionMath::OVERLAPPED) { //do a more accurate test against bounding box. aaBox=m_shadowVolume[i][j]->getBoundingBox(); aaBox.Translate(mesh->Get_Transform().Get_Translation()); //translate bounding box to world space. if (CollisionMath::Overlap_Test(*shadowCameraFrustum,aaBox) != CollisionMath::OUTSIDE) m_shadowVolume[i][j]->setVisibleState(Geometry::STATE_VISIBLE); else m_shadowVolume[i][j]->setVisibleState(Geometry::STATE_INVISIBLE); } else m_shadowVolume[i][j]->setVisibleState((Geometry::VisibleState)result); } } if (m_shadowVolume[i][j]->getVisibleState() == Geometry::STATE_VISIBLE) { //shadow volume is visible. Add it to list of rendertasks. W3DBufferManager::W3DVertexBufferSlot *vbSlot=m_shadowVolumeVB[i][j]; if (vbSlot) { //add to static mesh volume list. W3DBufferManager::W3DRenderTask *oldTask=vbSlot->m_VB->m_renderTaskList; vbSlot->m_VB->m_renderTaskList=&m_shadowVolumeRenderTask[i][j]; vbSlot->m_VB->m_renderTaskList->m_nextTask=oldTask; } else { TheW3DVolumetricShadowManager->addDynamicShadowTask(&m_shadowVolumeRenderTask[i][j]); } } } } } // end for j } // end for, i } /*floorZ is the assumed ground height below the model. The code will try to extrude shadows just long enough to hit this point in order to reduce fill rate usage.*/ void W3DVolumetricShadow::updateMeshVolume(Int meshIndex, Int lightIndex, const Matrix3D *meshXform, const AABoxClass &meshBox, float floorZ ) { Vector3 lightPosObject; Matrix4 worldToObject; Vector3 objectCenter; Vector3 toLight; Vector3 toPrevLight; Vector3 prevPosToLight; Vector3 lightPosWorld; Vector3 prevObjPosition; //Figuring out if mesh has rotated is cheaper (no normalization) than figuring out if light angle has changed. //So we divide the 2 tests. Also, our light (sun) almost never moves so no second test needed at all. Bool isMeshRotating = false; //flag if mesh has rotated since last update. Translation doesn't matter for infinite light source. Bool isLightMoving = false; //flag if light has moved since last update. Matrix4 objectToWorld(*meshXform); Matrix4 *prevXForm=&m_objectXformHistory[ lightIndex ][meshIndex]; // // build the shadow silhouette and construct shadow volume from // this light location. The for loop wrapped around this is // theoretical code for future enhancements of multiple lights that // cast shadows // #ifdef ASSUME_NEAR_LIGHTSOURCE if (memcmp(&objectToWorld,prevXForm,sizeof(objectToWorld))) isMeshRotating = true; //mesh transform has not changed since last update. #else //When dealing with infinite light sources, we can assume that the shadow doesn't //change much based on object position. Only the orientation to light matters. Real cosAngle = fabs (Vector3::Dot_Product((Vector3 &)(prevXForm->operator [](0)),(Vector3 &)(objectToWorld.operator [](0)))); if (cosAngle >= cosAngleToCare) { cosAngle = fabs (Vector3::Dot_Product((Vector3 &)(prevXForm->operator [](1)),(Vector3 &)(objectToWorld.operator [](1)))); if (cosAngle >= cosAngleToCare) { cosAngle = fabs (Vector3::Dot_Product((Vector3 &)(prevXForm->operator [](2)),(Vector3 &)(objectToWorld.operator [](2)))); if (cosAngle < cosAngleToCare) isMeshRotating=true; } else isMeshRotating =true; } else isMeshRotating =true; #endif // get the light lightPosWorld = TheW3DShadowManager->getLightPosWorld(lightIndex); // get the object meshXform->Get_Translation(&objectCenter); //current mesh position // check if object has a limit/clamp on shadow length and adjust light // position of necessary. if (m_shadowLengthScale) { //Find light's distance from origin in xy plane Real lightXYDistance = sqrt(lightPosWorld.X*lightPosWorld.X + lightPosWorld.Y * lightPosWorld.Y); Real newZ=lightXYDistance*m_shadowLengthScale; if (newZ > lightPosWorld.Z) { //clamped z component is higher than actual light position allows so adjust it. lightPosWorld.Z = newZ; } } if (lightPosWorld != m_lightPosHistory[ lightIndex ][meshIndex]) { //Light position has moved, see if enough to matter // compute vector from the light to the current object position toLight = objectCenter - lightPosWorld; toLight.Normalize(); // compute vector from the previous light to the object position toPrevLight = objectCenter - m_lightPosHistory[ lightIndex ][meshIndex]; toPrevLight.Normalize(); Real cosAngle = fabs (Vector3::Dot_Product(toLight,toPrevLight)); if (cosAngle < cosAngleToCare) //less than 45 degree change isLightMoving =true; } else ///@todo: Find a better way to deal with this - use maximum extrusion once! Also avoid hit for units climbing hills. if (fabs(objectCenter.Z - prevXForm->operator [](2).W) > SHADOW_EXTRUSION_BUFFER) isLightMoving = true; //treat model rising just like rotation since volume needs update for longer extrusion. // reconstruct if needed if (isLightMoving || isMeshRotating) { // // transform the light in the world to object space, we // care only about the rotation of components for the coordinate // system change, not the translations // Real det; D3DXMatrixInverse((D3DXMATRIX*)&worldToObject, &det, (D3DXMATRIX*)&objectToWorld); // find out light position in object space Matrix4::Transform_Vector(worldToObject,lightPosWorld,&lightPosObject); //Updating shadow volumes is expensive, so verify that this volume is even visible. //Generate bounding box around shadow volume by extruding AABB corners AABoxClass box(meshBox); //copy current mesh bounding box (will be smaller than shadow box). SphereClass sphere; //rough bounding sphere of shadow volume - based on box. Vector3 Corners[8]; Vector3 lightRay; Real vectorScale,vectorScaleTemp, vectorScaleMax; Real length; //Get vertices of top of bounding box Corners[0]=box.Center+box.Extent; //top right corner Corners[1]=Corners[0]; Corners[1].X -= 2.0f*box.Extent.X; //top left corner Corners[2]=Corners[1]; Corners[2].Y -= 2.0f*box.Extent.Y; //bottom left corner Corners[3]=Corners[2]; Corners[3].X += 2.0f*box.Extent.X; //bottom right corner //Project top volume corners onto ground plane lightRay = Corners[0] - lightPosWorld; //vector light to corner length= 1.0f/lightRay.Length(); lightRay *= length; vectorScaleMax=vectorScale=(Real)fabs((Corners[0].Z-floorZ)/lightRay.Z); //length of vector from top corner to ground. Corners[4]=Corners[0]+lightRay*vectorScale; vectorScaleMax *= length; lightRay = Corners[1] - lightPosWorld; //vector light to corner length= 1.0f/lightRay.Length(); lightRay *= length; vectorScaleTemp=(Real)fabs((Corners[1].Z-floorZ)/lightRay.Z); //length of vector from top corner to ground. Corners[5]=Corners[1]+lightRay*vectorScaleTemp; vectorScaleTemp *= length; if (vectorScaleTemp > vectorScaleMax) vectorScaleMax=vectorScaleTemp; //keep track of maximum required extrusion length. lightRay = Corners[2] - lightPosWorld; //vector light to corner length= 1.0f/lightRay.Length(); lightRay *= length; vectorScale=(Real)fabs((Corners[2].Z-floorZ)/lightRay.Z); //length of vector from top corner to ground. Corners[6]=Corners[2]+lightRay*vectorScale; vectorScale *= length; if (vectorScale > vectorScaleMax) vectorScaleMax=vectorScale; //keep track of maximum required extrusion length. lightRay = Corners[3] - lightPosWorld; //vector light to corner length= 1.0f/lightRay.Length(); lightRay *= length; vectorScaleTemp=(Real)fabs((Corners[3].Z-floorZ)/lightRay.Z); //length of vector from top corner to ground. Corners[7]=Corners[3]+lightRay*vectorScaleTemp; vectorScaleTemp *= length; if (vectorScaleTemp > vectorScaleMax) vectorScaleMax=vectorScaleTemp; //keep track of maximum required extrusion length. box.Init(Corners, 8); //generate a new bounding box sphere.Init(box.Center,box.Extent.Length()); //generate object space bounding sphere containing box. CollisionMath::OverlapType result=CollisionMath::Overlap_Test(*shadowCameraFrustum,sphere); if (result == CollisionMath::OVERLAPPED) //do a more accurate test result=CollisionMath::Overlap_Test(*shadowCameraFrustum, box); if (result != CollisionMath::OUTSIDE) { // // reset the silhouette data and build a new one from this light // source perspective // if (m_numSilhouetteIndices[meshIndex] != 0) { //this silhouette was built before and is being updated. //this probably means it will change again in the future. //make future updates faster by pre-caching face normals. m_geometry->getMesh(meshIndex)->buildPolygonNormals(); } resetSilhouette(meshIndex); buildSilhouette(meshIndex, &lightPosObject); // // in a multiple shadow situation we would be allocating a volume // for this current shadow light, not the 0 index volume all the time // if (!m_shadowVolume[ lightIndex ][meshIndex]) allocateShadowVolume( lightIndex,meshIndex ); if( m_shadowVolumeVB[ lightIndex ][meshIndex] ) { //Updating an existing vertex buffer shadow volume. This means we're //probably dealing with an animated mesh. Update flags to reflect this fact. if (isMeshRotating || isLightMoving) { if (isMeshRotating) { //rotating meshes will most likely need updates each frame, so stop using static vertex buffers. m_shadowVolume[ lightIndex ][meshIndex]->SetFlags( m_shadowVolume[ lightIndex ][meshIndex]->GetFlags() | SHADOW_DYNAMIC); } //release memory used to store vertices/polygons resetShadowVolume( lightIndex,meshIndex ); //free vertex buffers since not used for dynamic. //Resize the shadow volume since we'll need room to store the vertices in memory instead of VB. allocateShadowVolume( lightIndex,meshIndex ); } } // // construct the shadow volume at this light position in the // passed shadow volume geometry index // if (m_shadowVolume[ lightIndex ][meshIndex]->GetFlags() & SHADOW_DYNAMIC) constructVolume( &lightPosObject, vectorScaleMax, lightIndex, meshIndex ); else constructVolumeVB( &lightPosObject, vectorScaleMax, lightIndex, meshIndex ); // // store the current light position and orientation that // we constructed shadow info at // m_objectXformHistory[ lightIndex ][meshIndex] = objectToWorld; m_lightPosHistory[lightIndex][meshIndex] = lightPosWorld; box.Translate(-objectCenter); //translate box to object space. m_shadowVolume[ lightIndex ][meshIndex]->setBoundingBox(box); sphere.Center -= objectCenter; m_shadowVolume[ lightIndex ][meshIndex]->setBoundingSphere(sphere); m_shadowVolume[ lightIndex ][meshIndex]->setVisibleState(Geometry::STATE_VISIBLE); //this volume needs rendering. }//end if inside view frustum else if (m_shadowVolume[ lightIndex ][meshIndex]) { //outside view frustum, shadow wasn't updated. box.Translate(-objectCenter); //translate box to object space. m_shadowVolume[ lightIndex ][meshIndex]->setBoundingBox(box); sphere.Center -= objectCenter; m_shadowVolume[ lightIndex ][meshIndex]->setBoundingSphere(sphere); m_shadowVolume[ lightIndex ][meshIndex]->setVisibleState(Geometry::STATE_INVISIBLE); } } // end if else { //not reconstructing volume, so don't know if visible or not. if (m_shadowVolume[ lightIndex ][meshIndex]) m_shadowVolume[ lightIndex ][meshIndex]->setVisibleState(Geometry::STATE_UNKNOWN); } } // addSilhouetteEdge ========================================================== // It has been determined that the polygon neighbor in the "neighborIndex" // of "visible" needs to be added to the silhouette. We will add those two // vertex indices to the silhouette in the order they were specified in // "visible" to assure that the constructed edge is in counter clockwise order // ============================================================================ void W3DVolumetricShadow::addSilhouetteEdge(Int meshIndex, PolyNeighbor *visible, PolyNeighbor *hidden ) { Int i; Int neighborIndex = 0; Short visibleIndexList[ 3 ]; Short edgeStart, edgeEnd; W3DShadowGeometryMesh *geomMesh=m_geometry->getMesh(meshIndex); // sanity assert( visible && hidden ); // // which index in the neighbor list of "visible" refers to the // polygon "hidden" // for( i = 0; i < MAX_POLYGON_NEIGHBORS; i++ ) { if( visible->neighbor[ i ].neighborIndex == hidden->myIndex ) { neighborIndex = i; break; // exit for } // end if } // end for i // get the three vertex indices of "visible" geomMesh->GetPolygonIndex( visible->myIndex, visibleIndexList, 3 ); // // we know that 2 of the 3 vertex indices will be present in the edge. // will construct the edge as follows to ensure we have counter // clockwise order. note that this assumes the vertices of the // polygons specified in the geometry are in counter clockwise order, // which they are // // 1) [ v1 Absent, v2 Present, v3 Present ] -> edge = (v2, v3) // 2) [ v1 Present, v2 Absent, v3 Present ] -> edge = (v3, v1) // 3) [ v1 Present, v2 Present, v3 Absent ] -> edge = (v1, v2) // if( (visibleIndexList[ 0 ] != visible->neighbor[ neighborIndex ].neighborEdgeIndex[ 0 ]) && (visibleIndexList[ 0 ] != visible->neighbor[ neighborIndex ].neighborEdgeIndex[ 1 ]) ) { // case 1 above edgeStart = visibleIndexList[ 1 ]; edgeEnd = visibleIndexList[ 2 ]; } // end if else if( (visibleIndexList[ 1 ] != visible->neighbor[ neighborIndex ].neighborEdgeIndex[ 0 ]) && (visibleIndexList[ 1 ] != visible->neighbor[ neighborIndex ].neighborEdgeIndex[ 1 ]) ) { // case 2 above edgeStart = visibleIndexList[ 2 ]; edgeEnd = visibleIndexList[ 0 ]; } // end if else { // case 3 above edgeStart = visibleIndexList[ 0 ]; edgeEnd = visibleIndexList[ 1 ]; } // end if // add to silhouette edge list addSilhouetteIndices(meshIndex, edgeStart, edgeEnd ); } // end addSilhouetteEdge // addNeighborlessEdges ======================================================= // Given a polygon neighbor information, it has been determined that this // polygon is visible and has edges which are not connected to other // polygons, these edges need to be added to the silhouette. The edge(s) // must be added in such an order that we create silhouette edges in a // counter clockwise order. // ============================================================================ void W3DVolumetricShadow::addNeighborlessEdges(Int meshIndex, PolyNeighbor *us ) { Short vertexIndexList[ 3 ]; Int i, j; Short edgeStart, edgeEnd; Bool addEdge; // sanity assert( us ); W3DShadowGeometryMesh *geomMesh = m_geometry->getMesh(meshIndex); // get the vertex index list from the geometry geomMesh->GetPolygonIndex( us->myIndex, vertexIndexList, 3 ); // // go through each edge, if these indices to NOT appear TOGETHER in // neighbor list then we must add it. // for( i = 0; i < 3; i++ ) { // get the edge start and end vertex indices edgeStart = vertexIndexList[ i ]; if( i == 2 ) edgeEnd = vertexIndexList[ 0 ]; // wraps to begging of list else edgeEnd = vertexIndexList[ i + 1 ]; // do these two vertices appear in a neighbor list of the poly? addEdge = TRUE; for( j = 0; j < MAX_POLYGON_NEIGHBORS; j++ ) { if( us->neighbor[ j ].neighborIndex != NO_NEIGHBOR ) { if( (us->neighbor[ j ].neighborEdgeIndex[ 0 ] == edgeStart && us->neighbor[ j ].neighborEdgeIndex[ 1 ] == edgeEnd) || (us->neighbor[ j ].neighborEdgeIndex[ 1 ] == edgeStart && us->neighbor[ j ].neighborEdgeIndex[ 0 ] == edgeEnd) ) { addEdge = FALSE; break; // exit for j, no need to search on } // end if } // end if } // end for j // add the edge if no neighbors have that edge if( addEdge == TRUE ) { addSilhouetteIndices(meshIndex, edgeStart, edgeEnd ); } // end if } // end for i } // end addNeighborlessEdges // addSilhouetteIndices ======================================================= // Add these two indices to the silhouette data // ============================================================================ void W3DVolumetricShadow::addSilhouetteIndices(Int meshIndex, Short edgeStart, Short edgeEnd ) { // DBGPRINTF(( "addSilhouetteIndices: Adding (%d,%d), the storage before the add is = (%d/%d)\n", // edgeStart, edgeEnd, m_numSilhouetteIndices, m_maxSilhouetteEntries )); // add to silhouette edge list assert( m_numSilhouetteIndices[meshIndex] < m_maxSilhouetteEntries[meshIndex] ); m_silhouetteIndex[meshIndex][ m_numSilhouetteIndices[meshIndex]++ ] = edgeStart; assert( m_numSilhouetteIndices[meshIndex] < m_maxSilhouetteEntries[meshIndex] ); m_silhouetteIndex[meshIndex][ m_numSilhouetteIndices[meshIndex]++ ] = edgeEnd; } // end if // buildSilhouette ============================================================ // Given a light position, and our polygon neighbor information this will // build the silhouette of the object edges from the given light position // ============================================================================ void W3DVolumetricShadow::buildSilhouette(Int meshIndex, Vector3 *lightPosObject) { PolyNeighbor *polyNeighbor; // the poly we're looking at right now Vector3 normal; // normal of current polygon Vector3 lightVector; // vector from light to polygon Bool visibleNeighborless; Int numPolys; // number of polys in our geometry W3DShadowGeometryMesh *geomMesh; Int i, j; Int meshEdgeStart=0; //index to first edge contributed by specific mesh // // go through each of our shadow geometry polygon info and find out // which polys are visible from this light source and which ones are not // geomMesh = m_geometry->getMesh(meshIndex); //record where this meshes indices will begin. meshEdgeStart=m_numSilhouetteIndices[meshIndex]; numPolys = geomMesh->GetNumPolygon(); for( i = 0; i < numPolys; i++ ) { Short poly[ 3 ]; Vector3 vertex; // get this polygon neighbor information polyNeighbor = geomMesh->GetPolyNeighbor( i ); // take this opportunity to initialize our processing flags to zero polyNeighbor->status = 0; // get the normal for this polygon geomMesh->GetPolygonNormal( i, &normal ); // get the vertex indices at this polygon geomMesh->GetPolygonIndex( i, poly, 3 ); // // find out "lightVector" to this polygon // // since our light source could be very close to the object and that // would change the shadow we are going to say that the light vector // is from the light position to one of the vertices in the polygon. // To be more correct we should use the center of the polygon but // this is a good approximation ... an ever broader approximation that // we could use would be the object center // geomMesh->GetVertex( poly[ 0 ], &vertex ); lightVector= vertex - *lightPosObject; // // dot the light vector with the normal of the polygon to see if the // poly is visible from this location // if( Vector3::Dot_Product( lightVector, normal ) < 0.0f ) BitSet( polyNeighbor->status, POLY_VISIBLE ); } // end for i // // check all our polys using our poly neighbors, where one poly neighbor // is not the same visible status as a neighbor that represents a // silhouette edge // for( i = 0; i < numPolys; i++ ) { PolyNeighbor *otherNeighbor; // get this poly neighbor ... this is "us" polyNeighbor = geomMesh->GetPolyNeighbor( i ); // initialize ourselves to not be a visible edge visibleNeighborless = FALSE; // check our 3 potential neighbors for( j = 0; j < MAX_POLYGON_NEIGHBORS; j++ ) { // initialize this neighbor to nuttin otherNeighbor = NULL; // get our neighbor if present and cull them if processed if( polyNeighbor->neighbor[ j ].neighborIndex != NO_NEIGHBOR ) { // get the jth polygon neighbor ... this is "them" otherNeighbor = geomMesh->GetPolyNeighbor( polyNeighbor->neighbor[ j ].neighborIndex ); // // ignore neighbors that are marked as processed as those // onces have already detected edges if present // if( BitTest( otherNeighbor->status, POLY_PROCESSED ) ) continue; // for j } // end if // // finally, if our own visible status is different from our // neighbor visible status then that defines an edge we must // add to the silhouette. Also, a visible polygon that has // no neighbor automatically makes a silhouette edge. Note that // if we have no neighbor we just record the fact that we have // real model end edges to add after this inner j loop; // if( BitTest( polyNeighbor->status, POLY_VISIBLE ) ) { // check for no neighbor edges if( otherNeighbor == NULL ) { visibleNeighborless = TRUE; } // end if else if( BitTest( otherNeighbor->status, POLY_VISIBLE ) == FALSE ) { // "we" are visible and "they" are not addSilhouetteEdge(meshIndex, polyNeighbor, otherNeighbor ); } // end if } // end if else if( otherNeighbor != NULL && BitTest( otherNeighbor->status, POLY_VISIBLE ) ) { // "they" are visible and "we" are not addSilhouetteEdge(meshIndex, otherNeighbor, polyNeighbor ); } // end else } // end for j // // if this polygon is visible, add any edges that are not // neighbors of adjacent polygons. // if( visibleNeighborless == TRUE ) { addNeighborlessEdges(meshIndex, polyNeighbor ); } // end if // // this polyNeighbor is now considered "processed", any other // polygons that reference back to this one can ignore their // processing cause any edges were already detected // BitSet( polyNeighbor->status, POLY_PROCESSED ); } // end for i //record number of edge indices contrinuted by this mesh m_numIndicesPerMesh[meshIndex]=m_numSilhouetteIndices[meshIndex]-meshEdgeStart; } // end buildSilhouette // constructVolume ============================================================ // Given a fresh new geometry class called "shadowVolume" to hold the actual // shadow volume data, this method will create the shadow volume polygons // given the information in the current silhouette of this Shadow and the // light source position. // // The light source should be in object space and the shadow volume polygon // data is also constructed in object space. // // The polygon we will create for a given edge will be that edge extruded // out in the direction away from the light source. This conceptual 4 sided // polygon is however broken up into two triangles for storage. // // This version is designed to construct the volume inside a system memory // buffer - to be rendered via a dynamic vertex buffer. // // ============================================================================ void W3DVolumetricShadow::constructVolume( Vector3 *lightPosObject,Real shadowExtrudeDistance, Int volumeIndex, Int meshIndex ) { Geometry *shadowVolume; Vector3 extrude2; // the polypoints extruded from edge and light Vector3 edgeVertex2; // second edge of silhouette Short indexList[ 3 ]; Int i,k; Int vertexCount; Int polygonCount; Int indicesPerMesh; W3DShadowGeometryMesh *geomMesh; // sanity if( volumeIndex < 0 || volumeIndex >= MAX_SHADOW_LIGHTS || lightPosObject == NULL ) { assert( 0 ); return; } // end if // get the geometry struct we're storing the actual shadow volume data in shadowVolume = m_shadowVolume[ volumeIndex ][meshIndex]; if( shadowVolume == NULL ) { // DBGPRINTF(( "No volume allocated at index '%d'\n", volumeIndex )); assert( 0 ); return; } // end if // step through each of the silhouette pairs vertexCount = 0; polygonCount = 0; indicesPerMesh=m_numIndicesPerMesh[meshIndex]; if (!indicesPerMesh) return; //nothing to draw geomMesh = m_geometry->getMesh(meshIndex); shadowVolume->SetNumActivePolygon(0); shadowVolume->SetNumActiveVertex(0); #ifdef RECORD_SHADOW_STRIP_STATS Int numStrips=0; //keeps track of number of strips generated. Int stripLength=1; //keeps track of segments in strip (each being 2 triangles). Int maxStripLength=0; //keeps track of longest strip generated. #endif Short *silhouetteIndices=m_silhouetteIndex[meshIndex]; //Initialize first strip info Short stripStartIndex=silhouetteIndices[ 0 ]; Short stripStartVertex=0; //Insert the first vertex and extrusion into strip. // get edge point geomMesh->GetVertex( silhouetteIndices[ 0 ], &edgeVertex2 ); // take one edge point and extrude away from the light extrude2 = edgeVertex2 - *lightPosObject; extrude2 *= shadowExtrudeDistance; extrude2 += edgeVertex2; shadowVolume->SetVertex( vertexCount, &edgeVertex2 ); shadowVolume->SetVertex( vertexCount + 1, &extrude2 ); vertexCount=2; Int lastEdgeVertex2Index=0; Int lastExtrude2Index=1; for( i = 0; i < indicesPerMesh; i += 2 ) { Short currentEdgeEnd=silhouetteIndices[i+1]; //look for edge connected to this one and move it next to this edge //in index list. Rendering edges back-to-back improves vertex cache usage. for (k=i+2; k= indicesPerMesh) { //reached end of strip. Insert final edge. //Check if last edge wraps around to start. if (currentEdgeEnd == stripStartIndex) { //add end of strip that wraps around to start (forming closed cylinder/shape) // // add the polygon consisting of the two edge vertices and the // first extruded point // indexList[ 0 ] = lastEdgeVertex2Index; // lastedgeVertex2 index indexList[ 1 ] = lastExtrude2Index; // lastextrude2 index indexList[ 2 ] = stripStartVertex; // edgeVertex2 index shadowVolume->SetPolygonIndex( polygonCount, indexList, 3 ); indexList[ 0 ] = stripStartVertex; // edgeVertex2 index indexList[ 1 ] = lastExtrude2Index; // extrude1 index indexList[ 2 ] = stripStartVertex+1; // extrude2 index shadowVolume->SetPolygonIndex( polygonCount + 1, indexList, 3 ); } else { //add end of strip. Finishes the last 2 polygons. geomMesh->GetVertex( currentEdgeEnd, &edgeVertex2 ); shadowVolume->SetVertex( vertexCount, &edgeVertex2 ); // // add the polygon consisting of the two edge vertices and the // first extruded point // indexList[ 0 ] = lastEdgeVertex2Index; // lastedgeVertex2 index indexList[ 1 ] = lastExtrude2Index; // lastextrude2 index indexList[ 2 ] = vertexCount; // edgeVertex2 index shadowVolume->SetPolygonIndex( polygonCount, indexList, 3 ); // take the other edge point and extrude away from light extrude2 = edgeVertex2 - *lightPosObject; extrude2 *= shadowExtrudeDistance; extrude2 += edgeVertex2; // add the one new vertex shadowVolume->SetVertex( vertexCount + 1, &extrude2 ); indexList[ 0 ] = vertexCount; // edgeVertex2 index indexList[ 1 ] = lastExtrude2Index; // extrude1 index indexList[ 2 ] = vertexCount+1; // extrude2 index shadowVolume->SetPolygonIndex( polygonCount + 1, indexList, 3 ); lastEdgeVertex2Index=vertexCount; lastExtrude2Index=vertexCount+1; vertexCount += 2; } if ((i+2) >= indicesPerMesh) { //finished with all silhouette edges polygonCount += 2; #ifdef RECORD_SHADOW_STRIP_STATS numStrips++; #endif break; //reached end of all edges } //Start a new strip by adding first vertex and extrusion. geomMesh->GetVertex( silhouetteIndices[ i+2 ], &edgeVertex2 ); // take one edge point and extrude away from the light extrude2 = edgeVertex2 - *lightPosObject; extrude2 *= shadowExtrudeDistance; extrude2 += edgeVertex2; lastEdgeVertex2Index=vertexCount; lastExtrude2Index=vertexCount + 1; //record start of new strip info stripStartIndex=silhouetteIndices[ i+2 ]; stripStartVertex=lastEdgeVertex2Index; shadowVolume->SetVertex( lastEdgeVertex2Index, &edgeVertex2 ); shadowVolume->SetVertex( lastExtrude2Index, &extrude2 ); vertexCount += 2; polygonCount += 2; #ifdef RECORD_SHADOW_STRIP_STATS numStrips++; stripLength=1; #endif continue; } else { //continue existing strip by adding extra vertex and extrusion geomMesh->GetVertex( currentEdgeEnd, &edgeVertex2 ); shadowVolume->SetVertex( vertexCount, &edgeVertex2 ); // // add the polygon consisting of the two edge vertices and the // first extruded point // indexList[ 0 ] = lastEdgeVertex2Index; // lastedgeVertex2 index indexList[ 1 ] = lastExtrude2Index; // lastextrude2 index indexList[ 2 ] = vertexCount; // edgeVertex2 index shadowVolume->SetPolygonIndex( polygonCount, indexList, 3 ); // take the other edge point and extrude away from light extrude2 = edgeVertex2 - *lightPosObject; extrude2 *= shadowExtrudeDistance; extrude2 += edgeVertex2; // add the one new vertex shadowVolume->SetVertex( vertexCount + 1, &extrude2 ); indexList[ 0 ] = vertexCount; // edgeVertex2 index indexList[ 1 ] = lastExtrude2Index; // extrude1 index indexList[ 2 ] = vertexCount+1; // extrude2 index shadowVolume->SetPolygonIndex( polygonCount + 1, indexList, 3 ); lastEdgeVertex2Index=vertexCount; lastExtrude2Index=vertexCount+1; vertexCount += 2; polygonCount += 2; } #ifdef RECORD_SHADOW_STRIP_STATS //Continuing strip. stripLength++; maxStripLength=__max(maxStripLength,stripLength); #endif } shadowVolume->SetNumActivePolygon(polygonCount); shadowVolume->SetNumActiveVertex(vertexCount); } // end constructVolume // constructVolumeVB ========================================================== // Given a fresh new geometry class called "shadowVolume" to hold the actual // shadow volume data, this method will create the shadow volume polygons // given the information in the current silhouette of this Shadow and the // light source position. // // The light source should be in object space and the shadow volume polygon // data is also constructed in object space. // // The polygon we will create for a given edge will be that edge extruded // out in the direction away from the light source. This conceptual 4 sided // polygon is however broken up into two triangles for storage. // // This version is designed to construct the volume directly inside a vertex // buffer so it's optimal for static geometry. Since it's assumed to be called // only once per model, we can use some more expensive computations to generate // the volume. // // ============================================================================ void W3DVolumetricShadow::constructVolumeVB( Vector3 *lightPosObject,Real shadowExtrudeDistance, Int volumeIndex, Int meshIndex ) { Geometry *shadowVolume; Vector3 extrude2; // the polypoints extruded from edge and light Vector3 edgeVertex2; // second edge of silhouette Int i,k; Int vertexCount; Int polygonCount; Int indicesPerMesh; W3DShadowGeometryMesh *geomMesh; W3DBufferManager::W3DVertexBufferSlot *vbSlot; W3DBufferManager::W3DIndexBufferSlot *ibSlot; // sanity if( volumeIndex < 0 || volumeIndex >= MAX_SHADOW_LIGHTS || lightPosObject == NULL ) { assert( 0 ); return; } // end if // get the geometry struct we're storing the actual shadow volume data in shadowVolume = m_shadowVolume[ volumeIndex ][meshIndex]; if( shadowVolume == NULL ) { // DBGPRINTF(( "No volume allocated at index '%d'\n", volumeIndex )); assert( 0 ); return; } // end if //*****************************************************************************************/ //Do an initial pass through silhouette data to determine the actual vertex/polygon counts. //This number can't be determined any other way since it depends on degree of vertex sharing //in model. We don't want to overallocate because vertex buffer space is limited. //This pass is also used to sort the edges so they are all connected in strip order. { #ifdef RECORD_SHADOW_STRIP_STATS Int numStrips=0; //keeps track of number of strips generated. Int stripLength=1; //keeps track of segments in strip (each being 2 triangles). Int maxStripLength=0; //keeps track of longest strip generated. #endif // step through each of the silhouette pairs vertexCount = 0; polygonCount = 0; indicesPerMesh=m_numIndicesPerMesh[meshIndex]; if (!indicesPerMesh) return; //nothing to draw Short *silhouetteIndices=m_silhouetteIndex[meshIndex]; //Initialize first strip info Short stripStartIndex=silhouetteIndices[ 0 ]; Short stripStartVertex=0; vertexCount=2; Int lastEdgeVertex2Index=0; Int lastExtrude2Index=1; for( i = 0; i < indicesPerMesh; i += 2 ) { Short currentEdgeEnd=silhouetteIndices[i+1]; //look for edge connected to this one and move it next to this edge //in index list. Rendering edges back-to-back improves vertex cache usage. for (k=i+2; k= indicesPerMesh) { //reached end of strip. Insert final edge. //Check if last edge wraps around to start. if (currentEdgeEnd == stripStartIndex) { //add end of strip that wraps around to start (forming closed cylinder/shape) // // add the polygon consisting of the two edge vertices and the // first extruded point // } else { //add end of strip. Finishes the last 2 polygons. lastEdgeVertex2Index=vertexCount; lastExtrude2Index=vertexCount+1; vertexCount += 2; } if ((i+2) >= indicesPerMesh) { //finished with all silhouette edges polygonCount += 2; #ifdef RECORD_SHADOW_STRIP_STATS numStrips++; #endif break; //reached end of all edges } lastEdgeVertex2Index=vertexCount; lastExtrude2Index=vertexCount + 1; //record start of new strip info stripStartIndex=silhouetteIndices[ i+2 ]; stripStartVertex=lastEdgeVertex2Index; vertexCount += 2; polygonCount += 2; #ifdef RECORD_SHADOW_STRIP_STATS numStrips++; stripLength=1; #endif continue; } else { //continue existing strip by adding extra vertex and extrusion lastEdgeVertex2Index=vertexCount; lastExtrude2Index=vertexCount+1; vertexCount += 2; polygonCount += 2; } #ifdef RECORD_SHADOW_STRIP_STATS //Continuing strip. stripLength++; maxStripLength=__max(maxStripLength,stripLength); #endif } } //initial pass to determine vertex/polygon counts. //*********************************************************************************************** DEBUG_ASSERTCRASH(m_shadowVolumeVB[ volumeIndex ][meshIndex] == NULL,("Updating Existing Static Vertex Buffer Shadow")); vbSlot=m_shadowVolumeVB[ volumeIndex ][meshIndex] = TheW3DBufferManager->getSlot(W3DBufferManager::VBM_FVF_XYZ, vertexCount); DEBUG_ASSERTCRASH(vbSlot != NULL, ("Can't allocate vertex buffer slot for shadow volume")); DEBUG_ASSERTCRASH(vbSlot->m_size >= vertexCount,("Overflowing Shadow Vertex Buffer Slot")); DEBUG_ASSERTCRASH(m_shadowVolume[ volumeIndex ][meshIndex]->GetNumPolygon() == 0,("Updating Existing Static Shadow Volume")); DEBUG_ASSERTCRASH(m_shadowVolumeIB[ volumeIndex ][meshIndex] == NULL,("Updating Existing Static Index Buffer Shadow")); ibSlot=m_shadowVolumeIB[ volumeIndex ][meshIndex] = TheW3DBufferManager->getSlot(polygonCount*3); DEBUG_ASSERTCRASH(ibSlot != NULL, ("Can't allocate index buffer slot for shadow volume")); DEBUG_ASSERTCRASH(ibSlot->m_size >= (polygonCount*3),("Overflowing Shadow Index Buffer Slot")); if (!ibSlot || !vbSlot) { //could not allocate storage to hold buffers if (ibSlot) TheW3DBufferManager->releaseSlot(ibSlot); if (vbSlot) TheW3DBufferManager->releaseSlot(vbSlot); m_shadowVolumeIB[ volumeIndex ][meshIndex]=NULL; m_shadowVolumeVB[ volumeIndex ][meshIndex]=NULL; return; } geomMesh = m_geometry->getMesh(meshIndex); DX8VertexBufferClass::AppendLockClass lockVtxBuffer(vbSlot->m_VB->m_DX8VertexBuffer,vbSlot->m_start,vertexCount); VertexFormatXYZ *vb = (VertexFormatXYZ*)lockVtxBuffer.Get_Vertex_Array(); if (vb == NULL) return; DX8IndexBufferClass::AppendLockClass lockIdxBuffer(ibSlot->m_IB->m_DX8IndexBuffer,ibSlot->m_start,polygonCount*3); UnsignedShort *ib = (UnsignedShort*)lockIdxBuffer.Get_Index_Array(); if (ib == NULL) return; shadowVolume->SetNumActivePolygon(polygonCount); shadowVolume->SetNumActiveVertex(vertexCount); Short *silhouetteIndices=m_silhouetteIndex[meshIndex]; //Initialize first strip info Short stripStartIndex=silhouetteIndices[ 0 ]; Short stripStartVertex=0; //Insert the first vertex and extrusion into strip. // get edge point geomMesh->GetVertex( silhouetteIndices[ 0 ], &edgeVertex2 ); // take one edge point and extrude away from the light extrude2 = edgeVertex2 - *lightPosObject; extrude2 *= shadowExtrudeDistance; extrude2 += edgeVertex2; *vb++ = *(VertexFormatXYZ *)&edgeVertex2; *vb++ = *(VertexFormatXYZ *)&extrude2; vertexCount=2; polygonCount=0; Int lastEdgeVertex2Index=0; Int lastExtrude2Index=1; for( i = 0; i < indicesPerMesh; i += 2 ) { Short currentEdgeEnd=silhouetteIndices[i+1]; //Check if another edge is connected to this one, edges were sorted in initial //pass so only need to check the next one. if (((i+2) >= indicesPerMesh) || silhouetteIndices[i+2] != currentEdgeEnd) { //reached end of strip. Insert final edge. //Check if last edge wraps around to start. if (currentEdgeEnd == stripStartIndex) { //add end of strip that wraps around to start (forming closed cylinder/shape) // // add the polygon consisting of the two edge vertices and the // first extruded point // ib[ 0 ] = lastEdgeVertex2Index; // lastedgeVertex2 index ib[ 4 ] = ib[ 1 ] = lastExtrude2Index; // lastextrude2 index ib[ 3 ] = ib[ 2 ] = stripStartVertex; // edgeVertex2 index ib[ 5 ] = stripStartVertex+1; // extrude2 index ib += 6; //skip past 2 triangles just added. } else { //add end of strip. Finishes the last 2 polygons. geomMesh->GetVertex( currentEdgeEnd, &edgeVertex2 ); *vb++ = *(VertexFormatXYZ *)&edgeVertex2; // // add the polygon consisting of the two edge vertices and the // first extruded point // ib[ 0 ] = lastEdgeVertex2Index; // lastedgeVertex2 index ib[ 4 ] = ib[ 1 ] = lastExtrude2Index; // lastextrude2 index ib[ 3 ] = ib[ 2 ] = vertexCount; // edgeVertex2 index ib[ 5 ] = vertexCount+1; // extrude2 index ib += 6; //skip past 2 triangles just added. // take the other edge point and extrude away from light extrude2 = edgeVertex2 - *lightPosObject; extrude2 *= shadowExtrudeDistance; extrude2 += edgeVertex2; // add the one new vertex *vb++ = *(VertexFormatXYZ *)&extrude2; lastEdgeVertex2Index=vertexCount; lastExtrude2Index=vertexCount+1; vertexCount += 2; } if ((i+2) >= indicesPerMesh) { //finished with all silhouette edges polygonCount += 2; break; //reached end of all edges } //Start a new strip by adding first vertex and extrusion. geomMesh->GetVertex( silhouetteIndices[ i+2 ], &edgeVertex2 ); // take one edge point and extrude away from the light extrude2 = edgeVertex2 - *lightPosObject; extrude2 *= shadowExtrudeDistance; extrude2 += edgeVertex2; lastEdgeVertex2Index=vertexCount; lastExtrude2Index=vertexCount + 1; //record start of new strip info stripStartIndex=silhouetteIndices[ i+2 ]; stripStartVertex=lastEdgeVertex2Index; *vb++ = *(VertexFormatXYZ *)&edgeVertex2; *vb++ = *(VertexFormatXYZ *)&extrude2; vertexCount += 2; polygonCount += 2; continue; } else { //continue existing strip by adding extra vertex and extrusion geomMesh->GetVertex( currentEdgeEnd, &edgeVertex2 ); *vb++ = *(VertexFormatXYZ *)&edgeVertex2; // // add the polygon consisting of the two edge vertices and the // first extruded point // ib[ 0 ] = lastEdgeVertex2Index; // lastedgeVertex2 index ib[ 4 ] = ib[ 1 ] = lastExtrude2Index; // lastextrude2 index ib[ 3 ] = ib[ 2 ] = vertexCount; // edgeVertex2 index ib[ 5 ] = vertexCount+1; // extrude2 index ib += 6; //skip past 2 triangles just added // take the other edge point and extrude away from light extrude2 = edgeVertex2 - *lightPosObject; extrude2 *= shadowExtrudeDistance; extrude2 += edgeVertex2; // add the one new vertex *vb++ = *(VertexFormatXYZ *)&extrude2; lastEdgeVertex2Index=vertexCount; lastExtrude2Index=vertexCount+1; vertexCount += 2; polygonCount += 2; } } // DEBUG_ASSERTLOG(polygonCount == vertexCount, ("WARNING***Shadow volume mesh not optimal: %s\n",m_geometry->Get_Name())); } // end constructVolume // allocateShadowVolume ======================================================= // Allocate a space for us to construct the shadow volume in // ============================================================================ Bool W3DVolumetricShadow::allocateShadowVolume( Int volumeIndex, Int meshIndex ) { Int numVertices, numPolygons; Geometry *shadowVolume; // sanity if( volumeIndex < 0 || volumeIndex >= MAX_SHADOW_LIGHTS ) { // DBGPRINTF(( "Illegal allocate shadow volume index '%d'\n", volumeIndex )); assert( 0 ); return FALSE; } // end if if ((shadowVolume = m_shadowVolume[ volumeIndex ][meshIndex]) == 0) { // poolify shadowVolume = NEW Geometry; // create the new geometry // we now have one more valid geometry volume m_shadowVolumeCount[meshIndex]++; } if( shadowVolume == NULL ) { // DBGPRINTF(( "Unable to allocate '%d' shadow volume\n", volumeIndex )); assert( 0 ); // we now have one more valid geometry volume m_shadowVolumeCount[meshIndex]--; return FALSE; } // end if // assign to list m_shadowVolume[ volumeIndex ][meshIndex] = shadowVolume; // // polygons are determined from the edges extruded from the light. // since we have a list of disjoint edge pairs we will have a 4 sided // polygon for each edge pair, however we will be splitting the // 4 sided polys into 2 triangles so it just works out that num polys // is the number of silhouette indices // numPolygons = m_maxSilhouetteEntries[meshIndex]; // // vertices are an extrusion of the edges, and since we have disjoint // pairs of edge indices we will have num indices * 2 actual vertex // points. it may be a good future optimization to not duplicate // any vertices that appear twice here, but then again the cost over // optimizing this routine over the rendering and transformation // optimization may not be worth it // numVertices = m_maxSilhouetteEntries[meshIndex] * 2; //Only allocate space here for dynamic shadows. Shadows for static/non-animated //models will be stored in vertex buffers which are allocated once exact size //is known. if (shadowVolume->GetFlags() & SHADOW_DYNAMIC) { //for dynamic shadow casters, we need to allocate the maximum amount of vertices that could ever be required. // if (m_shadowVolumeVB[ volumeIndex ][meshIndex]) // TheW3DBufferManager->releaseSlot(m_shadowVolumeVB[ volumeIndex ][meshIndex]); // m_shadowVolumeVB[ volumeIndex ][meshIndex] = TheW3DVertexBufferManager->getSlot(W3DVertexBufferManager::VBM_FVF_XYZ, numVertices); // allocate memory for the vertices and polygons if( shadowVolume->Create( numVertices, numPolygons ) == FALSE ) { // DBGPRINTF(( "Unable to create shadow volume\n" )); assert( 0 ); delete shadowVolume; return FALSE; } // end if } return TRUE; // success } // end allocateShadowVolume // deleteShadowVolume ========================================================= // Free all resources allocated to the shadow volume(s) // ============================================================================ void W3DVolumetricShadow::deleteShadowVolume( Int volumeIndex ) { // sanity if( volumeIndex < 0 || volumeIndex >= MAX_SHADOW_LIGHTS ) { // DBGPRINTF(( "Illegal delete shadow volume index '%d'\n", volumeIndex )); assert( 0 ); return; } // end if // delete it! for (Int meshIndex=0; meshIndex= MAX_SHADOW_LIGHTS ) { // DBGPRINTF(( "Illegal reset shadow volume index '%d'\n", volumeIndex )); assert( 0 ); return; } // end if geometry = m_shadowVolume[ volumeIndex ][meshIndex]; //Release buffers used to hold shadow volume geometry if (geometry) { if (m_shadowVolumeVB[volumeIndex][meshIndex]) { TheW3DBufferManager->releaseSlot(m_shadowVolumeVB[volumeIndex][meshIndex]); m_shadowVolumeVB[volumeIndex][meshIndex]=NULL; } if (m_shadowVolumeIB[ volumeIndex ][meshIndex]) { TheW3DBufferManager->releaseSlot(m_shadowVolumeIB[volumeIndex][meshIndex]); m_shadowVolumeIB[volumeIndex][meshIndex]=NULL; } geometry->Release(); } } // end resetShadowVolume // allocateSilhouette ========================================================= // Allocate space for new silhouette storage, the number of vertices passed // in is the total vertices in the model, a silhouette must be able to // accomodate that as a series of disjoint edge pairs, otherwise known // as numVertices * 2 // ============================================================================ Bool W3DVolumetricShadow::allocateSilhouette(Int meshIndex, Int numVertices ) { Int numEntries = numVertices * 5; ///@todo: HACK, HACK... Should be 2! // sanity assert( m_silhouetteIndex[meshIndex] == NULL && m_numSilhouetteIndices[meshIndex] == 0 && numEntries > 0 ); // allocate memory m_silhouetteIndex[meshIndex] = NEW short[ numEntries ]; if( m_silhouetteIndex[meshIndex] == NULL ) { // DBGPRINTF(( "Unable to allcoate silhouette storage '%d'\n", numEntries )); assert( 0 ); return FALSE; } // end if // set our list to empty just to be clean m_numSilhouetteIndices[meshIndex] = 0; // save the size of our silhouette list m_maxSilhouetteEntries[meshIndex] = numEntries; return TRUE; // success } // end allocateSilhouette // deleteSilhouette =========================================================== // Delete all silhouette data and memory allocated // ============================================================================ void W3DVolumetricShadow::deleteSilhouette( Int meshIndex ) { if( m_silhouetteIndex[meshIndex]) delete [] m_silhouetteIndex[meshIndex]; m_silhouetteIndex[meshIndex] = NULL; m_numSilhouetteIndices[meshIndex] = 0; } // end deletesilhouette // resetSilhouette ============================================================ // Resets the silhouette to empty, it does NOT free any of the memory // allocated for silhouette data // ============================================================================ void W3DVolumetricShadow::resetSilhouette( Int meshIndex ) { m_numSilhouetteIndices[meshIndex] = 0; } // end resetSilhouette // renderStencilShadows ======================================================= // The stencil buffer now has our shadow information in it, take that // info and draw a big transparent rectangle over the screen for the final // shadow pass wherever there is data in the stencil buffer // ============================================================================ void W3DVolumetricShadowManager::renderStencilShadows( void ) { LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); if (!m_pDev) return; //need device to render anything. struct _TRANSLITVERTEX { D3DXVECTOR4 p; DWORD color; // diffuse color } v[4]; Int xpos, ypos, width, height; TheTacticalView->getOrigin(&xpos,&ypos); width=TheTacticalView->getWidth(); height=TheTacticalView->getHeight(); v[0].p = D3DXVECTOR4( xpos+width, ypos+height, 0.0f, 1.0f ); v[1].p = D3DXVECTOR4( xpos+width, 0, 0.0f, 1.0f ); v[2].p = D3DXVECTOR4( xpos, ypos+height, 0.0f, 1.0f ); v[3].p = D3DXVECTOR4( xpos, 0, 0.0f, 1.0f ); v[0].color = TheW3DShadowManager->getShadowColor(); v[1].color = TheW3DShadowManager->getShadowColor(); v[2].color = TheW3DShadowManager->getShadowColor(); v[3].color = TheW3DShadowManager->getShadowColor(); //draw polygons like this is very inefficient but for only 2 triangles, it's //not worth bothering with index/vertex buffers. m_pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE); // Use alpha blending to draw the transparent shadow m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); // m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); // m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR); m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO ); // Set stencil states m_pDev->SetRenderState( D3DRS_ZENABLE, TRUE ); m_pDev->SetRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS); // Only write where stencil val >= 1 (count indicates # of shadows that // overlap that pixel) m_pDev->SetRenderState( D3DRS_STENCILENABLE, TRUE ); m_pDev->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_LESSEQUAL ); //reference value is less or equal to stencil m_pDev->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_KEEP ); //Upper bits of stencil could be used for storing occluded models which are player colored. So we mask out those //pixels and only use the lower bits for shadow calculations. m_pDev->SetRenderState( D3DRS_STENCILMASK, ~TheW3DShadowManager->getStencilShadowMask()); m_pDev->SetRenderState( D3DRS_STENCILREF, 0x1 ); m_pDev->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT); if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) m_pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANSLITVERTEX)); m_pDev->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); // turn off the stencil buffer m_pDev->SetRenderState( D3DRS_STENCILENABLE, FALSE ); } // end renderStencilShadows void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) { W3DVolumetricShadow *shadow; Int numRenderedShadows = 0; AABoxClass bbox; SphereClass bsphere; //Get a bounding box around our visible universe. Bounded by terrain and the sky //so much tighter fitting volume than what's actually visible. This will cull //particles falling under the ground. TheTerrainRenderObject->getMaximumVisibleBox(*shadowCameraFrustum, &bbox, TRUE); bcX = bbox.Center.X; bcY = bbox.Center.Y; bcZ = bbox.Center.Z; beX = bbox.Extent.X; beY = bbox.Extent.Y; beZ = bbox.Extent.Z; if (m_shadowList && TheGlobalData->m_useShadowVolumes) { LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); if (!m_pDev) return; //need device to render anything. //According to Nvidia there's a D3D bug that happens if you don't start with a //new dynamic VB each frame - so we force a DISCARD by overflowing the counter. nShadowIndicesInBuf = 0xffff; nShadowVertsInBuf = 0xffff; //Set W3D to some known state VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); DX8Wrapper::Set_Material(vmat); REF_PTR_RELEASE(vmat); DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); DX8Wrapper::Set_Texture(0,NULL); //turn off textures DX8Wrapper::Set_Texture(1,NULL); //turn off textures DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices // turn off z writing m_pDev->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); m_pDev->SetRenderState( D3DRS_ZENABLE, TRUE ); m_pDev->SetRenderState(D3DRS_ZWRITEENABLE , FALSE); m_pDev->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); m_pDev->SetRenderState(D3DRS_FOGENABLE, FALSE); // setup the TMU to default m_pDev->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT); m_pDev->SetRenderState(D3DRS_LIGHTING, FALSE); m_pDev->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); m_pDev->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); m_pDev->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); m_pDev->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); m_pDev->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, 0 ); m_pDev->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_DISABLE); m_pDev->SetTextureStageState( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); m_pDev->SetTextureStageState( 1, D3DTSS_TEXCOORDINDEX, 1 ); m_pDev->SetTexture(0,NULL); m_pDev->SetTexture(1,NULL); DWORD oldColorWriteEnable=0x12345678; #ifdef SV_DEBUG m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE , TRUE); m_pDev->SetRenderState( D3DRS_STENCILENABLE, FALSE ); m_pDev->SetRenderState( D3DRS_SRCBLEND, /*D3DBLEND_DESTCOLOR*/D3DBLEND_ONE ); m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO ); m_pDev->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); #else //disable writes to color buffer if (DX8Caps::Get_Default_Caps().PrimitiveMiscCaps & D3DPMISCCAPS_COLORWRITEENABLE) { DX8Wrapper::_Get_D3D_Device8()->GetRenderState(D3DRS_COLORWRITEENABLE, &oldColorWriteEnable); DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,0); } else { //device does not support disabling writes to color buffer so fake it through alpha blending m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ZERO ); m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE , TRUE); } m_pDev->SetRenderState( D3DRS_STENCILENABLE, TRUE ); #endif //Any pixels with stencil already set to 128 contains a potential occluder. If this pixels also has any of the player //color stencil bits also set, it means that it's an occluded player color and we need to NOT render shadows here. We //do this determination by comparing the value in the combined bits against a value containing only a potential occluder. //If the value of just the potential occluder bit is >= than the combined bits, then we know none of the player color //bits were set and it's okay to render shadow. if (TheW3DShadowManager->getStencilShadowMask() == 0x80808080) m_pDev->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_NOTEQUAL ); //in this mode, MSB indicates occluded player pixels. else m_pDev->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_GREATEREQUAL ); //in this mode, multiple bits indicate occluded player pixels. m_pDev->SetRenderState( D3DRS_STENCILREF, 0x80808080 ); //isolate MSB, it's used to indicate pixels containing potential occluders. m_pDev->SetRenderState( D3DRS_STENCILMASK, TheW3DShadowManager->getStencilShadowMask()); //isolate upper bits containing PotentialOccluderBit|PlayerColorBits m_pDev->SetRenderState( D3DRS_STENCILWRITEMASK,0xffffffff ); m_pDev->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); m_pDev->SetRenderState( D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); m_pDev->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_INCR ); m_pDev->SetVertexShader(SHADOW_DYNAMIC_VOLUME_FVF); m_pDev->SetRenderState(D3DRS_CULLMODE,D3DCULL_CW); // m_pDev->SetRenderState(D3DRS_ZBIAS,1); ///@todo: See if this helps or makes things worse. //m_pDev->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME); lastActiveVertexBuffer=NULL; //reset m_dynamicShadowVolumesToRender=NULL; //clear list of pending dynamic shadows W3DVolumetricShadowRenderTask *shadowDynamicTasksStart,*shadowDynamicTask; // step through each of our shadows and render for( shadow = m_shadowList; shadow; shadow = shadow->m_next ) { if (shadow->m_isEnabled && !shadow->m_isInvisibleEnabled) { //Record last added task shadowDynamicTasksStart=m_dynamicShadowVolumesToRender; shadow->Update(); shadowDynamicTask=m_dynamicShadowVolumesToRender; while (shadowDynamicTask != shadowDynamicTasksStart) { //update() added a dynamic shadow //dynamic shadow columes don't need to wait in queue since they //all use the same vertex buffer. Flush them ASAP. shadow->RenderVolume(shadowDynamicTask->m_meshIndex,shadowDynamicTask->m_lightIndex); //move to next dynamic task shadowDynamicTask=(W3DVolumetricShadowRenderTask *)shadowDynamicTask->m_nextTask; numRenderedShadows++; } } } // end for // Set vertex format to that used by static shadow volumes m_pDev->SetVertexShader(W3DBufferManager::getDX8Format(W3DBufferManager::VBM_FVF_XYZ)); //Empty queue of static shadow volumes to render. W3DBufferManager::W3DVertexBuffer *nextVb; W3DVolumetricShadowRenderTask *nextTask; for (nextVb=TheW3DBufferManager->getNextVertexBuffer(NULL,W3DBufferManager::VBM_FVF_XYZ);nextVb != NULL; nextVb=TheW3DBufferManager->getNextVertexBuffer(nextVb,W3DBufferManager::VBM_FVF_XYZ)) { nextTask=(W3DVolumetricShadowRenderTask *)nextVb->m_renderTaskList; while (nextTask) { nextTask->m_parentShadow->RenderVolume(nextTask->m_meshIndex,nextTask->m_lightIndex); nextTask=(W3DVolumetricShadowRenderTask *)nextTask->m_nextTask; numRenderedShadows++; } } // change the stencil op to decrement m_pDev->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_DECRSAT); // // invert normals of shadow volumes so we can decrement in the // stencil buffer and render // m_pDev->SetRenderState(D3DRS_CULLMODE,D3DCULL_CCW); for (nextVb=TheW3DBufferManager->getNextVertexBuffer(NULL,W3DBufferManager::VBM_FVF_XYZ);nextVb != NULL; nextVb=TheW3DBufferManager->getNextVertexBuffer(nextVb,W3DBufferManager::VBM_FVF_XYZ)) { nextTask=(W3DVolumetricShadowRenderTask *)nextVb->m_renderTaskList; while (nextTask) { nextTask->m_parentShadow->RenderVolume(nextTask->m_meshIndex,nextTask->m_lightIndex); nextTask=(W3DVolumetricShadowRenderTask *)nextTask->m_nextTask; } } m_pDev->SetVertexShader(SHADOW_DYNAMIC_VOLUME_FVF); //flush any dynamic shadow volumes shadowDynamicTask=m_dynamicShadowVolumesToRender; while (shadowDynamicTask) { //dynamic shadow columes don't need to wait in queue since they //all use the same vertex buffer. Flush them ASAP. shadowDynamicTask->m_parentShadow->RenderVolume(shadowDynamicTask->m_meshIndex,shadowDynamicTask->m_lightIndex); shadowDynamicTask=(W3DVolumetricShadowRenderTask *)shadowDynamicTask->m_nextTask; } //Reset all render tasks for next frame. for (nextVb=TheW3DBufferManager->getNextVertexBuffer(NULL,W3DBufferManager::VBM_FVF_XYZ);nextVb != NULL; nextVb=TheW3DBufferManager->getNextVertexBuffer(nextVb,W3DBufferManager::VBM_FVF_XYZ)) { nextVb->m_renderTaskList=NULL; } m_pDev->SetRenderState(D3DRS_CULLMODE,D3DCULL_CW); // m_pDev->SetRenderState(D3DRS_ZBIAS,0); ///@todo: See if this helps or makes things worse. //m_pDev->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); if (oldColorWriteEnable != 0x12345678) DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,oldColorWriteEnable); // // render the big transparent square of shadows in the stencil buffer // to the screen // ///@todo: Put this check back in after water is fixed so it doesn't require shadow rendering to fix alpha. // if (numRenderedShadows) renderStencilShadows(); m_pDev->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE , FALSE); m_pDev->SetRenderState(D3DRS_LIGHTING, FALSE); DX8Wrapper::Invalidate_Cached_Render_States(); } else if (forceStencilFill) { //no shadows to render, but still need to fill stencil buffer //for other effects. //Set W3D to some known state VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); DX8Wrapper::Set_Material(vmat); REF_PTR_RELEASE(vmat); DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); DX8Wrapper::Set_Texture(0,NULL); DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices renderStencilShadows(); DX8Wrapper::Invalidate_Cached_Render_States(); } } // end RenderShadows /** This class will manage shadow geometry for each render object. Shadow geometry may be the same as render geometry but doesn't need to be. This allows lower LOD versions of the geometry to be used in shadow calculations. Shadow geometry also keeps extended vertex connectivity information that's not used during rendering. */ class W3DShadowGeometryManager { public: W3DShadowGeometryManager(void); ~W3DShadowGeometryManager(void); int Load_Geom(RenderObjClass *robj, const char *name); W3DShadowGeometry * Get_Geom(const char * name); W3DShadowGeometry * Peek_Geom(const char * name); Bool Add_Geom(W3DShadowGeometry *new_anim); void Free_All_Geoms(void); void Register_Missing( const char * name ); Bool Is_Missing( const char * name ); void Reset_Missing( void ); private: HashTableClass * GeomPtrTable; HashTableClass * MissingGeomTable; friend class W3DShadowGeometryManagerIterator; }; /* ** An Iterator to get to all loaded W3DShadowGeometries in a W3DShadowGeometryManager */ class W3DShadowGeometryManagerIterator : public HashTableIteratorClass { public: W3DShadowGeometryManagerIterator( W3DShadowGeometryManager & manager ) : HashTableIteratorClass( *manager.GeomPtrTable ) {} W3DShadowGeometry * Get_Current_Geom( void ); }; /** Used to cause a rebuild of all shadow volumes*/ void W3DVolumetricShadowManager::invalidateCachedLightPositions(void) { if (!m_shadowList) return; //there are no shadows to render. W3DVolumetricShadow *shadow; Vector3 vec(0,0,0); // step through each of our shadows and update previous light position. for( shadow = m_shadowList; shadow; shadow = shadow->m_next ) { for(Int i = 0; i < MAX_SHADOW_LIGHTS; i++ ) { for (Int meshIndex=0; meshIndexsetLightPosHistory(i,meshIndex,vec); } } } // end for } // W3DVolumetricShadowManager ============================================================= // ============================================================================ W3DVolumetricShadowManager::W3DVolumetricShadowManager( void ) { m_shadowList = NULL; m_W3DShadowGeometryManager = NEW W3DShadowGeometryManager; TheW3DBufferManager = NEW W3DBufferManager; } // end ShadowManager // ~W3DVolumetricShadowManager ============================================================ // ============================================================================ W3DVolumetricShadowManager::~W3DVolumetricShadowManager( void ) { ReleaseResources(); delete m_W3DShadowGeometryManager; m_W3DShadowGeometryManager = NULL; delete TheW3DBufferManager; TheW3DBufferManager=NULL; //all shadows should be freed up at this point but check anyway assert(m_shadowList==NULL); } // end ~W3DVolumetricShadowManager /** Releases all W3D/D3D assets before a reset.. */ void W3DVolumetricShadowManager::ReleaseResources(void) { if (shadowIndexBufferD3D) shadowIndexBufferD3D->Release(); if (shadowVertexBufferD3D) shadowVertexBufferD3D->Release(); shadowIndexBufferD3D=NULL; shadowVertexBufferD3D=NULL; if (TheW3DBufferManager) { TheW3DBufferManager->ReleaseResources(); invalidateCachedLightPositions(); //vertex buffers need to be refilled. } } /** (Re)allocates all W3D/D3D assets after a reset.. */ Bool W3DVolumetricShadowManager::ReAcquireResources(void) { ReleaseResources(); LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); DEBUG_ASSERTCRASH(m_pDev, ("Trying to ReAquireResources on W3DVolumetricShadowManager without device")); if (FAILED(m_pDev->CreateIndexBuffer ( SHADOW_INDEX_SIZE*sizeof(WORD), D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &shadowIndexBufferD3D ))) return FALSE; if (shadowVertexBufferD3D == NULL) { // Create vertex buffer if (FAILED(m_pDev->CreateVertexBuffer ( SHADOW_VERTEX_SIZE*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX), D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC, 0, D3DPOOL_DEFAULT, &shadowVertexBufferD3D ))) return FALSE; } if (TheW3DBufferManager) if (!TheW3DBufferManager->ReAcquireResources()) return FALSE; return TRUE; } // Init ======================================================================= // User called initialization // ============================================================================ Bool W3DVolumetricShadowManager::init( void ) { return TRUE; } // end Init // Reset ====================================================================== // Reset our list of shadows to empty // ============================================================================ void W3DVolumetricShadowManager::reset( void ) { assert (m_shadowList == NULL); m_W3DShadowGeometryManager->Free_All_Geoms(); TheW3DBufferManager->freeAllBuffers(); } // end Reset // addShadow ================================================================== // Add the shadows for this hierarchy to the shadow management for // rendering. // ============================================================================ W3DVolumetricShadow* W3DVolumetricShadowManager::addShadow(RenderObjClass *robj, Shadow::ShadowTypeInfo *shadowInfo, Drawable *draw) { if (!DX8Wrapper::Has_Stencil() || !robj || !TheGlobalData->m_useShadowVolumes) return NULL; //right now we require a stencil buffer W3DShadowGeometry *sg=NULL; if (!robj) return NULL; //must have a render object in order to read shadow geometry const char *name=robj->Get_Name(); if (!name) return NULL; sg=m_W3DShadowGeometryManager->Get_Geom(name); if (sg==NULL) { //did not find a cached copy of the shadow geometry, create a new one m_W3DShadowGeometryManager->Load_Geom(robj,name); //try loading again sg=m_W3DShadowGeometryManager->Get_Geom(name); if (sg==NULL) return NULL; //could not create the shadow geometry } W3DVolumetricShadow *shadow = NEW W3DVolumetricShadow; // poolify // sanity if( shadow == NULL ) return NULL; shadow->setRenderObject(robj); shadow->SetGeometry(sg); SphereClass sphere; robj->Get_Obj_Space_Bounding_Sphere(sphere); shadow->setRenderObjExtent(sphere.Radius*MAX_SHADOW_LENGTH_SCALE_FACTOR); Real sunElevationAngleTan = 0; if (shadowInfo->m_sizeX) { //need to adjust sun elevation for this model in order to limit shadow length sunElevationAngleTan=tan(shadowInfo->m_sizeX/180.0f*PI); } shadow->setShadowLengthScale(sunElevationAngleTan); if (!draw || !draw->isKindOf(KINDOF_IMMOBILE)) shadow->setOptimalExtrusionPadding(SHADOW_EXTRUSION_BUFFER); // add to our shadow list through the shadow next links shadow->m_next = m_shadowList; m_shadowList = shadow; return shadow; } /** removeShadow =========================================================== Removes the shadows for this hierarchy from the shadow manger. No further shadows from this caster will be rendered. =========================================================================== */ void W3DVolumetricShadowManager::removeShadow(W3DVolumetricShadow *shadow) { W3DVolumetricShadow *prev_shadow=NULL; W3DVolumetricShadow *next_shadow=NULL; //search for this shadow for( next_shadow = m_shadowList; next_shadow; prev_shadow=next_shadow, next_shadow = next_shadow->m_next ) { if (next_shadow == shadow) { if (prev_shadow) prev_shadow->m_next=shadow->m_next; else m_shadowList=shadow->m_next; delete shadow; break; } } // end for } /** removeAllShadows =========================================================== Removes all shadows from the shadow manger. No further shadows will be rendered. =========================================================================== */ void W3DVolumetricShadowManager::removeAllShadows(void) { W3DVolumetricShadow *cur_shadow=NULL; W3DVolumetricShadow *next_shadow=m_shadowList; m_shadowList = NULL; //search for this shadow for( cur_shadow = next_shadow; cur_shadow; cur_shadow = next_shadow ) { next_shadow = cur_shadow->m_next; cur_shadow->m_next = NULL; delete cur_shadow; } // end for } W3DShadowGeometryManager::W3DShadowGeometryManager(void) { // Create the hash tables GeomPtrTable = NEW HashTableClass( 2048 ); MissingGeomTable = NEW HashTableClass( 2048 ); } W3DShadowGeometryManager::~W3DShadowGeometryManager(void) { Free_All_Geoms(); delete GeomPtrTable; GeomPtrTable = NULL; delete MissingGeomTable; MissingGeomTable = NULL; } /** Release all loaded animations */ void W3DShadowGeometryManager::Free_All_Geoms(void) { // Make an iterator, and release all ptrs W3DShadowGeometryManagerIterator it( *this ); for( it.First(); !it.Is_Done(); it.Next() ) { W3DShadowGeometry *geom = it.Get_Current_Geom(); geom->Release_Ref(); } // Then clear the table GeomPtrTable->Reset(); } /** Find animation in cache */ W3DShadowGeometry * W3DShadowGeometryManager::Peek_Geom(const char * name) { return (W3DShadowGeometry*)GeomPtrTable->Find( name ); } /** Get animation from cache and increment its reference count */ W3DShadowGeometry * W3DShadowGeometryManager::Get_Geom(const char * name) { W3DShadowGeometry * geom = Peek_Geom( name ); if ( geom != NULL ) { geom->Add_Ref(); } return geom; } /** Add animation to cache */ Bool W3DShadowGeometryManager::Add_Geom(W3DShadowGeometry *new_geom) { WWASSERT (new_geom != NULL); // Increment the refcount on the new animation and add it to our table. new_geom->Add_Ref (); GeomPtrTable->Add( new_geom ); return true; } /* ** An entry for a table of anims not found, so we can quickly determine their loss */ class MissingGeomClass : public HashableClass { public: MissingGeomClass( const char * name ) : Name( name ) {} virtual ~MissingGeomClass( void ) {} virtual const char * Get_Key( void ) { return Name; } private: StringClass Name; }; /* ** Missing Geoms ** ** The idea here, allow the system to register which anims are determined to be missing ** so that if they are asked for again, we can quickly return NULL, without searching the ** disk again. */ void W3DShadowGeometryManager::Register_Missing( const char * name ) { MissingGeomTable->Add( NEW MissingGeomClass( name ) ); } Bool W3DShadowGeometryManager::Is_Missing( const char * name ) { return ( MissingGeomTable->Find( name ) != NULL ); } /** Create shadow geometry from a reference W3D RenderObject*/ int W3DShadowGeometryManager::Load_Geom(RenderObjClass *robj, const char *name) { Bool res=FALSE; W3DShadowGeometry * newgeom = NEW W3DShadowGeometry; if (newgeom == NULL) { goto Error; } SET_REF_OWNER( newgeom ); newgeom->Set_Name(name); switch (robj->Class_ID()) { case RenderObjClass::CLASSID_HLOD: res=newgeom->initFromHLOD(robj); break; case RenderObjClass::CLASSID_MESH: res=newgeom->initFromMesh(robj); break; default: break; //unknown render object type }; if (res != TRUE) { // load failed! newgeom->Release_Ref(); //DEBUG_LOG(("****Shadow Volume Creation Failed on %s\n",name)); goto Error; } else if (Peek_Geom(newgeom->Get_Name()) != NULL) { // duplicate exists! newgeom->Release_Ref(); // Release the one we just loaded goto Error; } else { Add_Geom( newgeom ); newgeom->Release_Ref(); } return 0; Error: return 1; } /* ** Iterator converter from HashableClass to GrannyAnimClass */ W3DShadowGeometry * W3DShadowGeometryManagerIterator::Get_Current_Geom( void ) { return (W3DShadowGeometry *)Get_Current(); }