123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- /*
- ===========================================================================
- Copyright (C) 1999-2005 Id Software, Inc.
- This file is part of Quake III Arena source code.
- Quake III Arena source code is free software; you can redistribute it
- and/or modify it under the terms of the GNU General Public License as
- published by the Free Software Foundation; either version 2 of the License,
- or (at your option) any later version.
- Quake III Arena source code is distributed in the hope that it will be
- useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with Foobar; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- ===========================================================================
- */
- // tr_flares.c
- #include "tr_local.h"
- /*
- =============================================================================
- LIGHT FLARES
- A light flare is an effect that takes place inside the eye when bright light
- sources are visible. The size of the flare reletive to the screen is nearly
- constant, irrespective of distance, but the intensity should be proportional to the
- projected area of the light source.
- A surface that has been flagged as having a light flare will calculate the depth
- buffer value that it's midpoint should have when the surface is added.
- After all opaque surfaces have been rendered, the depth buffer is read back for
- each flare in view. If the point has not been obscured by a closer surface, the
- flare should be drawn.
- Surfaces that have a repeated texture should never be flagged as flaring, because
- there will only be a single flare added at the midpoint of the polygon.
- To prevent abrupt popping, the intensity of the flare is interpolated up and
- down as it changes visibility. This involves scene to scene state, unlike almost
- all other aspects of the renderer, and is complicated by the fact that a single
- frame may have multiple scenes.
- RB_RenderFlares() will be called once per view (twice in a mirrored scene, potentially
- up to five or more times in a frame with 3D status bar icons).
- =============================================================================
- */
- // flare states maintain visibility over multiple frames for fading
- // layers: view, mirror, menu
- typedef struct flare_s {
- struct flare_s *next; // for active chain
- int addedFrame;
- qboolean inPortal; // true if in a portal view of the scene
- int frameSceneNum;
- void *surface;
- int fogNum;
- int fadeTime;
- qboolean visible; // state of last test
- float drawIntensity; // may be non 0 even if !visible due to fading
- int windowX, windowY;
- float eyeZ;
- vec3_t color;
- } flare_t;
- #define MAX_FLARES 128
- flare_t r_flareStructs[MAX_FLARES];
- flare_t *r_activeFlares, *r_inactiveFlares;
- /*
- ==================
- R_ClearFlares
- ==================
- */
- void R_ClearFlares( void ) {
- int i;
- Com_Memset( r_flareStructs, 0, sizeof( r_flareStructs ) );
- r_activeFlares = NULL;
- r_inactiveFlares = NULL;
- for ( i = 0 ; i < MAX_FLARES ; i++ ) {
- r_flareStructs[i].next = r_inactiveFlares;
- r_inactiveFlares = &r_flareStructs[i];
- }
- }
- /*
- ==================
- RB_AddFlare
- This is called at surface tesselation time
- ==================
- */
- void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ) {
- int i;
- flare_t *f, *oldest;
- vec3_t local;
- float d;
- vec4_t eye, clip, normalized, window;
- backEnd.pc.c_flareAdds++;
- // if the point is off the screen, don't bother adding it
- // calculate screen coordinates and depth
- R_TransformModelToClip( point, backEnd.or.modelMatrix,
- backEnd.viewParms.projectionMatrix, eye, clip );
- // check to see if the point is completely off screen
- for ( i = 0 ; i < 3 ; i++ ) {
- if ( clip[i] >= clip[3] || clip[i] <= -clip[3] ) {
- return;
- }
- }
- R_TransformClipToWindow( clip, &backEnd.viewParms, normalized, window );
- if ( window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth
- || window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight ) {
- return; // shouldn't happen, since we check the clip[] above, except for FP rounding
- }
- // see if a flare with a matching surface, scene, and view exists
- oldest = r_flareStructs;
- for ( f = r_activeFlares ; f ; f = f->next ) {
- if ( f->surface == surface && f->frameSceneNum == backEnd.viewParms.frameSceneNum
- && f->inPortal == backEnd.viewParms.isPortal ) {
- break;
- }
- }
- // allocate a new one
- if (!f ) {
- if ( !r_inactiveFlares ) {
- // the list is completely full
- return;
- }
- f = r_inactiveFlares;
- r_inactiveFlares = r_inactiveFlares->next;
- f->next = r_activeFlares;
- r_activeFlares = f;
- f->surface = surface;
- f->frameSceneNum = backEnd.viewParms.frameSceneNum;
- f->inPortal = backEnd.viewParms.isPortal;
- f->addedFrame = -1;
- }
- if ( f->addedFrame != backEnd.viewParms.frameCount - 1 ) {
- f->visible = qfalse;
- f->fadeTime = backEnd.refdef.time - 2000;
- }
- f->addedFrame = backEnd.viewParms.frameCount;
- f->fogNum = fogNum;
- VectorCopy( color, f->color );
- // fade the intensity of the flare down as the
- // light surface turns away from the viewer
- if ( normal ) {
- VectorSubtract( backEnd.viewParms.or.origin, point, local );
- VectorNormalizeFast( local );
- d = DotProduct( local, normal );
- VectorScale( f->color, d, f->color );
- }
- // save info needed to test
- f->windowX = backEnd.viewParms.viewportX + window[0];
- f->windowY = backEnd.viewParms.viewportY + window[1];
- f->eyeZ = eye[2];
- }
- /*
- ==================
- RB_AddDlightFlares
- ==================
- */
- void RB_AddDlightFlares( void ) {
- dlight_t *l;
- int i, j, k;
- fog_t *fog;
- if ( !r_flares->integer ) {
- return;
- }
- l = backEnd.refdef.dlights;
- fog = tr.world->fogs;
- for (i=0 ; i<backEnd.refdef.num_dlights ; i++, l++) {
- // find which fog volume the light is in
- for ( j = 1 ; j < tr.world->numfogs ; j++ ) {
- fog = &tr.world->fogs[j];
- for ( k = 0 ; k < 3 ; k++ ) {
- if ( l->origin[k] < fog->bounds[0][k] || l->origin[k] > fog->bounds[1][k] ) {
- break;
- }
- }
- if ( k == 3 ) {
- break;
- }
- }
- if ( j == tr.world->numfogs ) {
- j = 0;
- }
- RB_AddFlare( (void *)l, j, l->origin, l->color, NULL );
- }
- }
- /*
- ===============================================================================
- FLARE BACK END
- ===============================================================================
- */
- /*
- ==================
- RB_TestFlare
- ==================
- */
- void RB_TestFlare( flare_t *f ) {
- float depth;
- qboolean visible;
- float fade;
- float screenZ;
- backEnd.pc.c_flareTests++;
- // doing a readpixels is as good as doing a glFinish(), so
- // don't bother with another sync
- glState.finishCalled = qfalse;
- // read back the z buffer contents
- qglReadPixels( f->windowX, f->windowY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth );
- screenZ = backEnd.viewParms.projectionMatrix[14] /
- ( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] );
- visible = ( -f->eyeZ - -screenZ ) < 24;
- if ( visible ) {
- if ( !f->visible ) {
- f->visible = qtrue;
- f->fadeTime = backEnd.refdef.time - 1;
- }
- fade = ( ( backEnd.refdef.time - f->fadeTime ) /1000.0f ) * r_flareFade->value;
- } else {
- if ( f->visible ) {
- f->visible = qfalse;
- f->fadeTime = backEnd.refdef.time - 1;
- }
- fade = 1.0f - ( ( backEnd.refdef.time - f->fadeTime ) / 1000.0f ) * r_flareFade->value;
- }
- if ( fade < 0 ) {
- fade = 0;
- }
- if ( fade > 1 ) {
- fade = 1;
- }
- f->drawIntensity = fade;
- }
- /*
- ==================
- RB_RenderFlare
- ==================
- */
- void RB_RenderFlare( flare_t *f ) {
- float size;
- vec3_t color;
- int iColor[3];
- backEnd.pc.c_flareRenders++;
- VectorScale( f->color, f->drawIntensity*tr.identityLight, color );
- iColor[0] = color[0] * 255;
- iColor[1] = color[1] * 255;
- iColor[2] = color[2] * 255;
- size = backEnd.viewParms.viewportWidth * ( r_flareSize->value/640.0f + 8 / -f->eyeZ );
- RB_BeginSurface( tr.flareShader, f->fogNum );
- // FIXME: use quadstamp?
- tess.xyz[tess.numVertexes][0] = f->windowX - size;
- tess.xyz[tess.numVertexes][1] = f->windowY - size;
- tess.texCoords[tess.numVertexes][0][0] = 0;
- tess.texCoords[tess.numVertexes][0][1] = 0;
- tess.vertexColors[tess.numVertexes][0] = iColor[0];
- tess.vertexColors[tess.numVertexes][1] = iColor[1];
- tess.vertexColors[tess.numVertexes][2] = iColor[2];
- tess.vertexColors[tess.numVertexes][3] = 255;
- tess.numVertexes++;
- tess.xyz[tess.numVertexes][0] = f->windowX - size;
- tess.xyz[tess.numVertexes][1] = f->windowY + size;
- tess.texCoords[tess.numVertexes][0][0] = 0;
- tess.texCoords[tess.numVertexes][0][1] = 1;
- tess.vertexColors[tess.numVertexes][0] = iColor[0];
- tess.vertexColors[tess.numVertexes][1] = iColor[1];
- tess.vertexColors[tess.numVertexes][2] = iColor[2];
- tess.vertexColors[tess.numVertexes][3] = 255;
- tess.numVertexes++;
- tess.xyz[tess.numVertexes][0] = f->windowX + size;
- tess.xyz[tess.numVertexes][1] = f->windowY + size;
- tess.texCoords[tess.numVertexes][0][0] = 1;
- tess.texCoords[tess.numVertexes][0][1] = 1;
- tess.vertexColors[tess.numVertexes][0] = iColor[0];
- tess.vertexColors[tess.numVertexes][1] = iColor[1];
- tess.vertexColors[tess.numVertexes][2] = iColor[2];
- tess.vertexColors[tess.numVertexes][3] = 255;
- tess.numVertexes++;
- tess.xyz[tess.numVertexes][0] = f->windowX + size;
- tess.xyz[tess.numVertexes][1] = f->windowY - size;
- tess.texCoords[tess.numVertexes][0][0] = 1;
- tess.texCoords[tess.numVertexes][0][1] = 0;
- tess.vertexColors[tess.numVertexes][0] = iColor[0];
- tess.vertexColors[tess.numVertexes][1] = iColor[1];
- tess.vertexColors[tess.numVertexes][2] = iColor[2];
- tess.vertexColors[tess.numVertexes][3] = 255;
- tess.numVertexes++;
- tess.indexes[tess.numIndexes++] = 0;
- tess.indexes[tess.numIndexes++] = 1;
- tess.indexes[tess.numIndexes++] = 2;
- tess.indexes[tess.numIndexes++] = 0;
- tess.indexes[tess.numIndexes++] = 2;
- tess.indexes[tess.numIndexes++] = 3;
- RB_EndSurface();
- }
- /*
- ==================
- RB_RenderFlares
- Because flares are simulating an occular effect, they should be drawn after
- everything (all views) in the entire frame has been drawn.
- Because of the way portals use the depth buffer to mark off areas, the
- needed information would be lost after each view, so we are forced to draw
- flares after each view.
- The resulting artifact is that flares in mirrors or portals don't dim properly
- when occluded by something in the main view, and portal flares that should
- extend past the portal edge will be overwritten.
- ==================
- */
- void RB_RenderFlares (void) {
- flare_t *f;
- flare_t **prev;
- qboolean draw;
- if ( !r_flares->integer ) {
- return;
- }
- // RB_AddDlightFlares();
- // perform z buffer readback on each flare in this view
- draw = qfalse;
- prev = &r_activeFlares;
- while ( ( f = *prev ) != NULL ) {
- // throw out any flares that weren't added last frame
- if ( f->addedFrame < backEnd.viewParms.frameCount - 1 ) {
- *prev = f->next;
- f->next = r_inactiveFlares;
- r_inactiveFlares = f;
- continue;
- }
- // don't draw any here that aren't from this scene / portal
- f->drawIntensity = 0;
- if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum
- && f->inPortal == backEnd.viewParms.isPortal ) {
- RB_TestFlare( f );
- if ( f->drawIntensity ) {
- draw = qtrue;
- } else {
- // this flare has completely faded out, so remove it from the chain
- *prev = f->next;
- f->next = r_inactiveFlares;
- r_inactiveFlares = f;
- continue;
- }
- }
- prev = &f->next;
- }
- if ( !draw ) {
- return; // none visible
- }
- if ( backEnd.viewParms.isPortal ) {
- qglDisable (GL_CLIP_PLANE0);
- }
- qglPushMatrix();
- qglLoadIdentity();
- qglMatrixMode( GL_PROJECTION );
- qglPushMatrix();
- qglLoadIdentity();
- qglOrtho( backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth,
- backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight,
- -99999, 99999 );
- for ( f = r_activeFlares ; f ; f = f->next ) {
- if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum
- && f->inPortal == backEnd.viewParms.isPortal
- && f->drawIntensity ) {
- RB_RenderFlare( f );
- }
- }
- qglPopMatrix();
- qglMatrixMode( GL_MODELVIEW );
- qglPopMatrix();
- }
|