/* =========================================================================== 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 Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // tr_sky.c #include "tr_local.h" #define SKY_SUBDIVISIONS 8 #define HALF_SKY_SUBDIVISIONS (SKY_SUBDIVISIONS/2) static float s_cloudTexCoords[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2]; /* =================================================================================== POLYGON TO BOX SIDE PROJECTION =================================================================================== */ static const vec3_t sky_clip[6] = { { 1, 1, 0}, { 1,-1, 0}, { 0,-1, 1}, { 0, 1, 1}, { 1, 0, 1}, {-1, 0, 1} }; static float sky_mins[2][6], sky_maxs[2][6]; static float sky_min, sky_max; static float sky_min_depth; /* ================ AddSkyPolygon ================ */ static void AddSkyPolygon (int nump, vec3_t vecs) { int i,j; vec3_t v, av; float s, t, dv; int axis; float *vp; // s = [0]/[2], t = [1]/[2] static const int vec_to_st[6][3] = { {-2,3,1}, {2,3,-1}, {1,3,2}, {-1,3,-2}, {-2,-1,3}, {-2,1,-3} // {-1,2,3}, // {1,2,-3} }; // decide which face it maps to VectorCopy (vec3_origin, v); for (i=0, vp=vecs ; i av[1] && av[0] > av[2]) { if (v[0] < 0) axis = 1; else axis = 0; } else if (av[1] > av[2] && av[1] > av[0]) { if (v[1] < 0) axis = 3; else axis = 2; } else { if (v[2] < 0) axis = 5; else axis = 4; } // project new texture coords for (i=0 ; i 0) dv = vecs[j - 1]; else dv = -vecs[-j - 1]; if (dv < 0.001) continue; // don't divide by zero j = vec_to_st[axis][0]; if (j < 0) s = -vecs[-j -1] / dv; else s = vecs[j-1] / dv; j = vec_to_st[axis][1]; if (j < 0) t = -vecs[-j -1] / dv; else t = vecs[j-1] / dv; if (s < sky_mins[0][axis]) sky_mins[0][axis] = s; if (t < sky_mins[1][axis]) sky_mins[1][axis] = t; if (s > sky_maxs[0][axis]) sky_maxs[0][axis] = s; if (t > sky_maxs[1][axis]) sky_maxs[1][axis] = t; } } #define ON_EPSILON 0.1f // point on plane side epsilon #define MAX_CLIP_VERTS 64 /* ================ ClipSkyPolygon ================ */ static void ClipSkyPolygon (int nump, vec3_t vecs, int stage) { const float *norm; float *v; qboolean front, back; float d, e; float dists[MAX_CLIP_VERTS]; int sides[MAX_CLIP_VERTS]; vec3_t newv[2][MAX_CLIP_VERTS]; int newc[2]; int i, j; if (nump > MAX_CLIP_VERTS-2) ri.Error (ERR_DROP, "ClipSkyPolygon: MAX_CLIP_VERTS"); if (stage == 6) { // fully clipped, so draw it AddSkyPolygon (nump, vecs); return; } front = back = qfalse; norm = sky_clip[stage]; for (i=0, v = vecs ; i ON_EPSILON) { front = qtrue; sides[i] = SIDE_FRONT; } else if (d < -ON_EPSILON) { back = qtrue; sides[i] = SIDE_BACK; } else sides[i] = SIDE_ON; dists[i] = d; } if (!front || !back) { // not clipped ClipSkyPolygon (nump, vecs, stage+1); return; } // clip it sides[i] = sides[0]; dists[i] = dists[0]; VectorCopy (vecs, (vecs+(i*3)) ); newc[0] = newc[1] = 0; for (i=0, v = vecs ; inumIndexes; i += 3 ) { for (j = 0 ; j < 3 ; j++) { VectorSubtract( input->xyz[input->indexes[i+j]], backEnd.viewParms.or.origin, p[j] ); } ClipSkyPolygon( 3, p[0], 0 ); } } /* =================================================================================== CLOUD VERTEX GENERATION =================================================================================== */ /* ** MakeSkyVec ** ** Parms: s, t range from -1 to 1 */ static void MakeSkyVec( float s, float t, int axis, vec3_t outXYZ ) { // 1 = s, 2 = t, 3 = 2048 static const int st_to_vec[6][3] = { {3,-1,2}, {-3,1,2}, {1,3,2}, {-1,-3,2}, {-2,-1,3}, // 0 degrees yaw, look straight up {2,-1,-3} // look straight down }; vec3_t b; int j, k; float boxSize; boxSize = backEnd.viewParms.zFar / 1.75; // div sqrt(3) b[0] = s*boxSize; b[1] = t*boxSize; b[2] = boxSize; for ( j = 0; j < 3; j++ ) { k = st_to_vec[axis][j]; if ( k < 0 ) { outXYZ[j] = -b[-k - 1]; } else { outXYZ[j] = b[k - 1]; } } } static const int sky_texorder[6] = {0, 2, 1, 3, 4, 5}; static vec3_t s_skyPoints[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1]; static float s_skyTexCoords[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2]; /* ================= CullPoints ================= */ static qboolean CullPoints( vec4_t v[], const int count ) { const cplane_t *frust; int i, j; float dist; for ( i = 0; i < 5; i++ ) { frust = &backEnd.viewParms.frustum[i]; for ( j = 0; j < count; j++ ) { dist = DotProduct( v[j], frust->normal ) - frust->dist; if ( dist >= 0 ) { break; } } // all points are completely behind at least of one frustum plane if ( j == count ) { return qtrue; } } return qfalse; } static qboolean CullSkySide( const int mins[2], const int maxs[2] ) { int s, t; vec4_t v[4]; if ( r_nocull->integer ) return qfalse; s = mins[0] + HALF_SKY_SUBDIVISIONS; t = mins[1] + HALF_SKY_SUBDIVISIONS; VectorAdd( s_skyPoints[t][s], backEnd.viewParms.or.origin, v[0] ); s = mins[0] + HALF_SKY_SUBDIVISIONS; t = maxs[1] + HALF_SKY_SUBDIVISIONS; VectorAdd( s_skyPoints[t][s], backEnd.viewParms.or.origin, v[1] ); s = maxs[0] + HALF_SKY_SUBDIVISIONS; t = mins[1] + HALF_SKY_SUBDIVISIONS; VectorAdd( s_skyPoints[t][s], backEnd.viewParms.or.origin, v[2] ); s = maxs[0] + HALF_SKY_SUBDIVISIONS; t = maxs[1] + HALF_SKY_SUBDIVISIONS; VectorAdd( s_skyPoints[t][s], backEnd.viewParms.or.origin, v[3] ); if ( CullPoints( v, 4 ) ) return qtrue; return qfalse; } static void FillSkySide( const int mins[2], const int maxs[2], float skyTexCoords[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2] ) { const int vertexStart = tess.numVertexes; const int tHeight = maxs[1] - mins[1] + 1; const int sWidth = maxs[0] - mins[0] + 1; int s, t; if ( CullSkySide( mins, maxs ) ) return; #if ( (SKY_SUBDIVISIONS+1) * (SKY_SUBDIVISIONS+1) * 6 > SHADER_MAX_VERTEXES ) if ( tess.numVertexes + tHeight * sWidth > SHADER_MAX_VERTEXES ) ri.Error( ERR_DROP, "SHADER_MAX_VERTEXES hit in %s()", __func__ ); #endif #if ( SKY_SUBDIVISIONS * SKY_SUBDIVISIONS * 6 * 6 > SHADER_MAX_INDEXES ) if ( tess.numIndexes + (tHeight - 1) * (sWidth - 1) * 6 > SHADER_MAX_INDEXES ) ri.Error( ERR_DROP, "SHADER_MAX_INDEXES hit in %s()", __func__ ); #endif for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t <= maxs[1]+HALF_SKY_SUBDIVISIONS; t++ ) { for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ ) { VectorAdd( s_skyPoints[t][s], backEnd.viewParms.or.origin, tess.xyz[ tess.numVertexes ] ); tess.texCoords[0][tess.numVertexes][0] = skyTexCoords[t][s][0]; tess.texCoords[0][tess.numVertexes][1] = skyTexCoords[t][s][1]; tess.numVertexes++; } } for ( t = 0; t < tHeight-1; t++ ) { for ( s = 0; s < sWidth-1; s++ ) { tess.indexes[tess.numIndexes] = vertexStart + s + t * ( sWidth ); tess.numIndexes++; tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth ); tess.numIndexes++; tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth ); tess.numIndexes++; tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth ); tess.numIndexes++; tess.indexes[tess.numIndexes] = vertexStart + s + 1 + ( t + 1 ) * ( sWidth ); tess.numIndexes++; tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth ); tess.numIndexes++; } } } static void DrawSkySide( image_t *image, const int mins[2], const int maxs[2] ) { tess.numVertexes = 0; tess.numIndexes = 0; FillSkySide( mins, maxs, s_skyTexCoords ); if ( tess.numIndexes ) { GL_Bind( image ); qglVertexPointer( 3, GL_FLOAT, 16, tess.xyz ); qglTexCoordPointer( 2, GL_FLOAT, 0, tess.texCoords[0] ); R_DrawElements( tess.numIndexes, tess.indexes ); tess.numVertexes = 0; tess.numIndexes = 0; } } static void DrawSkyBox( const shader_t *shader ) { int i; sky_min = 0; sky_max = 1; for ( i = 0; i < 6; i++ ) { int sky_mins_subd[2], sky_maxs_subd[2]; int s, t; sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || ( sky_mins[1][i] >= sky_maxs[1][i] ) ) { continue; } sky_mins_subd[0] = sky_mins[0][i] * HALF_SKY_SUBDIVISIONS; sky_mins_subd[1] = sky_mins[1][i] * HALF_SKY_SUBDIVISIONS; sky_maxs_subd[0] = sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS; sky_maxs_subd[1] = sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS; if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; if ( sky_mins_subd[1] < -HALF_SKY_SUBDIVISIONS ) sky_mins_subd[1] = -HALF_SKY_SUBDIVISIONS; else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; if ( sky_maxs_subd[1] < -HALF_SKY_SUBDIVISIONS ) sky_maxs_subd[1] = -HALF_SKY_SUBDIVISIONS; else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; // // iterate through the subdivisions // for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ ) { for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ ) { MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, i, s_skyPoints[t][s] ); } } DrawSkySide( shader->sky.outerbox[sky_texorder[i]], sky_mins_subd, sky_maxs_subd ); } } static void FillCloudBox( void ) { int i; for ( i =0; i < 6; i++ ) { int sky_mins_subd[2], sky_maxs_subd[2]; int s, t; float MIN_T; if ( 1 ) // FIXME? shader->sky.fullClouds ) { MIN_T = -HALF_SKY_SUBDIVISIONS; // still don't want to draw the bottom, even if fullClouds if ( i == 5 ) continue; } else { switch( i ) { case 0: case 1: case 2: case 3: MIN_T = -1; break; case 5: // don't draw clouds beneath you continue; case 4: // top default: MIN_T = -HALF_SKY_SUBDIVISIONS; break; } } sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || ( sky_mins[1][i] >= sky_maxs[1][i] ) ) { continue; } sky_mins_subd[0] = myftol( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ); sky_mins_subd[1] = myftol( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ); sky_maxs_subd[0] = myftol( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ); sky_maxs_subd[1] = myftol( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ); if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; if ( sky_mins_subd[1] < MIN_T ) sky_mins_subd[1] = MIN_T; else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; if ( sky_maxs_subd[1] < MIN_T ) sky_maxs_subd[1] = MIN_T; else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; // // iterate through the subdivisions // for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ ) { for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ ) { MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, i, s_skyPoints[t][s] ); } } FillSkySide( sky_mins_subd, sky_maxs_subd, s_cloudTexCoords[i] ); } } /* ** R_BuildCloudData */ static void R_BuildCloudData( const shaderCommands_t *input ) { const shader_t *shader; shader = input->shader; sky_min = 1.0 / 256.0f; // FIXME: not correct? sky_max = 255.0 / 256.0f; // set up for drawing tess.numIndexes = 0; tess.numVertexes = 0; if ( shader->sky.cloudHeight ) { if ( tess.xstages[0] ) { FillCloudBox(); } } } static void BuildSkyTexCoords( void ) { float s, t; int i, j; for ( i = 0; i <= SKY_SUBDIVISIONS; i++ ) { for ( j = 0; j <= SKY_SUBDIVISIONS; j++ ) { s = ( j - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS; t = ( i - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS; // avoid bilerp seam s = (s+1)*0.5; t = (t+1)*0.5; if ( s < 0.0f ) { s = 0.0f; } else if ( s > 1.0f ) { s = 1.0f; } if ( t < 0.0f ) { t = 0.0f; } else if ( t > 1.0f ) { t = 1.0f; } t = 1.0f - t; s_skyTexCoords[i][j][0] = s; s_skyTexCoords[i][j][1] = t; } } } /* ** R_InitSkyTexCoords ** Called when a sky shader is parsed */ void R_InitSkyTexCoords( float heightCloud ) { int i, s, t; float radiusWorld = 4096; float p; float sRad, tRad; vec3_t skyVec; vec3_t v; if ( !Q_stricmp( glConfig.renderer_string, "GDI Generic" ) && !Q_stricmp( glConfig.version_string, "1.1.0" ) ) { // fix skybox rendering on MS software GL implementation sky_min_depth = 0.999f; } else { sky_min_depth = 1.0; } // init zfar so MakeSkyVec works even though // a world hasn't been bounded backEnd.viewParms.zFar = 1024; for ( i = 0; i < 6; i++ ) { for ( t = 0; t <= SKY_SUBDIVISIONS; t++ ) { for ( s = 0; s <= SKY_SUBDIVISIONS; s++ ) { // compute vector from view origin to sky side integral point MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, i, skyVec ); // compute parametric value 'p' that intersects with cloud layer p = ( 1.0f / ( 2 * DotProduct( skyVec, skyVec ) ) ) * ( -2 * skyVec[2] * radiusWorld + 2 * sqrt( Square( skyVec[2] ) * Square( radiusWorld ) + 2 * Square( skyVec[0] ) * radiusWorld * heightCloud + Square( skyVec[0] ) * Square( heightCloud ) + 2 * Square( skyVec[1] ) * radiusWorld * heightCloud + Square( skyVec[1] ) * Square( heightCloud ) + 2 * Square( skyVec[2] ) * radiusWorld * heightCloud + Square( skyVec[2] ) * Square( heightCloud ) ) ); // compute intersection point based on p VectorScale( skyVec, p, v ); v[2] += radiusWorld; // compute vector from world origin to intersection point 'v' VectorNormalize( v ); sRad = Q_acos( v[0] ); tRad = Q_acos( v[1] ); s_cloudTexCoords[i][t][s][0] = sRad; s_cloudTexCoords[i][t][s][1] = tRad; } } } BuildSkyTexCoords(); } //====================================================================================== /* ** RB_DrawSun */ void RB_DrawSun( float scale, shader_t *shader ) { float size; float dist; vec3_t origin, vec1, vec2; color4ub_t sunColor; if ( !backEnd.skyRenderedThisView ) return; sunColor.u32 = 0xFFFFFFFF; qglLoadMatrixf( backEnd.viewParms.world.modelMatrix ); dist = backEnd.viewParms.zFar / 1.75; // div sqrt(3) size = dist * scale; VectorMA( backEnd.viewParms.or.origin, dist, tr.sunDirection, origin ); PerpendicularVector( vec1, tr.sunDirection ); CrossProduct( tr.sunDirection, vec1, vec2 ); VectorScale( vec1, size, vec1 ); VectorScale( vec2, size, vec2 ); // farthest depth range qglDepthRange( sky_min_depth, 1.0 ); RB_BeginSurface( shader, 0 ); RB_AddQuadStamp( origin, vec1, vec2, sunColor ); RB_EndSurface(); // back to normal depth range qglDepthRange( 0.0, 1.0 ); } /* ================ RB_StageIteratorSky All of the visible sky triangles are in tess Other things could be stuck in here, like birds in the sky, etc ================ */ void RB_StageIteratorSky( void ) { #ifdef USE_PMLIGHT #ifdef USE_LEGACY_DLIGHTS if ( r_dlightMode->integer ) #endif { GL_ProgramDisable(); } #endif // USE_PMLIGHT if ( r_fastsky->integer ) { return; } VBO_UnBind(); // go through all the polygons and project them onto // the sky box to see which blocks on each side need // to be drawn RB_ClipSkyPolygons( &tess ); // r_showsky will let all the sky blocks be drawn in // front of everything to allow developers to see how // much sky is getting sucked in if ( r_showsky->integer ) { qglDepthRange( 0.0, 0.0 ); } else { qglDepthRange( sky_min_depth, 1.0 ); } // draw the outer skybox if ( tess.shader->sky.outerbox[0] && tess.shader->sky.outerbox[0] != tr.defaultImage ) { GL_ClientState( 1, CLS_NONE ); GL_ClientState( 0, CLS_TEXCOORD_ARRAY ); qglColor4f( tr.identityLight, tr.identityLight, tr.identityLight, 1.0 ); GL_State( 0 ); GL_Cull( CT_FRONT_SIDED ); DrawSkyBox( tess.shader ); } // generate the vertexes for all the clouds, which will be drawn // by the generic shader routine R_BuildCloudData( &tess ); // draw the inner skybox if ( tess.numVertexes ) { RB_StageIteratorGeneric(); } // back to normal depth range qglDepthRange( 0.0, 1.0 ); // note that sky was drawn so we will draw a sun later backEnd.skyRenderedThisView = qtrue; }