Merge pull request #1528 from marauder2k9-torque/Recast-Update

Recast refactor
This commit is contained in:
Brian Roberts 2025-08-04 08:50:11 -05:00 committed by GitHub
commit c7d966c0de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 3812 additions and 1639 deletions

View file

@ -168,11 +168,20 @@ bool AIController::getAIMove(Move* movePtr)
{
obj = getAIInfo()->mObj;
}
Point3F start = obj->getPosition();
Point3F end = start;
start.z = obj->getBoxCenter().z;
end.z -= mControllerData->mHeightTolerance;
obj->disableCollision();
// Only repath if not already adjusted and on risky ground
RayInfo info;
if (obj->getContainer()->castRay(obj->getPosition(), obj->getPosition() - Point3F(0, 0, mControllerData->mHeightTolerance), StaticShapeObjectType, &info))
if (obj->getContainer()->castRay(start, end, StaticShapeObjectType, &info))
{
getNav()->repath();
}
obj->enableCollision();
getGoal()->mInRange = false;
}
if (getGoal()->getDist() < mControllerData->mFollowTolerance )
@ -530,11 +539,15 @@ AIControllerData::AIControllerData()
mAttackRadius = 2.0f;
mMoveStuckTolerance = 0.01f;
mMoveStuckTestDelay = 30;
mHeightTolerance = 0.001f;
mHeightTolerance = 0.1f;
mFollowTolerance = 1.0f;
#ifdef TORQUE_NAVIGATION_ENABLED
mLinkTypes = LinkData(AllFlags);
mFilter.setIncludeFlags(mLinkTypes.getFlags());
mFilter.setExcludeFlags(0);
mAreaCosts.setSize(PolyAreas::NumAreas);
mAreaCosts.fill(1.0f);
mNavSize = AINavigation::Regular;
mFlocking.mChance = 90;
mFlocking.mMin = 1.0f;
@ -560,6 +573,8 @@ AIControllerData::AIControllerData(const AIControllerData& other, bool temp_clon
#ifdef TORQUE_NAVIGATION_ENABLED
mLinkTypes = other.mLinkTypes;
mFilter = other.mFilter;
mAreaCosts = other.mAreaCosts;
mNavSize = other.mNavSize;
mFlocking.mChance = other.mFlocking.mChance;
mFlocking.mMin = other.mFlocking.mMin;
@ -629,6 +644,8 @@ void AIControllerData::initPersistFields()
addFieldV("FlockSideStep", TypeRangedF32, Offset(mFlocking.mSideStep, AIControllerData), &CommonValidators::PositiveFloat,
"@brief Distance from destination before we stop moving out of the way.");
addField("areaCosts", TypeF32Vector, Offset(mAreaCosts, AIControllerData),
"Vector of costs for each PolyArea.");
addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, AIControllerData),
"Allow the character to walk on dry land.");
addField("allowJump", TypeBool, Offset(mLinkTypes.jump, AIControllerData),
@ -662,6 +679,10 @@ void AIControllerData::packData(BitStream* stream)
#ifdef TORQUE_NAVIGATION_ENABLED
//enums
stream->write(mAreaCosts.size());
for (U32 i = 0; i < mAreaCosts.size(); i++) {
stream->write(mAreaCosts[i]);
}
stream->write(mLinkTypes.getFlags());
stream->write((U32)mNavSize);
// end enums
@ -684,10 +705,23 @@ void AIControllerData::unpackData(BitStream* stream)
stream->read(&mFollowTolerance);
#ifdef TORQUE_NAVIGATION_ENABLED
U32 num;
stream->read(&num);
mAreaCosts.setSize(num);
for (U32 i = 0; i < num; i++)
{
stream->read(&mAreaCosts[i]);
}
//enums
U16 linkFlags;
stream->read(&linkFlags);
mLinkTypes = LinkData(linkFlags);
mFilter.setIncludeFlags(mLinkTypes.getFlags());
mFilter.setExcludeFlags(mLinkTypes.getExcludeFlags());
for (U32 i = 0; i < PolyAreas::NumAreas; i++)
{
mFilter.setAreaCost((PolyAreas)i, mAreaCosts[i]);
}
U32 navSize;
stream->read(&navSize);
mNavSize = (AINavigation::NavSize)(navSize);

View file

@ -168,6 +168,8 @@ public:
/// Types of link we can use.
LinkData mLinkTypes;
dtQueryFilter mFilter;
Vector<F32> mAreaCosts;
AINavigation::NavSize mNavSize;
#endif
Delegate<void(AIController* obj, Point3F location, Move* movePtr)> resolveYawPtr;

View file

@ -80,7 +80,7 @@ bool AIController::findCover(const Point3F& from, F32 radius)
if (s.point)
{
// Calling setPathDestination clears cover...
bool foundPath = getNav()->setPathDestination(s.point->getPosition());
bool foundPath = getNav()->setPathDestination(s.point->getPosition(), true);
setCover(s.point);
s.point->setOccupied(true);
return foundPath;

View file

@ -23,7 +23,7 @@
#include "AIController.h"
#include "T3D/shapeBase.h"
static U32 sAILoSMask = TerrainObjectType | StaticShapeObjectType | StaticObjectType | AIObjectType;
static U32 sAILoSMask = TerrainObjectType | StaticShapeObjectType | StaticObjectType;
AINavigation::AINavigation(AIController* controller)
{
@ -77,6 +77,7 @@ bool AINavigation::setPathDestination(const Point3F& pos, bool replace)
path->mAlwaysRender = true;
path->mLinkTypes = getCtrl()->mControllerData->mLinkTypes;
path->mXray = true;
path->mFilter = getCtrl()->mControllerData->mFilter;
// Paths plan automatically upon being registered.
if (!path->registerObject())
{
@ -338,7 +339,11 @@ void AINavigation::repath()
if (mPathData.path.isNull() || !mPathData.owned)
return;
if (mRandI(0, 100) < getCtrl()->mControllerData->mFlocking.mChance && flock())
if (avoidObstacles())
{
mPathData.path->mTo = mMoveDestination;
}
else if (mRandI(0, 100) < getCtrl()->mControllerData->mFlocking.mChance && flock())
{
mPathData.path->mTo = mMoveDestination;
}
@ -379,6 +384,60 @@ void AINavigation::clearPath()
mPathData = PathData();
}
bool AINavigation::avoidObstacles()
{
SimObjectPtr<SceneObject> obj = getCtrl()->getAIInfo()->mObj;
obj->disableCollision();
Point3F pos = obj->getBoxCenter();
VectorF forward = obj->getTransform().getForwardVector();
forward.normalizeSafe();
// Generate forward-left and forward-right by rotating forward vector
VectorF right = mCross(forward, Point3F(0, 0, 1));
VectorF leftDir = forward + right * -0.5f; // front-left
VectorF rightDir = forward + right * 0.5f; // front-right
leftDir.normalizeSafe();
rightDir.normalizeSafe();
F32 rayLength = obj->getVelocity().lenSquared() * TickSec * 2 + getCtrl()->getAIInfo()->mRadius;
Point3F directions[3] = {
forward,
leftDir,
rightDir
};
bool hit[3] = { false, false, false };
RayInfo info;
for (int i = 0; i < 3; ++i)
{
Point3F end = pos + directions[i] * rayLength;
if (obj->getContainer()->castRay(pos, end, sAILoSMask, &info))
{
hit[i] = true;
}
}
Point3F avoidance = Point3F::Zero;
if (hit[0]) avoidance += right * 1.0f;
if (hit[1]) avoidance += right * 1.5f;
if (hit[2]) avoidance -= right * 1.5f;
if (!avoidance.isZero())
{
avoidance.normalizeSafe();
F32 clearance = getCtrl()->getAIInfo()->mRadius * 1.5f;
Point3F newDest = info.point + avoidance * rayLength;
mMoveDestination = newDest;
obj->enableCollision();
return true;
}
obj->enableCollision();
return false;
}
bool AINavigation::flock()
{
AIControllerData::Flocking flockingData = getCtrl()->mControllerData->mFlocking;
@ -386,9 +445,10 @@ bool AINavigation::flock()
obj->disableCollision();
Point3F pos = obj->getBoxCenter();
Point3F searchArea = Point3F(flockingData.mMin / 2, flockingData.mMax / 2, getCtrl()->getAIInfo()->mObj->getObjBox().maxExtents.z / 2);
F32 maxFlocksq = flockingData.mMax * flockingData.mMax;
Point3F searchArea = Point3F(maxFlocksq, maxFlocksq, getCtrl()->getAIInfo()->mObj->getObjBox().maxExtents.z / 2);
bool flocking = false;
U32 found = 0;
if (getCtrl()->getGoal())
@ -412,41 +472,35 @@ bool AINavigation::flock()
sql.mList.remove(obj);
Point3F avoidanceOffset = Point3F::Zero;
F32 avoidanceAmtSq = 0;
//avoid objects in the way
RayInfo info;
if (obj->getContainer()->castRay(pos, dest + Point3F(0, 0, obj->getObjBox().len_z() / 2), sAILoSMask, &info))
{
Point3F blockerOffset = (info.point - dest);
blockerOffset.z = 0;
avoidanceOffset += blockerOffset;
}
//avoid bots that are too close
for (U32 i = 0; i < sql.mList.size(); i++)
{
ShapeBase* other = dynamic_cast<ShapeBase*>(sql.mList[i]);
Point3F objectCenter = other->getBoxCenter();
F32 sumRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin;
F32 sumMinRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin;
F32 separation = getCtrl()->getAIInfo()->mRadius + other->getAIController()->getAIInfo()->mRadius;
sumRad += separation;
separation += sumMinRad;
Point3F offset = (pos - objectCenter);
F32 offsetLensq = offset.lenSquared(); //square roots are expensive, so use squared val compares
if ((flockingData.mMin > 0) && (offsetLensq < (sumRad * sumRad)))
if ((flockingData.mMin > 0) && (offsetLensq < (sumMinRad * sumMinRad)))
{
other->disableCollision();
if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask, &info))
if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask | AIObjectType, &info))
{
found++;
offset.normalizeSafe();
offset *= sumRad + separation;
offset *= separation;
avoidanceOffset += offset; //accumulate total group, move away from that
avoidanceAmtSq += offsetLensq;
}
other->enableCollision();
}
}
//if we don't have to worry about bumping into one another (nothing found lower than minFLock), see about grouping up
if (found == 0)
{
@ -455,20 +509,20 @@ bool AINavigation::flock()
ShapeBase* other = static_cast<ShapeBase*>(sql.mList[i]);
Point3F objectCenter = other->getBoxCenter();
F32 sumRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin;
F32 sumMaxRad = flockingData.mMax + other->getAIController()->mControllerData->mFlocking.mMax;
F32 separation = getCtrl()->getAIInfo()->mRadius + other->getAIController()->getAIInfo()->mRadius;
sumRad += separation;
separation += sumMaxRad;
Point3F offset = (pos - objectCenter);
if ((flockingData.mMin > 0) && ((sumRad * sumRad) < (maxFlocksq)))
F32 offsetLensq = offset.lenSquared(); //square roots are expensive, so use squared val compares
if ((flockingData.mMax > 0) && (offsetLensq < (sumMaxRad * sumMaxRad)))
{
other->disableCollision();
if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask, &info))
if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask | AIObjectType, &info))
{
found++;
offset.normalizeSafe();
offset *= sumRad + separation;
avoidanceOffset -= offset; // subtract total group, move toward it
avoidanceAmtSq -= offsetLensq;
}
other->enableCollision();
}
@ -476,27 +530,36 @@ bool AINavigation::flock()
}
if (found > 0)
{
//ephasize the *side* portion of sidestep to better avoid clumps
if (avoidanceOffset.x < avoidanceOffset.y)
avoidanceOffset.x *= 2.0;
else
avoidanceOffset.y *= 2.0;
//add fuzz to sidestepping
avoidanceOffset.z = 0;
avoidanceOffset.x = (mRandF() * avoidanceOffset.x) * 0.5 + avoidanceOffset.x * 0.75;
avoidanceOffset.y = (mRandF() * avoidanceOffset.y) * 0.5 + avoidanceOffset.y * 0.75;
if (avoidanceOffset.lenSquared() < (maxFlocksq))
avoidanceOffset.normalizeSafe();
avoidanceOffset *= avoidanceAmtSq;
if ((avoidanceAmtSq) > flockingData.mMin * flockingData.mMin)
{
dest += avoidanceOffset;
dest = obj->getPosition()+avoidanceOffset;
}
//if we're not jumping...
if (mJump == None)
{
dest.z = obj->getPosition().z;
//make sure we don't run off a cliff
Point3F zlen(0, 0, getCtrl()->mControllerData->mHeightTolerance);
if (obj->getContainer()->castRay(dest + zlen, dest - zlen, TerrainObjectType | StaticShapeObjectType | StaticObjectType, &info))
{
if ((mMoveDestination - dest).len() > getCtrl()->mControllerData->mMoveTolerance)
{
mMoveDestination = dest;
flocking = true;
}
mMoveDestination = dest;
flocking = true;
}
}
}

View file

@ -98,6 +98,7 @@ struct AINavigation
/// Move to the specified node in the current path.
void moveToNode(S32 node);
bool avoidObstacles();
bool flock();
#endif
};

View file

@ -114,6 +114,10 @@ AIPlayer::AIPlayer()
mJump = None;
mNavSize = Regular;
mLinkTypes = LinkData(AllFlags);
mFilter.setIncludeFlags(mLinkTypes.getFlags());
mFilter.setExcludeFlags(0);
mAreaCosts.setSize(PolyAreas::NumAreas);
mAreaCosts.fill(1.0f);
#endif
mIsAiControlled = true;
@ -163,7 +167,8 @@ void AIPlayer::initPersistFields()
#ifdef TORQUE_NAVIGATION_ENABLED
addGroup("Pathfinding");
addField("areaCosts", TypeF32Vector, Offset(mAreaCosts, AIPlayer),
"Vector of costs for each PolyArea.");
addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, AIPlayer),
"Allow the character to walk on dry land.");
addField("allowJump", TypeBool, Offset(mLinkTypes.jump, AIPlayer),
@ -785,6 +790,7 @@ void AIPlayer::moveToNode(S32 node)
bool AIPlayer::setPathDestination(const Point3F &pos)
{
#ifdef TORQUE_NAVIGATION_ENABLED
// Pathfinding only happens on the server.
if(!isServerObject())
return false;
@ -799,6 +805,13 @@ bool AIPlayer::setPathDestination(const Point3F &pos)
return false;
}
mFilter.setIncludeFlags(mLinkTypes.getFlags());
mFilter.setExcludeFlags(mLinkTypes.getExcludeFlags());
for (U32 i = 0; i < PolyAreas::NumAreas; i++)
{
mFilter.setAreaCost((PolyAreas)i, mAreaCosts[i]);
}
// Create a new path.
NavPath *path = new NavPath();
@ -808,6 +821,7 @@ bool AIPlayer::setPathDestination(const Point3F &pos)
path->mFromSet = path->mToSet = true;
path->mAlwaysRender = true;
path->mLinkTypes = mLinkTypes;
path->mFilter = mFilter;
path->mXray = true;
// Paths plan automatically upon being registered.
if(!path->registerObject())
@ -839,6 +853,9 @@ bool AIPlayer::setPathDestination(const Point3F &pos)
path->deleteObject();
return false;
}
#else
setMoveDestination(pos, false);
#endif
}
DefineEngineMethod(AIPlayer, setPathDestination, bool, (Point3F goal),,

View file

@ -228,6 +228,8 @@ public:
/// Types of link we can use.
LinkData mLinkTypes;
dtQueryFilter mFilter;
Vector<F32> mAreaCosts;
/// @}
#endif // TORQUE_NAVIGATION_ENABLED