/* =========================================================================== 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 "dmap.h" interAreaPortal_t interAreaPortals[MAX_INTER_AREA_PORTALS]; int numInterAreaPortals; int c_active_portals; int c_peak_portals; /* =========== 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 ; iepairs.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; } //============================================================= /* ================= 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 } 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; } } /* ============= 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; // 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] ) { numInterAreaPortals = 0; FindInterAreaPortals_r( e->tree->headnode ); } } /* ====================================================== 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); }