/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see .
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "precompiled.h"
#include "tr_local.h"
/*
===========================================================================
idInteraction implementation
===========================================================================
*/
/*
================
R_CalcInteractionFacing
Determines which triangles of the surface are facing towards the light origin.
The facing array should be allocated with one extra index than
the number of surface triangles, which will be used to handle dangling
edge silhouettes.
================
*/
void R_CalcInteractionFacing( const idRenderEntityLocal* ent, const srfTriangles_t* tri, const idRenderLightLocal* light, srfCullInfo_t& cullInfo )
{
SCOPED_PROFILE_EVENT( "R_CalcInteractionFacing" );
if( cullInfo.facing != NULL )
{
return;
}
idVec3 localLightOrigin;
R_GlobalPointToLocal( ent->modelMatrix, light->globalLightOrigin, localLightOrigin );
const int numFaces = tri->numIndexes / 3;
cullInfo.facing = ( byte* ) R_StaticAlloc( ( numFaces + 1 ) * sizeof( cullInfo.facing[0] ), TAG_RENDER_INTERACTION );
// exact geometric cull against face
for( int i = 0, face = 0; i < tri->numIndexes; i += 3, face++ )
{
const idDrawVert& v0 = tri->verts[tri->indexes[i + 0]];
const idDrawVert& v1 = tri->verts[tri->indexes[i + 1]];
const idDrawVert& v2 = tri->verts[tri->indexes[i + 2]];
const idPlane plane( v0.xyz, v1.xyz, v2.xyz );
const float d = plane.Distance( localLightOrigin );
cullInfo.facing[face] = ( d >= 0.0f );
}
cullInfo.facing[numFaces] = 1; // for dangling edges to reference
}
/*
=====================
R_CalcInteractionCullBits
We want to cull a little on the sloppy side, because the pre-clipping
of geometry to the lights in dmap will give many cases that are right
at the border. We throw things out on the border, because if any one
vertex is clearly inside, the entire triangle will be accepted.
=====================
*/
void R_CalcInteractionCullBits( const idRenderEntityLocal* ent, const srfTriangles_t* tri, const idRenderLightLocal* light, srfCullInfo_t& cullInfo )
{
SCOPED_PROFILE_EVENT( "R_CalcInteractionCullBits" );
if( cullInfo.cullBits != NULL )
{
return;
}
idPlane frustumPlanes[6];
idRenderMatrix::GetFrustumPlanes( frustumPlanes, light->baseLightProject, true, true );
int frontBits = 0;
// cull the triangle surface bounding box
for( int i = 0; i < 6; i++ )
{
R_GlobalPlaneToLocal( ent->modelMatrix, frustumPlanes[i], cullInfo.localClipPlanes[i] );
// get front bits for the whole surface
if( tri->bounds.PlaneDistance( cullInfo.localClipPlanes[i] ) >= LIGHT_CLIP_EPSILON )
{
frontBits |= 1 << i;
}
}
// if the surface is completely inside the light frustum
if( frontBits == ( ( 1 << 6 ) - 1 ) )
{
cullInfo.cullBits = LIGHT_CULL_ALL_FRONT;
return;
}
cullInfo.cullBits = ( byte* ) R_StaticAlloc( tri->numVerts * sizeof( cullInfo.cullBits[0] ), TAG_RENDER_INTERACTION );
memset( cullInfo.cullBits, 0, tri->numVerts * sizeof( cullInfo.cullBits[0] ) );
for( int i = 0; i < 6; i++ )
{
// if completely infront of this clipping plane
if( frontBits & ( 1 << i ) )
{
continue;
}
for( int j = 0; j < tri->numVerts; j++ )
{
float d = cullInfo.localClipPlanes[i].Distance( tri->verts[j].xyz );
cullInfo.cullBits[j] |= ( d < LIGHT_CLIP_EPSILON ) << i;
}
}
}
/*
================
R_FreeInteractionCullInfo
================
*/
void R_FreeInteractionCullInfo( srfCullInfo_t& cullInfo )
{
if( cullInfo.facing != NULL )
{
R_StaticFree( cullInfo.facing );
cullInfo.facing = NULL;
}
if( cullInfo.cullBits != NULL )
{
if( cullInfo.cullBits != LIGHT_CULL_ALL_FRONT )
{
R_StaticFree( cullInfo.cullBits );
}
cullInfo.cullBits = NULL;
}
}
/*
====================
R_CreateInteractionLightTris
This is only used for the static interaction case, dynamic interactions
just draw everything and let the GPU deal with it.
The resulting surface will be a subset of the original triangles,
it will never clip triangles, but it may cull on a per-triangle basis.
====================
*/
static srfTriangles_t* R_CreateInteractionLightTris( const idRenderEntityLocal* ent,
const srfTriangles_t* tri, const idRenderLightLocal* light,
const idMaterial* shader )
{
SCOPED_PROFILE_EVENT( "R_CreateInteractionLightTris" );
int i;
int numIndexes;
triIndex_t* indexes;
srfTriangles_t* newTri;
int c_backfaced;
int c_distance;
idBounds bounds;
bool includeBackFaces;
int faceNum;
c_backfaced = 0;
c_distance = 0;
numIndexes = 0;
indexes = NULL;
// it is debatable if non-shadowing lights should light back faces. we aren't at the moment
if( r_lightAllBackFaces.GetBool() || light->lightShader->LightEffectsBackSides()
|| shader->ReceivesLightingOnBackSides() || ent->parms.noSelfShadow || ent->parms.noShadow )
{
includeBackFaces = true;
}
else
{
includeBackFaces = false;
}
// allocate a new surface for the lit triangles
newTri = R_AllocStaticTriSurf();
// save a reference to the original surface
newTri->ambientSurface = const_cast( tri );
// the light surface references the verts of the ambient surface
newTri->numVerts = tri->numVerts;
R_ReferenceStaticTriSurfVerts( newTri, tri );
// calculate cull information
srfCullInfo_t cullInfo = {};
if( !includeBackFaces )
{
R_CalcInteractionFacing( ent, tri, light, cullInfo );
}
R_CalcInteractionCullBits( ent, tri, light, cullInfo );
// if the surface is completely inside the light frustum
if( cullInfo.cullBits == LIGHT_CULL_ALL_FRONT )
{
// if we aren't self shadowing, let back facing triangles get
// through so the smooth shaded bump maps light all the way around
if( includeBackFaces )
{
// the whole surface is lit so the light surface just references the indexes of the ambient surface
newTri->indexes = tri->indexes;
newTri->indexCache = tri->indexCache;
// R_ReferenceStaticTriSurfIndexes( newTri, tri );
numIndexes = tri->numIndexes;
bounds = tri->bounds;
}
else
{
// the light tris indexes are going to be a subset of the original indexes so we generally
// allocate too much memory here but we decrease the memory block when the number of indexes is known
R_AllocStaticTriSurfIndexes( newTri, tri->numIndexes );
// back face cull the individual triangles
indexes = newTri->indexes;
const byte* facing = cullInfo.facing;
for( faceNum = i = 0; i < tri->numIndexes; i += 3, faceNum++ )
{
if( !facing[ faceNum ] )
{
c_backfaced++;
continue;
}
indexes[numIndexes + 0] = tri->indexes[i + 0];
indexes[numIndexes + 1] = tri->indexes[i + 1];
indexes[numIndexes + 2] = tri->indexes[i + 2];
numIndexes += 3;
}
// get bounds for the surface
SIMDProcessor->MinMax( bounds[0], bounds[1], tri->verts, indexes, numIndexes );
// decrease the size of the memory block to the size of the number of used indexes
newTri->numIndexes = numIndexes;
R_ResizeStaticTriSurfIndexes( newTri, numIndexes );
}
}
else
{
// the light tris indexes are going to be a subset of the original indexes so we generally
// allocate too much memory here but we decrease the memory block when the number of indexes is known
R_AllocStaticTriSurfIndexes( newTri, tri->numIndexes );
// cull individual triangles
indexes = newTri->indexes;
const byte* facing = cullInfo.facing;
const byte* cullBits = cullInfo.cullBits;
for( faceNum = i = 0; i < tri->numIndexes; i += 3, faceNum++ )
{
int i1, i2, i3;
// if we aren't self shadowing, let back facing triangles get
// through so the smooth shaded bump maps light all the way around
if( !includeBackFaces )
{
// back face cull
if( !facing[ faceNum ] )
{
c_backfaced++;
continue;
}
}
i1 = tri->indexes[i + 0];
i2 = tri->indexes[i + 1];
i3 = tri->indexes[i + 2];
// fast cull outside the frustum
// if all three points are off one plane side, it definately isn't visible
if( cullBits[i1] & cullBits[i2] & cullBits[i3] )
{
c_distance++;
continue;
}
// add to the list
indexes[numIndexes + 0] = i1;
indexes[numIndexes + 1] = i2;
indexes[numIndexes + 2] = i3;
numIndexes += 3;
}
// get bounds for the surface
SIMDProcessor->MinMax( bounds[0], bounds[1], tri->verts, indexes, numIndexes );
// decrease the size of the memory block to the size of the number of used indexes
newTri->numIndexes = numIndexes;
R_ResizeStaticTriSurfIndexes( newTri, numIndexes );
}
// free the cull information when it's no longer needed
R_FreeInteractionCullInfo( cullInfo );
if( !numIndexes )
{
R_FreeStaticTriSurf( newTri );
return NULL;
}
newTri->numIndexes = numIndexes;
newTri->bounds = bounds;
return newTri;
}
/*
=====================
R_CreateInteractionShadowVolume
Note that dangling edges outside the light frustum don't make silhouette planes because
a triangle outside the light frustum is considered facing and the "fake triangle" on
the outside of the dangling edge is also set to facing: cullInfo.facing[numFaces] = 1;
=====================
*/
static srfTriangles_t* R_CreateInteractionShadowVolume( const idRenderEntityLocal* ent,
const srfTriangles_t* tri, const idRenderLightLocal* light )
{
SCOPED_PROFILE_EVENT( "R_CreateInteractionShadowVolume" );
srfCullInfo_t cullInfo = {};
R_CalcInteractionFacing( ent, tri, light, cullInfo );
R_CalcInteractionCullBits( ent, tri, light, cullInfo );
int numFaces = tri->numIndexes / 3;
int numShadowingFaces = 0;
const byte* facing = cullInfo.facing;
// if all the triangles are inside the light frustum
if( cullInfo.cullBits == LIGHT_CULL_ALL_FRONT )
{
// count the number of shadowing faces
for( int i = 0; i < numFaces; i++ )
{
numShadowingFaces += facing[i];
}
numShadowingFaces = numFaces - numShadowingFaces;
}
else
{
// make all triangles that are outside the light frustum "facing", so they won't cast shadows
const triIndex_t* indexes = tri->indexes;
byte* modifyFacing = cullInfo.facing;
const byte* cullBits = cullInfo.cullBits;
for( int i = 0, j = 0; i < tri->numIndexes; i += 3, j++ )
{
if( !modifyFacing[j] )
{
int i1 = indexes[i + 0];
int i2 = indexes[i + 1];
int i3 = indexes[i + 2];
if( cullBits[i1] & cullBits[i2] & cullBits[i3] )
{
modifyFacing[j] = 1;
}
else
{
numShadowingFaces++;
}
}
}
}
if( !numShadowingFaces )
{
// no faces are inside the light frustum and still facing the right way
R_FreeInteractionCullInfo( cullInfo );
return NULL;
}
// shadowVerts will be NULL on these surfaces, so the shadowVerts will be taken from the ambient surface
srfTriangles_t* newTri = R_AllocStaticTriSurf();
newTri->numVerts = tri->numVerts * 2;
// alloc the max possible size
R_AllocStaticTriSurfIndexes( newTri, ( numShadowingFaces + tri->numSilEdges ) * 6 );
triIndex_t* tempIndexes = newTri->indexes;
triIndex_t* shadowIndexes = newTri->indexes;
// create new triangles along sil planes
const silEdge_t* sil = tri->silEdges;
for( int i = tri->numSilEdges; i > 0; i--, sil++ )
{
int f1 = facing[sil->p1];
int f2 = facing[sil->p2];
if( !( f1 ^ f2 ) )
{
continue;
}
int v1 = sil->v1 << 1;
int v2 = sil->v2 << 1;
// set the two triangle winding orders based on facing
// without using a poorly-predictable branch
shadowIndexes[0] = v1;
shadowIndexes[1] = v2 ^ f1;
shadowIndexes[2] = v2 ^ f2;
shadowIndexes[3] = v1 ^ f2;
shadowIndexes[4] = v1 ^ f1;
shadowIndexes[5] = v2 ^ 1;
shadowIndexes += 6;
}
int numShadowIndexes = shadowIndexes - tempIndexes;
// we aren't bothering to separate front and back caps on these
newTri->numIndexes = newTri->numShadowIndexesNoFrontCaps = numShadowIndexes + numShadowingFaces * 6;
newTri->numShadowIndexesNoCaps = numShadowIndexes;
newTri->shadowCapPlaneBits = SHADOW_CAP_INFINITE;
// decrease the size of the memory block to only store the used indexes
// R_ResizeStaticTriSurfIndexes( newTri, newTri->numIndexes );
// these have no effect, because they extend to infinity
newTri->bounds.Clear();
// put some faces on the model and some on the distant projection
const triIndex_t* indexes = tri->indexes;
shadowIndexes = newTri->indexes + numShadowIndexes;
for( int i = 0, j = 0; i < tri->numIndexes; i += 3, j++ )
{
if( facing[j] )
{
continue;
}
int i0 = indexes[i + 0] << 1;
int i1 = indexes[i + 1] << 1;
int i2 = indexes[i + 2] << 1;
shadowIndexes[0] = i2;
shadowIndexes[1] = i1;
shadowIndexes[2] = i0;
shadowIndexes[3] = i0 ^ 1;
shadowIndexes[4] = i1 ^ 1;
shadowIndexes[5] = i2 ^ 1;
shadowIndexes += 6;
}
R_FreeInteractionCullInfo( cullInfo );
return newTri;
}
/*
===============
idInteraction::idInteraction
===============
*/
idInteraction::idInteraction()
{
numSurfaces = 0;
surfaces = NULL;
entityDef = NULL;
lightDef = NULL;
lightNext = NULL;
lightPrev = NULL;
entityNext = NULL;
entityPrev = NULL;
staticInteraction = false;
}
/*
===============
idInteraction::AllocAndLink
===============
*/
idInteraction* idInteraction::AllocAndLink( idRenderEntityLocal* edef, idRenderLightLocal* ldef )
{
if( edef == NULL || ldef == NULL )
{
common->Error( "idInteraction::AllocAndLink: NULL parm" );
return NULL;
}
idRenderWorldLocal* renderWorld = edef->world;
idInteraction* interaction = renderWorld->interactionAllocator.Alloc();
// link and initialize
interaction->lightDef = ldef;
interaction->entityDef = edef;
interaction->numSurfaces = -1; // not checked yet
interaction->surfaces = NULL;
// link at the start of the entity's list
interaction->lightNext = ldef->firstInteraction;
interaction->lightPrev = NULL;
ldef->firstInteraction = interaction;
if( interaction->lightNext != NULL )
{
interaction->lightNext->lightPrev = interaction;
}
else
{
ldef->lastInteraction = interaction;
}
// link at the start of the light's list
interaction->entityNext = edef->firstInteraction;
interaction->entityPrev = NULL;
edef->firstInteraction = interaction;
if( interaction->entityNext != NULL )
{
interaction->entityNext->entityPrev = interaction;
}
else
{
edef->lastInteraction = interaction;
}
// update the interaction table
if( renderWorld->interactionTable != NULL )
{
int index = ldef->index * renderWorld->interactionTableWidth + edef->index;
if( renderWorld->interactionTable[index] != NULL )
{
common->Error( "idInteraction::AllocAndLink: non NULL table entry" );
}
renderWorld->interactionTable[ index ] = interaction;
}
return interaction;
}
/*
===============
idInteraction::FreeSurfaces
Frees the surfaces, but leaves the interaction linked in, so it
will be regenerated automatically
===============
*/
void idInteraction::FreeSurfaces()
{
// anything regenerated is no longer an optimized static version
this->staticInteraction = false;
if( this->surfaces != NULL )
{
for( int i = 0; i < this->numSurfaces; i++ )
{
surfaceInteraction_t& srf = this->surfaces[i];
Mem_Free( srf.shadowIndexes );
srf.shadowIndexes = NULL;
}
R_StaticFree( this->surfaces );
this->surfaces = NULL;
}
this->numSurfaces = -1;
}
/*
===============
idInteraction::Unlink
===============
*/
void idInteraction::Unlink()
{
// unlink from the entity's list
if( this->entityPrev )
{
this->entityPrev->entityNext = this->entityNext;
}
else
{
this->entityDef->firstInteraction = this->entityNext;
}
if( this->entityNext )
{
this->entityNext->entityPrev = this->entityPrev;
}
else
{
this->entityDef->lastInteraction = this->entityPrev;
}
this->entityNext = this->entityPrev = NULL;
// unlink from the light's list
if( this->lightPrev )
{
this->lightPrev->lightNext = this->lightNext;
}
else
{
this->lightDef->firstInteraction = this->lightNext;
}
if( this->lightNext )
{
this->lightNext->lightPrev = this->lightPrev;
}
else
{
this->lightDef->lastInteraction = this->lightPrev;
}
this->lightNext = this->lightPrev = NULL;
}
/*
===============
idInteraction::UnlinkAndFree
Removes links and puts it back on the free list.
===============
*/
void idInteraction::UnlinkAndFree()
{
// clear the table pointer
idRenderWorldLocal* renderWorld = this->lightDef->world;
int index = this->lightDef->index * renderWorld->interactionTableWidth + this->entityDef->index;
if( renderWorld->interactionTable[index] != this && renderWorld->interactionTable[index] != INTERACTION_EMPTY )
{
common->Error( "idInteraction::UnlinkAndFree: interactionTable wasn't set" );
}
renderWorld->interactionTable[index] = NULL;
Unlink();
FreeSurfaces();
// put it back on the free list
renderWorld->interactionAllocator.Free( this );
}
/*
===============
idInteraction::MakeEmpty
Relinks the interaction at the end of both the light and entity chains
and adds the INTERACTION_EMPTY marker to the interactionTable.
It is necessary to keep the empty interaction so when entities or lights move
they can set all the interactionTable values to NULL.
===============
*/
void idInteraction::MakeEmpty()
{
// an empty interaction has no surfaces
numSurfaces = 0;
Unlink();
// relink at the end of the entity's list
this->entityNext = NULL;
this->entityPrev = this->entityDef->lastInteraction;
this->entityDef->lastInteraction = this;
if( this->entityPrev )
{
this->entityPrev->entityNext = this;
}
else
{
this->entityDef->firstInteraction = this;
}
// relink at the end of the light's list
this->lightNext = NULL;
this->lightPrev = this->lightDef->lastInteraction;
this->lightDef->lastInteraction = this;
if( this->lightPrev )
{
this->lightPrev->lightNext = this;
}
else
{
this->lightDef->firstInteraction = this;
}
// store the special marker in the interaction table
const int interactionIndex = lightDef->index * entityDef->world->interactionTableWidth + entityDef->index;
assert( entityDef->world->interactionTable[ interactionIndex ] == this );
entityDef->world->interactionTable[ interactionIndex ] = INTERACTION_EMPTY;
}
/*
===============
idInteraction::HasShadows
===============
*/
bool idInteraction::HasShadows() const
{
return !entityDef->parms.noShadow && lightDef->LightCastsShadows();
}
/*
======================
CreateStaticInteraction
Called by idRenderWorldLocal::GenerateAllInteractions
======================
*/
void idInteraction::CreateStaticInteraction()
{
// note that it is a static interaction
staticInteraction = true;
const idRenderModel* model = entityDef->parms.hModel;
if( model == NULL || model->NumSurfaces() <= 0 || model->IsDynamicModel() != DM_STATIC )
{
MakeEmpty();
return;
}
const idBounds bounds = model->Bounds( &entityDef->parms );
// if it doesn't contact the light frustum, none of the surfaces will
if( R_CullModelBoundsToLight( lightDef, bounds, entityDef->modelRenderMatrix ) )
{
MakeEmpty();
return;
}
//
// create slots for each of the model's surfaces
//
numSurfaces = model->NumSurfaces();
surfaces = ( surfaceInteraction_t* )R_ClearedStaticAlloc( sizeof( *surfaces ) * numSurfaces );
bool interactionGenerated = false;
// check each surface in the model
for( int c = 0 ; c < model->NumSurfaces() ; c++ )
{
const modelSurface_t* surf = model->Surface( c );
const srfTriangles_t* tri = surf->geometry;
if( tri == NULL )
{
continue;
}
// determine the shader for this surface, possibly by skinning
// Note that this will be wrong if customSkin/customShader are
// changed after map load time without invalidating the interaction!
const idMaterial* const shader = R_RemapShaderBySkin( surf->shader,
entityDef->parms.customSkin, entityDef->parms.customShader );
if( shader == NULL )
{
continue;
}
// try to cull each surface
if( R_CullModelBoundsToLight( lightDef, tri->bounds, entityDef->modelRenderMatrix ) )
{
continue;
}
surfaceInteraction_t* sint = &surfaces[c];
// generate a set of indexes for the lit surfaces, culling away triangles that are
// not at least partially inside the light
if( shader->ReceivesLighting() )
{
srfTriangles_t* lightTris = R_CreateInteractionLightTris( entityDef, tri, lightDef, shader );
if( lightTris != NULL )
{
// make a static index cache
sint->numLightTrisIndexes = lightTris->numIndexes;
sint->lightTrisIndexCache = vertexCache.AllocStaticIndex( lightTris->indexes, ALIGN( lightTris->numIndexes * sizeof( lightTris->indexes[0] ), INDEX_CACHE_ALIGN ) );
interactionGenerated = true;
R_FreeStaticTriSurf( lightTris );
}
}
// if the interaction has shadows and this surface casts a shadow
if( HasShadows() && shader->SurfaceCastsShadow() && tri->silEdges != NULL )
{
// if the light has an optimized shadow volume, don't create shadows for any models that are part of the base areas
if( lightDef->parms.prelightModel == NULL || !model->IsStaticWorldModel() || r_skipPrelightShadows.GetBool() )
{
srfTriangles_t* shadowTris = R_CreateInteractionShadowVolume( entityDef, tri, lightDef );
if( shadowTris != NULL )
{
// make a static index cache
sint->shadowIndexCache = vertexCache.AllocStaticIndex( shadowTris->indexes, ALIGN( shadowTris->numIndexes * sizeof( shadowTris->indexes[0] ), INDEX_CACHE_ALIGN ) );
sint->numShadowIndexes = shadowTris->numIndexes;
#if defined( KEEP_INTERACTION_CPU_DATA )
sint->shadowIndexes = shadowTris->indexes;
shadowTris->indexes = NULL;
#endif
if( shader->Coverage() != MC_OPAQUE )
{
// if any surface is a shadow-casting perforated or translucent surface, or the
// base surface is suppressed in the view (world weapon shadows) we can't use
// the external shadow optimizations because we can see through some of the faces
sint->numShadowIndexesNoCaps = shadowTris->numIndexes;
}
else
{
sint->numShadowIndexesNoCaps = shadowTris->numShadowIndexesNoCaps;
}
R_FreeStaticTriSurf( shadowTris );
}
interactionGenerated = true;
}
}
}
// if none of the surfaces generated anything, don't even bother checking?
if( !interactionGenerated )
{
MakeEmpty();
}
}
/*
===================
R_ShowInteractionMemory_f
===================
*/
void R_ShowInteractionMemory_f( const idCmdArgs& args )
{
int entities = 0;
int interactions = 0;
int deferredInteractions = 0;
int emptyInteractions = 0;
int lightTris = 0;
int lightTriIndexes = 0;
int shadowTris = 0;
int shadowTriIndexes = 0;
int maxInteractionsForEntity = 0;
int maxInteractionsForLight = 0;
for( int i = 0; i < tr.primaryWorld->lightDefs.Num(); i++ )
{
idRenderLightLocal* light = tr.primaryWorld->lightDefs[i];
if( light == NULL )
{
continue;
}
int numInteractionsForLight = 0;
for( idInteraction* inter = light->firstInteraction; inter != NULL; inter = inter->lightNext )
{
if( !inter->IsEmpty() )
{
numInteractionsForLight++;
}
}
if( numInteractionsForLight > maxInteractionsForLight )
{
maxInteractionsForLight = numInteractionsForLight;
}
}
for( int i = 0; i < tr.primaryWorld->entityDefs.Num(); i++ )
{
idRenderEntityLocal* def = tr.primaryWorld->entityDefs[i];
if( def == NULL )
{
continue;
}
if( def->firstInteraction == NULL )
{
continue;
}
entities++;
int numInteractionsForEntity = 0;
for( idInteraction* inter = def->firstInteraction; inter != NULL; inter = inter->entityNext )
{
interactions++;
if( !inter->IsEmpty() )
{
numInteractionsForEntity++;
}
if( inter->IsDeferred() )
{
deferredInteractions++;
continue;
}
if( inter->IsEmpty() )
{
emptyInteractions++;
continue;
}
for( int j = 0; j < inter->numSurfaces; j++ )
{
surfaceInteraction_t* srf = &inter->surfaces[j];
if( srf->numLightTrisIndexes )
{
lightTris++;
lightTriIndexes += srf->numLightTrisIndexes;
}
if( srf->numShadowIndexes )
{
shadowTris++;
shadowTriIndexes += srf->numShadowIndexes;
}
}
}
if( numInteractionsForEntity > maxInteractionsForEntity )
{
maxInteractionsForEntity = numInteractionsForEntity;
}
}
common->Printf( "%i entities with %i total interactions\n", entities, interactions );
common->Printf( "%i deferred interactions, %i empty interactions\n", deferredInteractions, emptyInteractions );
common->Printf( "%5i indexes in %5i light tris\n", lightTriIndexes, lightTris );
common->Printf( "%5i indexes in %5i shadow tris\n", shadowTriIndexes, shadowTris );
common->Printf( "%i maxInteractionsForEntity\n", maxInteractionsForEntity );
common->Printf( "%i maxInteractionsForLight\n", maxInteractionsForLight );
}