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