diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 2bdeb27179..a2d304c2e0 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -109,24 +109,25 @@ namespace MWMechanics return std::vector(std::begin(idle), std::end(idle)); } - void trimAllowedNodes(const std::deque& path, std::vector& nodes) + void trimAllowedPositions(const std::deque& path, std::vector& 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 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(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().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(); - 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(dest.mX), static_cast(dest.mY), static_cast(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().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().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(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::max(); + float distanceToClosestPosition = std::numeric_limits::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 diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 0bb0f64a83..0518015936 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -51,14 +51,13 @@ namespace MWMechanics unsigned short mIdleAnimation; std::vector 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 mAllowedNodes; + // allowed destination positions based on mDistance from the spawn point + std::vector 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 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 diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 94242404e4..b68532c9d4 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -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(v[0]), static_cast(v[1]), static_cast(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(p.pos[0]), static_cast(p.pos[1]), static_cast(p.pos[2])); - } - static osg::Vec3f makeOsgVec3(const ESM::Pathgrid::Point& p) { return osg::Vec3f(static_cast(p.mX), static_cast(p.mY), static_cast(p.mZ)); diff --git a/components/misc/coordinateconverter.hpp b/components/misc/coordinateconverter.hpp index 7853880809..6c4d8dbf71 100644 --- a/components/misc/coordinateconverter.hpp +++ b/components/misc/coordinateconverter.hpp @@ -59,10 +59,18 @@ namespace Misc point.y() -= static_cast(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(mCellX), point.y() - static_cast(mCellY), point.z()); + osg::Vec3f result = point; + toLocal(result); + return result; } private: