mirror of
https://github.com/dhewm/dhewm3.git
synced 2025-02-05 07:51:32 +00:00
736ec20d4d
Don't include the lazy precompiled.h everywhere, only what's required for the compilation unit. platform.h needs to be included instead to provide all essential defines and types. All includes use the relative path to the neo or the game specific root. Move all idlib related includes from idlib/Lib.h to precompiled.h. precompiled.h still exists for the MFC stuff in tools/. Add some missing header guards.
2148 lines
47 KiB
C++
2148 lines
47 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "sys/platform.h"
|
|
#include "framework/FileSystem.h"
|
|
|
|
#include "tools/compilers/aas/BrushBSP.h"
|
|
|
|
#define BSP_GRID_SIZE 512.0f
|
|
#define SPLITTER_EPSILON 0.1f
|
|
#define VERTEX_MELT_EPSILON 0.1f
|
|
#define VERTEX_MELT_HASH_SIZE 32
|
|
|
|
#define PORTAL_PLANE_NORMAL_EPSILON 0.00001f
|
|
#define PORTAL_PLANE_DIST_EPSILON 0.01f
|
|
|
|
//#define OUPUT_BSP_STATS_PER_GRID_CELL
|
|
|
|
//===============================================================
|
|
//
|
|
// idBrushBSPPortal
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
============
|
|
idBrushBSPPortal::idBrushBSPPortal
|
|
============
|
|
*/
|
|
idBrushBSPPortal::idBrushBSPPortal( void ) {
|
|
planeNum = -1;
|
|
winding = NULL;
|
|
nodes[0] = nodes[1] = NULL;
|
|
next[0] = next[1] = NULL;
|
|
faceNum = 0;
|
|
flags = 0;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSPPortal::~idBrushBSPPortal
|
|
============
|
|
*/
|
|
idBrushBSPPortal::~idBrushBSPPortal( void ) {
|
|
if ( winding ) {
|
|
delete winding;
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSPPortal::AddToNodes
|
|
============
|
|
*/
|
|
void idBrushBSPPortal::AddToNodes( idBrushBSPNode *front, idBrushBSPNode *back ) {
|
|
if ( nodes[0] || nodes[1] ) {
|
|
common->Error( "AddToNode: allready included" );
|
|
}
|
|
|
|
assert( front && back );
|
|
|
|
nodes[0] = front;
|
|
next[0] = front->portals;
|
|
front->portals = this;
|
|
|
|
nodes[1] = back;
|
|
next[1] = back->portals;
|
|
back->portals = this;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSPPortal::RemoveFromNode
|
|
============
|
|
*/
|
|
void idBrushBSPPortal::RemoveFromNode( idBrushBSPNode *l ) {
|
|
idBrushBSPPortal **pp, *t;
|
|
|
|
// remove reference to the current portal
|
|
pp = &l->portals;
|
|
while (1)
|
|
{
|
|
t = *pp;
|
|
if ( !t ) {
|
|
common->Error( "idBrushBSPPortal::RemoveFromNode: portal not in node" );
|
|
}
|
|
|
|
if ( t == this ) {
|
|
break;
|
|
}
|
|
|
|
if ( t->nodes[0] == l ) {
|
|
pp = &t->next[0];
|
|
}
|
|
else if ( t->nodes[1] == l ) {
|
|
pp = &t->next[1];
|
|
}
|
|
else {
|
|
common->Error( "idBrushBSPPortal::RemoveFromNode: portal not bounding node" );
|
|
}
|
|
}
|
|
|
|
if ( nodes[0] == l ) {
|
|
*pp = next[0];
|
|
nodes[0] = NULL;
|
|
}
|
|
else if ( nodes[1] == l ) {
|
|
*pp = next[1];
|
|
nodes[1] = NULL;
|
|
}
|
|
else {
|
|
common->Error( "idBrushBSPPortal::RemoveFromNode: mislinked portal" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSPPortal::Flip
|
|
============
|
|
*/
|
|
void idBrushBSPPortal::Flip( void ) {
|
|
idBrushBSPNode *frontNode, *backNode;
|
|
|
|
frontNode = nodes[0];
|
|
backNode = nodes[1];
|
|
|
|
if ( frontNode ) {
|
|
RemoveFromNode( frontNode );
|
|
}
|
|
if ( backNode ) {
|
|
RemoveFromNode( backNode );
|
|
}
|
|
AddToNodes( frontNode, backNode );
|
|
|
|
plane = -plane;
|
|
planeNum ^= 1;
|
|
winding->ReverseSelf();
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSPPortal::Split
|
|
============
|
|
*/
|
|
int idBrushBSPPortal::Split( const idPlane &splitPlane, idBrushBSPPortal **front, idBrushBSPPortal **back ) {
|
|
idWinding *frontWinding, *backWinding;
|
|
|
|
(*front) = (*back) = NULL;
|
|
winding->Split( splitPlane, 0.1f, &frontWinding, &backWinding );
|
|
if ( frontWinding ) {
|
|
(*front) = new idBrushBSPPortal();
|
|
(*front)->plane = plane;
|
|
(*front)->planeNum = planeNum;
|
|
(*front)->flags = flags;
|
|
(*front)->winding = frontWinding;
|
|
}
|
|
if ( backWinding ) {
|
|
(*back) = new idBrushBSPPortal();
|
|
(*back)->plane = plane;
|
|
(*back)->planeNum = planeNum;
|
|
(*back)->flags = flags;
|
|
(*back)->winding = backWinding;
|
|
}
|
|
|
|
if ( frontWinding && backWinding ) {
|
|
return PLANESIDE_CROSS;
|
|
}
|
|
else if ( frontWinding ) {
|
|
return PLANESIDE_FRONT;
|
|
}
|
|
else {
|
|
return PLANESIDE_BACK;
|
|
}
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idBrushBSPNode
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
============
|
|
idBrushBSPNode::idBrushBSPNode
|
|
============
|
|
*/
|
|
idBrushBSPNode::idBrushBSPNode( void ) {
|
|
brushList.Clear();
|
|
contents = 0;
|
|
flags = 0;
|
|
volume = NULL;
|
|
portals = NULL;
|
|
children[0] = children[1] = NULL;
|
|
areaNum = 0;
|
|
occupied = 0;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSPNode::~idBrushBSPNode
|
|
============
|
|
*/
|
|
idBrushBSPNode::~idBrushBSPNode( void ) {
|
|
idBrushBSPPortal *p;
|
|
|
|
// delete brushes
|
|
brushList.Free();
|
|
|
|
// delete volume brush
|
|
if ( volume ) {
|
|
delete volume;
|
|
}
|
|
|
|
// delete portals
|
|
for ( p = portals; p; p = portals ) {
|
|
p->RemoveFromNode( this );
|
|
if ( !p->nodes[0] && !p->nodes[1] ) {
|
|
delete p;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSPNode::SetContentsFromBrushes
|
|
============
|
|
*/
|
|
void idBrushBSPNode::SetContentsFromBrushes( void ) {
|
|
idBrush *brush;
|
|
|
|
contents = 0;
|
|
for ( brush = brushList.Head(); brush; brush = brush->Next() ) {
|
|
contents |= brush->GetContents();
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSPNode::GetPortalBounds
|
|
============
|
|
*/
|
|
idBounds idBrushBSPNode::GetPortalBounds( void ) {
|
|
int s, i;
|
|
idBrushBSPPortal *p;
|
|
idBounds bounds;
|
|
|
|
bounds.Clear();
|
|
for ( p = portals; p; p = p->next[s] ) {
|
|
s = (p->nodes[1] == this);
|
|
|
|
for ( i = 0; i < p->winding->GetNumPoints(); i++ ) {
|
|
bounds.AddPoint( (*p->winding)[i].ToVec3() );
|
|
}
|
|
}
|
|
return bounds;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSPNode::TestLeafNode
|
|
============
|
|
*/
|
|
bool idBrushBSPNode::TestLeafNode( void ) {
|
|
int s, n;
|
|
float d;
|
|
idBrushBSPPortal *p;
|
|
idVec3 center;
|
|
idPlane plane;
|
|
|
|
n = 0;
|
|
center = vec3_origin;
|
|
for ( p = portals; p; p = p->next[s] ) {
|
|
s = (p->nodes[1] == this);
|
|
center += p->winding->GetCenter();
|
|
n++;
|
|
}
|
|
|
|
center /= n;
|
|
|
|
for ( p = portals; p; p = p->next[s] ) {
|
|
s = (p->nodes[1] == this);
|
|
if ( s ) {
|
|
plane = -p->GetPlane();
|
|
}
|
|
else {
|
|
plane = p->GetPlane();
|
|
}
|
|
d = plane.Distance( center );
|
|
if ( d < 0.0f ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSPNode::Split
|
|
============
|
|
*/
|
|
bool idBrushBSPNode::Split( const idPlane &splitPlane, int splitPlaneNum ) {
|
|
int s, i;
|
|
idWinding *mid;
|
|
idBrushBSPPortal *p, *midPortal, *newPortals[2];
|
|
idBrushBSPNode *newNodes[2];
|
|
|
|
mid = new idWinding( splitPlane.Normal(), splitPlane.Dist() );
|
|
|
|
for ( p = portals; p && mid; p = p->next[s] ) {
|
|
s = (p->nodes[1] == this);
|
|
if ( s ) {
|
|
mid = mid->Clip( -p->plane, 0.1f, false );
|
|
}
|
|
else {
|
|
mid = mid->Clip( p->plane, 0.1f, false );
|
|
}
|
|
}
|
|
|
|
if ( !mid ) {
|
|
return false;
|
|
}
|
|
|
|
// allocate two new nodes
|
|
for ( i = 0; i < 2; i++ ) {
|
|
newNodes[i] = new idBrushBSPNode();
|
|
newNodes[i]->flags = flags;
|
|
newNodes[i]->contents = contents;
|
|
newNodes[i]->parent = this;
|
|
}
|
|
|
|
// split all portals of the node
|
|
for ( p = portals; p; p = portals ) {
|
|
s = (p->nodes[1] == this);
|
|
p->Split( splitPlane, &newPortals[0], &newPortals[1] );
|
|
for ( i = 0; i < 2; i++ ) {
|
|
if ( newPortals[i] ) {
|
|
if ( s ) {
|
|
newPortals[i]->AddToNodes( p->nodes[0], newNodes[i] );
|
|
}
|
|
else {
|
|
newPortals[i]->AddToNodes( newNodes[i], p->nodes[1] );
|
|
}
|
|
}
|
|
}
|
|
p->RemoveFromNode( p->nodes[0] );
|
|
p->RemoveFromNode( p->nodes[1] );
|
|
delete p;
|
|
}
|
|
|
|
// add seperating portal
|
|
midPortal = new idBrushBSPPortal();
|
|
midPortal->plane = splitPlane;
|
|
midPortal->planeNum = splitPlaneNum;
|
|
midPortal->winding = mid;
|
|
midPortal->AddToNodes( newNodes[0], newNodes[1] );
|
|
|
|
// set new child nodes
|
|
children[0] = newNodes[0];
|
|
children[1] = newNodes[1];
|
|
plane = splitPlane;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSPNode::PlaneSide
|
|
============
|
|
*/
|
|
int idBrushBSPNode::PlaneSide( const idPlane &plane, float epsilon ) const {
|
|
int s, side;
|
|
idBrushBSPPortal *p;
|
|
bool front, back;
|
|
|
|
front = back = false;
|
|
for ( p = portals; p; p = p->next[s] ) {
|
|
s = (p->nodes[1] == this);
|
|
|
|
side = p->winding->PlaneSide( plane, epsilon );
|
|
if ( side == SIDE_CROSS || side == SIDE_ON) {
|
|
return side;
|
|
}
|
|
if ( side == SIDE_FRONT ) {
|
|
if ( back ) {
|
|
return SIDE_CROSS;
|
|
}
|
|
front = true;
|
|
}
|
|
if ( side == SIDE_BACK ) {
|
|
if ( front ) {
|
|
return SIDE_CROSS;
|
|
}
|
|
back = true;
|
|
}
|
|
}
|
|
|
|
if ( front ) {
|
|
return SIDE_FRONT;
|
|
}
|
|
return SIDE_BACK;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSPNode::RemoveFlagFlood
|
|
============
|
|
*/
|
|
void idBrushBSPNode::RemoveFlagFlood( int flag ) {
|
|
int s;
|
|
idBrushBSPPortal *p;
|
|
|
|
RemoveFlag( flag );
|
|
|
|
for ( p = GetPortals(); p; p = p->Next(s) ) {
|
|
s = (p->GetNode(1) == this);
|
|
|
|
if ( !(p->GetNode( !s )->GetFlags() & flag ) ) {
|
|
continue;
|
|
}
|
|
|
|
p->GetNode( !s )->RemoveFlagFlood( flag );
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSPNode::RemoveFlagRecurse
|
|
============
|
|
*/
|
|
void idBrushBSPNode::RemoveFlagRecurse( int flag ) {
|
|
RemoveFlag( flag );
|
|
if ( children[0] ) {
|
|
children[0]->RemoveFlagRecurse( flag );
|
|
}
|
|
if ( children[1] ) {
|
|
children[1]->RemoveFlagRecurse( flag );
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSPNode::RemoveFlagRecurseFlood
|
|
============
|
|
*/
|
|
void idBrushBSPNode::RemoveFlagRecurseFlood( int flag ) {
|
|
RemoveFlag( flag );
|
|
if ( !children[0] && !children[1] ) {
|
|
RemoveFlagFlood( flag );
|
|
}
|
|
else {
|
|
if ( children[0] ) {
|
|
children[0]->RemoveFlagRecurseFlood( flag );
|
|
}
|
|
if ( children[1] ) {
|
|
children[1]->RemoveFlagRecurseFlood( flag );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//===============================================================
|
|
//
|
|
// idBrushBSP
|
|
//
|
|
//===============================================================
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::idBrushBSP
|
|
============
|
|
*/
|
|
idBrushBSP::idBrushBSP( void ) {
|
|
root = outside = NULL;
|
|
numSplits = numPrunedSplits = 0;
|
|
brushMapContents = 0;
|
|
brushMap = NULL;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::~idBrushBSP
|
|
============
|
|
*/
|
|
idBrushBSP::~idBrushBSP( void ) {
|
|
|
|
RemoveMultipleLeafNodeReferences_r( root );
|
|
Free_r( root );
|
|
|
|
if ( outside ) {
|
|
delete outside;
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::RemoveMultipleLeafNodeReferences_r
|
|
============
|
|
*/
|
|
void idBrushBSP::RemoveMultipleLeafNodeReferences_r( idBrushBSPNode *node ) {
|
|
if ( !node ) {
|
|
return;
|
|
}
|
|
|
|
if ( node->children[0] ) {
|
|
if ( node->children[0]->parent != node ) {
|
|
node->children[0] = NULL;
|
|
}
|
|
else {
|
|
RemoveMultipleLeafNodeReferences_r( node->children[0] );
|
|
}
|
|
}
|
|
if ( node->children[1] ) {
|
|
if ( node->children[1]->parent != node ) {
|
|
node->children[1] = NULL;
|
|
}
|
|
else {
|
|
RemoveMultipleLeafNodeReferences_r( node->children[1] );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::Free_r
|
|
============
|
|
*/
|
|
void idBrushBSP::Free_r( idBrushBSPNode *node ) {
|
|
if ( !node ) {
|
|
return;
|
|
}
|
|
|
|
Free_r( node->children[0] );
|
|
Free_r( node->children[1] );
|
|
|
|
delete node;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::IsValidSplitter
|
|
============
|
|
*/
|
|
ID_INLINE bool idBrushBSP::IsValidSplitter( const idBrushSide *side ) {
|
|
return !( side->GetFlags() & ( SFL_SPLIT | SFL_USED_SPLITTER ) );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::BrushSplitterStats
|
|
============
|
|
*/
|
|
typedef struct splitterStats_s {
|
|
int numFront; // number of brushes at the front of the splitter
|
|
int numBack; // number of brushes at the back of the splitter
|
|
int numSplits; // number of brush sides split by the splitter
|
|
int numFacing; // number of brushes facing this splitter
|
|
int epsilonBrushes; // number of tiny brushes this splitter would create
|
|
} splitterStats_t;
|
|
|
|
int idBrushBSP::BrushSplitterStats( const idBrush *brush, int planeNum, const idPlaneSet &planeList, bool *testedPlanes, struct splitterStats_s &stats ) {
|
|
int i, j, num, s, lastNumSplits;
|
|
const idPlane *plane;
|
|
const idWinding *w;
|
|
float d, d_front, d_back, brush_front, brush_back;
|
|
|
|
plane = &planeList[planeNum];
|
|
|
|
// get the plane side for the brush bounds
|
|
s = brush->GetBounds().PlaneSide( *plane, SPLITTER_EPSILON );
|
|
if ( s == PLANESIDE_FRONT ) {
|
|
stats.numFront++;
|
|
return BRUSH_PLANESIDE_FRONT;
|
|
}
|
|
if ( s == PLANESIDE_BACK ) {
|
|
stats.numBack++;
|
|
return BRUSH_PLANESIDE_BACK;
|
|
}
|
|
|
|
// if the brush actually uses the planenum, we can tell the side for sure
|
|
for ( i = 0; i < brush->GetNumSides(); i++ ) {
|
|
num = brush->GetSide( i )->GetPlaneNum();
|
|
|
|
if ( !(( num ^ planeNum ) >> 1) ) {
|
|
if ( num == planeNum ) {
|
|
stats.numBack++;
|
|
stats.numFacing++;
|
|
return ( BRUSH_PLANESIDE_BACK | BRUSH_PLANESIDE_FACING );
|
|
}
|
|
if ( num == ( planeNum ^ 1 ) ) {
|
|
stats.numFront++;
|
|
stats.numFacing++;
|
|
return ( BRUSH_PLANESIDE_FRONT | BRUSH_PLANESIDE_FACING );
|
|
}
|
|
}
|
|
}
|
|
|
|
lastNumSplits = stats.numSplits;
|
|
brush_front = brush_back = 0.0f;
|
|
for ( i = 0; i < brush->GetNumSides(); i++ ) {
|
|
|
|
if ( !IsValidSplitter( brush->GetSide( i ) ) ) {
|
|
continue;
|
|
}
|
|
|
|
j = brush->GetSide( i )->GetPlaneNum();
|
|
if ( testedPlanes[j] || testedPlanes[j^1] ) {
|
|
continue;
|
|
}
|
|
|
|
w = brush->GetSide(i)->GetWinding();
|
|
if ( !w ) {
|
|
continue;
|
|
}
|
|
d_front = d_back = 0.0f;
|
|
for ( j = 0; j < w->GetNumPoints(); j++ ) {
|
|
d = plane->Distance( (*w)[j].ToVec3() );
|
|
if ( d > d_front ) {
|
|
d_front = d;
|
|
}
|
|
else if ( d < d_back ) {
|
|
d_back = d;
|
|
}
|
|
}
|
|
if ( d_front > SPLITTER_EPSILON && d_back < -SPLITTER_EPSILON ) {
|
|
stats.numSplits++;
|
|
}
|
|
if ( d_front > brush_front ) {
|
|
brush_front = d_front;
|
|
}
|
|
else if ( d_back < brush_back ) {
|
|
brush_back = d_back;
|
|
}
|
|
}
|
|
|
|
// if brush sides are split and the brush only pokes one unit through the plane
|
|
if ( stats.numSplits > lastNumSplits && (brush_front < 1.0f || brush_back > -1.0f) ) {
|
|
stats.epsilonBrushes++;
|
|
}
|
|
|
|
return BRUSH_PLANESIDE_BOTH;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::FindSplitter
|
|
============
|
|
*/
|
|
int idBrushBSP::FindSplitter( idBrushBSPNode *node, const idPlaneSet &planeList, bool *testedPlanes, struct splitterStats_s &bestStats ) {
|
|
int i, planeNum, bestSplitter, value, bestValue, f, numBrushSides;
|
|
idBrush *brush, *b;
|
|
splitterStats_t stats;
|
|
|
|
memset( testedPlanes, 0, planeList.Num() * sizeof( bool ) );
|
|
|
|
bestSplitter = -1;
|
|
bestValue = -99999999;
|
|
for ( brush = node->brushList.Head(); brush; brush = brush->Next() ) {
|
|
|
|
if ( brush->GetFlags() & BFL_NO_VALID_SPLITTERS ) {
|
|
continue;
|
|
}
|
|
|
|
for ( i = 0; i < brush->GetNumSides(); i++ ) {
|
|
|
|
if ( !IsValidSplitter( brush->GetSide(i) ) ) {
|
|
continue;
|
|
}
|
|
|
|
planeNum = brush->GetSide(i)->GetPlaneNum();
|
|
|
|
if ( testedPlanes[planeNum] || testedPlanes[planeNum^1] ) {
|
|
continue;
|
|
}
|
|
|
|
testedPlanes[planeNum] = testedPlanes[planeNum^1] = true;
|
|
|
|
if ( node->volume->Split( planeList[planeNum], planeNum, NULL, NULL ) != PLANESIDE_CROSS ) {
|
|
continue;
|
|
}
|
|
|
|
memset( &stats, 0, sizeof( stats ) );
|
|
|
|
f = 15 + 5 * (brush->GetSide(i)->GetPlane().Type() < PLANETYPE_TRUEAXIAL);
|
|
numBrushSides = node->brushList.NumSides();
|
|
|
|
for ( b = node->brushList.Head(); b; b = b->Next() ) {
|
|
|
|
// if the brush has no valid splitters left
|
|
if ( b->GetFlags() & BFL_NO_VALID_SPLITTERS ) {
|
|
b->SetPlaneSide( BRUSH_PLANESIDE_BOTH );
|
|
}
|
|
else {
|
|
b->SetPlaneSide( BrushSplitterStats( b, planeNum, planeList, testedPlanes, stats ) );
|
|
}
|
|
|
|
numBrushSides -= b->GetNumSides();
|
|
// best value we can get using this plane as a splitter
|
|
value = f * (stats.numFacing + numBrushSides) - 10 * stats.numSplits - stats.epsilonBrushes * 1000;
|
|
// if the best value for this plane can't get any better than the best value we have
|
|
if ( value < bestValue ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( b ) {
|
|
continue;
|
|
}
|
|
|
|
value = f * stats.numFacing - 10 * stats.numSplits - abs(stats.numFront - stats.numBack) - stats.epsilonBrushes * 1000;
|
|
|
|
if ( value > bestValue ) {
|
|
bestValue = value;
|
|
bestSplitter = planeNum;
|
|
bestStats = stats;
|
|
|
|
for ( b = node->brushList.Head(); b; b = b->Next() ) {
|
|
b->SavePlaneSide();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bestSplitter;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::SetSplitterUsed
|
|
============
|
|
*/
|
|
void idBrushBSP::SetSplitterUsed( idBrushBSPNode *node, int planeNum ) {
|
|
int i, numValidBrushSplitters;
|
|
idBrush *brush;
|
|
|
|
for ( brush = node->brushList.Head(); brush; brush = brush->Next() ) {
|
|
if ( !( brush->GetSavedPlaneSide() & BRUSH_PLANESIDE_FACING ) ) {
|
|
continue;
|
|
}
|
|
numValidBrushSplitters = 0;
|
|
for ( i = 0; i < brush->GetNumSides(); i++ ) {
|
|
|
|
if ( !(( brush->GetSide(i)->GetPlaneNum() ^ planeNum ) >> 1) ) {
|
|
brush->GetSide(i)->SetFlag( SFL_USED_SPLITTER );
|
|
}
|
|
else if ( IsValidSplitter( brush->GetSide(i) ) ) {
|
|
numValidBrushSplitters++;
|
|
}
|
|
}
|
|
if ( numValidBrushSplitters == 0 ) {
|
|
brush->SetFlag( BFL_NO_VALID_SPLITTERS );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::BuildBrushBSP_r
|
|
============
|
|
*/
|
|
idBrushBSPNode *idBrushBSP::BuildBrushBSP_r( idBrushBSPNode *node, const idPlaneSet &planeList, bool *testedPlanes, int skipContents ) {
|
|
int planeNum;
|
|
splitterStats_t bestStats;
|
|
|
|
planeNum = FindSplitter( node, planeList, testedPlanes, bestStats );
|
|
|
|
// if no split plane found this is a leaf node
|
|
if ( planeNum == -1 ) {
|
|
|
|
node->SetContentsFromBrushes();
|
|
|
|
if ( brushMap && ( node->contents & brushMapContents ) ) {
|
|
brushMap->WriteBrush( node->volume );
|
|
}
|
|
|
|
// free node memory
|
|
node->brushList.Free();
|
|
delete node->volume;
|
|
node->volume = NULL;
|
|
|
|
node->children[0] = node->children[1] = NULL;
|
|
return node;
|
|
}
|
|
|
|
numSplits++;
|
|
numGridCellSplits++;
|
|
|
|
// mark all brush sides on the split plane as used
|
|
SetSplitterUsed( node, planeNum );
|
|
|
|
// set node split plane
|
|
node->plane = planeList[planeNum];
|
|
|
|
// allocate children
|
|
node->children[0] = new idBrushBSPNode();
|
|
node->children[1] = new idBrushBSPNode();
|
|
|
|
// split node volume and brush list for children
|
|
node->volume->Split( node->plane, -1, &node->children[0]->volume, &node->children[1]->volume );
|
|
node->brushList.Split( node->plane, -1, node->children[0]->brushList, node->children[1]->brushList, true );
|
|
node->children[0]->parent = node->children[1]->parent = node;
|
|
|
|
// free node memory
|
|
node->brushList.Free();
|
|
delete node->volume;
|
|
node->volume = NULL;
|
|
|
|
// process children
|
|
node->children[0] = BuildBrushBSP_r( node->children[0], planeList, testedPlanes, skipContents );
|
|
node->children[1] = BuildBrushBSP_r( node->children[1], planeList, testedPlanes, skipContents );
|
|
|
|
// if both children contain the skip contents
|
|
if ( node->children[0]->contents & node->children[1]->contents & skipContents ) {
|
|
node->contents = node->children[0]->contents | node->children[1]->contents;
|
|
delete node->children[0];
|
|
delete node->children[1];
|
|
node->children[0] = node->children[1] = NULL;
|
|
numSplits--;
|
|
numGridCellSplits--;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::ProcessGridCell
|
|
============
|
|
*/
|
|
idBrushBSPNode *idBrushBSP::ProcessGridCell( idBrushBSPNode *node, int skipContents ) {
|
|
idPlaneSet planeList;
|
|
bool *testedPlanes;
|
|
|
|
#ifdef OUPUT_BSP_STATS_PER_GRID_CELL
|
|
common->Printf( "[Grid Cell %d]\n", ++numGridCells );
|
|
common->Printf( "%6d brushes\n", node->brushList.Num() );
|
|
#endif
|
|
|
|
numGridCellSplits = 0;
|
|
|
|
// chop away all brush overlap
|
|
node->brushList.Chop( BrushChopAllowed );
|
|
|
|
// merge brushes if possible
|
|
//node->brushList.Merge( BrushMergeAllowed );
|
|
|
|
// create a list with planes for this grid cell
|
|
node->brushList.CreatePlaneList( planeList );
|
|
|
|
#ifdef OUPUT_BSP_STATS_PER_GRID_CELL
|
|
common->Printf( "[Grid Cell BSP]\n" );
|
|
#endif
|
|
|
|
testedPlanes = new bool[planeList.Num()];
|
|
|
|
BuildBrushBSP_r( node, planeList, testedPlanes, skipContents );
|
|
|
|
delete testedPlanes;
|
|
|
|
#ifdef OUPUT_BSP_STATS_PER_GRID_CELL
|
|
common->Printf( "\r%6d splits\n", numGridCellSplits );
|
|
#endif
|
|
|
|
return node;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::BuildGrid_r
|
|
============
|
|
*/
|
|
void idBrushBSP::BuildGrid_r( idList<idBrushBSPNode *> &gridCells, idBrushBSPNode *node ) {
|
|
int axis;
|
|
float dist;
|
|
idBounds bounds;
|
|
idVec3 normal, halfSize;
|
|
|
|
if ( !node->brushList.Num() ) {
|
|
delete node->volume;
|
|
node->volume = NULL;
|
|
node->children[0] = node->children[1] = NULL;
|
|
return;
|
|
}
|
|
|
|
bounds = node->volume->GetBounds();
|
|
halfSize = (bounds[1] - bounds[0]) * 0.5f;
|
|
for ( axis = 0; axis < 3; axis++ ) {
|
|
if ( halfSize[axis] > BSP_GRID_SIZE ) {
|
|
dist = BSP_GRID_SIZE * ( floor( (bounds[0][axis] + halfSize[axis]) / BSP_GRID_SIZE ) + 1 );
|
|
}
|
|
else {
|
|
dist = BSP_GRID_SIZE * ( floor( bounds[0][axis] / BSP_GRID_SIZE ) + 1 );
|
|
}
|
|
if ( dist > bounds[0][axis] + 1.0f && dist < bounds[1][axis] - 1.0f ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( axis >= 3 ) {
|
|
gridCells.Append( node );
|
|
return;
|
|
}
|
|
|
|
numSplits++;
|
|
|
|
normal = vec3_origin;
|
|
normal[axis] = 1.0f;
|
|
node->plane.SetNormal( normal );
|
|
node->plane.SetDist( (int) dist );
|
|
|
|
// allocate children
|
|
node->children[0] = new idBrushBSPNode();
|
|
node->children[1] = new idBrushBSPNode();
|
|
|
|
// split volume and brush list for children
|
|
node->volume->Split( node->plane, -1, &node->children[0]->volume, &node->children[1]->volume );
|
|
node->brushList.Split( node->plane, -1, node->children[0]->brushList, node->children[1]->brushList );
|
|
node->children[0]->brushList.SetFlagOnFacingBrushSides( node->plane, SFL_USED_SPLITTER );
|
|
node->children[1]->brushList.SetFlagOnFacingBrushSides( node->plane, SFL_USED_SPLITTER );
|
|
node->children[0]->parent = node->children[1]->parent = node;
|
|
|
|
// free node memory
|
|
node->brushList.Free();
|
|
delete node->volume;
|
|
node->volume = NULL;
|
|
|
|
// process children
|
|
BuildGrid_r( gridCells, node->children[0] );
|
|
BuildGrid_r( gridCells, node->children[1] );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::Build
|
|
============
|
|
*/
|
|
void idBrushBSP::Build( idBrushList brushList, int skipContents,
|
|
bool (*ChopAllowed)( idBrush *b1, idBrush *b2 ),
|
|
bool (*MergeAllowed)( idBrush *b1, idBrush *b2 ) ) {
|
|
|
|
int i;
|
|
idList<idBrushBSPNode *> gridCells;
|
|
|
|
common->Printf( "[Brush BSP]\n" );
|
|
common->Printf( "%6d brushes\n", brushList.Num() );
|
|
|
|
BrushChopAllowed = ChopAllowed;
|
|
BrushMergeAllowed = MergeAllowed;
|
|
|
|
numGridCells = 0;
|
|
treeBounds = brushList.GetBounds();
|
|
root = new idBrushBSPNode();
|
|
root->brushList = brushList;
|
|
root->volume = new idBrush();
|
|
root->volume->FromBounds( treeBounds );
|
|
root->parent = NULL;
|
|
|
|
BuildGrid_r( gridCells, root );
|
|
|
|
common->Printf( "\r%6d grid cells\n", gridCells.Num() );
|
|
|
|
#ifdef OUPUT_BSP_STATS_PER_GRID_CELL
|
|
for ( i = 0; i < gridCells.Num(); i++ ) {
|
|
ProcessGridCell( gridCells[i], skipContents );
|
|
}
|
|
#else
|
|
common->Printf( "\r%6d %%", 0 );
|
|
for ( i = 0; i < gridCells.Num(); i++ ) {
|
|
DisplayRealTimeString( "\r%6d", i * 100 / gridCells.Num() );
|
|
ProcessGridCell( gridCells[i], skipContents );
|
|
}
|
|
common->Printf( "\r%6d %%\n", 100 );
|
|
#endif
|
|
|
|
common->Printf( "\r%6d splits\n", numSplits );
|
|
|
|
if ( brushMap ) {
|
|
delete brushMap;
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::WriteBrushMap
|
|
============
|
|
*/
|
|
void idBrushBSP::WriteBrushMap( const idStr &fileName, const idStr &ext, int contents ) {
|
|
brushMap = new idBrushMap( fileName, ext );
|
|
brushMapContents = contents;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::PruneTree_r
|
|
============
|
|
*/
|
|
void idBrushBSP::PruneTree_r( idBrushBSPNode *node, int contents ) {
|
|
int i, s;
|
|
idBrushBSPNode *nodes[2];
|
|
idBrushBSPPortal *p, *nextp;
|
|
|
|
if ( !node->children[0] || !node->children[1] ) {
|
|
return;
|
|
}
|
|
|
|
PruneTree_r( node->children[0], contents );
|
|
PruneTree_r( node->children[1], contents );
|
|
|
|
if ( ( node->children[0]->contents & node->children[1]->contents & contents ) ) {
|
|
|
|
node->contents = node->children[0]->contents | node->children[1]->contents;
|
|
// move all child portals to parent
|
|
for ( i = 0; i < 2; i++ ) {
|
|
for ( p = node->children[i]->portals; p; p = nextp ) {
|
|
s = ( p->nodes[1] == node->children[i] );
|
|
nextp = p->next[s];
|
|
nodes[s] = node;
|
|
nodes[!s] = p->nodes[!s];
|
|
p->RemoveFromNode( p->nodes[0] );
|
|
p->RemoveFromNode( p->nodes[1] );
|
|
if ( nodes[!s] == node->children[!i] ) {
|
|
delete p; // portal seperates both children
|
|
}
|
|
else {
|
|
p->AddToNodes( nodes[0], nodes[1] );
|
|
}
|
|
}
|
|
}
|
|
|
|
delete node->children[0];
|
|
delete node->children[1];
|
|
node->children[0] = NULL;
|
|
node->children[1] = NULL;
|
|
|
|
numPrunedSplits++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::PruneTree
|
|
============
|
|
*/
|
|
void idBrushBSP::PruneTree( int contents ) {
|
|
numPrunedSplits = 0;
|
|
common->Printf( "[Prune BSP]\n" );
|
|
PruneTree_r( root, contents );
|
|
common->Printf( "%6d splits pruned\n", numPrunedSplits );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::BaseWindingForNode
|
|
============
|
|
*/
|
|
#define BASE_WINDING_EPSILON 0.001f
|
|
|
|
idWinding *idBrushBSP::BaseWindingForNode( idBrushBSPNode *node ) {
|
|
idWinding *w;
|
|
idBrushBSPNode *n;
|
|
|
|
w = new idWinding( node->plane.Normal(), node->plane.Dist() );
|
|
|
|
// clip by all the parents
|
|
for ( n = node->parent; n && w; n = n->parent ) {
|
|
|
|
if ( n->children[0] == node ) {
|
|
// take front
|
|
w = w->Clip( n->plane, BASE_WINDING_EPSILON );
|
|
}
|
|
else {
|
|
// take back
|
|
w = w->Clip( -n->plane, BASE_WINDING_EPSILON );
|
|
}
|
|
node = n;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::MakeNodePortal
|
|
|
|
create the new portal by taking the full plane winding for the cutting
|
|
plane and clipping it by all of parents of this node
|
|
============
|
|
*/
|
|
void idBrushBSP::MakeNodePortal( idBrushBSPNode *node ) {
|
|
idBrushBSPPortal *newPortal, *p;
|
|
idWinding *w;
|
|
int side = 0;
|
|
|
|
w = BaseWindingForNode( node );
|
|
|
|
// clip the portal by all the other portals in the node
|
|
for ( p = node->portals; p && w; p = p->next[side] ) {
|
|
if ( p->nodes[0] == node ) {
|
|
side = 0;
|
|
w = w->Clip( p->plane, 0.1f );
|
|
}
|
|
else if ( p->nodes[1] == node ) {
|
|
side = 1;
|
|
w = w->Clip( -p->plane, 0.1f );
|
|
}
|
|
else {
|
|
common->Error( "MakeNodePortal: mislinked portal" );
|
|
}
|
|
}
|
|
|
|
if ( !w ) {
|
|
return;
|
|
}
|
|
|
|
if ( w->IsTiny() ) {
|
|
delete w;
|
|
return;
|
|
}
|
|
|
|
newPortal = new idBrushBSPPortal();
|
|
newPortal->plane = node->plane;
|
|
newPortal->winding = w;
|
|
newPortal->AddToNodes( node->children[0], node->children[1] );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::SplitNodePortals
|
|
|
|
Move or split the portals that bound the node so that the node's children have portals instead of node.
|
|
============
|
|
*/
|
|
#define SPLIT_WINDING_EPSILON 0.001f
|
|
|
|
void idBrushBSP::SplitNodePortals( idBrushBSPNode *node ) {
|
|
int side;
|
|
idBrushBSPPortal *p, *nextPortal, *newPortal;
|
|
idBrushBSPNode *f, *b, *otherNode;
|
|
idPlane *plane;
|
|
idWinding *frontWinding, *backWinding;
|
|
|
|
plane = &node->plane;
|
|
f = node->children[0];
|
|
b = node->children[1];
|
|
|
|
for ( p = node->portals; p; p = nextPortal ) {
|
|
if (p->nodes[0] == node) {
|
|
side = 0;
|
|
}
|
|
else if (p->nodes[1] == node) {
|
|
side = 1;
|
|
}
|
|
else {
|
|
common->Error( "idBrushBSP::SplitNodePortals: mislinked portal" );
|
|
return;
|
|
}
|
|
nextPortal = p->next[side];
|
|
|
|
otherNode = p->nodes[!side];
|
|
p->RemoveFromNode( p->nodes[0] );
|
|
p->RemoveFromNode( p->nodes[1] );
|
|
|
|
// cut the portal into two portals, one on each side of the cut plane
|
|
p->winding->Split( *plane, SPLIT_WINDING_EPSILON, &frontWinding, &backWinding );
|
|
|
|
if ( frontWinding && frontWinding->IsTiny() ) {
|
|
delete frontWinding;
|
|
frontWinding = NULL;
|
|
//tinyportals++;
|
|
}
|
|
|
|
if ( backWinding && backWinding->IsTiny() ) {
|
|
delete backWinding;
|
|
backWinding = NULL;
|
|
//tinyportals++;
|
|
}
|
|
|
|
if ( !frontWinding && !backWinding ) {
|
|
// tiny windings on both sides
|
|
continue;
|
|
}
|
|
|
|
if ( !frontWinding ) {
|
|
delete backWinding;
|
|
if ( side == 0 ) {
|
|
p->AddToNodes( b, otherNode );
|
|
}
|
|
else {
|
|
p->AddToNodes( otherNode, b );
|
|
}
|
|
continue;
|
|
}
|
|
if ( !backWinding ) {
|
|
delete frontWinding;
|
|
if ( side == 0 ) {
|
|
p->AddToNodes( f, otherNode );
|
|
}
|
|
else {
|
|
p->AddToNodes( otherNode, f );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// the winding is split
|
|
newPortal = new idBrushBSPPortal();
|
|
*newPortal = *p;
|
|
newPortal->winding = backWinding;
|
|
delete p->winding;
|
|
p->winding = frontWinding;
|
|
|
|
if ( side == 0 ) {
|
|
p->AddToNodes( f, otherNode );
|
|
newPortal->AddToNodes( b, otherNode );
|
|
}
|
|
else {
|
|
p->AddToNodes( otherNode, f );
|
|
newPortal->AddToNodes( otherNode, b );
|
|
}
|
|
}
|
|
|
|
node->portals = NULL;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::MakeTreePortals_r
|
|
============
|
|
*/
|
|
void idBrushBSP::MakeTreePortals_r( idBrushBSPNode *node ) {
|
|
int i;
|
|
idBounds bounds;
|
|
|
|
numPortals++;
|
|
DisplayRealTimeString( "\r%6d", numPortals );
|
|
|
|
bounds = node->GetPortalBounds();
|
|
|
|
if ( bounds[0][0] >= bounds[1][0] ) {
|
|
//common->Warning( "node without volume" );
|
|
}
|
|
|
|
for ( i = 0; i < 3; i++ ) {
|
|
if ( bounds[0][i] < MIN_WORLD_COORD || bounds[1][i] > MAX_WORLD_COORD ) {
|
|
common->Warning( "node with unbounded volume" );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !node->children[0] || !node->children[1] ) {
|
|
return;
|
|
}
|
|
|
|
MakeNodePortal( node );
|
|
SplitNodePortals( node );
|
|
|
|
MakeTreePortals_r( node->children[0] );
|
|
MakeTreePortals_r( node->children[1] );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::MakeOutsidePortals
|
|
============
|
|
*/
|
|
void idBrushBSP::MakeOutsidePortals( void ) {
|
|
int i, j, n;
|
|
idBounds bounds;
|
|
idBrushBSPPortal *p, *portals[6];
|
|
idVec3 normal;
|
|
|
|
// pad with some space so there will never be null volume leaves
|
|
bounds = treeBounds.Expand( 32 );
|
|
|
|
for ( i = 0; i < 3; i++ ) {
|
|
if ( bounds[0][i] > bounds[1][i] ) {
|
|
common->Error( "empty BSP tree" );
|
|
}
|
|
}
|
|
|
|
outside = new idBrushBSPNode();
|
|
outside->parent = NULL;
|
|
outside->children[0] = outside->children[1] = NULL;
|
|
outside->brushList.Clear();
|
|
outside->portals = NULL;
|
|
outside->contents = 0;
|
|
|
|
for ( i = 0; i < 3; i++ ) {
|
|
for ( j = 0; j < 2; j++ ) {
|
|
|
|
p = new idBrushBSPPortal();
|
|
normal = vec3_origin;
|
|
normal[i] = j ? -1 : 1;
|
|
p->plane.SetNormal( normal );
|
|
p->plane.SetDist( j ? -bounds[j][i] : bounds[j][i] );
|
|
p->winding = new idWinding( p->plane.Normal(), p->plane.Dist() );
|
|
p->AddToNodes( root, outside );
|
|
|
|
n = j * 3 + i;
|
|
portals[n] = p;
|
|
}
|
|
}
|
|
|
|
// clip the base windings with all the other planes
|
|
for ( i = 0; i < 6; i++ ) {
|
|
for ( j = 0; j < 6; j++ ) {
|
|
if (j == i) {
|
|
continue;
|
|
}
|
|
portals[i]->winding = portals[i]->winding->Clip( portals[j]->plane, ON_EPSILON );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::Portalize
|
|
============
|
|
*/
|
|
void idBrushBSP::Portalize( void ) {
|
|
common->Printf( "[Portalize BSP]\n" );
|
|
common->Printf( "%6d nodes\n", (numSplits - numPrunedSplits) * 2 + 1 );
|
|
numPortals = 0;
|
|
MakeOutsidePortals();
|
|
MakeTreePortals_r( root );
|
|
common->Printf( "\r%6d nodes portalized\n", numPortals );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
LeakFile
|
|
|
|
Finds the shortest possible chain of portals that
|
|
leads from the outside leaf to a specific occupied leaf.
|
|
=============
|
|
*/
|
|
void idBrushBSP::LeakFile( const idStr &fileName ) {
|
|
int count, next, s;
|
|
idVec3 mid;
|
|
idFile *lineFile;
|
|
idBrushBSPNode *node, *nextNode = NULL;
|
|
idBrushBSPPortal *p, *nextPortal = NULL;
|
|
idStr qpath, name;
|
|
|
|
if ( !outside->occupied ) {
|
|
return;
|
|
}
|
|
|
|
qpath = fileName;
|
|
qpath.SetFileExtension( "lin" );
|
|
|
|
common->Printf( "writing %s...\n", qpath.c_str() );
|
|
|
|
lineFile = fileSystem->OpenFileWrite( qpath, "fs_devpath" );
|
|
if ( !lineFile ) {
|
|
common->Error( "Couldn't open %s\n", qpath.c_str() );
|
|
return;
|
|
}
|
|
|
|
count = 0;
|
|
node = outside;
|
|
while( node->occupied > 1 ) {
|
|
|
|
// find the best portal exit
|
|
next = node->occupied;
|
|
for (p = node->portals; p; p = p->next[!s] ) {
|
|
s = (p->nodes[0] == node);
|
|
if ( p->nodes[s]->occupied && p->nodes[s]->occupied < next ) {
|
|
nextPortal = p;
|
|
nextNode = p->nodes[s];
|
|
next = nextNode->occupied;
|
|
}
|
|
}
|
|
node = nextNode;
|
|
mid = nextPortal->winding->GetCenter();
|
|
lineFile->Printf( "%f %f %f\n", mid[0], mid[1], mid[2] );
|
|
count++;
|
|
}
|
|
|
|
// add the origin of the entity from which the leak was found
|
|
lineFile->Printf( "%f %f %f\n", leakOrigin[0], leakOrigin[1], leakOrigin[2] );
|
|
|
|
fileSystem->CloseFile( lineFile );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::FloodThroughPortals_r
|
|
============
|
|
*/
|
|
void idBrushBSP::FloodThroughPortals_r( idBrushBSPNode *node, int contents, int depth ) {
|
|
idBrushBSPPortal *p;
|
|
int s;
|
|
|
|
if ( !node ) {
|
|
common->Error( "FloodThroughPortals_r: NULL node\n" );
|
|
}
|
|
|
|
if ( node->occupied ) {
|
|
common->Error( "FloodThroughPortals_r: node already occupied\n" );
|
|
}
|
|
node->occupied = depth;
|
|
|
|
for ( p = node->portals; p; p = p->next[s] ) {
|
|
s = (p->nodes[1] == node);
|
|
|
|
// if the node at the other side of the portal is removed
|
|
if ( !p->nodes[!s] ) {
|
|
continue;
|
|
}
|
|
|
|
// if the node at the other side of the portal is occupied already
|
|
if ( p->nodes[!s]->occupied ) {
|
|
continue;
|
|
}
|
|
|
|
// can't flood through the portal if it has the seperating contents at the other side
|
|
if ( p->nodes[!s]->contents & contents ) {
|
|
continue;
|
|
}
|
|
|
|
// flood recursively through the current portal
|
|
FloodThroughPortals_r( p->nodes[!s], contents, depth+1 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::FloodFromOrigin
|
|
============
|
|
*/
|
|
bool idBrushBSP::FloodFromOrigin( const idVec3 &origin, int contents ) {
|
|
idBrushBSPNode *node;
|
|
|
|
//find the leaf to start in
|
|
node = root;
|
|
while( node->children[0] && node->children[1] ) {
|
|
|
|
if ( node->plane.Side( origin ) == PLANESIDE_BACK ) {
|
|
node = node->children[1];
|
|
}
|
|
else {
|
|
node = node->children[0];
|
|
}
|
|
}
|
|
|
|
if ( !node ) {
|
|
return false;
|
|
}
|
|
|
|
// if inside the inside/outside seperating contents
|
|
if ( node->contents & contents ) {
|
|
return false;
|
|
}
|
|
|
|
// if the node is already occupied
|
|
if ( node->occupied ) {
|
|
return false;
|
|
}
|
|
|
|
FloodThroughPortals_r( node, contents, 1 );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::FloodFromEntities
|
|
|
|
Marks all nodes that can be reached by entites.
|
|
============
|
|
*/
|
|
bool idBrushBSP::FloodFromEntities( const idMapFile *mapFile, int contents, const idStrList &classNames ) {
|
|
int i, j;
|
|
bool inside;
|
|
idVec3 origin;
|
|
idMapEntity *mapEnt;
|
|
idStr classname;
|
|
|
|
inside = false;
|
|
outside->occupied = 0;
|
|
|
|
// skip the first entity which is assumed to be the worldspawn
|
|
for ( i = 1; i < mapFile->GetNumEntities(); i++ ) {
|
|
|
|
mapEnt = mapFile->GetEntity( i );
|
|
|
|
if ( !mapEnt->epairs.GetVector( "origin", "", origin ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( !mapEnt->epairs.GetString( "classname", "", classname ) ) {
|
|
continue;
|
|
}
|
|
|
|
for ( j = 0; j < classNames.Num(); j++ ) {
|
|
if ( classname.Icmp( classNames[j] ) == 0 ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( j >= classNames.Num() ) {
|
|
continue;
|
|
}
|
|
|
|
origin[2] += 1;
|
|
|
|
// nudge around a little
|
|
if ( FloodFromOrigin( origin, contents ) ) {
|
|
inside = true;
|
|
}
|
|
|
|
if ( outside->occupied ) {
|
|
leakOrigin = origin;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !inside ) {
|
|
common->Warning( "no entities inside" );
|
|
}
|
|
else if ( outside->occupied ) {
|
|
common->Warning( "reached outside from entity %d (%s)", i, classname.c_str() );
|
|
}
|
|
|
|
return ( inside && !outside->occupied );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::RemoveOutside_r
|
|
============
|
|
*/
|
|
void idBrushBSP::RemoveOutside_r( idBrushBSPNode *node, int contents ) {
|
|
|
|
if ( !node ) {
|
|
return;
|
|
}
|
|
|
|
if ( node->children[0] || node->children[1] ) {
|
|
RemoveOutside_r( node->children[0], contents );
|
|
RemoveOutside_r( node->children[1], contents );
|
|
return;
|
|
}
|
|
|
|
if ( !node->occupied ) {
|
|
if ( !( node->contents & contents ) ) {
|
|
outsideLeafNodes++;
|
|
node->contents |= contents;
|
|
}
|
|
else {
|
|
solidLeafNodes++;
|
|
}
|
|
}
|
|
else {
|
|
insideLeafNodes++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::RemoveOutside
|
|
============
|
|
*/
|
|
bool idBrushBSP::RemoveOutside( const idMapFile *mapFile, int contents, const idStrList &classNames ) {
|
|
common->Printf( "[Remove Outside]\n" );
|
|
|
|
solidLeafNodes = outsideLeafNodes = insideLeafNodes = 0;
|
|
|
|
if ( !FloodFromEntities( mapFile, contents, classNames ) ) {
|
|
return false;
|
|
}
|
|
|
|
RemoveOutside_r( root, contents );
|
|
|
|
common->Printf( "%6d solid leaf nodes\n", solidLeafNodes );
|
|
common->Printf( "%6d outside leaf nodes\n", outsideLeafNodes );
|
|
common->Printf( "%6d inside leaf nodes\n", insideLeafNodes );
|
|
|
|
//PruneTree( contents );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::SetPortalPlanes_r
|
|
============
|
|
*/
|
|
void idBrushBSP::SetPortalPlanes_r( idBrushBSPNode *node, idPlaneSet &planeList ) {
|
|
int s;
|
|
idBrushBSPPortal *p;
|
|
|
|
if ( !node ) {
|
|
return;
|
|
}
|
|
|
|
for ( p = node->portals; p; p = p->next[s] ) {
|
|
s = (p->nodes[1] == node);
|
|
if ( p->planeNum == -1 ) {
|
|
p->planeNum = planeList.FindPlane( p->plane, PORTAL_PLANE_NORMAL_EPSILON, PORTAL_PLANE_DIST_EPSILON );
|
|
}
|
|
}
|
|
SetPortalPlanes_r( node->children[0], planeList );
|
|
SetPortalPlanes_r( node->children[1], planeList );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::SetPortalPlanes
|
|
|
|
give all portals a plane number
|
|
============
|
|
*/
|
|
void idBrushBSP::SetPortalPlanes( void ) {
|
|
SetPortalPlanes_r( root, portalPlanes );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::MergeLeafNodePortals
|
|
============
|
|
*/
|
|
void idBrushBSP::MergeLeafNodePortals( idBrushBSPNode *node, int skipContents ) {
|
|
int s1, s2;
|
|
bool foundPortal;
|
|
idBrushBSPPortal *p1, *p2, *nextp1, *nextp2;
|
|
idWinding *newWinding, *reverse;
|
|
|
|
// pass 1: merge all portals that seperate the same leaf nodes
|
|
for ( p1 = node->GetPortals(); p1; p1 = nextp1 ) {
|
|
s1 = (p1->GetNode(1) == node);
|
|
nextp1 = p1->Next(s1);
|
|
|
|
for ( p2 = nextp1; p2; p2 = nextp2 ) {
|
|
s2 = (p2->GetNode(1) == node);
|
|
nextp2 = p2->Next(s2);
|
|
|
|
// if both portals seperate the same leaf nodes
|
|
if ( p1->nodes[!s1] == p2->nodes[!s2] ) {
|
|
|
|
// add the winding of p2 to the winding of p1
|
|
p1->winding->AddToConvexHull( p2->winding, p1->plane.Normal() );
|
|
|
|
// delete p2
|
|
p2->RemoveFromNode( p2->nodes[0] );
|
|
p2->RemoveFromNode( p2->nodes[1] );
|
|
delete p2;
|
|
|
|
numMergedPortals++;
|
|
|
|
nextp1 = node->GetPortals();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// pass 2: merge all portals in the same plane if they all have the skip contents at the other side
|
|
for ( p1 = node->GetPortals(); p1; p1 = nextp1 ) {
|
|
s1 = (p1->GetNode(1) == node);
|
|
nextp1 = p1->Next(s1);
|
|
|
|
if ( !(p1->nodes[!s1]->contents & skipContents) ) {
|
|
continue;
|
|
}
|
|
|
|
// test if all portals in this plane have the skip contents at the other side
|
|
foundPortal = false;
|
|
for ( p2 = node->GetPortals(); p2; p2 = nextp2 ) {
|
|
s2 = (p2->GetNode(1) == node);
|
|
nextp2 = p2->Next(s2);
|
|
|
|
if ( p2 == p1 || (p2->planeNum & ~1) != (p1->planeNum & ~1) ) {
|
|
continue;
|
|
}
|
|
foundPortal = true;
|
|
if ( !(p2->nodes[!s2]->contents & skipContents) ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if all portals in this plane have the skip contents at the other side
|
|
if ( !p2 && foundPortal ) {
|
|
for ( p2 = node->GetPortals(); p2; p2 = nextp2 ) {
|
|
s2 = (p2->GetNode(1) == node);
|
|
nextp2 = p2->Next(s2);
|
|
|
|
if ( p2 == p1 || (p2->planeNum & ~1) != (p1->planeNum & ~1) ) {
|
|
continue;
|
|
}
|
|
|
|
// add the winding of p2 to the winding of p1
|
|
p1->winding->AddToConvexHull( p2->winding, p1->plane.Normal() );
|
|
|
|
// delete p2
|
|
p2->RemoveFromNode( p2->nodes[0] );
|
|
p2->RemoveFromNode( p2->nodes[1] );
|
|
delete p2;
|
|
|
|
numMergedPortals++;
|
|
}
|
|
nextp1 = node->GetPortals();
|
|
}
|
|
}
|
|
|
|
// pass 3: try to merge portals in the same plane that have the skip contents at the other side
|
|
for ( p1 = node->GetPortals(); p1; p1 = nextp1 ) {
|
|
s1 = (p1->GetNode(1) == node);
|
|
nextp1 = p1->Next(s1);
|
|
|
|
if ( !(p1->nodes[!s1]->contents & skipContents) ) {
|
|
continue;
|
|
}
|
|
|
|
for ( p2 = nextp1; p2; p2 = nextp2 ) {
|
|
s2 = (p2->GetNode(1) == node);
|
|
nextp2 = p2->Next(s2);
|
|
|
|
if ( !(p2->nodes[!s2]->contents & skipContents) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( (p2->planeNum & ~1) != (p1->planeNum & ~1) ) {
|
|
continue;
|
|
}
|
|
|
|
// try to merge the two portal windings
|
|
if ( p2->planeNum == p1->planeNum ) {
|
|
newWinding = p1->winding->TryMerge( *p2->winding, p1->plane.Normal() );
|
|
}
|
|
else {
|
|
reverse = p2->winding->Reverse();
|
|
newWinding = p1->winding->TryMerge( *reverse, p1->plane.Normal() );
|
|
delete reverse;
|
|
}
|
|
|
|
// if successfully merged
|
|
if ( newWinding ) {
|
|
|
|
// replace the winding of the first portal
|
|
delete p1->winding;
|
|
p1->winding = newWinding;
|
|
|
|
// delete p2
|
|
p2->RemoveFromNode( p2->nodes[0] );
|
|
p2->RemoveFromNode( p2->nodes[1] );
|
|
delete p2;
|
|
|
|
numMergedPortals++;
|
|
|
|
nextp1 = node->GetPortals();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::MergePortals_r
|
|
============
|
|
*/
|
|
void idBrushBSP::MergePortals_r( idBrushBSPNode *node, int skipContents ) {
|
|
|
|
if ( !node ) {
|
|
return;
|
|
}
|
|
|
|
if ( node->contents & skipContents ) {
|
|
return;
|
|
}
|
|
|
|
if ( !node->children[0] && !node->children[1] ) {
|
|
MergeLeafNodePortals( node, skipContents );
|
|
return;
|
|
}
|
|
|
|
MergePortals_r( node->children[0], skipContents );
|
|
MergePortals_r( node->children[1], skipContents );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::MergePortals
|
|
============
|
|
*/
|
|
void idBrushBSP::MergePortals( int skipContents ) {
|
|
numMergedPortals = 0;
|
|
common->Printf( "[Merge Portals]\n" );
|
|
SetPortalPlanes();
|
|
MergePortals_r( root, skipContents );
|
|
common->Printf( "%6d portals merged\n", numMergedPortals );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::PruneMergedTree_r
|
|
============
|
|
*/
|
|
void idBrushBSP::PruneMergedTree_r( idBrushBSPNode *node ) {
|
|
int i;
|
|
idBrushBSPNode *leafNode;
|
|
|
|
if ( !node ) {
|
|
return;
|
|
}
|
|
|
|
PruneMergedTree_r( node->children[0] );
|
|
PruneMergedTree_r( node->children[1] );
|
|
|
|
for ( i = 0; i < 2; i++ ) {
|
|
if ( node->children[i] ) {
|
|
leafNode = node->children[i]->children[0];
|
|
if ( leafNode && leafNode == node->children[i]->children[1] ) {
|
|
if ( leafNode->parent == node->children[i] ) {
|
|
leafNode->parent = node;
|
|
}
|
|
delete node->children[i];
|
|
node->children[i] = leafNode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::UpdateTreeAfterMerge_r
|
|
============
|
|
*/
|
|
void idBrushBSP::UpdateTreeAfterMerge_r( idBrushBSPNode *node, const idBounds &bounds, idBrushBSPNode *oldNode, idBrushBSPNode *newNode ) {
|
|
|
|
if ( !node ) {
|
|
return;
|
|
}
|
|
|
|
if ( !node->children[0] && !node->children[1] ) {
|
|
return;
|
|
}
|
|
|
|
if ( node->children[0] == oldNode ) {
|
|
node->children[0] = newNode;
|
|
}
|
|
if ( node->children[1] == oldNode ) {
|
|
node->children[1] = newNode;
|
|
}
|
|
|
|
switch( bounds.PlaneSide( node->plane, 2.0f ) ) {
|
|
case PLANESIDE_FRONT:
|
|
UpdateTreeAfterMerge_r( node->children[0], bounds, oldNode, newNode );
|
|
break;
|
|
case PLANESIDE_BACK:
|
|
UpdateTreeAfterMerge_r( node->children[1], bounds, oldNode, newNode );
|
|
break;
|
|
default:
|
|
UpdateTreeAfterMerge_r( node->children[0], bounds, oldNode, newNode );
|
|
UpdateTreeAfterMerge_r( node->children[1], bounds, oldNode, newNode );
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::TryMergeLeafNodes
|
|
|
|
NOTE: multiple brances of the BSP tree might point to the same leaf node after merging
|
|
============
|
|
*/
|
|
bool idBrushBSP::TryMergeLeafNodes( idBrushBSPPortal *portal, int side ) {
|
|
int i, j, k, s1, s2, s;
|
|
idBrushBSPNode *nodes[2], *node1, *node2;
|
|
idBrushBSPPortal *p1, *p2, *p, *nextp;
|
|
idPlane plane;
|
|
idWinding *w;
|
|
idBounds bounds, b;
|
|
|
|
nodes[0] = node1 = portal->nodes[side];
|
|
nodes[1] = node2 = portal->nodes[!side];
|
|
|
|
// check if the merged node would still be convex
|
|
for ( i = 0; i < 2; i++ ) {
|
|
|
|
j = !i;
|
|
|
|
for ( p1 = nodes[i]->portals; p1; p1 = p1->next[s1] ) {
|
|
s1 = (p1->nodes[1] == nodes[i]);
|
|
|
|
if ( p1->nodes[!s1] == nodes[j] ) {
|
|
continue;
|
|
}
|
|
|
|
if ( s1 ) {
|
|
plane = -p1->plane;
|
|
}
|
|
else {
|
|
plane = p1->plane;
|
|
}
|
|
|
|
// all the non seperating portals of the other node should be at the front or on the plane
|
|
for ( p2 = nodes[j]->portals; p2; p2 = p2->next[s2] ) {
|
|
s2 = (p2->nodes[1] == nodes[j]);
|
|
|
|
if ( p2->nodes[!s2] == nodes[i] ) {
|
|
continue;
|
|
}
|
|
|
|
w = p2->winding;
|
|
for ( k = 0; k < w->GetNumPoints(); k++ ) {
|
|
if ( plane.Distance( (*w)[k].ToVec3() ) < -0.1f ) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove all portals that seperate the two nodes
|
|
for ( p = node1->portals; p; p = nextp ) {
|
|
s = (p->nodes[1] == node1);
|
|
nextp = p->next[s];
|
|
|
|
if ( p->nodes[!s] == node2 ) {
|
|
p->RemoveFromNode( p->nodes[0] );
|
|
p->RemoveFromNode( p->nodes[1] );
|
|
delete p;
|
|
}
|
|
}
|
|
|
|
// move all portals of node2 to node1
|
|
for ( p = node2->portals; p; p = node2->portals ) {
|
|
s = (p->nodes[1] == node2);
|
|
|
|
nodes[s] = node1;
|
|
nodes[!s] = p->nodes[!s];
|
|
p->RemoveFromNode( p->nodes[0] );
|
|
p->RemoveFromNode( p->nodes[1] );
|
|
p->AddToNodes( nodes[0], nodes[1] );
|
|
}
|
|
|
|
// get bounds for the new node
|
|
bounds.Clear();
|
|
for ( p = node1->portals; p; p = p->next[s] ) {
|
|
s = (p->nodes[1] == node1);
|
|
p->GetWinding()->GetBounds( b );
|
|
bounds += b;
|
|
}
|
|
|
|
// replace every reference to node2 by a reference to node1
|
|
UpdateTreeAfterMerge_r( root, bounds, node2, node1 );
|
|
|
|
delete node2;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::MeltFloor_r
|
|
|
|
flood through portals touching the bounds to find all vertices that might be inside the bounds
|
|
============
|
|
*/
|
|
void idBrushBSP::MeltFlood_r( idBrushBSPNode *node, int skipContents, idBounds &bounds, idVectorSet<idVec3,3> &vertexList ) {
|
|
int s1, i;
|
|
idBrushBSPPortal *p1;
|
|
idBounds b;
|
|
const idWinding *w;
|
|
|
|
node->SetFlag( NODE_VISITED );
|
|
|
|
for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) {
|
|
s1 = (p1->GetNode(1) == node);
|
|
|
|
if ( p1->GetNode( !s1 )->GetFlags() & NODE_VISITED ) {
|
|
continue;
|
|
}
|
|
|
|
w = p1->GetWinding();
|
|
|
|
for ( i = 0; i < w->GetNumPoints(); i++ ) {
|
|
if ( bounds.ContainsPoint( (*w)[i].ToVec3() ) ) {
|
|
vertexList.FindVector( (*w)[i].ToVec3(), VERTEX_MELT_EPSILON );
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) {
|
|
s1 = (p1->GetNode(1) == node);
|
|
|
|
if ( p1->GetNode( !s1 )->GetFlags() & NODE_VISITED ) {
|
|
continue;
|
|
}
|
|
|
|
if ( p1->GetNode( !s1 )->GetContents() & skipContents ) {
|
|
continue;
|
|
}
|
|
|
|
w = p1->GetWinding();
|
|
w->GetBounds( b );
|
|
|
|
if ( !bounds.IntersectsBounds( b ) ) {
|
|
continue;
|
|
}
|
|
|
|
MeltFlood_r( p1->GetNode( !s1 ), skipContents, bounds, vertexList );
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::MeltLeafNodePortals
|
|
============
|
|
*/
|
|
void idBrushBSP::MeltLeafNodePortals( idBrushBSPNode *node, int skipContents, idVectorSet<idVec3,3> &vertexList ) {
|
|
int s1, i;
|
|
idBrushBSPPortal *p1;
|
|
idBounds bounds;
|
|
|
|
if ( node->GetFlags() & NODE_DONE ) {
|
|
return;
|
|
}
|
|
|
|
node->SetFlag( NODE_DONE );
|
|
|
|
// melt things together
|
|
for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) {
|
|
s1 = (p1->GetNode(1) == node);
|
|
|
|
if ( p1->GetNode( !s1 )->GetFlags() & NODE_DONE ) {
|
|
continue;
|
|
}
|
|
|
|
p1->winding->GetBounds( bounds );
|
|
bounds.ExpandSelf( 2 * VERTEX_MELT_HASH_SIZE * VERTEX_MELT_EPSILON );
|
|
vertexList.Init( bounds[0], bounds[1], VERTEX_MELT_HASH_SIZE, 128 );
|
|
|
|
// get all vertices to be considered
|
|
MeltFlood_r( node, skipContents, bounds, vertexList );
|
|
node->RemoveFlagFlood( NODE_VISITED );
|
|
|
|
for ( i = 0; i < vertexList.Num(); i++ ) {
|
|
if ( p1->winding->InsertPointIfOnEdge( vertexList[i], p1->plane, 0.1f ) ) {
|
|
numInsertedPoints++;
|
|
}
|
|
}
|
|
}
|
|
DisplayRealTimeString( "\r%6d", numInsertedPoints );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::MeltPortals_r
|
|
============
|
|
*/
|
|
void idBrushBSP::MeltPortals_r( idBrushBSPNode *node, int skipContents, idVectorSet<idVec3,3> &vertexList ) {
|
|
if ( !node ) {
|
|
return;
|
|
}
|
|
|
|
if ( node->contents & skipContents ) {
|
|
return;
|
|
}
|
|
|
|
if ( !node->children[0] && !node->children[1] ) {
|
|
MeltLeafNodePortals( node, skipContents, vertexList );
|
|
return;
|
|
}
|
|
|
|
MeltPortals_r( node->children[0], skipContents, vertexList );
|
|
MeltPortals_r( node->children[1], skipContents, vertexList );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::RemoveLeafNodeColinearPoints
|
|
============
|
|
*/
|
|
void idBrushBSP::RemoveLeafNodeColinearPoints( idBrushBSPNode *node ) {
|
|
int s1;
|
|
idBrushBSPPortal *p1;
|
|
|
|
// remove colinear points
|
|
for ( p1 = node->GetPortals(); p1; p1 = p1->Next(s1) ) {
|
|
s1 = (p1->GetNode(1) == node);
|
|
p1->winding->RemoveColinearPoints( p1->plane.Normal(), 0.1f );
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::RemoveColinearPoints_r
|
|
============
|
|
*/
|
|
void idBrushBSP::RemoveColinearPoints_r( idBrushBSPNode *node, int skipContents ) {
|
|
if ( !node ) {
|
|
return;
|
|
}
|
|
|
|
if ( node->contents & skipContents ) {
|
|
return;
|
|
}
|
|
|
|
if ( !node->children[0] && !node->children[1] ) {
|
|
RemoveLeafNodeColinearPoints( node );
|
|
return;
|
|
}
|
|
|
|
RemoveColinearPoints_r( node->children[0], skipContents );
|
|
RemoveColinearPoints_r( node->children[1], skipContents );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idBrushBSP::MeltPortals
|
|
============
|
|
*/
|
|
void idBrushBSP::MeltPortals( int skipContents ) {
|
|
idVectorSet<idVec3,3> vertexList;
|
|
|
|
numInsertedPoints = 0;
|
|
common->Printf( "[Melt Portals]\n" );
|
|
RemoveColinearPoints_r( root, skipContents );
|
|
MeltPortals_r( root, skipContents, vertexList );
|
|
root->RemoveFlagRecurse( NODE_DONE );
|
|
common->Printf( "\r%6d points inserted\n", numInsertedPoints );
|
|
}
|