mirror of
https://github.com/tribes2/engine.git
synced 2026-01-20 03:34:48 +00:00
389 lines
10 KiB
C++
389 lines
10 KiB
C++
//-----------------------------------------------------------------------------
|
|
// V12 Engine
|
|
//
|
|
// Copyright (c) 2001 GarageGames.Com
|
|
// Portions Copyright (c) 2001 by Sierra Online, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "ai/graph.h"
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// GraphEdge
|
|
|
|
GraphEdge::GraphEdge()
|
|
{
|
|
mDest = -1; // Destination node index
|
|
mBorder = -1; // Border segment on interiors
|
|
mDist = 1.0; // 3D dist, though jet edges are adjusted a little so ratings work
|
|
mJet = 0; // Jetting connection
|
|
mHopOver = 0; // For jetting, amount to hop up and over
|
|
|
|
// Factors that influence edge scaling-
|
|
//
|
|
mTeam = 0; // An edge that only this team can pass - i.e. force fields.
|
|
mJump = 0; // On jetting edges - is it flat enough to jump?
|
|
mLateral = 0; // Crude lateral distance - needed to know if we can jet down.
|
|
mDown = 0; // Down side of a jet?
|
|
mSteep = 0; // Can't walk up
|
|
setInverse(1.0);
|
|
}
|
|
|
|
const char * GraphEdge::problems() const
|
|
{
|
|
if (mDist < 0)
|
|
return "Dijkstra() forbids edge with negative distance";
|
|
if (getInverse() < 0)
|
|
return "Edge with negative scale";
|
|
return NULL;
|
|
}
|
|
|
|
// Given the jet rating, can this connection be traversed? If down, must satisfy
|
|
// lateral only. Used by searcher, and by partition builder. In the latter case it
|
|
// must (on 1st of its 2 passes) treat down same as up to find stranded partitions.
|
|
bool GraphEdge::canJet(const F32* ratings, bool both) const
|
|
{
|
|
if (!mDown || both)
|
|
{
|
|
F32 rating = ratings[mJump];
|
|
return (rating >= mDist);
|
|
}
|
|
else
|
|
{
|
|
F32 rating = ratings[mJump];
|
|
return rating > F32(mLateral);
|
|
}
|
|
}
|
|
|
|
// This table gives our inverse speed values, so we can keep it in a few bits. Should
|
|
// still give the desired behavior, though a little coarsely at times. Perfect paths
|
|
// in terms of travel times is not something that people seem to percieve that much...
|
|
const F32 GraphEdge::csInvSpdTab[InvTabSz + 1] =
|
|
{
|
|
0.0, 0.1, 0.2, 0.3, 0.4,
|
|
0.6, 0.8, 1.0, 1.2, 1.4,
|
|
|
|
1.7, 2.0, 2.6, 3.5, 4.6,
|
|
5.7, 6.8, 8.0, 9.2, 10.4,
|
|
|
|
12.0, 14.1, 17.0, 20.0, 24.0,
|
|
30.0, 37.0, 45.0, 54.0, 64.0,
|
|
|
|
80.0, 100.0, 125.0, 156.0, 200.0,
|
|
400.0, 800.0, 1600.0, 3200.0, 6400.0,
|
|
|
|
2.56e4, 1.02e5, 4.1e5, 1.6e6, 6.5e6,
|
|
2.6e7, 1.05e8, 4.19e8, 1.68e9, 9.9e10,
|
|
|
|
1e11, 1e12, 1e13, 1e14, 1e15,
|
|
1e16, 1e17, 1e18, 1e19, 1e20,
|
|
1e21, 1e22, 1e23, 1e24,
|
|
|
|
// Sentinal value -
|
|
1e40,
|
|
};
|
|
|
|
// Find the 6-bit number using a binary search...
|
|
void GraphEdge::setInverse(F32 is)
|
|
{
|
|
S32 lo = 0;
|
|
S32 hi = InvTabSz;
|
|
|
|
AssertFatal(is < csInvSpdTab[hi] && is > csInvSpdTab[lo], "Bad edge inverse speed");
|
|
|
|
// Find the largest value we're greater than in the table-
|
|
for (S32 i = InvSpdBits; i > 0; i--)
|
|
{
|
|
S32 mid = (hi + lo >> 1);
|
|
if (is >= csInvSpdTab[mid])
|
|
lo = mid;
|
|
else
|
|
hi = mid;
|
|
}
|
|
|
|
mInverse = lo;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// GraphNode Methods, Default Virtuals
|
|
|
|
GraphNode::GraphNode()
|
|
{
|
|
mIsland = -1;
|
|
mOnPath = 0;
|
|
mAvoidUntil = 0;
|
|
mLoc.set(-1,-1,-1);
|
|
// mOwnedPtr = NULL;
|
|
// mOwnedCnt = 0;
|
|
mThreats = 0;
|
|
}
|
|
|
|
// Derived classes must supply fetcher which takes a buffer to build in. This method
|
|
// patches to that, and should serve fine if pointers to these aren't kept around.
|
|
const Point3F& GraphNode::location() const
|
|
{
|
|
static Point3F buff[8];
|
|
static U8 ind = 0;
|
|
return fetchLoc(buff[ind++ & 7]);
|
|
}
|
|
|
|
// Outdoor nodes use level (and they start at -1), others don't. Though this is also
|
|
// used by path randomization to determine if it's reasonable to avoid the node.
|
|
S32 GraphNode::getLevel() const
|
|
{
|
|
return -2;
|
|
}
|
|
|
|
Point3F GraphNode::getRenderPos() const
|
|
{
|
|
Point3F buff, adjusted;
|
|
adjusted = fetchLoc(buff);
|
|
adjusted.z += 0.3;
|
|
return adjusted;
|
|
}
|
|
|
|
// Set if not already set (shouldn't be set already, caller asserts)
|
|
void GraphNode::setIsland(S32 num)
|
|
{
|
|
AssertFatal(!mFlags.test(GotIsland), "GraphNode::setIsland()");
|
|
mFlags.set(GotIsland);
|
|
mIsland = num;
|
|
}
|
|
|
|
// Ideally this ratio would accurately reflect travel speeds along the slopes.
|
|
// Here we use
|
|
// xy / total If we're going down. Hence XY is net path distance.
|
|
// total / xy If we're going up.
|
|
static F32 getBaseEdgeScale(const Point3F& src, Point3F dst)
|
|
{
|
|
F32 totalDist = (dst -= src).len();
|
|
bool goingUp = (dst.z > 0);
|
|
dst.z = 0;
|
|
F32 xyDist = dst.len();
|
|
F32 ratio = 1.0;
|
|
|
|
if (xyDist > 0.01 && totalDist > 0.01)
|
|
{
|
|
if (goingUp)
|
|
{
|
|
ratio = (totalDist / xyDist);
|
|
if (ratio < 1.2)
|
|
ratio = 1.0;
|
|
}
|
|
else
|
|
ratio = (xyDist / totalDist);
|
|
|
|
// Want to do something between square root and straight value, so we'll
|
|
// raise it to the 3/4 power here, which will bring it to 1 from either side.
|
|
ratio = mSqrt(mSqrt(ratio) * ratio);
|
|
}
|
|
|
|
return ratio;
|
|
}
|
|
|
|
F32 GraphNode::edgeScale(const GraphNode * dest) const
|
|
{
|
|
// F32 scale = 1.0;
|
|
F32 scale = getBaseEdgeScale(location(), dest->location());
|
|
|
|
if (submerged())
|
|
scale *= gNavGraph->submergedScale();
|
|
else if (shoreline())
|
|
scale *= gNavGraph->shoreLineScale();
|
|
|
|
if (dest->submerged())
|
|
scale *= gNavGraph->submergedScale();
|
|
else if (dest->shoreline())
|
|
scale *= gNavGraph->shoreLineScale();
|
|
|
|
// Searcher reserves REALLY large numbers (10^20th about) to signal search
|
|
// failure, so make sure we don't inadvertently interfere with any of that.
|
|
scale = getMin(scale, 1000000000.0f);
|
|
|
|
return scale;
|
|
}
|
|
|
|
static GraphEdge sEdgeBuff[MaxOnDemandEdges];
|
|
|
|
bool GraphNode::neighbors(const GraphNode* other) const
|
|
{
|
|
GraphEdgeArray edgeList = other->getEdges(sEdgeBuff);
|
|
S32 indexOfThis = getIndex();
|
|
|
|
while (GraphEdge * edge = edgeList++)
|
|
if (edge->mDest == indexOfThis)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
GraphEdge * GraphNode::getEdgeTo(S32 to) const
|
|
{
|
|
GraphEdgeArray edgeList = getEdges(sEdgeBuff);
|
|
|
|
while (GraphEdge * edge = edgeList++)
|
|
if (edge->mDest == to)
|
|
return edge;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// Virtual that is only used on non-grid nodes
|
|
const GraphEdge * GraphNode::getEdgePtr() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// This is only called on nodes known to be terrain.
|
|
F32 GraphNode::terrHeight() const
|
|
{
|
|
return 1e17;
|
|
}
|
|
|
|
// Initial use of this is for weighted random selection of nodes for spawn points.
|
|
F32 GraphNode::radius() const
|
|
{
|
|
return 2.0;
|
|
}
|
|
|
|
// Not in yet, but we'll use this instead of radius for weighted random spawn points
|
|
F32 GraphNode::area() const
|
|
{
|
|
return 0.01;
|
|
}
|
|
|
|
// Used to know thinness of node. Spawn system avoids, as do smoothing edges.
|
|
F32 GraphNode::minDim() const
|
|
{
|
|
return 0.01;
|
|
}
|
|
|
|
const Point3F& GraphNode::getNormal() const
|
|
{
|
|
static const Point3F sStraightUp(0,0,1);
|
|
return sStraightUp;
|
|
}
|
|
|
|
// Used for finding how much a node contains a point. More negative => point is
|
|
// more inside.
|
|
NodeProximity GraphNode::containment(const Point3F& point) const
|
|
{
|
|
NodeProximity nodeProx;
|
|
nodeProx.mLateral = (point - location()).len() - radius();
|
|
return nodeProx;
|
|
}
|
|
|
|
Point3F GraphNode::randomLoc() const
|
|
{
|
|
return location();
|
|
}
|
|
|
|
// Nodes which have volumes associated. Added to allow the transient nodes to
|
|
// respond with the proper volume that they may be associated with.
|
|
S32 GraphNode::volumeIndex() const
|
|
{
|
|
return getIndex();
|
|
}
|
|
|
|
// Set the avoidance if it isn't already. The inmportant flag will override though.
|
|
void GraphNode::setAvoid(U32 duration, bool isImportant)
|
|
{
|
|
U32 curTime = Sim::getCurrentTime();
|
|
|
|
if (curTime > mAvoidUntil || isImportant)
|
|
{
|
|
mAvoidUntil = curTime + duration;
|
|
mFlags.set(StuckAvoid, isImportant);
|
|
|
|
// On avoidance, also mark neighbors-
|
|
if (isImportant)
|
|
{
|
|
GraphEdgeArray edgeList = getEdges(sEdgeBuff);
|
|
while (GraphEdge * edge = edgeList++)
|
|
{
|
|
GraphNode * node = gNavGraph->lookupNode(edge->mDest);
|
|
node->mAvoidUntil = mAvoidUntil;
|
|
node->mFlags.clear(StuckAvoid); // slight avoid here.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// Default RegularNode Virtuals
|
|
|
|
RegularNode::RegularNode()
|
|
{
|
|
mIndex = -1;
|
|
mNormal.set(0,0,1);
|
|
}
|
|
|
|
GraphEdgeArray RegularNode::getEdges(GraphEdge*) const
|
|
{
|
|
return GraphEdgeArray(mEdges.size(), mEdges.address());
|
|
}
|
|
|
|
GraphEdge & RegularNode::pushEdge(GraphNode * node)
|
|
{
|
|
GraphEdge edge;
|
|
edge.mDest = node->getIndex();
|
|
mEdges.push_back(edge);
|
|
return mEdges.last();
|
|
}
|
|
|
|
const Point3F& RegularNode::getNormal() const
|
|
{
|
|
return mNormal;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
void GraphNodeList::setFlags(U32 bits)
|
|
{
|
|
for(const_iterator it = begin(); it != end(); it++)
|
|
if(*it)
|
|
(*it)->mFlags.set(bits);
|
|
}
|
|
|
|
void GraphNodeList::clearFlags(U32 bits)
|
|
{
|
|
for(const_iterator it = begin(); it != end(); it++)
|
|
if(*it)
|
|
(*it)->mFlags.clear(bits);
|
|
}
|
|
|
|
S32 GraphNodeList::searchSphere(const SphereF& sphere, const GraphNodeList& listIn)
|
|
{
|
|
clear();
|
|
F32 radiusSquared = (sphere.radius * sphere.radius);
|
|
for(const_iterator it = listIn.begin(); it != listIn.end(); it++)
|
|
if(*it)
|
|
if((sphere.center - (*it)->location()).lenSquared() < radiusSquared)
|
|
push_back(*it);
|
|
return size();
|
|
}
|
|
|
|
GraphNode * GraphNodeList::closest(const Point3F& loc, bool)
|
|
{
|
|
GraphNode * bestNode = NULL;
|
|
F32 bestRadSquared = 1e12, dSqrd;
|
|
for (const_iterator it = begin(); it != end(); it++)
|
|
if (*it)
|
|
if ((dSqrd = (loc - (*it)->location()).lenSquared()) < bestRadSquared)
|
|
{
|
|
bestRadSquared = dSqrd;
|
|
bestNode = *it;
|
|
}
|
|
return bestNode;
|
|
}
|
|
|
|
// Add the node pointer if it's not already in the list.
|
|
bool GraphNodeList::addUnique(GraphNode * node)
|
|
{
|
|
for (iterator n = begin(); n != end(); n++)
|
|
if (* n == node)
|
|
return false;
|
|
push_back(node);
|
|
return true;
|
|
}
|
|
|