/* =========================================================================== Return to Castle Wolfenstein single player GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Return to Castle Wolfenstein single player GPL Source Code (“RTCW SP Source Code”). RTCW SP 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. RTCW SP 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 RTCW SP Source Code. If not, see . In addition, the RTCW SP 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 RTCW SP 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. =========================================================================== */ //=========================================================================== // // Name: Portals // Function: partalizing a bsp tree, flooding through portals // Programmer: id Software & Mr Elusive (MrElusive@worldentity.com) // Last update: 1999-08-10 // Tab Size: 3 //=========================================================================== #include "qbsp.h" #include "l_mem.h" int c_active_portals; int c_peak_portals; int c_boundary; int c_boundary_sides; int c_portalmemory; //portal_t *portallist = NULL; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== portal_t *AllocPortal( void ) { portal_t *p; p = GetMemory( sizeof( portal_t ) ); memset( p, 0, sizeof( portal_t ) ); if ( numthreads == 1 ) { c_active_portals++; if ( c_active_portals > c_peak_portals ) { c_peak_portals = c_active_portals; } //end if c_portalmemory += MemorySize( p ); } //end if // p->nextportal = portallist; // portallist = p; return p; } //end of the function AllocPortal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FreePortal( portal_t *p ) { if ( p->winding ) { FreeWinding( p->winding ); } if ( numthreads == 1 ) { c_active_portals--; c_portalmemory -= MemorySize( p ); } //end if FreeMemory( p ); } //end of the function FreePortal //=========================================================================== // Returns the single content bit of the // strongest visible content present // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int VisibleContents( int contents ) { int i; for ( i = 1 ; i <= LAST_VISIBLE_CONTENTS ; i <<= 1 ) if ( contents & i ) { return i; } return 0; } //end of the function VisibleContents //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int ClusterContents( node_t *node ) { int c1, c2, c; if ( node->planenum == PLANENUM_LEAF ) { return node->contents; } c1 = ClusterContents( node->children[0] ); c2 = ClusterContents( node->children[1] ); c = c1 | c2; // a cluster may include some solid detail areas, but // still be seen into if ( !( c1 & CONTENTS_SOLID ) || !( c2 & CONTENTS_SOLID ) ) { c &= ~CONTENTS_SOLID; } return c; } //end of the function ClusterContents //=========================================================================== // Returns true if the portal is empty or translucent, allowing // the PVS calculation to see through it. // The nodes on either side of the portal may actually be clusters, // not leaves, so all contents should be ored together // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean Portal_VisFlood( portal_t *p ) { int c1, c2; if ( !p->onnode ) { return false; // to global outsideleaf } c1 = ClusterContents( p->nodes[0] ); c2 = ClusterContents( p->nodes[1] ); if ( !VisibleContents( c1 ^ c2 ) ) { return true; } if ( c1 & ( CONTENTS_Q2TRANSLUCENT | CONTENTS_DETAIL ) ) { c1 = 0; } if ( c2 & ( CONTENTS_Q2TRANSLUCENT | CONTENTS_DETAIL ) ) { c2 = 0; } if ( ( c1 | c2 ) & CONTENTS_SOLID ) { return false; // can't see through solid } if ( !( c1 ^ c2 ) ) { return true; // identical on both sides } if ( !VisibleContents( c1 ^ c2 ) ) { return true; } return false; } //end of the function Portal_VisFlood //=========================================================================== // The entity flood determines which areas are // "outside" on the map, which are then filled in. // Flowing from side s to side !s // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean Portal_EntityFlood( portal_t *p, int s ) { if ( p->nodes[0]->planenum != PLANENUM_LEAF || p->nodes[1]->planenum != PLANENUM_LEAF ) { Error( "Portal_EntityFlood: not a leaf" ); } // can never cross to a solid if ( ( p->nodes[0]->contents & CONTENTS_SOLID ) || ( p->nodes[1]->contents & CONTENTS_SOLID ) ) { return false; } // can flood through everything else return true; } //============================================================================= int c_tinyportals; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AddPortalToNodes( portal_t *p, node_t *front, node_t *back ) { if ( p->nodes[0] || p->nodes[1] ) { 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; } //end of the function AddPortalToNodes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void RemovePortalFromNode( portal_t *portal, node_t *l ) { portal_t **pp, *t; int s, i, n; portal_t *p; portal_t *portals[4096]; // remove reference to the current portal pp = &l->portals; while ( 1 ) { t = *pp; if ( !t ) { 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 { Error( "RemovePortalFromNode: portal not bounding leaf" ); } } if ( portal->nodes[0] == l ) { *pp = portal->next[0]; portal->nodes[0] = NULL; } //end if else if ( portal->nodes[1] == l ) { *pp = portal->next[1]; portal->nodes[1] = NULL; } //end else if else { Error( "RemovePortalFromNode: mislinked portal" ); } //end else //#ifdef ME n = 0; for ( p = l->portals; p; p = p->next[s] ) { for ( i = 0; i < n; i++ ) { if ( p == portals[i] ) { Error( "RemovePortalFromNode: circular linked\n" ); } } //end for if ( p->nodes[0] != l && p->nodes[1] != l ) { Error( "RemovePortalFromNodes: portal does not belong to node\n" ); } //end if portals[n] = p; s = ( p->nodes[1] == l ); // if (++n >= 4096) Error("RemovePortalFromNode: more than 4096 portals\n"); } //end for //#endif } //end of the function RemovePortalFromNode //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void PrintPortal( portal_t *p ) { int i; winding_t *w; w = p->winding; for ( i = 0 ; i < w->numpoints ; i++ ) printf( "(%5.0f,%5.0f,%5.0f)\n",w->p[i][0] , w->p[i][1], w->p[i][2] ); } //end of the function PrintPortal //=========================================================================== // The created portals will face the global outside_node // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #define SIDESPACE 8 void MakeHeadnodePortals( tree_t *tree ) { vec3_t bounds[2]; int i, j, n; portal_t *p, *portals[6]; plane_t bplanes[6], *pl; node_t *node; node = tree->headnode; // pad with some space so there will never be null volume leaves for ( i = 0 ; i < 3 ; i++ ) { bounds[0][i] = tree->mins[i] - SIDESPACE; bounds[1][i] = tree->maxs[i] + SIDESPACE; } tree->outside_node.planenum = PLANENUM_LEAF; tree->outside_node.brushlist = NULL; tree->outside_node.portals = NULL; tree->outside_node.contents = 0; 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->normal[i] = -1; pl->dist = -bounds[j][i]; } else { pl->normal[i] = 1; pl->dist = bounds[j][i]; } p->plane = *pl; p->winding = BaseWindingForPlane( pl->normal, pl->dist ); 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; } ChopWindingInPlace( &portals[i]->winding, bplanes[j].normal, bplanes[j].dist, ON_EPSILON ); } //end for } //end for } //end of the function MakeHeadNodePortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #define BASE_WINDING_EPSILON 0.001 #define SPLIT_WINDING_EPSILON 0.001 winding_t *BaseWindingForNode( node_t *node ) { winding_t *w; node_t *n; plane_t *plane; vec3_t normal; vec_t dist; w = BaseWindingForPlane( mapplanes[node->planenum].normal , mapplanes[node->planenum].dist ); // clip by all the parents for ( n = node->parent ; n && w ; ) { plane = &mapplanes[n->planenum]; if ( n->children[0] == node ) { // take front ChopWindingInPlace( &w, plane->normal, plane->dist, BASE_WINDING_EPSILON ); } else { // take back VectorSubtract( vec3_origin, plane->normal, normal ); dist = -plane->dist; ChopWindingInPlace( &w, normal, dist, BASE_WINDING_EPSILON ); } node = n; n = n->parent; } return w; } //end of the function BaseWindingForNode //=========================================================================== // create the new portal by taking the full plane winding for the cutting // plane and clipping it by all of parents of this node // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean WindingIsTiny( winding_t *w ); void MakeNodePortal( node_t *node ) { portal_t *new_portal, *p; winding_t *w; vec3_t normal; float dist = 0; // TTimo: init int side = 0; w = BaseWindingForNode( node ); // clip the portal by all the other portals in the node for ( p = node->portals; p && w; p = p->next[side] ) { if ( p->nodes[0] == node ) { side = 0; VectorCopy( p->plane.normal, normal ); dist = p->plane.dist; } //end if else if ( p->nodes[1] == node ) { side = 1; VectorSubtract( vec3_origin, p->plane.normal, normal ); dist = -p->plane.dist; } //end else if else { Error( "MakeNodePortal: mislinked portal" ); } //end else ChopWindingInPlace( &w, normal, dist, 0.1 ); } //end for if ( !w ) { return; } //end if if ( WindingIsTiny( w ) ) { c_tinyportals++; FreeWinding( w ); return; } //end if #ifdef DEBUG /* //NOTE: don't use this winding ok check // all the invalid windings only have a degenerate edge if (WindingError(w)) { Log_Print("MakeNodePortal: %s\n", WindingErrorString()); FreeWinding(w); return; } //end if*/ #endif //DEBUG new_portal = AllocPortal(); new_portal->plane = mapplanes[node->planenum]; #ifdef ME new_portal->planenum = node->planenum; #endif //ME new_portal->onnode = node; new_portal->winding = w; AddPortalToNodes( new_portal, node->children[0], node->children[1] ); } //end of the function MakeNodePortal //=========================================================================== // Move or split the portals that bound node so that the node's // children have portals instead of node. // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void SplitNodePortals( node_t *node ) { portal_t *p, *next_portal, *new_portal; node_t *f, *b, *other_node; int side = 0; // TTimo: init plane_t *plane; winding_t *frontwinding, *backwinding; plane = &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 { Error( "SplitNodePortals: mislinked portal" );} 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 // ClipWindingEpsilon( p->winding, plane->normal, plane->dist, SPLIT_WINDING_EPSILON, &frontwinding, &backwinding ); if ( frontwinding && WindingIsTiny( frontwinding ) ) { FreeWinding( frontwinding ); frontwinding = NULL; c_tinyportals++; } //end if if ( backwinding && WindingIsTiny( backwinding ) ) { FreeWinding( backwinding ); backwinding = NULL; c_tinyportals++; } //end if #ifdef DEBUG /* //NOTE: don't use this winding ok check // all the invalid windings only have a degenerate edge if (frontwinding && WindingError(frontwinding)) { Log_Print("SplitNodePortals: front %s\n", WindingErrorString()); FreeWinding(frontwinding); frontwinding = NULL; } //end if if (backwinding && WindingError(backwinding)) { Log_Print("SplitNodePortals: back %s\n", WindingErrorString()); FreeWinding(backwinding); backwinding = NULL; } //end if*/ #endif //DEBUG if ( !frontwinding && !backwinding ) { // tiny windings on both sides continue; } if ( !frontwinding ) { FreeWinding( backwinding ); if ( side == 0 ) { AddPortalToNodes( p, b, other_node ); } else { AddPortalToNodes( p, other_node, b );} continue; } if ( !backwinding ) { FreeWinding( 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; FreeWinding( p->winding ); p->winding = frontwinding; if ( side == 0 ) { AddPortalToNodes( p, f, other_node ); AddPortalToNodes( new_portal, b, other_node ); } //end if else { AddPortalToNodes( p, other_node, f ); AddPortalToNodes( new_portal, other_node, b ); } //end else } node->portals = NULL; } //end of the function SplitNodePortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void CalcNodeBounds( node_t *node ) { portal_t *p; int s; int i; // calc mins/maxs for both leaves and nodes ClearBounds( node->mins, node->maxs ); for ( p = node->portals ; p ; p = p->next[s] ) { s = ( p->nodes[1] == node ); for ( i = 0 ; i < p->winding->numpoints ; i++ ) AddPointToBounds( p->winding->p[i], node->mins, node->maxs ); } } //end of the function CalcNodeBounds //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int c_numportalizednodes; void MakeTreePortals_r( node_t *node ) { int i; #ifdef ME qprintf( "\r%6d", ++c_numportalizednodes ); if ( cancelconversion ) { return; } #endif //ME CalcNodeBounds( node ); if ( node->mins[0] >= node->maxs[0] ) { Log_Print( "WARNING: node without a volume\n" ); } for ( i = 0 ; i < 3 ; i++ ) { if ( node->mins[i] < -MAX_MAP_BOUNDS || node->maxs[i] > MAX_MAP_BOUNDS ) { Log_Print( "WARNING: node with unbounded volume\n" ); break; } } if ( node->planenum == PLANENUM_LEAF ) { return; } MakeNodePortal( node ); SplitNodePortals( node ); MakeTreePortals_r( node->children[0] ); MakeTreePortals_r( node->children[1] ); } //end of the function MakeTreePortals_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void MakeTreePortals( tree_t *tree ) { #ifdef ME Log_Print( "---- Node Portalization ----\n" ); c_numportalizednodes = 0; c_portalmemory = 0; qprintf( "%6d nodes portalized", c_numportalizednodes ); #endif //ME MakeHeadnodePortals( tree ); MakeTreePortals_r( tree->headnode ); #ifdef ME qprintf( "\n" ); Log_Write( "\r%6d nodes portalized\r\n", c_numportalizednodes ); Log_Print( "%6d tiny portals\r\n", c_tinyportals ); Log_Print( "%6d KB of portal memory\r\n", c_portalmemory >> 10 ); Log_Print( "%6i KB of winding memory\r\n", WindingMemory() >> 10 ); #endif //ME } //end of the function MakeTreePortals /* ========================================================= FLOOD ENTITIES ========================================================= */ //#define P_NODESTACK node_t *p_firstnode; node_t *p_lastnode; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef P_NODESTACK void P_AddNodeToList( node_t *node ) { node->next = p_firstnode; p_firstnode = node; if ( !p_lastnode ) { p_lastnode = node; } } //end of the function P_AddNodeToList #else //it's a node queue //add the node to the end of the node list void P_AddNodeToList( node_t *node ) { node->next = NULL; if ( p_lastnode ) { p_lastnode->next = node; } else { p_firstnode = node;} p_lastnode = node; } //end of the function P_AddNodeToList #endif //P_NODESTACK //=========================================================================== // get the first node from the front of the node list // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== node_t *P_NextNodeFromList( void ) { node_t *node; node = p_firstnode; if ( p_firstnode ) { p_firstnode = p_firstnode->next; } if ( !p_firstnode ) { p_lastnode = NULL; } return node; } //end of the function P_NextNodeFromList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FloodPortals( node_t *firstnode ) { node_t *node; portal_t *p; int s; firstnode->occupied = 1; P_AddNodeToList( firstnode ); for ( node = P_NextNodeFromList(); node; node = P_NextNodeFromList() ) { 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 occupied already if ( p->nodes[!s]->occupied ) { continue; } //if it isn't possible to flood through this portal if ( !Portal_EntityFlood( p, s ) ) { continue; } // p->nodes[!s]->occupied = node->occupied + 1; // P_AddNodeToList( p->nodes[!s] ); } //end for } //end for } //end of the function FloodPortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int numrec; void FloodPortals_r( node_t *node, int dist ) { portal_t *p; int s; // int i; Log_Print( "\r%6d", ++numrec ); if ( node->occupied ) { Error( "FloodPortals_r: node already occupied\n" ); } if ( !node ) { Error( "FloodPortals_r: NULL node\n" ); } //end if*/ node->occupied = dist; 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 occupied already if ( p->nodes[!s]->occupied ) { continue; } //if it isn't possible to flood through this portal if ( !Portal_EntityFlood( p, s ) ) { continue; } //flood recursively through the current portal FloodPortals_r( p->nodes[!s], dist + 1 ); } //end for Log_Print( "\r%6d", --numrec ); } //end of the function FloodPortals_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean PlaceOccupant( node_t *headnode, vec3_t origin, entity_t *occupant ) { node_t *node; vec_t d; plane_t *plane; //find the leaf to start in node = headnode; while ( node->planenum != PLANENUM_LEAF ) { if ( node->planenum < 0 || node->planenum > nummapplanes ) { Error( "PlaceOccupant: invalid node->planenum\n" ); } //end if plane = &mapplanes[node->planenum]; d = DotProduct( origin, plane->normal ) - plane->dist; if ( d >= 0 ) { node = node->children[0]; } else { node = node->children[1];} if ( !node ) { Error( "PlaceOccupant: invalid child %d\n", d < 0 ); } //end if } //end while //don't start in solid // if (node->contents == CONTENTS_SOLID) //ME: replaced because in LeafNode in brushbsp.c // some nodes have contents solid with other contents if ( node->contents & CONTENTS_SOLID ) { return false; } //if the node is already occupied if ( node->occupied ) { return false; } //place the occupant in the first leaf node->occupant = occupant; numrec = 0; // Log_Print("%6d recurses", numrec); // FloodPortals_r(node, 1); // Log_Print("\n"); FloodPortals( node ); return true; } //end of the function PlaceOccupant //=========================================================================== // Marks all nodes that can be reached by entites // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean FloodEntities( tree_t *tree ) { int i; int x, y; vec3_t origin; char *cl; qboolean inside; node_t *headnode; headnode = tree->headnode; Log_Print( "------ FloodEntities -------\n" ); inside = false; tree->outside_node.occupied = 0; //start at entity 1 not the world ( = 0) for ( i = 1; i < num_entities; i++ ) { GetVectorForKey( &entities[i], "origin", origin ); if ( VectorCompare( origin, vec3_origin ) ) { continue; } cl = ValueForKey( &entities[i], "classname" ); origin[2] += 1; //so objects on floor are ok // Log_Print("flooding from entity %d: %s\n", i, cl); //nudge playerstart around if needed so clipping hulls allways //have a valid point if ( !strcmp( cl, "info_player_start" ) ) { for ( x = -16; x <= 16; x += 16 ) { for ( y = -16; y <= 16; y += 16 ) { origin[0] += x; origin[1] += y; if ( PlaceOccupant( headnode, origin, &entities[i] ) ) { inside = true; x = 999; //stop for this info_player_start break; } //end if origin[0] -= x; origin[1] -= y; } //end for } //end for } //end if else { if ( PlaceOccupant( headnode, origin, &entities[i] ) ) { inside = true; } //end if } //end else } //end for if ( !inside ) { Log_Print( "WARNING: no entities inside\n" ); } //end if else if ( tree->outside_node.occupied ) { Log_Print( "WARNING: entity reached from outside\n" ); } //end else if return (qboolean)( inside && !tree->outside_node.occupied ); } //end of the function FloodEntities /* ========================================================= FILL OUTSIDE ========================================================= */ int c_outside; int c_inside; int c_solid; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FillOutside_r( node_t *node ) { if ( node->planenum != PLANENUM_LEAF ) { FillOutside_r( node->children[0] ); FillOutside_r( node->children[1] ); return; } //end if // anything not reachable by an entity // can be filled away (by setting solid contents) if ( !node->occupied ) { if ( !( node->contents & CONTENTS_SOLID ) ) { c_outside++; node->contents |= CONTENTS_SOLID; } //end if else { c_solid++; } //end else } //end if else { c_inside++; } //end else } //end of the function FillOutside_r //=========================================================================== // Fill all nodes that can't be reached by entities // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FillOutside( node_t *headnode ) { c_outside = 0; c_inside = 0; c_solid = 0; Log_Print( "------- FillOutside --------\n" ); FillOutside_r( headnode ); Log_Print( "%5i solid leaves\n", c_solid ); Log_Print( "%5i leaves filled\n", c_outside ); Log_Print( "%5i inside leaves\n", c_inside ); } //end of the function FillOutside /* ========================================================= FLOOD AREAS ========================================================= */ int c_areas; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FloodAreas_r( node_t *node ) { portal_t *p; int s; bspbrush_t *b; entity_t *e; if ( node->contents == CONTENTS_AREAPORTAL ) { // this node is part of an area portal b = node->brushlist; e = &entities[b->original->entitynum]; // if the current area has allready touched this // portal, we are done if ( e->portalareas[0] == c_areas || e->portalareas[1] == c_areas ) { return; } // note the current area as bounding the portal if ( e->portalareas[1] ) { Log_Print( "WARNING: areaportal entity %i touches > 2 areas\n", b->original->entitynum ); return; } if ( e->portalareas[0] ) { e->portalareas[1] = c_areas; } else { e->portalareas[0] = c_areas; } return; } //end if if ( node->area ) { return; // allready got it } node->area = c_areas; for ( p = node->portals ; p ; p = p->next[s] ) { s = ( p->nodes[1] == node ); #if 0 if ( p->nodes[!s]->occupied ) { continue; } #endif if ( !Portal_EntityFlood( p, s ) ) { continue; } FloodAreas_r( p->nodes[!s] ); } //end for } //end of the function FloodAreas_r //=========================================================================== // Just decend the tree, and for each node that hasn't had an // area set, flood fill out from there // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FindAreas_r( node_t *node ) { if ( node->planenum != PLANENUM_LEAF ) { FindAreas_r( node->children[0] ); FindAreas_r( node->children[1] ); return; } if ( node->area ) { return; // allready got it } if ( node->contents & CONTENTS_SOLID ) { return; } if ( !node->occupied ) { return; // not reachable by entities } // area portals are allways only flooded into, never // out of if ( node->contents == CONTENTS_AREAPORTAL ) { return; } c_areas++; FloodAreas_r( node ); } //end of the function FindAreas_r //=========================================================================== // Just decend the tree, and for each node that hasn't had an // area set, flood fill out from there // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void SetAreaPortalAreas_r( node_t *node ) { bspbrush_t *b; entity_t *e; if ( node->planenum != PLANENUM_LEAF ) { SetAreaPortalAreas_r( node->children[0] ); SetAreaPortalAreas_r( node->children[1] ); return; } //end if if ( node->contents == CONTENTS_AREAPORTAL ) { if ( node->area ) { return; // allready set } b = node->brushlist; e = &entities[b->original->entitynum]; node->area = e->portalareas[0]; if ( !e->portalareas[1] ) { Log_Print( "WARNING: areaportal entity %i doesn't touch two areas\n", b->original->entitynum ); return; } //end if } //end if } //end of the function SetAreaPortalAreas_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== /* void EmitAreaPortals(node_t *headnode) { int i, j; entity_t *e; dareaportal_t *dp; if (c_areas > MAX_MAP_AREAS) Error ("MAX_MAP_AREAS"); numareas = c_areas+1; numareaportals = 1; // leave 0 as an error for (i=1 ; i<=c_areas ; i++) { dareas[i].firstareaportal = numareaportals; for (j=0 ; jareaportalnum) continue; dp = &dareaportals[numareaportals]; if (e->portalareas[0] == i) { dp->portalnum = e->areaportalnum; dp->otherarea = e->portalareas[1]; numareaportals++; } //end if else if (e->portalareas[1] == i) { dp->portalnum = e->areaportalnum; dp->otherarea = e->portalareas[0]; numareaportals++; } //end else if } //end for dareas[i].numareaportals = numareaportals - dareas[i].firstareaportal; } //end for Log_Print("%5i numareas\n", numareas); Log_Print("%5i numareaportals\n", numareaportals); } //end of the function EmitAreaPortals */ //=========================================================================== // Mark each leaf with an area, bounded by CONTENTS_AREAPORTAL // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FloodAreas( tree_t *tree ) { Log_Print( "--- FloodAreas ---\n" ); FindAreas_r( tree->headnode ); SetAreaPortalAreas_r( tree->headnode ); Log_Print( "%5i areas\n", c_areas ); } //end of the function FloodAreas //=========================================================================== // Finds a brush side to use for texturing the given portal // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FindPortalSide( portal_t *p ) { int viscontents; bspbrush_t *bb; mapbrush_t *brush; node_t *n; int i,j; int planenum; side_t *side, *bestside; float dot, bestdot; plane_t *p1, *p2; // decide which content change is strongest // solid > lava > water, etc viscontents = VisibleContents( p->nodes[0]->contents ^ p->nodes[1]->contents ); if ( !viscontents ) { return; } planenum = p->onnode->planenum; bestside = NULL; bestdot = 0; for ( j = 0 ; j < 2 ; j++ ) { n = p->nodes[j]; p1 = &mapplanes[p->onnode->planenum]; for ( bb = n->brushlist ; bb ; bb = bb->next ) { brush = bb->original; if ( !( brush->contents & viscontents ) ) { continue; } for ( i = 0 ; i < brush->numsides ; i++ ) { side = &brush->original_sides[i]; if ( side->flags & SFL_BEVEL ) { continue; } if ( side->texinfo == TEXINFO_NODE ) { continue; // non-visible } if ( ( side->planenum & ~1 ) == planenum ) { // exact match bestside = &brush->original_sides[i]; goto gotit; } //end if // see how close the match is p2 = &mapplanes[side->planenum & ~1]; dot = DotProduct( p1->normal, p2->normal ); if ( dot > bestdot ) { bestdot = dot; bestside = side; } //end if } //end for } //end for } //end for gotit: if ( !bestside ) { Log_Print( "WARNING: side not found for portal\n" ); } p->sidefound = true; p->side = bestside; } //end of the function FindPortalSide //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void MarkVisibleSides_r( node_t *node ) { portal_t *p; int s; if ( node->planenum != PLANENUM_LEAF ) { MarkVisibleSides_r( node->children[0] ); MarkVisibleSides_r( node->children[1] ); return; } //end if // empty leaves are never boundary leaves if ( !node->contents ) { return; } // see if there is a visible face for ( p = node->portals ; p ; p = p->next[!s] ) { s = ( p->nodes[0] == node ); if ( !p->onnode ) { continue; // edge of world } if ( !p->sidefound ) { FindPortalSide( p ); } if ( p->side ) { p->side->flags |= SFL_VISIBLE; } } //end for } //end of the function MarkVisibleSides_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void MarkVisibleSides( tree_t *tree, int startbrush, int endbrush ) { int i, j; mapbrush_t *mb; int numsides; Log_Print( "--- MarkVisibleSides ---\n" ); // clear all the visible flags for ( i = startbrush ; i < endbrush ; i++ ) { mb = &mapbrushes[i]; numsides = mb->numsides; for ( j = 0 ; j < numsides ; j++ ) mb->original_sides[j].flags &= ~SFL_VISIBLE; } // set visible flags on the sides that are used by portals MarkVisibleSides_r( tree->headnode ); } //end of the function MarkVisibleSides