mirror of
https://github.com/tribes2/engine.git
synced 2026-01-20 03:34:48 +00:00
563 lines
17 KiB
C++
563 lines
17 KiB
C++
|
|
//-----------------------------------------------------------------------------
|
||
|
|
// V12 Engine
|
||
|
|
//
|
||
|
|
// Copyright (c) 2001 GarageGames.Com
|
||
|
|
// Portions Copyright (c) 2001 by Sierra Online, Inc.
|
||
|
|
//-----------------------------------------------------------------------------
|
||
|
|
|
||
|
|
#include "ai/graph.h"
|
||
|
|
|
||
|
|
U32 gCallsToClosestNode = 0;
|
||
|
|
|
||
|
|
//-------------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
bool NavigationGraph::possibleToJet(const Point3F& from, const Point3F& to, U32)
|
||
|
|
{
|
||
|
|
// To be refined-
|
||
|
|
return (from - to).len() < 60;
|
||
|
|
}
|
||
|
|
|
||
|
|
//-------------------------------------------------------------------------------------
|
||
|
|
// This routine is used by the seed scattering approach - generate points that are
|
||
|
|
// inside nearby volumes, reflecting across one of the walls.
|
||
|
|
|
||
|
|
S32 NavigationGraph::crossNearbyVolumes(const Point3F& from, Vector<Point3F>& points)
|
||
|
|
{
|
||
|
|
Point3F size(20, 20, 20);
|
||
|
|
Box3F checkBox(from, from);
|
||
|
|
checkBox.min -= size;
|
||
|
|
checkBox.max += size;
|
||
|
|
GraphNodeList nodeList;
|
||
|
|
|
||
|
|
points.clear();
|
||
|
|
if (S32 N = getNodesInBox(checkBox, nodeList, true))
|
||
|
|
{
|
||
|
|
// Find ones we're outside of a little ways - i.e. positive containment, but not
|
||
|
|
// too low a number. The caller will do LOS checks... Also, we verify that we're
|
||
|
|
// below the cieling as well.
|
||
|
|
for (S32 i = 0; i < N; i++)
|
||
|
|
{
|
||
|
|
GraphNode * node = nodeList[i];
|
||
|
|
NodeProximity prox = node->containment(from);
|
||
|
|
|
||
|
|
// Make sure we're somewhat near. Note Z check needs to come last here...
|
||
|
|
if ((F32(prox) > 0.1) && (F32(prox) < 6.0) && prox.insideZ())
|
||
|
|
{
|
||
|
|
// It's a candidate. Now find closest point on volume....
|
||
|
|
Point3F soln;
|
||
|
|
if (closestPointOnVol(node, from, soln))
|
||
|
|
{
|
||
|
|
// ...and reflect across point..
|
||
|
|
VectorF inVec(soln.x - from.x, soln.y - from.y, 0.0);
|
||
|
|
inVec.normalize(0.8);
|
||
|
|
soln += inVec;
|
||
|
|
soln.z = solveForZ(getFloorPlane(node), soln) + 0.2;
|
||
|
|
|
||
|
|
// See if that point is reasonably well inside the volume...
|
||
|
|
if (node->containment(soln) < -0.3)
|
||
|
|
points.push_back(soln);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return points.size();
|
||
|
|
}
|
||
|
|
|
||
|
|
//-------------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
S32 NavigationGraph::getNodesInBox(Box3F worldBox, GraphNodeList& listOut, bool justIndoor)
|
||
|
|
{
|
||
|
|
// Box is assumed to be square in XY, get "radius"
|
||
|
|
Point3F center;
|
||
|
|
worldBox.getCenter(¢er);
|
||
|
|
|
||
|
|
listOut.clear();
|
||
|
|
|
||
|
|
if (!justIndoor && haveTerrain())
|
||
|
|
{
|
||
|
|
// Fetch terrain nodes-
|
||
|
|
F32 rad = worldBox.len_x() * 0.5;
|
||
|
|
S32 gridRadius = S32(rad / gNavGlobs.mSquareWidth) + 1;
|
||
|
|
GridArea gridArea = getGridRectangle(center, gridRadius);
|
||
|
|
getNodesInArea(listOut, gridArea);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fetch from the indoor node BSP (this one doesn't clear the list)
|
||
|
|
Point3F radExt(MaxGraphNodeVolRad, MaxGraphNodeVolRad, MaxGraphNodeVolRad);
|
||
|
|
worldBox.min -= radExt;
|
||
|
|
worldBox.max += radExt;
|
||
|
|
mIndoorTree.getIntersecting(listOut, worldBox);
|
||
|
|
|
||
|
|
return listOut.size();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool NavigationGraph::haveMuzzleLOS(S32 nodeInd1, S32 nodeInd2)
|
||
|
|
{
|
||
|
|
if (nodeInd1 != nodeInd2 && mValidLOSTable)
|
||
|
|
return mLOSTable->muzzleLOS(nodeInd1, nodeInd2);
|
||
|
|
else
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//-------------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
const GraphNodeList& NavigationGraph::getVisibleNodes(GraphNode * from, const Point3F& loc, F32 rad)
|
||
|
|
{
|
||
|
|
mUtilityNodeList1.clear();
|
||
|
|
mUtilityNodeList2.clear();
|
||
|
|
|
||
|
|
if (from)
|
||
|
|
{
|
||
|
|
SphereF sphere(loc, rad);
|
||
|
|
|
||
|
|
mUtilityNodeList1.clear();
|
||
|
|
|
||
|
|
if (haveTerrain())
|
||
|
|
{
|
||
|
|
// Fetch terrain.
|
||
|
|
S32 gridRadius = S32(rad / gNavGlobs.mSquareWidth) + 1;
|
||
|
|
GridArea gridArea = getGridRectangle(loc, gridRadius);
|
||
|
|
getNodesInArea(mUtilityNodeList1, gridArea);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fetch from the indoor node BSP (this one doesn't clear the list)
|
||
|
|
Box3F checkBox(loc, loc, true);
|
||
|
|
rad += MaxGraphNodeVolRad;
|
||
|
|
Point3F radExt(rad, rad, rad);
|
||
|
|
checkBox.min -= radExt;
|
||
|
|
checkBox.max += radExt;
|
||
|
|
mIndoorTree.getIntersecting(mUtilityNodeList1, checkBox);
|
||
|
|
|
||
|
|
S32 fromInd = from->getIndex();
|
||
|
|
|
||
|
|
for (S32 i = 0; i < mUtilityNodeList1.size(); i++)
|
||
|
|
if (haveMuzzleLOS(fromInd, mUtilityNodeList1[i]->getIndex()))
|
||
|
|
mUtilityNodeList2.push_back(mUtilityNodeList1[i]);
|
||
|
|
}
|
||
|
|
return mUtilityNodeList2;
|
||
|
|
}
|
||
|
|
|
||
|
|
const GraphNodeList& NavigationGraph::getVisibleNodes(const Point3F& loc, F32 rad)
|
||
|
|
{
|
||
|
|
FindGraphNode finder(loc);
|
||
|
|
return getVisibleNodes(finder.closest(), loc, rad);
|
||
|
|
}
|
||
|
|
|
||
|
|
//-------------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
#define CrossingSegAdjustUp 0.3
|
||
|
|
|
||
|
|
// Figure out what constitutes a crossing path.
|
||
|
|
S32 NavigationGraph::crossingSegs(const GraphNode * node, const GraphEdge * edge,
|
||
|
|
LineSegment * const segBuffer)
|
||
|
|
{
|
||
|
|
LineSegment * segs = segBuffer;
|
||
|
|
Point3F nodeLoc = node->location();
|
||
|
|
Point3F destLoc = lookupNode(edge->mDest)->location();
|
||
|
|
|
||
|
|
nodeLoc.z += CrossingSegAdjustUp;
|
||
|
|
destLoc.z += CrossingSegAdjustUp;
|
||
|
|
|
||
|
|
if (edge->isBorder()) {
|
||
|
|
Point3F crossingPt = getBoundary(edge->mBorder).midpoint();
|
||
|
|
crossingPt.z += CrossingSegAdjustUp;
|
||
|
|
(segs++)->set(nodeLoc, crossingPt);
|
||
|
|
(segs++)->set(crossingPt, destLoc);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
// Do non-jetting like jetting as well - otherwise we miss collisions.
|
||
|
|
// if (edge->isJetting()) {
|
||
|
|
Point3F arcCorner;
|
||
|
|
if (nodeLoc.z > destLoc.z)
|
||
|
|
(arcCorner = destLoc).z = nodeLoc.z;
|
||
|
|
else
|
||
|
|
(arcCorner = nodeLoc).z = destLoc.z;
|
||
|
|
(segs++)->set(nodeLoc, arcCorner);
|
||
|
|
(segs++)->set(arcCorner, destLoc);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (segs - segBuffer);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Find all edges that are blocked by the given object. This is used by the force field
|
||
|
|
// monitor code, called at mission start to find affected edges.
|
||
|
|
const GraphEdgePtrs& NavigationGraph::getBlockedEdges(GameBase* object, U32 typeMask)
|
||
|
|
{
|
||
|
|
Box3F objBox = object->getWorldBox();
|
||
|
|
GraphEdge edgeBuffer[MaxOnDemandEdges];
|
||
|
|
LineSegment segments[4];
|
||
|
|
RayInfo collision;
|
||
|
|
GraphNodeList consider;
|
||
|
|
|
||
|
|
mVisibleEdges.clear();
|
||
|
|
if (S32 numNodes = getNodesInBox(objBox, consider)) {
|
||
|
|
for (S32 i = 0; i < numNodes; i++) {
|
||
|
|
GraphNode * node = consider[i];
|
||
|
|
GraphEdgeArray edges = node->getEdges(edgeBuffer);
|
||
|
|
while (GraphEdge * edge = edges++)
|
||
|
|
for (S32 j = crossingSegs(node, edge, segments) - 1; j >= 0; j--) {
|
||
|
|
Point3F A = segments[j].getEnd(0);
|
||
|
|
Point3F B = segments[j].getEnd(1);
|
||
|
|
// Quick crude test-
|
||
|
|
if (objBox.collideLine(A, B))
|
||
|
|
// Now do LOS, must hit object in question. We must be careful
|
||
|
|
// that force fields don't overlap in navigable area.
|
||
|
|
if (gServerContainer.castRay(A, B, typeMask, &collision))
|
||
|
|
if (collision.object == object) {
|
||
|
|
mVisibleEdges.push_back(edge);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// mVisibleEdges is a generic utility buffer for several queries, user copies off
|
||
|
|
return mVisibleEdges;
|
||
|
|
}
|
||
|
|
|
||
|
|
//-------------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
// For an outdoor node location, fetch the roaming radius off the terrain graph
|
||
|
|
// information. Return 0.0 if nothing found.
|
||
|
|
F32 NavigationGraph::getRoamRadius(const Point3F &loc)
|
||
|
|
{
|
||
|
|
SphereF sphere;
|
||
|
|
Point3F loc2D(loc.x, loc.y, 0.0f);
|
||
|
|
|
||
|
|
if (mTerrainInfo.inGraphArea(loc2D))
|
||
|
|
if (mTerrainInfo.locToIndexAndSphere(sphere, loc2D) >= 0)
|
||
|
|
return sphere.radius;
|
||
|
|
|
||
|
|
return 0.0f;
|
||
|
|
}
|
||
|
|
|
||
|
|
//-------------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
// Patch function to translate into block space. 10/27/00- added optional normal
|
||
|
|
bool NavigationGraph::terrainHeight(Point3F pos, F32 * height, Point3F * normal)
|
||
|
|
{
|
||
|
|
pos -= mTerrainInfo.originWorld;
|
||
|
|
if (mTerrainBlock) {
|
||
|
|
Point2F point2F(pos.x, pos.y);
|
||
|
|
if (mTerrainBlock->getHeight(point2F, height)) {
|
||
|
|
if (normal != NULL)
|
||
|
|
mTerrainBlock->getNormal(point2F, normal, true);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Look for terrain node here using grid lookup.
|
||
|
|
GraphNode * NavigationGraph::findTerrainNode(const Point3F & atLocation)
|
||
|
|
{
|
||
|
|
Point3F loc;
|
||
|
|
bool inArea;
|
||
|
|
|
||
|
|
if (haveTerrain()) {
|
||
|
|
// Bots can enter outside of area- we don't do height check below for such locs.
|
||
|
|
if (mTerrainInfo.inGraphArea(atLocation)) {
|
||
|
|
inArea = true;
|
||
|
|
loc = atLocation;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
inArea = false;
|
||
|
|
loc = mTerrainInfo.whereToInbound(atLocation);
|
||
|
|
}
|
||
|
|
|
||
|
|
GraphNode * node = NULL;
|
||
|
|
S32 gridIndex = mTerrainInfo.locToIndex(loc);
|
||
|
|
|
||
|
|
if (gridIndex >= 0) {
|
||
|
|
if (GraphNode * node = mNodeGrid[gridIndex]) {
|
||
|
|
if (inArea) {
|
||
|
|
F32 height;
|
||
|
|
if (terrainHeight(loc, &height))
|
||
|
|
{
|
||
|
|
if (height < (loc.z + 0.04))
|
||
|
|
{
|
||
|
|
// Point3F terrLoc = node->location();
|
||
|
|
// F32 nodeZ = terrLoc.z;
|
||
|
|
F32 terrHt = node->terrHeight();
|
||
|
|
if (loc.z < (height + terrHt))
|
||
|
|
return node;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
return node;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
// If the findTerrainNode() fails, we see if we're on terrain with a
|
||
|
|
// node that is within one grid location.
|
||
|
|
GraphNode * NavigationGraph::nearbyTerrainNode(const Point3F& loc)
|
||
|
|
{
|
||
|
|
GraphNode * node = NULL;
|
||
|
|
|
||
|
|
if (haveTerrain())
|
||
|
|
{
|
||
|
|
F32 H;
|
||
|
|
S32 gridIndex = mTerrainInfo.locToIndex(loc);
|
||
|
|
|
||
|
|
if( (gridIndex >= 0) && terrainHeight(loc, &H) && (H < loc.z + 0.04) )
|
||
|
|
{
|
||
|
|
// look for an immediately neighboring node - find the nearest one
|
||
|
|
S32 *offs = mTerrainInfo.indOffs, i, n;
|
||
|
|
F32 bestDist = 1e9, dSq;
|
||
|
|
for (i = 0; i < 8; i++)
|
||
|
|
if (validArrayIndex(n = gridIndex + offs[i], mTerrainInfo.nodeCount))
|
||
|
|
if (GraphNode* N = mNodeGrid[n])
|
||
|
|
if ( (dSq = (loc - N->location()).lenSquared()) < bestDist )
|
||
|
|
bestDist = dSq, node = N;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return node;
|
||
|
|
}
|
||
|
|
|
||
|
|
GraphNode * NavigationGraph::closestNode(const Point3F& loc, F32 * containment)
|
||
|
|
{
|
||
|
|
GraphNode * terrNode = findTerrainNode(loc);
|
||
|
|
|
||
|
|
gCallsToClosestNode++;
|
||
|
|
|
||
|
|
if (terrNode)
|
||
|
|
return terrNode;
|
||
|
|
else
|
||
|
|
{
|
||
|
|
terrNode = nearbyTerrainNode(loc);
|
||
|
|
|
||
|
|
Box3F box(loc, loc, true);
|
||
|
|
Point3F boundUp(0, 0, 20);
|
||
|
|
Point3F boundDown(0, 0, 80);
|
||
|
|
Point3F boundOut(35, 35, 0);
|
||
|
|
|
||
|
|
box.max += boundUp;
|
||
|
|
box.max += boundOut;
|
||
|
|
box.min -= boundDown;
|
||
|
|
box.min -= boundOut;
|
||
|
|
|
||
|
|
GraphNodeList indoor;
|
||
|
|
|
||
|
|
// We really need to use two trees - one for big nodes, and one for smaller.
|
||
|
|
// This will keep the searches much more intelligent- oversize only a little
|
||
|
|
// for the tree with small nodes - and so we'll not get so many. Oversize
|
||
|
|
// a lot for the big nodes - but there aren't nearly as many of them anyway.
|
||
|
|
mIndoorTree.getIntersecting(indoor, box);
|
||
|
|
|
||
|
|
// Find the node with the best containment metric
|
||
|
|
GraphNode * bestNode = NULL;
|
||
|
|
F32 bestMetric = 1e13;
|
||
|
|
F32 metric;
|
||
|
|
for (GraphNodeList::iterator i = indoor.begin(); i != indoor.end(); i++) {
|
||
|
|
metric = F32((*i)->containment(loc));
|
||
|
|
if (metric < bestMetric) {
|
||
|
|
bestMetric = metric;
|
||
|
|
bestNode = *i;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (! bestNode)
|
||
|
|
return terrNode;
|
||
|
|
else {
|
||
|
|
if (terrNode) {
|
||
|
|
//==> Move this to a terrain containment function -
|
||
|
|
F32 terrMetric = (terrNode->location() - loc).len() - terrNode->radius();
|
||
|
|
if (terrMetric < bestMetric)
|
||
|
|
return terrNode;
|
||
|
|
}
|
||
|
|
if (containment)
|
||
|
|
*containment = bestMetric;
|
||
|
|
return bestNode;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//-------------------------------------------------------------------------------------
|
||
|
|
// Structure which remembers results of searches, and which are also kept in a hash
|
||
|
|
// table as well. Shows up in profiles now that we use canReachLoc() a lot in the
|
||
|
|
// objective weighting.
|
||
|
|
|
||
|
|
FindGraphNode::FindGraphNode()
|
||
|
|
{
|
||
|
|
init();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Manage loc-to-node searches since many are the same.
|
||
|
|
FindGraphNode::FindGraphNode(const Point3F& pt, GraphNode* hint)
|
||
|
|
{
|
||
|
|
init();
|
||
|
|
setPoint(pt, hint);
|
||
|
|
}
|
||
|
|
|
||
|
|
void FindGraphNode::init()
|
||
|
|
{
|
||
|
|
mClosest = NULL;
|
||
|
|
// Should be safe value that no-one will use:
|
||
|
|
mPoint.set(-3.14159e14, 3.1e13, -2.22e22);
|
||
|
|
}
|
||
|
|
|
||
|
|
U32 FindGraphNode::calcHash(const Point3F& point)
|
||
|
|
{
|
||
|
|
register const U32 * hashNums = (const U32 *)(&point);
|
||
|
|
U32 val0 = (hashNums[0] >> 7) ^ (hashNums[0] << 5);
|
||
|
|
U32 val1 = (hashNums[1] + 77773) & 0xFFFFFFF;
|
||
|
|
U32 val2 = (hashNums[2] * 37) & 0xFFFFFFF;
|
||
|
|
return (val0 + val1 + val2) % HashTableSize;
|
||
|
|
}
|
||
|
|
|
||
|
|
void FindGraphNode::setPoint(const Point3F& point, GraphNode * hint/*=0*/)
|
||
|
|
{
|
||
|
|
// If point has not changed- we're done.
|
||
|
|
if (point == mPoint)
|
||
|
|
return;
|
||
|
|
|
||
|
|
// Hash point into our cache (which NavigationGraph holds for us)-
|
||
|
|
mPoint = point;
|
||
|
|
U32 hash = calcHash(mPoint);
|
||
|
|
FindGraphNode & cacheEntry = gNavGraph->mFoundNodes[hash];
|
||
|
|
|
||
|
|
// See if hash table contains it.
|
||
|
|
if (cacheEntry.mPoint == point)
|
||
|
|
{
|
||
|
|
mClosest = cacheEntry.mClosest;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// If there's a hint - see if we're inside it - that is then the closest. Don't
|
||
|
|
// put these in the hash table though since the hints usually refer to locations
|
||
|
|
// that are moving, and we don't want them walking over our cache.
|
||
|
|
if (hint && hint->containment(point) < 0)
|
||
|
|
{
|
||
|
|
mClosest = hint;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// Otherwise do the search-
|
||
|
|
mClosest = gNavGraph->closestNode(point);
|
||
|
|
|
||
|
|
// Put into cache. We do this even for NULL results.
|
||
|
|
cacheEntry = * this;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//-------------------------------------------------------------------------------------
|
||
|
|
// Grid <-> World Translation Methods
|
||
|
|
|
||
|
|
GridArea NavigationGraph::getGridRectangle(const Point3F& atPos, S32 gridRadius)
|
||
|
|
{
|
||
|
|
GridArea atRect;
|
||
|
|
Point2I atGridLoc;
|
||
|
|
|
||
|
|
worldToGrid(atPos, atGridLoc);
|
||
|
|
atRect.point.x = atGridLoc.x - gridRadius;
|
||
|
|
atRect.point.y = atGridLoc.y - gridRadius;
|
||
|
|
atRect.extent.x = atRect.extent.y = (gridRadius << 1);
|
||
|
|
return atRect;
|
||
|
|
}
|
||
|
|
|
||
|
|
void NavigationGraph::worldToGrid(const Point3F &wPos, Point2I &gPos)
|
||
|
|
{
|
||
|
|
TerrainBlock *terrain = GroundPlan::getTerrainObj();
|
||
|
|
Point3F origin;
|
||
|
|
terrain->getTransform().getColumn(3, &origin);
|
||
|
|
|
||
|
|
float x = (wPos.x - origin.x) / (float)terrain->getSquareSize();
|
||
|
|
float y = (wPos.y - origin.y) / (float)terrain->getSquareSize();
|
||
|
|
|
||
|
|
gPos.x = (S32)mFloor(x);
|
||
|
|
gPos.y = (S32)mFloor(y);
|
||
|
|
}
|
||
|
|
|
||
|
|
Point3F NavigationGraph::gridToWorld(const Point2I& gPos)
|
||
|
|
{
|
||
|
|
Point3F retVal;
|
||
|
|
if (!mTerrainInfo.posToLoc(retVal, gPos))
|
||
|
|
retVal.set(-1, -1, -1);
|
||
|
|
return retVal;
|
||
|
|
}
|
||
|
|
|
||
|
|
GridArea NavigationGraph::getWorldRect()
|
||
|
|
{
|
||
|
|
return GridArea(mTerrainInfo.originGrid, mTerrainInfo.gridDimensions);
|
||
|
|
}
|
||
|
|
|
||
|
|
//-------------------------------------------------------------------------------------
|
||
|
|
// Visitor to find terrain nodes in rectangular area.
|
||
|
|
|
||
|
|
class VisitNodeGrid : public GridVisitor
|
||
|
|
{
|
||
|
|
protected:
|
||
|
|
const GridArea& mWorld; // invariant is that the mNodeGrid
|
||
|
|
const GraphNodeList& mGrid; // corresponds to mWorld area.
|
||
|
|
GraphNodeList& mListOut;
|
||
|
|
|
||
|
|
GraphNode * getNodeAt(const GridArea& R);
|
||
|
|
|
||
|
|
public:
|
||
|
|
VisitNodeGrid( const GridArea& toVisit, const GridArea& worldArea,
|
||
|
|
const GraphNodeList& gridList, GraphNodeList& listOut )
|
||
|
|
: GridVisitor(toVisit), mWorld(worldArea),
|
||
|
|
mGrid(gridList), mListOut(listOut) { }
|
||
|
|
|
||
|
|
bool beforeDivide(const GridArea& R, S32 level);
|
||
|
|
bool atLevelZero(const GridArea& R);
|
||
|
|
};
|
||
|
|
|
||
|
|
GraphNode * VisitNodeGrid::getNodeAt(const GridArea& R)
|
||
|
|
{
|
||
|
|
S32 index = mWorld.getIndex(R.point);
|
||
|
|
AssertFatal( validArrayIndex(index, mGrid.size()), "VisitNodeGrid- bad index" );
|
||
|
|
return mGrid[index];
|
||
|
|
}
|
||
|
|
|
||
|
|
bool VisitNodeGrid::beforeDivide(const GridArea& R, S32 level)
|
||
|
|
{
|
||
|
|
if (GraphNode * node = getNodeAt(R)) {
|
||
|
|
if(node->getLevel() == level) {
|
||
|
|
mListOut.push_back(node);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool VisitNodeGrid::atLevelZero(const GridArea& R)
|
||
|
|
{
|
||
|
|
if (GraphNode * node = getNodeAt(R))
|
||
|
|
mListOut.push_back( node );
|
||
|
|
return true; //N/A
|
||
|
|
}
|
||
|
|
|
||
|
|
// Query list for list of nodes within a certain area.
|
||
|
|
S32 NavigationGraph::getNodesInArea(GraphNodeList& listOut, GridArea toVisit)
|
||
|
|
{
|
||
|
|
listOut.clear();
|
||
|
|
if (haveTerrain())
|
||
|
|
{
|
||
|
|
GridArea worldRect = getWorldRect();
|
||
|
|
if (mNodeGrid.size() == (worldRect.extent.x * worldRect.extent.y))
|
||
|
|
{
|
||
|
|
if (toVisit.intersect(worldRect))
|
||
|
|
{
|
||
|
|
VisitNodeGrid visitor(toVisit, worldRect, mNodeGrid, listOut);
|
||
|
|
visitor.traverse();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return listOut.size();
|
||
|
|
}
|
||
|
|
|
||
|
|
void NavigationGraph::getOutdoorList(GraphNodeList& to)
|
||
|
|
{
|
||
|
|
to.setSize(mNumOutdoor);
|
||
|
|
dMemcpy(to.address(), mNodeList.address(), mNumOutdoor * sizeof(GraphNode*));
|
||
|
|
}
|