]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/renderer/Interaction.cpp
Use the same OpenAL headers on all platforms.
[icculus/iodoom3.git] / neo / renderer / Interaction.cpp
1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 
6
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  
8
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the Doom 3 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 Source Code.  If not, please request a copy in writing from id Software at the address below.
23
24 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.
25
26 ===========================================================================
27 */
28
29 #include "../idlib/precompiled.h"
30 #pragma hdrstop
31
32 #include "tr_local.h"
33
34 /*
35 ===========================================================================
36
37 idInteraction implementation
38
39 ===========================================================================
40 */
41
42 // FIXME: use private allocator for srfCullInfo_t
43
44 /*
45 ================
46 R_CalcInteractionFacing
47
48 Determines which triangles of the surface are facing towards the light origin.
49
50 The facing array should be allocated with one extra index than
51 the number of surface triangles, which will be used to handle dangling
52 edge silhouettes.
53 ================
54 */
55 void R_CalcInteractionFacing( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, srfCullInfo_t &cullInfo ) {
56         idVec3 localLightOrigin;
57
58         if ( cullInfo.facing != NULL ) {
59                 return;
60         }
61
62         R_GlobalPointToLocal( ent->modelMatrix, light->globalLightOrigin, localLightOrigin );
63
64         int numFaces = tri->numIndexes / 3;
65
66         if ( !tri->facePlanes || !tri->facePlanesCalculated ) {
67                 R_DeriveFacePlanes( const_cast<srfTriangles_t *>(tri) );
68         }
69
70         cullInfo.facing = (byte *) R_StaticAlloc( ( numFaces + 1 ) * sizeof( cullInfo.facing[0] ) );
71
72         // calculate back face culling
73         float *planeSide = (float *) _alloca16( numFaces * sizeof( float ) );
74
75         // exact geometric cull against face
76         SIMDProcessor->Dot( planeSide, localLightOrigin, tri->facePlanes, numFaces );
77         SIMDProcessor->CmpGE( cullInfo.facing, planeSide, 0.0f, numFaces );
78
79         cullInfo.facing[ numFaces ] = 1;        // for dangling edges to reference
80 }
81
82 /*
83 =====================
84 R_CalcInteractionCullBits
85
86 We want to cull a little on the sloppy side, because the pre-clipping
87 of geometry to the lights in dmap will give many cases that are right
88 at the border we throw things out on the border, because if any one
89 vertex is clearly inside, the entire triangle will be accepted.
90 =====================
91 */
92 void R_CalcInteractionCullBits( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, srfCullInfo_t &cullInfo ) {
93         int i, frontBits;
94
95         if ( cullInfo.cullBits != NULL ) {
96                 return;
97         }
98
99         frontBits = 0;
100
101         // cull the triangle surface bounding box
102         for ( i = 0; i < 6; i++ ) {
103
104                 R_GlobalPlaneToLocal( ent->modelMatrix, -light->frustum[i], cullInfo.localClipPlanes[i] );
105
106                 // get front bits for the whole surface
107                 if ( tri->bounds.PlaneDistance( cullInfo.localClipPlanes[i] ) >= LIGHT_CLIP_EPSILON ) {
108                         frontBits |= 1<<i;
109                 }
110         }
111
112         // if the surface is completely inside the light frustum
113         if ( frontBits == ( ( 1 << 6 ) - 1 ) ) {
114                 cullInfo.cullBits = LIGHT_CULL_ALL_FRONT;
115                 return;
116         }
117
118         cullInfo.cullBits = (byte *) R_StaticAlloc( tri->numVerts * sizeof( cullInfo.cullBits[0] ) );
119         SIMDProcessor->Memset( cullInfo.cullBits, 0, tri->numVerts * sizeof( cullInfo.cullBits[0] ) );
120
121         float *planeSide = (float *) _alloca16( tri->numVerts * sizeof( float ) );
122
123         for ( i = 0; i < 6; i++ ) {
124                 // if completely infront of this clipping plane
125                 if ( frontBits & ( 1 << i ) ) {
126                         continue;
127                 }
128                 SIMDProcessor->Dot( planeSide, cullInfo.localClipPlanes[i], tri->verts, tri->numVerts );
129                 SIMDProcessor->CmpLT( cullInfo.cullBits, i, planeSide, LIGHT_CLIP_EPSILON, tri->numVerts );
130         }
131 }
132
133 /*
134 ================
135 R_FreeInteractionCullInfo
136 ================
137 */
138 void R_FreeInteractionCullInfo( srfCullInfo_t &cullInfo ) {
139         if ( cullInfo.facing != NULL ) {
140                 R_StaticFree( cullInfo.facing );
141                 cullInfo.facing = NULL;
142         }
143         if ( cullInfo.cullBits != NULL ) {
144                 if ( cullInfo.cullBits != LIGHT_CULL_ALL_FRONT ) {
145                         R_StaticFree( cullInfo.cullBits );
146                 }
147                 cullInfo.cullBits = NULL;
148         }
149 }
150
151 #define MAX_CLIPPED_POINTS      20
152 typedef struct {
153         int             numVerts;
154         idVec3  verts[MAX_CLIPPED_POINTS];
155 } clipTri_t;
156
157 /*
158 =============
159 R_ChopWinding
160
161 Clips a triangle from one buffer to another, setting edge flags
162 The returned buffer may be the same as inNum if no clipping is done
163 If entirely clipped away, clipTris[returned].numVerts == 0
164
165 I have some worries about edge flag cases when polygons are clipped
166 multiple times near the epsilon.
167 =============
168 */
169 static int R_ChopWinding( clipTri_t clipTris[2], int inNum, const idPlane plane ) {
170         clipTri_t       *in, *out;
171         float   dists[MAX_CLIPPED_POINTS];
172         int             sides[MAX_CLIPPED_POINTS];
173         int             counts[3];
174         float   dot;
175         int             i, j;
176         idVec3  mid;
177         bool    front;
178
179         in = &clipTris[inNum];
180         out = &clipTris[inNum^1];
181         counts[0] = counts[1] = counts[2] = 0;
182
183         // determine sides for each point
184         front = false;
185         for ( i = 0; i < in->numVerts; i++ ) {
186                 dot = in->verts[i] * plane.Normal() + plane[3];
187                 dists[i] = dot;
188                 if ( dot < LIGHT_CLIP_EPSILON ) {       // slop onto the back
189                         sides[i] = SIDE_BACK;
190                 } else {
191                         sides[i] = SIDE_FRONT;
192                         if ( dot > LIGHT_CLIP_EPSILON ) {
193                                 front = true;
194                         }
195                 }
196                 counts[sides[i]]++;
197         }
198
199         // if none in front, it is completely clipped away
200         if ( !front ) {
201                 in->numVerts = 0;
202                 return inNum;
203         }
204         if ( !counts[SIDE_BACK] ) {
205                 return inNum;           // inout stays the same
206         }
207
208         // avoid wrapping checks by duplicating first value to end
209         sides[i] = sides[0];
210         dists[i] = dists[0];
211         in->verts[in->numVerts] = in->verts[0];
212
213         out->numVerts = 0;
214         for ( i = 0 ; i < in->numVerts ; i++ ) {
215                 idVec3 &p1 = in->verts[i];
216                 
217                 if ( sides[i] == SIDE_FRONT ) {
218                         out->verts[out->numVerts] = p1;
219                         out->numVerts++;
220                 }
221
222                 if ( sides[i+1] == sides[i] ) {
223                         continue;
224                 }
225                         
226                 // generate a split point
227                 idVec3 &p2 = in->verts[i+1];
228                 
229                 dot = dists[i] / ( dists[i] - dists[i+1] );
230                 for ( j = 0; j < 3; j++ ) {
231                         mid[j] = p1[j] + dot * ( p2[j] - p1[j] );
232                 }
233                         
234                 out->verts[out->numVerts] = mid;
235
236                 out->numVerts++;
237         }
238
239         return inNum ^ 1;
240 }
241
242 /*
243 ===================
244 R_ClipTriangleToLight
245
246 Returns false if nothing is left after clipping
247 ===================
248 */
249 static bool     R_ClipTriangleToLight( const idVec3 &a, const idVec3 &b, const idVec3 &c, int planeBits, const idPlane frustum[6] ) {
250         int                     i;
251         clipTri_t       pingPong[2];
252         int                     p;
253
254         pingPong[0].numVerts = 3;
255         pingPong[0].verts[0] = a;
256         pingPong[0].verts[1] = b;
257         pingPong[0].verts[2] = c;
258
259         p = 0;
260         for ( i = 0 ; i < 6 ; i++ ) {
261                 if ( planeBits & ( 1 << i ) ) {
262                         p = R_ChopWinding( pingPong, p, frustum[i] );
263                         if ( pingPong[p].numVerts < 1 ) {
264                                 return false;
265                         }
266                 }
267         }
268
269         return true;
270 }
271
272 /*
273 ====================
274 R_CreateLightTris
275
276 The resulting surface will be a subset of the original triangles,
277 it will never clip triangles, but it may cull on a per-triangle basis.
278 ====================
279 */
280 static srfTriangles_t *R_CreateLightTris( const idRenderEntityLocal *ent, 
281                                                                          const srfTriangles_t *tri, const idRenderLightLocal *light,
282                                                                          const idMaterial *shader, srfCullInfo_t &cullInfo ) {
283         int                     i;
284         int                     numIndexes;
285         glIndex_t       *indexes;
286         srfTriangles_t  *newTri;
287         int                     c_backfaced;
288         int                     c_distance;
289         idBounds        bounds;
290         bool            includeBackFaces;
291         int                     faceNum;
292
293         tr.pc.c_createLightTris++;
294         c_backfaced = 0;
295         c_distance = 0;
296
297         numIndexes = 0;
298         indexes = NULL;
299
300         // it is debatable if non-shadowing lights should light back faces. we aren't at the moment
301         if ( r_lightAllBackFaces.GetBool() || light->lightShader->LightEffectsBackSides()
302                         || shader->ReceivesLightingOnBackSides()
303                                 || ent->parms.noSelfShadow || ent->parms.noShadow  ) {
304                 includeBackFaces = true;
305         } else {
306                 includeBackFaces = false;
307         }
308
309         // allocate a new surface for the lit triangles
310         newTri = R_AllocStaticTriSurf();
311
312         // save a reference to the original surface
313         newTri->ambientSurface = const_cast<srfTriangles_t *>(tri);
314
315         // the light surface references the verts of the ambient surface
316         newTri->numVerts = tri->numVerts;
317         R_ReferenceStaticTriSurfVerts( newTri, tri );
318
319         // calculate cull information
320         if ( !includeBackFaces ) {
321                 R_CalcInteractionFacing( ent, tri, light, cullInfo );
322         }
323         R_CalcInteractionCullBits( ent, tri, light, cullInfo );
324
325         // if the surface is completely inside the light frustum
326         if ( cullInfo.cullBits == LIGHT_CULL_ALL_FRONT ) {
327
328                 // if we aren't self shadowing, let back facing triangles get
329                 // through so the smooth shaded bump maps light all the way around
330                 if ( includeBackFaces ) {
331
332                         // the whole surface is lit so the light surface just references the indexes of the ambient surface
333                         R_ReferenceStaticTriSurfIndexes( newTri, tri );
334                         numIndexes = tri->numIndexes;
335                         bounds = tri->bounds;
336
337                 } else {
338
339                         // the light tris indexes are going to be a subset of the original indexes so we generally
340                         // allocate too much memory here but we decrease the memory block when the number of indexes is known
341                         R_AllocStaticTriSurfIndexes( newTri, tri->numIndexes );
342
343                         // back face cull the individual triangles
344                         indexes = newTri->indexes;
345                         const byte *facing = cullInfo.facing;
346                         for ( faceNum = i = 0; i < tri->numIndexes; i += 3, faceNum++ ) {
347                                 if ( !facing[ faceNum ] ) {
348                                         c_backfaced++;
349                                         continue;
350                                 }
351                                 indexes[numIndexes+0] = tri->indexes[i+0];
352                                 indexes[numIndexes+1] = tri->indexes[i+1];
353                                 indexes[numIndexes+2] = tri->indexes[i+2];
354                                 numIndexes += 3;
355                         }
356
357                         // get bounds for the surface
358                         SIMDProcessor->MinMax( bounds[0], bounds[1], tri->verts, indexes, numIndexes );
359
360                         // decrease the size of the memory block to the size of the number of used indexes
361                         R_ResizeStaticTriSurfIndexes( newTri, numIndexes );
362                 }
363
364         } else {
365
366                 // the light tris indexes are going to be a subset of the original indexes so we generally
367                 // allocate too much memory here but we decrease the memory block when the number of indexes is known
368                 R_AllocStaticTriSurfIndexes( newTri, tri->numIndexes );
369
370                 // cull individual triangles
371                 indexes = newTri->indexes;
372                 const byte *facing = cullInfo.facing;
373                 const byte *cullBits = cullInfo.cullBits;
374                 for ( faceNum = i = 0; i < tri->numIndexes; i += 3, faceNum++ ) {
375                         int i1, i2, i3;
376
377                         // if we aren't self shadowing, let back facing triangles get
378                         // through so the smooth shaded bump maps light all the way around
379                         if ( !includeBackFaces ) {
380                                 // back face cull
381                                 if ( !facing[ faceNum ] ) {
382                                         c_backfaced++;
383                                         continue;
384                                 }
385                         }
386
387                         i1 = tri->indexes[i+0];
388                         i2 = tri->indexes[i+1];
389                         i3 = tri->indexes[i+2];
390
391                         // fast cull outside the frustum
392                         // if all three points are off one plane side, it definately isn't visible
393                         if ( cullBits[i1] & cullBits[i2] & cullBits[i3] ) {
394                                 c_distance++;
395                                 continue;
396                         }
397
398                         if ( r_usePreciseTriangleInteractions.GetBool() ) {
399                                 // do a precise clipped cull if none of the points is completely inside the frustum
400                                 // note that we do not actually use the clipped triangle, which would have Z fighting issues.
401                                 if ( cullBits[i1] && cullBits[i2] && cullBits[i3] ) {
402                                         int cull = cullBits[i1] | cullBits[i2] | cullBits[i3];
403                                         if ( !R_ClipTriangleToLight( tri->verts[i1].xyz, tri->verts[i2].xyz, tri->verts[i3].xyz, cull, cullInfo.localClipPlanes ) ) {
404                                                 continue;
405                                         }
406                                 }
407                         }
408
409                         // add to the list
410                         indexes[numIndexes+0] = i1;
411                         indexes[numIndexes+1] = i2;
412                         indexes[numIndexes+2] = i3;
413                         numIndexes += 3;
414                 }
415
416                 // get bounds for the surface
417                 SIMDProcessor->MinMax( bounds[0], bounds[1], tri->verts, indexes, numIndexes );
418
419                 // decrease the size of the memory block to the size of the number of used indexes
420                 R_ResizeStaticTriSurfIndexes( newTri, numIndexes );
421         }
422
423         if ( !numIndexes ) {
424                 R_ReallyFreeStaticTriSurf( newTri );
425                 return NULL;
426         }
427
428         newTri->numIndexes = numIndexes;
429
430         newTri->bounds = bounds;
431
432         return newTri;
433 }
434
435 /*
436 ===============
437 idInteraction::idInteraction
438 ===============
439 */
440 idInteraction::idInteraction( void ) {
441         numSurfaces                             = 0;
442         surfaces                                = NULL;
443         entityDef                               = NULL;
444         lightDef                                = NULL;
445         lightNext                               = NULL;
446         lightPrev                               = NULL;
447         entityNext                              = NULL;
448         entityPrev                              = NULL;
449         dynamicModelFrameCount  = 0;
450         frustumState                    = FRUSTUM_UNINITIALIZED;
451         frustumAreas                    = NULL;
452 }
453
454 /*
455 ===============
456 idInteraction::AllocAndLink
457 ===============
458 */
459 idInteraction *idInteraction::AllocAndLink( idRenderEntityLocal *edef, idRenderLightLocal *ldef ) {
460         if ( !edef || !ldef ) {
461                 common->Error( "idInteraction::AllocAndLink: NULL parm" );
462         }
463
464         idRenderWorldLocal *renderWorld = edef->world;
465
466         idInteraction *interaction = renderWorld->interactionAllocator.Alloc();
467
468         // link and initialize
469         interaction->dynamicModelFrameCount = 0;
470
471         interaction->lightDef = ldef;
472         interaction->entityDef = edef;
473
474         interaction->numSurfaces = -1;          // not checked yet
475         interaction->surfaces = NULL;
476
477         interaction->frustumState = idInteraction::FRUSTUM_UNINITIALIZED;
478         interaction->frustumAreas = NULL;
479
480         // link at the start of the entity's list
481         interaction->lightNext = ldef->firstInteraction;
482         interaction->lightPrev = NULL;
483         ldef->firstInteraction = interaction;
484         if ( interaction->lightNext != NULL ) {
485                 interaction->lightNext->lightPrev = interaction;
486         } else {
487                 ldef->lastInteraction = interaction;
488         }
489
490         // link at the start of the light's list
491         interaction->entityNext = edef->firstInteraction;
492         interaction->entityPrev = NULL;
493         edef->firstInteraction = interaction;
494         if ( interaction->entityNext != NULL ) {
495                 interaction->entityNext->entityPrev = interaction;
496         } else {
497                 edef->lastInteraction = interaction;
498         }
499
500         // update the interaction table
501         if ( renderWorld->interactionTable ) {
502                 int index = ldef->index * renderWorld->interactionTableWidth + edef->index;
503                 if ( renderWorld->interactionTable[index] != NULL ) {
504                         common->Error( "idInteraction::AllocAndLink: non NULL table entry" );
505                 }
506                 renderWorld->interactionTable[ index ] = interaction;
507         }
508
509         return interaction;
510 }
511
512 /*
513 ===============
514 idInteraction::FreeSurfaces
515
516 Frees the surfaces, but leaves the interaction linked in, so it
517 will be regenerated automatically
518 ===============
519 */
520 void idInteraction::FreeSurfaces( void ) {
521         if ( this->surfaces ) {
522                 for ( int i = 0 ; i < this->numSurfaces ; i++ ) {
523                         surfaceInteraction_t *sint = &this->surfaces[i];
524
525                         if ( sint->lightTris ) {
526                                 if ( sint->lightTris != LIGHT_TRIS_DEFERRED ) {
527                                         R_FreeStaticTriSurf( sint->lightTris );
528                                 }
529                                 sint->lightTris = NULL;
530                         }
531                         if ( sint->shadowTris ) {
532                                 // if it doesn't have an entityDef, it is part of a prelight
533                                 // model, not a generated interaction
534                                 if ( this->entityDef ) {
535                                         R_FreeStaticTriSurf( sint->shadowTris );
536                                         sint->shadowTris = NULL;
537                                 }
538                         }
539                         R_FreeInteractionCullInfo( sint->cullInfo );
540                 }
541
542                 R_StaticFree( this->surfaces );
543                 this->surfaces = NULL;
544         }
545         this->numSurfaces = -1;
546 }
547
548 /*
549 ===============
550 idInteraction::Unlink
551 ===============
552 */
553 void idInteraction::Unlink( void ) {
554
555         // unlink from the entity's list
556         if ( this->entityPrev ) {
557                 this->entityPrev->entityNext = this->entityNext;
558         } else {
559                 this->entityDef->firstInteraction = this->entityNext;
560         }
561         if ( this->entityNext ) {
562                 this->entityNext->entityPrev = this->entityPrev;
563         } else {
564                 this->entityDef->lastInteraction = this->entityPrev;
565         }
566         this->entityNext = this->entityPrev = NULL;
567
568         // unlink from the light's list
569         if ( this->lightPrev ) {
570                 this->lightPrev->lightNext = this->lightNext;
571         } else {
572                 this->lightDef->firstInteraction = this->lightNext;
573         }
574         if ( this->lightNext ) {
575                 this->lightNext->lightPrev = this->lightPrev;
576         } else {
577                 this->lightDef->lastInteraction = this->lightPrev;
578         }
579         this->lightNext = this->lightPrev = NULL;
580 }
581
582 /*
583 ===============
584 idInteraction::UnlinkAndFree
585
586 Removes links and puts it back on the free list.
587 ===============
588 */
589 void idInteraction::UnlinkAndFree( void ) {
590
591         // clear the table pointer
592         idRenderWorldLocal *renderWorld = this->lightDef->world;
593         if ( renderWorld->interactionTable ) {
594                 int index = this->lightDef->index * renderWorld->interactionTableWidth + this->entityDef->index;
595                 if ( renderWorld->interactionTable[index] != this ) {
596                         common->Error( "idInteraction::UnlinkAndFree: interactionTable wasn't set" );
597                 }
598                 renderWorld->interactionTable[index] = NULL;
599         }
600
601         Unlink();
602
603         FreeSurfaces();
604
605         // free the interaction area references
606         areaNumRef_t *area, *nextArea;
607         for ( area = frustumAreas; area; area = nextArea ) {
608                 nextArea = area->next;
609                 renderWorld->areaNumRefAllocator.Free( area );
610         }
611
612         // put it back on the free list
613         renderWorld->interactionAllocator.Free( this );
614 }
615
616 /*
617 ===============
618 idInteraction::MakeEmpty
619
620 Makes the interaction empty and links it at the end of the entity's and light's interaction lists.
621 ===============
622 */
623 void idInteraction::MakeEmpty( void ) {
624
625         // an empty interaction has no surfaces
626         numSurfaces = 0;
627
628         Unlink();
629
630         // relink at the end of the entity's list
631         this->entityNext = NULL;
632         this->entityPrev = this->entityDef->lastInteraction;
633         this->entityDef->lastInteraction = this;
634         if ( this->entityPrev ) {
635                 this->entityPrev->entityNext = this;
636         } else {
637                 this->entityDef->firstInteraction = this;
638         }
639
640         // relink at the end of the light's list
641         this->lightNext = NULL;
642         this->lightPrev = this->lightDef->lastInteraction;
643         this->lightDef->lastInteraction = this;
644         if ( this->lightPrev ) {
645                 this->lightPrev->lightNext = this;
646         } else {
647                 this->lightDef->firstInteraction = this;
648         }
649 }
650
651 /*
652 ===============
653 idInteraction::HasShadows
654 ===============
655 */
656 ID_INLINE bool idInteraction::HasShadows( void ) const {
657         return ( !lightDef->parms.noShadows && !entityDef->parms.noShadow && lightDef->lightShader->LightCastsShadows() );
658 }
659
660 /*
661 ===============
662 idInteraction::MemoryUsed
663
664 Counts up the memory used by all the surfaceInteractions, which
665 will be used to determine when we need to start purging old interactions.
666 ===============
667 */
668 int idInteraction::MemoryUsed( void ) {
669         int             total = 0;
670
671         for ( int i = 0 ; i < numSurfaces ; i++ ) {
672                 surfaceInteraction_t *inter = &surfaces[i];
673
674                 total += R_TriSurfMemory( inter->lightTris );
675                 total += R_TriSurfMemory( inter->shadowTris );
676         }
677
678         return total;
679 }
680
681 /*
682 ==================
683 idInteraction::CalcInteractionScissorRectangle
684 ==================
685 */
686 idScreenRect idInteraction::CalcInteractionScissorRectangle( const idFrustum &viewFrustum ) {
687         idBounds                projectionBounds;
688         idScreenRect    portalRect;
689         idScreenRect    scissorRect;
690
691         if ( r_useInteractionScissors.GetInteger() == 0 ) {
692                 return lightDef->viewLight->scissorRect;
693         }
694
695         if ( r_useInteractionScissors.GetInteger() < 0 ) {
696                 // this is the code from Cass at nvidia, it is more precise, but slower
697                 return R_CalcIntersectionScissor( lightDef, entityDef, tr.viewDef );
698         }
699
700         // the following is Mr.E's code
701
702         // frustum must be initialized and valid
703         if ( frustumState == idInteraction::FRUSTUM_UNINITIALIZED || frustumState == idInteraction::FRUSTUM_INVALID ) {
704                 return lightDef->viewLight->scissorRect;
705         }
706
707         // calculate scissors for the portals through which the interaction is visible
708         if ( r_useInteractionScissors.GetInteger() > 1 ) {
709                 areaNumRef_t *area;
710
711                 if ( frustumState == idInteraction::FRUSTUM_VALID ) {
712                         // retrieve all the areas the interaction frustum touches
713                         for ( areaReference_t *ref = entityDef->entityRefs; ref; ref = ref->ownerNext ) {
714                                 area = entityDef->world->areaNumRefAllocator.Alloc();
715                                 area->areaNum = ref->area->areaNum;
716                                 area->next = frustumAreas;
717                                 frustumAreas = area;
718                         }
719                         frustumAreas = tr.viewDef->renderWorld->FloodFrustumAreas( frustum, frustumAreas );
720                         frustumState = idInteraction::FRUSTUM_VALIDAREAS;
721                 }
722
723                 portalRect.Clear();
724                 for ( area = frustumAreas; area; area = area->next ) {
725                         portalRect.Union( entityDef->world->GetAreaScreenRect( area->areaNum ) );
726                 }
727                 portalRect.Intersect( lightDef->viewLight->scissorRect );
728         } else {
729                 portalRect = lightDef->viewLight->scissorRect;
730         }
731
732         // early out if the interaction is not visible through any portals
733         if ( portalRect.IsEmpty() ) {
734                 return portalRect;
735         }
736
737         // calculate bounds of the interaction frustum projected into the view frustum
738         if ( lightDef->parms.pointLight ) {
739                 viewFrustum.ClippedProjectionBounds( frustum, idBox( lightDef->parms.origin, lightDef->parms.lightRadius, lightDef->parms.axis ), projectionBounds );
740         } else {
741                 viewFrustum.ClippedProjectionBounds( frustum, idBox( lightDef->frustumTris->bounds ), projectionBounds );
742         }
743
744         if ( projectionBounds.IsCleared() ) {
745                 return portalRect;
746         }
747
748         // derive a scissor rectangle from the projection bounds
749         scissorRect = R_ScreenRectFromViewFrustumBounds( projectionBounds );
750
751         // intersect with the portal crossing scissor rectangle
752         scissorRect.Intersect( portalRect );
753
754         if ( r_showInteractionScissors.GetInteger() > 0 ) {
755                 R_ShowColoredScreenRect( scissorRect, lightDef->index );
756         }
757
758         return scissorRect;
759 }
760
761 /*
762 ===================
763 idInteraction::CullInteractionByViewFrustum
764 ===================
765 */
766 bool idInteraction::CullInteractionByViewFrustum( const idFrustum &viewFrustum ) {
767
768         if ( !r_useInteractionCulling.GetBool() ) {
769                 return false;
770         }
771
772         if ( frustumState == idInteraction::FRUSTUM_INVALID ) {
773                 return false;
774         }
775
776         if ( frustumState == idInteraction::FRUSTUM_UNINITIALIZED ) {
777
778                 frustum.FromProjection( idBox( entityDef->referenceBounds, entityDef->parms.origin, entityDef->parms.axis ), lightDef->globalLightOrigin, MAX_WORLD_SIZE );
779
780                 if ( !frustum.IsValid() ) {
781                         frustumState = idInteraction::FRUSTUM_INVALID;
782                         return false;
783                 }
784
785                 if ( lightDef->parms.pointLight ) {
786                         frustum.ConstrainToBox( idBox( lightDef->parms.origin, lightDef->parms.lightRadius, lightDef->parms.axis ) );
787                 } else {
788                         frustum.ConstrainToBox( idBox( lightDef->frustumTris->bounds ) );
789                 }
790
791                 frustumState = idInteraction::FRUSTUM_VALID;
792         }
793
794         if ( !viewFrustum.IntersectsFrustum( frustum ) ) {
795                 return true;
796         }
797
798         if ( r_showInteractionFrustums.GetInteger() ) {
799                 static idVec4 colors[] = { colorRed, colorGreen, colorBlue, colorYellow, colorMagenta, colorCyan, colorWhite, colorPurple };
800                 tr.viewDef->renderWorld->DebugFrustum( colors[lightDef->index & 7], frustum, ( r_showInteractionFrustums.GetInteger() > 1 ) );
801                 if ( r_showInteractionFrustums.GetInteger() > 2 ) {
802                         tr.viewDef->renderWorld->DebugBox( colorWhite, idBox( entityDef->referenceBounds, entityDef->parms.origin, entityDef->parms.axis ) );
803                 }
804         }
805
806         return false;
807 }
808
809 /*
810 ====================
811 idInteraction::CreateInteraction
812
813 Called when a entityDef and a lightDef are both present in a
814 portalArea, and might be visible.  Performs cull checking before doing the expensive
815 computations.
816
817 References tr.viewCount so lighting surfaces will only be created if the ambient surface is visible,
818 otherwise it will be marked as deferred.
819
820 The results of this are cached and valid until the light or entity change.
821 ====================
822 */
823 void idInteraction::CreateInteraction( const idRenderModel *model ) {
824         const idMaterial *      lightShader = lightDef->lightShader;
825         const idMaterial*       shader;
826         bool                            interactionGenerated;
827         idBounds                        bounds;
828
829         tr.pc.c_createInteractions++;
830
831         bounds = model->Bounds( &entityDef->parms );
832
833         // if it doesn't contact the light frustum, none of the surfaces will
834         if ( R_CullLocalBox( bounds, entityDef->modelMatrix, 6, lightDef->frustum ) ) {
835                 MakeEmpty();
836                 return;
837         }
838
839         // use the turbo shadow path
840         shadowGen_t shadowGen = SG_DYNAMIC;
841
842         // really large models, like outside terrain meshes, should use
843         // the more exactly culled static shadow path instead of the turbo shadow path.
844         // FIXME: this is a HACK, we should probably have a material flag.
845         if ( bounds[1][0] - bounds[0][0] > 3000 ) {
846                 shadowGen = SG_STATIC;
847         }
848
849         //
850         // create slots for each of the model's surfaces
851         //
852         numSurfaces = model->NumSurfaces();
853         surfaces = (surfaceInteraction_t *)R_ClearedStaticAlloc( sizeof( *surfaces ) * numSurfaces );
854
855         interactionGenerated = false;
856
857         // check each surface in the model
858         for ( int c = 0 ; c < model->NumSurfaces() ; c++ ) {
859                 const modelSurface_t    *surf;
860                 srfTriangles_t  *tri;
861         
862                 surf = model->Surface( c );
863
864                 tri = surf->geometry;
865                 if ( !tri ) {
866                         continue;
867                 }
868
869                 // determine the shader for this surface, possibly by skinning
870                 shader = surf->shader;
871                 shader = R_RemapShaderBySkin( shader, entityDef->parms.customSkin, entityDef->parms.customShader );
872
873                 if ( !shader ) {
874                         continue;
875                 }
876
877                 // try to cull each surface
878                 if ( R_CullLocalBox( tri->bounds, entityDef->modelMatrix, 6, lightDef->frustum ) ) {
879                         continue;
880                 }
881
882                 surfaceInteraction_t *sint = &surfaces[c];
883
884                 sint->shader = shader;
885
886                 // save the ambient tri pointer so we can reject lightTri interactions
887                 // when the ambient surface isn't in view, and we can get shared vertex
888                 // and shadow data from the source surface
889                 sint->ambientTris = tri;
890
891                 // "invisible ink" lights and shaders
892                 if ( shader->Spectrum() != lightShader->Spectrum() ) {
893                         continue;
894                 }
895
896                 // generate a lighted surface and add it
897                 if ( shader->ReceivesLighting() ) {
898                         if ( tri->ambientViewCount == tr.viewCount ) {
899                                 sint->lightTris = R_CreateLightTris( entityDef, tri, lightDef, shader, sint->cullInfo );
900                         } else {
901                                 // this will be calculated when sint->ambientTris is actually in view
902                                 sint->lightTris = LIGHT_TRIS_DEFERRED;
903                         }
904                         interactionGenerated = true;
905                 }
906
907                 // if the interaction has shadows and this surface casts a shadow
908                 if ( HasShadows() && shader->SurfaceCastsShadow() && tri->silEdges != NULL ) {
909
910                         // if the light has an optimized shadow volume, don't create shadows for any models that are part of the base areas
911                         if ( lightDef->parms.prelightModel == NULL || !model->IsStaticWorldModel() || !r_useOptimizedShadows.GetBool() ) {
912
913                                 // this is the only place during gameplay (outside the utilities) that R_CreateShadowVolume() is called
914                                 sint->shadowTris = R_CreateShadowVolume( entityDef, tri, lightDef, shadowGen, sint->cullInfo );
915                                 if ( sint->shadowTris ) {
916                                         if ( shader->Coverage() != MC_OPAQUE || ( !r_skipSuppress.GetBool() && entityDef->parms.suppressSurfaceInViewID ) ) {
917                                                 // if any surface is a shadow-casting perforated or translucent surface, or the
918                                                 // base surface is suppressed in the view (world weapon shadows) we can't use
919                                                 // the external shadow optimizations because we can see through some of the faces
920                                                 sint->shadowTris->numShadowIndexesNoCaps = sint->shadowTris->numIndexes;
921                                                 sint->shadowTris->numShadowIndexesNoFrontCaps = sint->shadowTris->numIndexes;
922                                         }
923                                 }
924                                 interactionGenerated = true;
925                         }
926                 }
927
928                 // free the cull information when it's no longer needed
929                 if ( sint->lightTris != LIGHT_TRIS_DEFERRED ) {
930                         R_FreeInteractionCullInfo( sint->cullInfo );
931                 }
932         }
933
934         // if none of the surfaces generated anything, don't even bother checking?
935         if ( !interactionGenerated ) {
936                 MakeEmpty();
937         }
938 }
939
940 /*
941 ======================
942 R_PotentiallyInsideInfiniteShadow
943
944 If we know that we are "off to the side" of an infinite shadow volume,
945 we can draw it without caps in zpass mode
946 ======================
947 */
948 static bool R_PotentiallyInsideInfiniteShadow( const srfTriangles_t *occluder,
949                                                                                           const idVec3 &localView, const idVec3 &localLight ) {
950         idBounds        exp;
951
952         // expand the bounds to account for the near clip plane, because the
953         // view could be mathematically outside, but if the near clip plane
954         // chops a volume edge, the zpass rendering would fail.
955         float   znear = r_znear.GetFloat();
956         if ( tr.viewDef->renderView.cramZNear ) {
957                 znear *= 0.25f;
958         }
959         float   stretch = znear * 2;    // in theory, should vary with FOV
960         exp[0][0] = occluder->bounds[0][0] - stretch;
961         exp[0][1] = occluder->bounds[0][1] - stretch;
962         exp[0][2] = occluder->bounds[0][2] - stretch;
963         exp[1][0] = occluder->bounds[1][0] + stretch;
964         exp[1][1] = occluder->bounds[1][1] + stretch;
965         exp[1][2] = occluder->bounds[1][2] + stretch;
966
967         if ( exp.ContainsPoint( localView ) ) {
968                 return true;
969         }
970         if ( exp.ContainsPoint( localLight ) ) {
971                 return true;
972         }
973
974         // if the ray from localLight to localView intersects a face of the
975         // expanded bounds, we will be inside the projection
976
977         idVec3  ray = localView - localLight;
978
979         // intersect the ray from the view to the light with the near side of the bounds
980         for ( int axis = 0; axis < 3; axis++ ) {
981                 float   d, frac;
982                 idVec3  hit;
983
984                 if ( localLight[axis] < exp[0][axis] ) {
985                         if ( localView[axis] < exp[0][axis] ) {
986                                 continue;
987                         }
988                         d = exp[0][axis] - localLight[axis];
989                         frac = d / ray[axis];
990                         hit = localLight + frac * ray;
991                         hit[axis] = exp[0][axis];
992                 } else if ( localLight[axis] > exp[1][axis] ) {
993                         if ( localView[axis] > exp[1][axis] ) {
994                                 continue;
995                         }
996                         d = exp[1][axis] - localLight[axis];
997                         frac = d / ray[axis];
998                         hit = localLight + frac * ray;
999                         hit[axis] = exp[1][axis];
1000                 } else {
1001                         continue;
1002                 }
1003
1004                 if ( exp.ContainsPoint( hit ) ) {
1005                         return true;
1006                 }
1007         }
1008
1009         // the view is definitely not inside the projected shadow
1010         return false;
1011 }
1012
1013 /*
1014 ==================
1015 idInteraction::AddActiveInteraction
1016
1017 Create and add any necessary light and shadow triangles
1018
1019 If the model doesn't have any surfaces that need interactions
1020 with this type of light, it can be skipped, but we might need to
1021 instantiate the dynamic model to find out
1022 ==================
1023 */
1024 void idInteraction::AddActiveInteraction( void ) {
1025         viewLight_t *   vLight;
1026         viewEntity_t *  vEntity;
1027         idScreenRect    shadowScissor;
1028         idScreenRect    lightScissor;
1029         idVec3                  localLightOrigin;
1030         idVec3                  localViewOrigin;
1031
1032         vLight = lightDef->viewLight;
1033         vEntity = entityDef->viewEntity;
1034
1035         // do not waste time culling the interaction frustum if there will be no shadows
1036         if ( !HasShadows() ) {
1037
1038                 // use the entity scissor rectangle
1039                 shadowScissor = vEntity->scissorRect;
1040
1041         // culling does not seem to be worth it for static world models
1042         } else if ( entityDef->parms.hModel->IsStaticWorldModel() ) {
1043
1044                 // use the light scissor rectangle
1045                 shadowScissor = vLight->scissorRect;
1046
1047         } else {
1048
1049                 // try to cull the interaction
1050                 // this will also cull the case where the light origin is inside the
1051                 // view frustum and the entity bounds are outside the view frustum
1052                 if ( CullInteractionByViewFrustum( tr.viewDef->viewFrustum ) ) {
1053                         return;
1054                 }
1055
1056                 // calculate the shadow scissor rectangle
1057                 shadowScissor = CalcInteractionScissorRectangle( tr.viewDef->viewFrustum );
1058         }
1059
1060         // get out before making the dynamic model if the shadow scissor rectangle is empty
1061         if ( shadowScissor.IsEmpty() ) {
1062                 return;
1063         }
1064
1065         // We will need the dynamic surface created to make interactions, even if the
1066         // model itself wasn't visible.  This just returns a cached value after it
1067         // has been generated once in the view.
1068         idRenderModel *model = R_EntityDefDynamicModel( entityDef );
1069         if ( model == NULL || model->NumSurfaces() <= 0 ) {
1070                 return;
1071         }
1072
1073         // the dynamic model may have changed since we built the surface list
1074         if ( !IsDeferred() && entityDef->dynamicModelFrameCount != dynamicModelFrameCount ) {
1075                 FreeSurfaces();
1076         }
1077         dynamicModelFrameCount = entityDef->dynamicModelFrameCount;
1078
1079         // actually create the interaction if needed, building light and shadow surfaces as needed
1080         if ( IsDeferred() ) {
1081                 CreateInteraction( model );
1082         }
1083
1084         R_GlobalPointToLocal( vEntity->modelMatrix, lightDef->globalLightOrigin, localLightOrigin );
1085         R_GlobalPointToLocal( vEntity->modelMatrix, tr.viewDef->renderView.vieworg, localViewOrigin );
1086
1087         // calculate the scissor as the intersection of the light and model rects
1088         // this is used for light triangles, but not for shadow triangles
1089         lightScissor = vLight->scissorRect;
1090         lightScissor.Intersect( vEntity->scissorRect );
1091
1092         bool lightScissorsEmpty = lightScissor.IsEmpty();
1093
1094         // for each surface of this entity / light interaction
1095         for ( int i = 0; i < numSurfaces; i++ ) {
1096                 surfaceInteraction_t *sint = &surfaces[i];
1097
1098                 // see if the base surface is visible, we may still need to add shadows even if empty
1099                 if ( !lightScissorsEmpty && sint->ambientTris && sint->ambientTris->ambientViewCount == tr.viewCount ) {
1100
1101                         // make sure we have created this interaction, which may have been deferred
1102                         // on a previous use that only needed the shadow
1103                         if ( sint->lightTris == LIGHT_TRIS_DEFERRED ) {
1104                                 sint->lightTris = R_CreateLightTris( vEntity->entityDef, sint->ambientTris, vLight->lightDef, sint->shader, sint->cullInfo );
1105                                 R_FreeInteractionCullInfo( sint->cullInfo );
1106                         }
1107
1108                         srfTriangles_t *lightTris = sint->lightTris;
1109
1110                         if ( lightTris ) {
1111
1112                                 // try to cull before adding
1113                                 // FIXME: this may not be worthwhile. We have already done culling on the ambient,
1114                                 // but individual surfaces may still be cropped somewhat more
1115                                 if ( !R_CullLocalBox( lightTris->bounds, vEntity->modelMatrix, 5, tr.viewDef->frustum ) ) {
1116
1117                                         // make sure the original surface has its ambient cache created
1118                                         srfTriangles_t *tri = sint->ambientTris;
1119                                         if ( !tri->ambientCache ) {
1120                                                 if ( !R_CreateAmbientCache( tri, sint->shader->ReceivesLighting() ) ) {
1121                                                         // skip if we were out of vertex memory
1122                                                         continue;
1123                                                 }
1124                                         }
1125
1126                                         // reference the original surface's ambient cache
1127                                         lightTris->ambientCache = tri->ambientCache;
1128
1129                                         // touch the ambient surface so it won't get purged
1130                                         vertexCache.Touch( lightTris->ambientCache );
1131
1132                                         // regenerate the lighting cache (for non-vertex program cards) if it has been purged
1133                                         if ( !lightTris->lightingCache ) {
1134                                                 if ( !R_CreateLightingCache( entityDef, lightDef, lightTris ) ) {
1135                                                         // skip if we are out of vertex memory
1136                                                         continue;
1137                                                 }
1138                                         }
1139                                         // touch the light surface so it won't get purged
1140                                         // (vertex program cards won't have a light cache at all)
1141                                         if ( lightTris->lightingCache ) {
1142                                                 vertexCache.Touch( lightTris->lightingCache );
1143                                         }
1144
1145                                         if ( !lightTris->indexCache && r_useIndexBuffers.GetBool() ) {
1146                                                 vertexCache.Alloc( lightTris->indexes, lightTris->numIndexes * sizeof( lightTris->indexes[0] ), &lightTris->indexCache, true );
1147                                         }
1148                                         if ( lightTris->indexCache ) {
1149                                                 vertexCache.Touch( lightTris->indexCache );
1150                                         }
1151
1152                                         // add the surface to the light list
1153
1154                                         const idMaterial *shader = sint->shader;
1155                                         R_GlobalShaderOverride( &shader );
1156
1157                                         // there will only be localSurfaces if the light casts shadows and
1158                                         // there are surfaces with NOSELFSHADOW
1159                                         if ( sint->shader->Coverage() == MC_TRANSLUCENT ) {
1160                                                 R_LinkLightSurf( &vLight->translucentInteractions, lightTris, 
1161                                                         vEntity, lightDef, shader, lightScissor, false );
1162                                         } else if ( !lightDef->parms.noShadows && sint->shader->TestMaterialFlag(MF_NOSELFSHADOW) ) {
1163                                                 R_LinkLightSurf( &vLight->localInteractions, lightTris, 
1164                                                         vEntity, lightDef, shader, lightScissor, false );
1165                                         } else {
1166                                                 R_LinkLightSurf( &vLight->globalInteractions, lightTris, 
1167                                                         vEntity, lightDef, shader, lightScissor, false );
1168                                         }
1169                                 }
1170                         }
1171                 }
1172
1173                 srfTriangles_t *shadowTris = sint->shadowTris;
1174
1175                 // the shadows will always have to be added, unless we can tell they
1176                 // are from a surface in an unconnected area
1177                 if ( shadowTris ) {
1178                         
1179                         // check for view specific shadow suppression (player shadows, etc)
1180                         if ( !r_skipSuppress.GetBool() ) {
1181                                 if ( entityDef->parms.suppressShadowInViewID &&
1182                                         entityDef->parms.suppressShadowInViewID == tr.viewDef->renderView.viewID ) {
1183                                         continue;
1184                                 }
1185                                 if ( entityDef->parms.suppressShadowInLightID &&
1186                                         entityDef->parms.suppressShadowInLightID == lightDef->parms.lightId ) {
1187                                         continue;
1188                                 }
1189                         }
1190
1191                         // cull static shadows that have a non-empty bounds
1192                         // dynamic shadows that use the turboshadow code will not have valid
1193                         // bounds, because the perspective projection extends them to infinity
1194                         if ( r_useShadowCulling.GetBool() && !shadowTris->bounds.IsCleared() ) {
1195                                 if ( R_CullLocalBox( shadowTris->bounds, vEntity->modelMatrix, 5, tr.viewDef->frustum ) ) {
1196                                         continue;
1197                                 }
1198                         }
1199
1200                         // copy the shadow vertexes to the vertex cache if they have been purged
1201
1202                         // if we are using shared shadowVertexes and letting a vertex program fix them up,
1203                         // get the shadowCache from the parent ambient surface
1204                         if ( !shadowTris->shadowVertexes ) {
1205                                 // the data may have been purged, so get the latest from the "home position"
1206                                 shadowTris->shadowCache = sint->ambientTris->shadowCache;
1207                         }
1208
1209                         // if we have been purged, re-upload the shadowVertexes
1210                         if ( !shadowTris->shadowCache ) {
1211                                 if ( shadowTris->shadowVertexes ) {
1212                                         // each interaction has unique vertexes
1213                                         R_CreatePrivateShadowCache( shadowTris );
1214                                 } else {
1215                                         R_CreateVertexProgramShadowCache( sint->ambientTris );
1216                                         shadowTris->shadowCache = sint->ambientTris->shadowCache;
1217                                 }
1218                                 // if we are out of vertex cache space, skip the interaction
1219                                 if ( !shadowTris->shadowCache ) {
1220                                         continue;
1221                                 }
1222                         }
1223
1224                         // touch the shadow surface so it won't get purged
1225                         vertexCache.Touch( shadowTris->shadowCache );
1226
1227                         if ( !shadowTris->indexCache && r_useIndexBuffers.GetBool() ) {
1228                                 vertexCache.Alloc( shadowTris->indexes, shadowTris->numIndexes * sizeof( shadowTris->indexes[0] ), &shadowTris->indexCache, true );
1229                                 vertexCache.Touch( shadowTris->indexCache );
1230                         }
1231
1232                         // see if we can avoid using the shadow volume caps
1233                         bool inside = R_PotentiallyInsideInfiniteShadow( sint->ambientTris, localViewOrigin, localLightOrigin );
1234
1235                         if ( sint->shader->TestMaterialFlag( MF_NOSELFSHADOW ) ) {
1236                                 R_LinkLightSurf( &vLight->localShadows,
1237                                         shadowTris, vEntity, lightDef, NULL, shadowScissor, inside );
1238                         } else {
1239                                 R_LinkLightSurf( &vLight->globalShadows,
1240                                         shadowTris, vEntity, lightDef, NULL, shadowScissor, inside );
1241                         }
1242                 }
1243         }
1244 }
1245
1246 /*
1247 ===================
1248 R_ShowInteractionMemory_f
1249 ===================
1250 */
1251 void R_ShowInteractionMemory_f( const idCmdArgs &args ) {
1252         int total = 0;
1253         int entities = 0;
1254         int interactions = 0;
1255         int deferredInteractions = 0;
1256         int emptyInteractions = 0;
1257         int lightTris = 0;
1258         int lightTriVerts = 0;
1259         int lightTriIndexes = 0;
1260         int shadowTris = 0;
1261         int shadowTriVerts = 0;
1262         int shadowTriIndexes = 0;
1263
1264         for ( int i = 0; i < tr.primaryWorld->entityDefs.Num(); i++ ) {
1265                 idRenderEntityLocal     *def = tr.primaryWorld->entityDefs[i];
1266                 if ( !def ) {
1267                         continue;
1268                 }
1269                 if ( def->firstInteraction == NULL ) {
1270                         continue;
1271                 }
1272                 entities++;
1273
1274                 for ( idInteraction *inter = def->firstInteraction; inter != NULL; inter = inter->entityNext ) {
1275                         interactions++;
1276                         total += inter->MemoryUsed();
1277
1278                         if ( inter->IsDeferred() ) {
1279                                 deferredInteractions++;
1280                                 continue;
1281                         }
1282                         if ( inter->IsEmpty() ) {
1283                                 emptyInteractions++;
1284                                 continue;
1285                         }
1286
1287                         for ( int j = 0; j < inter->numSurfaces; j++ ) {
1288                                 surfaceInteraction_t *srf = &inter->surfaces[j];
1289
1290                                 if ( srf->lightTris && srf->lightTris != LIGHT_TRIS_DEFERRED ) {
1291                                         lightTris++;
1292                                         lightTriVerts += srf->lightTris->numVerts;
1293                                         lightTriIndexes += srf->lightTris->numIndexes;
1294                                 }
1295                                 if ( srf->shadowTris ) {
1296                                         shadowTris++;
1297                                         shadowTriVerts += srf->shadowTris->numVerts;
1298                                         shadowTriIndexes += srf->shadowTris->numIndexes;
1299                                 }
1300                         }
1301                 }
1302         }
1303
1304         common->Printf( "%i entities with %i total interactions totalling %ik\n", entities, interactions, total / 1024 );
1305         common->Printf( "%i deferred interactions, %i empty interactions\n", deferredInteractions, emptyInteractions );
1306         common->Printf( "%5i indexes %5i verts in %5i light tris\n", lightTriIndexes, lightTriVerts, lightTris );
1307         common->Printf( "%5i indexes %5i verts in %5i shadow tris\n", shadowTriIndexes, shadowTriVerts, shadowTris );
1308 }