You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1810 lines
39 KiB
1810 lines
39 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
// faces.c |
|
|
|
#include "vbsp.h" |
|
#include "utlvector.h" |
|
#include "utilmatlib.h" |
|
#include <float.h> |
|
#include "mstristrip.h" |
|
#include "tier1/strtools.h" |
|
#include "materialpatch.h" |
|
/* |
|
|
|
some faces will be removed before saving, but still form nodes: |
|
|
|
the insides of sky volumes |
|
meeting planes of different water current volumes |
|
|
|
*/ |
|
|
|
// undefine for dumb linear searches |
|
#define USE_HASHING |
|
|
|
#define INTEGRAL_EPSILON 0.01 |
|
#define POINT_EPSILON 0.1 |
|
#define OFF_EPSILON 0.25 |
|
|
|
int c_merge; |
|
int c_subdivide; |
|
|
|
int c_totalverts; |
|
int c_uniqueverts; |
|
int c_degenerate; |
|
int c_tjunctions; |
|
int c_faceoverflows; |
|
int c_facecollapse; |
|
int c_badstartverts; |
|
|
|
#define MAX_SUPERVERTS 512 |
|
int superverts[MAX_SUPERVERTS]; |
|
int numsuperverts; |
|
|
|
face_t *edgefaces[MAX_MAP_EDGES][2]; |
|
int firstmodeledge = 1; |
|
int firstmodelface; |
|
|
|
int c_tryedges; |
|
|
|
Vector edge_dir; |
|
Vector edge_start; |
|
vec_t edge_len; |
|
|
|
int num_edge_verts; |
|
int edge_verts[MAX_MAP_VERTS]; |
|
|
|
|
|
float g_maxLightmapDimension = 32; |
|
|
|
|
|
face_t *NewFaceFromFace (face_t *f); |
|
|
|
// Used to speed up GetEdge2(). Holds a list of edges connected to each vert. |
|
CUtlVector<int> g_VertEdgeList[MAX_MAP_VERTS]; |
|
|
|
|
|
//=========================================================================== |
|
|
|
typedef struct hashvert_s |
|
{ |
|
struct hashvert_s *next; |
|
int num; |
|
} hashvert_t; |
|
|
|
#define HASH_BITS 7 |
|
#define HASH_SIZE (COORD_EXTENT>>HASH_BITS) |
|
|
|
|
|
int vertexchain[MAX_MAP_VERTS]; // the next vertex in a hash chain |
|
int hashverts[HASH_SIZE*HASH_SIZE]; // a vertex number, or 0 for no verts |
|
|
|
//face_t *edgefaces[MAX_MAP_EDGES][2]; |
|
|
|
//============================================================================ |
|
|
|
|
|
unsigned HashVec (Vector& vec) |
|
{ |
|
int x, y; |
|
|
|
x = (MAX_COORD_INTEGER + (int)(vec[0]+0.5)) >> HASH_BITS; |
|
y = (MAX_COORD_INTEGER + (int)(vec[1]+0.5)) >> HASH_BITS; |
|
|
|
if ( x < 0 || x >= HASH_SIZE || y < 0 || y >= HASH_SIZE ) |
|
Error ("HashVec: point outside valid range"); |
|
|
|
return y*HASH_SIZE + x; |
|
} |
|
|
|
#ifdef USE_HASHING |
|
/* |
|
============= |
|
GetVertex |
|
|
|
Uses hashing |
|
============= |
|
*/ |
|
int GetVertexnum (Vector& in) |
|
{ |
|
int h; |
|
int i; |
|
Vector vert; |
|
int vnum; |
|
|
|
c_totalverts++; |
|
|
|
for (i=0 ; i<3 ; i++) |
|
{ |
|
if ( fabs(in[i] - (int)(in[i]+0.5)) < INTEGRAL_EPSILON) |
|
vert[i] = (int)(in[i]+0.5); |
|
else |
|
vert[i] = in[i]; |
|
} |
|
|
|
h = HashVec (vert); |
|
|
|
for (vnum=hashverts[h] ; vnum ; vnum=vertexchain[vnum]) |
|
{ |
|
Vector& p = dvertexes[vnum].point; |
|
if ( fabs(p[0]-vert[0])<POINT_EPSILON |
|
&& fabs(p[1]-vert[1])<POINT_EPSILON |
|
&& fabs(p[2]-vert[2])<POINT_EPSILON ) |
|
return vnum; |
|
} |
|
|
|
// emit a vertex |
|
if (numvertexes == MAX_MAP_VERTS) |
|
Error ("Too many unique verts, max = %d (map has too much brush geometry)\n", MAX_MAP_VERTS); |
|
|
|
dvertexes[numvertexes].point[0] = vert[0]; |
|
dvertexes[numvertexes].point[1] = vert[1]; |
|
dvertexes[numvertexes].point[2] = vert[2]; |
|
|
|
vertexchain[numvertexes] = hashverts[h]; |
|
hashverts[h] = numvertexes; |
|
|
|
c_uniqueverts++; |
|
|
|
numvertexes++; |
|
|
|
return numvertexes-1; |
|
} |
|
#else |
|
/* |
|
================== |
|
GetVertexnum |
|
|
|
Dumb linear search |
|
================== |
|
*/ |
|
int GetVertexnum (Vector& v) |
|
{ |
|
int i, j; |
|
dvertex_t *dv; |
|
vec_t d; |
|
|
|
c_totalverts++; |
|
|
|
// make really close values exactly integral |
|
for (i=0 ; i<3 ; i++) |
|
{ |
|
if ( fabs(v[i] - (int)(v[i]+0.5)) < INTEGRAL_EPSILON ) |
|
v[i] = (int)(v[i]+0.5); |
|
if (v[i] < MIN_COORD_INTEGER || v[i] > MAX_COORD_INTEGER) |
|
Error ("GetVertexnum: outside world, vertex %.1f %.1f %.1f", v.x, v.y, v.z); |
|
} |
|
|
|
// search for an existing vertex match |
|
for (i=0, dv=dvertexes ; i<numvertexes ; i++, dv++) |
|
{ |
|
for (j=0 ; j<3 ; j++) |
|
{ |
|
d = v[j] - dv->point[j]; |
|
if ( d > POINT_EPSILON || d < -POINT_EPSILON) |
|
break; |
|
} |
|
if (j == 3) |
|
return i; // a match |
|
} |
|
|
|
// new point |
|
if (numvertexes == MAX_MAP_VERTS) |
|
Error ("Too many unique verts, max = %d (map has too much brush geometry)\n", MAX_MAP_VERTS); |
|
VectorCopy (v, dv->point); |
|
numvertexes++; |
|
c_uniqueverts++; |
|
|
|
return numvertexes-1; |
|
} |
|
#endif |
|
|
|
|
|
/* |
|
================== |
|
FaceFromSuperverts |
|
|
|
The faces vertexes have beeb added to the superverts[] array, |
|
and there may be more there than can be held in a face (MAXEDGES). |
|
|
|
If less, the faces vertexnums[] will be filled in, otherwise |
|
face will reference a tree of split[] faces until all of the |
|
vertexnums can be added. |
|
|
|
superverts[base] will become face->vertexnums[0], and the others |
|
will be circularly filled in. |
|
================== |
|
*/ |
|
void FaceFromSuperverts (face_t **pListHead, face_t *f, int base) |
|
{ |
|
face_t *newf; |
|
int remaining; |
|
int i; |
|
|
|
remaining = numsuperverts; |
|
while (remaining > MAXEDGES) |
|
{ // must split into two faces, because of vertex overload |
|
c_faceoverflows++; |
|
|
|
newf = NewFaceFromFace (f); |
|
f->split[0] = newf; |
|
|
|
newf->next = *pListHead; |
|
*pListHead = newf; |
|
|
|
newf->numpoints = MAXEDGES; |
|
for (i=0 ; i<MAXEDGES ; i++) |
|
newf->vertexnums[i] = superverts[(i+base)%numsuperverts]; |
|
|
|
f->split[1] = NewFaceFromFace (f); |
|
f = f->split[1]; |
|
|
|
f->next = *pListHead; |
|
*pListHead = f; |
|
|
|
remaining -= (MAXEDGES-2); |
|
base = (base+MAXEDGES-1)%numsuperverts; |
|
} |
|
|
|
// copy the vertexes back to the face |
|
f->numpoints = remaining; |
|
for (i=0 ; i<remaining ; i++) |
|
f->vertexnums[i] = superverts[(i+base)%numsuperverts]; |
|
} |
|
|
|
|
|
/* |
|
================== |
|
EmitFaceVertexes |
|
================== |
|
*/ |
|
void EmitFaceVertexes (face_t **pListHead, face_t *f) |
|
{ |
|
winding_t *w; |
|
int i; |
|
|
|
if (f->merged || f->split[0] || f->split[1]) |
|
return; |
|
|
|
w = f->w; |
|
for (i=0 ; i<w->numpoints ; i++) |
|
{ |
|
if (noweld) |
|
{ // make every point unique |
|
if (numvertexes == MAX_MAP_VERTS) |
|
Error ("Too many unique verts, max = %d (map has too much brush geometry)\n", MAX_MAP_VERTS); |
|
superverts[i] = numvertexes; |
|
VectorCopy (w->p[i], dvertexes[numvertexes].point); |
|
numvertexes++; |
|
c_uniqueverts++; |
|
c_totalverts++; |
|
} |
|
else |
|
superverts[i] = GetVertexnum (w->p[i]); |
|
} |
|
numsuperverts = w->numpoints; |
|
|
|
// this may fragment the face if > MAXEDGES |
|
FaceFromSuperverts (pListHead, f, 0); |
|
} |
|
|
|
/* |
|
================== |
|
EmitNodeFaceVertexes_r |
|
================== |
|
*/ |
|
void EmitNodeFaceVertexes_r (node_t *node) |
|
{ |
|
int i; |
|
face_t *f; |
|
|
|
if (node->planenum == PLANENUM_LEAF) |
|
{ |
|
// leaf faces are emitted in second pass |
|
return; |
|
} |
|
|
|
for (f=node->faces ; f ; f=f->next) |
|
{ |
|
EmitFaceVertexes (&node->faces, f); |
|
} |
|
|
|
for (i=0 ; i<2 ; i++) |
|
{ |
|
EmitNodeFaceVertexes_r (node->children[i]); |
|
} |
|
} |
|
|
|
void EmitLeafFaceVertexes( face_t **ppLeafFaceList ) |
|
{ |
|
face_t *f = *ppLeafFaceList; |
|
|
|
while ( f ) |
|
{ |
|
EmitFaceVertexes( ppLeafFaceList, f ); |
|
f = f->next; |
|
} |
|
} |
|
|
|
|
|
#ifdef USE_HASHING |
|
/* |
|
========== |
|
FindEdgeVerts |
|
|
|
Uses the hash tables to cut down to a small number |
|
========== |
|
*/ |
|
void FindEdgeVerts (Vector& v1, Vector& v2) |
|
{ |
|
int x1, x2, y1, y2, t; |
|
int x, y; |
|
int vnum; |
|
|
|
#if 0 |
|
{ |
|
int i; |
|
num_edge_verts = numvertexes-1; |
|
for (i=0 ; i<numvertexes-1 ; i++) |
|
edge_verts[i] = i+1; |
|
} |
|
#endif |
|
|
|
x1 = (MAX_COORD_INTEGER + (int)(v1[0]+0.5)) >> HASH_BITS; |
|
y1 = (MAX_COORD_INTEGER + (int)(v1[1]+0.5)) >> HASH_BITS; |
|
x2 = (MAX_COORD_INTEGER + (int)(v2[0]+0.5)) >> HASH_BITS; |
|
y2 = (MAX_COORD_INTEGER + (int)(v2[1]+0.5)) >> HASH_BITS; |
|
|
|
if (x1 > x2) |
|
{ |
|
t = x1; |
|
x1 = x2; |
|
x2 = t; |
|
} |
|
if (y1 > y2) |
|
{ |
|
t = y1; |
|
y1 = y2; |
|
y2 = t; |
|
} |
|
#if 0 |
|
x1--; |
|
x2++; |
|
y1--; |
|
y2++; |
|
if (x1 < 0) |
|
x1 = 0; |
|
if (x2 >= HASH_SIZE) |
|
x2 = HASH_SIZE; |
|
if (y1 < 0) |
|
y1 = 0; |
|
if (y2 >= HASH_SIZE) |
|
y2 = HASH_SIZE; |
|
#endif |
|
num_edge_verts = 0; |
|
for (x=x1 ; x <= x2 ; x++) |
|
{ |
|
for (y=y1 ; y <= y2 ; y++) |
|
{ |
|
for (vnum=hashverts[y*HASH_SIZE+x] ; vnum ; vnum=vertexchain[vnum]) |
|
{ |
|
edge_verts[num_edge_verts++] = vnum; |
|
} |
|
} |
|
} |
|
} |
|
|
|
#else |
|
/* |
|
========== |
|
FindEdgeVerts |
|
|
|
Forced a dumb check of everything |
|
========== |
|
*/ |
|
void FindEdgeVerts (Vector& v1, Vector& v2) |
|
{ |
|
int i; |
|
|
|
num_edge_verts = numvertexes-1; |
|
for (i=0 ; i<num_edge_verts ; i++) |
|
edge_verts[i] = i+1; |
|
} |
|
#endif |
|
|
|
/* |
|
========== |
|
TestEdge |
|
|
|
Can be recursively reentered |
|
========== |
|
*/ |
|
void TestEdge (vec_t start, vec_t end, int p1, int p2, int startvert) |
|
{ |
|
int j, k; |
|
vec_t dist; |
|
Vector delta; |
|
Vector exact; |
|
Vector off; |
|
vec_t error; |
|
Vector p; |
|
|
|
if (p1 == p2) |
|
{ |
|
c_degenerate++; |
|
return; // degenerate edge |
|
} |
|
|
|
for (k=startvert ; k<num_edge_verts ; k++) |
|
{ |
|
j = edge_verts[k]; |
|
if (j==p1 || j == p2) |
|
continue; |
|
|
|
VectorCopy (dvertexes[j].point, p); |
|
|
|
VectorSubtract (p, edge_start, delta); |
|
dist = DotProduct (delta, edge_dir); |
|
if (dist <=start || dist >= end) |
|
continue; // off an end |
|
VectorMA (edge_start, dist, edge_dir, exact); |
|
VectorSubtract (p, exact, off); |
|
error = off.Length(); |
|
|
|
if (error > OFF_EPSILON) |
|
continue; // not on the edge |
|
|
|
// break the edge |
|
c_tjunctions++; |
|
TestEdge (start, dist, p1, j, k+1); |
|
TestEdge (dist, end, j, p2, k+1); |
|
return; |
|
} |
|
|
|
// the edge p1 to p2 is now free of tjunctions |
|
if (numsuperverts >= MAX_SUPERVERTS) |
|
Error ("Edge with too many vertices due to t-junctions. Max %d verts along an edge!\n", MAX_SUPERVERTS); |
|
superverts[numsuperverts] = p1; |
|
numsuperverts++; |
|
} |
|
|
|
|
|
// stores the edges that each vert is part of |
|
struct face_vert_table_t |
|
{ |
|
face_vert_table_t() |
|
{ |
|
edge0 = -1; |
|
edge1 = -1; |
|
} |
|
|
|
void AddEdge( int edge ) |
|
{ |
|
if ( edge0 == -1 ) |
|
{ |
|
edge0 = edge; |
|
} |
|
else |
|
{ |
|
// can only have two edges |
|
Assert(edge1==-1); |
|
edge1 = edge; |
|
} |
|
} |
|
|
|
bool HasEdge( int edge ) const |
|
{ |
|
if ( edge >= 0 ) |
|
{ |
|
if ( edge0 == edge || edge1 == edge ) |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
int edge0; |
|
int edge1; |
|
}; |
|
|
|
// if these two verts share an edge, they must be collinear |
|
bool IsDiagonal( const face_vert_table_t &v0, const face_vert_table_t &v1 ) |
|
{ |
|
if ( v1.HasEdge(v0.edge0) || v1.HasEdge(v0.edge1) ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
|
|
void Triangulate_r( CUtlVector<int> &out, const CUtlVector<int> &inIndices, const CUtlVector<face_vert_table_t> &poly ) |
|
{ |
|
Assert( inIndices.Count() > 2 ); |
|
|
|
// one triangle left, return |
|
if ( inIndices.Count() == 3 ) |
|
{ |
|
for ( int i = 0; i < inIndices.Count(); i++ ) |
|
{ |
|
out.AddToTail( inIndices[i] ); |
|
} |
|
return; |
|
} |
|
|
|
// check each pair of verts and see if they are diagonal (not on a shared edge) |
|
// if so, split & recurse |
|
for ( int i = 0; i < inIndices.Count(); i++ ) |
|
{ |
|
int count = inIndices.Count(); |
|
|
|
// i + count is myself, i + count-1 is previous, so we need to stop at i+count-2 |
|
for ( int j = 2; j < count-1; j++ ) |
|
{ |
|
// if these two form a diagonal, split the poly along |
|
// the diagonal and triangulate the two sub-polys |
|
int index = inIndices[i]; |
|
int nextArray = (i+j)%count; |
|
int nextIndex = inIndices[nextArray]; |
|
if ( IsDiagonal(poly[index], poly[nextIndex]) ) |
|
{ |
|
// add the poly up to the diagonal |
|
CUtlVector<int> in1; |
|
for ( int k = i; k != nextArray; k = (k+1)%count ) |
|
{ |
|
in1.AddToTail(inIndices[k]); |
|
} |
|
in1.AddToTail(nextIndex); |
|
|
|
// add the rest of the poly starting with the diagonal |
|
CUtlVector<int> in2; |
|
in2.AddToTail(index); |
|
for ( int l = nextArray; l != i; l = (l+1)%count ) |
|
{ |
|
in2.AddToTail(inIndices[l]); |
|
} |
|
|
|
// triangulate the sub-polys |
|
Triangulate_r( out, in1, poly ); |
|
Triangulate_r( out, in2, poly ); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
// didn't find a diagonal |
|
Assert(0); |
|
} |
|
|
|
/* |
|
================== |
|
FixFaceEdges |
|
|
|
================== |
|
*/ |
|
void FixFaceEdges (face_t **pList, face_t *f) |
|
{ |
|
int p1, p2; |
|
int i; |
|
Vector e2; |
|
vec_t len; |
|
int count[MAX_SUPERVERTS], start[MAX_SUPERVERTS]; |
|
int base; |
|
|
|
if (f->merged || f->split[0] || f->split[1]) |
|
return; |
|
|
|
numsuperverts = 0; |
|
|
|
int originalPoints = f->numpoints; |
|
for (i=0 ; i<f->numpoints ; i++) |
|
{ |
|
p1 = f->vertexnums[i]; |
|
p2 = f->vertexnums[(i+1)%f->numpoints]; |
|
|
|
VectorCopy (dvertexes[p1].point, edge_start); |
|
VectorCopy (dvertexes[p2].point, e2); |
|
|
|
FindEdgeVerts (edge_start, e2); |
|
|
|
VectorSubtract (e2, edge_start, edge_dir); |
|
len = VectorNormalize (edge_dir); |
|
|
|
start[i] = numsuperverts; |
|
TestEdge (0, len, p1, p2, 0); |
|
|
|
count[i] = numsuperverts - start[i]; |
|
} |
|
|
|
if (numsuperverts < 3) |
|
{ // entire face collapsed |
|
f->numpoints = 0; |
|
c_facecollapse++; |
|
return; |
|
} |
|
|
|
// we want to pick a vertex that doesn't have tjunctions |
|
// on either side, which can cause artifacts on trifans, |
|
// especially underwater |
|
for (i=0 ; i<f->numpoints ; i++) |
|
{ |
|
if (count[i] == 1 && count[(i+f->numpoints-1)%f->numpoints] == 1) |
|
break; |
|
} |
|
if (i == f->numpoints) |
|
{ |
|
f->badstartvert = true; |
|
c_badstartverts++; |
|
base = 0; |
|
|
|
} |
|
else |
|
{ // rotate the vertex order |
|
base = start[i]; |
|
} |
|
|
|
// this may fragment the face if > MAXEDGES |
|
FaceFromSuperverts (pList, f, base); |
|
|
|
// if this is the world, then re-triangulate to sew cracks |
|
if ( f->badstartvert && entity_num == 0 ) |
|
{ |
|
CUtlVector<face_vert_table_t> poly; |
|
CUtlVector<int> inIndices; |
|
CUtlVector<int> outIndices; |
|
poly.AddMultipleToTail( numsuperverts ); |
|
for ( i = 0; i < originalPoints; i++ ) |
|
{ |
|
// edge may not have output any points. Don't mark |
|
if ( !count[i] ) |
|
continue; |
|
// mark each edge the point is a member of |
|
// we'll use this as a fast "is collinear" test |
|
for ( int j = 0; j <= count[i]; j++ ) |
|
{ |
|
int polyIndex = (start[i] + j) % numsuperverts; |
|
poly[polyIndex].AddEdge( i ); |
|
} |
|
} |
|
for ( i = 0; i < numsuperverts; i++ ) |
|
{ |
|
inIndices.AddToTail( i ); |
|
} |
|
Triangulate_r( outIndices, inIndices, poly ); |
|
dprimitive_t &newPrim = g_primitives[g_numprimitives]; |
|
f->firstPrimID = g_numprimitives; |
|
g_numprimitives++; |
|
f->numPrims = 1; |
|
newPrim.firstIndex = g_numprimindices; |
|
newPrim.firstVert = g_numprimverts; |
|
newPrim.indexCount = outIndices.Count(); |
|
newPrim.vertCount = 0; |
|
newPrim.type = PRIM_TRILIST; |
|
g_numprimindices += newPrim.indexCount; |
|
if ( g_numprimitives > MAX_MAP_PRIMITIVES || g_numprimindices > MAX_MAP_PRIMINDICES ) |
|
{ |
|
Error("Too many t-junctions to fix up! (%d prims, max %d :: %d indices, max %d)\n", g_numprimitives, MAX_MAP_PRIMITIVES, g_numprimindices, MAX_MAP_PRIMINDICES ); |
|
} |
|
for ( i = 0; i < outIndices.Count(); i++ ) |
|
{ |
|
g_primindices[newPrim.firstIndex + i] = outIndices[i]; |
|
} |
|
} |
|
} |
|
|
|
/* |
|
================== |
|
FixEdges_r |
|
================== |
|
*/ |
|
void FixEdges_r (node_t *node) |
|
{ |
|
int i; |
|
face_t *f; |
|
|
|
if (node->planenum == PLANENUM_LEAF) |
|
{ |
|
return; |
|
} |
|
|
|
for (f=node->faces ; f ; f=f->next) |
|
FixFaceEdges (&node->faces, f); |
|
|
|
for (i=0 ; i<2 ; i++) |
|
FixEdges_r (node->children[i]); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Fix the t-junctions on detail faces |
|
//----------------------------------------------------------------------------- |
|
void FixLeafFaceEdges( face_t **ppLeafFaceList ) |
|
{ |
|
face_t *f; |
|
|
|
for ( f = *ppLeafFaceList; f; f = f->next ) |
|
{ |
|
FixFaceEdges( ppLeafFaceList, f ); |
|
} |
|
} |
|
|
|
/* |
|
=========== |
|
FixTjuncs |
|
|
|
=========== |
|
*/ |
|
|
|
face_t *FixTjuncs (node_t *headnode, face_t *pLeafFaceList) |
|
{ |
|
// snap and merge all vertexes |
|
qprintf ("---- snap verts ----\n"); |
|
memset (hashverts, 0, sizeof(hashverts)); |
|
memset (vertexchain, 0, sizeof(vertexchain)); |
|
c_totalverts = 0; |
|
c_uniqueverts = 0; |
|
c_faceoverflows = 0; |
|
EmitNodeFaceVertexes_r (headnode); |
|
|
|
// UNDONE: This count is wrong with tjuncs off on details - since |
|
|
|
// break edges on tjunctions |
|
qprintf ("---- tjunc ----\n"); |
|
c_tryedges = 0; |
|
c_degenerate = 0; |
|
c_facecollapse = 0; |
|
c_tjunctions = 0; |
|
|
|
if ( g_bAllowDetailCracks ) |
|
{ |
|
FixEdges_r (headnode); |
|
EmitLeafFaceVertexes( &pLeafFaceList ); |
|
FixLeafFaceEdges( &pLeafFaceList ); |
|
} |
|
else |
|
{ |
|
EmitLeafFaceVertexes( &pLeafFaceList ); |
|
if (!notjunc) |
|
{ |
|
FixEdges_r (headnode); |
|
FixLeafFaceEdges( &pLeafFaceList ); |
|
} |
|
} |
|
|
|
|
|
qprintf ("%i unique from %i\n", c_uniqueverts, c_totalverts); |
|
qprintf ("%5i edges degenerated\n", c_degenerate); |
|
qprintf ("%5i faces degenerated\n", c_facecollapse); |
|
qprintf ("%5i edges added by tjunctions\n", c_tjunctions); |
|
qprintf ("%5i faces added by tjunctions\n", c_faceoverflows); |
|
qprintf ("%5i bad start verts\n", c_badstartverts); |
|
|
|
return pLeafFaceList; |
|
} |
|
|
|
|
|
//======================================================== |
|
|
|
int c_faces; |
|
|
|
face_t *AllocFace (void) |
|
{ |
|
static int s_FaceId = 0; |
|
|
|
face_t *f; |
|
|
|
f = (face_t*)malloc(sizeof(*f)); |
|
memset (f, 0, sizeof(*f)); |
|
f->id = s_FaceId; |
|
++s_FaceId; |
|
|
|
c_faces++; |
|
|
|
return f; |
|
} |
|
|
|
face_t *NewFaceFromFace (face_t *f) |
|
{ |
|
face_t *newf; |
|
|
|
newf = AllocFace (); |
|
*newf = *f; |
|
newf->merged = NULL; |
|
newf->split[0] = newf->split[1] = NULL; |
|
newf->w = NULL; |
|
return newf; |
|
} |
|
|
|
void FreeFace (face_t *f) |
|
{ |
|
if (f->w) |
|
FreeWinding (f->w); |
|
free (f); |
|
c_faces--; |
|
} |
|
|
|
|
|
void FreeFaceList( face_t *pFaces ) |
|
{ |
|
while ( pFaces ) |
|
{ |
|
face_t *next = pFaces->next; |
|
|
|
FreeFace( pFaces ); |
|
pFaces = next; |
|
} |
|
} |
|
|
|
//======================================================== |
|
|
|
void GetEdge2_InitOptimizedList() |
|
{ |
|
for( int i=0; i < MAX_MAP_VERTS; i++ ) |
|
g_VertEdgeList[i].RemoveAll(); |
|
} |
|
|
|
|
|
void IntSort( CUtlVector<int> &theList ) |
|
{ |
|
for( int i=0; i < theList.Size()-1; i++ ) |
|
{ |
|
if( theList[i] > theList[i+1] ) |
|
{ |
|
int temp = theList[i]; |
|
theList[i] = theList[i+1]; |
|
theList[i+1] = temp; |
|
if( i > 0 ) |
|
i -= 2; |
|
else |
|
i = -1; |
|
} |
|
} |
|
} |
|
|
|
|
|
int AddEdge( int v1, int v2, face_t *f ) |
|
{ |
|
if (numedges >= MAX_MAP_EDGES) |
|
Error ("Too many edges in map, max == %d", MAX_MAP_EDGES); |
|
|
|
g_VertEdgeList[v1].AddToTail( numedges ); |
|
g_VertEdgeList[v2].AddToTail( numedges ); |
|
IntSort( g_VertEdgeList[v1] ); |
|
IntSort( g_VertEdgeList[v2] ); |
|
|
|
dedge_t *edge = &dedges[numedges]; |
|
numedges++; |
|
|
|
edge->v[0] = v1; |
|
edge->v[1] = v2; |
|
edgefaces[numedges-1][0] = f; |
|
return numedges - 1; |
|
} |
|
|
|
|
|
/* |
|
================== |
|
GetEdge |
|
|
|
Called by writebsp. |
|
Don't allow four way edges |
|
================== |
|
*/ |
|
int GetEdge2 (int v1, int v2, face_t *f) |
|
{ |
|
dedge_t *edge; |
|
|
|
c_tryedges++; |
|
|
|
if (!noshare) |
|
{ |
|
// Check all edges connected to v1. |
|
CUtlVector<int> &theList = g_VertEdgeList[v1]; |
|
for( int i=0; i < theList.Size(); i++ ) |
|
{ |
|
int iEdge = theList[i]; |
|
edge = &dedges[iEdge]; |
|
if (v1 == edge->v[1] && v2 == edge->v[0] && edgefaces[iEdge][0]->contents == f->contents) |
|
{ |
|
if (edgefaces[iEdge][1]) |
|
continue; |
|
|
|
edgefaces[iEdge][1] = f; |
|
return -iEdge; |
|
} |
|
} |
|
} |
|
|
|
return AddEdge( v1, v2, f ); |
|
} |
|
|
|
/* |
|
=========================================================================== |
|
|
|
FACE MERGING |
|
|
|
=========================================================================== |
|
*/ |
|
|
|
#define CONTINUOUS_EPSILON 0.001 |
|
|
|
/* |
|
============= |
|
TryMergeWinding |
|
|
|
If two polygons share a common edge and the edges that meet at the |
|
common points are both inside the other polygons, merge them |
|
|
|
Returns NULL if the faces couldn't be merged, or the new face. |
|
The originals will NOT be freed. |
|
============= |
|
*/ |
|
winding_t *TryMergeWinding (winding_t *f1, winding_t *f2, Vector& planenormal) |
|
{ |
|
Vector *p1, *p2, *p3, *p4, *back; |
|
winding_t *newf; |
|
int i, j, k, l; |
|
Vector normal, delta; |
|
vec_t dot; |
|
qboolean keep1, keep2; |
|
|
|
|
|
// |
|
// find a common edge |
|
// |
|
p1 = p2 = NULL; // stop compiler warning |
|
j = 0; // |
|
|
|
for (i=0 ; i<f1->numpoints ; i++) |
|
{ |
|
p1 = &f1->p[i]; |
|
p2 = &f1->p[(i+1)%f1->numpoints]; |
|
for (j=0 ; j<f2->numpoints ; j++) |
|
{ |
|
p3 = &f2->p[j]; |
|
p4 = &f2->p[(j+1)%f2->numpoints]; |
|
for (k=0 ; k<3 ; k++) |
|
{ |
|
if (fabs((*p1)[k] - (*p4)[k]) > EQUAL_EPSILON) |
|
break; |
|
if (fabs((*p2)[k] - (*p3)[k]) > EQUAL_EPSILON) |
|
break; |
|
} |
|
if (k==3) |
|
break; |
|
} |
|
if (j < f2->numpoints) |
|
break; |
|
} |
|
|
|
if (i == f1->numpoints) |
|
return NULL; // no matching edges |
|
|
|
// |
|
// check slope of connected lines |
|
// if the slopes are colinear, the point can be removed |
|
// |
|
back = &f1->p[(i+f1->numpoints-1)%f1->numpoints]; |
|
VectorSubtract (*p1, *back, delta); |
|
CrossProduct (planenormal, delta, normal); |
|
VectorNormalize (normal); |
|
|
|
back = &f2->p[(j+2)%f2->numpoints]; |
|
VectorSubtract (*back, *p1, delta); |
|
dot = DotProduct (delta, normal); |
|
if (dot > CONTINUOUS_EPSILON) |
|
return NULL; // not a convex polygon |
|
keep1 = (qboolean)(dot < -CONTINUOUS_EPSILON); |
|
|
|
back = &f1->p[(i+2)%f1->numpoints]; |
|
VectorSubtract (*back, *p2, delta); |
|
CrossProduct (planenormal, delta, normal); |
|
VectorNormalize (normal); |
|
|
|
back = &f2->p[(j+f2->numpoints-1)%f2->numpoints]; |
|
VectorSubtract (*back, *p2, delta); |
|
dot = DotProduct (delta, normal); |
|
if (dot > CONTINUOUS_EPSILON) |
|
return NULL; // not a convex polygon |
|
keep2 = (qboolean)(dot < -CONTINUOUS_EPSILON); |
|
|
|
// |
|
// build the new polygon |
|
// |
|
newf = AllocWinding (f1->numpoints + f2->numpoints); |
|
|
|
// copy first polygon |
|
for (k=(i+1)%f1->numpoints ; k != i ; k=(k+1)%f1->numpoints) |
|
{ |
|
if (k==(i+1)%f1->numpoints && !keep2) |
|
continue; |
|
|
|
VectorCopy (f1->p[k], newf->p[newf->numpoints]); |
|
newf->numpoints++; |
|
} |
|
|
|
// copy second polygon |
|
for (l= (j+1)%f2->numpoints ; l != j ; l=(l+1)%f2->numpoints) |
|
{ |
|
if (l==(j+1)%f2->numpoints && !keep1) |
|
continue; |
|
VectorCopy (f2->p[l], newf->p[newf->numpoints]); |
|
newf->numpoints++; |
|
} |
|
|
|
return newf; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool OverlaysAreEqual( face_t *f1, face_t *f2 ) |
|
{ |
|
// Check the overlay ids - see if they are the same. |
|
if ( f1->originalface->aOverlayIds.Count() != f2->originalface->aOverlayIds.Count() ) |
|
return false; |
|
|
|
int nOverlayCount = f1->originalface->aOverlayIds.Count(); |
|
for ( int iOverlay = 0; iOverlay < nOverlayCount; ++iOverlay ) |
|
{ |
|
int nOverlayId = f1->originalface->aOverlayIds[iOverlay]; |
|
if ( f2->originalface->aOverlayIds.Find( nOverlayId ) == -1 ) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool FaceOnWaterBrush( face_t *face ) |
|
{ |
|
side_t *pSide = face->originalface; |
|
if ( !pSide ) |
|
return false; |
|
|
|
if ( pSide->contents & ( CONTENTS_WATER | CONTENTS_SLIME ) ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
/* |
|
============= |
|
TryMerge |
|
|
|
If two polygons share a common edge and the edges that meet at the |
|
common points are both inside the other polygons, merge them |
|
|
|
Returns NULL if the faces couldn't be merged, or the new face. |
|
The originals will NOT be freed. |
|
============= |
|
*/ |
|
face_t *TryMerge (face_t *f1, face_t *f2, Vector& planenormal) |
|
{ |
|
face_t *newf; |
|
winding_t *nw; |
|
|
|
if (!f1->w || !f2->w) |
|
return NULL; |
|
if (f1->texinfo != f2->texinfo) |
|
return NULL; |
|
if (f1->planenum != f2->planenum) // on front and back sides |
|
return NULL; |
|
if (f1->contents != f2->contents) |
|
return NULL; |
|
if ( f1->originalface->smoothingGroups != f2->originalface->smoothingGroups ) |
|
return NULL; |
|
if ( !OverlaysAreEqual( f1, f2 ) ) |
|
return NULL; |
|
if ( nomergewater && ( FaceOnWaterBrush( f1 ) || FaceOnWaterBrush( f2 ) ) ) |
|
return NULL; |
|
|
|
nw = TryMergeWinding (f1->w, f2->w, planenormal); |
|
if (!nw) |
|
return NULL; |
|
|
|
c_merge++; |
|
newf = NewFaceFromFace (f1); |
|
newf->w = nw; |
|
|
|
f1->merged = newf; |
|
f2->merged = newf; |
|
|
|
return newf; |
|
} |
|
|
|
/* |
|
=============== |
|
MergeFaceList |
|
=============== |
|
*/ |
|
void MergeFaceList(face_t **pList) |
|
{ |
|
face_t *f1, *f2, *end; |
|
face_t *merged; |
|
plane_t *plane; |
|
|
|
merged = NULL; |
|
|
|
for (f1 = *pList; f1 ; f1 = f1->next) |
|
{ |
|
if (f1->merged || f1->split[0] || f1->split[1]) |
|
continue; |
|
for (f2 = *pList; f2 != f1 ; f2=f2->next) |
|
{ |
|
if (f2->merged || f2->split[0] || f2->split[1]) |
|
continue; |
|
|
|
plane = &g_MainMap->mapplanes[f1->planenum]; |
|
merged = TryMerge (f1, f2, plane->normal); |
|
if (!merged) |
|
continue; |
|
|
|
// add merged to the end of the face list |
|
// so it will be checked against all the faces again |
|
for (end = *pList; end->next ; end = end->next) |
|
; |
|
merged->next = NULL; |
|
end->next = merged; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
//===================================================================== |
|
|
|
/* |
|
=============== |
|
SubdivideFace |
|
|
|
Chop up faces that are larger than we want in the surface cache |
|
=============== |
|
*/ |
|
void SubdivideFace (face_t **pFaceList, face_t *f) |
|
{ |
|
float mins, maxs; |
|
vec_t v; |
|
vec_t luxelsPerWorldUnit; |
|
int axis, i; |
|
texinfo_t *tex; |
|
Vector temp; |
|
vec_t dist; |
|
winding_t *w, *frontw, *backw; |
|
|
|
if ( f->merged || f->split[0] || f->split[1] ) |
|
return; |
|
|
|
// special (non-surface cached) faces don't need subdivision |
|
tex = &texinfo[f->texinfo]; |
|
|
|
if( tex->flags & SURF_NOLIGHT ) |
|
{ |
|
return; |
|
} |
|
|
|
for (axis = 0 ; axis < 2 ; axis++) |
|
{ |
|
while (1) |
|
{ |
|
mins = 999999; |
|
maxs = -999999; |
|
|
|
VECTOR_COPY (tex->lightmapVecsLuxelsPerWorldUnits[axis], temp); |
|
w = f->w; |
|
for (i=0 ; i<w->numpoints ; i++) |
|
{ |
|
v = DotProduct (w->p[i], temp); |
|
if (v < mins) |
|
mins = v; |
|
if (v > maxs) |
|
maxs = v; |
|
} |
|
#if 0 |
|
if (maxs - mins <= 0) |
|
Error ("zero extents"); |
|
#endif |
|
if (maxs - mins <= g_maxLightmapDimension) |
|
break; |
|
|
|
// split it |
|
c_subdivide++; |
|
|
|
luxelsPerWorldUnit = VectorNormalize (temp); |
|
|
|
dist = ( mins + g_maxLightmapDimension - 1 ) / luxelsPerWorldUnit; |
|
|
|
ClipWindingEpsilon (w, temp, dist, ON_EPSILON, &frontw, &backw); |
|
if (!frontw || !backw) |
|
Error ("SubdivideFace: didn't split the polygon"); |
|
|
|
f->split[0] = NewFaceFromFace (f); |
|
f->split[0]->w = frontw; |
|
f->split[0]->next = *pFaceList; |
|
*pFaceList = f->split[0]; |
|
|
|
f->split[1] = NewFaceFromFace (f); |
|
f->split[1]->w = backw; |
|
f->split[1]->next = *pFaceList; |
|
*pFaceList = f->split[1]; |
|
|
|
SubdivideFace (pFaceList, f->split[0]); |
|
SubdivideFace (pFaceList, f->split[1]); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
void SubdivideFaceList(face_t **pFaceList) |
|
{ |
|
face_t *f; |
|
|
|
for (f = *pFaceList ; f ; f=f->next) |
|
{ |
|
SubdivideFace (pFaceList, f); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Assigns the bottom material to the bottom face |
|
//----------------------------------------------------------------------------- |
|
static bool AssignBottomWaterMaterialToFace( face_t *f ) |
|
{ |
|
// NOTE: This happens *after* cubemap fixup occurs, so we need to get the |
|
// fixed-up bottom material for this |
|
texinfo_t *pTexInfo = &texinfo[f->texinfo]; |
|
dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); |
|
const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); |
|
|
|
char pBottomMatName[512]; |
|
if ( !GetValueFromPatchedMaterial( pMaterialName, "$bottommaterial", pBottomMatName, 512 ) ) |
|
{ |
|
if( !Q_stristr( pMaterialName, "nodraw" ) && !Q_stristr( pMaterialName, "toolsskip" ) ) |
|
{ |
|
Warning("error: material %s doesn't have a $bottommaterial\n", pMaterialName ); |
|
} |
|
return false; |
|
} |
|
|
|
//Assert( mapplanes[f->planenum].normal.z < 0 ); |
|
texinfo_t newTexInfo; |
|
newTexInfo.flags = pTexInfo->flags; |
|
int j, k; |
|
for (j=0 ; j<2 ; j++) |
|
{ |
|
for (k=0 ; k<4 ; k++) |
|
{ |
|
newTexInfo.textureVecsTexelsPerWorldUnits[j][k] = pTexInfo->textureVecsTexelsPerWorldUnits[j][k]; |
|
newTexInfo.lightmapVecsLuxelsPerWorldUnits[j][k] = pTexInfo->lightmapVecsLuxelsPerWorldUnits[j][k]; |
|
} |
|
} |
|
newTexInfo.texdata = FindOrCreateTexData( pBottomMatName ); |
|
f->texinfo = FindOrCreateTexInfo( newTexInfo ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
//=========================================================================== |
|
|
|
int c_nodefaces; |
|
|
|
static void SubdivideFaceBySubdivSize( face_t *f, float subdivsize ); |
|
void SubdivideFaceBySubdivSize( face_t *f ); |
|
|
|
/* |
|
============ |
|
FaceFromPortal |
|
|
|
============ |
|
*/ |
|
extern int FindOrCreateTexInfo( const texinfo_t &searchTexInfo ); |
|
|
|
face_t *FaceFromPortal (portal_t *p, int pside) |
|
{ |
|
face_t *f; |
|
side_t *side; |
|
int deltaContents; |
|
|
|
// portal does not bridge different visible contents |
|
side = p->side; |
|
if (!side) |
|
return NULL; |
|
|
|
// allocate a new face |
|
f = AllocFace(); |
|
|
|
// save the original "side" from the map brush -- portal->side |
|
// see FindPortalSide(...) |
|
f->originalface = side; |
|
|
|
// |
|
// save material info |
|
// |
|
f->texinfo = side->texinfo; |
|
f->dispinfo = -1; // all faces with displacement info are created elsewhere |
|
f->smoothingGroups = side->smoothingGroups; |
|
|
|
// save plane info |
|
f->planenum = (side->planenum & ~1) | pside; |
|
if ( entity_num != 0 ) |
|
{ |
|
// the brush model renderer doesn't use PLANEBACK, so write the real plane |
|
// inside water faces can be flipped because they are generated on the inside of the brush |
|
if ( p->nodes[pside]->contents & (CONTENTS_WATER|CONTENTS_SLIME) ) |
|
{ |
|
f->planenum = (side->planenum & ~1) | pside; |
|
} |
|
else |
|
{ |
|
f->planenum = side->planenum; |
|
} |
|
} |
|
|
|
// save portal info |
|
f->portal = p; |
|
f->fogVolumeLeaf = NULL; |
|
|
|
deltaContents = VisibleContents(p->nodes[!pside]->contents^p->nodes[pside]->contents); |
|
|
|
// don't show insides of windows or grates |
|
if ( ((p->nodes[pside]->contents & CONTENTS_WINDOW) && deltaContents == CONTENTS_WINDOW) || |
|
((p->nodes[pside]->contents & CONTENTS_GRATE) && deltaContents == CONTENTS_GRATE) ) |
|
{ |
|
FreeFace( f ); |
|
return NULL; |
|
} |
|
|
|
if ( p->nodes[pside]->contents & MASK_WATER ) |
|
{ |
|
f->fogVolumeLeaf = p->nodes[pside]; |
|
} |
|
else if ( p->nodes[!pside]->contents & MASK_WATER ) |
|
{ |
|
f->fogVolumeLeaf = p->nodes[!pside]; |
|
} |
|
|
|
// If it's the underside of water, we need to figure out what material to use, etc. |
|
if( ( p->nodes[pside]->contents & CONTENTS_WATER ) && deltaContents == CONTENTS_WATER ) |
|
{ |
|
if ( !AssignBottomWaterMaterialToFace( f ) ) |
|
{ |
|
FreeFace( f ); |
|
return NULL; |
|
} |
|
} |
|
|
|
// |
|
// generate the winding for the face and save face contents |
|
// |
|
if( pside ) |
|
{ |
|
f->w = ReverseWinding(p->winding); |
|
f->contents = p->nodes[1]->contents; |
|
} |
|
else |
|
{ |
|
f->w = CopyWinding(p->winding); |
|
f->contents = p->nodes[0]->contents; |
|
} |
|
|
|
f->numPrims = 0; |
|
f->firstPrimID = 0; |
|
|
|
// return the created face |
|
return f; |
|
} |
|
|
|
/* |
|
=============== |
|
MakeFaces_r |
|
|
|
If a portal will make a visible face, |
|
mark the side that originally created it |
|
|
|
solid / empty : solid |
|
solid / water : solid |
|
water / empty : water |
|
water / water : none |
|
=============== |
|
*/ |
|
void MakeFaces_r (node_t *node) |
|
{ |
|
portal_t *p; |
|
int s; |
|
|
|
// recurse down to leafs |
|
if (node->planenum != PLANENUM_LEAF) |
|
{ |
|
MakeFaces_r (node->children[0]); |
|
MakeFaces_r (node->children[1]); |
|
|
|
// merge together all visible faces on the node |
|
if (!nomerge) |
|
MergeFaceList(&node->faces); |
|
if (!nosubdiv) |
|
SubdivideFaceList(&node->faces); |
|
|
|
return; |
|
} |
|
|
|
// solid leafs never have visible faces |
|
if (node->contents & CONTENTS_SOLID) |
|
return; |
|
|
|
// see which portals are valid |
|
for (p=node->portals ; p ; p = p->next[s]) |
|
{ |
|
s = (p->nodes[1] == node); |
|
|
|
p->face[s] = FaceFromPortal (p, s); |
|
if (p->face[s]) |
|
{ |
|
c_nodefaces++; |
|
p->face[s]->next = p->onnode->faces; |
|
p->onnode->faces = p->face[s]; |
|
} |
|
} |
|
} |
|
|
|
typedef winding_t *pwinding_t; |
|
|
|
static void PrintWinding( winding_t *w ) |
|
{ |
|
int i; |
|
Msg( "\t---\n" ); |
|
for( i = 0; i < w->numpoints; i++ ) |
|
{ |
|
Msg( "\t%f %f %f\n", w->p[i].x, w->p[i].y, w->p[i].z ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Adds a winding to the current list of primverts |
|
// Input : *w - the winding |
|
// *pIndices - The output indices |
|
// vertStart - the starting vert index |
|
// vertCount - current count |
|
// Output : int - output count including new verts from this winding |
|
//----------------------------------------------------------------------------- |
|
int AddWindingToPrimverts( const winding_t *w, unsigned short *pIndices, int vertStart, int vertCount ) |
|
{ |
|
for( int i = 0; i < w->numpoints; i++ ) |
|
{ |
|
int j; |
|
for( j = vertStart; j < vertStart + vertCount; j++ ) |
|
{ |
|
Vector tmp = g_primverts[j].pos - w->p[i]; |
|
|
|
if( tmp.LengthSqr() < POINT_EPSILON*POINT_EPSILON ) |
|
{ |
|
pIndices[i] = j; |
|
break; |
|
} |
|
} |
|
if ( j >= vertStart + vertCount ) |
|
{ |
|
pIndices[i] = j; |
|
g_primverts[j].pos = w->p[i]; |
|
vertCount++; |
|
g_numprimverts++; |
|
if ( g_numprimverts > MAX_MAP_PRIMVERTS ) |
|
{ |
|
Error( "Exceeded max water verts.\nIncrease surface subdivision size or lower your subdivision size in vmt files! (%d>%d)\n", |
|
( int )g_numprimverts, ( int )MAX_MAP_PRIMVERTS ); |
|
} |
|
} |
|
} |
|
|
|
return vertCount; |
|
} |
|
|
|
|
|
|
|
#pragma optimize( "g", off ) |
|
#define USE_TRISTRIPS |
|
|
|
// UNDONE: Should split this function into subdivide and primitive building parts |
|
// UNDONE: We should try building strips of shared verts for all water faces in a leaf |
|
// since those will be drawn concurrently anyway. It should be more efficient. |
|
static void SubdivideFaceBySubdivSize( face_t *f, float subdivsize ) |
|
{ |
|
// garymcthack - REFACTOR ME!!! |
|
|
|
vec_t dummy; |
|
Vector hackNormal; |
|
WindingPlane( f->w, hackNormal, &dummy ); |
|
|
|
// HACK - only subdivide stuff that is facing up or down (for water) |
|
if( fabs(hackNormal[2]) < .9f ) |
|
{ |
|
return; |
|
} |
|
|
|
// Get the extents of the surface. |
|
// garymcthack - this assumes a surface of constant z for now (for water). . can generalize later. |
|
subdivsize = ( int )subdivsize; |
|
winding_t *w; |
|
w = CopyWinding( f->w ); |
|
|
|
Vector min, max; |
|
WindingBounds( w, min, max ); |
|
|
|
#if 0 |
|
Msg( "START WINDING: \n" ); |
|
PrintWinding( w ); |
|
#endif |
|
int xStart, yStart, xEnd, yEnd, xSteps, ySteps; |
|
xStart = ( int )subdivsize * ( int )( ( min[0] - subdivsize ) / subdivsize ); |
|
xEnd = ( int )subdivsize * ( int )( ( max[0] + subdivsize ) / subdivsize ); |
|
yStart = ( int )subdivsize * ( int )( ( min[1] - subdivsize ) / subdivsize ); |
|
yEnd = ( int )subdivsize * ( int )( ( max[1] + subdivsize ) / subdivsize ); |
|
xSteps = ( xEnd - xStart ) / subdivsize; |
|
ySteps = ( yEnd - yStart ) / subdivsize; |
|
int x, y; |
|
int xi, yi; |
|
winding_t **windings = ( winding_t ** )new pwinding_t[xSteps * ySteps]; |
|
memset( windings, 0, sizeof( winding_t * ) * xSteps * ySteps ); |
|
|
|
for( yi = 0, y = yStart; y < yEnd; y += ( int )subdivsize, yi++ ) |
|
{ |
|
for( xi = 0, x = xStart; x < xEnd; x += ( int )subdivsize, xi++ ) |
|
{ |
|
winding_t *tempWinding, *frontWinding, *backWinding; |
|
float planeDist; |
|
Vector normal; |
|
normal.Init( 1.0f, 0.0f, 0.0f ); |
|
planeDist = ( float )x; |
|
tempWinding = CopyWinding( w ); |
|
ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, |
|
&frontWinding, &backWinding ); |
|
if( tempWinding ) |
|
{ |
|
FreeWinding( tempWinding ); |
|
} |
|
if( backWinding ) |
|
{ |
|
FreeWinding( backWinding ); |
|
} |
|
if( !frontWinding ) |
|
{ |
|
continue; |
|
} |
|
tempWinding = frontWinding; |
|
|
|
normal.Init( -1.0f, 0.0f, 0.0f ); |
|
planeDist = -( float )( x + subdivsize ); |
|
ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, |
|
&frontWinding, &backWinding ); |
|
if( tempWinding ) |
|
{ |
|
FreeWinding( tempWinding ); |
|
} |
|
if( backWinding ) |
|
{ |
|
FreeWinding( backWinding ); |
|
} |
|
if( !frontWinding ) |
|
{ |
|
continue; |
|
} |
|
tempWinding = frontWinding; |
|
|
|
normal.Init( 0.0f, 1.0f, 0.0f ); |
|
planeDist = ( float )y; |
|
ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, |
|
&frontWinding, &backWinding ); |
|
if( tempWinding ) |
|
{ |
|
FreeWinding( tempWinding ); |
|
} |
|
if( backWinding ) |
|
{ |
|
FreeWinding( backWinding ); |
|
} |
|
if( !frontWinding ) |
|
{ |
|
continue; |
|
} |
|
tempWinding = frontWinding; |
|
|
|
normal.Init( 0.0f, -1.0f, 0.0f ); |
|
planeDist = -( float )( y + subdivsize ); |
|
ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, |
|
&frontWinding, &backWinding ); |
|
if( tempWinding ) |
|
{ |
|
FreeWinding( tempWinding ); |
|
} |
|
if( backWinding ) |
|
{ |
|
FreeWinding( backWinding ); |
|
} |
|
if( !frontWinding ) |
|
{ |
|
continue; |
|
} |
|
|
|
#if 0 |
|
Msg( "output winding:\n" ); |
|
PrintWinding( frontWinding ); |
|
#endif |
|
|
|
if( frontWinding ) |
|
{ |
|
windings[xi + yi * xSteps] = frontWinding; |
|
} |
|
} |
|
} |
|
FreeWinding( w ); |
|
dprimitive_t &newPrim = g_primitives[g_numprimitives]; |
|
f->firstPrimID = g_numprimitives; |
|
f->numPrims = 1; |
|
newPrim.firstIndex = g_numprimindices; |
|
newPrim.firstVert = g_numprimverts; |
|
newPrim.indexCount = 0; |
|
newPrim.vertCount = 0; |
|
#ifdef USE_TRISTRIPS |
|
newPrim.type = PRIM_TRISTRIP; |
|
#else |
|
newPrim.type = PRIM_TRILIST; |
|
#endif |
|
|
|
CUtlVector<WORD> triListIndices; |
|
int i; |
|
for( i = 0; i < xSteps * ySteps; i++ ) |
|
{ |
|
if( !windings[i] ) |
|
{ |
|
continue; |
|
} |
|
unsigned short *pIndices = |
|
( unsigned short * )_alloca( windings[i]->numpoints * sizeof( unsigned short ) ); |
|
// find indices for the verts. |
|
newPrim.vertCount = AddWindingToPrimverts( windings[i], pIndices, newPrim.firstVert, newPrim.vertCount ); |
|
|
|
// Now that we have indices for the verts, fan-tesselate the polygon and spit out tris. |
|
for( int j = 0; j < windings[i]->numpoints - 2; j++ ) |
|
{ |
|
triListIndices.AddToTail( pIndices[0] ); |
|
triListIndices.AddToTail( pIndices[j+1] ); |
|
triListIndices.AddToTail( pIndices[j+2] ); |
|
} |
|
} |
|
|
|
delete [] windings; |
|
// We've already updated the verts and have a trilist. . let's strip it! |
|
if( !triListIndices.Size() ) |
|
{ |
|
return; |
|
} |
|
|
|
#ifdef USE_TRISTRIPS |
|
int numTristripIndices; |
|
WORD *pStripIndices = NULL; |
|
Stripify( triListIndices.Size() / 3, triListIndices.Base(), &numTristripIndices, |
|
&pStripIndices ); |
|
Assert( pStripIndices ); |
|
|
|
// FIXME: Should also call ComputeVertexPermutation and reorder the verts. |
|
|
|
for( i = 0; i < numTristripIndices; i++ ) |
|
{ |
|
Assert( pStripIndices[i] >= newPrim.firstVert && |
|
pStripIndices[i] < newPrim.firstVert + newPrim.vertCount ); |
|
g_primindices[newPrim.firstIndex + newPrim.indexCount] = pStripIndices[i]; |
|
newPrim.indexCount++; |
|
g_numprimindices++; |
|
if( g_numprimindices > MAX_MAP_PRIMINDICES ) |
|
{ |
|
Error( "Exceeded max water indicies.\nIncrease surface subdivision size! (%d>%d)\n", g_numprimindices, MAX_MAP_PRIMINDICES ); |
|
} |
|
} |
|
delete [] pStripIndices; |
|
#else |
|
for( i = 0; i < triListIndices.Size(); i++ ) |
|
{ |
|
g_primindices[newPrim.firstIndex + newPrim.indexCount] = triListIndices[i]; |
|
newPrim.indexCount++; |
|
g_numprimindices++; |
|
if( g_numprimindices > MAX_MAP_PRIMINDICES ) |
|
{ |
|
Error( "Exceeded max water indicies.\nIncrease surface subdivision size! (%d>%d)\n", g_numprimindices, MAX_MAP_PRIMINDICES ); |
|
} |
|
} |
|
#endif |
|
g_numprimitives++; // don't increment until we get here and are sure that we have a primitive. |
|
if( g_numprimitives > MAX_MAP_PRIMITIVES ) |
|
{ |
|
Error( "Exceeded max water primitives.\nIncrease surface subdivision size! (%d>%d)\n", ( int )g_numprimitives, ( int )MAX_MAP_PRIMITIVES ); |
|
} |
|
} |
|
|
|
void SubdivideFaceBySubdivSize( face_t *f ) |
|
{ |
|
if( f->numpoints == 0 || f->split[0] || f->split[1] || f->merged || !f->w ) |
|
{ |
|
return; |
|
} |
|
// see if the face needs to be subdivided. |
|
texinfo_t *pTexInfo = &texinfo[f->texinfo]; |
|
dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); |
|
bool bFound; |
|
const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); |
|
MaterialSystemMaterial_t matID = |
|
FindOriginalMaterial( pMaterialName, &bFound, false ); |
|
|
|
if( !bFound ) |
|
{ |
|
return; |
|
} |
|
const char *subdivsizeString = GetMaterialVar( matID, "$subdivsize" ); |
|
if( subdivsizeString ) |
|
{ |
|
float subdivSize = atof( subdivsizeString ); |
|
if( subdivSize > 0.0f ) |
|
{ |
|
// NOTE: Subdivision is unsupported and should be phased out |
|
Warning("Using subdivision on %s\n", pMaterialName ); |
|
SubdivideFaceBySubdivSize( f, subdivSize ); |
|
} |
|
} |
|
} |
|
|
|
void SplitSubdividedFaces_Node_r( node_t *node ) |
|
{ |
|
if (node->planenum == PLANENUM_LEAF) |
|
{ |
|
return; |
|
} |
|
face_t *f; |
|
for( f = node->faces; f ;f = f->next ) |
|
{ |
|
SubdivideFaceBySubdivSize( f ); |
|
} |
|
|
|
// |
|
// recursively output the other nodes |
|
// |
|
SplitSubdividedFaces_Node_r( node->children[0] ); |
|
SplitSubdividedFaces_Node_r( node->children[1] ); |
|
} |
|
|
|
void SplitSubdividedFaces( face_t *pLeafFaceList, node_t *headnode ) |
|
{ |
|
// deal with leaf faces. |
|
face_t *f = pLeafFaceList; |
|
while ( f ) |
|
{ |
|
SubdivideFaceBySubdivSize( f ); |
|
f = f->next; |
|
} |
|
|
|
// deal with node faces. |
|
SplitSubdividedFaces_Node_r( headnode ); |
|
} |
|
|
|
#pragma optimize( "", on ) |
|
|
|
/* |
|
============ |
|
MakeFaces |
|
============ |
|
*/ |
|
void MakeFaces (node_t *node) |
|
{ |
|
qprintf ("--- MakeFaces ---\n"); |
|
c_merge = 0; |
|
c_subdivide = 0; |
|
c_nodefaces = 0; |
|
|
|
MakeFaces_r (node); |
|
|
|
qprintf ("%5i makefaces\n", c_nodefaces); |
|
qprintf ("%5i merged\n", c_merge); |
|
qprintf ("%5i subdivided\n", c_subdivide); |
|
} |