gtkradiant/contrib/gtkgensurf/dec.cpp

1328 lines
38 KiB
C++

/*
GenSurf plugin for GtkRadiant
Copyright (C) 2001 David Hyde, Loki software and qeradiant.com
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#define SINGLE
#ifdef SINGLE
#define REAL float
#else /* not SINGLE */
#define REAL double
#endif /* not SINGLE */
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "gensurf.h"
#include "triangle.h"
typedef struct
{
float error;
int node;
} TRITABLE;
double dh, dv;
int NVP1;
#define Absolute(a) ((a) >= 0.0 ? (a) : -(a))
void MakeDecimatedMap(int *NumNodes, int *NumTris, NODE **pNode, TRI **pTri)
{
int compare(TRITABLE *, TRITABLE *);
int Bisect(NODE *, int, int, int);
void CalcAngles(NODE *, int *, float *);
void EdgeOnSide(int *, int *, int *);
int tricall(int, NODE *, int *, TRI **, TRI **, const char *);
int CheckBorders(int *,int,NODE *,int *,TRI **);
float biggesterror;
int i, j, N;
int j0, j1, j2;
int NumNodesToSave;
int NumNodesUsed;
NODE *Node;
TRI *Tri;
TRITABLE *TriTable;
if(Decimate <= 0) return;
/*
ghCursorCurrent = LoadCursor(NULL,IDC_WAIT);
SetCursor(ghCursorCurrent);
*/
dh = (Hur-Hll)/NH;
dv = (Vur-Vll)/NV;
NVP1 = NV+1;
NumNodes[0] = (NH+1)*(NVP1);
*pNode = (NODE *) malloc(NumNodes[0] * sizeof(NODE));
Node = *pNode;
memset(Node,0,NumNodes[0]*sizeof(NODE));
// Copy [NH][NV] vertex array to our working node array
for(i=0,N=0; i<=NH; i++)
{
for(j=0; j<=NV; j++, N++)
{
Node[N].p[0] = (float)xyz[i][j].p[0];
Node[N].p[1] = (float)xyz[i][j].p[1];
Node[N].p[2] = (float)xyz[i][j].p[2];
Node[N].fixed = xyz[i][j].fixed;
}
}
// Start things off with the corner values
Node[ 0].used = 1;
Node[NV].used = 1;
Node[NH*NVP1].used = 1;
Node[NH*NVP1+NV].used = 1;
NumNodesUsed = 4;
tricall(NumNodes[0], Node, NumTris, NULL, pTri, "cnzBNPY");
Tri = *pTri;
// Which coordinates are we triangulating on?
switch(Plane)
{
case PLANE_XZ0:
case PLANE_XZ1:
j0 = 1;
j1 = 0;
j2 = 2;
break;
case PLANE_YZ0:
case PLANE_YZ1:
j0 = 0;
j1 = 1;
j2 = 2;
break;
default:
j0 = 2;
j1 = 0;
j2 = 1;
}
// TriTable stores the largest error in a triangle and the node where that
// error occurs
TriTable = (TRITABLE *) malloc(NH*NV*2 * sizeof(TRITABLE));
NumNodesToSave = min(NumNodes[0], (int)(0.01*(100-Decimate)*(NumNodes[0]-NumNodesUsed)+NumNodesUsed));
while(NumNodesUsed < NumNodesToSave)
{
for(i=0; i<NumTris[0]; i++)
Tri[i].flag = 0;
// For every node that's not currently used, find what triangle it
// lies on, and the error at this node
for(i=0, biggesterror=0; i<NumNodes[0]; i++)
{
if(Node[i].used) continue;
for(j=0, Node[i].tri=-1; (j<NumTris[0]) && (Node[i].tri==-1); j++)
{
if( side(Node[i].p[j1], Node[i].p[j2],
Node[Tri[j].v[0]].p[j1],Node[Tri[j].v[0]].p[j2],
Node[Tri[j].v[1]].p[j1],Node[Tri[j].v[1]].p[j2]) < 0. ) continue;
if( side(Node[i].p[j1], Node[i].p[j2],
Node[Tri[j].v[1]].p[j1],Node[Tri[j].v[1]].p[j2],
Node[Tri[j].v[2]].p[j1],Node[Tri[j].v[2]].p[j2]) < 0. ) continue;
if( side(Node[i].p[j1], Node[i].p[j2],
Node[Tri[j].v[2]].p[j1],Node[Tri[j].v[2]].p[j2],
Node[Tri[j].v[0]].p[j1],Node[Tri[j].v[0]].p[j2]) < 0. ) continue;
Node[i].tri = j;
}
if(Node[i].tri < 0)
{
/*
ghCursorCurrent = ghCursorDefault;
SetCursor(ghCursorCurrent);
*/
g_FuncTable.m_pfnMessageBox(g_pRadiantWnd,
"Error: Couldn't find the triangle bounding a point.",
"Decimation Error",MB_ICONEXCLAMATION, NULL);
return;
}
if(!Tri[Node[i].tri].flag)
{
PlaneFromPoints(Node[Tri[Node[i].tri].v[0]].p,
Node[Tri[Node[i].tri].v[1]].p,
Node[Tri[Node[i].tri].v[2]].p,
&Tri[Node[i].tri].plane);
Tri[Node[i].tri].flag = 1;
}
Node[i].error =
Node[i].p[j0] - (Tri[Node[i].tri].plane.dist -
Tri[Node[i].tri].plane.normal[j1]*Node[i].p[j1] -
Tri[Node[i].tri].plane.normal[j2]*Node[i].p[j2] )/
Tri[Node[i].tri].plane.normal[j0];
biggesterror = max(biggesterror,Absolute(Node[i].error));
}
if(biggesterror == 0)
NumNodesToSave = NumNodesUsed;
else
{
// For all current triangles, build a list of worst-case nodes
memset(TriTable,0,NH*NV*2*sizeof(TRITABLE));
for(i=0; i<NumNodes[0]; i++)
{
if(Node[i].used) continue;
if(Absolute(Node[i].error) > TriTable[Node[i].tri].error)
{
TriTable[Node[i].tri].error = (float)(Absolute(Node[i].error));
TriTable[Node[i].tri].node = i;
}
}
qsort( (void *)TriTable, (size_t)(NumTris[0]), sizeof(TRITABLE), (int (*)(const void *, const void *))compare );
for(i=0; i<NumTris[0] && NumNodesUsed < NumNodesToSave && TriTable[i].error > 0.5*biggesterror; i++)
{
if(Node[TriTable[i].node].used) continue; // shouldn't happen
NumNodesUsed++;
Node[TriTable[i].node].used++;
}
free(Tri);
tricall(NumNodes[0], Node, NumTris, NULL, pTri, "cnzBNPY");
Tri = *pTri;
// Sliver-check along borders. Since borders are often linear, the errors
// along borders will often be zero, so no new points will be added. This
// tends to produce long, thin brushes. For all border triangles, check
// that minimum angle isn't less than SLIVER_ANGLE. If it is, add another
// vertex.
while(CheckBorders(&NumNodesUsed,NumNodes[0],Node,NumTris,pTri) > 0)
{
}
Tri = *pTri;
}
}
free(TriTable);
// One last time (because we're pessimistic), check border triangles
// CheckBorders(&NumNodesUsed,NumNodes[0],Node,NumTris,pTri);
// Tri = *pTri;
// Check that all fixed points are exact. If not, add them to the mix.
// First check to see if we have any fixed points that aren't already used.
for(i=0, N=0; i<NumNodes[0] && !N; i++)
{
if(Node[i].used) continue;
if(Node[i].fixed) N++;
}
if(N)
{
// Zero out the flag member of all triangles, indicating that
// the plane equation has not been found.
for(i=0; i<NumTris[0]; i++)
Tri[i].flag = 0;
for(i=0; i<NumNodes[0]; i++)
{
if(Node[i].used) continue;
if(!Node[i].fixed) continue;
Node[i].tri = -1;
for(j=0; j<NumTris[0] && Node[i].tri==-1; j++)
{
if( side(Node[i].p[j1], Node[i].p[j2],
Node[Tri[j].v[0]].p[j1],Node[Tri[j].v[0]].p[j2],
Node[Tri[j].v[1]].p[j1],Node[Tri[j].v[1]].p[j2]) < 0. ) continue;
if( side(Node[i].p[j1], Node[i].p[j2],
Node[Tri[j].v[1]].p[j1],Node[Tri[j].v[1]].p[j2],
Node[Tri[j].v[2]].p[j1],Node[Tri[j].v[2]].p[j2]) < 0. ) continue;
if( side(Node[i].p[j1], Node[i].p[j2],
Node[Tri[j].v[2]].p[j1],Node[Tri[j].v[2]].p[j2],
Node[Tri[j].v[0]].p[j1],Node[Tri[j].v[0]].p[j2]) < 0. ) continue;
Node[i].tri = j;
}
if(Node[i].tri < 0)
{
/*
ghCursorCurrent = ghCursorDefault;
SetCursor(ghCursorCurrent);
*/
g_FuncTable.m_pfnMessageBox(g_pRadiantWnd,
"Error: Couldn't find the triangle bounding a point.",
"Decimation Error",MB_ICONEXCLAMATION, NULL);
return;
}
if(!Tri[Node[i].tri].flag)
{
PlaneFromPoints(Node[Tri[Node[i].tri].v[0]].p,
Node[Tri[Node[i].tri].v[1]].p,
Node[Tri[Node[i].tri].v[2]].p,
&Tri[Node[i].tri].plane);
Tri[Node[i].tri].flag = 1;
}
Node[i].error =
Node[i].p[j0] - (Tri[Node[i].tri].plane.dist -
Tri[Node[i].tri].plane.normal[j1]*Node[i].p[j1] -
Tri[Node[i].tri].plane.normal[j2]*Node[i].p[j2] )/
Tri[Node[i].tri].plane.normal[j0];
if(Absolute(Node[i].error) > 0.5)
{
NumNodesUsed++;
Node[i].used++;
free(Tri);
tricall(NumNodes[0], Node, NumTris, NULL, pTri, "cnzBNPY");
Tri = *pTri;
}
}
}
// Swap node orders for surfaces facing down, north or west so that
// they are counterclockwise when facing the surface
if((Plane == PLANE_XY1) || (Plane == PLANE_XZ0) || (Plane == PLANE_YZ1) )
{
for(i=0; i<NumTris[0]; i++)
{
j = Tri[i].v[1];
Tri[i].v[1] = Tri[i].v[2];
Tri[i].v[2] = j;
}
}
// Store bounding box coords
for(i=0; i<NumTris[0]; i++)
{
Tri[i].min[0] = Node[Tri[i].v[0]].p[0];
Tri[i].min[0] = min(Tri[i].min[0],Node[Tri[i].v[1]].p[0]);
Tri[i].min[0] = min(Tri[i].min[0],Node[Tri[i].v[2]].p[0]);
Tri[i].min[1] = Node[Tri[i].v[0]].p[1];
Tri[i].min[1] = min(Tri[i].min[1],Node[Tri[i].v[1]].p[1]);
Tri[i].min[1] = min(Tri[i].min[1],Node[Tri[i].v[2]].p[1]);
Tri[i].min[2] = Node[Tri[i].v[0]].p[2];
Tri[i].min[2] = min(Tri[i].min[2],Node[Tri[i].v[1]].p[2]);
Tri[i].min[2] = min(Tri[i].min[2],Node[Tri[i].v[2]].p[2]);
Tri[i].max[0] = Node[Tri[i].v[0]].p[0];
Tri[i].max[0] = max(Tri[i].max[0],Node[Tri[i].v[1]].p[0]);
Tri[i].max[0] = max(Tri[i].max[0],Node[Tri[i].v[2]].p[0]);
Tri[i].max[1] = Node[Tri[i].v[0]].p[1];
Tri[i].max[1] = max(Tri[i].max[1],Node[Tri[i].v[1]].p[1]);
Tri[i].max[1] = max(Tri[i].max[1],Node[Tri[i].v[2]].p[1]);
Tri[i].max[2] = Node[Tri[i].v[0]].p[2];
Tri[i].max[2] = max(Tri[i].max[2],Node[Tri[i].v[1]].p[2]);
Tri[i].max[2] = max(Tri[i].max[2],Node[Tri[i].v[2]].p[2]);
}
/*
ghCursorCurrent = ghCursorDefault;
SetCursor(ghCursorCurrent);
*/
}
/* end MakeDecimatedMap */
/*****************************************************************************/
/* */
/* tricall Takes an array of nodes, spits out an array of triangles */
/* */
/*****************************************************************************/
int tricall(int NumNodes, NODE *Node, int *NumTris, TRI **inTri, TRI **Tri, const char *Options)
{
struct triangulateio in, out;
int i, N;
int NumUsedNodes;
int *NodeTable;
TRI *ptri;
/* Define input points. */
for(i=0,NumUsedNodes=0; i<NumNodes; i++)
if(Node[i].used) NumUsedNodes++;
memset(&in, 0,sizeof(in));
memset(&out,0,sizeof(out));
NodeTable = (int *) malloc(NumUsedNodes * sizeof(int));
in.numberofpoints = NumUsedNodes;
in.numberofpointattributes = 0;
in.pointlist = (REAL *) malloc(in.numberofpoints * 2 * sizeof(REAL));
for(i=0,N=0; i<NumNodes; i++)
{
if(Node[i].used)
{
switch(Plane)
{
case PLANE_XZ0:
case PLANE_XZ1:
in.pointlist[N*2 ] = Node[i].p[0];
in.pointlist[N*2+1] = Node[i].p[2];
break;
case PLANE_YZ0:
case PLANE_YZ1:
in.pointlist[N*2 ] = Node[i].p[1];
in.pointlist[N*2+1] = Node[i].p[2];
break;
default:
in.pointlist[N*2 ] = Node[i].p[0];
in.pointlist[N*2+1] = Node[i].p[1];
}
NodeTable[N] = i;
N++;
}
}
in.pointattributelist = (REAL *) NULL;
in.pointmarkerlist = (int *) NULL;
if(strstr(Options,"r"))
{
int *TriTable;
TriTable = (int *) malloc(NumNodes * sizeof(int));
for(i=0,N=0; i<NumNodes; i++)
{
if(Node[i].used)
{
TriTable[i] = N;
N++;
}
}
in.numberoftriangles = NumTris[0];
in.numberofcorners = 3;
in.numberoftriangleattributes = 0;
in.trianglelist = (int *) malloc(in.numberofcorners * in.numberoftriangles * sizeof(int));
in.triangleattributelist = (REAL *) NULL;
in.trianglearealist = (REAL *) NULL;
ptri = *inTri;
for(i=0; i<in.numberoftriangles; i++)
{
in.trianglelist[i*in.numberofcorners ] = TriTable[ptri[i].v[0]];
in.trianglelist[i*in.numberofcorners+1] = TriTable[ptri[i].v[1]];
in.trianglelist[i*in.numberofcorners+2] = TriTable[ptri[i].v[2]];
}
free(TriTable);
}
else
{
in.numberoftriangles = 0;
in.numberofcorners = 3;
in.numberoftriangleattributes = 0;
in.trianglelist = (int *) NULL;
in.triangleattributelist = (REAL *) NULL;
in.trianglearealist = (REAL *) NULL;
}
in.numberofsegments = 0;
in.segmentlist = (int *) NULL;
in.segmentmarkerlist = (int *) NULL;
in.numberofholes = 0;
in.holelist = (REAL *) NULL;
in.numberofregions = 0;
in.regionlist = (REAL *) NULL;
in.numberofedges = 0;
in.edgelist = (int *) NULL;
in.edgemarkerlist = (int *) NULL;
in.normlist = (REAL *) NULL;
/* Make necessary initializations */
out.pointlist = (REAL *) NULL; /* Not needed if -N switch used. */
out.pointattributelist = (REAL *) NULL; /* Not needed if -N switch used or
number of point attributes is zero: */
out.pointmarkerlist = (int *) NULL; /* Not needed if -N or -B switch used. */
out.trianglelist = (int *) NULL; /* Not needed if -E switch used. */
out.triangleattributelist = (REAL *) NULL; /* Not needed if -E switch used or
number of triangle attributes is
zero: */
out.trianglearealist = (REAL *) NULL;
out.neighborlist = (int *) NULL; /* Needed only if -n switch used. */
out.segmentlist = (int *) NULL; /* Needed only if segments are output
(-p or -c) and -P not used: */
out.segmentmarkerlist = (int *) NULL; /* Needed only if segments are output
(-p or -c) and -P and -B not used: */
out.edgelist = (int *) NULL; /* Needed only if -e switch used. */
out.edgemarkerlist = (int *) NULL; /* Needed if -e used and -B not used. */
triangulate(Options, &in, &out, NULL);
NumTris[0] = out.numberoftriangles;
*Tri = (TRI *) malloc(NumTris[0] * sizeof(TRI));
ptri = *Tri;
for(i=0; i<NumTris[0]; i++)
{
ptri[i].v[0] = NodeTable[out.trianglelist[i*out.numberofcorners ]];
ptri[i].v[1] = NodeTable[out.trianglelist[i*out.numberofcorners+1]];
ptri[i].v[2] = NodeTable[out.trianglelist[i*out.numberofcorners+2]];
ptri[i].n[0] = out.neighborlist[i*3 ];
ptri[i].n[1] = out.neighborlist[i*3+1];
ptri[i].n[2] = out.neighborlist[i*3+2];
}
/* Free all allocated arrays, including those allocated by Triangle. */
if(in.pointlist) free(in.pointlist);
if(in.pointattributelist) free(in.pointattributelist);
if(in.pointmarkerlist) free(in.pointmarkerlist);
if(in.trianglelist) free(in.trianglelist);
if(in.triangleattributelist) free(in.triangleattributelist);
if(in.trianglearealist) free(in.trianglearealist);
if(in.neighborlist) free(in.neighborlist);
if(in.segmentlist) free(in.segmentlist);
if(in.segmentmarkerlist) free(in.segmentmarkerlist);
if(in.holelist) free(in.holelist);
if(in.regionlist) free(in.regionlist);
if(in.edgelist) free(in.edgelist);
if(in.edgemarkerlist) free(in.edgemarkerlist);
if(in.normlist) free(in.normlist);
if(out.pointlist) free(out.pointlist);
if(out.pointattributelist) free(out.pointattributelist);
if(out.pointmarkerlist) free(out.pointmarkerlist);
if(out.trianglelist) free(out.trianglelist);
if(out.triangleattributelist) free(out.triangleattributelist);
if(out.trianglearealist) free(out.trianglearealist);
if(out.neighborlist) free(out.neighborlist);
if(out.segmentlist) free(out.segmentlist);
if(out.segmentmarkerlist) free(out.segmentmarkerlist);
if(out.holelist) free(out.holelist);
if(out.regionlist) free(out.regionlist);
if(out.edgelist) free(out.edgelist);
if(out.edgemarkerlist) free(out.edgemarkerlist);
if(out.normlist) free(out.normlist);
free(NodeTable);
return 0;
}
void EdgeOnSide(int *v, int *edge, int *border)
{
int R;
int k0, k1, N;
float Ndv;
border[0] = -1;
if( (v[0] <= NV) && (v[1] <= NV) )
{
edge[0] = 0;
border[0] = 0;
}
if( (v[1] <= NV) && (v[2] <= NV) )
{
edge[0] = 1;
border[0] = 0;
}
if( (v[2] <= NV) && (v[0] <= NV) )
{
edge[0] = 2;
border[0] = 0;
}
R = NH*NVP1;
if( (v[0] >= R) && (v[1] >= R) )
{
edge[0] = 0;
border[0] = 1;
}
if( (v[1] >= R) && (v[2] >= R) )
{
edge[0] = 1;
border[0] = 1;
}
if( (v[2] >= R) && (v[0] >= R) )
{
edge[0] = 2;
border[0] = 1;
}
if(border[0] >= 0)
{
k0 = edge[0];
k1 = (k0+1) % 3;
N = Absolute(v[k0] - v[k1]);
Ndv = (float)(N*dv);
}
if( ((v[0] % NVP1) == 0) && ((v[1] % NVP1) == 0) )
{
if(border[0] >= 0)
if( Ndv > (Absolute(v[0] - v[1])*dh)) return;
edge[0] = 0;
border[0] = 2;
return;
}
if( ((v[1] % NVP1) == 0) && ((v[2] % NVP1) == 0) )
{
if(border[0] >= 0)
if( Ndv > (Absolute(v[1] - v[2])*dh)) return;
edge[0] = 1;
border[0] = 2;
return;
}
if( ((v[2] % NVP1) == 0) && ((v[0] % NVP1) == 0) )
{
if(border[0] >= 0)
if( Ndv > (Absolute(v[2] - v[0])*dh)) return;
edge[0] = 2;
border[0] = 2;
return;
}
if( ((v[0] % NVP1) == NV) && ((v[1] % NVP1) == NV) )
{
if(border[0] >= 0)
if( Ndv > (Absolute(v[0] - v[1])*dh)) return;
edge[0] = 0;
border[0] = 3;
return;
}
if( ((v[1] % NVP1) == NV) && ((v[2] % NVP1) == NV) )
{
if(border[0] >= 0)
if( Ndv > (Absolute(v[1] - v[2])*dh)) return;
edge[0] = 1;
border[0] = 3;
return;
}
if( ((v[2] % NVP1) == NV) && ((v[0] % NVP1) == NV) )
{
if(border[0] >= 0)
if( Ndv > (Absolute(v[2] - v[0])*dh)) return;
edge[0] = 2;
border[0] = 3;
return;
}
return;
}
void CalcAngles(NODE *node, int *v, float *angle)
{
int i, j, k;
vec l;
vec x0, x1, x2, y0, y1, y2;
vec2 vv[3];
vec dot;
switch(Plane)
{
case PLANE_XZ0:
case PLANE_XZ1:
i = 0;
j = 2;
break;
case PLANE_YZ0:
case PLANE_YZ1:
i = 1;
j = 2;
break;
default:
i = 0;
j = 1;
}
x0 = node[v[0]].p[i];
x1 = node[v[1]].p[i];
x2 = node[v[2]].p[i];
y0 = node[v[0]].p[j];
y1 = node[v[1]].p[j];
y2 = node[v[2]].p[j];
vv[0][0] = x1-x0;
vv[0][1] = y1-y0;
vv[1][0] = x2-x1;
vv[1][1] = y2-y1;
vv[2][0] = x0-x2;
vv[2][1] = y0-y2;
for(k=0; k<3; k++)
{
l = (vec)(sqrt( vv[k][0]*vv[k][0] + vv[k][1]*vv[k][1] ));
if(l > 0.)
{
vv[k][0] /= l;
vv[k][1] /= l;
}
}
dot = -(vv[0][0]*vv[2][0] + vv[0][1]*vv[2][1]);
angle[0] = (float)(acos(dot));
dot = -(vv[1][0]*vv[0][0] + vv[1][1]*vv[0][1]);
angle[1] = (float)(acos(dot));
dot = -(vv[2][0]*vv[1][0] + vv[2][1]*vv[1][1]);
angle[2] = (float)(acos(dot));
}
//=================================================================
int Bisect(NODE *node, int border, int j0, int j1)
{
int k;
switch(border)
{
case 0:
k = (j0+j1)/2;
break;
case 1:
k = (j0+j1)/2;
break;
case 2:
k = (int)((j0+j1)/(2*NVP1)) * NVP1;
break;
case 3:
k = (int)((j0+j1+2)/(2*NVP1)) * NVP1 - 1;
break;
}
return( ((k != j0) && (k != j1)) ? k : 0 );
}
//=================================================================
int compare(TRITABLE *t1, TRITABLE *t2)
{
if(t1->error > t2->error) return -1;
if(t1->error < t2->error) return 1;
return 0;
}
void MakeBrushes(int NumTris, NODE *Node, TRI *Tri,bool surf,
int offset,char *texture0, char *texture1, char *texture2)
{
extern double backface;
BRUSH brush;
int contents;
int i, j;
float Steep;
vec3_t PlaneNormal,SurfNormal;
bool CheckAngle;
vec3_t t[2];
// if texture2 is identical to texture0, there's no need to
// check surface angle
if(!g_strcasecmp(texture0,texture2) || !strlen(texture2))
CheckAngle = FALSE;
else
{
CheckAngle = TRUE;
Steep = (float)cos((double)SlantAngle/57.2957795);
switch(Plane)
{
case PLANE_XY0: PlaneNormal[0]= 0.;PlaneNormal[1]= 0.;PlaneNormal[2]= 1.;break;
case PLANE_XY1: PlaneNormal[0]= 0.;PlaneNormal[1]= 0.;PlaneNormal[2]=-1.;break;
case PLANE_XZ0: PlaneNormal[0]= 0.;PlaneNormal[1]= 1.;PlaneNormal[2]= 1.;break;
case PLANE_XZ1: PlaneNormal[0]= 0.;PlaneNormal[1]=-1.;PlaneNormal[2]= 1.;break;
case PLANE_YZ0: PlaneNormal[0]= 1.;PlaneNormal[1]= 0.;PlaneNormal[2]= 1.;break;
case PLANE_YZ1: PlaneNormal[0]=-1.;PlaneNormal[1]= 0.;PlaneNormal[2]= 1.;break;
}
}
contents = 0;
if(surf)
{
if(UseDetail) contents += CONTENTS_DETAIL;
if(UseLadder) contents += CONTENTS_LADDER;
}
OpenFuncGroup();
for(i=0; i<NumTris; i++)
{
brush.Number = i;
brush.NumFaces = 5;
// front
brush.face[0].v[0][0] = Node[Tri[i].v[0]].p[0];
brush.face[0].v[0][1] = Node[Tri[i].v[0]].p[1];
brush.face[0].v[0][2] = Node[Tri[i].v[0]].p[2];
brush.face[0].v[1][0] = Node[Tri[i].v[2]].p[0];
brush.face[0].v[1][1] = Node[Tri[i].v[2]].p[1];
brush.face[0].v[1][2] = Node[Tri[i].v[2]].p[2];
brush.face[0].v[2][0] = Node[Tri[i].v[1]].p[0];
brush.face[0].v[2][1] = Node[Tri[i].v[1]].p[1];
brush.face[0].v[2][2] = Node[Tri[i].v[1]].p[2];
if(offset != 0)
{
switch(Plane)
{
case PLANE_XY0:
brush.face[0].v[0][2] += offset;
brush.face[0].v[1][2] += offset;
brush.face[0].v[1][2] += offset;
break;
case PLANE_XY1:
brush.face[0].v[0][2] -= offset;
brush.face[0].v[1][2] -= offset;
brush.face[0].v[1][2] -= offset;
break;
case PLANE_XZ0:
brush.face[0].v[0][1] += offset;
brush.face[0].v[1][1] += offset;
brush.face[0].v[1][1] += offset;
break;
case PLANE_XZ1:
brush.face[0].v[0][1] -= offset;
brush.face[0].v[1][1] -= offset;
brush.face[0].v[1][1] -= offset;
break;
case PLANE_YZ0:
brush.face[0].v[0][0] += offset;
brush.face[0].v[1][0] += offset;
brush.face[0].v[1][0] += offset;
break;
case PLANE_YZ1:
brush.face[0].v[0][0] -= offset;
brush.face[0].v[1][0] -= offset;
brush.face[0].v[1][0] -= offset;
break;
}
}
switch(Plane)
{
case PLANE_XZ0:
case PLANE_XZ1:
// back
brush.face[1].v[0][0] = Node[Tri[i].v[0]].p[0];
brush.face[1].v[0][1] = (float)backface;
brush.face[1].v[0][2] = Node[Tri[i].v[0]].p[2];
brush.face[1].v[1][0] = Node[Tri[i].v[1]].p[0];
brush.face[1].v[1][1] = (float)backface;
brush.face[1].v[1][2] = Node[Tri[i].v[1]].p[2];
brush.face[1].v[2][0] = Node[Tri[i].v[2]].p[0];
brush.face[1].v[2][1] = (float)backface;
brush.face[1].v[2][2] = Node[Tri[i].v[2]].p[2];
// 0-1 side
brush.face[2].v[0][0] = Node[Tri[i].v[0]].p[0];
brush.face[2].v[0][1] = Node[Tri[i].v[0]].p[1];
brush.face[2].v[0][2] = Node[Tri[i].v[0]].p[2];
brush.face[2].v[1][0] = Node[Tri[i].v[1]].p[0];
brush.face[2].v[1][1] = Node[Tri[i].v[1]].p[1];
brush.face[2].v[1][2] = Node[Tri[i].v[1]].p[2];
brush.face[2].v[2][0] = Node[Tri[i].v[1]].p[0];
brush.face[2].v[2][1] = (float)backface;
brush.face[2].v[2][2] = Node[Tri[i].v[1]].p[2];
// 1-2 side
brush.face[3].v[0][0] = Node[Tri[i].v[1]].p[0];
brush.face[3].v[0][1] = Node[Tri[i].v[1]].p[1];
brush.face[3].v[0][2] = Node[Tri[i].v[1]].p[2];
brush.face[3].v[1][0] = Node[Tri[i].v[2]].p[0];
brush.face[3].v[1][1] = Node[Tri[i].v[2]].p[1];
brush.face[3].v[1][2] = Node[Tri[i].v[2]].p[2];
brush.face[3].v[2][0] = Node[Tri[i].v[2]].p[0];
brush.face[3].v[2][1] = (float)backface;
brush.face[3].v[2][2] = Node[Tri[i].v[2]].p[2];
// 2-0 side
brush.face[4].v[0][0] = Node[Tri[i].v[2]].p[0];
brush.face[4].v[0][1] = Node[Tri[i].v[2]].p[1];
brush.face[4].v[0][2] = Node[Tri[i].v[2]].p[2];
brush.face[4].v[1][0] = Node[Tri[i].v[0]].p[0];
brush.face[4].v[1][1] = Node[Tri[i].v[0]].p[1];
brush.face[4].v[1][2] = Node[Tri[i].v[0]].p[2];
brush.face[4].v[2][0] = Node[Tri[i].v[0]].p[0];
brush.face[4].v[2][1] = (float)backface;
brush.face[4].v[2][2] = Node[Tri[i].v[0]].p[2];
break;
case PLANE_YZ0:
case PLANE_YZ1:
// back
brush.face[1].v[0][0] = (float)backface;
brush.face[1].v[0][1] = Node[Tri[i].v[0]].p[1];
brush.face[1].v[0][2] = Node[Tri[i].v[0]].p[2];
brush.face[1].v[1][0] = (float)backface;
brush.face[1].v[1][1] = Node[Tri[i].v[1]].p[1];
brush.face[1].v[1][2] = Node[Tri[i].v[1]].p[2];
brush.face[1].v[2][0] = (float)backface;
brush.face[1].v[2][1] = Node[Tri[i].v[2]].p[1];
brush.face[1].v[2][2] = Node[Tri[i].v[2]].p[2];
// 0-1 side
brush.face[2].v[0][0] = Node[Tri[i].v[0]].p[0];
brush.face[2].v[0][1] = Node[Tri[i].v[0]].p[1];
brush.face[2].v[0][2] = Node[Tri[i].v[0]].p[2];
brush.face[2].v[1][0] = Node[Tri[i].v[1]].p[0];
brush.face[2].v[1][1] = Node[Tri[i].v[1]].p[1];
brush.face[2].v[1][2] = Node[Tri[i].v[1]].p[2];
brush.face[2].v[2][0] = (float)backface;
brush.face[2].v[2][1] = Node[Tri[i].v[1]].p[1];
brush.face[2].v[2][2] = Node[Tri[i].v[1]].p[2];
// 1-2 side
brush.face[3].v[0][0] = Node[Tri[i].v[1]].p[0];
brush.face[3].v[0][1] = Node[Tri[i].v[1]].p[1];
brush.face[3].v[0][2] = Node[Tri[i].v[1]].p[2];
brush.face[3].v[1][0] = Node[Tri[i].v[2]].p[0];
brush.face[3].v[1][1] = Node[Tri[i].v[2]].p[1];
brush.face[3].v[1][2] = Node[Tri[i].v[2]].p[2];
brush.face[3].v[2][0] = (float)backface;
brush.face[3].v[2][1] = Node[Tri[i].v[2]].p[1];
brush.face[3].v[2][2] = Node[Tri[i].v[2]].p[2];
// 2-0 side
brush.face[4].v[0][0] = Node[Tri[i].v[2]].p[0];
brush.face[4].v[0][1] = Node[Tri[i].v[2]].p[1];
brush.face[4].v[0][2] = Node[Tri[i].v[2]].p[2];
brush.face[4].v[1][0] = Node[Tri[i].v[0]].p[0];
brush.face[4].v[1][1] = Node[Tri[i].v[0]].p[1];
brush.face[4].v[1][2] = Node[Tri[i].v[0]].p[2];
brush.face[4].v[2][0] = (float)backface;
brush.face[4].v[2][1] = Node[Tri[i].v[0]].p[1];
brush.face[4].v[2][2] = Node[Tri[i].v[0]].p[2];
break;
default:
// back
brush.face[1].v[0][0] = Node[Tri[i].v[0]].p[0];
brush.face[1].v[0][1] = Node[Tri[i].v[0]].p[1];
brush.face[1].v[0][2] = (float)backface;
brush.face[1].v[1][0] = Node[Tri[i].v[1]].p[0];
brush.face[1].v[1][1] = Node[Tri[i].v[1]].p[1];
brush.face[1].v[1][2] = (float)backface;
brush.face[1].v[2][0] = Node[Tri[i].v[2]].p[0];
brush.face[1].v[2][1] = Node[Tri[i].v[2]].p[1];
brush.face[1].v[2][2] = (float)backface;
// 0-1 side
brush.face[2].v[0][0] = Node[Tri[i].v[0]].p[0];
brush.face[2].v[0][1] = Node[Tri[i].v[0]].p[1];
brush.face[2].v[0][2] = Node[Tri[i].v[0]].p[2];
brush.face[2].v[1][0] = Node[Tri[i].v[1]].p[0];
brush.face[2].v[1][1] = Node[Tri[i].v[1]].p[1];
brush.face[2].v[1][2] = Node[Tri[i].v[1]].p[2];
brush.face[2].v[2][0] = Node[Tri[i].v[1]].p[0];
brush.face[2].v[2][1] = Node[Tri[i].v[1]].p[1];
brush.face[2].v[2][2] = (float)backface;
// 1-2 side
brush.face[3].v[0][0] = Node[Tri[i].v[1]].p[0];
brush.face[3].v[0][1] = Node[Tri[i].v[1]].p[1];
brush.face[3].v[0][2] = Node[Tri[i].v[1]].p[2];
brush.face[3].v[1][0] = Node[Tri[i].v[2]].p[0];
brush.face[3].v[1][1] = Node[Tri[i].v[2]].p[1];
brush.face[3].v[1][2] = Node[Tri[i].v[2]].p[2];
brush.face[3].v[2][0] = Node[Tri[i].v[2]].p[0];
brush.face[3].v[2][1] = Node[Tri[i].v[2]].p[1];
brush.face[3].v[2][2] = (float)backface;
// 2-0 side
brush.face[4].v[0][0] = Node[Tri[i].v[2]].p[0];
brush.face[4].v[0][1] = Node[Tri[i].v[2]].p[1];
brush.face[4].v[0][2] = Node[Tri[i].v[2]].p[2];
brush.face[4].v[1][0] = Node[Tri[i].v[0]].p[0];
brush.face[4].v[1][1] = Node[Tri[i].v[0]].p[1];
brush.face[4].v[1][2] = Node[Tri[i].v[0]].p[2];
brush.face[4].v[2][0] = Node[Tri[i].v[0]].p[0];
brush.face[4].v[2][1] = Node[Tri[i].v[0]].p[1];
brush.face[4].v[2][2] = (float)backface;
}
for(j=0; j<5; j++)
{
strcpy(brush.face[j].texture,
(strlen(texture1) ? texture1 : texture0));
brush.face[j].Shift[0] = (float)TexOffset[0];
brush.face[j].Shift[1] = (float)TexOffset[1];
brush.face[j].Rotate = 0.;
brush.face[j].Scale[0] = (float)TexScale[0];
brush.face[j].Scale[1] = (float)TexScale[1];
brush.face[j].Contents = contents;
if(surf)
brush.face[j].Surface = 0;
else
brush.face[j].Surface = SURF_HINT;
brush.face[j].Value = 0;
}
if(CheckAngle)
{
XYZVectorSubtract(brush.face[0].v[2],brush.face[0].v[0],t[0]);
XYZVectorSubtract(brush.face[0].v[1],brush.face[0].v[2],t[1]);
CrossProduct(t[0],t[1],SurfNormal);
VectorNormalize(SurfNormal,SurfNormal);
if(DotProduct(SurfNormal,PlaneNormal) < Steep)
strcpy(brush.face[0].texture,texture2);
else
strcpy(brush.face[0].texture,texture0);
}
else
strcpy(brush.face[0].texture,texture0);
if(surf) brush.face[0].Value = ArghRad2;
MakeBrush(&brush);
}
CloseFuncGroup();
} // end MakeBrushes
//=================================================================
void MapOut(int NumNodes,int NumTris, NODE *Node, TRI *Tri)
{
extern double backface;
extern double xmin, xmax, ymin, ymax, zmin, zmax;
BRUSH brush;
char hint[32], skip[32];
int i, j;
int face;
/*
ghCursorCurrent = LoadCursor(NULL,IDC_WAIT);
SetCursor(ghCursorCurrent);
*/
UseDetail = 1; // this is temporary
MakeBrushes(NumTris,Node,Tri,TRUE,0,Texture[Game][0],Texture[Game][1],Texture[Game][2]);
if(AddHints || GimpHints)
{
switch(Game)
{
case SIN:
strcpy(hint,"generic/misc/hint");
strcpy(skip,"generic/misc/skip");
break;
case HALFLIFE:
strcpy(hint,"HINT");
strcpy(skip,"HINT");
break;
case HERETIC2:
strcpy(hint,"general/hint");
strcpy(skip,"general/skip");
break;
case KINGPIN:
strcpy(hint,"common/0_hint");
strcpy(skip,"common/0_skip");
break;
case QUAKE3:
strcpy(hint,"common/hint");
strcpy(skip,"common/skip");
break;
default:
strcpy(hint,"e1u1/hint");
strcpy(skip,"e1u1/skip");
}
}
if( GimpHints )
MakeBrushes(NumTris,Node,Tri,FALSE,HINT_OFFSET,hint,hint,hint);
if( AddHints==1 )
{
int j0, j1, j2, k, k0, k1;
int q[4];
int w,h,h0,h1,t,OK;
float s[3];
double front;
int MaxHints; // We don't want a whole slew of hint brushes, which we'd get
// with low decimation values and our current placement scheme.
// Limit number of hint brushes to number of undecimated grid
// squares.
switch(Plane)
{
case PLANE_XY1:
front = LessThan(zmin,32.);
break;
case PLANE_XZ0:
front = MoreThan(ymax,32.);
break;
case PLANE_XZ1:
front = LessThan(ymin,32.);
break;
case PLANE_YZ0:
front = MoreThan(xmax,32.);
break;
case PLANE_YZ1:
front = LessThan(xmin,32.);
break;
default:
front = MoreThan(zmax,32.);
}
for(i=0; i<NumTris; i++)
Tri[i].flag = 0;
switch(Plane)
{
case PLANE_XZ0:
case PLANE_XZ1:
j0 = 1;
j1 = 0;
j2 = 2;
break;
case PLANE_YZ0:
case PLANE_YZ1:
j0 = 0;
j1 = 1;
j2 = 2;
break;
default:
j0 = 2;
j1 = 0;
j2 = 1;
}
brush.Number = 0;
brush.NumFaces = 6;
MaxHints = NH*NV-1;
for(w=1; w<min(16,NH) && brush.Number < MaxHints; w++)
{
for(h=max(1,w/2); h<min(16,NV) && brush.Number < MaxHints; h++)
{
for(i=0; i<=NH-w && brush.Number < MaxHints; i++)
{
for(j=0; j<=NV-h && brush.Number < MaxHints; j++)
{
q[0] = i*NVP1+j;
q[2] = q[0] + w*NVP1 + h;
switch(Plane)
{
case PLANE_XY1:
case PLANE_XZ0:
case PLANE_YZ1:
q[1] = q[0] + h;
q[3] = q[2] - h;
break;
default:
q[1] = q[2] - h;
q[3] = q[0] + h;
}
for(k=0, OK=1; k<NumTris && OK; k++)
{
if(Tri[k].min[j1] >= max(Node[q[0]].p[j1],Node[q[2]].p[j1])) continue;
if(Tri[k].min[j2] >= max(Node[q[0]].p[j2],Node[q[2]].p[j2])) continue;
if(Tri[k].max[j1] <= min(Node[q[0]].p[j1],Node[q[2]].p[j1])) continue;
if(Tri[k].max[j2] <= min(Node[q[0]].p[j2],Node[q[2]].p[j2])) continue;
for(h0=0; h0<4 && OK; h0++)
{
h1 = (h0+1)%4;
for(t=0; t<3 && OK; t++)
{
s[t] = side(Node[q[h0]].p[j1],Node[q[h0]].p[j2],
Node[q[h1]].p[j1],Node[q[h1]].p[j2],
Node[Tri[k].v[t]].p[j1],Node[Tri[k].v[t]].p[j2]);
}
if((s[1] > 0 || s[2] > 0) && s[0] < 0) OK=0;
if((s[2] > 0 || s[0] > 0) && s[1] < 0) OK=0;
if((s[0] > 0 || s[1] > 0) && s[2] < 0) OK=0;
}
}
if(!OK) continue;
switch(Plane)
{
case PLANE_XZ0:
case PLANE_XZ1:
// front
brush.face[0].v[0][0] = Node[q[2]].p[0];
brush.face[0].v[0][1] = (float)front;
brush.face[0].v[0][2] = Node[q[2]].p[2];
brush.face[0].v[1][0] = Node[q[1]].p[0];
brush.face[0].v[1][1] = (float)front;
brush.face[0].v[1][2] = Node[q[1]].p[2];
brush.face[0].v[2][0] = Node[q[0]].p[0];
brush.face[0].v[2][1] = (float)front;
brush.face[0].v[2][2] = Node[q[0]].p[2];
// back
brush.face[1].v[0][0] = Node[q[0]].p[0];
brush.face[1].v[0][1] = (float)backface;
brush.face[1].v[0][2] = Node[q[0]].p[2];
brush.face[1].v[1][0] = Node[q[1]].p[0];
brush.face[1].v[1][1] = (float)backface;
brush.face[1].v[1][2] = Node[q[1]].p[2];
brush.face[1].v[2][0] = Node[q[2]].p[0];
brush.face[1].v[2][1] = (float)backface;
brush.face[1].v[2][2] = Node[q[2]].p[2];
for(k0=0; k0<brush.NumFaces-2; k0++)
{
k =k0+2;
k1=(k0+1) % (brush.NumFaces-2);
brush.face[k].v[0][0] = Node[q[k0]].p[0];
brush.face[k].v[0][1] = (float)front;
brush.face[k].v[0][2] = Node[q[k0]].p[2];
brush.face[k].v[1][0] = Node[q[k1]].p[0];
brush.face[k].v[1][1] = (float)front;
brush.face[k].v[1][2] = Node[q[k1]].p[2];
brush.face[k].v[2][0] = Node[q[k1]].p[0];
brush.face[k].v[2][1] = (float)backface;
brush.face[k].v[2][2] = Node[q[k1]].p[2];
}
break;
case PLANE_YZ0:
case PLANE_YZ1:
// front
brush.face[0].v[0][0] = (float)front;
brush.face[0].v[0][1] = Node[q[2]].p[1];
brush.face[0].v[0][2] = Node[q[2]].p[2];
brush.face[0].v[1][0] = (float)front;
brush.face[0].v[1][1] = Node[q[1]].p[1];
brush.face[0].v[1][2] = Node[q[1]].p[2];
brush.face[0].v[2][0] = (float)front;
brush.face[0].v[2][1] = Node[q[0]].p[1];
brush.face[0].v[2][2] = Node[q[0]].p[2];
// back
brush.face[1].v[0][0] = (float)backface;
brush.face[1].v[0][1] = Node[q[0]].p[1];
brush.face[1].v[0][2] = Node[q[0]].p[2];
brush.face[1].v[1][0] = (float)backface;
brush.face[1].v[1][1] = Node[q[1]].p[1];
brush.face[1].v[1][2] = Node[q[1]].p[2];
brush.face[1].v[2][0] = (float)backface;
brush.face[1].v[2][1] = Node[q[2]].p[1];
brush.face[1].v[2][2] = Node[q[2]].p[2];
for(k0=0; k0<brush.NumFaces-2; k0++)
{
k =k0+2;
k1=(k0+1) % (brush.NumFaces-2);
brush.face[k].v[0][0] = (float)front;
brush.face[k].v[0][1] = Node[q[k0]].p[1];
brush.face[k].v[0][2] = Node[q[k0]].p[2];
brush.face[k].v[1][0] = (float)front;
brush.face[k].v[1][1] = Node[q[k1]].p[1];
brush.face[k].v[1][2] = Node[q[k1]].p[2];
brush.face[k].v[2][0] = (float)backface;
brush.face[k].v[2][1] = Node[q[k1]].p[1];
brush.face[k].v[2][2] = Node[q[k1]].p[2];
}
break;
default:
// front
brush.face[0].v[0][0] = Node[q[2]].p[0];
brush.face[0].v[0][1] = Node[q[2]].p[1];
brush.face[0].v[0][2] = (float)front;
brush.face[0].v[1][0] = Node[q[1]].p[0];
brush.face[0].v[1][1] = Node[q[1]].p[1];
brush.face[0].v[1][2] = (float)front;
brush.face[0].v[2][0] = Node[q[0]].p[0];
brush.face[0].v[2][1] = Node[q[0]].p[1];
brush.face[0].v[2][2] = (float)front;
// back
brush.face[1].v[0][0] = Node[q[0]].p[0];
brush.face[1].v[0][1] = Node[q[0]].p[1];
brush.face[1].v[0][2] = (float)backface;
brush.face[1].v[1][0] = Node[q[1]].p[0];
brush.face[1].v[1][1] = Node[q[1]].p[1];
brush.face[1].v[1][2] = (float)backface;
brush.face[1].v[2][0] = Node[q[2]].p[0];
brush.face[1].v[2][1] = Node[q[2]].p[1];
brush.face[1].v[2][2] = (float)backface;
for(k0=0; k0<brush.NumFaces-2; k0++)
{
k =k0+2;
k1=(k0+1) % (brush.NumFaces-2);
brush.face[k].v[0][0] = Node[q[k0]].p[0];
brush.face[k].v[0][1] = Node[q[k0]].p[1];
brush.face[k].v[0][2] = (float)front;
brush.face[k].v[1][0] = Node[q[k1]].p[0];
brush.face[k].v[1][1] = Node[q[k1]].p[1];
brush.face[k].v[1][2] = (float)front;
brush.face[k].v[2][0] = Node[q[k1]].p[0];
brush.face[k].v[2][1] = Node[q[k1]].p[1];
brush.face[k].v[2][2] = (float)backface;
}
break;
} // switch (Plane)
for(face=0; face<6; face++)
{
strcpy(brush.face[face].texture,(face<=1 ? skip : hint));
brush.face[face].Shift[0] = 0;
brush.face[face].Shift[1] = 0;
brush.face[face].Rotate = 0.;
brush.face[face].Scale[0] = 1;
brush.face[face].Scale[1] = 1;
brush.face[face].Contents = CONTENTS_DETAIL;
brush.face[face].Surface = (face<=1 ? SURF_SKIP : SURF_HINT);
brush.face[face].Value = 0;
}
if(!brush.Number) OpenFuncGroup();
MakeBrush(&brush);
brush.Number++;
} // for(j=
} // for(i=
} // for(h=
} // for(w=
if(brush.Number) CloseFuncGroup();
}
/*
ghCursorCurrent = ghCursorDefault;
SetCursor(ghCursorCurrent);
*/
}
//===========================================================================
int CheckBorders(int *NumNodesUsed, int NumNodes, NODE *Node, int *NumTris, TRI **pTri)
{
int border;
int i, j, k0, k1, N;
float angle[3];
TRI *Tri;
N = NumNodesUsed[0];
Tri = *pTri;
for(i=0; i<NumTris[0]; i++)
{
EdgeOnSide(Tri[i].v,&k0,&border);
if(border < 0) continue;
CalcAngles(Node, Tri[i].v, angle);
k1 = (k0+1) % 3;
if((angle[k0] < SLIVER_ANGLE) || (angle[k1] < SLIVER_ANGLE))
{
j = Bisect(Node, border, Tri[i].v[k0], Tri[i].v[k1]);
if(j >= 0)
{
if(!Node[j].used) // Shouldn't be used, but...
{
NumNodesUsed[0]++;
Node[j].used++;
}
}
}
}
if(NumNodesUsed[0] > N)
{
free(*pTri);
tricall(NumNodes, Node, NumTris, NULL, pTri, "cnzBNPY");
Tri = *pTri;
}
return (NumNodesUsed[0] - N);
}