/* =========================================================================== 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_shade.c #include "tr_local.h" /* THIS ENTIRE FILE IS BACK END This file deals with applying shaders to surface data in the tess struct. */ /* ================ R_ArrayElementDiscrete This is just for OpenGL conformance testing, it should never be the fastest ================ */ static void APIENTRY R_ArrayElementDiscrete( GLint index ) { qglColor4ubv( tess.svars.colors[ index ] ); if ( glState.currenttmu ) { qglMultiTexCoord2fARB( 0, tess.svars.texcoords[ 0 ][ index ][0], tess.svars.texcoords[ 0 ][ index ][1] ); qglMultiTexCoord2fARB( 1, tess.svars.texcoords[ 1 ][ index ][0], tess.svars.texcoords[ 1 ][ index ][1] ); } else { qglTexCoord2fv( tess.svars.texcoords[ 0 ][ index ] ); } qglVertex3fv( tess.xyz[ index ] ); } /* =================== R_DrawStripElements =================== */ static int c_vertexes; // for seeing how long our average strips are static int c_begins; static void R_DrawStripElements( int numIndexes, const glIndex_t *indexes, void ( APIENTRY *element )(GLint) ) { int i; int last[3] = { -1, -1, -1 }; qboolean even; c_begins++; if ( numIndexes <= 0 ) { return; } qglBegin( GL_TRIANGLE_STRIP ); // prime the strip element( indexes[0] ); element( indexes[1] ); element( indexes[2] ); c_vertexes += 3; last[0] = indexes[0]; last[1] = indexes[1]; last[2] = indexes[2]; even = qfalse; for ( i = 3; i < numIndexes; i += 3 ) { // odd numbered triangle in potential strip if ( !even ) { // check previous triangle to see if we're continuing a strip if ( ( indexes[i+0] == last[2] ) && ( indexes[i+1] == last[1] ) ) { element( indexes[i+2] ); c_vertexes++; assert( indexes[i+2] < tess.numVertexes ); even = qtrue; } // otherwise we're done with this strip so finish it and start // a new one else { qglEnd(); qglBegin( GL_TRIANGLE_STRIP ); c_begins++; element( indexes[i+0] ); element( indexes[i+1] ); element( indexes[i+2] ); c_vertexes += 3; even = qfalse; } } else { // check previous triangle to see if we're continuing a strip if ( ( last[2] == indexes[i+1] ) && ( last[0] == indexes[i+0] ) ) { element( indexes[i+2] ); c_vertexes++; even = qfalse; } // otherwise we're done with this strip so finish it and start // a new one else { qglEnd(); qglBegin( GL_TRIANGLE_STRIP ); c_begins++; element( indexes[i+0] ); element( indexes[i+1] ); element( indexes[i+2] ); c_vertexes += 3; even = qfalse; } } // cache the last three vertices last[0] = indexes[i+0]; last[1] = indexes[i+1]; last[2] = indexes[i+2]; } qglEnd(); } /* ================== R_DrawElements Optionally performs our own glDrawElements that looks for strip conditions instead of using the single glDrawElements call that may be inefficient without compiled vertex arrays. ================== */ static void R_DrawElements( int numIndexes, const glIndex_t *indexes ) { int primitives; primitives = r_primitives->integer; // default is to use triangles if compiled vertex arrays are present if ( primitives == 0 ) { if ( qglLockArraysEXT ) { primitives = 2; } else { primitives = 1; } } if ( primitives == 2 ) { qglDrawElements( GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, indexes ); return; } if ( primitives == 1 ) { R_DrawStripElements( numIndexes, indexes, qglArrayElement ); return; } if ( primitives == 3 ) { R_DrawStripElements( numIndexes, indexes, R_ArrayElementDiscrete ); return; } // anything else will cause no drawing } /* ============================================================= SURFACE SHADERS ============================================================= */ shaderCommands_t tess; static qboolean setArraysOnce; /* ================= R_BindAnimatedImage ================= */ static void R_BindAnimatedImage( textureBundle_t *bundle ) { int index; if (bundle->numImageAnimations <= 1) { GL_Bind(bundle->image[0]); return; } // it is necessary to do this messy calc to make sure animations line up // exactly with waveforms of the same frequency index = myftol((tess.shaderTime + bundle->imageAnimationPhase) * bundle->imageAnimationSpeed * FUNCTABLE_SIZE); index >>= FUNCTABLE_SIZE2; if (index < 0) { index = 0; // may happen with shader time offsets } if (!(bundle->flags & BUNDLE_ANIMATE_ONCE)) { index %= bundle->numImageAnimations; } else if (index >= bundle->numImageAnimations) { index = bundle->numImageAnimations - 1; } GL_Bind(bundle->image[index]); } /* ================ DrawTris Draws triangle outlines for debugging ================ */ static void DrawTris (shaderCommands_t *input, int flags) { GL_Bind( tr.whiteImage ); qglColor3f (1,1,1); if (flags == 1) { GL_State(GLS_POLYMODE_LINE); } else { GL_State(GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE); qglDepthRange(0, 0); } if (flags < 3) { qglDisableClientState(GL_COLOR_ARRAY); qglDisableClientState(GL_TEXTURE_COORD_ARRAY); qglVertexPointer(3, GL_FLOAT, 16, input->xyz); // padded for SIMD if (qglLockArraysEXT) { qglLockArraysEXT(0, input->numVertexes); GLimp_LogComment("glLockArraysEXT\n"); } R_DrawElements(input->numIndexes, input->indexes); if (qglUnlockArraysEXT) { qglUnlockArraysEXT(); GLimp_LogComment("glUnlockArraysEXT\n"); } qglDepthRange(0, 1); } if (flags > 1) { vec3_t start, end; start[1] = start[0] = 0.0; end[1] = end[0] = 0.0; GL_State(GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE); qglDepthRange(0, 0); qglDisable(GL_TEXTURE_2D); qglBegin(GL_LINES); qglColor4f(1.0, 0.0, 0.0, 1.0); start[2] = 0.0 - 5.0; end[2] = 0.0 + 5.0; qglVertex3fv(start); qglVertex3fv(end); qglColor4f(0.0, 1.0, 0.0, 1.0); start[2] = start[2] + 5.0; end[2] = end[2] - 5.0; start[1] = start[1] - 5.0; end[1] = end[1] + 5.0; qglVertex3fv(start); qglVertex3fv(end); qglColor4f(0.0, 0.0, 1.0, 1.0); start[1] = start[1] + 5.0; start[0] = start[0] - 5.0; end[0] = end[0] + 5.0; qglVertex3fv(start); qglVertex3fv(end); start[0] = start[0] + 5.0; end[0] = end[0] - 5.0; qglEnd(); qglEnable(GL_TEXTURE_2D); qglDepthRange(0.0, 1.0); } } /* ================ DrawNormals Draws vertex normals for debugging ================ */ static void DrawNormals (shaderCommands_t *input) { int i; vec3_t temp; GL_Bind( tr.whiteImage ); qglColor3f (1,1,1); qglDepthRange( 0, 0 ); // never occluded GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE ); qglBegin (GL_LINES); for (i = 0 ; i < input->numVertexes ; i++) { qglVertex3fv (input->xyz[i]); VectorMA (input->xyz[i], 2, input->normal[i], temp); qglVertex3fv (temp); } qglEnd (); qglDepthRange( 0, 1 ); } /* ============== RB_BeginSurface We must set some things up before beginning any tesselation, because a surface may be forced to perform a RB_End due to overflow. ============== */ void RB_BeginSurface( shader_t *shader ) { tess.numIndexes = 0; tess.numVertexes = 0; tess.shader = shader; tess.dlightBits = 0; // will be OR'd in by surface functions tess.vertexColorValid = r_vertexLight->integer != 0; tess.xstages = shader->unfoggedStages; tess.numPasses = shader->numUnfoggedPasses; tess.currentStageIteratorFunc = shader->optimalStageIteratorFunc; } /* =================== DrawMultitextured output = t0 * t1 or t0 + t1 t0 = most upstream according to spec t1 = most downstream according to spec =================== */ static void DrawMultitextured( shaderCommands_t *input, int stage ) { shaderStage_t *pStage; pStage = tess.xstages[stage]; GL_State( pStage->stateBits ); // this is an ugly hack to work around a GeForce driver // bug with multitexture and clip planes if ( backEnd.viewParms.isPortal ) { qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); } // // base // GL_SelectTexture( 0 ); qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] ); R_BindAnimatedImage( &pStage->bundle[0] ); /* // was removed since 2.0 if (pStage->stateBits & GLS_MULTITEXTURE_ENV) { glState.cntnvblendmode = pStage->multitextureEnv; glState.cntTexEnvExt = GLS_MULTITEXTURE_ENV; if (pStage->multitextureEnv == GL_ADD) { qglTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD); qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0); qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB, 0); qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_ONE_MINUS_SRC_COLOR); qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_TEXTURE1); qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_COLOR); qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE3_RGB, 0); qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND3_RGB, GL_ONE_MINUS_SRC_COLOR); } else if (pStage->multitextureEnv == GL_MODULATE) { qglTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0); qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE1); qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); } else { ri.Printf(3, "Unknown MT mode for: %s\n", input->shader); } } */ // // lightmap/secondary pass // GL_SelectTexture( 1 ); qglEnable( GL_TEXTURE_2D ); qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); if ( r_lightmap->integer && pStage->bundle[1].isLightmap ) { GL_TexEnv( GL_REPLACE ); /* // was removed since 2.0 } else if (pStage->stateBits & GLS_MULTITEXTURE_ENV) { glState.cntTexEnvExt = GLS_MULTITEXTURE_ENV; GL_TexEnv(GL_COMBINE); qglTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE); qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PRIMARY_COLOR); qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PREVIOUS); qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_SRC_ALPHA); qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA); */ } else { GL_TexEnv( pStage->multitextureEnv ); } qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[1] ); if (!input->dlightMap || !pStage->bundle[1].isLightmap) { R_BindAnimatedImage(&pStage->bundle[1]); } else { GL_Bind(tr.dlightImages[input->dlightMap - 1]); } R_DrawElements( input->numIndexes, input->indexes ); qglDisable(GL_TEXTURE_2D); // // disable texturing on TEXTURE1, then select TEXTURE0 // qglDisableClientState(GL_TEXTURE_COORD_ARRAY); GL_SelectTexture( 0 ); } /* =================== ProjectDlightTexture Perform dynamic lighting with another rendering pass =================== */ static void ProjectDlightTexture( void ) { int i, l; #if idppc_altivec vec_t origin0, origin1, origin2; float texCoords0, texCoords1; vector float floatColorVec0, floatColorVec1; vector float modulateVec, colorVec, zero; vector short colorShort; vector signed int colorInt; vector unsigned char floatColorVecPerm, modulatePerm, colorChar; vector unsigned char vSel = (vector unsigned char)(0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff); #else vec3_t origin; vec3_t normal; #endif float *texCoords; byte *colors; byte clipBits[SHADER_MAX_VERTEXES]; MAC_STATIC float texCoordsArray[SHADER_MAX_VERTEXES][2]; byte colorArray[SHADER_MAX_VERTEXES][4]; unsigned hitIndexes[SHADER_MAX_INDEXES]; int numIndexes; int planetype; float scale; float radius; vec3_t floatColor; float modulate; if ( !backEnd.refdef.num_dlights ) { return; } #if idppc_altivec // There has to be a better way to do this so that floatColor // and/or modulate are already 16-byte aligned. floatColorVecPerm = vec_lvsl(0,(float *)floatColor); modulatePerm = vec_lvsl(0,(float *)&modulate); modulatePerm = (vector unsigned char)vec_splat((vector unsigned int)modulatePerm,0); zero = (vector float)vec_splat_s8(0); #endif for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) { dlight_t *dl; if ( !( tess.dlightBits & ( 1 << l ) ) ) { continue; // this surface definately doesn't have any of this light } texCoords = texCoordsArray[0]; colors = colorArray[0]; dl = &backEnd.refdef.dlights[l]; #if idppc_altivec origin0 = dl->transformed[0]; origin1 = dl->transformed[1]; origin2 = dl->transformed[2]; #else VectorCopy( dl->transformed, origin ); #endif radius = dl->radius * 1.75f; scale = 1.75f / radius; floatColor[0] = dl->color[0] * 255.0f; floatColor[1] = dl->color[1] * 255.0f; floatColor[2] = dl->color[2] * 255.0f; #if idppc_altivec floatColorVec0 = vec_ld(0, floatColor); floatColorVec1 = vec_ld(11, floatColor); floatColorVec0 = vec_perm(floatColorVec0,floatColorVec0,floatColorVecPerm); #endif for ( i = 0 ; i < tess.numVertexes ; i++, texCoords += 2, colors += 4 ) { #if idppc_altivec vec_t dist0, dist1, dist2; #else vec3_t dist; #endif float vertdist; int clip; backEnd.pc.c_dlightVertexes++; #if idppc_altivec //VectorSubtract( origin, tess.xyz[i], dist ); dist0 = origin0 - tess.xyz[i][0]; dist1 = origin1 - tess.xyz[i][1]; dist2 = origin2 - tess.xyz[i][2]; texCoords0 = 0.5f + dist0 * scale; texCoords1 = 0.5f + dist1 * scale; clip = 0; if ( texCoords0 < 0.0f ) { clip |= 1; } else if ( texCoords0 > 1.0f ) { clip |= 2; } if ( texCoords1 < 0.0f ) { clip |= 4; } else if ( texCoords1 > 1.0f ) { clip |= 8; } texCoords[0] = texCoords0; texCoords[1] = texCoords1; // modulate the strength based on the height and color if ( dist2 > radius ) { clip |= 16; modulate = 0.0f; } else if ( dist2 < -radius ) { clip |= 32; modulate = 0.0f; } else { dist2 = Q_fabs(dist2); if ( dist2 < radius * 0.5f ) { modulate = 1.0f; } else { modulate = 2.0f * (radius - dist2) * scale; } } clipBits[i] = clip; modulateVec = vec_ld(0,(float *)&modulate); modulateVec = vec_perm(modulateVec,modulateVec,modulatePerm); colorVec = vec_madd(floatColorVec0,modulateVec,zero); colorInt = vec_cts(colorVec,0); // RGBx colorShort = vec_pack(colorInt,colorInt); // RGBxRGBx colorChar = vec_packsu(colorShort,colorShort); // RGBxRGBxRGBxRGBx colorChar = vec_sel(colorChar,vSel,vSel); // RGBARGBARGBARGBA replace alpha with 255 vec_ste((vector unsigned int)colorChar,0,(unsigned int *)colors); // store color #else VectorCopy(tess.normal[i], normal); if (fabs(normal[0]) > fabs(normal[1])) { if (fabs(normal[2]) < fabs(normal[0])) { planetype = 0; } else { planetype = 1; } } else { if (fabs(normal[2]) < fabs(normal[1])) { planetype = 1; } else { planetype = 2; } } VectorSubtract( origin, tess.xyz[i], dist ); if (planetype == 0) { texCoords[0] = 0.5f + dist[1] * scale; texCoords[1] = 0.5f + dist[2] * scale; } else if (planetype == 1) { texCoords[0] = 0.5f + dist[0] * scale; texCoords[1] = 0.5f + dist[2] * scale; } else { texCoords[0] = 0.5f + dist[0] * scale; texCoords[1] = 0.5f + dist[1] * scale; } vertdist = DotProduct(dist, normal); clip = 0; if ( texCoords[0] < 0.0f ) { clip |= 1; } else if ( texCoords[0] > 1.0f ) { clip |= 2; } if ( texCoords[1] < 0.0f ) { clip |= 4; } else if ( texCoords[1] > 1.0f ) { clip |= 8; } // modulate the strength based on the height and color if (vertdist > radius) { clip |= 16; modulate = 0.0f; } else if (vertdist < -radius) { clip |= 32; modulate = 0.0f; } else { dist[2] = Q_fabs(dist[2]); if (dist[2] < radius * 0.5f) { modulate = 1.0f; } else { modulate = 2.0f * (radius - dist[2]) * scale; } } clipBits[i] = clip; colors[0] = myftol(floatColor[0] * modulate); colors[1] = myftol(floatColor[1] * modulate); colors[2] = myftol(floatColor[2] * modulate); colors[3] = 255; #endif } // build a list of triangles that need light numIndexes = 0; for ( i = 0 ; i < tess.numIndexes ; i += 3 ) { int a, b, c; a = tess.indexes[i]; b = tess.indexes[i+1]; c = tess.indexes[i+2]; if ( clipBits[a] & clipBits[b] & clipBits[c] ) { continue; // not lighted } hitIndexes[numIndexes] = a; hitIndexes[numIndexes+1] = b; hitIndexes[numIndexes+2] = c; numIndexes += 3; } if ( !numIndexes ) { continue; } qglEnableClientState(GL_COLOR_ARRAY); qglEnableClientState(GL_TEXTURE_COORD_ARRAY); qglColorPointer(4, GL_UNSIGNED_BYTE, 0, colorArray); qglTexCoordPointer(2, GL_FLOAT, 0, texCoordsArray[0]); GL_Bind( tr.dlightImage ); // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light // where they aren't rendered if ( dl->type & additive) { GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); } else { GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); } R_DrawElements( numIndexes, hitIndexes ); backEnd.pc.c_totalIndexes += numIndexes; backEnd.pc.c_dlightIndexes += numIndexes; } } /* =============== ComputeColors =============== */ static void ComputeColors( shaderStage_t *pStage ) { int i; // // rgbGen // switch ( pStage->rgbGen ) { case CGEN_IDENTITY: Com_Memset( tess.svars.colors, 0xff, tess.numVertexes * 4 ); break; default: case CGEN_IDENTITY_LIGHTING: Com_Memset( tess.svars.colors, tr.identityLightByte, tess.numVertexes * 4 ); break; case CGEN_ENTITY: RB_CalcColorFromEntity((unsigned char*)tess.svars.colors); break; case CGEN_ONE_MINUS_ENTITY: RB_CalcColorFromOneMinusEntity((unsigned char*)tess.svars.colors); break; case CGEN_EXACT_VERTEX: if (!tess.vertexColorValid) { static qboolean bWarned = qfalse; if (!bWarned) { // mohta and mohtt shows the warning once bWarned = qtrue; ri.Printf( PRINT_WARNING, "Vertex color specified for shader '%s', but vertex colors are not valid for this model\n", tess.shader->name ); } break; } Com_Memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) ); break; case CGEN_VERTEX: if (!tess.vertexColorValid) { static qboolean bWarned = qfalse; if (!bWarned) { bWarned = qtrue; ri.Printf( PRINT_WARNING, "Vertex color specified for shader '%s', but vertex colors are not valid for this model\n", tess.shader->name); } break; } if (tr.identityLight == 1) { Com_Memcpy(tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof(tess.vertexColors[0])); } else { for (i = 0; i < tess.numVertexes; i++) { tess.svars.colors[i][0] = tess.vertexColors[i][0] * tr.identityLight; tess.svars.colors[i][1] = tess.vertexColors[i][1] * tr.identityLight; tess.svars.colors[i][2] = tess.vertexColors[i][2] * tr.identityLight; tess.svars.colors[i][3] = tess.vertexColors[i][3]; } } break; case CGEN_ONE_MINUS_VERTEX: if ( tr.identityLight == 1 ) { for ( i = 0; i < tess.numVertexes; i++ ) { tess.svars.colors[i][0] = 255 - tess.vertexColors[i][0]; tess.svars.colors[i][1] = 255 - tess.vertexColors[i][1]; tess.svars.colors[i][2] = 255 - tess.vertexColors[i][2]; } } else { for ( i = 0; i < tess.numVertexes; i++ ) { tess.svars.colors[i][0] = ( 255 - tess.vertexColors[i][0] ) * tr.identityLight; tess.svars.colors[i][1] = ( 255 - tess.vertexColors[i][1] ) * tr.identityLight; tess.svars.colors[i][2] = ( 255 - tess.vertexColors[i][2] ) * tr.identityLight; } } break; case CGEN_WAVEFORM: RB_CalcWaveColor(&pStage->rgbWave, (unsigned char*)tess.svars.colors, NULL); break; case CGEN_MULTIPLY_BY_WAVEFORM: RB_CalcWaveColor(&pStage->rgbWave, (unsigned char*)tess.svars.colors, pStage->colorConst); break; case CGEN_LIGHTING_GRID: RB_CalcLightGridColor((unsigned char*)tess.svars.colors); break; case CGEN_LIGHTING_SPHERICAL: case CGEN_STATIC: if (!r_drawspherelights->integer) { break; } if (!backEnd.currentStaticModel) { if (backEnd.currentSphere->TessFunction) { backEnd.currentSphere->TessFunction((unsigned char*)tess.svars.colors); } break; } if (!tess.vertexColorValid) { ri.Printf(PRINT_WARNING, "Vertex color specified for shader '%s', but vertex colors are not valid for this model\n", tess.shader->name); } if (backEnd.currentStaticModel->useSpecialLighting) { for (i = 0; i < tess.numVertexes; i++) { int j; vec3_t colorout; int r, g, b; colorout[0] = tess.vertexColors[i][0]; colorout[1] = tess.vertexColors[i][1]; colorout[2] = tess.vertexColors[i][2]; for (j = 0; j < backEnd.currentStaticModel->numdlights; j++) { float ooLightDistSquared; float dot; vec3_t diff; dlight_t* dl; dl = &backEnd.refdef.dlights[backEnd.currentStaticModel->dlights[j].index]; VectorSubtract(backEnd.currentStaticModel->dlights[j].transformed, tess.xyz[i], diff); dot = DotProduct(diff, tess.normal[i]); if (dot >= 0) { float ooLen; ooLen = 1.0 / VectorLengthSquared(diff); ooLightDistSquared = dot * (7500.0 * dl->radius * ooLen * sqrt(ooLen)); colorout[0] = dl->color[0] * ooLightDistSquared + colorout[0]; colorout[1] = dl->color[1] * ooLightDistSquared + colorout[1]; colorout[2] = dl->color[2] * ooLightDistSquared + colorout[2]; } } r = colorout[0]; g = colorout[1]; b = colorout[2]; if (tr.overbrightShift) { r = (int)((float)r * tr.overbrightMult); g = (int)((float)g * tr.overbrightMult); b = (int)((float)b * tr.overbrightMult); } if (r > 0xFF || g > 0xFF || b > 0xFF) { float t; t = 255.0 / (float)Q_max(r, Q_max(g, b)); r = (int)((float)r * t); g = (int)((float)g * t); b = (int)((float)b * t); } tess.svars.colors[i][0] = r; tess.svars.colors[i][1] = g; tess.svars.colors[i][2] = b; // Fixed in OPM // This sets the alpha channel which will prevent vertices from hiding tess.svars.colors[i][3] = tess.vertexColors[i][3]; } } else { if (tr.identityLight == 1.0) { Com_Memcpy(tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof(tess.vertexColors[0])); } else { for (i = 0; i < tess.numVertexes; i++) { tess.svars.colors[i][0] = tess.vertexColors[i][0] * tr.identityLight; tess.svars.colors[i][1] = tess.vertexColors[i][1] * tr.identityLight; tess.svars.colors[i][2] = tess.vertexColors[i][2] * tr.identityLight; } } } break; case CGEN_CONSTANT: RB_CalcColorFromConstant((unsigned char*)tess.svars.colors, pStage->colorConst); break; case CGEN_GLOBAL_COLOR: RB_CalcColorFromConstant((unsigned char*)tess.svars.colors, backEnd.color2D); break; case CGEN_SCOORD: RB_CalcRGBFromTexCoords( (unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax, pStage->alphaConstMin, pStage->alphaConst, 1.0, 0.0, tess.svars.texcoords[0][0] ); break; case CGEN_TCOORD: RB_CalcRGBFromTexCoords( (unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax, pStage->alphaConstMin, pStage->alphaConst, 0.0, 1.0, tess.svars.texcoords[0][0] ); break; case CGEN_DOT: RB_CalcRGBFromDot((unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax); break; case CGEN_ONE_MINUS_DOT: RB_CalcRGBFromOneMinusDot((unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax); break; } // // alphaGen // switch ( pStage->alphaGen ) { case AGEN_SKIP: break; case AGEN_IDENTITY: if ( pStage->rgbGen != CGEN_IDENTITY && pStage->rgbGen != CGEN_LIGHTING_GRID && pStage->rgbGen != CGEN_LIGHTING_SPHERICAL) { if ( pStage->rgbGen != CGEN_VERTEX || tr.identityLight != 1 ) { for ( i = 0; i < tess.numVertexes; i++ ) { tess.svars.colors[i][3] = 0xff; } } } break; case AGEN_ENTITY: RB_CalcAlphaFromEntity((unsigned char*)tess.svars.colors); break; case AGEN_ONE_MINUS_ENTITY: RB_CalcAlphaFromOneMinusEntity((unsigned char*)tess.svars.colors); break; case AGEN_VERTEX: if (pStage->rgbGen != CGEN_VERTEX) { for (i = 0; i < tess.numVertexes; i++) { tess.svars.colors[i][3] = tess.vertexColors[i][3]; } } break; case AGEN_ONE_MINUS_VERTEX: for (i = 0; i < tess.numVertexes; i++) { tess.svars.colors[i][3] = 255 - tess.vertexColors[i][3]; } break; case AGEN_LIGHTING_SPECULAR: RB_CalcSpecularAlpha((unsigned char*)tess.svars.colors, pStage->alphaMax, pStage->specOrigin); break; case AGEN_WAVEFORM: RB_CalcWaveAlpha(&pStage->alphaWave, (unsigned char*)tess.svars.colors); break; case AGEN_PORTAL: { unsigned char alpha; for ( i = 0; i < tess.numVertexes; i++ ) { float len; vec3_t v; VectorSubtract( tess.xyz[i], backEnd.viewParms.ori.origin, v ); len = VectorLength( v ); len /= tess.shader->portalRange; if ( len < 0 ) { alpha = 0; } else if ( len > 1 ) { alpha = 0xff; } else { alpha = len * 0xff; } tess.svars.colors[i][3] = alpha; } } break; case AGEN_DOT: RB_CalcAlphaFromDot((unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax); return; case AGEN_ONE_MINUS_DOT: RB_CalcAlphaFromOneMinusDot((unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax); return; case AGEN_CONSTANT: RB_CalcAlphaFromConstant((unsigned char*)tess.svars.colors, pStage->colorConst[3]); break; case AGEN_GLOBAL_ALPHA: RB_CalcAlphaFromConstant((unsigned char*)tess.svars.colors, backEnd.color2D[3]); break; case AGEN_SKYALPHA: RB_CalcAlphaFromConstant((unsigned char*)tess.svars.colors, tr.refdef.sky_alpha * 255.0); break; case AGEN_ONE_MINUS_SKYALPHA: RB_CalcAlphaFromConstant((unsigned char*)tess.svars.colors, (1.0 - tr.refdef.sky_alpha) * 255.0); break; case AGEN_SCOORD: RB_CalcAlphaFromTexCoords( (unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax, pStage->alphaConstMin, pStage->alphaConst, 1.0, 0.0, tess.svars.texcoords[0][0] ); break; case AGEN_TCOORD: RB_CalcAlphaFromTexCoords( (unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax, pStage->alphaConstMin, pStage->alphaConst, 0.0, 1.0, tess.svars.texcoords[0][0] ); break; case AGEN_DIST_FADE: if (backEnd.currentStaticModel) { unsigned char alpha; for (i = 0; i < tess.numVertexes; i++) { float len; vec3_t org, v; VectorSubtract(backEnd.viewParms.ori.origin, backEnd.currentStaticModel->origin, v); org[0] = tess.xyz[i][0] - DotProduct(v, backEnd.currentStaticModel->axis[0]); org[1] = tess.xyz[i][1] - DotProduct(v, backEnd.currentStaticModel->axis[1]); org[2] = tess.xyz[i][2] - DotProduct(v, backEnd.currentStaticModel->axis[2]); len = (VectorLength(org) - tess.shader->fDistNear) / tess.shader->fDistRange; if (len < 0) { alpha = 0xff; } else if (len > 1) { alpha = 0; } else { alpha = ((1.0 - len) * 255.0); } tess.svars.colors[i][3] = alpha; } } else { unsigned char alpha; for (i = 0; i < tess.numVertexes; i++) { float len; vec3_t org, v; if (backEnd.currentEntity) { VectorSubtract(backEnd.viewParms.ori.origin, backEnd.currentEntity->e.origin, v); org[0] = tess.xyz[i][0] - DotProduct(v, backEnd.currentEntity->e.axis[0]); org[1] = tess.xyz[i][1] - DotProduct(v, backEnd.currentEntity->e.axis[1]); org[2] = tess.xyz[i][2] - DotProduct(v, backEnd.currentEntity->e.axis[2]); } else { VectorCopy(backEnd.viewParms.ori.origin, org); } len = (VectorLength(org) - tess.shader->fDistNear) / tess.shader->fDistRange; if (len < 0) { alpha = 0xff; } else if (len > 1) { alpha = 0; } else { alpha = ((1.0 - len) * 255.0); } tess.svars.colors[i][3] = alpha; } } break; case AGEN_ONE_MINUS_DIST_FADE: if (backEnd.currentStaticModel) { unsigned char alpha; for (i = 0; i < tess.numVertexes; i++) { float len; vec3_t org, v; VectorSubtract(backEnd.viewParms.ori.origin, backEnd.currentStaticModel->origin, v); org[0] = tess.xyz[i][0] - DotProduct(v, backEnd.currentStaticModel->axis[0]); org[1] = tess.xyz[i][1] - DotProduct(v, backEnd.currentStaticModel->axis[1]); org[2] = tess.xyz[i][2] - DotProduct(v, backEnd.currentStaticModel->axis[2]); len = (VectorLength(org) - tess.shader->fDistNear) / tess.shader->fDistRange; if (len < 0) { alpha = 0; } else if (len > 1) { alpha = 0xff; } else { alpha = (len * 255.0); } tess.svars.colors[i][3] = alpha; } } else { unsigned char alpha; for (i = 0; i < tess.numVertexes; i++) { float len; vec3_t org, v; if (backEnd.currentEntity) { VectorSubtract(backEnd.viewParms.ori.origin, backEnd.currentEntity->e.origin, v); org[0] = tess.xyz[i][0] - DotProduct(v, backEnd.currentEntity->e.axis[0]); org[1] = tess.xyz[i][1] - DotProduct(v, backEnd.currentEntity->e.axis[1]); org[2] = tess.xyz[i][2] - DotProduct(v, backEnd.currentEntity->e.axis[2]); } else { VectorCopy(backEnd.viewParms.ori.origin, org); } len = (VectorLength(org) - tess.shader->fDistNear) / tess.shader->fDistRange; if (len < 0) { alpha = 0; } else if (len > 1) { alpha = 0xff; } else { alpha = (len * 255.0); } tess.svars.colors[i][3] = alpha; } } break; case AGEN_TIKI_DIST_FADE: case AGEN_ONE_MINUS_TIKI_DIST_FADE: { unsigned char alpha; if (backEnd.currentStaticModel) { float lenSqr; float fDistNearSqr; float fDistFarSqr; vec3_t org; VectorSubtract(backEnd.currentStaticModel->origin, backEnd.viewParms.ori.origin, org); lenSqr = VectorLengthSquared(org); fDistNearSqr = tess.shader->fDistNear * tess.shader->fDistNear; fDistFarSqr = (tess.shader->fDistNear + tess.shader->fDistRange) * (tess.shader->fDistNear + tess.shader->fDistRange); if (lenSqr <= fDistNearSqr) { alpha = 0; } else if (lenSqr >= fDistFarSqr) { alpha = 0xff; } else { float len; len = VectorLength(org); alpha = (((VectorLength(org) - tess.shader->fDistNear) / tess.shader->fDistRange) * 255.0); } if (pStage->alphaGen == AGEN_TIKI_DIST_FADE) { alpha = 0xff - alpha; } } else if (backEnd.currentEntity) { float lenSqr; float fDistNearSqr; float fDistFarSqr; vec3_t org; VectorSubtract(backEnd.currentEntity->e.origin, backEnd.viewParms.ori.origin, org); lenSqr = VectorLengthSquared(org); fDistNearSqr = tess.shader->fDistNear * tess.shader->fDistNear; fDistFarSqr = (tess.shader->fDistNear + tess.shader->fDistRange) * (tess.shader->fDistNear + tess.shader->fDistRange); if (lenSqr <= fDistNearSqr) { alpha = 0; } else if (lenSqr >= fDistFarSqr) { alpha = 0xff; } else { float len; len = VectorLength(org); alpha = (((VectorLength(org) - tess.shader->fDistNear) / tess.shader->fDistRange) * 255.0); } if (pStage->alphaGen == AGEN_TIKI_DIST_FADE) { alpha = 0xff - alpha; } } else { const char* type; if (pStage->alphaGen == AGEN_TIKI_DIST_FADE) { type = "tikiDistFade"; } else { type = "oneMinusTikiDistFade"; } ri.Error(ERR_DROP, "ERROR: '%s' shader command used on a non-tiki\n", type); alpha = 0xff; } RB_CalcAlphaFromConstant((unsigned char*)tess.svars.colors, alpha); } break; case AGEN_DOT_VIEW: RB_CalcAlphaFromDotView((unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax); break; case AGEN_ONE_MINUS_DOT_VIEW: RB_CalcAlphaFromOneMinusDotView((unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax); break; case AGEN_HEIGHT_FADE: RB_CalcAlphaFromHeightFade((unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax); break; default: break; } } /* =============== ComputeTexCoords =============== */ static void ComputeTexCoords( shaderStage_t *pStage ) { int i; int b; for ( b = 0; b < NUM_TEXTURE_BUNDLES && pStage->bundle[b].image[0]; b++ ) { int tm; // // generate the texture coordinates // switch ( pStage->bundle[b].tcGen ) { case TCGEN_IDENTITY: Com_Memset( tess.svars.texcoords[b], 0, sizeof( float ) * 2 * tess.numVertexes ); break; case TCGEN_TEXTURE: for ( i = 0 ; i < tess.numVertexes ; i++ ) { tess.svars.texcoords[b][i][0] = tess.texCoords[i][0][0]; tess.svars.texcoords[b][i][1] = tess.texCoords[i][0][1]; } break; case TCGEN_LIGHTMAP: for ( i = 0 ; i < tess.numVertexes ; i++ ) { tess.svars.texcoords[b][i][0] = tess.texCoords[i][1][0]; tess.svars.texcoords[b][i][1] = tess.texCoords[i][1][1]; } break; case TCGEN_ENVIRONMENT_MAPPED: RB_CalcEnvironmentTexCoords( ( float * ) tess.svars.texcoords[b] ); break; case TCGEN_VECTOR: for (i = 0; i < tess.numVertexes; i++) { tess.svars.texcoords[b][i][0] = DotProduct(tess.xyz[i], pStage->bundle[b].tcGenVectors[0]); tess.svars.texcoords[b][i][1] = DotProduct(tess.xyz[i], pStage->bundle[b].tcGenVectors[1]); } break; case TCGEN_ENVIRONMENT_MAPPED2: RB_CalcEnvironmentTexCoords2((float*)tess.svars.texcoords[b]); break; case TCGEN_SUN_REFLECTION: RB_CalcSunReflectionTexCoords(tess.svars.texcoords[b][0]); break; default: ri.Printf( PRINT_DEVELOPER, "WARNING: invalid tcGen '%d' specified for shader '%s'\n", pStage->bundle[b].tcGen, tess.shader->name ); break; } // // alter texture coordinates // for ( tm = 0; tm < pStage->bundle[b].numTexMods ; tm++ ) { switch ( pStage->bundle[b].texMods[tm].type ) { case TMOD_NONE: tm = TR_MAX_TEXMODS; // break out of for loop break; case TMOD_TURBULENT: RB_CalcTurbulentTexCoords( &pStage->bundle[b].texMods[tm].wave, ( float * ) tess.svars.texcoords[b] ); break; case TMOD_ENTITY_TRANSLATE: RB_CalcScrollTexCoords( backEnd.currentEntity->e.shaderTexCoord, ( float * ) tess.svars.texcoords[b] ); break; case TMOD_SCROLL: RB_CalcScrollTexCoords( pStage->bundle[b].texMods[tm].scroll, ( float * ) tess.svars.texcoords[b] ); break; case TMOD_SCALE: RB_CalcScaleTexCoords( pStage->bundle[b].texMods[tm].scale, ( float * ) tess.svars.texcoords[b] ); break; case TMOD_STRETCH: RB_CalcStretchTexCoords( &pStage->bundle[b].texMods[tm].wave, ( float * ) tess.svars.texcoords[b] ); break; case TMOD_TRANSFORM: RB_CalcTransformTexCoords( &pStage->bundle[b].texMods[tm], ( float * ) tess.svars.texcoords[b] ); break; case TMOD_ROTATE: RB_CalcRotateTexCoords( pStage->bundle[b].texMods[tm].rotateSpeed, pStage->bundle[b].texMods[tm].rotateCoef, ( float * ) tess.svars.texcoords[b], pStage->bundle[b].texMods[tm].rotateStart ); break; case TMOD_OFFSET: RB_CalcOffsetTexCoords(pStage->bundle[b].texMods[tm].scroll, tess.svars.texcoords[b][0]); break; case TMOD_PARALLAX: RB_CalcParallaxTexCoords(pStage->bundle[b].texMods[tm].rate, tess.svars.texcoords[b][0]); break; case TMOD_MACRO: RB_CalcMacroTexCoords(pStage->bundle[b].texMods[tm].scale, tess.svars.texcoords[b][0]); break; case TMOD_WAVETRANS: RB_CalcTransWaveTexCoords(&pStage->bundle[b].texMods[tm].wave, tess.svars.texcoords[b][0]); break; case TMOD_WAVETRANT: RB_CalcTransWaveTexCoordsT(&pStage->bundle[b].texMods[tm].wave, tess.svars.texcoords[b][0]); break; case TMOD_BULGETRANS: RB_CalcBulgeTexCoords(&pStage->bundle[b].texMods[tm].wave, tess.svars.texcoords[b][0]); break; default: ri.Printf(PRINT_WARNING, "WARNING: invalid tcMod '%d' specified for shader '%s'\n", pStage->bundle[b].texMods[tm].type, tess.shader->name ); break; } } } } /* ** RB_IterateStagesGeneric */ static void RB_IterateStagesGeneric( shaderCommands_t *input ) { int stage; for (stage = 0; stage < MAX_SHADER_STAGES; stage++) { shaderStage_t* pStage = tess.xstages[stage]; if (!pStage) { break; } if (pStage->alphaGen == AGEN_SKYALPHA && tr.refdef.sky_alpha < 0.01) { continue; } else if (pStage->alphaGen == AGEN_ONE_MINUS_SKYALPHA && tr.refdef.sky_alpha > 0.99) { continue; } ComputeTexCoords(pStage); ComputeColors(pStage); if (!setArraysOnce) { qglEnableClientState(GL_COLOR_ARRAY); qglColorPointer(4, GL_UNSIGNED_BYTE, 0, input->svars.colors); } // // do multitexture // if (pStage->bundle[1].image[0] != 0) { DrawMultitextured(input, stage); } else { if (backEnd.in2D) { GL_State(pStage->stateBits | GLS_DEPTHTEST_DISABLE); } else { GL_State(pStage->stateBits); } if (!setArraysOnce) { qglTexCoordPointer(2, GL_FLOAT, 0, input->svars.texcoords[0]); } // // set state // if (pStage->bundle[0].vertexLightmap && r_vertexLight->integer && r_lightmap->integer) { GL_Bind(tr.whiteImage); } else { if (input->dlightMap && tess.xstages[stage]->bundle[0].isLightmap) { GL_Bind(tr.dlightImages[input->dlightMap - 1]); } else { R_BindAnimatedImage(&pStage->bundle[0]); } } // // draw // R_DrawElements(input->numIndexes, input->indexes); } // allow skipping out to show just lightmaps during development if (r_lightmap->integer && (pStage->bundle[0].isLightmap || pStage->bundle[1].isLightmap || pStage->bundle[0].vertexLightmap)) { break; } } } /* ** RB_StageIteratorGeneric */ void RB_StageIteratorGeneric( void ) { shaderCommands_t *input; input = &tess; input->shaderTime = backEnd.refdef.floatTime; if ((input->shader->flags & 1) != 0) { input->shaderTime = backEnd.refdef.floatTime - backEnd.shaderStartTime; } RB_DeformTessGeometry(); // // log this call // if ( r_logFile->integer ) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment( va("--- RB_StageIteratorGeneric( %s ) ---\n", tess.shader->name) ); } // // set face culling appropriately // GL_Cull( input->shader->cullType ); // set polygon offset if necessary if ( input->shader->polygonOffset ) { qglEnable( GL_POLYGON_OFFSET_FILL ); qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value ); } // // if there is only a single pass then we can enable color // and texture arrays before we compile, otherwise we need // to avoid compiling those arrays since they will change // during multipass rendering // if (tess.numPasses > 1 || (tess.xstages[0] && tess.xstages[0]->multitextureEnv)) { setArraysOnce = qfalse; qglDisableClientState(GL_COLOR_ARRAY); qglDisableClientState(GL_TEXTURE_COORD_ARRAY); } else { setArraysOnce = qtrue; qglEnableClientState(GL_COLOR_ARRAY); qglColorPointer(4, GL_UNSIGNED_BYTE, 0, tess.svars.colors); qglEnableClientState(GL_TEXTURE_COORD_ARRAY); qglTexCoordPointer(2, GL_FLOAT, 0, tess.svars.texcoords[0]); } // // lock XYZ // qglVertexPointer (3, GL_FLOAT, 16, input->xyz); // padded for SIMD if (qglLockArraysEXT) { qglLockArraysEXT(0, input->numVertexes); GLimp_LogComment( "glLockArraysEXT\n" ); } // // enable color and texcoord arrays after the lock if necessary // if ( !setArraysOnce ) { qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); qglEnableClientState( GL_COLOR_ARRAY ); } // // call shader function // RB_IterateStagesGeneric( input ); // // now do any dynamic lighting needed // if (tess.dlightBits && !tess.dlightMap && tess.shader->sort <= SS_OPAQUE) { ProjectDlightTexture(); } // // unlock arrays // if (qglUnlockArraysEXT) { qglUnlockArraysEXT(); GLimp_LogComment( "glUnlockArraysEXT\n" ); } // // reset polygon offset // if ( input->shader->polygonOffset ) { qglDisable( GL_POLYGON_OFFSET_FILL ); } } /* ** RB_StageIteratorVertexLitTexture */ void RB_StageIteratorVertexLitTextureUnfogged( void ) { shaderCommands_t *input; shader_t *shader; input = &tess; shader = input->shader; if (backEnd.currentSphere->TessFunction && r_drawspherelights->integer) { // // compute colors using the sphere function // backEnd.currentSphere->TessFunction((unsigned char*)tess.svars.colors); } else if (backEnd.currentStaticModel) { // // compute colors // ComputeColors(input->xstages[0]); } else { // // calculate using previously calculated lighting grid // for the entity // RB_CalcLightGridColor((unsigned char*)tess.svars.colors); } // // log this call // if ( r_logFile->integer ) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment( va("--- RB_StageIteratorVertexLitTexturedUnfogged( %s ) ---\n", tess.shader->name) ); } // // set face culling appropriately // GL_Cull( input->shader->cullType ); // // set arrays and lock // qglEnableClientState( GL_COLOR_ARRAY); qglEnableClientState( GL_TEXTURE_COORD_ARRAY); qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors ); qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][0] ); qglVertexPointer (3, GL_FLOAT, 16, input->xyz); if ( qglLockArraysEXT ) { qglLockArraysEXT(0, input->numVertexes); GLimp_LogComment( "glLockArraysEXT\n" ); } // // call special shade routine // R_BindAnimatedImage( &tess.xstages[0]->bundle[0] ); GL_State( tess.xstages[0]->stateBits ); R_DrawElements( input->numIndexes, input->indexes ); // // now do any dynamic lighting needed // if ( tess.dlightBits && !tess.dlightMap && tess.shader->sort <= SS_OPAQUE ) { ProjectDlightTexture(); } // // unlock arrays // if (qglUnlockArraysEXT) { qglUnlockArraysEXT(); GLimp_LogComment( "glUnlockArraysEXT\n" ); } } //define REPLACE_MODE void RB_StageIteratorLightmappedMultitextureUnfogged( void ) { shaderCommands_t *input; input = &tess; // // log this call // if ( r_logFile->integer ) { // don't just call LogComment, or we will get // a call to va() every frame! GLimp_LogComment( va("--- RB_StageIteratorLightmappedMultitexture( %s ) ---\n", tess.shader->name) ); } // // set face culling appropriately // GL_Cull( input->shader->cullType ); // // set color, pointers, and lock // GL_State( GLS_DEFAULT | (tess.xstages[0]->stateBits & (GLS_ATEST_BITS | GLS_FOG_BITS))); qglVertexPointer( 3, GL_FLOAT, 16, input->xyz ); #ifdef REPLACE_MODE qglDisableClientState( GL_COLOR_ARRAY ); qglColor3f( 1, 1, 1 ); qglShadeModel( GL_FLAT ); #else qglEnableClientState( GL_COLOR_ARRAY ); qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.constantColor255 ); #endif // // select base stage // GL_SelectTexture( 0 ); qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); R_BindAnimatedImage( &tess.xstages[0]->bundle[0] ); qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][0] ); // // configure second stage // GL_SelectTexture( 1 ); qglEnable( GL_TEXTURE_2D ); if ( r_lightmap->integer ) { GL_TexEnv( GL_REPLACE ); } else { GL_TexEnv( GL_MODULATE ); } if (tess.dlightMap && tess.xstages[0]->bundle[1].isLightmap) { GL_Bind(tr.dlightImages[tess.dlightMap - 1]); } else { R_BindAnimatedImage(&tess.xstages[0]->bundle[1]); } qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][1] ); // // lock arrays // if ( qglLockArraysEXT ) { qglLockArraysEXT(0, input->numVertexes); GLimp_LogComment( "glLockArraysEXT\n" ); } R_DrawElements( input->numIndexes, input->indexes ); // // disable texturing on TEXTURE1, then select TEXTURE0 // qglDisable( GL_TEXTURE_2D ); qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); GL_SelectTexture( 0 ); #ifdef REPLACE_MODE GL_TexEnv( GL_MODULATE ); qglShadeModel( GL_SMOOTH ); #endif // // now do any dynamic lighting needed // if ( tess.dlightBits && !tess.dlightMap && tess.shader->sort <= SS_OPAQUE ) { ProjectDlightTexture(); } // // unlock arrays // if ( qglUnlockArraysEXT ) { qglUnlockArraysEXT(); GLimp_LogComment( "glUnlockArraysEXT\n" ); } } /* ** RB_EndSurface */ void RB_EndSurface( void ) { shaderCommands_t *input; input = &tess; if (input->numIndexes == 0) { return; } if (input->indexes[SHADER_MAX_INDEXES-1] != 0) { ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_INDEXES hit"); } if (input->xyz[SHADER_MAX_VERTEXES-1][0] != 0) { ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_VERTEXES hit"); } if (tess.xyz[SHADER_MAX_VERTEXES - 1][0] && r_shadows->integer != 3) { tess.xyz[SHADER_MAX_VERTEXES - 1][0] = 0; } if ( tess.shader == tr.shadowShader ) { RB_ComputeShadowVolume(); return; } // for debugging of sort order issues, stop rendering after a given sort value if ( r_debugSort->integer && r_debugSort->integer < tess.shader->sort ) { return; } // // update performance counters // backEnd.pc.c_shaders++; backEnd.pc.c_vertexes += tess.numVertexes; backEnd.pc.c_indexes += tess.numIndexes; backEnd.pc.c_totalIndexes += tess.numIndexes * tess.numPasses; // // call off to shader specific tess end function // tess.currentStageIteratorFunc(); if (!(tr.refdef.rdflags & RDF_NOWORLDMODEL) && !backEnd.in2D) { // // draw debugging stuff // if (r_showtris->integer && r_developer->integer) { DrawTris(input, r_showtris->integer); } if (r_shownormals->integer && r_developer->integer) { DrawNormals(input); } } // clear shader so we can tell we don't have any unclosed surfaces tess.numIndexes = 0; GLimp_LogComment( "----------\n" ); }