/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 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 3 of the License, or (at your option) any later version. Doom 3 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 Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "sys/platform.h" #include "idlib/containers/BinSearch.h" #include "renderer/VertexCache.h" #include "renderer/tr_local.h" /* ================= R_FinishDeform The ambientCache is on the stack, so we don't want to leave a reference to it that would try to be freed later. Create the ambientCache immediately. ================= */ static void R_FinishDeform( drawSurf_t *drawSurf, srfTriangles_t *newTri, idDrawVert *ac ) { if ( !newTri ) { return; } // generate current normals, tangents, and bitangents // We might want to support the possibility of deform functions generating // explicit normals, and we might also want to allow the cached deformInfo // optimization for these. // FIXME: this doesn't work, because the deformed surface is just the // ambient one, and there isn't an opportunity to generate light interactions if ( drawSurf->material->ReceivesLighting() ) { newTri->verts = ac; R_DeriveTangents( newTri, false ); newTri->verts = NULL; } newTri->ambientCache = vertexCache.AllocFrameTemp( ac, newTri->numVerts * sizeof( idDrawVert ) ); // if we are out of vertex cache, leave it the way it is if ( newTri->ambientCache ) { drawSurf->geo = newTri; } } /* ===================== R_AutospriteDeform Assuming all the triangles for this shader are independant quads, rebuild them as forward facing sprites ===================== */ static void R_AutospriteDeform( drawSurf_t *surf ) { int i; const idDrawVert *v; idVec3 mid, delta; float radius; idVec3 left, up; idVec3 leftDir, upDir; const srfTriangles_t *tri; srfTriangles_t *newTri; tri = surf->geo; if ( tri->numVerts & 3 ) { common->Warning( "R_AutospriteDeform: shader had odd vertex count" ); return; } if ( tri->numIndexes != ( tri->numVerts >> 2 ) * 6 ) { common->Warning( "R_AutospriteDeform: autosprite had odd index count" ); return; } R_GlobalVectorToLocal( surf->space->modelMatrix, tr.viewDef->renderView.viewaxis[1], leftDir ); R_GlobalVectorToLocal( surf->space->modelMatrix, tr.viewDef->renderView.viewaxis[2], upDir ); if ( tr.viewDef->isMirror ) { leftDir = vec3_origin - leftDir; } // this srfTriangles_t and all its indexes and caches are in frame // memory, and will be automatically disposed of newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); newTri->numVerts = tri->numVerts; newTri->numIndexes = tri->numIndexes; newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) ); idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); for ( i = 0 ; i < tri->numVerts ; i+=4 ) { // find the midpoint v = &tri->verts[i]; mid[0] = 0.25 * (v->xyz[0] + (v+1)->xyz[0] + (v+2)->xyz[0] + (v+3)->xyz[0]); mid[1] = 0.25 * (v->xyz[1] + (v+1)->xyz[1] + (v+2)->xyz[1] + (v+3)->xyz[1]); mid[2] = 0.25 * (v->xyz[2] + (v+1)->xyz[2] + (v+2)->xyz[2] + (v+3)->xyz[2]); delta = v->xyz - mid; radius = delta.Length() * 0.707; // / sqrt(2) left = leftDir * radius; up = upDir * radius; ac[i+0].xyz = mid + left + up; ac[i+0].st[0] = 0; ac[i+0].st[1] = 0; ac[i+1].xyz = mid - left + up; ac[i+1].st[0] = 1; ac[i+1].st[1] = 0; ac[i+2].xyz = mid - left - up; ac[i+2].st[0] = 1; ac[i+2].st[1] = 1; ac[i+3].xyz = mid + left - up; ac[i+3].st[0] = 0; ac[i+3].st[1] = 1; newTri->indexes[6*(i>>2)+0] = i; newTri->indexes[6*(i>>2)+1] = i+1; newTri->indexes[6*(i>>2)+2] = i+2; newTri->indexes[6*(i>>2)+3] = i; newTri->indexes[6*(i>>2)+4] = i+2; newTri->indexes[6*(i>>2)+5] = i+3; } R_FinishDeform( surf, newTri, ac ); } /* ===================== R_TubeDeform will pivot a rectangular quad along the center of its long axis Note that a geometric tube with even quite a few sides tube will almost certainly render much faster than this, so this should only be for faked volumetric tubes. Make sure this is used with twosided translucent shaders, because the exact side order may not be correct. ===================== */ static void R_TubeDeform( drawSurf_t *surf ) { int i, j; int indexes; const srfTriangles_t *tri; static int edgeVerts[6][2] = { { 0, 1 }, { 1, 2 }, { 2, 0 }, { 3, 4 }, { 4, 5 }, { 5, 3 } }; tri = surf->geo; if ( tri->numVerts & 3 ) { common->Error( "R_AutospriteDeform: shader had odd vertex count" ); } if ( tri->numIndexes != ( tri->numVerts >> 2 ) * 6 ) { common->Error( "R_AutospriteDeform: autosprite had odd index count" ); } // we need the view direction to project the minor axis of the tube // as the view changes idVec3 localView; R_GlobalPointToLocal( surf->space->modelMatrix, tr.viewDef->renderView.vieworg, localView ); // this srfTriangles_t and all its indexes and caches are in frame // memory, and will be automatically disposed of srfTriangles_t *newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); newTri->numVerts = tri->numVerts; newTri->numIndexes = tri->numIndexes; newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) ); memcpy( newTri->indexes, tri->indexes, newTri->numIndexes * sizeof( newTri->indexes[0] ) ); idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); memset( ac, 0, sizeof( idDrawVert ) * newTri->numVerts ); // this is a lot of work for two triangles... // we could precalculate a lot if it is an issue, but it would mess up // the shader abstraction for ( i = 0, indexes = 0 ; i < tri->numVerts ; i+=4, indexes+=6 ) { float lengths[2]; int nums[2]; idVec3 mid[2]; idVec3 major, minor; const idDrawVert *v1, *v2; // identify the two shortest edges out of the six defined by the indexes nums[0] = nums[1] = 0; lengths[0] = lengths[1] = 999999; for ( j = 0 ; j < 6 ; j++ ) { float l; v1 = &tri->verts[tri->indexes[i+edgeVerts[j][0]]]; v2 = &tri->verts[tri->indexes[i+edgeVerts[j][1]]]; l = ( v1->xyz - v2->xyz ).Length(); 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; } } // find the midpoints of the two short edges, which // will give us the major axis in object coordinates for ( j = 0 ; j < 2 ; j++ ) { v1 = &tri->verts[tri->indexes[i+edgeVerts[nums[j]][0]]]; v2 = &tri->verts[tri->indexes[i+edgeVerts[nums[j]][1]]]; mid[j][0] = 0.5 * (v1->xyz[0] + v2->xyz[0]); mid[j][1] = 0.5 * (v1->xyz[1] + v2->xyz[1]); mid[j][2] = 0.5 * (v1->xyz[2] + v2->xyz[2]); } // find the vector of the major axis major = mid[1] - mid[0]; // re-project the points for ( j = 0 ; j < 2 ; j++ ) { float l; int i1 = tri->indexes[i+edgeVerts[nums[j]][0]]; int i2 = tri->indexes[i+edgeVerts[nums[j]][1]]; idDrawVert *av1 = &ac[i1]; idDrawVert *av2 = &ac[i2]; *av1 = *(idDrawVert *)&tri->verts[i1]; *av2 = *(idDrawVert *)&tri->verts[i2]; l = 0.5 * lengths[j]; // cross this with the view direction to get minor axis idVec3 dir = mid[j] - localView; minor.Cross( major, dir ); minor.Normalize(); if ( j ) { av1->xyz = mid[j] - l * minor; av2->xyz = mid[j] + l * minor; } else { av1->xyz = mid[j] + l * minor; av2->xyz = mid[j] - l * minor; } } } R_FinishDeform( surf, newTri, ac ); } /* ===================== R_WindingFromTriangles ===================== */ #define MAX_TRI_WINDING_INDEXES 16 int R_WindingFromTriangles( const srfTriangles_t *tri, glIndex_t indexes[MAX_TRI_WINDING_INDEXES] ) { int i, j, k, l; indexes[0] = tri->indexes[0]; int numIndexes = 1; int numTris = tri->numIndexes / 3; do { // find an edge that goes from the current index to another // index that isn't already used, and isn't an internal edge for ( i = 0 ; i < numTris ; i++ ) { for ( j = 0 ; j < 3 ; j++ ) { if ( tri->indexes[i*3+j] != indexes[numIndexes-1] ) { continue; } int next = tri->indexes[i*3+(j+1)%3]; // make sure it isn't already used if ( numIndexes == 1 ) { if ( next == indexes[0] ) { continue; } } else { for ( k = 1 ; k < numIndexes ; k++ ) { if ( indexes[k] == next ) { break; } } if ( k != numIndexes ) { continue; } } // make sure it isn't an interior edge for ( k = 0 ; k < numTris ; k++ ) { if ( k == i ) { continue; } for ( l = 0 ; l < 3 ; l++ ) { int a, b; a = tri->indexes[k*3+l]; if ( a != next ) { continue; } b = tri->indexes[k*3+(l+1)%3]; if ( b != indexes[numIndexes-1] ) { continue; } // this is an interior edge break; } if ( l != 3 ) { break; } } if ( k != numTris ) { continue; } // add this to the list indexes[numIndexes] = next; numIndexes++; break; } if ( j != 3 ) { break; } } if ( numIndexes == tri->numVerts ) { break; } } while ( i != numTris ); return numIndexes; } /* ===================== R_FlareDeform ===================== */ /* static void R_FlareDeform( drawSurf_t *surf ) { const srfTriangles_t *tri; srfTriangles_t *newTri; idPlane plane; float dot; idVec3 localViewer; int j; tri = surf->geo; if ( tri->numVerts != 4 || tri->numIndexes != 6 ) { //FIXME: temp hack for flares on tripleted models common->Warning( "R_FlareDeform: not a single quad" ); return; } // this srfTriangles_t and all its indexes and caches are in frame // memory, and will be automatically disposed of newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); newTri->numVerts = 4; newTri->numIndexes = 2*3; newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) ); idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); // find the plane plane.FromPoints( tri->verts[tri->indexes[0]].xyz, tri->verts[tri->indexes[1]].xyz, tri->verts[tri->indexes[2]].xyz ); // if viewer is behind the plane, draw nothing R_GlobalPointToLocal( surf->space->modelMatrix, tr.viewDef->renderView.vieworg, localViewer ); float distFromPlane = localViewer * plane.Normal() + plane[3]; if ( distFromPlane <= 0 ) { newTri->numIndexes = 0; surf->geo = newTri; return; } idVec3 center; center = tri->verts[0].xyz; for ( j = 1 ; j < tri->numVerts ; j++ ) { center += tri->verts[j].xyz; } center *= 1.0/tri->numVerts; idVec3 dir = localViewer - center; dir.Normalize(); dot = dir * plane.Normal(); // set vertex colors based on plane angle int color = (int)(dot * 8 * 256); if ( color > 255 ) { color = 255; } for ( j = 0 ; j < newTri->numVerts ; j++ ) { ac[j].color[0] = ac[j].color[1] = ac[j].color[2] = color; ac[j].color[3] = 255; } float spread = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ] * r_flareSize.GetFloat(); idVec3 edgeDir[4][3]; glIndex_t indexes[MAX_TRI_WINDING_INDEXES]; int numIndexes = R_WindingFromTriangles( tri, indexes ); surf->material = declManager->FindMaterial( "textures/smf/anamorphicFlare" ); // only deal with quads if ( numIndexes != 4 ) { return; } // compute centroid idVec3 centroid, toeye, forward, up, left; centroid.Set( 0, 0, 0 ); for ( int i = 0; i < 4; i++ ) { centroid += tri->verts[ indexes[i] ].xyz; } centroid /= 4; // compute basis vectors up.Set( 0, 0, 1 ); toeye = centroid - localViewer; toeye.Normalize(); left = toeye.Cross( up ); up = left.Cross( toeye ); left = left * 40 * 6; up = up * 40; // compute flares struct flare_t { float angle; float length; }; static flare_t flares[] = { { 0, 100 }, { 90, 100 } }; for ( int i = 0; i < 4; i++ ) { memset( ac + i, 0, sizeof( ac[i] ) ); } ac[0].xyz = centroid - left; ac[0].st[0] = 0; ac[0].st[1] = 0; ac[1].xyz = centroid + up; ac[1].st[0] = 1; ac[1].st[1] = 0; ac[2].xyz = centroid + left; ac[2].st[0] = 1; ac[2].st[1] = 1; ac[3].xyz = centroid - up; ac[3].st[0] = 0; ac[3].st[1] = 1; // setup colors for ( j = 0 ; j < newTri->numVerts ; j++ ) { ac[j].color[0] = ac[j].color[1] = ac[j].color[2] = 255; ac[j].color[3] = 255; } // setup indexes static glIndex_t triIndexes[2*3] = { 0,1,2, 0,2,3 }; memcpy( newTri->indexes, triIndexes, sizeof( triIndexes ) ); R_FinishDeform( surf, newTri, ac ); } */ static void R_FlareDeform( drawSurf_t *surf ) { const srfTriangles_t *tri; srfTriangles_t *newTri; idPlane plane; float dot; idVec3 localViewer; int j; tri = surf->geo; if ( tri->numVerts != 4 || tri->numIndexes != 6 ) { //FIXME: temp hack for flares on tripleted models common->DPrintf( "R_FlareDeform: not a single quad\n" ); return; } // this srfTriangles_t and all its indexes and caches are in frame // memory, and will be automatically disposed of newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); newTri->numVerts = 16; newTri->numIndexes = 18*3; newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) ); idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); // find the plane if (!plane.FromPoints( tri->verts[tri->indexes[0]].xyz, tri->verts[tri->indexes[1]].xyz, tri->verts[tri->indexes[2]].xyz )) { common->Warning( "R_FlareDeform: plane.FromPoints failed" ); return; } // if viewer is behind the plane, draw nothing R_GlobalPointToLocal( surf->space->modelMatrix, tr.viewDef->renderView.vieworg, localViewer ); float distFromPlane = localViewer * plane.Normal() + plane[3]; if ( distFromPlane <= 0 ) { newTri->numIndexes = 0; surf->geo = newTri; return; } idVec3 center; center = tri->verts[0].xyz; for ( j = 1 ; j < tri->numVerts ; j++ ) { center += tri->verts[j].xyz; } center *= 1.0/tri->numVerts; idVec3 dir = localViewer - center; dir.Normalize(); dot = dir * plane.Normal(); // set vertex colors based on plane angle int color = (int)(dot * 8 * 256); if ( color > 255 ) { color = 255; } for ( j = 0 ; j < newTri->numVerts ; j++ ) { ac[j].color[0] = ac[j].color[1] = ac[j].color[2] = color; ac[j].color[3] = 255; } float spread = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ] * r_flareSize.GetFloat(); idVec3 edgeDir[4][3]; glIndex_t indexes[MAX_TRI_WINDING_INDEXES]; int numIndexes = R_WindingFromTriangles( tri, indexes ); // only deal with quads if ( numIndexes != 4 ) { return; } int i; // calculate vector directions for ( i = 0 ; i < 4 ; i++ ) { ac[i].xyz = tri->verts[ indexes[i] ].xyz; ac[i].st[0] = ac[i].st[1] = 0.5; idVec3 toEye = tri->verts[ indexes[i] ].xyz - localViewer; toEye.Normalize(); idVec3 d1 = tri->verts[ indexes[(i+1)%4] ].xyz - localViewer; d1.Normalize(); edgeDir[i][1].Cross( toEye, d1 ); edgeDir[i][1].Normalize(); edgeDir[i][1] = vec3_origin - edgeDir[i][1]; idVec3 d2 = tri->verts[ indexes[(i+3)%4] ].xyz - localViewer; d2.Normalize(); edgeDir[i][0].Cross( toEye, d2 ); edgeDir[i][0].Normalize(); edgeDir[i][2] = edgeDir[i][0] + edgeDir[i][1]; edgeDir[i][2].Normalize(); } // build all the points ac[4].xyz = tri->verts[ indexes[0] ].xyz + spread * edgeDir[0][0]; ac[4].st[0] = 0; ac[4].st[1] = 0.5; ac[5].xyz = tri->verts[ indexes[0] ].xyz + spread * edgeDir[0][2]; ac[5].st[0] = 0; ac[5].st[1] = 0; ac[6].xyz = tri->verts[ indexes[0] ].xyz + spread * edgeDir[0][1]; ac[6].st[0] = 0.5; ac[6].st[1] = 0; ac[7].xyz = tri->verts[ indexes[1] ].xyz + spread * edgeDir[1][0]; ac[7].st[0] = 0.5; ac[7].st[1] = 0; ac[8].xyz = tri->verts[ indexes[1] ].xyz + spread * edgeDir[1][2]; ac[8].st[0] = 1; ac[8].st[1] = 0; ac[9].xyz = tri->verts[ indexes[1] ].xyz + spread * edgeDir[1][1]; ac[9].st[0] = 1; ac[9].st[1] = 0.5; ac[10].xyz = tri->verts[ indexes[2] ].xyz + spread * edgeDir[2][0]; ac[10].st[0] = 1; ac[10].st[1] = 0.5; ac[11].xyz = tri->verts[ indexes[2] ].xyz + spread * edgeDir[2][2]; ac[11].st[0] = 1; ac[11].st[1] = 1; ac[12].xyz = tri->verts[ indexes[2] ].xyz + spread * edgeDir[2][1]; ac[12].st[0] = 0.5; ac[12].st[1] = 1; ac[13].xyz = tri->verts[ indexes[3] ].xyz + spread * edgeDir[3][0]; ac[13].st[0] = 0.5; ac[13].st[1] = 1; ac[14].xyz = tri->verts[ indexes[3] ].xyz + spread * edgeDir[3][2]; ac[14].st[0] = 0; ac[14].st[1] = 1; ac[15].xyz = tri->verts[ indexes[3] ].xyz + spread * edgeDir[3][1]; ac[15].st[0] = 0; ac[15].st[1] = 0.5; for ( i = 4 ; i < 16 ; i++ ) { idVec3 dir = ac[i].xyz - localViewer; float len = dir.Normalize(); float ang = dir * plane.Normal(); // ac[i].xyz -= dir * spread * 2; float newLen = -( distFromPlane / ang ); if ( newLen > 0 && newLen < len ) { ac[i].xyz = localViewer + dir * newLen; } ac[i].st[0] = 0; ac[i].st[1] = 0.5; } #if 1 static glIndex_t triIndexes[18*3] = { 0,4,5, 0,5,6, 0,6,7, 0,7,1, 1,7,8, 1,8,9, 15,4,0, 15,0,3, 3,0,1, 3,1,2, 2,1,9, 2,9,10, 14,15,3, 14,3,13, 13,3,2, 13,2,12, 12,2,11, 11,2,10 }; #else newTri->numIndexes = 12; static glIndex_t triIndexes[4*3] = { 0,1,2, 0,2,3, 0,4,5,0,5,6 }; #endif memcpy( newTri->indexes, triIndexes, sizeof( triIndexes ) ); R_FinishDeform( surf, newTri, ac ); } /* ===================== R_ExpandDeform Expands the surface along it's normals by a shader amount ===================== */ static void R_ExpandDeform( drawSurf_t *surf ) { int i; const srfTriangles_t *tri; srfTriangles_t *newTri; tri = surf->geo; // this srfTriangles_t and all its indexes and caches are in frame // memory, and will be automatically disposed of newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); newTri->numVerts = tri->numVerts; newTri->numIndexes = tri->numIndexes; newTri->indexes = tri->indexes; idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); float dist = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ]; for ( i = 0 ; i < tri->numVerts ; i++ ) { ac[i] = *(idDrawVert *)&tri->verts[i]; ac[i].xyz = tri->verts[i].xyz + tri->verts[i].normal * dist; } R_FinishDeform( surf, newTri, ac ); } /* ===================== R_MoveDeform Moves the surface along the X axis, mostly just for demoing the deforms ===================== */ static void R_MoveDeform( drawSurf_t *surf ) { int i; const srfTriangles_t *tri; srfTriangles_t *newTri; tri = surf->geo; // this srfTriangles_t and all its indexes and caches are in frame // memory, and will be automatically disposed of newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); newTri->numVerts = tri->numVerts; newTri->numIndexes = tri->numIndexes; newTri->indexes = tri->indexes; idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); float dist = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ]; for ( i = 0 ; i < tri->numVerts ; i++ ) { ac[i] = *(idDrawVert *)&tri->verts[i]; ac[i].xyz[0] += dist; } R_FinishDeform( surf, newTri, ac ); } //===================================================================================== /* ===================== R_TurbulentDeform Turbulently deforms the XYZ, S, and T values ===================== */ static void R_TurbulentDeform( drawSurf_t *surf ) { int i; const srfTriangles_t *tri; srfTriangles_t *newTri; tri = surf->geo; // this srfTriangles_t and all its indexes and caches are in frame // memory, and will be automatically disposed of newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); newTri->numVerts = tri->numVerts; newTri->numIndexes = tri->numIndexes; newTri->indexes = tri->indexes; idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); idDeclTable *table = (idDeclTable *)surf->material->GetDeformDecl(); float range = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ]; float timeOfs = surf->shaderRegisters[ surf->material->GetDeformRegister(1) ]; float domain = surf->shaderRegisters[ surf->material->GetDeformRegister(2) ]; float tOfs = 0.5; for ( i = 0 ; i < tri->numVerts ; i++ ) { float f = tri->verts[i].xyz[0] * 0.003 + tri->verts[i].xyz[1] * 0.007 + tri->verts[i].xyz[2] * 0.011; f = timeOfs + domain * f; f += timeOfs; ac[i] = *(idDrawVert *)&tri->verts[i]; ac[i].st[0] += range * table->TableLookup( f ); ac[i].st[1] += range * table->TableLookup( f + tOfs ); } R_FinishDeform( surf, newTri, ac ); } //===================================================================================== /* ===================== AddTriangleToIsland_r ===================== */ #define MAX_EYEBALL_TRIS 10 #define MAX_EYEBALL_ISLANDS 6 typedef struct { int tris[MAX_EYEBALL_TRIS]; int numTris; idBounds bounds; idVec3 mid; } eyeIsland_t; static void AddTriangleToIsland_r( const srfTriangles_t *tri, int triangleNum, bool *usedList, eyeIsland_t *island ) { int a, b, c; usedList[triangleNum] = true; // add to the current island if ( island->numTris == MAX_EYEBALL_TRIS ) { common->Error( "MAX_EYEBALL_TRIS" ); } island->tris[island->numTris] = triangleNum; island->numTris++; // recurse into all neighbors a = tri->indexes[triangleNum*3]; b = tri->indexes[triangleNum*3+1]; c = tri->indexes[triangleNum*3+2]; island->bounds.AddPoint( tri->verts[a].xyz ); island->bounds.AddPoint( tri->verts[b].xyz ); island->bounds.AddPoint( tri->verts[c].xyz ); int numTri = tri->numIndexes / 3; for ( int i = 0 ; i < numTri ; i++ ) { if ( usedList[i] ) { continue; } if ( tri->indexes[i*3+0] == a || tri->indexes[i*3+1] == a || tri->indexes[i*3+2] == a || tri->indexes[i*3+0] == b || tri->indexes[i*3+1] == b || tri->indexes[i*3+2] == b || tri->indexes[i*3+0] == c || tri->indexes[i*3+1] == c || tri->indexes[i*3+2] == c ) { AddTriangleToIsland_r( tri, i, usedList, island ); } } } /* ===================== R_EyeballDeform Each eyeball surface should have an separate upright triangle behind it, long end pointing out the eye, and another single triangle in front of the eye for the focus point. ===================== */ static void R_EyeballDeform( drawSurf_t *surf ) { int i, j, k; const srfTriangles_t *tri; srfTriangles_t *newTri; eyeIsland_t islands[MAX_EYEBALL_ISLANDS]; int numIslands; bool triUsed[MAX_EYEBALL_ISLANDS*MAX_EYEBALL_TRIS]; tri = surf->geo; // separate all the triangles into islands int numTri = tri->numIndexes / 3; if ( numTri > MAX_EYEBALL_ISLANDS*MAX_EYEBALL_TRIS ) { common->Printf( "R_EyeballDeform: too many triangles in surface" ); return; } memset( triUsed, 0, sizeof( triUsed ) ); for ( numIslands = 0 ; numIslands < MAX_EYEBALL_ISLANDS ; numIslands++ ) { islands[numIslands].numTris = 0; islands[numIslands].bounds.Clear(); for ( i = 0 ; i < numTri ; i++ ) { if ( !triUsed[i] ) { AddTriangleToIsland_r( tri, i, triUsed, &islands[numIslands] ); break; } } if ( i == numTri ) { break; } } // assume we always have two eyes, two origins, and two targets if ( numIslands != 3 ) { common->Printf( "R_EyeballDeform: %i triangle islands\n", numIslands ); return; } // this srfTriangles_t and all its indexes and caches are in frame // memory, and will be automatically disposed of // the surface cannot have more indexes or verts than the original newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); memset( newTri, 0, sizeof( *newTri ) ); newTri->numVerts = tri->numVerts; newTri->numIndexes = tri->numIndexes; newTri->indexes = (glIndex_t *)R_FrameAlloc( tri->numIndexes * sizeof( newTri->indexes[0] ) ); idDrawVert *ac = (idDrawVert *)_alloca16( tri->numVerts * sizeof( idDrawVert ) ); newTri->numIndexes = 0; // decide which islands are the eyes and points for ( i = 0 ; i < numIslands ; i++ ) { islands[i].mid = islands[i].bounds.GetCenter(); } for ( i = 0 ; i < numIslands ; i++ ) { eyeIsland_t *island = &islands[i]; if ( island->numTris == 1 ) { continue; } // the closest single triangle point will be the eye origin // and the next-to-farthest will be the focal point idVec3 origin, focus; int originIsland = 0; float dist[MAX_EYEBALL_ISLANDS]; int sortOrder[MAX_EYEBALL_ISLANDS]; for ( j = 0 ; j < numIslands ; j++ ) { idVec3 dir = islands[j].mid - island->mid; dist[j] = dir.Length(); sortOrder[j] = j; for ( k = j-1 ; k >= 0 ; k-- ) { if ( dist[k] > dist[k+1] ) { int temp = sortOrder[k]; sortOrder[k] = sortOrder[k+1]; sortOrder[k+1] = temp; float ftemp = dist[k]; dist[k] = dist[k+1]; dist[k+1] = ftemp; } } } originIsland = sortOrder[1]; origin = islands[originIsland].mid; focus = islands[sortOrder[2]].mid; // determine the projection directions based on the origin island triangle idVec3 dir = focus - origin; dir.Normalize(); const idVec3 &p1 = tri->verts[tri->indexes[islands[originIsland].tris[0]+0]].xyz; const idVec3 &p2 = tri->verts[tri->indexes[islands[originIsland].tris[0]+1]].xyz; const idVec3 &p3 = tri->verts[tri->indexes[islands[originIsland].tris[0]+2]].xyz; idVec3 v1 = p2 - p1; v1.Normalize(); idVec3 v2 = p3 - p1; v2.Normalize(); // texVec[0] will be the normal to the origin triangle idVec3 texVec[2]; texVec[0].Cross( v1, v2 ); texVec[1].Cross( texVec[0], dir ); for ( j = 0 ; j < 2 ; j++ ) { texVec[j] -= dir * ( texVec[j] * dir ); texVec[j].Normalize(); } // emit these triangles, generating the projected texcoords for ( j = 0 ; j < islands[i].numTris ; j++ ) { for ( k = 0 ; k < 3 ; k++ ) { int index = islands[i].tris[j] * 3; index = tri->indexes[index+k]; newTri->indexes[newTri->numIndexes++] = index; ac[index].xyz = tri->verts[index].xyz; idVec3 local = tri->verts[index].xyz - origin; ac[index].st[0] = 0.5 + local * texVec[0]; ac[index].st[1] = 0.5 + local * texVec[1]; } } } R_FinishDeform( surf, newTri, ac ); } //========================================================================================== /* ===================== R_ParticleDeform Emit particles from the surface instead of drawing it ===================== */ static void R_ParticleDeform( drawSurf_t *surf, bool useArea ) { const struct renderEntity_s *renderEntity = &surf->space->entityDef->parms; const struct viewDef_s *viewDef = tr.viewDef; const idDeclParticle *particleSystem = (idDeclParticle *)surf->material->GetDeformDecl(); if ( r_skipParticles.GetBool() ) { return; } #if 0 if ( renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME] && viewDef->renderView.time*0.001 >= renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME] ) { // the entire system has faded out return NULL; } #endif // // calculate the area of all the triangles // int numSourceTris = surf->geo->numIndexes / 3; float totalArea = 0; float *sourceTriAreas = NULL; const srfTriangles_t *srcTri = surf->geo; if ( useArea ) { sourceTriAreas = (float *)_alloca( sizeof( *sourceTriAreas ) * numSourceTris ); int triNum = 0; for ( int i = 0 ; i < srcTri->numIndexes ; i += 3, triNum++ ) { float area; area = idWinding::TriangleArea( srcTri->verts[srcTri->indexes[i]].xyz, srcTri->verts[srcTri->indexes[i+1]].xyz, srcTri->verts[srcTri->indexes[i+2]].xyz ); sourceTriAreas[triNum] = totalArea; totalArea += area; } } // // create the particles almost exactly the way idRenderModelPrt does // particleGen_t g; g.renderEnt = renderEntity; g.renderView = &viewDef->renderView; g.origin.Zero(); g.axis = mat3_identity; for ( int currentTri = 0; currentTri < ( ( useArea ) ? 1 : numSourceTris ); currentTri++ ) { for ( int stageNum = 0 ; stageNum < particleSystem->stages.Num() ; stageNum++ ) { idParticleStage *stage = particleSystem->stages[stageNum]; if ( !stage->material ) { continue; } if ( !stage->cycleMsec ) { continue; } if ( stage->hidden ) { // just for gui particle editor use continue; } // we interpret stage->totalParticles as "particles per map square area" // so the systems look the same on different size surfaces int totalParticles = ( useArea ) ? stage->totalParticles * totalArea / 4096.0 : ( stage->totalParticles ); int count = totalParticles * stage->NumQuadsPerParticle(); // allocate a srfTriangles in temp memory that can hold all the particles srfTriangles_t *tri; tri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *tri ) ); tri->numVerts = 4 * count; tri->numIndexes = 6 * count; tri->verts = (idDrawVert *)R_FrameAlloc( tri->numVerts * sizeof( tri->verts[0] ) ); tri->indexes = (glIndex_t *)R_FrameAlloc( tri->numIndexes * sizeof( tri->indexes[0] ) ); // just always draw the particles tri->bounds = stage->bounds; tri->numVerts = 0; idRandom steppingRandom, steppingRandom2; int stageAge = g.renderView->time + renderEntity->shaderParms[SHADERPARM_TIMEOFFSET] * 1000 - stage->timeOffset * 1000; int stageCycle = stageAge / stage->cycleMsec; // some particles will be in this cycle, some will be in the previous cycle steppingRandom.SetSeed( (( stageCycle << 10 ) & idRandom::MAX_RAND) ^ (int)( renderEntity->shaderParms[SHADERPARM_DIVERSITY] * idRandom::MAX_RAND ) ); steppingRandom2.SetSeed( (( (stageCycle-1) << 10 ) & idRandom::MAX_RAND) ^ (int)( renderEntity->shaderParms[SHADERPARM_DIVERSITY] * idRandom::MAX_RAND ) ); for ( int index = 0 ; index < totalParticles ; index++ ) { g.index = index; // bump the random steppingRandom.RandomInt(); steppingRandom2.RandomInt(); // calculate local age for this index int bunchOffset = stage->particleLife * 1000 * stage->spawnBunching * index / totalParticles; int particleAge = stageAge - bunchOffset; int particleCycle = particleAge / stage->cycleMsec; if ( particleCycle < 0 ) { // before the particleSystem spawned continue; } if ( stage->cycles && particleCycle >= stage->cycles ) { // cycled systems will only run cycle times continue; } if ( particleCycle == stageCycle ) { g.random = steppingRandom; } else { g.random = steppingRandom2; } int inCycleTime = particleAge - particleCycle * stage->cycleMsec; if ( renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME] && g.renderView->time - inCycleTime >= renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME]*1000 ) { // don't fire any more particles continue; } // supress particles before or after the age clamp g.frac = (float)inCycleTime / ( stage->particleLife * 1000 ); if ( g.frac < 0 ) { // yet to be spawned continue; } if ( g.frac > 1.0 ) { // this particle is in the deadTime band continue; } //--------------- // locate the particle origin and axis somewhere on the surface //--------------- int pointTri = currentTri; if ( useArea ) { // select a triangle based on an even area distribution pointTri = idBinSearch_LessEqual( sourceTriAreas, numSourceTris, g.random.RandomFloat() * totalArea ); } // now pick a random point inside pointTri const idDrawVert *v1 = &srcTri->verts[ srcTri->indexes[ pointTri * 3 + 0 ] ]; const idDrawVert *v2 = &srcTri->verts[ srcTri->indexes[ pointTri * 3 + 1 ] ]; const idDrawVert *v3 = &srcTri->verts[ srcTri->indexes[ pointTri * 3 + 2 ] ]; float f1 = g.random.RandomFloat(); float f2 = g.random.RandomFloat(); float f3 = g.random.RandomFloat(); float ft = 1.0f / ( f1 + f2 + f3 + 0.0001f ); f1 *= ft; f2 *= ft; f3 *= ft; g.origin = v1->xyz * f1 + v2->xyz * f2 + v3->xyz * f3; g.axis[0] = v1->tangents[0] * f1 + v2->tangents[0] * f2 + v3->tangents[0] * f3; g.axis[1] = v1->tangents[1] * f1 + v2->tangents[1] * f2 + v3->tangents[1] * f3; g.axis[2] = v1->normal * f1 + v2->normal * f2 + v3->normal * f3; //----------------------- // this is needed so aimed particles can calculate origins at different times g.originalRandom = g.random; g.age = g.frac * stage->particleLife; // if the particle doesn't get drawn because it is faded out or beyond a kill region, // don't increment the verts tri->numVerts += stage->CreateParticle( &g, tri->verts + tri->numVerts ); } if ( tri->numVerts > 0 ) { // build the index list int indexes = 0; for ( int i = 0 ; i < tri->numVerts ; i += 4 ) { tri->indexes[indexes+0] = i; tri->indexes[indexes+1] = i+2; tri->indexes[indexes+2] = i+3; tri->indexes[indexes+3] = i; tri->indexes[indexes+4] = i+3; tri->indexes[indexes+5] = i+1; indexes += 6; } tri->numIndexes = indexes; tri->ambientCache = vertexCache.AllocFrameTemp( tri->verts, tri->numVerts * sizeof( idDrawVert ) ); if ( tri->ambientCache ) { // add the drawsurf R_AddDrawSurf( tri, surf->space, renderEntity, stage->material, surf->scissorRect ); } } } } } //======================================================================================== /* ================= R_DeformDrawSurf ================= */ void R_DeformDrawSurf( drawSurf_t *drawSurf ) { if ( !drawSurf->material ) { return; } if ( r_skipDeforms.GetBool() ) { return; } switch ( drawSurf->material->Deform() ) { case DFRM_NONE: return; case DFRM_SPRITE: R_AutospriteDeform( drawSurf ); break; case DFRM_TUBE: R_TubeDeform( drawSurf ); break; case DFRM_FLARE: R_FlareDeform( drawSurf ); break; case DFRM_EXPAND: R_ExpandDeform( drawSurf ); break; case DFRM_MOVE: R_MoveDeform( drawSurf ); break; case DFRM_TURB: R_TurbulentDeform( drawSurf ); break; case DFRM_EYEBALL: R_EyeballDeform( drawSurf ); break; case DFRM_PARTICLE: R_ParticleDeform( drawSurf, true ); break; case DFRM_PARTICLE2: R_ParticleDeform( drawSurf, false ); break; } }