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