/*
** 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();
}