From 9a02a85a244fc5738dd12de0307256ff81285c47 Mon Sep 17 00:00:00 2001 From: dteviot Date: Sun, 5 Jul 2015 18:07:14 +1200 Subject: [PATCH 1/8] Pulled duplicate code into function. --- apps/openmw/mwmechanics/aiwander.cpp | 24 +++++++++++------------- apps/openmw/mwmechanics/aiwander.hpp | 3 +++ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index f32636b23e..3c69a2f5eb 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -498,14 +498,8 @@ namespace MWMechanics { assert(mAllowedNodes.size()); unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size()); - // NOTE: initially constructed with local (i.e. cell) co-ordinates - // convert dest to use world co-ordinates ESM::Pathgrid::Point dest(mAllowedNodes[randNode]); - if (currentCell->getCell()->isExterior()) - { - dest.mX += currentCell->getCell()->mData.mX * ESM::Land::REAL_SIZE; - dest.mY += currentCell->getCell()->mData.mY * ESM::Land::REAL_SIZE; - } + ToWorldCoordinates(dest, currentCell->getCell()); // actor position is already in world co-ordinates ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); @@ -537,6 +531,15 @@ namespace MWMechanics return false; // AiWander package not yet completed } + void AiWander::ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell) + { + if (cell->isExterior()) + { + point.mX += cell->mData.mX * ESM::Land::REAL_SIZE; + point.mY += cell->mData.mY * ESM::Land::REAL_SIZE; + } + } + void AiWander::trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder) { @@ -643,12 +646,7 @@ namespace MWMechanics // apply a slight offset to prevent overcrowding dest.mX += static_cast(Misc::Rng::rollProbability() * 128 - 64); dest.mY += static_cast(Misc::Rng::rollProbability() * 128 - 64); - - if (actor.getCell()->isExterior()) - { - dest.mX += actor.getCell()->getCell()->mData.mX * ESM::Land::REAL_SIZE; - dest.mY += actor.getCell()->getCell()->mData.mY * ESM::Land::REAL_SIZE; - } + ToWorldCoordinates(dest, actor.getCell()->getCell()); MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), static_cast(dest.mY), static_cast(dest.mZ)); diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 75b2230942..d69a72585f 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -118,6 +118,9 @@ namespace MWMechanics GroupIndex_MaxIdle = 9 }; + /// convert point from local (i.e. cell) to world co-ordinates + void ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell); + /// lookup table for converting idleSelect value to groupName static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; }; From f942db2b2761483b9e262fbdfd50302f9c2903b5 Mon Sep 17 00:00:00 2001 From: dteviot Date: Sun, 5 Jul 2015 18:08:09 +1200 Subject: [PATCH 2/8] Simplified code. --- apps/openmw/mwmechanics/aiwander.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 3c69a2f5eb..1567f48f6f 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -682,19 +682,14 @@ namespace MWMechanics // ... pathgrids don't usually include water, so swimmers ignore them if (mDistance && !actor.getClass().isPureWaterCreature(actor)) { - float cellXOffset = 0; - float cellYOffset = 0; + // get NPC's position in local (i.e. cell) co-ordinates + osg::Vec3f npcPos(mInitialActorPosition); if(cell->isExterior()) { - cellXOffset = static_cast(cell->mData.mX * ESM::Land::REAL_SIZE); - cellYOffset = static_cast(cell->mData.mY * ESM::Land::REAL_SIZE); + npcPos[0] = npcPos[0] - static_cast(cell->mData.mX * ESM::Land::REAL_SIZE); + npcPos[1] = npcPos[1] - static_cast(cell->mData.mY * ESM::Land::REAL_SIZE); } - // convert npcPos to local (i.e. cell) co-ordinates - osg::Vec3f npcPos(mInitialActorPosition); - npcPos[0] = npcPos[0] - cellXOffset; - npcPos[1] = npcPos[1] - cellYOffset; - // mAllowedNodes for this actor with pathgrid point indexes based on mDistance // NOTE: mPoints and mAllowedNodes are in local co-ordinates for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) From 0095737c407e9513cea0b294338ce84807be8704 Mon Sep 17 00:00:00 2001 From: dteviot Date: Sun, 5 Jul 2015 18:09:14 +1200 Subject: [PATCH 3/8] Use correct type of variable. Remove unnecessary casts. --- apps/openmw/mwmechanics/pathgrid.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index 4e9bc89048..66af864ea1 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -313,27 +313,27 @@ namespace MWMechanics return path; // for some reason couldn't build a path // reconstruct path to return, using world co-ordinates - float xCell = 0; - float yCell = 0; + int xCell = 0; + int yCell = 0; if (mIsExterior) { - xCell = static_cast(mPathgrid->mData.mX * ESM::Land::REAL_SIZE); - yCell = static_cast(mPathgrid->mData.mY * ESM::Land::REAL_SIZE); + xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE; + yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE; } while(graphParent[current] != -1) { ESM::Pathgrid::Point pt = mPathgrid->mPoints[current]; - pt.mX += static_cast(xCell); - pt.mY += static_cast(yCell); + pt.mX += xCell; + pt.mY += yCell; path.push_front(pt); current = graphParent[current]; } // add first node to path explicitly ESM::Pathgrid::Point pt = mPathgrid->mPoints[start]; - pt.mX += static_cast(xCell); - pt.mY += static_cast(yCell); + pt.mX += xCell; + pt.mY += yCell; path.push_front(pt); return path; } From 76f20b8b2024ca59ba0349f9c9988c4abafa81fb Mon Sep 17 00:00:00 2001 From: dteviot Date: Sun, 5 Jul 2015 18:10:02 +1200 Subject: [PATCH 4/8] fix 'WIN32_LEAN_AND_MEAN' macro redefinition warning. --- apps/openmw/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index dc58daed9a..85a0dbe55d 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -14,7 +14,9 @@ #if defined(_WIN32) // For OutputDebugString +#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN +#endif #include // makes __argc and __argv available on windows #include From eb2aa965b9fece4dbae0b414e0a737a8ca6e43f5 Mon Sep 17 00:00:00 2001 From: dteviot Date: Sun, 5 Jul 2015 18:17:18 +1200 Subject: [PATCH 5/8] Extracted function SetCurrentNodeToClosestAllowedNode() --- apps/openmw/mwmechanics/aiwander.cpp | 33 ++++++++++++++++++---------- apps/openmw/mwmechanics/aiwander.hpp | 2 ++ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 1567f48f6f..6f2c6969c3 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -1,5 +1,7 @@ #include "aiwander.hpp" +#include + #include #include @@ -700,23 +702,30 @@ namespace MWMechanics } if(!mAllowedNodes.empty()) { - osg::Vec3f firstNodePos(PathFinder::MakeOsgVec3(mAllowedNodes[0])); - float closestNode = (npcPos - firstNodePos).length2(); - unsigned int index = 0; - for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++) - { - osg::Vec3f nodePos(PathFinder::MakeOsgVec3(mAllowedNodes[counterThree])); - float tempDist = (npcPos - nodePos).length2(); - if(tempDist < closestNode) - index = counterThree; - } - mCurrentNode = mAllowedNodes[index]; - mAllowedNodes.erase(mAllowedNodes.begin() + index); + SetCurrentNodeToClosestAllowedNode(npcPos); } mStoredAvailableNodes = true; // set only if successful in finding allowed nodes } } + void AiWander::SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos) + { + float distanceToClosestNode = FLT_MAX; + unsigned int index = 0; + for (unsigned int counterThree = 0; counterThree < mAllowedNodes.size(); counterThree++) + { + osg::Vec3f nodePos(PathFinder::MakeOsgVec3(mAllowedNodes[counterThree])); + float tempDist = (npcPos - nodePos).length2(); + if (tempDist < distanceToClosestNode) + { + index = counterThree; + distanceToClosestNode = tempDist; + } + } + mCurrentNode = mAllowedNodes[index]; + mAllowedNodes.erase(mAllowedNodes.begin() + index); + } + void AiWander::writeState(ESM::AiSequence::AiSequence &sequence) const { std::auto_ptr wander(new ESM::AiSequence::AiWander()); diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index d69a72585f..94758afc8d 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -121,6 +121,8 @@ namespace MWMechanics /// convert point from local (i.e. cell) to world co-ordinates void ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell); + void SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos); + /// lookup table for converting idleSelect value to groupName static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; }; From 1239667cb415835d2d4cb6f3aba941c92e4fecaa Mon Sep 17 00:00:00 2001 From: dteviot Date: Sun, 5 Jul 2015 18:21:35 +1200 Subject: [PATCH 6/8] AiWander uses points between PathGrid points (Fixes #1317) When there is only on PathGrid point within a NPC's wander distance, expand possible wander destinations by using positions between PathGrid points. --- apps/openmw/mwmechanics/aiwander.cpp | 44 ++++++++++++++++++++++++- apps/openmw/mwmechanics/aiwander.hpp | 4 +++ apps/openmw/mwmechanics/pathfinding.hpp | 4 ++- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 6f2c6969c3..25ed4694b9 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -216,7 +216,7 @@ namespace MWMechanics // Are we there yet? bool& chooseAction = storage.mChooseAction; if(walking && - storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], 64.f)) + storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) { stopWalking(actor, storage); moveNow = false; @@ -694,11 +694,19 @@ namespace MWMechanics // mAllowedNodes for this actor with pathgrid point indexes based on mDistance // NOTE: mPoints and mAllowedNodes are in local co-ordinates + int pointIndex = 0; for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) { osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter])); if((npcPos - nodePos).length2() <= mDistance * mDistance) + { mAllowedNodes.push_back(pathgrid->mPoints[counter]); + pointIndex = counter; + } + } + if (mAllowedNodes.size() == 1) + { + AddNonPathGridAllowedPoints(npcPos, pathgrid, pointIndex); } if(!mAllowedNodes.empty()) { @@ -708,6 +716,40 @@ namespace MWMechanics } } + // When only one path grid point in wander distance, + // additional points for NPC to wander to are: + // 1. NPC's initial location + // 2. Partway along the path between the point and its connected points. + void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex) + { + mAllowedNodes.push_back(PathFinder::MakePathgridPoint(npcPos)); + for (std::vector::const_iterator it = pathGrid->mEdges.begin(); it != pathGrid->mEdges.end(); ++it) + { + if (it->mV0 == pointIndex) + { + AddPointBetweenPathGridPoints(pathGrid->mPoints[it->mV0], pathGrid->mPoints[it->mV1]); + } + } + } + + void AiWander::AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end) + { + osg::Vec3f vectorStart = PathFinder::MakeOsgVec3(start); + osg::Vec3f delta = PathFinder::MakeOsgVec3(end) - vectorStart; + float length = delta.length(); + delta.normalize(); + + // destination must be far enough away that NPC will need to move to get there. + const int threshold = PathFinder::PathTolerance * 2; + int distance = std::max(mDistance / 2, threshold); + + // must not travel more than 1/2 way between waypoints, + // otherwise, NPC goes to far endpoint then comes back. Looks weird. + distance = std::min(distance, static_cast(length / 2)); + delta *= distance; + mAllowedNodes.push_back(PathFinder::MakePathgridPoint(vectorStart + delta)); + } + void AiWander::SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos) { float distanceToClosestNode = FLT_MAX; diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 94758afc8d..18f98cfd51 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -123,6 +123,10 @@ namespace MWMechanics void SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos); + void AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex); + + void AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end); + /// lookup table for converting idleSelect value to groupName static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; }; diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 45d9dd9731..a4886a8400 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -19,6 +19,8 @@ namespace MWMechanics public: PathFinder(); + static const int PathTolerance = 32; + static float sgn(float val) { if(val > 0) @@ -35,7 +37,7 @@ namespace MWMechanics void clearPath(); - bool checkPathCompleted(float x, float y, float tolerance=32.f); + bool checkPathCompleted(float x, float y, float tolerance = PathTolerance); ///< \Returns true if we are within \a tolerance units of the last path point. /// In degrees From 52cf8541f5d02677a893bfda0542267df1249568 Mon Sep 17 00:00:00 2001 From: dteviot Date: Wed, 8 Jul 2015 18:41:03 +1200 Subject: [PATCH 7/8] End point tolerance restored to 64 units. Corrected problem pointed out by Scrawl. Destination needs tolerance of 64 to avoid overcrowding. --- apps/openmw/mwmechanics/aiwander.cpp | 21 +++++++++++---------- apps/openmw/mwmechanics/aiwander.hpp | 8 ++++++++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 25ed4694b9..31687edf2a 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -216,7 +216,7 @@ namespace MWMechanics // Are we there yet? bool& chooseAction = storage.mChooseAction; if(walking && - storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) + storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DestinationTolerance)) { stopWalking(actor, storage); moveNow = false; @@ -645,9 +645,8 @@ namespace MWMechanics int index = Misc::Rng::rollDice(mAllowedNodes.size()); ESM::Pathgrid::Point dest = mAllowedNodes[index]; - // apply a slight offset to prevent overcrowding - dest.mX += static_cast(Misc::Rng::rollProbability() * 128 - 64); - dest.mY += static_cast(Misc::Rng::rollProbability() * 128 - 64); + dest.mX += OffsetToPreventOvercrowding(); + dest.mY += OffsetToPreventOvercrowding(); ToWorldCoordinates(dest, actor.getCell()->getCell()); MWBase::Environment::get().getWorld()->moveObject(actor, static_cast(dest.mX), @@ -658,6 +657,11 @@ namespace MWMechanics mStoredAvailableNodes = false; } + int AiWander::OffsetToPreventOvercrowding() + { + return static_cast(DestinationTolerance * (Misc::Rng::rollProbability() * 2.0f - 1.0f)); + } + void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell) { if (!mStoredInitialActorPosition) @@ -739,13 +743,10 @@ namespace MWMechanics float length = delta.length(); delta.normalize(); - // destination must be far enough away that NPC will need to move to get there. - const int threshold = PathFinder::PathTolerance * 2; - int distance = std::max(mDistance / 2, threshold); + int distance = std::max(mDistance / 2, MinimumWanderDistance); - // must not travel more than 1/2 way between waypoints, - // otherwise, NPC goes to far endpoint then comes back. Looks weird. - distance = std::min(distance, static_cast(length / 2)); + // must not travel longer than distance between waypoints or NPC goes past waypoint + distance = std::min(distance, static_cast(length)); delta *= distance; mAllowedNodes.push_back(PathFinder::MakePathgridPoint(vectorStart + delta)); } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 18f98cfd51..c15ec7c3cd 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -118,6 +118,12 @@ namespace MWMechanics GroupIndex_MaxIdle = 9 }; + // to prevent overcrowding + static const int DestinationTolerance = 64; + + // distance must be long enough that NPC will need to move to get there. + static const int MinimumWanderDistance = DestinationTolerance * 2; + /// convert point from local (i.e. cell) to world co-ordinates void ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell); @@ -129,6 +135,8 @@ namespace MWMechanics /// lookup table for converting idleSelect value to groupName static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; + + static int OffsetToPreventOvercrowding(); }; From f1774ee7c37b89ad03508fa040b482d0662aee6b Mon Sep 17 00:00:00 2001 From: dteviot Date: Wed, 8 Jul 2015 19:34:33 +1200 Subject: [PATCH 8/8] Fixed compile failing on OSX and Linux. --- apps/openmw/mwmechanics/aiwander.cpp | 12 +++++++++--- apps/openmw/mwmechanics/aiwander.hpp | 6 ------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 31687edf2a..4aef8f8ba4 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -30,6 +30,12 @@ namespace MWMechanics static const int GREETING_SHOULD_START = 4; //how many reaction intervals should pass before NPC can greet player static const int GREETING_SHOULD_END = 10; + // to prevent overcrowding + static const int DESTINATION_TOLERANCE = 64; + + // distance must be long enough that NPC will need to move to get there. + static const int MINIMUM_WANDER_DISTANCE = DESTINATION_TOLERANCE * 2; + const std::string AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = { std::string("idle2"), @@ -216,7 +222,7 @@ namespace MWMechanics // Are we there yet? bool& chooseAction = storage.mChooseAction; if(walking && - storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DestinationTolerance)) + storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) { stopWalking(actor, storage); moveNow = false; @@ -659,7 +665,7 @@ namespace MWMechanics int AiWander::OffsetToPreventOvercrowding() { - return static_cast(DestinationTolerance * (Misc::Rng::rollProbability() * 2.0f - 1.0f)); + return static_cast(DESTINATION_TOLERANCE * (Misc::Rng::rollProbability() * 2.0f - 1.0f)); } void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell) @@ -743,7 +749,7 @@ namespace MWMechanics float length = delta.length(); delta.normalize(); - int distance = std::max(mDistance / 2, MinimumWanderDistance); + int distance = std::max(mDistance / 2, MINIMUM_WANDER_DISTANCE); // must not travel longer than distance between waypoints or NPC goes past waypoint distance = std::min(distance, static_cast(length)); diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index c15ec7c3cd..926017b46a 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -118,12 +118,6 @@ namespace MWMechanics GroupIndex_MaxIdle = 9 }; - // to prevent overcrowding - static const int DestinationTolerance = 64; - - // distance must be long enough that NPC will need to move to get there. - static const int MinimumWanderDistance = DestinationTolerance * 2; - /// convert point from local (i.e. cell) to world co-ordinates void ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell);