engine/ai/graphLOS.cc
2024-01-07 04:36:33 +00:00

226 lines
6.1 KiB
C++

//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
#include "ai/graphLOS.h"
U32 Loser::mCasts = 0; // Just for informal profiling, counting casts.
static const F32 scGraphStepCheck = 0.75;
Loser::Loser(U32 mask, bool checkFF)
: mCheckingFF(checkFF),
mMask(mask)
{
mHitForceField = false;
}
bool Loser::haveLOS(const Point3F& src, const Point3F& dst)
{
if (mCheckingFF)
{
// Keep track if a force field is encountered. Walkable connections allow,
// jettable connections can't go through them.
U32 maskWithFF = (mMask | ForceFieldObjectType);
mCasts++;
mColl.object = NULL; // (Not sure if system clears)
if (gServerContainer.castRay(src, dst, maskWithFF, &mColl))
{
if (mColl.object->getTypeMask() & ForceFieldObjectType)
mHitForceField = true; // and fall through do normal LOS.
else
return false;
}
}
mCasts++;
mColl.object = NULL;
return !gServerContainer.castRay(src, dst, mMask, &mColl);
}
// Drop the point down to collision (if any) below. If no collision, we don't
// change the drop point, and return false.
bool Loser::hitBelow(Point3F& drop, F32 down)
{
Point3F below(drop.x, drop.y, drop.z - down);
if (gServerContainer.castRay(drop, below, mMask, &mColl))
{
drop = mColl.point;
return true;
}
return false;
}
// Height above, capped at UpHeightMax.
F32 Loser::heightUp(const Point3F& from, F32 maxUp)
{
Point3F up(from.x, from.y, from.z + maxUp);
if (haveLOS(from, up))
return maxUp;
else
return (mColl.point.z - from.z);
}
// Do LOS fanning one of the points. Just do a bunch of lines. We pre-increment
// and pre-decrement since the S-to-D los has already been checked.
bool Loser::fannedLOS1(const Point3F& S, const Point3F& D, const VectorF& inc, S32 N)
{
Point3F D0 = D, D1 = D;
while(N--)
if(!haveLOS(S, D0 += inc) || !haveLOS(S, D1 -= inc))
return false;
return true;
}
// Do LOS fanning both points.
bool Loser::fannedLOS2(const Point3F& S, const Point3F& D, const VectorF& inc, S32 N)
{
Point3F D0 = D, D1 = D, S0 = S, S1 = S;
while(N--)
if(!haveLOS(S0 += inc, D0 += inc) || !haveLOS(S1 -= inc, D1 -= inc))
return false;
return true;
}
// Do LOS checks downward to look for changes greater than step height.
bool Loser::walkOverBumps(Point3F S, Point3F D, F32 inc)
{
VectorF step = (D - S);
S32 N = getMax(S32(step.len() / inc), 2);
// Note D becomes our stepper directly under S.
F32 downDist = 140;
(D = S).z -= downDist;
if (haveLOS(S, D))
return false;
// Set up initial Z down.
F32 zdown = mColl.point.z;
step *= (1.0f / F32(N));
while (N--)
{
if (haveLOS(S += step, D += step))
return false;
if (mColl.object->getTypeMask() & WaterObjectType)
return false;
F32 getZ = mColl.point.z;
if (mFabs(getZ - zdown) > scGraphStepCheck)
return false;
// Save the Z, unless we're not walkable, in which case accumulate.
//==> Use proper slope values from PlayerData.
if (mColl.normal.z > gNavGlobs.mWalkableDot)
zdown = getZ;
}
return true;
}
// Fan the walk - this is SLOW - we'll use less resolution. Note we could try:
// ==> Walk from outside in (on all fanned checks), which will probably
// ==> result in earlier exits whenever false is the result.
// ==> Note we have to profile this whole process.
bool Loser::fannedBumpWalk(const Point3F& S, const Point3F& D, VectorF inc, S32 N)
{
Point3F D0 = D, D1 = D, S0 = S, S1 = S;
inc *= 2.0f;
while ((N -= 2) >= 0)
if (!walkOverBumps(S0 += inc, D0 += inc) || !walkOverBumps(S1 -= inc, D1 -= inc))
return false;
return true;
}
// (If both are outdoor, we'll look for a larger hop amount).
static const F32 sHopLookInc = 0.2;
// Move lines up, looking for a free line. We also want to assure that while there's
// not a free line, we at least have a minimum amount of breathing room - about 1.5m.
bool Loser::findHopLine(Point3F S, Point3F D, F32 maxUp, F32& freeHt)
{
F32 tThresh = (1.5f / (S - D).len());
F32 total = 0;
while (total < maxUp)
{
S.z += sHopLookInc;
D.z += sHopLookInc;
total += sHopLookInc;
if (haveLOS(S, D))
{
freeHt = total;
return true;
}
else
{
if (mColl.t < tThresh) // look for minimum amount in
break;
haveLOS(D, S); // ... from both directions
if (mColl.t < tThresh)
break;
}
}
return false;
}
#define GapWalkInc 0.15f
bool Loser::walkOverGaps(Point3F S, Point3F D, F32 allowedGap)
{
VectorF incVec = (D - S);
VectorF step2D(incVec.x, incVec.y, 0);
F32 inc2D = step2D.len();
S32 N = getMax(S32(inc2D / GapWalkInc) + 1, 2);
F32 bandWid = gNavGlobs.mStepHeight;
F32 saveL = 0, total2D = 0;
bool wasBelow = false;
// D serves as our stepper to do LOS down to
(D = S).z -= 100;
incVec *= (1.0f / F32(N));
inc2D *= (1.0f / F32(N));
while (N--)
{
bool los = !gServerContainer.castRay(S, D, mMask, &mColl);
// Must handle water-
if (!los)
if (mColl.object->getTypeMask() & WaterObjectType)
return false;
// check if we're below the allowed band-
if (los || mColl.point.z < (S.z - bandWid))
{
if (wasBelow)
{
if (total2D - saveL > allowedGap)
return false;
}
else
{
wasBelow = true;
saveL = total2D;
}
}
else
{
wasBelow = false;
saveL = total2D;
}
S += incVec;
D += incVec;
total2D += inc2D;
}
return true;
}