/* FLOOD_MAP.C Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc. and the "Aleph One" developers. This program 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 2 of the License, or (at your option) any later version. This program 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. This license is contained in the file "COPYING", which is included with this source code; it is available online at http://www.gnu.org/licenses/gpl.html Sunday, June 5, 1994 3:25:22 PM Friday, July 15, 1994 12:54:35 PM added visited_polygon array for quickly determining whether we have visited a polygon yet. Monday, September 5, 1994 2:31:12 PM static memory allocated is 512 bytes (visited polygons) and 4k (flood nodes). Wednesday, October 26, 1994 3:18:59 PM (Jason) added _flagged_breadth_first. Jan 30, 2000 (Loren Petrich): Did some typecasts Feb. 4, 2000 (Loren Petrich): Changed halt() to assert(false) for better debugging */ /* on very small maps, choose_random_flood_node() may not terminate //for performance, maybe we should allow the caller to specify breadth-first instead of best-first. */ #include "cseries.h" #include "map.h" #include "flood_map.h" #include #include #include #ifdef env68k #pragma segment map #endif /* ---------- constants */ #define MAXIMUM_FLOOD_NODES 255 #define UNVISITED NONE /* ---------- structures */ #define NODE_IS_EXPANDED(n) ((n)->flags&(uint16)0x8000) #define NODE_IS_UNEXPANDED(n) (!NODE_IS_EXPANDED(n)) #define MARK_NODE_AS_EXPANDED(n) ((n)->flags|=(uint16)0x8000) struct node_data /* 16 bytes */ { uint16 flags; int16 parent_node_index; /* node index of the node we came from to get here; only used for backtracking */ int16 polygon_index; /* index of this polygon */ int32 cost; /* the cost to evaluate this entry */ int16 depth; int32 user_flags; }; /* ---------- globals */ static short node_count= 0, last_node_index_expanded= NONE; static struct node_data *nodes = NULL; static short *visited_polygons = NULL; /* ---------- private prototypes */ static void add_node(short parent_node_index, short polygon_index, short depth, long cost, long user_flags); /* ---------- code */ void allocate_flood_map_memory( void) { // Made reentrant because this must be called every time a map is loaded if (nodes) delete []nodes; nodes= new node_data[MAXIMUM_FLOOD_NODES]; if (visited_polygons) delete []visited_polygons; visited_polygons= new short[MAXIMUM_POLYGONS_PER_MAP]; assert(nodes&&visited_polygons); } /* returns next polygon index or NONE if there are no more polygons left cheaper than maximum_cost */ short flood_map( short first_polygon_index, long maximum_cost, cost_proc_ptr cost_proc, short flood_mode, void *caller_data) { short lowest_cost_node_index, node_index; struct node_data *node; short polygon_index; long lowest_cost; /* initialize ourselves if first_polygon_index!=NONE */ if (first_polygon_index!=NONE) { /* clear the visited polygon array */ objlist_set(visited_polygons, NONE, MAXIMUM_POLYGONS_PER_MAP); node_count= 0; last_node_index_expanded= NONE; add_node(NONE, first_polygon_index, 0, 0, (flood_mode==_flagged_breadth_first) ? *((int32*)caller_data) : 0); } switch (flood_mode) { case _best_first: /* find the unexpanded node with the lowest cost */ lowest_cost= maximum_cost, lowest_cost_node_index= NONE; for (node= nodes, node_index= 0; node_indexcostcost; } } break; case _breadth_first: case _flagged_breadth_first: /* find the next unexpanded node in the list under maximum_cost */ node_index= (last_node_index_expanded==NONE) ? 0 : (last_node_index_expanded+1); for (node= nodes+node_index; node_indexcostcost; } break; case _depth_first: /* implementation left to the caller (c.f., zen() in fareast.c) */ assert(false); break; default: assert(false); break; } /* if we found a node, mark it as expanded and add itÕs adjacent non-solid polygons to the search tree */ if (lowest_cost_node_index!=NONE) { struct polygon_data *polygon; short i; /* for flood_depth() and reverse_flood_map(), remember which node we successfully expanded last */ last_node_index_expanded= lowest_cost_node_index; /* get pointer to lowest cost node */ assert(lowest_cost_node_index>=0&&lowest_cost_node_indexpolygon_index); assert(!POLYGON_IS_DETACHED(polygon)); /* mark node as expanded */ MARK_NODE_AS_EXPANDED(node); for (i= 0; ivertex_count; ++i) { short destination_polygon_index= polygon->adjacent_polygon_indexes[i]; if (destination_polygon_index!=NONE && (maximum_cost!=LONG_MAX || visited_polygons[destination_polygon_index]==UNVISITED)) { long new_user_flags= node->user_flags; long cost= cost_proc ? cost_proc(node->polygon_index, polygon->line_indexes[i], destination_polygon_index, (flood_mode==_flagged_breadth_first) ? &new_user_flags : caller_data) : polygon->area; /* polygons with zero or negative costs are not added to the node list */ if (cost>0) add_node(lowest_cost_node_index, destination_polygon_index, node->depth+1, lowest_cost+cost, new_user_flags); } } polygon_index= node->polygon_index; if (flood_mode==_flagged_breadth_first) *((int32*)caller_data)= node->user_flags; } else { polygon_index= NONE; } return polygon_index; } /* walks backwards from the last node expanded, returning polygons as it goes; returns NONE when there are no more polygons to return. this is useful for pathfinding: when flood_map() returns the destination polygon index, calling reverse_flood_map() will return the polygons traversed to reach the destination) */ short reverse_flood_map( void) { short polygon_index= NONE; if (last_node_index_expanded!=NONE) { struct node_data *node; assert(last_node_index_expanded>=0&&last_node_index_expandedparent_node_index; polygon_index= node->polygon_index; } return polygon_index; } /* returns depth (in polygons) at last_node_index_expanded */ short flood_depth( void) { assert(last_node_index_expanded>=0&&last_node_index_expanded=1); find_center_of_polygon(nodes[0].polygon_index, &origin); if (node_count>1) { bool suitable; short retries= MAXIMUM_BIASED_RETRIES; do { do { last_node_index_expanded= global_random()%node_count; } while (NODE_IS_UNEXPANDED(nodes+last_node_index_expanded)); /* if we have no bias, this node is automatically suitable if it has been expanded; if we have a bias, this node is only suitable if it is in the same general direction (by sign of dot product) as the bias */ suitable= true; if (bias && (retries-= 1)>=0) { struct node_data *node= nodes+last_node_index_expanded; world_point2d destination; find_center_of_polygon(node->polygon_index, &destination); if (bias->i*(destination.x-origin.x) + bias->j*(destination.y-origin.y)<0) suitable= false; } } while (!suitable); } } /* ---------- private code */ /* checks to see if the given node is already in the node list */ static void add_node( short parent_node_index, short polygon_index, short depth, long cost, long user_flags) { if (node_count=0&&polygon_indexpolygon_count); if ((node_index= visited_polygons[polygon_index])!=UNVISITED) { /* there is already a node referencing this polygon; if it has a higher cost than the cost we are attempting to add, replace it (because we are doing a best-first search, we are guarenteed never to find a better path to an expanded node, and in fact if we find a path to a node we have already expanded weÕre backtracking and can ignore the node) */ assert(node_index>=0&&node_indexcost<=cost) node= (struct node_data *) NULL; } else { node_index= node_count; node= nodes + node_index; } if (node) { if (node_index==node_count) { node_count+= 1; } node->flags= 0; node->parent_node_index= parent_node_index; node->polygon_index= polygon_index; node->depth= depth; node->cost= cost; node->user_flags= user_flags; assert(polygon_index>=0&&polygon_indexpolygon_count); visited_polygons[polygon_index]= node_index; // dprintf("added polygon #%d to node #%d (nodes=%p,visited=%p)", polygon_index, node_index, nodes, visited_polygons); } } }