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_flares.c
24
25 #include "tr_local.h"
26
27 /*
28 =============================================================================
29
30 LIGHT FLARES
31
32 A light flare is an effect that takes place inside the eye when bright light
33 sources are visible. The size of the flare reletive to the screen is nearly
34 constant, irrespective of distance, but the intensity should be proportional to the
35 projected area of the light source.
36
37 A surface that has been flagged as having a light flare will calculate the depth
38 buffer value that it's midpoint should have when the surface is added.
39
40 After all opaque surfaces have been rendered, the depth buffer is read back for
41 each flare in view. If the point has not been obscured by a closer surface, the
42 flare should be drawn.
43
44 Surfaces that have a repeated texture should never be flagged as flaring, because
45 there will only be a single flare added at the midpoint of the polygon.
46
47 To prevent abrupt popping, the intensity of the flare is interpolated up and
48 down as it changes visibility. This involves scene to scene state, unlike almost
49 all other aspects of the renderer, and is complicated by the fact that a single
50 frame may have multiple scenes.
51
52 RB_RenderFlares() will be called once per view (twice in a mirrored scene, potentially
53 up to five or more times in a frame with 3D status bar icons).
54
55 =============================================================================
56 */
57
58
59 // flare states maintain visibility over multiple frames for fading
60 // layers: view, mirror, menu
61 typedef struct flare_s {
62 struct flare_s *next; // for active chain
63
64 int addedFrame;
65
66 qboolean inPortal; // true if in a portal view of the scene
67 int frameSceneNum;
68 void *surface;
69 int fogNum;
70
71 int fadeTime;
72
73 qboolean visible; // state of last test
74 float drawIntensity; // may be non 0 even if !visible due to fading
75
76 int windowX, windowY;
77 float eyeZ;
78
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 /*
88 ==================
89 R_ClearFlares
90 ==================
91 */
R_ClearFlares(void)92 void R_ClearFlares( void ) {
93 int i;
94
95 Com_Memset( r_flareStructs, 0, sizeof( r_flareStructs ) );
96 r_activeFlares = NULL;
97 r_inactiveFlares = NULL;
98
99 for ( i = 0 ; i < MAX_FLARES ; i++ ) {
100 r_flareStructs[i].next = r_inactiveFlares;
101 r_inactiveFlares = &r_flareStructs[i];
102 }
103 }
104
105
106 /*
107 ==================
108 RB_AddFlare
109
110 This is called at surface tesselation time
111 ==================
112 */
RB_AddFlare(void * surface,int fogNum,vec3_t point,vec3_t color,vec3_t normal)113 void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ) {
114 int i;
115 flare_t *f, *oldest;
116 vec3_t local;
117 float d;
118 vec4_t eye, clip, normalized, window;
119
120 backEnd.pc.c_flareAdds++;
121
122 // if the point is off the screen, don't bother adding it
123 // calculate screen coordinates and depth
124 R_TransformModelToClip( point, backEnd.or.modelMatrix,
125 backEnd.viewParms.projectionMatrix, eye, clip );
126
127 // check to see if the point is completely off screen
128 for ( i = 0 ; i < 3 ; i++ ) {
129 if ( clip[i] >= clip[3] || clip[i] <= -clip[3] ) {
130 return;
131 }
132 }
133
134 R_TransformClipToWindow( clip, &backEnd.viewParms, normalized, window );
135
136 if ( window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth
137 || window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight ) {
138 return; // shouldn't happen, since we check the clip[] above, except for FP rounding
139 }
140
141 // see if a flare with a matching surface, scene, and view exists
142 oldest = r_flareStructs;
143 for ( f = r_activeFlares ; f ; f = f->next ) {
144 if ( f->surface == surface && f->frameSceneNum == backEnd.viewParms.frameSceneNum
145 && f->inPortal == backEnd.viewParms.isPortal ) {
146 break;
147 }
148 }
149
150 // allocate a new one
151 if (!f ) {
152 if ( !r_inactiveFlares ) {
153 // the list is completely full
154 return;
155 }
156 f = r_inactiveFlares;
157 r_inactiveFlares = r_inactiveFlares->next;
158 f->next = r_activeFlares;
159 r_activeFlares = f;
160
161 f->surface = surface;
162 f->frameSceneNum = backEnd.viewParms.frameSceneNum;
163 f->inPortal = backEnd.viewParms.isPortal;
164 f->addedFrame = -1;
165 }
166
167 if ( f->addedFrame != backEnd.viewParms.frameCount - 1 ) {
168 f->visible = qfalse;
169 f->fadeTime = backEnd.refdef.time - 2000;
170 }
171
172 f->addedFrame = backEnd.viewParms.frameCount;
173 f->fogNum = fogNum;
174
175 VectorCopy( color, f->color );
176
177 // fade the intensity of the flare down as the
178 // light surface turns away from the viewer
179 if ( normal ) {
180 VectorSubtract( backEnd.viewParms.or.origin, point, local );
181 VectorNormalizeFast( local );
182 d = DotProduct( local, normal );
183 VectorScale( f->color, d, f->color );
184 }
185
186 // save info needed to test
187 f->windowX = backEnd.viewParms.viewportX + window[0];
188 f->windowY = backEnd.viewParms.viewportY + window[1];
189
190 f->eyeZ = eye[2];
191 }
192
193 /*
194 ==================
195 RB_AddDlightFlares
196 ==================
197 */
RB_AddDlightFlares(void)198 void RB_AddDlightFlares( void ) {
199 dlight_t *l;
200 int i, j, k;
201 fog_t *fog;
202
203 if ( !r_flares->integer ) {
204 return;
205 }
206
207 l = backEnd.refdef.dlights;
208 fog = tr.world->fogs;
209 for (i=0 ; i<backEnd.refdef.num_dlights ; i++, l++) {
210
211 // find which fog volume the light is in
212 for ( j = 1 ; j < tr.world->numfogs ; j++ ) {
213 fog = &tr.world->fogs[j];
214 for ( k = 0 ; k < 3 ; k++ ) {
215 if ( l->origin[k] < fog->bounds[0][k] || l->origin[k] > fog->bounds[1][k] ) {
216 break;
217 }
218 }
219 if ( k == 3 ) {
220 break;
221 }
222 }
223 if ( j == tr.world->numfogs ) {
224 j = 0;
225 }
226
227 RB_AddFlare( (void *)l, j, l->origin, l->color, NULL );
228 }
229 }
230
231 /*
232 ===============================================================================
233
234 FLARE BACK END
235
236 ===============================================================================
237 */
238
239 /*
240 ==================
241 RB_TestFlare
242 ==================
243 */
RB_TestFlare(flare_t * f)244 void RB_TestFlare( flare_t *f ) {
245 float depth;
246 qboolean visible;
247 float fade;
248 float screenZ;
249
250 backEnd.pc.c_flareTests++;
251
252 // doing a readpixels is as good as doing a glFinish(), so
253 // don't bother with another sync
254 glState.finishCalled = qfalse;
255
256 // read back the z buffer contents
257 qglReadPixels( f->windowX, f->windowY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth );
258
259 screenZ = backEnd.viewParms.projectionMatrix[14] /
260 ( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] );
261
262 visible = ( -f->eyeZ - -screenZ ) < 24;
263
264 if ( visible ) {
265 if ( !f->visible ) {
266 f->visible = qtrue;
267 f->fadeTime = backEnd.refdef.time - 1;
268 }
269 fade = ( ( backEnd.refdef.time - f->fadeTime ) /1000.0f ) * r_flareFade->value;
270 } else {
271 if ( f->visible ) {
272 f->visible = qfalse;
273 f->fadeTime = backEnd.refdef.time - 1;
274 }
275 fade = 1.0f - ( ( backEnd.refdef.time - f->fadeTime ) / 1000.0f ) * r_flareFade->value;
276 }
277
278 if ( fade < 0 ) {
279 fade = 0;
280 }
281 if ( fade > 1 ) {
282 fade = 1;
283 }
284
285 f->drawIntensity = fade;
286 }
287
288
289 /*
290 ==================
291 RB_RenderFlare
292 ==================
293 */
RB_RenderFlare(flare_t * f)294 void RB_RenderFlare( flare_t *f ) {
295 float size;
296 vec3_t color;
297 int iColor[3];
298
299 backEnd.pc.c_flareRenders++;
300
301 VectorScale( f->color, f->drawIntensity*tr.identityLight, color );
302 iColor[0] = color[0] * 255;
303 iColor[1] = color[1] * 255;
304 iColor[2] = color[2] * 255;
305
306 size = backEnd.viewParms.viewportWidth * ( r_flareSize->value/640.0f + 8 / -f->eyeZ );
307
308 RB_BeginSurface( tr.flareShader, f->fogNum );
309
310 // FIXME: use quadstamp?
311 tess.xyz[tess.numVertexes][0] = f->windowX - size;
312 tess.xyz[tess.numVertexes][1] = f->windowY - size;
313 tess.texCoords[tess.numVertexes][0][0] = 0;
314 tess.texCoords[tess.numVertexes][0][1] = 0;
315 tess.vertexColors[tess.numVertexes][0] = iColor[0];
316 tess.vertexColors[tess.numVertexes][1] = iColor[1];
317 tess.vertexColors[tess.numVertexes][2] = iColor[2];
318 tess.vertexColors[tess.numVertexes][3] = 255;
319 tess.numVertexes++;
320
321 tess.xyz[tess.numVertexes][0] = f->windowX - size;
322 tess.xyz[tess.numVertexes][1] = f->windowY + size;
323 tess.texCoords[tess.numVertexes][0][0] = 0;
324 tess.texCoords[tess.numVertexes][0][1] = 1;
325 tess.vertexColors[tess.numVertexes][0] = iColor[0];
326 tess.vertexColors[tess.numVertexes][1] = iColor[1];
327 tess.vertexColors[tess.numVertexes][2] = iColor[2];
328 tess.vertexColors[tess.numVertexes][3] = 255;
329 tess.numVertexes++;
330
331 tess.xyz[tess.numVertexes][0] = f->windowX + size;
332 tess.xyz[tess.numVertexes][1] = f->windowY + size;
333 tess.texCoords[tess.numVertexes][0][0] = 1;
334 tess.texCoords[tess.numVertexes][0][1] = 1;
335 tess.vertexColors[tess.numVertexes][0] = iColor[0];
336 tess.vertexColors[tess.numVertexes][1] = iColor[1];
337 tess.vertexColors[tess.numVertexes][2] = iColor[2];
338 tess.vertexColors[tess.numVertexes][3] = 255;
339 tess.numVertexes++;
340
341 tess.xyz[tess.numVertexes][0] = f->windowX + size;
342 tess.xyz[tess.numVertexes][1] = f->windowY - size;
343 tess.texCoords[tess.numVertexes][0][0] = 1;
344 tess.texCoords[tess.numVertexes][0][1] = 0;
345 tess.vertexColors[tess.numVertexes][0] = iColor[0];
346 tess.vertexColors[tess.numVertexes][1] = iColor[1];
347 tess.vertexColors[tess.numVertexes][2] = iColor[2];
348 tess.vertexColors[tess.numVertexes][3] = 255;
349 tess.numVertexes++;
350
351 tess.indexes[tess.numIndexes++] = 0;
352 tess.indexes[tess.numIndexes++] = 1;
353 tess.indexes[tess.numIndexes++] = 2;
354 tess.indexes[tess.numIndexes++] = 0;
355 tess.indexes[tess.numIndexes++] = 2;
356 tess.indexes[tess.numIndexes++] = 3;
357
358 RB_EndSurface();
359 }
360
361 /*
362 ==================
363 RB_RenderFlares
364
365 Because flares are simulating an occular effect, they should be drawn after
366 everything (all views) in the entire frame has been drawn.
367
368 Because of the way portals use the depth buffer to mark off areas, the
369 needed information would be lost after each view, so we are forced to draw
370 flares after each view.
371
372 The resulting artifact is that flares in mirrors or portals don't dim properly
373 when occluded by something in the main view, and portal flares that should
374 extend past the portal edge will be overwritten.
375 ==================
376 */
RB_RenderFlares(void)377 void RB_RenderFlares (void) {
378 flare_t *f;
379 flare_t **prev;
380 qboolean draw;
381
382 if ( !r_flares->integer ) {
383 return;
384 }
385
386 // RB_AddDlightFlares();
387
388 // perform z buffer readback on each flare in this view
389 draw = qfalse;
390 prev = &r_activeFlares;
391 while ( ( f = *prev ) != NULL ) {
392 // throw out any flares that weren't added last frame
393 if ( f->addedFrame < backEnd.viewParms.frameCount - 1 ) {
394 *prev = f->next;
395 f->next = r_inactiveFlares;
396 r_inactiveFlares = f;
397 continue;
398 }
399
400 // don't draw any here that aren't from this scene / portal
401 f->drawIntensity = 0;
402 if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum
403 && f->inPortal == backEnd.viewParms.isPortal ) {
404 RB_TestFlare( f );
405 if ( f->drawIntensity ) {
406 draw = qtrue;
407 } else {
408 // this flare has completely faded out, so remove it from the chain
409 *prev = f->next;
410 f->next = r_inactiveFlares;
411 r_inactiveFlares = f;
412 continue;
413 }
414 }
415
416 prev = &f->next;
417 }
418
419 if ( !draw ) {
420 return; // none visible
421 }
422
423 if ( backEnd.viewParms.isPortal ) {
424 qglDisable (GL_CLIP_PLANE0);
425 }
426
427 qglPushMatrix();
428 qglLoadIdentity();
429 qglMatrixMode( GL_PROJECTION );
430 qglPushMatrix();
431 qglLoadIdentity();
432 qglOrtho( backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth,
433 backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight,
434 -99999, 99999 );
435
436 for ( f = r_activeFlares ; f ; f = f->next ) {
437 if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum
438 && f->inPortal == backEnd.viewParms.isPortal
439 && f->drawIntensity ) {
440 RB_RenderFlare( f );
441 }
442 }
443
444 qglPopMatrix();
445 qglMatrixMode( GL_MODELVIEW );
446 qglPopMatrix();
447 }
448
449