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);
|
|
}
|