/*
===========================================================================
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 .
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 "../../../idlib/precompiled.h"
#pragma hdrstop
#include "Brush.h"
#include "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 &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 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;
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" );
}
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;
idPlane planes[6];
// 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;
idBrushBSPPortal *p, *nextPortal;
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->occupied ) {
common->Error( "FloodThroughPortals_r: node already occupied\n" );
}
if ( !node ) {
common->Error( "FloodThroughPortals_r: NULL node\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 &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 &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 &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 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 );
}