1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 Copyright (C) 2000-2006 Tim Angus
5
6 This file is part of Tremulous.
7
8 Tremulous is free software; you can redistribute it
9 and/or modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the License,
11 or (at your option) any later version.
12
13 Tremulous is distributed in the hope that it will be
14 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Tremulous; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 ===========================================================================
22 */
23 // tr_main.c -- main control flow for each frame
24
25 #include "tr_local.h"
26
27 #include <string.h> // memcpy
28
29 trGlobals_t tr;
30
31 static float s_flipMatrix[16] = {
32 // convert from our coordinate system (looking down X)
33 // to OpenGL's coordinate system (looking down -Z)
34 0, 0, -1, 0,
35 -1, 0, 0, 0,
36 0, 1, 0, 0,
37 0, 0, 0, 1
38 };
39
40
41 refimport_t ri;
42
43 // entities that will have procedurally generated surfaces will just
44 // point at this for their sorting surface
45 surfaceType_t entitySurface = SF_ENTITY;
46
47 /*
48 =================
49 R_CullLocalBox
50
51 Returns CULL_IN, CULL_CLIP, or CULL_OUT
52 =================
53 */
R_CullLocalBox(vec3_t bounds[2])54 int R_CullLocalBox (vec3_t bounds[2]) {
55 int i, j;
56 vec3_t transformed[8];
57 float dists[8];
58 vec3_t v;
59 cplane_t *frust;
60 int anyBack;
61 int front, back;
62
63 if ( r_nocull->integer ) {
64 return CULL_CLIP;
65 }
66
67 // transform into world space
68 for (i = 0 ; i < 8 ; i++) {
69 v[0] = bounds[i&1][0];
70 v[1] = bounds[(i>>1)&1][1];
71 v[2] = bounds[(i>>2)&1][2];
72
73 VectorCopy( tr.or.origin, transformed[i] );
74 VectorMA( transformed[i], v[0], tr.or.axis[0], transformed[i] );
75 VectorMA( transformed[i], v[1], tr.or.axis[1], transformed[i] );
76 VectorMA( transformed[i], v[2], tr.or.axis[2], transformed[i] );
77 }
78
79 // check against frustum planes
80 anyBack = 0;
81 for (i = 0 ; i < 4 ; i++) {
82 frust = &tr.viewParms.frustum[i];
83
84 front = back = 0;
85 for (j = 0 ; j < 8 ; j++) {
86 dists[j] = DotProduct(transformed[j], frust->normal);
87 if ( dists[j] > frust->dist ) {
88 front = 1;
89 if ( back ) {
90 break; // a point is in front
91 }
92 } else {
93 back = 1;
94 }
95 }
96 if ( !front ) {
97 // all points were behind one of the planes
98 return CULL_OUT;
99 }
100 anyBack |= back;
101 }
102
103 if ( !anyBack ) {
104 return CULL_IN; // completely inside frustum
105 }
106
107 return CULL_CLIP; // partially clipped
108 }
109
110 /*
111 ** R_CullLocalPointAndRadius
112 */
R_CullLocalPointAndRadius(vec3_t pt,float radius)113 int R_CullLocalPointAndRadius( vec3_t pt, float radius )
114 {
115 vec3_t transformed;
116
117 R_LocalPointToWorld( pt, transformed );
118
119 return R_CullPointAndRadius( transformed, radius );
120 }
121
122 /*
123 ** R_CullPointAndRadius
124 */
R_CullPointAndRadius(vec3_t pt,float radius)125 int R_CullPointAndRadius( vec3_t pt, float radius )
126 {
127 int i;
128 float dist;
129 cplane_t *frust;
130 qboolean mightBeClipped = qfalse;
131
132 if ( r_nocull->integer ) {
133 return CULL_CLIP;
134 }
135
136 // check against frustum planes
137 for (i = 0 ; i < 4 ; i++)
138 {
139 frust = &tr.viewParms.frustum[i];
140
141 dist = DotProduct( pt, frust->normal) - frust->dist;
142 if ( dist < -radius )
143 {
144 return CULL_OUT;
145 }
146 else if ( dist <= radius )
147 {
148 mightBeClipped = qtrue;
149 }
150 }
151
152 if ( mightBeClipped )
153 {
154 return CULL_CLIP;
155 }
156
157 return CULL_IN; // completely inside frustum
158 }
159
160
161 /*
162 =================
163 R_LocalNormalToWorld
164
165 =================
166 */
R_LocalNormalToWorld(vec3_t local,vec3_t world)167 void R_LocalNormalToWorld (vec3_t local, vec3_t world) {
168 world[0] = local[0] * tr.or.axis[0][0] + local[1] * tr.or.axis[1][0] + local[2] * tr.or.axis[2][0];
169 world[1] = local[0] * tr.or.axis[0][1] + local[1] * tr.or.axis[1][1] + local[2] * tr.or.axis[2][1];
170 world[2] = local[0] * tr.or.axis[0][2] + local[1] * tr.or.axis[1][2] + local[2] * tr.or.axis[2][2];
171 }
172
173 /*
174 =================
175 R_LocalPointToWorld
176
177 =================
178 */
R_LocalPointToWorld(vec3_t local,vec3_t world)179 void R_LocalPointToWorld (vec3_t local, vec3_t world) {
180 world[0] = local[0] * tr.or.axis[0][0] + local[1] * tr.or.axis[1][0] + local[2] * tr.or.axis[2][0] + tr.or.origin[0];
181 world[1] = local[0] * tr.or.axis[0][1] + local[1] * tr.or.axis[1][1] + local[2] * tr.or.axis[2][1] + tr.or.origin[1];
182 world[2] = local[0] * tr.or.axis[0][2] + local[1] * tr.or.axis[1][2] + local[2] * tr.or.axis[2][2] + tr.or.origin[2];
183 }
184
185 /*
186 =================
187 R_WorldToLocal
188
189 =================
190 */
R_WorldToLocal(vec3_t world,vec3_t local)191 void R_WorldToLocal (vec3_t world, vec3_t local) {
192 local[0] = DotProduct(world, tr.or.axis[0]);
193 local[1] = DotProduct(world, tr.or.axis[1]);
194 local[2] = DotProduct(world, tr.or.axis[2]);
195 }
196
197 /*
198 ==========================
199 R_TransformModelToClip
200
201 ==========================
202 */
R_TransformModelToClip(const vec3_t src,const float * modelMatrix,const float * projectionMatrix,vec4_t eye,vec4_t dst)203 void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix,
204 vec4_t eye, vec4_t dst ) {
205 int i;
206
207 for ( i = 0 ; i < 4 ; i++ ) {
208 eye[i] =
209 src[0] * modelMatrix[ i + 0 * 4 ] +
210 src[1] * modelMatrix[ i + 1 * 4 ] +
211 src[2] * modelMatrix[ i + 2 * 4 ] +
212 1 * modelMatrix[ i + 3 * 4 ];
213 }
214
215 for ( i = 0 ; i < 4 ; i++ ) {
216 dst[i] =
217 eye[0] * projectionMatrix[ i + 0 * 4 ] +
218 eye[1] * projectionMatrix[ i + 1 * 4 ] +
219 eye[2] * projectionMatrix[ i + 2 * 4 ] +
220 eye[3] * projectionMatrix[ i + 3 * 4 ];
221 }
222 }
223
224 /*
225 ==========================
226 R_TransformClipToWindow
227
228 ==========================
229 */
R_TransformClipToWindow(const vec4_t clip,const viewParms_t * view,vec4_t normalized,vec4_t window)230 void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ) {
231 normalized[0] = clip[0] / clip[3];
232 normalized[1] = clip[1] / clip[3];
233 normalized[2] = ( clip[2] + clip[3] ) / ( 2 * clip[3] );
234
235 window[0] = 0.5f * ( 1.0f + normalized[0] ) * view->viewportWidth;
236 window[1] = 0.5f * ( 1.0f + normalized[1] ) * view->viewportHeight;
237 window[2] = normalized[2];
238
239 window[0] = (int) ( window[0] + 0.5 );
240 window[1] = (int) ( window[1] + 0.5 );
241 }
242
243
244 /*
245 ==========================
246 myGlMultMatrix
247
248 ==========================
249 */
myGlMultMatrix(const float * a,const float * b,float * out)250 void myGlMultMatrix( const float *a, const float *b, float *out ) {
251 int i, j;
252
253 for ( i = 0 ; i < 4 ; i++ ) {
254 for ( j = 0 ; j < 4 ; j++ ) {
255 out[ i * 4 + j ] =
256 a [ i * 4 + 0 ] * b [ 0 * 4 + j ]
257 + a [ i * 4 + 1 ] * b [ 1 * 4 + j ]
258 + a [ i * 4 + 2 ] * b [ 2 * 4 + j ]
259 + a [ i * 4 + 3 ] * b [ 3 * 4 + j ];
260 }
261 }
262 }
263
264 /*
265 =================
266 R_RotateForEntity
267
268 Generates an orientation for an entity and viewParms
269 Does NOT produce any GL calls
270 Called by both the front end and the back end
271 =================
272 */
R_RotateForEntity(const trRefEntity_t * ent,const viewParms_t * viewParms,orientationr_t * or)273 void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms,
274 orientationr_t *or ) {
275 float glMatrix[16];
276 vec3_t delta;
277 float axisLength;
278
279 if ( ent->e.reType != RT_MODEL ) {
280 *or = viewParms->world;
281 return;
282 }
283
284 VectorCopy( ent->e.origin, or->origin );
285
286 VectorCopy( ent->e.axis[0], or->axis[0] );
287 VectorCopy( ent->e.axis[1], or->axis[1] );
288 VectorCopy( ent->e.axis[2], or->axis[2] );
289
290 glMatrix[0] = or->axis[0][0];
291 glMatrix[4] = or->axis[1][0];
292 glMatrix[8] = or->axis[2][0];
293 glMatrix[12] = or->origin[0];
294
295 glMatrix[1] = or->axis[0][1];
296 glMatrix[5] = or->axis[1][1];
297 glMatrix[9] = or->axis[2][1];
298 glMatrix[13] = or->origin[1];
299
300 glMatrix[2] = or->axis[0][2];
301 glMatrix[6] = or->axis[1][2];
302 glMatrix[10] = or->axis[2][2];
303 glMatrix[14] = or->origin[2];
304
305 glMatrix[3] = 0;
306 glMatrix[7] = 0;
307 glMatrix[11] = 0;
308 glMatrix[15] = 1;
309
310 myGlMultMatrix( glMatrix, viewParms->world.modelMatrix, or->modelMatrix );
311
312 // calculate the viewer origin in the model's space
313 // needed for fog, specular, and environment mapping
314 VectorSubtract( viewParms->or.origin, or->origin, delta );
315
316 // compensate for scale in the axes if necessary
317 if ( ent->e.nonNormalizedAxes ) {
318 axisLength = VectorLength( ent->e.axis[0] );
319 if ( !axisLength ) {
320 axisLength = 0;
321 } else {
322 axisLength = 1.0f / axisLength;
323 }
324 } else {
325 axisLength = 1.0f;
326 }
327
328 or->viewOrigin[0] = DotProduct( delta, or->axis[0] ) * axisLength;
329 or->viewOrigin[1] = DotProduct( delta, or->axis[1] ) * axisLength;
330 or->viewOrigin[2] = DotProduct( delta, or->axis[2] ) * axisLength;
331 }
332
333 /*
334 =================
335 R_RotateForViewer
336
337 Sets up the modelview matrix for a given viewParm
338 =================
339 */
R_RotateForViewer(void)340 void R_RotateForViewer (void)
341 {
342 float viewerMatrix[16];
343 vec3_t origin;
344
345 Com_Memset (&tr.or, 0, sizeof(tr.or));
346 tr.or.axis[0][0] = 1;
347 tr.or.axis[1][1] = 1;
348 tr.or.axis[2][2] = 1;
349 VectorCopy (tr.viewParms.or.origin, tr.or.viewOrigin);
350
351 // transform by the camera placement
352 VectorCopy( tr.viewParms.or.origin, origin );
353
354 viewerMatrix[0] = tr.viewParms.or.axis[0][0];
355 viewerMatrix[4] = tr.viewParms.or.axis[0][1];
356 viewerMatrix[8] = tr.viewParms.or.axis[0][2];
357 viewerMatrix[12] = -origin[0] * viewerMatrix[0] + -origin[1] * viewerMatrix[4] + -origin[2] * viewerMatrix[8];
358
359 viewerMatrix[1] = tr.viewParms.or.axis[1][0];
360 viewerMatrix[5] = tr.viewParms.or.axis[1][1];
361 viewerMatrix[9] = tr.viewParms.or.axis[1][2];
362 viewerMatrix[13] = -origin[0] * viewerMatrix[1] + -origin[1] * viewerMatrix[5] + -origin[2] * viewerMatrix[9];
363
364 viewerMatrix[2] = tr.viewParms.or.axis[2][0];
365 viewerMatrix[6] = tr.viewParms.or.axis[2][1];
366 viewerMatrix[10] = tr.viewParms.or.axis[2][2];
367 viewerMatrix[14] = -origin[0] * viewerMatrix[2] + -origin[1] * viewerMatrix[6] + -origin[2] * viewerMatrix[10];
368
369 viewerMatrix[3] = 0;
370 viewerMatrix[7] = 0;
371 viewerMatrix[11] = 0;
372 viewerMatrix[15] = 1;
373
374 // convert from our coordinate system (looking down X)
375 // to OpenGL's coordinate system (looking down -Z)
376 myGlMultMatrix( viewerMatrix, s_flipMatrix, tr.or.modelMatrix );
377
378 tr.viewParms.world = tr.or;
379
380 }
381
382 /*
383 ** SetFarClip
384 */
SetFarClip(void)385 static void SetFarClip( void )
386 {
387 float farthestCornerDistance = 0;
388 int i;
389
390 // if not rendering the world (icons, menus, etc)
391 // set a 2k far clip plane
392 if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
393 tr.viewParms.zFar = 2048;
394 return;
395 }
396
397 //
398 // set far clipping planes dynamically
399 //
400 farthestCornerDistance = 0;
401 for ( i = 0; i < 8; i++ )
402 {
403 vec3_t v;
404 vec3_t vecTo;
405 float distance;
406
407 if ( i & 1 )
408 {
409 v[0] = tr.viewParms.visBounds[0][0];
410 }
411 else
412 {
413 v[0] = tr.viewParms.visBounds[1][0];
414 }
415
416 if ( i & 2 )
417 {
418 v[1] = tr.viewParms.visBounds[0][1];
419 }
420 else
421 {
422 v[1] = tr.viewParms.visBounds[1][1];
423 }
424
425 if ( i & 4 )
426 {
427 v[2] = tr.viewParms.visBounds[0][2];
428 }
429 else
430 {
431 v[2] = tr.viewParms.visBounds[1][2];
432 }
433
434 VectorSubtract( v, tr.viewParms.or.origin, vecTo );
435
436 distance = vecTo[0] * vecTo[0] + vecTo[1] * vecTo[1] + vecTo[2] * vecTo[2];
437
438 if ( distance > farthestCornerDistance )
439 {
440 farthestCornerDistance = distance;
441 }
442 }
443 tr.viewParms.zFar = sqrt( farthestCornerDistance );
444 }
445
446
447 /*
448 ===============
449 R_SetupProjection
450 ===============
451 */
R_SetupProjection(void)452 void R_SetupProjection( void ) {
453 float xmin, xmax, ymin, ymax;
454 float width, height, depth;
455 float zNear, zFar;
456
457 // dynamically compute far clip plane distance
458 SetFarClip();
459
460 //
461 // set up projection matrix
462 //
463 zNear = r_znear->value;
464 zFar = tr.viewParms.zFar;
465
466 ymax = zNear * tan( tr.refdef.fov_y * M_PI / 360.0f );
467 ymin = -ymax;
468
469 xmax = zNear * tan( tr.refdef.fov_x * M_PI / 360.0f );
470 xmin = -xmax;
471
472 width = xmax - xmin;
473 height = ymax - ymin;
474 depth = zFar - zNear;
475
476 tr.viewParms.projectionMatrix[0] = 2 * zNear / width;
477 tr.viewParms.projectionMatrix[4] = 0;
478 tr.viewParms.projectionMatrix[8] = ( xmax + xmin ) / width; // normally 0
479 tr.viewParms.projectionMatrix[12] = 0;
480
481 tr.viewParms.projectionMatrix[1] = 0;
482 tr.viewParms.projectionMatrix[5] = 2 * zNear / height;
483 tr.viewParms.projectionMatrix[9] = ( ymax + ymin ) / height; // normally 0
484 tr.viewParms.projectionMatrix[13] = 0;
485
486 tr.viewParms.projectionMatrix[2] = 0;
487 tr.viewParms.projectionMatrix[6] = 0;
488 tr.viewParms.projectionMatrix[10] = -( zFar + zNear ) / depth;
489 tr.viewParms.projectionMatrix[14] = -2 * zFar * zNear / depth;
490
491 tr.viewParms.projectionMatrix[3] = 0;
492 tr.viewParms.projectionMatrix[7] = 0;
493 tr.viewParms.projectionMatrix[11] = -1;
494 tr.viewParms.projectionMatrix[15] = 0;
495 }
496
497 /*
498 =================
499 R_SetupFrustum
500
501 Setup that culling frustum planes for the current view
502 =================
503 */
R_SetupFrustum(void)504 void R_SetupFrustum (void) {
505 int i;
506 float xs, xc;
507 float ang;
508
509 ang = tr.viewParms.fovX / 180 * M_PI * 0.5f;
510 xs = sin( ang );
511 xc = cos( ang );
512
513 VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[0].normal );
514 VectorMA( tr.viewParms.frustum[0].normal, xc, tr.viewParms.or.axis[1], tr.viewParms.frustum[0].normal );
515
516 VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[1].normal );
517 VectorMA( tr.viewParms.frustum[1].normal, -xc, tr.viewParms.or.axis[1], tr.viewParms.frustum[1].normal );
518
519 ang = tr.viewParms.fovY / 180 * M_PI * 0.5f;
520 xs = sin( ang );
521 xc = cos( ang );
522
523 VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[2].normal );
524 VectorMA( tr.viewParms.frustum[2].normal, xc, tr.viewParms.or.axis[2], tr.viewParms.frustum[2].normal );
525
526 VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[3].normal );
527 VectorMA( tr.viewParms.frustum[3].normal, -xc, tr.viewParms.or.axis[2], tr.viewParms.frustum[3].normal );
528
529 for (i=0 ; i<4 ; i++) {
530 tr.viewParms.frustum[i].type = PLANE_NON_AXIAL;
531 tr.viewParms.frustum[i].dist = DotProduct (tr.viewParms.or.origin, tr.viewParms.frustum[i].normal);
532 SetPlaneSignbits( &tr.viewParms.frustum[i] );
533 }
534 }
535
536
537 /*
538 =================
539 R_MirrorPoint
540 =================
541 */
R_MirrorPoint(vec3_t in,orientation_t * surface,orientation_t * camera,vec3_t out)542 void R_MirrorPoint (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) {
543 int i;
544 vec3_t local;
545 vec3_t transformed;
546 float d;
547
548 VectorSubtract( in, surface->origin, local );
549
550 VectorClear( transformed );
551 for ( i = 0 ; i < 3 ; i++ ) {
552 d = DotProduct(local, surface->axis[i]);
553 VectorMA( transformed, d, camera->axis[i], transformed );
554 }
555
556 VectorAdd( transformed, camera->origin, out );
557 }
558
R_MirrorVector(vec3_t in,orientation_t * surface,orientation_t * camera,vec3_t out)559 void R_MirrorVector (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) {
560 int i;
561 float d;
562
563 VectorClear( out );
564 for ( i = 0 ; i < 3 ; i++ ) {
565 d = DotProduct(in, surface->axis[i]);
566 VectorMA( out, d, camera->axis[i], out );
567 }
568 }
569
570
571 /*
572 =============
573 R_PlaneForSurface
574 =============
575 */
R_PlaneForSurface(surfaceType_t * surfType,cplane_t * plane)576 void R_PlaneForSurface (surfaceType_t *surfType, cplane_t *plane) {
577 srfTriangles_t *tri;
578 srfPoly_t *poly;
579 drawVert_t *v1, *v2, *v3;
580 vec4_t plane4;
581
582 if (!surfType) {
583 Com_Memset (plane, 0, sizeof(*plane));
584 plane->normal[0] = 1;
585 return;
586 }
587 switch (*surfType) {
588 case SF_FACE:
589 *plane = ((srfSurfaceFace_t *)surfType)->plane;
590 return;
591 case SF_TRIANGLES:
592 tri = (srfTriangles_t *)surfType;
593 v1 = tri->verts + tri->indexes[0];
594 v2 = tri->verts + tri->indexes[1];
595 v3 = tri->verts + tri->indexes[2];
596 PlaneFromPoints( plane4, v1->xyz, v2->xyz, v3->xyz );
597 VectorCopy( plane4, plane->normal );
598 plane->dist = plane4[3];
599 return;
600 case SF_POLY:
601 poly = (srfPoly_t *)surfType;
602 PlaneFromPoints( plane4, poly->verts[0].xyz, poly->verts[1].xyz, poly->verts[2].xyz );
603 VectorCopy( plane4, plane->normal );
604 plane->dist = plane4[3];
605 return;
606 default:
607 Com_Memset (plane, 0, sizeof(*plane));
608 plane->normal[0] = 1;
609 return;
610 }
611 }
612
613 /*
614 =================
615 R_GetPortalOrientation
616
617 entityNum is the entity that the portal surface is a part of, which may
618 be moving and rotating.
619
620 Returns qtrue if it should be mirrored
621 =================
622 */
R_GetPortalOrientations(drawSurf_t * drawSurf,int entityNum,orientation_t * surface,orientation_t * camera,vec3_t pvsOrigin,qboolean * mirror)623 qboolean R_GetPortalOrientations( drawSurf_t *drawSurf, int entityNum,
624 orientation_t *surface, orientation_t *camera,
625 vec3_t pvsOrigin, qboolean *mirror ) {
626 int i;
627 cplane_t originalPlane, plane;
628 trRefEntity_t *e;
629 float d;
630 vec3_t transformed;
631
632 // create plane axis for the portal we are seeing
633 R_PlaneForSurface( drawSurf->surface, &originalPlane );
634
635 // rotate the plane if necessary
636 if ( entityNum != ENTITYNUM_WORLD ) {
637 tr.currentEntityNum = entityNum;
638 tr.currentEntity = &tr.refdef.entities[entityNum];
639
640 // get the orientation of the entity
641 R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.or );
642
643 // rotate the plane, but keep the non-rotated version for matching
644 // against the portalSurface entities
645 R_LocalNormalToWorld( originalPlane.normal, plane.normal );
646 plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.or.origin );
647
648 // translate the original plane
649 originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.or.origin );
650 } else {
651 plane = originalPlane;
652 }
653
654 VectorCopy( plane.normal, surface->axis[0] );
655 PerpendicularVector( surface->axis[1], surface->axis[0] );
656 CrossProduct( surface->axis[0], surface->axis[1], surface->axis[2] );
657
658 // locate the portal entity closest to this plane.
659 // origin will be the origin of the portal, origin2 will be
660 // the origin of the camera
661 for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) {
662 e = &tr.refdef.entities[i];
663 if ( e->e.reType != RT_PORTALSURFACE ) {
664 continue;
665 }
666
667 d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist;
668 if ( d > 64 || d < -64) {
669 continue;
670 }
671
672 // get the pvsOrigin from the entity
673 VectorCopy( e->e.oldorigin, pvsOrigin );
674
675 // if the entity is just a mirror, don't use as a camera point
676 if ( e->e.oldorigin[0] == e->e.origin[0] &&
677 e->e.oldorigin[1] == e->e.origin[1] &&
678 e->e.oldorigin[2] == e->e.origin[2] ) {
679 VectorScale( plane.normal, plane.dist, surface->origin );
680 VectorCopy( surface->origin, camera->origin );
681 VectorSubtract( vec3_origin, surface->axis[0], camera->axis[0] );
682 VectorCopy( surface->axis[1], camera->axis[1] );
683 VectorCopy( surface->axis[2], camera->axis[2] );
684
685 *mirror = qtrue;
686 return qtrue;
687 }
688
689 // project the origin onto the surface plane to get
690 // an origin point we can rotate around
691 d = DotProduct( e->e.origin, plane.normal ) - plane.dist;
692 VectorMA( e->e.origin, -d, surface->axis[0], surface->origin );
693
694 // now get the camera origin and orientation
695 VectorCopy( e->e.oldorigin, camera->origin );
696 AxisCopy( e->e.axis, camera->axis );
697 VectorSubtract( vec3_origin, camera->axis[0], camera->axis[0] );
698 VectorSubtract( vec3_origin, camera->axis[1], camera->axis[1] );
699
700 // optionally rotate
701 if ( e->e.oldframe ) {
702 // if a speed is specified
703 if ( e->e.frame ) {
704 // continuous rotate
705 d = (tr.refdef.time/1000.0f) * e->e.frame;
706 VectorCopy( camera->axis[1], transformed );
707 RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d );
708 CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] );
709 } else {
710 // bobbing rotate, with skinNum being the rotation offset
711 d = sin( tr.refdef.time * 0.003f );
712 d = e->e.skinNum + d * 4;
713 VectorCopy( camera->axis[1], transformed );
714 RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d );
715 CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] );
716 }
717 }
718 else if ( e->e.skinNum ) {
719 d = e->e.skinNum;
720 VectorCopy( camera->axis[1], transformed );
721 RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d );
722 CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] );
723 }
724 *mirror = qfalse;
725 return qtrue;
726 }
727
728 // if we didn't locate a portal entity, don't render anything.
729 // We don't want to just treat it as a mirror, because without a
730 // portal entity the server won't have communicated a proper entity set
731 // in the snapshot
732
733 // unfortunately, with local movement prediction it is easily possible
734 // to see a surface before the server has communicated the matching
735 // portal surface entity, so we don't want to print anything here...
736
737 //ri.Printf( PRINT_ALL, "Portal surface without a portal entity\n" );
738
739 return qfalse;
740 }
741
IsMirror(const drawSurf_t * drawSurf,int entityNum)742 static qboolean IsMirror( const drawSurf_t *drawSurf, int entityNum )
743 {
744 int i;
745 cplane_t originalPlane, plane;
746 trRefEntity_t *e;
747 float d;
748
749 // create plane axis for the portal we are seeing
750 R_PlaneForSurface( drawSurf->surface, &originalPlane );
751
752 // rotate the plane if necessary
753 if ( entityNum != ENTITYNUM_WORLD )
754 {
755 tr.currentEntityNum = entityNum;
756 tr.currentEntity = &tr.refdef.entities[entityNum];
757
758 // get the orientation of the entity
759 R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.or );
760
761 // rotate the plane, but keep the non-rotated version for matching
762 // against the portalSurface entities
763 R_LocalNormalToWorld( originalPlane.normal, plane.normal );
764 plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.or.origin );
765
766 // translate the original plane
767 originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.or.origin );
768 }
769 else
770 {
771 plane = originalPlane;
772 }
773
774 // locate the portal entity closest to this plane.
775 // origin will be the origin of the portal, origin2 will be
776 // the origin of the camera
777 for ( i = 0 ; i < tr.refdef.num_entities ; i++ )
778 {
779 e = &tr.refdef.entities[i];
780 if ( e->e.reType != RT_PORTALSURFACE ) {
781 continue;
782 }
783
784 d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist;
785 if ( d > 64 || d < -64) {
786 continue;
787 }
788
789 // if the entity is just a mirror, don't use as a camera point
790 if ( e->e.oldorigin[0] == e->e.origin[0] &&
791 e->e.oldorigin[1] == e->e.origin[1] &&
792 e->e.oldorigin[2] == e->e.origin[2] )
793 {
794 return qtrue;
795 }
796
797 return qfalse;
798 }
799 return qfalse;
800 }
801
802 /*
803 ** SurfIsOffscreen
804 **
805 ** Determines if a surface is completely offscreen.
806 */
SurfIsOffscreen(const drawSurf_t * drawSurf,vec4_t clipDest[128])807 static qboolean SurfIsOffscreen( const drawSurf_t *drawSurf, vec4_t clipDest[128] ) {
808 float shortest = 100000000;
809 int entityNum;
810 int numTriangles;
811 shader_t *shader;
812 int fogNum;
813 int dlighted;
814 vec4_t clip, eye;
815 int i;
816 unsigned int pointOr = 0;
817 unsigned int pointAnd = (unsigned int)~0;
818
819 if ( glConfig.smpActive ) { // FIXME! we can't do RB_BeginSurface/RB_EndSurface stuff with smp!
820 return qfalse;
821 }
822
823 R_RotateForViewer();
824
825 R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted );
826 RB_BeginSurface( shader, fogNum );
827 rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface );
828
829 assert( tess.numVertexes < 128 );
830
831 for ( i = 0; i < tess.numVertexes; i++ )
832 {
833 int j;
834 unsigned int pointFlags = 0;
835
836 R_TransformModelToClip( tess.xyz[i], tr.or.modelMatrix, tr.viewParms.projectionMatrix, eye, clip );
837
838 for ( j = 0; j < 3; j++ )
839 {
840 if ( clip[j] >= clip[3] )
841 {
842 pointFlags |= (1 << (j*2));
843 }
844 else if ( clip[j] <= -clip[3] )
845 {
846 pointFlags |= ( 1 << (j*2+1));
847 }
848 }
849 pointAnd &= pointFlags;
850 pointOr |= pointFlags;
851 }
852
853 // trivially reject
854 if ( pointAnd )
855 {
856 return qtrue;
857 }
858
859 // determine if this surface is backfaced and also determine the distance
860 // to the nearest vertex so we can cull based on portal range. Culling
861 // based on vertex distance isn't 100% correct (we should be checking for
862 // range to the surface), but it's good enough for the types of portals
863 // we have in the game right now.
864 numTriangles = tess.numIndexes / 3;
865
866 for ( i = 0; i < tess.numIndexes; i += 3 )
867 {
868 vec3_t normal;
869 float dot;
870 float len;
871
872 VectorSubtract( tess.xyz[tess.indexes[i]], tr.viewParms.or.origin, normal );
873
874 len = VectorLengthSquared( normal ); // lose the sqrt
875 if ( len < shortest )
876 {
877 shortest = len;
878 }
879
880 if ( ( dot = DotProduct( normal, tess.normal[tess.indexes[i]] ) ) >= 0 )
881 {
882 numTriangles--;
883 }
884 }
885 if ( !numTriangles )
886 {
887 return qtrue;
888 }
889
890 // mirrors can early out at this point, since we don't do a fade over distance
891 // with them (although we could)
892 if ( IsMirror( drawSurf, entityNum ) )
893 {
894 return qfalse;
895 }
896
897 if ( shortest > (tess.shader->portalRange*tess.shader->portalRange) )
898 {
899 return qtrue;
900 }
901
902 return qfalse;
903 }
904
905 /*
906 ========================
907 R_MirrorViewBySurface
908
909 Returns qtrue if another view has been rendered
910 ========================
911 */
R_MirrorViewBySurface(drawSurf_t * drawSurf,int entityNum)912 qboolean R_MirrorViewBySurface (drawSurf_t *drawSurf, int entityNum) {
913 vec4_t clipDest[128];
914 viewParms_t newParms;
915 viewParms_t oldParms;
916 orientation_t surface, camera;
917
918 // don't recursively mirror
919 if (tr.viewParms.isPortal) {
920 ri.Printf( PRINT_DEVELOPER, "WARNING: recursive mirror/portal found\n" );
921 return qfalse;
922 }
923
924 if ( r_noportals->integer || (r_fastsky->integer == 1) ) {
925 return qfalse;
926 }
927
928 // trivially reject portal/mirror
929 if ( SurfIsOffscreen( drawSurf, clipDest ) ) {
930 return qfalse;
931 }
932
933 // save old viewParms so we can return to it after the mirror view
934 oldParms = tr.viewParms;
935
936 newParms = tr.viewParms;
937 newParms.isPortal = qtrue;
938 if ( !R_GetPortalOrientations( drawSurf, entityNum, &surface, &camera,
939 newParms.pvsOrigin, &newParms.isMirror ) ) {
940 return qfalse; // bad portal, no portalentity
941 }
942
943 R_MirrorPoint (oldParms.or.origin, &surface, &camera, newParms.or.origin );
944
945 VectorSubtract( vec3_origin, camera.axis[0], newParms.portalPlane.normal );
946 newParms.portalPlane.dist = DotProduct( camera.origin, newParms.portalPlane.normal );
947
948 R_MirrorVector (oldParms.or.axis[0], &surface, &camera, newParms.or.axis[0]);
949 R_MirrorVector (oldParms.or.axis[1], &surface, &camera, newParms.or.axis[1]);
950 R_MirrorVector (oldParms.or.axis[2], &surface, &camera, newParms.or.axis[2]);
951
952 // OPTIMIZE: restrict the viewport on the mirrored view
953
954 // render the mirror view
955 R_RenderView (&newParms);
956
957 tr.viewParms = oldParms;
958
959 return qtrue;
960 }
961
962 /*
963 =================
964 R_SpriteFogNum
965
966 See if a sprite is inside a fog volume
967 =================
968 */
R_SpriteFogNum(trRefEntity_t * ent)969 int R_SpriteFogNum( trRefEntity_t *ent ) {
970 int i, j;
971 fog_t *fog;
972
973 if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
974 return 0;
975 }
976
977 for ( i = 1 ; i < tr.world->numfogs ; i++ ) {
978 fog = &tr.world->fogs[i];
979 for ( j = 0 ; j < 3 ; j++ ) {
980 if ( ent->e.origin[j] - ent->e.radius >= fog->bounds[1][j] ) {
981 break;
982 }
983 if ( ent->e.origin[j] + ent->e.radius <= fog->bounds[0][j] ) {
984 break;
985 }
986 }
987 if ( j == 3 ) {
988 return i;
989 }
990 }
991
992 return 0;
993 }
994
995 /*
996 ==========================================================================================
997
998 DRAWSURF SORTING
999
1000 ==========================================================================================
1001 */
1002
1003 /*
1004 ===============
1005 R_Radix
1006 ===============
1007 */
R_Radix(int byte,int size,drawSurf_t * source,drawSurf_t * dest)1008 static ID_INLINE void R_Radix( int byte, int size, drawSurf_t *source, drawSurf_t *dest )
1009 {
1010 int count[ 256 ] = { 0 };
1011 int index[ 256 ];
1012 int i;
1013 unsigned char *sortKey = NULL;
1014 unsigned char *end = NULL;
1015
1016 sortKey = ( (unsigned char *)&source[ 0 ].sort ) + byte;
1017 end = sortKey + ( size * sizeof( drawSurf_t ) );
1018 for( ; sortKey < end; sortKey += sizeof( drawSurf_t ) )
1019 ++count[ *sortKey ];
1020
1021 index[ 0 ] = 0;
1022
1023 for( i = 1; i < 256; ++i )
1024 index[ i ] = index[ i - 1 ] + count[ i - 1 ];
1025
1026 sortKey = ( (unsigned char *)&source[ 0 ].sort ) + byte;
1027 for( i = 0; i < size; ++i, sortKey += sizeof( drawSurf_t ) )
1028 dest[ index[ *sortKey ]++ ] = source[ i ];
1029 }
1030
1031 /*
1032 ===============
1033 R_RadixSort
1034
1035 Radix sort with 4 byte size buckets
1036 ===============
1037 */
R_RadixSort(drawSurf_t * source,int size)1038 static void R_RadixSort( drawSurf_t *source, int size )
1039 {
1040 static drawSurf_t scratch[ MAX_DRAWSURFS ];
1041
1042 R_Radix( 0, size, source, scratch );
1043 R_Radix( 1, size, scratch, source );
1044 R_Radix( 2, size, source, scratch );
1045 R_Radix( 3, size, scratch, source );
1046 }
1047
1048 //==========================================================================================
1049
1050 /*
1051 =================
1052 R_AddDrawSurf
1053 =================
1054 */
R_AddDrawSurf(surfaceType_t * surface,shader_t * shader,int fogIndex,int dlightMap)1055 void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader,
1056 int fogIndex, int dlightMap ) {
1057 int index;
1058
1059 // instead of checking for overflow, we just mask the index
1060 // so it wraps around
1061 index = tr.refdef.numDrawSurfs & DRAWSURF_MASK;
1062 // the sort data is packed into a single 32 bit value so it can be
1063 // compared quickly during the qsorting process
1064 tr.refdef.drawSurfs[index].sort = (shader->sortedIndex << QSORT_SHADERNUM_SHIFT)
1065 | tr.shiftedEntityNum | ( fogIndex << QSORT_FOGNUM_SHIFT ) | (int)dlightMap;
1066 tr.refdef.drawSurfs[index].surface = surface;
1067 tr.refdef.numDrawSurfs++;
1068 }
1069
1070 /*
1071 =================
1072 R_DecomposeSort
1073 =================
1074 */
R_DecomposeSort(unsigned sort,int * entityNum,shader_t ** shader,int * fogNum,int * dlightMap)1075 void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader,
1076 int *fogNum, int *dlightMap ) {
1077 *fogNum = ( sort >> QSORT_FOGNUM_SHIFT ) & 31;
1078 *shader = tr.sortedShaders[ ( sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1) ];
1079 *entityNum = ( sort >> QSORT_ENTITYNUM_SHIFT ) & 1023;
1080 *dlightMap = sort & 3;
1081 }
1082
1083 /*
1084 =================
1085 R_SortDrawSurfs
1086 =================
1087 */
R_SortDrawSurfs(drawSurf_t * drawSurfs,int numDrawSurfs)1088 void R_SortDrawSurfs( drawSurf_t *drawSurfs, int numDrawSurfs ) {
1089 shader_t *shader;
1090 int fogNum;
1091 int entityNum;
1092 int dlighted;
1093 int i;
1094
1095 // it is possible for some views to not have any surfaces
1096 if ( numDrawSurfs < 1 ) {
1097 // we still need to add it for hyperspace cases
1098 R_AddDrawSurfCmd( drawSurfs, numDrawSurfs );
1099 return;
1100 }
1101
1102 // if we overflowed MAX_DRAWSURFS, the drawsurfs
1103 // wrapped around in the buffer and we will be missing
1104 // the first surfaces, not the last ones
1105 if ( numDrawSurfs > MAX_DRAWSURFS ) {
1106 numDrawSurfs = MAX_DRAWSURFS;
1107 }
1108
1109 // sort the drawsurfs by sort type, then orientation, then shader
1110 R_RadixSort( drawSurfs, numDrawSurfs );
1111
1112 // check for any pass through drawing, which
1113 // may cause another view to be rendered first
1114 for ( i = 0 ; i < numDrawSurfs ; i++ ) {
1115 R_DecomposeSort( (drawSurfs+i)->sort, &entityNum, &shader, &fogNum, &dlighted );
1116
1117 if ( shader->sort > SS_PORTAL ) {
1118 break;
1119 }
1120
1121 // no shader should ever have this sort type
1122 if ( shader->sort == SS_BAD ) {
1123 ri.Error (ERR_DROP, "Shader '%s'with sort == SS_BAD", shader->name );
1124 }
1125
1126 // if the mirror was completely clipped away, we may need to check another surface
1127 if ( R_MirrorViewBySurface( (drawSurfs+i), entityNum) ) {
1128 // this is a debug option to see exactly what is being mirrored
1129 if ( r_portalOnly->integer ) {
1130 return;
1131 }
1132 break; // only one mirror view at a time
1133 }
1134 }
1135
1136 R_AddDrawSurfCmd( drawSurfs, numDrawSurfs );
1137 }
1138
1139 /*
1140 =============
1141 R_AddEntitySurfaces
1142 =============
1143 */
R_AddEntitySurfaces(void)1144 void R_AddEntitySurfaces (void) {
1145 trRefEntity_t *ent;
1146 shader_t *shader;
1147
1148 if ( !r_drawentities->integer ) {
1149 return;
1150 }
1151
1152 for ( tr.currentEntityNum = 0;
1153 tr.currentEntityNum < tr.refdef.num_entities;
1154 tr.currentEntityNum++ ) {
1155 ent = tr.currentEntity = &tr.refdef.entities[tr.currentEntityNum];
1156
1157 ent->needDlights = qfalse;
1158
1159 // preshift the value we are going to OR into the drawsurf sort
1160 tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT;
1161
1162 //
1163 // the weapon model must be handled special --
1164 // we don't want the hacked weapon position showing in
1165 // mirrors, because the true body position will already be drawn
1166 //
1167 if ( (ent->e.renderfx & RF_FIRST_PERSON) && tr.viewParms.isPortal) {
1168 continue;
1169 }
1170
1171 // simple generated models, like sprites and beams, are not culled
1172 switch ( ent->e.reType ) {
1173 case RT_PORTALSURFACE:
1174 break; // don't draw anything
1175 case RT_SPRITE:
1176 case RT_BEAM:
1177 case RT_LIGHTNING:
1178 case RT_RAIL_CORE:
1179 case RT_RAIL_RINGS:
1180 // self blood sprites, talk balloons, etc should not be drawn in the primary
1181 // view. We can't just do this check for all entities, because md3
1182 // entities may still want to cast shadows from them
1183 if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) {
1184 continue;
1185 }
1186 shader = R_GetShaderByHandle( ent->e.customShader );
1187 R_AddDrawSurf( &entitySurface, shader, R_SpriteFogNum( ent ), 0 );
1188 break;
1189
1190 case RT_MODEL:
1191 // we must set up parts of tr.or for model culling
1192 R_RotateForEntity( ent, &tr.viewParms, &tr.or );
1193
1194 tr.currentModel = R_GetModelByHandle( ent->e.hModel );
1195 if (!tr.currentModel) {
1196 R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0 );
1197 } else {
1198 switch ( tr.currentModel->type ) {
1199 case MOD_MESH:
1200 R_AddMD3Surfaces( ent );
1201 break;
1202 case MOD_MD4:
1203 R_AddAnimSurfaces( ent );
1204 break;
1205 #ifdef RAVENMD4
1206 case MOD_MDR:
1207 R_MDRAddAnimSurfaces( ent );
1208 break;
1209 #endif
1210 case MOD_BRUSH:
1211 R_AddBrushModelSurfaces( ent );
1212 break;
1213 case MOD_BAD: // null model axis
1214 if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) {
1215 break;
1216 }
1217 shader = R_GetShaderByHandle( ent->e.customShader );
1218 R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0 );
1219 break;
1220 default:
1221 ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad modeltype" );
1222 break;
1223 }
1224 }
1225 break;
1226 default:
1227 ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad reType" );
1228 }
1229 }
1230
1231 }
1232
1233
1234 /*
1235 ====================
1236 R_GenerateDrawSurfs
1237 ====================
1238 */
R_GenerateDrawSurfs(void)1239 void R_GenerateDrawSurfs( void ) {
1240 R_AddWorldSurfaces ();
1241
1242 R_AddPolygonSurfaces();
1243
1244 // set the projection matrix with the minimum zfar
1245 // now that we have the world bounded
1246 // this needs to be done before entities are
1247 // added, because they use the projection
1248 // matrix for lod calculation
1249 R_SetupProjection ();
1250
1251 R_AddEntitySurfaces ();
1252 }
1253
1254 /*
1255 ================
1256 R_DebugPolygon
1257 ================
1258 */
R_DebugPolygon(int color,int numPoints,float * points)1259 void R_DebugPolygon( int color, int numPoints, float *points ) {
1260 int i;
1261
1262 GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
1263
1264 // draw solid shade
1265
1266 qglColor3f( color&1, (color>>1)&1, (color>>2)&1 );
1267 qglBegin( GL_POLYGON );
1268 for ( i = 0 ; i < numPoints ; i++ ) {
1269 qglVertex3fv( points + i * 3 );
1270 }
1271 qglEnd();
1272
1273 // draw wireframe outline
1274 GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
1275 qglDepthRange( 0, 0 );
1276 qglColor3f( 1, 1, 1 );
1277 qglBegin( GL_POLYGON );
1278 for ( i = 0 ; i < numPoints ; i++ ) {
1279 qglVertex3fv( points + i * 3 );
1280 }
1281 qglEnd();
1282 qglDepthRange( 0, 1 );
1283 }
1284
1285 /*
1286 ====================
1287 R_DebugGraphics
1288
1289 Visualization aid for movement clipping debugging
1290 ====================
1291 */
R_DebugGraphics(void)1292 void R_DebugGraphics( void ) {
1293 if ( !r_debugSurface->integer ) {
1294 return;
1295 }
1296
1297 // the render thread can't make callbacks to the main thread
1298 R_SyncRenderThread();
1299
1300 GL_Bind( tr.whiteImage);
1301 GL_Cull( CT_FRONT_SIDED );
1302 ri.CM_DrawDebugSurface( R_DebugPolygon );
1303 }
1304
1305
1306 /*
1307 ================
1308 R_RenderView
1309
1310 A view may be either the actual camera view,
1311 or a mirror / remote location
1312 ================
1313 */
R_RenderView(viewParms_t * parms)1314 void R_RenderView (viewParms_t *parms) {
1315 int firstDrawSurf;
1316
1317 if ( parms->viewportWidth <= 0 || parms->viewportHeight <= 0 ) {
1318 return;
1319 }
1320
1321 tr.viewCount++;
1322
1323 tr.viewParms = *parms;
1324 tr.viewParms.frameSceneNum = tr.frameSceneNum;
1325 tr.viewParms.frameCount = tr.frameCount;
1326
1327 firstDrawSurf = tr.refdef.numDrawSurfs;
1328
1329 tr.viewCount++;
1330
1331 // set viewParms.world
1332 R_RotateForViewer ();
1333
1334 R_SetupFrustum ();
1335
1336 R_GenerateDrawSurfs();
1337
1338 R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, tr.refdef.numDrawSurfs - firstDrawSurf );
1339
1340 // draw main system development information (surface outlines, etc)
1341 R_DebugGraphics();
1342 }
1343
1344
1345
1346