1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22 // tr_flares.c
23
24 #include "tr_local.h"
25
26 /*
27 =============================================================================
28
29 LIGHT FLARES
30
31 A light flare is an effect that takes place inside the eye when bright light
32 sources are visible. The size of the flare reletive to the screen is nearly
33 constant, irrespective of distance, but the intensity should be proportional to the
34 projected area of the light source.
35
36 A surface that has been flagged as having a light flare will calculate the depth
37 buffer value that it's midpoint should have when the surface is added.
38
39 After all opaque surfaces have been rendered, the depth buffer is read back for
40 each flare in view. If the point has not been obscured by a closer surface, the
41 flare should be drawn.
42
43 Surfaces that have a repeated texture should never be flagged as flaring, because
44 there will only be a single flare added at the midpoint of the polygon.
45
46 To prevent abrupt popping, the intensity of the flare is interpolated up and
47 down as it changes visibility. This involves scene to scene state, unlike almost
48 all other aspects of the renderer, and is complicated by the fact that a single
49 frame may have multiple scenes.
50
51 RB_RenderFlares() will be called once per view (twice in a mirrored scene, potentially
52 up to five or more times in a frame with 3D status bar icons).
53
54 =============================================================================
55 */
56
57
58 // flare states maintain visibility over multiple frames for fading
59 // layers: view, mirror, menu
60 typedef struct flare_s {
61 struct flare_s *next; // for active chain
62
63 int addedFrame;
64
65 qboolean inPortal; // true if in a portal view of the scene
66 int frameSceneNum;
67 void *surface;
68 int fogNum;
69
70 int fadeTime;
71
72 qboolean visible; // state of last test
73 float drawIntensity; // may be non 0 even if !visible due to fading
74
75 int windowX, windowY;
76 float eyeZ;
77
78 vec3_t origin;
79 vec3_t color;
80 } flare_t;
81
82 #define MAX_FLARES 128
83
84 flare_t r_flareStructs[MAX_FLARES];
85 flare_t *r_activeFlares, *r_inactiveFlares;
86
87 int flareCoeff;
88
89 /*
90 ==================
91 R_ClearFlares
92 ==================
93 */
R_ClearFlares(void)94 void R_ClearFlares( void ) {
95 int i;
96
97 Com_Memset( r_flareStructs, 0, sizeof( r_flareStructs ) );
98 r_activeFlares = NULL;
99 r_inactiveFlares = NULL;
100
101 for ( i = 0 ; i < MAX_FLARES ; i++ ) {
102 r_flareStructs[i].next = r_inactiveFlares;
103 r_inactiveFlares = &r_flareStructs[i];
104 }
105 }
106
107
108 /*
109 ==================
110 RB_AddFlare
111
112 This is called at surface tesselation time
113 ==================
114 */
RB_AddFlare(void * surface,int fogNum,vec3_t point,vec3_t color,vec3_t normal)115 void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ) {
116 int i;
117 flare_t *f, *oldest;
118 vec3_t local;
119 float d = 1;
120 vec4_t eye, clip, normalized, window;
121
122 backEnd.pc.c_flareAdds++;
123
124 if(normal && (normal[0] || normal[1] || normal[2]))
125 {
126 VectorSubtract( backEnd.viewParms.or.origin, point, local );
127 VectorNormalizeFast(local);
128 d = DotProduct(local, normal);
129
130 // If the viewer is behind the flare don't add it.
131 if(d < 0)
132 return;
133 }
134
135 // if the point is off the screen, don't bother adding it
136 // calculate screen coordinates and depth
137 R_TransformModelToClip( point, backEnd.or.modelMatrix,
138 backEnd.viewParms.projectionMatrix, eye, clip );
139
140 // check to see if the point is completely off screen
141 for ( i = 0 ; i < 3 ; i++ ) {
142 if ( clip[i] >= clip[3] || clip[i] <= -clip[3] ) {
143 return;
144 }
145 }
146
147 R_TransformClipToWindow( clip, &backEnd.viewParms, normalized, window );
148
149 if ( window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth
150 || window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight ) {
151 return; // shouldn't happen, since we check the clip[] above, except for FP rounding
152 }
153
154 // see if a flare with a matching surface, scene, and view exists
155 oldest = r_flareStructs;
156 for ( f = r_activeFlares ; f ; f = f->next ) {
157 if ( f->surface == surface && f->frameSceneNum == backEnd.viewParms.frameSceneNum
158 && f->inPortal == backEnd.viewParms.isPortal ) {
159 break;
160 }
161 }
162
163 // allocate a new one
164 if (!f ) {
165 if ( !r_inactiveFlares ) {
166 // the list is completely full
167 return;
168 }
169 f = r_inactiveFlares;
170 r_inactiveFlares = r_inactiveFlares->next;
171 f->next = r_activeFlares;
172 r_activeFlares = f;
173
174 f->surface = surface;
175 f->frameSceneNum = backEnd.viewParms.frameSceneNum;
176 f->inPortal = backEnd.viewParms.isPortal;
177 f->addedFrame = -1;
178 }
179
180 if ( f->addedFrame != backEnd.viewParms.frameCount - 1 ) {
181 f->visible = qfalse;
182 f->fadeTime = backEnd.refdef.time - 2000;
183 }
184
185 f->addedFrame = backEnd.viewParms.frameCount;
186 f->fogNum = fogNum;
187
188 VectorCopy(point, f->origin);
189 VectorCopy( color, f->color );
190
191 // fade the intensity of the flare down as the
192 // light surface turns away from the viewer
193 VectorScale( f->color, d, f->color );
194
195 // save info needed to test
196 f->windowX = backEnd.viewParms.viewportX + window[0];
197 f->windowY = backEnd.viewParms.viewportY + window[1];
198
199 f->eyeZ = eye[2];
200 }
201
202 /*
203 ==================
204 RB_AddDlightFlares
205 ==================
206 */
RB_AddDlightFlares(void)207 void RB_AddDlightFlares( void ) {
208 dlight_t *l;
209 int i, j, k;
210 fog_t *fog = NULL;
211
212 if ( !r_flares->integer ) {
213 return;
214 }
215
216 l = backEnd.refdef.dlights;
217
218 if(tr.world)
219 fog = tr.world->fogs;
220
221 for (i=0 ; i<backEnd.refdef.num_dlights ; i++, l++) {
222
223 if(fog)
224 {
225 // find which fog volume the light is in
226 for ( j = 1 ; j < tr.world->numfogs ; j++ ) {
227 fog = &tr.world->fogs[j];
228 for ( k = 0 ; k < 3 ; k++ ) {
229 if ( l->origin[k] < fog->bounds[0][k] || l->origin[k] > fog->bounds[1][k] ) {
230 break;
231 }
232 }
233 if ( k == 3 ) {
234 break;
235 }
236 }
237 if ( j == tr.world->numfogs ) {
238 j = 0;
239 }
240 }
241 else
242 j = 0;
243
244 RB_AddFlare( (void *)l, j, l->origin, l->color, NULL );
245 }
246 }
247
248 /*
249 ===============================================================================
250
251 FLARE BACK END
252
253 ===============================================================================
254 */
255
256 /*
257 ==================
258 RB_TestFlare
259 ==================
260 */
RB_TestFlare(flare_t * f)261 void RB_TestFlare( flare_t *f ) {
262 float depth;
263 qboolean visible;
264 float fade;
265 float screenZ;
266
267 backEnd.pc.c_flareTests++;
268
269 // doing a readpixels is as good as doing a glFinish(), so
270 // don't bother with another sync
271 glState.finishCalled = qfalse;
272
273 // read back the z buffer contents
274 qglReadPixels( f->windowX, f->windowY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth );
275
276 screenZ = backEnd.viewParms.projectionMatrix[14] /
277 ( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] );
278
279 visible = ( -f->eyeZ - -screenZ ) < 24;
280
281 if ( visible ) {
282 if ( !f->visible ) {
283 f->visible = qtrue;
284 f->fadeTime = backEnd.refdef.time - 1;
285 }
286 fade = ( ( backEnd.refdef.time - f->fadeTime ) /1000.0f ) * r_flareFade->value;
287 } else {
288 if ( f->visible ) {
289 f->visible = qfalse;
290 f->fadeTime = backEnd.refdef.time - 1;
291 }
292 fade = 1.0f - ( ( backEnd.refdef.time - f->fadeTime ) / 1000.0f ) * r_flareFade->value;
293 }
294
295 if ( fade < 0 ) {
296 fade = 0;
297 }
298 if ( fade > 1 ) {
299 fade = 1;
300 }
301
302 f->drawIntensity = fade;
303 }
304
305
306 /*
307 ==================
308 RB_RenderFlare
309 ==================
310 */
RB_RenderFlare(flare_t * f)311 void RB_RenderFlare( flare_t *f ) {
312 float size;
313 vec3_t color;
314 int iColor[3];
315 float distance, intensity, factor;
316 byte fogFactors[3] = {255, 255, 255};
317
318 backEnd.pc.c_flareRenders++;
319
320 // We don't want too big values anyways when dividing by distance.
321 if(f->eyeZ > -1.0f)
322 distance = 1.0f;
323 else
324 distance = -f->eyeZ;
325
326 // calculate the flare size..
327 size = backEnd.viewParms.viewportWidth * ( r_flareSize->value/640.0f + 8 / distance );
328
329 /*
330 * This is an alternative to intensity scaling. It changes the size of the flare on screen instead
331 * with growing distance. See in the description at the top why this is not the way to go.
332 // size will change ~ 1/r.
333 size = backEnd.viewParms.viewportWidth * (r_flareSize->value / (distance * -2.0f));
334 */
335
336 /*
337 * As flare sizes stay nearly constant with increasing distance we must decrease the intensity
338 * to achieve a reasonable visual result. The intensity is ~ (size^2 / distance^2) which can be
339 * got by considering the ratio of
340 * (flaresurface on screen) : (Surface of sphere defined by flare origin and distance from flare)
341 * An important requirement is:
342 * intensity <= 1 for all distances.
343 *
344 * The formula used here to compute the intensity is as follows:
345 * intensity = flareCoeff * size^2 / (distance + size*sqrt(flareCoeff))^2
346 * As you can see, the intensity will have a max. of 1 when the distance is 0.
347 * The coefficient flareCoeff will determine the falloff speed with increasing distance.
348 */
349
350 factor = distance + size * sqrt(flareCoeff);
351
352 intensity = flareCoeff * size * size / (factor * factor);
353
354 VectorScale(f->color, f->drawIntensity * intensity, color);
355
356 // Calculations for fogging
357 if(tr.world && f->fogNum < tr.world->numfogs)
358 {
359 tess.numVertexes = 1;
360 VectorCopy(f->origin, tess.xyz[0]);
361 tess.fogNum = f->fogNum;
362
363 RB_CalcModulateColorsByFog(fogFactors);
364
365 // We don't need to render the flare if colors are 0 anyways.
366 if(!(fogFactors[0] || fogFactors[1] || fogFactors[2]))
367 return;
368 }
369
370 iColor[0] = color[0] * fogFactors[0];
371 iColor[1] = color[1] * fogFactors[1];
372 iColor[2] = color[2] * fogFactors[2];
373
374 RB_BeginSurface( tr.flareShader, f->fogNum );
375
376 // FIXME: use quadstamp?
377 tess.xyz[tess.numVertexes][0] = f->windowX - size;
378 tess.xyz[tess.numVertexes][1] = f->windowY - size;
379 tess.texCoords[tess.numVertexes][0][0] = 0;
380 tess.texCoords[tess.numVertexes][0][1] = 0;
381 tess.vertexColors[tess.numVertexes][0] = iColor[0];
382 tess.vertexColors[tess.numVertexes][1] = iColor[1];
383 tess.vertexColors[tess.numVertexes][2] = iColor[2];
384 tess.vertexColors[tess.numVertexes][3] = 255;
385 tess.numVertexes++;
386
387 tess.xyz[tess.numVertexes][0] = f->windowX - size;
388 tess.xyz[tess.numVertexes][1] = f->windowY + size;
389 tess.texCoords[tess.numVertexes][0][0] = 0;
390 tess.texCoords[tess.numVertexes][0][1] = 1;
391 tess.vertexColors[tess.numVertexes][0] = iColor[0];
392 tess.vertexColors[tess.numVertexes][1] = iColor[1];
393 tess.vertexColors[tess.numVertexes][2] = iColor[2];
394 tess.vertexColors[tess.numVertexes][3] = 255;
395 tess.numVertexes++;
396
397 tess.xyz[tess.numVertexes][0] = f->windowX + size;
398 tess.xyz[tess.numVertexes][1] = f->windowY + size;
399 tess.texCoords[tess.numVertexes][0][0] = 1;
400 tess.texCoords[tess.numVertexes][0][1] = 1;
401 tess.vertexColors[tess.numVertexes][0] = iColor[0];
402 tess.vertexColors[tess.numVertexes][1] = iColor[1];
403 tess.vertexColors[tess.numVertexes][2] = iColor[2];
404 tess.vertexColors[tess.numVertexes][3] = 255;
405 tess.numVertexes++;
406
407 tess.xyz[tess.numVertexes][0] = f->windowX + size;
408 tess.xyz[tess.numVertexes][1] = f->windowY - size;
409 tess.texCoords[tess.numVertexes][0][0] = 1;
410 tess.texCoords[tess.numVertexes][0][1] = 0;
411 tess.vertexColors[tess.numVertexes][0] = iColor[0];
412 tess.vertexColors[tess.numVertexes][1] = iColor[1];
413 tess.vertexColors[tess.numVertexes][2] = iColor[2];
414 tess.vertexColors[tess.numVertexes][3] = 255;
415 tess.numVertexes++;
416
417 tess.indexes[tess.numIndexes++] = 0;
418 tess.indexes[tess.numIndexes++] = 1;
419 tess.indexes[tess.numIndexes++] = 2;
420 tess.indexes[tess.numIndexes++] = 0;
421 tess.indexes[tess.numIndexes++] = 2;
422 tess.indexes[tess.numIndexes++] = 3;
423
424 RB_EndSurface();
425 }
426
427 /*
428 ==================
429 RB_RenderFlares
430
431 Because flares are simulating an occular effect, they should be drawn after
432 everything (all views) in the entire frame has been drawn.
433
434 Because of the way portals use the depth buffer to mark off areas, the
435 needed information would be lost after each view, so we are forced to draw
436 flares after each view.
437
438 The resulting artifact is that flares in mirrors or portals don't dim properly
439 when occluded by something in the main view, and portal flares that should
440 extend past the portal edge will be overwritten.
441 ==================
442 */
RB_RenderFlares(void)443 void RB_RenderFlares (void) {
444 flare_t *f;
445 flare_t **prev;
446 qboolean draw;
447
448 if ( !r_flares->integer ) {
449 return;
450 }
451
452 if(r_flareCoeff->modified)
453 {
454 if(r_flareCoeff->value == 0.0f)
455 flareCoeff = atof(FLARE_STDCOEFF);
456 else
457 flareCoeff = r_flareCoeff->value;
458
459 r_flareCoeff->modified = qfalse;
460 }
461
462 // Reset currentEntity to world so that any previously referenced entities
463 // don't have influence on the rendering of these flares (i.e. RF_ renderer flags).
464 backEnd.currentEntity = &tr.worldEntity;
465 backEnd.or = backEnd.viewParms.world;
466
467 // RB_AddDlightFlares();
468
469 // perform z buffer readback on each flare in this view
470 draw = qfalse;
471 prev = &r_activeFlares;
472 while ( ( f = *prev ) != NULL ) {
473 // throw out any flares that weren't added last frame
474 if ( f->addedFrame < backEnd.viewParms.frameCount - 1 ) {
475 *prev = f->next;
476 f->next = r_inactiveFlares;
477 r_inactiveFlares = f;
478 continue;
479 }
480
481 // don't draw any here that aren't from this scene / portal
482 f->drawIntensity = 0;
483 if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum
484 && f->inPortal == backEnd.viewParms.isPortal ) {
485 RB_TestFlare( f );
486 if ( f->drawIntensity ) {
487 draw = qtrue;
488 } else {
489 // this flare has completely faded out, so remove it from the chain
490 *prev = f->next;
491 f->next = r_inactiveFlares;
492 r_inactiveFlares = f;
493 continue;
494 }
495 }
496
497 prev = &f->next;
498 }
499
500 if ( !draw ) {
501 return; // none visible
502 }
503
504 if ( backEnd.viewParms.isPortal ) {
505 qglDisable (GL_CLIP_PLANE0);
506 }
507
508 qglPushMatrix();
509 qglLoadIdentity();
510 qglMatrixMode( GL_PROJECTION );
511 qglPushMatrix();
512 qglLoadIdentity();
513 qglOrtho( backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth,
514 backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight,
515 -99999, 99999 );
516
517 for ( f = r_activeFlares ; f ; f = f->next ) {
518 if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum
519 && f->inPortal == backEnd.viewParms.isPortal
520 && f->drawIntensity ) {
521 RB_RenderFlare( f );
522 }
523 }
524
525 qglPopMatrix();
526 qglMatrixMode( GL_MODELVIEW );
527 qglPopMatrix();
528 }
529
530