mirror of
https://github.com/UberGames/GtkRadiant.git
synced 2024-11-23 04:12:06 +00:00
1872 lines
45 KiB
C
1872 lines
45 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
|
|
This file is part of Quake III Arena source code.
|
|
|
|
Quake III Arena source code is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Quake III Arena source code is distributed in the hope that it will be
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Foobar; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "qbsp.h"
|
|
#include "l_mem.h"
|
|
#include "botlib/aasfile.h"
|
|
#include "aas_store.h"
|
|
#include "aas_cfg.h"
|
|
|
|
#include <stddef.h>
|
|
#include <assert.h>
|
|
|
|
/*
|
|
each side has a count of the other sides it splits
|
|
|
|
the best split will be the one that minimizes the total split counts
|
|
of all remaining sides
|
|
|
|
precalc side on plane table
|
|
|
|
evaluate split side
|
|
{
|
|
cost = 0
|
|
for all sides
|
|
for all sides
|
|
get
|
|
if side splits side and splitside is on same child
|
|
cost++;
|
|
}
|
|
*/
|
|
|
|
int c_nodes;
|
|
int c_nonvis;
|
|
int c_active_brushes;
|
|
int c_solidleafnodes;
|
|
int c_totalsides;
|
|
int c_brushmemory;
|
|
int c_peak_brushmemory;
|
|
int c_nodememory;
|
|
int c_peak_totalbspmemory;
|
|
|
|
// if a brush just barely pokes onto the other side,
|
|
// let it slide by without chopping
|
|
#define PLANESIDE_EPSILON 0.001
|
|
//0.1
|
|
|
|
//#ifdef DEBUG
|
|
typedef struct cname_s
|
|
{
|
|
int value;
|
|
char *name;
|
|
} cname_t;
|
|
|
|
cname_t contentnames[] =
|
|
{
|
|
{CONTENTS_SOLID,"CONTENTS_SOLID"},
|
|
{CONTENTS_WINDOW,"CONTENTS_WINDOW"},
|
|
{CONTENTS_AUX,"CONTENTS_AUX"},
|
|
{CONTENTS_LAVA,"CONTENTS_LAVA"},
|
|
{CONTENTS_SLIME,"CONTENTS_SLIME"},
|
|
{CONTENTS_WATER,"CONTENTS_WATER"},
|
|
{CONTENTS_MIST,"CONTENTS_MIST"},
|
|
{LAST_VISIBLE_CONTENTS,"LAST_VISIBLE_CONTENTS"},
|
|
|
|
{CONTENTS_AREAPORTAL,"CONTENTS_AREAPORTAL"},
|
|
{CONTENTS_PLAYERCLIP,"CONTENTS_PLAYERCLIP"},
|
|
{CONTENTS_MONSTERCLIP,"CONTENTS_MONSTERCLIP"},
|
|
{CONTENTS_CURRENT_0,"CONTENTS_CURRENT_0"},
|
|
{CONTENTS_CURRENT_90,"CONTENTS_CURRENT_90"},
|
|
{CONTENTS_CURRENT_180,"CONTENTS_CURRENT_180"},
|
|
{CONTENTS_CURRENT_270,"CONTENTS_CURRENT_270"},
|
|
{CONTENTS_CURRENT_UP,"CONTENTS_CURRENT_UP"},
|
|
{CONTENTS_CURRENT_DOWN,"CONTENTS_CURRENT_DOWN"},
|
|
{CONTENTS_ORIGIN,"CONTENTS_ORIGIN"},
|
|
{CONTENTS_MONSTER,"CONTENTS_MONSTER"},
|
|
{CONTENTS_DEADMONSTER,"CONTENTS_DEADMONSTER"},
|
|
{CONTENTS_DETAIL,"CONTENTS_DETAIL"},
|
|
{CONTENTS_Q2TRANSLUCENT,"CONTENTS_TRANSLUCENT"},
|
|
{CONTENTS_LADDER,"CONTENTS_LADDER"},
|
|
{0, 0}
|
|
};
|
|
|
|
void PrintContents(int contents)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; contentnames[i].value; i++)
|
|
{
|
|
if (contents & contentnames[i].value)
|
|
{
|
|
Log_Write("%s,", contentnames[i].name);
|
|
} //end if
|
|
} //end for
|
|
} //end of the function PrintContents
|
|
|
|
//#endif
|
|
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
void ResetBrushBSP(void)
|
|
{
|
|
c_nodes = 0;
|
|
c_nonvis = 0;
|
|
c_active_brushes = 0;
|
|
c_solidleafnodes = 0;
|
|
c_totalsides = 0;
|
|
c_brushmemory = 0;
|
|
c_peak_brushmemory = 0;
|
|
c_nodememory = 0;
|
|
c_peak_totalbspmemory = 0;
|
|
} //end of the function ResetBrushBSP
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
void FindBrushInTree (node_t *node, int brushnum)
|
|
{
|
|
bspbrush_t *b;
|
|
|
|
if (node->planenum == PLANENUM_LEAF)
|
|
{
|
|
for (b=node->brushlist ; b ; b=b->next)
|
|
if (b->original->brushnum == brushnum)
|
|
Log_Print ("here\n");
|
|
return;
|
|
}
|
|
FindBrushInTree(node->children[0], brushnum);
|
|
FindBrushInTree(node->children[1], brushnum);
|
|
} //end of the function FindBrushInTree
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
void DrawBrushList (bspbrush_t *brush, node_t *node)
|
|
{
|
|
int i;
|
|
side_t *s;
|
|
|
|
GLS_BeginScene ();
|
|
for ( ; brush ; brush=brush->next)
|
|
{
|
|
for (i=0 ; i<brush->numsides ; i++)
|
|
{
|
|
s = &brush->sides[i];
|
|
if (!s->winding)
|
|
continue;
|
|
if (s->texinfo == TEXINFO_NODE)
|
|
GLS_Winding (s->winding, 1);
|
|
else if (!(s->flags & SFL_VISIBLE))
|
|
GLS_Winding (s->winding, 2);
|
|
else
|
|
GLS_Winding (s->winding, 0);
|
|
}
|
|
}
|
|
GLS_EndScene ();
|
|
} //end of the function DrawBrushList
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
void WriteBrushList (char *name, bspbrush_t *brush, qboolean onlyvis)
|
|
{
|
|
int i;
|
|
side_t *s;
|
|
FILE *f;
|
|
|
|
qprintf ("writing %s\n", name);
|
|
f = SafeOpenWrite (name);
|
|
|
|
for ( ; brush ; brush=brush->next)
|
|
{
|
|
for (i=0 ; i<brush->numsides ; i++)
|
|
{
|
|
s = &brush->sides[i];
|
|
if (!s->winding)
|
|
continue;
|
|
if (onlyvis && !(s->flags & SFL_VISIBLE))
|
|
continue;
|
|
OutputWinding (brush->sides[i].winding, f);
|
|
}
|
|
}
|
|
|
|
fclose (f);
|
|
} //end of the function WriteBrushList
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
void PrintBrush (bspbrush_t *brush)
|
|
{
|
|
int i;
|
|
|
|
printf ("brush: %p\n", brush);
|
|
for (i=0;i<brush->numsides ; i++)
|
|
{
|
|
pw(brush->sides[i].winding);
|
|
printf ("\n");
|
|
} //end for
|
|
} //end of the function PrintBrush
|
|
//===========================================================================
|
|
// Sets the mins/maxs based on the windings
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
void BoundBrush (bspbrush_t *brush)
|
|
{
|
|
int i, j;
|
|
winding_t *w;
|
|
|
|
ClearBounds (brush->mins, brush->maxs);
|
|
for (i=0 ; i<brush->numsides ; i++)
|
|
{
|
|
w = brush->sides[i].winding;
|
|
if (!w)
|
|
continue;
|
|
for (j=0 ; j<w->numpoints ; j++)
|
|
AddPointToBounds (w->p[j], brush->mins, brush->maxs);
|
|
}
|
|
} //end of the function BoundBrush
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
void CreateBrushWindings (bspbrush_t *brush)
|
|
{
|
|
int i, j;
|
|
winding_t *w;
|
|
side_t *side;
|
|
plane_t *plane;
|
|
|
|
for (i=0 ; i<brush->numsides ; i++)
|
|
{
|
|
side = &brush->sides[i];
|
|
plane = &mapplanes[side->planenum];
|
|
w = BaseWindingForPlane (plane->normal, plane->dist);
|
|
for (j=0 ; j<brush->numsides && w; j++)
|
|
{
|
|
if (i == j)
|
|
continue;
|
|
if (brush->sides[j].flags & SFL_BEVEL)
|
|
continue;
|
|
plane = &mapplanes[brush->sides[j].planenum^1];
|
|
ChopWindingInPlace (&w, plane->normal, plane->dist, 0); //CLIP_EPSILON);
|
|
}
|
|
|
|
side->winding = w;
|
|
}
|
|
|
|
BoundBrush (brush);
|
|
} //end of the function CreateBrushWindings
|
|
//===========================================================================
|
|
// Creates a new axial brush
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
bspbrush_t *BrushFromBounds (vec3_t mins, vec3_t maxs)
|
|
{
|
|
bspbrush_t *b;
|
|
int i;
|
|
vec3_t normal;
|
|
vec_t dist;
|
|
|
|
b = AllocBrush (6);
|
|
b->numsides = 6;
|
|
for (i=0 ; i<3 ; i++)
|
|
{
|
|
VectorClear (normal);
|
|
normal[i] = 1;
|
|
dist = maxs[i];
|
|
b->sides[i].planenum = FindFloatPlane (normal, dist);
|
|
|
|
normal[i] = -1;
|
|
dist = -mins[i];
|
|
b->sides[3+i].planenum = FindFloatPlane (normal, dist);
|
|
}
|
|
|
|
CreateBrushWindings (b);
|
|
|
|
return b;
|
|
} //end of the function BrushFromBounds
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
int BrushOutOfBounds(bspbrush_t *brush, vec3_t mins, vec3_t maxs, float epsilon)
|
|
{
|
|
int i, j, n;
|
|
winding_t *w;
|
|
side_t *side;
|
|
|
|
for (i = 0; i < brush->numsides; i++)
|
|
{
|
|
side = &brush->sides[i];
|
|
w = side->winding;
|
|
for (j = 0; j < w->numpoints; j++)
|
|
{
|
|
for (n = 0; n < 3; n++)
|
|
{
|
|
if (w->p[j][n] < (mins[n] + epsilon) || w->p[j][n] > (maxs[n] - epsilon)) return true;
|
|
} //end for
|
|
} //end for
|
|
} //end for
|
|
return false;
|
|
} //end of the function BrushOutOfBounds
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
vec_t BrushVolume (bspbrush_t *brush)
|
|
{
|
|
int i;
|
|
winding_t *w;
|
|
vec3_t corner;
|
|
vec_t d, area, volume;
|
|
plane_t *plane;
|
|
|
|
if (!brush) return 0;
|
|
|
|
// grab the first valid point as the corner
|
|
w = NULL;
|
|
for (i = 0; i < brush->numsides; i++)
|
|
{
|
|
w = brush->sides[i].winding;
|
|
if (w) break;
|
|
} //end for
|
|
if (!w) return 0;
|
|
VectorCopy (w->p[0], corner);
|
|
|
|
// make tetrahedrons to all other faces
|
|
volume = 0;
|
|
for ( ; i < brush->numsides; i++)
|
|
{
|
|
w = brush->sides[i].winding;
|
|
if (!w) continue;
|
|
plane = &mapplanes[brush->sides[i].planenum];
|
|
d = -(DotProduct (corner, plane->normal) - plane->dist);
|
|
area = WindingArea(w);
|
|
volume += d * area;
|
|
} //end for
|
|
|
|
volume /= 3;
|
|
return volume;
|
|
} //end of the function BrushVolume
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
int CountBrushList (bspbrush_t *brushes)
|
|
{
|
|
int c;
|
|
|
|
c = 0;
|
|
for ( ; brushes; brushes = brushes->next) c++;
|
|
return c;
|
|
} //end of the function CountBrushList
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
node_t *AllocNode (void)
|
|
{
|
|
node_t *node;
|
|
|
|
node = GetMemory(sizeof(*node));
|
|
memset (node, 0, sizeof(*node));
|
|
if (numthreads == 1)
|
|
{
|
|
c_nodememory += MemorySize(node);
|
|
} //end if
|
|
return node;
|
|
} //end of the function AllocNode
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
bspbrush_t *AllocBrush (int numsides)
|
|
{
|
|
bspbrush_t *bb;
|
|
size_t c;
|
|
|
|
c = offsetof(bspbrush_t, sides[numsides]);
|
|
bb = GetMemory(c);
|
|
memset (bb, 0, c);
|
|
if (numthreads == 1)
|
|
{
|
|
c_active_brushes++;
|
|
c_brushmemory += MemorySize(bb);
|
|
if (c_brushmemory > c_peak_brushmemory)
|
|
c_peak_brushmemory = c_brushmemory;
|
|
} //end if
|
|
return bb;
|
|
} //end of the function AllocBrush
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
void FreeBrush (bspbrush_t *brushes)
|
|
{
|
|
int i;
|
|
|
|
for (i=0 ; i<brushes->numsides ; i++)
|
|
if (brushes->sides[i].winding)
|
|
FreeWinding(brushes->sides[i].winding);
|
|
if (numthreads == 1)
|
|
{
|
|
c_active_brushes--;
|
|
c_brushmemory -= MemorySize(brushes);
|
|
if (c_brushmemory < 0) c_brushmemory = 0;
|
|
} //end if
|
|
FreeMemory(brushes);
|
|
} //end of the function FreeBrush
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
void FreeBrushList (bspbrush_t *brushes)
|
|
{
|
|
bspbrush_t *next;
|
|
|
|
for ( ; brushes; brushes = next)
|
|
{
|
|
next = brushes->next;
|
|
|
|
FreeBrush(brushes);
|
|
} //end for
|
|
} //end of the function FreeBrushList
|
|
//===========================================================================
|
|
// Duplicates the brush, the sides, and the windings
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
bspbrush_t *CopyBrush (bspbrush_t *brush)
|
|
{
|
|
bspbrush_t *newbrush;
|
|
size_t size;
|
|
int i;
|
|
|
|
size = offsetof(bspbrush_t, sides[brush->numsides]);
|
|
|
|
newbrush = AllocBrush (brush->numsides);
|
|
memcpy (newbrush, brush, size);
|
|
|
|
for (i=0 ; i<brush->numsides ; i++)
|
|
{
|
|
if (brush->sides[i].winding)
|
|
newbrush->sides[i].winding = CopyWinding (brush->sides[i].winding);
|
|
}
|
|
|
|
return newbrush;
|
|
} //end of the function CopyBrush
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
node_t *PointInLeaf (node_t *node, vec3_t point)
|
|
{
|
|
vec_t d;
|
|
plane_t *plane;
|
|
|
|
while (node->planenum != PLANENUM_LEAF)
|
|
{
|
|
plane = &mapplanes[node->planenum];
|
|
d = DotProduct (point, plane->normal) - plane->dist;
|
|
if (d > 0)
|
|
node = node->children[0];
|
|
else
|
|
node = node->children[1];
|
|
}
|
|
|
|
return node;
|
|
} //end of the function PointInLeaf
|
|
//===========================================================================
|
|
// Returns PSIDE_FRONT, PSIDE_BACK, or PSIDE_BOTH
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
#if 0
|
|
int BoxOnPlaneSide (vec3_t mins, vec3_t maxs, plane_t *plane)
|
|
{
|
|
int side;
|
|
int i;
|
|
vec3_t corners[2];
|
|
vec_t dist1, dist2;
|
|
|
|
// axial planes are easy
|
|
if (plane->type < 3)
|
|
{
|
|
side = 0;
|
|
if (maxs[plane->type] > plane->dist+PLANESIDE_EPSILON)
|
|
side |= PSIDE_FRONT;
|
|
if (mins[plane->type] < plane->dist-PLANESIDE_EPSILON)
|
|
side |= PSIDE_BACK;
|
|
return side;
|
|
}
|
|
|
|
// create the proper leading and trailing verts for the box
|
|
|
|
for (i=0 ; i<3 ; i++)
|
|
{
|
|
if (plane->normal[i] < 0)
|
|
{
|
|
corners[0][i] = mins[i];
|
|
corners[1][i] = maxs[i];
|
|
}
|
|
else
|
|
{
|
|
corners[1][i] = mins[i];
|
|
corners[0][i] = maxs[i];
|
|
}
|
|
}
|
|
|
|
dist1 = DotProduct (plane->normal, corners[0]) - plane->dist;
|
|
dist2 = DotProduct (plane->normal, corners[1]) - plane->dist;
|
|
side = 0;
|
|
if (dist1 >= PLANESIDE_EPSILON)
|
|
side = PSIDE_FRONT;
|
|
if (dist2 < PLANESIDE_EPSILON)
|
|
side |= PSIDE_BACK;
|
|
|
|
return side;
|
|
}
|
|
#else
|
|
int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, plane_t *p)
|
|
{
|
|
float dist1, dist2;
|
|
int sides;
|
|
|
|
// axial planes are easy
|
|
if (p->type < 3)
|
|
{
|
|
sides = 0;
|
|
if (emaxs[p->type] > p->dist+PLANESIDE_EPSILON) sides |= PSIDE_FRONT;
|
|
if (emins[p->type] < p->dist-PLANESIDE_EPSILON) sides |= PSIDE_BACK;
|
|
return sides;
|
|
} //end if
|
|
|
|
// general case
|
|
switch (p->signbits)
|
|
{
|
|
case 0:
|
|
dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
|
|
dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
|
|
break;
|
|
case 1:
|
|
dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
|
|
dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
|
|
break;
|
|
case 2:
|
|
dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
|
|
dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
|
|
break;
|
|
case 3:
|
|
dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
|
|
dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
|
|
break;
|
|
case 4:
|
|
dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
|
|
dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
|
|
break;
|
|
case 5:
|
|
dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];
|
|
dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];
|
|
break;
|
|
case 6:
|
|
dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
|
|
dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
|
|
break;
|
|
case 7:
|
|
dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];
|
|
dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];
|
|
break;
|
|
default:
|
|
dist1 = dist2 = 0; // shut up compiler
|
|
// assert( 0 );
|
|
break;
|
|
}
|
|
|
|
sides = 0;
|
|
if (dist1 - p->dist >= PLANESIDE_EPSILON) sides = PSIDE_FRONT;
|
|
if (dist2 - p->dist < PLANESIDE_EPSILON) sides |= PSIDE_BACK;
|
|
|
|
// assert(sides != 0);
|
|
|
|
return sides;
|
|
}
|
|
#endif
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
int QuickTestBrushToPlanenum (bspbrush_t *brush, int planenum, int *numsplits)
|
|
{
|
|
int i, num;
|
|
plane_t *plane;
|
|
int s;
|
|
|
|
*numsplits = 0;
|
|
|
|
plane = &mapplanes[planenum];
|
|
|
|
#ifdef ME
|
|
//fast axial cases
|
|
if (plane->type < 3)
|
|
{
|
|
if (plane->dist + PLANESIDE_EPSILON < brush->mins[plane->type])
|
|
return PSIDE_FRONT;
|
|
if (plane->dist - PLANESIDE_EPSILON > brush->maxs[plane->type])
|
|
return PSIDE_BACK;
|
|
} //end if
|
|
#endif //ME*/
|
|
|
|
// if the brush actually uses the planenum,
|
|
// we can tell the side for sure
|
|
for (i = 0; i < brush->numsides; i++)
|
|
{
|
|
num = brush->sides[i].planenum;
|
|
if (num >= MAX_MAPFILE_PLANES)
|
|
Error ("bad planenum");
|
|
if (num == planenum)
|
|
return PSIDE_BACK|PSIDE_FACING;
|
|
if (num == (planenum ^ 1) )
|
|
return PSIDE_FRONT|PSIDE_FACING;
|
|
|
|
}
|
|
|
|
// box on plane side
|
|
s = BoxOnPlaneSide (brush->mins, brush->maxs, plane);
|
|
|
|
// if both sides, count the visible faces split
|
|
if (s == PSIDE_BOTH)
|
|
{
|
|
*numsplits += 3;
|
|
}
|
|
|
|
return s;
|
|
} //end of the function QuickTestBrushToPlanenum
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
int TestBrushToPlanenum (bspbrush_t *brush, int planenum,
|
|
int *numsplits, qboolean *hintsplit, int *epsilonbrush)
|
|
{
|
|
int i, j, num;
|
|
plane_t *plane;
|
|
int s = 0;
|
|
winding_t *w;
|
|
vec_t d, d_front, d_back;
|
|
int front, back;
|
|
int type;
|
|
float dist;
|
|
|
|
*numsplits = 0;
|
|
*hintsplit = false;
|
|
|
|
plane = &mapplanes[planenum];
|
|
|
|
#ifdef ME
|
|
//fast axial cases
|
|
type = plane->type;
|
|
if (type < 3)
|
|
{
|
|
dist = plane->dist;
|
|
if (dist + PLANESIDE_EPSILON < brush->mins[type]) return PSIDE_FRONT;
|
|
if (dist - PLANESIDE_EPSILON > brush->maxs[type]) return PSIDE_BACK;
|
|
if (brush->mins[type] < dist - PLANESIDE_EPSILON &&
|
|
brush->maxs[type] > dist + PLANESIDE_EPSILON) s = PSIDE_BOTH;
|
|
} //end if
|
|
|
|
if (s != PSIDE_BOTH)
|
|
#endif //ME
|
|
{
|
|
// if the brush actually uses the planenum,
|
|
// we can tell the side for sure
|
|
for (i = 0; i < brush->numsides; i++)
|
|
{
|
|
num = brush->sides[i].planenum;
|
|
if (num >= MAX_MAPFILE_PLANES) Error ("bad planenum");
|
|
if (num == planenum)
|
|
{
|
|
//we don't need to test this side plane again
|
|
brush->sides[i].flags |= SFL_TESTED;
|
|
return PSIDE_BACK|PSIDE_FACING;
|
|
} //end if
|
|
if (num == (planenum ^ 1) )
|
|
{
|
|
//we don't need to test this side plane again
|
|
brush->sides[i].flags |= SFL_TESTED;
|
|
return PSIDE_FRONT|PSIDE_FACING;
|
|
} //end if
|
|
} //end for
|
|
|
|
// box on plane side
|
|
s = BoxOnPlaneSide (brush->mins, brush->maxs, plane);
|
|
|
|
if (s != PSIDE_BOTH) return s;
|
|
} //end if
|
|
|
|
// if both sides, count the visible faces split
|
|
d_front = d_back = 0;
|
|
|
|
for (i = 0; i < brush->numsides; i++)
|
|
{
|
|
if (brush->sides[i].texinfo == TEXINFO_NODE)
|
|
continue; // on node, don't worry about splits
|
|
if (!(brush->sides[i].flags & SFL_VISIBLE))
|
|
continue; // we don't care about non-visible
|
|
w = brush->sides[i].winding;
|
|
if (!w) continue;
|
|
front = back = 0;
|
|
for (j = 0; j < w->numpoints; j++)
|
|
{
|
|
d = DotProduct(w->p[j], plane->normal) - plane->dist;
|
|
if (d > d_front) d_front = d;
|
|
if (d < d_back) d_back = d;
|
|
if (d > 0.1) // PLANESIDE_EPSILON)
|
|
front = 1;
|
|
if (d < -0.1) // PLANESIDE_EPSILON)
|
|
back = 1;
|
|
} //end for
|
|
if (front && back)
|
|
{
|
|
if ( !(brush->sides[i].surf & SURF_SKIP) )
|
|
{
|
|
(*numsplits)++;
|
|
if (brush->sides[i].surf & SURF_HINT)
|
|
{
|
|
*hintsplit = true;
|
|
} //end if
|
|
} //end if
|
|
} //end if
|
|
} //end for
|
|
|
|
if ( (d_front > 0.0 && d_front < 1.0)
|
|
|| (d_back < 0.0 && d_back > -1.0) )
|
|
(*epsilonbrush)++;
|
|
|
|
#if 0
|
|
if (*numsplits == 0)
|
|
{ // didn't really need to be split
|
|
if (front) s = PSIDE_FRONT;
|
|
else if (back) s = PSIDE_BACK;
|
|
else s = 0;
|
|
}
|
|
#endif
|
|
|
|
return s;
|
|
} //end of the function TestBrushToPlanenum
|
|
//===========================================================================
|
|
// Returns true if the winding would be crunched out of
|
|
// existance by the vertex snapping.
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
#define EDGE_LENGTH 0.2
|
|
qboolean WindingIsTiny (winding_t *w)
|
|
{
|
|
#if 0
|
|
if (WindingArea (w) < 1)
|
|
return true;
|
|
return false;
|
|
#else
|
|
int i, j;
|
|
vec_t len;
|
|
vec3_t delta;
|
|
int edges;
|
|
|
|
edges = 0;
|
|
for (i=0 ; i<w->numpoints ; i++)
|
|
{
|
|
j = i == w->numpoints - 1 ? 0 : i+1;
|
|
VectorSubtract (w->p[j], w->p[i], delta);
|
|
len = VectorLength (delta);
|
|
if (len > EDGE_LENGTH)
|
|
{
|
|
if (++edges == 3)
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
#endif
|
|
} //end of the function WindingIsTiny
|
|
//===========================================================================
|
|
// Returns true if the winding still has one of the points
|
|
// from basewinding for plane
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
qboolean WindingIsHuge (winding_t *w)
|
|
{
|
|
int i, j;
|
|
|
|
for (i=0 ; i<w->numpoints ; i++)
|
|
{
|
|
for (j=0 ; j<3 ; j++)
|
|
if (w->p[i][j] < -BOGUS_RANGE+1 || w->p[i][j] > BOGUS_RANGE-1)
|
|
return true;
|
|
}
|
|
return false;
|
|
} //end of the function WindingIsHuge
|
|
//===========================================================================
|
|
// creates a leaf out of the given nodes with the given brushes
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
void LeafNode(node_t *node, bspbrush_t *brushes)
|
|
{
|
|
bspbrush_t *b;
|
|
int i;
|
|
|
|
node->side = NULL;
|
|
node->planenum = PLANENUM_LEAF;
|
|
node->contents = 0;
|
|
|
|
for (b = brushes; b; b = b->next)
|
|
{
|
|
// if the brush is solid and all of its sides are on nodes,
|
|
// it eats everything
|
|
if (b->original->contents & CONTENTS_SOLID)
|
|
{
|
|
for (i=0 ; i<b->numsides ; i++)
|
|
if (b->sides[i].texinfo != TEXINFO_NODE)
|
|
break;
|
|
if (i == b->numsides)
|
|
{
|
|
node->contents = CONTENTS_SOLID;
|
|
break;
|
|
} //end if
|
|
} //end if
|
|
node->contents |= b->original->contents;
|
|
} //end for
|
|
|
|
if (create_aas)
|
|
{
|
|
node->expansionbboxes = 0;
|
|
node->contents = 0;
|
|
for (b = brushes; b; b = b->next)
|
|
{
|
|
node->expansionbboxes |= b->original->expansionbbox;
|
|
node->contents |= b->original->contents;
|
|
if (b->original->modelnum)
|
|
node->modelnum = b->original->modelnum;
|
|
} //end for
|
|
if (node->contents & CONTENTS_SOLID)
|
|
{
|
|
if (node->expansionbboxes != cfg.allpresencetypes)
|
|
{
|
|
node->contents &= ~CONTENTS_SOLID;
|
|
} //end if
|
|
} //end if
|
|
} //end if
|
|
|
|
node->brushlist = brushes;
|
|
} //end of the function LeafNode
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
void CheckPlaneAgainstParents (int pnum, node_t *node)
|
|
{
|
|
node_t *p;
|
|
|
|
for (p = node->parent; p; p = p->parent)
|
|
{
|
|
if (p->planenum == pnum) Error("Tried parent");
|
|
} //end for
|
|
} //end of the function CheckPlaneAgainstParants
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
qboolean CheckPlaneAgainstVolume (int pnum, node_t *node)
|
|
{
|
|
bspbrush_t *front, *back;
|
|
qboolean good;
|
|
|
|
SplitBrush (node->volume, pnum, &front, &back);
|
|
|
|
good = (front && back);
|
|
|
|
if (front) FreeBrush (front);
|
|
if (back) FreeBrush (back);
|
|
|
|
return good;
|
|
} //end of the function CheckPlaneAgaintsVolume
|
|
//===========================================================================
|
|
// Using a hueristic, choses one of the sides out of the brushlist
|
|
// to partition the brushes with.
|
|
// Returns NULL if there are no valid planes to split with..
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
side_t *SelectSplitSide (bspbrush_t *brushes, node_t *node)
|
|
{
|
|
int value, bestvalue;
|
|
bspbrush_t *brush, *test;
|
|
side_t *side, *bestside;
|
|
int i, pass, numpasses;
|
|
int pnum;
|
|
int s;
|
|
int front, back, both, facing, splits;
|
|
int bsplits;
|
|
int bestsplits;
|
|
int epsilonbrush;
|
|
qboolean hintsplit = false;
|
|
|
|
bestside = NULL;
|
|
bestvalue = -99999;
|
|
bestsplits = 0;
|
|
|
|
// the search order goes: visible-structural, visible-detail,
|
|
// nonvisible-structural, nonvisible-detail.
|
|
// If any valid plane is available in a pass, no further
|
|
// passes will be tried.
|
|
numpasses = 2;
|
|
for (pass = 0; pass < numpasses; pass++)
|
|
{
|
|
for (brush = brushes; brush; brush = brush->next)
|
|
{
|
|
// only check detail the second pass
|
|
// if ( (pass & 1) && !(brush->original->contents & CONTENTS_DETAIL) )
|
|
// continue;
|
|
// if ( !(pass & 1) && (brush->original->contents & CONTENTS_DETAIL) )
|
|
// continue;
|
|
for (i = 0; i < brush->numsides; i++)
|
|
{
|
|
side = brush->sides + i;
|
|
// if (side->flags & SFL_BEVEL)
|
|
// continue; // never use a bevel as a spliter
|
|
if (!side->winding)
|
|
continue; // nothing visible, so it can't split
|
|
if (side->texinfo == TEXINFO_NODE)
|
|
continue; // allready a node splitter
|
|
if (side->flags & SFL_TESTED)
|
|
continue; // we allready have metrics for this plane
|
|
// if (side->surf & SURF_SKIP)
|
|
// continue; // skip surfaces are never chosen
|
|
|
|
// if (!(side->flags & SFL_VISIBLE) && (pass < 2))
|
|
// continue; // only check visible faces on first pass
|
|
|
|
if ((side->flags & SFL_CURVE) && (pass < 1))
|
|
continue; // only check curves the second pass
|
|
|
|
pnum = side->planenum;
|
|
pnum &= ~1; // allways use positive facing plane
|
|
|
|
CheckPlaneAgainstParents (pnum, node);
|
|
|
|
if (!CheckPlaneAgainstVolume (pnum, node))
|
|
continue; // would produce a tiny volume
|
|
|
|
front = 0;
|
|
back = 0;
|
|
both = 0;
|
|
facing = 0;
|
|
splits = 0;
|
|
epsilonbrush = 0;
|
|
|
|
//inner loop: optimize
|
|
for (test = brushes; test; test = test->next)
|
|
{
|
|
s = TestBrushToPlanenum (test, pnum, &bsplits, &hintsplit, &epsilonbrush);
|
|
|
|
splits += bsplits;
|
|
// if (bsplits && (s&PSIDE_FACING) )
|
|
// Error ("PSIDE_FACING with splits");
|
|
|
|
test->testside = s;
|
|
//
|
|
if (s & PSIDE_FACING) facing++;
|
|
if (s & PSIDE_FRONT) front++;
|
|
if (s & PSIDE_BACK) back++;
|
|
if (s == PSIDE_BOTH) both++;
|
|
} //end for
|
|
|
|
// give a value estimate for using this plane
|
|
value = 5*facing - 5*splits - abs(front-back);
|
|
// value = -5*splits;
|
|
// value = 5*facing - 5*splits;
|
|
if (mapplanes[pnum].type < 3)
|
|
value+=5; // axial is better
|
|
|
|
value -= epsilonbrush * 1000; // avoid!
|
|
|
|
// never split a hint side except with another hint
|
|
if (hintsplit && !(side->surf & SURF_HINT) )
|
|
value = -9999999;
|
|
|
|
// save off the side test so we don't need
|
|
// to recalculate it when we actually seperate
|
|
// the brushes
|
|
if (value > bestvalue)
|
|
{
|
|
bestvalue = value;
|
|
bestside = side;
|
|
bestsplits = splits;
|
|
for (test = brushes; test ; test = test->next)
|
|
test->side = test->testside;
|
|
} //end if
|
|
} //end for
|
|
} //end for (brush = brushes;
|
|
|
|
// if we found a good plane, don't bother trying any
|
|
// other passes
|
|
if (bestside)
|
|
{
|
|
if (pass > 1)
|
|
{
|
|
if (numthreads == 1) c_nonvis++;
|
|
}
|
|
if (pass > 0) node->detail_seperator = true; // not needed for vis
|
|
break;
|
|
} //end if
|
|
} //end for (pass = 0;
|
|
|
|
//
|
|
// clear all the tested flags we set
|
|
//
|
|
for (brush = brushes ; brush ; brush=brush->next)
|
|
{
|
|
for (i = 0; i < brush->numsides; i++)
|
|
{
|
|
brush->sides[i].flags &= ~SFL_TESTED;
|
|
} //end for
|
|
} //end for
|
|
|
|
return bestside;
|
|
} //end of the function SelectSplitSide
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
int BrushMostlyOnSide (bspbrush_t *brush, plane_t *plane)
|
|
{
|
|
int i, j;
|
|
winding_t *w;
|
|
vec_t d, max;
|
|
int side;
|
|
|
|
max = 0;
|
|
side = PSIDE_FRONT;
|
|
for (i=0 ; i<brush->numsides ; i++)
|
|
{
|
|
w = brush->sides[i].winding;
|
|
if (!w)
|
|
continue;
|
|
for (j=0 ; j<w->numpoints ; j++)
|
|
{
|
|
d = DotProduct (w->p[j], plane->normal) - plane->dist;
|
|
if (d > max)
|
|
{
|
|
max = d;
|
|
side = PSIDE_FRONT;
|
|
}
|
|
if (-d > max)
|
|
{
|
|
max = -d;
|
|
side = PSIDE_BACK;
|
|
}
|
|
}
|
|
}
|
|
return side;
|
|
} //end of the function BrushMostlyOnSide
|
|
//===========================================================================
|
|
// Generates two new brushes, leaving the original
|
|
// unchanged
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
void SplitBrush (bspbrush_t *brush, int planenum,
|
|
bspbrush_t **front, bspbrush_t **back)
|
|
{
|
|
bspbrush_t *b[2];
|
|
int i, j;
|
|
winding_t *w, *cw[2], *midwinding;
|
|
plane_t *plane, *plane2;
|
|
side_t *s, *cs;
|
|
float d, d_front, d_back;
|
|
|
|
*front = *back = NULL;
|
|
plane = &mapplanes[planenum];
|
|
|
|
// check all points
|
|
d_front = d_back = 0;
|
|
for (i=0 ; i<brush->numsides ; i++)
|
|
{
|
|
w = brush->sides[i].winding;
|
|
if (!w)
|
|
continue;
|
|
for (j=0 ; j<w->numpoints ; j++)
|
|
{
|
|
d = DotProduct (w->p[j], plane->normal) - plane->dist;
|
|
if (d > 0 && d > d_front)
|
|
d_front = d;
|
|
if (d < 0 && d < d_back)
|
|
d_back = d;
|
|
}
|
|
}
|
|
|
|
if (d_front < 0.2) // PLANESIDE_EPSILON)
|
|
{ // only on back
|
|
*back = CopyBrush (brush);
|
|
return;
|
|
}
|
|
if (d_back > -0.2) // PLANESIDE_EPSILON)
|
|
{ // only on front
|
|
*front = CopyBrush (brush);
|
|
return;
|
|
}
|
|
|
|
// create a new winding from the split plane
|
|
|
|
w = BaseWindingForPlane (plane->normal, plane->dist);
|
|
for (i=0 ; i<brush->numsides && w ; i++)
|
|
{
|
|
plane2 = &mapplanes[brush->sides[i].planenum ^ 1];
|
|
ChopWindingInPlace (&w, plane2->normal, plane2->dist, 0); // PLANESIDE_EPSILON);
|
|
}
|
|
|
|
if (!w || WindingIsTiny(w))
|
|
{ // the brush isn't really split
|
|
int side;
|
|
|
|
side = BrushMostlyOnSide (brush, plane);
|
|
if (side == PSIDE_FRONT)
|
|
*front = CopyBrush (brush);
|
|
if (side == PSIDE_BACK)
|
|
*back = CopyBrush (brush);
|
|
//free a possible winding
|
|
if (w) FreeWinding(w);
|
|
return;
|
|
}
|
|
|
|
if (WindingIsHuge (w))
|
|
{
|
|
Log_Write("WARNING: huge winding\n");
|
|
}
|
|
|
|
midwinding = w;
|
|
|
|
// split it for real
|
|
|
|
for (i=0 ; i<2 ; i++)
|
|
{
|
|
b[i] = AllocBrush (brush->numsides+1);
|
|
b[i]->original = brush->original;
|
|
}
|
|
|
|
// split all the current windings
|
|
|
|
for (i=0 ; i<brush->numsides ; i++)
|
|
{
|
|
s = &brush->sides[i];
|
|
w = s->winding;
|
|
if (!w)
|
|
continue;
|
|
ClipWindingEpsilon (w, plane->normal, plane->dist,
|
|
0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1]);
|
|
for (j=0 ; j<2 ; j++)
|
|
{
|
|
if (!cw[j])
|
|
continue;
|
|
#if 0
|
|
if (WindingIsTiny (cw[j]))
|
|
{
|
|
FreeWinding (cw[j]);
|
|
continue;
|
|
}
|
|
#endif
|
|
cs = &b[j]->sides[b[j]->numsides];
|
|
b[j]->numsides++;
|
|
*cs = *s;
|
|
// cs->planenum = s->planenum;
|
|
// cs->texinfo = s->texinfo;
|
|
// cs->original = s->original;
|
|
cs->winding = cw[j];
|
|
cs->flags &= ~SFL_TESTED;
|
|
}
|
|
}
|
|
|
|
|
|
// see if we have valid polygons on both sides
|
|
|
|
for (i=0 ; i<2 ; i++)
|
|
{
|
|
BoundBrush (b[i]);
|
|
for (j=0 ; j<3 ; j++)
|
|
{
|
|
if (b[i]->mins[j] < -MAX_MAP_BOUNDS || b[i]->maxs[j] > MAX_MAP_BOUNDS)
|
|
{
|
|
Log_Write("bogus brush after clip");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (b[i]->numsides < 3 || j < 3)
|
|
{
|
|
FreeBrush (b[i]);
|
|
b[i] = NULL;
|
|
}
|
|
}
|
|
|
|
if ( !(b[0] && b[1]) )
|
|
{
|
|
if (!b[0] && !b[1])
|
|
Log_Write("split removed brush\r\n");
|
|
else
|
|
Log_Write("split not on both sides\r\n");
|
|
if (b[0])
|
|
{
|
|
FreeBrush (b[0]);
|
|
*front = CopyBrush (brush);
|
|
}
|
|
if (b[1])
|
|
{
|
|
FreeBrush (b[1]);
|
|
*back = CopyBrush (brush);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// add the midwinding to both sides
|
|
for (i=0 ; i<2 ; i++)
|
|
{
|
|
cs = &b[i]->sides[b[i]->numsides];
|
|
b[i]->numsides++;
|
|
|
|
cs->planenum = planenum^i^1;
|
|
cs->texinfo = TEXINFO_NODE; //never use these sides as splitters
|
|
cs->flags &= ~SFL_VISIBLE;
|
|
cs->flags &= ~SFL_TESTED;
|
|
if (i==0)
|
|
cs->winding = CopyWinding (midwinding);
|
|
else
|
|
cs->winding = midwinding;
|
|
}
|
|
|
|
{
|
|
vec_t v1;
|
|
int i;
|
|
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
v1 = BrushVolume (b[i]);
|
|
if (v1 < 1.0)
|
|
{
|
|
FreeBrush(b[i]);
|
|
b[i] = NULL;
|
|
//Log_Write("tiny volume after clip");
|
|
}
|
|
}
|
|
if (!b[0] && !b[1])
|
|
{
|
|
Log_Write("two tiny brushes\r\n");
|
|
} //end if
|
|
}
|
|
|
|
*front = b[0];
|
|
*back = b[1];
|
|
} //end of the function SplitBrush
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
void SplitBrushList (bspbrush_t *brushes,
|
|
node_t *node, bspbrush_t **front, bspbrush_t **back)
|
|
{
|
|
bspbrush_t *brush, *newbrush, *newbrush2;
|
|
side_t *side;
|
|
int sides;
|
|
int i;
|
|
|
|
*front = *back = NULL;
|
|
|
|
for (brush = brushes; brush; brush = brush->next)
|
|
{
|
|
sides = brush->side;
|
|
|
|
if (sides == PSIDE_BOTH)
|
|
{ // split into two brushes
|
|
SplitBrush (brush, node->planenum, &newbrush, &newbrush2);
|
|
if (newbrush)
|
|
{
|
|
newbrush->next = *front;
|
|
*front = newbrush;
|
|
} //end if
|
|
if (newbrush2)
|
|
{
|
|
newbrush2->next = *back;
|
|
*back = newbrush2;
|
|
} //end if
|
|
continue;
|
|
} //end if
|
|
|
|
newbrush = CopyBrush (brush);
|
|
|
|
// if the planenum is actualy a part of the brush
|
|
// find the plane and flag it as used so it won't be tried
|
|
// as a splitter again
|
|
if (sides & PSIDE_FACING)
|
|
{
|
|
for (i=0 ; i<newbrush->numsides ; i++)
|
|
{
|
|
side = newbrush->sides + i;
|
|
if ( (side->planenum& ~1) == node->planenum)
|
|
side->texinfo = TEXINFO_NODE;
|
|
} //end for
|
|
} //end if
|
|
if (sides & PSIDE_FRONT)
|
|
{
|
|
newbrush->next = *front;
|
|
*front = newbrush;
|
|
continue;
|
|
} //end if
|
|
if (sides & PSIDE_BACK)
|
|
{
|
|
newbrush->next = *back;
|
|
*back = newbrush;
|
|
continue;
|
|
} //end if
|
|
} //end for
|
|
} //end of the function SplitBrushList
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
void CheckBrushLists(bspbrush_t *brushlist1, bspbrush_t *brushlist2)
|
|
{
|
|
bspbrush_t *brush1, *brush2;
|
|
|
|
for (brush1 = brushlist1; brush1; brush1 = brush1->next)
|
|
{
|
|
for (brush2 = brushlist2; brush2; brush2 = brush2->next)
|
|
{
|
|
assert(brush1 != brush2);
|
|
} //end for
|
|
} //end for
|
|
} //end of the function CheckBrushLists
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
int numrecurse = 0;
|
|
|
|
node_t *BuildTree_r (node_t *node, bspbrush_t *brushes)
|
|
{
|
|
node_t *newnode;
|
|
side_t *bestside;
|
|
int i, totalmem;
|
|
bspbrush_t *children[2];
|
|
|
|
qprintf("\r%6d", numrecurse);
|
|
numrecurse++;
|
|
|
|
if (numthreads == 1)
|
|
{
|
|
totalmem = WindingMemory() + c_nodememory + c_brushmemory;
|
|
if (totalmem > c_peak_totalbspmemory)
|
|
c_peak_totalbspmemory = totalmem;
|
|
c_nodes++;
|
|
} //endif
|
|
|
|
if (drawflag)
|
|
DrawBrushList(brushes, node);
|
|
|
|
// find the best plane to use as a splitter
|
|
bestside = SelectSplitSide (brushes, node);
|
|
if (!bestside)
|
|
{
|
|
// leaf node
|
|
node->side = NULL;
|
|
node->planenum = -1;
|
|
LeafNode(node, brushes);
|
|
if (node->contents & CONTENTS_SOLID) c_solidleafnodes++;
|
|
if (create_aas)
|
|
{
|
|
//free up memory!!!
|
|
FreeBrushList(node->brushlist);
|
|
node->brushlist = NULL;
|
|
//free the node volume brush
|
|
if (node->volume)
|
|
{
|
|
FreeBrush(node->volume);
|
|
node->volume = NULL;
|
|
} //end if
|
|
} //end if
|
|
return node;
|
|
} //end if
|
|
|
|
// this is a splitplane node
|
|
node->side = bestside;
|
|
node->planenum = bestside->planenum & ~1; // always use front facing
|
|
|
|
//split the brush list in two for both children
|
|
SplitBrushList (brushes, node, &children[0], &children[1]);
|
|
//free the old brush list
|
|
FreeBrushList (brushes);
|
|
|
|
// allocate children before recursing
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
newnode = AllocNode ();
|
|
newnode->parent = node;
|
|
node->children[i] = newnode;
|
|
} //end for
|
|
|
|
//split the volume brush of the node for the children
|
|
SplitBrush (node->volume, node->planenum, &node->children[0]->volume,
|
|
&node->children[1]->volume);
|
|
|
|
if (create_aas)
|
|
{
|
|
//free the volume brush
|
|
if (node->volume)
|
|
{
|
|
FreeBrush(node->volume);
|
|
node->volume = NULL;
|
|
} //end if
|
|
} //end if
|
|
// recursively process children
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
node->children[i] = BuildTree_r(node->children[i], children[i]);
|
|
} //end for
|
|
|
|
return node;
|
|
} //end of the function BuildTree_r
|
|
//===========================================================================
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
node_t *firstnode; //first node in the list
|
|
node_t *lastnode; //last node in the list
|
|
int nodelistsize; //number of nodes in the list
|
|
int use_nodequeue = 0; //use nodequeue, otherwise a node stack is used
|
|
int numwaiting = 0;
|
|
|
|
void (*AddNodeToList)(node_t *node);
|
|
|
|
//add the node to the front of the node list
|
|
//(effectively using a node stack)
|
|
void AddNodeToStack(node_t *node)
|
|
{
|
|
ThreadLock();
|
|
|
|
node->next = firstnode;
|
|
firstnode = node;
|
|
if (!lastnode) lastnode = node;
|
|
nodelistsize++;
|
|
|
|
ThreadUnlock();
|
|
//
|
|
ThreadSemaphoreIncrease(1);
|
|
} //end of the function AddNodeToStack
|
|
//add the node to the end of the node list
|
|
//(effectively using a node queue)
|
|
void AddNodeToQueue(node_t *node)
|
|
{
|
|
ThreadLock();
|
|
|
|
node->next = NULL;
|
|
if (lastnode) lastnode->next = node;
|
|
else firstnode = node;
|
|
lastnode = node;
|
|
nodelistsize++;
|
|
|
|
ThreadUnlock();
|
|
//
|
|
ThreadSemaphoreIncrease(1);
|
|
} //end of the function AddNodeToQueue
|
|
//get the first node from the front of the node list
|
|
node_t *NextNodeFromList(void)
|
|
{
|
|
node_t *node;
|
|
|
|
ThreadLock();
|
|
numwaiting++;
|
|
if (!firstnode)
|
|
{
|
|
if (numwaiting >= GetNumThreads()) ThreadSemaphoreIncrease(GetNumThreads());
|
|
} //end if
|
|
ThreadUnlock();
|
|
|
|
ThreadSemaphoreWait();
|
|
|
|
ThreadLock();
|
|
|
|
numwaiting--;
|
|
|
|
node = firstnode;
|
|
if (firstnode)
|
|
{
|
|
firstnode = firstnode->next;
|
|
nodelistsize--;
|
|
} //end if
|
|
if (!firstnode) lastnode = NULL;
|
|
|
|
ThreadUnlock();
|
|
|
|
return node;
|
|
} //end of the function NextNodeFromList
|
|
//returns the size of the node list
|
|
int NodeListSize(void)
|
|
{
|
|
int size;
|
|
|
|
ThreadLock();
|
|
size = nodelistsize;
|
|
ThreadUnlock();
|
|
|
|
return size;
|
|
} //end of the function NodeListSize
|
|
//
|
|
void IncreaseNodeCounter(void)
|
|
{
|
|
ThreadLock();
|
|
//if (verbose) printf("\r%6d", numrecurse++);
|
|
qprintf("\r%6d", numrecurse++);
|
|
//qprintf("\r%6d %d, %5d ", numrecurse++, GetNumThreads(), nodelistsize);
|
|
ThreadUnlock();
|
|
} //end of the function IncreaseNodeCounter
|
|
//thread function, gets nodes from the nodelist and processes them
|
|
void BuildTreeThread(int threadid)
|
|
{
|
|
node_t *newnode, *node;
|
|
side_t *bestside;
|
|
int i, totalmem;
|
|
bspbrush_t *brushes;
|
|
|
|
for (node = NextNodeFromList(); node; )
|
|
{
|
|
//if the nodelist isn't empty try to add another thread
|
|
//if (NodeListSize() > 10) AddThread(BuildTreeThread);
|
|
//display the number of nodes processed so far
|
|
if (numthreads == 1)
|
|
IncreaseNodeCounter();
|
|
|
|
brushes = node->brushlist;
|
|
|
|
if (numthreads == 1)
|
|
{
|
|
totalmem = WindingMemory() + c_nodememory + c_brushmemory;
|
|
if (totalmem > c_peak_totalbspmemory)
|
|
{
|
|
c_peak_totalbspmemory = totalmem;
|
|
} //end if
|
|
c_nodes++;
|
|
} //endif
|
|
|
|
if (drawflag)
|
|
{
|
|
DrawBrushList(brushes, node);
|
|
} //end if
|
|
|
|
if (cancelconversion)
|
|
{
|
|
bestside = NULL;
|
|
} //end if
|
|
else
|
|
{
|
|
// find the best plane to use as a splitter
|
|
bestside = SelectSplitSide(brushes, node);
|
|
} //end else
|
|
//if there's no split side left
|
|
if (!bestside)
|
|
{
|
|
//create a leaf out of the node
|
|
LeafNode(node, brushes);
|
|
if (node->contents & CONTENTS_SOLID) c_solidleafnodes++;
|
|
if (create_aas)
|
|
{
|
|
//free up memory!!!
|
|
FreeBrushList(node->brushlist);
|
|
node->brushlist = NULL;
|
|
} //end if
|
|
//free the node volume brush (it is not used anymore)
|
|
if (node->volume)
|
|
{
|
|
FreeBrush(node->volume);
|
|
node->volume = NULL;
|
|
} //end if
|
|
node = NextNodeFromList();
|
|
continue;
|
|
} //end if
|
|
|
|
// this is a splitplane node
|
|
node->side = bestside;
|
|
node->planenum = bestside->planenum & ~1; //always use front facing
|
|
|
|
//allocate children
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
newnode = AllocNode();
|
|
newnode->parent = node;
|
|
node->children[i] = newnode;
|
|
} //end for
|
|
|
|
//split the brush list in two for both children
|
|
SplitBrushList(brushes, node, &node->children[0]->brushlist, &node->children[1]->brushlist);
|
|
|
|
CheckBrushLists(node->children[0]->brushlist, node->children[1]->brushlist);
|
|
//free the old brush list
|
|
FreeBrushList(brushes);
|
|
node->brushlist = NULL;
|
|
|
|
//split the volume brush of the node for the children
|
|
SplitBrush(node->volume, node->planenum, &node->children[0]->volume,
|
|
&node->children[1]->volume);
|
|
|
|
if (!node->children[0]->volume || !node->children[1]->volume)
|
|
{
|
|
Error("child without volume brush");
|
|
} //end if
|
|
|
|
//free the volume brush
|
|
if (node->volume)
|
|
{
|
|
FreeBrush(node->volume);
|
|
node->volume = NULL;
|
|
} //end if
|
|
//add both children to the node list
|
|
//AddNodeToList(node->children[0]);
|
|
AddNodeToList(node->children[1]);
|
|
node = node->children[0];
|
|
} //end while
|
|
RemoveThread(threadid);
|
|
} //end of the function BuildTreeThread
|
|
//===========================================================================
|
|
// build the bsp tree using a node list
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
void BuildTree(tree_t *tree)
|
|
{
|
|
int i;
|
|
|
|
firstnode = NULL;
|
|
lastnode = NULL;
|
|
//use a node queue or node stack
|
|
if (use_nodequeue) AddNodeToList = AddNodeToQueue;
|
|
else AddNodeToList = AddNodeToStack;
|
|
//setup thread locking
|
|
ThreadSetupLock();
|
|
ThreadSetupSemaphore();
|
|
numwaiting = 0;
|
|
//
|
|
Log_Print("%6d threads max\n", numthreads);
|
|
if (use_nodequeue) Log_Print("breadth first bsp building\n");
|
|
else Log_Print("depth first bsp building\n");
|
|
qprintf("%6d splits", 0);
|
|
//add the first node to the list
|
|
AddNodeToList(tree->headnode);
|
|
//start the threads
|
|
for (i = 0; i < numthreads; i++)
|
|
AddThread(BuildTreeThread);
|
|
//wait for all added threads to be finished
|
|
WaitForAllThreadsFinished();
|
|
//shutdown the thread locking
|
|
ThreadShutdownLock();
|
|
ThreadShutdownSemaphore();
|
|
} //end of the function BuildTree
|
|
//===========================================================================
|
|
// The incoming brush list will be freed before exiting
|
|
//
|
|
// Parameter: -
|
|
// Returns: -
|
|
// Changes Globals: -
|
|
//===========================================================================
|
|
tree_t *BrushBSP(bspbrush_t *brushlist, vec3_t mins, vec3_t maxs)
|
|
{
|
|
int i, c_faces, c_nonvisfaces, c_brushes;
|
|
bspbrush_t *b;
|
|
node_t *node;
|
|
tree_t *tree;
|
|
vec_t volume;
|
|
// vec3_t point;
|
|
|
|
Log_Print("-------- Brush BSP ---------\n");
|
|
|
|
tree = Tree_Alloc();
|
|
|
|
c_faces = 0;
|
|
c_nonvisfaces = 0;
|
|
c_brushes = 0;
|
|
c_totalsides = 0;
|
|
for (b = brushlist; b; b = b->next)
|
|
{
|
|
c_brushes++;
|
|
|
|
volume = BrushVolume(b);
|
|
if (volume < microvolume)
|
|
{
|
|
Log_Print("WARNING: entity %i, brush %i: microbrush\n",
|
|
b->original->entitynum, b->original->brushnum);
|
|
} //end if
|
|
|
|
for (i=0 ; i<b->numsides ; i++)
|
|
{
|
|
if (b->sides[i].flags & SFL_BEVEL)
|
|
continue;
|
|
if (!b->sides[i].winding)
|
|
continue;
|
|
if (b->sides[i].texinfo == TEXINFO_NODE)
|
|
continue;
|
|
if (b->sides[i].flags & SFL_VISIBLE)
|
|
{
|
|
c_faces++;
|
|
} //end if
|
|
else
|
|
{
|
|
c_nonvisfaces++;
|
|
//if (create_aas) b->sides[i].texinfo = TEXINFO_NODE;
|
|
} //end if
|
|
} //end for
|
|
c_totalsides += b->numsides;
|
|
|
|
AddPointToBounds (b->mins, tree->mins, tree->maxs);
|
|
AddPointToBounds (b->maxs, tree->mins, tree->maxs);
|
|
} //end for
|
|
|
|
Log_Print("%6i brushes\n", c_brushes);
|
|
Log_Print("%6i visible faces\n", c_faces);
|
|
Log_Print("%6i nonvisible faces\n", c_nonvisfaces);
|
|
Log_Print("%6i total sides\n", c_totalsides);
|
|
|
|
c_active_brushes = c_brushes;
|
|
c_nodememory = 0;
|
|
c_brushmemory = 0;
|
|
c_peak_brushmemory = 0;
|
|
|
|
c_nodes = 0;
|
|
c_nonvis = 0;
|
|
node = AllocNode ();
|
|
|
|
//volume of first node (head node)
|
|
node->volume = BrushFromBounds (mins, maxs);
|
|
//
|
|
tree->headnode = node;
|
|
//just get some statistics and the mins/maxs of the node
|
|
numrecurse = 0;
|
|
// qprintf("%6d splits", numrecurse);
|
|
|
|
tree->headnode->brushlist = brushlist;
|
|
BuildTree(tree);
|
|
|
|
//build the bsp tree with the start node from the brushlist
|
|
// node = BuildTree_r(node, brushlist);
|
|
|
|
//if the conversion is cancelled
|
|
if (cancelconversion) return tree;
|
|
|
|
qprintf("\n");
|
|
Log_Write("%6d splits\r\n", numrecurse);
|
|
// Log_Print("%6i visible nodes\n", c_nodes/2 - c_nonvis);
|
|
// Log_Print("%6i nonvis nodes\n", c_nonvis);
|
|
// Log_Print("%6i leaves\n", (c_nodes+1)/2);
|
|
// Log_Print("%6i solid leaf nodes\n", c_solidleafnodes);
|
|
// Log_Print("%6i active brushes\n", c_active_brushes);
|
|
if (numthreads == 1)
|
|
{
|
|
// Log_Print("%6i KB of node memory\n", c_nodememory >> 10);
|
|
// Log_Print("%6i KB of brush memory\n", c_brushmemory >> 10);
|
|
// Log_Print("%6i KB of peak brush memory\n", c_peak_brushmemory >> 10);
|
|
// Log_Print("%6i KB of winding memory\n", WindingMemory() >> 10);
|
|
// Log_Print("%6i KB of peak winding memory\n", WindingPeakMemory() >> 10);
|
|
Log_Print("%6i KB of peak total bsp memory\n", c_peak_totalbspmemory >> 10);
|
|
} //end if
|
|
|
|
/*
|
|
point[0] = 1485;
|
|
point[1] = 956.125;
|
|
point[2] = 352.125;
|
|
node = PointInLeaf(tree->headnode, point);
|
|
if (node->planenum != PLANENUM_LEAF)
|
|
{
|
|
Log_Print("node not a leaf\n");
|
|
} //end if
|
|
Log_Print("at %f %f %f:\n", point[0], point[1], point[2]);
|
|
PrintContents(node->contents);
|
|
Log_Print("node->expansionbboxes = %d\n", node->expansionbboxes);
|
|
//*/
|
|
return tree;
|
|
} //end of the function BrushBSP
|
|
|