Files
DoomRTX/neo/engine/tools/compilers/dmap/portals.cpp
T

1275 lines
27 KiB
C++

/*
===========================================================================
IceTech GPL Source Code
Copyright (C) 2026 Justin Marshall
This file is part of the IceTech GPL Source Code (?IceTech Source Code?).
IceTech 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.
IceTech 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 IceTech Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the IceTech 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 IceTech 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 Justin Marshall, justinmarshall20@gmail.com
===========================================================================
*/
#include "precompiled.h"
#pragma hdrstop
#include "dmap.h"
interAreaPortal_t interAreaPortals[MAX_INTER_AREA_PORTALS];
int numInterAreaPortals;
int c_active_portals;
int c_peak_portals;
static int c_autoAreaRefineSplits;
static int c_autoAreaRefinePortals;
static int c_autoAreaRefineFlood;
#define AUTO_AREA_REFINE_MIN_LEAFS 12
#define AUTO_AREA_REFINE_MIN_SPLIT_LEAFS 4
#define AUTO_AREA_REFINE_MAX_SPLITS 256
#define AUTO_AREA_REFINE_MIN_PORTAL_AREA 64.0f
#define AUTO_AREA_REFINE_MAX_PORTAL_AREA 65536.0f
/*
=============
ClearInterAreaPortals
=============
*/
void ClearInterAreaPortals( void ) {
for ( int i = 0 ; i < numInterAreaPortals ; i++ ) {
if ( interAreaPortals[i].generated && interAreaPortals[i].winding ) {
delete interAreaPortals[i].winding;
}
interAreaPortals[i].side = NULL;
interAreaPortals[i].winding = NULL;
interAreaPortals[i].generated = false;
}
numInterAreaPortals = 0;
}
/*
===========
AllocPortal
===========
*/
uPortal_t *AllocPortal (void)
{
uPortal_t *p;
c_active_portals++;
if (c_active_portals > c_peak_portals)
c_peak_portals = c_active_portals;
p = (uPortal_t *)Mem_Alloc (sizeof(uPortal_t ));
memset (p, 0, sizeof(uPortal_t ));
return p;
}
void FreePortal (uPortal_t *p)
{
if (p->winding)
delete p->winding;
c_active_portals--;
Mem_Free (p);
}
//==============================================================
/*
=============
Portal_Passable
Returns true if the portal has non-opaque leafs on both sides
=============
*/
static bool Portal_Passable( uPortal_t *p ) {
if (!p->onnode) {
return false; // to global outsideleaf
}
if (p->nodes[0]->planenum != PLANENUM_LEAF
|| p->nodes[1]->planenum != PLANENUM_LEAF) {
common->Error( "Portal_EntityFlood: not a leaf");
}
if ( !p->nodes[0]->opaque && !p->nodes[1]->opaque ) {
return true;
}
return false;
}
//=============================================================================
int c_tinyportals;
/*
=============
AddPortalToNodes
=============
*/
void AddPortalToNodes (uPortal_t *p, node_t *front, node_t *back) {
if (p->nodes[0] || p->nodes[1]) {
common->Error( "AddPortalToNode: allready included");
}
p->nodes[0] = front;
p->next[0] = front->portals;
front->portals = p;
p->nodes[1] = back;
p->next[1] = back->portals;
back->portals = p;
}
/*
=============
RemovePortalFromNode
=============
*/
void RemovePortalFromNode (uPortal_t *portal, node_t *l)
{
uPortal_t **pp, *t;
// remove reference to the current portal
pp = &l->portals;
while (1)
{
t = *pp;
if (!t)
common->Error( "RemovePortalFromNode: portal not in leaf");
if ( t == portal )
break;
if (t->nodes[0] == l)
pp = &t->next[0];
else if (t->nodes[1] == l)
pp = &t->next[1];
else
common->Error( "RemovePortalFromNode: portal not bounding leaf");
}
if ( portal->nodes[0] == l ) {
*pp = portal->next[0];
portal->nodes[0] = NULL;
} else if ( portal->nodes[1] == l ) {
*pp = portal->next[1];
portal->nodes[1] = NULL;
} else {
common->Error( "RemovePortalFromNode: mislinked" );
}
}
//============================================================================
void PrintPortal (uPortal_t *p)
{
int i;
idWinding *w;
w = p->winding;
for ( i = 0; i < w->GetNumPoints(); i++ )
common->Printf("(%5.0f,%5.0f,%5.0f)\n",(*w)[i][0], (*w)[i][1], (*w)[i][2]);
}
/*
================
MakeHeadnodePortals
The created portals will face the global outside_node
================
*/
#define SIDESPACE 8
static void MakeHeadnodePortals( tree_t *tree ) {
idBounds bounds;
int i, j, n;
uPortal_t *p, *portals[6];
idPlane bplanes[6], *pl;
node_t *node;
node = tree->headnode;
tree->outside_node.planenum = PLANENUM_LEAF;
tree->outside_node.brushlist = NULL;
tree->outside_node.portals = NULL;
tree->outside_node.opaque = false;
// if no nodes, don't go any farther
if ( node->planenum == PLANENUM_LEAF ) {
return;
}
// pad with some space so there will never be null volume leafs
for (i=0 ; i<3 ; i++) {
bounds[0][i] = tree->bounds[0][i] - SIDESPACE;
bounds[1][i] = tree->bounds[1][i] + SIDESPACE;
if ( bounds[0][i] >= bounds[1][i] ) {
common->Error( "Backwards tree volume" );
}
}
for (i=0 ; i<3 ; i++) {
for (j=0 ; j<2 ; j++) {
n = j*3 + i;
p = AllocPortal ();
portals[n] = p;
pl = &bplanes[n];
memset (pl, 0, sizeof(*pl));
if (j) {
(*pl)[i] = -1;
(*pl)[3] = bounds[j][i];
} else {
(*pl)[i] = 1;
(*pl)[3] = -bounds[j][i];
}
p->plane = *pl;
p->winding = new idWinding( *pl );
AddPortalToNodes (p, node, &tree->outside_node);
}
}
// clip the basewindings by 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( bplanes[j], ON_EPSILON );
}
}
}
//===================================================
/*
================
BaseWindingForNode
================
*/
#define BASE_WINDING_EPSILON 0.001f
#define SPLIT_WINDING_EPSILON 0.001f
idWinding *BaseWindingForNode (node_t *node) {
idWinding *w;
node_t *n;
w = new idWinding( dmapGlobals.mapPlanes[node->planenum] );
// clip by all the parents
for ( n = node->parent ; n && w ; ) {
idPlane &plane = dmapGlobals.mapPlanes[n->planenum];
if ( n->children[0] == node ) {
// take front
w = w->Clip( plane, BASE_WINDING_EPSILON );
} else {
// take back
idPlane back = -plane;
w = w->Clip( back, BASE_WINDING_EPSILON );
}
node = n;
n = n->parent;
}
return w;
}
//============================================================
/*
==================
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
==================
*/
static void MakeNodePortal( node_t *node ) {
uPortal_t *new_portal, *p;
idWinding *w;
idVec3 normal;
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])
{
idPlane plane;
if (p->nodes[0] == node)
{
side = 0;
plane = p->plane;
}
else if (p->nodes[1] == node)
{
side = 1;
plane = -p->plane;
}
else {
common->Error( "CutNodePortals_r: mislinked portal");
side = 0; // quiet a compiler warning
}
w = w->Clip( plane, CLIP_EPSILON );
}
if (!w)
{
return;
}
if ( w->IsTiny() )
{
c_tinyportals++;
delete w;
return;
}
new_portal = AllocPortal ();
new_portal->plane = dmapGlobals.mapPlanes[node->planenum];
new_portal->onnode = node;
new_portal->winding = w;
AddPortalToNodes (new_portal, node->children[0], node->children[1]);
}
/*
==============
SplitNodePortals
Move or split the portals that bound node so that the node's
children have portals instead of node.
==============
*/
static void SplitNodePortals( node_t *node ) {
uPortal_t *p, *next_portal, *new_portal;
node_t *f, *b, *other_node;
int side;
idPlane *plane;
idWinding *frontwinding, *backwinding;
plane = &dmapGlobals.mapPlanes[node->planenum];
f = node->children[0];
b = node->children[1];
for ( p = node->portals ; p ; p = next_portal ) {
if (p->nodes[0] == node ) {
side = 0;
} else if ( p->nodes[1] == node ) {
side = 1;
} else {
common->Error( "SplitNodePortals: mislinked portal" );
side = 0; // quiet a compiler warning
}
next_portal = p->next[side];
other_node = p->nodes[!side];
RemovePortalFromNode (p, p->nodes[0]);
RemovePortalFromNode (p, 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;
c_tinyportals++;
}
if ( backwinding && backwinding->IsTiny() )
{
delete backwinding;
backwinding = NULL;
c_tinyportals++;
}
if ( !frontwinding && !backwinding )
{ // tiny windings on both sides
continue;
}
if (!frontwinding)
{
delete backwinding;
if (side == 0)
AddPortalToNodes (p, b, other_node);
else
AddPortalToNodes (p, other_node, b);
continue;
}
if (!backwinding)
{
delete frontwinding;
if (side == 0)
AddPortalToNodes (p, f, other_node);
else
AddPortalToNodes (p, other_node, f);
continue;
}
// the winding is split
new_portal = AllocPortal ();
*new_portal = *p;
new_portal->winding = backwinding;
delete p->winding;
p->winding = frontwinding;
if (side == 0)
{
AddPortalToNodes (p, f, other_node);
AddPortalToNodes (new_portal, b, other_node);
}
else
{
AddPortalToNodes (p, other_node, f);
AddPortalToNodes (new_portal, other_node, b);
}
}
node->portals = NULL;
}
/*
================
CalcNodeBounds
================
*/
void CalcNodeBounds (node_t *node)
{
uPortal_t *p;
int s;
int i;
// calc mins/maxs for both leafs and nodes
node->bounds.Clear();
for (p = node->portals ; p ; p = p->next[s]) {
s = (p->nodes[1] == node);
for ( i = 0; i < p->winding->GetNumPoints(); i++ ) {
node->bounds.AddPoint( (*p->winding)[i].ToVec3() );
}
}
}
/*
==================
MakeTreePortals_r
==================
*/
void MakeTreePortals_r (node_t *node)
{
int i;
CalcNodeBounds( node );
if ( node->bounds[0][0] >= node->bounds[1][0]) {
common->Warning( "node without a volume" );
}
for ( i = 0; i < 3; i++ ) {
if ( node->bounds[0][i] < MIN_WORLD_COORD || node->bounds[1][i] > MAX_WORLD_COORD ) {
common->Warning( "node with unbounded volume");
break;
}
}
if ( node->planenum == PLANENUM_LEAF ) {
return;
}
MakeNodePortal (node);
SplitNodePortals (node);
MakeTreePortals_r (node->children[0]);
MakeTreePortals_r (node->children[1]);
}
/*
==================
MakeTreePortals
==================
*/
void MakeTreePortals (tree_t *tree)
{
common->Printf( "----- MakeTreePortals -----\n");
MakeHeadnodePortals (tree);
MakeTreePortals_r (tree->headnode);
}
/*
=========================================================
FLOOD ENTITIES
=========================================================
*/
int c_floodedleafs;
/*
=============
FloodPortals_r
=============
*/
void FloodPortals_r (node_t *node, int dist) {
uPortal_t *p;
int s;
if ( node->occupied ) {
return;
}
if ( node->opaque ) {
return;
}
c_floodedleafs++;
node->occupied = dist;
for (p=node->portals ; p ; p = p->next[s]) {
s = (p->nodes[1] == node);
FloodPortals_r (p->nodes[!s], dist+1);
}
}
/*
=============
PlaceOccupant
=============
*/
bool PlaceOccupant( node_t *headnode, idVec3 origin, uEntity_t *occupant ) {
node_t *node;
float d;
idPlane *plane;
// find the leaf to start in
node = headnode;
while ( node->planenum != PLANENUM_LEAF ) {
plane = &dmapGlobals.mapPlanes[node->planenum];
d = plane->Distance( origin );
if ( d >= 0.0f ) {
node = node->children[0];
} else {
node = node->children[1];
}
}
if ( node->opaque ) {
return false;
}
node->occupant = occupant;
FloodPortals_r (node, 1);
return true;
}
/*
=============
FloodEntities
Marks all nodes that can be reached by entites
=============
*/
bool FloodEntities( tree_t *tree ) {
int i;
idVec3 origin;
const char *cl;
bool inside;
node_t *headnode;
headnode = tree->headnode;
common->Printf ("--- FloodEntities ---\n");
inside = false;
tree->outside_node.occupied = 0;
c_floodedleafs = 0;
bool errorShown = false;
for (i=1 ; i<dmapGlobals.num_entities ; i++) {
idMapEntity *mapEnt;
mapEnt = dmapGlobals.uEntities[i].mapEntity;
if ( !mapEnt->epairs.GetVector( "origin", "", origin) ) {
continue;
}
// any entity can have "noFlood" set to skip it
if ( mapEnt->epairs.GetString( "noFlood", "", &cl ) ) {
continue;
}
mapEnt->epairs.GetString( "classname", "", &cl );
if ( !strcmp( cl, "light" ) ) {
const char *v;
// don't place lights that have a light_start field, because they can still
// be valid if their origin is outside the world
mapEnt->epairs.GetString( "light_start", "", &v);
if ( v[0] ) {
continue;
}
// don't place fog lights, because they often
// have origins outside the light
mapEnt->epairs.GetString( "texture", "", &v);
if ( v[0] ) {
const idMaterial *mat = declManager->FindMaterial( v );
if ( mat->IsFogLight() ) {
continue;
}
}
}
if (PlaceOccupant (headnode, origin, &dmapGlobals.uEntities[i])) {
inside = true;
}
if (tree->outside_node.occupied && !errorShown) {
errorShown = true;
common->Printf("Leak on entity # %d\n", i);
const char *p;
mapEnt->epairs.GetString( "classname", "", &p);
common->Printf("Entity classname was: %s\n", p);
mapEnt->epairs.GetString( "name", "", &p);
common->Printf("Entity name was: %s\n", p);
idVec3 origin;
if ( mapEnt->epairs.GetVector( "origin", "", origin)) {
common->Printf("Entity origin is: %f %f %f\n\n\n", origin.x, origin.y, origin.z);
}
}
}
common->Printf("%5i flooded leafs\n", c_floodedleafs );
if (!inside)
{
common->Printf ("no entities in open -- no filling\n");
}
else if (tree->outside_node.occupied)
{
common->Printf ("entity reached from outside -- no filling\n");
}
return (bool)(inside && !tree->outside_node.occupied);
}
/*
=========================================================
FLOOD AREAS
=========================================================
*/
static int c_areas;
static int c_areaFloods;
/*
=================
FindSideForPortal
=================
*/
static side_t *FindSideForPortal( uPortal_t *p ) {
int i, j, k;
node_t *node;
uBrush_t *b, *orig;
side_t *s, *s2;
// scan both bordering nodes brush lists for a portal brush
// that shares the plane
for ( i = 0 ; i < 2 ; i++ ) {
node = p->nodes[i];
for ( b = node->brushlist ; b ; b = b->next ) {
if ( !( b->contents & CONTENTS_AREAPORTAL ) ) {
continue;
}
orig = b->original;
for ( j = 0 ; j < orig->numsides ; j++ ) {
s = orig->sides + j;
if ( !s->visibleHull ) {
continue;
}
if ( !( s->material->GetContentFlags() & CONTENTS_AREAPORTAL ) ) {
continue;
}
if ( ( s->planenum & ~1 ) != ( p->onnode->planenum & ~1 ) ) {
continue;
}
// remove the visible hull from any other portal sides of this portal brush
for ( k = 0; k < orig->numsides; k++ ) {
if ( k == j ) {
continue;
}
s2 = orig->sides + k;
if ( s2->visibleHull == NULL ) {
continue;
}
if ( !( s2->material->GetContentFlags() & CONTENTS_AREAPORTAL ) ) {
continue;
}
common->Warning( "brush has multiple area portal sides at %s", s2->visibleHull->GetCenter().ToString() );
delete s2->visibleHull;
s2->visibleHull = NULL;
}
return s;
}
}
}
return NULL;
}
/*
=============
FloodAreas_r
=============
*/
void FloodAreas_r (node_t *node)
{
uPortal_t *p;
int s;
if ( node->area != -1 ) {
return; // allready got it
}
if ( node->opaque ) {
return;
}
c_areaFloods++;
node->area = c_areas;
for ( p=node->portals ; p ; p = p->next[s] ) {
node_t *other;
s = (p->nodes[1] == node);
other = p->nodes[!s];
if ( !Portal_Passable(p) ) {
continue;
}
// can't flood through an area portal
if ( FindSideForPortal( p ) ) {
continue;
}
FloodAreas_r( other );
}
}
/*
=============
FindAreas_r
Just decend the tree, and for each node that hasn't had an
area set, flood fill out from there
=============
*/
void FindAreas_r( node_t *node ) {
if ( node->planenum != PLANENUM_LEAF ) {
FindAreas_r (node->children[0]);
FindAreas_r (node->children[1]);
return;
}
if ( node->opaque ) {
return;
}
if ( node->area != -1 ) {
return; // allready got it
}
c_areaFloods = 0;
FloodAreas_r (node);
common->Printf( "area %i has %i leafs\n", c_areas, c_areaFloods );
c_areas++;
}
/*
============
CheckAreas_r
============
*/
void CheckAreas_r( node_t *node ) {
if ( node->planenum != PLANENUM_LEAF ) {
CheckAreas_r (node->children[0]);
CheckAreas_r (node->children[1]);
return;
}
if ( !node->opaque && node->area < 0 ) {
common->Error( "CheckAreas_r: area = %i", node->area );
}
}
/*
============
ClearAreas_r
Set all the areas to -1 before filling
============
*/
void ClearAreas_r( node_t *node ) {
if ( node->planenum != PLANENUM_LEAF ) {
ClearAreas_r (node->children[0]);
ClearAreas_r (node->children[1]);
return;
}
node->area = -1;
node->originalArea = -1;
node->areaRefineFlood = 0;
}
//=============================================================
static void MarkOriginalAreas_r( node_t *node ) {
if ( node->planenum != PLANENUM_LEAF ) {
MarkOriginalAreas_r( node->children[0] );
MarkOriginalAreas_r( node->children[1] );
return;
}
node->originalArea = node->area;
}
static void CollectAreaLeaves_r( node_t *node, int area, idList<node_t *> &leaves ) {
if ( node->planenum != PLANENUM_LEAF ) {
CollectAreaLeaves_r( node->children[0], area, leaves );
CollectAreaLeaves_r( node->children[1], area, leaves );
return;
}
if ( !node->opaque && node->area == area ) {
leaves.Append( node );
}
}
static void FloodAreaComponent_r( node_t *node, int area, const uPortal_t *blockedPortal, int floodNum, idList<node_t *> &component ) {
uPortal_t *p;
int s;
if ( node->planenum != PLANENUM_LEAF || node->opaque || node->area != area ) {
return;
}
if ( node->areaRefineFlood == floodNum ) {
return;
}
node->areaRefineFlood = floodNum;
component.Append( node );
for ( p = node->portals ; p ; p = p->next[s] ) {
s = ( p->nodes[1] == node );
if ( p == blockedPortal ) {
continue;
}
if ( !Portal_Passable( p ) ) {
continue;
}
FloodAreaComponent_r( p->nodes[!s], area, blockedPortal, floodNum, component );
}
}
static uPortal_t *FindBestAutoAreaPortal( int area, const idList<node_t *> &leaves ) {
uPortal_t *bestPortal;
float bestScore;
bestPortal = NULL;
bestScore = 0.0f;
for ( int i = 0 ; i < leaves.Num() ; i++ ) {
node_t *node = leaves[i];
int s;
for ( uPortal_t *p = node->portals ; p ; p = p->next[s] ) {
s = ( p->nodes[1] == node );
if ( p->nodes[0] != node ) {
continue; // evaluate each portal once
}
if ( !Portal_Passable( p ) ) {
continue;
}
if ( p->nodes[1]->area != area ) {
continue;
}
float portalArea = p->winding->GetArea();
if ( portalArea < AUTO_AREA_REFINE_MIN_PORTAL_AREA || portalArea > AUTO_AREA_REFINE_MAX_PORTAL_AREA ) {
continue;
}
idList<node_t *> component;
FloodAreaComponent_r( p->nodes[0], area, p, ++c_autoAreaRefineFlood, component );
int frontLeafs = component.Num();
int backLeafs = leaves.Num() - frontLeafs;
if ( frontLeafs < AUTO_AREA_REFINE_MIN_SPLIT_LEAFS || backLeafs < AUTO_AREA_REFINE_MIN_SPLIT_LEAFS ) {
continue;
}
int smaller = frontLeafs < backLeafs ? frontLeafs : backLeafs;
int larger = frontLeafs > backLeafs ? frontLeafs : backLeafs;
float balance = (float)smaller / (float)larger;
float score = balance * 100000.0f / portalArea;
if ( score > bestScore ) {
bestScore = score;
bestPortal = p;
}
}
}
return bestPortal;
}
static bool SplitAreaWithGeneratedPortal( uEntity_t *e, int area ) {
idList<node_t *> leaves;
idList<node_t *> component;
CollectAreaLeaves_r( e->tree->headnode, area, leaves );
if ( leaves.Num() < AUTO_AREA_REFINE_MIN_LEAFS ) {
return false;
}
uPortal_t *portal = FindBestAutoAreaPortal( area, leaves );
if ( !portal ) {
return false;
}
int floodNum = ++c_autoAreaRefineFlood;
FloodAreaComponent_r( portal->nodes[0], area, portal, floodNum, component );
if ( component.Num() <= 0 || component.Num() >= leaves.Num() ) {
return false;
}
int newArea = e->numAreas;
e->numAreas++;
// Keep the larger partition on the original area number so repeated
// refinement tends to continue from the broadest remaining space.
if ( component.Num() <= leaves.Num() / 2 ) {
for ( int i = 0 ; i < component.Num() ; i++ ) {
component[i]->area = newArea;
}
} else {
for ( int i = 0 ; i < leaves.Num() ; i++ ) {
if ( leaves[i]->areaRefineFlood != floodNum ) {
leaves[i]->area = newArea;
}
}
}
c_autoAreaRefineSplits++;
return true;
}
static void AutoRefineAreas( uEntity_t *e ) {
int startingAreas;
if ( !dmapGlobals.autoAreaRefine ) {
return;
}
if ( e != &dmapGlobals.uEntities[0] ) {
return;
}
startingAreas = e->numAreas;
c_autoAreaRefineSplits = 0;
c_autoAreaRefinePortals = 0;
c_autoAreaRefineFlood = 0;
for ( int area = 0 ; area < e->numAreas && c_autoAreaRefineSplits < AUTO_AREA_REFINE_MAX_SPLITS ; area++ ) {
while ( c_autoAreaRefineSplits < AUTO_AREA_REFINE_MAX_SPLITS ) {
if ( !SplitAreaWithGeneratedPortal( e, area ) ) {
break;
}
}
}
if ( c_autoAreaRefineSplits > 0 ) {
common->Printf( "%5i auto area refinement splits (%i -> %i areas)\n", c_autoAreaRefineSplits, startingAreas, e->numAreas );
} else {
common->Printf( "%5i auto area refinement splits\n", 0 );
}
}
static void AddGeneratedInterAreaPortal( uPortal_t *p ) {
if ( numInterAreaPortals >= MAX_INTER_AREA_PORTALS ) {
common->Warning( "MAX_INTER_AREA_PORTALS hit while adding generated area portals" );
return;
}
if ( !p->winding || p->winding->IsTiny() ) {
return;
}
interAreaPortal_t *iap = &interAreaPortals[numInterAreaPortals];
numInterAreaPortals++;
iap->area0 = p->nodes[0]->area;
iap->area1 = p->nodes[1]->area;
iap->side = NULL;
iap->winding = p->winding->Copy();
iap->generated = true;
c_autoAreaRefinePortals++;
}
static void FindGeneratedInterAreaPortals_r( node_t *node ) {
uPortal_t *p;
int s;
if ( node->planenum != PLANENUM_LEAF ) {
FindGeneratedInterAreaPortals_r( node->children[0] );
FindGeneratedInterAreaPortals_r( node->children[1] );
return;
}
if ( node->opaque ) {
return;
}
for ( p = node->portals ; p ; p = p->next[s] ) {
node_t *other;
s = ( p->nodes[1] == node );
other = p->nodes[!s];
if ( other->opaque ) {
continue;
}
if ( !Portal_Passable( p ) ) {
continue;
}
if ( other->area <= node->area ) {
continue;
}
if ( other->originalArea != node->originalArea ) {
continue; // authored areaportal boundary
}
if ( FindSideForPortal( p ) ) {
continue; // designer-authored portal, already emitted
}
AddGeneratedInterAreaPortal( p );
}
}
/*
=================
FindInterAreaPortals_r
=================
*/
static void FindInterAreaPortals_r( node_t *node ) {
uPortal_t *p;
int s;
int i;
idWinding *w;
interAreaPortal_t *iap;
side_t *side;
if ( node->planenum != PLANENUM_LEAF ) {
FindInterAreaPortals_r( node->children[0] );
FindInterAreaPortals_r( node->children[1] );
return;
}
if ( node->opaque ) {
return;
}
for ( p=node->portals ; p ; p = p->next[s] ) {
node_t *other;
s = (p->nodes[1] == node);
other = p->nodes[!s];
if ( other->opaque ) {
continue;
}
// only report areas going from lower number to higher number
// so we don't report the portal twice
if ( other->area <= node->area ) {
continue;
}
side = FindSideForPortal( p );
// w = p->winding;
if ( !side ) {
common->Warning( "FindSideForPortal failed at %s", p->winding->GetCenter().ToString() );
continue;
}
w = side->visibleHull;
if ( !w ) {
continue;
}
// see if we have created this portal before
for ( i = 0 ; i < numInterAreaPortals ; i++ ) {
iap = &interAreaPortals[i];
if ( side == iap->side &&
( ( p->nodes[0]->area == iap->area0 && p->nodes[1]->area == iap->area1 )
|| ( p->nodes[1]->area == iap->area0 && p->nodes[0]->area == iap->area1 ) ) ) {
break;
}
}
if ( i != numInterAreaPortals ) {
continue; // already emited
}
if ( numInterAreaPortals >= MAX_INTER_AREA_PORTALS ) {
common->Warning( "MAX_INTER_AREA_PORTALS hit while adding designer area portals" );
continue;
}
iap = &interAreaPortals[numInterAreaPortals];
numInterAreaPortals++;
if ( side->planenum == p->onnode->planenum ) {
iap->area0 = p->nodes[0]->area;
iap->area1 = p->nodes[1]->area;
} else {
iap->area0 = p->nodes[1]->area;
iap->area1 = p->nodes[0]->area;
}
iap->side = side;
iap->winding = NULL;
iap->generated = false;
}
}
/*
=============
FloodAreas
Mark each leaf with an area, bounded by CONTENTS_AREAPORTAL
Sets e->areas.numAreas
=============
*/
void FloodAreas( uEntity_t *e ) {
common->Printf ("--- FloodAreas ---\n");
// set all areas to -1
ClearAreas_r( e->tree->headnode );
// flood fill from non-opaque areas
c_areas = 0;
FindAreas_r( e->tree->headnode );
common->Printf ("%5i areas\n", c_areas);
e->numAreas = c_areas;
MarkOriginalAreas_r( e->tree->headnode );
AutoRefineAreas( e );
// make sure we got all of them
CheckAreas_r( e->tree->headnode );
// identify all portals between areas if this is the world
if ( e == &dmapGlobals.uEntities[0] ) {
ClearInterAreaPortals();
FindInterAreaPortals_r( e->tree->headnode );
if ( dmapGlobals.autoAreaRefine ) {
FindGeneratedInterAreaPortals_r( e->tree->headnode );
common->Printf( "%5i generated inter-area portals\n", c_autoAreaRefinePortals );
}
}
}
/*
======================================================
FILL OUTSIDE
======================================================
*/
static int c_outside;
static int c_inside;
static int c_solid;
void FillOutside_r (node_t *node)
{
if (node->planenum != PLANENUM_LEAF)
{
FillOutside_r (node->children[0]);
FillOutside_r (node->children[1]);
return;
}
// anything not reachable by an entity
// can be filled away
if (!node->occupied) {
if ( !node->opaque ) {
c_outside++;
node->opaque = true;
} else {
c_solid++;
}
} else {
c_inside++;
}
}
/*
=============
FillOutside
Fill (set node->opaque = true) all nodes that can't be reached by entities
=============
*/
void FillOutside( uEntity_t *e ) {
c_outside = 0;
c_inside = 0;
c_solid = 0;
common->Printf ("--- FillOutside ---\n");
FillOutside_r( e->tree->headnode );
common->Printf ("%5i solid leafs\n", c_solid);
common->Printf ("%5i leafs filled\n", c_outside);
common->Printf ("%5i inside leafs\n", c_inside);
}