mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-30 16:11:11 +00:00
1395 lines
42 KiB
C++
1395 lines
42 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 "../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "tr_local.h"
|
|
|
|
// tr_stencilShadow.c -- creaton of stencil shadow volumes
|
|
|
|
/*
|
|
|
|
Should we split shadow volume surfaces when they exceed max verts
|
|
or max indexes?
|
|
|
|
a problem is that the number of vertexes needed for the
|
|
shadow volume will be twice the number in the original,
|
|
and possibly up to 8/3 when near plane clipped.
|
|
|
|
The maximum index count is 7x when not clipped and all
|
|
triangles are completely discrete. Near plane clipping
|
|
can increase this to 10x.
|
|
|
|
The maximum expansions are always with discrete triangles.
|
|
Meshes of triangles will result in less index expansion because
|
|
there will be less silhouette edges, although it will always be
|
|
greater than the source if a cap is present.
|
|
|
|
can't just project onto a plane if some surface points are
|
|
behind the light.
|
|
|
|
The cases when a face is edge on to a light is robustly handled
|
|
with closed volumes, because only a single one of it's neighbors
|
|
will pass the edge test. It may be an issue with non-closed models.
|
|
|
|
It is crucial that the shadow volumes be completely enclosed.
|
|
The triangles identified as shadow sources will be projected
|
|
directly onto the light far plane.
|
|
The sil edges must be handled carefully.
|
|
A partially clipped explicit sil edge will still generate a sil
|
|
edge.
|
|
EVERY new edge generated by clipping the triangles to the view
|
|
will generate a sil edge.
|
|
|
|
If a triangle has no points inside the frustum, it is completely
|
|
culled away. If a sil edge is either in or on the frustum, it is
|
|
added.
|
|
If a triangle has no points outside the frustum, it does not
|
|
need to be clipped.
|
|
|
|
|
|
|
|
USING THE STENCIL BUFFER FOR SHADOWING
|
|
|
|
basic triangle property
|
|
|
|
view plane inside shadow volume problem
|
|
|
|
quad triangulation issue
|
|
|
|
issues with silhouette optimizations
|
|
|
|
the shapes of shadow projections are poor for sphere or box culling
|
|
|
|
the gouraud shading problem
|
|
|
|
|
|
// epsilon culling rules:
|
|
|
|
// the positive side of the frustum is inside
|
|
d = tri->verts[i].xyz * frustum[j].Normal() + frustum[j][3];
|
|
if ( d < LIGHT_CLIP_EPSILON ) {
|
|
pointCull[i] |= ( 1 << j );
|
|
}
|
|
if ( d > -LIGHT_CLIP_EPSILON ) {
|
|
pointCull[i] |= ( 1 << (6+j) );
|
|
}
|
|
|
|
If a low order bit is set, the point is on or outside the plane
|
|
If a high order bit is set, the point is on or inside the plane
|
|
If a low order bit is clear, the point is inside the plane (definately positive)
|
|
If a high order bit is clear, the point is outside the plane (definately negative)
|
|
|
|
|
|
*/
|
|
|
|
#define TRIANGLE_CULLED(p1,p2,p3) ( pointCull[p1] & pointCull[p2] & pointCull[p3] & 0x3f )
|
|
|
|
//#define TRIANGLE_CLIPPED(p1,p2,p3) ( ( pointCull[p1] | pointCull[p2] | pointCull[p3] ) & 0xfc0 )
|
|
#define TRIANGLE_CLIPPED(p1,p2,p3) ( ( ( pointCull[p1] & pointCull[p2] & pointCull[p3] ) & 0xfc0 ) != 0xfc0 )
|
|
|
|
// an edge that is on the plane is NOT culled
|
|
#define EDGE_CULLED(p1,p2) ( ( pointCull[p1] ^ 0xfc0 ) & ( pointCull[p2] ^ 0xfc0 ) & 0xfc0 )
|
|
|
|
#define EDGE_CLIPPED(p1,p2) ( ( pointCull[p1] & pointCull[p2] & 0xfc0 ) != 0xfc0 )
|
|
|
|
// a point that is on the plane is NOT culled
|
|
//#define POINT_CULLED(p1) ( ( pointCull[p1] ^ 0xfc0 ) & 0xfc0 )
|
|
#define POINT_CULLED(p1) ( ( pointCull[p1] & 0xfc0 ) != 0xfc0 )
|
|
|
|
//#define LIGHT_CLIP_EPSILON 0.001f
|
|
#define LIGHT_CLIP_EPSILON 0.1f
|
|
|
|
#define MAX_CLIP_SIL_EDGES 2048
|
|
static int numClipSilEdges;
|
|
static int clipSilEdges[MAX_CLIP_SIL_EDGES][2];
|
|
|
|
// facing will be 0 if forward facing, 1 if backwards facing
|
|
// grabbed with alloca
|
|
static byte *globalFacing;
|
|
|
|
// faceCastsShadow will be 1 if the face is in the projection
|
|
// and facing the apropriate direction
|
|
static byte *faceCastsShadow;
|
|
|
|
static int *remap;
|
|
|
|
#define MAX_SHADOW_INDEXES 0x18000
|
|
#define MAX_SHADOW_VERTS 0x18000
|
|
static int numShadowIndexes;
|
|
static glIndex_t shadowIndexes[MAX_SHADOW_INDEXES];
|
|
static int numShadowVerts;
|
|
static idVec4 shadowVerts[MAX_SHADOW_VERTS];
|
|
static bool overflowed;
|
|
|
|
idPlane pointLightFrustums[6][6] = {
|
|
{
|
|
idPlane( 1,0,0,0 ),
|
|
idPlane( 1,1,0,0 ),
|
|
idPlane( 1,-1,0,0 ),
|
|
idPlane( 1,0,1,0 ),
|
|
idPlane( 1,0,-1,0 ),
|
|
idPlane( -1,0,0,0 ),
|
|
},
|
|
{
|
|
idPlane( -1,0,0,0 ),
|
|
idPlane( -1,1,0,0 ),
|
|
idPlane( -1,-1,0,0 ),
|
|
idPlane( -1,0,1,0 ),
|
|
idPlane( -1,0,-1,0 ),
|
|
idPlane( 1,0,0,0 ),
|
|
},
|
|
|
|
{
|
|
idPlane( 0,1,0,0 ),
|
|
idPlane( 0,1,1,0 ),
|
|
idPlane( 0,1,-1,0 ),
|
|
idPlane( 1,1,0,0 ),
|
|
idPlane( -1,1,0,0 ),
|
|
idPlane( 0,-1,0,0 ),
|
|
},
|
|
{
|
|
idPlane( 0,-1,0,0 ),
|
|
idPlane( 0,-1,1,0 ),
|
|
idPlane( 0,-1,-1,0 ),
|
|
idPlane( 1,-1,0,0 ),
|
|
idPlane( -1,-1,0,0 ),
|
|
idPlane( 0,1,0,0 ),
|
|
},
|
|
|
|
{
|
|
idPlane( 0,0,1,0 ),
|
|
idPlane( 1,0,1,0 ),
|
|
idPlane( -1,0,1,0 ),
|
|
idPlane( 0,1,1,0 ),
|
|
idPlane( 0,-1,1,0 ),
|
|
idPlane( 0,0,-1,0 ),
|
|
},
|
|
{
|
|
idPlane( 0,0,-1,0 ),
|
|
idPlane( 1,0,-1,0 ),
|
|
idPlane( -1,0,-1,0 ),
|
|
idPlane( 0,1,-1,0 ),
|
|
idPlane( 0,-1,-1,0 ),
|
|
idPlane( 0,0,1,0 ),
|
|
},
|
|
};
|
|
|
|
int c_caps, c_sils;
|
|
|
|
static bool callOptimizer; // call the preprocessor optimizer after clipping occluders
|
|
|
|
typedef struct {
|
|
int frontCapStart;
|
|
int rearCapStart;
|
|
int silStart;
|
|
int end;
|
|
} indexRef_t;
|
|
static indexRef_t indexRef[6];
|
|
static int indexFrustumNumber; // which shadow generating side of a light the indexRef is for
|
|
|
|
/*
|
|
===============
|
|
PointsOrdered
|
|
|
|
To make sure the triangulations of the sil edges is consistant,
|
|
we need to be able to order two points. We don't care about how
|
|
they compare with any other points, just that when the same two
|
|
points are passed in (in either order), they will always specify
|
|
the same one as leading.
|
|
|
|
Currently we need to have separate faces in different surfaces
|
|
order the same way, so we must look at the actual coordinates.
|
|
If surfaces are ever guaranteed to not have to edge match with
|
|
other surfaces, we could just compare indexes.
|
|
===============
|
|
*/
|
|
static bool PointsOrdered( const idVec3 &a, const idVec3 &b ) {
|
|
float i, j;
|
|
|
|
// vectors that wind up getting an equal hash value will
|
|
// potentially cause a misorder, which can show as a couple
|
|
// crack pixels in a shadow
|
|
|
|
// scale by some odd numbers so -8, 8, 8 will not be equal
|
|
// to 8, -8, 8
|
|
|
|
// in the very rare case that these might be equal, all that would
|
|
// happen is an oportunity for a tiny rasterization shadow crack
|
|
i = a[0] + a[1]*127 + a[2]*1023;
|
|
j = b[0] + b[1]*127 + b[2]*1023;
|
|
|
|
return (bool)(i < j);
|
|
}
|
|
|
|
/*
|
|
====================
|
|
R_LightProjectionMatrix
|
|
|
|
====================
|
|
*/
|
|
void R_LightProjectionMatrix( const idVec3 &origin, const idPlane &rearPlane, idVec4 mat[4] ) {
|
|
idVec4 lv;
|
|
float lg;
|
|
|
|
// calculate the homogenious light vector
|
|
lv.x = origin.x;
|
|
lv.y = origin.y;
|
|
lv.z = origin.z;
|
|
lv.w = 1;
|
|
|
|
lg = rearPlane.ToVec4() * lv;
|
|
|
|
// outer product
|
|
mat[0][0] = lg -rearPlane[0] * lv[0];
|
|
mat[0][1] = -rearPlane[1] * lv[0];
|
|
mat[0][2] = -rearPlane[2] * lv[0];
|
|
mat[0][3] = -rearPlane[3] * lv[0];
|
|
|
|
mat[1][0] = -rearPlane[0] * lv[1];
|
|
mat[1][1] = lg -rearPlane[1] * lv[1];
|
|
mat[1][2] = -rearPlane[2] * lv[1];
|
|
mat[1][3] = -rearPlane[3] * lv[1];
|
|
|
|
mat[2][0] = -rearPlane[0] * lv[2];
|
|
mat[2][1] = -rearPlane[1] * lv[2];
|
|
mat[2][2] = lg -rearPlane[2] * lv[2];
|
|
mat[2][3] = -rearPlane[3] * lv[2];
|
|
|
|
mat[3][0] = -rearPlane[0] * lv[3];
|
|
mat[3][1] = -rearPlane[1] * lv[3];
|
|
mat[3][2] = -rearPlane[2] * lv[3];
|
|
mat[3][3] = lg -rearPlane[3] * lv[3];
|
|
}
|
|
|
|
/*
|
|
===================
|
|
R_ProjectPointsToFarPlane
|
|
|
|
make a projected copy of the even verts into the odd spots
|
|
that is on the far light clip plane
|
|
===================
|
|
*/
|
|
static void R_ProjectPointsToFarPlane( const idRenderEntityLocal *ent, const idRenderLightLocal *light,
|
|
const idPlane &lightPlaneLocal,
|
|
int firstShadowVert, int numShadowVerts ) {
|
|
idVec3 lv;
|
|
idVec4 mat[4];
|
|
int i;
|
|
idVec4 *in;
|
|
|
|
R_GlobalPointToLocal( ent->modelMatrix, light->globalLightOrigin, lv );
|
|
R_LightProjectionMatrix( lv, lightPlaneLocal, mat );
|
|
|
|
#if 1
|
|
// make a projected copy of the even verts into the odd spots
|
|
in = &shadowVerts[firstShadowVert];
|
|
for ( i = firstShadowVert ; i < numShadowVerts ; i+= 2, in += 2 ) {
|
|
float w, oow;
|
|
|
|
in[0].w = 1;
|
|
|
|
w = in->ToVec3() * mat[3].ToVec3() + mat[3][3];
|
|
if ( w == 0 ) {
|
|
in[1] = in[0];
|
|
continue;
|
|
}
|
|
|
|
oow = 1.0 / w;
|
|
in[1].x = ( in->ToVec3() * mat[0].ToVec3() + mat[0][3] ) * oow;
|
|
in[1].y = ( in->ToVec3() * mat[1].ToVec3() + mat[1][3] ) * oow;
|
|
in[1].z = ( in->ToVec3() * mat[2].ToVec3() + mat[2][3] ) * oow;
|
|
in[1].w = 1;
|
|
}
|
|
|
|
#else
|
|
// messing with W seems to cause some depth precision problems
|
|
|
|
// make a projected copy of the even verts into the odd spots
|
|
in = &shadowVerts[firstShadowVert];
|
|
for ( i = firstShadowVert ; i < numShadowVerts ; i+= 2, in += 2 ) {
|
|
in[0].w = 1;
|
|
in[1].x = *in * mat[0].ToVec3() + mat[0][3];
|
|
in[1].y = *in * mat[1].ToVec3() + mat[1][3];
|
|
in[1].z = *in * mat[2].ToVec3() + mat[2][3];
|
|
in[1].w = *in * mat[3].ToVec3() + mat[3][3];
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
#define MAX_CLIPPED_POINTS 20
|
|
typedef struct {
|
|
int numVerts;
|
|
idVec3 verts[MAX_CLIPPED_POINTS];
|
|
int edgeFlags[MAX_CLIPPED_POINTS];
|
|
} clipTri_t;
|
|
|
|
/*
|
|
=============
|
|
R_ChopWinding
|
|
|
|
Clips a triangle from one buffer to another, setting edge flags
|
|
The returned buffer may be the same as inNum if no clipping is done
|
|
If entirely clipped away, clipTris[returned].numVerts == 0
|
|
|
|
I have some worries about edge flag cases when polygons are clipped
|
|
multiple times near the epsilon.
|
|
=============
|
|
*/
|
|
static int R_ChopWinding( clipTri_t clipTris[2], int inNum, const idPlane &plane ) {
|
|
clipTri_t *in, *out;
|
|
float dists[MAX_CLIPPED_POINTS];
|
|
int sides[MAX_CLIPPED_POINTS];
|
|
int counts[3];
|
|
float dot;
|
|
int i, j;
|
|
idVec3 *p1, *p2;
|
|
idVec3 mid;
|
|
|
|
in = &clipTris[inNum];
|
|
out = &clipTris[inNum^1];
|
|
counts[0] = counts[1] = counts[2] = 0;
|
|
|
|
// determine sides for each point
|
|
for ( i = 0 ; i < in->numVerts ; i++ ) {
|
|
dot = plane.Distance( in->verts[i] );
|
|
dists[i] = dot;
|
|
if ( dot < -LIGHT_CLIP_EPSILON ) {
|
|
sides[i] = SIDE_BACK;
|
|
} else if ( dot > LIGHT_CLIP_EPSILON ) {
|
|
sides[i] = SIDE_FRONT;
|
|
} else {
|
|
sides[i] = SIDE_ON;
|
|
}
|
|
counts[sides[i]]++;
|
|
}
|
|
|
|
// if none in front, it is completely clipped away
|
|
if ( !counts[SIDE_FRONT] ) {
|
|
in->numVerts = 0;
|
|
return inNum;
|
|
}
|
|
if ( !counts[SIDE_BACK] ) {
|
|
return inNum; // inout stays the same
|
|
}
|
|
|
|
// avoid wrapping checks by duplicating first value to end
|
|
sides[i] = sides[0];
|
|
dists[i] = dists[0];
|
|
in->verts[in->numVerts] = in->verts[0];
|
|
in->edgeFlags[in->numVerts] = in->edgeFlags[0];
|
|
|
|
out->numVerts = 0;
|
|
for ( i = 0 ; i < in->numVerts ; i++ ) {
|
|
p1 = &in->verts[i];
|
|
|
|
if ( sides[i] != SIDE_BACK ) {
|
|
out->verts[out->numVerts] = *p1;
|
|
if ( sides[i] == SIDE_ON && sides[i+1] == SIDE_BACK ) {
|
|
out->edgeFlags[out->numVerts] = 1;
|
|
} else {
|
|
out->edgeFlags[out->numVerts] = in->edgeFlags[i];
|
|
}
|
|
out->numVerts++;
|
|
}
|
|
|
|
if ( (sides[i] == SIDE_FRONT && sides[i+1] == SIDE_BACK)
|
|
|| (sides[i] == SIDE_BACK && sides[i+1] == SIDE_FRONT) ) {
|
|
// generate a split point
|
|
p2 = &in->verts[i+1];
|
|
|
|
dot = dists[i] / (dists[i]-dists[i+1]);
|
|
for ( j=0 ; j<3 ; j++ ) {
|
|
mid[j] = (*p1)[j] + dot*((*p2)[j]-(*p1)[j]);
|
|
}
|
|
|
|
out->verts[out->numVerts] = mid;
|
|
|
|
// set the edge flag
|
|
if ( sides[i+1] != SIDE_FRONT ) {
|
|
out->edgeFlags[out->numVerts] = 1;
|
|
} else {
|
|
out->edgeFlags[out->numVerts] = in->edgeFlags[i];
|
|
}
|
|
|
|
out->numVerts++;
|
|
}
|
|
}
|
|
|
|
return inNum ^ 1;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
R_ClipTriangleToLight
|
|
|
|
Returns false if nothing is left after clipping
|
|
===================
|
|
*/
|
|
static bool R_ClipTriangleToLight( const idVec3 &a, const idVec3 &b, const idVec3 &c, int planeBits,
|
|
const idPlane frustum[6] ) {
|
|
int i;
|
|
int base;
|
|
clipTri_t pingPong[2], *ct;
|
|
int p;
|
|
|
|
pingPong[0].numVerts = 3;
|
|
pingPong[0].edgeFlags[0] = 0;
|
|
pingPong[0].edgeFlags[1] = 0;
|
|
pingPong[0].edgeFlags[2] = 0;
|
|
pingPong[0].verts[0] = a;
|
|
pingPong[0].verts[1] = b;
|
|
pingPong[0].verts[2] = c;
|
|
|
|
p = 0;
|
|
for ( i = 0 ; i < 6 ; i++ ) {
|
|
if ( planeBits & ( 1 << i ) ) {
|
|
p = R_ChopWinding( pingPong, p, frustum[i] );
|
|
if ( pingPong[p].numVerts < 1 ) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
ct = &pingPong[p];
|
|
|
|
// copy the clipped points out to shadowVerts
|
|
if ( numShadowVerts + ct->numVerts * 2 > MAX_SHADOW_VERTS ) {
|
|
overflowed = true;
|
|
return false;
|
|
}
|
|
|
|
base = numShadowVerts;
|
|
for ( i = 0 ; i < ct->numVerts ; i++ ) {
|
|
shadowVerts[ base + i*2 ].ToVec3() = ct->verts[i];
|
|
}
|
|
numShadowVerts += ct->numVerts * 2;
|
|
|
|
if ( numShadowIndexes + 3 * ( ct->numVerts - 2 ) > MAX_SHADOW_INDEXES ) {
|
|
overflowed = true;
|
|
return false;
|
|
}
|
|
|
|
for ( i = 2 ; i < ct->numVerts ; i++ ) {
|
|
shadowIndexes[numShadowIndexes++] = base + i * 2;
|
|
shadowIndexes[numShadowIndexes++] = base + ( i - 1 ) * 2;
|
|
shadowIndexes[numShadowIndexes++] = base;
|
|
}
|
|
|
|
// any edges that were created by the clipping process will
|
|
// have a silhouette quad created for it, because it is one
|
|
// of the exterior bounds of the shadow volume
|
|
for ( i = 0 ; i < ct->numVerts ; i++ ) {
|
|
if ( ct->edgeFlags[i] ) {
|
|
if ( numClipSilEdges == MAX_CLIP_SIL_EDGES ) {
|
|
break;
|
|
}
|
|
clipSilEdges[ numClipSilEdges ][0] = base + i * 2;
|
|
if ( i == ct->numVerts - 1 ) {
|
|
clipSilEdges[ numClipSilEdges ][1] = base;
|
|
} else {
|
|
clipSilEdges[ numClipSilEdges ][1] = base + ( i + 1 ) * 2;
|
|
}
|
|
numClipSilEdges++;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
R_ClipLineToLight
|
|
|
|
If neither point is clearly behind the clipping
|
|
plane, the edge will be passed unmodified. A sil edge that
|
|
is on a border plane must be drawn.
|
|
|
|
If one point is clearly clipped by the plane and the
|
|
other point is on the plane, it will be completely removed.
|
|
===================
|
|
*/
|
|
static bool R_ClipLineToLight( const idVec3 &a, const idVec3 &b, const idPlane frustum[6],
|
|
idVec3 &p1, idVec3 &p2 ) {
|
|
float *clip;
|
|
int j;
|
|
float d1, d2;
|
|
float f;
|
|
|
|
p1 = a;
|
|
p2 = b;
|
|
|
|
// clip it
|
|
for ( j = 0 ; j < 6 ; j++ ) {
|
|
d1 = frustum[j].Distance( p1 );
|
|
d2 = frustum[j].Distance( p2 );
|
|
|
|
// if both on or in front, not clipped to this plane
|
|
if ( d1 > -LIGHT_CLIP_EPSILON && d2 > -LIGHT_CLIP_EPSILON ) {
|
|
continue;
|
|
}
|
|
|
|
// if one is behind and the other isn't clearly in front, the edge is clipped off
|
|
if ( d1 <= -LIGHT_CLIP_EPSILON && d2 < LIGHT_CLIP_EPSILON ) {
|
|
return false;
|
|
}
|
|
if ( d2 <= -LIGHT_CLIP_EPSILON && d1 < LIGHT_CLIP_EPSILON ) {
|
|
return false;
|
|
}
|
|
|
|
// clip it, keeping the negative side
|
|
if ( d1 < 0 ) {
|
|
clip = p1.ToFloatPtr();
|
|
} else {
|
|
clip = p2.ToFloatPtr();
|
|
}
|
|
|
|
#if 0
|
|
if ( idMath::Fabs(d1 - d2) < 0.001 ) {
|
|
d2 = d1 - 0.1;
|
|
}
|
|
#endif
|
|
|
|
f = d1 / ( d1 - d2 );
|
|
clip[0] = p1[0] + f * ( p2[0] - p1[0] );
|
|
clip[1] = p1[1] + f * ( p2[1] - p1[1] );
|
|
clip[2] = p1[2] + f * ( p2[2] - p1[2] );
|
|
}
|
|
|
|
return true; // retain a fragment
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
R_AddClipSilEdges
|
|
|
|
Add sil edges for each triangle clipped to the side of
|
|
the frustum.
|
|
|
|
Only done for simple projected lights, not point lights.
|
|
==================
|
|
*/
|
|
static void R_AddClipSilEdges( void ) {
|
|
int v1, v2;
|
|
int v1_back, v2_back;
|
|
int i;
|
|
|
|
// don't allow it to overflow
|
|
if ( numShadowIndexes + numClipSilEdges * 6 > MAX_SHADOW_INDEXES ) {
|
|
overflowed = true;
|
|
return;
|
|
}
|
|
|
|
for ( i = 0 ; i < numClipSilEdges ; i++ ) {
|
|
v1 = clipSilEdges[i][0];
|
|
v2 = clipSilEdges[i][1];
|
|
v1_back = v1 + 1;
|
|
v2_back = v2 + 1;
|
|
if ( PointsOrdered( shadowVerts[ v1 ].ToVec3(), shadowVerts[ v2 ].ToVec3() ) ) {
|
|
shadowIndexes[numShadowIndexes++] = v1;
|
|
shadowIndexes[numShadowIndexes++] = v2;
|
|
shadowIndexes[numShadowIndexes++] = v1_back;
|
|
shadowIndexes[numShadowIndexes++] = v2;
|
|
shadowIndexes[numShadowIndexes++] = v2_back;
|
|
shadowIndexes[numShadowIndexes++] = v1_back;
|
|
} else {
|
|
shadowIndexes[numShadowIndexes++] = v1;
|
|
shadowIndexes[numShadowIndexes++] = v2;
|
|
shadowIndexes[numShadowIndexes++] = v2_back;
|
|
shadowIndexes[numShadowIndexes++] = v1;
|
|
shadowIndexes[numShadowIndexes++] = v2_back;
|
|
shadowIndexes[numShadowIndexes++] = v1_back;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_AddSilEdges
|
|
|
|
Add quads from the front points to the projected points
|
|
for each silhouette edge in the light
|
|
=================
|
|
*/
|
|
static void R_AddSilEdges( const srfTriangles_t *tri, unsigned short *pointCull, const idPlane frustum[6] ) {
|
|
int v1, v2;
|
|
int i;
|
|
silEdge_t *sil;
|
|
int numPlanes;
|
|
|
|
numPlanes = tri->numIndexes / 3;
|
|
|
|
// add sil edges for any true silhouette boundaries on the surface
|
|
for ( i = 0 ; i < tri->numSilEdges ; i++ ) {
|
|
sil = tri->silEdges + i;
|
|
if ( sil->p1 < 0 || sil->p1 > numPlanes || sil->p2 < 0 || sil->p2 > numPlanes ) {
|
|
common->Error( "Bad sil planes" );
|
|
}
|
|
|
|
// an edge will be a silhouette edge if the face on one side
|
|
// casts a shadow, but the face on the other side doesn't.
|
|
// "casts a shadow" means that it has some surface in the projection,
|
|
// not just that it has the correct facing direction
|
|
// This will cause edges that are exactly on the frustum plane
|
|
// to be considered sil edges if the face inside casts a shadow.
|
|
if ( !( faceCastsShadow[ sil->p1 ] ^ faceCastsShadow[ sil->p2 ] ) ) {
|
|
continue;
|
|
}
|
|
|
|
// if the edge is completely off the negative side of
|
|
// a frustum plane, don't add it at all. This can still
|
|
// happen even if the face is visible and casting a shadow
|
|
// if it is partially clipped
|
|
if ( EDGE_CULLED( sil->v1, sil->v2 ) ) {
|
|
continue;
|
|
}
|
|
|
|
// see if the edge needs to be clipped
|
|
if ( EDGE_CLIPPED( sil->v1, sil->v2 ) ) {
|
|
if ( numShadowVerts + 4 > MAX_SHADOW_VERTS ) {
|
|
overflowed = true;
|
|
return;
|
|
}
|
|
v1 = numShadowVerts;
|
|
v2 = v1 + 2;
|
|
if ( !R_ClipLineToLight( tri->verts[ sil->v1 ].xyz, tri->verts[ sil->v2 ].xyz,
|
|
frustum, shadowVerts[v1].ToVec3(), shadowVerts[v2].ToVec3() ) ) {
|
|
continue; // clipped away
|
|
}
|
|
|
|
numShadowVerts += 4;
|
|
} else {
|
|
// use the entire edge
|
|
v1 = remap[ sil->v1 ];
|
|
v2 = remap[ sil->v2 ];
|
|
if ( v1 < 0 || v2 < 0 ) {
|
|
common->Error( "R_AddSilEdges: bad remap[]" );
|
|
}
|
|
}
|
|
|
|
// don't overflow
|
|
if ( numShadowIndexes + 6 > MAX_SHADOW_INDEXES ) {
|
|
overflowed = true;
|
|
return;
|
|
}
|
|
|
|
// we need to choose the correct way of triangulating the silhouette quad
|
|
// consistantly between any two points, no matter which order they are specified.
|
|
// If this wasn't done, slight rasterization cracks would show in the shadow
|
|
// volume when two sil edges were exactly coincident
|
|
if ( faceCastsShadow[ sil->p2 ] ) {
|
|
if ( PointsOrdered( shadowVerts[ v1 ].ToVec3(), shadowVerts[ v2 ].ToVec3() ) ) {
|
|
shadowIndexes[numShadowIndexes++] = v1;
|
|
shadowIndexes[numShadowIndexes++] = v1+1;
|
|
shadowIndexes[numShadowIndexes++] = v2;
|
|
shadowIndexes[numShadowIndexes++] = v2;
|
|
shadowIndexes[numShadowIndexes++] = v1+1;
|
|
shadowIndexes[numShadowIndexes++] = v2+1;
|
|
} else {
|
|
shadowIndexes[numShadowIndexes++] = v1;
|
|
shadowIndexes[numShadowIndexes++] = v2+1;
|
|
shadowIndexes[numShadowIndexes++] = v2;
|
|
shadowIndexes[numShadowIndexes++] = v1;
|
|
shadowIndexes[numShadowIndexes++] = v1+1;
|
|
shadowIndexes[numShadowIndexes++] = v2+1;
|
|
}
|
|
} else {
|
|
if ( PointsOrdered( shadowVerts[ v1 ].ToVec3(), shadowVerts[ v2 ].ToVec3() ) ) {
|
|
shadowIndexes[numShadowIndexes++] = v1;
|
|
shadowIndexes[numShadowIndexes++] = v2;
|
|
shadowIndexes[numShadowIndexes++] = v1+1;
|
|
shadowIndexes[numShadowIndexes++] = v2;
|
|
shadowIndexes[numShadowIndexes++] = v2+1;
|
|
shadowIndexes[numShadowIndexes++] = v1+1;
|
|
} else {
|
|
shadowIndexes[numShadowIndexes++] = v1;
|
|
shadowIndexes[numShadowIndexes++] = v2;
|
|
shadowIndexes[numShadowIndexes++] = v2+1;
|
|
shadowIndexes[numShadowIndexes++] = v1;
|
|
shadowIndexes[numShadowIndexes++] = v2+1;
|
|
shadowIndexes[numShadowIndexes++] = v1+1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_CalcPointCull
|
|
|
|
Also inits the remap[] array to all -1
|
|
================
|
|
*/
|
|
static void R_CalcPointCull( const srfTriangles_t *tri, const idPlane frustum[6], unsigned short *pointCull ) {
|
|
int i;
|
|
int frontBits;
|
|
float *planeSide;
|
|
byte *side1, *side2;
|
|
|
|
SIMDProcessor->Memset( remap, -1, tri->numVerts * sizeof( remap[0] ) );
|
|
|
|
for ( frontBits = 0, i = 0; i < 6; i++ ) {
|
|
// get front bits for the whole surface
|
|
if ( tri->bounds.PlaneDistance( frustum[i] ) >= LIGHT_CLIP_EPSILON ) {
|
|
frontBits |= 1<<(i+6);
|
|
}
|
|
}
|
|
|
|
// initialize point cull
|
|
for ( i = 0; i < tri->numVerts; i++ ) {
|
|
pointCull[i] = frontBits;
|
|
}
|
|
|
|
// if the surface is not completely inside the light frustum
|
|
if ( frontBits == ( ( ( 1 << 6 ) - 1 ) ) << 6 ) {
|
|
return;
|
|
}
|
|
|
|
planeSide = (float *) _alloca16( tri->numVerts * sizeof( float ) );
|
|
side1 = (byte *) _alloca16( tri->numVerts * sizeof( byte ) );
|
|
side2 = (byte *) _alloca16( tri->numVerts * sizeof( byte ) );
|
|
SIMDProcessor->Memset( side1, 0, tri->numVerts * sizeof( byte ) );
|
|
SIMDProcessor->Memset( side2, 0, tri->numVerts * sizeof( byte ) );
|
|
|
|
for ( i = 0; i < 6; i++ ) {
|
|
|
|
if ( frontBits & (1<<(i+6)) ) {
|
|
continue;
|
|
}
|
|
|
|
SIMDProcessor->Dot( planeSide, frustum[i], tri->verts, tri->numVerts );
|
|
SIMDProcessor->CmpLT( side1, i, planeSide, LIGHT_CLIP_EPSILON, tri->numVerts );
|
|
SIMDProcessor->CmpGT( side2, i, planeSide, -LIGHT_CLIP_EPSILON, tri->numVerts );
|
|
}
|
|
for ( i = 0; i < tri->numVerts; i++ ) {
|
|
pointCull[i] |= side1[i] | (side2[i] << 6);
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_CreateShadowVolumeInFrustum
|
|
|
|
Adds new verts and indexes to the shadow volume.
|
|
|
|
If the frustum completely defines the projected light,
|
|
makeClippedPlanes should be true, which will cause sil quads to
|
|
be added along all clipped edges.
|
|
|
|
If the frustum is just part of a point light, clipped planes don't
|
|
need to be added.
|
|
=================
|
|
*/
|
|
static void R_CreateShadowVolumeInFrustum( const idRenderEntityLocal *ent,
|
|
const srfTriangles_t *tri,
|
|
const idRenderLightLocal *light,
|
|
const idVec3 lightOrigin,
|
|
const idPlane frustum[6],
|
|
const idPlane &farPlane,
|
|
bool makeClippedPlanes ) {
|
|
int i;
|
|
int numTris;
|
|
unsigned short *pointCull;
|
|
int numCapIndexes;
|
|
int firstShadowIndex;
|
|
int firstShadowVert;
|
|
int cullBits;
|
|
|
|
pointCull = (unsigned short *)_alloca16( tri->numVerts * sizeof( pointCull[0] ) );
|
|
|
|
// test the vertexes for inside the light frustum, which will allow
|
|
// us to completely cull away some triangles from consideration.
|
|
R_CalcPointCull( tri, frustum, pointCull );
|
|
|
|
// this may not be the first frustum added to the volume
|
|
firstShadowIndex = numShadowIndexes;
|
|
firstShadowVert = numShadowVerts;
|
|
|
|
// decide which triangles front shadow volumes, clipping as needed
|
|
numClipSilEdges = 0;
|
|
numTris = tri->numIndexes / 3;
|
|
for ( i = 0 ; i < numTris ; i++ ) {
|
|
int i1, i2, i3;
|
|
|
|
faceCastsShadow[i] = 0; // until shown otherwise
|
|
|
|
// if it isn't facing the right way, don't add it
|
|
// to the shadow volume
|
|
if ( globalFacing[i] ) {
|
|
continue;
|
|
}
|
|
|
|
i1 = tri->silIndexes[ i*3 + 0 ];
|
|
i2 = tri->silIndexes[ i*3 + 1 ];
|
|
i3 = tri->silIndexes[ i*3 + 2 ];
|
|
|
|
// if all the verts are off one side of the frustum,
|
|
// don't add any of them
|
|
if ( TRIANGLE_CULLED( i1, i2, i3 ) ) {
|
|
continue;
|
|
}
|
|
|
|
// make sure the verts that are not on the negative sides
|
|
// of the frustum are copied over.
|
|
// we need to get the original verts even from clipped triangles
|
|
// so the edges reference correctly, because an edge may be unclipped
|
|
// even when a triangle is clipped.
|
|
if ( numShadowVerts + 6 > MAX_SHADOW_VERTS ) {
|
|
overflowed = true;
|
|
return;
|
|
}
|
|
|
|
if ( !POINT_CULLED(i1) && remap[i1] == -1 ) {
|
|
remap[i1] = numShadowVerts;
|
|
shadowVerts[ numShadowVerts ].ToVec3() = tri->verts[i1].xyz;
|
|
numShadowVerts+=2;
|
|
}
|
|
if ( !POINT_CULLED(i2) && remap[i2] == -1 ) {
|
|
remap[i2] = numShadowVerts;
|
|
shadowVerts[ numShadowVerts ].ToVec3() = tri->verts[i2].xyz;
|
|
numShadowVerts+=2;
|
|
}
|
|
if ( !POINT_CULLED(i3) && remap[i3] == -1 ) {
|
|
remap[i3] = numShadowVerts;
|
|
shadowVerts[ numShadowVerts ].ToVec3() = tri->verts[i3].xyz;
|
|
numShadowVerts+=2;
|
|
}
|
|
|
|
// clip the triangle if any points are on the negative sides
|
|
if ( TRIANGLE_CLIPPED( i1, i2, i3 ) ) {
|
|
cullBits = ( ( pointCull[ i1 ] ^ 0xfc0 ) | ( pointCull[ i2 ] ^ 0xfc0 ) | ( pointCull[ i3 ] ^ 0xfc0 ) ) >> 6;
|
|
// this will also define clip edges that will become
|
|
// silhouette planes
|
|
if ( R_ClipTriangleToLight( tri->verts[i1].xyz, tri->verts[i2].xyz,
|
|
tri->verts[i3].xyz, cullBits, frustum ) ) {
|
|
faceCastsShadow[i] = 1;
|
|
}
|
|
} else {
|
|
// instead of overflowing or drawing a streamer shadow, don't draw a shadow at all
|
|
if ( numShadowIndexes + 3 > MAX_SHADOW_INDEXES ) {
|
|
overflowed = true;
|
|
return;
|
|
}
|
|
if ( remap[i1] == -1 || remap[i2] == -1 || remap[i3] == -1 ) {
|
|
common->Error( "R_CreateShadowVolumeInFrustum: bad remap[]" );
|
|
}
|
|
shadowIndexes[numShadowIndexes++] = remap[i3];
|
|
shadowIndexes[numShadowIndexes++] = remap[i2];
|
|
shadowIndexes[numShadowIndexes++] = remap[i1];
|
|
faceCastsShadow[i] = 1;
|
|
}
|
|
}
|
|
|
|
// add indexes for the back caps, which will just be reversals of the
|
|
// front caps using the back vertexes
|
|
numCapIndexes = numShadowIndexes - firstShadowIndex;
|
|
|
|
// if no faces have been defined for the shadow volume,
|
|
// there won't be anything at all
|
|
if ( numCapIndexes == 0 ) {
|
|
return;
|
|
}
|
|
|
|
//--------------- off-line processing ------------------
|
|
|
|
// if we are running from dmap, perform the (very) expensive shadow optimizations
|
|
// to remove internal sil edges and optimize the caps
|
|
if ( callOptimizer ) {
|
|
optimizedShadow_t opt;
|
|
|
|
// project all of the vertexes to the shadow plane, generating
|
|
// an equal number of back vertexes
|
|
// R_ProjectPointsToFarPlane( ent, light, farPlane, firstShadowVert, numShadowVerts );
|
|
|
|
opt = SuperOptimizeOccluders( shadowVerts, shadowIndexes + firstShadowIndex, numCapIndexes, farPlane, lightOrigin );
|
|
|
|
// pull off the non-optimized data
|
|
numShadowIndexes = firstShadowIndex;
|
|
numShadowVerts = firstShadowVert;
|
|
|
|
// add the optimized data
|
|
if ( numShadowIndexes + opt.totalIndexes > MAX_SHADOW_INDEXES
|
|
|| numShadowVerts + opt.numVerts > MAX_SHADOW_VERTS ) {
|
|
overflowed = true;
|
|
common->Printf( "WARNING: overflowed MAX_SHADOW tables, shadow discarded\n" );
|
|
Mem_Free( opt.verts );
|
|
Mem_Free( opt.indexes );
|
|
return;
|
|
}
|
|
|
|
for ( i = 0 ; i < opt.numVerts ; i++ ) {
|
|
shadowVerts[numShadowVerts+i][0] = opt.verts[i][0];
|
|
shadowVerts[numShadowVerts+i][1] = opt.verts[i][1];
|
|
shadowVerts[numShadowVerts+i][2] = opt.verts[i][2];
|
|
shadowVerts[numShadowVerts+i][3] = 1;
|
|
}
|
|
for ( i = 0 ; i < opt.totalIndexes ; i++ ) {
|
|
int index = opt.indexes[i];
|
|
if ( index < 0 || index > opt.numVerts ) {
|
|
common->Error( "optimized shadow index out of range" );
|
|
}
|
|
shadowIndexes[numShadowIndexes+i] = index + numShadowVerts;
|
|
}
|
|
|
|
numShadowVerts += opt.numVerts;
|
|
numShadowIndexes += opt.totalIndexes;
|
|
|
|
// note the index distribution so we can sort all the caps after all the sils
|
|
indexRef[indexFrustumNumber].frontCapStart = firstShadowIndex;
|
|
indexRef[indexFrustumNumber].rearCapStart = firstShadowIndex+opt.numFrontCapIndexes;
|
|
indexRef[indexFrustumNumber].silStart = firstShadowIndex+opt.numFrontCapIndexes+opt.numRearCapIndexes;
|
|
indexRef[indexFrustumNumber].end = numShadowIndexes;
|
|
indexFrustumNumber++;
|
|
|
|
Mem_Free( opt.verts );
|
|
Mem_Free( opt.indexes );
|
|
return;
|
|
}
|
|
|
|
//--------------- real-time processing ------------------
|
|
|
|
// the dangling edge "face" is never considered to cast a shadow,
|
|
// so any face with dangling edges that casts a shadow will have
|
|
// it's dangling sil edge trigger a sil plane
|
|
faceCastsShadow[numTris] = 0;
|
|
|
|
// instead of overflowing or drawing a streamer shadow, don't draw a shadow at all
|
|
// if we ran out of space
|
|
if ( numShadowIndexes + numCapIndexes > MAX_SHADOW_INDEXES ) {
|
|
overflowed = true;
|
|
return;
|
|
}
|
|
for ( i = 0 ; i < numCapIndexes ; i += 3 ) {
|
|
shadowIndexes[ numShadowIndexes + i + 0 ] = shadowIndexes[ firstShadowIndex + i + 2 ] + 1;
|
|
shadowIndexes[ numShadowIndexes + i + 1 ] = shadowIndexes[ firstShadowIndex + i + 1 ] + 1;
|
|
shadowIndexes[ numShadowIndexes + i + 2 ] = shadowIndexes[ firstShadowIndex + i + 0 ] + 1;
|
|
}
|
|
numShadowIndexes += numCapIndexes;
|
|
|
|
c_caps += numCapIndexes * 2;
|
|
|
|
int preSilIndexes = numShadowIndexes;
|
|
|
|
// if any triangles were clipped, we will have a list of edges
|
|
// on the frustum which must now become sil edges
|
|
if ( makeClippedPlanes ) {
|
|
R_AddClipSilEdges();
|
|
}
|
|
|
|
// any edges that are a transition between a shadowing and
|
|
// non-shadowing triangle will cast a silhouette edge
|
|
R_AddSilEdges( tri, pointCull, frustum );
|
|
|
|
c_sils += numShadowIndexes - preSilIndexes;
|
|
|
|
// project all of the vertexes to the shadow plane, generating
|
|
// an equal number of back vertexes
|
|
R_ProjectPointsToFarPlane( ent, light, farPlane, firstShadowVert, numShadowVerts );
|
|
|
|
// note the index distribution so we can sort all the caps after all the sils
|
|
indexRef[indexFrustumNumber].frontCapStart = firstShadowIndex;
|
|
indexRef[indexFrustumNumber].rearCapStart = firstShadowIndex+numCapIndexes;
|
|
indexRef[indexFrustumNumber].silStart = preSilIndexes;
|
|
indexRef[indexFrustumNumber].end = numShadowIndexes;
|
|
indexFrustumNumber++;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
R_MakeShadowFrustums
|
|
|
|
Called at definition derivation time
|
|
===================
|
|
*/
|
|
void R_MakeShadowFrustums( idRenderLightLocal *light ) {
|
|
int i, j;
|
|
|
|
if ( light->parms.pointLight ) {
|
|
#if 0
|
|
idVec3 adjustedRadius;
|
|
|
|
// increase the light radius to cover any origin offsets.
|
|
// this will cause some shadows to extend out of the exact light
|
|
// volume, but is simpler than adjusting all the frustums
|
|
adjustedRadius[0] = light->parms.lightRadius[0] + idMath::Fabs( light->parms.lightCenter[0] );
|
|
adjustedRadius[1] = light->parms.lightRadius[1] + idMath::Fabs( light->parms.lightCenter[1] );
|
|
adjustedRadius[2] = light->parms.lightRadius[2] + idMath::Fabs( light->parms.lightCenter[2] );
|
|
|
|
light->numShadowFrustums = 0;
|
|
// a point light has to project against six planes
|
|
for ( i = 0 ; i < 6 ; i++ ) {
|
|
shadowFrustum_t *frust = &light->shadowFrustums[ light->numShadowFrustums ];
|
|
|
|
frust->numPlanes = 6;
|
|
frust->makeClippedPlanes = false;
|
|
for ( j = 0 ; j < 6 ; j++ ) {
|
|
idPlane &plane = frust->planes[j];
|
|
plane[0] = pointLightFrustums[i][j][0] / adjustedRadius[0];
|
|
plane[1] = pointLightFrustums[i][j][1] / adjustedRadius[1];
|
|
plane[2] = pointLightFrustums[i][j][2] / adjustedRadius[2];
|
|
plane.Normalize();
|
|
plane[3] = -( plane.Normal() * light->globalLightOrigin );
|
|
if ( j == 5 ) {
|
|
plane[3] += adjustedRadius[i>>1];
|
|
}
|
|
}
|
|
|
|
light->numShadowFrustums++;
|
|
}
|
|
#else
|
|
// exact projection,taking into account asymetric frustums when
|
|
// globalLightOrigin isn't centered
|
|
|
|
static int faceCorners[6][4] = {
|
|
{ 7, 5, 1, 3 }, // positive X side
|
|
{ 4, 6, 2, 0 }, // negative X side
|
|
{ 6, 7, 3, 2 }, // positive Y side
|
|
{ 5, 4, 0, 1 }, // negative Y side
|
|
{ 6, 4, 5, 7 }, // positive Z side
|
|
{ 3, 1, 0, 2 } // negative Z side
|
|
};
|
|
static int faceEdgeAdjacent[6][4] = {
|
|
{ 4, 4, 2, 2 }, // positive X side
|
|
{ 7, 7, 1, 1 }, // negative X side
|
|
{ 5, 5, 0, 0 }, // positive Y side
|
|
{ 6, 6, 3, 3 }, // negative Y side
|
|
{ 0, 0, 3, 3 }, // positive Z side
|
|
{ 5, 5, 6, 6 } // negative Z side
|
|
};
|
|
|
|
bool centerOutside = false;
|
|
|
|
// if the light center of projection is outside the light bounds,
|
|
// we will need to build the planes a little differently
|
|
if ( fabs( light->parms.lightCenter[0] ) > light->parms.lightRadius[0]
|
|
|| fabs( light->parms.lightCenter[1] ) > light->parms.lightRadius[1]
|
|
|| fabs( light->parms.lightCenter[2] ) > light->parms.lightRadius[2] ) {
|
|
centerOutside = true;
|
|
}
|
|
|
|
// make the corners
|
|
idVec3 corners[8];
|
|
|
|
for ( i = 0 ; i < 8 ; i++ ) {
|
|
idVec3 temp;
|
|
for ( j = 0 ; j < 3 ; j++ ) {
|
|
if ( i & ( 1 << j ) ) {
|
|
temp[j] = light->parms.lightRadius[j];
|
|
} else {
|
|
temp[j] = -light->parms.lightRadius[j];
|
|
}
|
|
}
|
|
|
|
// transform to global space
|
|
corners[i] = light->parms.origin + light->parms.axis * temp;
|
|
}
|
|
|
|
light->numShadowFrustums = 0;
|
|
for ( int side = 0 ; side < 6 ; side++ ) {
|
|
shadowFrustum_t *frust = &light->shadowFrustums[ light->numShadowFrustums ];
|
|
idVec3 &p1 = corners[faceCorners[side][0]];
|
|
idVec3 &p2 = corners[faceCorners[side][1]];
|
|
idVec3 &p3 = corners[faceCorners[side][2]];
|
|
idPlane backPlane;
|
|
|
|
// plane will have positive side inward
|
|
backPlane.FromPoints( p1, p2, p3 );
|
|
|
|
// if center of projection is on the wrong side, skip
|
|
float d = backPlane.Distance( light->globalLightOrigin );
|
|
if ( d < 0 ) {
|
|
continue;
|
|
}
|
|
|
|
frust->numPlanes = 6;
|
|
frust->planes[5] = backPlane;
|
|
frust->planes[4] = backPlane; // we don't really need the extra plane
|
|
|
|
// make planes with positive side facing inwards in light local coordinates
|
|
for ( int edge = 0 ; edge < 4 ; edge++ ) {
|
|
idVec3 &p1 = corners[faceCorners[side][edge]];
|
|
idVec3 &p2 = corners[faceCorners[side][(edge+1)&3]];
|
|
|
|
// create a plane that goes through the center of projection
|
|
frust->planes[edge].FromPoints( p2, p1, light->globalLightOrigin );
|
|
|
|
// see if we should use an adjacent plane instead
|
|
if ( centerOutside ) {
|
|
idVec3 &p3 = corners[faceEdgeAdjacent[side][edge]];
|
|
idPlane sidePlane;
|
|
|
|
sidePlane.FromPoints( p2, p1, p3 );
|
|
d = sidePlane.Distance( light->globalLightOrigin );
|
|
if ( d < 0 ) {
|
|
// use this plane instead of the edged plane
|
|
frust->planes[edge] = sidePlane;
|
|
}
|
|
// we can't guarantee a neighbor, so add sill planes at edge
|
|
light->shadowFrustums[ light->numShadowFrustums ].makeClippedPlanes = true;
|
|
}
|
|
}
|
|
light->numShadowFrustums++;
|
|
}
|
|
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// projected light
|
|
|
|
light->numShadowFrustums = 1;
|
|
shadowFrustum_t *frust = &light->shadowFrustums[ 0 ];
|
|
|
|
// flip and transform the frustum planes so the positive side faces
|
|
// inward in local coordinates
|
|
|
|
// it is important to clip against even the near clip plane, because
|
|
// many projected lights that are faking area lights will have their
|
|
// origin behind solid surfaces.
|
|
for ( i = 0 ; i < 6 ; i++ ) {
|
|
idPlane &plane = frust->planes[i];
|
|
|
|
plane.SetNormal( -light->frustum[i].Normal() );
|
|
plane.SetDist( -light->frustum[i].Dist() );
|
|
}
|
|
|
|
frust->numPlanes = 6;
|
|
|
|
frust->makeClippedPlanes = true;
|
|
// projected lights don't have shared frustums, so any clipped edges
|
|
// right on the planes must have a sil plane created for them
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_CreateShadowVolume
|
|
|
|
The returned surface will have a valid bounds and radius for culling.
|
|
|
|
Triangles are clipped to the light frustum before projecting.
|
|
|
|
A single triangle can clip to as many as 7 vertexes, so
|
|
the worst case expansion is 2*(numindexes/3)*7 verts when counting both
|
|
the front and back caps, although it will usually only be a modest
|
|
increase in vertexes for closed modesl
|
|
|
|
The worst case index count is much larger, when the 7 vertex clipped triangle
|
|
needs 15 indexes for the front, 15 for the back, and 42 (a quad on seven sides)
|
|
for the sides, for a total of 72 indexes from the original 3. Ouch.
|
|
|
|
NULL may be returned if the surface doesn't create a shadow volume at all,
|
|
as with a single face that the light is behind.
|
|
|
|
If an edge is within an epsilon of the border of the volume, it must be treated
|
|
as if it is clipped for triangles, generating a new sil edge, and act
|
|
as if it was culled for edges, because the sil edge will have been
|
|
generated by the triangle irregardless of if it actually was a sil edge.
|
|
=================
|
|
*/
|
|
srfTriangles_t *R_CreateShadowVolume( const idRenderEntityLocal *ent,
|
|
const srfTriangles_t *tri, const idRenderLightLocal *light,
|
|
shadowGen_t optimize, srfCullInfo_t &cullInfo ) {
|
|
int i, j;
|
|
idVec3 lightOrigin;
|
|
srfTriangles_t *newTri;
|
|
int capPlaneBits;
|
|
|
|
if ( !r_shadows.GetBool() ) {
|
|
return NULL;
|
|
}
|
|
|
|
if ( tri->numSilEdges == 0 || tri->numIndexes == 0 || tri->numVerts == 0 ) {
|
|
return NULL;
|
|
}
|
|
|
|
if ( tri->numIndexes < 0 ) {
|
|
common->Error( "R_CreateShadowVolume: tri->numIndexes = %i", tri->numIndexes );
|
|
}
|
|
|
|
if ( tri->numVerts < 0 ) {
|
|
common->Error( "R_CreateShadowVolume: tri->numVerts = %i", tri->numVerts );
|
|
}
|
|
|
|
tr.pc.c_createShadowVolumes++;
|
|
|
|
// use the fast infinite projection in dynamic situations, which
|
|
// trades somewhat more overdraw and no cap optimizations for
|
|
// a very simple generation process
|
|
if ( optimize == SG_DYNAMIC && r_useTurboShadow.GetBool() ) {
|
|
if ( tr.backEndRendererHasVertexPrograms && r_useShadowVertexProgram.GetBool() ) {
|
|
return R_CreateVertexProgramTurboShadowVolume( ent, tri, light, cullInfo );
|
|
} else {
|
|
return R_CreateTurboShadowVolume( ent, tri, light, cullInfo );
|
|
}
|
|
}
|
|
|
|
R_CalcInteractionFacing( ent, tri, light, cullInfo );
|
|
|
|
int numFaces = tri->numIndexes / 3;
|
|
int allFront = 1;
|
|
for ( i = 0; i < numFaces && allFront; i++ ) {
|
|
allFront &= cullInfo.facing[i];
|
|
}
|
|
if ( allFront ) {
|
|
// if no faces are the right direction, don't make a shadow at all
|
|
return NULL;
|
|
}
|
|
|
|
// clear the shadow volume
|
|
numShadowIndexes = 0;
|
|
numShadowVerts = 0;
|
|
overflowed = false;
|
|
indexFrustumNumber = 0;
|
|
capPlaneBits = 0;
|
|
callOptimizer = (optimize == SG_OFFLINE);
|
|
|
|
// the facing information will be the same for all six projections
|
|
// from a point light, as well as for any directed lights
|
|
globalFacing = cullInfo.facing;
|
|
faceCastsShadow = (byte *)_alloca16( tri->numIndexes / 3 + 1 ); // + 1 for fake dangling edge face
|
|
remap = (int *)_alloca16( tri->numVerts * sizeof( remap[0] ) );
|
|
|
|
R_GlobalPointToLocal( ent->modelMatrix, light->globalLightOrigin, lightOrigin );
|
|
|
|
// run through all the shadow frustums, which is one for a projected light,
|
|
// and usually six for a point light, but point lights with centers outside
|
|
// the box may have less
|
|
for ( int frustumNum = 0 ; frustumNum < light->numShadowFrustums ; frustumNum++ ) {
|
|
const shadowFrustum_t *frust = &light->shadowFrustums[frustumNum];
|
|
ALIGN16( idPlane frustum[6] );
|
|
|
|
// transform the planes into entity space
|
|
// we could share and reverse some of the planes between frustums for a minor
|
|
// speed increase
|
|
|
|
// the cull test is redundant for a single shadow frustum projected light, because
|
|
// the surface has already been checked against the main light frustums
|
|
|
|
for ( j = 0 ; j < frust->numPlanes ; j++ ) {
|
|
R_GlobalPlaneToLocal( ent->modelMatrix, frust->planes[j], frustum[j] );
|
|
|
|
// try to cull the entire surface against this frustum
|
|
float d = tri->bounds.PlaneDistance( frustum[j] );
|
|
if ( d < -LIGHT_CLIP_EPSILON ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( j != frust->numPlanes ) {
|
|
continue;
|
|
}
|
|
// we need to check all the triangles
|
|
int oldFrustumNumber = indexFrustumNumber;
|
|
|
|
R_CreateShadowVolumeInFrustum( ent, tri, light, lightOrigin, frustum, frustum[5], frust->makeClippedPlanes );
|
|
|
|
// if we couldn't make a complete shadow volume, it is better to
|
|
// not draw one at all, avoiding streamer problems
|
|
if ( overflowed ) {
|
|
return NULL;
|
|
}
|
|
|
|
if ( indexFrustumNumber != oldFrustumNumber ) {
|
|
// note that we have caps projected against this frustum,
|
|
// which may allow us to skip drawing the caps if all projected
|
|
// planes face away from the viewer and the viewer is outside the light volume
|
|
capPlaneBits |= 1<<frustumNum;
|
|
}
|
|
}
|
|
|
|
// if no faces have been defined for the shadow volume,
|
|
// there won't be anything at all
|
|
if ( numShadowIndexes == 0 ) {
|
|
return NULL;
|
|
}
|
|
|
|
// this should have been prevented by the overflowed flag, so if it ever happens,
|
|
// it is a code error
|
|
if ( numShadowVerts > MAX_SHADOW_VERTS || numShadowIndexes > MAX_SHADOW_INDEXES ) {
|
|
common->FatalError( "Shadow volume exceeded allocation" );
|
|
}
|
|
|
|
// allocate a new surface for the shadow volume
|
|
newTri = R_AllocStaticTriSurf();
|
|
|
|
// we might consider setting this, but it would only help for
|
|
// large lights that are partially off screen
|
|
newTri->bounds.Clear();
|
|
|
|
// copy off the verts and indexes
|
|
newTri->numVerts = numShadowVerts;
|
|
newTri->numIndexes = numShadowIndexes;
|
|
|
|
// the shadow verts will go into a main memory buffer as well as a vertex
|
|
// cache buffer, so they can be copied back if they are purged
|
|
R_AllocStaticTriSurfShadowVerts( newTri, newTri->numVerts );
|
|
SIMDProcessor->Memcpy( newTri->shadowVertexes, shadowVerts, newTri->numVerts * sizeof( newTri->shadowVertexes[0] ) );
|
|
|
|
R_AllocStaticTriSurfIndexes( newTri, newTri->numIndexes );
|
|
|
|
if ( 1 /* sortCapIndexes */ ) {
|
|
newTri->shadowCapPlaneBits = capPlaneBits;
|
|
|
|
// copy the sil indexes first
|
|
newTri->numShadowIndexesNoCaps = 0;
|
|
for ( i = 0 ; i < indexFrustumNumber ; i++ ) {
|
|
int c = indexRef[i].end - indexRef[i].silStart;
|
|
SIMDProcessor->Memcpy( newTri->indexes+newTri->numShadowIndexesNoCaps,
|
|
shadowIndexes+indexRef[i].silStart, c * sizeof( newTri->indexes[0] ) );
|
|
newTri->numShadowIndexesNoCaps += c;
|
|
}
|
|
// copy rear cap indexes next
|
|
newTri->numShadowIndexesNoFrontCaps = newTri->numShadowIndexesNoCaps;
|
|
for ( i = 0 ; i < indexFrustumNumber ; i++ ) {
|
|
int c = indexRef[i].silStart - indexRef[i].rearCapStart;
|
|
SIMDProcessor->Memcpy( newTri->indexes+newTri->numShadowIndexesNoFrontCaps,
|
|
shadowIndexes+indexRef[i].rearCapStart, c * sizeof( newTri->indexes[0] ) );
|
|
newTri->numShadowIndexesNoFrontCaps += c;
|
|
}
|
|
// copy front cap indexes last
|
|
newTri->numIndexes = newTri->numShadowIndexesNoFrontCaps;
|
|
for ( i = 0 ; i < indexFrustumNumber ; i++ ) {
|
|
int c = indexRef[i].rearCapStart - indexRef[i].frontCapStart;
|
|
SIMDProcessor->Memcpy( newTri->indexes+newTri->numIndexes,
|
|
shadowIndexes+indexRef[i].frontCapStart, c * sizeof( newTri->indexes[0] ) );
|
|
newTri->numIndexes += c;
|
|
}
|
|
|
|
} else {
|
|
newTri->shadowCapPlaneBits = 63; // we don't have optimized index lists
|
|
SIMDProcessor->Memcpy( newTri->indexes, shadowIndexes, newTri->numIndexes * sizeof( newTri->indexes[0] ) );
|
|
}
|
|
|
|
if ( optimize == SG_OFFLINE ) {
|
|
CleanupOptimizedShadowTris( newTri );
|
|
}
|
|
|
|
return newTri;
|
|
}
|