/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2000-2013 Darklegion Development Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous 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 3 of the License, or (at your option) any later version. Tremulous 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 Tremulous; if not, see =========================================================================== */ // tr_shade_calc.c #include "tr_local.h" #if idppc_altivec && !defined(__APPLE__) #include #endif #define WAVEVALUE( table, base, amplitude, phase, freq ) ((base) + table[ static_cast( ( ( (phase) + tess.shaderTime * (freq) ) * FUNCTABLE_SIZE ) ) & FUNCTABLE_MASK ] * (amplitude)) static float *TableForFunc( genFunc_t func ) { switch ( func ) { case GF_SIN: return tr.sinTable; case GF_TRIANGLE: return tr.triangleTable; case GF_SQUARE: return tr.squareTable; case GF_SAWTOOTH: return tr.sawToothTable; case GF_INVERSE_SAWTOOTH: return tr.inverseSawToothTable; case GF_NONE: default: break; } ri.Error( ERR_DROP, "TableForFunc called with invalid function '%d' in shader '%s'", func, tess.shader->name ); return NULL; } /* ** EvalWaveForm ** ** Evaluates a given waveForm_t, referencing backEnd.refdef.time directly */ static float EvalWaveForm( const waveForm_t *wf ) { float *table; table = TableForFunc( wf->func ); return WAVEVALUE( table, wf->base, wf->amplitude, wf->phase, wf->frequency ); } static float EvalWaveFormClamped( const waveForm_t *wf ) { float glow = EvalWaveForm( wf ); if ( glow < 0 ) { return 0; } if ( glow > 1 ) { return 1; } return glow; } /* ** RB_CalcStretchTexMatrix */ void RB_CalcStretchTexMatrix( const waveForm_t *wf, float *matrix ) { float p; p = 1.0f / EvalWaveForm( wf ); matrix[0] = p; matrix[2] = 0; matrix[4] = 0.5f - 0.5f * p; matrix[1] = 0; matrix[3] = p; matrix[5] = 0.5f - 0.5f * p; } /* ==================================================================== DEFORMATIONS ==================================================================== */ /* ======================== RB_CalcDeformVertexes ======================== */ void RB_CalcDeformVertexes( deformStage_t *ds ) { int i; vec3_t offset; float scale; float *xyz = ( float * ) tess.xyz; int16_t *normal = tess.normal[0]; float *table; if ( ds->deformationWave.frequency == 0 ) { scale = EvalWaveForm( &ds->deformationWave ); for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) { R_VaoUnpackNormal(offset, normal); xyz[0] += offset[0] * scale; xyz[1] += offset[1] * scale; xyz[2] += offset[2] * scale; } } else { table = TableForFunc( ds->deformationWave.func ); for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) { float off = ( xyz[0] + xyz[1] + xyz[2] ) * ds->deformationSpread; scale = WAVEVALUE( table, ds->deformationWave.base, ds->deformationWave.amplitude, ds->deformationWave.phase + off, ds->deformationWave.frequency ); R_VaoUnpackNormal(offset, normal); xyz[0] += offset[0] * scale; xyz[1] += offset[1] * scale; xyz[2] += offset[2] * scale; } } } /* ========================= RB_CalcDeformNormals Wiggle the normals for wavy environment mapping ========================= */ void RB_CalcDeformNormals( deformStage_t *ds ) { int i; float scale; float *xyz = ( float * ) tess.xyz; int16_t *normal = tess.normal[0]; for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) { vec3_t fNormal; R_VaoUnpackNormal(fNormal, normal); scale = 0.98f; scale = R_NoiseGet4f( xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, tess.shaderTime * ds->deformationWave.frequency ); fNormal[ 0 ] += ds->deformationWave.amplitude * scale; scale = 0.98f; scale = R_NoiseGet4f( 100 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, tess.shaderTime * ds->deformationWave.frequency ); fNormal[ 1 ] += ds->deformationWave.amplitude * scale; scale = 0.98f; scale = R_NoiseGet4f( 200 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, tess.shaderTime * ds->deformationWave.frequency ); fNormal[ 2 ] += ds->deformationWave.amplitude * scale; VectorNormalizeFast( fNormal ); R_VaoPackNormal(normal, fNormal); } } /* ======================== RB_CalcBulgeVertexes ======================== */ void RB_CalcBulgeVertexes( deformStage_t *ds ) { int i; const float *st = ( const float * ) tess.texCoords[0]; float *xyz = ( float * ) tess.xyz; int16_t *normal = tess.normal[0]; double now = backEnd.refdef.time * 0.001 * ds->bulgeSpeed; for ( i = 0; i < tess.numVertexes; i++, xyz += 4, st += 2, normal += 4 ) { int64_t off; float scale; vec3_t fNormal; R_VaoUnpackNormal(fNormal, normal); off = (float)( FUNCTABLE_SIZE / (M_PI*2) ) * ( st[0] * ds->bulgeWidth + now ); scale = tr.sinTable[ off & FUNCTABLE_MASK ] * ds->bulgeHeight; xyz[0] += fNormal[0] * scale; xyz[1] += fNormal[1] * scale; xyz[2] += fNormal[2] * scale; } } /* ====================== RB_CalcMoveVertexes A deformation that can move an entire surface along a wave path ====================== */ void RB_CalcMoveVertexes( deformStage_t *ds ) { int i; float *xyz; float *table; float scale; vec3_t offset; table = TableForFunc( ds->deformationWave.func ); scale = WAVEVALUE( table, ds->deformationWave.base, ds->deformationWave.amplitude, ds->deformationWave.phase, ds->deformationWave.frequency ); VectorScale( ds->moveVector, scale, offset ); xyz = ( float * ) tess.xyz; for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { VectorAdd( xyz, offset, xyz ); } } /* ============= DeformText Change a polygon into a bunch of text polygons ============= */ void DeformText( const char *text ) { int i; vec3_t origin, width, height; int len; int ch; float color[4]; float bottom, top; vec3_t mid; vec3_t fNormal; height[0] = 0; height[1] = 0; height[2] = -1; R_VaoUnpackNormal(fNormal, tess.normal[0]); CrossProduct( fNormal, height, width ); // find the midpoint of the box VectorClear( mid ); bottom = 999999; top = -999999; for ( i = 0 ; i < 4 ; i++ ) { VectorAdd( tess.xyz[i], mid, mid ); if ( tess.xyz[i][2] < bottom ) { bottom = tess.xyz[i][2]; } if ( tess.xyz[i][2] > top ) { top = tess.xyz[i][2]; } } VectorScale( mid, 0.25f, origin ); // determine the individual character size height[0] = 0; height[1] = 0; height[2] = ( top - bottom ) * 0.5f; VectorScale( width, height[2] * -0.75f, width ); // determine the starting position len = strlen( text ); VectorMA( origin, (len-1), width, origin ); // clear the shader indexes tess.numIndexes = 0; tess.numVertexes = 0; tess.firstIndex = 0; color[0] = color[1] = color[2] = color[3] = 1.0f; // draw each character for ( i = 0 ; i < len ; i++ ) { ch = text[i]; ch &= 255; if ( ch != ' ' ) { int row, col; float frow, fcol, size; row = ch>>4; col = ch&15; frow = row*0.0625f; fcol = col*0.0625f; size = 0.0625f; RB_AddQuadStampExt( origin, width, height, color, fcol, frow, fcol + size, frow + size ); } VectorMA( origin, -2, width, origin ); } } /* ================== GlobalVectorToLocal ================== */ static void GlobalVectorToLocal( const vec3_t in, vec3_t out ) { out[0] = DotProduct( in, backEnd.orientation.axis[0] ); out[1] = DotProduct( in, backEnd.orientation.axis[1] ); out[2] = DotProduct( in, backEnd.orientation.axis[2] ); } /* ===================== AutospriteDeform Assuming all the triangles for this shader are independant quads, rebuild them as forward facing sprites ===================== */ static void AutospriteDeform( void ) { int i; int oldVerts; float *xyz; vec3_t mid, delta; float radius; vec3_t left, up; vec3_t leftDir, upDir; if ( tess.numVertexes & 3 ) { ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd vertex count\n", tess.shader->name ); } if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd index count\n", tess.shader->name ); } oldVerts = tess.numVertexes; tess.numVertexes = 0; tess.numIndexes = 0; tess.firstIndex = 0; if ( backEnd.currentEntity != &tr.worldEntity ) { GlobalVectorToLocal( backEnd.viewParms.orientation.axis[1], leftDir ); GlobalVectorToLocal( backEnd.viewParms.orientation.axis[2], upDir ); } else { VectorCopy( backEnd.viewParms.orientation.axis[1], leftDir ); VectorCopy( backEnd.viewParms.orientation.axis[2], upDir ); } for ( i = 0 ; i < oldVerts ; i+=4 ) { vec4_t color; // find the midpoint xyz = tess.xyz[i]; mid[0] = 0.25f * (xyz[0] + xyz[4] + xyz[8] + xyz[12]); mid[1] = 0.25f * (xyz[1] + xyz[5] + xyz[9] + xyz[13]); mid[2] = 0.25f * (xyz[2] + xyz[6] + xyz[10] + xyz[14]); VectorSubtract( xyz, mid, delta ); radius = VectorLength( delta ) * 0.707f; // / sqrt(2) VectorScale( leftDir, radius, left ); VectorScale( upDir, radius, up ); if ( backEnd.viewParms.isMirror ) { VectorSubtract( vec3_origin, left, left ); } // compensate for scale in the axes if necessary if ( backEnd.currentEntity->e.nonNormalizedAxes ) { float axisLength; axisLength = VectorLength( backEnd.currentEntity->e.axis[0] ); if ( !axisLength ) { axisLength = 0; } else { axisLength = 1.0f / axisLength; } VectorScale(left, axisLength, left); VectorScale(up, axisLength, up); } VectorScale4(tess.color[i], 1.0f / 65535.0f, color); RB_AddQuadStamp( mid, left, up, color ); } } /* ===================== Autosprite2Deform Autosprite2 will pivot a rectangular quad along the center of its long axis ===================== */ int edgeVerts[6][2] = { { 0, 1 }, { 0, 2 }, { 0, 3 }, { 1, 2 }, { 1, 3 }, { 2, 3 } }; static void Autosprite2Deform( void ) { int i, j, k; int indexes; float *xyz; vec3_t forward; if ( tess.numVertexes & 3 ) { ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd vertex count\n", tess.shader->name ); } if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd index count\n", tess.shader->name ); } if ( backEnd.currentEntity != &tr.worldEntity ) { GlobalVectorToLocal( backEnd.viewParms.orientation.axis[0], forward ); } else { VectorCopy( backEnd.viewParms.orientation.axis[0], forward ); } // this is a lot of work for two triangles... // we could precalculate a lot of it is an issue, but it would mess up // the shader abstraction for ( i = 0, indexes = 0 ; i < tess.numVertexes ; i+=4, indexes+=6 ) { float lengths[2]; int nums[2]; vec3_t mid[2]; vec3_t major, minor; float *v1, *v2; // find the midpoint xyz = tess.xyz[i]; // identify the two shortest edges nums[0] = nums[1] = 0; lengths[0] = lengths[1] = 999999; for ( j = 0 ; j < 6 ; j++ ) { float l; vec3_t temp; v1 = xyz + 4 * edgeVerts[j][0]; v2 = xyz + 4 * edgeVerts[j][1]; VectorSubtract( v1, v2, temp ); l = DotProduct( temp, temp ); if ( l < lengths[0] ) { nums[1] = nums[0]; lengths[1] = lengths[0]; nums[0] = j; lengths[0] = l; } else if ( l < lengths[1] ) { nums[1] = j; lengths[1] = l; } } for ( j = 0 ; j < 2 ; j++ ) { v1 = xyz + 4 * edgeVerts[nums[j]][0]; v2 = xyz + 4 * edgeVerts[nums[j]][1]; mid[j][0] = 0.5f * (v1[0] + v2[0]); mid[j][1] = 0.5f * (v1[1] + v2[1]); mid[j][2] = 0.5f * (v1[2] + v2[2]); } // find the vector of the major axis VectorSubtract( mid[1], mid[0], major ); // cross this with the view direction to get minor axis CrossProduct( major, forward, minor ); VectorNormalize( minor ); // re-project the points for ( j = 0 ; j < 2 ; j++ ) { float l; v1 = xyz + 4 * edgeVerts[nums[j]][0]; v2 = xyz + 4 * edgeVerts[nums[j]][1]; l = 0.5 * sqrt( lengths[j] ); // we need to see which direction this edge // is used to determine direction of projection for ( k = 0 ; k < 5 ; k++ ) { if ( tess.indexes[ indexes + k ] == i + edgeVerts[nums[j]][0] && tess.indexes[ indexes + k + 1 ] == i + edgeVerts[nums[j]][1] ) { break; } } if ( k == 5 ) { VectorMA( mid[j], l, minor, v1 ); VectorMA( mid[j], -l, minor, v2 ); } else { VectorMA( mid[j], -l, minor, v1 ); VectorMA( mid[j], l, minor, v2 ); } } } } /* ===================== RB_DeformTessGeometry ===================== */ void RB_DeformTessGeometry( void ) { int i; deformStage_t *ds; if(!ShaderRequiresCPUDeforms(tess.shader)) { // we don't need the following CPU deforms return; } for ( i = 0 ; i < tess.shader->numDeforms ; i++ ) { ds = &tess.shader->deforms[ i ]; switch ( ds->deformation ) { case DEFORM_NONE: break; case DEFORM_NORMALS: RB_CalcDeformNormals( ds ); break; case DEFORM_WAVE: RB_CalcDeformVertexes( ds ); break; case DEFORM_BULGE: RB_CalcBulgeVertexes( ds ); break; case DEFORM_MOVE: RB_CalcMoveVertexes( ds ); break; case DEFORM_PROJECTION_SHADOW: RB_ProjectionShadowDeform(); break; case DEFORM_AUTOSPRITE: AutospriteDeform(); break; case DEFORM_AUTOSPRITE2: Autosprite2Deform(); break; case DEFORM_TEXT0: case DEFORM_TEXT1: case DEFORM_TEXT2: case DEFORM_TEXT3: case DEFORM_TEXT4: case DEFORM_TEXT5: case DEFORM_TEXT6: case DEFORM_TEXT7: DeformText( backEnd.refdef.text[ds->deformation - DEFORM_TEXT0] ); break; } } } /* ==================================================================== COLORS ==================================================================== */ /* ** RB_CalcWaveColorSingle */ float RB_CalcWaveColorSingle( const waveForm_t *wf ) { float glow; if ( wf->func == GF_NOISE ) { glow = wf->base + R_NoiseGet4f( 0, 0, 0, ( tess.shaderTime + wf->phase ) * wf->frequency ) * wf->amplitude; } else { glow = EvalWaveForm( wf ) * tr.identityLight; } if ( glow < 0 ) { glow = 0; } else if ( glow > 1 ) { glow = 1; } return glow; } /* ** RB_CalcWaveAlphaSingle */ float RB_CalcWaveAlphaSingle( const waveForm_t *wf ) { return EvalWaveFormClamped( wf ); } /* ** RB_CalcModulateColorsByFog */ void RB_CalcModulateColorsByFog( unsigned char *colors ) { int i; float texCoords[SHADER_MAX_VERTEXES][2] = {{0.0f}}; // calculate texcoords so we can derive density // this is not wasted, because it would only have // been previously called if the surface was opaque RB_CalcFogTexCoords( texCoords[0] ); for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); colors[0] *= f; colors[1] *= f; colors[2] *= f; } } /* ==================================================================== TEX COORDS ==================================================================== */ /* ======================== RB_CalcFogTexCoords To do the clipped fog plane really correctly, we should use projected textures, but I don't trust the drivers and it doesn't fit our shader data. ======================== */ void RB_CalcFogTexCoords( float *st ) { int i; float *v; float s, t; float eyeT; bool eyeOutside; fog_t *fog; vec3_t local; vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0}; fog = tr.world->fogs + tess.fogNum; // all fogging distance is based on world Z units VectorSubtract( backEnd.orientation.origin, backEnd.viewParms.orientation.origin, local ); fogDistanceVector[0] = -backEnd.orientation.modelMatrix[2]; fogDistanceVector[1] = -backEnd.orientation.modelMatrix[6]; fogDistanceVector[2] = -backEnd.orientation.modelMatrix[10]; fogDistanceVector[3] = DotProduct( local, backEnd.viewParms.orientation.axis[0] ); // scale the fog vectors based on the fog's thickness fogDistanceVector[0] *= fog->tcScale; fogDistanceVector[1] *= fog->tcScale; fogDistanceVector[2] *= fog->tcScale; fogDistanceVector[3] *= fog->tcScale; // rotate the gradient vector for this orientation if ( fog->hasSurface ) { fogDepthVector[0] = fog->surface[0] * backEnd.orientation.axis[0][0] + fog->surface[1] * backEnd.orientation.axis[0][1] + fog->surface[2] * backEnd.orientation.axis[0][2]; fogDepthVector[1] = fog->surface[0] * backEnd.orientation.axis[1][0] + fog->surface[1] * backEnd.orientation.axis[1][1] + fog->surface[2] * backEnd.orientation.axis[1][2]; fogDepthVector[2] = fog->surface[0] * backEnd.orientation.axis[2][0] + fog->surface[1] * backEnd.orientation.axis[2][1] + fog->surface[2] * backEnd.orientation.axis[2][2]; fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.orientation.origin, fog->surface ); eyeT = DotProduct( backEnd.orientation.viewOrigin, fogDepthVector ) + fogDepthVector[3]; } else { eyeT = 1; // non-surface fog always has eye inside } // see if the viewpoint is outside // this is needed for clipping distance even for constant fog if ( eyeT < 0 ) { eyeOutside = true; } else { eyeOutside = false; } fogDistanceVector[3] += 1.0/512; // calculate density for each point for (i = 0, v = tess.xyz[0] ; i < tess.numVertexes ; i++, v += 4) { // calculate the length in fog s = DotProduct( v, fogDistanceVector ) + fogDistanceVector[3]; t = DotProduct( v, fogDepthVector ) + fogDepthVector[3]; // partially clipped fogs use the T axis if ( eyeOutside ) { if ( t < 1.0 ) { t = 1.0/32; // point is outside, so no fogging } else { t = 1.0/32 + 30.0/32 * t / ( t - eyeT ); // cut the distance at the fog plane } } else { if ( t < 0 ) { t = 1.0/32; // point is outside, so no fogging } else { t = 31.0/32; } } st[0] = s; st[1] = t; st += 2; } } /* ** RB_CalcTurbulentFactors */ void RB_CalcTurbulentFactors( const waveForm_t *wf, float *amplitude, float *now ) { *now = wf->phase + tess.shaderTime * wf->frequency; *amplitude = wf->amplitude; } /* ** RB_CalcScaleTexMatrix */ void RB_CalcScaleTexMatrix( const float scale[2], float *matrix ) { matrix[0] = scale[0]; matrix[2] = 0.0f; matrix[4] = 0.0f; matrix[1] = 0.0f; matrix[3] = scale[1]; matrix[5] = 0.0f; } /* ** RB_CalcScrollTexMatrix */ void RB_CalcScrollTexMatrix( const float scrollSpeed[2], float *matrix ) { double timeScale = tess.shaderTime; double adjustedScrollS = scrollSpeed[0] * timeScale; double adjustedScrollT = scrollSpeed[1] * timeScale; // clamp so coordinates don't continuously get larger, causing problems // with hardware limits adjustedScrollS -= floor( adjustedScrollS ); adjustedScrollT -= floor( adjustedScrollT ); matrix[0] = 1.0f; matrix[2] = 0.0f; matrix[4] = adjustedScrollS; matrix[1] = 0.0f; matrix[3] = 1.0f; matrix[5] = adjustedScrollT; } /* ** RB_CalcTransformTexMatrix */ void RB_CalcTransformTexMatrix( const texModInfo_t *tmi, float *matrix ) { matrix[0] = tmi->matrix[0][0]; matrix[2] = tmi->matrix[1][0]; matrix[4] = tmi->translate[0]; matrix[1] = tmi->matrix[0][1]; matrix[3] = tmi->matrix[1][1]; matrix[5] = tmi->translate[1]; } /* ** RB_CalcRotateTexMatrix */ void RB_CalcRotateTexMatrix( float degsPerSecond, float *matrix ) { double timeScale = tess.shaderTime; double degs; float sinValue, cosValue; degs = -degsPerSecond * timeScale; int i = degs * ( FUNCTABLE_SIZE / 360.0f ); sinValue = tr.sinTable[ i & FUNCTABLE_MASK ]; cosValue = tr.sinTable[ ( i + FUNCTABLE_SIZE / 4 ) & FUNCTABLE_MASK ]; matrix[0] = cosValue; matrix[2] = -sinValue; matrix[4] = 0.5 - 0.5 * cosValue + 0.5 * sinValue; matrix[1] = sinValue; matrix[3] = cosValue; matrix[5] = 0.5 - 0.5 * sinValue - 0.5 * cosValue; } bool ShaderRequiresCPUDeforms(const shader_t * shader) { if(shader->numDeforms) { const deformStage_t *ds = &shader->deforms[0]; if (shader->numDeforms > 1) return true; switch (ds->deformation) { case DEFORM_WAVE: case DEFORM_BULGE: // need CPU deforms at high level-times to avoid floating point percision loss return ( backEnd.refdef.floatTime != (float)backEnd.refdef.floatTime ); default: return true; } } return false; }