2011-11-22 21:28:15 +00:00
|
|
|
/*
|
|
|
|
===========================================================================
|
|
|
|
|
|
|
|
Doom 3 GPL Source Code
|
2011-12-06 18:20:15 +00:00
|
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
2011-11-22 21:28:15 +00:00
|
|
|
|
2011-12-06 16:14:59 +00:00
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
2011-11-22 21:28:15 +00:00
|
|
|
|
|
|
|
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 "../../../idlib/precompiled.h"
|
|
|
|
#pragma hdrstop
|
|
|
|
|
|
|
|
#include "Brush.h"
|
|
|
|
|
|
|
|
#define BRUSH_EPSILON 0.1f
|
|
|
|
#define BRUSH_PLANE_NORMAL_EPSILON 0.00001f
|
|
|
|
#define BRUSH_PLANE_DIST_EPSILON 0.01f
|
|
|
|
|
|
|
|
#define OUTPUT_UPDATE_TIME 500 // update every 500 msec
|
|
|
|
|
|
|
|
//#define OUTPUT_CHOP_STATS
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
DisplayRealTimeString
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void DisplayRealTimeString( char *string, ... ) {
|
|
|
|
va_list argPtr;
|
|
|
|
char buf[MAX_STRING_CHARS];
|
|
|
|
static int lastUpdateTime;
|
|
|
|
int time;
|
|
|
|
|
|
|
|
time = Sys_Milliseconds();
|
|
|
|
if ( time > lastUpdateTime + OUTPUT_UPDATE_TIME ) {
|
|
|
|
va_start( argPtr, string );
|
|
|
|
vsprintf( buf, string, argPtr );
|
|
|
|
va_end( argPtr );
|
|
|
|
common->Printf( buf );
|
|
|
|
lastUpdateTime = time;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===============================================================
|
|
|
|
//
|
|
|
|
// idBrushSide
|
|
|
|
//
|
|
|
|
//===============================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushSide::idBrushSide
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
idBrushSide::idBrushSide( void ) {
|
|
|
|
flags = 0;
|
|
|
|
planeNum = -1;
|
|
|
|
winding = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushSide::idBrushSide
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
idBrushSide::idBrushSide( const idPlane &plane, int planeNum ) {
|
|
|
|
this->flags = 0;
|
|
|
|
this->plane = plane;
|
|
|
|
this->planeNum = planeNum;
|
|
|
|
this->winding = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushSide::~idBrushSide
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
idBrushSide::~idBrushSide( void ) {
|
|
|
|
if ( winding ) {
|
|
|
|
delete winding;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushSide::Copy
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
idBrushSide *idBrushSide::Copy( void ) const {
|
|
|
|
idBrushSide *side;
|
|
|
|
|
|
|
|
side = new idBrushSide( plane, planeNum );
|
|
|
|
side->flags = flags;
|
|
|
|
if ( winding ) {
|
|
|
|
side->winding = winding->Copy();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
side->winding = NULL;
|
|
|
|
}
|
|
|
|
return side;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushSide::Split
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
int idBrushSide::Split( const idPlane &splitPlane, idBrushSide **front, idBrushSide **back ) const {
|
|
|
|
idWinding *frontWinding, *backWinding;
|
|
|
|
|
|
|
|
assert( winding );
|
|
|
|
|
|
|
|
*front = *back = NULL;
|
|
|
|
|
|
|
|
winding->Split( splitPlane, 0.0f, &frontWinding, &backWinding );
|
|
|
|
|
|
|
|
if ( frontWinding ) {
|
|
|
|
(*front) = new idBrushSide( plane, planeNum );
|
|
|
|
(*front)->winding = frontWinding;
|
|
|
|
(*front)->flags = flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( backWinding ) {
|
|
|
|
(*back) = new idBrushSide( plane, planeNum );
|
|
|
|
(*back)->winding = backWinding;
|
|
|
|
(*back)->flags = flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( frontWinding && backWinding ) {
|
|
|
|
return PLANESIDE_CROSS;
|
|
|
|
}
|
|
|
|
else if ( frontWinding ) {
|
|
|
|
return PLANESIDE_FRONT;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return PLANESIDE_BACK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===============================================================
|
|
|
|
//
|
|
|
|
// idBrushSide
|
|
|
|
//
|
|
|
|
//===============================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::idBrush
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
idBrush::idBrush( void ) {
|
|
|
|
contents = flags = 0;
|
|
|
|
bounds.Clear();
|
|
|
|
sides.Clear();
|
|
|
|
windingsValid = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::~idBrush
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
idBrush::~idBrush( void ) {
|
|
|
|
for ( int i = 0; i < sides.Num(); i++ ) {
|
|
|
|
delete sides[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::RemoveSidesWithoutWinding
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
bool idBrush::RemoveSidesWithoutWinding( void ) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for ( i = 0; i < sides.Num(); i++ ) {
|
|
|
|
|
|
|
|
if ( sides[i]->winding ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
sides.RemoveIndex( i );
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ( sides.Num() >= 4 );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::CreateWindings
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
bool idBrush::CreateWindings( void ) {
|
|
|
|
int i, j;
|
|
|
|
idBrushSide *side;
|
|
|
|
|
|
|
|
bounds.Clear();
|
|
|
|
for ( i = 0; i < sides.Num(); i++ ) {
|
|
|
|
side = sides[i];
|
|
|
|
|
|
|
|
if ( side->winding ) {
|
|
|
|
delete side->winding;
|
|
|
|
}
|
|
|
|
|
|
|
|
side->winding = new idWinding( side->plane.Normal(), side->plane.Dist() );
|
|
|
|
|
|
|
|
for ( j = 0; j < sides.Num() && side->winding; j++ ) {
|
|
|
|
if ( i == j ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// keep the winding if on the clip plane
|
|
|
|
side->winding = side->winding->Clip( -sides[j]->plane, BRUSH_EPSILON, true );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( side->winding ) {
|
|
|
|
for ( j = 0; j < side->winding->GetNumPoints(); j++ ) {
|
|
|
|
bounds.AddPoint( (*side->winding)[j].ToVec3() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( bounds[0][0] > bounds[1][0] ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for ( i = 0; i < 3; i++ ) {
|
|
|
|
if ( bounds[0][i] < MIN_WORLD_COORD || bounds[1][i] > MAX_WORLD_COORD ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
windingsValid = true;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::BoundBrush
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrush::BoundBrush( const idBrush *original ) {
|
|
|
|
int i, j;
|
|
|
|
idBrushSide *side;
|
|
|
|
idWinding *w;
|
|
|
|
|
|
|
|
assert( windingsValid );
|
|
|
|
|
|
|
|
bounds.Clear();
|
|
|
|
for ( i = 0; i < sides.Num(); i++ ) {
|
|
|
|
side = sides[i];
|
|
|
|
|
|
|
|
w = side->winding;
|
|
|
|
|
|
|
|
if ( !w ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( j = 0; j < w->GetNumPoints(); j++ ) {
|
|
|
|
bounds.AddPoint( (*w)[j].ToVec3() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( bounds[0][0] > bounds[1][0] ) {
|
|
|
|
if ( original ) {
|
|
|
|
idBrushMap *bm = new idBrushMap( "error_brush", "_original" );
|
|
|
|
bm->WriteBrush( original );
|
|
|
|
delete bm;
|
|
|
|
}
|
|
|
|
common->Error( "idBrush::BoundBrush: brush %d on entity %d without windings", primitiveNum, entityNum );
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( i = 0; i < 3; i++ ) {
|
|
|
|
if ( bounds[0][i] < MIN_WORLD_COORD || bounds[1][i] > MAX_WORLD_COORD ) {
|
|
|
|
if ( original ) {
|
|
|
|
idBrushMap *bm = new idBrushMap( "error_brush", "_original" );
|
|
|
|
bm->WriteBrush( original );
|
|
|
|
delete bm;
|
|
|
|
}
|
|
|
|
common->Error( "idBrush::BoundBrush: brush %d on entity %d is unbounded", primitiveNum, entityNum );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::FromSides
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
bool idBrush::FromSides( idList<idBrushSide *> &sideList ) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for ( i = 0; i < sideList.Num(); i++ ) {
|
|
|
|
sides.Append( sideList[i] );
|
|
|
|
}
|
|
|
|
|
|
|
|
sideList.Clear();
|
|
|
|
|
|
|
|
return CreateWindings();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::FromWinding
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
bool idBrush::FromWinding( const idWinding &w, const idPlane &windingPlane ) {
|
|
|
|
int i, j, bestAxis;
|
|
|
|
idPlane plane;
|
|
|
|
idVec3 normal, axialNormal;
|
|
|
|
|
|
|
|
sides.Append( new idBrushSide( windingPlane, -1 ) );
|
|
|
|
sides.Append( new idBrushSide( -windingPlane, -1 ) );
|
|
|
|
|
|
|
|
bestAxis = 0;
|
|
|
|
for ( i = 1; i < 3; i++ ) {
|
|
|
|
if ( idMath::Fabs( windingPlane.Normal()[i] ) > idMath::Fabs( windingPlane.Normal()[bestAxis] ) ) {
|
|
|
|
bestAxis = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
axialNormal = vec3_origin;
|
|
|
|
if ( windingPlane.Normal()[bestAxis] > 0.0f ) {
|
|
|
|
axialNormal[bestAxis] = 1.0f;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
axialNormal[bestAxis] = -1.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( i = 0; i < w.GetNumPoints(); i++ ) {
|
|
|
|
j = (i+1) % w.GetNumPoints();
|
|
|
|
normal = ( w[j].ToVec3() - w[i].ToVec3() ).Cross( axialNormal );
|
|
|
|
if ( normal.Normalize() < 0.5f ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
plane.SetNormal( normal );
|
|
|
|
plane.FitThroughPoint( w[j].ToVec3() );
|
|
|
|
sides.Append( new idBrushSide( plane, -1 ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( sides.Num() < 4 ) {
|
|
|
|
for ( i = 0; i < sides.Num(); i++ ) {
|
|
|
|
delete sides[i];
|
|
|
|
}
|
|
|
|
sides.Clear();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
sides[0]->winding = w.Copy();
|
|
|
|
windingsValid = true;
|
|
|
|
BoundBrush();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::FromBounds
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
bool idBrush::FromBounds( const idBounds &bounds ) {
|
|
|
|
int axis, dir;
|
|
|
|
idVec3 normal;
|
|
|
|
idPlane plane;
|
|
|
|
|
|
|
|
for ( axis = 0; axis < 3; axis++ ) {
|
|
|
|
for ( dir = -1; dir <= 1; dir += 2 ) {
|
|
|
|
normal = vec3_origin;
|
|
|
|
normal[axis] = dir;
|
|
|
|
plane.SetNormal( normal );
|
|
|
|
plane.SetDist( dir * bounds[(dir == 1)][axis] );
|
|
|
|
sides.Append( new idBrushSide( plane, -1 ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return CreateWindings();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::Transform
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrush::Transform( const idVec3 &origin, const idMat3 &axis ) {
|
|
|
|
int i;
|
|
|
|
bool transformed = false;
|
|
|
|
|
|
|
|
if ( axis.IsRotated() ) {
|
|
|
|
for ( i = 0; i < sides.Num(); i++ ) {
|
|
|
|
sides[i]->plane.RotateSelf( vec3_origin, axis );
|
|
|
|
}
|
|
|
|
transformed = true;
|
|
|
|
}
|
|
|
|
if ( origin != vec3_origin ) {
|
|
|
|
for ( i = 0; i < sides.Num(); i++ ) {
|
|
|
|
sides[i]->plane.TranslateSelf( origin );
|
|
|
|
}
|
|
|
|
transformed = true;
|
|
|
|
}
|
|
|
|
if ( transformed ) {
|
|
|
|
CreateWindings();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::GetVolume
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
float idBrush::GetVolume( void ) const {
|
|
|
|
int i;
|
|
|
|
idWinding *w;
|
|
|
|
idVec3 corner;
|
|
|
|
float d, area, volume;
|
|
|
|
|
|
|
|
// grab the first valid point as a corner
|
|
|
|
w = NULL;
|
|
|
|
for ( i = 0; i < sides.Num(); i++ ) {
|
|
|
|
w = sides[i]->winding;
|
|
|
|
if ( w ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( !w ) {
|
|
|
|
return 0.0f;
|
|
|
|
}
|
|
|
|
corner = (*w)[0].ToVec3();
|
|
|
|
|
|
|
|
// create tetrahedrons to all other sides
|
|
|
|
volume = 0.0f;
|
|
|
|
for ( ; i < sides.Num(); i++) {
|
|
|
|
w = sides[i]->winding;
|
|
|
|
if ( !w ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
d = -( corner * sides[i]->plane.Normal() - sides[i]->plane.Dist() );
|
|
|
|
area = w->GetArea();
|
|
|
|
volume += d * area;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ( volume * ( 1.0f / 3.0f ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::Subtract
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
bool idBrush::Subtract( const idBrush *b, idBrushList &list ) const {
|
|
|
|
int i;
|
|
|
|
idBrush *front, *back;
|
|
|
|
const idBrush *in;
|
|
|
|
|
|
|
|
list.Clear();
|
|
|
|
in = this;
|
|
|
|
for ( i = 0; i < b->sides.Num() && in; i++ ) {
|
|
|
|
|
|
|
|
in->Split( b->sides[i]->plane, b->sides[i]->planeNum, &front, &back );
|
|
|
|
|
|
|
|
if ( in != this ) {
|
|
|
|
delete in;
|
|
|
|
}
|
|
|
|
if ( front ) {
|
|
|
|
list.AddToTail( front );
|
|
|
|
}
|
|
|
|
in = back;
|
|
|
|
}
|
|
|
|
// if didn't really intersect
|
|
|
|
if ( !in ) {
|
|
|
|
list.Free();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
delete in;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::TryMerge
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
bool idBrush::TryMerge( const idBrush *brush, const idPlaneSet &planeList ) {
|
|
|
|
int i, j, k, l, m, seperatingPlane;
|
|
|
|
const idBrush *brushes[2];
|
|
|
|
const idWinding *w;
|
|
|
|
const idPlane *plane;
|
|
|
|
|
|
|
|
// brush bounds should overlap
|
|
|
|
for ( i = 0; i < 3; i++ ) {
|
|
|
|
if ( bounds[0][i] > brush->bounds[1][i] + 0.1f ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ( bounds[1][i] < brush->bounds[0][i] - 0.1f ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// the brushes should share an opposite plane
|
|
|
|
seperatingPlane = -1;
|
|
|
|
for ( i = 0; i < GetNumSides(); i++ ) {
|
|
|
|
for ( j = 0; j < brush->GetNumSides(); j++ ) {
|
|
|
|
if ( GetSide(i)->GetPlaneNum() == (brush->GetSide(j)->GetPlaneNum() ^ 1) ) {
|
|
|
|
// may only have one seperating plane
|
|
|
|
if ( seperatingPlane != -1 ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
seperatingPlane = GetSide(i)->GetPlaneNum();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( seperatingPlane == -1 ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
brushes[0] = this;
|
|
|
|
brushes[1] = brush;
|
|
|
|
|
|
|
|
for ( i = 0; i < 2; i++ ) {
|
|
|
|
|
|
|
|
j = !i;
|
|
|
|
|
|
|
|
for ( k = 0; k < brushes[i]->GetNumSides(); k++ ) {
|
|
|
|
|
|
|
|
// if the brush side plane is the seprating plane
|
|
|
|
if ( !( ( brushes[i]->GetSide(k)->GetPlaneNum() ^ seperatingPlane ) >> 1 ) ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
plane = &brushes[i]->GetSide(k)->GetPlane();
|
|
|
|
|
|
|
|
// all the non seperating brush sides of the other brush should be at the back or on the plane
|
|
|
|
for ( l = 0; l < brushes[j]->GetNumSides(); l++ ) {
|
|
|
|
|
|
|
|
w = brushes[j]->GetSide(l)->GetWinding();
|
|
|
|
if ( !w ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !( ( brushes[j]->GetSide(l)->GetPlaneNum() ^ seperatingPlane ) >> 1 ) ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( m = 0; m < w->GetNumPoints(); m++ ) {
|
|
|
|
if ( plane->Distance( (*w)[m].ToVec3() ) > 0.1f ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// add any sides from the other brush to this brush
|
|
|
|
for ( i = 0; i < brush->GetNumSides(); i++ ) {
|
|
|
|
for ( j = 0; j < GetNumSides(); j++ ) {
|
|
|
|
if ( !( ( brush->GetSide(i)->GetPlaneNum() ^ GetSide(j)->GetPlaneNum() ) >> 1 ) ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( j < GetNumSides() ) {
|
|
|
|
sides[j]->flags &= brush->GetSide(i)->GetFlags();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
sides.Append( brush->GetSide(i)->Copy() );
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove any side from this brush that is the opposite of a side of the other brush
|
|
|
|
for ( i = 0; i < GetNumSides(); i++ ) {
|
|
|
|
for ( j = 0; j < brush->GetNumSides(); j++ ) {
|
|
|
|
if ( GetSide(i)->GetPlaneNum() == ( brush->GetSide(j)->GetPlaneNum() ^ 1 ) ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( j < brush->GetNumSides() ) {
|
|
|
|
delete sides[i];
|
|
|
|
sides.RemoveIndex(i);
|
|
|
|
i--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
contents |= brush->contents;
|
|
|
|
|
|
|
|
CreateWindings();
|
|
|
|
BoundBrush();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::Split
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
int idBrush::Split( const idPlane &plane, int planeNum, idBrush **front, idBrush **back ) const {
|
|
|
|
int res, i, j;
|
|
|
|
idBrushSide *side, *frontSide, *backSide;
|
|
|
|
float dist, maxBack, maxFront, *maxBackWinding, *maxFrontWinding;
|
|
|
|
idWinding *w, *mid;
|
|
|
|
|
|
|
|
assert( windingsValid );
|
|
|
|
|
|
|
|
if ( front ) {
|
|
|
|
*front = NULL;
|
|
|
|
}
|
|
|
|
if ( back ) {
|
|
|
|
*back = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
res = bounds.PlaneSide( plane, -BRUSH_EPSILON );
|
|
|
|
if ( res == PLANESIDE_FRONT ) {
|
|
|
|
if ( front ) {
|
|
|
|
*front = Copy();
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
if ( res == PLANESIDE_BACK ) {
|
|
|
|
if ( back ) {
|
|
|
|
*back = Copy();
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
maxBackWinding = (float *) _alloca16( sides.Num() * sizeof(float) );
|
|
|
|
maxFrontWinding = (float *) _alloca16( sides.Num() * sizeof(float) );
|
|
|
|
|
|
|
|
maxFront = maxBack = 0.0f;
|
|
|
|
for ( i = 0; i < sides.Num(); i++ ) {
|
|
|
|
side = sides[i];
|
|
|
|
|
|
|
|
w = side->winding;
|
|
|
|
|
|
|
|
if ( !w ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
maxBackWinding[i] = 10.0f;
|
|
|
|
maxFrontWinding[i] = -10.0f;
|
|
|
|
|
|
|
|
for ( j = 0; j < w->GetNumPoints(); j++ ) {
|
|
|
|
|
|
|
|
dist = plane.Distance( (*w)[j].ToVec3() );
|
|
|
|
if ( dist > maxFrontWinding[i] ) {
|
|
|
|
maxFrontWinding[i] = dist;
|
|
|
|
}
|
|
|
|
if ( dist < maxBackWinding[i] ) {
|
|
|
|
maxBackWinding[i] = dist;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( maxFrontWinding[i] > maxFront ) {
|
|
|
|
maxFront = maxFrontWinding[i];
|
|
|
|
}
|
|
|
|
if ( maxBackWinding[i] < maxBack ) {
|
|
|
|
maxBack = maxBackWinding[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( maxFront < BRUSH_EPSILON ) {
|
|
|
|
if ( back ) {
|
|
|
|
*back = Copy();
|
|
|
|
}
|
|
|
|
return PLANESIDE_BACK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( maxBack > -BRUSH_EPSILON ) {
|
|
|
|
if ( front ) {
|
|
|
|
*front = Copy();
|
|
|
|
}
|
|
|
|
return PLANESIDE_FRONT;
|
|
|
|
}
|
|
|
|
|
|
|
|
mid = new idWinding( plane.Normal(), plane.Dist() );
|
|
|
|
|
|
|
|
for ( i = 0; i < sides.Num() && mid; i++ ) {
|
|
|
|
mid = mid->Clip( -sides[i]->plane, BRUSH_EPSILON, false );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( mid ) {
|
|
|
|
if ( mid->IsTiny() ) {
|
|
|
|
delete mid;
|
|
|
|
mid = NULL;
|
|
|
|
}
|
|
|
|
else if ( mid->IsHuge() ) {
|
|
|
|
// if the winding is huge then the brush is unbounded
|
|
|
|
common->Warning( "brush %d on entity %d is unbounded"
|
|
|
|
"( %1.2f %1.2f %1.2f )-( %1.2f %1.2f %1.2f )-( %1.2f %1.2f %1.2f )", primitiveNum, entityNum,
|
|
|
|
bounds[0][0], bounds[0][1], bounds[0][2], bounds[1][0], bounds[1][1], bounds[1][2],
|
|
|
|
bounds[1][0]-bounds[0][0], bounds[1][1]-bounds[0][1], bounds[1][2]-bounds[0][2] );
|
|
|
|
delete mid;
|
|
|
|
mid = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !mid ) {
|
|
|
|
if ( maxFront > - maxBack ) {
|
|
|
|
if ( front ) {
|
|
|
|
*front = Copy();
|
|
|
|
}
|
|
|
|
return PLANESIDE_FRONT;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if ( back ) {
|
|
|
|
*back = Copy();
|
|
|
|
}
|
|
|
|
return PLANESIDE_BACK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !front && !back ) {
|
|
|
|
delete mid;
|
|
|
|
return PLANESIDE_CROSS;
|
|
|
|
}
|
|
|
|
|
|
|
|
*front = new idBrush();
|
|
|
|
(*front)->SetContents( contents );
|
|
|
|
(*front)->SetEntityNum( entityNum );
|
|
|
|
(*front)->SetPrimitiveNum( primitiveNum );
|
|
|
|
*back = new idBrush();
|
|
|
|
(*back)->SetContents( contents );
|
|
|
|
(*back)->SetEntityNum( entityNum );
|
|
|
|
(*back)->SetPrimitiveNum( primitiveNum );
|
|
|
|
|
|
|
|
for ( i = 0; i < sides.Num(); i++ ) {
|
|
|
|
side = sides[i];
|
|
|
|
|
|
|
|
if ( !side->winding ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if completely at the front
|
|
|
|
if ( maxBackWinding[i] >= BRUSH_EPSILON ) {
|
|
|
|
(*front)->sides.Append( side->Copy() );
|
|
|
|
}
|
|
|
|
// if completely at the back
|
|
|
|
else if ( maxFrontWinding[i] <= -BRUSH_EPSILON ) {
|
|
|
|
(*back)->sides.Append( side->Copy() );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// split the side
|
|
|
|
side->Split( plane, &frontSide, &backSide );
|
|
|
|
if ( frontSide ) {
|
|
|
|
(*front)->sides.Append( frontSide );
|
|
|
|
}
|
|
|
|
else if ( maxFrontWinding[i] > -BRUSH_EPSILON ) {
|
|
|
|
// favor an overconstrained brush
|
|
|
|
side = side->Copy();
|
|
|
|
side->winding = side->winding->Clip( idPlane( plane.Normal(), (plane.Dist() - (BRUSH_EPSILON+0.02f)) ), 0.01f, true );
|
|
|
|
assert( side->winding );
|
|
|
|
(*front)->sides.Append( side );
|
|
|
|
}
|
|
|
|
if ( backSide ) {
|
|
|
|
(*back)->sides.Append( backSide );
|
|
|
|
}
|
|
|
|
else if ( maxBackWinding[i] < BRUSH_EPSILON ) {
|
|
|
|
// favor an overconstrained brush
|
|
|
|
side = side->Copy();
|
|
|
|
side->winding = side->winding->Clip( idPlane( -plane.Normal(), -(plane.Dist() + (BRUSH_EPSILON+0.02f)) ), 0.01f, true );
|
|
|
|
assert( side->winding );
|
|
|
|
(*back)->sides.Append( side );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
side = new idBrushSide( -plane, planeNum^1 );
|
|
|
|
side->winding = mid->Reverse();
|
|
|
|
side->flags |= SFL_SPLIT;
|
|
|
|
(*front)->sides.Append( side );
|
|
|
|
(*front)->windingsValid = true;
|
|
|
|
(*front)->BoundBrush( this );
|
|
|
|
|
|
|
|
side = new idBrushSide( plane, planeNum );
|
|
|
|
side->winding = mid;
|
|
|
|
side->flags |= SFL_SPLIT;
|
|
|
|
(*back)->sides.Append( side );
|
|
|
|
(*back)->windingsValid = true;
|
|
|
|
(*back)->BoundBrush( this );
|
|
|
|
|
|
|
|
return PLANESIDE_CROSS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::AddBevelsForAxialBox
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
#define BRUSH_BEVEL_EPSILON 0.1f
|
|
|
|
|
|
|
|
void idBrush::AddBevelsForAxialBox( void ) {
|
|
|
|
int axis, dir, i, j, k, l, order;
|
|
|
|
idBrushSide *side, *newSide;
|
|
|
|
idPlane plane;
|
|
|
|
idVec3 normal, vec;
|
|
|
|
idWinding *w, *w2;
|
|
|
|
float d, minBack;
|
|
|
|
|
|
|
|
assert( windingsValid );
|
|
|
|
|
|
|
|
// add the axial planes
|
|
|
|
order = 0;
|
|
|
|
for ( axis = 0; axis < 3; axis++ ) {
|
|
|
|
|
|
|
|
for ( dir = -1; dir <= 1; dir += 2, order++ ) {
|
|
|
|
|
|
|
|
// see if the plane is already present
|
|
|
|
for ( i = 0; i < sides.Num(); i++ ) {
|
|
|
|
if ( dir > 0 ) {
|
|
|
|
if ( sides[i]->plane.Normal()[axis] >= 0.9999f ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if ( sides[i]->plane.Normal()[axis] <= -0.9999f ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( i >= sides.Num() ) {
|
|
|
|
normal = vec3_origin;
|
|
|
|
normal[axis] = dir;
|
|
|
|
plane.SetNormal( normal );
|
|
|
|
plane.SetDist( dir * bounds[(dir == 1)][axis] );
|
|
|
|
newSide = new idBrushSide( plane, -1 );
|
|
|
|
newSide->SetFlag( SFL_BEVEL );
|
|
|
|
sides.Append( newSide );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the brush is pure axial we're done
|
|
|
|
if ( sides.Num() == 6 ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// test the non-axial plane edges
|
|
|
|
for ( i = 0; i < sides.Num(); i++ ) {
|
|
|
|
side = sides[i];
|
|
|
|
w = side->winding;
|
|
|
|
if ( !w ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( j = 0; j < w->GetNumPoints(); j++) {
|
|
|
|
k = (j+1) % w->GetNumPoints();
|
|
|
|
vec = (*w)[j].ToVec3() - (*w)[k].ToVec3();
|
|
|
|
if ( vec.Normalize() < 0.5f ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
for ( k = 0; k < 3; k++ ) {
|
|
|
|
if ( vec[k] == 1.0f || vec[k] == -1.0f || (vec[k] == 0.0f && vec[(k+1)%3] == 0.0f) ) {
|
|
|
|
break; // axial
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( k < 3 ) {
|
|
|
|
continue; // only test non-axial edges
|
|
|
|
}
|
|
|
|
|
|
|
|
// try the six possible slanted axials from this edge
|
|
|
|
for ( axis = 0; axis < 3; axis++ ) {
|
|
|
|
|
|
|
|
for ( dir = -1; dir <= 1; dir += 2 ) {
|
|
|
|
|
|
|
|
// construct a plane
|
|
|
|
normal = vec3_origin;
|
|
|
|
normal[axis] = dir;
|
|
|
|
normal = vec.Cross( normal );
|
|
|
|
if ( normal.Normalize() < 0.5f ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
plane.SetNormal( normal );
|
|
|
|
plane.FitThroughPoint( (*w)[j].ToVec3() );
|
|
|
|
|
|
|
|
// if all the points on all the sides are
|
|
|
|
// behind this plane, it is a proper edge bevel
|
|
|
|
for ( k = 0; k < sides.Num(); k++ ) {
|
|
|
|
|
|
|
|
// if this plane has allready been used, skip it
|
|
|
|
if ( plane.Compare( sides[k]->plane, 0.001f, 0.1f ) ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
w2 = sides[k]->winding;
|
|
|
|
if ( !w2 ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
minBack = 0.0f;
|
|
|
|
for ( l = 0; l < w2->GetNumPoints(); l++ ) {
|
|
|
|
d = plane.Distance( (*w2)[l].ToVec3() );
|
|
|
|
if ( d > BRUSH_BEVEL_EPSILON ) {
|
|
|
|
break; // point at the front
|
|
|
|
}
|
|
|
|
if ( d < minBack ) {
|
|
|
|
minBack = d;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if some point was at the front
|
|
|
|
if ( l < w2->GetNumPoints() ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// if no points at the back then the winding is on the bevel plane
|
|
|
|
if ( minBack > -BRUSH_BEVEL_EPSILON ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( k < sides.Num() ) {
|
|
|
|
continue; // wasn't part of the outer hull
|
|
|
|
}
|
|
|
|
|
|
|
|
// add this plane
|
|
|
|
newSide = new idBrushSide( plane, -1 );
|
|
|
|
newSide->SetFlag( SFL_BEVEL );
|
|
|
|
sides.Append( newSide );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::ExpandForAxialBox
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrush::ExpandForAxialBox( const idBounds &bounds ) {
|
|
|
|
int i, j;
|
|
|
|
idBrushSide *side;
|
|
|
|
idVec3 v;
|
|
|
|
|
|
|
|
AddBevelsForAxialBox();
|
|
|
|
|
|
|
|
for ( i = 0; i < sides.Num(); i++ ) {
|
|
|
|
side = sides[i];
|
|
|
|
|
|
|
|
for ( j = 0; j < 3; j++ ) {
|
|
|
|
if ( side->plane.Normal()[j] > 0.0f ) {
|
|
|
|
v[j] = bounds[0][j];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
v[j] = bounds[1][j];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
side->plane.SetDist( side->plane.Dist() + v * -side->plane.Normal() );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !CreateWindings() ) {
|
|
|
|
common->Error( "idBrush::ExpandForAxialBox: brush %d on entity %d imploded", primitiveNum, entityNum );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
// after expansion at least all non bevel sides should have a winding
|
|
|
|
for ( i = 0; i < sides.Num(); i++ ) {
|
|
|
|
side = sides[i];
|
|
|
|
if ( !side->winding ) {
|
|
|
|
if ( !( side->flags & SFL_BEVEL ) ) {
|
|
|
|
int shit = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrush::Copy
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
idBrush *idBrush::Copy( void ) const {
|
|
|
|
int i;
|
|
|
|
idBrush *b;
|
|
|
|
|
|
|
|
b = new idBrush();
|
|
|
|
b->entityNum = entityNum;
|
|
|
|
b->primitiveNum = primitiveNum;
|
|
|
|
b->contents = contents;
|
|
|
|
b->windingsValid = windingsValid;
|
|
|
|
b->bounds = bounds;
|
|
|
|
for ( i = 0; i < sides.Num(); i++ ) {
|
|
|
|
b->sides.Append( sides[i]->Copy() );
|
|
|
|
}
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===============================================================
|
|
|
|
//
|
|
|
|
// idBrushList
|
|
|
|
//
|
|
|
|
//===============================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::idBrushList
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
idBrushList::idBrushList( void ) {
|
|
|
|
numBrushes = numBrushSides = 0;
|
|
|
|
head = tail = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::~idBrushList
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
idBrushList::~idBrushList( void ) {
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::GetBounds
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
idBounds idBrushList::GetBounds( void ) const {
|
|
|
|
idBounds bounds;
|
|
|
|
idBrush *b;
|
|
|
|
|
|
|
|
bounds.Clear();
|
|
|
|
for ( b = Head(); b; b = b->Next() ) {
|
|
|
|
bounds += b->GetBounds();
|
|
|
|
}
|
|
|
|
return bounds;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::AddToTail
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrushList::AddToTail( idBrush *brush ) {
|
|
|
|
brush->next = NULL;
|
|
|
|
if ( tail ) {
|
|
|
|
tail->next = brush;
|
|
|
|
}
|
|
|
|
tail = brush;
|
|
|
|
if ( !head ) {
|
|
|
|
head = brush;
|
|
|
|
}
|
|
|
|
numBrushes++;
|
|
|
|
numBrushSides += brush->sides.Num();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::AddToTail
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrushList::AddToTail( idBrushList &list ) {
|
|
|
|
idBrush *brush, *next;
|
|
|
|
|
|
|
|
for ( brush = list.head; brush; brush = next ) {
|
|
|
|
next = brush->next;
|
|
|
|
brush->next = NULL;
|
|
|
|
if ( tail ) {
|
|
|
|
tail->next = brush;
|
|
|
|
}
|
|
|
|
tail = brush;
|
|
|
|
if ( !head ) {
|
|
|
|
head = brush;
|
|
|
|
}
|
|
|
|
numBrushes++;
|
|
|
|
numBrushSides += brush->sides.Num();
|
|
|
|
}
|
|
|
|
list.head = list.tail = NULL;
|
|
|
|
list.numBrushes = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::AddToFront
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrushList::AddToFront( idBrush *brush ) {
|
|
|
|
brush->next = head;
|
|
|
|
head = brush;
|
|
|
|
if ( !tail ) {
|
|
|
|
tail = brush;
|
|
|
|
}
|
|
|
|
numBrushes++;
|
|
|
|
numBrushSides += brush->sides.Num();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::AddToFront
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrushList::AddToFront( idBrushList &list ) {
|
|
|
|
idBrush *brush, *next;
|
|
|
|
|
|
|
|
for ( brush = list.head; brush; brush = next ) {
|
|
|
|
next = brush->next;
|
|
|
|
brush->next = head;
|
|
|
|
head = brush;
|
|
|
|
if ( !tail ) {
|
|
|
|
tail = brush;
|
|
|
|
}
|
|
|
|
numBrushes++;
|
|
|
|
numBrushSides += brush->sides.Num();
|
|
|
|
}
|
|
|
|
list.head = list.tail = NULL;
|
|
|
|
list.numBrushes = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::Remove
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrushList::Remove( idBrush *brush ) {
|
|
|
|
idBrush *b, *last;
|
|
|
|
|
|
|
|
last = NULL;
|
|
|
|
for ( b = head; b; b = b->next ) {
|
|
|
|
if ( b == brush ) {
|
|
|
|
if ( last ) {
|
|
|
|
last->next = b->next;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
head = b->next;
|
|
|
|
}
|
|
|
|
if ( b == tail ) {
|
|
|
|
tail = last;
|
|
|
|
}
|
|
|
|
numBrushes--;
|
|
|
|
numBrushSides -= brush->sides.Num();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
last = b;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::Delete
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrushList::Delete( idBrush *brush ) {
|
|
|
|
idBrush *b, *last;
|
|
|
|
|
|
|
|
last = NULL;
|
|
|
|
for ( b = head; b; b = b->next ) {
|
|
|
|
if ( b == brush ) {
|
|
|
|
if ( last ) {
|
|
|
|
last->next = b->next;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
head = b->next;
|
|
|
|
}
|
|
|
|
if ( b == tail ) {
|
|
|
|
tail = last;
|
|
|
|
}
|
|
|
|
numBrushes--;
|
|
|
|
numBrushSides -= b->sides.Num();
|
|
|
|
delete b;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
last = b;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::Copy
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
idBrushList *idBrushList::Copy( void ) const {
|
|
|
|
idBrush *brush;
|
|
|
|
idBrushList *list;
|
|
|
|
|
|
|
|
list = new idBrushList;
|
|
|
|
|
|
|
|
for ( brush = head; brush; brush = brush->next ) {
|
|
|
|
list->AddToTail( brush->Copy() );
|
|
|
|
}
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::Free
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrushList::Free( void ) {
|
|
|
|
idBrush *brush, *next;
|
|
|
|
|
|
|
|
for ( brush = head; brush; brush = next ) {
|
|
|
|
next = brush->next;
|
|
|
|
delete brush;
|
|
|
|
}
|
|
|
|
head = tail = NULL;
|
|
|
|
numBrushes = numBrushSides = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::Split
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrushList::Split( const idPlane &plane, int planeNum, idBrushList &frontList, idBrushList &backList, bool useBrushSavedPlaneSide ) {
|
|
|
|
idBrush *b, *front, *back;
|
|
|
|
|
|
|
|
frontList.Clear();
|
|
|
|
backList.Clear();
|
|
|
|
|
|
|
|
if ( !useBrushSavedPlaneSide ) {
|
|
|
|
for ( b = head; b; b = b->next ) {
|
|
|
|
b->Split( plane, planeNum, &front, &back );
|
|
|
|
if ( front ) {
|
|
|
|
frontList.AddToTail( front );
|
|
|
|
}
|
|
|
|
if ( back ) {
|
|
|
|
backList.AddToTail( back );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( b = head; b; b = b->next ) {
|
|
|
|
if ( b->savedPlaneSide & BRUSH_PLANESIDE_BOTH ) {
|
|
|
|
b->Split( plane, planeNum, &front, &back );
|
|
|
|
if ( front ) {
|
|
|
|
frontList.AddToTail( front );
|
|
|
|
}
|
|
|
|
if ( back ) {
|
|
|
|
backList.AddToTail( back );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( b->savedPlaneSide & BRUSH_PLANESIDE_FRONT ) {
|
|
|
|
frontList.AddToTail( b->Copy() );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
backList.AddToTail( b->Copy() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::Chop
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrushList::Chop( bool (*ChopAllowed)( idBrush *b1, idBrush *b2 ) ) {
|
|
|
|
idBrush *b1, *b2, *next;
|
|
|
|
idBrushList sub1, sub2, keep;
|
|
|
|
int i, j, c1, c2;
|
|
|
|
idPlaneSet planeList;
|
|
|
|
|
|
|
|
#ifdef OUTPUT_CHOP_STATS
|
|
|
|
common->Printf( "[Brush CSG]\n");
|
|
|
|
common->Printf( "%6d original brushes\n", this->Num() );
|
|
|
|
#endif
|
|
|
|
|
|
|
|
CreatePlaneList( planeList );
|
|
|
|
|
|
|
|
for ( b1 = this->Head(); b1; b1 = this->Head() ) {
|
|
|
|
|
|
|
|
for ( b2 = b1->next; b2; b2 = next ) {
|
|
|
|
|
|
|
|
next = b2->next;
|
|
|
|
|
|
|
|
for ( i = 0; i < 3; i++ ) {
|
|
|
|
if ( b1->bounds[0][i] >= b2->bounds[1][i] ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if ( b1->bounds[1][i] <= b2->bounds[0][i] ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( i < 3 ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( i = 0; i < b1->GetNumSides(); i++ ) {
|
|
|
|
for ( j = 0; j < b2->GetNumSides(); j++ ) {
|
|
|
|
if ( b1->GetSide(i)->GetPlaneNum() == ( b2->GetSide(j)->GetPlaneNum() ^ 1 ) ) {
|
|
|
|
// opposite planes, so not touching
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( j < b2->GetNumSides() ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( i < b1->GetNumSides() ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub1.Clear();
|
|
|
|
sub2.Clear();
|
|
|
|
|
|
|
|
c1 = 999999;
|
|
|
|
c2 = 999999;
|
|
|
|
|
|
|
|
// if b2 may chop up b1
|
|
|
|
if ( !ChopAllowed || ChopAllowed( b2, b1 ) ) {
|
|
|
|
if ( !b1->Subtract( b2, sub1 ) ) {
|
|
|
|
// didn't really intersect
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ( sub1.IsEmpty() ) {
|
|
|
|
// b1 is swallowed by b2
|
|
|
|
this->Delete( b1 );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
c1 = sub1.Num();
|
|
|
|
}
|
|
|
|
|
|
|
|
// if b1 may chop up b2
|
|
|
|
if ( !ChopAllowed || ChopAllowed( b1, b2 ) ) {
|
|
|
|
if ( !b2->Subtract( b1, sub2 ) ) {
|
|
|
|
// didn't really intersect
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ( sub2.IsEmpty() ) {
|
|
|
|
// b2 is swallowed by b1
|
|
|
|
sub1.Free();
|
|
|
|
this->Delete( b2 );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
c2 = sub2.Num();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( sub1.IsEmpty() && sub2.IsEmpty() ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// don't allow too much fragmentation
|
|
|
|
if ( c1 > 2 && c2 > 2 ) {
|
|
|
|
sub1.Free();
|
|
|
|
sub2.Free();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( c1 < c2 ) {
|
|
|
|
sub2.Free();
|
|
|
|
this->AddToTail( sub1 );
|
|
|
|
this->Delete( b1 );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
sub1.Free();
|
|
|
|
this->AddToTail( sub2 );
|
|
|
|
this->Delete( b2 );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !b2 ) {
|
|
|
|
// b1 is no longer intersecting anything, so keep it
|
|
|
|
this->Remove( b1 );
|
|
|
|
keep.AddToTail( b1 );
|
|
|
|
#ifdef OUTPUT_CHOP_STATS
|
|
|
|
DisplayRealTimeString( "\r%6d", keep.numBrushes );
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*this = keep;
|
|
|
|
|
|
|
|
#ifdef OUTPUT_CHOP_STATS
|
|
|
|
common->Printf( "\r%6d output brushes\n", Num() );
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::Merge
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrushList::Merge( bool (*MergeAllowed)( idBrush *b1, idBrush *b2 ) ) {
|
|
|
|
idPlaneSet planeList;
|
|
|
|
idBrush *b1, *b2, *nextb2;
|
|
|
|
int numMerges;
|
|
|
|
|
|
|
|
common->Printf( "[Brush Merge]\n");
|
|
|
|
common->Printf( "%6d original brushes\n", Num() );
|
|
|
|
|
|
|
|
CreatePlaneList( planeList );
|
|
|
|
|
|
|
|
numMerges = 0;
|
|
|
|
for ( b1 = Head(); b1; b1 = b1->next ) {
|
|
|
|
|
|
|
|
for ( b2 = Head(); b2; b2 = nextb2 ) {
|
|
|
|
nextb2 = b2->Next();
|
|
|
|
|
|
|
|
if ( b2 == b1 ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( MergeAllowed && !MergeAllowed( b1, b2 ) ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( b1->TryMerge( b2, planeList ) ) {
|
|
|
|
Delete( b2 );
|
|
|
|
DisplayRealTimeString( "\r%6d", ++numMerges );
|
|
|
|
nextb2 = Head();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
common->Printf( "\r%6d brushes merged\n", numMerges );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::SetFlagOnFacingBrushSides
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrushList::SetFlagOnFacingBrushSides( const idPlane &plane, int flag ) {
|
|
|
|
int i;
|
|
|
|
idBrush *b;
|
|
|
|
const idWinding *w;
|
|
|
|
|
|
|
|
for ( b = head; b; b = b->next ) {
|
|
|
|
if ( idMath::Fabs( b->GetBounds().PlaneDistance( plane ) ) > 0.1f ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
for ( i = 0; i < b->GetNumSides(); i++ ) {
|
|
|
|
w = b->GetSide(i)->GetWinding();
|
|
|
|
if ( !w ) {
|
|
|
|
if ( b->GetSide(i)->GetPlane().Compare( plane, BRUSH_PLANE_NORMAL_EPSILON, BRUSH_PLANE_DIST_EPSILON ) ) {
|
|
|
|
b->GetSide(i)->SetFlag( flag );
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ( w->PlaneSide( plane ) == SIDE_ON ) {
|
|
|
|
b->GetSide(i)->SetFlag( flag );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::CreatePlaneList
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrushList::CreatePlaneList( idPlaneSet &planeList ) const {
|
|
|
|
int i;
|
|
|
|
idBrush *b;
|
|
|
|
idBrushSide *side;
|
|
|
|
|
|
|
|
planeList.Resize( 512, 128 );
|
|
|
|
for ( b = Head(); b; b = b->Next() ) {
|
|
|
|
for ( i = 0; i < b->GetNumSides(); i++ ) {
|
|
|
|
side = b->GetSide( i );
|
|
|
|
side->SetPlaneNum( planeList.FindPlane( side->GetPlane(), BRUSH_PLANE_NORMAL_EPSILON, BRUSH_PLANE_DIST_EPSILON ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushList::CreatePlaneList
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrushList::WriteBrushMap( const idStr &fileName, const idStr &ext ) const {
|
|
|
|
idBrushMap *map;
|
|
|
|
|
|
|
|
map = new idBrushMap( fileName, ext );
|
|
|
|
map->WriteBrushList( *this );
|
|
|
|
delete map;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===============================================================
|
|
|
|
//
|
|
|
|
// idBrushMap
|
|
|
|
//
|
|
|
|
//===============================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushMap::idBrushMap
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
idBrushMap::idBrushMap( const idStr &fileName, const idStr &ext ) {
|
|
|
|
idStr qpath;
|
|
|
|
|
|
|
|
qpath = fileName;
|
|
|
|
qpath.StripFileExtension();
|
|
|
|
qpath += ext;
|
|
|
|
qpath.SetFileExtension( "map" );
|
|
|
|
|
|
|
|
common->Printf( "writing %s...\n", qpath.c_str() );
|
|
|
|
|
|
|
|
fp = fileSystem->OpenFileWrite( qpath, "fs_devpath" );
|
|
|
|
if ( !fp ) {
|
|
|
|
common->Error( "Couldn't open %s\n", qpath.c_str() );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
texture = "textures/washroom/btile01";
|
|
|
|
|
|
|
|
fp->WriteFloatString( "Version %1.2f\n", (float) CURRENT_MAP_VERSION );
|
|
|
|
fp->WriteFloatString( "{\n" );
|
|
|
|
fp->WriteFloatString( "\"classname\" \"worldspawn\"\n" );
|
|
|
|
|
|
|
|
brushCount = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushMap::~idBrushMap
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
idBrushMap::~idBrushMap( void ) {
|
|
|
|
if ( !fp ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
fp->WriteFloatString( "}\n" );
|
|
|
|
fileSystem->CloseFile( fp );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushMap::WriteBrush
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrushMap::WriteBrush( const idBrush *brush ) {
|
|
|
|
int i;
|
|
|
|
idBrushSide *side;
|
|
|
|
|
|
|
|
if ( !fp ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
fp->WriteFloatString( "// primitive %d\n{\nbrushDef3\n{\n", brushCount++ );
|
|
|
|
|
|
|
|
for ( i = 0; i < brush->GetNumSides(); i++ ) {
|
|
|
|
side = brush->GetSide( i );
|
|
|
|
fp->WriteFloatString( " ( %f %f %f %f ) ", side->GetPlane()[0], side->GetPlane()[1], side->GetPlane()[2], -side->GetPlane().Dist() );
|
|
|
|
fp->WriteFloatString( "( ( 0.031250 0 0 ) ( 0 0.031250 0 ) ) %s 0 0 0\n", texture.c_str() );
|
|
|
|
|
|
|
|
}
|
|
|
|
fp->WriteFloatString( "}\n}\n" );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
idBrushMap::WriteBrushList
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void idBrushMap::WriteBrushList( const idBrushList &brushList ) {
|
|
|
|
idBrush *b;
|
|
|
|
|
|
|
|
if ( !fp ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( b = brushList.Head(); b; b = b->Next() ) {
|
|
|
|
WriteBrush( b );
|
|
|
|
}
|
|
|
|
}
|