mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-12-04 02:03:43 +00:00
736ec20d4d
Don't include the lazy precompiled.h everywhere, only what's required for the compilation unit. platform.h needs to be included instead to provide all essential defines and types. All includes use the relative path to the neo or the game specific root. Move all idlib related includes from idlib/Lib.h to precompiled.h. precompiled.h still exists for the MFC stuff in tools/. Add some missing header guards.
1275 lines
35 KiB
C++
1275 lines
35 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
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 "renderer/tr_local.h"
|
|
|
|
#include "tools/compilers/dmap/dmap.h"
|
|
|
|
/*
|
|
|
|
given a set of faces that are clipped to the required frustum
|
|
|
|
make 2D projection for each vertex
|
|
|
|
for each edge
|
|
add edge, generating new points at each edge intersection
|
|
|
|
?add all additional edges to make a full triangulation
|
|
|
|
make full triangulation
|
|
|
|
for each triangle
|
|
find midpoint
|
|
find original triangle with midpoint closest to view
|
|
annotate triangle with that data
|
|
project all vertexes to that plane
|
|
output the triangle as a front cap
|
|
|
|
snap all vertexes
|
|
make a back plane projection for all vertexes
|
|
|
|
for each edge
|
|
if one side doesn't have a triangle
|
|
make a sil edge to back plane projection
|
|
continue
|
|
if triangles on both sides have two verts in common
|
|
continue
|
|
make a sil edge from one triangle to the other
|
|
|
|
|
|
|
|
|
|
classify triangles on common planes, so they can be optimized
|
|
|
|
what about interpenetrating triangles???
|
|
|
|
a perfect shadow volume will have every edge exactly matched with
|
|
an opposite, and no two triangles covering the same area on either
|
|
the back projection or a silhouette edge.
|
|
|
|
Optimizing the triangles on the projected plane can give a significant
|
|
improvement, but the quadratic time nature of the optimization process
|
|
probably makes it untenable.
|
|
|
|
There exists some small room for further triangle count optimizations of the volumes
|
|
by collapsing internal surface geometry in some cases, or allowing original triangles
|
|
to extend outside the exactly light frustum without being clipped, but it probably
|
|
isn't worth it.
|
|
|
|
Triangle count optimizations at the expense of a slight fill rate cost
|
|
may be apropriate in some cases.
|
|
|
|
|
|
Perform the complete clipping on all triangles
|
|
for each vertex
|
|
project onto the apropriate plane and mark plane bit as in use
|
|
for each triangle
|
|
if points project onto different planes, clip
|
|
*/
|
|
|
|
|
|
typedef struct {
|
|
idVec3 v[3];
|
|
idVec3 edge[3]; // positive side is inside the triangle
|
|
glIndex_t index[3];
|
|
idPlane plane; // positive side is forward for the triangle, which is away from the light
|
|
int planeNum; // from original triangle, not calculated from the clipped verts
|
|
} shadowTri_t;
|
|
|
|
static const int MAX_SHADOW_TRIS = 32768;
|
|
|
|
static shadowTri_t outputTris[MAX_SHADOW_TRIS];
|
|
static int numOutputTris;
|
|
|
|
typedef struct shadowOptEdge_s {
|
|
glIndex_t index[2];
|
|
struct shadowOptEdge_s *nextEdge;
|
|
} shadowOptEdge_t;
|
|
|
|
static const int MAX_SIL_EDGES = MAX_SHADOW_TRIS*3;
|
|
static shadowOptEdge_t silEdges[MAX_SIL_EDGES];
|
|
static int numSilEdges;
|
|
|
|
typedef struct silQuad_s {
|
|
int nearV[2];
|
|
int farV[2]; // will always be a projection of near[]
|
|
struct silQuad_s *nextQuad;
|
|
} silQuad_t;
|
|
|
|
static const int MAX_SIL_QUADS = MAX_SHADOW_TRIS*3;
|
|
static silQuad_t silQuads[MAX_SIL_QUADS];
|
|
static int numSilQuads;
|
|
|
|
|
|
typedef struct {
|
|
idVec3 normal; // all sil planes go through the projection origin
|
|
shadowOptEdge_t *edges;
|
|
silQuad_t *fragmentedQuads;
|
|
} silPlane_t;
|
|
|
|
static float EDGE_PLANE_EPSILON = 0.1f;
|
|
static float UNIQUE_EPSILON = 0.1f;
|
|
|
|
static int numSilPlanes;
|
|
static silPlane_t *silPlanes;
|
|
|
|
// the uniqued verts are still in projection centered space, not global space
|
|
static int numUniqued;
|
|
static int numUniquedBeforeProjection;
|
|
static int maxUniqued;
|
|
static idVec3 *uniqued;
|
|
|
|
static optimizedShadow_t ret;
|
|
static int maxRetIndexes;
|
|
|
|
static int FindUniqueVert( idVec3 &v );
|
|
|
|
//=====================================================================================
|
|
|
|
/*
|
|
=================
|
|
CreateEdgesForTri
|
|
=================
|
|
*/
|
|
static void CreateEdgesForTri( shadowTri_t *tri ) {
|
|
for ( int j = 0 ; j < 3 ; j++ ) {
|
|
idVec3 &v1 = tri->v[j];
|
|
idVec3 &v2 = tri->v[(j+1)%3];
|
|
|
|
tri->edge[j].Cross( v2, v1 );
|
|
tri->edge[j].Normalize();
|
|
}
|
|
}
|
|
|
|
|
|
static const float EDGE_EPSILON = 0.1f;
|
|
|
|
static bool TriOutsideTri( const shadowTri_t *a, const shadowTri_t *b ) {
|
|
#if 0
|
|
if ( a->v[0] * b->edge[0] <= EDGE_EPSILON
|
|
&& a->v[1] * b->edge[0] <= EDGE_EPSILON
|
|
&& a->v[2] * b->edge[0] <= EDGE_EPSILON ) {
|
|
return true;
|
|
}
|
|
if ( a->v[0] * b->edge[1] <= EDGE_EPSILON
|
|
&& a->v[1] * b->edge[1] <= EDGE_EPSILON
|
|
&& a->v[2] * b->edge[1] <= EDGE_EPSILON ) {
|
|
return true;
|
|
}
|
|
if ( a->v[0] * b->edge[2] <= EDGE_EPSILON
|
|
&& a->v[1] * b->edge[2] <= EDGE_EPSILON
|
|
&& a->v[2] * b->edge[2] <= EDGE_EPSILON ) {
|
|
return true;
|
|
}
|
|
#else
|
|
for ( int i = 0 ; i < 3 ; i++ ) {
|
|
int j;
|
|
for ( j = 0 ; j < 3 ; j++ ) {
|
|
float d = a->v[j] * b->edge[i];
|
|
if ( d > EDGE_EPSILON ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( j == 3 ) {
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
static bool TriBehindTri( const shadowTri_t *a, const shadowTri_t *b ) {
|
|
float d;
|
|
|
|
d = b->plane.Distance( a->v[0] );
|
|
if ( d > 0 ) {
|
|
return true;
|
|
}
|
|
d = b->plane.Distance( a->v[1] );
|
|
if ( d > 0 ) {
|
|
return true;
|
|
}
|
|
d = b->plane.Distance( a->v[2] );
|
|
if ( d > 0 ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
ClipTriangle_r
|
|
===================
|
|
*/
|
|
static int c_removedFragments;
|
|
static void ClipTriangle_r( const shadowTri_t *tri, int startTri, int skipTri, int numTris, const shadowTri_t *tris ) {
|
|
// create edge planes for this triangle
|
|
|
|
// compare against all the other triangles
|
|
for ( int i = startTri ; i < numTris ; i++ ) {
|
|
if ( i == skipTri ) {
|
|
continue;
|
|
}
|
|
const shadowTri_t *other = &tris[i];
|
|
|
|
if ( TriOutsideTri( tri, other ) ) {
|
|
continue;
|
|
}
|
|
if ( TriOutsideTri( other, tri ) ) {
|
|
continue;
|
|
}
|
|
// they overlap to some degree
|
|
|
|
// if other is behind tri, it doesn't clip it
|
|
if ( !TriBehindTri( tri, other ) ) {
|
|
continue;
|
|
}
|
|
|
|
// clip it
|
|
idWinding *w = new idWinding( tri->v, 3 );
|
|
|
|
for ( int j = 0 ; j < 4 && w ; j++ ) {
|
|
idWinding *front, *back;
|
|
|
|
// keep any portion in front of other's plane
|
|
if ( j == 0 ) {
|
|
w->Split( other->plane, ON_EPSILON, &front, &back );
|
|
} else {
|
|
w->Split( idPlane( other->edge[j-1], 0.0f ), ON_EPSILON, &front, &back );
|
|
}
|
|
if ( back ) {
|
|
// recursively clip these triangles to all subsequent triangles
|
|
for ( int k = 2 ; k < back->GetNumPoints() ; k++ ) {
|
|
shadowTri_t fragment = *tri;
|
|
|
|
fragment.v[0] = (*back)[0].ToVec3();
|
|
fragment.v[1] = (*back)[k-1].ToVec3();
|
|
fragment.v[2] = (*back)[k].ToVec3();
|
|
CreateEdgesForTri( &fragment );
|
|
ClipTriangle_r( &fragment, i + 1, skipTri, numTris, tris );
|
|
}
|
|
delete back;
|
|
}
|
|
|
|
delete w;
|
|
w = front;
|
|
}
|
|
if ( w ) {
|
|
delete w;
|
|
}
|
|
|
|
c_removedFragments++;
|
|
// any fragments will have been added recursively
|
|
return;
|
|
}
|
|
|
|
// this fragment is frontmost, so add it to the output list
|
|
if ( numOutputTris == MAX_SHADOW_TRIS ) {
|
|
common->Error( "numOutputTris == MAX_SHADOW_TRIS" );
|
|
}
|
|
|
|
outputTris[numOutputTris] = *tri;
|
|
numOutputTris++;
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
ClipOccluders
|
|
|
|
Generates outputTris by clipping all the triangles against each other,
|
|
retaining only those closest to the projectionOrigin
|
|
====================
|
|
*/
|
|
static void ClipOccluders( idVec4 *verts, glIndex_t *indexes, int numIndexes,
|
|
idVec3 projectionOrigin ) {
|
|
int numTris = numIndexes / 3;
|
|
int i;
|
|
shadowTri_t *tris = (shadowTri_t *)_alloca( numTris * sizeof( *tris ) );
|
|
shadowTri_t *tri;
|
|
|
|
common->Printf( "ClipOccluders: %i triangles\n", numTris );
|
|
|
|
for ( i = 0 ; i < numTris ; i++ ) {
|
|
tri = &tris[i];
|
|
|
|
// the indexes are in reversed order from tr_stencilshadow
|
|
tri->v[0] = verts[indexes[i*3+2]].ToVec3() - projectionOrigin;
|
|
tri->v[1] = verts[indexes[i*3+1]].ToVec3() - projectionOrigin;
|
|
tri->v[2] = verts[indexes[i*3+0]].ToVec3() - projectionOrigin;
|
|
|
|
idVec3 d1 = tri->v[1] - tri->v[0];
|
|
idVec3 d2 = tri->v[2] - tri->v[0];
|
|
|
|
tri->plane.ToVec4().ToVec3().Cross( d2, d1 );
|
|
tri->plane.ToVec4().ToVec3().Normalize();
|
|
tri->plane[3] = - ( tri->v[0] * tri->plane.ToVec4().ToVec3() );
|
|
|
|
// get the plane number before any clipping
|
|
// we should avoid polluting the regular dmap planes with these
|
|
// that are offset from the light origin...
|
|
tri->planeNum = FindFloatPlane( tri->plane );
|
|
|
|
CreateEdgesForTri( tri );
|
|
}
|
|
|
|
// clear our output buffer
|
|
numOutputTris = 0;
|
|
|
|
// for each triangle, clip against all other triangles
|
|
int numRemoved = 0;
|
|
int numComplete = 0;
|
|
int numFragmented = 0;
|
|
|
|
for ( i = 0 ; i < numTris ; i++ ) {
|
|
int oldOutput = numOutputTris;
|
|
c_removedFragments = 0;
|
|
ClipTriangle_r( &tris[i], 0, i, numTris, tris );
|
|
if ( numOutputTris == oldOutput ) {
|
|
numRemoved++; // completely unused
|
|
} else if ( c_removedFragments == 0 ) {
|
|
// the entire triangle is visible
|
|
numComplete++;
|
|
shadowTri_t *out = &outputTris[oldOutput];
|
|
*out = tris[i];
|
|
numOutputTris = oldOutput+1;
|
|
} else {
|
|
numFragmented++;
|
|
// we made at least one fragment
|
|
|
|
// if we are at the low optimization level, just use a single
|
|
// triangle if it produced any fragments
|
|
if ( dmapGlobals.shadowOptLevel == SO_CULL_OCCLUDED ) {
|
|
shadowTri_t *out = &outputTris[oldOutput];
|
|
*out = tris[i];
|
|
numOutputTris = oldOutput+1;
|
|
}
|
|
}
|
|
}
|
|
common->Printf( "%i triangles completely invisible\n", numRemoved );
|
|
common->Printf( "%i triangles completely visible\n", numComplete );
|
|
common->Printf( "%i triangles fragmented\n", numFragmented );
|
|
common->Printf( "%i shadowing fragments before optimization\n", numOutputTris );
|
|
}
|
|
|
|
//=====================================================================================
|
|
|
|
/*
|
|
================
|
|
OptimizeOutputTris
|
|
================
|
|
*/
|
|
static void OptimizeOutputTris( void ) {
|
|
int i;
|
|
|
|
// optimize the clipped surfaces
|
|
optimizeGroup_t *optGroups = NULL;
|
|
optimizeGroup_t *checkGroup;
|
|
|
|
for ( i = 0 ; i < numOutputTris ; i++ ) {
|
|
shadowTri_t *tri = &outputTris[i];
|
|
|
|
int planeNum = tri->planeNum;
|
|
|
|
// add it to an optimize group
|
|
for ( checkGroup = optGroups ; checkGroup ; checkGroup = checkGroup->nextGroup ) {
|
|
if ( checkGroup->planeNum == planeNum ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( !checkGroup ) {
|
|
// create a new optGroup
|
|
checkGroup = (optimizeGroup_t *)Mem_ClearedAlloc( sizeof( *checkGroup ) );
|
|
checkGroup->planeNum = planeNum;
|
|
checkGroup->nextGroup = optGroups;
|
|
optGroups = checkGroup;
|
|
}
|
|
|
|
// create a mapTri for the optGroup
|
|
mapTri_t *mtri = (mapTri_t *)Mem_ClearedAlloc( sizeof( *mtri ) );
|
|
mtri->v[0].xyz = tri->v[0];
|
|
mtri->v[1].xyz = tri->v[1];
|
|
mtri->v[2].xyz = tri->v[2];
|
|
mtri->next = checkGroup->triList;
|
|
checkGroup->triList = mtri;
|
|
}
|
|
|
|
OptimizeGroupList( optGroups );
|
|
|
|
numOutputTris = 0;
|
|
for ( checkGroup = optGroups ; checkGroup ; checkGroup = checkGroup->nextGroup ) {
|
|
for ( mapTri_t *mtri = checkGroup->triList ; mtri ; mtri = mtri->next ) {
|
|
shadowTri_t *tri = &outputTris[numOutputTris];
|
|
numOutputTris++;
|
|
tri->v[0] = mtri->v[0].xyz;
|
|
tri->v[1] = mtri->v[1].xyz;
|
|
tri->v[2] = mtri->v[2].xyz;
|
|
}
|
|
}
|
|
FreeOptimizeGroupList( optGroups );
|
|
}
|
|
|
|
//==================================================================================
|
|
|
|
static int EdgeSort( const void *a, const void *b ) {
|
|
if ( *(unsigned *)a < *(unsigned *)b ) {
|
|
return -1;
|
|
}
|
|
if ( *(unsigned *)a > *(unsigned *)b ) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
GenerateSilEdges
|
|
|
|
Output tris must be tjunction fixed and vertex uniqued
|
|
A edge that is not exactly matched is a silhouette edge
|
|
We could skip this and rely completely on the matched quad removal
|
|
for all sil edges, but this will avoid the bulk of the checks.
|
|
=====================
|
|
*/
|
|
static void GenerateSilEdges( void ) {
|
|
int i, j;
|
|
|
|
unsigned *edges = (unsigned *)_alloca( (numOutputTris*3+1)*sizeof(*edges) );
|
|
int numEdges = 0;
|
|
|
|
numSilEdges = 0;
|
|
|
|
for ( i = 0 ; i < numOutputTris ; i++ ) {
|
|
int a = outputTris[i].index[0];
|
|
int b = outputTris[i].index[1];
|
|
int c = outputTris[i].index[2];
|
|
if ( a == b || a == c || b == c ) {
|
|
continue; // degenerate
|
|
}
|
|
|
|
for ( j = 0 ; j < 3 ; j++ ) {
|
|
int v1, v2;
|
|
|
|
v1 = outputTris[i].index[j];
|
|
v2 = outputTris[i].index[(j+1)%3];
|
|
if ( v1 == v2 ) {
|
|
continue; // degenerate
|
|
}
|
|
if ( v1 > v2 ) {
|
|
edges[numEdges] = ( v1 << 16 ) | ( v2 << 1 );
|
|
} else {
|
|
edges[numEdges] = ( v2 << 16 ) | ( v1 << 1 ) | 1;
|
|
}
|
|
numEdges++;
|
|
}
|
|
}
|
|
|
|
qsort( edges, numEdges, sizeof( edges[0] ), EdgeSort );
|
|
edges[numEdges] = -1; // force the last to make an edge if no matched to previous
|
|
|
|
for ( i = 0 ; i < numEdges ; i++ ) {
|
|
if ( ( edges[i] ^ edges[i+1] ) == 1 ) {
|
|
// skip the next one, because we matched and
|
|
// removed both
|
|
i++;
|
|
continue;
|
|
}
|
|
// this is an unmatched edge, so we need to generate a sil plane
|
|
int v1, v2;
|
|
if ( edges[i] & 1 ) {
|
|
v2 = edges[i] >> 16;
|
|
v1 = ( edges[i] >> 1 ) & 0x7fff;
|
|
} else {
|
|
v1 = edges[i] >> 16;
|
|
v2 = ( edges[i] >> 1 ) & 0x7fff;
|
|
}
|
|
|
|
if ( numSilEdges == MAX_SIL_EDGES ) {
|
|
common->Error( "numSilEdges == MAX_SIL_EDGES" );
|
|
}
|
|
silEdges[numSilEdges].index[0] = v1;
|
|
silEdges[numSilEdges].index[1] = v2;
|
|
numSilEdges++;
|
|
}
|
|
}
|
|
|
|
//==================================================================================
|
|
|
|
/*
|
|
=====================
|
|
GenerateSilPlanes
|
|
|
|
Groups the silEdges into common planes
|
|
=====================
|
|
*/
|
|
void GenerateSilPlanes( void ) {
|
|
numSilPlanes = 0;
|
|
silPlanes = (silPlane_t *)Mem_Alloc( sizeof( *silPlanes ) * numSilEdges );
|
|
|
|
// identify the silPlanes
|
|
numSilPlanes = 0;
|
|
for ( int i = 0 ; i < numSilEdges ; i++ ) {
|
|
if ( silEdges[i].index[0] == silEdges[i].index[1] ) {
|
|
continue; // degenerate
|
|
}
|
|
|
|
idVec3 &v1 = uniqued[silEdges[i].index[0]];
|
|
idVec3 &v2 = uniqued[silEdges[i].index[1]];
|
|
|
|
// search for an existing plane
|
|
int j;
|
|
for ( j = 0 ; j < numSilPlanes ; j++ ) {
|
|
float d = v1 * silPlanes[j].normal;
|
|
float d2 = v2 * silPlanes[j].normal;
|
|
|
|
if ( fabs( d ) < EDGE_PLANE_EPSILON
|
|
&& fabs( d2 ) < EDGE_PLANE_EPSILON ) {
|
|
silEdges[i].nextEdge = silPlanes[j].edges;
|
|
silPlanes[j].edges = &silEdges[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( j == numSilPlanes ) {
|
|
// create a new silPlane
|
|
silPlanes[j].normal.Cross( v2, v1 );
|
|
silPlanes[j].normal.Normalize();
|
|
silEdges[i].nextEdge = NULL;
|
|
silPlanes[j].edges = &silEdges[i];
|
|
silPlanes[j].fragmentedQuads = NULL;
|
|
numSilPlanes++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//==================================================================================
|
|
|
|
/*
|
|
=============
|
|
SaveQuad
|
|
=============
|
|
*/
|
|
static void SaveQuad( silPlane_t *silPlane, silQuad_t &quad ) {
|
|
// this fragment is a final fragment
|
|
if ( numSilQuads == MAX_SIL_QUADS ) {
|
|
common->Error( "numSilQuads == MAX_SIL_QUADS" );
|
|
}
|
|
silQuads[numSilQuads] = quad;
|
|
silQuads[numSilQuads].nextQuad = silPlane->fragmentedQuads;
|
|
silPlane->fragmentedQuads = &silQuads[numSilQuads];
|
|
numSilQuads++;
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
FragmentSilQuad
|
|
|
|
Clip quads, or reconstruct?
|
|
Generate them T-junction free, or require another pass of fix-tjunc?
|
|
Call optimizer on a per-sil-plane basis?
|
|
will this ever introduce tjunctions with the front faces?
|
|
removal of planes can allow the rear projection to be farther optimized
|
|
|
|
For quad clipping
|
|
PlaneThroughEdge
|
|
|
|
quad clipping introduces new vertexes
|
|
|
|
Cannot just fragment edges, must emit full indexes
|
|
|
|
what is the bounds on max indexes?
|
|
the worst case is that all edges but one carve an existing edge in the middle,
|
|
giving twice the input number of indexes (I think)
|
|
|
|
can we avoid knowing about projected positions and still optimize?
|
|
|
|
Fragment all edges first
|
|
Introduces T-junctions
|
|
create additional silEdges, linked to silPlanes
|
|
|
|
In theory, we should never have more than one edge clipping a given
|
|
fragment, but it is more robust if we check them all
|
|
===================
|
|
*/
|
|
static void FragmentSilQuad( silQuad_t quad, silPlane_t *silPlane,
|
|
shadowOptEdge_t *startEdge, shadowOptEdge_t *skipEdge ) {
|
|
if ( quad.nearV[0] == quad.nearV[1] ) {
|
|
return;
|
|
}
|
|
|
|
for ( shadowOptEdge_t *check = startEdge ; check ; check = check->nextEdge ) {
|
|
if ( check == skipEdge ) {
|
|
// don't clip against self
|
|
continue;
|
|
}
|
|
|
|
if ( check->index[0] == check->index[1] ) {
|
|
continue;
|
|
}
|
|
|
|
// make planes through both points of check
|
|
for ( int i = 0 ; i < 2 ; i++ ) {
|
|
idVec3 plane;
|
|
|
|
plane.Cross( uniqued[check->index[i]], silPlane->normal );
|
|
plane.Normalize();
|
|
|
|
if ( plane.Length() < 0.9 ) {
|
|
continue;
|
|
}
|
|
|
|
// if the other point on check isn't on the negative side of the plane,
|
|
// flip the plane
|
|
if ( uniqued[check->index[!i]] * plane > 0 ) {
|
|
plane = -plane;
|
|
}
|
|
|
|
float d1 = uniqued[quad.nearV[0]] * plane;
|
|
float d2 = uniqued[quad.nearV[1]] * plane;
|
|
|
|
float d3 = uniqued[quad.farV[0]] * plane;
|
|
float d4 = uniqued[quad.farV[1]] * plane;
|
|
|
|
// it is better to conservatively NOT split the quad, which, at worst,
|
|
// will leave some extra overdraw
|
|
|
|
// if the plane divides the incoming edge, split it and recurse
|
|
// with the outside fraction before continuing with the inside fraction
|
|
if ( ( d1 > EDGE_PLANE_EPSILON && d3 > EDGE_PLANE_EPSILON && d2 < -EDGE_PLANE_EPSILON && d4 < -EDGE_PLANE_EPSILON )
|
|
|| ( d2 > EDGE_PLANE_EPSILON && d4 > EDGE_PLANE_EPSILON && d1 < -EDGE_PLANE_EPSILON && d3 < -EDGE_PLANE_EPSILON ) ) {
|
|
float f = d1 / ( d1 - d2 );
|
|
float f2 = d3 / ( d3 - d4 );
|
|
f = f2;
|
|
if ( f <= 0.0001 || f >= 0.9999 ) {
|
|
common->Error( "Bad silQuad fraction" );
|
|
}
|
|
|
|
// finding uniques may be causing problems here
|
|
idVec3 nearMid = (1-f) * uniqued[quad.nearV[0]] + f * uniqued[quad.nearV[1]];
|
|
int nearMidIndex = FindUniqueVert( nearMid );
|
|
idVec3 farMid = (1-f) * uniqued[quad.farV[0]] + f * uniqued[quad.farV[1]];
|
|
int farMidIndex = FindUniqueVert( farMid );
|
|
|
|
silQuad_t clipped = quad;
|
|
|
|
if ( d1 > EDGE_PLANE_EPSILON ) {
|
|
clipped.nearV[1] = nearMidIndex;
|
|
clipped.farV[1] = farMidIndex;
|
|
FragmentSilQuad( clipped, silPlane, check->nextEdge, skipEdge );
|
|
quad.nearV[0] = nearMidIndex;
|
|
quad.farV[0] = farMidIndex;
|
|
} else {
|
|
clipped.nearV[0] = nearMidIndex;
|
|
clipped.farV[0] = farMidIndex;
|
|
FragmentSilQuad( clipped, silPlane, check->nextEdge, skipEdge );
|
|
quad.nearV[1] = nearMidIndex;
|
|
quad.farV[1] = farMidIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
// make a plane through the line of check
|
|
idPlane separate;
|
|
|
|
idVec3 dir = uniqued[check->index[1]] - uniqued[check->index[0]];
|
|
separate.Normal().Cross( dir, silPlane->normal );
|
|
separate.Normal().Normalize();
|
|
separate.ToVec4()[3] = -(uniqued[check->index[1]] * separate.Normal());
|
|
|
|
// this may miss a needed separation when the quad would be
|
|
// clipped into a triangle and a quad
|
|
float d1 = separate.Distance( uniqued[quad.nearV[0]] );
|
|
float d2 = separate.Distance( uniqued[quad.farV[0]] );
|
|
|
|
if ( ( d1 < EDGE_PLANE_EPSILON && d2 < EDGE_PLANE_EPSILON )
|
|
|| ( d1 > -EDGE_PLANE_EPSILON && d2 > -EDGE_PLANE_EPSILON ) ) {
|
|
continue;
|
|
}
|
|
|
|
// split the quad at this plane
|
|
float f = d1 / ( d1 - d2 );
|
|
idVec3 mid0 = (1-f) * uniqued[quad.nearV[0]] + f * uniqued[quad.farV[0]];
|
|
int mid0Index = FindUniqueVert( mid0 );
|
|
|
|
d1 = separate.Distance( uniqued[quad.nearV[1]] );
|
|
d2 = separate.Distance( uniqued[quad.farV[1]] );
|
|
f = d1 / ( d1 - d2 );
|
|
if ( f < 0 || f > 1 ) {
|
|
continue;
|
|
}
|
|
|
|
idVec3 mid1 = (1-f) * uniqued[quad.nearV[1]] + f * uniqued[quad.farV[1]];
|
|
int mid1Index = FindUniqueVert( mid1 );
|
|
|
|
silQuad_t clipped = quad;
|
|
|
|
clipped.nearV[0] = mid0Index;
|
|
clipped.nearV[1] = mid1Index;
|
|
FragmentSilQuad( clipped, silPlane, check->nextEdge, skipEdge );
|
|
quad.farV[0] = mid0Index;
|
|
quad.farV[1] = mid1Index;
|
|
}
|
|
|
|
SaveQuad( silPlane, quad );
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
FragmentSilQuads
|
|
===============
|
|
*/
|
|
static void FragmentSilQuads( void ) {
|
|
// group the edges into common planes
|
|
GenerateSilPlanes();
|
|
|
|
numSilQuads = 0;
|
|
|
|
// fragment overlapping edges
|
|
for ( int i = 0 ; i < numSilPlanes ; i++ ) {
|
|
silPlane_t *sil = &silPlanes[i];
|
|
|
|
for ( shadowOptEdge_t *e1 = sil->edges ; e1 ; e1 = e1->nextEdge ) {
|
|
silQuad_t quad;
|
|
|
|
quad.nearV[0] = e1->index[0];
|
|
quad.nearV[1] = e1->index[1];
|
|
if ( e1->index[0] == e1->index[1] ) {
|
|
common->Error( "FragmentSilQuads: degenerate edge" );
|
|
}
|
|
quad.farV[0] = e1->index[0] + numUniquedBeforeProjection;
|
|
quad.farV[1] = e1->index[1] + numUniquedBeforeProjection;
|
|
FragmentSilQuad( quad, sil, sil->edges, e1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
//=======================================================================
|
|
|
|
/*
|
|
=====================
|
|
EmitFragmentedSilQuads
|
|
|
|
=====================
|
|
*/
|
|
static void EmitFragmentedSilQuads( void ) {
|
|
int i, j, k;
|
|
mapTri_t *mtri;
|
|
|
|
for ( i = 0 ; i < numSilPlanes ; i++ ) {
|
|
silPlane_t *sil = &silPlanes[i];
|
|
|
|
// prepare for optimizing the sil quads on each side of the sil plane
|
|
optimizeGroup_t groups[2];
|
|
memset( &groups, 0, sizeof( groups ) );
|
|
idPlane planes[2];
|
|
planes[0].Normal() = sil->normal;
|
|
planes[0][3] = 0;
|
|
planes[1] = -planes[0];
|
|
groups[0].planeNum = FindFloatPlane( planes[0] );
|
|
groups[1].planeNum = FindFloatPlane( planes[1] );
|
|
|
|
// emit the quads that aren't matched
|
|
for ( silQuad_t *f1 = sil->fragmentedQuads ; f1 ; f1 = f1->nextQuad ) {
|
|
silQuad_t *f2;
|
|
for ( f2 = sil->fragmentedQuads ; f2 ; f2 = f2->nextQuad ) {
|
|
if ( f2 == f1 ) {
|
|
continue;
|
|
}
|
|
// in theory, this is sufficient, but we might
|
|
// have some cases of tripple+ matching, or unclipped rear projections
|
|
if ( f1->nearV[0] == f2->nearV[1] && f1->nearV[1] == f2->nearV[0] ) {
|
|
break;
|
|
}
|
|
}
|
|
// if we went through all the quads without finding a match, emit the quad
|
|
if ( !f2 ) {
|
|
optimizeGroup_t *gr;
|
|
idVec3 v1, v2, normal;
|
|
|
|
mtri = (mapTri_t *)Mem_ClearedAlloc( sizeof( *mtri ) );
|
|
mtri->v[0].xyz = uniqued[f1->nearV[0]];
|
|
mtri->v[1].xyz = uniqued[f1->nearV[1]];
|
|
mtri->v[2].xyz = uniqued[f1->farV[1]];
|
|
|
|
v1 = mtri->v[1].xyz - mtri->v[0].xyz;
|
|
v2 = mtri->v[2].xyz - mtri->v[0].xyz;
|
|
normal.Cross( v2, v1 );
|
|
|
|
if ( normal * planes[0].Normal() > 0 ) {
|
|
gr = &groups[0];
|
|
} else {
|
|
gr = &groups[1];
|
|
}
|
|
|
|
mtri->next = gr->triList;
|
|
gr->triList = mtri;
|
|
|
|
mtri = (mapTri_t *)Mem_ClearedAlloc( sizeof( *mtri ) );
|
|
mtri->v[0].xyz = uniqued[f1->farV[0]];
|
|
mtri->v[1].xyz = uniqued[f1->nearV[0]];
|
|
mtri->v[2].xyz = uniqued[f1->farV[1]];
|
|
|
|
mtri->next = gr->triList;
|
|
gr->triList = mtri;
|
|
|
|
#if 0
|
|
// emit a sil quad all the way to the projection plane
|
|
int index = ret.totalIndexes;
|
|
if ( index + 6 > maxRetIndexes ) {
|
|
common->Error( "maxRetIndexes exceeded" );
|
|
}
|
|
ret.indexes[index+0] = f1->nearV[0];
|
|
ret.indexes[index+1] = f1->nearV[1];
|
|
ret.indexes[index+2] = f1->farV[1];
|
|
ret.indexes[index+3] = f1->farV[0];
|
|
ret.indexes[index+4] = f1->nearV[0];
|
|
ret.indexes[index+5] = f1->farV[1];
|
|
ret.totalIndexes += 6;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
// optimize
|
|
for ( j = 0 ; j < 2 ; j++ ) {
|
|
if ( !groups[j].triList ) {
|
|
continue;
|
|
}
|
|
if ( dmapGlobals.shadowOptLevel == SO_SIL_OPTIMIZE ) {
|
|
OptimizeGroupList( &groups[j] );
|
|
}
|
|
// add as indexes
|
|
for ( mtri = groups[j].triList ; mtri ; mtri = mtri->next ) {
|
|
for ( k = 0 ; k < 3 ; k++ ) {
|
|
if ( ret.totalIndexes == maxRetIndexes ) {
|
|
common->Error( "maxRetIndexes exceeded" );
|
|
}
|
|
ret.indexes[ret.totalIndexes] = FindUniqueVert( mtri->v[k].xyz );
|
|
ret.totalIndexes++;
|
|
}
|
|
}
|
|
FreeTriList( groups[j].triList );
|
|
}
|
|
}
|
|
|
|
// we don't need the silPlane grouping anymore
|
|
Mem_Free( silPlanes );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
EmitUnoptimizedSilEdges
|
|
=================
|
|
*/
|
|
static void EmitUnoptimizedSilEdges( void ) {
|
|
int i;
|
|
|
|
for ( i = 0 ; i < numSilEdges ; i++ ) {
|
|
int v1 = silEdges[i].index[0];
|
|
int v2 = silEdges[i].index[1];
|
|
int index = ret.totalIndexes;
|
|
ret.indexes[index+0] = v1;
|
|
ret.indexes[index+1] = v2;
|
|
ret.indexes[index+2] = v2+numUniquedBeforeProjection;
|
|
ret.indexes[index+3] = v1+numUniquedBeforeProjection;
|
|
ret.indexes[index+4] = v1;
|
|
ret.indexes[index+5] = v2+numUniquedBeforeProjection;
|
|
ret.totalIndexes += 6;
|
|
}
|
|
}
|
|
|
|
//==================================================================================
|
|
|
|
/*
|
|
================
|
|
FindUniqueVert
|
|
================
|
|
*/
|
|
static int FindUniqueVert( idVec3 &v ) {
|
|
int k;
|
|
|
|
for ( k = 0 ; k < numUniqued ; k++ ) {
|
|
idVec3 &check = uniqued[k];
|
|
if ( fabs( v[0] - check[0] ) < UNIQUE_EPSILON
|
|
&& fabs( v[1] - check[1] ) < UNIQUE_EPSILON
|
|
&& fabs( v[2] - check[2] ) < UNIQUE_EPSILON ) {
|
|
return k;
|
|
}
|
|
}
|
|
if ( numUniqued == maxUniqued ) {
|
|
common->Error( "FindUniqueVert: numUniqued == maxUniqued" );
|
|
}
|
|
uniqued[numUniqued] = v;
|
|
numUniqued++;
|
|
|
|
return k;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
UniqueVerts
|
|
|
|
Snaps all triangle verts together, setting tri->index[]
|
|
and generating numUniqued and uniqued.
|
|
These are still in projection-centered space, not global space
|
|
===================
|
|
*/
|
|
static void UniqueVerts( void ) {
|
|
int i, j;
|
|
|
|
// we may add to uniqued later when splitting sil edges, so leave
|
|
// some extra room
|
|
maxUniqued = 100000; // numOutputTris * 10 + 1000;
|
|
uniqued = (idVec3 *)Mem_Alloc( sizeof( *uniqued ) * maxUniqued );
|
|
numUniqued = 0;
|
|
|
|
for ( i = 0 ; i < numOutputTris ; i++ ) {
|
|
for ( j = 0 ; j < 3 ; j++ ) {
|
|
outputTris[i].index[j] = FindUniqueVert( outputTris[i].v[j] );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================
|
|
ProjectUniqued
|
|
======================
|
|
*/
|
|
static void ProjectUniqued( idVec3 projectionOrigin, idPlane projectionPlane ) {
|
|
// calculate the projection
|
|
idVec4 mat[4];
|
|
|
|
R_LightProjectionMatrix( projectionOrigin, projectionPlane, mat );
|
|
|
|
if ( numUniqued * 2 > maxUniqued ) {
|
|
common->Error( "ProjectUniqued: numUniqued * 2 > maxUniqued" );
|
|
}
|
|
|
|
// this is goofy going back and forth between the spaces,
|
|
// but I don't want to change R_LightProjectionMatrix righ tnow...
|
|
for ( int i = 0 ; i < numUniqued ; i++ ) {
|
|
// put the vert back in global space, instead of light centered space
|
|
idVec3 in = uniqued[i] + projectionOrigin;
|
|
|
|
// project to far plane
|
|
float w, oow;
|
|
idVec3 out;
|
|
|
|
w = in * mat[3].ToVec3() + mat[3][3];
|
|
|
|
oow = 1.0 / w;
|
|
out.x = ( in * mat[0].ToVec3() + mat[0][3] ) * oow;
|
|
out.y = ( in * mat[1].ToVec3() + mat[1][3] ) * oow;
|
|
out.z = ( in * mat[2].ToVec3() + mat[2][3] ) * oow;
|
|
|
|
uniqued[numUniqued+i] = out - projectionOrigin;
|
|
}
|
|
numUniqued *= 2;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
SuperOptimizeOccluders
|
|
|
|
This is the callback from the renderer shadow generation routine, after
|
|
verts have been culled against individual frustums of point lights
|
|
|
|
====================
|
|
*/
|
|
optimizedShadow_t SuperOptimizeOccluders( idVec4 *verts, glIndex_t *indexes, int numIndexes,
|
|
idPlane projectionPlane, idVec3 projectionOrigin )
|
|
{
|
|
memset( &ret, 0, sizeof( ret ) );
|
|
|
|
// generate outputTris, removing fragments that are occluded by closer fragments
|
|
ClipOccluders( verts, indexes, numIndexes, projectionOrigin );
|
|
|
|
if ( dmapGlobals.shadowOptLevel >= SO_CULL_OCCLUDED ) {
|
|
OptimizeOutputTris();
|
|
}
|
|
|
|
// match up common verts
|
|
UniqueVerts();
|
|
|
|
// now that we have uniqued the vertexes, we can find unmatched
|
|
// edges, which are silhouette planes
|
|
GenerateSilEdges();
|
|
|
|
// generate the projected verts
|
|
numUniquedBeforeProjection = numUniqued;
|
|
ProjectUniqued( projectionOrigin, projectionPlane );
|
|
|
|
// fragment the sil edges where the overlap,
|
|
// possibly generating some additional unique verts
|
|
if ( dmapGlobals.shadowOptLevel >= SO_CLIP_SILS ) {
|
|
FragmentSilQuads();
|
|
}
|
|
|
|
// indexes for face and projection caps
|
|
ret.numFrontCapIndexes = numOutputTris * 3;
|
|
ret.numRearCapIndexes = numOutputTris * 3;
|
|
if ( dmapGlobals.shadowOptLevel >= SO_CLIP_SILS ) {
|
|
ret.numSilPlaneIndexes = numSilQuads * 12; // this is the worst case with clipping
|
|
} else {
|
|
ret.numSilPlaneIndexes = numSilEdges * 6; // this is the worst case with clipping
|
|
}
|
|
|
|
ret.totalIndexes = 0;
|
|
|
|
maxRetIndexes = ret.numFrontCapIndexes + ret.numRearCapIndexes + ret.numSilPlaneIndexes;
|
|
|
|
ret.indexes = (glIndex_t *)Mem_Alloc( maxRetIndexes * sizeof( ret.indexes[0] ) );
|
|
for ( int i = 0 ; i < numOutputTris ; i++ ) {
|
|
// flip the indexes so the surface triangle faces outside the shadow volume
|
|
ret.indexes[i*3+0] = outputTris[i].index[2];
|
|
ret.indexes[i*3+1] = outputTris[i].index[1];
|
|
ret.indexes[i*3+2] = outputTris[i].index[0];
|
|
|
|
ret.indexes[(numOutputTris+i)*3+0] = numUniquedBeforeProjection + outputTris[i].index[0];
|
|
ret.indexes[(numOutputTris+i)*3+1] = numUniquedBeforeProjection + outputTris[i].index[1];
|
|
ret.indexes[(numOutputTris+i)*3+2] = numUniquedBeforeProjection + outputTris[i].index[2];
|
|
}
|
|
// emit the sil planes
|
|
ret.totalIndexes = ret.numFrontCapIndexes + ret.numRearCapIndexes;
|
|
|
|
if ( dmapGlobals.shadowOptLevel >= SO_CLIP_SILS ) {
|
|
// re-optimize the sil planes, cutting
|
|
EmitFragmentedSilQuads();
|
|
} else {
|
|
// indexes for silhouette edges
|
|
EmitUnoptimizedSilEdges();
|
|
}
|
|
|
|
// we have all the verts now
|
|
// create twice the uniqued verts
|
|
ret.numVerts = numUniqued;
|
|
ret.verts = (idVec3 *)Mem_Alloc( ret.numVerts * sizeof( ret.verts[0] ) );
|
|
for ( int i = 0 ; i < numUniqued ; i++ ) {
|
|
// put the vert back in global space, instead of light centered space
|
|
ret.verts[i] = uniqued[i] + projectionOrigin;
|
|
}
|
|
|
|
// set the final index count
|
|
ret.numSilPlaneIndexes = ret.totalIndexes - (ret.numFrontCapIndexes + ret.numRearCapIndexes);
|
|
|
|
// free out local data
|
|
Mem_Free( uniqued );
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
RemoveDegenerateTriangles
|
|
=================
|
|
*/
|
|
static void RemoveDegenerateTriangles( srfTriangles_t *tri ) {
|
|
int c_removed;
|
|
int i;
|
|
int a, b, c;
|
|
|
|
// check for completely degenerate triangles
|
|
c_removed = 0;
|
|
for ( i = 0 ; i < tri->numIndexes ; i+=3 ) {
|
|
a = tri->indexes[i];
|
|
b = tri->indexes[i+1];
|
|
c = tri->indexes[i+2];
|
|
if ( a == b || a == c || b == c ) {
|
|
c_removed++;
|
|
memmove( tri->indexes + i, tri->indexes + i + 3, ( tri->numIndexes - i - 3 ) * sizeof( tri->indexes[0] ) );
|
|
tri->numIndexes -= 3;
|
|
if ( i < tri->numShadowIndexesNoCaps ) {
|
|
tri->numShadowIndexesNoCaps -= 3;
|
|
}
|
|
if ( i < tri->numShadowIndexesNoFrontCaps ) {
|
|
tri->numShadowIndexesNoFrontCaps -= 3;
|
|
}
|
|
i -= 3;
|
|
}
|
|
}
|
|
|
|
// this doesn't free the memory used by the unused verts
|
|
|
|
if ( c_removed ) {
|
|
common->Printf( "removed %i degenerate triangles from shadow\n", c_removed );
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CleanupOptimizedShadowTris
|
|
|
|
Uniques all verts across the frustums
|
|
removes matched sil quads at frustum seams
|
|
removes degenerate tris
|
|
====================
|
|
*/
|
|
void CleanupOptimizedShadowTris( srfTriangles_t *tri ) {
|
|
int i;
|
|
|
|
// unique all the verts
|
|
maxUniqued = tri->numVerts;
|
|
uniqued = (idVec3 *)_alloca( sizeof( *uniqued ) * maxUniqued );
|
|
numUniqued = 0;
|
|
|
|
glIndex_t *remap = (glIndex_t *)_alloca( sizeof( *remap ) * tri->numVerts );
|
|
|
|
for ( i = 0 ; i < tri->numIndexes ; i++ ) {
|
|
if ( tri->indexes[i] > tri->numVerts || tri->indexes[i] < 0 ) {
|
|
common->Error( "CleanupOptimizedShadowTris: index out of range" );
|
|
}
|
|
}
|
|
|
|
for ( i = 0 ; i < tri->numVerts ; i++ ) {
|
|
remap[i] = FindUniqueVert( tri->shadowVertexes[i].xyz.ToVec3() );
|
|
}
|
|
tri->numVerts = numUniqued;
|
|
for ( i = 0 ; i < tri->numVerts ; i++ ) {
|
|
tri->shadowVertexes[i].xyz.ToVec3() = uniqued[i];
|
|
tri->shadowVertexes[i].xyz[3] = 1;
|
|
}
|
|
|
|
for ( i = 0 ; i < tri->numIndexes ; i++ ) {
|
|
tri->indexes[i] = remap[tri->indexes[i]];
|
|
}
|
|
|
|
// remove matched quads
|
|
int numSilIndexes = tri->numShadowIndexesNoCaps;
|
|
for ( int i = 0 ; i < numSilIndexes ; i+=6 ) {
|
|
int j;
|
|
for ( j = i+6 ; j < numSilIndexes ; j+=6 ) {
|
|
// if there is a reversed quad match, we can throw both of them out
|
|
// this is not a robust check, it relies on the exact ordering of
|
|
// quad indexes
|
|
if ( tri->indexes[i+0] == tri->indexes[j+1]
|
|
&& tri->indexes[i+1] == tri->indexes[j+0]
|
|
&& tri->indexes[i+2] == tri->indexes[j+3]
|
|
&& tri->indexes[i+3] == tri->indexes[j+5]
|
|
&& tri->indexes[i+4] == tri->indexes[j+1]
|
|
&& tri->indexes[i+5] == tri->indexes[j+3] ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( j == numSilIndexes ) {
|
|
continue;
|
|
}
|
|
int k;
|
|
// remove first quad
|
|
for ( k = i+6 ; k < j ; k++ ) {
|
|
tri->indexes[k-6] = tri->indexes[k];
|
|
}
|
|
// remove second quad
|
|
for ( k = j+6 ; k < tri->numIndexes ; k++ ) {
|
|
tri->indexes[k-12] = tri->indexes[k];
|
|
}
|
|
numSilIndexes -= 12;
|
|
i -= 6;
|
|
}
|
|
|
|
int removed = tri->numShadowIndexesNoCaps - numSilIndexes;
|
|
|
|
tri->numIndexes -= removed;
|
|
tri->numShadowIndexesNoCaps -= removed;
|
|
tri->numShadowIndexesNoFrontCaps -= removed;
|
|
|
|
// remove degenerates after we have removed quads, so the double
|
|
// triangle pairing isn't disturbed
|
|
RemoveDegenerateTriangles( tri );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
CreateLightShadow
|
|
|
|
This is called from dmap in util/surface.cpp
|
|
shadowerGroups should be exactly clipped to the light frustum before calling.
|
|
shadowerGroups is optimized by this function, but the contents can be freed, because the returned
|
|
lightShadow_t list is a further culling and optimization of the data.
|
|
========================
|
|
*/
|
|
srfTriangles_t *CreateLightShadow( optimizeGroup_t *shadowerGroups, const mapLight_t *light ) {;
|
|
|
|
common->Printf( "----- CreateLightShadow %p -----\n", light );
|
|
|
|
// optimize all the groups
|
|
OptimizeGroupList( shadowerGroups );
|
|
|
|
// combine all the triangles into one list
|
|
mapTri_t *combined;
|
|
|
|
combined = NULL;
|
|
for ( optimizeGroup_t *group = shadowerGroups ; group ; group = group->nextGroup ) {
|
|
combined = MergeTriLists( combined, CopyTriList( group->triList ) );
|
|
}
|
|
|
|
if ( !combined ) {
|
|
return NULL;
|
|
}
|
|
|
|
// find uniqued vertexes
|
|
srfTriangles_t *occluders = ShareMapTriVerts( combined );
|
|
|
|
FreeTriList( combined );
|
|
|
|
// find silhouette information for the triSurf
|
|
R_CleanupTriangles( occluders, false, true, false );
|
|
|
|
// let the renderer build the shadow volume normally
|
|
idRenderEntityLocal space;
|
|
|
|
space.modelMatrix[0] = 1;
|
|
space.modelMatrix[5] = 1;
|
|
space.modelMatrix[10] = 1;
|
|
space.modelMatrix[15] = 1;
|
|
|
|
srfCullInfo_t cullInfo;
|
|
memset( &cullInfo, 0, sizeof( cullInfo ) );
|
|
|
|
// call the normal shadow creation, but with the superOptimize flag set, which will
|
|
// call back to SuperOptimizeOccluders after clipping the triangles to each frustum
|
|
srfTriangles_t *shadowTris;
|
|
if ( dmapGlobals.shadowOptLevel == SO_MERGE_SURFACES ) {
|
|
shadowTris = R_CreateShadowVolume( &space, occluders, &light->def, SG_STATIC, cullInfo );
|
|
} else {
|
|
shadowTris = R_CreateShadowVolume( &space, occluders, &light->def, SG_OFFLINE, cullInfo );
|
|
}
|
|
R_FreeStaticTriSurf( occluders );
|
|
|
|
R_FreeInteractionCullInfo( cullInfo );
|
|
|
|
if ( shadowTris ) {
|
|
dmapGlobals.totalShadowTriangles += shadowTris->numIndexes / 3;
|
|
dmapGlobals.totalShadowVerts += shadowTris->numVerts / 3;
|
|
}
|
|
|
|
return shadowTris;
|
|
}
|