Store allowed positions as osg::Vec3f

There is no need to store pathgrid points.
This commit is contained in:
elsid 2025-04-21 14:51:41 +02:00
parent 7c46635a5a
commit 50f5bc51c6
No known key found for this signature in database
GPG Key ID: B845CB9FEE18AB40
4 changed files with 108 additions and 116 deletions

View File

@ -109,24 +109,25 @@ namespace MWMechanics
return std::vector<unsigned char>(std::begin(idle), std::end(idle));
}
void trimAllowedNodes(const std::deque<osg::Vec3f>& path, std::vector<ESM::Pathgrid::Point>& nodes)
void trimAllowedPositions(const std::deque<osg::Vec3f>& path, std::vector<osg::Vec3f>& allowedPositions)
{
// TODO: how to add these back in once the door opens?
// Idea: keep a list of detected closed doors (see aicombat.cpp)
// Every now and then check whether one of the doors is opened. (maybe
// at the end of playing idle?) If the door is opened then re-calculate
// allowed nodes starting from the spawn point.
// allowed positions starting from the spawn point.
std::vector<osg::Vec3f> points(path.begin(), path.end());
while (points.size() >= 2)
{
const osg::Vec3f point = points.back();
for (std::size_t j = 0; j < nodes.size(); j++)
for (std::size_t j = 0; j < allowedPositions.size(); j++)
{
// FIXME: doesn't handle a door with the same X/Y
// coordinates but with a different Z
if (std::abs(nodes[j].mX - point.x()) <= 0.5 && std::abs(nodes[j].mY - point.y()) <= 0.5)
if (std::abs(allowedPositions[j].x() - point.x()) <= 0.5
&& std::abs(allowedPositions[j].y() - point.y()) <= 0.5)
{
nodes.erase(nodes.begin() + j);
allowedPositions.erase(allowedPositions.begin() + j);
break;
}
}
@ -143,9 +144,9 @@ namespace MWMechanics
, mCanWanderAlongPathGrid(true)
, mIdleAnimation(0)
, mBadIdles()
, mPopulateAvailableNodes(true)
, mAllowedNodes()
, mTrimCurrentNode(false)
, mPopulateAvailablePositions(true)
, mAllowedPositions()
, mTrimCurrentPosition(false)
, mCheckIdlePositionTimer(0)
, mStuckCount(0)
{
@ -298,10 +299,10 @@ namespace MWMechanics
mStoredInitialActorPosition = true;
}
// Initialization to discover & store allowed node points for this actor.
if (storage.mPopulateAvailableNodes)
// Initialization to discover & store allowed positions points for this actor.
if (storage.mPopulateAvailablePositions)
{
getAllowedNodes(actor, storage);
fillAllowedPositions(actor, storage);
}
MWBase::World& world = *MWBase::Environment::get().getWorld();
@ -319,7 +320,7 @@ namespace MWMechanics
}
// If the package has a wander distance but no pathgrid is available,
// randomly idle or wander near spawn point
else if (storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually)
else if (storage.mAllowedPositions.empty() && mDistance > 0 && !storage.mIsWanderingManually)
{
// Typically want to idle for a short time before the next wander
if (Misc::Rng::rollDice(100, prng) >= 96)
@ -331,7 +332,7 @@ namespace MWMechanics
storage.setState(AiWanderStorage::Wander_IdleNow);
}
}
else if (storage.mAllowedNodes.empty() && !storage.mIsWanderingManually)
else if (storage.mAllowedPositions.empty() && !storage.mIsWanderingManually)
{
storage.mCanWanderAlongPathGrid = false;
}
@ -347,9 +348,9 @@ namespace MWMechanics
// Construct a new path if there isn't one
if (!mPathFinder.isPathConstructed())
{
if (!storage.mAllowedNodes.empty())
if (!storage.mAllowedPositions.empty())
{
setPathToAnAllowedNode(actor, storage, pos);
setPathToAnAllowedPosition(actor, storage, pos);
}
}
}
@ -515,17 +516,17 @@ namespace MWMechanics
void AiWander::onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
{
// Check if an idle actor is too far from all allowed nodes or too close to a door - if so start walking.
// Check if an idle actor is too far from all allowed positions or too close to a door - if so start walking.
storage.mCheckIdlePositionTimer += duration;
if (storage.mCheckIdlePositionTimer >= idlePositionCheckInterval && !isStationary())
{
storage.mCheckIdlePositionTimer = 0; // restart timer
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance() * 1.6f;
if (proximityToDoor(actor, distance) || !isNearAllowedNode(actor, storage, distance))
if (proximityToDoor(actor, distance) || !isNearAllowedPosition(actor, storage, distance))
{
storage.setState(AiWanderStorage::Wander_MoveNow);
storage.mTrimCurrentNode = false; // just in case
storage.mTrimCurrentPosition = false; // just in case
return;
}
}
@ -541,16 +542,14 @@ namespace MWMechanics
}
}
bool AiWander::isNearAllowedNode(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const
bool AiWander::isNearAllowedPosition(
const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const
{
const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
for (const ESM::Pathgrid::Point& node : storage.mAllowedNodes)
{
osg::Vec3f point(node.mX, node.mY, node.mZ);
if ((actorPos - point).length2() < distance * distance)
return true;
}
return false;
const float squaredDistance = distance * distance;
return std::ranges::find_if(storage.mAllowedPositions, [&](const osg::Vec3& v) {
return (actorPos - v).length2() < squaredDistance;
}) != storage.mAllowedPositions.end();
}
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration,
@ -610,8 +609,8 @@ namespace MWMechanics
if (proximityToDoor(actor, distance))
{
// remove allowed points then select another random destination
storage.mTrimCurrentNode = true;
trimAllowedNodes(mPathFinder.getPath(), storage.mAllowedNodes);
storage.mTrimCurrentPosition = true;
trimAllowedPositions(mPathFinder.getPath(), storage.mAllowedPositions);
mObstacleCheck.clear();
stopWalking(actor);
storage.setState(AiWanderStorage::Wander_MoveNow);
@ -630,42 +629,41 @@ namespace MWMechanics
}
}
void AiWander::setPathToAnAllowedNode(
void AiWander::setPathToAnAllowedPosition(
const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos)
{
auto world = MWBase::Environment::get().getWorld();
auto& prng = world->getPrng();
unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng);
const ESM::Pathgrid::Point& dest = storage.mAllowedNodes[randNode];
const std::size_t randomAllowedPositionIndex
= static_cast<std::size_t>(Misc::Rng::rollDice(storage.mAllowedPositions.size(), prng));
const osg::Vec3f randomAllowedPosition = storage.mAllowedPositions[randomAllowedPositionIndex];
const osg::Vec3f start = actorPos.asVec3();
// don't take shortcuts for wandering
const ESM::Pathgrid* pathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
const osg::Vec3f destVec3f = PathFinder::makeOsgVec3(dest);
mPathFinder.buildPathByPathgrid(start, destVec3f, actor.getCell(), getPathGridGraph(pathgrid));
mPathFinder.buildPathByPathgrid(start, randomAllowedPosition, actor.getCell(), getPathGridGraph(pathgrid));
if (mPathFinder.isPathConstructed())
{
mDestination = destVec3f;
mDestination = randomAllowedPosition;
mHasDestination = true;
mUsePathgrid = true;
// Remove this node as an option and add back the previously used node (stops NPC from picking the same
// node):
ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode];
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode);
// check if mCurrentNode was taken out of mAllowedNodes
if (storage.mTrimCurrentNode && storage.mAllowedNodes.size() > 1)
storage.mTrimCurrentNode = false;
// Remove this position as an option and add back the previously used position (stops NPC from picking the
// same position):
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + randomAllowedPositionIndex);
// check if mCurrentPosition was taken out of mAllowedPositions
if (storage.mTrimCurrentPosition && storage.mAllowedPositions.size() > 1)
storage.mTrimCurrentPosition = false;
else
storage.mAllowedNodes.push_back(storage.mCurrentNode);
storage.mCurrentNode = temp;
storage.mAllowedPositions.push_back(storage.mCurrentPosition);
storage.mCurrentPosition = randomAllowedPosition;
storage.setState(AiWanderStorage::Wander_Walking);
}
// Choose a different node and delete this one from possible nodes because it is uncreachable:
// Choose a different position and delete this one from possible positions because it is uncreachable:
else
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode);
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + randomAllowedPositionIndex);
}
void AiWander::stopWalking(const MWWorld::Ptr& actor)
@ -741,20 +739,20 @@ namespace MWMechanics
return;
AiWanderStorage& storage = state.get<AiWanderStorage>();
if (storage.mPopulateAvailableNodes)
getAllowedNodes(actor, storage);
if (storage.mPopulateAvailablePositions)
fillAllowedPositions(actor, storage);
if (storage.mAllowedNodes.empty())
if (storage.mAllowedPositions.empty())
return;
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
int index = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng);
ESM::Pathgrid::Point worldDest = storage.mAllowedNodes[index];
int index = Misc::Rng::rollDice(storage.mAllowedPositions.size(), prng);
const osg::Vec3f worldDest = storage.mAllowedPositions[index];
const Misc::CoordinateConverter converter = Misc::makeCoordinateConverter(*actor.getCell()->getCell());
ESM::Pathgrid::Point dest = converter.toLocalPoint(worldDest);
osg::Vec3f dest = converter.toLocalVec3(worldDest);
bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(
PathFinder::makeOsgVec3(worldDest), 60);
const bool isPathGridOccupied
= MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(worldDest, 60);
// add offset only if the selected pathgrid is occupied by another actor
if (isPathGridOccupied)
@ -774,19 +772,17 @@ namespace MWMechanics
const ESM::Pathgrid::Point& connDest = points[randomIndex];
// add an offset towards random neighboring node
osg::Vec3f dir = PathFinder::makeOsgVec3(connDest) - PathFinder::makeOsgVec3(dest);
float length = dir.length();
osg::Vec3f dir = PathFinder::makeOsgVec3(connDest) - dest;
const float length = dir.length();
dir.normalize();
for (int j = 1; j <= 3; j++)
{
// move for 5-15% towards random neighboring node
dest
= PathFinder::makePathgridPoint(PathFinder::makeOsgVec3(dest) + dir * (j * 5 * length / 100.f));
worldDest = converter.toWorldPoint(dest);
dest = dest + dir * (j * 5 * length / 100.f);
isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(
PathFinder::makeOsgVec3(worldDest), 60);
converter.toWorldVec3(dest), 60);
if (!isOccupied)
break;
@ -806,19 +802,18 @@ namespace MWMechanics
// place above to prevent moving inside objects, e.g. stairs, because a vector between pathgrids can be
// underground. Adding 20 in adjustPosition() is not enough.
dest.mZ += 60;
dest.z() += 60;
converter.toWorld(dest);
state.reset();
osg::Vec3f pos(static_cast<float>(dest.mX), static_cast<float>(dest.mY), static_cast<float>(dest.mZ));
MWBase::Environment::get().getWorld()->moveObject(actor, pos);
MWBase::Environment::get().getWorld()->moveObject(actor, dest);
actor.getClass().adjustPosition(actor, false);
}
void AiWander::getNeighbouringNodes(
ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points)
const osg::Vec3f& dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points)
{
const ESM::Pathgrid* pathgrid
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*currentCell->getCell());
@ -826,19 +821,19 @@ namespace MWMechanics
if (pathgrid == nullptr || pathgrid->mPoints.empty())
return;
size_t index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest));
const size_t index = PathFinder::getClosestPoint(pathgrid, dest);
getPathGridGraph(pathgrid).getNeighbouringPoints(index, points);
}
void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, AiWanderStorage& storage)
void AiWander::fillAllowedPositions(const MWWorld::Ptr& actor, AiWanderStorage& storage)
{
// infrequently used, therefore no benefit in caching it as a member
const MWWorld::CellStore* cellStore = actor.getCell();
const ESM::Pathgrid* pathgrid
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*cellStore->getCell());
storage.mAllowedNodes.clear();
storage.mAllowedPositions.clear();
// If there is no path this actor doesn't go anywhere. See:
// https://forum.openmw.org/viewtopic.php?t=1556
@ -861,32 +856,33 @@ namespace MWMechanics
// Find closest pathgrid point
size_t closestPointIndex = PathFinder::getClosestPoint(pathgrid, npcPos);
// mAllowedNodes for this actor with pathgrid point indexes based on mDistance
// mAllowedPositions for this actor with pathgrid point indexes based on mDistance
// and if the point is connected to the closest current point
// NOTE: mPoints is in local coordinates
size_t pointIndex = 0;
for (size_t counter = 0; counter < pathgrid->mPoints.size(); counter++)
{
osg::Vec3f nodePos(PathFinder::makeOsgVec3(pathgrid->mPoints[counter]));
const osg::Vec3f nodePos = PathFinder::makeOsgVec3(pathgrid->mPoints[counter]);
if ((npcPos - nodePos).length2() <= mDistance * mDistance
&& getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, counter))
{
storage.mAllowedNodes.push_back(converter.toWorldPoint(pathgrid->mPoints[counter]));
storage.mAllowedPositions.push_back(
Misc::Convert::makeOsgVec3f(converter.toWorldPoint(pathgrid->mPoints[counter])));
pointIndex = counter;
}
}
if (storage.mAllowedNodes.size() == 1)
if (storage.mAllowedPositions.size() == 1)
{
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(mInitialActorPosition));
storage.mAllowedPositions.push_back(mInitialActorPosition);
addNonPathGridAllowedPoints(pathgrid, pointIndex, storage, converter);
}
if (!storage.mAllowedNodes.empty())
if (!storage.mAllowedPositions.empty())
{
setCurrentNodeToClosestAllowedNode(storage);
setCurrentPositionToClosestAllowedPosition(storage);
}
}
storage.mPopulateAvailableNodes = false;
storage.mPopulateAvailablePositions = false;
}
// When only one path grid point in wander distance,
@ -900,13 +896,13 @@ namespace MWMechanics
{
if (edge.mV0 == pointIndex)
{
AddPointBetweenPathGridPoints(converter.toWorldPoint(pathGrid->mPoints[edge.mV0]),
addPositionBetweenPathgridPoints(converter.toWorldPoint(pathGrid->mPoints[edge.mV0]),
converter.toWorldPoint(pathGrid->mPoints[edge.mV1]), storage);
}
}
}
void AiWander::AddPointBetweenPathGridPoints(
void AiWander::addPositionBetweenPathgridPoints(
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage)
{
osg::Vec3f vectorStart = PathFinder::makeOsgVec3(start);
@ -919,25 +915,25 @@ namespace MWMechanics
// must not travel longer than distance between waypoints or NPC goes past waypoint
distance = std::min(distance, static_cast<unsigned>(length));
delta *= distance;
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(vectorStart + delta));
storage.mAllowedPositions.push_back(vectorStart + delta);
}
void AiWander::setCurrentNodeToClosestAllowedNode(AiWanderStorage& storage)
void AiWander::setCurrentPositionToClosestAllowedPosition(AiWanderStorage& storage)
{
float distanceToClosestNode = std::numeric_limits<float>::max();
float distanceToClosestPosition = std::numeric_limits<float>::max();
size_t index = 0;
for (size_t i = 0; i < storage.mAllowedNodes.size(); ++i)
for (size_t i = 0; i < storage.mAllowedPositions.size(); ++i)
{
osg::Vec3f nodePos(PathFinder::makeOsgVec3(storage.mAllowedNodes[i]));
float tempDist = (mInitialActorPosition - nodePos).length2();
if (tempDist < distanceToClosestNode)
const osg::Vec3f position = storage.mAllowedPositions[i];
const float tempDist = (mInitialActorPosition - position).length2();
if (tempDist < distanceToClosestPosition)
{
index = i;
distanceToClosestNode = tempDist;
distanceToClosestPosition = tempDist;
}
}
storage.mCurrentNode = storage.mAllowedNodes[index];
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + index);
storage.mCurrentPosition = storage.mAllowedPositions[index];
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + index);
}
void AiWander::writeState(ESM::AiSequence::AiSequence& sequence) const

View File

@ -51,14 +51,13 @@ namespace MWMechanics
unsigned short mIdleAnimation;
std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors
// do we need to calculate allowed nodes based on mDistance
bool mPopulateAvailableNodes;
bool mPopulateAvailablePositions;
// allowed pathgrid nodes based on mDistance from the spawn point
std::vector<ESM::Pathgrid::Point> mAllowedNodes;
// allowed destination positions based on mDistance from the spawn point
std::vector<osg::Vec3f> mAllowedPositions;
ESM::Pathgrid::Point mCurrentNode;
bool mTrimCurrentNode;
osg::Vec3f mCurrentPosition;
bool mTrimCurrentPosition;
float mCheckIdlePositionTimer;
int mStuckCount;
@ -132,7 +131,8 @@ namespace MWMechanics
bool playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
int getRandomIdle() const;
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
void setPathToAnAllowedPosition(
const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration,
MWWorld::MovementDirectionFlags supportedMovementDirections, AiWanderStorage& storage);
@ -145,26 +145,27 @@ namespace MWMechanics
void wanderNearStart(const MWWorld::Ptr& actor, AiWanderStorage& storage, int wanderDistance);
bool destinationIsAtWater(const MWWorld::Ptr& actor, const osg::Vec3f& destination);
void completeManualWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage);
bool isNearAllowedNode(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const;
bool isNearAllowedPosition(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const;
const unsigned mDistance; // how far the actor can wander from the spawn point
// how far the actor can wander from the spawn point
const unsigned mDistance;
const unsigned mDuration;
float mRemainingDuration;
const int mTimeOfDay;
const std::vector<unsigned char> mIdle;
bool mStoredInitialActorPosition;
osg::Vec3f
mInitialActorPosition; // Note: an original engine does not reset coordinates even when actor changes a cell
// Note: an original engine does not reset coordinates even when actor changes a cell
osg::Vec3f mInitialActorPosition;
bool mHasDestination;
osg::Vec3f mDestination;
bool mUsePathgrid;
void getNeighbouringNodes(
ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);
const osg::Vec3f& dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);
void getAllowedNodes(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void fillAllowedPositions(const MWWorld::Ptr& actor, AiWanderStorage& storage);
// constants for converting idleSelect values into groupNames
enum GroupIndex
@ -173,12 +174,12 @@ namespace MWMechanics
GroupIndex_MaxIdle = 9
};
void setCurrentNodeToClosestAllowedNode(AiWanderStorage& storage);
void setCurrentPositionToClosestAllowedPosition(AiWanderStorage& storage);
void addNonPathGridAllowedPoints(const ESM::Pathgrid* pathGrid, size_t pointIndex, AiWanderStorage& storage,
const Misc::CoordinateConverter& converter);
void AddPointBetweenPathGridPoints(
void addPositionBetweenPathgridPoints(
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage);
/// lookup table for converting idleSelect value to groupName

View File

@ -145,19 +145,6 @@ namespace MWMechanics
mPath.push_back(point);
}
/// utility function to convert a osg::Vec3f to a Pathgrid::Point
static ESM::Pathgrid::Point makePathgridPoint(const osg::Vec3f& v)
{
return ESM::Pathgrid::Point(static_cast<int>(v[0]), static_cast<int>(v[1]), static_cast<int>(v[2]));
}
/// utility function to convert an ESM::Position to a Pathgrid::Point
static ESM::Pathgrid::Point makePathgridPoint(const ESM::Position& p)
{
return ESM::Pathgrid::Point(
static_cast<int>(p.pos[0]), static_cast<int>(p.pos[1]), static_cast<int>(p.pos[2]));
}
static osg::Vec3f makeOsgVec3(const ESM::Pathgrid::Point& p)
{
return osg::Vec3f(static_cast<float>(p.mX), static_cast<float>(p.mY), static_cast<float>(p.mZ));

View File

@ -59,10 +59,18 @@ namespace Misc
point.y() -= static_cast<float>(mCellY);
}
osg::Vec3f toWorldVec3(const osg::Vec3f& point) const
{
osg::Vec3f result = point;
toWorld(result);
return result;
}
osg::Vec3f toLocalVec3(const osg::Vec3f& point) const
{
return osg::Vec3f(
point.x() - static_cast<float>(mCellX), point.y() - static_cast<float>(mCellY), point.z());
osg::Vec3f result = point;
toLocal(result);
return result;
}
private: