From 0cfdf0c7b6cf4a5997d3911491152bf176b93c4e Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 12:52:17 +0300 Subject: [PATCH 001/133] Remove unused virtual --- apps/openmw/mwmechanics/aipackage.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 5a468ae30c..7b33ea8712 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -121,7 +121,7 @@ namespace MWMechanics /// Check if the way to the destination is clear, taking into account actor speed bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor); - virtual bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell); + bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell); void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); void openDoors(const MWWorld::Ptr& actor); From 33dfe284bd40066588f6ca1f49dbce7ca17e35b6 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 15 Mar 2018 00:42:12 +0300 Subject: [PATCH 002/133] Mark local variables const which one does not change --- apps/openmw/mwmechanics/aipackage.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 9a42f191ef..cdc2288e92 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -25,7 +25,7 @@ MWMechanics::AiPackage::~AiPackage() {} -MWMechanics::AiPackage::AiPackage() : +MWMechanics::AiPackage::AiPackage() : mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild mTargetActorRefId(""), mTargetActorId(-1), @@ -100,7 +100,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr { mTimer += duration; //Update timer - ESM::Position pos = actor.getRefData().getPosition(); //position of the actor + const ESM::Position pos = actor.getRefData().getPosition(); //position of the actor /// Stops the actor when it gets too close to a unloaded cell //... At current time, this test is unnecessary. AI shuts down when actor is more than 7168 @@ -113,17 +113,17 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr } // handle path building and shortcutting - ESM::Pathgrid::Point start = pos.pos; + const ESM::Pathgrid::Point start = pos.pos; - float distToTarget = distance(start, dest); - bool isDestReached = (distToTarget <= destTolerance); + const float distToTarget = distance(start, dest); + const bool isDestReached = (distToTarget <= destTolerance); if (!isDestReached && mTimer > AI_REACTION_TIME) { if (actor.getClass().isBipedal(actor)) openDoors(actor); - bool wasShortcutting = mIsShortcutting; + const bool wasShortcutting = mIsShortcutting; bool destInLOS = false; const MWWorld::Class& actorClass = actor.getClass(); @@ -381,7 +381,7 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act osg::Vec3f radiusDir = dir ^ osg::Z_AXIS; // radius is perpendicular to a tangent radiusDir.normalize(); radiusDir *= radius; - + // pick up the nearest center candidate osg::Vec3f dest_ = PathFinder::MakeOsgVec3(dest); osg::Vec3f pos = actor.getRefData().getPosition().asVec3(); From 4d868bec92fb499079f9ce14ddd9267774df0ae5 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 21 Jul 2018 12:30:14 +0300 Subject: [PATCH 003/133] Use osg::Vec3f to store path nodes in Pathfinder --- apps/openmw/mwmechanics/aiactivate.cpp | 2 +- apps/openmw/mwmechanics/aicast.cpp | 2 +- apps/openmw/mwmechanics/aicombat.cpp | 4 +- apps/openmw/mwmechanics/aiescort.cpp | 2 +- apps/openmw/mwmechanics/aifollow.cpp | 4 +- apps/openmw/mwmechanics/aipackage.cpp | 50 ++++----- apps/openmw/mwmechanics/aipackage.hpp | 13 +-- apps/openmw/mwmechanics/aipursue.cpp | 4 +- apps/openmw/mwmechanics/aitravel.cpp | 5 +- apps/openmw/mwmechanics/aiwander.cpp | 33 +++--- .../mwmechanics/coordinateconverter.cpp | 9 +- .../mwmechanics/coordinateconverter.hpp | 2 +- apps/openmw/mwmechanics/pathfinding.cpp | 102 +++++------------- apps/openmw/mwmechanics/pathfinding.hpp | 43 +++++--- 14 files changed, 126 insertions(+), 149 deletions(-) diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index d38a1299fd..f1d42ea1c4 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -36,7 +36,7 @@ namespace MWMechanics return true; //Target doesn't exist //Set the target destination for the actor - ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; + const auto dest = target.getRefData().getPosition().asVec3(); if (pathTo(actor, dest, duration, MWBase::Environment::get().getWorld()->getMaxActivationDistance())) //Stop when you get in activation range { diff --git a/apps/openmw/mwmechanics/aicast.cpp b/apps/openmw/mwmechanics/aicast.cpp index 948ffb3aac..38dc2fa732 100644 --- a/apps/openmw/mwmechanics/aicast.cpp +++ b/apps/openmw/mwmechanics/aicast.cpp @@ -37,7 +37,7 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac if (!target) return true; - if (!mManual && !pathTo(actor, target.getRefData().getPosition().pos, duration, mDistance)) + if (!mManual && !pathTo(actor, target.getRefData().getPosition().asVec3(), duration, mDistance)) { return false; } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 8f9545f99d..4cb36448dd 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -124,7 +124,7 @@ namespace MWMechanics float targetReachedTolerance = 0.0f; if (storage.mLOS) targetReachedTolerance = storage.mAttackRange; - bool is_target_reached = pathTo(actor, target.getRefData().getPosition().pos, duration, targetReachedTolerance); + const bool is_target_reached = pathTo(actor, target.getRefData().getPosition().asVec3(), duration, targetReachedTolerance); if (is_target_reached) storage.mReadyToAttack = true; } @@ -355,7 +355,7 @@ namespace MWMechanics float dist = (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length(); if ((dist > fFleeDistance && !storage.mLOS) - || pathTo(actor, storage.mFleeDest, duration)) + || pathTo(actor, PathFinder::MakeOsgVec3(storage.mFleeDest), duration)) { state = AiCombatStorage::FleeState_Idle; } diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index a86d13d752..59c840a49a 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -100,7 +100,7 @@ namespace MWMechanics point.mAutogenerated = 0; point.mConnectionNum = 0; point.mUnknown = 0; - if (pathTo(actor,point,duration)) //Returns true on path complete + if (pathTo(actor, osg::Vec3f(mX, mY, mZ), duration)) //Returns true on path complete { mRemainingDuration = mDuration; return true; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index a8f0713101..2bd6850354 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -163,7 +163,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte } //Set the target destination from the actor - ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; + const auto dest = target.getRefData().getPosition().asVec3(); short baseFollowDistance = followDistance; short threshold = 30; // to avoid constant switching between moving/stopping @@ -196,7 +196,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte if (storage.mMoving) { //Check if you're far away - float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]); + float dist = distance(dest, pos.asVec3()); if (dist > 450) actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index cdc2288e92..c483ef0425 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -90,13 +90,13 @@ void MWMechanics::AiPackage::reset() mTimer = AI_REACTION_TIME + 1.0f; mIsShortcutting = false; mShortcutProhibited = false; - mShortcutFailPos = ESM::Pathgrid::Point(); + mShortcutFailPos = osg::Vec3f(); mPathFinder.clearPath(); mObstacleCheck.clear(); } -bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance) +bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance) { mTimer += duration; //Update timer @@ -113,7 +113,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr } // handle path building and shortcutting - const ESM::Pathgrid::Point start = pos.pos; + const osg::Vec3f start = pos.asVec3(); const float distToTarget = distance(start, dest); const bool isDestReached = (distToTarget <= destTolerance); @@ -148,7 +148,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr if (destInLOS && mPathFinder.getPath().size() > 1) { // get point just before dest - std::list::const_iterator pPointBeforeDest = mPathFinder.getPath().end(); + auto pPointBeforeDest = mPathFinder.getPath().end(); --pPointBeforeDest; --pPointBeforeDest; @@ -163,7 +163,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr if (!mPathFinder.getPath().empty()) //Path has points in it { - ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path + const auto& lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path if(distance(dest, lastPos) > 100) //End of the path is far from the destination mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go @@ -266,14 +266,15 @@ const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const return *cache[id].get(); } -bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear) +bool MWMechanics::AiPackage::shortcutPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear) { - if (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST) + if (!mShortcutProhibited || (mShortcutFailPos - startPoint).length() >= PATHFIND_SHORTCUT_RETRY_DIST) { // check if target is clearly visible isPathClear = !MWBase::Environment::get().getWorld()->castRay( - static_cast(startPoint.mX), static_cast(startPoint.mY), static_cast(startPoint.mZ), - static_cast(endPoint.mX), static_cast(endPoint.mY), static_cast(endPoint.mZ)); + startPoint.x(), startPoint.y(), startPoint.z(), + endPoint.x(), endPoint.y(), endPoint.z()); if (destInLOS != nullptr) *destInLOS = isPathClear; @@ -294,21 +295,21 @@ bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint return false; } -bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor) +bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor) { - bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + const bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) || MWBase::Environment::get().getWorld()->isFlying(actor); if (actorCanMoveByZ) return true; - float actorSpeed = actor.getClass().getSpeed(actor); - float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability - osg::Vec3f::value_type distToTarget = osg::Vec3f(static_cast(endPoint.mX), static_cast(endPoint.mY), 0).length(); + const float actorSpeed = actor.getClass().getSpeed(actor); + const float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability + const float distToTarget = osg::Vec2f(endPoint.x(), endPoint.y()).length(); - float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2; + const float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2; - bool isClear = checkWayIsClear(PathFinder::MakeOsgVec3(startPoint), PathFinder::MakeOsgVec3(endPoint), offsetXY); + const bool isClear = checkWayIsClear(startPoint, endPoint, offsetXY); // update shortcut prohibit state if (isClear) @@ -316,12 +317,12 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& if (mShortcutProhibited) { mShortcutProhibited = false; - mShortcutFailPos = ESM::Pathgrid::Point(); + mShortcutFailPos = osg::Vec3f(); } } if (!isClear) { - if (mShortcutFailPos.mX == 0 && mShortcutFailPos.mY == 0 && mShortcutFailPos.mZ == 0) + if (mShortcutFailPos == osg::Vec3f()) { mShortcutProhibited = true; mShortcutFailPos = startPoint; @@ -331,9 +332,11 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& return isClear; } -bool MWMechanics::AiPackage::doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell) +bool MWMechanics::AiPackage::doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::CellStore* currentCell) { - return mPathFinder.getPath().empty() || (distance(mPathFinder.getPath().back(), newDest) > 10) || mPathFinder.getPathCell() != currentCell; + return mPathFinder.getPath().empty() + || (distance(mPathFinder.getPath().back(), newDest) > 10) + || mPathFinder.getPathCell() != currentCell; } bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target) @@ -367,7 +370,7 @@ bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos) } } -bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest) +bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest) { // get actor's shortest radius for moving in circle float speed = actor.getClass().getSpeed(actor); @@ -383,13 +386,12 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act radiusDir *= radius; // pick up the nearest center candidate - osg::Vec3f dest_ = PathFinder::MakeOsgVec3(dest); osg::Vec3f pos = actor.getRefData().getPosition().asVec3(); osg::Vec3f center1 = pos - radiusDir; osg::Vec3f center2 = pos + radiusDir; - osg::Vec3f center = (center1 - dest_).length2() < (center2 - dest_).length2() ? center1 : center2; + osg::Vec3f center = (center1 - dest).length2() < (center2 - dest).length2() ? center1 : center2; - float distToDest = (center - dest_).length(); + float distToDest = (center - dest).length(); // if pathpoint is reachable for the actor rotating on the run: // no points of actor's circle should be farther from the center than destination point diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 7b33ea8712..74c05f7cdd 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -105,23 +105,24 @@ namespace MWMechanics bool isTargetMagicallyHidden(const MWWorld::Ptr& target); /// Return if actor's rotation speed is sufficient to rotate to the destination pathpoint on the run. Otherwise actor should rotate while standing. - static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest); + static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest); protected: /// Handles path building and shortcutting with obstacles avoiding /** \return If the actor has arrived at his destination **/ - bool pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance = 0.0f); + bool pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance = 0.0f); /// Check if there aren't any obstacles along the path to make shortcut possible /// If a shortcut is possible then path will be cleared and filled with the destination point. /// \param destInLOS If not nullptr function will return ray cast check result /// \return If can shortcut the path - bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear); + bool shortcutPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor, + bool *destInLOS, bool isPathClear); /// Check if the way to the destination is clear, taking into account actor speed - bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor); + bool checkWayIsClearForActor(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor); - bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell); + bool doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::CellStore* currentCell); void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); void openDoors(const MWWorld::Ptr& actor); @@ -143,7 +144,7 @@ namespace MWMechanics bool mIsShortcutting; // if shortcutting at the moment bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt - ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail + osg::Vec3f mShortcutFailPos; // position of last shortcut fail private: bool isNearInactiveCell(const ESM::Position& actorPos); diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index f465946552..f746df3ffe 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -49,13 +49,13 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); //Set the target desition from the actor - ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; + const auto dest = target.getRefData().getPosition().asVec3(); ESM::Position aPos = actor.getRefData().getPosition(); float pathTolerance = 100.0; if (pathTo(actor, dest, duration, pathTolerance) && - std::abs(dest.mZ - aPos.pos[2]) < pathTolerance) // check the true distance in case the target is far away in Z-direction + std::abs(dest.z() - aPos.pos[2]) < pathTolerance) // check the true distance in case the target is far away in Z-direction { target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached return true; diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 8b52b15a4b..5c03d9fc11 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -56,8 +56,7 @@ namespace MWMechanics // Unfortunately, with vanilla assets destination is sometimes blocked by other actor. // If we got close to target, check for actors nearby. If they are, finish AI package. int destinationTolerance = 64; - ESM::Pathgrid::Point dest(static_cast(mX), static_cast(mY), static_cast(mZ)); - if (distance(pos.pos, dest) <= destinationTolerance) + if (distance(pos.asVec3(), osg::Vec3f(mX, mY, mZ)) <= destinationTolerance) { std::vector targetActors; std::pair result = MWBase::Environment::get().getWorld()->getHitContact(actor, destinationTolerance, targetActors); @@ -69,7 +68,7 @@ namespace MWMechanics } } - if (pathTo(actor, dest, duration)) + if (pathTo(actor, osg::Vec3f(mX, mY, mZ), duration)) { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; return true; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index caacf8cb64..0c1c1ba320 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -151,10 +151,8 @@ namespace MWMechanics // rebuild a path to it if (!mPathFinder.isPathConstructed() && mHasDestination) { - ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination)); - ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); - - mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); + mPathFinder.buildSyncedPath(pos.asVec3(), mDestination, actor.getCell(), + getPathGridGraph(actor.getCell())); if (mPathFinder.isPathConstructed()) storage.setState(AiWanderStorage::Wander_Walking); @@ -292,11 +290,9 @@ namespace MWMechanics * Commands actor to walk to a random location near original spawn location. */ void AiWander::wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance) { - const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos; - const osg::Vec3f currentPositionVec3f = osg::Vec3f(currentPosition.mX, currentPosition.mY, currentPosition.mZ); + const auto currentPosition = actor.getRefData().getPosition().asVec3(); std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here - ESM::Pathgrid::Point destinationPosition; bool isWaterCreature = actor.getClass().isPureWaterCreature(actor); do { // Determine a random location within radius of original position @@ -305,13 +301,15 @@ namespace MWMechanics const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection); const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection); const float destinationZ = mInitialActorPosition.z(); - destinationPosition = ESM::Pathgrid::Point(destinationX, destinationY, destinationZ); + const osg::Vec3f destinationPosition(destinationX, destinationY, destinationZ); mDestination = osg::Vec3f(destinationX, destinationY, destinationZ); // Check if land creature will walk onto water or if water creature will swim onto land if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) || - (isWaterCreature && !destinationThroughGround(currentPositionVec3f, mDestination))) { - mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), getPathGridGraph(actor.getCell())); + (isWaterCreature && !destinationThroughGround(currentPosition, mDestination))) + { + mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), + getPathGridGraph(actor.getCell())); mPathFinder.addPointToPath(destinationPosition); if (mPathFinder.isPathConstructed()) @@ -417,7 +415,7 @@ namespace MWMechanics float duration, AiWanderStorage& storage, ESM::Position& pos) { // Is there no destination or are we there yet? - if ((!mPathFinder.isPathConstructed()) || pathTo(actor, ESM::Pathgrid::Point(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE)) + if ((!mPathFinder.isPathConstructed()) || pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE)) { stopWalking(actor, storage); storage.setState(AiWanderStorage::Wander_ChooseAction); @@ -592,14 +590,15 @@ namespace MWMechanics ToWorldCoordinates(dest, storage.mCell->getCell()); // actor position is already in world coordinates - ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos)); + const auto start = actorPos.asVec3(); // don't take shortcuts for wandering - mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); + const auto destVec3f = PathFinder::MakeOsgVec3(dest); + mPathFinder.buildSyncedPath(start, destVec3f, actor.getCell(), getPathGridGraph(actor.getCell())); if (mPathFinder.isPathConstructed()) { - mDestination = osg::Vec3f(dest.mX, dest.mY, dest.mZ); + mDestination = destVec3f; mHasDestination = 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]; @@ -631,15 +630,15 @@ namespace MWMechanics // 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. - std::list paths = pathfinder.getPath(); + auto paths = pathfinder.getPath(); while(paths.size() >= 2) { - ESM::Pathgrid::Point pt = paths.back(); + const auto pt = paths.back(); for(unsigned int j = 0; j < nodes.size(); j++) { // FIXME: doesn't handle a door with the same X/Y // coordinates but with a different Z - if(nodes[j].mX == pt.mX && nodes[j].mY == pt.mY) + if (std::abs(nodes[j].mX - pt.x()) <= 0.5 && std::abs(nodes[j].mY - pt.y()) <= 0.5) { nodes.erase(nodes.begin() + j); break; diff --git a/apps/openmw/mwmechanics/coordinateconverter.cpp b/apps/openmw/mwmechanics/coordinateconverter.cpp index 0a2d99f924..04155ea493 100644 --- a/apps/openmw/mwmechanics/coordinateconverter.cpp +++ b/apps/openmw/mwmechanics/coordinateconverter.cpp @@ -33,11 +33,12 @@ namespace MWMechanics point.y() -= static_cast(mCellY); } - osg::Vec3f CoordinateConverter::toLocalVec3(const ESM::Pathgrid::Point& point) + osg::Vec3f CoordinateConverter::toLocalVec3(const osg::Vec3f& point) { return osg::Vec3f( - static_cast(point.mX - mCellX), - static_cast(point.mY - mCellY), - static_cast(point.mZ)); + point.x() - static_cast(mCellX), + point.y() - static_cast(mCellY), + point.z() + ); } } diff --git a/apps/openmw/mwmechanics/coordinateconverter.hpp b/apps/openmw/mwmechanics/coordinateconverter.hpp index cd855e84a2..f7dda33cb1 100644 --- a/apps/openmw/mwmechanics/coordinateconverter.hpp +++ b/apps/openmw/mwmechanics/coordinateconverter.hpp @@ -26,7 +26,7 @@ namespace MWMechanics /// in-place conversion from world to local void toLocal(osg::Vec3f& point); - osg::Vec3f toLocalVec3(const ESM::Pathgrid::Point& point); + osg::Vec3f toLocalVec3(const osg::Vec3f& point); private: int mCellX; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 1da97a6459..38ddec4dc8 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -55,56 +55,16 @@ namespace (closestReachableIndex, closestReachableIndex == closestIndex); } -} - -namespace MWMechanics -{ - float sqrDistanceIgnoreZ(const ESM::Pathgrid::Point& point, float x, float y) + float sqrDistanceIgnoreZ(const osg::Vec3f& point, float x, float y) { - x -= point.mX; - y -= point.mY; + x -= point.x(); + y -= point.y(); return (x * x + y * y); } +} - float distance(const ESM::Pathgrid::Point& point, float x, float y, float z) - { - x -= point.mX; - y -= point.mY; - z -= point.mZ; - return sqrt(x * x + y * y + z * z); - } - - float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b) - { - float x = static_cast(a.mX - b.mX); - float y = static_cast(a.mY - b.mY); - float z = static_cast(a.mZ - b.mZ); - return sqrt(x * x + y * y + z * z); - } - - float getZAngleToDir(const osg::Vec3f& dir) - { - return std::atan2(dir.x(), dir.y()); - } - - float getXAngleToDir(const osg::Vec3f& dir) - { - float dirLen = dir.length(); - return (dirLen != 0) ? -std::asin(dir.z() / dirLen) : 0; - } - - float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest) - { - osg::Vec3f dir = PathFinder::MakeOsgVec3(dest) - PathFinder::MakeOsgVec3(origin); - return getZAngleToDir(dir); - } - - float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest) - { - osg::Vec3f dir = PathFinder::MakeOsgVec3(dest) - PathFinder::MakeOsgVec3(origin); - return getXAngleToDir(dir); - } - +namespace MWMechanics +{ bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY) { osg::Vec3f dir = to - from; @@ -167,8 +127,7 @@ namespace MWMechanics * j = @.x in local coordinates (i.e. within the cell) * k = @.x in world coordinates */ - void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, - const ESM::Pathgrid::Point &endPoint, + void PathFinder::buildPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph) { mPath.clear(); @@ -225,17 +184,17 @@ namespace MWMechanics { ESM::Pathgrid::Point temp(mPathgrid->mPoints[startNode]); converter.toWorld(temp); - mPath.push_back(temp); + mPath.push_back(MakeOsgVec3(temp)); } else { - mPath = pathgridGraph.aStarSearch(startNode, endNode.first); + auto path = pathgridGraph.aStarSearch(startNode, endNode.first); // If nearest path node is in opposite direction from second, remove it from path. // Especially useful for wandering actors, if the nearest node is blocked for some reason. - if (mPath.size() > 1) + if (path.size() > 1) { - ESM::Pathgrid::Point secondNode = *(++mPath.begin()); + ESM::Pathgrid::Point secondNode = *(++path.begin()); osg::Vec3f firstNodeVec3f = MakeOsgVec3(mPathgrid->mPoints[startNode]); osg::Vec3f secondNodeVec3f = MakeOsgVec3(secondNode); osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f; @@ -247,21 +206,23 @@ namespace MWMechanics // Add Z offset since path node can overlap with other objects. // Also ignore doors in raytesting. bool isPathClear = !MWBase::Environment::get().getWorld()->castRay( - startPoint.mX, startPoint.mY, startPoint.mZ+16, temp.mX, temp.mY, temp.mZ+16, true); + startPoint.x(), startPoint.y(), startPoint.z() + 16, temp.mX, temp.mY, temp.mZ + 16, true); if (isPathClear) - mPath.pop_front(); + path.pop_front(); } } // convert supplied path to world coordinates - for (std::list::iterator iter(mPath.begin()); iter != mPath.end(); ++iter) - { - converter.toWorld(*iter); - } + std::transform(path.begin(), path.end(), std::back_inserter(mPath), + [&] (ESM::Pathgrid::Point& point) + { + converter.toWorld(point); + return MakeOsgVec3(point); + }); } // If endNode found is NOT the closest PathGrid point to the endPoint, - // assume endPoint is not reachable from endNode. In which case, + // assume endPoint is not reachable from endNode. In which case, // path ends at endNode. // // So only add the destination (which may be different to the closest @@ -283,9 +244,9 @@ namespace MWMechanics if(mPath.empty()) return 0.; - const ESM::Pathgrid::Point &nextPoint = *mPath.begin(); - float directionX = nextPoint.mX - x; - float directionY = nextPoint.mY - y; + const auto& nextPoint = mPath.front(); + const float directionX = nextPoint.x() - x; + const float directionY = nextPoint.y() - y; return std::atan2(directionX, directionY); } @@ -297,8 +258,7 @@ namespace MWMechanics if(mPath.empty()) return 0.; - const ESM::Pathgrid::Point &nextPoint = *mPath.begin(); - osg::Vec3f dir = MakeOsgVec3(nextPoint) - osg::Vec3f(x,y,z); + const osg::Vec3f dir = mPath.front() - osg::Vec3f(x, y, z); return getXAngleToDir(dir); } @@ -308,8 +268,7 @@ namespace MWMechanics if(mPath.empty()) return true; - const ESM::Pathgrid::Point& nextPoint = *mPath.begin(); - if (sqrDistanceIgnoreZ(nextPoint, x, y) < tolerance*tolerance) + if (sqrDistanceIgnoreZ(mPath.front(), x, y) < tolerance*tolerance) { mPath.pop_front(); if(mPath.empty()) @@ -322,8 +281,7 @@ namespace MWMechanics } // see header for the rationale - void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint, - const ESM::Pathgrid::Point &endPoint, + void PathFinder::buildSyncedPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const MWMechanics::PathgridGraph& pathgridGraph) { if (mPath.size() < 2) @@ -334,16 +292,14 @@ namespace MWMechanics } else { - const ESM::Pathgrid::Point oldStart(*getPath().begin()); + const auto oldStart = getPath().front(); buildPath(startPoint, endPoint, cell, pathgridGraph); if (mPath.size() >= 2) { // if 2nd waypoint of new path == 1st waypoint of old, // delete 1st waypoint of new path. - std::list::iterator iter = ++mPath.begin(); - if (iter->mX == oldStart.mX - && iter->mY == oldStart.mY - && iter->mZ == oldStart.mZ) + const auto iter = ++mPath.begin(); + if (*iter == oldStart) { mPath.pop_front(); } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index ebc22f10d4..4f403a32ce 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -16,12 +16,31 @@ namespace MWMechanics { class PathgridGraph; - float distance(const ESM::Pathgrid::Point& point, float x, float y, float); - float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b); - float getZAngleToDir(const osg::Vec3f& dir); - float getXAngleToDir(const osg::Vec3f& dir); - float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest); - float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest); + inline float distance(const osg::Vec3f& lhs, const osg::Vec3f& rhs) + { + return (lhs - rhs).length(); + } + + inline float getZAngleToDir(const osg::Vec3f& dir) + { + return std::atan2(dir.x(), dir.y()); + } + + inline float getXAngleToDir(const osg::Vec3f& dir) + { + float dirLen = dir.length(); + return (dirLen != 0) ? -std::asin(dir.z() / dirLen) : 0; + } + + inline float getZAngleToPoint(const osg::Vec3f& origin, const osg::Vec3f& dest) + { + return getZAngleToDir(dest - origin); + } + + inline float getXAngleToPoint(const osg::Vec3f& origin, const osg::Vec3f& dest) + { + return getXAngleToDir(dest - origin); + } const float PATHFIND_Z_REACH = 50.0f; //static const float sMaxSlope = 49.0f; // duplicate as in physicssystem @@ -55,7 +74,7 @@ namespace MWMechanics void clearPath(); - void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, + void buildPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); bool checkPathCompleted(float x, float y, float tolerance = PathTolerance); @@ -76,7 +95,7 @@ namespace MWMechanics return mPath.size(); } - const std::list& getPath() const + const std::list& getPath() const { return mPath; } @@ -90,10 +109,10 @@ namespace MWMechanics makes the 2nd point of the new path == the 1st point of old path. Which results in NPC "running in a circle" back to the just passed waypoint. */ - void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, + void buildSyncedPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); - void addPointToPath(const ESM::Pathgrid::Point &point) + void addPointToPath(const osg::Vec3f& point) { mPath.push_back(point); } @@ -114,7 +133,7 @@ namespace MWMechanics { return osg::Vec3f(static_cast(p.mX), static_cast(p.mY), static_cast(p.mZ)); } - + // Slightly cheaper version for comparisons. // Caller needs to be careful for very short distances (i.e. less than 1) // or when accumuating the results i.e. (a + b)^2 != a^2 + b^2 @@ -153,7 +172,7 @@ namespace MWMechanics } private: - std::list mPath; + std::list mPath; const ESM::Pathgrid *mPathgrid; const MWWorld::CellStore* mCell; From fbaa525c6f57055770e3545b7c98dfe8a8572c24 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 13:35:37 +0300 Subject: [PATCH 004/133] Fix warning --- apps/openmw/mwmechanics/pathfinding.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 4f403a32ce..9cb418b406 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -90,7 +90,7 @@ namespace MWMechanics return !mPath.empty(); } - int getPathSize() const + std::size_t getPathSize() const { return mPath.size(); } From 925d909fea2fd790f634960da6de496836317972 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 28 Jul 2018 21:21:37 +0300 Subject: [PATCH 005/133] Use rbegin --- apps/openmw/mwmechanics/aipackage.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index c483ef0425..a781eb1fff 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -148,9 +148,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& if (destInLOS && mPathFinder.getPath().size() > 1) { // get point just before dest - auto pPointBeforeDest = mPathFinder.getPath().end(); - --pPointBeforeDest; - --pPointBeforeDest; + auto pPointBeforeDest = mPathFinder.getPath().rbegin(); + ++pPointBeforeDest; // if start point is closer to the target then last point of path (excluding target itself) then go straight on the target if (distance(start, dest) <= distance(dest, *pPointBeforeDest)) From 9b3756f8bc3ab131ab4a627c13feee032930b97c Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 13:40:04 +0300 Subject: [PATCH 006/133] Store path points in deque --- apps/openmw/mwmechanics/aipackage.cpp | 3 +-- apps/openmw/mwmechanics/pathfinding.cpp | 3 +-- apps/openmw/mwmechanics/pathfinding.hpp | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index a781eb1fff..9bc3ed103e 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -148,8 +148,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& if (destInLOS && mPathFinder.getPath().size() > 1) { // get point just before dest - auto pPointBeforeDest = mPathFinder.getPath().rbegin(); - ++pPointBeforeDest; + auto pPointBeforeDest = mPathFinder.getPath().rbegin() + 1; // if start point is closer to the target then last point of path (excluding target itself) then go straight on the target if (distance(start, dest) <= distance(dest, *pPointBeforeDest)) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 38ddec4dc8..952c92ad57 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -298,8 +298,7 @@ namespace MWMechanics { // if 2nd waypoint of new path == 1st waypoint of old, // delete 1st waypoint of new path. - const auto iter = ++mPath.begin(); - if (*iter == oldStart) + if (mPath[1] == oldStart) { mPath.pop_front(); } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 9cb418b406..0d3b6bdff2 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWMECHANICS_PATHFINDING_H #define GAME_MWMECHANICS_PATHFINDING_H -#include +#include #include #include @@ -95,7 +95,7 @@ namespace MWMechanics return mPath.size(); } - const std::list& getPath() const + const std::deque& getPath() const { return mPath; } @@ -172,7 +172,7 @@ namespace MWMechanics } private: - std::list mPath; + std::deque mPath; const ESM::Pathgrid *mPathgrid; const MWWorld::CellStore* mCell; From 2f424f6be28e4c39f32b9968fa83710016c1340e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 19 Aug 2018 00:28:11 +0300 Subject: [PATCH 007/133] Store aStarSearch result to deque --- apps/openmw/mwmechanics/pathgrid.cpp | 5 ++--- apps/openmw/mwmechanics/pathgrid.hpp | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index 6b5db64eab..7bcdc8926c 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -257,10 +257,9 @@ namespace MWMechanics * pathgrid points form (currently they are converted to world * coordinates). Essentially trading speed w/ memory. */ - std::list PathgridGraph::aStarSearch(const int start, - const int goal) const + std::deque PathgridGraph::aStarSearch(const int start, const int goal) const { - std::list path; + std::deque path; if(!isPointConnected(start, goal)) { return path; // there is no path, return an empty path diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index 0c71c45617..6b67bb5073 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWMECHANICS_PATHGRID_H #define GAME_MWMECHANICS_PATHGRID_H -#include +#include #include @@ -38,8 +38,8 @@ namespace MWMechanics // cells) coordinates // // NOTE: if start equals end an empty path is returned - std::list aStarSearch(const int start, - const int end) const; + std::deque aStarSearch(const int start, const int end) const; + private: const ESM::Cell *mCell; From 31340a212ae0dc87c3c2531d97612c697ef35d8a Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 13:42:11 +0300 Subject: [PATCH 008/133] Fix functions name style --- apps/openmw/mwmechanics/aicombat.cpp | 4 ++-- apps/openmw/mwmechanics/aiwander.cpp | 26 ++++++++++++------------- apps/openmw/mwmechanics/pathfinding.cpp | 22 ++++++++++----------- apps/openmw/mwmechanics/pathfinding.hpp | 16 +++++++-------- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 4cb36448dd..301c5fe612 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -303,7 +303,7 @@ namespace MWMechanics osg::Vec3f localPos = actor.getRefData().getPosition().asVec3(); coords.toLocal(localPos); - int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, localPos); + int closestPointIndex = PathFinder::getClosestPoint(pathgrid, localPos); for (int i = 0; i < static_cast(pathgrid->mPoints.size()); i++) { if (i != closestPointIndex && getPathGridGraph(storage.mCell).isPointConnected(closestPointIndex, i)) @@ -355,7 +355,7 @@ namespace MWMechanics float dist = (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length(); if ((dist > fFleeDistance && !storage.mLOS) - || pathTo(actor, PathFinder::MakeOsgVec3(storage.mFleeDest), duration)) + || pathTo(actor, PathFinder::makeOsgVec3(storage.mFleeDest), duration)) { state = AiCombatStorage::FleeState_Idle; } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 0c1c1ba320..54536a93b1 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -593,7 +593,7 @@ namespace MWMechanics const auto start = actorPos.asVec3(); // don't take shortcuts for wandering - const auto destVec3f = PathFinder::MakeOsgVec3(dest); + const auto destVec3f = PathFinder::makeOsgVec3(dest); mPathFinder.buildSyncedPath(start, destVec3f, actor.getCell(), getPathGridGraph(actor.getCell())); if (mPathFinder.isPathConstructed()) @@ -730,7 +730,7 @@ namespace MWMechanics ESM::Pathgrid::Point worldDest = dest; ToWorldCoordinates(worldDest, actor.getCell()->getCell()); - bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60); + bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::makeOsgVec3(worldDest), 60); // add offset only if the selected pathgrid is occupied by another actor if (isPathGridOccupied) @@ -751,18 +751,18 @@ namespace MWMechanics ESM::Pathgrid::Point connDest = points[randomIndex]; // add an offset towards random neighboring node - osg::Vec3f dir = PathFinder::MakeOsgVec3(connDest) - PathFinder::MakeOsgVec3(dest); + osg::Vec3f dir = PathFinder::makeOsgVec3(connDest) - PathFinder::makeOsgVec3(dest); 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)); + dest = PathFinder::makePathgridPoint(PathFinder::makeOsgVec3(dest) + dir * (j * 5 * length / 100.f)); worldDest = dest; ToWorldCoordinates(worldDest, actor.getCell()->getCell()); - isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60); + isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::makeOsgVec3(worldDest), 60); if (!isOccupied) break; @@ -798,7 +798,7 @@ namespace MWMechanics const ESM::Pathgrid *pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*currentCell->getCell()); - int index = PathFinder::GetClosestPoint(pathgrid, PathFinder::MakeOsgVec3(dest)); + int index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest)); getPathGridGraph(currentCell).getNeighbouringPoints(index, points); } @@ -831,7 +831,7 @@ namespace MWMechanics CoordinateConverter(cell).toLocal(npcPos); // Find closest pathgrid point - int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, npcPos); + int closestPointIndex = PathFinder::getClosestPoint(pathgrid, npcPos); // mAllowedNodes for this actor with pathgrid point indexes based on mDistance // and if the point is connected to the closest current point @@ -839,7 +839,7 @@ namespace MWMechanics int pointIndex = 0; for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) { - osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter])); + osg::Vec3f nodePos(PathFinder::makeOsgVec3(pathgrid->mPoints[counter])); if((npcPos - nodePos).length2() <= mDistance * mDistance && getPathGridGraph(cellStore).isPointConnected(closestPointIndex, counter)) { @@ -866,7 +866,7 @@ namespace MWMechanics // 2. Partway along the path between the point and its connected points. void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage) { - storage.mAllowedNodes.push_back(PathFinder::MakePathgridPoint(npcPos)); + storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(npcPos)); for (std::vector::const_iterator it = pathGrid->mEdges.begin(); it != pathGrid->mEdges.end(); ++it) { if (it->mV0 == pointIndex) @@ -878,8 +878,8 @@ namespace MWMechanics void AiWander::AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage) { - osg::Vec3f vectorStart = PathFinder::MakeOsgVec3(start); - osg::Vec3f delta = PathFinder::MakeOsgVec3(end) - vectorStart; + osg::Vec3f vectorStart = PathFinder::makeOsgVec3(start); + osg::Vec3f delta = PathFinder::makeOsgVec3(end) - vectorStart; float length = delta.length(); delta.normalize(); @@ -888,7 +888,7 @@ 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.mAllowedNodes.push_back(PathFinder::makePathgridPoint(vectorStart + delta)); } void AiWander::SetCurrentNodeToClosestAllowedNode(const osg::Vec3f& npcPos, AiWanderStorage& storage) @@ -897,7 +897,7 @@ namespace MWMechanics unsigned int index = 0; for (unsigned int counterThree = 0; counterThree < storage.mAllowedNodes.size(); counterThree++) { - osg::Vec3f nodePos(PathFinder::MakeOsgVec3(storage.mAllowedNodes[counterThree])); + osg::Vec3f nodePos(PathFinder::makeOsgVec3(storage.mAllowedNodes[counterThree])); float tempDist = (npcPos - nodePos).length2(); if (tempDist < distanceToClosestNode) { diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 952c92ad57..410bde5127 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -27,7 +27,7 @@ namespace // points to a quadtree may help for(unsigned int counter = 0; counter < grid->mPoints.size(); counter++) { - float potentialDistBetween = MWMechanics::PathFinder::DistanceSquared(grid->mPoints[counter], pos); + float potentialDistBetween = MWMechanics::PathFinder::distanceSquared(grid->mPoints[counter], pos); if (potentialDistBetween < closestDistanceReachable) { // found a closer one @@ -108,7 +108,7 @@ namespace MWMechanics * pathgrid point (e.g. wander) then it may be worth while to call * pop_back() to remove the redundant entry. * - * NOTE: coordinates must be converted prior to calling GetClosestPoint() + * NOTE: coordinates must be converted prior to calling getClosestPoint() * * | * | cell @@ -148,16 +148,16 @@ namespace MWMechanics return; } - // NOTE: GetClosestPoint expects local coordinates + // NOTE: getClosestPoint expects local coordinates CoordinateConverter converter(mCell->getCell()); - // NOTE: It is possible that GetClosestPoint returns a pathgrind point index + // NOTE: It is possible that getClosestPoint returns a pathgrind point index // that is unreachable in some situations. e.g. actor is standing // outside an area enclosed by walls, but there is a pathgrid // point right behind the wall that is closer than any pathgrid // point outside the wall osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint)); - int startNode = GetClosestPoint(mPathgrid, startPointInLocalCoords); + int startNode = getClosestPoint(mPathgrid, startPointInLocalCoords); osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint)); std::pair endNode = getClosestReachablePoint(mPathgrid, &pathgridGraph, @@ -167,8 +167,8 @@ namespace MWMechanics // if it's shorter for actor to travel from start to end, than to travel from either // start or end to nearest pathgrid point, just travel from start to end. float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2(); - float endTolastNodeLength2 = DistanceSquared(mPathgrid->mPoints[endNode.first], endPointInLocalCoords); - float startTo1stNodeLength2 = DistanceSquared(mPathgrid->mPoints[startNode], startPointInLocalCoords); + float endTolastNodeLength2 = distanceSquared(mPathgrid->mPoints[endNode.first], endPointInLocalCoords); + float startTo1stNodeLength2 = distanceSquared(mPathgrid->mPoints[startNode], startPointInLocalCoords); if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2)) { mPath.push_back(endPoint); @@ -184,7 +184,7 @@ namespace MWMechanics { ESM::Pathgrid::Point temp(mPathgrid->mPoints[startNode]); converter.toWorld(temp); - mPath.push_back(MakeOsgVec3(temp)); + mPath.push_back(makeOsgVec3(temp)); } else { @@ -195,8 +195,8 @@ namespace MWMechanics if (path.size() > 1) { ESM::Pathgrid::Point secondNode = *(++path.begin()); - osg::Vec3f firstNodeVec3f = MakeOsgVec3(mPathgrid->mPoints[startNode]); - osg::Vec3f secondNodeVec3f = MakeOsgVec3(secondNode); + osg::Vec3f firstNodeVec3f = makeOsgVec3(mPathgrid->mPoints[startNode]); + osg::Vec3f secondNodeVec3f = makeOsgVec3(secondNode); osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f; osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f; if (toSecondNodeVec3f * toStartPointVec3f > 0) @@ -217,7 +217,7 @@ namespace MWMechanics [&] (ESM::Pathgrid::Point& point) { converter.toWorld(point); - return MakeOsgVec3(point); + return makeOsgVec3(point); }); } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 0d3b6bdff2..1463e78eca 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -118,18 +118,18 @@ namespace MWMechanics } /// utility function to convert a osg::Vec3f to a Pathgrid::Point - static ESM::Pathgrid::Point MakePathgridPoint(const osg::Vec3f& v) + 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) + 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) + static osg::Vec3f makeOsgVec3(const ESM::Pathgrid::Point& p) { return osg::Vec3f(static_cast(p.mX), static_cast(p.mY), static_cast(p.mZ)); } @@ -138,9 +138,9 @@ namespace MWMechanics // Caller needs to be careful for very short distances (i.e. less than 1) // or when accumuating the results i.e. (a + b)^2 != a^2 + b^2 // - static float DistanceSquared(ESM::Pathgrid::Point point, const osg::Vec3f& pos) + static float distanceSquared(ESM::Pathgrid::Point point, const osg::Vec3f& pos) { - return (MWMechanics::PathFinder::MakeOsgVec3(point) - pos).length2(); + return (MWMechanics::PathFinder::makeOsgVec3(point) - pos).length2(); } // Return the closest pathgrid point index from the specified position @@ -149,18 +149,18 @@ namespace MWMechanics // // NOTE: pos is expected to be in local coordinates, as is grid->mPoints // - static int GetClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos) + static int getClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos) { assert(grid && !grid->mPoints.empty()); - float distanceBetween = DistanceSquared(grid->mPoints[0], pos); + float distanceBetween = distanceSquared(grid->mPoints[0], pos); int closestIndex = 0; // TODO: if this full scan causes performance problems mapping pathgrid // points to a quadtree may help for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++) { - float potentialDistBetween = DistanceSquared(grid->mPoints[counter], pos); + float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos); if(potentialDistBetween < distanceBetween) { distanceBetween = potentialDistBetween; From d3667945c514f6e01cd350821aaca8ad79dc971b Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 13:42:31 +0300 Subject: [PATCH 009/133] Remove unused functions --- apps/openmw/mwmechanics/pathfinding.hpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 1463e78eca..0fc39bc61b 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -58,20 +58,6 @@ namespace MWMechanics static const int PathTolerance = 32; - static float sgn(float val) - { - if(val > 0) - return 1.0; - return -1.0; - } - - static int sgn(int a) - { - if(a > 0) - return 1; - return -1; - } - void clearPath(); void buildPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, From 6411c1955de6240a21c6f57ab453b150c388b1d2 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 13:43:29 +0300 Subject: [PATCH 010/133] Fix indent --- apps/openmw/mwmechanics/pathfinding.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 0fc39bc61b..fddd62c29a 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -42,7 +42,7 @@ namespace MWMechanics return getXAngleToDir(dest - origin); } - const float PATHFIND_Z_REACH = 50.0f; + const float PATHFIND_Z_REACH = 50.0f; //static const float sMaxSlope = 49.0f; // duplicate as in physicssystem // distance after which actor (failed previously to shortcut) will try again const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; From 2c6daa74a9e5afbba4e29b27e6f0baf354d1ad76 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 13:51:31 +0300 Subject: [PATCH 011/133] Simplify PathFinder::checkPathCompleted --- apps/openmw/mwmechanics/pathfinding.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 410bde5127..b2b96539ac 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -265,19 +265,10 @@ namespace MWMechanics bool PathFinder::checkPathCompleted(float x, float y, float tolerance) { - if(mPath.empty()) - return true; - - if (sqrDistanceIgnoreZ(mPath.front(), x, y) < tolerance*tolerance) - { + if (!mPath.empty() && sqrDistanceIgnoreZ(mPath.front(), x, y) < tolerance*tolerance) mPath.pop_front(); - if(mPath.empty()) - { - return true; - } - } - return false; + return mPath.empty(); } // see header for the rationale From 6d892411781f89b16ea9f1756943ebc914acbbcb Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 14:01:02 +0300 Subject: [PATCH 012/133] Check is path completed by osg::Vec3f position --- apps/openmw/mwmechanics/aipackage.cpp | 2 +- apps/openmw/mwmechanics/aiwander.cpp | 4 +++- apps/openmw/mwmechanics/pathfinding.cpp | 15 +++++++++------ apps/openmw/mwmechanics/pathfinding.hpp | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 9bc3ed103e..c7cc649d46 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -171,7 +171,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& mTimer = 0; } - if (isDestReached || mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) // if path is finished + if (isDestReached || mPathFinder.checkPathCompleted(pos.asVec3())) // if path is finished { // turn to destination point zTurn(actor, getZAngleToPoint(start, dest)); diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 54536a93b1..8e78e10bd5 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -249,7 +249,9 @@ namespace MWMechanics setPathToAnAllowedNode(actor, storage, pos); } } - } else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) { + } + else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.asVec3(), DESTINATION_TOLERANCE)) + { completeManualWalking(actor, storage); } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index b2b96539ac..2efd06e7b2 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -55,11 +55,14 @@ namespace (closestReachableIndex, closestReachableIndex == closestIndex); } - float sqrDistanceIgnoreZ(const osg::Vec3f& point, float x, float y) + float sqrDistance(const osg::Vec2f& lhs, const osg::Vec2f& rhs) { - x -= point.x(); - y -= point.y(); - return (x * x + y * y); + return (lhs - rhs).length2(); + } + + float sqrDistanceIgnoreZ(const osg::Vec3f& lhs, const osg::Vec3f& rhs) + { + return sqrDistance(osg::Vec2f(lhs.x(), lhs.y()), osg::Vec2f(rhs.x(), rhs.y())); } } @@ -263,9 +266,9 @@ namespace MWMechanics return getXAngleToDir(dir); } - bool PathFinder::checkPathCompleted(float x, float y, float tolerance) + bool PathFinder::checkPathCompleted(const osg::Vec3f& position, const float tolerance) { - if (!mPath.empty() && sqrDistanceIgnoreZ(mPath.front(), x, y) < tolerance*tolerance) + if (!mPath.empty() && sqrDistanceIgnoreZ(mPath.front(), position) < tolerance*tolerance) mPath.pop_front(); return mPath.empty(); diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index fddd62c29a..596178655a 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -63,7 +63,7 @@ namespace MWMechanics void buildPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); - bool checkPathCompleted(float x, float y, float tolerance = PathTolerance); + bool checkPathCompleted(const osg::Vec3f& position, const float tolerance = PathTolerance); ///< \Returns true if we are within \a tolerance units of the last path point. /// In radians From ad027d13fa0dae702548fab949d402ca1d6629e8 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 16:22:06 +0300 Subject: [PATCH 013/133] Remove unused --- apps/openmw/mwmechanics/aipackage.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 74c05f7cdd..f90be0c4a0 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -138,8 +138,6 @@ namespace MWMechanics std::string mTargetActorRefId; mutable int mTargetActorId; - osg::Vec3f mLastActorPos; - short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility bool mIsShortcutting; // if shortcutting at the moment From c9f3064cbd5248acba28dbc6bb4965b509cde26a Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 18:26:00 +0300 Subject: [PATCH 014/133] Update ObstacleCheck once per frame --- apps/openmw/mwmechanics/aipackage.cpp | 8 +++++--- apps/openmw/mwmechanics/aipackage.hpp | 3 ++- apps/openmw/mwmechanics/aiwander.cpp | 6 +++--- apps/openmw/mwmechanics/aiwander.hpp | 2 +- apps/openmw/mwmechanics/obstacle.cpp | 12 ++---------- apps/openmw/mwmechanics/obstacle.hpp | 6 ++---- 6 files changed, 15 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index c7cc649d46..83e718dcd2 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -187,8 +187,10 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& if (mRotateOnTheRunChecks > 0) mRotateOnTheRunChecks--; } + mObstacleCheck.update(actor, duration); + // handle obstacles on the way - evadeObstacles(actor, duration, pos); + evadeObstacles(actor, pos); } // turn to next path point by X,Z axes @@ -198,14 +200,14 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& return false; } -void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos) +void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, const ESM::Position& pos) { zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); // check if stuck due to obstacles - if (!mObstacleCheck.check(actor, duration)) return; + if (!mObstacleCheck.isEvading()) return; // first check if obstacle is a door static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index f90be0c4a0..4113bd1412 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -124,7 +124,8 @@ namespace MWMechanics bool doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::CellStore* currentCell); - void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); + void evadeObstacles(const MWWorld::Ptr& actor, const ESM::Position& pos); + void openDoors(const MWWorld::Ptr& actor); const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell); diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 8e78e10bd5..0d0daf80b4 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -167,14 +167,14 @@ namespace MWMechanics if (AI_REACTION_TIME <= lastReaction) { lastReaction = 0; - return reactionTimeActions(actor, storage, currentCell, cellChange, pos, duration); + return reactionTimeActions(actor, storage, currentCell, cellChange, pos); } else return false; } bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, - const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration) + const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos) { if (mDistance <= 0) storage.mCanWanderAlongPathGrid = false; @@ -224,7 +224,7 @@ namespace MWMechanics } // If Wandering manually and hit an obstacle, stop - if (storage.mIsWanderingManually && mObstacleCheck.check(actor, duration, 2.0f)) { + if (storage.mIsWanderingManually && mObstacleCheck.isEvading()) { completeManualWalking(actor, storage); } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 479ed4869c..41c1fdf029 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -145,7 +145,7 @@ namespace MWMechanics void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos); void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage); bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, - const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration); + const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos); bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance); bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination); diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 0635a5520a..9707ddb252 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -96,11 +96,6 @@ namespace MWMechanics mEvadeDuration = 0; } - bool ObstacleCheck::isNormalState() const - { - return mWalkState == State_Norm; - } - bool ObstacleCheck::isEvading() const { return mWalkState == State_Evade; @@ -128,7 +123,7 @@ namespace MWMechanics * u = how long to move sideways * */ - bool ObstacleCheck::check(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance) + void ObstacleCheck::update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance) { const MWWorld::Class& cls = actor.getClass(); ESM::Position pos = actor.getRefData().getPosition(); @@ -180,9 +175,7 @@ namespace MWMechanics case State_Evade: { mEvadeDuration += duration; - if(mEvadeDuration < DURATION_TO_EVADE) - return true; - else + if(mEvadeDuration >= DURATION_TO_EVADE) { // tried to evade, assume all is ok and start again mWalkState = State_Norm; @@ -191,7 +184,6 @@ namespace MWMechanics } /* NO DEFAULT CASE */ } - return false; // no obstacles to evade (yet) } void ObstacleCheck::takeEvasiveAction(MWMechanics::Movement& actorMovement) diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index c553745014..1672e0b81d 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -27,12 +27,10 @@ namespace MWMechanics // Clear the timers and set the state machine to default void clear(); - bool isNormalState() const; bool isEvading() const; - // Returns true if there is an obstacle and an evasive action - // should be taken - bool check(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f); + // Updates internal state, call each frame for moving actor + void update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f); // change direction to try to fix "stuck" actor void takeEvasiveAction(MWMechanics::Movement& actorMovement); From 63b3a70ca84ea1b95b2305e080cb5700e272abe2 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 18:28:12 +0300 Subject: [PATCH 015/133] Remove useless else --- apps/openmw/mwmechanics/aipackage.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 83e718dcd2..fda7c3a643 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -178,20 +178,18 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& smoothTurn(actor, getXAngleToPoint(start, dest), 0); return true; } - else + + if (mRotateOnTheRunChecks == 0 + || isReachableRotatingOnTheRun(actor, *mPathFinder.getPath().begin())) // to prevent circling around a path point { - if (mRotateOnTheRunChecks == 0 - || isReachableRotatingOnTheRun(actor, *mPathFinder.getPath().begin())) // to prevent circling around a path point - { - actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // move to the target - if (mRotateOnTheRunChecks > 0) mRotateOnTheRunChecks--; - } + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // move to the target + if (mRotateOnTheRunChecks > 0) mRotateOnTheRunChecks--; + } - mObstacleCheck.update(actor, duration); + mObstacleCheck.update(actor, duration); - // handle obstacles on the way - evadeObstacles(actor, pos); - } + // handle obstacles on the way + evadeObstacles(actor, pos); // turn to next path point by X,Z axes zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); From d0bc1b75e8ea236ee06cbbb289fe028da18b6c78 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 18:38:08 +0300 Subject: [PATCH 016/133] Remove unused parameters --- apps/openmw/mwmechanics/aiwander.cpp | 4 ++-- apps/openmw/mwmechanics/aiwander.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 0d0daf80b4..e6c25db971 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -425,7 +425,7 @@ namespace MWMechanics else { // have not yet reached the destination - evadeObstacles(actor, storage, duration, pos); + evadeObstacles(actor, storage); } } @@ -456,7 +456,7 @@ namespace MWMechanics storage.setState(AiWanderStorage::Wander_IdleNow); } - void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos) + void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage) { if (mObstacleCheck.isEvading()) { diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 41c1fdf029..c385e915f7 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -137,7 +137,7 @@ namespace MWMechanics short unsigned getRandomIdle(); void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos); void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage); - void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos); + void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage); void playIdleDialogueRandomly(const MWWorld::Ptr& actor); void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage); void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos); From 1a95b7a1542ac049069ab014ab92ab2eac657a4c Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 18:41:48 +0300 Subject: [PATCH 017/133] Remove duplicate zTurn call --- apps/openmw/mwmechanics/aipackage.cpp | 6 ++---- apps/openmw/mwmechanics/aipackage.hpp | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index fda7c3a643..2483ce030b 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -189,7 +189,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& mObstacleCheck.update(actor, duration); // handle obstacles on the way - evadeObstacles(actor, pos); + evadeObstacles(actor); // turn to next path point by X,Z axes zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); @@ -198,10 +198,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& return false; } -void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, const ESM::Position& pos) +void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor) { - zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); - MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); // check if stuck due to obstacles diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 4113bd1412..440cf64b5e 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -124,7 +124,7 @@ namespace MWMechanics bool doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::CellStore* currentCell); - void evadeObstacles(const MWWorld::Ptr& actor, const ESM::Position& pos); + void evadeObstacles(const MWWorld::Ptr& actor); void openDoors(const MWWorld::Ptr& actor); From 66e5a4d59188bcf309d426555c69b0e919b05994 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 18:42:21 +0300 Subject: [PATCH 018/133] Remove useless variable --- apps/openmw/mwmechanics/aipackage.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 2483ce030b..78219a3db5 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -200,8 +200,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor) { - MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); - // check if stuck due to obstacles if (!mObstacleCheck.isEvading()) return; @@ -215,7 +213,7 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor) } else { - mObstacleCheck.takeEvasiveAction(movement); + mObstacleCheck.takeEvasiveAction(actor.getClass().getMovementSettings(actor)); } } From eb10add0c462905b1f5f1ced88cba3d126b2ea6e Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 18:48:34 +0300 Subject: [PATCH 019/133] Remove unused parameters --- apps/openmw/mwmechanics/aiwander.cpp | 9 ++++----- apps/openmw/mwmechanics/aiwander.hpp | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index e6c25db971..6ca23df955 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -158,7 +158,7 @@ namespace MWMechanics storage.setState(AiWanderStorage::Wander_Walking); } - doPerFrameActionsForState(actor, duration, storage, pos); + doPerFrameActionsForState(actor, duration, storage); playIdleDialogueRandomly(actor); @@ -348,7 +348,7 @@ namespace MWMechanics storage.setState(AiWanderStorage::Wander_IdleNow); } - void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos) + void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage) { switch (storage.mState) { @@ -357,7 +357,7 @@ namespace MWMechanics break; case AiWanderStorage::Wander_Walking: - onWalkingStatePerFrameActions(actor, duration, storage, pos); + onWalkingStatePerFrameActions(actor, duration, storage); break; case AiWanderStorage::Wander_ChooseAction: @@ -413,8 +413,7 @@ namespace MWMechanics } } - void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, - float duration, AiWanderStorage& storage, ESM::Position& pos) + void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage) { // Is there no destination or are we there yet? if ((!mPathFinder.isPathConstructed()) || pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE)) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index c385e915f7..8df863590d 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -140,9 +140,9 @@ namespace MWMechanics void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage); void playIdleDialogueRandomly(const MWWorld::Ptr& actor); void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage); - void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos); + void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); - void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos); + void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage); bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos); From 2c464bd682fea67b5cf4b34305fdffc5dfb4e3c0 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 18:52:11 +0300 Subject: [PATCH 020/133] Evade obstacles after set rotation --- apps/openmw/mwmechanics/aipackage.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 78219a3db5..43f555d319 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -186,15 +186,15 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& if (mRotateOnTheRunChecks > 0) mRotateOnTheRunChecks--; } + // turn to next path point by X,Z axes + zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); + smoothTurn(actor, mPathFinder.getXAngleToNext(pos.pos[0], pos.pos[1], pos.pos[2]), 0); + mObstacleCheck.update(actor, duration); // handle obstacles on the way evadeObstacles(actor); - // turn to next path point by X,Z axes - zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); - smoothTurn(actor, mPathFinder.getXAngleToNext(pos.pos[0], pos.pos[1], pos.pos[2]), 0); - return false; } From f9c651bdf364b41ba399a4f4e70ad15f375074b4 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 23:17:52 +0300 Subject: [PATCH 021/133] Add const --- apps/openmw/mwmechanics/obstacle.cpp | 2 +- apps/openmw/mwmechanics/obstacle.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 9707ddb252..9562c74df0 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -186,7 +186,7 @@ namespace MWMechanics } } - void ObstacleCheck::takeEvasiveAction(MWMechanics::Movement& actorMovement) + void ObstacleCheck::takeEvasiveAction(MWMechanics::Movement& actorMovement) const { actorMovement.mPosition[0] = evadeDirections[mEvadeDirectionIndex][0]; actorMovement.mPosition[1] = evadeDirections[mEvadeDirectionIndex][1]; diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index 1672e0b81d..d7e582f8c5 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -33,7 +33,7 @@ namespace MWMechanics void update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f); // change direction to try to fix "stuck" actor - void takeEvasiveAction(MWMechanics::Movement& actorMovement); + void takeEvasiveAction(MWMechanics::Movement& actorMovement) const; private: From 2ad35430889bfd0bb9a137f8085c412bfa4010f7 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 18 Aug 2018 23:46:06 +0300 Subject: [PATCH 022/133] Fix constant style --- apps/openmw/mwmechanics/pathfinding.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 596178655a..c03c69cf5f 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -47,6 +47,8 @@ namespace MWMechanics // distance after which actor (failed previously to shortcut) will try again const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; + const float DEFAULT_TOLERANCE = 32.0f; + // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY); @@ -56,14 +58,12 @@ namespace MWMechanics public: PathFinder(); - static const int PathTolerance = 32; - void clearPath(); void buildPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); - bool checkPathCompleted(const osg::Vec3f& position, const float tolerance = PathTolerance); + bool checkPathCompleted(const osg::Vec3f& position, const float tolerance = DEFAULT_TOLERANCE); ///< \Returns true if we are within \a tolerance units of the last path point. /// In radians From 3565d92e1196705b5aae6f9922a78037e178cacb Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 20 Aug 2018 01:08:14 +0300 Subject: [PATCH 023/133] Make PathFinder::getPathCell inline --- apps/openmw/mwmechanics/pathfinding.cpp | 5 ----- apps/openmw/mwmechanics/pathfinding.hpp | 5 ++++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 2efd06e7b2..c74eddf4c4 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -299,9 +299,4 @@ namespace MWMechanics } } } - - const MWWorld::CellStore* PathFinder::getPathCell() const - { - return mCell; - } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index c03c69cf5f..09ef3929d7 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -86,7 +86,10 @@ namespace MWMechanics return mPath; } - const MWWorld::CellStore* getPathCell() const; + const MWWorld::CellStore* getPathCell() const + { + return mCell; + } /** Synchronize new path with old one to avoid visiting 1 waypoint 2 times @note From ca3d0594b381f8a06e10dcc4484a9c663454b813 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 20 Aug 2018 01:11:21 +0300 Subject: [PATCH 024/133] Do not store pointer to Pathgrid in PathFinder --- apps/openmw/mwmechanics/pathfinding.cpp | 25 ++++++++++--------------- apps/openmw/mwmechanics/pathfinding.hpp | 1 - 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index c74eddf4c4..2709ef6bab 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -83,8 +83,7 @@ namespace MWMechanics } PathFinder::PathFinder() - : mPathgrid(nullptr) - , mCell(nullptr) + : mCell(nullptr) { } @@ -134,18 +133,14 @@ namespace MWMechanics const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph) { mPath.clear(); + mCell = cell; - // TODO: consider removing mCell / mPathgrid in favor of mPathgridGraph - if(mCell != cell || !mPathgrid) - { - mCell = cell; - mPathgrid = pathgridGraph.getPathgrid(); - } + const auto pathgrid = pathgridGraph.getPathgrid(); // Refer to AiWander reseach topic on openmw forums for some background. // Maybe there is no pathgrid for this cell. Just go to destination and let // physics take care of any blockages. - if(!mPathgrid || mPathgrid->mPoints.empty()) + if(!pathgrid || pathgrid->mPoints.empty()) { mPath.push_back(endPoint); return; @@ -160,18 +155,18 @@ namespace MWMechanics // point right behind the wall that is closer than any pathgrid // point outside the wall osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint)); - int startNode = getClosestPoint(mPathgrid, startPointInLocalCoords); + int startNode = getClosestPoint(pathgrid, startPointInLocalCoords); osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint)); - std::pair endNode = getClosestReachablePoint(mPathgrid, &pathgridGraph, + std::pair endNode = getClosestReachablePoint(pathgrid, &pathgridGraph, endPointInLocalCoords, startNode); // if it's shorter for actor to travel from start to end, than to travel from either // start or end to nearest pathgrid point, just travel from start to end. float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2(); - float endTolastNodeLength2 = distanceSquared(mPathgrid->mPoints[endNode.first], endPointInLocalCoords); - float startTo1stNodeLength2 = distanceSquared(mPathgrid->mPoints[startNode], startPointInLocalCoords); + float endTolastNodeLength2 = distanceSquared(pathgrid->mPoints[endNode.first], endPointInLocalCoords); + float startTo1stNodeLength2 = distanceSquared(pathgrid->mPoints[startNode], startPointInLocalCoords); if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2)) { mPath.push_back(endPoint); @@ -185,7 +180,7 @@ namespace MWMechanics // nodes are the same if(startNode == endNode.first) { - ESM::Pathgrid::Point temp(mPathgrid->mPoints[startNode]); + ESM::Pathgrid::Point temp(pathgrid->mPoints[startNode]); converter.toWorld(temp); mPath.push_back(makeOsgVec3(temp)); } @@ -198,7 +193,7 @@ namespace MWMechanics if (path.size() > 1) { ESM::Pathgrid::Point secondNode = *(++path.begin()); - osg::Vec3f firstNodeVec3f = makeOsgVec3(mPathgrid->mPoints[startNode]); + osg::Vec3f firstNodeVec3f = makeOsgVec3(pathgrid->mPoints[startNode]); osg::Vec3f secondNodeVec3f = makeOsgVec3(secondNode); osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f; osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f; diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 09ef3929d7..ae330fb92f 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -163,7 +163,6 @@ namespace MWMechanics private: std::deque mPath; - const ESM::Pathgrid *mPathgrid; const MWWorld::CellStore* mCell; }; } From 85bbf9d0349314c764b07aa6f67da8a1659789b9 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 20 Aug 2018 01:12:09 +0300 Subject: [PATCH 025/133] Clear path without check for empty --- apps/openmw/mwmechanics/pathfinding.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 2709ef6bab..be4d047a63 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -89,8 +89,7 @@ namespace MWMechanics void PathFinder::clearPath() { - if(!mPath.empty()) - mPath.clear(); + mPath.clear(); } /* From 3655f19373160ba71ea90696de6c2d0a2faf37b8 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 20 Aug 2018 01:12:40 +0300 Subject: [PATCH 026/133] Set PathFinder::mCell to nullptr when clear path --- apps/openmw/mwmechanics/pathfinding.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index be4d047a63..f58686438c 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -90,6 +90,7 @@ namespace MWMechanics void PathFinder::clearPath() { mPath.clear(); + mCell = nullptr; } /* From 92f52287bfa96394fe8ab6b7f1ed469a9e9e6bd6 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 20 Aug 2018 01:13:16 +0300 Subject: [PATCH 027/133] Make PathFinder::ClearPath inline --- apps/openmw/mwmechanics/pathfinding.cpp | 6 ------ apps/openmw/mwmechanics/pathfinding.hpp | 6 +++++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index f58686438c..b5d59b5211 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -87,12 +87,6 @@ namespace MWMechanics { } - void PathFinder::clearPath() - { - mPath.clear(); - mCell = nullptr; - } - /* * NOTE: This method may fail to find a path. The caller must check the * result before using it. If there is no path the AI routies need to diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index ae330fb92f..4f71d18cdf 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -58,7 +58,11 @@ namespace MWMechanics public: PathFinder(); - void clearPath(); + void clearPath() + { + mPath.clear(); + mCell = nullptr; + } void buildPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); From b6dd2119a63c62760631612fe27affaa6d01eb00 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 20 Aug 2018 01:13:59 +0300 Subject: [PATCH 028/133] Make Pathfinder constructor inline --- apps/openmw/mwmechanics/pathfinding.cpp | 5 ----- apps/openmw/mwmechanics/pathfinding.hpp | 5 ++++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index b5d59b5211..23a468d21a 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -82,11 +82,6 @@ namespace MWMechanics return (std::abs(from.z() - h) <= PATHFIND_Z_REACH); } - PathFinder::PathFinder() - : mCell(nullptr) - { - } - /* * NOTE: This method may fail to find a path. The caller must check the * result before using it. If there is no path the AI routies need to diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 4f71d18cdf..47d5640ad3 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -56,7 +56,10 @@ namespace MWMechanics class PathFinder { public: - PathFinder(); + PathFinder() + : mCell(nullptr) + { + } void clearPath() { From 4fe764c3a53049607cb2107ee1f720f9762b3e35 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 20 Aug 2018 01:26:58 +0300 Subject: [PATCH 029/133] Update and check for complete Pathfinder path by different methods --- apps/openmw/mwmechanics/aipackage.cpp | 4 +++- apps/openmw/mwmechanics/aiwander.cpp | 2 +- apps/openmw/mwmechanics/pathfinding.cpp | 5 ++--- apps/openmw/mwmechanics/pathfinding.hpp | 17 +++++++++++++---- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 43f555d319..0b06d8c1dc 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -171,7 +171,9 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& mTimer = 0; } - if (isDestReached || mPathFinder.checkPathCompleted(pos.asVec3())) // if path is finished + mPathFinder.update(pos.asVec3(), std::max(destTolerance, DEFAULT_TOLERANCE)); + + if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished { // turn to destination point zTurn(actor, getZAngleToPoint(start, dest)); diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 6ca23df955..4a452e1f70 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -250,7 +250,7 @@ namespace MWMechanics } } } - else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.asVec3(), DESTINATION_TOLERANCE)) + else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted()) { completeManualWalking(actor, storage); } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 23a468d21a..e0a83910f3 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -121,6 +121,7 @@ namespace MWMechanics void PathFinder::buildPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph) { + mConstructed = true; mPath.clear(); mCell = cell; @@ -250,12 +251,10 @@ namespace MWMechanics return getXAngleToDir(dir); } - bool PathFinder::checkPathCompleted(const osg::Vec3f& position, const float tolerance) + void PathFinder::update(const osg::Vec3f& position, const float tolerance) { if (!mPath.empty() && sqrDistanceIgnoreZ(mPath.front(), position) < tolerance*tolerance) mPath.pop_front(); - - return mPath.empty(); } // see header for the rationale diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 47d5640ad3..419b185852 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -57,12 +57,14 @@ namespace MWMechanics { public: PathFinder() - : mCell(nullptr) + : mConstructed(false) + , mCell(nullptr) { } void clearPath() { + mConstructed = false; mPath.clear(); mCell = nullptr; } @@ -70,8 +72,13 @@ namespace MWMechanics void buildPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); - bool checkPathCompleted(const osg::Vec3f& position, const float tolerance = DEFAULT_TOLERANCE); - ///< \Returns true if we are within \a tolerance units of the last path point. + /// Remove front point if exist and within tolerance + void update(const osg::Vec3f& position, const float tolerance = DEFAULT_TOLERANCE); + + bool checkPathCompleted() const + { + return mConstructed && mPath.empty(); + } /// In radians float getZAngleToNext(float x, float y) const; @@ -80,7 +87,7 @@ namespace MWMechanics bool isPathConstructed() const { - return !mPath.empty(); + return mConstructed && !mPath.empty(); } std::size_t getPathSize() const @@ -110,6 +117,7 @@ namespace MWMechanics void addPointToPath(const osg::Vec3f& point) { + mConstructed = true; mPath.push_back(point); } @@ -168,6 +176,7 @@ namespace MWMechanics } private: + bool mConstructed; std::deque mPath; const MWWorld::CellStore* mCell; From c866fdff86e3d4528b789b8069070669693245a9 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Mar 2018 14:32:45 +0300 Subject: [PATCH 030/133] Move physics object, heightfield, ptrholder into separate files --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwphysics/actor.hpp | 26 +--- apps/openmw/mwphysics/heightfield.cpp | 54 ++++++++ apps/openmw/mwphysics/heightfield.hpp | 36 +++++ apps/openmw/mwphysics/object.cpp | 130 ++++++++++++++++++ apps/openmw/mwphysics/object.hpp | 48 +++++++ apps/openmw/mwphysics/physicssystem.cpp | 171 +----------------------- apps/openmw/mwphysics/ptrholder.hpp | 33 +++++ 8 files changed, 305 insertions(+), 195 deletions(-) create mode 100644 apps/openmw/mwphysics/heightfield.cpp create mode 100644 apps/openmw/mwphysics/heightfield.hpp create mode 100644 apps/openmw/mwphysics/object.cpp create mode 100644 apps/openmw/mwphysics/object.hpp create mode 100644 apps/openmw/mwphysics/ptrholder.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 0092712dbc..99b123fd75 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -70,7 +70,7 @@ add_openmw_dir (mwworld ) add_openmw_dir (mwphysics - physicssystem trace collisiontype actor convert + physicssystem trace collisiontype actor convert object heightfield ) add_openmw_dir (mwclass diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 8ec94200fb..31dd22a221 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -3,7 +3,7 @@ #include -#include "../mwworld/ptr.hpp" +#include "ptrholder.hpp" #include #include @@ -22,30 +22,6 @@ namespace Resource namespace MWPhysics { - class PtrHolder - { - public: - virtual ~PtrHolder() {} - - void updatePtr(const MWWorld::Ptr& updated) - { - mPtr = updated; - } - - MWWorld::Ptr getPtr() - { - return mPtr; - } - - MWWorld::ConstPtr getPtr() const - { - return mPtr; - } - - protected: - MWWorld::Ptr mPtr; - }; - class Actor : public PtrHolder { public: diff --git a/apps/openmw/mwphysics/heightfield.cpp b/apps/openmw/mwphysics/heightfield.cpp new file mode 100644 index 0000000000..52aed9c072 --- /dev/null +++ b/apps/openmw/mwphysics/heightfield.cpp @@ -0,0 +1,54 @@ +#include "heightfield.hpp" + +#include + +#include +#include + +#include + +namespace MWPhysics +{ + HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) + { + mShape = new btHeightfieldTerrainShape( + sqrtVerts, sqrtVerts, heights, 1, + minH, maxH, 2, + PHY_FLOAT, false + ); + mShape->setUseDiamondSubdivision(true); + mShape->setLocalScaling(btVector3(triSize, triSize, 1)); + + btTransform transform(btQuaternion::getIdentity(), + btVector3((x+0.5f) * triSize * (sqrtVerts-1), + (y+0.5f) * triSize * (sqrtVerts-1), + (maxH+minH)*0.5f)); + + mCollisionObject = new btCollisionObject; + mCollisionObject->setCollisionShape(mShape); + mCollisionObject->setWorldTransform(transform); + + mHoldObject = holdObject; + } + + HeightField::~HeightField() + { + delete mCollisionObject; + delete mShape; + } + + btCollisionObject* HeightField::getCollisionObject() + { + return mCollisionObject; + } + + const btCollisionObject* HeightField::getCollisionObject() const + { + return mCollisionObject; + } + + const btHeightfieldTerrainShape* HeightField::getShape() const + { + return mShape; + } +} diff --git a/apps/openmw/mwphysics/heightfield.hpp b/apps/openmw/mwphysics/heightfield.hpp new file mode 100644 index 0000000000..f248186db3 --- /dev/null +++ b/apps/openmw/mwphysics/heightfield.hpp @@ -0,0 +1,36 @@ +#ifndef OPENMW_MWPHYSICS_HEIGHTFIELD_H +#define OPENMW_MWPHYSICS_HEIGHTFIELD_H + +#include + +class btCollisionObject; +class btHeightfieldTerrainShape; + +namespace osg +{ + class Object; +} + +namespace MWPhysics +{ + class HeightField + { + public: + HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); + ~HeightField(); + + btCollisionObject* getCollisionObject(); + const btCollisionObject* getCollisionObject() const; + const btHeightfieldTerrainShape* getShape() const; + + private: + btHeightfieldTerrainShape* mShape; + btCollisionObject* mCollisionObject; + osg::ref_ptr mHoldObject; + + void operator=(const HeightField&); + HeightField(const HeightField&); + }; +} + +#endif diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp new file mode 100644 index 0000000000..d14f579dd0 --- /dev/null +++ b/apps/openmw/mwphysics/object.cpp @@ -0,0 +1,130 @@ +#include "object.hpp" +#include "convert.hpp" + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +namespace MWPhysics +{ + Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance) + : mShapeInstance(shapeInstance) + , mSolid(true) + { + mPtr = ptr; + + mCollisionObject.reset(new btCollisionObject); + mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape()); + + mCollisionObject->setUserPointer(static_cast(this)); + + setScale(ptr.getCellRef().getScale()); + setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude())); + const float* pos = ptr.getRefData().getPosition().pos; + setOrigin(btVector3(pos[0], pos[1], pos[2])); + } + + const Resource::BulletShapeInstance* Object::getShapeInstance() const + { + return mShapeInstance.get(); + } + + void Object::setScale(float scale) + { + mShapeInstance->getCollisionShape()->setLocalScaling(btVector3(scale,scale,scale)); + } + + void Object::setRotation(const btQuaternion& quat) + { + mCollisionObject->getWorldTransform().setRotation(quat); + } + + void Object::setOrigin(const btVector3& vec) + { + mCollisionObject->getWorldTransform().setOrigin(vec); + } + + btCollisionObject* Object::getCollisionObject() + { + return mCollisionObject.get(); + } + + const btCollisionObject* Object::getCollisionObject() const + { + return mCollisionObject.get(); + } + + bool Object::isSolid() const + { + return mSolid; + } + + void Object::setSolid(bool solid) + { + mSolid = solid; + } + + bool Object::isAnimated() const + { + return !mShapeInstance->mAnimatedShapes.empty(); + } + + void Object::animateCollisionShapes(btCollisionWorld* collisionWorld) + { + if (mShapeInstance->mAnimatedShapes.empty()) + return; + + assert (mShapeInstance->getCollisionShape()->isCompound()); + + btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); + for (std::map::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it) + { + int recIndex = it->first; + int shapeIndex = it->second; + + std::map::iterator nodePathFound = mRecIndexToNodePath.find(recIndex); + if (nodePathFound == mRecIndexToNodePath.end()) + { + NifOsg::FindGroupByRecIndex visitor(recIndex); + mPtr.getRefData().getBaseNode()->accept(visitor); + if (!visitor.mFound) + { + Log(Debug::Warning) << "Warning: animateCollisionShapes can't find node " << recIndex << " for " << mPtr.getCellRef().getRefId(); + + // Remove nonexistent nodes from animated shapes map and early out + mShapeInstance->mAnimatedShapes.erase(recIndex); + return; + } + osg::NodePath nodePath = visitor.mFoundPath; + nodePath.erase(nodePath.begin()); + nodePathFound = mRecIndexToNodePath.insert(std::make_pair(recIndex, nodePath)).first; + } + + osg::NodePath& nodePath = nodePathFound->second; + osg::Matrixf matrix = osg::computeLocalToWorld(nodePath); + matrix.orthoNormalize(matrix); + + btTransform transform; + transform.setOrigin(toBullet(matrix.getTrans()) * compound->getLocalScaling()); + for (int i=0; i<3; ++i) + for (int j=0; j<3; ++j) + transform.getBasis()[i][j] = matrix(j,i); // NB column/row major difference + + // Note: we can not apply scaling here for now since we treat scaled shapes + // as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now + if (!(transform == compound->getChildTransform(shapeIndex))) + compound->updateChildTransform(shapeIndex, transform); + } + + collisionWorld->updateSingleAabb(mCollisionObject.get()); + } +} diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp new file mode 100644 index 0000000000..b948433e36 --- /dev/null +++ b/apps/openmw/mwphysics/object.hpp @@ -0,0 +1,48 @@ +#ifndef OPENMW_MWPHYSICS_OBJECT_H +#define OPENMW_MWPHYSICS_OBJECT_H + +#include "ptrholder.hpp" + +#include + +#include +#include + +namespace Resource +{ + class BulletShapeInstance; +} + +class btCollisionObject; +class btCollisionWorld; +class btQuaternion; +class btVector3; + +namespace MWPhysics +{ + class Object : public PtrHolder + { + public: + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance); + + const Resource::BulletShapeInstance* getShapeInstance() const; + void setScale(float scale); + void setRotation(const btQuaternion& quat); + void setOrigin(const btVector3& vec); + btCollisionObject* getCollisionObject(); + const btCollisionObject* getCollisionObject() const; + /// Return solid flag. Not used by the object itself, true by default. + bool isSolid() const; + void setSolid(bool solid); + bool isAnimated() const; + void animateCollisionShapes(btCollisionWorld* collisionWorld); + + private: + std::unique_ptr mCollisionObject; + osg::ref_ptr mShapeInstance; + std::map mRecIndexToNodePath; + bool mSolid; + }; +} + +#endif diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 363f28e70f..74354e101c 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -48,6 +48,8 @@ #include "actor.hpp" #include "convert.hpp" #include "trace.h" +#include "object.hpp" +#include "heightfield.hpp" namespace MWPhysics { @@ -507,175 +509,6 @@ namespace MWPhysics // --------------------------------------------------------------- - class HeightField - { - public: - HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) - { - mShape = new btHeightfieldTerrainShape( - sqrtVerts, sqrtVerts, heights, 1, - minH, maxH, 2, - PHY_FLOAT, false - ); - mShape->setUseDiamondSubdivision(true); - mShape->setLocalScaling(btVector3(triSize, triSize, 1)); - - btTransform transform(btQuaternion::getIdentity(), - btVector3((x+0.5f) * triSize * (sqrtVerts-1), - (y+0.5f) * triSize * (sqrtVerts-1), - (maxH+minH)*0.5f)); - - mCollisionObject = new btCollisionObject; - mCollisionObject->setCollisionShape(mShape); - mCollisionObject->setWorldTransform(transform); - - mHoldObject = holdObject; - } - ~HeightField() - { - delete mCollisionObject; - delete mShape; - } - btCollisionObject* getCollisionObject() - { - return mCollisionObject; - } - - private: - btHeightfieldTerrainShape* mShape; - btCollisionObject* mCollisionObject; - osg::ref_ptr mHoldObject; - - void operator=(const HeightField&); - HeightField(const HeightField&); - }; - - // -------------------------------------------------------------- - - class Object : public PtrHolder - { - public: - Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance) - : mShapeInstance(shapeInstance) - , mSolid(true) - { - mPtr = ptr; - - mCollisionObject.reset(new btCollisionObject); - mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape()); - - mCollisionObject->setUserPointer(static_cast(this)); - - setScale(ptr.getCellRef().getScale()); - setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude())); - const float* pos = ptr.getRefData().getPosition().pos; - setOrigin(btVector3(pos[0], pos[1], pos[2])); - } - - const Resource::BulletShapeInstance* getShapeInstance() const - { - return mShapeInstance.get(); - } - - void setScale(float scale) - { - mShapeInstance->getCollisionShape()->setLocalScaling(btVector3(scale,scale,scale)); - } - - void setRotation(const btQuaternion& quat) - { - mCollisionObject->getWorldTransform().setRotation(quat); - } - - void setOrigin(const btVector3& vec) - { - mCollisionObject->getWorldTransform().setOrigin(vec); - } - - btCollisionObject* getCollisionObject() - { - return mCollisionObject.get(); - } - - const btCollisionObject* getCollisionObject() const - { - return mCollisionObject.get(); - } - - /// Return solid flag. Not used by the object itself, true by default. - bool isSolid() const - { - return mSolid; - } - - void setSolid(bool solid) - { - mSolid = solid; - } - - bool isAnimated() const - { - return !mShapeInstance->mAnimatedShapes.empty(); - } - - void animateCollisionShapes(btCollisionWorld* collisionWorld) - { - if (mShapeInstance->mAnimatedShapes.empty()) - return; - - assert (mShapeInstance->getCollisionShape()->isCompound()); - - btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); - for (std::map::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it) - { - int recIndex = it->first; - int shapeIndex = it->second; - - std::map::iterator nodePathFound = mRecIndexToNodePath.find(recIndex); - if (nodePathFound == mRecIndexToNodePath.end()) - { - NifOsg::FindGroupByRecIndex visitor(recIndex); - mPtr.getRefData().getBaseNode()->accept(visitor); - if (!visitor.mFound) - { - Log(Debug::Warning) << "Warning: animateCollisionShapes can't find node " << recIndex << " for " << mPtr.getCellRef().getRefId(); - - // Remove nonexistent nodes from animated shapes map and early out - mShapeInstance->mAnimatedShapes.erase(recIndex); - return; - } - osg::NodePath nodePath = visitor.mFoundPath; - nodePath.erase(nodePath.begin()); - nodePathFound = mRecIndexToNodePath.insert(std::make_pair(recIndex, nodePath)).first; - } - - osg::NodePath& nodePath = nodePathFound->second; - osg::Matrixf matrix = osg::computeLocalToWorld(nodePath); - matrix.orthoNormalize(matrix); - - btTransform transform; - transform.setOrigin(toBullet(matrix.getTrans()) * compound->getLocalScaling()); - for (int i=0; i<3; ++i) - for (int j=0; j<3; ++j) - transform.getBasis()[i][j] = matrix(j,i); // NB column/row major difference - - // Note: we can not apply scaling here for now since we treat scaled shapes - // as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now - if (!(transform == compound->getChildTransform(shapeIndex))) - compound->updateChildTransform(shapeIndex, transform); - } - - collisionWorld->updateSingleAabb(mCollisionObject.get()); - } - - private: - std::unique_ptr mCollisionObject; - osg::ref_ptr mShapeInstance; - std::map mRecIndexToNodePath; - bool mSolid; - }; - - // --------------------------------------------------------------- PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode) : mShapeManager(new Resource::BulletShapeManager(resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager())) diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp new file mode 100644 index 0000000000..f8188b43ed --- /dev/null +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_MWPHYSICS_PTRHOLDER_H +#define OPENMW_MWPHYSICS_PTRHOLDER_H + +#include "../mwworld/ptr.hpp" + +namespace MWPhysics +{ + class PtrHolder + { + public: + virtual ~PtrHolder() {} + + void updatePtr(const MWWorld::Ptr& updated) + { + mPtr = updated; + } + + MWWorld::Ptr getPtr() + { + return mPtr; + } + + MWWorld::ConstPtr getPtr() const + { + return mPtr; + } + + protected: + MWWorld::Ptr mPtr; + }; +} + +#endif From ed891268286846d72d0a5eb4d7485ee393a2cdc3 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 Apr 2018 16:48:25 +0300 Subject: [PATCH 031/133] Fix warning implicit conversion changes singedness --- apps/openmw/mwworld/cellstore.cpp | 2 +- apps/openmw/mwworld/cellstore.hpp | 2 +- apps/openmw/mwworld/scene.cpp | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 239d714a06..520630bac9 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -432,7 +432,7 @@ namespace MWWorld mHasState = true; } - int CellStore::count() const + std::size_t CellStore::count() const { return mMergedRefs.size(); } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 1c4d8f5d87..f4d4305214 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -240,7 +240,7 @@ namespace MWWorld ESM::FogState* getFog () const; - int count() const; + std::size_t count() const; ///< Return total number of references, including deleted ones. void load (); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 30e73df588..f554b5a646 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -379,7 +379,7 @@ namespace MWWorld unloadCell (active++); } - int refsToLoad = 0; + std::size_t refsToLoad = 0; // get the number of refs to load for (int x=X-mHalfGridSize; x<=X+mHalfGridSize; ++x) { @@ -553,8 +553,7 @@ namespace MWWorld while (active!=mActiveCells.end()) unloadCell (active++); - int refsToLoad = cell->count(); - loadingListener->setProgressRange(refsToLoad); + loadingListener->setProgressRange(cell->count()); // Load cell. loadCell (cell, loadingListener, changeEvent); From e707202f88a6b84f8532d4fa5330b5ee8b05cbf5 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 31 Mar 2018 19:35:02 +0300 Subject: [PATCH 032/133] Use local constant --- apps/openmw/mwworld/scene.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index f554b5a646..2d629adb58 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -284,7 +284,7 @@ namespace MWWorld const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : 0; if (data) { - mPhysics->addHeightField (data->mHeights, cellX, cell->getCell()->getGridY(), worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); + mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); } else { From c01be7b07f6eca1d99d5686d0eef0dcefeab5a43 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 7 Oct 2018 01:03:29 +0300 Subject: [PATCH 033/133] Add operators for bullet types --- components/bullethelpers/operators.hpp | 71 ++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 components/bullethelpers/operators.hpp diff --git a/components/bullethelpers/operators.hpp b/components/bullethelpers/operators.hpp new file mode 100644 index 0000000000..ea88deddf0 --- /dev/null +++ b/components/bullethelpers/operators.hpp @@ -0,0 +1,71 @@ +#ifndef OPENMW_COMPONENTS_BULLETHELPERS_OPERATORS_H +#define OPENMW_COMPONENTS_BULLETHELPERS_OPERATORS_H + +#include +#include +#include + +#include +#include +#include +#include + +inline std::ostream& operator <<(std::ostream& stream, const btVector3& value) +{ + return stream << "btVector3(" << std::setprecision(std::numeric_limits::max_exponent10) << value.x() + << ", " << std::setprecision(std::numeric_limits::max_exponent10) << value.y() + << ", " << std::setprecision(std::numeric_limits::max_exponent10) << value.z() + << ')'; +} + +inline std::ostream& operator <<(std::ostream& stream, BroadphaseNativeTypes value) +{ + switch (value) + { +#ifndef SHAPE_NAME +#define SHAPE_NAME(name) case name: return stream << #name; + SHAPE_NAME(BOX_SHAPE_PROXYTYPE) + SHAPE_NAME(TRIANGLE_SHAPE_PROXYTYPE) + SHAPE_NAME(TETRAHEDRAL_SHAPE_PROXYTYPE) + SHAPE_NAME(CONVEX_TRIANGLEMESH_SHAPE_PROXYTYPE) + SHAPE_NAME(CONVEX_HULL_SHAPE_PROXYTYPE) + SHAPE_NAME(CONVEX_POINT_CLOUD_SHAPE_PROXYTYPE) + SHAPE_NAME(CUSTOM_POLYHEDRAL_SHAPE_TYPE) + SHAPE_NAME(IMPLICIT_CONVEX_SHAPES_START_HERE) + SHAPE_NAME(SPHERE_SHAPE_PROXYTYPE) + SHAPE_NAME(MULTI_SPHERE_SHAPE_PROXYTYPE) + SHAPE_NAME(CAPSULE_SHAPE_PROXYTYPE) + SHAPE_NAME(CONE_SHAPE_PROXYTYPE) + SHAPE_NAME(CONVEX_SHAPE_PROXYTYPE) + SHAPE_NAME(CYLINDER_SHAPE_PROXYTYPE) + SHAPE_NAME(UNIFORM_SCALING_SHAPE_PROXYTYPE) + SHAPE_NAME(MINKOWSKI_SUM_SHAPE_PROXYTYPE) + SHAPE_NAME(MINKOWSKI_DIFFERENCE_SHAPE_PROXYTYPE) + SHAPE_NAME(BOX_2D_SHAPE_PROXYTYPE) + SHAPE_NAME(CONVEX_2D_SHAPE_PROXYTYPE) + SHAPE_NAME(CUSTOM_CONVEX_SHAPE_TYPE) + SHAPE_NAME(CONCAVE_SHAPES_START_HERE) + SHAPE_NAME(TRIANGLE_MESH_SHAPE_PROXYTYPE) + SHAPE_NAME(SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE) + SHAPE_NAME(FAST_CONCAVE_MESH_PROXYTYPE) + SHAPE_NAME(TERRAIN_SHAPE_PROXYTYPE) + SHAPE_NAME(GIMPACT_SHAPE_PROXYTYPE) + SHAPE_NAME(MULTIMATERIAL_TRIANGLE_MESH_PROXYTYPE) + SHAPE_NAME(EMPTY_SHAPE_PROXYTYPE) + SHAPE_NAME(STATIC_PLANE_PROXYTYPE) + SHAPE_NAME(CUSTOM_CONCAVE_SHAPE_TYPE) + SHAPE_NAME(CONCAVE_SHAPES_END_HERE) + SHAPE_NAME(COMPOUND_SHAPE_PROXYTYPE) + SHAPE_NAME(SOFTBODY_SHAPE_PROXYTYPE) + SHAPE_NAME(HFFLUID_SHAPE_PROXYTYPE) + SHAPE_NAME(HFFLUID_BUOYANT_CONVEX_SHAPE_PROXYTYPE) + SHAPE_NAME(INVALID_SHAPE_PROXYTYPE) + SHAPE_NAME(MAX_BROADPHASE_COLLISION_TYPES) +#undef SHAPE_NAME +#endif + default: + return stream << "undefined(" << static_cast(value) << ")"; + } +} + +#endif From 4f2bfa1e9de29f10a97f8ecc85fabaed82d74366 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 7 Oct 2018 01:04:01 +0300 Subject: [PATCH 034/133] Add operators for osg types --- components/osghelpers/operators.hpp | 35 +++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 components/osghelpers/operators.hpp diff --git a/components/osghelpers/operators.hpp b/components/osghelpers/operators.hpp new file mode 100644 index 0000000000..fdb0227ead --- /dev/null +++ b/components/osghelpers/operators.hpp @@ -0,0 +1,35 @@ +#ifndef OPENMW_COMPONENTS_OSGHELPERS_OPERATORS_H +#define OPENMW_COMPONENTS_OSGHELPERS_OPERATORS_H + +#include +#include +#include + +#include +#include +#include + +namespace osg +{ + inline std::ostream& operator <<(std::ostream& stream, const Vec2i& value) + { + return stream << "osg::Vec2i(" << value.x() << ", " << value.y() << ")"; + } + + inline std::ostream& operator <<(std::ostream& stream, const Vec2f& value) + { + return stream << "osg::Vec2f(" << std::setprecision(std::numeric_limits::max_exponent10) << value.x() + << ", " << std::setprecision(std::numeric_limits::max_exponent10) << value.y() + << ')'; + } + + inline std::ostream& operator <<(std::ostream& stream, const Vec3f& value) + { + return stream << "osg::Vec3f(" << std::setprecision(std::numeric_limits::max_exponent10) << value.x() + << ", " << std::setprecision(std::numeric_limits::max_exponent10) << value.y() + << ", " << std::setprecision(std::numeric_limits::max_exponent10) << value.z() + << ')'; + } +} + +#endif From 3d97e96f55d7b6db796b093b8878af1dcb9adf9e Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 13 Mar 2018 21:58:52 +0300 Subject: [PATCH 035/133] Add dependency to recastnavigation --- CI/before_install.linux.sh | 2 +- CI/before_install.osx.sh | 2 +- CI/before_script.linux.sh | 4 ++++ CI/before_script.msvc.sh | 13 ++++++++++- CI/before_script.osx.sh | 6 ++++- CI/build_recastnavigation.sh | 17 ++++++++++++++ apps/openmw/CMakeLists.txt | 5 ++++ appveyor.yml | 2 +- cmake/FindRecastNavigation.cmake | 40 ++++++++++++++++++++++++++++++++ 9 files changed, 86 insertions(+), 5 deletions(-) create mode 100755 CI/build_recastnavigation.sh create mode 100644 cmake/FindRecastNavigation.cmake diff --git a/CI/before_install.linux.sh b/CI/before_install.linux.sh index 25d05e619c..1ec88fb9e9 100755 --- a/CI/before_install.linux.sh +++ b/CI/before_install.linux.sh @@ -1,3 +1,3 @@ -#!/bin/sh +#!/bin/sh -e sudo ln -s /usr/bin/clang-3.6 /usr/local/bin/clang sudo ln -s /usr/bin/clang++-3.6 /usr/local/bin/clang++ diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 2ab996b10f..95d061c793 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/sh -e brew update diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index dd879989a8..ff6f2218a7 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -5,6 +5,9 @@ free -m env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh GOOGLETEST_DIR="$(pwd)/googletest/build" +env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_recastnavigation.sh +RECASTNAVIGATION_DIR="$(pwd)/recastnavigation/build" + mkdir build cd build export CODE_COVERAGE=1 @@ -18,4 +21,5 @@ ${ANALYZE}cmake \ -DUSE_SYSTEM_TINYXML=TRUE \ -DGTEST_ROOT="${GOOGLETEST_DIR}" \ -DGMOCK_ROOT="${GOOGLETEST_DIR}" \ + -DRecastNavigation_ROOT="${RECASTNAVIGATION_DIR}" \ .. diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 8ee4b7fd99..add6f0ace9 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -304,6 +304,10 @@ if ! [ -z $UNITY_BUILD ]; then add_cmake_opts "-DOPENMW_UNITY_BUILD=True" fi +if [ ${BITS} -eq 64 ]; then + GENERATOR="${GENERATOR} Win64" +fi + echo echo "===================================" echo "Starting prebuild on MSVC${MSVC_DISPLAY_YEAR} WIN${BITS}" @@ -449,7 +453,7 @@ fi else LIB_SUFFIX="0" fi - + add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ -DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.${LIB_SUFFIX}" add_cmake_opts -DBoost_COMPILER="-${TOOLSET}" @@ -644,6 +648,13 @@ printf "SDL 2.0.7... " echo Done. } echo +# recastnavigation +printf 'recastnavigation...' +{ + env GENERATOR="${GENERATOR}" CONFIGURATION="${CONFIGURATION}" ${DEPS_INSTALL}/../../CI/build_recastnavigation.sh + add_cmake_opts -DRecastNavigation_ROOT="$(pwd)/recastnavigation/build" +} +echo cd $DEPS_INSTALL/.. echo echo "Setting up OpenMW build..." diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 8ee01b652d..772a06ad49 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -1,8 +1,11 @@ -#!/bin/sh +#!/bin/sh -e export CXX=clang++ export CC=clang +env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_recastnavigation.sh +RECASTNAVIGATION_DIR="$(pwd)/recastnavigation/build" + DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps" QT_PATH=`brew --prefix qt` mkdir build @@ -17,5 +20,6 @@ cmake \ -D DESIRED_QT_VERSION=5 \ -D BUILD_ESMTOOL=FALSE \ -D BUILD_MYGUI_PLUGIN=FALSE \ +-D RecastNavigation_ROOT="${RECASTNAVIGATION_DIR}" \ -G"Unix Makefiles" \ .. diff --git a/CI/build_recastnavigation.sh b/CI/build_recastnavigation.sh new file mode 100755 index 0000000000..46f3c74808 --- /dev/null +++ b/CI/build_recastnavigation.sh @@ -0,0 +1,17 @@ +#!/bin/sh -e + +git clone https://github.com/recastnavigation/recastnavigation.git +cd recastnavigation +mkdir build +cd build +cmake \ + -DCMAKE_BUILD_TYPE="${CONFIGURATION}" \ + -DRECASTNAVIGATION_DEMO=OFF \ + -DRECASTNAVIGATION_TESTS=OFF \ + -DRECASTNAVIGATION_EXAMPLES=OFF \ + -DRECASTNAVIGATION_STATIC=ON \ + -DCMAKE_INSTALL_PREFIX=. \ + -G "${GENERATOR}" \ + .. +cmake --build . --config "${CONFIGURATION}" +cmake --build . --target install --config "${CONFIGURATION}" diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 99b123fd75..85906b16ee 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -117,6 +117,10 @@ include_directories( ${FFmpeg_INCLUDE_DIRS} ) +find_package(RecastNavigation COMPONENTS Detour Recast REQUIRED) + +include_directories(SYSTEM ${RecastNavigation_INCLUDE_DIRS}) + target_link_libraries(openmw ${OSG_LIBRARIES} ${OPENTHREADS_LIBRARIES} @@ -133,6 +137,7 @@ target_link_libraries(openmw ${FFmpeg_LIBRARIES} ${MyGUI_LIBRARIES} ${SDL2_LIBRARY} + ${RecastNavigation_LIBRARIES} "osg-ffmpeg-videoplayer" "oics" components diff --git a/appveyor.yml b/appveyor.yml index 9877d2105f..5c3522535d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -68,7 +68,7 @@ test: off #notifications: # - provider: Email # to: -# - +# - # on_build_failure: true # on_build_status_changed: true diff --git a/cmake/FindRecastNavigation.cmake b/cmake/FindRecastNavigation.cmake new file mode 100644 index 0000000000..2126b88550 --- /dev/null +++ b/cmake/FindRecastNavigation.cmake @@ -0,0 +1,40 @@ +find_path(RecastNavigation_INCLUDE_DIR + NAMES Recast.h + HINTS $ENV{RecastNavigation_ROOT} + ${RecastNavigation_ROOT} + PATH_SUFFIXES include +) +mark_as_advanced(RecastNavigation_INCLUDE_DIR) + +include(FindPackageHandleStandardArgs) + +set(RecastNavigation_LIBRARIES "") + +foreach(COMPONENT ${RecastNavigation_FIND_COMPONENTS}) + if(NOT RecastNavigation_${COMPONENT}_FOUND) + find_library(RecastNavigation_${COMPONENT}_LIBRARY + HINTS $ENV{RecastNavigation_ROOT} + ${RecastNavigation_ROOT} + NAMES ${COMPONENT} + PATH_SUFFIXES lib + ) + find_package_handle_standard_args(RecastNavigation_${COMPONENT} DEFAULT_MSG + RecastNavigation_${COMPONENT}_LIBRARY + RecastNavigation_INCLUDE_DIR + ) + mark_as_advanced(RecastNavigation_${COMPONENT}_LIBRARY) + if(RecastNavigation_${COMPONENT}_FOUND) + list(APPEND RecastNavigation_LIBRARIES ${RecastNavigation_${COMPONENT}_LIBRARY}) + endif() + endif() +endforeach() +mark_as_advanced(RecastNavigation_LIBRARIES) + +find_package_handle_standard_args(RecastNavigation DEFAULT_MSG + RecastNavigation_LIBRARIES + RecastNavigation_INCLUDE_DIR +) + +if(RecastNavigation_FOUND) + set(RecastNavigation_INCLUDE_DIRS ${RecastNavigation_INCLUDE_DIR}) +endif() From fafba8ea0c6b0efc4af68ac6a726c86d061ede17 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 14 Mar 2018 01:49:08 +0300 Subject: [PATCH 036/133] Use recastnavigation to find path --- apps/openmw/mwbase/world.hpp | 7 + apps/openmw/mwmechanics/aipackage.hpp | 2 + apps/openmw/mwmechanics/pathfinding.cpp | 25 ++ apps/openmw/mwmechanics/pathfinding.hpp | 3 + apps/openmw/mwphysics/physicssystem.cpp | 23 +- apps/openmw/mwphysics/physicssystem.hpp | 6 +- apps/openmw/mwworld/cellstore.hpp | 2 +- apps/openmw/mwworld/scene.cpp | 54 +++- apps/openmw/mwworld/worldimp.cpp | 27 ++ apps/openmw/mwworld/worldimp.hpp | 3 + apps/openmw_test_suite/CMakeLists.txt | 8 + .../detournavigator/navigator.cpp | 159 ++++++++++ .../detournavigator/operators.hpp | 40 +++ .../detournavigator/recastmeshbuilder.cpp | 81 +++++ .../detournavigator/settingsutils.cpp | 68 +++++ components/CMakeLists.txt | 19 ++ .../detournavigator/asyncnavmeshupdater.cpp | 113 +++++++ .../detournavigator/asyncnavmeshupdater.hpp | 75 +++++ .../cachedrecastmeshmanager.cpp | 25 ++ .../cachedrecastmeshmanager.hpp | 34 +++ components/detournavigator/chunkytrimesh.cpp | 174 +++++++++++ components/detournavigator/chunkytrimesh.hpp | 93 ++++++ components/detournavigator/debug.cpp | 163 ++++++++++ components/detournavigator/debug.hpp | 63 ++++ components/detournavigator/dtstatus.hpp | 66 ++++ components/detournavigator/exceptions.hpp | 15 + components/detournavigator/findsmoothpath.cpp | 143 +++++++++ components/detournavigator/findsmoothpath.hpp | 266 ++++++++++++++++ components/detournavigator/makenavmesh.cpp | 288 ++++++++++++++++++ components/detournavigator/makenavmesh.hpp | 20 ++ components/detournavigator/navigator.cpp | 42 +++ components/detournavigator/navigator.hpp | 50 +++ components/detournavigator/navmeshmanager.cpp | 50 +++ components/detournavigator/navmeshmanager.hpp | 48 +++ components/detournavigator/recastmesh.cpp | 12 + components/detournavigator/recastmesh.hpp | 50 +++ .../detournavigator/recastmeshbuilder.cpp | 72 +++++ .../detournavigator/recastmeshbuilder.hpp | 38 +++ .../detournavigator/recastmeshmanager.cpp | 57 ++++ .../detournavigator/recastmeshmanager.hpp | 42 +++ components/detournavigator/settings.hpp | 30 ++ components/detournavigator/settingsutils.hpp | 65 ++++ components/detournavigator/tilebounds.hpp | 15 + components/detournavigator/tileposition.hpp | 11 + files/settings-default.cfg | 60 ++++ 45 files changed, 2698 insertions(+), 9 deletions(-) create mode 100644 apps/openmw_test_suite/detournavigator/navigator.cpp create mode 100644 apps/openmw_test_suite/detournavigator/operators.hpp create mode 100644 apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp create mode 100644 apps/openmw_test_suite/detournavigator/settingsutils.cpp create mode 100644 components/detournavigator/asyncnavmeshupdater.cpp create mode 100644 components/detournavigator/asyncnavmeshupdater.hpp create mode 100644 components/detournavigator/cachedrecastmeshmanager.cpp create mode 100644 components/detournavigator/cachedrecastmeshmanager.hpp create mode 100644 components/detournavigator/chunkytrimesh.cpp create mode 100644 components/detournavigator/chunkytrimesh.hpp create mode 100644 components/detournavigator/debug.cpp create mode 100644 components/detournavigator/debug.hpp create mode 100644 components/detournavigator/dtstatus.hpp create mode 100644 components/detournavigator/exceptions.hpp create mode 100644 components/detournavigator/findsmoothpath.cpp create mode 100644 components/detournavigator/findsmoothpath.hpp create mode 100644 components/detournavigator/makenavmesh.cpp create mode 100644 components/detournavigator/makenavmesh.hpp create mode 100644 components/detournavigator/navigator.cpp create mode 100644 components/detournavigator/navigator.hpp create mode 100644 components/detournavigator/navmeshmanager.cpp create mode 100644 components/detournavigator/navmeshmanager.hpp create mode 100644 components/detournavigator/recastmesh.cpp create mode 100644 components/detournavigator/recastmesh.hpp create mode 100644 components/detournavigator/recastmeshbuilder.cpp create mode 100644 components/detournavigator/recastmeshbuilder.hpp create mode 100644 components/detournavigator/recastmeshmanager.cpp create mode 100644 components/detournavigator/recastmeshmanager.hpp create mode 100644 components/detournavigator/settings.hpp create mode 100644 components/detournavigator/settingsutils.hpp create mode 100644 components/detournavigator/tilebounds.hpp create mode 100644 components/detournavigator/tileposition.hpp diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index e17935abcd..18f997324f 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -54,6 +54,11 @@ namespace MWMechanics struct Movement; } +namespace DetourNavigator +{ + class Navigator; +} + namespace MWWorld { class CellStore; @@ -590,6 +595,8 @@ namespace MWBase /// Preload VFX associated with this effect list virtual void preloadEffects(const ESM::EffectList* effectList) = 0; + + virtual DetourNavigator::Navigator* getNavigator() const = 0; }; } diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 440cf64b5e..59bd6689d5 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -7,6 +7,8 @@ #include "obstacle.hpp" #include "aistate.hpp" +#include + namespace MWWorld { class Ptr; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index e0a83910f3..5a34bb2300 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -1,7 +1,13 @@ #include "pathfinding.hpp" +#include #include +#include +#include +#include +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -282,4 +288,23 @@ namespace MWMechanics } } } + + void PathFinder::buildPathByNavigator(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const osg::Vec3f& halfExtents) + { + try + { + mPath.clear(); + + const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); + navigator->findPath(halfExtents, startPoint, endPoint, std::back_inserter(mPath)); + + mConstructed = true; + } + catch (const DetourNavigator::NavigatorException& exception) + { + DetourNavigator::log("PathFinder::buildPathByNavigator navigator exception: ", exception.what()); + Log(Debug::Error) << "Build path by navigator exception: " << exception.what(); + } + } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 419b185852..f488e3b147 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -175,6 +175,9 @@ namespace MWMechanics return closestIndex; } + void buildPathByNavigator(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const osg::Vec3f& halfExtents); + private: bool mConstructed; std::deque mPath; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 74354e101c..c90dcbf98b 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1,6 +1,11 @@ -#include "physicssystem.hpp" +#include "physicssystem.hpp" #include +#include +#include +#include + +#include #include @@ -16,6 +21,12 @@ #include +#include +#include +#include +#include +#include + #include #include #include @@ -54,8 +65,6 @@ namespace MWPhysics { - static const float sMaxSlope = 49.0f; - static const float sStepSizeUp = 34.0f; static const float sStepSizeDown = 62.0f; static const float sMinStep = 10.f; static const float sGroundOffset = 1.0f; @@ -1004,6 +1013,14 @@ namespace MWPhysics } } + const HeightField* PhysicsSystem::getHeightField(int x, int y) const + { + const auto heightField = mHeightFields.find(std::make_pair(x, y)); + if (heightField == mHeightFields.end()) + return nullptr; + return heightField->second; + } + void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 3ef9990f53..b084cc4c9e 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -49,6 +49,9 @@ namespace MWPhysics class Object; class Actor; + static const float sMaxSlope = 49.0f; + static const float sStepSizeUp = 34.0f; + class PhysicsSystem { public: @@ -85,6 +88,8 @@ namespace MWPhysics void removeHeightField (int x, int y); + const HeightField* getHeightField(int x, int y) const; + bool toggleCollisionMode(); void stepSimulation(float dt); @@ -169,7 +174,6 @@ namespace MWPhysics void markAsNonSolid (const MWWorld::ConstPtr& ptr); bool isOnSolidGround (const MWWorld::Ptr& actor) const; - private: void updateWater(); diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index f4d4305214..49bc0b3639 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -283,7 +283,7 @@ namespace MWWorld /// \attention This function also lists deleted (count 0) objects! /// \return Iteration completed? template - bool forEachConst (Visitor& visitor) const + bool forEachConst (Visitor&& visitor) const { if (mState != State_Loaded) return false; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 2d629adb58..2626bc641c 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -2,12 +2,16 @@ #include +#include + #include #include #include #include #include #include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -19,6 +23,9 @@ #include "../mwrender/landmanager.hpp" #include "../mwphysics/physicssystem.hpp" +#include "../mwphysics/actor.hpp" +#include "../mwphysics/object.hpp" +#include "../mwphysics/heightfield.hpp" #include "player.hpp" #include "localscripts.hpp" @@ -74,6 +81,21 @@ namespace ptr.getClass().insertObject (ptr, model, physics); + if (const auto object = physics.getObject(ptr)) + { + if (const auto concaveShape = dynamic_cast(object->getShapeInstance()->mCollisionShape)) + { + const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); + navigator->addObject(reinterpret_cast(object), *concaveShape, + object->getCollisionObject()->getWorldTransform()); + } + } + else if (const auto actor = physics.getActor(ptr)) + { + const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); + navigator->addAgent(actor->getHalfExtents()); + } + if (useAnim) MWBase::Environment::get().getMechanicsManager()->add(ptr); @@ -233,13 +255,18 @@ namespace MWWorld void Scene::unloadCell (CellStoreCollection::iterator iter) { Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription(); + + const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); ListAndResetObjectsVisitor visitor; (*iter)->forEach(visitor); - for (std::vector::const_iterator iter2 (visitor.mObjects.begin()); - iter2!=visitor.mObjects.end(); ++iter2) + for (const auto& ptr : visitor.mObjects) { - mPhysics->remove(*iter2); + if (const auto object = mPhysics->getObject(ptr)) + navigator->removeObject(reinterpret_cast(object)); + else if (const auto actor = mPhysics->getActor(ptr)) + navigator->removeAgent(actor->getHalfExtents()); + mPhysics->remove(ptr); } if ((*iter)->getCell()->isExterior()) @@ -250,7 +277,13 @@ namespace MWWorld (*iter)->getCell()->getGridY() ); if (land && land->mDataTypes&ESM::Land::DATA_VHGT) - mPhysics->removeHeightField ((*iter)->getCell()->getGridX(), (*iter)->getCell()->getGridY()); + { + const auto cellX = (*iter)->getCell()->getGridX(); + const auto cellY = (*iter)->getCell()->getGridY(); + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->removeObject(reinterpret_cast(heightField)); + mPhysics->removeHeightField(cellX, cellY); + } } MWBase::Environment::get().getMechanicsManager()->drop (*iter); @@ -275,6 +308,8 @@ namespace MWWorld float verts = ESM::Land::LAND_SIZE; float worldsize = ESM::Land::REAL_SIZE; + const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); + // Load terrain physics first... if (cell->getCell()->isExterior()) { @@ -292,6 +327,10 @@ namespace MWWorld defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); } + + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addObject(reinterpret_cast(heightField), *heightField->getShape(), + heightField->getCollisionObject()->getWorldTransform()); } // register local scripts @@ -317,6 +356,8 @@ namespace MWWorld else mPhysics->disableWater(); + navigator->update(); + if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) mRendering.configureAmbient(cell->getCell()); } @@ -631,6 +672,11 @@ namespace MWWorld { MWBase::Environment::get().getMechanicsManager()->remove (ptr); MWBase::Environment::get().getSoundManager()->stopSound3D (ptr); + const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); + if (const auto object = mPhysics->getObject(ptr)) + navigator->removeObject(reinterpret_cast(object)); + else if (const auto actor = mPhysics->getActor(ptr)) + navigator->removeAgent(actor->getHalfExtents()); mPhysics->remove(ptr); mRendering.removeObject (ptr); if (ptr.getClass().isActor()) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0872e589dc..322559e3bd 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -19,6 +19,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -163,6 +165,26 @@ namespace MWWorld mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, &mFallback, resourcePath)); mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get())); + DetourNavigator::Settings navigatorSettings; + navigatorSettings.mCellHeight = Settings::Manager::getFloat("cell height", "Navigator"); + navigatorSettings.mCellSize = Settings::Manager::getFloat("cell size", "Navigator"); + navigatorSettings.mDetailSampleDist = Settings::Manager::getFloat("detail sample dist", "Navigator"); + navigatorSettings.mDetailSampleMaxError = Settings::Manager::getFloat("detail sample max error", "Navigator"); + navigatorSettings.mMaxClimb = MWPhysics::sStepSizeUp; + navigatorSettings.mMaxSimplificationError = Settings::Manager::getFloat("max simplification error", "Navigator"); + navigatorSettings.mMaxSlope = MWPhysics::sMaxSlope; + navigatorSettings.mRecastScaleFactor = Settings::Manager::getFloat("recast scale factor", "Navigator"); + navigatorSettings.mMaxEdgeLen = Settings::Manager::getInt("max edge len", "Navigator"); + navigatorSettings.mMaxNavMeshQueryNodes = Settings::Manager::getInt("max nav mesh query nodes", "Navigator"); + navigatorSettings.mMaxVertsPerPoly = Settings::Manager::getInt("max verts per poly", "Navigator"); + navigatorSettings.mRegionMergeSize = Settings::Manager::getInt("region merge size", "Navigator"); + navigatorSettings.mRegionMinSize = Settings::Manager::getInt("region min size", "Navigator"); + navigatorSettings.mTileSize = Settings::Manager::getInt("tile size", "Navigator"); + navigatorSettings.mMaxPolygonPathSize = static_cast(Settings::Manager::getInt("max polygon path size", "Navigator")); + navigatorSettings.mMaxSmoothPathSize = static_cast(Settings::Manager::getInt("max smooth path size", "Navigator")); + navigatorSettings.mTrianglesPerChunk = static_cast(Settings::Manager::getInt("triangles per chunk", "Navigator")); + mNavigator.reset(new DetourNavigator::Navigator(navigatorSettings)); + mRendering->preloadCommonAssets(); mEsm.resize(contentFiles.size()); @@ -3684,4 +3706,9 @@ namespace MWWorld } } + DetourNavigator::Navigator* World::getNavigator() const + { + return mNavigator.get(); + } + } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 7df8d1af5b..4d264ff7f6 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -98,6 +98,7 @@ namespace MWWorld std::unique_ptr mWorldScene; std::unique_ptr mWeatherManager; std::shared_ptr mProjectileManager; + std::unique_ptr mNavigator; bool mGodMode; bool mScriptsEnabled; @@ -688,6 +689,8 @@ namespace MWWorld /// Preload VFX associated with this effect list void preloadEffects(const ESM::EffectList* effectList) override; + + DetourNavigator::Navigator* getNavigator() const override; }; } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index bc39bc2be6..57fb1a0128 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -17,10 +17,18 @@ if (GTEST_FOUND AND GMOCK_FOUND) misc/test_stringops.cpp nifloader/testbulletnifloader.cpp + + detournavigator/navigator.cpp + detournavigator/settingsutils.cpp + detournavigator/recastmeshbuilder.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) + find_package(RecastNavigation COMPONENTS DebugUtils Detour Recast REQUIRED) + + include_directories(SYSTEM ${RecastNavigation_INCLUDE_DIRS}) + openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) target_link_libraries(openmw_test_suite ${GMOCK_LIBRARIES} components) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp new file mode 100644 index 0000000000..1a317da843 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -0,0 +1,159 @@ +#include "operators.hpp" + +#include +#include + +#include + +#include + +#include +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator; + + struct DetourNavigatorNavigatorTest : Test + { + Settings mSettings; + std::unique_ptr mNavigator; + osg::Vec3f mPlayerPosition; + osg::Vec3f mAgentHalfExtents; + osg::Vec3f mStart; + osg::Vec3f mEnd; + std::deque mPath; + std::back_insert_iterator> mOut; + + DetourNavigatorNavigatorTest() + : mPlayerPosition(0, 0, 0) + , mAgentHalfExtents(29, 29, 66) + , mStart(-215, 215, 1) + , mEnd(215, -215, 1) + , mOut(mPath) + { + mSettings.mCellHeight = 0.2f; + mSettings.mCellSize = 0.2f; + mSettings.mDetailSampleDist = 6; + mSettings.mDetailSampleMaxError = 1; + mSettings.mMaxClimb = 34; + mSettings.mMaxSimplificationError = 1.3f; + mSettings.mMaxSlope = 49; + mSettings.mRecastScaleFactor = 0.017647058823529415f; + mSettings.mMaxEdgeLen = 12; + mSettings.mMaxNavMeshQueryNodes = 2048; + mSettings.mMaxVertsPerPoly = 6; + mSettings.mRegionMergeSize = 20; + mSettings.mRegionMinSize = 8; + mSettings.mTileSize = 64; + mSettings.mMaxPolygonPathSize = 1024; + mSettings.mMaxSmoothPathSize = 1024; + mSettings.mTrianglesPerChunk = 256; + mNavigator.reset(new Navigator(mSettings)); + } + }; + + TEST_F(DetourNavigatorNavigatorTest, update_then_find_path_should_return_path) + { + const std::array heightfieldData {{ + 0, 0, 0, 0, 0, + 0, -25, -25, -25, -25, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + }}; + btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + shape.setLocalScaling(btVector3(128, 128, 1)); + + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addObject(1, shape, btTransform::getIdentity()); + mNavigator->update(); + mNavigator->wait(); + + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); + + EXPECT_EQ(mPath, std::deque({ + osg::Vec3f(-215, 215, 1.85963428020477294921875), + osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.5760211944580078125), + osg::Vec3f(-174.930633544921875, 174.930633544921875, -15.01167774200439453125), + osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473323822021484375), + osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829898834228515625), + osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875), + osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.39907073974609375), + osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375), + osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625), + osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37929534912109375), + osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875), + osg::Vec3f(5.3815765380859375, -5.3815765380859375, -75.35065460205078125), + osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.96945953369140625), + osg::Vec3f(45.450958251953125, -45.450958251953125, -60.58824920654296875), + osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), + osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.825855255126953125), + osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), + osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625), + osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625), + osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875), + osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), + osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.53864824771881103515625), + osg::Vec3f(215, -215, 1.877177715301513671875), + })) << mPath; + } + + TEST_F(DetourNavigatorNavigatorTest, for_overlapping_heightfields_should_use_higher) + { + const std::array heightfieldData {{ + 0, 0, 0, 0, 0, + 0, -25, -25, -25, -25, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + }}; + btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + shape.setLocalScaling(btVector3(128, 128, 1)); + + const std::array heightfieldData2 {{ + -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, + }}; + btHeightfieldTerrainShape shape2(5, 5, heightfieldData2.data(), 1, 0, 0, 2, PHY_FLOAT, false); + shape2.setLocalScaling(btVector3(128, 128, 1)); + + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addObject(1, shape, btTransform::getIdentity()); + mNavigator->addObject(2, shape2, btTransform::getIdentity()); + mNavigator->update(); + mNavigator->wait(); + + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); + + EXPECT_EQ(mPath, std::deque({ + osg::Vec3f(-215, 215, 1.96328866481781005859375), + osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -0.2422157227993011474609375), + osg::Vec3f(-174.930633544921875, 174.930633544921875, -2.44772052764892578125), + osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -4.653223514556884765625), + osg::Vec3f(-134.86126708984375, 134.86126708984375, -6.858728885650634765625), + osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -9.0642337799072265625), + osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -11.26973724365234375), + osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -13.26497173309326171875), + osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -15.24860286712646484375), + osg::Vec3f(-34.68780517578125, 34.68780517578125, -17.2322368621826171875), + osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -19.2158660888671875), + osg::Vec3f(5.3815765380859375, -5.3815765380859375, -20.1338443756103515625), + osg::Vec3f(25.41626739501953125, -25.41626739501953125, -18.150211334228515625), + osg::Vec3f(45.450958251953125, -45.450958251953125, -16.1665802001953125), + osg::Vec3f(65.48564910888671875, -65.48564910888671875, -14.18294811248779296875), + osg::Vec3f(85.5203399658203125, -85.5203399658203125, -12.19931507110595703125), + osg::Vec3f(105.55503082275390625, -105.55503082275390625, -10.08488559722900390625), + osg::Vec3f(125.5897216796875, -125.5897216796875, -7.879383563995361328125), + osg::Vec3f(145.6244049072265625, -145.6244049072265625, -5.673877239227294921875), + osg::Vec3f(165.659088134765625, -165.659088134765625, -3.4683735370635986328125), + osg::Vec3f(185.6937713623046875, -185.6937713623046875, -1.2628715038299560546875), + osg::Vec3f(205.7284698486328125, -205.7284698486328125, 0.9426348209381103515625), + osg::Vec3f(215, -215, 1.96328866481781005859375), + })) << mPath; + } +} diff --git a/apps/openmw_test_suite/detournavigator/operators.hpp b/apps/openmw_test_suite/detournavigator/operators.hpp new file mode 100644 index 0000000000..8e6b97af6f --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/operators.hpp @@ -0,0 +1,40 @@ +#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_OPERATORS_H +#define OPENMW_TEST_SUITE_DETOURNAVIGATOR_OPERATORS_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace DetourNavigator +{ + static inline bool operator ==(const TileBounds& lhs, const TileBounds& rhs) + { + return lhs.mMin == rhs.mMin && lhs.mMax == rhs.mMax; + } +} + +namespace testing +{ + template <> + inline testing::Message& Message::operator <<(const std::deque& value) + { + (*this) << "{\n"; + for (const auto& v : value) + { + std::ostringstream stream; + stream << v; + (*this) << stream.str() << ",\n"; + } + return (*this) << "}"; + } +} + +#endif diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp new file mode 100644 index 0000000000..ff72bc3eb8 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -0,0 +1,81 @@ +#include "operators.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator; + + struct DetourNavigatorRecastMeshBuilderTest : Test + { + Settings mSettings; + RecastMeshBuilder mBuilder; + + DetourNavigatorRecastMeshBuilderTest() + : mBuilder(mSettings) + { + mSettings.mRecastScaleFactor = 1.0f; + mSettings.mTrianglesPerChunk = 256; + } + }; + + TEST_F(DetourNavigatorRecastMeshBuilderTest, add_bhv_triangle_mesh_shape) + { + btTriangleMesh mesh; + mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); + btBvhTriangleMeshShape shape(&mesh, true); + mBuilder.addObject(shape, btTransform::getIdentity()); + const auto recastMesh = mBuilder.create(); + EXPECT_EQ(recastMesh->getVertices(), std::vector({ + 1, 0, -1, + -1, 0, 1, + -1, 0, -1, + })); + EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, add_transformed_bhv_triangle_mesh_shape) + { + btTriangleMesh mesh; + mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); + btBvhTriangleMeshShape shape(&mesh, true); + mBuilder.addObject(shape, + btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3))); + const auto recastMesh = mBuilder.create(); + EXPECT_EQ(recastMesh->getVertices(), std::vector({ + 2, 3, 0, + 0, 3, 4, + 0, 3, 0, + })); + EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_terrian_shape) + { + const std::array heightfieldData {{0, 0, 0, 0}}; + btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + mBuilder.addObject(shape, btTransform::getIdentity()); + const auto recastMesh = mBuilder.create(); + EXPECT_EQ(recastMesh->getVertices(), std::vector({ + -0.5, 0, -0.5, + -0.5, 0, 0.5, + 0.5, 0, -0.5, + 0.5, 0, -0.5, + -0.5, 0, 0.5, + 0.5, 0, 0.5, + })); + EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 3, 4, 5})); + } +} diff --git a/apps/openmw_test_suite/detournavigator/settingsutils.cpp b/apps/openmw_test_suite/detournavigator/settingsutils.cpp new file mode 100644 index 0000000000..ffed64ab81 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/settingsutils.cpp @@ -0,0 +1,68 @@ +#include "operators.hpp" + +#include + +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator; + + struct DetourNavigatorGetTilePositionTest : Test + { + Settings mSettings; + + DetourNavigatorGetTilePositionTest() + { + mSettings.mCellSize = 0.5; + mSettings.mTileSize = 64; + } + }; + + TEST_F(DetourNavigatorGetTilePositionTest, for_zero_coordinates_should_return_zero_tile_position) + { + EXPECT_EQ(getTilePosition(mSettings, osg::Vec3f(0, 0, 0)), TilePosition(0, 0)); + } + + TEST_F(DetourNavigatorGetTilePositionTest, tile_size_should_be_multiplied_by_cell_size) + { + EXPECT_EQ(getTilePosition(mSettings, osg::Vec3f(32, 0, 0)), TilePosition(1, 0)); + } + + TEST_F(DetourNavigatorGetTilePositionTest, tile_position_calculates_by_floor) + { + EXPECT_EQ(getTilePosition(mSettings, osg::Vec3f(31, 0, 0)), TilePosition(0, 0)); + } + + TEST_F(DetourNavigatorGetTilePositionTest, tile_position_depends_on_x_and_z_coordinates) + { + EXPECT_EQ(getTilePosition(mSettings, osg::Vec3f(32, 64, 128)), TilePosition(1, 4)); + } + + TEST_F(DetourNavigatorGetTilePositionTest, tile_position_works_for_negative_coordinates) + { + EXPECT_EQ(getTilePosition(mSettings, osg::Vec3f(-31, 0, -32)), TilePosition(-1, -1)); + } + + struct DetourNavigatorMakeTileBoundsTest : Test + { + Settings mSettings; + + DetourNavigatorMakeTileBoundsTest() + { + mSettings.mCellSize = 0.5; + mSettings.mTileSize = 64; + } + }; + + TEST_F(DetourNavigatorMakeTileBoundsTest, tile_bounds_depend_on_tile_size_and_cell_size) + { + EXPECT_EQ(makeTileBounds(mSettings, TilePosition(0, 0)), (TileBounds {osg::Vec2f(0, 0), osg::Vec2f(32, 32)})); + } + + TEST_F(DetourNavigatorMakeTileBoundsTest, tile_bounds_are_multiplied_by_tile_position) + { + EXPECT_EQ(makeTileBounds(mSettings, TilePosition(1, 2)), (TileBounds {osg::Vec2f(32, 64), osg::Vec2f(64, 96)})); + } +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 5c245afd08..2b857d8111 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -156,6 +156,20 @@ if(NOT WIN32 AND NOT ANDROID) ) endif() +add_component_dir(detournavigator + debug + makenavmesh + findsmoothpath + recastmeshbuilder + recastmeshmanager + cachedrecastmeshmanager + navmeshmanager + navigator + asyncnavmeshupdater + chunkytrimesh + recastmesh + ) + set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) @@ -196,6 +210,10 @@ include_directories(${Bullet_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) +find_package(RecastNavigation COMPONENTS Detour Recast REQUIRED) + +include_directories(SYSTEM ${RecastNavigation_INCLUDE_DIRS}) + target_link_libraries(components ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} @@ -214,6 +232,7 @@ target_link_libraries(components ${SDL2_LIBRARIES} ${OPENGL_gl_LIBRARY} ${MyGUI_LIBRARIES} + ${RecastNavigation_LIBRARIES} ) if (WIN32) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp new file mode 100644 index 0000000000..3a95f19d90 --- /dev/null +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -0,0 +1,113 @@ +#include "asyncnavmeshupdater.hpp" +#include "debug.hpp" +#include "makenavmesh.hpp" +#include "settings.hpp" + +#include + +#include + +namespace DetourNavigator +{ + AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings) + : mSettings(settings) + , mMaxRevision(0) + , mShouldStop() + , mThread([&] { process(); }) + { + } + + AsyncNavMeshUpdater::~AsyncNavMeshUpdater() + { + mShouldStop = true; + std::unique_lock lock(mMutex); + mJobs.clear(); + mHasJob.notify_all(); + lock.unlock(); + mThread.join(); + } + + void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& recastMesh, + const std::shared_ptr& navMeshCacheItem) + { + const std::lock_guard lock(mMutex); + mJobs[agentHalfExtents] = Job {agentHalfExtents, recastMesh, navMeshCacheItem}; + mHasJob.notify_all(); + } + + void AsyncNavMeshUpdater::wait() + { + std::unique_lock lock(mMutex); + mDone.wait(lock, [&] { return mJobs.empty(); }); + } + + void AsyncNavMeshUpdater::process() throw() + { + log("start process jobs"); + while (!mShouldStop) + { + try + { + if (const auto job = getNextJob()) + processJob(*job); + } + catch (const std::exception& e) + { + DetourNavigator::log("AsyncNavMeshUpdater::process exception: ", e.what()); + ::Log(Debug::Error) << "Exception while process navmesh updated job: " << e.what(); + } + } + log("stop process jobs"); + } + + void AsyncNavMeshUpdater::processJob(const Job& job) + { + log("process job for agent=", job.mAgentHalfExtents, + " revision=", job.mNavMeshCacheItem->mRevision, + " max_revision=", mMaxRevision); + + if (job.mNavMeshCacheItem->mRevision < mMaxRevision) + return; + + mMaxRevision = job.mNavMeshCacheItem->mRevision; + + const auto start = std::chrono::steady_clock::now(); + + job.mNavMeshCacheItem->mValue = makeNavMesh(job.mAgentHalfExtents, *job.mRecastMesh, mSettings); + + const auto finish = std::chrono::steady_clock::now(); + + writeDebugFiles(job); + + using FloatMs = std::chrono::duration; + + log("cache updated for agent=", job.mAgentHalfExtents, + " time=", std::chrono::duration_cast(finish - start).count(), "ms"); + } + + boost::optional AsyncNavMeshUpdater::getNextJob() + { + std::unique_lock lock(mMutex); + if (mJobs.empty()) + mHasJob.wait_for(lock, std::chrono::milliseconds(10)); + if (mJobs.empty()) + { + mDone.notify_all(); + return boost::none; + } + log("got ", mJobs.size(), " jobs"); + const auto job = mJobs.begin()->second; + mJobs.erase(mJobs.begin()); + return job; + } + + void AsyncNavMeshUpdater::writeDebugFiles(const Job& job) const + { +#ifdef OPENMW_WRITE_TO_FILE + const auto revision = std::to_string((std::chrono::steady_clock::now() + - std::chrono::steady_clock::time_point()).count()); + writeToFile(*job.mRecastMesh, revision); + writeToFile(*job.mNavMeshCacheItem->mValue, revision); +#endif + } +} diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp new file mode 100644 index 0000000000..cdcc3432c9 --- /dev/null +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -0,0 +1,75 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_ASYNCNAVMESHUPDATER_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_ASYNCNAVMESHUPDATER_H + +#include "recastmesh.hpp" + +#include + +#include + +#include +#include +#include +#include +#include +#include + +class dtNavMesh; + +namespace DetourNavigator +{ + using NavMeshConstPtr = std::shared_ptr; + + struct NavMeshCacheItem + { + NavMeshConstPtr mValue = nullptr; + std::size_t mRevision; + + NavMeshCacheItem(std::size_t mRevision) + : mRevision(mRevision) + {} + }; + + class AsyncNavMeshUpdater + { + public: + AsyncNavMeshUpdater(const Settings& settings); + ~AsyncNavMeshUpdater(); + + void post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& recastMesh, + const std::shared_ptr& navMeshCacheItem); + + void wait(); + + private: + struct Job + { + osg::Vec3f mAgentHalfExtents; + std::shared_ptr mRecastMesh; + std::shared_ptr mNavMeshCacheItem; + }; + + using Jobs = std::map; + + std::reference_wrapper mSettings; + std::atomic_size_t mMaxRevision; + std::atomic_bool mShouldStop; + std::mutex mMutex; + std::condition_variable mHasJob; + std::condition_variable mDone; + Jobs mJobs; + std::thread mThread; + + void process() throw(); + + void processJob(const Job& job); + + boost::optional getNextJob(); + + void notifyHasJob(); + + void writeDebugFiles(const Job& job) const; + }; +} + +#endif diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp new file mode 100644 index 0000000000..1267320380 --- /dev/null +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -0,0 +1,25 @@ +#include "cachedrecastmeshmanager.hpp" +#include "debug.hpp" + +namespace DetourNavigator +{ + CachedRecastMeshManager::CachedRecastMeshManager(const Settings& settings) + : mImpl(settings) + { + } + + bool CachedRecastMeshManager::removeObject(std::size_t id) + { + if (!mImpl.removeObject(id)) + return false; + mCached.reset(); + return true; + } + + std::shared_ptr CachedRecastMeshManager::getMesh() + { + if (!mCached) + mCached = mImpl.getMesh(); + return mCached; + } +} diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp new file mode 100644 index 0000000000..3cfe9a56f5 --- /dev/null +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -0,0 +1,34 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CACHEDRECASTMESHMANAGER_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_CACHEDRECASTMESHMANAGER_H + +#include "recastmeshmanager.hpp" + +#include + +namespace DetourNavigator +{ + class CachedRecastMeshManager + { + public: + CachedRecastMeshManager(const Settings& settings); + + template + bool addObject(std::size_t id, const T& shape, const btTransform& transform) + { + if (!mImpl.addObject(id, shape, transform)) + return false; + mCached.reset(); + return true; + } + + bool removeObject(std::size_t id); + + std::shared_ptr getMesh(); + + private: + RecastMeshManager mImpl; + std::shared_ptr mCached; + }; +} + +#endif diff --git a/components/detournavigator/chunkytrimesh.cpp b/components/detournavigator/chunkytrimesh.cpp new file mode 100644 index 0000000000..9402569fc1 --- /dev/null +++ b/components/detournavigator/chunkytrimesh.cpp @@ -0,0 +1,174 @@ +#include "chunkytrimesh.hpp" + +#include + +#include + +namespace DetourNavigator +{ + namespace + { + struct BoundsItem + { + Rect mBounds; + std::ptrdiff_t mOffset; + }; + + template + struct LessBoundsItem + { + bool operator ()(const BoundsItem& lhs, const BoundsItem& rhs) const + { + return lhs.mBounds.mMinBound[axis] < rhs.mBounds.mMinBound[axis]; + } + }; + + void calcExtends(const std::vector& items, const std::size_t imin, const std::size_t imax, + Rect& bounds) + { + bounds = items[imin].mBounds; + + std::for_each( + items.begin() + static_cast(imin) + 1, + items.begin() + static_cast(imax), + [&] (const BoundsItem& item) + { + for (int i = 0; i < 2; ++i) + { + bounds.mMinBound[i] = std::min(bounds.mMinBound[i], item.mBounds.mMinBound[i]); + bounds.mMaxBound[i] = std::max(bounds.mMaxBound[i], item.mBounds.mMaxBound[i]); + } + }); + } + + void subdivide(std::vector& items, const std::size_t imin, const std::size_t imax, + const std::size_t trisPerChunk, const std::vector& inIndices, + std::size_t& curNode, std::vector& nodes, std::size_t& curTri, + std::vector& outIndices) + { + const auto inum = imax - imin; + const auto icur = curNode; + + if (curNode > nodes.size()) + return; + + ChunkyTriMeshNode& node = nodes[curNode++]; + + if (inum <= trisPerChunk) + { + // Leaf + calcExtends(items, imin, imax, node.mBounds); + + // Copy triangles. + node.mOffset = static_cast(curTri); + node.mSize = inum; + + for (std::size_t i = imin; i < imax; ++i) + { + std::copy( + inIndices.begin() + items[i].mOffset * 3, + inIndices.begin() + items[i].mOffset * 3 + 3, + outIndices.begin() + static_cast(curTri) * 3 + ); + curTri++; + } + } + else + { + // Split + calcExtends(items, imin, imax, node.mBounds); + + if (node.mBounds.mMaxBound.x() - node.mBounds.mMinBound.x() + >= node.mBounds.mMaxBound.y() - node.mBounds.mMinBound.y()) + { + // Sort along x-axis + std::sort( + items.begin() + static_cast(imin), + items.begin() + static_cast(imax), + LessBoundsItem<0> {} + ); + } + else + { + // Sort along y-axis + std::sort( + items.begin() + static_cast(imin), + items.begin() + static_cast(imax), + LessBoundsItem<1> {} + ); + } + + const auto isplit = imin + inum / 2; + + // Left + subdivide(items, imin, isplit, trisPerChunk, inIndices, curNode, nodes, curTri, outIndices); + // Right + subdivide(items, isplit, imax, trisPerChunk, inIndices, curNode, nodes, curTri, outIndices); + + const auto iescape = static_cast(curNode) - static_cast(icur); + // Negative index means escape. + node.mOffset = -iescape; + } + } + } + + ChunkyTriMesh::ChunkyTriMesh(const std::vector& verts, const std::vector& indices, + const std::size_t trisPerChunk) + : mMaxTrisPerChunk(0) + { + const auto trianglesCount = indices.size() / 3; + + if (trianglesCount == 0) + return; + + const auto nchunks = (trianglesCount + trisPerChunk - 1) / trisPerChunk; + + mNodes.resize(nchunks * 4); + mIndices.resize(trianglesCount * 3); + + // Build tree + std::vector items(trianglesCount); + + for (std::size_t i = 0; i < trianglesCount; i++) + { + auto& item = items[i]; + + item.mOffset = static_cast(i); + + // Calc triangle XZ bounds. + const auto baseIndex = static_cast(indices[i * 3]) * 3; + + item.mBounds.mMinBound.x() = item.mBounds.mMaxBound.x() = verts[baseIndex + 0]; + item.mBounds.mMinBound.y() = item.mBounds.mMaxBound.y() = verts[baseIndex + 2]; + + for (std::size_t j = 1; j < 3; ++j) + { + const auto index = static_cast(indices[i * 3 + j]) * 3; + + item.mBounds.mMinBound.x() = std::min(item.mBounds.mMinBound.x(), verts[index + 0]); + item.mBounds.mMinBound.y() = std::min(item.mBounds.mMinBound.y(), verts[index + 2]); + + item.mBounds.mMaxBound.x() = std::max(item.mBounds.mMaxBound.x(), verts[index + 0]); + item.mBounds.mMaxBound.y() = std::max(item.mBounds.mMaxBound.y(), verts[index + 2]); + } + } + + std::size_t curTri = 0; + std::size_t curNode = 0; + subdivide(items, 0, trianglesCount, trisPerChunk, indices, curNode, mNodes, curTri, mIndices); + + items.clear(); + + mNodes.resize(curNode); + + // Calc max tris per node. + for (auto& node : mNodes) + { + const bool isLeaf = node.mOffset >= 0; + if (!isLeaf) + continue; + if (node.mSize > mMaxTrisPerChunk) + mMaxTrisPerChunk = node.mSize; + } + } +} diff --git a/components/detournavigator/chunkytrimesh.hpp b/components/detournavigator/chunkytrimesh.hpp new file mode 100644 index 0000000000..bc1898e2fe --- /dev/null +++ b/components/detournavigator/chunkytrimesh.hpp @@ -0,0 +1,93 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CHUNKYTRIMESH_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_CHUNKYTRIMESH_H + +#include + +#include +#include + +namespace DetourNavigator +{ + struct Rect + { + osg::Vec2f mMinBound; + osg::Vec2f mMaxBound; + }; + + struct ChunkyTriMeshNode + { + Rect mBounds; + std::ptrdiff_t mOffset; + std::size_t mSize; + }; + + struct Chunk + { + const int* const mIndices; + const std::size_t mSize; + }; + + inline bool checkOverlapRect(const Rect& lhs, const Rect& rhs) + { + bool overlap = true; + overlap = (lhs.mMinBound.x() > rhs.mMaxBound.x() || lhs.mMaxBound.x() < rhs.mMinBound.x()) ? false : overlap; + overlap = (lhs.mMinBound.y() > rhs.mMaxBound.y() || lhs.mMaxBound.y() < rhs.mMinBound.y()) ? false : overlap; + return overlap; + } + + class ChunkyTriMesh + { + public: + /// Creates partitioned triangle mesh (AABB tree), + /// where each node contains at max trisPerChunk triangles. + ChunkyTriMesh(const std::vector& verts, const std::vector& tris, std::size_t trisPerChunk); + + ChunkyTriMesh(const ChunkyTriMesh&) = delete; + ChunkyTriMesh& operator=(const ChunkyTriMesh&) = delete; + + /// Returns the chunk indices which overlap the input rectable. + template + void getChunksOverlappingRect(const Rect& rect, OutputIterator out) const + { + // Traverse tree + for (std::size_t i = 0; i < mNodes.size(); ) + { + const ChunkyTriMeshNode* node = &mNodes[i]; + const bool overlap = checkOverlapRect(rect, node->mBounds); + const bool isLeafNode = node->mOffset >= 0; + + if (isLeafNode && overlap) + *out++ = i; + + if (overlap || isLeafNode) + i++; + else + { + const auto escapeIndex = -node->mOffset; + i += static_cast(escapeIndex); + } + } + } + + Chunk getChunk(const std::size_t chunkId) const + { + const auto& node = mNodes[chunkId]; + return Chunk { + mIndices.data() + node.mOffset * 3, + node.mSize + }; + } + + std::size_t getMaxTrisPerChunk() const + { + return mMaxTrisPerChunk; + } + + private: + std::vector mNodes; + std::vector mIndices; + std::size_t mMaxTrisPerChunk; + }; +} + +#endif diff --git a/components/detournavigator/debug.cpp b/components/detournavigator/debug.cpp new file mode 100644 index 0000000000..0b783fd908 --- /dev/null +++ b/components/detournavigator/debug.cpp @@ -0,0 +1,163 @@ +#include "debug.hpp" + +#define OPENMW_WRITE_TO_FILE +#define OPENMW_WRITE_OBJ + +#ifdef OPENMW_WRITE_OBJ +#include "exceptions.hpp" + +#include +#endif + +#ifdef OPENMW_WRITE_TO_FILE +#include "exceptions.hpp" +#include "recastmesh.hpp" + +#include + +#include +#endif + +#include +#include + +namespace +{ +#ifdef OPENMW_WRITE_TO_FILE + static const int NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; //'MSET'; + static const int NAVMESHSET_VERSION = 1; + + struct NavMeshSetHeader + { + int magic; + int version; + int numTiles; + dtNavMeshParams params; + }; + + struct NavMeshTileHeader + { + dtTileRef tileRef; + int dataSize; + }; +#endif +} + +namespace DetourNavigator +{ +// Use to dump scene to load from recastnavigation demo tool +#ifdef OPENMW_WRITE_OBJ + void writeObj(const std::vector& vertices, const std::vector& indices) + { + const auto path = std::string("scene.") + std::to_string(std::time(nullptr)) + ".obj"; + std::ofstream file(path); + if (!file.is_open()) + throw NavigatorException("Open file failed: " + path); + file.exceptions(std::ios::failbit | std::ios::badbit); + file.precision(std::numeric_limits::max_exponent10); + std::size_t count = 0; + for (auto v : vertices) + { + if (count % 3 == 0) + { + if (count != 0) + file << '\n'; + file << 'v'; + } + file << ' ' << v; + ++count; + } + file << '\n'; + count = 0; + for (auto v : indices) + { + if (count % 3 == 0) + { + if (count != 0) + file << '\n'; + file << 'f'; + } + file << ' ' << (v + 1); + ++count; + } + file << '\n'; + } +#endif + +#ifdef OPENMW_WRITE_TO_FILE + void writeToFile(const RecastMesh& recastMesh, const std::string& revision) + { + const auto path = "recastmesh." + revision + ".obj"; + std::ofstream file(path); + if (!file.is_open()) + throw NavigatorException("Open file failed: " + path); + file.exceptions(std::ios::failbit | std::ios::badbit); + file.precision(std::numeric_limits::max_exponent10); + std::size_t count = 0; + for (auto v : recastMesh.getVertices()) + { + if (count % 3 == 0) + { + if (count != 0) + file << '\n'; + file << 'v'; + } + file << ' ' << v; + ++count; + } + file << '\n'; + count = 0; + for (auto v : recastMesh.getIndices()) + { + if (count % 3 == 0) + { + if (count != 0) + file << '\n'; + file << 'f'; + } + file << ' ' << (v + 1); + ++count; + } + file << '\n'; + } + + void writeToFile(const dtNavMesh& navMesh, const std::string& revision) + { + const auto path = "navmesh." + revision + ".bin"; + std::ofstream file(path, std::ios::binary); + if (!file.is_open()) + throw NavigatorException("Open file failed: " + path); + file.exceptions(std::ios::failbit | std::ios::badbit); + + NavMeshSetHeader header; + header.magic = NAVMESHSET_MAGIC; + header.version = NAVMESHSET_VERSION; + header.numTiles = 0; + for (int i = 0; i < navMesh.getMaxTiles(); ++i) + { + const auto tile = navMesh.getTile(i); + if (!tile || !tile->header || !tile->dataSize) + continue; + header.numTiles++; + } + header.params = *navMesh.getParams(); + + using const_char_ptr = const char*; + file.write(const_char_ptr(&header), sizeof(header)); + + for (int i = 0; i < navMesh.getMaxTiles(); ++i) + { + const auto tile = navMesh.getTile(i); + if (!tile || !tile->header || !tile->dataSize) + continue; + + NavMeshTileHeader tileHeader; + tileHeader.tileRef = navMesh.getTileRef(tile); + tileHeader.dataSize = tile->dataSize; + + file.write(const_char_ptr(&tileHeader), sizeof(tileHeader)); + file.write(const_char_ptr(tile->data), tile->dataSize); + } + } +#endif +} diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp new file mode 100644 index 0000000000..c9f25ca496 --- /dev/null +++ b/components/detournavigator/debug.hpp @@ -0,0 +1,63 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_H + +#include "tilebounds.hpp" + +#include +#include + +#include +#include +#include +#include + +#ifdef OPENMW_WRITE_OBJ +#include +#endif + +#ifdef OPENMW_WRITE_TO_FILE +class dtNavMesh; +#endif + +namespace DetourNavigator +{ + inline std::ostream& operator <<(std::ostream& stream, const TileBounds& value) + { + return stream << "TileBounds {" << value.mMin << ", " << value.mMax << "}"; + } + +// Use to dump scene to load from recastnavigation demo tool +#ifdef OPENMW_WRITE_OBJ + void writeObj(const std::vector& vertices, const std::vector& indices); +#endif + +#ifdef OPENMW_WRITE_TO_FILE + class RecastMesh; + + void writeToFile(const RecastMesh& recastMesh, const std::string& revision); + + void writeToFile(const dtNavMesh& navMesh, const std::string& revision); +#endif + + inline void write(std::ostream& stream) + { + stream << '\n'; + } + + template + void write(std::ostream& stream, const Head& head, const Tail& ... tail) + { + stream << head; + write(stream, tail ...); + } + + template + void log(Ts&& ... values) + { + std::ostringstream stream; + write(stream, std::forward(values) ...); + std::cout << stream.str(); + } +} + +#endif diff --git a/components/detournavigator/dtstatus.hpp b/components/detournavigator/dtstatus.hpp new file mode 100644 index 0000000000..5360b5ce36 --- /dev/null +++ b/components/detournavigator/dtstatus.hpp @@ -0,0 +1,66 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_DTSTATUS_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_DTSTATUS_H + +#include "exceptions.hpp" + +#include + +#include +#include + +namespace DetourNavigator +{ + struct WriteDtStatus + { + dtStatus status; + }; + + static const std::vector> dtStatuses { + {DT_FAILURE, "DT_FAILURE"}, + {DT_SUCCESS, "DT_SUCCESS"}, + {DT_IN_PROGRESS, "DT_IN_PROGRESS"}, + {DT_WRONG_MAGIC, "DT_WRONG_MAGIC"}, + {DT_WRONG_VERSION, "DT_WRONG_VERSION"}, + {DT_OUT_OF_MEMORY, "DT_OUT_OF_MEMORY"}, + {DT_INVALID_PARAM, "DT_INVALID_PARAM"}, + {DT_BUFFER_TOO_SMALL, "DT_BUFFER_TOO_SMALL"}, + {DT_OUT_OF_NODES, "DT_OUT_OF_NODES"}, + {DT_PARTIAL_RESULT, "DT_PARTIAL_RESULT"}, + }; + + inline std::ostream& operator <<(std::ostream& stream, const WriteDtStatus& value) + { + for (const auto& status : dtStatuses) + if (value.status & status.first) + stream << status.second << " "; + return stream; + } + + inline void checkDtStatus(dtStatus status, const char* call, int line) + { + if (!dtStatusSucceed(status)) + { + std::ostringstream message; + message << call << " failed with status=" << WriteDtStatus {status} << " at " __FILE__ ":" << line; + throw NavigatorException(message.str()); + } + } + + inline void checkDtResult(bool result, const char* call, int line) + { + if (!result) + { + std::ostringstream message; + message << call << " failed at " __FILE__ ":" << line; + throw NavigatorException(message.str()); + } + } +} + +#define OPENMW_CHECK_DT_STATUS(call) \ + do { DetourNavigator::checkDtStatus((call), #call, __LINE__); } while (false) + +#define OPENMW_CHECK_DT_RESULT(call) \ + do { DetourNavigator::checkDtResult((call), #call, __LINE__); } while (false) + +#endif diff --git a/components/detournavigator/exceptions.hpp b/components/detournavigator/exceptions.hpp new file mode 100644 index 0000000000..e547aaaf4d --- /dev/null +++ b/components/detournavigator/exceptions.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_EXCEPTIONS_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_EXCEPTIONS_H + +#include + +namespace DetourNavigator +{ + struct NavigatorException : std::runtime_error + { + NavigatorException(const std::string& message) : std::runtime_error(message) {} + NavigatorException(const char* message) : std::runtime_error(message) {} + }; +} + +#endif diff --git a/components/detournavigator/findsmoothpath.cpp b/components/detournavigator/findsmoothpath.cpp new file mode 100644 index 0000000000..e59b801144 --- /dev/null +++ b/components/detournavigator/findsmoothpath.cpp @@ -0,0 +1,143 @@ +#include "findsmoothpath.hpp" + +#include +#include + +namespace DetourNavigator +{ + std::vector fixupCorridor(const std::vector& path, const std::vector& visited) + { + std::vector::const_reverse_iterator furthestVisited; + + // Find furthest common polygon. + const auto it = std::find_if(path.rbegin(), path.rend(), [&] (dtPolyRef pathValue) + { + const auto it = std::find(visited.rbegin(), visited.rend(), pathValue); + if (it == visited.rend()) + return false; + furthestVisited = it; + return true; + }); + + // If no intersection found just return current path. + if (it == path.rend()) + return path; + const auto furthestPath = it.base() - 1; + + // Concatenate paths. + + // visited: a_1 ... a_n x b_1 ... b_n + // furthestVisited ^ + // path: C x D + // ^ furthestPath + // result: x b_n ... b_1 D + + std::vector result; + result.reserve(static_cast(furthestVisited - visited.rbegin()) + + static_cast(path.end() - furthestPath) - 1); + std::copy(visited.rbegin(), furthestVisited + 1, std::back_inserter(result)); + std::copy(furthestPath + 1, path.end(), std::back_inserter(result)); + + return result; + } + + // This function checks if the path has a small U-turn, that is, + // a polygon further in the path is adjacent to the first polygon + // in the path. If that happens, a shortcut is taken. + // This can happen if the target (T) location is at tile boundary, + // and we're (S) approaching it parallel to the tile edge. + // The choice at the vertex can be arbitrary, + // +---+---+ + // |:::|:::| + // +-S-+-T-+ + // |:::| | <-- the step can end up in here, resulting U-turn path. + // +---+---+ + std::vector fixupShortcuts(const std::vector& path, const dtNavMeshQuery& navQuery) + { + if (path.size() < 3) + return path; + + // Get connected polygons + const dtMeshTile* tile = 0; + const dtPoly* poly = 0; + if (dtStatusFailed(navQuery.getAttachedNavMesh()->getTileAndPolyByRef(path[0], &tile, &poly))) + return path; + + const std::size_t maxNeis = 16; + std::array neis; + std::size_t nneis = 0; + + for (unsigned int k = poly->firstLink; k != DT_NULL_LINK; k = tile->links[k].next) + { + const dtLink* link = &tile->links[k]; + if (link->ref != 0) + { + if (nneis < maxNeis) + neis[nneis++] = link->ref; + } + } + + // If any of the neighbour polygons is within the next few polygons + // in the path, short cut to that polygon directly. + const std::size_t maxLookAhead = 6; + std::size_t cut = 0; + for (std::size_t i = std::min(maxLookAhead, path.size()) - 1; i > 1 && cut == 0; i--) + { + for (std::size_t j = 0; j < nneis; j++) + { + if (path[i] == neis[j]) + { + cut = i; + break; + } + } + } + if (cut <= 1) + return path; + + std::vector result; + const auto offset = cut - 1; + result.reserve(1 + path.size() - offset); + result.push_back(path.front()); + std::copy(path.begin() + std::ptrdiff_t(offset), path.end(), std::back_inserter(result)); + return result; + } + + boost::optional getSteerTarget(const dtNavMeshQuery& navQuery, const osg::Vec3f& startPos, + const osg::Vec3f& endPos, const float minTargetDist, const std::vector& path) + { + // Find steer target. + SteerTarget result; + const int MAX_STEER_POINTS = 3; + std::array steerPath; + std::array steerPathFlags; + std::array steerPathPolys; + int nsteerPath = 0; + navQuery.findStraightPath(startPos.ptr(), endPos.ptr(), path.data(), int(path.size()), steerPath.data(), + steerPathFlags.data(), steerPathPolys.data(), &nsteerPath, MAX_STEER_POINTS); + assert(nsteerPath >= 0); + if (!nsteerPath) + return boost::none; + + // Find vertex far enough to steer to. + std::size_t ns = 0; + while (ns < static_cast(nsteerPath)) + { + // Stop at Off-Mesh link or when point is further than slop away. + if ((steerPathFlags[ns] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) || + !inRange(makeOsgVec3f(&steerPath[ns * 3]), startPos, minTargetDist, 1000.0f)) + break; + ns++; + } + // Failed to find good point to steer to. + if (ns >= static_cast(nsteerPath)) + return boost::none; + + dtVcopy(result.steerPos.ptr(), &steerPath[ns * 3]); + result.steerPos.y() = startPos[1]; + result.steerPosFlag = steerPathFlags[ns]; + result.steerPosRef = steerPathPolys[ns]; + + return result; + } +} diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp new file mode 100644 index 0000000000..2fbc07c5fd --- /dev/null +++ b/components/detournavigator/findsmoothpath.hpp @@ -0,0 +1,266 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDSMOOTHPATH_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDSMOOTHPATH_H + +#include "dtstatus.hpp" +#include "exceptions.hpp" +#include "settings.hpp" +#include "settingsutils.hpp" + +#include +#include +#include + +#include + +#include + +#include + +class dtNavMesh; + +namespace DetourNavigator +{ + struct Settings; + + inline osg::Vec3f makeOsgVec3f(const float* values) + { + return osg::Vec3f(values[0], values[1], values[2]); + } + + inline bool inRange(const osg::Vec3f& v1, const osg::Vec3f& v2, const float r, const float h) + { + const auto d = v2 - v1; + return (d.x() * d.x() + d.z() * d.z()) < r * r && std::abs(d.y()) < h; + } + + std::vector fixupCorridor(const std::vector& path, const std::vector& visited); + + // This function checks if the path has a small U-turn, that is, + // a polygon further in the path is adjacent to the first polygon + // in the path. If that happens, a shortcut is taken. + // This can happen if the target (T) location is at tile boundary, + // and we're (S) approaching it parallel to the tile edge. + // The choice at the vertex can be arbitrary, + // +---+---+ + // |:::|:::| + // +-S-+-T-+ + // |:::| | <-- the step can end up in here, resulting U-turn path. + // +---+---+ + std::vector fixupShortcuts(const std::vector& path, const dtNavMeshQuery& navQuery); + + struct SteerTarget + { + osg::Vec3f steerPos; + unsigned char steerPosFlag; + dtPolyRef steerPosRef; + }; + + boost::optional getSteerTarget(const dtNavMeshQuery& navQuery, const osg::Vec3f& startPos, + const osg::Vec3f& endPos, const float minTargetDist, const std::vector& path); + + template + class OutputTransformIterator + { + public: + OutputTransformIterator(OutputIterator& impl, const Settings& settings) + : mImpl(impl), mSettings(settings) + { + } + + OutputTransformIterator& operator *() + { + return *this; + } + + OutputTransformIterator& operator ++(int) + { + mImpl++; + return *this; + } + + OutputTransformIterator& operator =(const osg::Vec3f& value) + { + *mImpl = fromNavMeshCoordinates(mSettings, value); + return *this; + } + + private: + OutputIterator& mImpl; + const Settings& mSettings; + }; + + template + OutputIterator makeSmoothPath(const dtNavMesh& navMesh, const dtNavMeshQuery& navMeshQuery, + const dtQueryFilter& filter, const osg::Vec3f& start, const osg::Vec3f& end, + std::vector polygonPath, std::size_t maxSmoothPathSize, OutputIterator out) + { + // Iterate over the path to find smooth path on the detail mesh surface. + osg::Vec3f iterPos; + navMeshQuery.closestPointOnPoly(polygonPath.front(), start.ptr(), iterPos.ptr(), 0); + + osg::Vec3f targetPos; + navMeshQuery.closestPointOnPoly(polygonPath.back(), end.ptr(), targetPos.ptr(), 0); + + const float STEP_SIZE = 0.5f; + const float SLOP = 0.01f; + + *out++ = iterPos; + + std::size_t smoothPathSize = 1; + + // Move towards target a small advancement at a time until target reached or + // when ran out of memory to store the path. + while (!polygonPath.empty() && smoothPathSize < maxSmoothPathSize) + { + // Find location to steer towards. + const auto steerTarget = getSteerTarget(navMeshQuery, iterPos, targetPos, SLOP, polygonPath); + + if (!steerTarget) + break; + + const bool endOfPath = bool(steerTarget->steerPosFlag & DT_STRAIGHTPATH_END); + const bool offMeshConnection = bool(steerTarget->steerPosFlag & DT_STRAIGHTPATH_OFFMESH_CONNECTION); + + // Find movement delta. + const osg::Vec3f delta = steerTarget->steerPos - iterPos; + float len = delta.length(); + // If the steer target is end of path or off-mesh link, do not move past the location. + if ((endOfPath || offMeshConnection) && len < STEP_SIZE) + len = 1; + else + len = STEP_SIZE / len; + + const osg::Vec3f moveTgt = iterPos + delta * len; + + // Move + osg::Vec3f result; + std::vector visited(16); + int nvisited = 0; + OPENMW_CHECK_DT_STATUS(navMeshQuery.moveAlongSurface(polygonPath.front(), iterPos.ptr(), moveTgt.ptr(), + &filter, result.ptr(), visited.data(), &nvisited, int(visited.size()))); + + assert(nvisited >= 0); + assert(nvisited <= int(visited.size())); + visited.resize(static_cast(nvisited)); + + polygonPath = fixupCorridor(polygonPath, visited); + polygonPath = fixupShortcuts(polygonPath, navMeshQuery); + + float h = 0; + navMeshQuery.getPolyHeight(polygonPath.front(), result.ptr(), &h); + result.y() = h; + iterPos = result; + + // Handle end of path and off-mesh links when close enough. + if (endOfPath && inRange(iterPos, steerTarget->steerPos, SLOP, 1.0f)) + { + // Reached end of path. + iterPos = targetPos; + *out++ = iterPos; + ++smoothPathSize; + break; + } + else if (offMeshConnection && inRange(iterPos, steerTarget->steerPos, SLOP, 1.0f)) + { + // Advance the path up to and over the off-mesh connection. + dtPolyRef prevRef = 0; + dtPolyRef polyRef = polygonPath.front(); + std::size_t npos = 0; + while (npos < polygonPath.size() && polyRef != steerTarget->steerPosRef) + { + prevRef = polyRef; + polyRef = polygonPath[npos]; + ++npos; + } + std::copy(polygonPath.begin() + std::ptrdiff_t(npos), polygonPath.end(), polygonPath.begin()); + polygonPath.resize(polygonPath.size() - npos); + + // Reached off-mesh connection. + osg::Vec3f startPos; + osg::Vec3f endPos; + + // Handle the connection. + if (dtStatusSucceed(navMesh.getOffMeshConnectionPolyEndPoints(prevRef, polyRef, + startPos.ptr(), endPos.ptr()))) + { + *out++ = startPos; + ++smoothPathSize; + + // Hack to make the dotted path not visible during off-mesh connection. + if (smoothPathSize & 1) + { + *out++ = startPos; + ++smoothPathSize; + } + + // Move position at the other side of the off-mesh link. + iterPos = endPos; + float eh = 0.0f; + OPENMW_CHECK_DT_STATUS(navMeshQuery.getPolyHeight(polygonPath.front(), iterPos.ptr(), &eh)); + iterPos.y() = eh; + } + } + + // Store results. + *out++ = iterPos; + ++smoothPathSize; + } + + return out; + } + + template + OutputIterator findSmoothPath(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, + const osg::Vec3f& start, const osg::Vec3f& end, const Settings& settings, OutputIterator out) + { + dtNavMeshQuery navMeshQuery; + OPENMW_CHECK_DT_STATUS(navMeshQuery.init(&navMesh, settings.mMaxNavMeshQueryNodes)); + + dtQueryFilter queryFilter; + + dtPolyRef startRef = 0; + osg::Vec3f startPolygonPosition; + for (int i = 0; i < 3; ++i) + { + const auto status = navMeshQuery.findNearestPoly(start.ptr(), (halfExtents * (1 << i)).ptr(), &queryFilter, + &startRef, startPolygonPosition.ptr()); + if (!dtStatusFailed(status) && startRef != 0) + break; + } + + if (startRef == 0) + throw NavigatorException("start polygon is not found at " __FILE__ ":" + std::to_string(__LINE__)); + + dtPolyRef endRef = 0; + osg::Vec3f endPolygonPosition; + for (int i = 0; i < 3; ++i) + { + const auto status = navMeshQuery.findNearestPoly(end.ptr(), (halfExtents * (1 << i)).ptr(), &queryFilter, + &endRef, endPolygonPosition.ptr()); + if (!dtStatusFailed(status) && endRef != 0) + break; + } + + if (endRef == 0) + throw NavigatorException("end polygon is not found at " __FILE__ ":" + std::to_string(__LINE__)); + + std::vector polygonPath(settings.mMaxPolygonPathSize); + int pathLen = 0; + OPENMW_CHECK_DT_STATUS(navMeshQuery.findPath(startRef, endRef, start.ptr(), end.ptr(), &queryFilter, + polygonPath.data(), &pathLen, static_cast(polygonPath.size()))); + + assert(pathLen >= 0); + + polygonPath.resize(static_cast(pathLen)); + + if (polygonPath.empty() || polygonPath.back() != endRef) + return out; + + makeSmoothPath(navMesh, navMeshQuery, queryFilter, start, end, std::move(polygonPath), + settings.mMaxSmoothPathSize, OutputTransformIterator(out, settings)); + + return out; + } +} + +#endif diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp new file mode 100644 index 0000000000..ef9b063747 --- /dev/null +++ b/components/detournavigator/makenavmesh.cpp @@ -0,0 +1,288 @@ +#include "makenavmesh.hpp" +#include "chunkytrimesh.hpp" +#include "dtstatus.hpp" +#include "exceptions.hpp" +#include "recastmesh.hpp" +#include "settings.hpp" +#include "settingsutils.hpp" + +#include +#include +#include +#include + +#include + +namespace +{ + using namespace DetourNavigator; + + void initPolyMeshDetail(rcPolyMeshDetail& value) + { + value.meshes = nullptr; + value.verts = nullptr; + value.tris = nullptr; + } + + struct PolyMeshDetailStackDeleter + { + void operator ()(rcPolyMeshDetail* value) const + { + rcFree(value->meshes); + rcFree(value->verts); + rcFree(value->tris); + } + }; + + using PolyMeshDetailStackPtr = std::unique_ptr; + + struct NavMeshDataValueDeleter + { + void operator ()(unsigned char* value) const + { + dtFree(value); + } + }; + + using NavMeshDataValue = std::unique_ptr; + + struct NavMeshData + { + NavMeshDataValue mValue; + int mSize; + + NavMeshData() = default; + + NavMeshData(unsigned char* value, int size) + : mValue(value) + , mSize(size) + {} + }; + + NavMeshData makeNavMeshTileData(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, + const int tileX, const int tileY, const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax, + const Settings& settings) + { + rcContext context; + rcConfig config; + + config.cs = settings.mCellSize; + config.ch = settings.mCellHeight; + config.walkableSlopeAngle = settings.mMaxSlope; + config.walkableHeight = static_cast(std::ceil(getHeight(settings, agentHalfExtents) / config.ch)); + config.walkableClimb = static_cast(std::floor(getMaxClimb(settings) / config.ch)); + config.walkableRadius = static_cast(std::ceil(getRadius(settings, agentHalfExtents) / config.cs)); + config.maxEdgeLen = static_cast(std::round(settings.mMaxEdgeLen / config.cs)); + config.maxSimplificationError = settings.mMaxSimplificationError; + config.minRegionArea = settings.mRegionMinSize * settings.mRegionMinSize; + config.mergeRegionArea = settings.mRegionMergeSize * settings.mRegionMergeSize; + config.maxVertsPerPoly = settings.mMaxVertsPerPoly; + config.detailSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : config.cs * settings.mDetailSampleDist; + config.detailSampleMaxError = config.ch * settings.mDetailSampleMaxError; + config.borderSize = config.walkableRadius + 3; + config.width = settings.mTileSize + config.borderSize * 2; + config.height = settings.mTileSize + config.borderSize * 2; + rcVcopy(config.bmin, boundsMin.ptr()); + rcVcopy(config.bmax, boundsMax.ptr()); + config.bmin[0] -= config.borderSize * config.cs; + config.bmin[2] -= config.borderSize * config.cs; + config.bmax[0] += config.borderSize * config.cs; + config.bmax[2] += config.borderSize * config.cs; + + rcHeightfield solid; + OPENMW_CHECK_DT_RESULT(rcCreateHeightfield(nullptr, solid, config.width, config.height, + config.bmin, config.bmax, config.cs, config.ch)); + + { + const auto& chunkyMesh = recastMesh.getChunkyTriMesh(); + std::vector areas(chunkyMesh.getMaxTrisPerChunk(), 0); + const osg::Vec2f tileBoundsMin(config.bmin[0], config.bmin[2]); + const osg::Vec2f tileBoundsMax(config.bmax[0], config.bmax[2]); + std::vector cids; + chunkyMesh.getChunksOverlappingRect(Rect {tileBoundsMin, tileBoundsMax}, std::back_inserter(cids)); + + if (cids.empty()) + return NavMeshData(); + + for (const auto cid : cids) + { + const auto chunk = chunkyMesh.getChunk(cid); + + std::fill( + areas.begin(), + std::min(areas.begin() + static_cast(chunk.mSize), + areas.end()), + 0 + ); + + rcMarkWalkableTriangles( + &context, + config.walkableSlopeAngle, + recastMesh.getVertices().data(), + static_cast(recastMesh.getVerticesCount()), + chunk.mIndices, + static_cast(chunk.mSize), + areas.data() + ); + + OPENMW_CHECK_DT_RESULT(rcRasterizeTriangles( + &context, + recastMesh.getVertices().data(), + static_cast(recastMesh.getVerticesCount()), + chunk.mIndices, + areas.data(), + static_cast(chunk.mSize), + solid, + config.walkableClimb + )); + } + } + + rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, solid); + rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, solid); + rcFilterWalkableLowHeightSpans(&context, config.walkableHeight, solid); + + rcPolyMesh polyMesh; + rcPolyMeshDetail polyMeshDetail; + initPolyMeshDetail(polyMeshDetail); + const PolyMeshDetailStackPtr polyMeshDetailPtr(&polyMeshDetail); + { + rcCompactHeightfield compact; + + OPENMW_CHECK_DT_RESULT(rcBuildCompactHeightfield(&context, config.walkableHeight, config.walkableClimb, + solid, compact)); + OPENMW_CHECK_DT_RESULT(rcErodeWalkableArea(&context, config.walkableRadius, compact)); + OPENMW_CHECK_DT_RESULT(rcBuildDistanceField(&context, compact)); + OPENMW_CHECK_DT_RESULT(rcBuildRegions(&context, compact, config.borderSize, config.minRegionArea, + config.mergeRegionArea)); + + rcContourSet contourSet; + OPENMW_CHECK_DT_RESULT(rcBuildContours(&context, compact, config.maxSimplificationError, config.maxEdgeLen, + contourSet)); + + if (contourSet.nconts == 0) + return NavMeshData(); + + OPENMW_CHECK_DT_RESULT(rcBuildPolyMesh(&context, contourSet, config.maxVertsPerPoly, polyMesh)); + OPENMW_CHECK_DT_RESULT(rcBuildPolyMeshDetail(&context, polyMesh, compact, config.detailSampleDist, + config.detailSampleMaxError, polyMeshDetail)); + } + + for (int i = 0; i < polyMesh.npolys; ++i) + if (polyMesh.areas[i] == RC_WALKABLE_AREA) + polyMesh.flags[i] = 1; + + dtNavMeshCreateParams params; + params.verts = polyMesh.verts; + params.vertCount = polyMesh.nverts; + params.polys = polyMesh.polys; + params.polyAreas = polyMesh.areas; + params.polyFlags = polyMesh.flags; + params.polyCount = polyMesh.npolys; + params.nvp = polyMesh.nvp; + params.detailMeshes = polyMeshDetail.meshes; + params.detailVerts = polyMeshDetail.verts; + params.detailVertsCount = polyMeshDetail.nverts; + params.detailTris = polyMeshDetail.tris; + params.detailTriCount = polyMeshDetail.ntris; + params.offMeshConVerts = nullptr; + params.offMeshConRad = nullptr; + params.offMeshConDir = nullptr; + params.offMeshConAreas = nullptr; + params.offMeshConFlags = nullptr; + params.offMeshConUserID = nullptr; + params.offMeshConCount = 0; + params.walkableHeight = getHeight(settings, agentHalfExtents); + params.walkableRadius = getRadius(settings, agentHalfExtents); + params.walkableClimb = getMaxClimb(settings); + rcVcopy(params.bmin, polyMesh.bmin); + rcVcopy(params.bmax, polyMesh.bmax); + params.cs = config.cs; + params.ch = config.ch; + params.buildBvTree = true; + params.userId = 0; + params.tileX = tileX; + params.tileY = tileY; + params.tileLayer = 0; + + unsigned char* navMeshData; + int navMeshDataSize; + OPENMW_CHECK_DT_RESULT(dtCreateNavMeshData(¶ms, &navMeshData, &navMeshDataSize)); + + return NavMeshData(navMeshData, navMeshDataSize); + } + + int nextPow2(int v) + { + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return v; + } + + NavMeshPtr makeNavMeshWithMultiTiles(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, + const Settings& settings) + { + osg::Vec3f boundsMin; + osg::Vec3f boundsMax; + rcCalcBounds(recastMesh.getVertices().data(), static_cast(recastMesh.getVerticesCount()), + boundsMin.ptr(), boundsMax.ptr()); + + const auto minTilePosition = getTilePosition(settings, boundsMin); + const auto maxTilePosition = getTilePosition(settings, boundsMax); + + const auto tileWidth = maxTilePosition.x() - minTilePosition.x() + 1; + const auto tileHeight = maxTilePosition.y() - minTilePosition.y() + 1; + + // Max tiles and max polys affect how the tile IDs are caculated. + // There are 22 bits available for identifying a tile and a polygon. + const auto tileBits = std::min(static_cast(std::log2(nextPow2(tileWidth * tileHeight))), 14); + const auto polyBits = 22 - tileBits; + const auto maxTiles = 1 << tileBits; + const auto maxPolysPerTile = 1 << polyBits; + + dtNavMeshParams params; + std::fill_n(params.orig, 3, 0.0f); + params.tileWidth = getTileSize(settings); + params.tileHeight = getTileSize(settings); + params.maxTiles = maxTiles; + params.maxPolys = maxPolysPerTile; + + NavMeshPtr navMesh(dtAllocNavMesh(), &dtFreeNavMesh); + OPENMW_CHECK_DT_STATUS(navMesh->init(¶ms)); + + for (int y = minTilePosition.y(); y <= maxTilePosition.y(); ++y) + { + for (int x = minTilePosition.x(); x <= maxTilePosition.x(); ++x) + { + const auto tileBounds = makeTileBounds(settings, TilePosition(x, y)); + const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y()); + const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y()); + + auto navMeshData = makeNavMeshTileData(agentHalfExtents, recastMesh, x, y, + tileBorderMin, tileBorderMax, settings); + + if (!navMeshData.mValue) + continue; + + OPENMW_CHECK_DT_STATUS(navMesh->addTile(navMeshData.mValue.get(), navMeshData.mSize, + DT_TILE_FREE_DATA, 0, 0)); + navMeshData.mValue.release(); + } + } + + return navMesh; + } +} + +namespace DetourNavigator +{ + NavMeshPtr makeNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, const Settings& settings) + { + return makeNavMeshWithMultiTiles(agentHalfExtents, recastMesh, settings); + } +} diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp new file mode 100644 index 0000000000..a94e71c3f7 --- /dev/null +++ b/components/detournavigator/makenavmesh.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H + +#include + +#include + +class dtNavMesh; + +namespace DetourNavigator +{ + class RecastMesh; + struct Settings; + + using NavMeshPtr = std::shared_ptr; + + NavMeshPtr makeNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, const Settings& settings); +} + +#endif diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp new file mode 100644 index 0000000000..9f1d77a1cb --- /dev/null +++ b/components/detournavigator/navigator.cpp @@ -0,0 +1,42 @@ +#include "navigator.hpp" +#include "debug.hpp" +#include "settingsutils.hpp" + +namespace DetourNavigator +{ + Navigator::Navigator(const Settings& settings) + : mSettings(settings) + , mNavMeshManager(mSettings) + { + } + + void Navigator::addAgent(const osg::Vec3f& agentHalfExtents) + { + ++mAgents[agentHalfExtents]; + } + + void Navigator::removeAgent(const osg::Vec3f& agentHalfExtents) + { + const auto it = mAgents.find(agentHalfExtents); + if (it == mAgents.end() || --it->second) + return; + mAgents.erase(it); + mNavMeshManager.reset(agentHalfExtents); + } + + bool Navigator::removeObject(std::size_t id) + { + return mNavMeshManager.removeObject(id); + } + + void Navigator::update() + { + for (const auto& v : mAgents) + mNavMeshManager.update(v.first); + } + + void Navigator::wait() + { + mNavMeshManager.wait(); + } +} diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp new file mode 100644 index 0000000000..779506a9a2 --- /dev/null +++ b/components/detournavigator/navigator.hpp @@ -0,0 +1,50 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H + +#include "findsmoothpath.hpp" +#include "navmeshmanager.hpp" +#include "settings.hpp" +#include "settingsutils.hpp" + +namespace DetourNavigator +{ + class Navigator + { + public: + Navigator(const Settings& settings); + + void addAgent(const osg::Vec3f& agentHalfExtents); + + void removeAgent(const osg::Vec3f& agentHalfExtents); + + template + bool addObject(std::size_t id, const T& shape, const btTransform& transform) + { + return mNavMeshManager.addObject(id, shape, transform); + } + + bool removeObject(std::size_t id); + + void update(); + + void wait(); + + template + OutputIterator findPath(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, + const osg::Vec3f& end, OutputIterator out) const + { + const auto navMesh = mNavMeshManager.getNavMesh(agentHalfExtents); + if (!navMesh) + return out; + return findSmoothPath(*navMesh, toNavMeshCoordinates(mSettings, agentHalfExtents), + toNavMeshCoordinates(mSettings, start), toNavMeshCoordinates(mSettings, end), mSettings, out); + } + + private: + Settings mSettings; + NavMeshManager mNavMeshManager; + std::map mAgents; + }; +} + +#endif diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp new file mode 100644 index 0000000000..55e0236edb --- /dev/null +++ b/components/detournavigator/navmeshmanager.cpp @@ -0,0 +1,50 @@ +#include "navmeshmanager.hpp" +#include "debug.hpp" + +#include + +namespace DetourNavigator +{ + NavMeshManager::NavMeshManager(const Settings& settings) + : mRecastMeshManager(settings) + , mAsyncNavMeshUpdater(settings) + { + } + + bool NavMeshManager::removeObject(std::size_t id) + { + if (!mRecastMeshManager.removeObject(id)) + return false; + ++mRevision; + return true; + } + + void NavMeshManager::reset(const osg::Vec3f& agentHalfExtents) + { + mCache.erase(agentHalfExtents); + } + + void NavMeshManager::update(const osg::Vec3f& agentHalfExtents) + { + auto it = mCache.find(agentHalfExtents); + if (it == mCache.end()) + it = mCache.insert(std::make_pair(agentHalfExtents, std::make_shared(mRevision))).first; + else if (it->second->mRevision >= mRevision) + return; + it->second->mRevision = mRevision; + mAsyncNavMeshUpdater.post(agentHalfExtents, mRecastMeshManager.getMesh(), it->second); + } + + void NavMeshManager::wait() + { + mAsyncNavMeshUpdater.wait(); + } + + NavMeshConstPtr NavMeshManager::getNavMesh(const osg::Vec3f& agentHalfExtents) const + { + const auto it = mCache.find(agentHalfExtents); + if (it == mCache.end()) + return nullptr; + return it->second->mValue; + } +} diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp new file mode 100644 index 0000000000..02309c6373 --- /dev/null +++ b/components/detournavigator/navmeshmanager.hpp @@ -0,0 +1,48 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHMANAGER_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHMANAGER_H + +#include "asyncnavmeshupdater.hpp" +#include "cachedrecastmeshmanager.hpp" + +#include + +#include +#include + +class dtNavMesh; + +namespace DetourNavigator +{ + class NavMeshManager + { + public: + NavMeshManager(const Settings& settings); + + template + bool addObject(std::size_t id, const T& shape, const btTransform& transform) + { + if (!mRecastMeshManager.addObject(id, shape, transform)) + return false; + ++mRevision; + return true; + } + + bool removeObject(std::size_t id); + + void reset(const osg::Vec3f& agentHalfExtents); + + void update(const osg::Vec3f& agentHalfExtents); + + void wait(); + + NavMeshConstPtr getNavMesh(const osg::Vec3f& agentHalfExtents) const; + + private: + std::size_t mRevision = 0; + CachedRecastMeshManager mRecastMeshManager; + std::map> mCache; + AsyncNavMeshUpdater mAsyncNavMeshUpdater; + }; +} + +#endif diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp new file mode 100644 index 0000000000..eaeea92f18 --- /dev/null +++ b/components/detournavigator/recastmesh.cpp @@ -0,0 +1,12 @@ +#include "recastmesh.hpp" +#include "settings.hpp" + +namespace DetourNavigator +{ + RecastMesh::RecastMesh(std::vector indices, std::vector vertices, const Settings& settings) + : mIndices(std::move(indices)) + , mVertices(std::move(vertices)) + , mChunkyTriMesh(mVertices, mIndices, settings.mTrianglesPerChunk) + { + } +} diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp new file mode 100644 index 0000000000..35d5cb2472 --- /dev/null +++ b/components/detournavigator/recastmesh.hpp @@ -0,0 +1,50 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESH_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESH_H + +#include "chunkytrimesh.hpp" + +#include +#include + +namespace DetourNavigator +{ + struct Settings; + + class RecastMesh + { + public: + RecastMesh(std::vector indices, std::vector vertices, const Settings& settings); + + const std::vector& getIndices() const + { + return mIndices; + } + + const std::vector& getVertices() const + { + return mVertices; + } + + std::size_t getVerticesCount() const + { + return mVertices.size() / 3; + } + + std::size_t getTrianglesCount() const + { + return mIndices.size() / 3; + } + + const ChunkyTriMesh& getChunkyTriMesh() const + { + return mChunkyTriMesh; + } + + private: + std::vector mIndices; + std::vector mVertices; + ChunkyTriMesh mChunkyTriMesh; + }; +} + +#endif diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp new file mode 100644 index 0000000000..83caa52878 --- /dev/null +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -0,0 +1,72 @@ +#include "recastmeshbuilder.hpp" +#include "settings.hpp" +#include "settingsutils.hpp" + +#include + +#include +#include + +namespace +{ + osg::Vec3f makeOsgVec3f(const btVector3& value) + { + return osg::Vec3f(value.x(), value.y(), value.z()); + } +} + +namespace DetourNavigator +{ + using BulletHelpers::makeProcessTriangleCallback; + + RecastMeshBuilder::RecastMeshBuilder(const Settings& settings) + : mSettings(settings) + { + } + + void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform) + { + return addObject(shape, makeProcessTriangleCallback([&] (btVector3* triangle, int, int) + { + for (std::size_t i = 3; i > 0; --i) + addVertex(transform(triangle[i - 1])); + })); + } + + void RecastMeshBuilder::addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform) + { + return addObject(shape, makeProcessTriangleCallback([&] (btVector3* triangle, int, int) + { + for (std::size_t i = 0; i < 3; ++i) + addVertex(transform(triangle[i])); + })); + } + + std::shared_ptr RecastMeshBuilder::create() const + { + return std::make_shared(mIndices, mVertices, mSettings); + } + + void RecastMeshBuilder::reset() + { + mIndices.clear(); + mVertices.clear(); + } + + void RecastMeshBuilder::addObject(const btConcaveShape& shape, btTriangleCallback&& callback) + { + btVector3 aabbMin; + btVector3 aabbMax; + shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); + shape.processAllTriangles(&callback, aabbMin, aabbMax); + } + + void RecastMeshBuilder::addVertex(const btVector3& worldPosition) + { + const auto navMeshPosition = toNavMeshCoordinates(mSettings, makeOsgVec3f(worldPosition)); + mIndices.push_back(static_cast(mIndices.size())); + mVertices.push_back(navMeshPosition.x()); + mVertices.push_back(navMeshPosition.y()); + mVertices.push_back(navMeshPosition.z()); + } +} diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp new file mode 100644 index 0000000000..3236b63982 --- /dev/null +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -0,0 +1,38 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHBUILDER_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHBUILDER_H + +#include "recastmesh.hpp" + +class btConcaveShape; +class btHeightfieldTerrainShape; +class btTransform; +class btTriangleCallback; +class btVector3; + +namespace DetourNavigator +{ + class RecastMeshBuilder + { + public: + RecastMeshBuilder(const Settings& settings); + + void addObject(const btConcaveShape& shape, const btTransform& transform); + + void addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform); + + std::shared_ptr create() const; + + void reset(); + + private: + std::reference_wrapper mSettings; + std::vector mIndices; + std::vector mVertices; + + void addObject(const btConcaveShape& shape, btTriangleCallback&& callback); + + void addVertex(const btVector3& worldPosition); + }; +} + +#endif diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp new file mode 100644 index 0000000000..25b3bda963 --- /dev/null +++ b/components/detournavigator/recastmeshmanager.cpp @@ -0,0 +1,57 @@ +#include "recastmeshmanager.hpp" + +#include + +namespace DetourNavigator +{ + RecastMeshManager::RecastMeshManager(const Settings& settings) + : mShouldRebuild(false) + , mMeshBuilder(settings) + { + } + + bool RecastMeshManager::addObject(std::size_t id, const btHeightfieldTerrainShape& shape, const btTransform& transform) + { + if (!mObjects.insert(std::make_pair(id, Object {&shape, transform})).second) + return false; + mShouldRebuild = true; + return true; + } + + bool RecastMeshManager::addObject(std::size_t id, const btConcaveShape& shape, const btTransform& transform) + { + if (!mObjects.insert(std::make_pair(id, Object {&shape, transform})).second) + return false; + mShouldRebuild = true; + return true; + } + + bool RecastMeshManager::removeObject(std::size_t id) + { + if (!mObjects.erase(id)) + return false; + mShouldRebuild = true; + return true; + } + + std::shared_ptr RecastMeshManager::getMesh() + { + rebuild(); + return mMeshBuilder.create(); + } + + void RecastMeshManager::rebuild() + { + if (!mShouldRebuild) + return; + mMeshBuilder.reset(); + for (const auto& v : mObjects) + { + if (v.second.mShape->getShapeType() == TERRAIN_SHAPE_PROXYTYPE) + mMeshBuilder.addObject(*static_cast(v.second.mShape), v.second.mTransform); + else if (v.second.mShape->isConcave()) + mMeshBuilder.addObject(*static_cast(v.second.mShape), v.second.mTransform); + } + mShouldRebuild = false; + } +} diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp new file mode 100644 index 0000000000..12c7b391bc --- /dev/null +++ b/components/detournavigator/recastmeshmanager.hpp @@ -0,0 +1,42 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H + +#include "recastmeshbuilder.hpp" + +#include + +#include + +class btCollisionShape; + +namespace DetourNavigator +{ + class RecastMeshManager + { + public: + RecastMeshManager(const Settings& settings); + + bool addObject(std::size_t id, const btHeightfieldTerrainShape& shape, const btTransform& transform); + + bool addObject(std::size_t id, const btConcaveShape& shape, const btTransform& transform); + + bool removeObject(std::size_t id); + + std::shared_ptr getMesh(); + + private: + struct Object + { + const btCollisionShape* mShape; + btTransform mTransform; + }; + + bool mShouldRebuild; + RecastMeshBuilder mMeshBuilder; + std::unordered_map mObjects; + + void rebuild(); + }; +} + +#endif diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp new file mode 100644 index 0000000000..11e1f99904 --- /dev/null +++ b/components/detournavigator/settings.hpp @@ -0,0 +1,30 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H + +#include + +namespace DetourNavigator +{ + struct Settings + { + float mCellHeight; + float mCellSize; + float mDetailSampleDist; + float mDetailSampleMaxError; + float mMaxClimb; + float mMaxSimplificationError; + float mMaxSlope; + float mRecastScaleFactor; + int mMaxEdgeLen; + int mMaxNavMeshQueryNodes; + int mMaxVertsPerPoly; + int mRegionMergeSize; + int mRegionMinSize; + int mTileSize; + std::size_t mMaxPolygonPathSize; + std::size_t mMaxSmoothPathSize; + std::size_t mTrianglesPerChunk; + }; +} + +#endif diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp new file mode 100644 index 0000000000..d14863dca8 --- /dev/null +++ b/components/detournavigator/settingsutils.hpp @@ -0,0 +1,65 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H + +#include "settings.hpp" +#include "tileposition.hpp" +#include "tilebounds.hpp" + +#include + +#include + +namespace DetourNavigator +{ + inline float getHeight(const Settings& settings,const osg::Vec3f& agentHalfExtents) + { + return 2.0f * agentHalfExtents.z() * settings.mRecastScaleFactor; + } + + inline float getMaxClimb(const Settings& settings) + { + return settings.mMaxClimb * settings.mRecastScaleFactor; + } + + inline float getRadius(const Settings& settings, const osg::Vec3f& agentHalfExtents) + { + return agentHalfExtents.x() * settings.mRecastScaleFactor; + } + + inline osg::Vec3f toNavMeshCoordinates(const Settings& settings, osg::Vec3f position) + { + std::swap(position.y(), position.z()); + return position * settings.mRecastScaleFactor; + } + + inline osg::Vec3f fromNavMeshCoordinates(const Settings& settings, osg::Vec3f position) + { + const auto factor = 1.0f / settings.mRecastScaleFactor; + position *= factor; + std::swap(position.y(), position.z()); + return position; + } + + inline float getTileSize(const Settings& settings) + { + return settings.mTileSize * settings.mCellSize; + } + + inline TilePosition getTilePosition(const Settings& settings, const osg::Vec3f& position) + { + return TilePosition( + static_cast(std::floor(position.x() / getTileSize(settings))), + static_cast(std::floor(position.z() / getTileSize(settings))) + ); + } + + inline TileBounds makeTileBounds(const Settings& settings, const TilePosition& tilePosition) + { + return TileBounds { + osg::Vec2f(tilePosition.x(), tilePosition.y()) * getTileSize(settings), + osg::Vec2f(tilePosition.x() + 1, tilePosition.y() + 1) * getTileSize(settings), + }; + } +} + +#endif diff --git a/components/detournavigator/tilebounds.hpp b/components/detournavigator/tilebounds.hpp new file mode 100644 index 0000000000..83fe2b6296 --- /dev/null +++ b/components/detournavigator/tilebounds.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEBOUNDS_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEBOUNDS_H + +#include + +namespace DetourNavigator +{ + struct TileBounds + { + osg::Vec2f mMin; + osg::Vec2f mMax; + }; +} + +#endif diff --git a/components/detournavigator/tileposition.hpp b/components/detournavigator/tileposition.hpp new file mode 100644 index 0000000000..ad7b226b61 --- /dev/null +++ b/components/detournavigator/tileposition.hpp @@ -0,0 +1,11 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEPOSITION_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEPOSITION_H + +#include + +namespace DetourNavigator +{ + using TilePosition = osg::Vec2i; +} + +#endif diff --git a/files/settings-default.cfg b/files/settings-default.cfg index ea6f6e7b66..008d34ee07 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -530,3 +530,63 @@ companion x = 0.6 companion y = 0.27 companion w = 0.38 companion h = 0.63 + +[Navigator] + +# Scale of NavMesh coordinates to world coordinates (value > 0.0). Recastnavigation builds voxels for world geometry. +# Basically voxel size is 1 / "cell size". To reduce amount of voxels we apply scale factor, to make voxel size +# "recast scale factor" / "cell size". Default value calculates by this equation: +# sStepSizeUp * "recast scale factor" / "cell size" = 3 (max climb height should be equal to 3 voxels) +recast scale factor = 0.017647058823529415 + +# The z-axis cell size to use for fields. (value > 0.0) +# Defines voxel/grid/cell size. So their values have significant +# side effects on all parameters defined in voxel units. +# The minimum value for this parameter depends on the platform's floating point +# accuracy, with the practical minimum usually around 0.05. +# Same default value is used in RecastDemo. +cell height = 0.2 + +# The xy-plane cell size to use for fields. (value > 0.0) +# Defines voxel/grid/cell size. So their values have significant +# side effects on all parameters defined in voxel units. +# The minimum value for this parameter depends on the platform's floating point +# accuracy, with the practical minimum usually around 0.05. +# Same default value is used in RecastDemo. +cell size = 0.2 + +# Sets the sampling distance to use when generating the detail mesh. (value = 0.0 or value >= 0.9) +detail sample dist = 6.0 + +# The maximum distance the detail mesh surface should deviate from heightfield data. (value >= 0.0) +detail sample max error = 1.0 + +# The maximum distance a simplfied contour's border edges should deviate the original raw contour. (value >= 0.0) +max simplification error = 1.3 + +# The width and height of each tile. (value > 0) +tile size = 64 + +# The maximum allowed length for contour edges along the border of the mesh. (value >= 0) +max edge len = 12 + +# Maximum number of search nodes. (0 < value <= 65535) +max nav mesh query nodes = 2048 + +# The maximum number of vertices allowed for polygons generated during the contour to polygon conversion process. (value >= 3) +max verts per poly = 6 + +# Any regions with a span count smaller than this value will, if possible, be merged with larger regions. (value >= 0) +region merge size = 20 + +# The minimum number of cells allowed to form isolated island areas. (value >= 0) +region min size = 8 + +# Maximum size of path over polygons (value > 0) +max polygon path size = 1024 + +# Maximum size of smoothed path (value > 0) +max smooth path size = 1024 + +# Maximum number of triangles in each node of mesh AABB tree (value > 0) +triangles per chunk = 256 From 0c8a7295e6da58ea5c0f9bca10ca58fad6696a3a Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 22 Apr 2018 19:22:00 +0300 Subject: [PATCH 037/133] Use only player's half extents --- apps/openmw/mwworld/scene.cpp | 11 ++++++++--- apps/openmw/mwworld/worldimp.cpp | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 2626bc641c..492a22c904 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -93,7 +93,8 @@ namespace else if (const auto actor = physics.getActor(ptr)) { const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); - navigator->addAgent(actor->getHalfExtents()); + const auto playerHalfExtents = physics.getHalfExtents(MWBase::Environment::get().getWorld()->getPlayerPtr()); + navigator->addAgent(playerHalfExtents); } if (useAnim) @@ -260,12 +261,13 @@ namespace MWWorld ListAndResetObjectsVisitor visitor; (*iter)->forEach(visitor); + const auto playerHalfExtents = mPhysics->getHalfExtents(MWBase::Environment::get().getWorld()->getPlayerPtr()); for (const auto& ptr : visitor.mObjects) { if (const auto object = mPhysics->getObject(ptr)) navigator->removeObject(reinterpret_cast(object)); else if (const auto actor = mPhysics->getActor(ptr)) - navigator->removeAgent(actor->getHalfExtents()); + navigator->removeAgent(playerHalfExtents); mPhysics->remove(ptr); } @@ -676,7 +678,10 @@ namespace MWWorld if (const auto object = mPhysics->getObject(ptr)) navigator->removeObject(reinterpret_cast(object)); else if (const auto actor = mPhysics->getActor(ptr)) - navigator->removeAgent(actor->getHalfExtents()); + { + const auto playerHalfExtents = mPhysics->getHalfExtents(MWBase::Environment::get().getWorld()->getPlayerPtr()); + navigator->removeAgent(playerHalfExtents); + } mPhysics->remove(ptr); mRendering.removeObject (ptr); if (ptr.getClass().isActor()) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 322559e3bd..7d9f9559d1 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2313,6 +2313,7 @@ namespace MWWorld { // Remove the old CharacterController MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr()); + mNavigator->removeAgent(mPhysics->getHalfExtents(getPlayerPtr())); mPhysics->remove(getPlayerPtr()); mRendering->removePlayer(getPlayerPtr()); @@ -2347,6 +2348,8 @@ namespace MWWorld mPhysics->addActor(getPlayerPtr(), model); applyLoopingParticles(player); + + mNavigator->addAgent(mPhysics->getHalfExtents(getPlayerPtr())); } World::RestPermitted World::canRest () const From 430ba9d7a55fa0d7131fb18c2aadf17441364a83 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 Apr 2018 03:44:16 +0300 Subject: [PATCH 038/133] Build NavMesh tile data only for changed tiles --- .../detournavigator/asyncnavmeshupdater.cpp | 31 ++--- .../detournavigator/asyncnavmeshupdater.hpp | 14 +-- .../cachedrecastmeshmanager.cpp | 12 +- .../cachedrecastmeshmanager.hpp | 2 +- components/detournavigator/makenavmesh.cpp | 110 ++++++++++++++---- components/detournavigator/makenavmesh.hpp | 12 +- components/detournavigator/navmeshmanager.cpp | 83 +++++++++++-- components/detournavigator/navmeshmanager.hpp | 9 ++ .../detournavigator/recastmeshmanager.cpp | 14 ++- .../detournavigator/recastmeshmanager.hpp | 16 +-- 10 files changed, 231 insertions(+), 72 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 3a95f19d90..ba4bd3ab87 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -10,8 +10,7 @@ namespace DetourNavigator { AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings) - : mSettings(settings) - , mMaxRevision(0) + : mSettings(std::cref(settings)) , mShouldStop() , mThread([&] { process(); }) { @@ -28,10 +27,19 @@ namespace DetourNavigator } void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& recastMesh, - const std::shared_ptr& navMeshCacheItem) + const std::shared_ptr& navMeshCacheItem, std::set&& changedTiles) { const std::lock_guard lock(mMutex); - mJobs[agentHalfExtents] = Job {agentHalfExtents, recastMesh, navMeshCacheItem}; + const auto job = mJobs.find(agentHalfExtents); + if (job == mJobs.end() || job->second.mChangedTiles.empty()) + { + mJobs[agentHalfExtents] = Job {agentHalfExtents, recastMesh, navMeshCacheItem, std::move(changedTiles)}; + } + else + { + job->second.mRecastMesh = recastMesh; + job->second.mChangedTiles.insert(changedTiles.begin(), changedTiles.end()); + } mHasJob.notify_all(); } @@ -62,18 +70,15 @@ namespace DetourNavigator void AsyncNavMeshUpdater::processJob(const Job& job) { - log("process job for agent=", job.mAgentHalfExtents, - " revision=", job.mNavMeshCacheItem->mRevision, - " max_revision=", mMaxRevision); - - if (job.mNavMeshCacheItem->mRevision < mMaxRevision) - return; - - mMaxRevision = job.mNavMeshCacheItem->mRevision; + log("process job for agent=", job.mAgentHalfExtents); const auto start = std::chrono::steady_clock::now(); - job.mNavMeshCacheItem->mValue = makeNavMesh(job.mAgentHalfExtents, *job.mRecastMesh, mSettings); + if (job.mNavMeshCacheItem->mValue && !job.mChangedTiles.empty()) + updateNavMesh(job.mAgentHalfExtents, *job.mRecastMesh, job.mChangedTiles, mSettings, + *job.mNavMeshCacheItem->mValue); + else + job.mNavMeshCacheItem->mValue = makeNavMesh(job.mAgentHalfExtents, *job.mRecastMesh, mSettings); const auto finish = std::chrono::steady_clock::now(); diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index cdcc3432c9..ec8f0e9de8 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_ASYNCNAVMESHUPDATER_H #include "recastmesh.hpp" +#include "tileposition.hpp" #include @@ -12,22 +13,19 @@ #include #include #include +#include #include class dtNavMesh; namespace DetourNavigator { - using NavMeshConstPtr = std::shared_ptr; + using NavMeshPtr = std::shared_ptr; struct NavMeshCacheItem { - NavMeshConstPtr mValue = nullptr; + NavMeshPtr mValue; std::size_t mRevision; - - NavMeshCacheItem(std::size_t mRevision) - : mRevision(mRevision) - {} }; class AsyncNavMeshUpdater @@ -37,7 +35,7 @@ namespace DetourNavigator ~AsyncNavMeshUpdater(); void post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& recastMesh, - const std::shared_ptr& navMeshCacheItem); + const std::shared_ptr& mNavMeshCacheItem, std::set&& changedTiles); void wait(); @@ -47,12 +45,12 @@ namespace DetourNavigator osg::Vec3f mAgentHalfExtents; std::shared_ptr mRecastMesh; std::shared_ptr mNavMeshCacheItem; + std::set mChangedTiles; }; using Jobs = std::map; std::reference_wrapper mSettings; - std::atomic_size_t mMaxRevision; std::atomic_bool mShouldStop; std::mutex mMutex; std::condition_variable mHasJob; diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index 1267320380..0fcaf116e0 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -8,12 +8,12 @@ namespace DetourNavigator { } - bool CachedRecastMeshManager::removeObject(std::size_t id) + boost::optional CachedRecastMeshManager::removeObject(std::size_t id) { - if (!mImpl.removeObject(id)) - return false; - mCached.reset(); - return true; + const auto object = mImpl.removeObject(id); + if (object) + mCached.reset(); + return object; } std::shared_ptr CachedRecastMeshManager::getMesh() @@ -21,5 +21,5 @@ namespace DetourNavigator if (!mCached) mCached = mImpl.getMesh(); return mCached; - } +} } diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index 3cfe9a56f5..4be6fb1343 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -21,7 +21,7 @@ namespace DetourNavigator return true; } - bool removeObject(std::size_t id); + boost::optional removeObject(std::size_t id); std::shared_ptr getMesh(); diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index ef9b063747..cac6d0952b 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -5,13 +5,16 @@ #include "recastmesh.hpp" #include "settings.hpp" #include "settingsutils.hpp" +#include "debug.hpp" #include #include #include #include -#include +#include +#include +#include namespace { @@ -211,22 +214,57 @@ namespace return NavMeshData(navMeshData, navMeshDataSize); } +} - int nextPow2(int v) +namespace DetourNavigator +{ + NavMeshPtr makeEmptyNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, + const Settings& settings) { - v--; - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - v++; - return v; + log("build empty NavMesh:", + " agentHeight=", std::setprecision(std::numeric_limits::max_exponent10), + getHeight(settings, agentHalfExtents), + " agentMaxClimb=", std::setprecision(std::numeric_limits::max_exponent10), + getMaxClimb(settings), + " agentRadius=", std::setprecision(std::numeric_limits::max_exponent10), + getRadius(settings, agentHalfExtents)); + + osg::Vec3f boundsMin; + osg::Vec3f boundsMax; + rcCalcBounds(recastMesh.getVertices().data(), static_cast(recastMesh.getVerticesCount()), + boundsMin.ptr(), boundsMax.ptr()); + + // Max tiles and max polys affect how the tile IDs are caculated. + // There are 22 bits available for identifying a tile and a polygon. + const auto tileBits = 10; + const auto polyBits = 22 - tileBits; + const auto maxTiles = 1 << tileBits; + const auto maxPolysPerTile = 1 << polyBits; + + dtNavMeshParams params; + rcVcopy(params.orig, boundsMin.ptr()); + params.tileWidth = settings.mTileSize * settings.mCellSize; + params.tileHeight = settings.mTileSize * settings.mCellSize; + params.maxTiles = maxTiles; + params.maxPolys = maxPolysPerTile; + + NavMeshPtr navMesh(dtAllocNavMesh(), &dtFreeNavMesh); + OPENMW_CHECK_DT_STATUS(navMesh->init(¶ms)); + + return navMesh; } - NavMeshPtr makeNavMeshWithMultiTiles(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, + NavMeshPtr makeNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, const Settings& settings) { + log("build NavMesh with mutiple tiles:", + " agentHeight=", std::setprecision(std::numeric_limits::max_exponent10), + getHeight(settings, agentHalfExtents), + " agentMaxClimb=", std::setprecision(std::numeric_limits::max_exponent10), + getMaxClimb(settings), + " agentRadius=", std::setprecision(std::numeric_limits::max_exponent10), + getRadius(settings, agentHalfExtents)); + osg::Vec3f boundsMin; osg::Vec3f boundsMax; rcCalcBounds(recastMesh.getVertices().data(), static_cast(recastMesh.getVerticesCount()), @@ -235,12 +273,9 @@ namespace const auto minTilePosition = getTilePosition(settings, boundsMin); const auto maxTilePosition = getTilePosition(settings, boundsMax); - const auto tileWidth = maxTilePosition.x() - minTilePosition.x() + 1; - const auto tileHeight = maxTilePosition.y() - minTilePosition.y() + 1; - // Max tiles and max polys affect how the tile IDs are caculated. // There are 22 bits available for identifying a tile and a polygon. - const auto tileBits = std::min(static_cast(std::log2(nextPow2(tileWidth * tileHeight))), 14); + const auto tileBits = 10; const auto polyBits = 22 - tileBits; const auto maxTiles = 1 << tileBits; const auto maxPolysPerTile = 1 << polyBits; @@ -277,12 +312,47 @@ namespace return navMesh; } -} -namespace DetourNavigator -{ - NavMeshPtr makeNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, const Settings& settings) + void updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, + const std::set& changedTiles, const Settings& settings, dtNavMesh& navMesh) { - return makeNavMeshWithMultiTiles(agentHalfExtents, recastMesh, settings); + log("update NavMesh with mutiple tiles:", + " agentHeight=", std::setprecision(std::numeric_limits::max_exponent10), + getHeight(settings, agentHalfExtents), + " agentMaxClimb=", std::setprecision(std::numeric_limits::max_exponent10), + getMaxClimb(settings), + " agentRadius=", std::setprecision(std::numeric_limits::max_exponent10), + getRadius(settings, agentHalfExtents), + " changedTiles.size()=", changedTiles.size()); + + osg::Vec3f boundsMin; + osg::Vec3f boundsMax; + rcCalcBounds(recastMesh.getVertices().data(), int(recastMesh.getVerticesCount()), + boundsMin.ptr(), boundsMax.ptr()); + + const auto& params = *navMesh.getParams(); + const osg::Vec3f origin(params.orig[0], params.orig[1], params.orig[2]); + + for (const auto& tilePosition : changedTiles) + { + const auto x = tilePosition.x(); + const auto y = tilePosition.y(); + + navMesh.removeTile(navMesh.getTileRefAt(x, y, 0), nullptr, nullptr); + + const auto tileBounds = makeTileBounds(settings, tilePosition); + const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y()); + const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y()); + + auto navMeshData = makeNavMeshTileData(agentHalfExtents, recastMesh, x, y, + tileBorderMin, tileBorderMax, settings); + + if (!navMeshData.mValue) + continue; + + OPENMW_CHECK_DT_STATUS(navMesh.addTile(navMeshData.mValue.get(), navMeshData.mSize, + DT_TILE_FREE_DATA, 0, 0)); + navMeshData.mValue.release(); + } } } diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index a94e71c3f7..96521608bb 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -1,9 +1,12 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H +#include "tileposition.hpp" + #include #include +#include class dtNavMesh; @@ -14,7 +17,14 @@ namespace DetourNavigator using NavMeshPtr = std::shared_ptr; - NavMeshPtr makeNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, const Settings& settings); + NavMeshPtr makeEmptyNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, + const Settings& settings); + + NavMeshPtr makeNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, + const Settings& settings); + + void updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, + const std::set& changedTiles, const Settings& settings, dtNavMesh& navMesh); } #endif diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 55e0236edb..d423f21206 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -1,21 +1,29 @@ #include "navmeshmanager.hpp" #include "debug.hpp" +#include "makenavmesh.hpp" +#include "settings.hpp" + +#include + +#include #include namespace DetourNavigator { NavMeshManager::NavMeshManager(const Settings& settings) - : mRecastMeshManager(settings) + : mSettings(settings) + , mRecastMeshManager(settings) , mAsyncNavMeshUpdater(settings) - { - } + {} bool NavMeshManager::removeObject(std::size_t id) { - if (!mRecastMeshManager.removeObject(id)) + const auto object = mRecastMeshManager.removeObject(id); + if (!object) return false; ++mRevision; + addChangedTiles(*object->mShape, object->mTransform); return true; } @@ -26,13 +34,28 @@ namespace DetourNavigator void NavMeshManager::update(const osg::Vec3f& agentHalfExtents) { - auto it = mCache.find(agentHalfExtents); - if (it == mCache.end()) - it = mCache.insert(std::make_pair(agentHalfExtents, std::make_shared(mRevision))).first; - else if (it->second->mRevision >= mRevision) + auto cached = mCache.find(agentHalfExtents); + if (cached == mCache.end()) + cached = mCache.insert(std::make_pair(agentHalfExtents, + std::make_shared(NavMeshCacheItem { + makeEmptyNavMesh(agentHalfExtents, *mRecastMeshManager.getMesh(), mSettings), + mRevision + }))).first; + else if (cached->second->mRevision >= mRevision) return; - it->second->mRevision = mRevision; - mAsyncNavMeshUpdater.post(agentHalfExtents, mRecastMeshManager.getMesh(), it->second); + cached->second->mRevision = mRevision; + const auto changedTiles = mChangedTiles.find(agentHalfExtents); + if (changedTiles == mChangedTiles.end()) + { + mAsyncNavMeshUpdater.post(agentHalfExtents, mRecastMeshManager.getMesh(), cached->second, + std::set()); + } + else + { + mAsyncNavMeshUpdater.post(agentHalfExtents, mRecastMeshManager.getMesh(), cached->second, + std::move(changedTiles->second)); + mChangedTiles.erase(changedTiles); + } } void NavMeshManager::wait() @@ -47,4 +70,44 @@ namespace DetourNavigator return nullptr; return it->second->mValue; } + + void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform) + { + btVector3 aabbMin; + btVector3 aabbMax; + shape.getAabb(transform, aabbMin, aabbMax); + osg::Vec3f min(aabbMin.x(), aabbMin.z(), aabbMin.y()); + osg::Vec3f max(aabbMax.x(), aabbMax.z(), aabbMax.y()); + min *= mSettings.mRecastScaleFactor; + max *= mSettings.mRecastScaleFactor; + + for (auto& v : mCache) + { + if (const auto& item = v.second) + { + if (const auto& navMesh = item->mValue) + { + auto& changedTiles = mChangedTiles[v.first]; + + int minTileX; + int minTileY; + navMesh->calcTileLoc(min.ptr(), &minTileX, &minTileY); + + int maxTileX; + int maxTileY; + navMesh->calcTileLoc(max.ptr(), &maxTileX, &maxTileY); + + if (minTileX > maxTileX) + std::swap(minTileX, maxTileX); + + if (minTileY > maxTileY) + std::swap(minTileY, maxTileY); + + for (int tileX = minTileX; tileX <= maxTileX; ++tileX) + for (int tileY = minTileY; tileY <= maxTileY; ++tileY) + changedTiles.insert(TilePosition {tileX, tileY}); + } + } + } + } } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 02309c6373..40440bafa5 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -4,6 +4,8 @@ #include "asyncnavmeshupdater.hpp" #include "cachedrecastmeshmanager.hpp" +#include + #include #include @@ -13,6 +15,8 @@ class dtNavMesh; namespace DetourNavigator { + using NavMeshConstPtr = std::shared_ptr; + class NavMeshManager { public: @@ -24,6 +28,7 @@ namespace DetourNavigator if (!mRecastMeshManager.addObject(id, shape, transform)) return false; ++mRevision; + addChangedTiles(shape, transform); return true; } @@ -39,9 +44,13 @@ namespace DetourNavigator private: std::size_t mRevision = 0; + const Settings& mSettings; CachedRecastMeshManager mRecastMeshManager; std::map> mCache; + std::map> mChangedTiles; AsyncNavMeshUpdater mAsyncNavMeshUpdater; + + void addChangedTiles(const btCollisionShape& shape, const btTransform& transform); }; } diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 25b3bda963..8568c6e148 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -7,8 +7,7 @@ namespace DetourNavigator RecastMeshManager::RecastMeshManager(const Settings& settings) : mShouldRebuild(false) , mMeshBuilder(settings) - { - } + {} bool RecastMeshManager::addObject(std::size_t id, const btHeightfieldTerrainShape& shape, const btTransform& transform) { @@ -26,12 +25,15 @@ namespace DetourNavigator return true; } - bool RecastMeshManager::removeObject(std::size_t id) + boost::optional RecastMeshManager::removeObject(std::size_t id) { - if (!mObjects.erase(id)) - return false; + const auto object = mObjects.find(id); + if (object == mObjects.end()) + return boost::none; + const auto result = object->second; + mObjects.erase(object); mShouldRebuild = true; - return true; + return result; } std::shared_ptr RecastMeshManager::getMesh() diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index 12c7b391bc..b4a57ffe0b 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -5,6 +5,8 @@ #include +#include + #include class btCollisionShape; @@ -14,23 +16,23 @@ namespace DetourNavigator class RecastMeshManager { public: + struct Object + { + const btCollisionShape* mShape; + btTransform mTransform; + }; + RecastMeshManager(const Settings& settings); bool addObject(std::size_t id, const btHeightfieldTerrainShape& shape, const btTransform& transform); bool addObject(std::size_t id, const btConcaveShape& shape, const btTransform& transform); - bool removeObject(std::size_t id); + boost::optional removeObject(std::size_t id); std::shared_ptr getMesh(); private: - struct Object - { - const btCollisionShape* mShape; - btTransform mTransform; - }; - bool mShouldRebuild; RecastMeshBuilder mMeshBuilder; std::unordered_map mObjects; From 0c8db84962f8c8b133f6fe26213eeb15417aba16 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 Apr 2018 16:54:10 +0300 Subject: [PATCH 039/133] Load cells in order from nearest to player to furthest --- apps/openmw/mwworld/scene.cpp | 70 +++++++++++++++++++++++------------ apps/openmw/mwworld/scene.hpp | 2 +- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 492a22c904..c01478d4c4 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -201,6 +201,11 @@ namespace } }; + int getCellPositionDistanceToOrigin(const std::pair& cellPosition) + { + return std::abs(cellPosition.first) + std::abs(cellPosition.second); + } + } @@ -398,7 +403,7 @@ namespace MWWorld } } - void Scene::changeCellGrid (int X, int Y, bool changeEvent) + void Scene::changeCellGrid (int playerCellX, int playerCellY, bool changeEvent) { Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -411,8 +416,8 @@ namespace MWWorld { if ((*active)->getCell()->isExterior()) { - if (std::abs (X-(*active)->getCell()->getGridX())<=mHalfGridSize && - std::abs (Y-(*active)->getCell()->getGridY())<=mHalfGridSize) + if (std::abs (playerCellX-(*active)->getCell()->getGridX())<=mHalfGridSize && + std::abs (playerCellY-(*active)->getCell()->getGridY())<=mHalfGridSize) { // keep cells within the new grid ++active; @@ -423,10 +428,11 @@ namespace MWWorld } std::size_t refsToLoad = 0; + std::vector> cellsPositionsToLoad; // get the number of refs to load - for (int x=X-mHalfGridSize; x<=X+mHalfGridSize; ++x) + for (int x = playerCellX - mHalfGridSize; x <= playerCellX + mHalfGridSize; ++x) { - for (int y=Y-mHalfGridSize; y<=Y+mHalfGridSize; ++y) + for (int y = playerCellY - mHalfGridSize; y <= playerCellY + mHalfGridSize; ++y) { CellStoreCollection::iterator iter = mActiveCells.begin(); @@ -442,40 +448,58 @@ namespace MWWorld } if (iter==mActiveCells.end()) + { refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); + cellsPositionsToLoad.push_back(std::make_pair(x, y)); + } } } loadingListener->setProgressRange(refsToLoad); + const auto getDistanceToPlayerCell = [&] (const std::pair& cellPosition) + { + return std::abs(cellPosition.first - playerCellX) + std::abs(cellPosition.second - playerCellY); + }; + + const auto getCellPositionPriority = [&] (const std::pair& cellPosition) + { + return std::make_pair(getDistanceToPlayerCell(cellPosition), getCellPositionDistanceToOrigin(cellPosition)); + }; + + std::sort(cellsPositionsToLoad.begin(), cellsPositionsToLoad.end(), + [&] (const std::pair& lhs, const std::pair& rhs) { + return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); + }); + // Load cells - for (int x=X-mHalfGridSize; x<=X+mHalfGridSize; ++x) + for (const auto& cellPosition : cellsPositionsToLoad) { - for (int y=Y-mHalfGridSize; y<=Y+mHalfGridSize; ++y) - { - CellStoreCollection::iterator iter = mActiveCells.begin(); + const auto x = cellPosition.first; + const auto y = cellPosition.second; - while (iter!=mActiveCells.end()) - { - assert ((*iter)->getCell()->isExterior()); + CellStoreCollection::iterator iter = mActiveCells.begin(); - if (x==(*iter)->getCell()->getGridX() && - y==(*iter)->getCell()->getGridY()) - break; + while (iter != mActiveCells.end()) + { + assert ((*iter)->getCell()->isExterior()); - ++iter; - } + if (x == (*iter)->getCell()->getGridX() && + y == (*iter)->getCell()->getGridY()) + break; - if (iter==mActiveCells.end()) - { - CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); + ++iter; + } - loadCell (cell, loadingListener, changeEvent); - } + if (iter == mActiveCells.end()) + { + CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); + + loadCell (cell, loadingListener, changeEvent); } } - CellStore* current = MWBase::Environment::get().getWorld()->getExterior(X,Y); + CellStore* current = MWBase::Environment::get().getWorld()->getExterior(playerCellX, playerCellY); MWBase::Environment::get().getWindowManager()->changeCell(current); if (changeEvent) diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index e2fac64383..e1144d97a2 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -74,7 +74,7 @@ namespace MWWorld void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener); // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center - void changeCellGrid (int X, int Y, bool changeEvent = true); + void changeCellGrid (int playerCellX, int playerCellY, bool changeEvent = true); void getGridCenter(int& cellX, int& cellY); From 6d233ae8686f920ff1a8c6d12918d75727ae06e2 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 Apr 2018 17:41:24 +0300 Subject: [PATCH 040/133] Option in settings to enable/disable detour navigator debug log --- apps/openmw/mwworld/worldimp.cpp | 2 ++ components/detournavigator/debug.hpp | 36 +++++++++++++++++++++- components/detournavigator/makenavmesh.cpp | 5 +-- files/settings-default.cfg | 3 ++ 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 7d9f9559d1..f9c2019a98 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -20,6 +20,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" @@ -183,6 +184,7 @@ namespace MWWorld navigatorSettings.mMaxPolygonPathSize = static_cast(Settings::Manager::getInt("max polygon path size", "Navigator")); navigatorSettings.mMaxSmoothPathSize = static_cast(Settings::Manager::getInt("max smooth path size", "Navigator")); navigatorSettings.mTrianglesPerChunk = static_cast(Settings::Manager::getInt("triangles per chunk", "Navigator")); + DetourNavigator::Log::instance().setEnabled(Settings::Manager::getBool("enable log", "Navigator")); mNavigator.reset(new DetourNavigator::Navigator(navigatorSettings)); mRendering->preloadCommonAssets(); diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp index c9f25ca496..59bfb73d44 100644 --- a/components/detournavigator/debug.hpp +++ b/components/detournavigator/debug.hpp @@ -39,6 +39,37 @@ namespace DetourNavigator void writeToFile(const dtNavMesh& navMesh, const std::string& revision); #endif + class Log + { + public: + Log() : mEnabled(false) {} + + void setEnabled(bool value) + { + mEnabled = value; + } + + bool isEnabled() const + { + return mEnabled; + } + + void write(const std::string& text) + { + if (mEnabled) + std::cout << text; + } + + static Log& instance() + { + static Log value; + return value; + } + + private: + bool mEnabled; + }; + inline void write(std::ostream& stream) { stream << '\n'; @@ -54,9 +85,12 @@ namespace DetourNavigator template void log(Ts&& ... values) { + auto& log = Log::instance(); + if (!log.isEnabled()) + return; std::ostringstream stream; write(stream, std::forward(values) ...); - std::cout << stream.str(); + log.write(stream.str()); } } diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index cac6d0952b..eb9cd6c5a3 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -1,5 +1,6 @@ #include "makenavmesh.hpp" #include "chunkytrimesh.hpp" +#include "debug.hpp" #include "dtstatus.hpp" #include "exceptions.hpp" #include "recastmesh.hpp" @@ -12,10 +13,6 @@ #include #include -#include -#include -#include - namespace { using namespace DetourNavigator; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 008d34ee07..7bc9ac6c06 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -590,3 +590,6 @@ max smooth path size = 1024 # Maximum number of triangles in each node of mesh AABB tree (value > 0) triangles per chunk = 256 + +# Enable debug log (true, false) +enable log = false From 41caca24eeeeeee090fb50b0a01db6a89b3d3b9e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 Apr 2018 18:09:43 +0300 Subject: [PATCH 041/133] Options to enable/disable write recast mesh and nav mesh into file --- apps/openmw/mwworld/worldimp.cpp | 4 + .../detournavigator/navigator.cpp | 2 + .../detournavigator/asyncnavmeshupdater.cpp | 14 ++- components/detournavigator/debug.cpp | 105 ++++-------------- components/detournavigator/debug.hpp | 20 +--- components/detournavigator/makenavmesh.cpp | 3 + components/detournavigator/recastmesh.cpp | 1 + .../detournavigator/recastmeshbuilder.cpp | 1 + components/detournavigator/settings.hpp | 6 +- files/settings-default.cfg | 4 + 10 files changed, 53 insertions(+), 107 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f9c2019a98..b07d96585f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -184,6 +184,10 @@ namespace MWWorld navigatorSettings.mMaxPolygonPathSize = static_cast(Settings::Manager::getInt("max polygon path size", "Navigator")); navigatorSettings.mMaxSmoothPathSize = static_cast(Settings::Manager::getInt("max smooth path size", "Navigator")); navigatorSettings.mTrianglesPerChunk = static_cast(Settings::Manager::getInt("triangles per chunk", "Navigator")); + navigatorSettings.mEnableWriteRecastMeshToFile = Settings::Manager::getBool("enable write recast mesh to file", "Navigator"); + navigatorSettings.mEnableWriteNavMeshToFile = Settings::Manager::getBool("enable write nav mesh to file", "Navigator"); + navigatorSettings.mRecastMeshPathPrefix = Settings::Manager::getString("recast mesh path prefix", "Navigator"); + navigatorSettings.mNavMeshPathPrefix = Settings::Manager::getString("nav mesh path prefix", "Navigator"); DetourNavigator::Log::instance().setEnabled(Settings::Manager::getBool("enable log", "Navigator")); mNavigator.reset(new DetourNavigator::Navigator(navigatorSettings)); diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 1a317da843..0e30dd9ce2 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -33,6 +33,8 @@ namespace , mEnd(215, -215, 1) , mOut(mPath) { + mSettings.mEnableWriteRecastMeshToFile = false; + mSettings.mEnableWriteNavMeshToFile = false; mSettings.mCellHeight = 0.2f; mSettings.mCellSize = 0.2f; mSettings.mDetailSampleDist = 6; diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index ba4bd3ab87..12abe63995 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -108,11 +108,13 @@ namespace DetourNavigator void AsyncNavMeshUpdater::writeDebugFiles(const Job& job) const { -#ifdef OPENMW_WRITE_TO_FILE - const auto revision = std::to_string((std::chrono::steady_clock::now() - - std::chrono::steady_clock::time_point()).count()); - writeToFile(*job.mRecastMesh, revision); - writeToFile(*job.mNavMeshCacheItem->mValue, revision); -#endif + std::string revision; + if (mSettings.get().mEnableWriteNavMeshToFile || mSettings.get().mEnableWriteRecastMeshToFile) + revision = std::to_string((std::chrono::steady_clock::now() + - std::chrono::steady_clock::time_point()).count()); + if (mSettings.get().mEnableWriteRecastMeshToFile) + writeToFile(*job.mRecastMesh, mSettings.get().mRecastMeshPathPrefix, revision); + if (mSettings.get().mEnableWriteNavMeshToFile) + writeToFile(*job.mNavMeshCacheItem->mValue, mSettings.get().mNavMeshPathPrefix, revision); } } diff --git a/components/detournavigator/debug.cpp b/components/detournavigator/debug.cpp index 0b783fd908..d1c6edefdb 100644 --- a/components/detournavigator/debug.cpp +++ b/components/detournavigator/debug.cpp @@ -1,62 +1,23 @@ #include "debug.hpp" - -#define OPENMW_WRITE_TO_FILE -#define OPENMW_WRITE_OBJ - -#ifdef OPENMW_WRITE_OBJ -#include "exceptions.hpp" - -#include -#endif - -#ifdef OPENMW_WRITE_TO_FILE #include "exceptions.hpp" #include "recastmesh.hpp" #include #include -#endif - -#include -#include - -namespace -{ -#ifdef OPENMW_WRITE_TO_FILE - static const int NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; //'MSET'; - static const int NAVMESHSET_VERSION = 1; - - struct NavMeshSetHeader - { - int magic; - int version; - int numTiles; - dtNavMeshParams params; - }; - - struct NavMeshTileHeader - { - dtTileRef tileRef; - int dataSize; - }; -#endif -} namespace DetourNavigator { -// Use to dump scene to load from recastnavigation demo tool -#ifdef OPENMW_WRITE_OBJ - void writeObj(const std::vector& vertices, const std::vector& indices) + void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision) { - const auto path = std::string("scene.") + std::to_string(std::time(nullptr)) + ".obj"; + const auto path = pathPrefix + "recastmesh." + revision + ".obj"; std::ofstream file(path); if (!file.is_open()) throw NavigatorException("Open file failed: " + path); file.exceptions(std::ios::failbit | std::ios::badbit); file.precision(std::numeric_limits::max_exponent10); std::size_t count = 0; - for (auto v : vertices) + for (auto v : recastMesh.getVertices()) { if (count % 3 == 0) { @@ -69,7 +30,7 @@ namespace DetourNavigator } file << '\n'; count = 0; - for (auto v : indices) + for (auto v : recastMesh.getIndices()) { if (count % 3 == 0) { @@ -82,56 +43,35 @@ namespace DetourNavigator } file << '\n'; } -#endif -#ifdef OPENMW_WRITE_TO_FILE - void writeToFile(const RecastMesh& recastMesh, const std::string& revision) + void writeToFile(const dtNavMesh& navMesh, const std::string& pathPrefix, const std::string& revision) { - const auto path = "recastmesh." + revision + ".obj"; - std::ofstream file(path); - if (!file.is_open()) - throw NavigatorException("Open file failed: " + path); - file.exceptions(std::ios::failbit | std::ios::badbit); - file.precision(std::numeric_limits::max_exponent10); - std::size_t count = 0; - for (auto v : recastMesh.getVertices()) + const int navMeshSetMagic = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; //'MSET'; + const int navMeshSetVersion = 1; + + struct NavMeshSetHeader { - if (count % 3 == 0) - { - if (count != 0) - file << '\n'; - file << 'v'; - } - file << ' ' << v; - ++count; - } - file << '\n'; - count = 0; - for (auto v : recastMesh.getIndices()) + int magic; + int version; + int numTiles; + dtNavMeshParams params; + }; + + struct NavMeshTileHeader { - if (count % 3 == 0) - { - if (count != 0) - file << '\n'; - file << 'f'; - } - file << ' ' << (v + 1); - ++count; - } - file << '\n'; - } + dtTileRef tileRef; + int dataSize; + }; - void writeToFile(const dtNavMesh& navMesh, const std::string& revision) - { - const auto path = "navmesh." + revision + ".bin"; + const auto path = pathPrefix + "navmesh." + revision + ".bin"; std::ofstream file(path, std::ios::binary); if (!file.is_open()) throw NavigatorException("Open file failed: " + path); file.exceptions(std::ios::failbit | std::ios::badbit); NavMeshSetHeader header; - header.magic = NAVMESHSET_MAGIC; - header.version = NAVMESHSET_VERSION; + header.magic = navMeshSetMagic; + header.version = navMeshSetVersion; header.numTiles = 0; for (int i = 0; i < navMesh.getMaxTiles(); ++i) { @@ -159,5 +99,4 @@ namespace DetourNavigator file.write(const_char_ptr(tile->data), tile->dataSize); } } -#endif } diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp index 59bfb73d44..2a9f851a36 100644 --- a/components/detournavigator/debug.hpp +++ b/components/detournavigator/debug.hpp @@ -11,13 +11,7 @@ #include #include -#ifdef OPENMW_WRITE_OBJ -#include -#endif - -#ifdef OPENMW_WRITE_TO_FILE class dtNavMesh; -#endif namespace DetourNavigator { @@ -26,19 +20,8 @@ namespace DetourNavigator return stream << "TileBounds {" << value.mMin << ", " << value.mMax << "}"; } -// Use to dump scene to load from recastnavigation demo tool -#ifdef OPENMW_WRITE_OBJ - void writeObj(const std::vector& vertices, const std::vector& indices); -#endif - -#ifdef OPENMW_WRITE_TO_FILE class RecastMesh; - void writeToFile(const RecastMesh& recastMesh, const std::string& revision); - - void writeToFile(const dtNavMesh& navMesh, const std::string& revision); -#endif - class Log { public: @@ -92,6 +75,9 @@ namespace DetourNavigator write(stream, std::forward(values) ...); log.write(stream.str()); } + + void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision); + void writeToFile(const dtNavMesh& navMesh, const std::string& pathPrefix, const std::string& revision); } #endif diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index eb9cd6c5a3..1c711048d3 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -13,6 +13,9 @@ #include #include +#include +#include + namespace { using namespace DetourNavigator; diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index eaeea92f18..866fdfa408 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -1,4 +1,5 @@ #include "recastmesh.hpp" +#include "chunkytrimesh.hpp" #include "settings.hpp" namespace DetourNavigator diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 83caa52878..ba9b8a3de8 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -1,4 +1,5 @@ #include "recastmeshbuilder.hpp" +#include "chunkytrimesh.hpp" #include "settings.hpp" #include "settingsutils.hpp" diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 11e1f99904..e06b8159f2 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -1,12 +1,14 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H -#include +#include namespace DetourNavigator { struct Settings { + bool mEnableWriteRecastMeshToFile; + bool mEnableWriteNavMeshToFile; float mCellHeight; float mCellSize; float mDetailSampleDist; @@ -24,6 +26,8 @@ namespace DetourNavigator std::size_t mMaxPolygonPathSize; std::size_t mMaxSmoothPathSize; std::size_t mTrianglesPerChunk; + std::string mRecastMeshPathPrefix; + std::string mNavMeshPathPrefix; }; } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 7bc9ac6c06..58213f0893 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -593,3 +593,7 @@ triangles per chunk = 256 # Enable debug log (true, false) enable log = false +enable write recast mesh to file = false +enable write nav mesh to file = false +recast mesh path prefix = +nav mesh path prefix = From d1d034a1ec55a28d8240ae66938bd07c96f08f35 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 Apr 2018 20:24:02 +0300 Subject: [PATCH 042/133] Update NavMesh one by one tile in order from nearest to player --- apps/openmw/mwworld/scene.cpp | 3 +- apps/openmw/mwworld/worldimp.cpp | 1 + .../detournavigator/navigator.cpp | 30 ++++- .../detournavigator/asyncnavmeshupdater.cpp | 44 ++++--- .../detournavigator/asyncnavmeshupdater.hpp | 19 ++- components/detournavigator/exceptions.hpp | 6 + components/detournavigator/makenavmesh.cpp | 117 ++++-------------- components/detournavigator/makenavmesh.hpp | 9 +- components/detournavigator/navigator.cpp | 5 +- components/detournavigator/navigator.hpp | 6 +- components/detournavigator/navmeshmanager.cpp | 91 ++++++++------ components/detournavigator/navmeshmanager.hpp | 13 +- components/detournavigator/sharednavmesh.hpp | 58 +++++++++ 13 files changed, 224 insertions(+), 178 deletions(-) create mode 100644 components/detournavigator/sharednavmesh.hpp diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index c01478d4c4..3aca0af325 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -363,7 +363,8 @@ namespace MWWorld else mPhysics->disableWater(); - navigator->update(); + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + navigator->update(player.getRefData().getPosition().asVec3()); if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) mRendering.configureAmbient(cell->getCell()); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b07d96585f..db9ba211b3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -19,6 +19,7 @@ #include +#include #include #include diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 0e30dd9ce2..4ac1b15acd 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -56,6 +56,32 @@ namespace } }; + TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_throw_exception) + { + EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut), InvalidArgument); + } + + TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception) + { + mNavigator->addAgent(mAgentHalfExtents); + EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut), NavigatorException); + } + + TEST_F(DetourNavigatorNavigatorTest, find_path_for_removed_agent_should_throw_exception) + { + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->removeAgent(mAgentHalfExtents); + EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut), InvalidArgument); + } + + TEST_F(DetourNavigatorNavigatorTest, add_agent_should_count_each_agent) + { + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->removeAgent(mAgentHalfExtents); + EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut), NavigatorException); + } + TEST_F(DetourNavigatorNavigatorTest, update_then_find_path_should_return_path) { const std::array heightfieldData {{ @@ -70,7 +96,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addObject(1, shape, btTransform::getIdentity()); - mNavigator->update(); + mNavigator->update(mPlayerPosition); mNavigator->wait(); mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); @@ -127,7 +153,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addObject(1, shape, btTransform::getIdentity()); mNavigator->addObject(2, shape2, btTransform::getIdentity()); - mNavigator->update(); + mNavigator->update(mPlayerPosition); mNavigator->wait(); mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 12abe63995..4897d25946 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -7,6 +7,21 @@ #include +namespace +{ + using DetourNavigator::TilePosition; + + int getDistance(const TilePosition& lhs, const TilePosition& rhs) + { + return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y()); + } + + std::pair makePriority(const TilePosition& changedTile, const TilePosition& playerTile) + { + return std::make_pair(getDistance(changedTile, playerTile), getDistance(changedTile, TilePosition {0, 0})); + } +} + namespace DetourNavigator { AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings) @@ -20,25 +35,21 @@ namespace DetourNavigator { mShouldStop = true; std::unique_lock lock(mMutex); - mJobs.clear(); + mJobs = decltype(mJobs)(); mHasJob.notify_all(); lock.unlock(); mThread.join(); } void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& recastMesh, - const std::shared_ptr& navMeshCacheItem, std::set&& changedTiles) + const std::shared_ptr& navMeshCacheItem, const TilePosition& playerTile, + const std::set& changedTiles) { const std::lock_guard lock(mMutex); - const auto job = mJobs.find(agentHalfExtents); - if (job == mJobs.end() || job->second.mChangedTiles.empty()) - { - mJobs[agentHalfExtents] = Job {agentHalfExtents, recastMesh, navMeshCacheItem, std::move(changedTiles)}; - } - else + for (const auto& changedTile : changedTiles) { - job->second.mRecastMesh = recastMesh; - job->second.mChangedTiles.insert(changedTiles.begin(), changedTiles.end()); + mJobs.push(Job {agentHalfExtents, recastMesh, navMeshCacheItem, changedTile, + makePriority(changedTile, playerTile)}); } mHasJob.notify_all(); } @@ -74,11 +85,8 @@ namespace DetourNavigator const auto start = std::chrono::steady_clock::now(); - if (job.mNavMeshCacheItem->mValue && !job.mChangedTiles.empty()) - updateNavMesh(job.mAgentHalfExtents, *job.mRecastMesh, job.mChangedTiles, mSettings, - *job.mNavMeshCacheItem->mValue); - else - job.mNavMeshCacheItem->mValue = makeNavMesh(job.mAgentHalfExtents, *job.mRecastMesh, mSettings); + updateNavMesh(job.mAgentHalfExtents, *job.mRecastMesh, job.mChangedTile, mSettings, + job.mNavMeshCacheItem->mValue); const auto finish = std::chrono::steady_clock::now(); @@ -101,8 +109,8 @@ namespace DetourNavigator return boost::none; } log("got ", mJobs.size(), " jobs"); - const auto job = mJobs.begin()->second; - mJobs.erase(mJobs.begin()); + const auto job = mJobs.top(); + mJobs.pop(); return job; } @@ -115,6 +123,6 @@ namespace DetourNavigator if (mSettings.get().mEnableWriteRecastMeshToFile) writeToFile(*job.mRecastMesh, mSettings.get().mRecastMeshPathPrefix, revision); if (mSettings.get().mEnableWriteNavMeshToFile) - writeToFile(*job.mNavMeshCacheItem->mValue, mSettings.get().mNavMeshPathPrefix, revision); + writeToFile(*job.mNavMeshCacheItem->mValue.lock(), mSettings.get().mNavMeshPathPrefix, revision); } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index ec8f0e9de8..a9432ceaf3 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_ASYNCNAVMESHUPDATER_H #include "recastmesh.hpp" +#include "sharednavmesh.hpp" #include "tileposition.hpp" #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include @@ -20,11 +22,9 @@ class dtNavMesh; namespace DetourNavigator { - using NavMeshPtr = std::shared_ptr; - struct NavMeshCacheItem { - NavMeshPtr mValue; + SharedNavMesh mValue; std::size_t mRevision; }; @@ -35,7 +35,8 @@ namespace DetourNavigator ~AsyncNavMeshUpdater(); void post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& recastMesh, - const std::shared_ptr& mNavMeshCacheItem, std::set&& changedTiles); + const std::shared_ptr& navMeshCacheItem, const TilePosition& playerTile, + const std::set& changedTiles); void wait(); @@ -45,10 +46,16 @@ namespace DetourNavigator osg::Vec3f mAgentHalfExtents; std::shared_ptr mRecastMesh; std::shared_ptr mNavMeshCacheItem; - std::set mChangedTiles; + TilePosition mChangedTile; + std::pair mPriority; + + friend inline bool operator <(const Job& lhs, const Job& rhs) + { + return lhs.mPriority > rhs.mPriority; + } }; - using Jobs = std::map; + using Jobs = std::priority_queue>; std::reference_wrapper mSettings; std::atomic_bool mShouldStop; diff --git a/components/detournavigator/exceptions.hpp b/components/detournavigator/exceptions.hpp index e547aaaf4d..fb31172eec 100644 --- a/components/detournavigator/exceptions.hpp +++ b/components/detournavigator/exceptions.hpp @@ -10,6 +10,12 @@ namespace DetourNavigator NavigatorException(const std::string& message) : std::runtime_error(message) {} NavigatorException(const char* message) : std::runtime_error(message) {} }; + + struct InvalidArgument : std::invalid_argument + { + InvalidArgument(const std::string& message) : std::invalid_argument(message) {} + InvalidArgument(const char* message) : std::invalid_argument(message) {} + }; } #endif diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 1c711048d3..b08cba2382 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -6,7 +6,7 @@ #include "recastmesh.hpp" #include "settings.hpp" #include "settingsutils.hpp" -#include "debug.hpp" +#include "sharednavmesh.hpp" #include #include @@ -218,22 +218,8 @@ namespace namespace DetourNavigator { - NavMeshPtr makeEmptyNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, - const Settings& settings) + NavMeshPtr makeEmptyNavMesh(const Settings& settings) { - log("build empty NavMesh:", - " agentHeight=", std::setprecision(std::numeric_limits::max_exponent10), - getHeight(settings, agentHalfExtents), - " agentMaxClimb=", std::setprecision(std::numeric_limits::max_exponent10), - getMaxClimb(settings), - " agentRadius=", std::setprecision(std::numeric_limits::max_exponent10), - getRadius(settings, agentHalfExtents)); - - osg::Vec3f boundsMin; - osg::Vec3f boundsMax; - rcCalcBounds(recastMesh.getVertices().data(), static_cast(recastMesh.getVerticesCount()), - boundsMin.ptr(), boundsMax.ptr()); - // Max tiles and max polys affect how the tile IDs are caculated. // There are 22 bits available for identifying a tile and a polygon. const auto tileBits = 10; @@ -242,7 +228,7 @@ namespace DetourNavigator const auto maxPolysPerTile = 1 << polyBits; dtNavMeshParams params; - rcVcopy(params.orig, boundsMin.ptr()); + std::fill_n(params.orig, 3, 0.0f); params.tileWidth = settings.mTileSize * settings.mCellSize; params.tileHeight = settings.mTileSize * settings.mCellSize; params.maxTiles = maxTiles; @@ -254,67 +240,8 @@ namespace DetourNavigator return navMesh; } - NavMeshPtr makeNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, - const Settings& settings) - { - log("build NavMesh with mutiple tiles:", - " agentHeight=", std::setprecision(std::numeric_limits::max_exponent10), - getHeight(settings, agentHalfExtents), - " agentMaxClimb=", std::setprecision(std::numeric_limits::max_exponent10), - getMaxClimb(settings), - " agentRadius=", std::setprecision(std::numeric_limits::max_exponent10), - getRadius(settings, agentHalfExtents)); - - osg::Vec3f boundsMin; - osg::Vec3f boundsMax; - rcCalcBounds(recastMesh.getVertices().data(), static_cast(recastMesh.getVerticesCount()), - boundsMin.ptr(), boundsMax.ptr()); - - const auto minTilePosition = getTilePosition(settings, boundsMin); - const auto maxTilePosition = getTilePosition(settings, boundsMax); - - // Max tiles and max polys affect how the tile IDs are caculated. - // There are 22 bits available for identifying a tile and a polygon. - const auto tileBits = 10; - const auto polyBits = 22 - tileBits; - const auto maxTiles = 1 << tileBits; - const auto maxPolysPerTile = 1 << polyBits; - - dtNavMeshParams params; - std::fill_n(params.orig, 3, 0.0f); - params.tileWidth = getTileSize(settings); - params.tileHeight = getTileSize(settings); - params.maxTiles = maxTiles; - params.maxPolys = maxPolysPerTile; - - NavMeshPtr navMesh(dtAllocNavMesh(), &dtFreeNavMesh); - OPENMW_CHECK_DT_STATUS(navMesh->init(¶ms)); - - for (int y = minTilePosition.y(); y <= maxTilePosition.y(); ++y) - { - for (int x = minTilePosition.x(); x <= maxTilePosition.x(); ++x) - { - const auto tileBounds = makeTileBounds(settings, TilePosition(x, y)); - const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y()); - const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y()); - - auto navMeshData = makeNavMeshTileData(agentHalfExtents, recastMesh, x, y, - tileBorderMin, tileBorderMax, settings); - - if (!navMeshData.mValue) - continue; - - OPENMW_CHECK_DT_STATUS(navMesh->addTile(navMeshData.mValue.get(), navMeshData.mSize, - DT_TILE_FREE_DATA, 0, 0)); - navMeshData.mValue.release(); - } - } - - return navMesh; - } - void updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, - const std::set& changedTiles, const Settings& settings, dtNavMesh& navMesh) + const TilePosition& changedTile, const Settings& settings, SharedNavMesh& navMesh) { log("update NavMesh with mutiple tiles:", " agentHeight=", std::setprecision(std::numeric_limits::max_exponent10), @@ -323,36 +250,36 @@ namespace DetourNavigator getMaxClimb(settings), " agentRadius=", std::setprecision(std::numeric_limits::max_exponent10), getRadius(settings, agentHalfExtents), - " changedTiles.size()=", changedTiles.size()); + " changedTile=", changedTile); osg::Vec3f boundsMin; osg::Vec3f boundsMax; rcCalcBounds(recastMesh.getVertices().data(), int(recastMesh.getVerticesCount()), boundsMin.ptr(), boundsMax.ptr()); - const auto& params = *navMesh.getParams(); + const auto& params = *navMesh.lock()->getParams(); const osg::Vec3f origin(params.orig[0], params.orig[1], params.orig[2]); - for (const auto& tilePosition : changedTiles) - { - const auto x = tilePosition.x(); - const auto y = tilePosition.y(); + const auto x = changedTile.x(); + const auto y = changedTile.y(); - navMesh.removeTile(navMesh.getTileRefAt(x, y, 0), nullptr, nullptr); + { + const auto locked = navMesh.lock(); + locked->removeTile(locked->getTileRefAt(x, y, 0), nullptr, nullptr); + } - const auto tileBounds = makeTileBounds(settings, tilePosition); - const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y()); - const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y()); + const auto tileBounds = makeTileBounds(settings, changedTile); + const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y()); + const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y()); - auto navMeshData = makeNavMeshTileData(agentHalfExtents, recastMesh, x, y, - tileBorderMin, tileBorderMax, settings); + auto navMeshData = makeNavMeshTileData(agentHalfExtents, recastMesh, x, y, + tileBorderMin, tileBorderMax, settings); - if (!navMeshData.mValue) - continue; + if (!navMeshData.mValue) + return; - OPENMW_CHECK_DT_STATUS(navMesh.addTile(navMeshData.mValue.get(), navMeshData.mSize, - DT_TILE_FREE_DATA, 0, 0)); - navMeshData.mValue.release(); - } + OPENMW_CHECK_DT_STATUS(navMesh.lock()->addTile(navMeshData.mValue.get(), navMeshData.mSize, + DT_TILE_FREE_DATA, 0, 0)); + navMeshData.mValue.release(); } } diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 96521608bb..0a47dd66dd 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -13,18 +13,15 @@ class dtNavMesh; namespace DetourNavigator { class RecastMesh; + class SharedNavMesh; struct Settings; using NavMeshPtr = std::shared_ptr; - NavMeshPtr makeEmptyNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, - const Settings& settings); - - NavMeshPtr makeNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, - const Settings& settings); + NavMeshPtr makeEmptyNavMesh(const Settings& settings); void updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, - const std::set& changedTiles, const Settings& settings, dtNavMesh& navMesh); + const TilePosition& changedTile, const Settings& settings, SharedNavMesh& navMesh); } #endif diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index 9f1d77a1cb..c3ec41e962 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -13,6 +13,7 @@ namespace DetourNavigator void Navigator::addAgent(const osg::Vec3f& agentHalfExtents) { ++mAgents[agentHalfExtents]; + mNavMeshManager.addAgent(agentHalfExtents); } void Navigator::removeAgent(const osg::Vec3f& agentHalfExtents) @@ -29,10 +30,10 @@ namespace DetourNavigator return mNavMeshManager.removeObject(id); } - void Navigator::update() + void Navigator::update(const osg::Vec3f& playerPosition) { for (const auto& v : mAgents) - mNavMeshManager.update(v.first); + mNavMeshManager.update(playerPosition, v.first); } void Navigator::wait() diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 779506a9a2..e48d630af0 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -25,7 +25,7 @@ namespace DetourNavigator bool removeObject(std::size_t id); - void update(); + void update(const osg::Vec3f& playerPosition); void wait(); @@ -34,9 +34,7 @@ namespace DetourNavigator const osg::Vec3f& end, OutputIterator out) const { const auto navMesh = mNavMeshManager.getNavMesh(agentHalfExtents); - if (!navMesh) - return out; - return findSmoothPath(*navMesh, toNavMeshCoordinates(mSettings, agentHalfExtents), + return findSmoothPath(*navMesh.lock(), toNavMeshCoordinates(mSettings, agentHalfExtents), toNavMeshCoordinates(mSettings, start), toNavMeshCoordinates(mSettings, end), mSettings, out); } diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index d423f21206..89d5e0809d 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -1,7 +1,9 @@ #include "navmeshmanager.hpp" #include "debug.hpp" +#include "exceptions.hpp" #include "makenavmesh.hpp" #include "settings.hpp" +#include "sharednavmesh.hpp" #include @@ -27,34 +29,39 @@ namespace DetourNavigator return true; } + void NavMeshManager::addAgent(const osg::Vec3f& agentHalfExtents) + { + auto cached = mCache.find(agentHalfExtents); + if (cached != mCache.end()) + return; + mCache.insert(std::make_pair(agentHalfExtents, + std::make_shared(NavMeshCacheItem {makeEmptyNavMesh(mSettings), mRevision})) + ); + log("cache add for agent=", agentHalfExtents); + } + void NavMeshManager::reset(const osg::Vec3f& agentHalfExtents) { mCache.erase(agentHalfExtents); } - void NavMeshManager::update(const osg::Vec3f& agentHalfExtents) + void NavMeshManager::update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents) { - auto cached = mCache.find(agentHalfExtents); - if (cached == mCache.end()) - cached = mCache.insert(std::make_pair(agentHalfExtents, - std::make_shared(NavMeshCacheItem { - makeEmptyNavMesh(agentHalfExtents, *mRecastMeshManager.getMesh(), mSettings), - mRevision - }))).first; - else if (cached->second->mRevision >= mRevision) + const auto& cached = getCached(agentHalfExtents); + if (cached->mRevision >= mRevision) return; - cached->second->mRevision = mRevision; + cached->mRevision = mRevision; const auto changedTiles = mChangedTiles.find(agentHalfExtents); - if (changedTiles == mChangedTiles.end()) + if (changedTiles != mChangedTiles.end()) { - mAsyncNavMeshUpdater.post(agentHalfExtents, mRecastMeshManager.getMesh(), cached->second, - std::set()); - } - else - { - mAsyncNavMeshUpdater.post(agentHalfExtents, mRecastMeshManager.getMesh(), cached->second, - std::move(changedTiles->second)); + TilePosition playerTile; + playerPosition *= mSettings.mRecastScaleFactor; + std::swap(playerPosition.y(), playerPosition.z()); + cached->mValue.raw()->calcTileLoc(playerPosition.ptr(), &playerTile.x(), &playerTile.y()); + mAsyncNavMeshUpdater.post(agentHalfExtents, mRecastMeshManager.getMesh(), cached, playerTile, + changedTiles->second); mChangedTiles.erase(changedTiles); + log("cache update posted for agent=", agentHalfExtents); } } @@ -63,12 +70,9 @@ namespace DetourNavigator mAsyncNavMeshUpdater.wait(); } - NavMeshConstPtr NavMeshManager::getNavMesh(const osg::Vec3f& agentHalfExtents) const + SharedNavMesh NavMeshManager::getNavMesh(const osg::Vec3f& agentHalfExtents) const { - const auto it = mCache.find(agentHalfExtents); - if (it == mCache.end()) - return nullptr; - return it->second->mValue; + return getCached(agentHalfExtents)->mValue; } void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform) @@ -85,29 +89,36 @@ namespace DetourNavigator { if (const auto& item = v.second) { - if (const auto& navMesh = item->mValue) - { - auto& changedTiles = mChangedTiles[v.first]; + auto& changedTiles = mChangedTiles[v.first]; - int minTileX; - int minTileY; - navMesh->calcTileLoc(min.ptr(), &minTileX, &minTileY); + int minTileX; + int minTileY; + item->mValue.raw()->calcTileLoc(min.ptr(), &minTileX, &minTileY); - int maxTileX; - int maxTileY; - navMesh->calcTileLoc(max.ptr(), &maxTileX, &maxTileY); + int maxTileX; + int maxTileY; + item->mValue.raw()->calcTileLoc(max.ptr(), &maxTileX, &maxTileY); - if (minTileX > maxTileX) - std::swap(minTileX, maxTileX); + if (minTileX > maxTileX) + std::swap(minTileX, maxTileX); - if (minTileY > maxTileY) - std::swap(minTileY, maxTileY); + if (minTileY > maxTileY) + std::swap(minTileY, maxTileY); - for (int tileX = minTileX; tileX <= maxTileX; ++tileX) - for (int tileY = minTileY; tileY <= maxTileY; ++tileY) - changedTiles.insert(TilePosition {tileX, tileY}); - } + for (int tileX = minTileX; tileX <= maxTileX; ++tileX) + for (int tileY = minTileY; tileY <= maxTileY; ++tileY) + changedTiles.insert(TilePosition {tileX, tileY}); } } } + + const std::shared_ptr& NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const + { + const auto cached = mCache.find(agentHalfExtents); + if (cached != mCache.end()) + return cached->second; + std::ostringstream stream; + stream << "Agent with half extents is not found: " << agentHalfExtents; + throw InvalidArgument(stream.str()); + } } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 40440bafa5..2736140f19 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -3,6 +3,9 @@ #include "asyncnavmeshupdater.hpp" #include "cachedrecastmeshmanager.hpp" +#include "sharednavmesh.hpp" + +#include #include @@ -15,8 +18,6 @@ class dtNavMesh; namespace DetourNavigator { - using NavMeshConstPtr = std::shared_ptr; - class NavMeshManager { public: @@ -34,13 +35,15 @@ namespace DetourNavigator bool removeObject(std::size_t id); + void addAgent(const osg::Vec3f& agentHalfExtents); + void reset(const osg::Vec3f& agentHalfExtents); - void update(const osg::Vec3f& agentHalfExtents); + void update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents); void wait(); - NavMeshConstPtr getNavMesh(const osg::Vec3f& agentHalfExtents) const; + SharedNavMesh getNavMesh(const osg::Vec3f& agentHalfExtents) const; private: std::size_t mRevision = 0; @@ -51,6 +54,8 @@ namespace DetourNavigator AsyncNavMeshUpdater mAsyncNavMeshUpdater; void addChangedTiles(const btCollisionShape& shape, const btTransform& transform); + + const std::shared_ptr& getCached(const osg::Vec3f& agentHalfExtents) const; }; } diff --git a/components/detournavigator/sharednavmesh.hpp b/components/detournavigator/sharednavmesh.hpp new file mode 100644 index 0000000000..101cc58666 --- /dev/null +++ b/components/detournavigator/sharednavmesh.hpp @@ -0,0 +1,58 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H + +#include +#include + +class dtNavMesh; + +namespace DetourNavigator +{ + using NavMeshPtr = std::shared_ptr; + + class LockedSharedNavMesh + { + public: + LockedSharedNavMesh(std::mutex& mutex, const NavMeshPtr& value) + : mLock(new std::lock_guard(mutex)), mValue(value) + {} + + dtNavMesh* operator ->() const + { + return mValue.get(); + } + + dtNavMesh& operator *() const + { + return *mValue; + } + + private: + std::unique_ptr> mLock; + NavMeshPtr mValue; + }; + + class SharedNavMesh + { + public: + SharedNavMesh(const NavMeshPtr& value) + : mMutex(std::make_shared()), mValue(value) + {} + + LockedSharedNavMesh lock() const + { + return LockedSharedNavMesh(*mMutex, mValue); + } + + NavMeshPtr raw() const + { + return mValue; + } + + private: + std::shared_ptr mMutex; + NavMeshPtr mValue; + }; +} + +#endif From f8909218ee4ee52943b17b97d315fc797c33e24e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 Apr 2018 22:17:04 +0300 Subject: [PATCH 043/133] Store recast mesh bounds --- components/detournavigator/makenavmesh.cpp | 6 ++---- components/detournavigator/recastmesh.cpp | 4 +++- components/detournavigator/recastmesh.hpp | 14 ++++++++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index b08cba2382..9be9ca5c30 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -252,10 +252,8 @@ namespace DetourNavigator getRadius(settings, agentHalfExtents), " changedTile=", changedTile); - osg::Vec3f boundsMin; - osg::Vec3f boundsMax; - rcCalcBounds(recastMesh.getVertices().data(), int(recastMesh.getVerticesCount()), - boundsMin.ptr(), boundsMax.ptr()); + const auto& boundsMin = recastMesh.getBoundsMin(); + const auto& boundsMax = recastMesh.getBoundsMax(); const auto& params = *navMesh.lock()->getParams(); const osg::Vec3f origin(params.orig[0], params.orig[1], params.orig[2]); diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index 866fdfa408..45c2f98e78 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -1,7 +1,8 @@ #include "recastmesh.hpp" -#include "chunkytrimesh.hpp" #include "settings.hpp" +#include + namespace DetourNavigator { RecastMesh::RecastMesh(std::vector indices, std::vector vertices, const Settings& settings) @@ -9,5 +10,6 @@ namespace DetourNavigator , mVertices(std::move(vertices)) , mChunkyTriMesh(mVertices, mIndices, settings.mTrianglesPerChunk) { + rcCalcBounds(mVertices.data(), static_cast(getVerticesCount()), mBoundsMin.ptr(), mBoundsMax.ptr()); } } diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 35d5cb2472..8dcb717e23 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -6,6 +6,8 @@ #include #include +#include + namespace DetourNavigator { struct Settings; @@ -40,10 +42,22 @@ namespace DetourNavigator return mChunkyTriMesh; } + const osg::Vec3f& getBoundsMin() const + { + return mBoundsMin; + } + + const osg::Vec3f& getBoundsMax() const + { + return mBoundsMax; + } + private: std::vector mIndices; std::vector mVertices; ChunkyTriMesh mChunkyTriMesh; + osg::Vec3f mBoundsMin; + osg::Vec3f mBoundsMax; }; } From 373adc6ec4e8235f3254f4c5059cb2374c138cbd Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 2 Apr 2018 09:55:12 +0300 Subject: [PATCH 044/133] Option to enable/disable file names revisions --- apps/openmw/mwworld/worldimp.cpp | 2 ++ .../detournavigator/navigator.cpp | 2 ++ .../detournavigator/asyncnavmeshupdater.cpp | 17 +++++++++++++---- components/detournavigator/debug.cpp | 4 ++-- components/detournavigator/settings.hpp | 2 ++ files/settings-default.cfg | 2 ++ 6 files changed, 23 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index db9ba211b3..e5a06abcc3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -189,6 +189,8 @@ namespace MWWorld navigatorSettings.mEnableWriteNavMeshToFile = Settings::Manager::getBool("enable write nav mesh to file", "Navigator"); navigatorSettings.mRecastMeshPathPrefix = Settings::Manager::getString("recast mesh path prefix", "Navigator"); navigatorSettings.mNavMeshPathPrefix = Settings::Manager::getString("nav mesh path prefix", "Navigator"); + navigatorSettings.mEnableRecastMeshFileNameRevision = Settings::Manager::getBool("enable recast mesh file name revision", "Navigator"); + navigatorSettings.mEnableNavMeshFileNameRevision = Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); DetourNavigator::Log::instance().setEnabled(Settings::Manager::getBool("enable log", "Navigator")); mNavigator.reset(new DetourNavigator::Navigator(navigatorSettings)); diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 4ac1b15acd..2bfab13a40 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -35,6 +35,8 @@ namespace { mSettings.mEnableWriteRecastMeshToFile = false; mSettings.mEnableWriteNavMeshToFile = false; + mSettings.mEnableRecastMeshFileNameRevision = false; + mSettings.mEnableNavMeshFileNameRevision = false; mSettings.mCellHeight = 0.2f; mSettings.mCellSize = 0.2f; mSettings.mDetailSampleDist = 6; diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 4897d25946..48860c33dd 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -117,12 +117,21 @@ namespace DetourNavigator void AsyncNavMeshUpdater::writeDebugFiles(const Job& job) const { std::string revision; - if (mSettings.get().mEnableWriteNavMeshToFile || mSettings.get().mEnableWriteRecastMeshToFile) - revision = std::to_string((std::chrono::steady_clock::now() + std::string recastMeshRevision; + std::string navMeshRevision; + if ((mSettings.get().mEnableWriteNavMeshToFile || mSettings.get().mEnableWriteRecastMeshToFile) + && (mSettings.get().mEnableRecastMeshFileNameRevision || mSettings.get().mEnableNavMeshFileNameRevision)) + { + revision = "." + std::to_string((std::chrono::steady_clock::now() - std::chrono::steady_clock::time_point()).count()); + if (mSettings.get().mEnableRecastMeshFileNameRevision) + recastMeshRevision = revision; + if (mSettings.get().mEnableNavMeshFileNameRevision) + navMeshRevision = revision; + } if (mSettings.get().mEnableWriteRecastMeshToFile) - writeToFile(*job.mRecastMesh, mSettings.get().mRecastMeshPathPrefix, revision); + writeToFile(*job.mRecastMesh, mSettings.get().mRecastMeshPathPrefix, recastMeshRevision); if (mSettings.get().mEnableWriteNavMeshToFile) - writeToFile(*job.mNavMeshCacheItem->mValue.lock(), mSettings.get().mNavMeshPathPrefix, revision); + writeToFile(*job.mNavMeshCacheItem->mValue.lock(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } } diff --git a/components/detournavigator/debug.cpp b/components/detournavigator/debug.cpp index d1c6edefdb..0ddf002d90 100644 --- a/components/detournavigator/debug.cpp +++ b/components/detournavigator/debug.cpp @@ -10,7 +10,7 @@ namespace DetourNavigator { void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision) { - const auto path = pathPrefix + "recastmesh." + revision + ".obj"; + const auto path = pathPrefix + "recastmesh" + revision + ".obj"; std::ofstream file(path); if (!file.is_open()) throw NavigatorException("Open file failed: " + path); @@ -63,7 +63,7 @@ namespace DetourNavigator int dataSize; }; - const auto path = pathPrefix + "navmesh." + revision + ".bin"; + const auto path = pathPrefix + "all_tiles_navmesh" + revision + ".bin"; std::ofstream file(path, std::ios::binary); if (!file.is_open()) throw NavigatorException("Open file failed: " + path); diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index e06b8159f2..6983eb4314 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -9,6 +9,8 @@ namespace DetourNavigator { bool mEnableWriteRecastMeshToFile; bool mEnableWriteNavMeshToFile; + bool mEnableRecastMeshFileNameRevision; + bool mEnableNavMeshFileNameRevision; float mCellHeight; float mCellSize; float mDetailSampleDist; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 58213f0893..8a78e745c4 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -595,5 +595,7 @@ triangles per chunk = 256 enable log = false enable write recast mesh to file = false enable write nav mesh to file = false +enable recast mesh file name revision = false +enable nav mesh file name revision = false recast mesh path prefix = nav mesh path prefix = From dd5e6a61a3202e57b199abb4a64bd802f16936d2 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 3 Apr 2018 00:04:19 +0300 Subject: [PATCH 045/133] Support btCompoundShape for RecastMesh --- apps/openmw/mwworld/scene.cpp | 10 ++++---- .../cachedrecastmeshmanager.cpp | 9 +++++++- .../cachedrecastmeshmanager.hpp | 9 +------- components/detournavigator/navigator.cpp | 5 ++++ components/detournavigator/navigator.hpp | 6 +---- components/detournavigator/navmeshmanager.cpp | 9 ++++++++ components/detournavigator/navmeshmanager.hpp | 10 +------- .../detournavigator/recastmeshbuilder.cpp | 23 ++++++++++++++++++- .../detournavigator/recastmeshbuilder.hpp | 6 +++++ .../detournavigator/recastmeshmanager.cpp | 10 +------- .../detournavigator/recastmeshmanager.hpp | 4 +--- 11 files changed, 59 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 3aca0af325..73dc632525 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -83,12 +84,9 @@ namespace if (const auto object = physics.getObject(ptr)) { - if (const auto concaveShape = dynamic_cast(object->getShapeInstance()->mCollisionShape)) - { - const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); - navigator->addObject(reinterpret_cast(object), *concaveShape, - object->getCollisionObject()->getWorldTransform()); - } + const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); + navigator->addObject(reinterpret_cast(object), *object->getCollisionObject()->getCollisionShape(), + object->getCollisionObject()->getWorldTransform()); } else if (const auto actor = physics.getActor(ptr)) { diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index 0fcaf116e0..60306ecc68 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -5,7 +5,14 @@ namespace DetourNavigator { CachedRecastMeshManager::CachedRecastMeshManager(const Settings& settings) : mImpl(settings) + {} + + bool CachedRecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) { + if (!mImpl.addObject(id, shape, transform)) + return false; + mCached.reset(); + return true; } boost::optional CachedRecastMeshManager::removeObject(std::size_t id) @@ -21,5 +28,5 @@ namespace DetourNavigator if (!mCached) mCached = mImpl.getMesh(); return mCached; -} + } } diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index 4be6fb1343..6705a26f19 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -12,14 +12,7 @@ namespace DetourNavigator public: CachedRecastMeshManager(const Settings& settings); - template - bool addObject(std::size_t id, const T& shape, const btTransform& transform) - { - if (!mImpl.addObject(id, shape, transform)) - return false; - mCached.reset(); - return true; - } + bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); boost::optional removeObject(std::size_t id); diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index c3ec41e962..fe1b931af3 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -25,6 +25,11 @@ namespace DetourNavigator mNavMeshManager.reset(agentHalfExtents); } + bool Navigator::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) + { + return mNavMeshManager.addObject(id, shape, transform); + } + bool Navigator::removeObject(std::size_t id) { return mNavMeshManager.removeObject(id); diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index e48d630af0..69db211b17 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -17,11 +17,7 @@ namespace DetourNavigator void removeAgent(const osg::Vec3f& agentHalfExtents); - template - bool addObject(std::size_t id, const T& shape, const btTransform& transform) - { - return mNavMeshManager.addObject(id, shape, transform); - } + bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); bool removeObject(std::size_t id); diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 89d5e0809d..e2aa148795 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -19,6 +19,15 @@ namespace DetourNavigator , mAsyncNavMeshUpdater(settings) {} + bool NavMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) + { + if (!mRecastMeshManager.addObject(id, shape, transform)) + return false; + ++mRevision; + addChangedTiles(shape, transform); + return true; + } + bool NavMeshManager::removeObject(std::size_t id) { const auto object = mRecastMeshManager.removeObject(id); diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 2736140f19..f3285a8db8 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -23,15 +23,7 @@ namespace DetourNavigator public: NavMeshManager(const Settings& settings); - template - bool addObject(std::size_t id, const T& shape, const btTransform& transform) - { - if (!mRecastMeshManager.addObject(id, shape, transform)) - return false; - ++mRevision; - addChangedTiles(shape, transform); - return true; - } + bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); bool removeObject(std::size_t id); diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index ba9b8a3de8..eb44dbcd52 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -1,12 +1,15 @@ #include "recastmeshbuilder.hpp" #include "chunkytrimesh.hpp" +#include "debug.hpp" #include "settings.hpp" #include "settingsutils.hpp" +#include "exceptions.hpp" #include -#include +#include #include +#include namespace { @@ -22,7 +25,25 @@ namespace DetourNavigator RecastMeshBuilder::RecastMeshBuilder(const Settings& settings) : mSettings(settings) + {} + + void RecastMeshBuilder::addObject(const btCollisionShape& shape, const btTransform& transform) + { + if (shape.isCompound()) + return addObject(static_cast(shape), transform); + else if (shape.getShapeType() == TERRAIN_SHAPE_PROXYTYPE) + return addObject(static_cast(shape), transform); + else if (shape.isConcave()) + return addObject(static_cast(shape), transform); + std::ostringstream message; + message << "Unsupported shape type: " << BroadphaseNativeTypes(shape.getShapeType()); + throw InvalidArgument(message.str()); + } + + void RecastMeshBuilder::addObject(const btCompoundShape& shape, const btTransform& transform) { + for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) + addObject(*shape.getChildShape(i), transform * shape.getChildTransform(i)); } void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform) diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index 3236b63982..5010a95e0d 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -3,6 +3,8 @@ #include "recastmesh.hpp" +class btCollisionShape; +class btCompoundShape; class btConcaveShape; class btHeightfieldTerrainShape; class btTransform; @@ -16,6 +18,10 @@ namespace DetourNavigator public: RecastMeshBuilder(const Settings& settings); + void addObject(const btCollisionShape& shape, const btTransform& transform); + + void addObject(const btCompoundShape& shape, const btTransform& transform); + void addObject(const btConcaveShape& shape, const btTransform& transform); void addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform); diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 8568c6e148..1c32ac1e77 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -9,15 +9,7 @@ namespace DetourNavigator , mMeshBuilder(settings) {} - bool RecastMeshManager::addObject(std::size_t id, const btHeightfieldTerrainShape& shape, const btTransform& transform) - { - if (!mObjects.insert(std::make_pair(id, Object {&shape, transform})).second) - return false; - mShouldRebuild = true; - return true; - } - - bool RecastMeshManager::addObject(std::size_t id, const btConcaveShape& shape, const btTransform& transform) + bool RecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) { if (!mObjects.insert(std::make_pair(id, Object {&shape, transform})).second) return false; diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index b4a57ffe0b..429430707b 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -24,9 +24,7 @@ namespace DetourNavigator RecastMeshManager(const Settings& settings); - bool addObject(std::size_t id, const btHeightfieldTerrainShape& shape, const btTransform& transform); - - bool addObject(std::size_t id, const btConcaveShape& shape, const btTransform& transform); + bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); boost::optional removeObject(std::size_t id); From 794cfc4aa3923d609c553083648df83a89c20e41 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 4 Apr 2018 01:10:43 +0300 Subject: [PATCH 046/133] Write detournavigator log to file --- components/detournavigator/debug.hpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp index 2a9f851a36..520851fbe6 100644 --- a/components/detournavigator/debug.hpp +++ b/components/detournavigator/debug.hpp @@ -6,8 +6,11 @@ #include #include +#include +#include #include #include +#include #include #include @@ -25,7 +28,11 @@ namespace DetourNavigator class Log { public: - Log() : mEnabled(false) {} + Log() + : mEnabled() + { + mFile.exceptions(std::ios::failbit | std::ios::badbit); + } void setEnabled(bool value) { @@ -40,7 +47,14 @@ namespace DetourNavigator void write(const std::string& text) { if (mEnabled) - std::cout << text; + { + const std::lock_guard lock(mMutex); + if (!mFile.is_open()) + { + mFile.open("detournavigator.log"); + } + mFile << text << std::flush; + } } static Log& instance() @@ -50,7 +64,9 @@ namespace DetourNavigator } private: - bool mEnabled; + std::mutex mMutex; + std::ofstream mFile; + std::atomic_bool mEnabled; }; inline void write(std::ostream& stream) From 1caa18bb4fbf612b0aab9eb10483113910b1fa3e Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 4 Apr 2018 03:20:48 +0300 Subject: [PATCH 047/133] Use one recast mesh for all jobs --- .../detournavigator/asyncnavmeshupdater.cpp | 34 ++++++++++++++----- .../detournavigator/asyncnavmeshupdater.hpp | 9 +++-- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 48860c33dd..d5b6d380f7 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -45,12 +45,16 @@ namespace DetourNavigator const std::shared_ptr& navMeshCacheItem, const TilePosition& playerTile, const std::set& changedTiles) { + setRecastMesh(recastMesh); + + if (changedTiles.empty()) + return; + const std::lock_guard lock(mMutex); + for (const auto& changedTile : changedTiles) - { - mJobs.push(Job {agentHalfExtents, recastMesh, navMeshCacheItem, changedTile, - makePriority(changedTile, playerTile)}); - } + mJobs.push(Job {agentHalfExtents, navMeshCacheItem, changedTile, makePriority(changedTile, playerTile)}); + mHasJob.notify_all(); } @@ -85,12 +89,14 @@ namespace DetourNavigator const auto start = std::chrono::steady_clock::now(); - updateNavMesh(job.mAgentHalfExtents, *job.mRecastMesh, job.mChangedTile, mSettings, + const auto recastMesh = getRecastMesh(); + + updateNavMesh(job.mAgentHalfExtents, *recastMesh, job.mChangedTile, mSettings, job.mNavMeshCacheItem->mValue); const auto finish = std::chrono::steady_clock::now(); - writeDebugFiles(job); + writeDebugFiles(job, *recastMesh); using FloatMs = std::chrono::duration; @@ -114,7 +120,7 @@ namespace DetourNavigator return job; } - void AsyncNavMeshUpdater::writeDebugFiles(const Job& job) const + void AsyncNavMeshUpdater::writeDebugFiles(const Job& job, const RecastMesh& recastMesh) const { std::string revision; std::string recastMeshRevision; @@ -130,8 +136,20 @@ namespace DetourNavigator navMeshRevision = revision; } if (mSettings.get().mEnableWriteRecastMeshToFile) - writeToFile(*job.mRecastMesh, mSettings.get().mRecastMeshPathPrefix, recastMeshRevision); + writeToFile(recastMesh, mSettings.get().mRecastMeshPathPrefix, recastMeshRevision); if (mSettings.get().mEnableWriteNavMeshToFile) writeToFile(*job.mNavMeshCacheItem->mValue.lock(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } + + std::shared_ptr AsyncNavMeshUpdater::getRecastMesh() + { + const std::lock_guard lock(mRecastMeshMutex); + return mRecastMesh; + } + + void AsyncNavMeshUpdater::setRecastMesh(const std::shared_ptr& value) + { + const std::lock_guard lock(mRecastMeshMutex); + mRecastMesh = value; + } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index a9432ceaf3..8a70278fb6 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -44,7 +44,6 @@ namespace DetourNavigator struct Job { osg::Vec3f mAgentHalfExtents; - std::shared_ptr mRecastMesh; std::shared_ptr mNavMeshCacheItem; TilePosition mChangedTile; std::pair mPriority; @@ -63,6 +62,8 @@ namespace DetourNavigator std::condition_variable mHasJob; std::condition_variable mDone; Jobs mJobs; + std::mutex mRecastMeshMutex; + std::shared_ptr mRecastMesh; std::thread mThread; void process() throw(); @@ -73,7 +74,11 @@ namespace DetourNavigator void notifyHasJob(); - void writeDebugFiles(const Job& job) const; + void writeDebugFiles(const Job& job, const RecastMesh& recastMesh) const; + + std::shared_ptr getRecastMesh(); + + void setRecastMesh(const std::shared_ptr& value); }; } From 70a369f70e1f298313d92ed48bc14a4020563aa9 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 7 Apr 2018 16:11:23 +0300 Subject: [PATCH 048/133] Add command to enable NavMesh render togglenavmesh or tnm --- apps/openmw/CMakeLists.txt | 4 +- apps/openmw/mwrender/navmesh.cpp | 63 +++++++++ apps/openmw/mwrender/navmesh.hpp | 39 ++++++ apps/openmw/mwrender/renderingmanager.cpp | 38 +++++- apps/openmw/mwrender/renderingmanager.hpp | 14 +- apps/openmw/mwrender/rendermode.hpp | 3 +- apps/openmw/mwscript/docs/vmformat.txt | 3 +- apps/openmw/mwscript/miscextensions.cpp | 15 +++ apps/openmw/mwworld/worldimp.cpp | 4 +- apps/openmw/mwworld/worldimp.hpp | 2 +- components/CMakeLists.txt | 4 +- components/compiler/extensions0.cpp | 2 + components/compiler/opcodes.hpp | 1 + .../detournavigator/asyncnavmeshupdater.cpp | 2 + .../detournavigator/asyncnavmeshupdater.hpp | 6 +- components/detournavigator/navigator.cpp | 10 ++ components/detournavigator/navigator.hpp | 4 + components/detournavigator/navmeshmanager.cpp | 11 +- components/detournavigator/navmeshmanager.hpp | 2 + components/sceneutil/detourdebugdraw.cpp | 123 ++++++++++++++++++ components/sceneutil/detourdebugdraw.hpp | 48 +++++++ components/sceneutil/navmesh.cpp | 22 ++++ components/sceneutil/navmesh.hpp | 23 ++++ components/sceneutil/util.cpp | 11 ++ components/sceneutil/util.hpp | 4 + 25 files changed, 436 insertions(+), 22 deletions(-) create mode 100644 apps/openmw/mwrender/navmesh.cpp create mode 100644 apps/openmw/mwrender/navmesh.hpp create mode 100644 components/sceneutil/detourdebugdraw.cpp create mode 100644 components/sceneutil/detourdebugdraw.hpp create mode 100644 components/sceneutil/navmesh.cpp create mode 100644 components/sceneutil/navmesh.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 85906b16ee..bac9634db1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -21,7 +21,7 @@ add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation - renderbin actoranimation landmanager + renderbin actoranimation landmanager navmesh ) add_openmw_dir (mwinput @@ -117,7 +117,7 @@ include_directories( ${FFmpeg_INCLUDE_DIRS} ) -find_package(RecastNavigation COMPONENTS Detour Recast REQUIRED) +find_package(RecastNavigation COMPONENTS DebugUtils Detour Recast REQUIRED) include_directories(SYSTEM ${RecastNavigation_INCLUDE_DIRS}) diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp new file mode 100644 index 0000000000..0d8cb88677 --- /dev/null +++ b/apps/openmw/mwrender/navmesh.cpp @@ -0,0 +1,63 @@ +#include "navmesh.hpp" +#include "vismask.hpp" + +#include + +#include + +namespace MWRender +{ + NavMesh::NavMesh(const osg::ref_ptr& root) + : mRootNode(root) + , mEnabled(false) + , mRevision(0) + { + } + + NavMesh::~NavMesh() + { + if (mEnabled) + disable(); + } + + bool NavMesh::toggle() + { + if (mEnabled) + disable(); + else + enable(); + + return mEnabled; + } + + void NavMesh::update(const DetourNavigator::SharedNavMesh& sharedNavMesh, std::size_t revision, + const DetourNavigator::Settings& settings) + { + if (!mEnabled || mRevision >= revision) + return; + + mRevision = revision; + if (mGroup) + mRootNode->removeChild(mGroup); + mGroup = SceneUtil::createNavMeshGroup(*sharedNavMesh.lock(), settings); + if (mGroup) + { + mGroup->setNodeMask(Mask_Debug); + mRootNode->addChild(mGroup); + } + } + + void NavMesh::enable() + { + if (mGroup) + mRootNode->addChild(mGroup); + mEnabled = true; + } + + void NavMesh::disable() + { + if (mGroup) + mRootNode->removeChild(mGroup); + mEnabled = false; + } +} diff --git a/apps/openmw/mwrender/navmesh.hpp b/apps/openmw/mwrender/navmesh.hpp new file mode 100644 index 0000000000..5ce98ee7b0 --- /dev/null +++ b/apps/openmw/mwrender/navmesh.hpp @@ -0,0 +1,39 @@ +#ifndef OPENMW_MWRENDER_NAVMESH_H +#define OPENMW_MWRENDER_NAVMESH_H + +#include + +#include + +namespace osg +{ + class Group; + class Geometry; +} + +namespace MWRender +{ + class NavMesh + { + public: + NavMesh(const osg::ref_ptr& root); + ~NavMesh(); + + bool toggle(); + + void update(const DetourNavigator::SharedNavMesh& sharedNavMesh, std::size_t revision, + const DetourNavigator::Settings& settings); + + void enable(); + + void disable(); + + private: + osg::ref_ptr mRootNode; + bool mEnabled; + std::size_t mRevision; + osg::ref_ptr mGroup; + }; +} + +#endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 476beb990b..e29f9c7d27 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -46,6 +46,8 @@ #include #include +#include + #include #include "../mwworld/cellstore.hpp" @@ -63,6 +65,7 @@ #include "water.hpp" #include "terrainstorage.hpp" #include "util.hpp" +#include "navmesh.hpp" namespace { @@ -189,13 +192,16 @@ namespace MWRender Resource::ResourceSystem* mResourceSystem; }; - RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, - const Fallback::Map* fallback, const std::string& resourcePath) + RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, + Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, + const Fallback::Map* fallback, const std::string& resourcePath, + DetourNavigator::Navigator& navigator) : mViewer(viewer) , mRootNode(rootNode) , mResourceSystem(resourceSystem) , mWorkQueue(workQueue) , mUnrefQueue(new SceneUtil::UnrefQueue) + , mNavigator(navigator) , mLandFogStart(0.f) , mLandFogEnd(std::numeric_limits::max()) , mUnderwaterFogStart(0.f) @@ -228,6 +234,7 @@ namespace MWRender mRootNode->addChild(mSceneRoot); + mNavMesh.reset(new NavMesh(mRootNode)); mPathgrid.reset(new Pathgrid(mRootNode)); mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get())); @@ -482,7 +489,7 @@ namespace MWRender { mSky->setEnabled(enabled); } - + bool RenderingManager::toggleBorders() { mBorders = !mBorders; @@ -516,6 +523,10 @@ namespace MWRender mViewer->getCamera()->setCullMask(mask); return enabled; } + else if (mode == Render_NavMesh) + { + return mNavMesh->toggle(); + } return false; } @@ -581,6 +592,19 @@ namespace MWRender mWater->update(dt); } + const auto navMeshes = mNavigator.getNavMeshes(); + if (!navMeshes.empty()) + { + try + { + mNavMesh->update(navMeshes.begin()->second->mValue, navMeshes.begin()->second->mNavMeshRevision, + mNavigator.getSettings()); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "NavMesh render update exception: " << e.what(); + } + } mCamera->update(dt, paused); osg::Vec3f focal, cameraPos; @@ -749,8 +773,8 @@ namespace MWRender osg::Vec3 directions[6] = { rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1), - osg::Vec3(0,0,-1), - osg::Vec3(-1,0,0), + osg::Vec3(0,0,-1), + osg::Vec3(-1,0,0), rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0), osg::Vec3(0,1,0), osg::Vec3(0,-1,0)}; @@ -789,7 +813,7 @@ namespace MWRender mFieldOfView = fovBackup; if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images - { + { image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType()); for (int i = 0; i < 6; ++i) @@ -797,7 +821,7 @@ namespace MWRender return true; } - + // run on GPU now: osg::ref_ptr cubeTexture (new osg::TextureCubeMap); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index b4473c3b4f..a8eae52862 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -55,6 +55,11 @@ namespace SceneUtil class UnrefQueue; } +namespace DetourNavigator +{ + class Navigator; +} + namespace MWRender { @@ -68,12 +73,15 @@ namespace MWRender class Water; class TerrainStorage; class LandManager; + class NavMesh; class RenderingManager : public MWRender::RenderingInterface { public: - RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, - const Fallback::Map* fallback, const std::string& resourcePath); + RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, + Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, + const Fallback::Map* fallback, const std::string& resourcePath, + DetourNavigator::Navigator& navigator); ~RenderingManager(); MWRender::Objects& getObjects(); @@ -235,6 +243,8 @@ namespace MWRender osg::ref_ptr mSunLight; + DetourNavigator::Navigator& mNavigator; + std::unique_ptr mNavMesh; std::unique_ptr mPathgrid; std::unique_ptr mObjects; std::unique_ptr mWater; diff --git a/apps/openmw/mwrender/rendermode.hpp b/apps/openmw/mwrender/rendermode.hpp index 2847888d1e..9f0c5a7cd8 100644 --- a/apps/openmw/mwrender/rendermode.hpp +++ b/apps/openmw/mwrender/rendermode.hpp @@ -10,7 +10,8 @@ namespace MWRender Render_Wireframe, Render_Pathgrid, Render_Water, - Render_Scene + Render_Scene, + Render_NavMesh, }; } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index fed780be72..2ce22633c1 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -455,5 +455,6 @@ op 0x2000304: Show op 0x2000305: Show, explicit op 0x2000306: OnActivate, explicit op 0x2000307: ToggleBorders, tb +op 0x2000308: ToggleNavMesh -opcodes 0x2000308-0x3ffffff unused +opcodes 0x2000309-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 7250e9fcfc..8ccb619d01 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1317,6 +1317,20 @@ namespace MWScript } }; + class OpToggleNavMesh : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + bool enabled = + MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_NavMesh); + + runtime.getContext().report (enabled ? + "Navigation Mesh Rendering -> On" : "Navigation Mesh Rendering -> Off"); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox); @@ -1417,6 +1431,7 @@ namespace MWScript interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraph, new OpShowSceneGraph); interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraphExplicit, new OpShowSceneGraph); interpreter.installSegment5 (Compiler::Misc::opcodeToggleBorders, new OpToggleBorders); + interpreter.installSegment5 (Compiler::Misc::opcodeToggleNavMesh, new OpToggleNavMesh); } } } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e5a06abcc3..442fd04502 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -164,8 +164,6 @@ namespace MWWorld mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) { mPhysics.reset(new MWPhysics::PhysicsSystem(resourceSystem, rootNode)); - mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, &mFallback, resourcePath)); - mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get())); DetourNavigator::Settings navigatorSettings; navigatorSettings.mCellHeight = Settings::Manager::getFloat("cell height", "Navigator"); @@ -194,6 +192,8 @@ namespace MWWorld DetourNavigator::Log::instance().setEnabled(Settings::Manager::getBool("enable log", "Navigator")); mNavigator.reset(new DetourNavigator::Navigator(navigatorSettings)); + mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, &mFallback, resourcePath, *mNavigator)); + mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get())); mRendering->preloadCommonAssets(); mEsm.resize(contentFiles.size()); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4d264ff7f6..4e3deffbe7 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -94,11 +94,11 @@ namespace MWWorld std::unique_ptr mPlayer; std::unique_ptr mPhysics; + std::unique_ptr mNavigator; std::unique_ptr mRendering; std::unique_ptr mWorldScene; std::unique_ptr mWeatherManager; std::shared_ptr mProjectileManager; - std::unique_ptr mNavigator; bool mGodMode; bool mScriptsEnabled; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 2b857d8111..f7aea8f379 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -51,7 +51,7 @@ add_component_dir (shader add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer - actorutil + actorutil detourdebugdraw navmesh ) add_component_dir (nif @@ -210,7 +210,7 @@ include_directories(${Bullet_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) -find_package(RecastNavigation COMPONENTS Detour Recast REQUIRED) +find_package(RecastNavigation COMPONENTS DebugUtils Detour Recast REQUIRED) include_directories(SYSTEM ${RecastNavigation_INCLUDE_DIRS}) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 54b8c03cbe..e39b97e3fc 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -320,6 +320,8 @@ namespace Compiler extensions.registerInstruction ("removefromlevitem", "ccl", opcodeRemoveFromLevItem); extensions.registerInstruction ("tb", "", opcodeToggleBorders); extensions.registerInstruction ("toggleborders", "", opcodeToggleBorders); + extensions.registerInstruction ("togglenavmesh", "", opcodeToggleNavMesh); + extensions.registerInstruction ("tnm", "", opcodeToggleNavMesh); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index c141eec682..75ef738376 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -296,6 +296,7 @@ namespace Compiler const int opcodeShowSceneGraph = 0x2002f; const int opcodeShowSceneGraphExplicit = 0x20030; const int opcodeToggleBorders = 0x2000307; + const int opcodeToggleNavMesh = 0x2000308; } namespace Sky diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index d5b6d380f7..900d699420 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -94,6 +94,8 @@ namespace DetourNavigator updateNavMesh(job.mAgentHalfExtents, *recastMesh, job.mChangedTile, mSettings, job.mNavMeshCacheItem->mValue); + ++job.mNavMeshCacheItem->mNavMeshRevision; + const auto finish = std::chrono::steady_clock::now(); writeDebugFiles(job, *recastMesh); diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 8a70278fb6..649fa9ffb0 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -25,7 +25,11 @@ namespace DetourNavigator struct NavMeshCacheItem { SharedNavMesh mValue; - std::size_t mRevision; + std::size_t mRecastMeshRevision; + std::atomic_size_t mNavMeshRevision; + + NavMeshCacheItem(const NavMeshPtr& value, std::size_t revision) + : mValue(value), mRecastMeshRevision(revision), mNavMeshRevision(0) {} }; class AsyncNavMeshUpdater diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index fe1b931af3..e2fc976a3a 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -45,4 +45,14 @@ namespace DetourNavigator { mNavMeshManager.wait(); } + + std::map> Navigator::getNavMeshes() const + { + return mNavMeshManager.getNavMeshes(); + } + + const Settings& Navigator::getSettings() const + { + return mSettings; + } } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 69db211b17..b8d77c705a 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -34,6 +34,10 @@ namespace DetourNavigator toNavMeshCoordinates(mSettings, start), toNavMeshCoordinates(mSettings, end), mSettings, out); } + std::map> getNavMeshes() const; + + const Settings& getSettings() const; + private: Settings mSettings; NavMeshManager mNavMeshManager; diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index e2aa148795..741d6666e3 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -44,7 +44,7 @@ namespace DetourNavigator if (cached != mCache.end()) return; mCache.insert(std::make_pair(agentHalfExtents, - std::make_shared(NavMeshCacheItem {makeEmptyNavMesh(mSettings), mRevision})) + std::make_shared(makeEmptyNavMesh(mSettings), mRevision)) ); log("cache add for agent=", agentHalfExtents); } @@ -57,9 +57,9 @@ namespace DetourNavigator void NavMeshManager::update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents) { const auto& cached = getCached(agentHalfExtents); - if (cached->mRevision >= mRevision) + if (cached->mRecastMeshRevision >= mRevision) return; - cached->mRevision = mRevision; + cached->mRecastMeshRevision = mRevision; const auto changedTiles = mChangedTiles.find(agentHalfExtents); if (changedTiles != mChangedTiles.end()) { @@ -84,6 +84,11 @@ namespace DetourNavigator return getCached(agentHalfExtents)->mValue; } + std::map> NavMeshManager::getNavMeshes() const + { + return mCache; + } + void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform) { btVector3 aabbMin; diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index f3285a8db8..4f7d9d24dd 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -37,6 +37,8 @@ namespace DetourNavigator SharedNavMesh getNavMesh(const osg::Vec3f& agentHalfExtents) const; + std::map> getNavMeshes() const; + private: std::size_t mRevision = 0; const Settings& mSettings; diff --git a/components/sceneutil/detourdebugdraw.cpp b/components/sceneutil/detourdebugdraw.cpp new file mode 100644 index 0000000000..f6c438e791 --- /dev/null +++ b/components/sceneutil/detourdebugdraw.cpp @@ -0,0 +1,123 @@ +#include "detourdebugdraw.hpp" +#include "util.hpp" + +#include + +#include +#include +#include + +#define OPENMW_TO_STRING(X) #X +#define OPENMW_LINE_STRING OPENMW_TO_STRING(__LINE__) + +namespace +{ + using DetourNavigator::operator<<; + + osg::PrimitiveSet::Mode toOsgPrimitiveSetMode(duDebugDrawPrimitives value) + { + switch (value) + { + case DU_DRAW_POINTS: + return osg::PrimitiveSet::POINTS; + case DU_DRAW_LINES: + return osg::PrimitiveSet::LINES; + case DU_DRAW_TRIS: + return osg::PrimitiveSet::TRIANGLES; + case DU_DRAW_QUADS: + return osg::PrimitiveSet::QUADS; + } + throw std::logic_error("Can't convert duDebugDrawPrimitives to osg::PrimitiveSet::Mode, value=" + + std::to_string(value)); + } + +} + +namespace SceneUtil +{ + DebugDraw::DebugDraw(osg::Group& group, const osg::Vec3f& shift, float recastInvertedScaleFactor) + : mGroup(group) + , mShift(shift) + , mRecastInvertedScaleFactor(recastInvertedScaleFactor) + , mDepthMask(false) + , mMode(osg::PrimitiveSet::POINTS) + , mSize(1.0f) + { + } + + void DebugDraw::depthMask(bool state) + { + mDepthMask = state; + } + + void DebugDraw::texture(bool state) + { + if (state) + throw std::logic_error("DebugDraw does not support textures (at " __FILE__ ":" OPENMW_LINE_STRING ")"); + } + + void DebugDraw::begin(duDebugDrawPrimitives prim, float size) + { + mMode = toOsgPrimitiveSetMode(prim); + mVertices = new osg::Vec3Array; + mColors = new osg::Vec4Array; + mSize = size * mRecastInvertedScaleFactor; + } + + void DebugDraw::vertex(const float* pos, unsigned color) + { + vertex(pos[0], pos[1], pos[2], color); + } + + void DebugDraw::vertex(const float x, const float y, const float z, unsigned color) + { + addVertex(osg::Vec3f(x, y, z)); + addColor(SceneUtil::colourFromRGBA(color)); + } + + void DebugDraw::vertex(const float* pos, unsigned color, const float* uv) + { + vertex(pos[0], pos[1], pos[2], color, uv[0], uv[1]); + } + + void DebugDraw::vertex(const float, const float, const float, unsigned, const float, const float) + { + throw std::logic_error("Not implemented (at " __FILE__ ":" OPENMW_LINE_STRING ")"); + } + + void DebugDraw::end() + { + osg::ref_ptr stateSet(new osg::StateSet); + stateSet->setMode(GL_BLEND, osg::StateAttribute::ON); + stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + stateSet->setMode(GL_DEPTH, (mDepthMask ? osg::StateAttribute::ON : osg::StateAttribute::OFF)); + stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + stateSet->setAttributeAndModes(new osg::LineWidth(mSize)); + stateSet->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + osg::ref_ptr geometry(new osg::Geometry); + geometry->setStateSet(stateSet); + geometry->setUseDisplayList(false); + geometry->setVertexArray(mVertices); + geometry->setColorArray(mColors, osg::Array::BIND_PER_VERTEX); + geometry->addPrimitiveSet(new osg::DrawArrays(mMode, 0, static_cast(mVertices->size()))); + + mGroup.addChild(geometry); + mColors.release(); + mVertices.release(); + } + + void DebugDraw::addVertex(osg::Vec3f&& position) + { + std::swap(position.y(), position.z()); + mVertices->push_back(position * mRecastInvertedScaleFactor + mShift); + } + + void DebugDraw::addColor(osg::Vec4f&& value) + { + mColors->push_back(value); + } +} + +#undef OPENMW_TO_STRING +#undef OPENMW_LINE_STRING diff --git a/components/sceneutil/detourdebugdraw.hpp b/components/sceneutil/detourdebugdraw.hpp new file mode 100644 index 0000000000..87f501e2b1 --- /dev/null +++ b/components/sceneutil/detourdebugdraw.hpp @@ -0,0 +1,48 @@ +#include + +#include + +namespace osg +{ + class Group; +} + +namespace SceneUtil +{ + class DebugDraw : public duDebugDraw + { + public: + DebugDraw(osg::Group& group, const osg::Vec3f& shift, float recastInvertedScaleFactor); + + void depthMask(bool state) override; + + void texture(bool state) override; + + void begin(duDebugDrawPrimitives prim, float size) override; + + void vertex(const float* pos, unsigned int color) override; + + void vertex(const float x, const float y, const float z, unsigned int color) override; + + void vertex(const float* pos, unsigned int color, const float* uv) override; + + void vertex(const float x, const float y, const float z, unsigned int color, + const float u, const float v) override; + + void end() override; + + private: + osg::Group& mGroup; + osg::Vec3f mShift; + float mRecastInvertedScaleFactor; + bool mDepthMask; + osg::PrimitiveSet::Mode mMode; + float mSize; + osg::ref_ptr mVertices; + osg::ref_ptr mColors; + + void addVertex(osg::Vec3f&& position); + + void addColor(osg::Vec4f&& value); + }; +} diff --git a/components/sceneutil/navmesh.cpp b/components/sceneutil/navmesh.cpp new file mode 100644 index 0000000000..aeb1779bd6 --- /dev/null +++ b/components/sceneutil/navmesh.cpp @@ -0,0 +1,22 @@ +#include "navmesh.hpp" +#include "detourdebugdraw.hpp" + +#include + +#include + +#include + +namespace SceneUtil +{ + osg::ref_ptr createNavMeshGroup(const dtNavMesh& navMesh, const DetourNavigator::Settings& settings) + { + const osg::ref_ptr group(new osg::Group); + DebugDraw debugDraw(*group, osg::Vec3f(0, 0, 10), 1.0f / settings.mRecastScaleFactor); + dtNavMeshQuery navMeshQuery; + navMeshQuery.init(&navMesh, settings.mMaxNavMeshQueryNodes); + duDebugDrawNavMeshWithClosedList(&debugDraw, navMesh, navMeshQuery, + DU_DRAWNAVMESH_OFFMESHCONS | DU_DRAWNAVMESH_CLOSEDLIST); + return group; + } +} diff --git a/components/sceneutil/navmesh.hpp b/components/sceneutil/navmesh.hpp new file mode 100644 index 0000000000..b255b05756 --- /dev/null +++ b/components/sceneutil/navmesh.hpp @@ -0,0 +1,23 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_NAVMESH_H +#define OPENMW_COMPONENTS_SCENEUTIL_NAVMESH_H + +#include + +class dtNavMesh; + +namespace osg +{ + class Group; +} + +namespace DetourNavigator +{ + struct Settings; +} + +namespace SceneUtil +{ + osg::ref_ptr createNavMeshGroup(const dtNavMesh& navMesh, const DetourNavigator::Settings& settings); +} + +#endif diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index eec302965b..bbeda683ae 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -42,4 +42,15 @@ osg::Vec4f colourFromRGB(unsigned int clr) return colour; } +osg::Vec4f colourFromRGBA(unsigned int value) +{ + return osg::Vec4f(makeOsgColorComponent(value, 0), makeOsgColorComponent(value, 8), + makeOsgColorComponent(value, 16), makeOsgColorComponent(value, 24)); +} + +float makeOsgColorComponent(unsigned int value, unsigned int shift) +{ + return float((value >> shift) & 0xFFu) / 255.0f; +} + } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 1090997404..156d173d28 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -15,6 +15,10 @@ namespace SceneUtil osg::Vec4f colourFromRGB (unsigned int clr); + osg::Vec4f colourFromRGBA (unsigned int value); + + float makeOsgColorComponent (unsigned int value, unsigned int shift); + } #endif From d1e71f93227f6499b908722480eec76fdeca578f Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 7 Apr 2018 23:09:42 +0300 Subject: [PATCH 049/133] Support btBoxShape for RecastMesh --- .../detournavigator/recastmeshbuilder.cpp | 128 +++++++++++++++++- components/detournavigator/chunkytrimesh.cpp | 3 +- .../detournavigator/recastmeshbuilder.cpp | 47 ++++++- .../detournavigator/recastmeshbuilder.hpp | 5 + 4 files changed, 176 insertions(+), 7 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index ff72bc3eb8..63f6aa6b4c 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -31,12 +31,17 @@ namespace } }; + TEST_F(DetourNavigatorRecastMeshBuilderTest, create_for_empty_should_throw_exception) + { + EXPECT_THROW(mBuilder.create(), std::invalid_argument); + } + TEST_F(DetourNavigatorRecastMeshBuilderTest, add_bhv_triangle_mesh_shape) { btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape shape(&mesh, true); - mBuilder.addObject(shape, btTransform::getIdentity()); + mBuilder.addObject(static_cast(shape), btTransform::getIdentity()); const auto recastMesh = mBuilder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, @@ -51,7 +56,7 @@ namespace btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape shape(&mesh, true); - mBuilder.addObject(shape, + mBuilder.addObject(static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3))); const auto recastMesh = mBuilder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ @@ -66,7 +71,7 @@ namespace { const std::array heightfieldData {{0, 0, 0, 0}}; btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); - mBuilder.addObject(shape, btTransform::getIdentity()); + mBuilder.addObject(static_cast(shape), btTransform::getIdentity()); const auto recastMesh = mBuilder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -0.5, 0, -0.5, @@ -78,4 +83,121 @@ namespace })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 3, 4, 5})); } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, add_box_shape_should_produce_12_triangles) + { + btBoxShape shape(btVector3(1, 1, 2)); + mBuilder.addObject(static_cast(shape), btTransform::getIdentity()); + const auto recastMesh = mBuilder.create(); + EXPECT_EQ(recastMesh->getVertices(), std::vector({ + 1, 2, 1, + -1, 2, 1, + 1, 2, -1, + -1, 2, -1, + 1, -2, 1, + -1, -2, 1, + 1, -2, -1, + -1, -2, -1, + })); + EXPECT_EQ(recastMesh->getIndices(), std::vector({ + 0, 2, 3, + 3, 1, 0, + 0, 4, 6, + 6, 2, 0, + 0, 1, 5, + 5, 4, 0, + 7, 5, 1, + 1, 3, 7, + 7, 3, 2, + 2, 6, 7, + 7, 6, 4, + 4, 5, 7, + })); + } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, add_compound_shape) + { + btTriangleMesh mesh1; + mesh1.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); + btBvhTriangleMeshShape triangle1(&mesh1, true); + btBoxShape box(btVector3(1, 1, 2)); + btTriangleMesh mesh2; + mesh2.addTriangle(btVector3(1, 1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); + btBvhTriangleMeshShape triangle2(&mesh2, true); + btCompoundShape shape; + shape.addChildShape(btTransform::getIdentity(), &triangle1); + shape.addChildShape(btTransform::getIdentity(), &box); + shape.addChildShape(btTransform::getIdentity(), &triangle2); + mBuilder.addObject(static_cast(shape), btTransform::getIdentity()); + const auto recastMesh = mBuilder.create(); + EXPECT_EQ(recastMesh->getVertices(), std::vector({ + 1, 0, -1, + -1, 0, 1, + -1, 0, -1, + 1, 2, 1, + -1, 2, 1, + 1, 2, -1, + -1, 2, -1, + 1, -2, 1, + -1, -2, 1, + 1, -2, -1, + -1, -2, -1, + 1, 0, -1, + -1, 0, 1, + 1, 0, 1, + })); + EXPECT_EQ(recastMesh->getIndices(), std::vector({ + 0, 1, 2, + 3, 5, 6, + 6, 4, 3, + 3, 7, 9, + 9, 5, 3, + 3, 4, 8, + 8, 7, 3, + 10, 8, 4, + 4, 6, 10, + 10, 6, 5, + 5, 9, 10, + 10, 9, 7, + 7, 8, 10, + 11, 12, 13, + })); + } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, add_transformed_compound_shape) + { + btTriangleMesh mesh; + mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); + btBvhTriangleMeshShape triangle(&mesh, true); + btCompoundShape shape; + shape.addChildShape(btTransform::getIdentity(), &triangle); + mBuilder.addObject(static_cast(shape), + btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3))); + const auto recastMesh = mBuilder.create(); + EXPECT_EQ(recastMesh->getVertices(), std::vector({ + 2, 3, 0, + 0, 3, 4, + 0, 3, 0, + })); + EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, add_transformed_compound_shape_with_transformed_bhv_triangle_shape) + { + btTriangleMesh mesh; + mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); + btBvhTriangleMeshShape triangle(&mesh, true); + btCompoundShape shape; + shape.addChildShape(btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), + &triangle); + mBuilder.addObject(static_cast(shape), + btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3))); + const auto recastMesh = mBuilder.create(); + EXPECT_EQ(recastMesh->getVertices(), std::vector({ + 3, 12, 2, + 1, 12, 10, + 1, 12, 2, + })); + EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + } } diff --git a/components/detournavigator/chunkytrimesh.cpp b/components/detournavigator/chunkytrimesh.cpp index 9402569fc1..1b7f53845b 100644 --- a/components/detournavigator/chunkytrimesh.cpp +++ b/components/detournavigator/chunkytrimesh.cpp @@ -1,4 +1,5 @@ #include "chunkytrimesh.hpp" +#include "exceptions.hpp" #include @@ -119,7 +120,7 @@ namespace DetourNavigator const auto trianglesCount = indices.size() / 3; if (trianglesCount == 0) - return; + throw InvalidArgument("ChunkyTriMesh tris should contain at least 3 values"); const auto nchunks = (trianglesCount + trisPerChunk - 1) / trisPerChunk; diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index eb44dbcd52..1e0d2dfc80 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -7,9 +7,13 @@ #include +#include #include #include #include +#include + +#include namespace { @@ -35,6 +39,8 @@ namespace DetourNavigator return addObject(static_cast(shape), transform); else if (shape.isConcave()) return addObject(static_cast(shape), transform); + else if (shape.getShapeType() == BOX_SHAPE_PROXYTYPE) + return addObject(static_cast(shape), transform); std::ostringstream message; message << "Unsupported shape type: " << BroadphaseNativeTypes(shape.getShapeType()); throw InvalidArgument(message.str()); @@ -51,7 +57,7 @@ namespace DetourNavigator return addObject(shape, makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { for (std::size_t i = 3; i > 0; --i) - addVertex(transform(triangle[i - 1])); + addTriangleVertex(transform(triangle[i - 1])); })); } @@ -60,10 +66,40 @@ namespace DetourNavigator return addObject(shape, makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { for (std::size_t i = 0; i < 3; ++i) - addVertex(transform(triangle[i])); + addTriangleVertex(transform(triangle[i])); })); } + void RecastMeshBuilder::addObject(const btBoxShape& shape, const btTransform& transform) + { + const auto indexOffset = int(mVertices.size() / 3); + + for (int vertex = 0; vertex < shape.getNumVertices(); ++vertex) + { + btVector3 position; + shape.getVertex(vertex, position); + addVertex(transform(position)); + } + + static const std::array indices {{ + 0, 2, 3, + 3, 1, 0, + 0, 4, 6, + 6, 2, 0, + 0, 1, 5, + 5, 4, 0, + 7, 5, 1, + 1, 3, 7, + 7, 3, 2, + 2, 6, 7, + 7, 6, 4, + 4, 5, 7, + }}; + + std::transform(indices.begin(), indices.end(), std::back_inserter(mIndices), + [&] (int index) { return index + indexOffset; }); + } + std::shared_ptr RecastMeshBuilder::create() const { return std::make_shared(mIndices, mVertices, mSettings); @@ -83,10 +119,15 @@ namespace DetourNavigator shape.processAllTriangles(&callback, aabbMin, aabbMax); } + void RecastMeshBuilder::addTriangleVertex(const btVector3& worldPosition) + { + mIndices.push_back(static_cast(mVertices.size() / 3)); + addVertex(worldPosition); + } + void RecastMeshBuilder::addVertex(const btVector3& worldPosition) { const auto navMeshPosition = toNavMeshCoordinates(mSettings, makeOsgVec3f(worldPosition)); - mIndices.push_back(static_cast(mIndices.size())); mVertices.push_back(navMeshPosition.x()); mVertices.push_back(navMeshPosition.y()); mVertices.push_back(navMeshPosition.z()); diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index 5010a95e0d..28ca1f713a 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -3,6 +3,7 @@ #include "recastmesh.hpp" +class btBoxShape; class btCollisionShape; class btCompoundShape; class btConcaveShape; @@ -26,6 +27,8 @@ namespace DetourNavigator void addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform); + void addObject(const btBoxShape& shape, const btTransform& transform); + std::shared_ptr create() const; void reset(); @@ -37,6 +40,8 @@ namespace DetourNavigator void addObject(const btConcaveShape& shape, btTriangleCallback&& callback); + void addTriangleVertex(const btVector3& worldPosition); + void addVertex(const btVector3& worldPosition); }; } From ed3a255f65b43bbe507cd57d342b27e8de502210 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 15 Apr 2018 22:54:45 +0300 Subject: [PATCH 050/133] Do not use NavMesh to find changed tiles --- apps/openmw/mwworld/worldimp.cpp | 1 + apps/openmw_test_suite/CMakeLists.txt | 1 + .../detournavigator/gettilespositions.cpp | 104 ++++++++++++++++++ .../detournavigator/navigator.cpp | 1 + .../detournavigator/gettilespositions.hpp | 55 +++++++++ components/detournavigator/makenavmesh.cpp | 10 +- components/detournavigator/makenavmesh.hpp | 1 + components/detournavigator/navmeshmanager.cpp | 45 ++------ .../detournavigator/recastmeshmanager.cpp | 10 +- components/detournavigator/settings.hpp | 1 + components/detournavigator/settingsutils.hpp | 5 + components/detournavigator/sharednavmesh.hpp | 5 - files/settings-default.cfg | 15 +++ 13 files changed, 200 insertions(+), 54 deletions(-) create mode 100644 apps/openmw_test_suite/detournavigator/gettilespositions.cpp create mode 100644 components/detournavigator/gettilespositions.hpp diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 442fd04502..ce604580d0 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -166,6 +166,7 @@ namespace MWWorld mPhysics.reset(new MWPhysics::PhysicsSystem(resourceSystem, rootNode)); DetourNavigator::Settings navigatorSettings; + navigatorSettings.mBorderSize = Settings::Manager::getInt("border size", "Navigator"); navigatorSettings.mCellHeight = Settings::Manager::getFloat("cell height", "Navigator"); navigatorSettings.mCellSize = Settings::Manager::getFloat("cell size", "Navigator"); navigatorSettings.mDetailSampleDist = Settings::Manager::getFloat("detail sample dist", "Navigator"); diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 57fb1a0128..fc04648e5a 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -21,6 +21,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) detournavigator/navigator.cpp detournavigator/settingsutils.cpp detournavigator/recastmeshbuilder.cpp + detournavigator/gettilespositions.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp new file mode 100644 index 0000000000..1ad5c063d0 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp @@ -0,0 +1,104 @@ +#include +#include + +#include +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator; + + struct CollectTilesPositions + { + std::vector& mTilesPositions; + + void operator ()(const TilePosition& value) + { + mTilesPositions.push_back(value); + } + }; + + struct DetourNavigatorGetTilesPositionsTest : Test + { + Settings mSettings; + std::vector mTilesPositions; + CollectTilesPositions mCollect {mTilesPositions}; + + DetourNavigatorGetTilesPositionsTest() + { + mSettings.mBorderSize = 0; + mSettings.mCellSize = 0.5; + mSettings.mRecastScaleFactor = 1; + mSettings.mTileSize = 64; + } + }; + + TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_in_single_tile_should_return_one_tile) + { + getTilesPositions(osg::Vec3f(2, 2, 0), osg::Vec3f(31, 31, 1), mSettings, mCollect); + + EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); + } + + TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_x_bounds_in_two_tiles_should_return_two_tiles) + { + getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 31, 1), mSettings, mCollect); + + EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(1, 0))); + } + + TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_y_bounds_in_two_tiles_should_return_two_tiles) + { + getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 32, 1), mSettings, mCollect); + + EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(0, 1))); + } + + TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_works_only_for_x_and_y_coordinates) + { + getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 31, 32), mSettings, mCollect); + + EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); + } + + TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_should_work_with_negative_coordinates) + { + getTilesPositions(osg::Vec3f(-31, -31, 0), osg::Vec3f(31, 31, 1), mSettings, mCollect); + + EXPECT_THAT(mTilesPositions, ElementsAre( + TilePosition(-1, -1), + TilePosition(-1, 0), + TilePosition(0, -1), + TilePosition(0, 0) + )); + } + + TEST_F(DetourNavigatorGetTilesPositionsTest, border_size_should_extend_tile_bounds) + { + mSettings.mBorderSize = 1; + + getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31.5, 31.5, 1), mSettings, mCollect); + + EXPECT_THAT(mTilesPositions, ElementsAre( + TilePosition(-1, -1), + TilePosition(-1, 0), + TilePosition(-1, 1), + TilePosition(0, -1), + TilePosition(0, 0), + TilePosition(0, 1), + TilePosition(1, -1), + TilePosition(1, 0), + TilePosition(1, 1) + )); + } + + TEST_F(DetourNavigatorGetTilesPositionsTest, should_apply_recast_scale_factor) + { + mSettings.mRecastScaleFactor = 0.5; + + getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 32, 1), mSettings, mCollect); + + EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); + } +} diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 2bfab13a40..0e475ad9fc 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -37,6 +37,7 @@ namespace mSettings.mEnableWriteNavMeshToFile = false; mSettings.mEnableRecastMeshFileNameRevision = false; mSettings.mEnableNavMeshFileNameRevision = false; + mSettings.mBorderSize = 16; mSettings.mCellHeight = 0.2f; mSettings.mCellSize = 0.2f; mSettings.mDetailSampleDist = 6; diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp new file mode 100644 index 0000000000..96ab89fd73 --- /dev/null +++ b/components/detournavigator/gettilespositions.hpp @@ -0,0 +1,55 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H + +#include "settings.hpp" +#include "settingsutils.hpp" + +#include + +#include + +namespace DetourNavigator +{ + inline osg::Vec3f makeOsgVec3f(const btVector3& value) + { + return osg::Vec3f(value.x(), value.y(), value.z()); + } + + template + void getTilesPositions(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax, + const Settings& settings, Callback&& callback) + { + auto min = toNavMeshCoordinates(settings, aabbMin); + auto max = toNavMeshCoordinates(settings, aabbMax); + + const auto border = getBorderSize(settings); + min -= osg::Vec3f(border, border, border); + max += osg::Vec3f(border, border, border); + + auto minTile = getTilePosition(settings, min); + auto maxTile = getTilePosition(settings, max); + + if (minTile.x() > maxTile.x()) + std::swap(minTile.x(), maxTile.x()); + + if (minTile.y() > maxTile.y()) + std::swap(minTile.y(), maxTile.y()); + + for (int tileX = minTile.x(); tileX <= maxTile.x(); ++tileX) + for (int tileY = minTile.y(); tileY <= maxTile.y(); ++tileY) + callback(TilePosition {tileX, tileY}); + } + + template + void getTilesPositions(const btCollisionShape& shape, const btTransform& transform, + const Settings& settings, Callback&& callback) + { + btVector3 aabbMin; + btVector3 aabbMax; + shape.getAabb(transform, aabbMin, aabbMax); + + getTilesPositions(makeOsgVec3f(aabbMin), makeOsgVec3f(aabbMax), settings, std::forward(callback)); + } +} + +#endif diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 9be9ca5c30..2d5d4c2429 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -82,15 +82,15 @@ namespace config.maxVertsPerPoly = settings.mMaxVertsPerPoly; config.detailSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : config.cs * settings.mDetailSampleDist; config.detailSampleMaxError = config.ch * settings.mDetailSampleMaxError; - config.borderSize = config.walkableRadius + 3; + config.borderSize = settings.mBorderSize; config.width = settings.mTileSize + config.borderSize * 2; config.height = settings.mTileSize + config.borderSize * 2; rcVcopy(config.bmin, boundsMin.ptr()); rcVcopy(config.bmax, boundsMax.ptr()); - config.bmin[0] -= config.borderSize * config.cs; - config.bmin[2] -= config.borderSize * config.cs; - config.bmax[0] += config.borderSize * config.cs; - config.bmax[2] += config.borderSize * config.cs; + config.bmin[0] -= getBorderSize(settings); + config.bmin[2] -= getBorderSize(settings); + config.bmax[0] += getBorderSize(settings); + config.bmax[2] += getBorderSize(settings); rcHeightfield solid; OPENMW_CHECK_DT_RESULT(rcCreateHeightfield(nullptr, solid, config.width, config.height, diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 0a47dd66dd..60ad2acf7d 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H +#include "settings.hpp" #include "tileposition.hpp" #include diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 741d6666e3..eeaae309d3 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -1,6 +1,7 @@ #include "navmeshmanager.hpp" #include "debug.hpp" #include "exceptions.hpp" +#include "gettilespositions.hpp" #include "makenavmesh.hpp" #include "settings.hpp" #include "sharednavmesh.hpp" @@ -63,12 +64,10 @@ namespace DetourNavigator const auto changedTiles = mChangedTiles.find(agentHalfExtents); if (changedTiles != mChangedTiles.end()) { - TilePosition playerTile; playerPosition *= mSettings.mRecastScaleFactor; std::swap(playerPosition.y(), playerPosition.z()); - cached->mValue.raw()->calcTileLoc(playerPosition.ptr(), &playerTile.x(), &playerTile.y()); - mAsyncNavMeshUpdater.post(agentHalfExtents, mRecastMeshManager.getMesh(), cached, playerTile, - changedTiles->second); + mAsyncNavMeshUpdater.post(agentHalfExtents, mRecastMeshManager.getMesh(), cached, + getTilePosition(mSettings, playerPosition), changedTiles->second); mChangedTiles.erase(changedTiles); log("cache update posted for agent=", agentHalfExtents); } @@ -91,39 +90,11 @@ namespace DetourNavigator void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform) { - btVector3 aabbMin; - btVector3 aabbMax; - shape.getAabb(transform, aabbMin, aabbMax); - osg::Vec3f min(aabbMin.x(), aabbMin.z(), aabbMin.y()); - osg::Vec3f max(aabbMax.x(), aabbMax.z(), aabbMax.y()); - min *= mSettings.mRecastScaleFactor; - max *= mSettings.mRecastScaleFactor; - - for (auto& v : mCache) - { - if (const auto& item = v.second) - { - auto& changedTiles = mChangedTiles[v.first]; - - int minTileX; - int minTileY; - item->mValue.raw()->calcTileLoc(min.ptr(), &minTileX, &minTileY); - - int maxTileX; - int maxTileY; - item->mValue.raw()->calcTileLoc(max.ptr(), &maxTileX, &maxTileY); - - if (minTileX > maxTileX) - std::swap(minTileX, maxTileX); - - if (minTileY > maxTileY) - std::swap(minTileY, maxTileY); - - for (int tileX = minTileX; tileX <= maxTileX; ++tileX) - for (int tileY = minTileY; tileY <= maxTileY; ++tileY) - changedTiles.insert(TilePosition {tileX, tileY}); - } - } + getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& v) { + for (const auto& cached : mCache) + if (cached.second) + mChangedTiles[cached.first].insert(v); + }); } const std::shared_ptr& NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 1c32ac1e77..ccfa253724 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -7,7 +7,8 @@ namespace DetourNavigator RecastMeshManager::RecastMeshManager(const Settings& settings) : mShouldRebuild(false) , mMeshBuilder(settings) - {} + { + } bool RecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) { @@ -40,12 +41,7 @@ namespace DetourNavigator return; mMeshBuilder.reset(); for (const auto& v : mObjects) - { - if (v.second.mShape->getShapeType() == TERRAIN_SHAPE_PROXYTYPE) - mMeshBuilder.addObject(*static_cast(v.second.mShape), v.second.mTransform); - else if (v.second.mShape->isConcave()) - mMeshBuilder.addObject(*static_cast(v.second.mShape), v.second.mTransform); - } + mMeshBuilder.addObject(*v.second.mShape, v.second.mTransform); mShouldRebuild = false; } } diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 6983eb4314..0d34ae7e95 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -19,6 +19,7 @@ namespace DetourNavigator float mMaxSimplificationError; float mMaxSlope; float mRecastScaleFactor; + int mBorderSize; int mMaxEdgeLen; int mMaxNavMeshQueryNodes; int mMaxVertsPerPoly; diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index d14863dca8..d0d51e1670 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -60,6 +60,11 @@ namespace DetourNavigator osg::Vec2f(tilePosition.x() + 1, tilePosition.y() + 1) * getTileSize(settings), }; } + + inline float getBorderSize(const Settings& settings) + { + return settings.mBorderSize * settings.mCellSize; + } } #endif diff --git a/components/detournavigator/sharednavmesh.hpp b/components/detournavigator/sharednavmesh.hpp index 101cc58666..657973c2f6 100644 --- a/components/detournavigator/sharednavmesh.hpp +++ b/components/detournavigator/sharednavmesh.hpp @@ -44,11 +44,6 @@ namespace DetourNavigator return LockedSharedNavMesh(*mMutex, mValue); } - NavMeshPtr raw() const - { - return mValue; - } - private: std::shared_ptr mMutex; NavMeshPtr mValue; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 8a78e745c4..a1ea14678e 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -567,6 +567,9 @@ max simplification error = 1.3 # The width and height of each tile. (value > 0) tile size = 64 +# The size of the non-navigable border around the heightfield. (value >= 0) +border size = 16 + # The maximum allowed length for contour edges along the border of the mesh. (value >= 0) max edge len = 12 @@ -593,9 +596,21 @@ triangles per chunk = 256 # Enable debug log (true, false) enable log = false + +# Write recast mesh to file in .obj format for each use to update nav mesh (true, false) enable write recast mesh to file = false + +# Write NavMesh to file to be able to open by RecastDemo (true, false) enable write nav mesh to file = false + +# Write each recast mesh file with revision in name. Otherwise will rewrite same file. (true, false) enable recast mesh file name revision = false + +# Write each nav mesh file with revision in name. Otherwise will rewrite same file. (true, false) enable nav mesh file name revision = false + +# Write recast mesh file at path with this prefix recast mesh path prefix = + +# Write nav mesh file at path with this prefix nav mesh path prefix = From 02ce4a7e50e5b8e45603b8a10e4fa37859f98c68 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 21 Apr 2018 14:56:04 +0300 Subject: [PATCH 051/133] Log to detournavigator log cell load and unload --- apps/openmw/mwworld/scene.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 73dc632525..cee9310bd8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -259,6 +260,7 @@ namespace MWWorld void Scene::unloadCell (CellStoreCollection::iterator iter) { Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription(); + DetourNavigator::log("unload cell ", (*iter)->getCell()->getDescription()); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); ListAndResetObjectsVisitor visitor; @@ -309,6 +311,7 @@ namespace MWWorld if(result.second) { Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); + DetourNavigator::log("load cell ", cell->getCell()->getDescription()); float verts = ESM::Land::LAND_SIZE; float worldsize = ESM::Land::REAL_SIZE; From faaf50446d4a7077db61c07674c0fd1c42745323 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 21 Apr 2018 13:59:53 +0300 Subject: [PATCH 052/133] Option to initially enable NavMesh render --- apps/openmw/mwrender/navmesh.cpp | 4 ++-- apps/openmw/mwrender/navmesh.hpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 2 +- files/settings-default.cfg | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index 0d8cb88677..79ee33b973 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -7,9 +7,9 @@ namespace MWRender { - NavMesh::NavMesh(const osg::ref_ptr& root) + NavMesh::NavMesh(const osg::ref_ptr& root, bool enabled) : mRootNode(root) - , mEnabled(false) + , mEnabled(enabled) , mRevision(0) { } diff --git a/apps/openmw/mwrender/navmesh.hpp b/apps/openmw/mwrender/navmesh.hpp index 5ce98ee7b0..a6c1f086ea 100644 --- a/apps/openmw/mwrender/navmesh.hpp +++ b/apps/openmw/mwrender/navmesh.hpp @@ -16,7 +16,7 @@ namespace MWRender class NavMesh { public: - NavMesh(const osg::ref_ptr& root); + NavMesh(const osg::ref_ptr& root, bool enabled); ~NavMesh(); bool toggle(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index e29f9c7d27..400a2d05ce 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -234,7 +234,7 @@ namespace MWRender mRootNode->addChild(mSceneRoot); - mNavMesh.reset(new NavMesh(mRootNode)); + mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable render", "Navigator"))); mPathgrid.reset(new Pathgrid(mRootNode)); mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get())); diff --git a/files/settings-default.cfg b/files/settings-default.cfg index a1ea14678e..496cfaf2b5 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -614,3 +614,4 @@ recast mesh path prefix = # Write nav mesh file at path with this prefix nav mesh path prefix = +enable render = false From dd5f4498f65eada2ea357c969e9a36ccac672adc Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 16 Apr 2018 22:57:35 +0300 Subject: [PATCH 053/133] Increment NavMesh revision on remove or add tile --- .../detournavigator/asyncnavmeshupdater.cpp | 5 +-- .../detournavigator/asyncnavmeshupdater.hpp | 12 +------ components/detournavigator/makenavmesh.cpp | 36 ++++++++++++++++--- components/detournavigator/makenavmesh.hpp | 3 +- .../detournavigator/navmeshcacheitem.hpp | 21 +++++++++++ 5 files changed, 57 insertions(+), 20 deletions(-) create mode 100644 components/detournavigator/navmeshcacheitem.hpp diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 900d699420..cc3156a6c8 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -91,10 +91,7 @@ namespace DetourNavigator const auto recastMesh = getRecastMesh(); - updateNavMesh(job.mAgentHalfExtents, *recastMesh, job.mChangedTile, mSettings, - job.mNavMeshCacheItem->mValue); - - ++job.mNavMeshCacheItem->mNavMeshRevision; + updateNavMesh(job.mAgentHalfExtents, *recastMesh, job.mChangedTile, mSettings, *job.mNavMeshCacheItem); const auto finish = std::chrono::steady_clock::now(); diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 649fa9ffb0..7ffa8f5f06 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_ASYNCNAVMESHUPDATER_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_ASYNCNAVMESHUPDATER_H +#include "navmeshcacheitem.hpp" #include "recastmesh.hpp" -#include "sharednavmesh.hpp" #include "tileposition.hpp" #include @@ -22,16 +22,6 @@ class dtNavMesh; namespace DetourNavigator { - struct NavMeshCacheItem - { - SharedNavMesh mValue; - std::size_t mRecastMeshRevision; - std::atomic_size_t mNavMeshRevision; - - NavMeshCacheItem(const NavMeshPtr& value, std::size_t revision) - : mValue(value), mRecastMeshRevision(revision), mNavMeshRevision(0) {} - }; - class AsyncNavMeshUpdater { public: diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 2d5d4c2429..a1c6d732a0 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -214,6 +214,23 @@ namespace return NavMeshData(navMeshData, navMeshDataSize); } + + struct AutoIncrementRevision + { + std::atomic_size_t& mNavMeshRevision; + bool mNavMeshChanged; + + AutoIncrementRevision(std::atomic_size_t& navMeshRevision) + : mNavMeshRevision(navMeshRevision) + , mNavMeshChanged(false) + {} + + ~AutoIncrementRevision() + { + if (mNavMeshChanged) + ++mNavMeshRevision; + } + }; } namespace DetourNavigator @@ -241,7 +258,7 @@ namespace DetourNavigator } void updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, - const TilePosition& changedTile, const Settings& settings, SharedNavMesh& navMesh) + const TilePosition& changedTile, const Settings& settings, NavMeshCacheItem& navMeshCacheItem) { log("update NavMesh with mutiple tiles:", " agentHeight=", std::setprecision(std::numeric_limits::max_exponent10), @@ -255,15 +272,19 @@ namespace DetourNavigator const auto& boundsMin = recastMesh.getBoundsMin(); const auto& boundsMax = recastMesh.getBoundsMax(); + auto& navMesh = navMeshCacheItem.mValue; const auto& params = *navMesh.lock()->getParams(); const osg::Vec3f origin(params.orig[0], params.orig[1], params.orig[2]); const auto x = changedTile.x(); const auto y = changedTile.y(); + AutoIncrementRevision incRev(navMeshCacheItem.mNavMeshRevision); + { const auto locked = navMesh.lock(); - locked->removeTile(locked->getTileRefAt(x, y, 0), nullptr, nullptr); + incRev.mNavMeshChanged = dtStatusSucceed(locked->removeTile(locked->getTileRefAt(x, y, 0), + nullptr, nullptr)); } const auto tileBounds = makeTileBounds(settings, changedTile); @@ -274,10 +295,17 @@ namespace DetourNavigator tileBorderMin, tileBorderMax, settings); if (!navMeshData.mValue) + { + log("ignore add tile: NavMeshData is null"); return; + } - OPENMW_CHECK_DT_STATUS(navMesh.lock()->addTile(navMeshData.mValue.get(), navMeshData.mSize, - DT_TILE_FREE_DATA, 0, 0)); + const auto status = navMesh.lock()->addTile(navMeshData.mValue.get(), navMeshData.mSize, + DT_TILE_FREE_DATA, 0, 0); + if (dtStatusSucceed(status)) + incRev.mNavMeshChanged = true; + else + log("failed to add tile with status=", WriteDtStatus {status}); navMeshData.mValue.release(); } } diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 60ad2acf7d..03c3e63625 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H #include "settings.hpp" +#include "navmeshcacheitem.hpp" #include "tileposition.hpp" #include @@ -22,7 +23,7 @@ namespace DetourNavigator NavMeshPtr makeEmptyNavMesh(const Settings& settings); void updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, - const TilePosition& changedTile, const Settings& settings, SharedNavMesh& navMesh); + const TilePosition& changedTile, const Settings& settings, NavMeshCacheItem& navMeshCacheItem); } #endif diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp new file mode 100644 index 0000000000..4c73fd05d1 --- /dev/null +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -0,0 +1,21 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHCACHEITEM_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHCACHEITEM_H + +#include "sharednavmesh.hpp" + +#include + +namespace DetourNavigator +{ + struct NavMeshCacheItem + { + SharedNavMesh mValue; + std::size_t mRecastMeshRevision; + std::atomic_size_t mNavMeshRevision; + + NavMeshCacheItem(const NavMeshPtr& value, std::size_t revision) + : mValue(value), mRecastMeshRevision(revision), mNavMeshRevision(0) {} + }; +} + +#endif From 937e8e18036c2255ca84f6936b3b5cc530b99238 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 16 Apr 2018 01:39:51 +0300 Subject: [PATCH 054/133] Allow to create empty RecastMesh --- .../detournavigator/recastmeshbuilder.cpp | 6 ++++-- components/detournavigator/chunkytrimesh.cpp | 2 +- components/detournavigator/makenavmesh.cpp | 14 ++++++++++---- components/detournavigator/recastmesh.cpp | 3 ++- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index 63f6aa6b4c..a227df8031 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -31,9 +31,11 @@ namespace } }; - TEST_F(DetourNavigatorRecastMeshBuilderTest, create_for_empty_should_throw_exception) + TEST_F(DetourNavigatorRecastMeshBuilderTest, create_for_empty_should_return_empty) { - EXPECT_THROW(mBuilder.create(), std::invalid_argument); + const auto recastMesh = mBuilder.create(); + EXPECT_EQ(recastMesh->getVertices(), std::vector()); + EXPECT_EQ(recastMesh->getIndices(), std::vector()); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_bhv_triangle_mesh_shape) diff --git a/components/detournavigator/chunkytrimesh.cpp b/components/detournavigator/chunkytrimesh.cpp index 1b7f53845b..cdce8b0e1e 100644 --- a/components/detournavigator/chunkytrimesh.cpp +++ b/components/detournavigator/chunkytrimesh.cpp @@ -120,7 +120,7 @@ namespace DetourNavigator const auto trianglesCount = indices.size() / 3; if (trianglesCount == 0) - throw InvalidArgument("ChunkyTriMesh tris should contain at least 3 values"); + return; const auto nchunks = (trianglesCount + trisPerChunk - 1) / trisPerChunk; diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index a1c6d732a0..52fa530689 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -269,9 +269,6 @@ namespace DetourNavigator getRadius(settings, agentHalfExtents), " changedTile=", changedTile); - const auto& boundsMin = recastMesh.getBoundsMin(); - const auto& boundsMax = recastMesh.getBoundsMax(); - auto& navMesh = navMeshCacheItem.mValue; const auto& params = *navMesh.lock()->getParams(); const osg::Vec3f origin(params.orig[0], params.orig[1], params.orig[2]); @@ -287,6 +284,15 @@ namespace DetourNavigator nullptr, nullptr)); } + const auto& boundsMin = recastMesh.getBoundsMin(); + const auto& boundsMax = recastMesh.getBoundsMax(); + + if (boundsMin == boundsMax) + { + log("ignore add tile: recastMesh is empty"); + return; + } + const auto tileBounds = makeTileBounds(settings, changedTile); const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y()); const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y()); @@ -307,5 +313,5 @@ namespace DetourNavigator else log("failed to add tile with status=", WriteDtStatus {status}); navMeshData.mValue.release(); - } +} } diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index 45c2f98e78..d765656e45 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -10,6 +10,7 @@ namespace DetourNavigator , mVertices(std::move(vertices)) , mChunkyTriMesh(mVertices, mIndices, settings.mTrianglesPerChunk) { - rcCalcBounds(mVertices.data(), static_cast(getVerticesCount()), mBoundsMin.ptr(), mBoundsMax.ptr()); + if (getVerticesCount()) + rcCalcBounds(mVertices.data(), static_cast(getVerticesCount()), mBoundsMin.ptr(), mBoundsMax.ptr()); } } From 6f3028b8f9c81e430a0d884906af9fc46e62c9ef Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 21 Apr 2018 15:27:25 +0300 Subject: [PATCH 055/133] Update navigator when unload cell or add/remove object to scene --- apps/openmw/mwworld/scene.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index cee9310bd8..026d067dd8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -266,7 +266,8 @@ namespace MWWorld ListAndResetObjectsVisitor visitor; (*iter)->forEach(visitor); - const auto playerHalfExtents = mPhysics->getHalfExtents(MWBase::Environment::get().getWorld()->getPlayerPtr()); + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const auto playerHalfExtents = mPhysics->getHalfExtents(player); for (const auto& ptr : visitor.mObjects) { if (const auto object = mPhysics->getObject(ptr)) @@ -293,6 +294,8 @@ namespace MWWorld } } + navigator->update(player.getRefData().getPosition().asVec3()); + MWBase::Environment::get().getMechanicsManager()->drop (*iter); mRendering.removeCell(*iter); @@ -689,6 +692,9 @@ namespace MWWorld { addObject(ptr, *mPhysics, mRendering); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); + const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + navigator->update(player.getRefData().getPosition().asVec3()); } catch (std::exception& e) { @@ -702,7 +708,11 @@ namespace MWWorld MWBase::Environment::get().getSoundManager()->stopSound3D (ptr); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); if (const auto object = mPhysics->getObject(ptr)) + { navigator->removeObject(reinterpret_cast(object)); + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + navigator->update(player.getRefData().getPosition().asVec3()); + } else if (const auto actor = mPhysics->getActor(ptr)) { const auto playerHalfExtents = mPhysics->getHalfExtents(MWBase::Environment::get().getWorld()->getPlayerPtr()); From c26773bd295a9bd8529e58d7b86f3a4ad8eb6dba Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 21 Apr 2018 15:27:47 +0300 Subject: [PATCH 056/133] Log update NavMesh status --- .../detournavigator/asyncnavmeshupdater.cpp | 21 ++++++++++++-- components/detournavigator/makenavmesh.cpp | 28 +++++++++++++++---- components/detournavigator/makenavmesh.hpp | 10 ++++++- files/settings-default.cfg | 2 ++ 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index cc3156a6c8..424d1f260c 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -24,6 +24,22 @@ namespace namespace DetourNavigator { + static std::ostream& operator <<(std::ostream& stream, UpdateNavMeshStatus value) + { + switch (value) + { + case UpdateNavMeshStatus::ignore: + return stream << "ignore"; + case UpdateNavMeshStatus::removed: + return stream << "removed"; + case UpdateNavMeshStatus::add: + return stream << "add"; + case UpdateNavMeshStatus::replaced: + return stream << "replaced"; + } + return stream << "unknown"; + } + AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings) : mSettings(std::cref(settings)) , mShouldStop() @@ -91,7 +107,8 @@ namespace DetourNavigator const auto recastMesh = getRecastMesh(); - updateNavMesh(job.mAgentHalfExtents, *recastMesh, job.mChangedTile, mSettings, *job.mNavMeshCacheItem); + const auto status = updateNavMesh(job.mAgentHalfExtents, *recastMesh, job.mChangedTile, mSettings, + *job.mNavMeshCacheItem); const auto finish = std::chrono::steady_clock::now(); @@ -99,7 +116,7 @@ namespace DetourNavigator using FloatMs = std::chrono::duration; - log("cache updated for agent=", job.mAgentHalfExtents, + log("cache updated for agent=", job.mAgentHalfExtents, " status=", status, " time=", std::chrono::duration_cast(finish - start).count(), "ms"); } diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 52fa530689..fb2fb2bb5b 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -231,6 +231,18 @@ namespace ++mNavMeshRevision; } }; + + UpdateNavMeshStatus makeUpdateNavMeshStatus(bool removed, bool add) + { + if (removed && add) + return UpdateNavMeshStatus::replaced; + else if (removed) + return UpdateNavMeshStatus::removed; + else if (add) + return UpdateNavMeshStatus::add; + else + return UpdateNavMeshStatus::ignore; + } } namespace DetourNavigator @@ -257,7 +269,7 @@ namespace DetourNavigator return navMesh; } - void updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, + UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, const TilePosition& changedTile, const Settings& settings, NavMeshCacheItem& navMeshCacheItem) { log("update NavMesh with mutiple tiles:", @@ -277,20 +289,22 @@ namespace DetourNavigator const auto y = changedTile.y(); AutoIncrementRevision incRev(navMeshCacheItem.mNavMeshRevision); + bool removed = false; { const auto locked = navMesh.lock(); - incRev.mNavMeshChanged = dtStatusSucceed(locked->removeTile(locked->getTileRefAt(x, y, 0), - nullptr, nullptr)); + removed = dtStatusSucceed(locked->removeTile(locked->getTileRefAt(x, y, 0), nullptr, nullptr)); } + incRev.mNavMeshChanged = removed; + const auto& boundsMin = recastMesh.getBoundsMin(); const auto& boundsMax = recastMesh.getBoundsMax(); if (boundsMin == boundsMax) { log("ignore add tile: recastMesh is empty"); - return; + return makeUpdateNavMeshStatus(removed, false); } const auto tileBounds = makeTileBounds(settings, changedTile); @@ -303,7 +317,7 @@ namespace DetourNavigator if (!navMeshData.mValue) { log("ignore add tile: NavMeshData is null"); - return; + return makeUpdateNavMeshStatus(removed, false); } const auto status = navMesh.lock()->addTile(navMeshData.mValue.get(), navMeshData.mSize, @@ -313,5 +327,7 @@ namespace DetourNavigator else log("failed to add tile with status=", WriteDtStatus {status}); navMeshData.mValue.release(); -} + + return makeUpdateNavMeshStatus(removed, dtStatusSucceed(status)); + } } diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 03c3e63625..96e655be8b 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -20,9 +20,17 @@ namespace DetourNavigator using NavMeshPtr = std::shared_ptr; + enum class UpdateNavMeshStatus + { + ignore, + removed, + add, + replaced + }; + NavMeshPtr makeEmptyNavMesh(const Settings& settings); - void updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, + UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, const TilePosition& changedTile, const Settings& settings, NavMeshCacheItem& navMeshCacheItem); } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 496cfaf2b5..f6fc99c0e8 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -614,4 +614,6 @@ recast mesh path prefix = # Write nav mesh file at path with this prefix nav mesh path prefix = + +# Render nav mesh (true, false) enable render = false From f268ec5d34532d5b116a4fb8d0d5cd7e9305e023 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 21 Apr 2018 01:39:21 +0300 Subject: [PATCH 057/133] Measure total time for NavMesh build from first pop --- .../detournavigator/asyncnavmeshupdater.cpp | 18 +++++++++++++++++- .../detournavigator/asyncnavmeshupdater.hpp | 8 +++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 424d1f260c..c9fca3c92f 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -105,6 +105,8 @@ namespace DetourNavigator const auto start = std::chrono::steady_clock::now(); + setFirstStart(start); + const auto recastMesh = getRecastMesh(); const auto status = updateNavMesh(job.mAgentHalfExtents, *recastMesh, job.mChangedTile, mSettings, @@ -117,7 +119,8 @@ namespace DetourNavigator using FloatMs = std::chrono::duration; log("cache updated for agent=", job.mAgentHalfExtents, " status=", status, - " time=", std::chrono::duration_cast(finish - start).count(), "ms"); + " time=", std::chrono::duration_cast(finish - start).count(), "ms", + " total_time=", std::chrono::duration_cast(finish - getFirstStart()).count(), "ms"); } boost::optional AsyncNavMeshUpdater::getNextJob() @@ -168,4 +171,17 @@ namespace DetourNavigator const std::lock_guard lock(mRecastMeshMutex); mRecastMesh = value; } + + std::chrono::steady_clock::time_point AsyncNavMeshUpdater::getFirstStart() + { + const std::lock_guard lock(mFirstStartMutex); + return *mFirstStart; + } + + void AsyncNavMeshUpdater::setFirstStart(const std::chrono::steady_clock::time_point& value) + { + const std::lock_guard lock(mFirstStartMutex); + if (!mFirstStart) + mFirstStart = value; + } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 7ffa8f5f06..721b89cec7 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -10,8 +10,8 @@ #include #include +#include #include -#include #include #include #include @@ -58,6 +58,8 @@ namespace DetourNavigator Jobs mJobs; std::mutex mRecastMeshMutex; std::shared_ptr mRecastMesh; + std::mutex mFirstStartMutex; + boost::optional mFirstStart; std::thread mThread; void process() throw(); @@ -73,6 +75,10 @@ namespace DetourNavigator std::shared_ptr getRecastMesh(); void setRecastMesh(const std::shared_ptr& value); + + std::chrono::steady_clock::time_point getFirstStart(); + + void setFirstStart(const std::chrono::steady_clock::time_point& value); }; } From dbb1d99bffe88aaece0f0079ce1470d6e1c8e18b Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 21 Apr 2018 16:09:49 +0300 Subject: [PATCH 058/133] Add NavMeshItem generation to fix update NavMesh for render --- apps/openmw/mwrender/navmesh.cpp | 8 +++++--- apps/openmw/mwrender/navmesh.hpp | 3 ++- apps/openmw/mwrender/renderingmanager.cpp | 4 ++-- components/detournavigator/asyncnavmeshupdater.cpp | 2 ++ components/detournavigator/navmeshcacheitem.hpp | 5 +++-- components/detournavigator/navmeshmanager.cpp | 2 +- components/detournavigator/navmeshmanager.hpp | 1 + 7 files changed, 16 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index 79ee33b973..787e332e92 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -10,6 +10,7 @@ namespace MWRender NavMesh::NavMesh(const osg::ref_ptr& root, bool enabled) : mRootNode(root) , mEnabled(enabled) + , mGeneration(0) , mRevision(0) { } @@ -30,12 +31,13 @@ namespace MWRender return mEnabled; } - void NavMesh::update(const DetourNavigator::SharedNavMesh& sharedNavMesh, std::size_t revision, - const DetourNavigator::Settings& settings) + void NavMesh::update(const DetourNavigator::SharedNavMesh& sharedNavMesh, std::size_t generation, + std::size_t revision, const DetourNavigator::Settings& settings) { - if (!mEnabled || mRevision >= revision) + if (!mEnabled || (mGeneration >= generation && mRevision >= revision)) return; + mGeneration = generation; mRevision = revision; if (mGroup) mRootNode->removeChild(mGroup); diff --git a/apps/openmw/mwrender/navmesh.hpp b/apps/openmw/mwrender/navmesh.hpp index a6c1f086ea..0bed616f9c 100644 --- a/apps/openmw/mwrender/navmesh.hpp +++ b/apps/openmw/mwrender/navmesh.hpp @@ -21,7 +21,7 @@ namespace MWRender bool toggle(); - void update(const DetourNavigator::SharedNavMesh& sharedNavMesh, std::size_t revision, + void update(const DetourNavigator::SharedNavMesh& sharedNavMesh, std::size_t generation, std::size_t revision, const DetourNavigator::Settings& settings); void enable(); @@ -31,6 +31,7 @@ namespace MWRender private: osg::ref_ptr mRootNode; bool mEnabled; + std::size_t mGeneration; std::size_t mRevision; osg::ref_ptr mGroup; }; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 400a2d05ce..fbe8ba3459 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -597,8 +597,8 @@ namespace MWRender { try { - mNavMesh->update(navMeshes.begin()->second->mValue, navMeshes.begin()->second->mNavMeshRevision, - mNavigator.getSettings()); + mNavMesh->update(navMeshes.begin()->second->mValue, navMeshes.begin()->second->mGeneration, + navMeshes.begin()->second->mNavMeshRevision, mNavigator.getSettings()); } catch (const std::exception& e) { diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index c9fca3c92f..b809fc27c9 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -119,6 +119,8 @@ namespace DetourNavigator using FloatMs = std::chrono::duration; log("cache updated for agent=", job.mAgentHalfExtents, " status=", status, + " generation=", job.mNavMeshCacheItem->mGeneration, + " revision=", job.mNavMeshCacheItem->mNavMeshRevision, " time=", std::chrono::duration_cast(finish - start).count(), "ms", " total_time=", std::chrono::duration_cast(finish - getFirstStart()).count(), "ms"); } diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp index 4c73fd05d1..ffea1fbddc 100644 --- a/components/detournavigator/navmeshcacheitem.hpp +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -10,11 +10,12 @@ namespace DetourNavigator struct NavMeshCacheItem { SharedNavMesh mValue; + std::size_t mGeneration; std::size_t mRecastMeshRevision; std::atomic_size_t mNavMeshRevision; - NavMeshCacheItem(const NavMeshPtr& value, std::size_t revision) - : mValue(value), mRecastMeshRevision(revision), mNavMeshRevision(0) {} + NavMeshCacheItem(const NavMeshPtr& value, std::size_t generation, std::size_t revision) + : mValue(value), mGeneration(generation), mRecastMeshRevision(revision), mNavMeshRevision(0) {} }; } diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index eeaae309d3..ad9a3a5e70 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -45,7 +45,7 @@ namespace DetourNavigator if (cached != mCache.end()) return; mCache.insert(std::make_pair(agentHalfExtents, - std::make_shared(makeEmptyNavMesh(mSettings), mRevision)) + std::make_shared(makeEmptyNavMesh(mSettings), ++mGenerationCounter, mRevision)) ); log("cache add for agent=", agentHalfExtents); } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 4f7d9d24dd..63733cf018 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -46,6 +46,7 @@ namespace DetourNavigator std::map> mCache; std::map> mChangedTiles; AsyncNavMeshUpdater mAsyncNavMeshUpdater; + std::size_t mGenerationCounter = 0; void addChangedTiles(const btCollisionShape& shape, const btTransform& transform); From d2fd9abd51f81b2203ecb491e8df619096bdb1b9 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 16 Apr 2018 01:07:18 +0300 Subject: [PATCH 059/133] Split RecastMesh into tiles --- .../detournavigator/recastmeshbuilder.cpp | 149 +++++++++++++++--- components/CMakeLists.txt | 1 + .../detournavigator/asyncnavmeshupdater.cpp | 25 +-- .../detournavigator/asyncnavmeshupdater.hpp | 16 +- .../cachedrecastmeshmanager.cpp | 12 +- .../cachedrecastmeshmanager.hpp | 4 +- .../detournavigator/gettilespositions.hpp | 4 +- components/detournavigator/makenavmesh.cpp | 18 ++- components/detournavigator/makenavmesh.hpp | 5 +- .../detournavigator/navmeshcacheitem.hpp | 5 +- components/detournavigator/navmeshmanager.cpp | 20 +-- components/detournavigator/navmeshmanager.hpp | 3 +- .../detournavigator/recastmeshbuilder.cpp | 32 +++- .../detournavigator/recastmeshbuilder.hpp | 6 +- .../detournavigator/recastmeshmanager.cpp | 9 +- .../detournavigator/recastmeshmanager.hpp | 4 +- components/detournavigator/settingsutils.hpp | 1 + .../tilecachedrecastmeshmanager.cpp | 71 +++++++++ .../tilecachedrecastmeshmanager.hpp | 31 ++++ 19 files changed, 328 insertions(+), 88 deletions(-) create mode 100644 components/detournavigator/tilecachedrecastmeshmanager.cpp create mode 100644 components/detournavigator/tilecachedrecastmeshmanager.hpp diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index a227df8031..a2a47722d1 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -21,19 +21,23 @@ namespace struct DetourNavigatorRecastMeshBuilderTest : Test { Settings mSettings; - RecastMeshBuilder mBuilder; + TileBounds mBounds; DetourNavigatorRecastMeshBuilderTest() - : mBuilder(mSettings) { mSettings.mRecastScaleFactor = 1.0f; mSettings.mTrianglesPerChunk = 256; + mBounds.mMin = osg::Vec2f(-std::numeric_limits::max() * std::numeric_limits::epsilon(), + -std::numeric_limits::max() * std::numeric_limits::epsilon()); + mBounds.mMax = osg::Vec2f(std::numeric_limits::max() * std::numeric_limits::epsilon(), + std::numeric_limits::max() * std::numeric_limits::epsilon()); } }; TEST_F(DetourNavigatorRecastMeshBuilderTest, create_for_empty_should_return_empty) { - const auto recastMesh = mBuilder.create(); + RecastMeshBuilder builder(mSettings, mBounds); + const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector()); EXPECT_EQ(recastMesh->getIndices(), std::vector()); } @@ -43,8 +47,9 @@ namespace btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape shape(&mesh, true); - mBuilder.addObject(static_cast(shape), btTransform::getIdentity()); - const auto recastMesh = mBuilder.create(); + RecastMeshBuilder builder(mSettings, mBounds); + builder.addObject(static_cast(shape), btTransform::getIdentity()); + const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, -1, 0, 1, @@ -58,9 +63,10 @@ namespace btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape shape(&mesh, true); - mBuilder.addObject(static_cast(shape), + RecastMeshBuilder builder(mSettings, mBounds); + builder.addObject(static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3))); - const auto recastMesh = mBuilder.create(); + const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 2, 3, 0, 0, 3, 4, @@ -73,8 +79,9 @@ namespace { const std::array heightfieldData {{0, 0, 0, 0}}; btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); - mBuilder.addObject(static_cast(shape), btTransform::getIdentity()); - const auto recastMesh = mBuilder.create(); + RecastMeshBuilder builder(mSettings, mBounds); + builder.addObject(static_cast(shape), btTransform::getIdentity()); + const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -0.5, 0, -0.5, -0.5, 0, 0.5, @@ -89,8 +96,9 @@ namespace TEST_F(DetourNavigatorRecastMeshBuilderTest, add_box_shape_should_produce_12_triangles) { btBoxShape shape(btVector3(1, 1, 2)); - mBuilder.addObject(static_cast(shape), btTransform::getIdentity()); - const auto recastMesh = mBuilder.create(); + RecastMeshBuilder builder(mSettings, mBounds); + builder.addObject(static_cast(shape), btTransform::getIdentity()); + const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 2, 1, -1, 2, 1, @@ -130,8 +138,9 @@ namespace shape.addChildShape(btTransform::getIdentity(), &triangle1); shape.addChildShape(btTransform::getIdentity(), &box); shape.addChildShape(btTransform::getIdentity(), &triangle2); - mBuilder.addObject(static_cast(shape), btTransform::getIdentity()); - const auto recastMesh = mBuilder.create(); + RecastMeshBuilder builder(mSettings, mBounds); + builder.addObject(static_cast(shape), btTransform::getIdentity()); + const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, -1, 0, 1, @@ -173,9 +182,10 @@ namespace btBvhTriangleMeshShape triangle(&mesh, true); btCompoundShape shape; shape.addChildShape(btTransform::getIdentity(), &triangle); - mBuilder.addObject(static_cast(shape), - btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3))); - const auto recastMesh = mBuilder.create(); + RecastMeshBuilder builder(mSettings, mBounds); + builder.addObject(static_cast(shape), + btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3))); + const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 2, 3, 0, 0, 3, 4, @@ -192,9 +202,10 @@ namespace btCompoundShape shape; shape.addChildShape(btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), &triangle); - mBuilder.addObject(static_cast(shape), - btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3))); - const auto recastMesh = mBuilder.create(); + RecastMeshBuilder builder(mSettings, mBounds); + builder.addObject(static_cast(shape), + btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3))); + const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 3, 12, 2, 1, 12, 10, @@ -202,4 +213,104 @@ namespace })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, without_bounds_add_transformed_compound_shape_with_transformed_bhv_triangle_shape_should_not_filter_by_bounds) + { + btTriangleMesh mesh; + mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); + mesh.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0)); + btBvhTriangleMeshShape shape(&mesh, true); + RecastMeshBuilder builder(mSettings, mBounds); + builder.addObject(static_cast(shape), btTransform::getIdentity()); + const auto recastMesh = builder.create(); + EXPECT_EQ(recastMesh->getVertices(), std::vector({ + 1, 0, -1, + -1, 0, 1, + -1, 0, -1, + -2, 0, -3, + -3, 0, -2, + -3, 0, -3, + })); + EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 3, 4, 5})); + } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_transformed_compound_shape_with_transformed_bhv_triangle_shape_should_filter_by_bounds) + { + mSettings.mRecastScaleFactor = 0.1f; + mBounds.mMin = osg::Vec2f(-3, -3) * mSettings.mRecastScaleFactor; + mBounds.mMax = osg::Vec2f(-2, -2) * mSettings.mRecastScaleFactor; + btTriangleMesh mesh; + mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); + mesh.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0)); + btBvhTriangleMeshShape shape(&mesh, true); + RecastMeshBuilder builder(mSettings, mBounds); + builder.addObject(static_cast(shape), btTransform::getIdentity()); + const auto recastMesh = builder.create(); + EXPECT_EQ(recastMesh->getVertices(), std::vector({ + -0.2f, 0, -0.3f, + -0.3f, 0, -0.2f, + -0.3f, 0, -0.3f, + })); + EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_x_bhv_triangle_shape_should_filter_by_bounds) + { + mBounds.mMin = osg::Vec2f(-5, -5); + mBounds.mMax = osg::Vec2f(5, -3); + btTriangleMesh mesh; + mesh.addTriangle(btVector3(0, -1, -1), btVector3(0, -1, -1), btVector3(0, 1, -1)); + mesh.addTriangle(btVector3(0, -3, -3), btVector3(0, -3, -2), btVector3(0, -2, -3)); + btBvhTriangleMeshShape shape(&mesh, true); + RecastMeshBuilder builder(mSettings, mBounds); + builder.addObject(static_cast(shape), + btTransform(btQuaternion(btVector3(1, 0, 0), static_cast(-osg::PI_4)))); + const auto recastMesh = builder.create(); + EXPECT_EQ(recastMesh->getVertices(), std::vector({ + 0, -0.70710659027099609375, -3.535533905029296875, + 0, 0.707107067108154296875, -3.535533905029296875, + 0, 2.384185791015625e-07, -4.24264049530029296875, + })); + EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_y_bhv_triangle_shape_should_filter_by_bounds) + { + mBounds.mMin = osg::Vec2f(-5, -5); + mBounds.mMax = osg::Vec2f(-3, 5); + btTriangleMesh mesh; + mesh.addTriangle(btVector3(-1, 0, -1), btVector3(-1, 0, 1), btVector3(1, 0, -1)); + mesh.addTriangle(btVector3(-3, 0, -3), btVector3(-3, 0, -2), btVector3(-2, 0, -3)); + btBvhTriangleMeshShape shape(&mesh, true); + RecastMeshBuilder builder(mSettings, mBounds); + builder.addObject(static_cast(shape), + btTransform(btQuaternion(btVector3(0, 1, 0), static_cast(osg::PI_4)))); + const auto recastMesh = builder.create(); + EXPECT_EQ(recastMesh->getVertices(), std::vector({ + -3.535533905029296875, -0.70710659027099609375, 0, + -3.535533905029296875, 0.707107067108154296875, 0, + -4.24264049530029296875, 2.384185791015625e-07, 0, + })); + EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_z_bhv_triangle_shape_should_filter_by_bounds) + { + mBounds.mMin = osg::Vec2f(-5, -5); + mBounds.mMax = osg::Vec2f(-1, -1); + btTriangleMesh mesh; + mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); + mesh.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0)); + btBvhTriangleMeshShape shape(&mesh, true); + RecastMeshBuilder builder(mSettings, mBounds); + builder.addObject(static_cast(shape), + btTransform(btQuaternion(btVector3(0, 0, 1), static_cast(osg::PI_4)))); + const auto recastMesh = builder.create(); + EXPECT_EQ(recastMesh->getVertices(), std::vector({ + 0.707107067108154296875, 0, -3.535533905029296875, + -0.70710659027099609375, 0, -3.535533905029296875, + 2.384185791015625e-07, 0, -4.24264049530029296875, + })); + EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + } } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f7aea8f379..fbb74a65a7 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -168,6 +168,7 @@ add_component_dir(detournavigator asyncnavmeshupdater chunkytrimesh recastmesh + tilecachedrecastmeshmanager ) set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index b809fc27c9..fcfc342ebf 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -40,8 +40,9 @@ namespace DetourNavigator return stream << "unknown"; } - AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings) - : mSettings(std::cref(settings)) + AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager) + : mSettings(settings) + , mRecastMeshManager(recastMeshManager) , mShouldStop() , mThread([&] { process(); }) { @@ -57,11 +58,11 @@ namespace DetourNavigator mThread.join(); } - void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& recastMesh, + void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& navMeshCacheItem, const TilePosition& playerTile, const std::set& changedTiles) { - setRecastMesh(recastMesh); + log("post jobs playerTile=", playerTile); if (changedTiles.empty()) return; @@ -107,9 +108,9 @@ namespace DetourNavigator setFirstStart(start); - const auto recastMesh = getRecastMesh(); + const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile); - const auto status = updateNavMesh(job.mAgentHalfExtents, *recastMesh, job.mChangedTile, mSettings, + const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, mSettings, *job.mNavMeshCacheItem); const auto finish = std::chrono::steady_clock::now(); @@ -162,18 +163,6 @@ namespace DetourNavigator writeToFile(*job.mNavMeshCacheItem->mValue.lock(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } - std::shared_ptr AsyncNavMeshUpdater::getRecastMesh() - { - const std::lock_guard lock(mRecastMeshMutex); - return mRecastMesh; - } - - void AsyncNavMeshUpdater::setRecastMesh(const std::shared_ptr& value) - { - const std::lock_guard lock(mRecastMeshMutex); - mRecastMesh = value; - } - std::chrono::steady_clock::time_point AsyncNavMeshUpdater::getFirstStart() { const std::lock_guard lock(mFirstStartMutex); diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 721b89cec7..e31446a92a 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -2,7 +2,7 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_ASYNCNAVMESHUPDATER_H #include "navmeshcacheitem.hpp" -#include "recastmesh.hpp" +#include "tilecachedrecastmeshmanager.hpp" #include "tileposition.hpp" #include @@ -25,12 +25,11 @@ namespace DetourNavigator class AsyncNavMeshUpdater { public: - AsyncNavMeshUpdater(const Settings& settings); + AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager); ~AsyncNavMeshUpdater(); - void post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& recastMesh, - const std::shared_ptr& navMeshCacheItem, const TilePosition& playerTile, - const std::set& changedTiles); + void post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& mNavMeshCacheItem, + const TilePosition& playerTile, const std::set& changedTiles); void wait(); @@ -51,13 +50,12 @@ namespace DetourNavigator using Jobs = std::priority_queue>; std::reference_wrapper mSettings; + std::reference_wrapper mRecastMeshManager; std::atomic_bool mShouldStop; std::mutex mMutex; std::condition_variable mHasJob; std::condition_variable mDone; Jobs mJobs; - std::mutex mRecastMeshMutex; - std::shared_ptr mRecastMesh; std::mutex mFirstStartMutex; boost::optional mFirstStart; std::thread mThread; @@ -72,10 +70,6 @@ namespace DetourNavigator void writeDebugFiles(const Job& job, const RecastMesh& recastMesh) const; - std::shared_ptr getRecastMesh(); - - void setRecastMesh(const std::shared_ptr& value); - std::chrono::steady_clock::time_point getFirstStart(); void setFirstStart(const std::chrono::steady_clock::time_point& value); diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index 60306ecc68..3699bc77d0 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -3,9 +3,10 @@ namespace DetourNavigator { - CachedRecastMeshManager::CachedRecastMeshManager(const Settings& settings) - : mImpl(settings) - {} + CachedRecastMeshManager::CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds) + : mImpl(settings, bounds) + { + } bool CachedRecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) { @@ -29,4 +30,9 @@ namespace DetourNavigator mCached = mImpl.getMesh(); return mCached; } + + bool CachedRecastMeshManager::isEmpty() const + { + return mImpl.isEmpty(); + } } diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index 6705a26f19..5185c38169 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -10,7 +10,7 @@ namespace DetourNavigator class CachedRecastMeshManager { public: - CachedRecastMeshManager(const Settings& settings); + CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds); bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); @@ -18,6 +18,8 @@ namespace DetourNavigator std::shared_ptr getMesh(); + bool isEmpty() const; + private: RecastMeshManager mImpl; std::shared_ptr mCached; diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index 96ab89fd73..6f5a372600 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -17,7 +17,7 @@ namespace DetourNavigator template void getTilesPositions(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax, - const Settings& settings, Callback&& callback) + const Settings& settings, Callback&& callback) { auto min = toNavMeshCoordinates(settings, aabbMin); auto max = toNavMeshCoordinates(settings, aabbMax); @@ -42,7 +42,7 @@ namespace DetourNavigator template void getTilesPositions(const btCollisionShape& shape, const btTransform& transform, - const Settings& settings, Callback&& callback) + const Settings& settings, Callback&& callback) { btVector3 aabbMin; btVector3 aabbMax; diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index fb2fb2bb5b..4d54eac6cb 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -7,6 +7,7 @@ #include "settings.hpp" #include "settingsutils.hpp" #include "sharednavmesh.hpp" +#include "settingsutils.hpp" #include #include @@ -269,8 +270,9 @@ namespace DetourNavigator return navMesh; } - UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, - const TilePosition& changedTile, const Settings& settings, NavMeshCacheItem& navMeshCacheItem) + UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, + const RecastMesh* recastMesh, const TilePosition& changedTile, const Settings& settings, + NavMeshCacheItem& navMeshCacheItem) { log("update NavMesh with mutiple tiles:", " agentHeight=", std::setprecision(std::numeric_limits::max_exponent10), @@ -298,8 +300,14 @@ namespace DetourNavigator incRev.mNavMeshChanged = removed; - const auto& boundsMin = recastMesh.getBoundsMin(); - const auto& boundsMax = recastMesh.getBoundsMax(); + if (!recastMesh) + { + log("ignore add tile: recastMesh is null"); + return makeUpdateNavMeshStatus(removed, false); + } + + const auto& boundsMin = recastMesh->getBoundsMin(); + const auto& boundsMax = recastMesh->getBoundsMax(); if (boundsMin == boundsMax) { @@ -311,7 +319,7 @@ namespace DetourNavigator const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y()); const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y()); - auto navMeshData = makeNavMeshTileData(agentHalfExtents, recastMesh, x, y, + auto navMeshData = makeNavMeshTileData(agentHalfExtents, *recastMesh, x, y, tileBorderMin, tileBorderMax, settings); if (!navMeshData.mValue) diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 96e655be8b..c2767dc75a 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -4,6 +4,7 @@ #include "settings.hpp" #include "navmeshcacheitem.hpp" #include "tileposition.hpp" +#include "tilebounds.hpp" #include @@ -30,8 +31,8 @@ namespace DetourNavigator NavMeshPtr makeEmptyNavMesh(const Settings& settings); - UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, - const TilePosition& changedTile, const Settings& settings, NavMeshCacheItem& navMeshCacheItem); + UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, + const TilePosition& changedTile, const Settings& settings, NavMeshCacheItem& navMeshCacheItem); } #endif diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp index ffea1fbddc..537f6df5d7 100644 --- a/components/detournavigator/navmeshcacheitem.hpp +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -11,11 +11,10 @@ namespace DetourNavigator { SharedNavMesh mValue; std::size_t mGeneration; - std::size_t mRecastMeshRevision; std::atomic_size_t mNavMeshRevision; - NavMeshCacheItem(const NavMeshPtr& value, std::size_t generation, std::size_t revision) - : mValue(value), mGeneration(generation), mRecastMeshRevision(revision), mNavMeshRevision(0) {} + NavMeshCacheItem(const NavMeshPtr& value, std::size_t generation) + : mValue(value), mGeneration(generation), mNavMeshRevision(0) {} }; } diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index ad9a3a5e70..a9fb700b70 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -3,6 +3,7 @@ #include "exceptions.hpp" #include "gettilespositions.hpp" #include "makenavmesh.hpp" +#include "navmeshcacheitem.hpp" #include "settings.hpp" #include "sharednavmesh.hpp" @@ -17,14 +18,14 @@ namespace DetourNavigator NavMeshManager::NavMeshManager(const Settings& settings) : mSettings(settings) , mRecastMeshManager(settings) - , mAsyncNavMeshUpdater(settings) - {} + , mAsyncNavMeshUpdater(settings, mRecastMeshManager) + { + } bool NavMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) { if (!mRecastMeshManager.addObject(id, shape, transform)) return false; - ++mRevision; addChangedTiles(shape, transform); return true; } @@ -34,7 +35,6 @@ namespace DetourNavigator const auto object = mRecastMeshManager.removeObject(id); if (!object) return false; - ++mRevision; addChangedTiles(*object->mShape, object->mTransform); return true; } @@ -45,8 +45,7 @@ namespace DetourNavigator if (cached != mCache.end()) return; mCache.insert(std::make_pair(agentHalfExtents, - std::make_shared(makeEmptyNavMesh(mSettings), ++mGenerationCounter, mRevision)) - ); + std::make_shared(makeEmptyNavMesh(mSettings), ++mGenerationCounter))); log("cache add for agent=", agentHalfExtents); } @@ -58,18 +57,15 @@ namespace DetourNavigator void NavMeshManager::update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents) { const auto& cached = getCached(agentHalfExtents); - if (cached->mRecastMeshRevision >= mRevision) - return; - cached->mRecastMeshRevision = mRevision; const auto changedTiles = mChangedTiles.find(agentHalfExtents); if (changedTiles != mChangedTiles.end()) { playerPosition *= mSettings.mRecastScaleFactor; std::swap(playerPosition.y(), playerPosition.z()); - mAsyncNavMeshUpdater.post(agentHalfExtents, mRecastMeshManager.getMesh(), cached, - getTilePosition(mSettings, playerPosition), changedTiles->second); + mAsyncNavMeshUpdater.post(agentHalfExtents, cached, getTilePosition(mSettings, playerPosition), + changedTiles->second); + log("cache update posted for agent=", agentHalfExtents, " changedTiles=", changedTiles->second.size()); mChangedTiles.erase(changedTiles); - log("cache update posted for agent=", agentHalfExtents); } } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 63733cf018..21328454e2 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -40,9 +40,8 @@ namespace DetourNavigator std::map> getNavMeshes() const; private: - std::size_t mRevision = 0; const Settings& mSettings; - CachedRecastMeshManager mRecastMeshManager; + TileCachedRecastMeshManager mRecastMeshManager; std::map> mCache; std::map> mChangedTiles; AsyncNavMeshUpdater mAsyncNavMeshUpdater; diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 1e0d2dfc80..a34b114acb 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -27,9 +27,13 @@ namespace DetourNavigator { using BulletHelpers::makeProcessTriangleCallback; - RecastMeshBuilder::RecastMeshBuilder(const Settings& settings) + RecastMeshBuilder::RecastMeshBuilder(const Settings& settings, const TileBounds& bounds) : mSettings(settings) - {} + , mBounds(bounds) + { + mBounds.mMin /= mSettings.get().mRecastScaleFactor; + mBounds.mMax /= mSettings.get().mRecastScaleFactor; + } void RecastMeshBuilder::addObject(const btCollisionShape& shape, const btTransform& transform) { @@ -54,7 +58,7 @@ namespace DetourNavigator void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform) { - return addObject(shape, makeProcessTriangleCallback([&] (btVector3* triangle, int, int) + return addObject(shape, transform, makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { for (std::size_t i = 3; i > 0; --i) addTriangleVertex(transform(triangle[i - 1])); @@ -63,7 +67,7 @@ namespace DetourNavigator void RecastMeshBuilder::addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform) { - return addObject(shape, makeProcessTriangleCallback([&] (btVector3* triangle, int, int) + return addObject(shape, transform, makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { for (std::size_t i = 0; i < 3; ++i) addTriangleVertex(transform(triangle[i])); @@ -111,11 +115,29 @@ namespace DetourNavigator mVertices.clear(); } - void RecastMeshBuilder::addObject(const btConcaveShape& shape, btTriangleCallback&& callback) + void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform, + btTriangleCallback&& callback) { btVector3 aabbMin; btVector3 aabbMax; shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); + const btVector3 boundsMinMin(mBounds.mMin.x(), mBounds.mMin.y(), 0); + const btVector3 boundsMinMax(mBounds.mMin.x(), mBounds.mMax.y(), 0); + const btVector3 boundsMaxMin(mBounds.mMax.x(), mBounds.mMin.y(), 0); + const btVector3 boundsMaxMax(mBounds.mMax.x(), mBounds.mMax.y(), 0); + const auto inversedTransform = transform.inverse(); + const auto localBoundsMinMin = inversedTransform(boundsMinMin); + const auto localBoundsMinMax = inversedTransform(boundsMinMax); + const auto localBoundsMaxMin = inversedTransform(boundsMaxMin); + const auto localBoundsMaxMax = inversedTransform(boundsMaxMax); + aabbMin.setX(std::min({localBoundsMinMin.x(), localBoundsMinMax.x(), + localBoundsMaxMin.x(), localBoundsMaxMax.x()})); + aabbMin.setY(std::min({localBoundsMinMin.y(), localBoundsMinMax.y(), + localBoundsMaxMin.y(), localBoundsMaxMax.y()})); + aabbMax.setX(std::max({localBoundsMinMin.x(), localBoundsMinMax.x(), + localBoundsMaxMin.x(), localBoundsMaxMax.x()})); + aabbMax.setY(std::max({localBoundsMinMin.y(), localBoundsMinMax.y(), + localBoundsMaxMin.y(), localBoundsMaxMax.y()})); shape.processAllTriangles(&callback, aabbMin, aabbMax); } diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index 28ca1f713a..d06f2cdd65 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHBUILDER_H #include "recastmesh.hpp" +#include "tilebounds.hpp" class btBoxShape; class btCollisionShape; @@ -17,7 +18,7 @@ namespace DetourNavigator class RecastMeshBuilder { public: - RecastMeshBuilder(const Settings& settings); + RecastMeshBuilder(const Settings& settings, const TileBounds& bounds); void addObject(const btCollisionShape& shape, const btTransform& transform); @@ -35,10 +36,11 @@ namespace DetourNavigator private: std::reference_wrapper mSettings; + TileBounds mBounds; std::vector mIndices; std::vector mVertices; - void addObject(const btConcaveShape& shape, btTriangleCallback&& callback); + void addObject(const btConcaveShape& shape, const btTransform& transform, btTriangleCallback&& callback); void addTriangleVertex(const btVector3& worldPosition); diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index ccfa253724..919609190a 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -4,9 +4,9 @@ namespace DetourNavigator { - RecastMeshManager::RecastMeshManager(const Settings& settings) + RecastMeshManager::RecastMeshManager(const Settings& settings, const TileBounds& bounds) : mShouldRebuild(false) - , mMeshBuilder(settings) + , mMeshBuilder(settings, bounds) { } @@ -35,6 +35,11 @@ namespace DetourNavigator return mMeshBuilder.create(); } + bool RecastMeshManager::isEmpty() const + { + return mObjects.empty(); + } + void RecastMeshManager::rebuild() { if (!mShouldRebuild) diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index 429430707b..f2aa7f871d 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -22,7 +22,7 @@ namespace DetourNavigator btTransform mTransform; }; - RecastMeshManager(const Settings& settings); + RecastMeshManager(const Settings& settings, const TileBounds& bounds); bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); @@ -30,6 +30,8 @@ namespace DetourNavigator std::shared_ptr getMesh(); + bool isEmpty() const; + private: bool mShouldRebuild; RecastMeshBuilder mMeshBuilder; diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index d0d51e1670..0b327717f5 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H #include "settings.hpp" +#include "tilebounds.hpp" #include "tileposition.hpp" #include "tilebounds.hpp" diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp new file mode 100644 index 0000000000..9c4f63e59f --- /dev/null +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -0,0 +1,71 @@ +#include "tilecachedrecastmeshmanager.hpp" +#include "makenavmesh.hpp" +#include "gettilespositions.hpp" +#include "settingsutils.hpp" + +namespace DetourNavigator +{ + TileCachedRecastMeshManager::TileCachedRecastMeshManager(const Settings& settings) + : mSettings(settings) + { + } + + bool TileCachedRecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, + const btTransform& transform) + { + bool result = false; + auto& tilesPositions = mObjectsTilesPositions[id]; + const auto border = getBorderSize(mSettings); + getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& tilePosition) + { + std::unique_lock lock(mTilesMutex); + auto tile = mTiles.find(tilePosition); + if (tile == mTiles.end()) + { + auto tileBounds = makeTileBounds(mSettings, tilePosition); + tileBounds.mMin -= osg::Vec2f(border, border); + tileBounds.mMax += osg::Vec2f(border, border); + tile = mTiles.insert(std::make_pair(tilePosition, + CachedRecastMeshManager(mSettings, tileBounds))).first; + } + if (tile->second.addObject(id, shape, transform)) + { + lock.unlock(); + tilesPositions.push_back(tilePosition); + result = true; + } + }); + return result; + } + + boost::optional TileCachedRecastMeshManager::removeObject(std::size_t id) + { + const auto object = mObjectsTilesPositions.find(id); + if (object == mObjectsTilesPositions.end()) + return boost::none; + boost::optional result; + for (const auto& tilePosition : object->second) + { + std::unique_lock lock(mTilesMutex); + const auto tile = mTiles.find(tilePosition); + if (tile == mTiles.end()) + continue; + const auto tileResult = tile->second.removeObject(id); + if (tile->second.isEmpty()) + mTiles.erase(tile); + lock.unlock(); + if (tileResult && !result) + result = tileResult; + } + return result; + } + + std::shared_ptr TileCachedRecastMeshManager::getMesh(const TilePosition& tilePosition) + { + const std::lock_guard lock(mTilesMutex); + const auto it = mTiles.find(tilePosition); + if (it == mTiles.end()) + return nullptr; + return it->second.getMesh(); + } +} diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp new file mode 100644 index 0000000000..f957dec1dc --- /dev/null +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -0,0 +1,31 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILECACHEDRECASTMESHMANAGER_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILECACHEDRECASTMESHMANAGER_H + +#include "cachedrecastmeshmanager.hpp" +#include "tileposition.hpp" + +#include +#include + +namespace DetourNavigator +{ + class TileCachedRecastMeshManager + { + public: + TileCachedRecastMeshManager(const Settings& settings); + + bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); + + boost::optional removeObject(std::size_t id); + + std::shared_ptr getMesh(const TilePosition& tilePosition); + + private: + const Settings& mSettings; + std::mutex mTilesMutex; + std::map mTiles; + std::unordered_map> mObjectsTilesPositions; + }; +} + +#endif From 4aba0fa85fde3825df7db2c2abe4021562a9cfc4 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 21 Apr 2018 02:57:01 +0300 Subject: [PATCH 060/133] Limit number of NavMesh tiles to add by distance from player tile --- apps/openmw/mwworld/scene.cpp | 4 ++ .../detournavigator/asyncnavmeshupdater.cpp | 37 ++++++++++++---- .../detournavigator/asyncnavmeshupdater.hpp | 9 +++- .../detournavigator/gettilespositions.hpp | 1 + components/detournavigator/makenavmesh.cpp | 15 +++++-- components/detournavigator/makenavmesh.hpp | 19 ++++++++- components/detournavigator/navmeshmanager.cpp | 42 +++++++++++++++---- components/detournavigator/navmeshmanager.hpp | 2 + components/detournavigator/settingsutils.hpp | 2 + .../tilecachedrecastmeshmanager.cpp | 15 +++++++ .../tilecachedrecastmeshmanager.hpp | 13 ++++++ 11 files changed, 140 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 026d067dd8..32c83b95e3 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -390,6 +390,10 @@ namespace MWWorld void Scene::playerMoved(const osg::Vec3f &pos) { + const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + navigator->update(player.getRefData().getPosition().asVec3()); + if (!mCurrentCell || !mCurrentCell->isExterior()) return; diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index fcfc342ebf..20438674af 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -64,13 +64,19 @@ namespace DetourNavigator { log("post jobs playerTile=", playerTile); + setPlayerTile(playerTile); + if (changedTiles.empty()) return; const std::lock_guard lock(mMutex); for (const auto& changedTile : changedTiles) - mJobs.push(Job {agentHalfExtents, navMeshCacheItem, changedTile, makePriority(changedTile, playerTile)}); + { + if (mPushed[agentHalfExtents].insert(changedTile).second) + mJobs.push(Job {agentHalfExtents, navMeshCacheItem, changedTile, + makePriority(changedTile, playerTile)}); + } mHasJob.notify_all(); } @@ -109,13 +115,14 @@ namespace DetourNavigator setFirstStart(start); const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile); + const auto playerTile = getPlayerTile(); - const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, mSettings, - *job.mNavMeshCacheItem); + const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile, + mSettings, *job.mNavMeshCacheItem); const auto finish = std::chrono::steady_clock::now(); - writeDebugFiles(job, *recastMesh); + writeDebugFiles(job, recastMesh.get()); using FloatMs = std::chrono::duration; @@ -139,10 +146,14 @@ namespace DetourNavigator log("got ", mJobs.size(), " jobs"); const auto job = mJobs.top(); mJobs.pop(); + const auto pushed = mPushed.find(job.mAgentHalfExtents); + pushed->second.erase(job.mChangedTile); + if (pushed->second.empty()) + mPushed.erase(pushed); return job; } - void AsyncNavMeshUpdater::writeDebugFiles(const Job& job, const RecastMesh& recastMesh) const + void AsyncNavMeshUpdater::writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const { std::string revision; std::string recastMeshRevision; @@ -157,8 +168,8 @@ namespace DetourNavigator if (mSettings.get().mEnableNavMeshFileNameRevision) navMeshRevision = revision; } - if (mSettings.get().mEnableWriteRecastMeshToFile) - writeToFile(recastMesh, mSettings.get().mRecastMeshPathPrefix, recastMeshRevision); + if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile) + writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix, recastMeshRevision); if (mSettings.get().mEnableWriteNavMeshToFile) writeToFile(*job.mNavMeshCacheItem->mValue.lock(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } @@ -175,4 +186,16 @@ namespace DetourNavigator if (!mFirstStart) mFirstStart = value; } + + TilePosition AsyncNavMeshUpdater::getPlayerTile() + { + const std::lock_guard lock(mPlayerTileMutex); + return mPlayerTile; + } + + void AsyncNavMeshUpdater::setPlayerTile(const TilePosition& value) + { + const std::lock_guard lock(mPlayerTileMutex); + mPlayerTile = value; + } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index e31446a92a..6b4facf6de 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -56,6 +56,9 @@ namespace DetourNavigator std::condition_variable mHasJob; std::condition_variable mDone; Jobs mJobs; + std::map> mPushed; + std::mutex mPlayerTileMutex; + TilePosition mPlayerTile; std::mutex mFirstStartMutex; boost::optional mFirstStart; std::thread mThread; @@ -68,11 +71,15 @@ namespace DetourNavigator void notifyHasJob(); - void writeDebugFiles(const Job& job, const RecastMesh& recastMesh) const; + void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const; std::chrono::steady_clock::time_point getFirstStart(); void setFirstStart(const std::chrono::steady_clock::time_point& value); + + TilePosition getPlayerTile(); + + void setPlayerTile(const TilePosition& value); }; } diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index 6f5a372600..b52984452a 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -3,6 +3,7 @@ #include "settings.hpp" #include "settingsutils.hpp" +#include "tileposition.hpp" #include diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 4d54eac6cb..8db1d32b49 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -270,8 +270,8 @@ namespace DetourNavigator return navMesh; } - UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, - const RecastMesh* recastMesh, const TilePosition& changedTile, const Settings& settings, + UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, + const TilePosition& changedTile, const TilePosition& playerTile, const Settings& settings, NavMeshCacheItem& navMeshCacheItem) { log("update NavMesh with mutiple tiles:", @@ -281,7 +281,9 @@ namespace DetourNavigator getMaxClimb(settings), " agentRadius=", std::setprecision(std::numeric_limits::max_exponent10), getRadius(settings, agentHalfExtents), - " changedTile=", changedTile); + " changedTile=", changedTile, + " playerTile=", playerTile, + " changedTileDistance=", getDistance(changedTile, playerTile)); auto& navMesh = navMeshCacheItem.mValue; const auto& params = *navMesh.lock()->getParams(); @@ -315,6 +317,13 @@ namespace DetourNavigator return makeUpdateNavMeshStatus(removed, false); } + const auto maxTiles = navMesh.lock()->getParams()->maxTiles; + if (!shouldAddTile(changedTile, playerTile, maxTiles)) + { + log("ignore add tile: too far from player"); + return makeUpdateNavMeshStatus(removed, false); + } + const auto tileBounds = makeTileBounds(settings, changedTile); const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y()); const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y()); diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index c2767dc75a..cc9e6d8559 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -29,10 +29,27 @@ namespace DetourNavigator replaced }; + inline float getLength(const osg::Vec2i& value) + { + return std::sqrt(float(osg::square(value.x()) + osg::square(value.y()))); + } + + inline float getDistance(const TilePosition& lhs, const TilePosition& rhs) + { + return getLength(lhs - rhs); + } + + inline bool shouldAddTile(const TilePosition& changedTile, const TilePosition& playerTile, int maxTiles) + { + const auto expectedTilesCount = std::ceil(osg::PI * osg::square(getDistance(changedTile, playerTile))); + return expectedTilesCount * 3 <= maxTiles; + } + NavMeshPtr makeEmptyNavMesh(const Settings& settings); UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, - const TilePosition& changedTile, const Settings& settings, NavMeshCacheItem& navMeshCacheItem); + const TilePosition& changedTile, const TilePosition& playerTile, const Settings& settings, + NavMeshCacheItem& navMeshCacheItem); } #endif diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index a9fb700b70..5f6cdb93c3 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -56,17 +56,45 @@ namespace DetourNavigator void NavMeshManager::update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents) { + playerPosition *= mSettings.mRecastScaleFactor; + std::swap(playerPosition.y(), playerPosition.z()); + const auto playerTile = getTilePosition(mSettings, playerPosition); + if (mLastRecastMeshManagerRevision >= mRecastMeshManager.getRevision() && mPlayerTile + && *mPlayerTile == playerTile) + return; + mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision(); + mPlayerTile = playerTile; + std::set tilesToPost; const auto& cached = getCached(agentHalfExtents); const auto changedTiles = mChangedTiles.find(agentHalfExtents); - if (changedTiles != mChangedTiles.end()) { - playerPosition *= mSettings.mRecastScaleFactor; - std::swap(playerPosition.y(), playerPosition.z()); - mAsyncNavMeshUpdater.post(agentHalfExtents, cached, getTilePosition(mSettings, playerPosition), - changedTiles->second); - log("cache update posted for agent=", agentHalfExtents, " changedTiles=", changedTiles->second.size()); - mChangedTiles.erase(changedTiles); + const auto locked = cached->mValue.lock(); + if (changedTiles != mChangedTiles.end()) + { + for (const auto& tile : changedTiles->second) + if (locked->getTileAt(tile.x(), tile.y(), 0)) + tilesToPost.insert(tile); + for (const auto& tile : tilesToPost) + changedTiles->second.erase(tile); + if (changedTiles->second.empty()) + mChangedTiles.erase(changedTiles); + } + const auto maxTiles = locked->getParams()->maxTiles; + mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile) + { + if (tilesToPost.count(tile)) + return; + const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles); + const auto presentInNavMesh = bool(locked->getTileAt(tile.x(), tile.y(), 0)); + if ((shouldAdd && !presentInNavMesh) || (!shouldAdd && presentInNavMesh)) + tilesToPost.insert(tile); + }); } + mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); + log("cache update posted for agent=", agentHalfExtents, + " playerTile=", *mPlayerTile, + " recastMeshManagerRevision=", mLastRecastMeshManagerRevision, + " changedTiles=", changedTiles->second.size()); } void NavMeshManager::wait() diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 21328454e2..f0b273efd0 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -46,6 +46,8 @@ namespace DetourNavigator std::map> mChangedTiles; AsyncNavMeshUpdater mAsyncNavMeshUpdater; std::size_t mGenerationCounter = 0; + boost::optional mPlayerTile; + std::size_t mLastRecastMeshManagerRevision = 0; void addChangedTiles(const btCollisionShape& shape, const btTransform& transform); diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index 0b327717f5..8127536862 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -6,6 +6,8 @@ #include "tileposition.hpp" #include "tilebounds.hpp" +#include +#include #include #include diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 9c4f63e59f..eb4dffcb6b 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -35,6 +35,8 @@ namespace DetourNavigator result = true; } }); + if (result) + ++mRevision; return result; } @@ -57,6 +59,8 @@ namespace DetourNavigator if (tileResult && !result) result = tileResult; } + if (result) + ++mRevision; return result; } @@ -68,4 +72,15 @@ namespace DetourNavigator return nullptr; return it->second.getMesh(); } + + bool TileCachedRecastMeshManager::hasTile(const TilePosition& tilePosition) + { + const std::lock_guard lock(mTilesMutex); + return mTiles.count(tilePosition); + } + + std::size_t TileCachedRecastMeshManager::getRevision() const + { + return mRevision; + } } diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index f957dec1dc..1c685b2ec6 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -20,11 +20,24 @@ namespace DetourNavigator std::shared_ptr getMesh(const TilePosition& tilePosition); + bool hasTile(const TilePosition& tilePosition); + + template + void forEachTilePosition(Function&& function) + { + const std::lock_guard lock(mTilesMutex); + for (const auto& tile : mTiles) + function(tile.first); + } + + std::size_t getRevision() const; + private: const Settings& mSettings; std::mutex mTilesMutex; std::map mTiles; std::unordered_map> mObjectsTilesPositions; + std::size_t mRevision = 0; }; } From 144e1a063b320e94ccb9d2458a6c139925191b64 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 26 May 2018 17:44:25 +0300 Subject: [PATCH 061/133] Support animated objects --- apps/openmw/mwphysics/physicssystem.hpp | 8 + apps/openmw/mwworld/worldimp.cpp | 19 +- apps/openmw_test_suite/CMakeLists.txt | 1 + .../detournavigator/navigator.cpp | 171 ++++++++++++++++++ .../detournavigator/recastmeshobject.cpp | 66 +++++++ components/CMakeLists.txt | 1 + .../detournavigator/asyncnavmeshupdater.cpp | 5 +- .../cachedrecastmeshmanager.cpp | 10 +- .../cachedrecastmeshmanager.hpp | 4 +- components/detournavigator/debug.hpp | 9 + components/detournavigator/navigator.cpp | 5 + components/detournavigator/navigator.hpp | 2 + components/detournavigator/navmeshmanager.cpp | 10 +- components/detournavigator/navmeshmanager.hpp | 2 + .../detournavigator/recastmeshmanager.cpp | 22 ++- .../detournavigator/recastmeshmanager.hpp | 19 +- .../detournavigator/recastmeshobject.cpp | 58 ++++++ .../detournavigator/recastmeshobject.hpp | 44 +++++ .../tilecachedrecastmeshmanager.cpp | 23 ++- .../tilecachedrecastmeshmanager.hpp | 4 +- 20 files changed, 460 insertions(+), 23 deletions(-) create mode 100644 apps/openmw_test_suite/detournavigator/recastmeshobject.cpp create mode 100644 components/detournavigator/recastmeshobject.cpp create mode 100644 components/detournavigator/recastmeshobject.hpp diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index b084cc4c9e..21fbe3f17a 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -174,6 +175,13 @@ namespace MWPhysics void markAsNonSolid (const MWWorld::ConstPtr& ptr); bool isOnSolidGround (const MWWorld::Ptr& actor) const; + + template + void forEachAnimatedObject(Function&& function) const + { + std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function); + } + private: void updateWater(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ce604580d0..9b8f97e8c7 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include @@ -51,6 +53,7 @@ #include "../mwphysics/physicssystem.hpp" #include "../mwphysics/actor.hpp" #include "../mwphysics/collisiontype.hpp" +#include "../mwphysics/object.hpp" #include "player.hpp" #include "manualref.hpp" @@ -1535,6 +1538,16 @@ namespace MWWorld } if(player != results.end()) moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false); + + bool navigatorObjectsUpdated = false; + mPhysics->forEachAnimatedObject([&] (const MWPhysics::Object* object) + { + navigatorObjectsUpdated = mNavigator->updateObject(std::size_t(object), + *object->getCollisionObject()->getCollisionShape(), + object->getCollisionObject()->getWorldTransform()) || navigatorObjectsUpdated; + }); + if (navigatorObjectsUpdated) + mNavigator->update(getPlayerPtr().getRefData().getPosition().asVec3()); } bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors) @@ -2254,7 +2267,7 @@ namespace MWWorld float waterlevel = cell->getWaterLevel(); - // SwimHeightScale affects the upper z position an actor can swim to + // SwimHeightScale affects the upper z position an actor can swim to // while in water. Based on observation from the original engine, // the upper z position you get with a +1 SwimHeightScale is the depth // limit for being able to cast water walking on an underwater target. @@ -2400,7 +2413,7 @@ namespace MWWorld { mRendering->screenshot(image, w, h); } - + bool World::screenshot360(osg::Image* image, std::string settingStr) { return mRendering->screenshot360(image,settingStr); @@ -3573,7 +3586,7 @@ namespace MWWorld continue; } else - mRendering->spawnEffect("meshes\\" + areaStatic->mModel, texture, origin, static_cast(effectIt->mArea * 2)); + mRendering->spawnEffect("meshes\\" + areaStatic->mModel, texture, origin, static_cast(effectIt->mArea * 2)); // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now) static const std::string schools[] = { diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index fc04648e5a..b86f4b812c 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -22,6 +22,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) detournavigator/settingsutils.cpp detournavigator/recastmeshbuilder.cpp detournavigator/gettilespositions.cpp + detournavigator/recastmeshobject.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 0e475ad9fc..de0636ca11 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -4,6 +4,8 @@ #include #include +#include +#include #include @@ -131,6 +133,175 @@ namespace })) << mPath; } + TEST_F(DetourNavigatorNavigatorTest, add_object_should_change_navmesh) + { + const std::array heightfieldData {{ + 0, 0, 0, 0, 0, + 0, -25, -25, -25, -25, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + }}; + btHeightfieldTerrainShape heightfieldShape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); + + btBoxShape boxShape(btVector3(20, 20, 100)); + btCompoundShape compoundShape; + compoundShape.addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), &boxShape); + + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addObject(1, heightfieldShape, btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, std::back_inserter(mPath)); + + EXPECT_EQ(mPath, std::deque({ + osg::Vec3f(-215, 215, 1.85963428020477294921875), + osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.5760211944580078125), + osg::Vec3f(-174.930633544921875, 174.930633544921875, -15.01167774200439453125), + osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473323822021484375), + osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829898834228515625), + osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875), + osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.39907073974609375), + osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375), + osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625), + osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37929534912109375), + osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875), + osg::Vec3f(5.3815765380859375, -5.3815765380859375, -75.35065460205078125), + osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.96945953369140625), + osg::Vec3f(45.450958251953125, -45.450958251953125, -60.58824920654296875), + osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), + osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.825855255126953125), + osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), + osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625), + osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625), + osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875), + osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), + osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.53864824771881103515625), + osg::Vec3f(215, -215, 1.877177715301513671875), + })) << mPath; + + mNavigator->addObject(2, compoundShape, btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + mPath.clear(); + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, std::back_inserter(mPath)); + + EXPECT_EQ(mPath, std::deque({ + osg::Vec3f(-215, 215, 1.87827122211456298828125), + osg::Vec3f(-199.7968292236328125, 191.09100341796875, -3.54876613616943359375), + osg::Vec3f(-184.5936431884765625, 167.1819915771484375, -8.97847270965576171875), + osg::Vec3f(-169.3904571533203125, 143.2729949951171875, -14.40817737579345703125), + osg::Vec3f(-154.1872711181640625, 119.36397552490234375, -19.837890625), + osg::Vec3f(-138.9840850830078125, 95.45496368408203125, -25.2675952911376953125), + osg::Vec3f(-123.78090667724609375, 71.54595184326171875, -30.6972980499267578125), + osg::Vec3f(-108.57772064208984375, 47.636936187744140625, -36.12701416015625), + osg::Vec3f(-93.3745269775390625, 23.7279262542724609375, -40.754688262939453125), + osg::Vec3f(-78.17134857177734375, -0.18108306825160980224609375, -37.128787994384765625), + osg::Vec3f(-62.968158721923828125, -24.0900936126708984375, -33.50289154052734375), + osg::Vec3f(-47.764972686767578125, -47.999103546142578125, -30.797946929931640625), + osg::Vec3f(-23.852447509765625, -63.196765899658203125, -33.97112274169921875), + osg::Vec3f(0.0600789971649646759033203125, -78.39443206787109375, -37.14543914794921875), + osg::Vec3f(23.97260284423828125, -93.5920867919921875, -40.7740936279296875), + osg::Vec3f(47.885128021240234375, -108.78974151611328125, -36.051288604736328125), + osg::Vec3f(71.7976531982421875, -123.98740386962890625, -30.62355804443359375), + osg::Vec3f(95.71018218994140625, -139.18505859375, -25.1958160400390625), + osg::Vec3f(119.6226959228515625, -154.382720947265625, -19.7680912017822265625), + osg::Vec3f(143.53521728515625, -169.58038330078125, -14.3403491973876953125), + osg::Vec3f(167.4477386474609375, -184.778045654296875, -8.91261768341064453125), + osg::Vec3f(191.360260009765625, -199.9757080078125, -3.484879016876220703125), + osg::Vec3f(215, -215, 1.87827455997467041015625), + })) << mPath; + } + + TEST_F(DetourNavigatorNavigatorTest, update_changed_object_should_change_navmesh) + { + const std::array heightfieldData {{ + 0, 0, 0, 0, 0, + 0, -25, -25, -25, -25, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + }}; + btHeightfieldTerrainShape heightfieldShape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); + + btBoxShape boxShape(btVector3(20, 20, 100)); + btCompoundShape compoundShape; + compoundShape.addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), &boxShape); + + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addObject(1, heightfieldShape, btTransform::getIdentity()); + mNavigator->addObject(2, compoundShape, btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, std::back_inserter(mPath)); + + EXPECT_EQ(mPath, std::deque({ + osg::Vec3f(-215, 215, 1.87827122211456298828125), + osg::Vec3f(-199.7968292236328125, 191.09100341796875, -3.54876613616943359375), + osg::Vec3f(-184.5936431884765625, 167.1819915771484375, -8.97847270965576171875), + osg::Vec3f(-169.3904571533203125, 143.2729949951171875, -14.40817737579345703125), + osg::Vec3f(-154.1872711181640625, 119.36397552490234375, -19.837890625), + osg::Vec3f(-138.9840850830078125, 95.45496368408203125, -25.2675952911376953125), + osg::Vec3f(-123.78090667724609375, 71.54595184326171875, -30.6972980499267578125), + osg::Vec3f(-108.57772064208984375, 47.636936187744140625, -36.12701416015625), + osg::Vec3f(-93.3745269775390625, 23.7279262542724609375, -40.754688262939453125), + osg::Vec3f(-78.17134857177734375, -0.18108306825160980224609375, -37.128787994384765625), + osg::Vec3f(-62.968158721923828125, -24.0900936126708984375, -33.50289154052734375), + osg::Vec3f(-47.764972686767578125, -47.999103546142578125, -30.797946929931640625), + osg::Vec3f(-23.852447509765625, -63.196765899658203125, -33.97112274169921875), + osg::Vec3f(0.0600789971649646759033203125, -78.39443206787109375, -37.14543914794921875), + osg::Vec3f(23.97260284423828125, -93.5920867919921875, -40.7740936279296875), + osg::Vec3f(47.885128021240234375, -108.78974151611328125, -36.051288604736328125), + osg::Vec3f(71.7976531982421875, -123.98740386962890625, -30.62355804443359375), + osg::Vec3f(95.71018218994140625, -139.18505859375, -25.1958160400390625), + osg::Vec3f(119.6226959228515625, -154.382720947265625, -19.7680912017822265625), + osg::Vec3f(143.53521728515625, -169.58038330078125, -14.3403491973876953125), + osg::Vec3f(167.4477386474609375, -184.778045654296875, -8.91261768341064453125), + osg::Vec3f(191.360260009765625, -199.9757080078125, -3.484879016876220703125), + osg::Vec3f(215, -215, 1.87827455997467041015625), + })) << mPath; + + compoundShape.updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0))); + + mNavigator->updateObject(2, compoundShape, btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + mPath.clear(); + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); + + EXPECT_EQ(mPath, std::deque({ + osg::Vec3f(-215, 215, 1.85963428020477294921875), + osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.5760211944580078125), + osg::Vec3f(-174.930633544921875, 174.930633544921875, -15.01167774200439453125), + osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473323822021484375), + osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829898834228515625), + osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875), + osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.39907073974609375), + osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375), + osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625), + osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37929534912109375), + osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875), + osg::Vec3f(5.3815765380859375, -5.3815765380859375, -75.35065460205078125), + osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.96945953369140625), + osg::Vec3f(45.450958251953125, -45.450958251953125, -60.58824920654296875), + osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), + osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.825855255126953125), + osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), + osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625), + osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625), + osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875), + osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), + osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.53864824771881103515625), + osg::Vec3f(215, -215, 1.877177715301513671875), + })) << mPath; + } + TEST_F(DetourNavigatorNavigatorTest, for_overlapping_heightfields_should_use_higher) { const std::array heightfieldData {{ diff --git a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp new file mode 100644 index 0000000000..b0f03aa655 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp @@ -0,0 +1,66 @@ +#include "operators.hpp" + +#include + +#include +#include + +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator; + + struct DetourNavigatorRecastMeshObjectTest : Test + { + btBoxShape mBoxShape {btVector3(1, 2, 3)}; + btCompoundShape mCompoundShape {btVector3(1, 2, 3)}; + btTransform mTransform {btQuaternion(btVector3(1, 2, 3), 1), btVector3(1, 2, 3)}; + + DetourNavigatorRecastMeshObjectTest() + { + mCompoundShape.addChildShape(mTransform, std::addressof(mBoxShape)); + } + }; + + TEST_F(DetourNavigatorRecastMeshObjectTest, constructed_object_should_have_shape_and_transform) + { + const RecastMeshObject object(mBoxShape, mTransform); + EXPECT_EQ(std::addressof(object.getShape()), std::addressof(mBoxShape)); + EXPECT_EQ(object.getTransform(), mTransform); + } + + TEST_F(DetourNavigatorRecastMeshObjectTest, update_with_same_transform_for_not_compound_shape_should_return_false) + { + RecastMeshObject object(mBoxShape, mTransform); + EXPECT_FALSE(object.update(mTransform)); + } + + TEST_F(DetourNavigatorRecastMeshObjectTest, update_with_different_transform_should_return_true) + { + RecastMeshObject object(mBoxShape, mTransform); + EXPECT_TRUE(object.update(btTransform::getIdentity())); + } + + TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_compound_shape_with_same_transform_and_not_changed_child_transform_should_return_false) + { + RecastMeshObject object(mCompoundShape, mTransform); + EXPECT_FALSE(object.update(mTransform)); + } + + TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_compound_shape_with_same_transform_and_changed_child_transform_should_return_true) + { + RecastMeshObject object(mCompoundShape, mTransform); + mCompoundShape.updateChildTransform(0, btTransform::getIdentity()); + EXPECT_TRUE(object.update(mTransform)); + } + + TEST_F(DetourNavigatorRecastMeshObjectTest, repeated_update_for_compound_shape_without_changes_should_return_false) + { + RecastMeshObject object(mCompoundShape, mTransform); + mCompoundShape.updateChildTransform(0, btTransform::getIdentity()); + object.update(mTransform); + EXPECT_FALSE(object.update(mTransform)); + } +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index fbb74a65a7..7ca7ce6734 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -169,6 +169,7 @@ add_component_dir(detournavigator chunkytrimesh recastmesh tilecachedrecastmeshmanager + recastmeshobject ) set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 20438674af..91af31e22e 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -78,6 +78,8 @@ namespace DetourNavigator makePriority(changedTile, playerTile)}); } + log("posted ", mJobs.size(), " jobs"); + mHasJob.notify_all(); } @@ -169,7 +171,8 @@ namespace DetourNavigator navMeshRevision = revision; } if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile) - writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix, recastMeshRevision); + writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x()) + + "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision); if (mSettings.get().mEnableWriteNavMeshToFile) writeToFile(*job.mNavMeshCacheItem->mValue.lock(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index 3699bc77d0..cba3946e00 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -16,7 +16,15 @@ namespace DetourNavigator return true; } - boost::optional CachedRecastMeshManager::removeObject(std::size_t id) + bool CachedRecastMeshManager::updateObject(std::size_t id, const btTransform& transform) + { + if (!mImpl.updateObject(id, transform)) + return false; + mCached.reset(); + return true; + } + + boost::optional CachedRecastMeshManager::removeObject(std::size_t id) { const auto object = mImpl.removeObject(id); if (object) diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index 5185c38169..71229ab2f4 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -14,7 +14,9 @@ namespace DetourNavigator bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); - boost::optional removeObject(std::size_t id); + bool updateObject(std::size_t id, const btTransform& transform); + + boost::optional removeObject(std::size_t id); std::shared_ptr getMesh(); diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp index 520851fbe6..bc52543221 100644 --- a/components/detournavigator/debug.hpp +++ b/components/detournavigator/debug.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,13 @@ namespace DetourNavigator class RecastMesh; + inline std::ostream& operator <<(std::ostream& stream, const std::chrono::steady_clock::time_point& value) + { + using float_s = std::chrono::duration>; + return stream << std::fixed << std::setprecision(4) + << std::chrono::duration_cast(value.time_since_epoch()).count(); + } + class Log { public: @@ -88,6 +96,7 @@ namespace DetourNavigator if (!log.isEnabled()) return; std::ostringstream stream; + stream << '[' << std::chrono::steady_clock::now() << "] "; write(stream, std::forward(values) ...); log.write(stream.str()); } diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index e2fc976a3a..913ba2e2ad 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -30,6 +30,11 @@ namespace DetourNavigator return mNavMeshManager.addObject(id, shape, transform); } + bool Navigator::updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) + { + return mNavMeshManager.updateObject(id, shape, transform); + } + bool Navigator::removeObject(std::size_t id) { return mNavMeshManager.removeObject(id); diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index b8d77c705a..69529a9d45 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -19,6 +19,8 @@ namespace DetourNavigator bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); + bool updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); + bool removeObject(std::size_t id); void update(const osg::Vec3f& playerPosition); diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 5f6cdb93c3..c12351ee0a 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -30,12 +30,20 @@ namespace DetourNavigator return true; } + bool NavMeshManager::updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) + { + if (!mRecastMeshManager.updateObject(id, transform)) + return false; + addChangedTiles(shape, transform); + return true; + } + bool NavMeshManager::removeObject(std::size_t id) { const auto object = mRecastMeshManager.removeObject(id); if (!object) return false; - addChangedTiles(*object->mShape, object->mTransform); + addChangedTiles(object->mShape, object->mTransform); return true; } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index f0b273efd0..109aa2237f 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -25,6 +25,8 @@ namespace DetourNavigator bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); + bool updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); + bool removeObject(std::size_t id); void addAgent(const osg::Vec3f& agentHalfExtents); diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 919609190a..d4b905e7de 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -1,5 +1,6 @@ #include "recastmeshmanager.hpp" +#include #include namespace DetourNavigator @@ -12,18 +13,29 @@ namespace DetourNavigator bool RecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) { - if (!mObjects.insert(std::make_pair(id, Object {&shape, transform})).second) + if (!mObjects.emplace(id, RecastMeshObject(shape, transform)).second) return false; mShouldRebuild = true; - return true; + return mShouldRebuild; } - boost::optional RecastMeshManager::removeObject(std::size_t id) + bool RecastMeshManager::updateObject(std::size_t id, const btTransform& transform) + { + const auto object = mObjects.find(id); + if (object == mObjects.end()) + return false; + if (!object->second.update(transform)) + return false; + mShouldRebuild = true; + return mShouldRebuild; + } + + boost::optional RecastMeshManager::removeObject(std::size_t id) { const auto object = mObjects.find(id); if (object == mObjects.end()) return boost::none; - const auto result = object->second; + const RemovedRecastMeshObject result {object->second.getShape(), object->second.getTransform()}; mObjects.erase(object); mShouldRebuild = true; return result; @@ -46,7 +58,7 @@ namespace DetourNavigator return; mMeshBuilder.reset(); for (const auto& v : mObjects) - mMeshBuilder.addObject(*v.second.mShape, v.second.mTransform); + mMeshBuilder.addObject(v.second.getShape(), v.second.getTransform()); mShouldRebuild = false; } } diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index f2aa7f871d..c8e32714e2 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H #include "recastmeshbuilder.hpp" +#include "recastmeshobject.hpp" #include @@ -13,20 +14,22 @@ class btCollisionShape; namespace DetourNavigator { + struct RemovedRecastMeshObject + { + std::reference_wrapper mShape; + btTransform mTransform; + }; + class RecastMeshManager { public: - struct Object - { - const btCollisionShape* mShape; - btTransform mTransform; - }; - RecastMeshManager(const Settings& settings, const TileBounds& bounds); bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); - boost::optional removeObject(std::size_t id); + bool updateObject(std::size_t id, const btTransform& transform); + + boost::optional removeObject(std::size_t id); std::shared_ptr getMesh(); @@ -35,7 +38,7 @@ namespace DetourNavigator private: bool mShouldRebuild; RecastMeshBuilder mMeshBuilder; - std::unordered_map mObjects; + std::unordered_map mObjects; void rebuild(); }; diff --git a/components/detournavigator/recastmeshobject.cpp b/components/detournavigator/recastmeshobject.cpp new file mode 100644 index 0000000000..b6727ba5e1 --- /dev/null +++ b/components/detournavigator/recastmeshobject.cpp @@ -0,0 +1,58 @@ +#include "recastmeshobject.hpp" + +#include + +#include + +#include + +namespace DetourNavigator +{ + RecastMeshObject::RecastMeshObject(const btCollisionShape& shape, const btTransform& transform) + : mShape(shape) + , mTransform(transform) + , mChildren(makeChildrenObjects(shape)) + { + } + + bool RecastMeshObject::update(const btTransform& transform) + { + bool result = false; + if (!(mTransform == transform)) + { + mTransform = transform; + result = true; + } + if (mShape.get().isCompound()) + result = updateCompoundObject(static_cast(mShape.get()), mChildren) || result; + return result; + } + + bool RecastMeshObject::updateCompoundObject(const btCompoundShape& shape, std::vector& children) + { + assert(static_cast(shape.getNumChildShapes()) == children.size()); + bool result = false; + for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) + { + assert(shape.getChildShape(i) == std::addressof(children[static_cast(i)].mShape.get())); + result = children[static_cast(i)].update(shape.getChildTransform(i)) || result; + } + return result; + } + + std::vector makeChildrenObjects(const btCollisionShape& shape) + { + if (shape.isCompound()) + return makeChildrenObjects(static_cast(shape)); + else + return std::vector(); + } + + std::vector makeChildrenObjects(const btCompoundShape& shape) + { + std::vector result; + for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) + result.emplace_back(*shape.getChildShape(i), shape.getChildTransform(i)); + return result; + } +} diff --git a/components/detournavigator/recastmeshobject.hpp b/components/detournavigator/recastmeshobject.hpp new file mode 100644 index 0000000000..697acfa58e --- /dev/null +++ b/components/detournavigator/recastmeshobject.hpp @@ -0,0 +1,44 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHOBJECT_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHOBJECT_H + +#include + +#include +#include + +class btCollisionShape; +class btCompoundShape; + +namespace DetourNavigator +{ + class RecastMeshObject + { + public: + RecastMeshObject(const btCollisionShape& shape, const btTransform& transform); + + bool update(const btTransform& transform); + + const btCollisionShape& getShape() const + { + return mShape; + } + + const btTransform& getTransform() const + { + return mTransform; + } + + private: + std::reference_wrapper mShape; + btTransform mTransform; + std::vector mChildren; + + static bool updateCompoundObject(const btCompoundShape& shape, std::vector& children); + }; + + std::vector makeChildrenObjects(const btCollisionShape& shape); + + std::vector makeChildrenObjects(const btCompoundShape& shape); +} + +#endif diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index eb4dffcb6b..346ab9fd1c 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -40,12 +40,31 @@ namespace DetourNavigator return result; } - boost::optional TileCachedRecastMeshManager::removeObject(std::size_t id) + bool TileCachedRecastMeshManager::updateObject(std::size_t id, const btTransform& transform) + { + const auto object = mObjectsTilesPositions.find(id); + if (object == mObjectsTilesPositions.end()) + return false; + bool result = false; + std::unique_lock lock(mTilesMutex); + for (const auto& tilePosition : object->second) + { + const auto tile = mTiles.find(tilePosition); + if (tile != mTiles.end()) + result = tile->second.updateObject(id, transform) || result; + } + lock.unlock(); + if (result) + ++mRevision; + return result; + } + + boost::optional TileCachedRecastMeshManager::removeObject(std::size_t id) { const auto object = mObjectsTilesPositions.find(id); if (object == mObjectsTilesPositions.end()) return boost::none; - boost::optional result; + boost::optional result; for (const auto& tilePosition : object->second) { std::unique_lock lock(mTilesMutex); diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 1c685b2ec6..9e5c07e846 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -16,7 +16,9 @@ namespace DetourNavigator bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); - boost::optional removeObject(std::size_t id); + bool updateObject(std::size_t id, const btTransform& transform); + + boost::optional removeObject(std::size_t id); std::shared_ptr getMesh(const TilePosition& tilePosition); From 84949bedb1ef418ad6b6ddd8d2a0a0da3664cf80 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 3 Jun 2018 15:27:10 +0300 Subject: [PATCH 062/133] Remove and add tile in single critical section --- components/detournavigator/makenavmesh.cpp | 64 +++++++++------------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 8db1d32b49..f671344962 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -216,23 +216,6 @@ namespace return NavMeshData(navMeshData, navMeshDataSize); } - struct AutoIncrementRevision - { - std::atomic_size_t& mNavMeshRevision; - bool mNavMeshChanged; - - AutoIncrementRevision(std::atomic_size_t& navMeshRevision) - : mNavMeshRevision(navMeshRevision) - , mNavMeshChanged(false) - {} - - ~AutoIncrementRevision() - { - if (mNavMeshChanged) - ++mNavMeshRevision; - } - }; - UpdateNavMeshStatus makeUpdateNavMeshStatus(bool removed, bool add) { if (removed && add) @@ -292,20 +275,17 @@ namespace DetourNavigator const auto x = changedTile.x(); const auto y = changedTile.y(); - AutoIncrementRevision incRev(navMeshCacheItem.mNavMeshRevision); - bool removed = false; - - { + const auto removeTile = [&] { const auto locked = navMesh.lock(); - removed = dtStatusSucceed(locked->removeTile(locked->getTileRefAt(x, y, 0), nullptr, nullptr)); - } - - incRev.mNavMeshChanged = removed; + const auto removed = dtStatusSucceed(locked->removeTile(locked->getTileRefAt(x, y, 0), nullptr, nullptr)); + navMeshCacheItem.mNavMeshRevision += removed; + return makeUpdateNavMeshStatus(removed, false); + }; if (!recastMesh) { log("ignore add tile: recastMesh is null"); - return makeUpdateNavMeshStatus(removed, false); + return removeTile(); } const auto& boundsMin = recastMesh->getBoundsMin(); @@ -314,14 +294,14 @@ namespace DetourNavigator if (boundsMin == boundsMax) { log("ignore add tile: recastMesh is empty"); - return makeUpdateNavMeshStatus(removed, false); + return removeTile(); } const auto maxTiles = navMesh.lock()->getParams()->maxTiles; if (!shouldAddTile(changedTile, playerTile, maxTiles)) { log("ignore add tile: too far from player"); - return makeUpdateNavMeshStatus(removed, false); + return removeTile(); } const auto tileBounds = makeTileBounds(settings, changedTile); @@ -334,17 +314,27 @@ namespace DetourNavigator if (!navMeshData.mValue) { log("ignore add tile: NavMeshData is null"); - return makeUpdateNavMeshStatus(removed, false); + return removeTile(); } - const auto status = navMesh.lock()->addTile(navMeshData.mValue.get(), navMeshData.mSize, - DT_TILE_FREE_DATA, 0, 0); - if (dtStatusSucceed(status)) - incRev.mNavMeshChanged = true; - else - log("failed to add tile with status=", WriteDtStatus {status}); - navMeshData.mValue.release(); + dtStatus addStatus; + bool removed; + { + const auto locked = navMesh.lock(); + removed = dtStatusSucceed(locked->removeTile(locked->getTileRefAt(x, y, 0), nullptr, nullptr)); + addStatus = locked->addTile(navMeshData.mValue.get(), navMeshData.mSize, DT_TILE_FREE_DATA, 0, 0); + } - return makeUpdateNavMeshStatus(removed, dtStatusSucceed(status)); + if (dtStatusSucceed(addStatus)) + { + ++navMeshCacheItem.mNavMeshRevision; + navMeshData.mValue.release(); + return makeUpdateNavMeshStatus(removed, true); + } + else + { + log("failed to add tile with status=", WriteDtStatus {addStatus}); + return makeUpdateNavMeshStatus(removed, false); + } } } From c3298d13a6c19ee171cbf895e4136538c416dade Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 7 Jul 2018 16:19:24 +0300 Subject: [PATCH 063/133] Add log sinks (stdout and file) --- apps/openmw/mwworld/worldimp.cpp | 4 +- components/detournavigator/debug.hpp | 65 ++++++++++++++++++++-------- files/settings-default.cfg | 3 ++ 3 files changed, 52 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 9b8f97e8c7..94227a7245 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -193,7 +193,9 @@ namespace MWWorld navigatorSettings.mNavMeshPathPrefix = Settings::Manager::getString("nav mesh path prefix", "Navigator"); navigatorSettings.mEnableRecastMeshFileNameRevision = Settings::Manager::getBool("enable recast mesh file name revision", "Navigator"); navigatorSettings.mEnableNavMeshFileNameRevision = Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); - DetourNavigator::Log::instance().setEnabled(Settings::Manager::getBool("enable log", "Navigator")); + if (Settings::Manager::getBool("enable log", "Navigator")) + DetourNavigator::Log::instance().setSink(std::unique_ptr( + new DetourNavigator::FileSink(Settings::Manager::getString("log path", "Navigator")))); mNavigator.reset(new DetourNavigator::Navigator(navigatorSettings)); mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, &mFallback, resourcePath, *mNavigator)); diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp index bc52543221..2a19bcce85 100644 --- a/components/detournavigator/debug.hpp +++ b/components/detournavigator/debug.hpp @@ -6,11 +6,11 @@ #include #include -#include #include #include #include #include +#include #include #include #include @@ -33,36 +33,64 @@ namespace DetourNavigator << std::chrono::duration_cast(value.time_since_epoch()).count(); } - class Log + struct Sink + { + virtual ~Sink() = default; + virtual void write(const std::string& text) = 0; + }; + + class FileSink final : public Sink { public: - Log() - : mEnabled() + FileSink(std::string path) + : mPath(std::move(path)) { mFile.exceptions(std::ios::failbit | std::ios::badbit); } - void setEnabled(bool value) + void write(const std::string& text) override { - mEnabled = value; + if (!mFile.is_open()) + { + mFile.open(mPath); + } + mFile << text << std::flush; + } + + private: + std::string mPath; + std::ofstream mFile; + }; + + class StdoutSink final : public Sink + { + public: + void write(const std::string& text) override + { + std::cout << text << std::flush; + } + }; + + class Log + { + public: + void setSink(std::unique_ptr sink) + { + const std::lock_guard guard(mMutex); + mSink = std::move(sink); } bool isEnabled() const { - return mEnabled; + const std::lock_guard guard(mMutex); + return bool(mSink); } void write(const std::string& text) { - if (mEnabled) - { - const std::lock_guard lock(mMutex); - if (!mFile.is_open()) - { - mFile.open("detournavigator.log"); - } - mFile << text << std::flush; - } + const std::lock_guard guard(mMutex); + if (mSink) + mSink->write(text); } static Log& instance() @@ -72,9 +100,8 @@ namespace DetourNavigator } private: - std::mutex mMutex; - std::ofstream mFile; - std::atomic_bool mEnabled; + mutable std::mutex mMutex; + std::unique_ptr mSink; }; inline void write(std::ostream& stream) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f6fc99c0e8..1b346dad61 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -597,6 +597,9 @@ triangles per chunk = 256 # Enable debug log (true, false) enable log = false +# Write debug log to this file +log path = detournavigator.log + # Write recast mesh to file in .obj format for each use to update nav mesh (true, false) enable write recast mesh to file = false From 330e596c6494970279a8e3f15990f891195529b8 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 7 Jul 2018 20:30:07 +0300 Subject: [PATCH 064/133] Remove useless parameter --- components/nifbullet/bulletnifloader.cpp | 6 ++---- components/nifbullet/bulletnifloader.hpp | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 7b206e40c2..0554db08fc 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -131,16 +131,14 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) // Find a boundingBox in the node hierarchy. // Return: use bounding box for collision? -bool BulletNifLoader::findBoundingBox(const Nif::Node* node, int flags) +bool BulletNifLoader::findBoundingBox(const Nif::Node* node) { - flags |= node->flags; - if (node->hasBounds) { mShape->mCollisionBoxHalfExtents = node->boundXYZ; mShape->mCollisionBoxTranslate = node->boundPos; - if (flags & Nif::NiNode::Flag_BBoxCollision) + if (node->flags & Nif::NiNode::Flag_BBoxCollision) { return true; } diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index f970b7f3e5..26b5b329de 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -53,7 +53,7 @@ public: osg::ref_ptr load(const Nif::File& file); private: - bool findBoundingBox(const Nif::Node* node, int flags = 0); + bool findBoundingBox(const Nif::Node* node); void handleNode(const std::string& fileName, Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false); From e24d4d70524329a51a6193cb79fe8841712b6103 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 8 Jul 2018 12:43:04 +0300 Subject: [PATCH 065/133] Explicitly use RC_NULL_AREA constant to mark unwalkable areas --- components/detournavigator/makenavmesh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index f671344962..c3c12d675c 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -99,7 +99,7 @@ namespace { const auto& chunkyMesh = recastMesh.getChunkyTriMesh(); - std::vector areas(chunkyMesh.getMaxTrisPerChunk(), 0); + std::vector areas(chunkyMesh.getMaxTrisPerChunk(), RC_NULL_AREA); const osg::Vec2f tileBoundsMin(config.bmin[0], config.bmin[2]); const osg::Vec2f tileBoundsMax(config.bmax[0], config.bmax[2]); std::vector cids; @@ -116,7 +116,7 @@ namespace areas.begin(), std::min(areas.begin() + static_cast(chunk.mSize), areas.end()), - 0 + RC_NULL_AREA ); rcMarkWalkableTriangles( From f6a60790f8578d4c27e95fb1fa8f96cdd673e4da Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 11 Jul 2018 01:05:19 +0300 Subject: [PATCH 066/133] Create collision shape for all avoided nodes --- .../nifloader/testbulletnifloader.cpp | 5 ++ components/nifbullet/bulletnifloader.cpp | 87 +++++++++---------- components/nifbullet/bulletnifloader.hpp | 13 +-- components/resource/bulletshape.cpp | 11 +++ components/resource/bulletshape.hpp | 3 + 5 files changed, 67 insertions(+), 52 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index a2311be490..ceb035db8b 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -142,6 +142,7 @@ namespace Resource static bool operator ==(const Resource::BulletShape& lhs, const Resource::BulletShape& rhs) { return compareObjects(lhs.mCollisionShape, rhs.mCollisionShape) + && compareObjects(lhs.mAvoidCollisionShape, rhs.mAvoidCollisionShape) && lhs.mCollisionBoxHalfExtents == rhs.mCollisionBoxHalfExtents && lhs.mCollisionBoxTranslate == rhs.mCollisionBoxTranslate && lhs.mAnimatedShapes == rhs.mAnimatedShapes; @@ -151,6 +152,7 @@ namespace Resource { return stream << "Resource::BulletShape {" << value.mCollisionShape << ", " + << value.mAvoidCollisionShape << ", " << value.mCollisionBoxHalfExtents << ", " << value.mAnimatedShapes << "}"; @@ -837,7 +839,10 @@ namespace EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); Resource::BulletShape expected; + expected.mAvoidCollisionShape = new Resource::TriangleMeshShape(triangles.release(), false); EXPECT_EQ(*result, expected); } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 0554db08fc..991e9fff5e 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include @@ -42,27 +41,41 @@ bool pathFileNameStartsWithX(const std::string& path) return letterPos < path.size() && (path[letterPos] == 'x' || path[letterPos] == 'X'); } +void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriShapeData& data, const osg::Matrixf &transform) +{ + mesh.preallocateVertices(static_cast(data.vertices.size())); + mesh.preallocateIndices(static_cast(data.triangles.size())); + + const std::vector &vertices = data.vertices; + const std::vector &triangles = data.triangles; + + for (std::size_t i = 0; i < triangles.size(); i += 3) + { + mesh.addTriangle( + getbtVector(vertices[triangles[i + 0]] * transform), + getbtVector(vertices[triangles[i + 1]] * transform), + getbtVector(vertices[triangles[i + 2]] * transform) + ); + } } -namespace NifBullet +void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriShapeData& data) { + fillTriangleMeshWithTransform(mesh, data, osg::Matrixf()); +} -BulletNifLoader::BulletNifLoader() - : mCompoundShape() - , mStaticMesh() -{ } -BulletNifLoader::~BulletNifLoader() +namespace NifBullet { -} osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) { mShape = new Resource::BulletShape; - mCompoundShape = nullptr; - mStaticMesh = nullptr; + mCompoundShape.reset(); + mStaticMesh.reset(); + mAvoidStaticMesh.reset(); if (nif.numRoots() < 1) { @@ -125,6 +138,9 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) mStaticMesh.release(); } + if (mAvoidStaticMesh) + mShape->mAvoidCollisionShape = new Resource::TriangleMeshShape(mAvoidStaticMesh.release(), false); + return mShape; } } @@ -189,7 +205,7 @@ const Nif::Node* BulletNifLoader::getCollisionNode(const Nif::Node* rootNode) } void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *node, int flags, - bool isCollisionNode, bool isAnimated, bool autogenerated) + bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid) { // Accumulate the flags from all the child nodes. This works for all // the flags we currently use, at least. @@ -205,8 +221,7 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n Log(Debug::Info) << "Found RootCollisionNode attached to non-root node in " << fileName << ". Treat it as a common NiTriShape."; // Don't collide with AvoidNode shapes - if(node->recType == Nif::RC_AvoidNode) - flags |= 0x800; + avoid = avoid || (node->recType == Nif::RC_AvoidNode); // Check for extra data Nif::Extra const *e = node; @@ -242,7 +257,7 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n // (occurs in tr_ex_imp_wall_arch_04.nif) if(!node->hasBounds && node->recType == Nif::RC_NiTriShape) { - handleNiTriShape(static_cast(node), flags, getWorldTransform(node), isAnimated); + handleNiTriShape(static_cast(node), flags, getWorldTransform(node), isAnimated, avoid); } } @@ -254,12 +269,13 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n for(size_t i = 0;i < list.length();i++) { if(!list[i].empty()) - handleNode(fileName, list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated); + handleNode(fileName, list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated, avoid); } } } -void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf &transform, bool isAnimated) +void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf &transform, + bool isAnimated, bool avoid) { assert(shape != nullptr); @@ -285,21 +301,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, std::unique_ptr childMesh(new btTriangleMesh); - const Nif::NiTriShapeData *data = shape->data.getPtr(); - - childMesh->preallocateVertices(data->vertices.size()); - childMesh->preallocateIndices(data->triangles.size()); - - const std::vector &vertices = data->vertices; - const std::vector &triangles = data->triangles; - - for(size_t i = 0;i < data->triangles.size();i+=3) - { - osg::Vec3f b1 = vertices[triangles[i+0]]; - osg::Vec3f b2 = vertices[triangles[i+1]]; - osg::Vec3f b3 = vertices[triangles[i+2]]; - childMesh->addTriangle(getbtVector(b1), getbtVector(b2), getbtVector(b3)); - } + fillTriangleMesh(*childMesh, shape->data.get()); std::unique_ptr childShape(new Resource::TriangleMeshShape(childMesh.get(), true)); childMesh.release(); @@ -322,27 +324,20 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, mCompoundShape->addChildShape(trans, childShape.get()); childShape.release(); } + else if (avoid) + { + if (!mAvoidStaticMesh) + mAvoidStaticMesh.reset(new btTriangleMesh(false)); + + fillTriangleMeshWithTransform(*mAvoidStaticMesh, shape->data.get(), transform); + } else { if (!mStaticMesh) mStaticMesh.reset(new btTriangleMesh(false)); // Static shape, just transform all vertices into position - const Nif::NiTriShapeData *data = shape->data.getPtr(); - const std::vector &vertices = data->vertices; - const std::vector &triangles = data->triangles; - - mStaticMesh->preallocateVertices(data->vertices.size()); - mStaticMesh->preallocateIndices(data->triangles.size()); - - size_t numtris = data->triangles.size(); - for(size_t i = 0;i < numtris;i+=3) - { - osg::Vec3f b1 = vertices[triangles[i+0]]*transform; - osg::Vec3f b2 = vertices[triangles[i+1]]*transform; - osg::Vec3f b3 = vertices[triangles[i+2]]*transform; - mStaticMesh->addTriangle(getbtVector(b1), getbtVector(b2), getbtVector(b3)); - } + fillTriangleMeshWithTransform(*mStaticMesh, shape->data.get(), transform); } } diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 26b5b329de..8d55e0ba0b 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -11,6 +11,8 @@ #include #include +#include + #include #include #include @@ -35,10 +37,6 @@ namespace NifBullet class BulletNifLoader { public: - BulletNifLoader(); - - virtual ~BulletNifLoader(); - void warn(const std::string &msg) { Log(Debug::Warning) << "NIFLoader: Warn:" << msg; @@ -55,16 +53,19 @@ public: private: bool findBoundingBox(const Nif::Node* node); - void handleNode(const std::string& fileName, Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false); + void handleNode(const std::string& fileName, Nif::Node const *node, int flags, bool isCollisionNode, + bool isAnimated=false, bool autogenerated=false, bool avoid=false); const Nif::Node* getCollisionNode(const Nif::Node* rootNode); - void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf& transform, bool isAnimated); + void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf& transform, bool isAnimated, bool avoid); std::unique_ptr mCompoundShape; std::unique_ptr mStaticMesh; + std::unique_ptr mAvoidStaticMesh; + osg::ref_ptr mShape; }; diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 15a747dd3a..645a5dd3b9 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -13,12 +13,14 @@ namespace Resource BulletShape::BulletShape() : mCollisionShape(nullptr) + , mAvoidCollisionShape(nullptr) { } BulletShape::BulletShape(const BulletShape ©, const osg::CopyOp ©op) : mCollisionShape(duplicateCollisionShape(copy.mCollisionShape)) + , mAvoidCollisionShape(duplicateCollisionShape(copy.mAvoidCollisionShape)) , mCollisionBoxHalfExtents(copy.mCollisionBoxHalfExtents) , mCollisionBoxTranslate(copy.mCollisionBoxTranslate) , mAnimatedShapes(copy.mAnimatedShapes) @@ -27,6 +29,7 @@ BulletShape::BulletShape(const BulletShape ©, const osg::CopyOp ©op) BulletShape::~BulletShape() { + deleteShape(mAvoidCollisionShape); deleteShape(mCollisionShape); } @@ -82,6 +85,11 @@ btCollisionShape *BulletShape::getCollisionShape() return mCollisionShape; } +btCollisionShape *BulletShape::getAvoidCollisionShape() +{ + return mAvoidCollisionShape; +} + osg::ref_ptr BulletShape::makeInstance() const { osg::ref_ptr instance (new BulletShapeInstance(this)); @@ -99,6 +107,9 @@ BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) if (source->mCollisionShape) mCollisionShape = duplicateCollisionShape(source->mCollisionShape); + + if (source->mAvoidCollisionShape) + mAvoidCollisionShape = duplicateCollisionShape(source->mAvoidCollisionShape); } } diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index a418bb28ce..f2f45cac91 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -25,6 +25,7 @@ namespace Resource META_Object(Resource, BulletShape) btCollisionShape* mCollisionShape; + btCollisionShape* mAvoidCollisionShape; // Used for actors. Note, ideally actors would use a separate loader - as it is // we have to keep a redundant copy of the actor model around in mCollisionShape, which isn't used. @@ -44,6 +45,8 @@ namespace Resource btCollisionShape* getCollisionShape(); + btCollisionShape* getAvoidCollisionShape(); + private: void deleteShape(btCollisionShape* shape); From b33a291a67f2a96ef2dcce9b9f0dac2296cac93c Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 14 Jul 2018 12:06:15 +0300 Subject: [PATCH 067/133] Log thread id --- components/detournavigator/debug.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp index 2a19bcce85..fb8d5bd219 100644 --- a/components/detournavigator/debug.hpp +++ b/components/detournavigator/debug.hpp @@ -14,6 +14,7 @@ #include #include #include +#include class dtNavMesh; @@ -123,7 +124,7 @@ namespace DetourNavigator if (!log.isEnabled()) return; std::ostringstream stream; - stream << '[' << std::chrono::steady_clock::now() << "] "; + stream << '[' << std::chrono::steady_clock::now() << "] [" << std::this_thread::get_id() << "] "; write(stream, std::forward(values) ...); log.write(stream.str()); } From c771986c56710b63c5851bdcc717501d0badf025 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 14 Jul 2018 15:05:28 +0300 Subject: [PATCH 068/133] Prioritise NavMesh jobs first to remove and last to add When player move fast enough, tiles update for specific area square couldn't catch player move. Tiles to be removed are left in the queue with lower priority then tiles to be added which are nearest to player. This can lead to overflow for amount of tiles. So we try to do remove first. But we detect change type approximately using mixed change type, because even if we do it precise, change type could change while job is in queue. --- .../detournavigator/asyncnavmeshupdater.cpp | 20 +++++--- .../detournavigator/asyncnavmeshupdater.hpp | 11 ++++- components/detournavigator/navmeshmanager.cpp | 48 ++++++++++++++----- components/detournavigator/navmeshmanager.hpp | 4 +- 4 files changed, 61 insertions(+), 22 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 91af31e22e..bea6ffa57c 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -9,16 +9,22 @@ namespace { + using DetourNavigator::ChangeType; using DetourNavigator::TilePosition; - int getDistance(const TilePosition& lhs, const TilePosition& rhs) + int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs) { return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y()); } - std::pair makePriority(const TilePosition& changedTile, const TilePosition& playerTile) + std::tuple makePriority(const TilePosition& position, const ChangeType changeType, + const TilePosition& playerTile) { - return std::make_pair(getDistance(changedTile, playerTile), getDistance(changedTile, TilePosition {0, 0})); + return std::make_tuple( + changeType, + getManhattanDistance(position, playerTile), + getManhattanDistance(position, TilePosition {0, 0}) + ); } } @@ -60,7 +66,7 @@ namespace DetourNavigator void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& navMeshCacheItem, const TilePosition& playerTile, - const std::set& changedTiles) + const std::map& changedTiles) { log("post jobs playerTile=", playerTile); @@ -73,9 +79,9 @@ namespace DetourNavigator for (const auto& changedTile : changedTiles) { - if (mPushed[agentHalfExtents].insert(changedTile).second) - mJobs.push(Job {agentHalfExtents, navMeshCacheItem, changedTile, - makePriority(changedTile, playerTile)}); + if (mPushed[agentHalfExtents].insert(changedTile.first).second) + mJobs.push(Job {agentHalfExtents, navMeshCacheItem, changedTile.first, + makePriority(changedTile.first, changedTile.second, playerTile)}); } log("posted ", mJobs.size(), " jobs"); diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 6b4facf6de..bba788c3c4 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -22,6 +22,13 @@ class dtNavMesh; namespace DetourNavigator { + enum class ChangeType + { + remove = 0, + mixed = 1, + add = 2, + }; + class AsyncNavMeshUpdater { public: @@ -29,7 +36,7 @@ namespace DetourNavigator ~AsyncNavMeshUpdater(); void post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& mNavMeshCacheItem, - const TilePosition& playerTile, const std::set& changedTiles); + const TilePosition& playerTile, const std::map& changedTiles); void wait(); @@ -39,7 +46,7 @@ namespace DetourNavigator osg::Vec3f mAgentHalfExtents; std::shared_ptr mNavMeshCacheItem; TilePosition mChangedTile; - std::pair mPriority; + std::tuple mPriority; friend inline bool operator <(const Job& lhs, const Job& rhs) { diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index c12351ee0a..b13c5d83c9 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -13,6 +13,16 @@ #include +namespace +{ + using DetourNavigator::ChangeType; + + ChangeType addChangeType(const ChangeType current, const ChangeType add) + { + return current == add ? current : ChangeType::mixed; + } +} + namespace DetourNavigator { NavMeshManager::NavMeshManager(const Settings& settings) @@ -26,7 +36,7 @@ namespace DetourNavigator { if (!mRecastMeshManager.addObject(id, shape, transform)) return false; - addChangedTiles(shape, transform); + addChangedTiles(shape, transform, ChangeType::add); return true; } @@ -34,7 +44,7 @@ namespace DetourNavigator { if (!mRecastMeshManager.updateObject(id, transform)) return false; - addChangedTiles(shape, transform); + addChangedTiles(shape, transform, ChangeType::mixed); return true; } @@ -43,7 +53,7 @@ namespace DetourNavigator const auto object = mRecastMeshManager.removeObject(id); if (!object) return false; - addChangedTiles(object->mShape, object->mTransform); + addChangedTiles(object->mShape, object->mTransform, ChangeType::remove); return true; } @@ -72,7 +82,7 @@ namespace DetourNavigator return; mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision(); mPlayerTile = playerTile; - std::set tilesToPost; + std::map tilesToPost; const auto& cached = getCached(agentHalfExtents); const auto changedTiles = mChangedTiles.find(agentHalfExtents); { @@ -80,10 +90,16 @@ namespace DetourNavigator if (changedTiles != mChangedTiles.end()) { for (const auto& tile : changedTiles->second) - if (locked->getTileAt(tile.x(), tile.y(), 0)) - tilesToPost.insert(tile); + if (locked->getTileAt(tile.first.x(), tile.first.y(), 0)) + { + auto tileToPost = tilesToPost.find(tile.first); + if (tileToPost == tilesToPost.end()) + tilesToPost.insert(tile); + else + tileToPost->second = addChangeType(tileToPost->second, tile.second); + } for (const auto& tile : tilesToPost) - changedTiles->second.erase(tile); + changedTiles->second.erase(tile.first); if (changedTiles->second.empty()) mChangedTiles.erase(changedTiles); } @@ -94,8 +110,10 @@ namespace DetourNavigator return; const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles); const auto presentInNavMesh = bool(locked->getTileAt(tile.x(), tile.y(), 0)); - if ((shouldAdd && !presentInNavMesh) || (!shouldAdd && presentInNavMesh)) - tilesToPost.insert(tile); + if (shouldAdd && !presentInNavMesh) + tilesToPost.insert(std::make_pair(tile, ChangeType::add)); + else if (!shouldAdd && presentInNavMesh) + tilesToPost.insert(std::make_pair(tile, ChangeType::mixed)); }); } mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); @@ -120,12 +138,20 @@ namespace DetourNavigator return mCache; } - void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform) + void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, + const ChangeType changeType) { getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& v) { for (const auto& cached : mCache) if (cached.second) - mChangedTiles[cached.first].insert(v); + { + auto& tiles = mChangedTiles[cached.first]; + auto tile = tiles.find(v); + if (tile == tiles.end()) + tiles.insert(std::make_pair(v, changeType)); + else + tile->second = addChangeType(tile->second, changeType); + } }); } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 109aa2237f..01840a3b60 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -45,13 +45,13 @@ namespace DetourNavigator const Settings& mSettings; TileCachedRecastMeshManager mRecastMeshManager; std::map> mCache; - std::map> mChangedTiles; + std::map> mChangedTiles; AsyncNavMeshUpdater mAsyncNavMeshUpdater; std::size_t mGenerationCounter = 0; boost::optional mPlayerTile; std::size_t mLastRecastMeshManagerRevision = 0; - void addChangedTiles(const btCollisionShape& shape, const btTransform& transform); + void addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType); const std::shared_ptr& getCached(const osg::Vec3f& agentHalfExtents) const; }; From fa23b590a4dee50196190a75ff0926018476b694 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 12 Jul 2018 11:44:11 +0300 Subject: [PATCH 069/133] Add unwalkable areas to NavMesh --- apps/openmw/mwphysics/object.cpp | 2 +- apps/openmw/mwworld/scene.cpp | 7 +- apps/openmw/mwworld/worldimp.cpp | 9 ++- .../detournavigator/navigator.cpp | 57 ++++++++++++++ .../detournavigator/recastmeshbuilder.cpp | 76 +++++++++++++++---- .../detournavigator/recastmeshobject.cpp | 30 +++++--- .../cachedrecastmeshmanager.cpp | 12 +-- .../cachedrecastmeshmanager.hpp | 5 +- components/detournavigator/chunkytrimesh.cpp | 16 ++-- components/detournavigator/chunkytrimesh.hpp | 6 +- components/detournavigator/makenavmesh.cpp | 13 ++++ components/detournavigator/navigator.cpp | 52 ++++++++++++- components/detournavigator/navigator.hpp | 18 +++++ components/detournavigator/navmeshmanager.cpp | 13 ++-- components/detournavigator/navmeshmanager.hpp | 6 +- components/detournavigator/recastmesh.cpp | 10 ++- components/detournavigator/recastmesh.hpp | 9 ++- .../detournavigator/recastmeshbuilder.cpp | 32 +++++--- .../detournavigator/recastmeshbuilder.hpp | 11 +-- .../detournavigator/recastmeshmanager.cpp | 11 +-- .../detournavigator/recastmeshmanager.hpp | 5 +- .../detournavigator/recastmeshobject.cpp | 28 ++++--- .../detournavigator/recastmeshobject.hpp | 17 +++-- .../tilecachedrecastmeshmanager.cpp | 12 +-- .../tilecachedrecastmeshmanager.hpp | 5 +- components/resource/bulletshape.cpp | 11 ++- components/resource/bulletshape.hpp | 6 +- 27 files changed, 368 insertions(+), 111 deletions(-) diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index d14f579dd0..2f28668215 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -40,7 +40,7 @@ namespace MWPhysics void Object::setScale(float scale) { - mShapeInstance->getCollisionShape()->setLocalScaling(btVector3(scale,scale,scale)); + mShapeInstance->setLocalScaling(btVector3(scale, scale, scale)); } void Object::setRotation(const btQuaternion& quat) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 32c83b95e3..bfd21d5e27 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -86,7 +87,11 @@ namespace if (const auto object = physics.getObject(ptr)) { const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); - navigator->addObject(reinterpret_cast(object), *object->getCollisionObject()->getCollisionShape(), + const DetourNavigator::ObjectShapes shapes { + *object->getShapeInstance()->getCollisionShape(), + object->getShapeInstance()->getAvoidCollisionShape() + }; + navigator->addObject(reinterpret_cast(object), shapes, object->getCollisionObject()->getWorldTransform()); } else if (const auto actor = physics.getActor(ptr)) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 94227a7245..a7bda8c0be 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -4,6 +4,7 @@ #include #include +#include #include @@ -17,6 +18,7 @@ #include +#include #include #include @@ -1544,8 +1546,11 @@ namespace MWWorld bool navigatorObjectsUpdated = false; mPhysics->forEachAnimatedObject([&] (const MWPhysics::Object* object) { - navigatorObjectsUpdated = mNavigator->updateObject(std::size_t(object), - *object->getCollisionObject()->getCollisionShape(), + const DetourNavigator::ObjectShapes shapes { + *object->getShapeInstance()->getCollisionShape(), + object->getShapeInstance()->getAvoidCollisionShape() + }; + navigatorObjectsUpdated = mNavigator->updateObject(std::size_t(object), shapes, object->getCollisionObject()->getWorldTransform()) || navigatorObjectsUpdated; }); if (navigatorObjectsUpdated) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index de0636ca11..aa7906008a 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -358,4 +358,61 @@ namespace osg::Vec3f(215, -215, 1.96328866481781005859375), })) << mPath; } + + TEST_F(DetourNavigatorNavigatorTest, path_should_be_around_avoid_shape) + { + std::array heightfieldData {{ + 0, 0, 0, 0, 0, + 0, -25, -25, -25, -25, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + }}; + btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + shape.setLocalScaling(btVector3(128, 128, 1)); + + std::array heightfieldDataAvoid {{ + -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, + }}; + btHeightfieldTerrainShape shapeAvoid(5, 5, heightfieldDataAvoid.data(), 1, 0, 0, 2, PHY_FLOAT, false); + shapeAvoid.setLocalScaling(btVector3(128, 128, 1)); + + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addObject(1, ObjectShapes {shape, &shapeAvoid}, btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); + + EXPECT_EQ(mPath, std::deque({ + osg::Vec3f(-215, 215, 1.9393787384033203125), + osg::Vec3f(-200.8159637451171875, 190.47265625, -0.639537751674652099609375), + osg::Vec3f(-186.6319427490234375, 165.9453125, -3.2184507846832275390625), + osg::Vec3f(-172.447906494140625, 141.41796875, -5.797363758087158203125), + osg::Vec3f(-158.263885498046875, 116.8906097412109375, -8.37627887725830078125), + osg::Vec3f(-144.079864501953125, 92.3632659912109375, -10.95519161224365234375), + osg::Vec3f(-129.89581298828125, 67.83591461181640625, -13.534107208251953125), + osg::Vec3f(-115.7117919921875, 43.308563232421875, -16.1130199432373046875), + osg::Vec3f(-101.5277557373046875, 18.7812137603759765625, -18.6919345855712890625), + osg::Vec3f(-87.34372711181640625, -5.7461376190185546875, -20.4680538177490234375), + osg::Vec3f(-67.02922821044921875, -25.4970550537109375, -20.514247894287109375), + osg::Vec3f(-46.714717864990234375, -45.2479705810546875, -20.5604457855224609375), + osg::Vec3f(-26.40021514892578125, -64.99889373779296875, -20.6066417694091796875), + osg::Vec3f(-6.085712432861328125, -84.74980926513671875, -20.652835845947265625), + osg::Vec3f(14.22879505157470703125, -104.50072479248046875, -18.151393890380859375), + osg::Vec3f(39.05098724365234375, -118.16222381591796875, -15.6674861907958984375), + osg::Vec3f(63.87317657470703125, -131.82373046875, -13.18357944488525390625), + osg::Vec3f(88.69537353515625, -145.4852142333984375, -10.69967365264892578125), + osg::Vec3f(113.51757049560546875, -159.146697998046875, -8.21576690673828125), + osg::Vec3f(138.3397674560546875, -172.808197021484375, -5.731858730316162109375), + osg::Vec3f(163.1619720458984375, -186.469696044921875, -3.2479503154754638671875), + osg::Vec3f(187.984161376953125, -200.1311798095703125, -0.764044582843780517578125), + osg::Vec3f(212.8063507080078125, -213.7926788330078125, 1.7198636531829833984375), + osg::Vec3f(215, -215, 1.93937528133392333984375), + })) << mPath; + } } diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index a2a47722d1..ad0502cb83 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -40,6 +40,7 @@ namespace const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector()); EXPECT_EQ(recastMesh->getIndices(), std::vector()); + EXPECT_EQ(recastMesh->getFlags(), std::vector()); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_bhv_triangle_mesh_shape) @@ -48,7 +49,7 @@ namespace mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity()); + builder.addObject(static_cast(shape), btTransform::getIdentity(), 1); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, @@ -56,6 +57,7 @@ namespace -1, 0, -1, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_transformed_bhv_triangle_mesh_shape) @@ -64,8 +66,11 @@ namespace mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), - btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3))); + builder.addObject( + static_cast(shape), + btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), + 1 + ); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 2, 3, 0, @@ -73,6 +78,7 @@ namespace 0, 3, 0, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_terrian_shape) @@ -80,7 +86,7 @@ namespace const std::array heightfieldData {{0, 0, 0, 0}}; btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity()); + builder.addObject(static_cast(shape), btTransform::getIdentity(), 1); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -0.5, 0, -0.5, @@ -91,13 +97,14 @@ namespace 0.5, 0, 0.5, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 3, 4, 5})); + EXPECT_EQ(recastMesh->getFlags(), std::vector({1, 1})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_box_shape_should_produce_12_triangles) { btBoxShape shape(btVector3(1, 1, 2)); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity()); + builder.addObject(static_cast(shape), btTransform::getIdentity(), 1); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 2, 1, @@ -123,6 +130,7 @@ namespace 7, 6, 4, 4, 5, 7, })); + EXPECT_EQ(recastMesh->getFlags(), std::vector({1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_compound_shape) @@ -139,7 +147,7 @@ namespace shape.addChildShape(btTransform::getIdentity(), &box); shape.addChildShape(btTransform::getIdentity(), &triangle2); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity()); + builder.addObject(static_cast(shape), btTransform::getIdentity(), 1); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, @@ -173,6 +181,7 @@ namespace 7, 8, 10, 11, 12, 13, })); + EXPECT_EQ(recastMesh->getFlags(), std::vector({1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_transformed_compound_shape) @@ -184,7 +193,8 @@ namespace shape.addChildShape(btTransform::getIdentity(), &triangle); RecastMeshBuilder builder(mSettings, mBounds); builder.addObject(static_cast(shape), - btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3))); + btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), + 1); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 2, 3, 0, @@ -192,6 +202,7 @@ namespace 0, 3, 0, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_transformed_compound_shape_with_transformed_bhv_triangle_shape) @@ -203,8 +214,11 @@ namespace shape.addChildShape(btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), &triangle); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), - btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3))); + builder.addObject( + static_cast(shape), + btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), + 1 + ); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 3, 12, 2, @@ -212,16 +226,17 @@ namespace 1, 12, 2, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); } - TEST_F(DetourNavigatorRecastMeshBuilderTest, without_bounds_add_transformed_compound_shape_with_transformed_bhv_triangle_shape_should_not_filter_by_bounds) + TEST_F(DetourNavigatorRecastMeshBuilderTest, without_bounds_add_bhv_triangle_shape_should_not_filter_by_bounds) { btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); mesh.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity()); + builder.addObject(static_cast(shape), btTransform::getIdentity(), 1); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, @@ -232,9 +247,10 @@ namespace -3, 0, -3, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 3, 4, 5})); + EXPECT_EQ(recastMesh->getFlags(), std::vector({1, 1})); } - TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_transformed_compound_shape_with_transformed_bhv_triangle_shape_should_filter_by_bounds) + TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_bhv_triangle_shape_should_filter_by_bounds) { mSettings.mRecastScaleFactor = 0.1f; mBounds.mMin = osg::Vec2f(-3, -3) * mSettings.mRecastScaleFactor; @@ -244,7 +260,7 @@ namespace mesh.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity()); + builder.addObject(static_cast(shape), btTransform::getIdentity(), 1); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -0.2f, 0, -0.3f, @@ -252,6 +268,7 @@ namespace -0.3f, 0, -0.3f, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_x_bhv_triangle_shape_should_filter_by_bounds) @@ -264,7 +281,7 @@ namespace btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mSettings, mBounds); builder.addObject(static_cast(shape), - btTransform(btQuaternion(btVector3(1, 0, 0), static_cast(-osg::PI_4)))); + btTransform(btQuaternion(btVector3(1, 0, 0), static_cast(-osg::PI_4))), 1); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 0, -0.70710659027099609375, -3.535533905029296875, @@ -272,6 +289,7 @@ namespace 0, 2.384185791015625e-07, -4.24264049530029296875, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_y_bhv_triangle_shape_should_filter_by_bounds) @@ -284,7 +302,7 @@ namespace btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mSettings, mBounds); builder.addObject(static_cast(shape), - btTransform(btQuaternion(btVector3(0, 1, 0), static_cast(osg::PI_4)))); + btTransform(btQuaternion(btVector3(0, 1, 0), static_cast(osg::PI_4))), 1); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -3.535533905029296875, -0.70710659027099609375, 0, @@ -292,6 +310,7 @@ namespace -4.24264049530029296875, 2.384185791015625e-07, 0, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_z_bhv_triangle_shape_should_filter_by_bounds) @@ -304,7 +323,7 @@ namespace btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mSettings, mBounds); builder.addObject(static_cast(shape), - btTransform(btQuaternion(btVector3(0, 0, 1), static_cast(osg::PI_4)))); + btTransform(btQuaternion(btVector3(0, 0, 1), static_cast(osg::PI_4))), 1); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 0.707107067108154296875, 0, -3.535533905029296875, @@ -312,5 +331,30 @@ namespace 2.384185791015625e-07, 0, -4.24264049530029296875, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); + EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); + } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, flags_values_should_be_corresponding_to_added_objects) + { + btTriangleMesh mesh1; + mesh1.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); + btBvhTriangleMeshShape shape1(&mesh1, true); + btTriangleMesh mesh2; + mesh2.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0)); + btBvhTriangleMeshShape shape2(&mesh2, true); + RecastMeshBuilder builder(mSettings, mBounds); + builder.addObject(static_cast(shape1), btTransform::getIdentity(), 1); + builder.addObject(static_cast(shape2), btTransform::getIdentity(), 0); + const auto recastMesh = builder.create(); + EXPECT_EQ(recastMesh->getVertices(), std::vector({ + 1, 0, -1, + -1, 0, 1, + -1, 0, -1, + -2, 0, -3, + -3, 0, -2, + -3, 0, -3, + })); + EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 3, 4, 5})); + EXPECT_EQ(recastMesh->getFlags(), std::vector({1, 0})); } } diff --git a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp index b0f03aa655..ba8441c60b 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp @@ -26,41 +26,47 @@ namespace TEST_F(DetourNavigatorRecastMeshObjectTest, constructed_object_should_have_shape_and_transform) { - const RecastMeshObject object(mBoxShape, mTransform); + const RecastMeshObject object(mBoxShape, mTransform, 1); EXPECT_EQ(std::addressof(object.getShape()), std::addressof(mBoxShape)); EXPECT_EQ(object.getTransform(), mTransform); } TEST_F(DetourNavigatorRecastMeshObjectTest, update_with_same_transform_for_not_compound_shape_should_return_false) { - RecastMeshObject object(mBoxShape, mTransform); - EXPECT_FALSE(object.update(mTransform)); + RecastMeshObject object(mBoxShape, mTransform, 1); + EXPECT_FALSE(object.update(mTransform, 1)); } TEST_F(DetourNavigatorRecastMeshObjectTest, update_with_different_transform_should_return_true) { - RecastMeshObject object(mBoxShape, mTransform); - EXPECT_TRUE(object.update(btTransform::getIdentity())); + RecastMeshObject object(mBoxShape, mTransform, 1); + EXPECT_TRUE(object.update(btTransform::getIdentity(), 1)); + } + + TEST_F(DetourNavigatorRecastMeshObjectTest, update_with_different_flags_should_return_true) + { + RecastMeshObject object(mBoxShape, mTransform, 1); + EXPECT_TRUE(object.update(mTransform, 2)); } TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_compound_shape_with_same_transform_and_not_changed_child_transform_should_return_false) { - RecastMeshObject object(mCompoundShape, mTransform); - EXPECT_FALSE(object.update(mTransform)); + RecastMeshObject object(mCompoundShape, mTransform, 1); + EXPECT_FALSE(object.update(mTransform, 1)); } TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_compound_shape_with_same_transform_and_changed_child_transform_should_return_true) { - RecastMeshObject object(mCompoundShape, mTransform); + RecastMeshObject object(mCompoundShape, mTransform, 1); mCompoundShape.updateChildTransform(0, btTransform::getIdentity()); - EXPECT_TRUE(object.update(mTransform)); + EXPECT_TRUE(object.update(mTransform, 1)); } TEST_F(DetourNavigatorRecastMeshObjectTest, repeated_update_for_compound_shape_without_changes_should_return_false) { - RecastMeshObject object(mCompoundShape, mTransform); + RecastMeshObject object(mCompoundShape, mTransform, 1); mCompoundShape.updateChildTransform(0, btTransform::getIdentity()); - object.update(mTransform); - EXPECT_FALSE(object.update(mTransform)); + object.update(mTransform, 1); + EXPECT_FALSE(object.update(mTransform, 1)); } } diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index cba3946e00..ce9fe5c517 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -5,20 +5,20 @@ namespace DetourNavigator { CachedRecastMeshManager::CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds) : mImpl(settings, bounds) - { - } + {} - bool CachedRecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) + bool CachedRecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, + const btTransform& transform, const unsigned char flags) { - if (!mImpl.addObject(id, shape, transform)) + if (!mImpl.addObject(id, shape, transform, flags)) return false; mCached.reset(); return true; } - bool CachedRecastMeshManager::updateObject(std::size_t id, const btTransform& transform) + bool CachedRecastMeshManager::updateObject(std::size_t id, const btTransform& transform, const unsigned char flags) { - if (!mImpl.updateObject(id, transform)) + if (!mImpl.updateObject(id, transform, flags)) return false; mCached.reset(); return true; diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index 71229ab2f4..6f3f2a05ab 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -12,9 +12,10 @@ namespace DetourNavigator public: CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds); - bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); + bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + const unsigned char flags); - bool updateObject(std::size_t id, const btTransform& transform); + bool updateObject(std::size_t id, const btTransform& transform, const unsigned char flags); boost::optional removeObject(std::size_t id); diff --git a/components/detournavigator/chunkytrimesh.cpp b/components/detournavigator/chunkytrimesh.cpp index cdce8b0e1e..f6e257c54b 100644 --- a/components/detournavigator/chunkytrimesh.cpp +++ b/components/detournavigator/chunkytrimesh.cpp @@ -13,6 +13,7 @@ namespace DetourNavigator { Rect mBounds; std::ptrdiff_t mOffset; + unsigned char mFlags; }; template @@ -43,9 +44,9 @@ namespace DetourNavigator } void subdivide(std::vector& items, const std::size_t imin, const std::size_t imax, - const std::size_t trisPerChunk, const std::vector& inIndices, + const std::size_t trisPerChunk, const std::vector& inIndices, const std::vector& inFlags, std::size_t& curNode, std::vector& nodes, std::size_t& curTri, - std::vector& outIndices) + std::vector& outIndices, std::vector& outFlags) { const auto inum = imax - imin; const auto icur = curNode; @@ -71,6 +72,7 @@ namespace DetourNavigator inIndices.begin() + items[i].mOffset * 3 + 3, outIndices.begin() + static_cast(curTri) * 3 ); + outFlags[curTri] = inFlags[static_cast(items[i].mOffset)]; curTri++; } } @@ -102,9 +104,9 @@ namespace DetourNavigator const auto isplit = imin + inum / 2; // Left - subdivide(items, imin, isplit, trisPerChunk, inIndices, curNode, nodes, curTri, outIndices); + subdivide(items, imin, isplit, trisPerChunk, inIndices, inFlags, curNode, nodes, curTri, outIndices, outFlags); // Right - subdivide(items, isplit, imax, trisPerChunk, inIndices, curNode, nodes, curTri, outIndices); + subdivide(items, isplit, imax, trisPerChunk, inIndices, inFlags, curNode, nodes, curTri, outIndices, outFlags); const auto iescape = static_cast(curNode) - static_cast(icur); // Negative index means escape. @@ -114,7 +116,7 @@ namespace DetourNavigator } ChunkyTriMesh::ChunkyTriMesh(const std::vector& verts, const std::vector& indices, - const std::size_t trisPerChunk) + const std::vector& flags, const std::size_t trisPerChunk) : mMaxTrisPerChunk(0) { const auto trianglesCount = indices.size() / 3; @@ -126,6 +128,7 @@ namespace DetourNavigator mNodes.resize(nchunks * 4); mIndices.resize(trianglesCount * 3); + mFlags.resize(trianglesCount); // Build tree std::vector items(trianglesCount); @@ -135,6 +138,7 @@ namespace DetourNavigator auto& item = items[i]; item.mOffset = static_cast(i); + item.mFlags = flags[i]; // Calc triangle XZ bounds. const auto baseIndex = static_cast(indices[i * 3]) * 3; @@ -156,7 +160,7 @@ namespace DetourNavigator std::size_t curTri = 0; std::size_t curNode = 0; - subdivide(items, 0, trianglesCount, trisPerChunk, indices, curNode, mNodes, curTri, mIndices); + subdivide(items, 0, trianglesCount, trisPerChunk, indices, flags, curNode, mNodes, curTri, mIndices, mFlags); items.clear(); diff --git a/components/detournavigator/chunkytrimesh.hpp b/components/detournavigator/chunkytrimesh.hpp index bc1898e2fe..ddd0b93e67 100644 --- a/components/detournavigator/chunkytrimesh.hpp +++ b/components/detournavigator/chunkytrimesh.hpp @@ -24,6 +24,7 @@ namespace DetourNavigator struct Chunk { const int* const mIndices; + const unsigned char* const mFlags; const std::size_t mSize; }; @@ -40,7 +41,8 @@ namespace DetourNavigator public: /// Creates partitioned triangle mesh (AABB tree), /// where each node contains at max trisPerChunk triangles. - ChunkyTriMesh(const std::vector& verts, const std::vector& tris, std::size_t trisPerChunk); + ChunkyTriMesh(const std::vector& verts, const std::vector& tris, + const std::vector& flags, const std::size_t trisPerChunk); ChunkyTriMesh(const ChunkyTriMesh&) = delete; ChunkyTriMesh& operator=(const ChunkyTriMesh&) = delete; @@ -74,6 +76,7 @@ namespace DetourNavigator const auto& node = mNodes[chunkId]; return Chunk { mIndices.data() + node.mOffset * 3, + mFlags.data() + node.mOffset, node.mSize }; } @@ -86,6 +89,7 @@ namespace DetourNavigator private: std::vector mNodes; std::vector mIndices; + std::vector mFlags; std::size_t mMaxTrisPerChunk; }; } diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index c3c12d675c..4b77f45b76 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -129,6 +129,19 @@ namespace areas.data() ); + for (std::size_t i = 0; i < chunk.mSize; ++i) + areas[i] &= chunk.mFlags[i]; + + rcClearUnwalkableTriangles( + &context, + config.walkableSlopeAngle, + recastMesh.getVertices().data(), + static_cast(recastMesh.getVerticesCount()), + chunk.mIndices, + static_cast(chunk.mSize), + areas.data() + ); + OPENMW_CHECK_DT_RESULT(rcRasterizeTriangles( &context, recastMesh.getVertices().data(), diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index 913ba2e2ad..0fc195675d 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -2,6 +2,8 @@ #include "debug.hpp" #include "settingsutils.hpp" +#include + namespace DetourNavigator { Navigator::Navigator(const Settings& settings) @@ -27,17 +29,51 @@ namespace DetourNavigator bool Navigator::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) { - return mNavMeshManager.addObject(id, shape, transform); + return mNavMeshManager.addObject(id, shape, transform, RC_WALKABLE_AREA); + } + + bool Navigator::addObject(std::size_t id, const ObjectShapes& shapes, const btTransform& transform) + { + bool result = addObject(id, shapes.mShape, transform); + if (shapes.mAvoid) + { + const auto avoidId = reinterpret_cast(shapes.mAvoid); + if (mNavMeshManager.addObject(avoidId, *shapes.mAvoid, transform, RC_NULL_AREA)) + { + updateAvoidShapeId(id, avoidId); + result = true; + } + } + return result; } bool Navigator::updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) { - return mNavMeshManager.updateObject(id, shape, transform); + return mNavMeshManager.updateObject(id, shape, transform, RC_WALKABLE_AREA); + } + + bool Navigator::updateObject(std::size_t id, const ObjectShapes& shapes, const btTransform& transform) + { + bool result = updateObject(id, shapes.mShape, transform); + if (shapes.mAvoid) + { + const auto avoidId = reinterpret_cast(shapes.mAvoid); + if (mNavMeshManager.updateObject(avoidId, *shapes.mAvoid, transform, RC_NULL_AREA)) + { + updateAvoidShapeId(id, avoidId); + result = true; + } + } + return result; } bool Navigator::removeObject(std::size_t id) { - return mNavMeshManager.removeObject(id); + bool result = mNavMeshManager.removeObject(id); + const auto avoid = mAvoidIds.find(id); + if (avoid == mAvoidIds.end()) + return result; + return mNavMeshManager.removeObject(avoid->second) || result; } void Navigator::update(const osg::Vec3f& playerPosition) @@ -60,4 +96,14 @@ namespace DetourNavigator { return mSettings; } + + void Navigator::updateAvoidShapeId(std::size_t id, std::size_t avoidId) + { + auto inserted = mAvoidIds.insert(std::make_pair(id, avoidId)); + if (!inserted.second) + { + mNavMeshManager.removeObject(inserted.first->second); + inserted.second = avoidId; + } + } } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 69529a9d45..39dd41a140 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -8,6 +8,17 @@ namespace DetourNavigator { + struct ObjectShapes + { + const btCollisionShape& mShape; + const btCollisionShape* mAvoid; + + ObjectShapes(const btCollisionShape& shape, const btCollisionShape* avoid) + : mShape(shape), mAvoid(avoid) + { + } + }; + class Navigator { public: @@ -19,8 +30,12 @@ namespace DetourNavigator bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); + bool addObject(std::size_t id, const ObjectShapes& shapes, const btTransform& transform); + bool updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); + bool updateObject(std::size_t id, const ObjectShapes& shapes, const btTransform& transform); + bool removeObject(std::size_t id); void update(const osg::Vec3f& playerPosition); @@ -44,6 +59,9 @@ namespace DetourNavigator Settings mSettings; NavMeshManager mNavMeshManager; std::map mAgents; + std::unordered_map mAvoidIds; + + void updateAvoidShapeId(std::size_t id, std::size_t avoidId); }; } diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index b13c5d83c9..7ddaef55c7 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -29,20 +29,21 @@ namespace DetourNavigator : mSettings(settings) , mRecastMeshManager(settings) , mAsyncNavMeshUpdater(settings, mRecastMeshManager) - { - } + {} - bool NavMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) + bool NavMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + const unsigned char flags) { - if (!mRecastMeshManager.addObject(id, shape, transform)) + if (!mRecastMeshManager.addObject(id, shape, transform, flags)) return false; addChangedTiles(shape, transform, ChangeType::add); return true; } - bool NavMeshManager::updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) + bool NavMeshManager::updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + const unsigned char flags) { - if (!mRecastMeshManager.updateObject(id, transform)) + if (!mRecastMeshManager.updateObject(id, transform, flags)) return false; addChangedTiles(shape, transform, ChangeType::mixed); return true; diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 01840a3b60..fbbc81e6cf 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -23,9 +23,11 @@ namespace DetourNavigator public: NavMeshManager(const Settings& settings); - bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); + bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + const unsigned char flags); - bool updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); + bool updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + const unsigned char flags); bool removeObject(std::size_t id); diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index d765656e45..fc23c1feaf 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -1,15 +1,21 @@ #include "recastmesh.hpp" #include "settings.hpp" +#include "exceptions.hpp" #include namespace DetourNavigator { - RecastMesh::RecastMesh(std::vector indices, std::vector vertices, const Settings& settings) + RecastMesh::RecastMesh(std::vector indices, std::vector vertices, + std::vector flags, const Settings& settings) : mIndices(std::move(indices)) , mVertices(std::move(vertices)) - , mChunkyTriMesh(mVertices, mIndices, settings.mTrianglesPerChunk) + , mFlags(std::move(flags)) + , mChunkyTriMesh(mVertices, mIndices, mFlags, settings.mTrianglesPerChunk) { + if (getTrianglesCount() != mFlags.size()) + throw InvalidArgument("number of flags doesn't match number of triangles: triangles=" + + std::to_string(getTrianglesCount()) + ", flags=" + std::to_string(mFlags.size())); if (getVerticesCount()) rcCalcBounds(mVertices.data(), static_cast(getVerticesCount()), mBoundsMin.ptr(), mBoundsMax.ptr()); } diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 8dcb717e23..59f97b2ee0 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -15,7 +15,8 @@ namespace DetourNavigator class RecastMesh { public: - RecastMesh(std::vector indices, std::vector vertices, const Settings& settings); + RecastMesh(std::vector indices, std::vector vertices, + std::vector flags, const Settings& settings); const std::vector& getIndices() const { @@ -27,6 +28,11 @@ namespace DetourNavigator return mVertices; } + const std::vector& getFlags() const + { + return mFlags; + } + std::size_t getVerticesCount() const { return mVertices.size() / 3; @@ -55,6 +61,7 @@ namespace DetourNavigator private: std::vector mIndices; std::vector mVertices; + std::vector mFlags; ChunkyTriMesh mChunkyTriMesh; osg::Vec3f mBoundsMin; osg::Vec3f mBoundsMax; diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index a34b114acb..229f420224 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -35,46 +35,52 @@ namespace DetourNavigator mBounds.mMax /= mSettings.get().mRecastScaleFactor; } - void RecastMeshBuilder::addObject(const btCollisionShape& shape, const btTransform& transform) + void RecastMeshBuilder::addObject(const btCollisionShape& shape, const btTransform& transform, + const unsigned char flags) { if (shape.isCompound()) - return addObject(static_cast(shape), transform); + return addObject(static_cast(shape), transform, flags); else if (shape.getShapeType() == TERRAIN_SHAPE_PROXYTYPE) - return addObject(static_cast(shape), transform); + return addObject(static_cast(shape), transform, flags); else if (shape.isConcave()) - return addObject(static_cast(shape), transform); + return addObject(static_cast(shape), transform, flags); else if (shape.getShapeType() == BOX_SHAPE_PROXYTYPE) - return addObject(static_cast(shape), transform); + return addObject(static_cast(shape), transform, flags); std::ostringstream message; message << "Unsupported shape type: " << BroadphaseNativeTypes(shape.getShapeType()); throw InvalidArgument(message.str()); } - void RecastMeshBuilder::addObject(const btCompoundShape& shape, const btTransform& transform) + void RecastMeshBuilder::addObject(const btCompoundShape& shape, const btTransform& transform, + const unsigned char flags) { for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) - addObject(*shape.getChildShape(i), transform * shape.getChildTransform(i)); + addObject(*shape.getChildShape(i), transform * shape.getChildTransform(i), flags); } - void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform) + void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform, + const unsigned char flags) { return addObject(shape, transform, makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { for (std::size_t i = 3; i > 0; --i) addTriangleVertex(transform(triangle[i - 1])); + mFlags.push_back(flags); })); } - void RecastMeshBuilder::addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform) + void RecastMeshBuilder::addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform, + const unsigned char flags) { return addObject(shape, transform, makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { for (std::size_t i = 0; i < 3; ++i) addTriangleVertex(transform(triangle[i])); + mFlags.push_back(flags); })); } - void RecastMeshBuilder::addObject(const btBoxShape& shape, const btTransform& transform) + void RecastMeshBuilder::addObject(const btBoxShape& shape, const btTransform& transform, const unsigned char flags) { const auto indexOffset = int(mVertices.size() / 3); @@ -85,6 +91,9 @@ namespace DetourNavigator addVertex(transform(position)); } + for (int vertex = 0; vertex < 12; ++vertex) + mFlags.push_back(flags); + static const std::array indices {{ 0, 2, 3, 3, 1, 0, @@ -106,13 +115,14 @@ namespace DetourNavigator std::shared_ptr RecastMeshBuilder::create() const { - return std::make_shared(mIndices, mVertices, mSettings); + return std::make_shared(mIndices, mVertices, mFlags, mSettings); } void RecastMeshBuilder::reset() { mIndices.clear(); mVertices.clear(); + mFlags.clear(); } void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform, diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index d06f2cdd65..770acbf9d8 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -20,15 +20,15 @@ namespace DetourNavigator public: RecastMeshBuilder(const Settings& settings, const TileBounds& bounds); - void addObject(const btCollisionShape& shape, const btTransform& transform); + void addObject(const btCollisionShape& shape, const btTransform& transform, const unsigned char flags); - void addObject(const btCompoundShape& shape, const btTransform& transform); + void addObject(const btCompoundShape& shape, const btTransform& transform, const unsigned char flags); - void addObject(const btConcaveShape& shape, const btTransform& transform); + void addObject(const btConcaveShape& shape, const btTransform& transform, const unsigned char flags); - void addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform); + void addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform, const unsigned char flags); - void addObject(const btBoxShape& shape, const btTransform& transform); + void addObject(const btBoxShape& shape, const btTransform& transform, const unsigned char flags); std::shared_ptr create() const; @@ -39,6 +39,7 @@ namespace DetourNavigator TileBounds mBounds; std::vector mIndices; std::vector mVertices; + std::vector mFlags; void addObject(const btConcaveShape& shape, const btTransform& transform, btTriangleCallback&& callback); diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index d4b905e7de..66009d8b5c 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -11,20 +11,21 @@ namespace DetourNavigator { } - bool RecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) + bool RecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + const unsigned char flags) { - if (!mObjects.emplace(id, RecastMeshObject(shape, transform)).second) + if (!mObjects.emplace(id, RecastMeshObject(shape, transform, flags)).second) return false; mShouldRebuild = true; return mShouldRebuild; } - bool RecastMeshManager::updateObject(std::size_t id, const btTransform& transform) + bool RecastMeshManager::updateObject(std::size_t id, const btTransform& transform, const unsigned char flags) { const auto object = mObjects.find(id); if (object == mObjects.end()) return false; - if (!object->second.update(transform)) + if (!object->second.update(transform, flags)) return false; mShouldRebuild = true; return mShouldRebuild; @@ -58,7 +59,7 @@ namespace DetourNavigator return; mMeshBuilder.reset(); for (const auto& v : mObjects) - mMeshBuilder.addObject(v.second.getShape(), v.second.getTransform()); + mMeshBuilder.addObject(v.second.getShape(), v.second.getTransform(), v.second.getFlags()); mShouldRebuild = false; } } diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index c8e32714e2..d475d494de 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -25,9 +25,10 @@ namespace DetourNavigator public: RecastMeshManager(const Settings& settings, const TileBounds& bounds); - bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); + bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + const unsigned char flags); - bool updateObject(std::size_t id, const btTransform& transform); + bool updateObject(std::size_t id, const btTransform& transform, const unsigned char flags); boost::optional removeObject(std::size_t id); diff --git a/components/detournavigator/recastmeshobject.cpp b/components/detournavigator/recastmeshobject.cpp index b6727ba5e1..54fb25ea05 100644 --- a/components/detournavigator/recastmeshobject.cpp +++ b/components/detournavigator/recastmeshobject.cpp @@ -8,14 +8,16 @@ namespace DetourNavigator { - RecastMeshObject::RecastMeshObject(const btCollisionShape& shape, const btTransform& transform) + RecastMeshObject::RecastMeshObject(const btCollisionShape& shape, const btTransform& transform, + const unsigned char flags) : mShape(shape) , mTransform(transform) - , mChildren(makeChildrenObjects(shape)) + , mFlags(flags) + , mChildren(makeChildrenObjects(shape, mFlags)) { } - bool RecastMeshObject::update(const btTransform& transform) + bool RecastMeshObject::update(const btTransform& transform, const unsigned char flags) { bool result = false; if (!(mTransform == transform)) @@ -23,36 +25,42 @@ namespace DetourNavigator mTransform = transform; result = true; } + if (mFlags != flags) + { + mFlags = flags; + result = true; + } if (mShape.get().isCompound()) - result = updateCompoundObject(static_cast(mShape.get()), mChildren) || result; + result = updateCompoundObject(static_cast(mShape.get()), mFlags, mChildren) || result; return result; } - bool RecastMeshObject::updateCompoundObject(const btCompoundShape& shape, std::vector& children) + bool RecastMeshObject::updateCompoundObject(const btCompoundShape& shape, + const unsigned char flags, std::vector& children) { assert(static_cast(shape.getNumChildShapes()) == children.size()); bool result = false; for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) { assert(shape.getChildShape(i) == std::addressof(children[static_cast(i)].mShape.get())); - result = children[static_cast(i)].update(shape.getChildTransform(i)) || result; + result = children[static_cast(i)].update(shape.getChildTransform(i), flags) || result; } return result; } - std::vector makeChildrenObjects(const btCollisionShape& shape) + std::vector makeChildrenObjects(const btCollisionShape& shape, const unsigned char flags) { if (shape.isCompound()) - return makeChildrenObjects(static_cast(shape)); + return makeChildrenObjects(static_cast(shape), flags); else return std::vector(); } - std::vector makeChildrenObjects(const btCompoundShape& shape) + std::vector makeChildrenObjects(const btCompoundShape& shape, const unsigned char flags) { std::vector result; for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) - result.emplace_back(*shape.getChildShape(i), shape.getChildTransform(i)); + result.emplace_back(*shape.getChildShape(i), shape.getChildTransform(i), flags); return result; } } diff --git a/components/detournavigator/recastmeshobject.hpp b/components/detournavigator/recastmeshobject.hpp index 697acfa58e..bc7b54d5a0 100644 --- a/components/detournavigator/recastmeshobject.hpp +++ b/components/detournavigator/recastmeshobject.hpp @@ -14,9 +14,9 @@ namespace DetourNavigator class RecastMeshObject { public: - RecastMeshObject(const btCollisionShape& shape, const btTransform& transform); + RecastMeshObject(const btCollisionShape& shape, const btTransform& transform, const unsigned char flags); - bool update(const btTransform& transform); + bool update(const btTransform& transform, const unsigned char flags); const btCollisionShape& getShape() const { @@ -28,17 +28,24 @@ namespace DetourNavigator return mTransform; } + unsigned char getFlags() const + { + return mFlags; + } + private: std::reference_wrapper mShape; btTransform mTransform; + unsigned char mFlags; std::vector mChildren; - static bool updateCompoundObject(const btCompoundShape& shape, std::vector& children); + static bool updateCompoundObject(const btCompoundShape& shape, const unsigned char flags, + std::vector& children); }; - std::vector makeChildrenObjects(const btCollisionShape& shape); + std::vector makeChildrenObjects(const btCollisionShape& shape, const unsigned char flags); - std::vector makeChildrenObjects(const btCompoundShape& shape); + std::vector makeChildrenObjects(const btCompoundShape& shape, const unsigned char flags); } #endif diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 346ab9fd1c..a3dec79cd4 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -7,11 +7,10 @@ namespace DetourNavigator { TileCachedRecastMeshManager::TileCachedRecastMeshManager(const Settings& settings) : mSettings(settings) - { - } + {} bool TileCachedRecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, - const btTransform& transform) + const btTransform& transform, const unsigned char flags) { bool result = false; auto& tilesPositions = mObjectsTilesPositions[id]; @@ -28,7 +27,7 @@ namespace DetourNavigator tile = mTiles.insert(std::make_pair(tilePosition, CachedRecastMeshManager(mSettings, tileBounds))).first; } - if (tile->second.addObject(id, shape, transform)) + if (tile->second.addObject(id, shape, transform, flags)) { lock.unlock(); tilesPositions.push_back(tilePosition); @@ -40,7 +39,8 @@ namespace DetourNavigator return result; } - bool TileCachedRecastMeshManager::updateObject(std::size_t id, const btTransform& transform) + bool TileCachedRecastMeshManager::updateObject(std::size_t id, const btTransform& transform, + const unsigned char flags) { const auto object = mObjectsTilesPositions.find(id); if (object == mObjectsTilesPositions.end()) @@ -51,7 +51,7 @@ namespace DetourNavigator { const auto tile = mTiles.find(tilePosition); if (tile != mTiles.end()) - result = tile->second.updateObject(id, transform) || result; + result = tile->second.updateObject(id, transform, flags) || result; } lock.unlock(); if (result) diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 9e5c07e846..37299e281b 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -14,9 +14,10 @@ namespace DetourNavigator public: TileCachedRecastMeshManager(const Settings& settings); - bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); + bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + const unsigned char flags); - bool updateObject(std::size_t id, const btTransform& transform); + bool updateObject(std::size_t id, const btTransform& transform, const unsigned char flags); boost::optional removeObject(std::size_t id); diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 645a5dd3b9..e3f6b22b4a 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -80,16 +80,23 @@ btCollisionShape* BulletShape::duplicateCollisionShape(const btCollisionShape *s throw std::logic_error(std::string("Unhandled Bullet shape duplication: ")+shape->getName()); } -btCollisionShape *BulletShape::getCollisionShape() +btCollisionShape *BulletShape::getCollisionShape() const { return mCollisionShape; } -btCollisionShape *BulletShape::getAvoidCollisionShape() +btCollisionShape *BulletShape::getAvoidCollisionShape() const { return mAvoidCollisionShape; } +void BulletShape::setLocalScaling(const btVector3& scale) +{ + mCollisionShape->setLocalScaling(scale); + if (mAvoidCollisionShape) + mAvoidCollisionShape->setLocalScaling(scale); +} + osg::ref_ptr BulletShape::makeInstance() const { osg::ref_ptr instance (new BulletShapeInstance(this)); diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index f2f45cac91..8d6354b013 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -43,9 +43,11 @@ namespace Resource btCollisionShape* duplicateCollisionShape(const btCollisionShape* shape) const; - btCollisionShape* getCollisionShape(); + btCollisionShape* getCollisionShape() const; - btCollisionShape* getAvoidCollisionShape(); + btCollisionShape* getAvoidCollisionShape() const; + + void setLocalScaling(const btVector3& scale); private: From 72f211ef28d83bd76a135bc4cfc598e2da7c04cb Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 18 Jul 2018 22:09:50 +0300 Subject: [PATCH 070/133] Add enums for area type and flags --- .../detournavigator/recastmeshbuilder.cpp | 100 ++++++++++++------ .../detournavigator/recastmeshobject.cpp | 28 ++--- components/detournavigator/areatype.hpp | 15 +++ .../cachedrecastmeshmanager.cpp | 8 +- .../cachedrecastmeshmanager.hpp | 4 +- components/detournavigator/chunkytrimesh.cpp | 20 ++-- components/detournavigator/chunkytrimesh.hpp | 10 +- components/detournavigator/flags.hpp | 15 +++ components/detournavigator/makenavmesh.cpp | 11 +- components/detournavigator/navigator.cpp | 8 +- components/detournavigator/navigator.hpp | 3 +- components/detournavigator/navmeshmanager.cpp | 8 +- components/detournavigator/navmeshmanager.hpp | 4 +- components/detournavigator/recastmesh.cpp | 10 +- components/detournavigator/recastmesh.hpp | 9 +- .../detournavigator/recastmeshbuilder.cpp | 30 +++--- .../detournavigator/recastmeshbuilder.hpp | 12 +-- .../detournavigator/recastmeshmanager.cpp | 10 +- .../detournavigator/recastmeshmanager.hpp | 4 +- .../detournavigator/recastmeshobject.cpp | 27 ++--- .../detournavigator/recastmeshobject.hpp | 18 ++-- .../tilecachedrecastmeshmanager.cpp | 8 +- .../tilecachedrecastmeshmanager.hpp | 4 +- 23 files changed, 218 insertions(+), 148 deletions(-) create mode 100644 components/detournavigator/areatype.hpp create mode 100644 components/detournavigator/flags.hpp diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index ad0502cb83..c27f608a9e 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -40,7 +40,7 @@ namespace const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector()); EXPECT_EQ(recastMesh->getIndices(), std::vector()); - EXPECT_EQ(recastMesh->getFlags(), std::vector()); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector()); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_bhv_triangle_mesh_shape) @@ -49,7 +49,7 @@ namespace mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity(), 1); + builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, @@ -57,7 +57,7 @@ namespace -1, 0, -1, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); - EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_transformed_bhv_triangle_mesh_shape) @@ -69,7 +69,7 @@ namespace builder.addObject( static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), - 1 + AreaType_ground ); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ @@ -78,7 +78,7 @@ namespace 0, 3, 0, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); - EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_terrian_shape) @@ -86,7 +86,7 @@ namespace const std::array heightfieldData {{0, 0, 0, 0}}; btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity(), 1); + builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -0.5, 0, -0.5, @@ -97,14 +97,14 @@ namespace 0.5, 0, 0.5, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 3, 4, 5})); - EXPECT_EQ(recastMesh->getFlags(), std::vector({1, 1})); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground, AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_box_shape_should_produce_12_triangles) { btBoxShape shape(btVector3(1, 1, 2)); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity(), 1); + builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 2, 1, @@ -130,7 +130,7 @@ namespace 7, 6, 4, 4, 5, 7, })); - EXPECT_EQ(recastMesh->getFlags(), std::vector({1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1})); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector(12, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_compound_shape) @@ -147,7 +147,11 @@ namespace shape.addChildShape(btTransform::getIdentity(), &box); shape.addChildShape(btTransform::getIdentity(), &triangle2); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity(), 1); + builder.addObject( + static_cast(shape), + btTransform::getIdentity(), + AreaType_ground + ); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, @@ -181,7 +185,7 @@ namespace 7, 8, 10, 11, 12, 13, })); - EXPECT_EQ(recastMesh->getFlags(), std::vector({1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1})); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector(14, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_transformed_compound_shape) @@ -192,9 +196,11 @@ namespace btCompoundShape shape; shape.addChildShape(btTransform::getIdentity(), &triangle); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), - btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), - 1); + builder.addObject( + static_cast(shape), + btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), + AreaType_ground + ); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 2, 3, 0, @@ -202,7 +208,7 @@ namespace 0, 3, 0, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); - EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, add_transformed_compound_shape_with_transformed_bhv_triangle_shape) @@ -217,7 +223,7 @@ namespace builder.addObject( static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), - 1 + AreaType_ground ); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ @@ -226,7 +232,7 @@ namespace 1, 12, 2, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); - EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, without_bounds_add_bhv_triangle_shape_should_not_filter_by_bounds) @@ -236,7 +242,11 @@ namespace mesh.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity(), 1); + builder.addObject( + static_cast(shape), + btTransform::getIdentity(), + AreaType_ground + ); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, @@ -247,7 +257,7 @@ namespace -3, 0, -3, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 3, 4, 5})); - EXPECT_EQ(recastMesh->getFlags(), std::vector({1, 1})); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector(2, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_bhv_triangle_shape_should_filter_by_bounds) @@ -260,7 +270,11 @@ namespace mesh.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity(), 1); + builder.addObject( + static_cast(shape), + btTransform::getIdentity(), + AreaType_ground + ); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -0.2f, 0, -0.3f, @@ -268,7 +282,7 @@ namespace -0.3f, 0, -0.3f, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); - EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_x_bhv_triangle_shape_should_filter_by_bounds) @@ -280,8 +294,12 @@ namespace mesh.addTriangle(btVector3(0, -3, -3), btVector3(0, -3, -2), btVector3(0, -2, -3)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), - btTransform(btQuaternion(btVector3(1, 0, 0), static_cast(-osg::PI_4))), 1); + builder.addObject( + static_cast(shape), + btTransform(btQuaternion(btVector3(1, 0, 0), + static_cast(-osg::PI_4))), + AreaType_ground + ); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 0, -0.70710659027099609375, -3.535533905029296875, @@ -289,7 +307,7 @@ namespace 0, 2.384185791015625e-07, -4.24264049530029296875, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); - EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_y_bhv_triangle_shape_should_filter_by_bounds) @@ -301,8 +319,12 @@ namespace mesh.addTriangle(btVector3(-3, 0, -3), btVector3(-3, 0, -2), btVector3(-2, 0, -3)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), - btTransform(btQuaternion(btVector3(0, 1, 0), static_cast(osg::PI_4))), 1); + builder.addObject( + static_cast(shape), + btTransform(btQuaternion(btVector3(0, 1, 0), + static_cast(osg::PI_4))), + AreaType_ground + ); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -3.535533905029296875, -0.70710659027099609375, 0, @@ -310,7 +332,7 @@ namespace -4.24264049530029296875, 2.384185791015625e-07, 0, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); - EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_z_bhv_triangle_shape_should_filter_by_bounds) @@ -322,8 +344,12 @@ namespace mesh.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0)); btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape), - btTransform(btQuaternion(btVector3(0, 0, 1), static_cast(osg::PI_4))), 1); + builder.addObject( + static_cast(shape), + btTransform(btQuaternion(btVector3(0, 0, 1), + static_cast(osg::PI_4))), + AreaType_ground + ); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 0.707107067108154296875, 0, -3.535533905029296875, @@ -331,7 +357,7 @@ namespace 2.384185791015625e-07, 0, -4.24264049530029296875, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2})); - EXPECT_EQ(recastMesh->getFlags(), std::vector({1})); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground})); } TEST_F(DetourNavigatorRecastMeshBuilderTest, flags_values_should_be_corresponding_to_added_objects) @@ -343,8 +369,16 @@ namespace mesh2.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0)); btBvhTriangleMeshShape shape2(&mesh2, true); RecastMeshBuilder builder(mSettings, mBounds); - builder.addObject(static_cast(shape1), btTransform::getIdentity(), 1); - builder.addObject(static_cast(shape2), btTransform::getIdentity(), 0); + builder.addObject( + static_cast(shape1), + btTransform::getIdentity(), + AreaType_ground + ); + builder.addObject( + static_cast(shape2), + btTransform::getIdentity(), + AreaType_null + ); const auto recastMesh = builder.create(); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, @@ -355,6 +389,6 @@ namespace -3, 0, -3, })); EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 3, 4, 5})); - EXPECT_EQ(recastMesh->getFlags(), std::vector({1, 0})); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground, AreaType_null})); } } diff --git a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp index ba8441c60b..9b30cadd7a 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp @@ -26,47 +26,47 @@ namespace TEST_F(DetourNavigatorRecastMeshObjectTest, constructed_object_should_have_shape_and_transform) { - const RecastMeshObject object(mBoxShape, mTransform, 1); + const RecastMeshObject object(mBoxShape, mTransform, AreaType_ground); EXPECT_EQ(std::addressof(object.getShape()), std::addressof(mBoxShape)); EXPECT_EQ(object.getTransform(), mTransform); } TEST_F(DetourNavigatorRecastMeshObjectTest, update_with_same_transform_for_not_compound_shape_should_return_false) { - RecastMeshObject object(mBoxShape, mTransform, 1); - EXPECT_FALSE(object.update(mTransform, 1)); + RecastMeshObject object(mBoxShape, mTransform, AreaType_ground); + EXPECT_FALSE(object.update(mTransform, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshObjectTest, update_with_different_transform_should_return_true) { - RecastMeshObject object(mBoxShape, mTransform, 1); - EXPECT_TRUE(object.update(btTransform::getIdentity(), 1)); + RecastMeshObject object(mBoxShape, mTransform, AreaType_ground); + EXPECT_TRUE(object.update(btTransform::getIdentity(), AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshObjectTest, update_with_different_flags_should_return_true) { - RecastMeshObject object(mBoxShape, mTransform, 1); - EXPECT_TRUE(object.update(mTransform, 2)); + RecastMeshObject object(mBoxShape, mTransform, AreaType_ground); + EXPECT_TRUE(object.update(mTransform, AreaType_null)); } TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_compound_shape_with_same_transform_and_not_changed_child_transform_should_return_false) { - RecastMeshObject object(mCompoundShape, mTransform, 1); - EXPECT_FALSE(object.update(mTransform, 1)); + RecastMeshObject object(mCompoundShape, mTransform, AreaType_ground); + EXPECT_FALSE(object.update(mTransform, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_compound_shape_with_same_transform_and_changed_child_transform_should_return_true) { - RecastMeshObject object(mCompoundShape, mTransform, 1); + RecastMeshObject object(mCompoundShape, mTransform, AreaType_ground); mCompoundShape.updateChildTransform(0, btTransform::getIdentity()); - EXPECT_TRUE(object.update(mTransform, 1)); + EXPECT_TRUE(object.update(mTransform, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshObjectTest, repeated_update_for_compound_shape_without_changes_should_return_false) { - RecastMeshObject object(mCompoundShape, mTransform, 1); + RecastMeshObject object(mCompoundShape, mTransform, AreaType_ground); mCompoundShape.updateChildTransform(0, btTransform::getIdentity()); - object.update(mTransform, 1); - EXPECT_FALSE(object.update(mTransform, 1)); + object.update(mTransform, AreaType_ground); + EXPECT_FALSE(object.update(mTransform, AreaType_ground)); } } diff --git a/components/detournavigator/areatype.hpp b/components/detournavigator/areatype.hpp new file mode 100644 index 0000000000..3efaccc44c --- /dev/null +++ b/components/detournavigator/areatype.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_AREATYPE_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_AREATYPE_H + +#include + +namespace DetourNavigator +{ + enum AreaType : unsigned char + { + AreaType_null = RC_NULL_AREA, + AreaType_ground = RC_WALKABLE_AREA, + }; +} + +#endif diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index ce9fe5c517..c5ff00a265 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -8,17 +8,17 @@ namespace DetourNavigator {} bool CachedRecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, - const btTransform& transform, const unsigned char flags) + const btTransform& transform, const AreaType areaType) { - if (!mImpl.addObject(id, shape, transform, flags)) + if (!mImpl.addObject(id, shape, transform, areaType)) return false; mCached.reset(); return true; } - bool CachedRecastMeshManager::updateObject(std::size_t id, const btTransform& transform, const unsigned char flags) + bool CachedRecastMeshManager::updateObject(std::size_t id, const btTransform& transform, const AreaType areaType) { - if (!mImpl.updateObject(id, transform, flags)) + if (!mImpl.updateObject(id, transform, areaType)) return false; mCached.reset(); return true; diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index 6f3f2a05ab..15168f9ad6 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -13,9 +13,9 @@ namespace DetourNavigator CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds); bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, - const unsigned char flags); + const AreaType areaType); - bool updateObject(std::size_t id, const btTransform& transform, const unsigned char flags); + bool updateObject(std::size_t id, const btTransform& transform, const AreaType areaType); boost::optional removeObject(std::size_t id); diff --git a/components/detournavigator/chunkytrimesh.cpp b/components/detournavigator/chunkytrimesh.cpp index f6e257c54b..3a8fc34802 100644 --- a/components/detournavigator/chunkytrimesh.cpp +++ b/components/detournavigator/chunkytrimesh.cpp @@ -13,7 +13,7 @@ namespace DetourNavigator { Rect mBounds; std::ptrdiff_t mOffset; - unsigned char mFlags; + unsigned char mAreaTypes; }; template @@ -44,9 +44,9 @@ namespace DetourNavigator } void subdivide(std::vector& items, const std::size_t imin, const std::size_t imax, - const std::size_t trisPerChunk, const std::vector& inIndices, const std::vector& inFlags, + const std::size_t trisPerChunk, const std::vector& inIndices, const std::vector& inAreaTypes, std::size_t& curNode, std::vector& nodes, std::size_t& curTri, - std::vector& outIndices, std::vector& outFlags) + std::vector& outIndices, std::vector& outAreaTypes) { const auto inum = imax - imin; const auto icur = curNode; @@ -72,7 +72,7 @@ namespace DetourNavigator inIndices.begin() + items[i].mOffset * 3 + 3, outIndices.begin() + static_cast(curTri) * 3 ); - outFlags[curTri] = inFlags[static_cast(items[i].mOffset)]; + outAreaTypes[curTri] = inAreaTypes[static_cast(items[i].mOffset)]; curTri++; } } @@ -104,9 +104,9 @@ namespace DetourNavigator const auto isplit = imin + inum / 2; // Left - subdivide(items, imin, isplit, trisPerChunk, inIndices, inFlags, curNode, nodes, curTri, outIndices, outFlags); + subdivide(items, imin, isplit, trisPerChunk, inIndices, inAreaTypes, curNode, nodes, curTri, outIndices, outAreaTypes); // Right - subdivide(items, isplit, imax, trisPerChunk, inIndices, inFlags, curNode, nodes, curTri, outIndices, outFlags); + subdivide(items, isplit, imax, trisPerChunk, inIndices, inAreaTypes, curNode, nodes, curTri, outIndices, outAreaTypes); const auto iescape = static_cast(curNode) - static_cast(icur); // Negative index means escape. @@ -116,7 +116,7 @@ namespace DetourNavigator } ChunkyTriMesh::ChunkyTriMesh(const std::vector& verts, const std::vector& indices, - const std::vector& flags, const std::size_t trisPerChunk) + const std::vector& flags, const std::size_t trisPerChunk) : mMaxTrisPerChunk(0) { const auto trianglesCount = indices.size() / 3; @@ -128,7 +128,7 @@ namespace DetourNavigator mNodes.resize(nchunks * 4); mIndices.resize(trianglesCount * 3); - mFlags.resize(trianglesCount); + mAreaTypes.resize(trianglesCount); // Build tree std::vector items(trianglesCount); @@ -138,7 +138,7 @@ namespace DetourNavigator auto& item = items[i]; item.mOffset = static_cast(i); - item.mFlags = flags[i]; + item.mAreaTypes = flags[i]; // Calc triangle XZ bounds. const auto baseIndex = static_cast(indices[i * 3]) * 3; @@ -160,7 +160,7 @@ namespace DetourNavigator std::size_t curTri = 0; std::size_t curNode = 0; - subdivide(items, 0, trianglesCount, trisPerChunk, indices, flags, curNode, mNodes, curTri, mIndices, mFlags); + subdivide(items, 0, trianglesCount, trisPerChunk, indices, flags, curNode, mNodes, curTri, mIndices, mAreaTypes); items.clear(); diff --git a/components/detournavigator/chunkytrimesh.hpp b/components/detournavigator/chunkytrimesh.hpp index ddd0b93e67..c9d35f0865 100644 --- a/components/detournavigator/chunkytrimesh.hpp +++ b/components/detournavigator/chunkytrimesh.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CHUNKYTRIMESH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_CHUNKYTRIMESH_H +#include "areatype.hpp" + #include #include @@ -24,7 +26,7 @@ namespace DetourNavigator struct Chunk { const int* const mIndices; - const unsigned char* const mFlags; + const AreaType* const mAreaTypes; const std::size_t mSize; }; @@ -42,7 +44,7 @@ namespace DetourNavigator /// Creates partitioned triangle mesh (AABB tree), /// where each node contains at max trisPerChunk triangles. ChunkyTriMesh(const std::vector& verts, const std::vector& tris, - const std::vector& flags, const std::size_t trisPerChunk); + const std::vector& flags, const std::size_t trisPerChunk); ChunkyTriMesh(const ChunkyTriMesh&) = delete; ChunkyTriMesh& operator=(const ChunkyTriMesh&) = delete; @@ -76,7 +78,7 @@ namespace DetourNavigator const auto& node = mNodes[chunkId]; return Chunk { mIndices.data() + node.mOffset * 3, - mFlags.data() + node.mOffset, + mAreaTypes.data() + node.mOffset, node.mSize }; } @@ -89,7 +91,7 @@ namespace DetourNavigator private: std::vector mNodes; std::vector mIndices; - std::vector mFlags; + std::vector mAreaTypes; std::size_t mMaxTrisPerChunk; }; } diff --git a/components/detournavigator/flags.hpp b/components/detournavigator/flags.hpp new file mode 100644 index 0000000000..21a1d7694c --- /dev/null +++ b/components/detournavigator/flags.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_FLAGS_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_FLAGS_H + +namespace DetourNavigator +{ + using Flags = unsigned short; + + enum Flag : Flags + { + Flag_none = 0, + Flag_walk = 1 << 0, + }; +} + +#endif diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 4b77f45b76..454b88f9ea 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -8,6 +8,7 @@ #include "settingsutils.hpp" #include "sharednavmesh.hpp" #include "settingsutils.hpp" +#include "flags.hpp" #include #include @@ -99,7 +100,7 @@ namespace { const auto& chunkyMesh = recastMesh.getChunkyTriMesh(); - std::vector areas(chunkyMesh.getMaxTrisPerChunk(), RC_NULL_AREA); + std::vector areas(chunkyMesh.getMaxTrisPerChunk(), AreaType_null); const osg::Vec2f tileBoundsMin(config.bmin[0], config.bmin[2]); const osg::Vec2f tileBoundsMax(config.bmax[0], config.bmax[2]); std::vector cids; @@ -116,7 +117,7 @@ namespace areas.begin(), std::min(areas.begin() + static_cast(chunk.mSize), areas.end()), - RC_NULL_AREA + AreaType_null ); rcMarkWalkableTriangles( @@ -130,7 +131,7 @@ namespace ); for (std::size_t i = 0; i < chunk.mSize; ++i) - areas[i] &= chunk.mFlags[i]; + areas[i] = chunk.mAreaTypes[i]; rcClearUnwalkableTriangles( &context, @@ -186,8 +187,8 @@ namespace } for (int i = 0; i < polyMesh.npolys; ++i) - if (polyMesh.areas[i] == RC_WALKABLE_AREA) - polyMesh.flags[i] = 1; + if (polyMesh.areas[i] == AreaType_ground) + polyMesh.flags[i] = Flag_walk; dtNavMeshCreateParams params; params.verts = polyMesh.verts; diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index 0fc195675d..f6ead7c7db 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -29,7 +29,7 @@ namespace DetourNavigator bool Navigator::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) { - return mNavMeshManager.addObject(id, shape, transform, RC_WALKABLE_AREA); + return mNavMeshManager.addObject(id, shape, transform, AreaType_ground); } bool Navigator::addObject(std::size_t id, const ObjectShapes& shapes, const btTransform& transform) @@ -38,7 +38,7 @@ namespace DetourNavigator if (shapes.mAvoid) { const auto avoidId = reinterpret_cast(shapes.mAvoid); - if (mNavMeshManager.addObject(avoidId, *shapes.mAvoid, transform, RC_NULL_AREA)) + if (mNavMeshManager.addObject(avoidId, *shapes.mAvoid, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); result = true; @@ -49,7 +49,7 @@ namespace DetourNavigator bool Navigator::updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) { - return mNavMeshManager.updateObject(id, shape, transform, RC_WALKABLE_AREA); + return mNavMeshManager.updateObject(id, shape, transform, AreaType_ground); } bool Navigator::updateObject(std::size_t id, const ObjectShapes& shapes, const btTransform& transform) @@ -58,7 +58,7 @@ namespace DetourNavigator if (shapes.mAvoid) { const auto avoidId = reinterpret_cast(shapes.mAvoid); - if (mNavMeshManager.updateObject(avoidId, *shapes.mAvoid, transform, RC_NULL_AREA)) + if (mNavMeshManager.updateObject(avoidId, *shapes.mAvoid, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); result = true; diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 39dd41a140..0f82bd7abe 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -15,8 +15,7 @@ namespace DetourNavigator ObjectShapes(const btCollisionShape& shape, const btCollisionShape* avoid) : mShape(shape), mAvoid(avoid) - { - } + {} }; class Navigator diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 7ddaef55c7..265ef749f5 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -32,18 +32,18 @@ namespace DetourNavigator {} bool NavMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, - const unsigned char flags) + const AreaType areaType) { - if (!mRecastMeshManager.addObject(id, shape, transform, flags)) + if (!mRecastMeshManager.addObject(id, shape, transform, areaType)) return false; addChangedTiles(shape, transform, ChangeType::add); return true; } bool NavMeshManager::updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, - const unsigned char flags) + const AreaType areaType) { - if (!mRecastMeshManager.updateObject(id, transform, flags)) + if (!mRecastMeshManager.updateObject(id, transform, areaType)) return false; addChangedTiles(shape, transform, ChangeType::mixed); return true; diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index fbbc81e6cf..25ee305c4f 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -24,10 +24,10 @@ namespace DetourNavigator NavMeshManager(const Settings& settings); bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, - const unsigned char flags); + const AreaType areaType); bool updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, - const unsigned char flags); + const AreaType areaType); bool removeObject(std::size_t id); diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index fc23c1feaf..5a339d3427 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -7,15 +7,15 @@ namespace DetourNavigator { RecastMesh::RecastMesh(std::vector indices, std::vector vertices, - std::vector flags, const Settings& settings) + std::vector areaTypes, const Settings& settings) : mIndices(std::move(indices)) , mVertices(std::move(vertices)) - , mFlags(std::move(flags)) - , mChunkyTriMesh(mVertices, mIndices, mFlags, settings.mTrianglesPerChunk) + , mAreaTypes(std::move(areaTypes)) + , mChunkyTriMesh(mVertices, mIndices, mAreaTypes, settings.mTrianglesPerChunk) { - if (getTrianglesCount() != mFlags.size()) + if (getTrianglesCount() != mAreaTypes.size()) throw InvalidArgument("number of flags doesn't match number of triangles: triangles=" - + std::to_string(getTrianglesCount()) + ", flags=" + std::to_string(mFlags.size())); + + std::to_string(getTrianglesCount()) + ", areaTypes=" + std::to_string(mAreaTypes.size())); if (getVerticesCount()) rcCalcBounds(mVertices.data(), static_cast(getVerticesCount()), mBoundsMin.ptr(), mBoundsMax.ptr()); } diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 59f97b2ee0..2e31cd27d2 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESH_H +#include "areatype.hpp" #include "chunkytrimesh.hpp" #include @@ -16,7 +17,7 @@ namespace DetourNavigator { public: RecastMesh(std::vector indices, std::vector vertices, - std::vector flags, const Settings& settings); + std::vector areaTypes, const Settings& settings); const std::vector& getIndices() const { @@ -28,9 +29,9 @@ namespace DetourNavigator return mVertices; } - const std::vector& getFlags() const + const std::vector& getAreaTypes() const { - return mFlags; + return mAreaTypes; } std::size_t getVerticesCount() const @@ -61,7 +62,7 @@ namespace DetourNavigator private: std::vector mIndices; std::vector mVertices; - std::vector mFlags; + std::vector mAreaTypes; ChunkyTriMesh mChunkyTriMesh; osg::Vec3f mBoundsMin; osg::Vec3f mBoundsMax; diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 229f420224..23a8bb83e6 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -36,51 +36,51 @@ namespace DetourNavigator } void RecastMeshBuilder::addObject(const btCollisionShape& shape, const btTransform& transform, - const unsigned char flags) + const AreaType areaType) { if (shape.isCompound()) - return addObject(static_cast(shape), transform, flags); + return addObject(static_cast(shape), transform, areaType); else if (shape.getShapeType() == TERRAIN_SHAPE_PROXYTYPE) - return addObject(static_cast(shape), transform, flags); + return addObject(static_cast(shape), transform, areaType); else if (shape.isConcave()) - return addObject(static_cast(shape), transform, flags); + return addObject(static_cast(shape), transform, areaType); else if (shape.getShapeType() == BOX_SHAPE_PROXYTYPE) - return addObject(static_cast(shape), transform, flags); + return addObject(static_cast(shape), transform, areaType); std::ostringstream message; message << "Unsupported shape type: " << BroadphaseNativeTypes(shape.getShapeType()); throw InvalidArgument(message.str()); } void RecastMeshBuilder::addObject(const btCompoundShape& shape, const btTransform& transform, - const unsigned char flags) + const AreaType areaType) { for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) - addObject(*shape.getChildShape(i), transform * shape.getChildTransform(i), flags); + addObject(*shape.getChildShape(i), transform * shape.getChildTransform(i), areaType); } void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform, - const unsigned char flags) + const AreaType areaType) { return addObject(shape, transform, makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { for (std::size_t i = 3; i > 0; --i) addTriangleVertex(transform(triangle[i - 1])); - mFlags.push_back(flags); + mAreaTypes.push_back(areaType); })); } void RecastMeshBuilder::addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform, - const unsigned char flags) + const AreaType areaType) { return addObject(shape, transform, makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { for (std::size_t i = 0; i < 3; ++i) addTriangleVertex(transform(triangle[i])); - mFlags.push_back(flags); + mAreaTypes.push_back(areaType); })); } - void RecastMeshBuilder::addObject(const btBoxShape& shape, const btTransform& transform, const unsigned char flags) + void RecastMeshBuilder::addObject(const btBoxShape& shape, const btTransform& transform, const AreaType areaType) { const auto indexOffset = int(mVertices.size() / 3); @@ -92,7 +92,7 @@ namespace DetourNavigator } for (int vertex = 0; vertex < 12; ++vertex) - mFlags.push_back(flags); + mAreaTypes.push_back(areaType); static const std::array indices {{ 0, 2, 3, @@ -115,14 +115,14 @@ namespace DetourNavigator std::shared_ptr RecastMeshBuilder::create() const { - return std::make_shared(mIndices, mVertices, mFlags, mSettings); + return std::make_shared(mIndices, mVertices, mAreaTypes, mSettings); } void RecastMeshBuilder::reset() { mIndices.clear(); mVertices.clear(); - mFlags.clear(); + mAreaTypes.clear(); } void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform, diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index 770acbf9d8..2210933627 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -20,15 +20,15 @@ namespace DetourNavigator public: RecastMeshBuilder(const Settings& settings, const TileBounds& bounds); - void addObject(const btCollisionShape& shape, const btTransform& transform, const unsigned char flags); + void addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); - void addObject(const btCompoundShape& shape, const btTransform& transform, const unsigned char flags); + void addObject(const btCompoundShape& shape, const btTransform& transform, const AreaType areaType); - void addObject(const btConcaveShape& shape, const btTransform& transform, const unsigned char flags); + void addObject(const btConcaveShape& shape, const btTransform& transform, const AreaType areaType); - void addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform, const unsigned char flags); + void addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform, const AreaType areaType); - void addObject(const btBoxShape& shape, const btTransform& transform, const unsigned char flags); + void addObject(const btBoxShape& shape, const btTransform& transform, const AreaType areaType); std::shared_ptr create() const; @@ -39,7 +39,7 @@ namespace DetourNavigator TileBounds mBounds; std::vector mIndices; std::vector mVertices; - std::vector mFlags; + std::vector mAreaTypes; void addObject(const btConcaveShape& shape, const btTransform& transform, btTriangleCallback&& callback); diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 66009d8b5c..d511891942 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -12,20 +12,20 @@ namespace DetourNavigator } bool RecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, - const unsigned char flags) + const AreaType areaType) { - if (!mObjects.emplace(id, RecastMeshObject(shape, transform, flags)).second) + if (!mObjects.emplace(id, RecastMeshObject(shape, transform, areaType)).second) return false; mShouldRebuild = true; return mShouldRebuild; } - bool RecastMeshManager::updateObject(std::size_t id, const btTransform& transform, const unsigned char flags) + bool RecastMeshManager::updateObject(std::size_t id, const btTransform& transform, const AreaType areaType) { const auto object = mObjects.find(id); if (object == mObjects.end()) return false; - if (!object->second.update(transform, flags)) + if (!object->second.update(transform, areaType)) return false; mShouldRebuild = true; return mShouldRebuild; @@ -59,7 +59,7 @@ namespace DetourNavigator return; mMeshBuilder.reset(); for (const auto& v : mObjects) - mMeshBuilder.addObject(v.second.getShape(), v.second.getTransform(), v.second.getFlags()); + mMeshBuilder.addObject(v.second.getShape(), v.second.getTransform(), v.second.getAreaType()); mShouldRebuild = false; } } diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index d475d494de..7900592950 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -26,9 +26,9 @@ namespace DetourNavigator RecastMeshManager(const Settings& settings, const TileBounds& bounds); bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, - const unsigned char flags); + const AreaType areaType); - bool updateObject(std::size_t id, const btTransform& transform, const unsigned char flags); + bool updateObject(std::size_t id, const btTransform& transform, const AreaType areaType); boost::optional removeObject(std::size_t id); diff --git a/components/detournavigator/recastmeshobject.cpp b/components/detournavigator/recastmeshobject.cpp index 54fb25ea05..1aac9fd81d 100644 --- a/components/detournavigator/recastmeshobject.cpp +++ b/components/detournavigator/recastmeshobject.cpp @@ -9,15 +9,15 @@ namespace DetourNavigator { RecastMeshObject::RecastMeshObject(const btCollisionShape& shape, const btTransform& transform, - const unsigned char flags) + const AreaType areaType) : mShape(shape) , mTransform(transform) - , mFlags(flags) - , mChildren(makeChildrenObjects(shape, mFlags)) + , mAreaType(areaType) + , mChildren(makeChildrenObjects(shape, mAreaType)) { } - bool RecastMeshObject::update(const btTransform& transform, const unsigned char flags) + bool RecastMeshObject::update(const btTransform& transform, const AreaType areaType) { bool result = false; if (!(mTransform == transform)) @@ -25,42 +25,43 @@ namespace DetourNavigator mTransform = transform; result = true; } - if (mFlags != flags) + if (mAreaType != areaType) { - mFlags = flags; + mAreaType = areaType; result = true; } if (mShape.get().isCompound()) - result = updateCompoundObject(static_cast(mShape.get()), mFlags, mChildren) || result; + result = updateCompoundObject(static_cast(mShape.get()), mAreaType, mChildren) + || result; return result; } bool RecastMeshObject::updateCompoundObject(const btCompoundShape& shape, - const unsigned char flags, std::vector& children) + const AreaType areaType, std::vector& children) { assert(static_cast(shape.getNumChildShapes()) == children.size()); bool result = false; for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) { assert(shape.getChildShape(i) == std::addressof(children[static_cast(i)].mShape.get())); - result = children[static_cast(i)].update(shape.getChildTransform(i), flags) || result; + result = children[static_cast(i)].update(shape.getChildTransform(i), areaType) || result; } return result; } - std::vector makeChildrenObjects(const btCollisionShape& shape, const unsigned char flags) + std::vector makeChildrenObjects(const btCollisionShape& shape, const AreaType areaType) { if (shape.isCompound()) - return makeChildrenObjects(static_cast(shape), flags); + return makeChildrenObjects(static_cast(shape), areaType); else return std::vector(); } - std::vector makeChildrenObjects(const btCompoundShape& shape, const unsigned char flags) + std::vector makeChildrenObjects(const btCompoundShape& shape, const AreaType areaType) { std::vector result; for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) - result.emplace_back(*shape.getChildShape(i), shape.getChildTransform(i), flags); + result.emplace_back(*shape.getChildShape(i), shape.getChildTransform(i), areaType); return result; } } diff --git a/components/detournavigator/recastmeshobject.hpp b/components/detournavigator/recastmeshobject.hpp index bc7b54d5a0..aff4681222 100644 --- a/components/detournavigator/recastmeshobject.hpp +++ b/components/detournavigator/recastmeshobject.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHOBJECT_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHOBJECT_H +#include "areatype.hpp" + #include #include @@ -14,9 +16,9 @@ namespace DetourNavigator class RecastMeshObject { public: - RecastMeshObject(const btCollisionShape& shape, const btTransform& transform, const unsigned char flags); + RecastMeshObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); - bool update(const btTransform& transform, const unsigned char flags); + bool update(const btTransform& transform, const AreaType areaType); const btCollisionShape& getShape() const { @@ -28,24 +30,24 @@ namespace DetourNavigator return mTransform; } - unsigned char getFlags() const + AreaType getAreaType() const { - return mFlags; + return mAreaType; } private: std::reference_wrapper mShape; btTransform mTransform; - unsigned char mFlags; + AreaType mAreaType; std::vector mChildren; - static bool updateCompoundObject(const btCompoundShape& shape, const unsigned char flags, + static bool updateCompoundObject(const btCompoundShape& shape, const AreaType areaType, std::vector& children); }; - std::vector makeChildrenObjects(const btCollisionShape& shape, const unsigned char flags); + std::vector makeChildrenObjects(const btCollisionShape& shape, const AreaType areaType); - std::vector makeChildrenObjects(const btCompoundShape& shape, const unsigned char flags); + std::vector makeChildrenObjects(const btCompoundShape& shape, const AreaType areaType); } #endif diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index a3dec79cd4..f044b3c43e 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -10,7 +10,7 @@ namespace DetourNavigator {} bool TileCachedRecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, - const btTransform& transform, const unsigned char flags) + const btTransform& transform, const AreaType areaType) { bool result = false; auto& tilesPositions = mObjectsTilesPositions[id]; @@ -27,7 +27,7 @@ namespace DetourNavigator tile = mTiles.insert(std::make_pair(tilePosition, CachedRecastMeshManager(mSettings, tileBounds))).first; } - if (tile->second.addObject(id, shape, transform, flags)) + if (tile->second.addObject(id, shape, transform, areaType)) { lock.unlock(); tilesPositions.push_back(tilePosition); @@ -40,7 +40,7 @@ namespace DetourNavigator } bool TileCachedRecastMeshManager::updateObject(std::size_t id, const btTransform& transform, - const unsigned char flags) + const AreaType areaType) { const auto object = mObjectsTilesPositions.find(id); if (object == mObjectsTilesPositions.end()) @@ -51,7 +51,7 @@ namespace DetourNavigator { const auto tile = mTiles.find(tilePosition); if (tile != mTiles.end()) - result = tile->second.updateObject(id, transform, flags) || result; + result = tile->second.updateObject(id, transform, areaType) || result; } lock.unlock(); if (result) diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 37299e281b..32b06eadd6 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -15,9 +15,9 @@ namespace DetourNavigator TileCachedRecastMeshManager(const Settings& settings); bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, - const unsigned char flags); + const AreaType areaType); - bool updateObject(std::size_t id, const btTransform& transform, const unsigned char flags); + bool updateObject(std::size_t id, const btTransform& transform, const AreaType areaType); boost::optional removeObject(std::size_t id); From c95cea414c27f04a4acebcf796d7c12451b17465 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 20 Jul 2018 22:11:34 +0300 Subject: [PATCH 071/133] Support water for NavMesh --- apps/openmw/mwworld/scene.cpp | 25 +++- apps/openmw/mwworld/scene.hpp | 6 + apps/openmw/mwworld/worldimp.cpp | 57 +++---- .../detournavigator/navigator.cpp | 139 ++++++++++++++++++ .../detournavigator/recastmeshbuilder.cpp | 18 +++ components/detournavigator/areatype.hpp | 1 + .../cachedrecastmeshmanager.cpp | 17 +++ .../cachedrecastmeshmanager.hpp | 4 + components/detournavigator/findsmoothpath.hpp | 9 ++ components/detournavigator/flags.hpp | 1 + .../detournavigator/gettilespositions.hpp | 17 +++ components/detournavigator/makenavmesh.cpp | 101 ++++++++++++- components/detournavigator/makenavmesh.hpp | 1 - components/detournavigator/navigator.cpp | 37 ++++- components/detournavigator/navigator.hpp | 10 +- components/detournavigator/navmeshmanager.cpp | 65 +++++--- components/detournavigator/navmeshmanager.hpp | 10 +- components/detournavigator/recastmesh.cpp | 3 +- components/detournavigator/recastmesh.hpp | 16 +- .../detournavigator/recastmeshbuilder.cpp | 19 ++- .../detournavigator/recastmeshbuilder.hpp | 10 +- .../detournavigator/recastmeshmanager.cpp | 22 +++ .../detournavigator/recastmeshmanager.hpp | 14 ++ components/detournavigator/settings.hpp | 1 + components/detournavigator/settingsutils.hpp | 16 ++ .../tilecachedrecastmeshmanager.cpp | 74 ++++++++++ .../tilecachedrecastmeshmanager.hpp | 5 + 27 files changed, 626 insertions(+), 72 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index bfd21d5e27..ca19383d15 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -282,6 +282,9 @@ namespace MWWorld mPhysics->remove(ptr); } + const auto cellX = (*iter)->getCell()->getGridX(); + const auto cellY = (*iter)->getCell()->getGridY(); + if ((*iter)->getCell()->isExterior()) { const ESM::Land* land = @@ -291,14 +294,15 @@ namespace MWWorld ); if (land && land->mDataTypes&ESM::Land::DATA_VHGT) { - const auto cellX = (*iter)->getCell()->getGridX(); - const auto cellY = (*iter)->getCell()->getGridY(); if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) navigator->removeObject(reinterpret_cast(heightField)); mPhysics->removeHeightField(cellX, cellY); } } + if ((*iter)->getCell()->hasWater()) + navigator->removeWater(osg::Vec2i(cellX, cellY)); + navigator->update(player.getRefData().getPosition().asVec3()); MWBase::Environment::get().getMechanicsManager()->drop (*iter); @@ -326,11 +330,12 @@ namespace MWWorld const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); + const int cellX = cell->getCell()->getGridX(); + const int cellY = cell->getCell()->getGridY(); + // Load terrain physics first... if (cell->getCell()->isExterior()) { - int cellX = cell->getCell()->getGridX(); - int cellY = cell->getCell()->getGridY(); osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : 0; if (data) @@ -368,6 +373,18 @@ namespace MWWorld { mPhysics->enableWater(waterLevel); mRendering.setWaterHeight(waterLevel); + + if (cell->getCell()->isExterior()) + { + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, + cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); + } + else + { + navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), + cell->getWaterLevel(), btTransform::getIdentity()); + } } else mPhysics->disableWater(); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index e1144d97a2..c8578fc354 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace osg { @@ -27,6 +28,11 @@ namespace Loading class Listener; } +namespace DetourNavigator +{ + class Water; +} + namespace MWRender { class SkyManager; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index a7bda8c0be..bb8bd9615a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -168,6 +168,34 @@ namespace MWWorld mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) { + mEsm.resize(contentFiles.size()); + Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + listener->loadingOn(); + + GameContentLoader gameContentLoader(*listener); + EsmLoader esmLoader(mStore, mEsm, encoder, *listener); + + gameContentLoader.addLoader(".esm", &esmLoader); + gameContentLoader.addLoader(".esp", &esmLoader); + gameContentLoader.addLoader(".omwgame", &esmLoader); + gameContentLoader.addLoader(".omwaddon", &esmLoader); + gameContentLoader.addLoader(".project", &esmLoader); + + loadContentFiles(fileCollections, contentFiles, gameContentLoader); + + listener->loadingOff(); + + // insert records that may not be present in all versions of MW + if (mEsm[0].getFormat() == 0) + ensureNeededRecords(); + + fillGlobalVariables(); + + mStore.setUp(true); + mStore.movePlayerRecord(); + + mSwimHeightScale = mStore.get().find("fSwimHeightScale")->mValue.getFloat(); + mPhysics.reset(new MWPhysics::PhysicsSystem(resourceSystem, rootNode)); DetourNavigator::Settings navigatorSettings; @@ -180,6 +208,7 @@ namespace MWWorld navigatorSettings.mMaxSimplificationError = Settings::Manager::getFloat("max simplification error", "Navigator"); navigatorSettings.mMaxSlope = MWPhysics::sMaxSlope; navigatorSettings.mRecastScaleFactor = Settings::Manager::getFloat("recast scale factor", "Navigator"); + navigatorSettings.mSwimHeightScale = mSwimHeightScale; navigatorSettings.mMaxEdgeLen = Settings::Manager::getInt("max edge len", "Navigator"); navigatorSettings.mMaxNavMeshQueryNodes = Settings::Manager::getInt("max nav mesh query nodes", "Navigator"); navigatorSettings.mMaxVertsPerPoly = Settings::Manager::getInt("max verts per poly", "Navigator"); @@ -204,34 +233,6 @@ namespace MWWorld mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get())); mRendering->preloadCommonAssets(); - mEsm.resize(contentFiles.size()); - Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); - listener->loadingOn(); - - GameContentLoader gameContentLoader(*listener); - EsmLoader esmLoader(mStore, mEsm, encoder, *listener); - - gameContentLoader.addLoader(".esm", &esmLoader); - gameContentLoader.addLoader(".esp", &esmLoader); - gameContentLoader.addLoader(".omwgame", &esmLoader); - gameContentLoader.addLoader(".omwaddon", &esmLoader); - gameContentLoader.addLoader(".project", &esmLoader); - - loadContentFiles(fileCollections, contentFiles, gameContentLoader); - - listener->loadingOff(); - - // insert records that may not be present in all versions of MW - if (mEsm[0].getFormat() == 0) - ensureNeededRecords(); - - fillGlobalVariables(); - - mStore.setUp(true); - mStore.movePlayerRecord(); - - mSwimHeightScale = mStore.get().find("fSwimHeightScale")->mValue.getFloat(); - mWeatherManager.reset(new MWWorld::WeatherManager(*mRendering, mFallback, mStore)); mWorldScene.reset(new Scene(*mRendering.get(), mPhysics.get())); diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index aa7906008a..84aa344744 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -48,6 +48,7 @@ namespace mSettings.mMaxSimplificationError = 1.3f; mSettings.mMaxSlope = 49; mSettings.mRecastScaleFactor = 0.017647058823529415f; + mSettings.mSwimHeightScale = 0.89999997615814208984375f; mSettings.mMaxEdgeLen = 12; mSettings.mMaxNavMeshQueryNodes = 2048; mSettings.mMaxVertsPerPoly = 6; @@ -415,4 +416,142 @@ namespace osg::Vec3f(215, -215, 1.93937528133392333984375), })) << mPath; } + + TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_ground_lower_than_water) + { + std::array heightfieldData {{ + -50, -50, -50, -50, 0, + -50, -100, -150, -100, -50, + -50, -150, -200, -150, -100, + -50, -100, -150, -100, -100, + 0, -50, -100, -100, -100, + }}; + btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + shape.setLocalScaling(btVector3(128, 128, 1)); + + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, 300, btTransform::getIdentity()); + mNavigator->addObject(1, shape, btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + mStart.x() = 0; + mStart.z() = 300; + mEnd.x() = 0; + mEnd.z() = 300; + + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); + + EXPECT_EQ(mPath, std::deque({ + osg::Vec3f(0, 215, 185.33331298828125), + osg::Vec3f(0, 186.6666717529296875, 185.33331298828125), + osg::Vec3f(0, 158.333343505859375, 185.33331298828125), + osg::Vec3f(0, 130.0000152587890625, 185.33331298828125), + osg::Vec3f(0, 101.66667938232421875, 185.33331298828125), + osg::Vec3f(0, 73.333343505859375, 185.33331298828125), + osg::Vec3f(0, 45.0000152587890625, 185.33331298828125), + osg::Vec3f(0, 16.6666812896728515625, 185.33331298828125), + osg::Vec3f(0, -11.66664981842041015625, 185.33331298828125), + osg::Vec3f(0, -39.999980926513671875, 185.33331298828125), + osg::Vec3f(0, -68.33331298828125, 185.33331298828125), + osg::Vec3f(0, -96.66664886474609375, 185.33331298828125), + osg::Vec3f(0, -124.99997711181640625, 185.33331298828125), + osg::Vec3f(0, -153.33331298828125, 185.33331298828125), + osg::Vec3f(0, -181.6666412353515625, 185.33331298828125), + osg::Vec3f(0, -209.999969482421875, 185.33331298828125), + osg::Vec3f(0, -215, 185.33331298828125), + })) << mPath; + } + + TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_when_ground_cross_water) + { + std::array heightfieldData {{ + 0, 0, 0, 0, 0, 0, 0, + 0, -100, -100, -100, -100, -100, 0, + 0, -100, -150, -150, -150, -100, 0, + 0, -100, -150, -200, -150, -100, 0, + 0, -100, -150, -150, -150, -100, 0, + 0, -100, -100, -100, -100, -100, 0, + 0, 0, 0, 0, 0, 0, 0, + }}; + btHeightfieldTerrainShape shape(7, 7, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + shape.setLocalScaling(btVector3(128, 128, 1)); + + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, -25, btTransform::getIdentity()); + mNavigator->addObject(1, shape, btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + mStart.x() = 0; + mEnd.x() = 0; + + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); + + EXPECT_EQ(mPath, std::deque({ + osg::Vec3f(0, 215, -94.75363922119140625), + osg::Vec3f(0, 186.6666717529296875, -106.0000152587890625), + osg::Vec3f(0, 158.333343505859375, -115.85507965087890625), + osg::Vec3f(0, 130.0000152587890625, -125.71016693115234375), + osg::Vec3f(0, 101.66667938232421875, -135.5652313232421875), + osg::Vec3f(0, 73.333343505859375, -143.3333587646484375), + osg::Vec3f(0, 45.0000152587890625, -143.3333587646484375), + osg::Vec3f(0, 16.6666812896728515625, -143.3333587646484375), + osg::Vec3f(0, -11.66664981842041015625, -143.3333587646484375), + osg::Vec3f(0, -39.999980926513671875, -143.3333587646484375), + osg::Vec3f(0, -68.33331298828125, -143.3333587646484375), + osg::Vec3f(0, -96.66664886474609375, -137.3043670654296875), + osg::Vec3f(0, -124.99997711181640625, -127.44930267333984375), + osg::Vec3f(0, -153.33331298828125, -117.5942230224609375), + osg::Vec3f(0, -181.6666412353515625, -107.7391510009765625), + osg::Vec3f(0, -209.999969482421875, -97.79712677001953125), + osg::Vec3f(0, -215, -94.753631591796875), + })) << mPath; + } + + TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_when_ground_cross_water_with_max_int_cells_size) + { + std::array heightfieldData {{ + 0, 0, 0, 0, 0, 0, 0, + 0, -100, -100, -100, -100, -100, 0, + 0, -100, -150, -150, -150, -100, 0, + 0, -100, -150, -200, -150, -100, 0, + 0, -100, -150, -150, -150, -100, 0, + 0, -100, -100, -100, -100, -100, 0, + 0, 0, 0, 0, 0, 0, 0, + }}; + btHeightfieldTerrainShape shape(7, 7, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + shape.setLocalScaling(btVector3(128, 128, 1)); + + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addObject(1, shape, btTransform::getIdentity()); + mNavigator->addWater(osg::Vec2i(0, 0), std::numeric_limits::max(), -25, btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + mStart.x() = 0; + mEnd.x() = 0; + + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); + + EXPECT_EQ(mPath, std::deque({ + osg::Vec3f(0, 215, -94.75363922119140625), + osg::Vec3f(0, 186.6666717529296875, -106.0000152587890625), + osg::Vec3f(0, 158.333343505859375, -115.85507965087890625), + osg::Vec3f(0, 130.0000152587890625, -125.71016693115234375), + osg::Vec3f(0, 101.66667938232421875, -135.5652313232421875), + osg::Vec3f(0, 73.333343505859375, -143.3333587646484375), + osg::Vec3f(0, 45.0000152587890625, -143.3333587646484375), + osg::Vec3f(0, 16.6666812896728515625, -143.3333587646484375), + osg::Vec3f(0, -11.66664981842041015625, -143.3333587646484375), + osg::Vec3f(0, -39.999980926513671875, -143.3333587646484375), + osg::Vec3f(0, -68.33331298828125, -143.3333587646484375), + osg::Vec3f(0, -96.66664886474609375, -137.3043670654296875), + osg::Vec3f(0, -124.99997711181640625, -127.44930267333984375), + osg::Vec3f(0, -153.33331298828125, -117.5942230224609375), + osg::Vec3f(0, -181.6666412353515625, -107.7391510009765625), + osg::Vec3f(0, -209.999969482421875, -97.79712677001953125), + osg::Vec3f(0, -215, -94.753631591796875), + })) << mPath; + } } diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index c27f608a9e..ffef6114c7 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -13,6 +13,14 @@ #include +namespace DetourNavigator +{ + static inline bool operator ==(const RecastMesh::Water& lhs, const RecastMesh::Water& rhs) + { + return lhs.mCellSize == rhs.mCellSize && lhs.mTransform == rhs.mTransform; + } +} + namespace { using namespace testing; @@ -391,4 +399,14 @@ namespace EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 3, 4, 5})); EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground, AreaType_null})); } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, add_water_then_get_water_should_return_it) + { + RecastMeshBuilder builder(mSettings, mBounds); + builder.addWater(1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300))); + const auto recastMesh = builder.create(); + EXPECT_EQ(recastMesh->getWater(), std::vector({ + RecastMesh::Water {1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300))} + })); + } } diff --git a/components/detournavigator/areatype.hpp b/components/detournavigator/areatype.hpp index 3efaccc44c..0daa524ca1 100644 --- a/components/detournavigator/areatype.hpp +++ b/components/detournavigator/areatype.hpp @@ -8,6 +8,7 @@ namespace DetourNavigator enum AreaType : unsigned char { AreaType_null = RC_NULL_AREA, + AreaType_water, AreaType_ground = RC_WALKABLE_AREA, }; } diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index c5ff00a265..8a9fbd4ee8 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -32,6 +32,23 @@ namespace DetourNavigator return object; } + bool CachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, + const btTransform& transform) + { + if (!mImpl.addWater(cellPosition, cellSize, transform)) + return false; + mCached.reset(); + return true; + } + + boost::optional CachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) + { + const auto water = mImpl.removeWater(cellPosition); + if (water) + mCached.reset(); + return water; + } + std::shared_ptr CachedRecastMeshManager::getMesh() { if (!mCached) diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index 15168f9ad6..048d002701 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -17,6 +17,10 @@ namespace DetourNavigator bool updateObject(std::size_t id, const btTransform& transform, const AreaType areaType); + bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform); + + boost::optional removeWater(const osg::Vec2i& cellPosition); + boost::optional removeObject(std::size_t id); std::shared_ptr getMesh(); diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index 2fbc07c5fd..c145e828c6 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -3,6 +3,7 @@ #include "dtstatus.hpp" #include "exceptions.hpp" +#include "flags.hpp" #include "settings.hpp" #include "settingsutils.hpp" @@ -10,6 +11,8 @@ #include #include +#include + #include #include @@ -27,6 +30,11 @@ namespace DetourNavigator return osg::Vec3f(values[0], values[1], values[2]); } + inline osg::Vec3f makeOsgVec3f(const btVector3& value) + { + return osg::Vec3f(value.x(), value.y(), value.z()); + } + inline bool inRange(const osg::Vec3f& v1, const osg::Vec3f& v2, const float r, const float h) { const auto d = v2 - v1; @@ -217,6 +225,7 @@ namespace DetourNavigator OPENMW_CHECK_DT_STATUS(navMeshQuery.init(&navMesh, settings.mMaxNavMeshQueryNodes)); dtQueryFilter queryFilter; + queryFilter.setIncludeFlags(Flag_swim | Flag_walk); dtPolyRef startRef = 0; osg::Vec3f startPolygonPosition; diff --git a/components/detournavigator/flags.hpp b/components/detournavigator/flags.hpp index 21a1d7694c..38c0f13f6a 100644 --- a/components/detournavigator/flags.hpp +++ b/components/detournavigator/flags.hpp @@ -9,6 +9,7 @@ namespace DetourNavigator { Flag_none = 0, Flag_walk = 1 << 0, + Flag_swim = 1 << 1, }; } diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index b52984452a..86ce77402d 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -51,6 +51,23 @@ namespace DetourNavigator getTilesPositions(makeOsgVec3f(aabbMin), makeOsgVec3f(aabbMax), settings, std::forward(callback)); } + + template + void getTilesPositions(const int cellSize, const btTransform& transform, + const Settings& settings, Callback&& callback) + { + const auto halfCellSize = cellSize / 2; + auto aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0)); + auto aabbMax = transform(btVector3(halfCellSize, halfCellSize, 0)); + + aabbMin.setX(std::min(aabbMin.x(), aabbMax.x())); + aabbMin.setY(std::min(aabbMin.y(), aabbMax.y())); + + aabbMax.setX(std::max(aabbMin.x(), aabbMax.x())); + aabbMax.setY(std::max(aabbMin.y(), aabbMax.y())); + + getTilesPositions(makeOsgVec3f(aabbMin), makeOsgVec3f(aabbMax), settings, std::forward(callback)); + } } #endif diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 454b88f9ea..71e8881683 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -64,6 +65,41 @@ namespace {} }; + osg::Vec3f makeOsgVec3f(const btVector3& value) + { + return osg::Vec3f(value.x(), value.y(), value.z()); + } + + struct WaterBounds + { + osg::Vec3f mMin; + osg::Vec3f mMax; + }; + + WaterBounds getWaterBounds(const RecastMesh::Water& water, const Settings& settings, + const osg::Vec3f& agentHalfExtents) + { + if (water.mCellSize == std::numeric_limits::max()) + { + const auto transform = getSwimLevelTransform(settings, water.mTransform, agentHalfExtents.z()); + const auto min = toNavMeshCoordinates(settings, makeOsgVec3f(transform(btVector3(-1, -1, 0)))); + const auto max = toNavMeshCoordinates(settings, makeOsgVec3f(transform(btVector3(1, 1, 0)))); + return WaterBounds { + osg::Vec3f(-std::numeric_limits::max(), min.y(), -std::numeric_limits::max()), + osg::Vec3f(std::numeric_limits::max(), max.y(), std::numeric_limits::max()) + }; + } + else + { + const auto transform = getSwimLevelTransform(settings, water.mTransform, agentHalfExtents.z()); + const auto halfCellSize = water.mCellSize / 2.0f; + return WaterBounds { + toNavMeshCoordinates(settings, makeOsgVec3f(transform(btVector3(-halfCellSize, -halfCellSize, 0)))), + toNavMeshCoordinates(settings, makeOsgVec3f(transform(btVector3(halfCellSize, halfCellSize, 0)))) + }; + } + } + NavMeshData makeNavMeshTileData(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, const int tileX, const int tileY, const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax, const Settings& settings) @@ -156,6 +192,56 @@ namespace } } + { + const std::array areas {{AreaType_water, AreaType_water}}; + + for (const auto& water : recastMesh.getWater()) + { + const auto bounds = getWaterBounds(water, settings, agentHalfExtents); + + const osg::Vec2f tileBoundsMin( + std::min(config.bmax[0], std::max(config.bmin[0], bounds.mMin.x())), + std::min(config.bmax[2], std::max(config.bmin[2], bounds.mMin.z())) + ); + const osg::Vec2f tileBoundsMax( + std::min(config.bmax[0], std::max(config.bmin[0], bounds.mMax.x())), + std::min(config.bmax[2], std::max(config.bmin[2], bounds.mMax.z())) + ); + + if (tileBoundsMax == tileBoundsMin) + continue; + + const std::array vertices {{ + osg::Vec3f(tileBoundsMin.x(), bounds.mMin.y(), tileBoundsMin.y()), + osg::Vec3f(tileBoundsMin.x(), bounds.mMin.y(), tileBoundsMax.y()), + osg::Vec3f(tileBoundsMax.x(), bounds.mMin.y(), tileBoundsMax.y()), + osg::Vec3f(tileBoundsMax.x(), bounds.mMin.y(), tileBoundsMin.y()), + }}; + + std::array convertedVertices; + auto convertedVerticesIt = convertedVertices.begin(); + + for (const auto& vertex : vertices) + convertedVerticesIt = std::copy(vertex.ptr(), vertex.ptr() + 3, convertedVerticesIt); + + const std::array indices {{ + 0, 1, 2, + 0, 2, 3, + }}; + + OPENMW_CHECK_DT_RESULT(rcRasterizeTriangles( + &context, + convertedVertices.data(), + static_cast(convertedVertices.size() / 3), + indices.data(), + areas.data(), + static_cast(areas.size()), + solid, + config.walkableClimb + )); + } + } + rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, solid); rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, solid); rcFilterWalkableLowHeightSpans(&context, config.walkableHeight, solid); @@ -187,8 +273,12 @@ namespace } for (int i = 0; i < polyMesh.npolys; ++i) + { if (polyMesh.areas[i] == AreaType_ground) polyMesh.flags[i] = Flag_walk; + else if (polyMesh.areas[i] == AreaType_water) + polyMesh.flags[i] = Flag_swim; + } dtNavMeshCreateParams params; params.verts = polyMesh.verts; @@ -302,8 +392,15 @@ namespace DetourNavigator return removeTile(); } - const auto& boundsMin = recastMesh->getBoundsMin(); - const auto& boundsMax = recastMesh->getBoundsMax(); + auto boundsMin = recastMesh->getBoundsMin(); + auto boundsMax = recastMesh->getBoundsMax(); + + for (const auto& water : recastMesh->getWater()) + { + const auto bounds = getWaterBounds(water, settings, agentHalfExtents); + boundsMin.y() = std::min(boundsMin.y(), bounds.mMin.y()); + boundsMax.y() = std::max(boundsMax.y(), bounds.mMax.y()); + } if (boundsMin == boundsMax) { diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index cc9e6d8559..7a60152d71 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -9,7 +9,6 @@ #include #include -#include class dtNavMesh; diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index f6ead7c7db..359a9a316e 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -71,9 +71,24 @@ namespace DetourNavigator { bool result = mNavMeshManager.removeObject(id); const auto avoid = mAvoidIds.find(id); - if (avoid == mAvoidIds.end()) - return result; - return mNavMeshManager.removeObject(avoid->second) || result; + if (avoid != mAvoidIds.end()) + result = mNavMeshManager.removeObject(avoid->second) || result; + const auto water = mWaterIds.find(id); + if (water != mWaterIds.end()) + result = mNavMeshManager.removeObject(water->second) || result; + return result; + } + + bool Navigator::addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level, + const btTransform& transform) + { + return mNavMeshManager.addWater(cellPosition, cellSize, + btTransform(transform.getBasis(), btVector3(transform.getOrigin().x(), transform.getOrigin().y(), level))); + } + + bool Navigator::removeWater(const osg::Vec2i& cellPosition) + { + return mNavMeshManager.removeWater(cellPosition); } void Navigator::update(const osg::Vec3f& playerPosition) @@ -97,13 +112,23 @@ namespace DetourNavigator return mSettings; } - void Navigator::updateAvoidShapeId(std::size_t id, std::size_t avoidId) + void Navigator::updateAvoidShapeId(const std::size_t id, const std::size_t avoidId) + { + updateId(id, avoidId, mWaterIds); + } + + void Navigator::updateWaterShapeId(const std::size_t id, const std::size_t waterId) + { + updateId(id, waterId, mWaterIds); + } + + void Navigator::updateId(const std::size_t id, const std::size_t updateId, std::unordered_map& ids) { - auto inserted = mAvoidIds.insert(std::make_pair(id, avoidId)); + auto inserted = ids.insert(std::make_pair(id, updateId)); if (!inserted.second) { mNavMeshManager.removeObject(inserted.first->second); - inserted.second = avoidId; + inserted.second = updateId; } } } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 0f82bd7abe..b063975254 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -37,6 +37,11 @@ namespace DetourNavigator bool removeObject(std::size_t id); + bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level, + const btTransform& transform); + + bool removeWater(const osg::Vec2i& cellPosition); + void update(const osg::Vec3f& playerPosition); void wait(); @@ -59,8 +64,11 @@ namespace DetourNavigator NavMeshManager mNavMeshManager; std::map mAgents; std::unordered_map mAvoidIds; + std::unordered_map mWaterIds; - void updateAvoidShapeId(std::size_t id, std::size_t avoidId); + void updateAvoidShapeId(const std::size_t id, const std::size_t avoidId); + void updateWaterShapeId(const std::size_t id, const std::size_t waterId); + void updateId(const std::size_t id, const std::size_t waterId, std::unordered_map& ids); }; } diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 265ef749f5..bfca44f794 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -58,6 +58,23 @@ namespace DetourNavigator return true; } + bool NavMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform) + { + if (!mRecastMeshManager.addWater(cellPosition, cellSize, transform)) + return false; + addChangedTiles(cellSize, transform, ChangeType::add); + return true; + } + + bool NavMeshManager::removeWater(const osg::Vec2i& cellPosition) + { + const auto water = mRecastMeshManager.removeWater(cellPosition); + if (!water) + return false; + addChangedTiles(water->mCellSize, water->mTransform, ChangeType::remove); + return true; + } + void NavMeshManager::addAgent(const osg::Vec3f& agentHalfExtents) { auto cached = mCache.find(agentHalfExtents); @@ -75,9 +92,7 @@ namespace DetourNavigator void NavMeshManager::update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents) { - playerPosition *= mSettings.mRecastScaleFactor; - std::swap(playerPosition.y(), playerPosition.z()); - const auto playerTile = getTilePosition(mSettings, playerPosition); + const auto playerTile = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition)); if (mLastRecastMeshManagerRevision >= mRecastMeshManager.getRevision() && mPlayerTile && *mPlayerTile == playerTile) return; @@ -140,20 +155,36 @@ namespace DetourNavigator } void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, - const ChangeType changeType) - { - getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& v) { - for (const auto& cached : mCache) - if (cached.second) - { - auto& tiles = mChangedTiles[cached.first]; - auto tile = tiles.find(v); - if (tile == tiles.end()) - tiles.insert(std::make_pair(v, changeType)); - else - tile->second = addChangeType(tile->second, changeType); - } - }); + const ChangeType changeType) + { + getTilesPositions(shape, transform, mSettings, + [&] (const TilePosition& v) { addChangedTile(v, changeType); }); + } + + void NavMeshManager::addChangedTiles(const int cellSize, const btTransform& transform, + const ChangeType changeType) + { + if (cellSize == std::numeric_limits::max()) + return; + + getTilesPositions(cellSize, transform, mSettings, + [&] (const TilePosition& v) { addChangedTile(v, changeType); }); + } + + void NavMeshManager::addChangedTile(const TilePosition& tilePosition, const ChangeType changeType) + { + for (const auto& cached : mCache) + { + if (cached.second) + { + auto& tiles = mChangedTiles[cached.first]; + auto tile = tiles.find(tilePosition); + if (tile == tiles.end()) + tiles.insert(std::make_pair(tilePosition, changeType)); + else + tile->second = addChangeType(tile->second, changeType); + } + } } const std::shared_ptr& NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 25ee305c4f..6636402bf0 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -7,8 +7,6 @@ #include -#include - #include #include @@ -33,6 +31,10 @@ namespace DetourNavigator void addAgent(const osg::Vec3f& agentHalfExtents); + bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform); + + bool removeWater(const osg::Vec2i& cellPosition); + void reset(const osg::Vec3f& agentHalfExtents); void update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents); @@ -55,6 +57,10 @@ namespace DetourNavigator void addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType); + void addChangedTiles(const int cellSize, const btTransform& transform, const ChangeType changeType); + + void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType); + const std::shared_ptr& getCached(const osg::Vec3f& agentHalfExtents) const; }; } diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index 5a339d3427..b4897d14aa 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -7,10 +7,11 @@ namespace DetourNavigator { RecastMesh::RecastMesh(std::vector indices, std::vector vertices, - std::vector areaTypes, const Settings& settings) + std::vector areaTypes, std::vector water, const Settings& settings) : mIndices(std::move(indices)) , mVertices(std::move(vertices)) , mAreaTypes(std::move(areaTypes)) + , mWater(std::move(water)) , mChunkyTriMesh(mVertices, mIndices, mAreaTypes, settings.mTrianglesPerChunk) { if (getTrianglesCount() != mAreaTypes.size()) diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 2e31cd27d2..12dc73e488 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -9,6 +9,8 @@ #include +#include + namespace DetourNavigator { struct Settings; @@ -16,8 +18,14 @@ namespace DetourNavigator class RecastMesh { public: + struct Water + { + int mCellSize; + btTransform mTransform; + }; + RecastMesh(std::vector indices, std::vector vertices, - std::vector areaTypes, const Settings& settings); + std::vector areaTypes, std::vector water, const Settings& settings); const std::vector& getIndices() const { @@ -34,6 +42,11 @@ namespace DetourNavigator return mAreaTypes; } + const std::vector& getWater() const + { + return mWater; + } + std::size_t getVerticesCount() const { return mVertices.size() / 3; @@ -63,6 +76,7 @@ namespace DetourNavigator std::vector mIndices; std::vector mVertices; std::vector mAreaTypes; + std::vector mWater; ChunkyTriMesh mChunkyTriMesh; osg::Vec3f mBoundsMin; osg::Vec3f mBoundsMax; diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 23a8bb83e6..3e87aa34b7 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -82,19 +82,16 @@ namespace DetourNavigator void RecastMeshBuilder::addObject(const btBoxShape& shape, const btTransform& transform, const AreaType areaType) { - const auto indexOffset = int(mVertices.size() / 3); + const auto indexOffset = static_cast(mVertices.size() / 3); - for (int vertex = 0; vertex < shape.getNumVertices(); ++vertex) + for (int vertex = 0, count = shape.getNumVertices(); vertex < count; ++vertex) { btVector3 position; shape.getVertex(vertex, position); addVertex(transform(position)); } - for (int vertex = 0; vertex < 12; ++vertex) - mAreaTypes.push_back(areaType); - - static const std::array indices {{ + const std::array indices {{ 0, 2, 3, 3, 1, 0, 0, 4, 6, @@ -111,11 +108,18 @@ namespace DetourNavigator std::transform(indices.begin(), indices.end(), std::back_inserter(mIndices), [&] (int index) { return index + indexOffset; }); + + std::generate_n(std::back_inserter(mAreaTypes), 12, [=] { return areaType; }); + } + + void RecastMeshBuilder::addWater(const int cellSize, const btTransform& transform) + { + mWater.push_back(RecastMesh::Water {cellSize, transform}); } std::shared_ptr RecastMeshBuilder::create() const { - return std::make_shared(mIndices, mVertices, mAreaTypes, mSettings); + return std::make_shared(mIndices, mVertices, mAreaTypes, mWater, mSettings); } void RecastMeshBuilder::reset() @@ -123,6 +127,7 @@ namespace DetourNavigator mIndices.clear(); mVertices.clear(); mAreaTypes.clear(); + mWater.clear(); } void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform, diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index 2210933627..af0565cabd 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -4,14 +4,17 @@ #include "recastmesh.hpp" #include "tilebounds.hpp" +#include + +#include +#include + class btBoxShape; class btCollisionShape; class btCompoundShape; class btConcaveShape; class btHeightfieldTerrainShape; -class btTransform; class btTriangleCallback; -class btVector3; namespace DetourNavigator { @@ -30,6 +33,8 @@ namespace DetourNavigator void addObject(const btBoxShape& shape, const btTransform& transform, const AreaType areaType); + void addWater(const int mCellSize, const btTransform& transform); + std::shared_ptr create() const; void reset(); @@ -40,6 +45,7 @@ namespace DetourNavigator std::vector mIndices; std::vector mVertices; std::vector mAreaTypes; + std::vector mWater; void addObject(const btConcaveShape& shape, const btTransform& transform, btTriangleCallback&& callback); diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index d511891942..cabde7fdf8 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -42,6 +42,26 @@ namespace DetourNavigator return result; } + bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, + const btTransform& transform) + { + if (!mWater.insert(std::make_pair(cellPosition, Water {cellSize, transform})).second) + return false; + mShouldRebuild = true; + return true; + } + + boost::optional RecastMeshManager::removeWater(const osg::Vec2i& cellPosition) + { + const auto water = mWater.find(cellPosition); + if (water == mWater.end()) + return boost::none; + mShouldRebuild = true; + const auto result = water->second; + mWater.erase(water); + return result; + } + std::shared_ptr RecastMeshManager::getMesh() { rebuild(); @@ -58,6 +78,8 @@ namespace DetourNavigator if (!mShouldRebuild) return; mMeshBuilder.reset(); + for (const auto& v : mWater) + mMeshBuilder.addWater(v.second.mCellSize, v.second.mTransform); for (const auto& v : mObjects) mMeshBuilder.addObject(v.second.getShape(), v.second.getTransform(), v.second.getAreaType()); mShouldRebuild = false; diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index 7900592950..c4391b6821 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -6,8 +6,11 @@ #include +#include + #include +#include #include class btCollisionShape; @@ -23,6 +26,12 @@ namespace DetourNavigator class RecastMeshManager { public: + struct Water + { + int mCellSize; + btTransform mTransform; + }; + RecastMeshManager(const Settings& settings, const TileBounds& bounds); bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, @@ -30,6 +39,10 @@ namespace DetourNavigator bool updateObject(std::size_t id, const btTransform& transform, const AreaType areaType); + bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform); + + boost::optional removeWater(const osg::Vec2i& cellPosition); + boost::optional removeObject(std::size_t id); std::shared_ptr getMesh(); @@ -40,6 +53,7 @@ namespace DetourNavigator bool mShouldRebuild; RecastMeshBuilder mMeshBuilder; std::unordered_map mObjects; + std::map mWater; void rebuild(); }; diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 0d34ae7e95..ccfece3dae 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -19,6 +19,7 @@ namespace DetourNavigator float mMaxSimplificationError; float mMaxSlope; float mRecastScaleFactor; + float mSwimHeightScale; int mBorderSize; int mMaxEdgeLen; int mMaxNavMeshQueryNodes; diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index 8127536862..d96ea53cc2 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -6,6 +6,8 @@ #include "tileposition.hpp" #include "tilebounds.hpp" +#include + #include #include #include @@ -68,6 +70,20 @@ namespace DetourNavigator { return settings.mBorderSize * settings.mCellSize; } + + inline float getSwimLevel(const Settings& settings, const float agentHalfExtentsZ) + { + return - settings.mSwimHeightScale * agentHalfExtentsZ; + } + + inline btTransform getSwimLevelTransform(const Settings& settings, const btTransform& transform, + const float agentHalfExtentsZ) + { + return btTransform( + transform.getBasis(), + transform.getOrigin() + btVector3(0, 0, getSwimLevel(settings, agentHalfExtentsZ) - agentHalfExtentsZ) + ); + } } #endif diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index f044b3c43e..164253f856 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -83,6 +83,80 @@ namespace DetourNavigator return result; } + bool TileCachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, + const btTransform& transform) + { + const auto border = getBorderSize(mSettings); + + auto& tilesPositions = mWaterTilesPositions[cellPosition]; + + bool result = false; + + if (cellSize == std::numeric_limits::max()) + { + const std::lock_guard lock(mTilesMutex); + for (auto& tile : mTiles) + { + if (tile.second.addWater(cellPosition, cellSize, transform)) + { + tilesPositions.push_back(tile.first); + result = true; + } + } + } + else + { + getTilesPositions(cellSize, transform, mSettings, [&] (const TilePosition& tilePosition) + { + std::unique_lock lock(mTilesMutex); + auto tile = mTiles.find(tilePosition); + if (tile == mTiles.end()) + { + auto tileBounds = makeTileBounds(mSettings, tilePosition); + tileBounds.mMin -= osg::Vec2f(border, border); + tileBounds.mMax += osg::Vec2f(border, border); + tile = mTiles.insert(std::make_pair(tilePosition, + CachedRecastMeshManager(mSettings, tileBounds))).first; + } + if (tile->second.addWater(cellPosition, cellSize, transform)) + { + lock.unlock(); + tilesPositions.push_back(tilePosition); + result = true; + } + }); + } + + if (result) + ++mRevision; + + return result; + } + + boost::optional TileCachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) + { + const auto object = mWaterTilesPositions.find(cellPosition); + if (object == mWaterTilesPositions.end()) + return boost::none; + boost::optional result; + for (const auto& tilePosition : object->second) + { + std::unique_lock lock(mTilesMutex); + const auto tile = mTiles.find(tilePosition); + if (tile == mTiles.end()) + continue; + const auto tileResult = tile->second.removeWater(cellPosition); + if (tile->second.isEmpty()) + mTiles.erase(tile); + lock.unlock(); + if (tileResult && !result) + result = tileResult; + } + if (result) + ++mRevision; + return result; + } + std::shared_ptr TileCachedRecastMeshManager::getMesh(const TilePosition& tilePosition) { const std::lock_guard lock(mTilesMutex); diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 32b06eadd6..f67d2c3922 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -21,6 +21,10 @@ namespace DetourNavigator boost::optional removeObject(std::size_t id); + bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform); + + boost::optional removeWater(const osg::Vec2i& cellPosition); + std::shared_ptr getMesh(const TilePosition& tilePosition); bool hasTile(const TilePosition& tilePosition); @@ -40,6 +44,7 @@ namespace DetourNavigator std::mutex mTilesMutex; std::map mTiles; std::unordered_map> mObjectsTilesPositions; + std::map> mWaterTilesPositions; std::size_t mRevision = 0; }; } From dc09674362aaac2c447fe8ab30d8baa1e434edcf Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 21 Jul 2018 13:37:02 +0300 Subject: [PATCH 072/133] Add command and settings option to enable actors paths render --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwbase/world.hpp | 4 + apps/openmw/mwmechanics/aipackage.cpp | 7 ++ apps/openmw/mwrender/actorspaths.cpp | 99 +++++++++++++++++++++++ apps/openmw/mwrender/actorspaths.hpp | 51 ++++++++++++ apps/openmw/mwrender/renderingmanager.cpp | 20 ++++- apps/openmw/mwrender/renderingmanager.hpp | 10 +++ apps/openmw/mwrender/rendermode.hpp | 1 + apps/openmw/mwscript/docs/vmformat.txt | 3 +- apps/openmw/mwscript/miscextensions.cpp | 15 ++++ apps/openmw/mwworld/scene.cpp | 3 + apps/openmw/mwworld/worldimp.cpp | 6 ++ apps/openmw/mwworld/worldimp.hpp | 3 + components/CMakeLists.txt | 2 +- components/compiler/extensions0.cpp | 2 +- components/compiler/opcodes.hpp | 1 + components/sceneutil/agentpath.cpp | 71 ++++++++++++++++ components/sceneutil/agentpath.hpp | 26 ++++++ components/sceneutil/detourdebugdraw.cpp | 9 ++- components/sceneutil/detourdebugdraw.hpp | 2 + files/settings-default.cfg | 5 +- 21 files changed, 334 insertions(+), 8 deletions(-) create mode 100644 apps/openmw/mwrender/actorspaths.cpp create mode 100644 apps/openmw/mwrender/actorspaths.hpp create mode 100644 components/sceneutil/agentpath.cpp create mode 100644 components/sceneutil/agentpath.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index bac9634db1..ad8853753e 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -21,7 +21,7 @@ add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation - renderbin actoranimation landmanager navmesh + renderbin actoranimation landmanager navmesh actorspaths ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 18f997324f..db6bda1419 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -597,6 +598,9 @@ namespace MWBase virtual void preloadEffects(const ESM::EffectList* effectList) = 0; virtual DetourNavigator::Navigator* getNavigator() const = 0; + + virtual void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, + const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const = 0; }; } diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 0b06d8c1dc..16af9c8e7e 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -102,6 +103,12 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const ESM::Position pos = actor.getRefData().getPosition(); //position of the actor + { + const auto halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); + MWBase::Environment::get().getWorld()->updateActorPath(actor, mPathFinder.getPath(), halfExtents, + pos.asVec3(), dest); + } + /// Stops the actor when it gets too close to a unloaded cell //... At current time, this test is unnecessary. AI shuts down when actor is more than 7168 //... units from player, and exterior cells are 8192 units long and wide. diff --git a/apps/openmw/mwrender/actorspaths.cpp b/apps/openmw/mwrender/actorspaths.cpp new file mode 100644 index 0000000000..35b2553555 --- /dev/null +++ b/apps/openmw/mwrender/actorspaths.cpp @@ -0,0 +1,99 @@ +#include "actorspaths.hpp" +#include "vismask.hpp" + +#include + +#include + +namespace MWRender +{ + ActorsPaths::ActorsPaths(const osg::ref_ptr& root, bool enabled) + : mRootNode(root) + , mEnabled(enabled) + { + } + + ActorsPaths::~ActorsPaths() + { + if (mEnabled) + disable(); + } + + bool ActorsPaths::toggle() + { + if (mEnabled) + disable(); + else + enable(); + + return mEnabled; + } + + void ActorsPaths::update(const MWWorld::ConstPtr& actor, const std::deque& path, + const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, + const DetourNavigator::Settings& settings) + { + if (!mEnabled) + return; + + const auto group = mGroups.find(actor); + if (group != mGroups.end()) + mRootNode->removeChild(group->second); + + const auto newGroup = SceneUtil::createAgentPathGroup(path, halfExtents, start, end, settings); + if (newGroup) + { + newGroup->setNodeMask(Mask_Debug); + mRootNode->addChild(newGroup); + mGroups[actor] = newGroup; + } + } + + void ActorsPaths::remove(const MWWorld::ConstPtr& actor) + { + const auto group = mGroups.find(actor); + if (group != mGroups.end()) + { + mRootNode->removeChild(group->second); + mGroups.erase(group); + } + } + + void ActorsPaths::removeCell(const MWWorld::CellStore* const store) + { + for (auto it = mGroups.begin(); it != mGroups.end(); ) + { + if (it->first.getCell() == store) + { + mRootNode->removeChild(it->second); + it = mGroups.erase(it); + } + else + ++it; + } + } + + void ActorsPaths::updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated) + { + const auto it = mGroups.find(old); + if (it == mGroups.end()) + return; + auto group = std::move(it->second); + mGroups.erase(it); + mGroups.insert(std::make_pair(updated, std::move(group))); + } + + void ActorsPaths::enable() + { + std::for_each(mGroups.begin(), mGroups.end(), + [&] (const Groups::value_type& v) { mRootNode->addChild(v.second); }); + mEnabled = true; + } + + void ActorsPaths::disable() + { + std::for_each(mGroups.begin(), mGroups.end(), + [&] (const Groups::value_type& v) { mRootNode->removeChild(v.second); }); + mEnabled = false; + } +} diff --git a/apps/openmw/mwrender/actorspaths.hpp b/apps/openmw/mwrender/actorspaths.hpp new file mode 100644 index 0000000000..1f61834d46 --- /dev/null +++ b/apps/openmw/mwrender/actorspaths.hpp @@ -0,0 +1,51 @@ +#ifndef OPENMW_MWRENDER_AGENTSPATHS_H +#define OPENMW_MWRENDER_AGENTSPATHS_H + +#include + +#include + +#include + +#include +#include + +namespace osg +{ + class Group; +} + +namespace MWRender +{ + class ActorsPaths + { + public: + ActorsPaths(const osg::ref_ptr& root, bool enabled); + ~ActorsPaths(); + + bool toggle(); + + void update(const MWWorld::ConstPtr& actor, const std::deque& path, + const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, + const DetourNavigator::Settings& settings); + + void remove(const MWWorld::ConstPtr& actor); + + void removeCell(const MWWorld::CellStore* const store); + + void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated); + + void enable(); + + void disable(); + + private: + using Groups = std::map>; + + osg::ref_ptr mRootNode; + Groups mGroups; + bool mEnabled; + }; +} + +#endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index fbe8ba3459..5c0a3347cb 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -66,6 +66,7 @@ #include "terrainstorage.hpp" #include "util.hpp" #include "navmesh.hpp" +#include "actorspaths.hpp" namespace { @@ -234,7 +235,8 @@ namespace MWRender mRootNode->addChild(mSceneRoot); - mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable render", "Navigator"))); + mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable nav mesh render", "Navigator"))); + mActorsPaths.reset(new ActorsPaths(mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator"))); mPathgrid.reset(new Pathgrid(mRootNode)); mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get())); @@ -472,6 +474,7 @@ namespace MWRender void RenderingManager::removeCell(const MWWorld::CellStore *store) { mPathgrid->removeCell(store); + mActorsPaths->removeCell(store); mObjects->removeCell(store); if (store->getCell()->isExterior()) @@ -527,6 +530,10 @@ namespace MWRender { return mNavMesh->toggle(); } + else if (mode == Render_ActorsPaths) + { + return mActorsPaths->toggle(); + } return false; } @@ -666,6 +673,7 @@ namespace MWRender void RenderingManager::removeObject(const MWWorld::Ptr &ptr) { + mActorsPaths->remove(ptr); mObjects->removeObject(ptr); mWater->removeEmitter(ptr); } @@ -1049,6 +1057,7 @@ namespace MWRender void RenderingManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) { mObjects->updatePtr(old, updated); + mActorsPaths->updatePtr(old, updated); } void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) @@ -1369,5 +1378,14 @@ namespace MWRender return mTerrainStorage->getLandManager(); } + void RenderingManager::updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, + const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const + { + mActorsPaths->update(actor, path, halfExtents, start, end, mNavigator.getSettings()); + } + void RenderingManager::removeActorPath(const MWWorld::ConstPtr& actor) const + { + mActorsPaths->remove(actor); + } } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index a8eae52862..76ec674453 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -12,6 +12,8 @@ #include "renderinginterface.hpp" #include "rendermode.hpp" +#include + namespace osg { class Group; @@ -58,6 +60,7 @@ namespace SceneUtil namespace DetourNavigator { class Navigator; + struct Settings; } namespace MWRender @@ -74,6 +77,7 @@ namespace MWRender class TerrainStorage; class LandManager; class NavMesh; + class ActorsPaths; class RenderingManager : public MWRender::RenderingInterface { @@ -219,6 +223,11 @@ namespace MWRender bool toggleBorders(); + void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, + const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const; + + void removeActorPath(const MWWorld::ConstPtr& actor) const; + private: void updateProjectionMatrix(); void updateTextureFiltering(); @@ -245,6 +254,7 @@ namespace MWRender DetourNavigator::Navigator& mNavigator; std::unique_ptr mNavMesh; + std::unique_ptr mActorsPaths; std::unique_ptr mPathgrid; std::unique_ptr mObjects; std::unique_ptr mWater; diff --git a/apps/openmw/mwrender/rendermode.hpp b/apps/openmw/mwrender/rendermode.hpp index 9f0c5a7cd8..077710f4f4 100644 --- a/apps/openmw/mwrender/rendermode.hpp +++ b/apps/openmw/mwrender/rendermode.hpp @@ -12,6 +12,7 @@ namespace MWRender Render_Water, Render_Scene, Render_NavMesh, + Render_ActorsPaths, }; } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 2ce22633c1..bc7d93a136 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -456,5 +456,6 @@ op 0x2000305: Show, explicit op 0x2000306: OnActivate, explicit op 0x2000307: ToggleBorders, tb op 0x2000308: ToggleNavMesh +op 0x2000309: ToggleActorsPaths -opcodes 0x2000309-0x3ffffff unused +opcodes 0x200030a-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 8ccb619d01..8507b7628e 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1331,6 +1331,20 @@ namespace MWScript } }; + class OpToggleActorsPaths : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + bool enabled = + MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_ActorsPaths); + + runtime.getContext().report (enabled ? + "Agents Paths Rendering -> On" : "Agents Paths Rendering -> Off"); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox); @@ -1432,6 +1446,7 @@ namespace MWScript interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraphExplicit, new OpShowSceneGraph); interpreter.installSegment5 (Compiler::Misc::opcodeToggleBorders, new OpToggleBorders); interpreter.installSegment5 (Compiler::Misc::opcodeToggleNavMesh, new OpToggleNavMesh); + interpreter.installSegment5 (Compiler::Misc::opcodeToggleActorsPaths, new OpToggleActorsPaths); } } } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index ca19383d15..2f6c0330f3 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -278,7 +278,10 @@ namespace MWWorld if (const auto object = mPhysics->getObject(ptr)) navigator->removeObject(reinterpret_cast(object)); else if (const auto actor = mPhysics->getActor(ptr)) + { navigator->removeAgent(playerHalfExtents); + mRendering.removeActorPath(ptr); + } mPhysics->remove(ptr); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bb8bd9615a..2929a49747 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3745,4 +3745,10 @@ namespace MWWorld return mNavigator.get(); } + void World::updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, + const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const + { + mRendering->updateActorPath(actor, path, halfExtents, start, end); + } + } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4e3deffbe7..b492679625 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -691,6 +691,9 @@ namespace MWWorld void preloadEffects(const ESM::EffectList* effectList) override; DetourNavigator::Navigator* getNavigator() const override; + + void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, + const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const override; }; } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7ca7ce6734..cf45d3212b 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -51,7 +51,7 @@ add_component_dir (shader add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer - actorutil detourdebugdraw navmesh + actorutil detourdebugdraw navmesh agentpath ) add_component_dir (nif diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index e39b97e3fc..241abfaa40 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -321,7 +321,7 @@ namespace Compiler extensions.registerInstruction ("tb", "", opcodeToggleBorders); extensions.registerInstruction ("toggleborders", "", opcodeToggleBorders); extensions.registerInstruction ("togglenavmesh", "", opcodeToggleNavMesh); - extensions.registerInstruction ("tnm", "", opcodeToggleNavMesh); + extensions.registerInstruction ("toggleactorspaths", "", opcodeToggleActorsPaths); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 75ef738376..c591115ec2 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -297,6 +297,7 @@ namespace Compiler const int opcodeShowSceneGraphExplicit = 0x20030; const int opcodeToggleBorders = 0x2000307; const int opcodeToggleNavMesh = 0x2000308; + const int opcodeToggleActorsPaths = 0x2000309; } namespace Sky diff --git a/components/sceneutil/agentpath.cpp b/components/sceneutil/agentpath.cpp new file mode 100644 index 0000000000..aaee4dd1ef --- /dev/null +++ b/components/sceneutil/agentpath.cpp @@ -0,0 +1,71 @@ +#include "agentpath.hpp" +#include "detourdebugdraw.hpp" + +#include +#include + +#include + +namespace +{ + void drawAgent(duDebugDraw& debugDraw, const osg::Vec3f& pos, const float radius, const float height, + const float climb, const unsigned color) + { + debugDraw.depthMask(false); + + duDebugDrawCylinderWire(&debugDraw, pos.x() - radius, pos.z() + 0.02f, pos.y() - radius, pos.x() + radius, + pos.z() + height, pos.y() + radius, color, radius * 0.2f); + + duDebugDrawCircle(&debugDraw, pos.x(), pos.z() + climb, pos.y(), radius, duRGBA(0, 0 , 0, 64), radius * 0.1f); + + const auto colb = duRGBA(0, 0, 0, 196); + debugDraw.begin(DU_DRAW_LINES); + debugDraw.vertex(pos.x(), pos.z() - climb, pos.y(), colb); + debugDraw.vertex(pos.x(), pos.z() + climb, pos.y(), colb); + debugDraw.vertex(pos.x() - radius / 2, pos.z() + 0.02f, pos.y(), colb); + debugDraw.vertex(pos.x() + radius / 2, pos.z() + 0.02f, pos.y(), colb); + debugDraw.vertex(pos.x(), pos.z() + 0.02f, pos.y() - radius / 2, colb); + debugDraw.vertex(pos.x(), pos.z() + 0.02f, pos.y() + radius / 2, colb); + debugDraw.end(); + + debugDraw.depthMask(true); + } +} + +namespace SceneUtil +{ + osg::ref_ptr createAgentPathGroup(const std::deque& path, + const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, + const DetourNavigator::Settings& settings) + { + using namespace DetourNavigator; + + const osg::ref_ptr group(new osg::Group); + + DebugDraw debugDraw(*group, osg::Vec3f(0, 0, 0), 1); + + const auto agentRadius = halfExtents.x(); + const auto agentHeight = 2.0f * halfExtents.z(); + const auto agentClimb = settings.mMaxClimb; + const auto startColor = duRGBA(128, 25, 0, 192); + const auto endColor = duRGBA(51, 102, 0, 129); + + drawAgent(debugDraw, start, agentRadius, agentHeight, agentClimb, startColor); + drawAgent(debugDraw, end, agentRadius, agentHeight, agentClimb, endColor); + + const auto pathColor = duRGBA(0, 0, 0, 220); + + debugDraw.depthMask(false); + + debugDraw.begin(osg::PrimitiveSet::LINE_STRIP, agentRadius * 0.5f); + debugDraw.vertex(osg::Vec3f(start.x(), start.z() + agentClimb, start.y()).ptr(), startColor); + std::for_each(path.begin(), path.end(), + [&] (const osg::Vec3f& v) { debugDraw.vertex(osg::Vec3f(v.x(), v.z() + agentClimb, v.y()).ptr(), pathColor); }); + debugDraw.vertex(osg::Vec3f(end.x(), end.z() + agentClimb, end.y()).ptr(), endColor); + debugDraw.end(); + + debugDraw.depthMask(true); + + return group; + } +} diff --git a/components/sceneutil/agentpath.hpp b/components/sceneutil/agentpath.hpp new file mode 100644 index 0000000000..a8965d852e --- /dev/null +++ b/components/sceneutil/agentpath.hpp @@ -0,0 +1,26 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_AGENTPATH_H +#define OPENMW_COMPONENTS_SCENEUTIL_AGENTPATH_H + +#include + +#include + +namespace osg +{ + class Group; + class Vec3f; +} + +namespace DetourNavigator +{ + struct Settings; +} + +namespace SceneUtil +{ + osg::ref_ptr createAgentPathGroup(const std::deque& path, + const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, + const DetourNavigator::Settings& settings); +} + +#endif diff --git a/components/sceneutil/detourdebugdraw.cpp b/components/sceneutil/detourdebugdraw.cpp index f6c438e791..a0ae67dc29 100644 --- a/components/sceneutil/detourdebugdraw.cpp +++ b/components/sceneutil/detourdebugdraw.cpp @@ -56,14 +56,19 @@ namespace SceneUtil throw std::logic_error("DebugDraw does not support textures (at " __FILE__ ":" OPENMW_LINE_STRING ")"); } - void DebugDraw::begin(duDebugDrawPrimitives prim, float size) + void DebugDraw::begin(osg::PrimitiveSet::Mode mode, float size) { - mMode = toOsgPrimitiveSetMode(prim); + mMode = mode; mVertices = new osg::Vec3Array; mColors = new osg::Vec4Array; mSize = size * mRecastInvertedScaleFactor; } + void DebugDraw::begin(duDebugDrawPrimitives prim, float size) + { + begin(toOsgPrimitiveSetMode(prim), size); + } + void DebugDraw::vertex(const float* pos, unsigned color) { vertex(pos[0], pos[1], pos[2], color); diff --git a/components/sceneutil/detourdebugdraw.hpp b/components/sceneutil/detourdebugdraw.hpp index 87f501e2b1..bb170e7ba2 100644 --- a/components/sceneutil/detourdebugdraw.hpp +++ b/components/sceneutil/detourdebugdraw.hpp @@ -18,6 +18,8 @@ namespace SceneUtil void texture(bool state) override; + void begin(osg::PrimitiveSet::Mode mode, float size); + void begin(duDebugDrawPrimitives prim, float size) override; void vertex(const float* pos, unsigned int color) override; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 1b346dad61..c046a19b32 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -619,4 +619,7 @@ recast mesh path prefix = nav mesh path prefix = # Render nav mesh (true, false) -enable render = false +enable nav mesh render = false + +# Render agents paths (true, false) +enable agents paths render = false From d02beae5a8522b0c0c7ae6f90877d7d016d1d556 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 21 Jul 2018 14:16:19 +0300 Subject: [PATCH 073/133] Find path for actors according to their abilities to swim and walk --- apps/openmw/mwmechanics/aipackage.cpp | 16 +++- apps/openmw/mwmechanics/aipackage.hpp | 2 + apps/openmw/mwmechanics/pathfinding.cpp | 4 +- apps/openmw/mwmechanics/pathfinding.hpp | 3 +- .../detournavigator/navigator.cpp | 81 +++++++++++++++---- components/detournavigator/findsmoothpath.hpp | 5 +- components/detournavigator/navigator.hpp | 6 +- 7 files changed, 92 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 16af9c8e7e..a146172494 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -141,7 +141,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& || world->isFlying(actor); // Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions. - if (actorCanMoveByZ || getTypeId() != TypeIdWander) + if (actorCanMoveByZ) mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first if (!mIsShortcutting) @@ -400,3 +400,17 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act // no points of actor's circle should be farther from the center than destination point return (radius <= distToDest); } + +DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::Ptr& actor) const +{ + const auto& actorClass = actor.getClass(); + DetourNavigator::Flags result = DetourNavigator::Flag_none; + + if (actorClass.isPureWaterCreature(actor) || (getTypeId() != TypeIdWander && actorClass.canSwim(actor))) + result |= DetourNavigator::Flag_swim; + + if (actorClass.canWalk(actor)) + result |= DetourNavigator::Flag_walk; + + return result; +} diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 59bd6689d5..20f47480e7 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -132,6 +132,8 @@ namespace MWMechanics const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell); + DetourNavigator::Flags getNavigatorFlags(const MWWorld::Ptr& actor) const; + // TODO: all this does not belong here, move into temporary storage PathFinder mPathFinder; ObstacleCheck mObstacleCheck; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 5a34bb2300..2611cb5edc 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -290,14 +290,14 @@ namespace MWMechanics } void PathFinder::buildPathByNavigator(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, - const osg::Vec3f& halfExtents) + const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags) { try { mPath.clear(); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); - navigator->findPath(halfExtents, startPoint, endPoint, std::back_inserter(mPath)); + navigator->findPath(halfExtents, startPoint, endPoint, flags, std::back_inserter(mPath)); mConstructed = true; } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index f488e3b147..aaae2e88ea 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -176,7 +177,7 @@ namespace MWMechanics } void buildPathByNavigator(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, - const osg::Vec3f& halfExtents); + const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags); private: bool mConstructed; diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 84aa344744..52e350572d 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -64,20 +64,20 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_throw_exception) { - EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut), InvalidArgument); + EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut), InvalidArgument); } TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception) { mNavigator->addAgent(mAgentHalfExtents); - EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut), NavigatorException); + EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut), NavigatorException); } TEST_F(DetourNavigatorNavigatorTest, find_path_for_removed_agent_should_throw_exception) { mNavigator->addAgent(mAgentHalfExtents); mNavigator->removeAgent(mAgentHalfExtents); - EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut), InvalidArgument); + EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut), InvalidArgument); } TEST_F(DetourNavigatorNavigatorTest, add_agent_should_count_each_agent) @@ -85,7 +85,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents); mNavigator->removeAgent(mAgentHalfExtents); - EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut), NavigatorException); + EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut), NavigatorException); } TEST_F(DetourNavigatorNavigatorTest, update_then_find_path_should_return_path) @@ -105,7 +105,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.85963428020477294921875), @@ -155,7 +155,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, std::back_inserter(mPath)); + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, std::back_inserter(mPath)); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.85963428020477294921875), @@ -188,7 +188,7 @@ namespace mNavigator->wait(); mPath.clear(); - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, std::back_inserter(mPath)); + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, std::back_inserter(mPath)); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.87827122211456298828125), @@ -239,7 +239,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, std::back_inserter(mPath)); + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, std::back_inserter(mPath)); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.87827122211456298828125), @@ -274,7 +274,7 @@ namespace mNavigator->wait(); mPath.clear(); - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.85963428020477294921875), @@ -331,7 +331,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.96328866481781005859375), @@ -387,7 +387,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.9393787384033203125), @@ -417,7 +417,7 @@ namespace })) << mPath; } - TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_ground_lower_than_water) + TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_ground_lower_than_water_with_only_swim_flag) { std::array heightfieldData {{ -50, -50, -50, -50, 0, @@ -440,7 +440,7 @@ namespace mEnd.x() = 0; mEnd.z() = 300; - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_swim, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(0, 215, 185.33331298828125), @@ -463,7 +463,7 @@ namespace })) << mPath; } - TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_when_ground_cross_water) + TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_when_ground_cross_water_with_swim_and_walk_flags) { std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, 0, @@ -486,7 +486,7 @@ namespace mStart.x() = 0; mEnd.x() = 0; - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_swim | Flag_walk, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(0, 215, -94.75363922119140625), @@ -509,7 +509,7 @@ namespace })) << mPath; } - TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_when_ground_cross_water_with_max_int_cells_size) + TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_when_ground_cross_water_with_max_int_cells_size_and_swim_and_walk_flags) { std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, 0, @@ -532,7 +532,7 @@ namespace mStart.x() = 0; mEnd.x() = 0; - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, mOut); + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_swim | Flag_walk, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(0, 215, -94.75363922119140625), @@ -554,4 +554,51 @@ namespace osg::Vec3f(0, -215, -94.753631591796875), })) << mPath; } + + TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_ground_when_ground_cross_water_with_only_walk_flag) + { + std::array heightfieldData {{ + 0, 0, 0, 0, 0, 0, 0, + 0, -100, -100, -100, -100, -100, 0, + 0, -100, -150, -150, -150, -100, 0, + 0, -100, -150, -200, -150, -100, 0, + 0, -100, -150, -150, -150, -100, 0, + 0, -100, -100, -100, -100, -100, 0, + 0, 0, 0, 0, 0, 0, 0, + }}; + btHeightfieldTerrainShape shape(7, 7, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + shape.setLocalScaling(btVector3(128, 128, 1)); + + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, -25, btTransform::getIdentity()); + mNavigator->addObject(1, shape, btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + mStart.x() = 0; + mEnd.x() = 0; + + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut); + + EXPECT_EQ(mPath, std::deque({ + osg::Vec3f(0, 215, -94.75363922119140625), + osg::Vec3f(9.8083515167236328125, 188.4185333251953125, -105.19994354248046875), + osg::Vec3f(19.6167049407958984375, 161.837066650390625, -114.25496673583984375), + osg::Vec3f(29.42505645751953125, 135.255615234375, -123.309967041015625), + osg::Vec3f(39.23340606689453125, 108.674163818359375, -132.3649749755859375), + osg::Vec3f(49.04175567626953125, 82.09270477294921875, -137.2874755859375), + osg::Vec3f(58.8501129150390625, 55.5112457275390625, -139.2451171875), + osg::Vec3f(68.6584625244140625, 28.9297885894775390625, -141.2027740478515625), + osg::Vec3f(78.4668121337890625, 2.3483295440673828125, -143.1604156494140625), + osg::Vec3f(88.27516937255859375, -24.233127593994140625, -141.3894805908203125), + osg::Vec3f(83.73651885986328125, -52.2005767822265625, -142.3761444091796875), + osg::Vec3f(79.19786834716796875, -80.16802978515625, -143.114837646484375), + osg::Vec3f(64.8477935791015625, -104.598602294921875, -137.840911865234375), + osg::Vec3f(50.497714996337890625, -129.0291748046875, -131.45831298828125), + osg::Vec3f(36.147632598876953125, -153.459747314453125, -121.42321014404296875), + osg::Vec3f(21.7975559234619140625, -177.8903350830078125, -111.38809967041015625), + osg::Vec3f(7.44747829437255859375, -202.3209075927734375, -101.1938323974609375), + osg::Vec3f(0, -215, -94.753631591796875), + })) << mPath; + } } diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index c145e828c6..08bfa14da5 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -219,13 +219,14 @@ namespace DetourNavigator template OutputIterator findSmoothPath(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, - const osg::Vec3f& start, const osg::Vec3f& end, const Settings& settings, OutputIterator out) + const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, + const Settings& settings, OutputIterator out) { dtNavMeshQuery navMeshQuery; OPENMW_CHECK_DT_STATUS(navMeshQuery.init(&navMesh, settings.mMaxNavMeshQueryNodes)); dtQueryFilter queryFilter; - queryFilter.setIncludeFlags(Flag_swim | Flag_walk); + queryFilter.setIncludeFlags(includeFlags); dtPolyRef startRef = 0; osg::Vec3f startPolygonPosition; diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index b063975254..3e60d72e02 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H #include "findsmoothpath.hpp" +#include "flags.hpp" #include "navmeshmanager.hpp" #include "settings.hpp" #include "settingsutils.hpp" @@ -48,11 +49,12 @@ namespace DetourNavigator template OutputIterator findPath(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, - const osg::Vec3f& end, OutputIterator out) const + const osg::Vec3f& end, const Flags includeFlags, OutputIterator out) const { const auto navMesh = mNavMeshManager.getNavMesh(agentHalfExtents); return findSmoothPath(*navMesh.lock(), toNavMeshCoordinates(mSettings, agentHalfExtents), - toNavMeshCoordinates(mSettings, start), toNavMeshCoordinates(mSettings, end), mSettings, out); + toNavMeshCoordinates(mSettings, start), toNavMeshCoordinates(mSettings, end), includeFlags, + mSettings, out); } std::map> getNavMeshes() const; From 661da42bd299653d80f6155b62212af5bd7b37c3 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 23 Aug 2018 00:20:25 +0300 Subject: [PATCH 074/133] Build path by navigator --- apps/openmw/mwmechanics/aipackage.cpp | 4 +- apps/openmw/mwmechanics/aiwander.cpp | 27 +++++++-- apps/openmw/mwmechanics/aiwander.hpp | 1 + apps/openmw/mwmechanics/pathfinding.cpp | 75 +++++++++++-------------- apps/openmw/mwmechanics/pathfinding.hpp | 27 +++++---- 5 files changed, 72 insertions(+), 62 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index a146172494..5130488e3e 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -148,7 +148,9 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& { if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path { - mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell())); + const auto playerHalfExtents = world->getHalfExtents(world->getPlayerPtr()); + mPathFinder.buildPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()), + playerHalfExtents, getNavigatorFlags(actor)); mRotateOnTheRunChecks = 3; // give priority to go directly on target if there is minimal opportunity diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 4a452e1f70..e0822e8063 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -50,7 +50,8 @@ namespace MWMechanics AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), - mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)) + mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), + mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)), mUsePathgrid(false) { mIdle.resize(8, 0); init(); @@ -151,8 +152,18 @@ namespace MWMechanics // rebuild a path to it if (!mPathFinder.isPathConstructed() && mHasDestination) { - mPathFinder.buildSyncedPath(pos.asVec3(), mDestination, actor.getCell(), - getPathGridGraph(actor.getCell())); + if (mUsePathgrid) + { + mPathFinder.buildPathByPathgrid(pos.asVec3(), mDestination, actor.getCell(), + getPathGridGraph(actor.getCell())); + } + else + { + const auto world = MWBase::Environment::get().getWorld(); + const auto playerHalfExtents = world->getHalfExtents(world->getPlayerPtr()); + mPathFinder.buildPath(pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(actor.getCell()), + playerHalfExtents, getNavigatorFlags(actor)); + } if (mPathFinder.isPathConstructed()) storage.setState(AiWanderStorage::Wander_Walking); @@ -310,14 +321,17 @@ namespace MWMechanics if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) || (isWaterCreature && !destinationThroughGround(currentPosition, mDestination))) { - mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), - getPathGridGraph(actor.getCell())); + const auto world = MWBase::Environment::get().getWorld();; + const auto playerHalfExtents = world->getHalfExtents(world->getPlayerPtr()); + mPathFinder.buildPath(currentPosition, destinationPosition, actor.getCell(), + getPathGridGraph(actor.getCell()), playerHalfExtents, getNavigatorFlags(actor)); mPathFinder.addPointToPath(destinationPosition); if (mPathFinder.isPathConstructed()) { storage.setState(AiWanderStorage::Wander_Walking, true); mHasDestination = true; + mUsePathgrid = false; } return; } @@ -595,12 +609,13 @@ namespace MWMechanics // don't take shortcuts for wandering const auto destVec3f = PathFinder::makeOsgVec3(dest); - mPathFinder.buildSyncedPath(start, destVec3f, actor.getCell(), getPathGridGraph(actor.getCell())); + mPathFinder.buildPathByPathgrid(start, destVec3f, actor.getCell(), getPathGridGraph(actor.getCell())); if (mPathFinder.isPathConstructed()) { mDestination = destVec3f; 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); diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 8df863590d..bba6f71135 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -164,6 +164,7 @@ namespace MWMechanics bool mHasDestination; osg::Vec3f mDestination; + bool mUsePathgrid; void getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points); diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 2611cb5edc..ee091de4c3 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -124,13 +124,9 @@ namespace MWMechanics * j = @.x in local coordinates (i.e. within the cell) * k = @.x in world coordinates */ - void PathFinder::buildPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, - const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph) + void PathFinder::buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const PathgridGraph& pathgridGraph, std::back_insert_iterator> out) { - mConstructed = true; - mPath.clear(); - mCell = cell; - const auto pathgrid = pathgridGraph.getPathgrid(); // Refer to AiWander reseach topic on openmw forums for some background. @@ -138,7 +134,7 @@ namespace MWMechanics // physics take care of any blockages. if(!pathgrid || pathgrid->mPoints.empty()) { - mPath.push_back(endPoint); + *out++ = endPoint; return; } @@ -165,7 +161,7 @@ namespace MWMechanics float startTo1stNodeLength2 = distanceSquared(pathgrid->mPoints[startNode], startPointInLocalCoords); if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2)) { - mPath.push_back(endPoint); + *out++ = endPoint; return; } @@ -178,7 +174,7 @@ namespace MWMechanics { ESM::Pathgrid::Point temp(pathgrid->mPoints[startNode]); converter.toWorld(temp); - mPath.push_back(makeOsgVec3(temp)); + *out++ = makeOsgVec3(temp); } else { @@ -207,7 +203,7 @@ namespace MWMechanics } // convert supplied path to world coordinates - std::transform(path.begin(), path.end(), std::back_inserter(mPath), + std::transform(path.begin(), path.end(), out, [&] (ESM::Pathgrid::Point& point) { converter.toWorld(point); @@ -228,7 +224,7 @@ namespace MWMechanics // // The AI routines will have to deal with such situations. if(endNode.second) - mPath.push_back(endPoint); + *out++ = endPoint; } float PathFinder::getZAngleToNext(float x, float y) const @@ -263,43 +259,40 @@ namespace MWMechanics mPath.pop_front(); } - // see header for the rationale - void PathFinder::buildSyncedPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, - const MWWorld::CellStore* cell, const MWMechanics::PathgridGraph& pathgridGraph) + void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph) { - if (mPath.size() < 2) - { - // if path has one point, then it's the destination. - // don't need to worry about bad path for this case - buildPath(startPoint, endPoint, cell, pathgridGraph); - } - else - { - const auto oldStart = getPath().front(); - buildPath(startPoint, endPoint, cell, pathgridGraph); - if (mPath.size() >= 2) - { - // if 2nd waypoint of new path == 1st waypoint of old, - // delete 1st waypoint of new path. - if (mPath[1] == oldStart) - { - mPath.pop_front(); - } - } - } + mPath.clear(); + mCell = cell; + + buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath)); + + mConstructed = true; } - void PathFinder::buildPathByNavigator(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, - const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags) + void PathFinder::buildPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, + const DetourNavigator::Flags flags) + { + mPath.clear(); + mCell = cell; + + buildPathByNavigatorImpl(startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath)); + + if (mPath.empty()) + buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath)); + + mConstructed = true; + } + + void PathFinder::buildPathByNavigatorImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, + std::back_insert_iterator> out) { try { - mPath.clear(); - const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); - navigator->findPath(halfExtents, startPoint, endPoint, flags, std::back_inserter(mPath)); - - mConstructed = true; + navigator->findPath(halfExtents, startPoint, endPoint, flags, out); } catch (const DetourNavigator::NavigatorException& exception) { diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index aaae2e88ea..543305ff62 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -70,8 +71,12 @@ namespace MWMechanics mCell = nullptr; } + void buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); + void buildPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, - const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); + const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, + const DetourNavigator::Flags flags); /// Remove front point if exist and within tolerance void update(const osg::Vec3f& position, const float tolerance = DEFAULT_TOLERANCE); @@ -106,16 +111,6 @@ namespace MWMechanics return mCell; } - /** Synchronize new path with old one to avoid visiting 1 waypoint 2 times - @note - BuildPath() takes closest PathGrid point to NPC as first point of path. - This is undesirable if NPC has just passed a Pathgrid point, as this - makes the 2nd point of the new path == the 1st point of old path. - Which results in NPC "running in a circle" back to the just passed waypoint. - */ - void buildSyncedPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, - const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); - void addPointToPath(const osg::Vec3f& point) { mConstructed = true; @@ -176,14 +171,18 @@ namespace MWMechanics return closestIndex; } - void buildPathByNavigator(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, - const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags); - private: bool mConstructed; std::deque mPath; const MWWorld::CellStore* mCell; + + void buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const PathgridGraph& pathgridGraph, std::back_insert_iterator> out); + + void buildPathByNavigatorImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, + std::back_insert_iterator> out); }; } From f8dbd5902f9e73d8bf2efc74208bc24455ff9185 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 25 Aug 2018 23:51:54 +0300 Subject: [PATCH 075/133] Update doors objects in navigator --- apps/openmw/mwworld/worldimp.cpp | 32 ++++++++++++++++++++++++-------- apps/openmw/mwworld/worldimp.hpp | 9 +++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 2929a49747..b467a687aa 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1543,21 +1543,34 @@ namespace MWWorld } if(player != results.end()) moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false); + } + + void World::updateNavigator() + { + bool updated = false; - bool navigatorObjectsUpdated = false; mPhysics->forEachAnimatedObject([&] (const MWPhysics::Object* object) { - const DetourNavigator::ObjectShapes shapes { - *object->getShapeInstance()->getCollisionShape(), - object->getShapeInstance()->getAvoidCollisionShape() - }; - navigatorObjectsUpdated = mNavigator->updateObject(std::size_t(object), shapes, - object->getCollisionObject()->getWorldTransform()) || navigatorObjectsUpdated; + updated = updateNavigatorObject(object) || updated; }); - if (navigatorObjectsUpdated) + + for (const auto& door : mDoorStates) + if (const auto object = mPhysics->getObject(door.first)) + updated = updateNavigatorObject(object) || updated; + + if (updated) mNavigator->update(getPlayerPtr().getRefData().getPosition().asVec3()); } + bool World::updateNavigatorObject(const MWPhysics::Object* object) + { + const DetourNavigator::ObjectShapes shapes { + *object->getShapeInstance()->getCollisionShape(), + object->getShapeInstance()->getAvoidCollisionShape() + }; + return mNavigator->updateObject(std::size_t(object), shapes, object->getCollisionObject()->getWorldTransform()); + } + bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors) { osg::Vec3f a(x1,y1,z1); @@ -1747,7 +1760,10 @@ namespace MWWorld updateWeather(duration, paused); if (!paused) + { doPhysics (duration); + updateNavigator(); + } updatePlayer(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index b492679625..adf6ece3d3 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -61,6 +61,11 @@ namespace ToUTF8 struct ContentLoader; +namespace MWPhysics +{ + class Object; +} + namespace MWWorld { class WeatherManager; @@ -148,6 +153,10 @@ namespace MWWorld void doPhysics(float duration); ///< Run physics simulation and modify \a world accordingly. + void updateNavigator(); + + bool updateNavigatorObject(const MWPhysics::Object* object); + void ensureNeededRecords(); void fillGlobalVariables(); From 346e9e3141254e85abada1d475667429022c2b65 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 26 Aug 2018 23:27:38 +0300 Subject: [PATCH 076/133] Add off mesh connections for doors without teleport --- apps/openmw/mwmechanics/aipackage.cpp | 3 + apps/openmw/mwworld/scene.cpp | 164 +++++++++++++----- apps/openmw/mwworld/scene.hpp | 5 +- apps/openmw/mwworld/worldimp.cpp | 2 +- .../detournavigator/asyncnavmeshupdater.cpp | 7 +- .../detournavigator/asyncnavmeshupdater.hpp | 5 +- components/detournavigator/flags.hpp | 1 + components/detournavigator/makenavmesh.cpp | 49 ++++-- components/detournavigator/makenavmesh.hpp | 4 +- components/detournavigator/navigator.cpp | 20 +++ components/detournavigator/navigator.hpp | 17 ++ components/detournavigator/navmeshmanager.cpp | 31 +++- components/detournavigator/navmeshmanager.hpp | 6 + .../offmeshconnectionsmanager.hpp | 112 ++++++++++++ 14 files changed, 361 insertions(+), 65 deletions(-) create mode 100644 components/detournavigator/offmeshconnectionsmanager.hpp diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 5130488e3e..1797e31720 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -414,5 +414,8 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld:: if (actorClass.canWalk(actor)) result |= DetourNavigator::Flag_walk; + if (actorClass.isBipedal(actor) && getTypeId() != TypeIdWander) + result |= DetourNavigator::Flag_openDoor; + return result; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 2f6c0330f3..f68f1008c0 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -29,6 +29,7 @@ #include "../mwphysics/actor.hpp" #include "../mwphysics/object.hpp" #include "../mwphysics/heightfield.hpp" +#include "../mwphysics/convert.hpp" #include "player.hpp" #include "localscripts.hpp" @@ -40,25 +41,45 @@ namespace { + osg::Quat makeActorOsgQuat(const ESM::Position& position) + { + return osg::Quat(position.rot[2], osg::Vec3(0, 0, -1)); + } + + osg::Quat makeInversedOrderObjectOsgQuat(const ESM::Position& position) + { + const float xr = position.rot[0]; + const float yr = position.rot[1]; + const float zr = position.rot[2]; + + return osg::Quat(xr, osg::Vec3(-1, 0, 0)) + * osg::Quat(yr, osg::Vec3(0, -1, 0)) + * osg::Quat(zr, osg::Vec3(0, 0, -1)); + } - void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, bool inverseRotationOrder) + osg::Quat makeObjectOsgQuat(const ESM::Position& position) + { + const float xr = position.rot[0]; + const float yr = position.rot[1]; + const float zr = position.rot[2]; + + return osg::Quat(zr, osg::Vec3(0, 0, -1)) + * osg::Quat(yr, osg::Vec3(0, -1, 0)) + * osg::Quat(xr, osg::Vec3(-1, 0, 0)); + } + + void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, const bool inverseRotationOrder) { if (!ptr.getRefData().getBaseNode()) return; - osg::Quat worldRotQuat(ptr.getRefData().getPosition().rot[2], osg::Vec3(0,0,-1)); - if (!ptr.getClass().isActor()) - { - float xr = ptr.getRefData().getPosition().rot[0]; - float yr = ptr.getRefData().getPosition().rot[1]; - if (!inverseRotationOrder) - worldRotQuat = worldRotQuat * osg::Quat(yr, osg::Vec3(0,-1,0)) * - osg::Quat(xr, osg::Vec3(-1,0,0)); - else - worldRotQuat = osg::Quat(xr, osg::Vec3(-1,0,0)) * osg::Quat(yr, osg::Vec3(0,-1,0)) * worldRotQuat; - } - - rendering.rotateObject(ptr, worldRotQuat); + rendering.rotateObject(ptr, + ptr.getClass().isActor() + ? makeActorOsgQuat(ptr.getRefData().getPosition()) + : (inverseRotationOrder + ? makeInversedOrderObjectOsgQuat(ptr.getRefData().getPosition()) + : makeObjectOsgQuat(ptr.getRefData().getPosition())) + ); } void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, @@ -84,23 +105,6 @@ namespace ptr.getClass().insertObject (ptr, model, physics); - if (const auto object = physics.getObject(ptr)) - { - const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); - const DetourNavigator::ObjectShapes shapes { - *object->getShapeInstance()->getCollisionShape(), - object->getShapeInstance()->getAvoidCollisionShape() - }; - navigator->addObject(reinterpret_cast(object), shapes, - object->getCollisionObject()->getWorldTransform()); - } - else if (const auto actor = physics.getActor(ptr)) - { - const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); - const auto playerHalfExtents = physics.getHalfExtents(MWBase::Environment::get().getWorld()->getPlayerPtr()); - navigator->addAgent(playerHalfExtents); - } - if (useAnim) MWBase::Environment::get().getMechanicsManager()->add(ptr); @@ -111,6 +115,71 @@ namespace MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } + void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) + { + if (const auto object = physics.getObject(ptr)) + { + if (ptr.getClass().isDoor() && !ptr.getCellRef().getTeleport()) + { + const auto shape = object->getShapeInstance()->getCollisionShape(); + + btVector3 aabbMin; + btVector3 aabbMax; + shape->getAabb(btTransform::getIdentity(), aabbMin, aabbMax); + + const auto center = (aabbMax + aabbMin) * 0.5f; + + const auto distanceFromDoor = MWBase::Environment::get().getWorld()->getMaxActivationDistance() * 0.5f; + const auto toPoint = aabbMax.x() - aabbMin.x() < aabbMax.y() - aabbMin.y() + ? btVector3(distanceFromDoor, 0, 0) + : btVector3(0, distanceFromDoor, 0); + + const auto& transform = object->getCollisionObject()->getWorldTransform(); + const btTransform closedDoorTransform( + MWPhysics::toBullet(makeObjectOsgQuat(ptr.getCellRef().getPosition())), + transform.getOrigin() + ); + + const auto start = DetourNavigator::makeOsgVec3f(closedDoorTransform(center + toPoint)); + const auto startPoint = physics.castRay(start, start - osg::Vec3f(0, 0, 1000), ptr, {}, + MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water); + const auto connectionStart = startPoint.mHit ? startPoint.mHitPos : start; + + const auto end = DetourNavigator::makeOsgVec3f(closedDoorTransform(center - toPoint)); + const auto endPoint = physics.castRay(end, end - osg::Vec3f(0, 0, 1000), ptr, {}, + MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water); + const auto connectionEnd = endPoint.mHit ? endPoint.mHitPos : end; + + navigator.addObject( + reinterpret_cast(object), + DetourNavigator::DoorShapes( + *shape, + object->getShapeInstance()->getAvoidCollisionShape(), + connectionStart, + connectionEnd + ), + transform + ); + } + else + { + navigator.addObject( + reinterpret_cast(object), + DetourNavigator::ObjectShapes { + *object->getShapeInstance()->getCollisionShape(), + object->getShapeInstance()->getAvoidCollisionShape() + }, + object->getCollisionObject()->getWorldTransform() + ); + } + } + else if (const auto actor = physics.getActor(ptr)) + { + const auto playerHalfExtents = physics.getHalfExtents(MWBase::Environment::get().getWorld()->getPlayerPtr()); + navigator.addAgent(playerHalfExtents); + } + } + void updateObjectRotation (const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering, bool inverseRotationOrder) { @@ -137,24 +206,19 @@ namespace MWWorld::CellStore& mCell; bool mRescale; Loading::Listener& mLoadingListener; - MWPhysics::PhysicsSystem& mPhysics; - MWRender::RenderingManager& mRendering; std::vector mToInsert; - InsertVisitor (MWWorld::CellStore& cell, bool rescale, Loading::Listener& loadingListener, - MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering); + InsertVisitor (MWWorld::CellStore& cell, bool rescale, Loading::Listener& loadingListener); bool operator() (const MWWorld::Ptr& ptr); - void insert(); + + template + void insert(AddObject&& addObject); }; - InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, bool rescale, - Loading::Listener& loadingListener, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering) - : mCell (cell), mRescale (rescale), mLoadingListener (loadingListener), - mPhysics (physics), - mRendering (rendering) + InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, bool rescale, Loading::Listener& loadingListener) + : mCell (cell), mRescale (rescale), mLoadingListener (loadingListener) {} bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) @@ -165,7 +229,8 @@ namespace return true; } - void InsertVisitor::insert() + template + void InsertVisitor::insert(AddObject&& addObject) { for (std::vector::iterator it = mToInsert.begin(); it != mToInsert.end(); ++it) { @@ -182,7 +247,7 @@ namespace { try { - addObject(ptr, mPhysics, mRendering); + addObject(ptr); } catch (const std::exception& e) { @@ -577,8 +642,9 @@ namespace MWWorld mLastPlayerPos = pos.asVec3(); } - Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics) - : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering) + Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, + DetourNavigator::Navigator& navigator) + : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering), mNavigator(navigator) , mPreloadTimer(0.f) , mHalfGridSize(Settings::Manager::getInt("exterior cell load distance", "Cells")) , mCellLoadingThreshold(1024.f) @@ -706,9 +772,10 @@ namespace MWWorld void Scene::insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener) { - InsertVisitor insertVisitor (cell, rescale, *loadingListener, *mPhysics, mRendering); + InsertVisitor insertVisitor (cell, rescale, *loadingListener); cell.forEach (insertVisitor); - insertVisitor.insert(); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order AdjustPositionVisitor adjustPosVisitor; @@ -720,6 +787,7 @@ namespace MWWorld try { addObject(ptr, *mPhysics, mRendering); + addObject(ptr, *mPhysics, mNavigator); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index c8578fc354..00f5f98b83 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -30,6 +30,7 @@ namespace Loading namespace DetourNavigator { + class Navigator; class Water; } @@ -63,6 +64,7 @@ namespace MWWorld bool mCellChanged; MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; + DetourNavigator::Navigator& mNavigator; std::unique_ptr mPreloader; float mPreloadTimer; int mHalfGridSize; @@ -91,7 +93,8 @@ namespace MWWorld public: - Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics); + Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, + DetourNavigator::Navigator& navigator); ~Scene(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b467a687aa..bfa6b5124a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -235,7 +235,7 @@ namespace MWWorld mWeatherManager.reset(new MWWorld::WeatherManager(*mRendering, mFallback, mStore)); - mWorldScene.reset(new Scene(*mRendering.get(), mPhysics.get())); + mWorldScene.reset(new Scene(*mRendering.get(), mPhysics.get(), *mNavigator)); } void World::fillGlobalVariables() diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index bea6ffa57c..168ec8f417 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -46,9 +46,11 @@ namespace DetourNavigator return stream << "unknown"; } - AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager) + AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, + OffMeshConnectionsManager& offMeshConnectionsManager) : mSettings(settings) , mRecastMeshManager(recastMeshManager) + , mOffMeshConnectionsManager(offMeshConnectionsManager) , mShouldStop() , mThread([&] { process(); }) { @@ -124,9 +126,10 @@ namespace DetourNavigator const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile); const auto playerTile = getPlayerTile(); + const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile, - mSettings, *job.mNavMeshCacheItem); + offMeshConnections, mSettings, *job.mNavMeshCacheItem); const auto finish = std::chrono::steady_clock::now(); diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index bba788c3c4..d2d72a290a 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_ASYNCNAVMESHUPDATER_H #include "navmeshcacheitem.hpp" +#include "offmeshconnectionsmanager.hpp" #include "tilecachedrecastmeshmanager.hpp" #include "tileposition.hpp" @@ -32,7 +33,8 @@ namespace DetourNavigator class AsyncNavMeshUpdater { public: - AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager); + AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, + OffMeshConnectionsManager& offMeshConnectionsManager); ~AsyncNavMeshUpdater(); void post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& mNavMeshCacheItem, @@ -58,6 +60,7 @@ namespace DetourNavigator std::reference_wrapper mSettings; std::reference_wrapper mRecastMeshManager; + std::reference_wrapper mOffMeshConnectionsManager; std::atomic_bool mShouldStop; std::mutex mMutex; std::condition_variable mHasJob; diff --git a/components/detournavigator/flags.hpp b/components/detournavigator/flags.hpp index 38c0f13f6a..f52c0faf5a 100644 --- a/components/detournavigator/flags.hpp +++ b/components/detournavigator/flags.hpp @@ -10,6 +10,7 @@ namespace DetourNavigator Flag_none = 0, Flag_walk = 1 << 0, Flag_swim = 1 << 1, + Flag_openDoor = 1 << 2, }; } diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 71e8881683..07f2225046 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -100,9 +100,31 @@ namespace } } + std::vector getOffMeshVerts(const std::vector& connections) + { + std::vector result; + + result.reserve(connections.size() * 6); + + const auto add = [&] (const osg::Vec3f& v) + { + result.push_back(v.x()); + result.push_back(v.y()); + result.push_back(v.z()); + }; + + for (const auto& v : connections) + { + add(v.mStart); + add(v.mEnd); + } + + return result; + } + NavMeshData makeNavMeshTileData(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, - const int tileX, const int tileY, const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax, - const Settings& settings) + const std::vector& offMeshConnections, const int tileX, const int tileY, + const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax, const Settings& settings) { rcContext context; rcConfig config; @@ -280,6 +302,12 @@ namespace polyMesh.flags[i] = Flag_swim; } + const auto offMeshConVerts = getOffMeshVerts(offMeshConnections); + const std::vector offMeshConRad(offMeshConnections.size(), getRadius(settings, agentHalfExtents)); + const std::vector offMeshConDir(offMeshConnections.size(), DT_OFFMESH_CON_BIDIR); + const std::vector offMeshConAreas(offMeshConnections.size(), AreaType_ground); + const std::vector offMeshConFlags(offMeshConnections.size(), Flag_openDoor); + dtNavMeshCreateParams params; params.verts = polyMesh.verts; params.vertCount = polyMesh.nverts; @@ -293,13 +321,13 @@ namespace params.detailVertsCount = polyMeshDetail.nverts; params.detailTris = polyMeshDetail.tris; params.detailTriCount = polyMeshDetail.ntris; - params.offMeshConVerts = nullptr; - params.offMeshConRad = nullptr; - params.offMeshConDir = nullptr; - params.offMeshConAreas = nullptr; - params.offMeshConFlags = nullptr; + params.offMeshConVerts = offMeshConVerts.data(); + params.offMeshConRad = offMeshConRad.data(); + params.offMeshConDir = offMeshConDir.data(); + params.offMeshConAreas = offMeshConAreas.data(); + params.offMeshConFlags = offMeshConFlags.data(); params.offMeshConUserID = nullptr; - params.offMeshConCount = 0; + params.offMeshConCount = static_cast(offMeshConnections.size()); params.walkableHeight = getHeight(settings, agentHalfExtents); params.walkableRadius = getRadius(settings, agentHalfExtents); params.walkableClimb = getMaxClimb(settings); @@ -358,7 +386,8 @@ namespace DetourNavigator } UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, - const TilePosition& changedTile, const TilePosition& playerTile, const Settings& settings, + const TilePosition& changedTile, const TilePosition& playerTile, + const std::vector& offMeshConnections, const Settings& settings, NavMeshCacheItem& navMeshCacheItem) { log("update NavMesh with mutiple tiles:", @@ -419,7 +448,7 @@ namespace DetourNavigator const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y()); const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y()); - auto navMeshData = makeNavMeshTileData(agentHalfExtents, *recastMesh, x, y, + auto navMeshData = makeNavMeshTileData(agentHalfExtents, *recastMesh, offMeshConnections, x, y, tileBorderMin, tileBorderMax, settings); if (!navMeshData.mValue) diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 7a60152d71..f9c304ee99 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H +#include "offmeshconnectionsmanager.hpp" #include "settings.hpp" #include "navmeshcacheitem.hpp" #include "tileposition.hpp" @@ -47,7 +48,8 @@ namespace DetourNavigator NavMeshPtr makeEmptyNavMesh(const Settings& settings); UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, - const TilePosition& changedTile, const TilePosition& playerTile, const Settings& settings, + const TilePosition& changedTile, const TilePosition& playerTile, + const std::vector& offMeshConnections, const Settings& settings, NavMeshCacheItem& navMeshCacheItem); } diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index 359a9a316e..1c232a17c5 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -47,6 +47,20 @@ namespace DetourNavigator return result; } + bool Navigator::addObject(std::size_t id, const DoorShapes& shapes, const btTransform& transform) + { + if (addObject(id, static_cast(shapes), transform)) + { + mNavMeshManager.addOffMeshConnection( + id, + toNavMeshCoordinates(mSettings, shapes.mConnectionStart), + toNavMeshCoordinates(mSettings, shapes.mConnectionEnd) + ); + return true; + } + return false; + } + bool Navigator::updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) { return mNavMeshManager.updateObject(id, shape, transform, AreaType_ground); @@ -67,6 +81,11 @@ namespace DetourNavigator return result; } + bool Navigator::updateObject(std::size_t id, const DoorShapes& shapes, const btTransform& transform) + { + return updateObject(id, static_cast(shapes), transform); + } + bool Navigator::removeObject(std::size_t id) { bool result = mNavMeshManager.removeObject(id); @@ -76,6 +95,7 @@ namespace DetourNavigator const auto water = mWaterIds.find(id); if (water != mWaterIds.end()) result = mNavMeshManager.removeObject(water->second) || result; + mNavMeshManager.removeOffMeshConnection(id); return result; } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 3e60d72e02..e33f7c4abf 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -19,6 +19,19 @@ namespace DetourNavigator {} }; + struct DoorShapes : ObjectShapes + { + osg::Vec3f mConnectionStart; + osg::Vec3f mConnectionEnd; + + DoorShapes(const btCollisionShape& shape, const btCollisionShape* avoid, + const osg::Vec3f& connectionStart,const osg::Vec3f& connectionEnd) + : ObjectShapes(shape, avoid) + , mConnectionStart(connectionStart) + , mConnectionEnd(connectionEnd) + {} + }; + class Navigator { public: @@ -32,10 +45,14 @@ namespace DetourNavigator bool addObject(std::size_t id, const ObjectShapes& shapes, const btTransform& transform); + bool addObject(std::size_t id, const DoorShapes& shapes, const btTransform& transform); + bool updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); bool updateObject(std::size_t id, const ObjectShapes& shapes, const btTransform& transform); + bool updateObject(std::size_t id, const DoorShapes& shapes, const btTransform& transform); + bool removeObject(std::size_t id); bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level, diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index bfca44f794..07df5c7af3 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -28,7 +28,8 @@ namespace DetourNavigator NavMeshManager::NavMeshManager(const Settings& settings) : mSettings(settings) , mRecastMeshManager(settings) - , mAsyncNavMeshUpdater(settings, mRecastMeshManager) + , mOffMeshConnectionsManager(settings) + , mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager) {} bool NavMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, @@ -90,6 +91,34 @@ namespace DetourNavigator mCache.erase(agentHalfExtents); } + void NavMeshManager::addOffMeshConnection(std::size_t id, const osg::Vec3f& start, const osg::Vec3f& end) + { + if (!mOffMeshConnectionsManager.add(id, OffMeshConnection {start, end})) + return; + + const auto startTilePosition = getTilePosition(mSettings, start); + const auto endTilePosition = getTilePosition(mSettings, end); + + addChangedTile(startTilePosition, ChangeType::add); + + if (startTilePosition != endTilePosition) + addChangedTile(endTilePosition, ChangeType::add); + } + + void NavMeshManager::removeOffMeshConnection(std::size_t id) + { + if (const auto connection = mOffMeshConnectionsManager.remove(id)) + { + const auto startTilePosition = getTilePosition(mSettings, connection->mStart); + const auto endTilePosition = getTilePosition(mSettings, connection->mEnd); + + addChangedTile(startTilePosition, ChangeType::remove); + + if (startTilePosition != endTilePosition) + addChangedTile(endTilePosition, ChangeType::remove); + } + } + void NavMeshManager::update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents) { const auto playerTile = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition)); diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 6636402bf0..acec44eb9e 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -3,6 +3,7 @@ #include "asyncnavmeshupdater.hpp" #include "cachedrecastmeshmanager.hpp" +#include "offmeshconnectionsmanager.hpp" #include "sharednavmesh.hpp" #include @@ -37,6 +38,10 @@ namespace DetourNavigator void reset(const osg::Vec3f& agentHalfExtents); + void addOffMeshConnection(std::size_t id, const osg::Vec3f& start, const osg::Vec3f& end); + + void removeOffMeshConnection(std::size_t id); + void update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents); void wait(); @@ -48,6 +53,7 @@ namespace DetourNavigator private: const Settings& mSettings; TileCachedRecastMeshManager mRecastMeshManager; + OffMeshConnectionsManager mOffMeshConnectionsManager; std::map> mCache; std::map> mChangedTiles; AsyncNavMeshUpdater mAsyncNavMeshUpdater; diff --git a/components/detournavigator/offmeshconnectionsmanager.hpp b/components/detournavigator/offmeshconnectionsmanager.hpp new file mode 100644 index 0000000000..0b8f2010b9 --- /dev/null +++ b/components/detournavigator/offmeshconnectionsmanager.hpp @@ -0,0 +1,112 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTIONSMANAGER_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTIONSMANAGER_H + +#include "settings.hpp" +#include "settingsutils.hpp" +#include "tileposition.hpp" + +#include + +#include + +#include +#include +#include +#include +#include + +namespace DetourNavigator +{ + struct OffMeshConnection + { + osg::Vec3f mStart; + osg::Vec3f mEnd; + }; + + class OffMeshConnectionsManager + { + public: + OffMeshConnectionsManager(const Settings& settings) + : mSettings(settings) + {} + + bool add(const std::size_t id, const OffMeshConnection& value) + { + const std::lock_guard lock(mMutex); + + if (!mValuesById.insert(std::make_pair(id, value)).second) + return false; + + const auto startTilePosition = getTilePosition(mSettings, value.mStart); + const auto endTilePosition = getTilePosition(mSettings, value.mEnd); + + mValuesByTilePosition[startTilePosition].insert(id); + + if (startTilePosition != endTilePosition) + mValuesByTilePosition[endTilePosition].insert(id); + + return true; + } + + boost::optional remove(const std::size_t id) + { + const std::lock_guard lock(mMutex); + + const auto itById = mValuesById.find(id); + + if (itById == mValuesById.end()) + return boost::none; + + const auto result = itById->second; + + mValuesById.erase(itById); + + const auto startTilePosition = getTilePosition(mSettings, result.mStart); + const auto endTilePosition = getTilePosition(mSettings, result.mEnd); + + removeByTilePosition(startTilePosition, id); + + if (startTilePosition != endTilePosition) + removeByTilePosition(endTilePosition, id); + + return result; + } + + std::vector get(const TilePosition& tilePosition) + { + std::vector result; + + const std::lock_guard lock(mMutex); + + const auto itByTilePosition = mValuesByTilePosition.find(tilePosition); + + if (itByTilePosition == mValuesByTilePosition.end()) + return result; + + std::for_each(itByTilePosition->second.begin(), itByTilePosition->second.end(), + [&] (const std::size_t v) + { + const auto itById = mValuesById.find(v); + if (itById != mValuesById.end()) + result.push_back(itById->second); + }); + + return result; + } + + private: + const Settings& mSettings; + std::mutex mMutex; + std::unordered_map mValuesById; + std::map> mValuesByTilePosition; + + void removeByTilePosition(const TilePosition& tilePosition, const std::size_t id) + { + const auto it = mValuesByTilePosition.find(tilePosition); + if (it != mValuesByTilePosition.end()) + it->second.erase(id); + } + }; +} + +#endif From ff478aba6de1f8fa1082134d517e5edd7e8a3e54 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 31 Aug 2018 01:39:44 +0300 Subject: [PATCH 077/133] Use actor half extent for interior cells --- apps/openmw/mwbase/world.hpp | 2 ++ apps/openmw/mwrender/navmesh.cpp | 16 ++++++++++----- apps/openmw/mwrender/navmesh.hpp | 6 +++++- apps/openmw/mwrender/renderingmanager.cpp | 19 +++++++++++++++--- apps/openmw/mwrender/renderingmanager.hpp | 3 +++ apps/openmw/mwscript/docs/vmformat.txt | 3 ++- apps/openmw/mwscript/miscextensions.cpp | 20 +++++++++++++++++++ apps/openmw/mwworld/scene.cpp | 14 ++++++++----- apps/openmw/mwworld/worldimp.cpp | 5 +++++ apps/openmw/mwworld/worldimp.hpp | 2 ++ components/compiler/extensions0.cpp | 1 + components/compiler/opcodes.hpp | 1 + components/detournavigator/navmeshmanager.cpp | 17 ++++++++++------ components/detournavigator/navmeshmanager.hpp | 4 ++-- 14 files changed, 90 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index db6bda1419..bd7583f404 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -601,6 +601,8 @@ namespace MWBase virtual void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const = 0; + + virtual void setNavMeshNumberToRender(const std::size_t value) = 0; }; } diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index 787e332e92..23f4a8d58b 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -31,12 +31,13 @@ namespace MWRender return mEnabled; } - void NavMesh::update(const DetourNavigator::SharedNavMesh& sharedNavMesh, std::size_t generation, - std::size_t revision, const DetourNavigator::Settings& settings) + void NavMesh::update(const DetourNavigator::SharedNavMesh& sharedNavMesh, const std::size_t id, + const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings) { - if (!mEnabled || (mGeneration >= generation && mRevision >= revision)) + if (!mEnabled || (mId == id && mGeneration >= generation && mRevision >= revision)) return; + mId = id; mGeneration = generation; mRevision = revision; if (mGroup) @@ -49,6 +50,12 @@ namespace MWRender } } + void NavMesh::reset() + { + if (mGroup) + mRootNode->removeChild(mGroup); + } + void NavMesh::enable() { if (mGroup) @@ -58,8 +65,7 @@ namespace MWRender void NavMesh::disable() { - if (mGroup) - mRootNode->removeChild(mGroup); + reset(); mEnabled = false; } } diff --git a/apps/openmw/mwrender/navmesh.hpp b/apps/openmw/mwrender/navmesh.hpp index 0bed616f9c..34e71a70c2 100644 --- a/apps/openmw/mwrender/navmesh.hpp +++ b/apps/openmw/mwrender/navmesh.hpp @@ -21,9 +21,12 @@ namespace MWRender bool toggle(); - void update(const DetourNavigator::SharedNavMesh& sharedNavMesh, std::size_t generation, std::size_t revision, + void update(const DetourNavigator::SharedNavMesh& sharedNavMesh, + const std::size_t number, const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings); + void reset(); + void enable(); void disable(); @@ -31,6 +34,7 @@ namespace MWRender private: osg::ref_ptr mRootNode; bool mEnabled; + std::size_t mId = std::numeric_limits::max(); std::size_t mGeneration; std::size_t mRevision; osg::ref_ptr mGroup; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 5c0a3347cb..bc540d7e60 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -600,12 +600,20 @@ namespace MWRender } const auto navMeshes = mNavigator.getNavMeshes(); - if (!navMeshes.empty()) + + auto it = navMeshes.begin(); + for (std::size_t i = 0; it != navMeshes.end() && i < mNavMeshNumber; ++i) + ++it; + if (it == navMeshes.end()) + { + mNavMesh->reset(); + } + else { try { - mNavMesh->update(navMeshes.begin()->second->mValue, navMeshes.begin()->second->mGeneration, - navMeshes.begin()->second->mNavMeshRevision, mNavigator.getSettings()); + mNavMesh->update(it->second->mValue, mNavMeshNumber, it->second->mGeneration, + it->second->mNavMeshRevision, mNavigator.getSettings()); } catch (const std::exception& e) { @@ -1388,4 +1396,9 @@ namespace MWRender { mActorsPaths->remove(actor); } + + void RenderingManager::setNavMeshNumber(const std::size_t value) + { + mNavMeshNumber = value; + } } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 76ec674453..7a4500ac90 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -228,6 +228,8 @@ namespace MWRender void removeActorPath(const MWWorld::ConstPtr& actor) const; + void setNavMeshNumber(const std::size_t value); + private: void updateProjectionMatrix(); void updateTextureFiltering(); @@ -254,6 +256,7 @@ namespace MWRender DetourNavigator::Navigator& mNavigator; std::unique_ptr mNavMesh; + std::size_t mNavMeshNumber = 0; std::unique_ptr mActorsPaths; std::unique_ptr mPathgrid; std::unique_ptr mObjects; diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index bc7d93a136..d346ab6e43 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -457,5 +457,6 @@ op 0x2000306: OnActivate, explicit op 0x2000307: ToggleBorders, tb op 0x2000308: ToggleNavMesh op 0x2000309: ToggleActorsPaths +op 0x200030a: SetNavMeshNumber -opcodes 0x200030a-0x3ffffff unused +opcodes 0x200030b-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 8507b7628e..0cf2fc464b 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1345,6 +1345,25 @@ namespace MWScript } }; + class OpSetNavMeshNumberToRender : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + const auto navMeshNumber = runtime[0].mInteger; + runtime.pop(); + + if (navMeshNumber < 0) + { + runtime.getContext().report("Invalid navmesh number: use not less than zero values"); + return; + } + + MWBase::Environment::get().getWorld()->setNavMeshNumberToRender(static_cast(navMeshNumber)); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox); @@ -1447,6 +1466,7 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeToggleBorders, new OpToggleBorders); interpreter.installSegment5 (Compiler::Misc::opcodeToggleNavMesh, new OpToggleNavMesh); interpreter.installSegment5 (Compiler::Misc::opcodeToggleActorsPaths, new OpToggleActorsPaths); + interpreter.installSegment5 (Compiler::Misc::opcodeSetNavMeshNumberToRender, new OpSetNavMeshNumberToRender); } } } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index f68f1008c0..18ceffba42 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -175,8 +175,10 @@ namespace } else if (const auto actor = physics.getActor(ptr)) { - const auto playerHalfExtents = physics.getHalfExtents(MWBase::Environment::get().getWorld()->getPlayerPtr()); - navigator.addAgent(playerHalfExtents); + const auto halfExtents = ptr.getCell()->isExterior() + ? physics.getHalfExtents(MWBase::Environment::get().getWorld()->getPlayerPtr()) + : actor->getHalfExtents(); + navigator.addAgent(halfExtents); } } @@ -344,7 +346,7 @@ namespace MWWorld navigator->removeObject(reinterpret_cast(object)); else if (const auto actor = mPhysics->getActor(ptr)) { - navigator->removeAgent(playerHalfExtents); + navigator->removeAgent(ptr.getCell()->isExterior() ? playerHalfExtents : actor->getHalfExtents()); mRendering.removeActorPath(ptr); } mPhysics->remove(ptr); @@ -812,8 +814,10 @@ namespace MWWorld } else if (const auto actor = mPhysics->getActor(ptr)) { - const auto playerHalfExtents = mPhysics->getHalfExtents(MWBase::Environment::get().getWorld()->getPlayerPtr()); - navigator->removeAgent(playerHalfExtents); + const auto& halfExtents = ptr.getCell()->isExterior() + ? mPhysics->getHalfExtents(MWBase::Environment::get().getWorld()->getPlayerPtr()) + : actor->getHalfExtents(); + navigator->removeAgent(halfExtents); } mPhysics->remove(ptr); mRendering.removeObject (ptr); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bfa6b5124a..6cfba1e260 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3767,4 +3767,9 @@ namespace MWWorld mRendering->updateActorPath(actor, path, halfExtents, start, end); } + void World::setNavMeshNumberToRender(const std::size_t value) + { + mRendering->setNavMeshNumber(value); + } + } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index adf6ece3d3..49cae93ec6 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -703,6 +703,8 @@ namespace MWWorld void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const override; + + void setNavMeshNumberToRender(const std::size_t value) override; }; } diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 241abfaa40..a640d76d89 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -322,6 +322,7 @@ namespace Compiler extensions.registerInstruction ("toggleborders", "", opcodeToggleBorders); extensions.registerInstruction ("togglenavmesh", "", opcodeToggleNavMesh); extensions.registerInstruction ("toggleactorspaths", "", opcodeToggleActorsPaths); + extensions.registerInstruction ("setnavmeshnumber", "l", opcodeSetNavMeshNumberToRender); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index c591115ec2..a2d8a9467c 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -298,6 +298,7 @@ namespace Compiler const int opcodeToggleBorders = 0x2000307; const int opcodeToggleNavMesh = 0x2000308; const int opcodeToggleActorsPaths = 0x2000309; + const int opcodeSetNavMeshNumberToRender = 0x200030a; } namespace Sky diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 07df5c7af3..67514b8b78 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -122,11 +122,16 @@ namespace DetourNavigator void NavMeshManager::update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents) { const auto playerTile = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition)); - if (mLastRecastMeshManagerRevision >= mRecastMeshManager.getRevision() && mPlayerTile - && *mPlayerTile == playerTile) + auto& lastRevision = mLastRecastMeshManagerRevision[agentHalfExtents]; + auto lastPlayerTile = mPlayerTile.find(agentHalfExtents); + if (lastRevision >= mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end() + && lastPlayerTile->second == playerTile) return; - mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision(); - mPlayerTile = playerTile; + lastRevision = mRecastMeshManager.getRevision(); + if (lastPlayerTile == mPlayerTile.end()) + lastPlayerTile = mPlayerTile.insert(std::make_pair(agentHalfExtents, playerTile)).first; + else + lastPlayerTile->second = playerTile; std::map tilesToPost; const auto& cached = getCached(agentHalfExtents); const auto changedTiles = mChangedTiles.find(agentHalfExtents); @@ -163,8 +168,8 @@ namespace DetourNavigator } mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); log("cache update posted for agent=", agentHalfExtents, - " playerTile=", *mPlayerTile, - " recastMeshManagerRevision=", mLastRecastMeshManagerRevision, + " playerTile=", lastPlayerTile->second, + " recastMeshManagerRevision=", lastRevision, " changedTiles=", changedTiles->second.size()); } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index acec44eb9e..cd8c55aa06 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -58,8 +58,8 @@ namespace DetourNavigator std::map> mChangedTiles; AsyncNavMeshUpdater mAsyncNavMeshUpdater; std::size_t mGenerationCounter = 0; - boost::optional mPlayerTile; - std::size_t mLastRecastMeshManagerRevision = 0; + std::map mPlayerTile; + std::map mLastRecastMeshManagerRevision; void addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType); From 27a74725f1b2a61cc3d62d0ad666a133d3cc0d41 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 4 Sep 2018 00:10:58 +0300 Subject: [PATCH 078/133] Use osg::Vec3f --- apps/openmw/mwmechanics/aipackage.cpp | 36 ++++++++++++--------------- apps/openmw/mwmechanics/aipackage.hpp | 2 +- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 1797e31720..c397901fb5 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -101,28 +101,25 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& { mTimer += duration; //Update timer - const ESM::Position pos = actor.getRefData().getPosition(); //position of the actor + const auto position = actor.getRefData().getPosition().asVec3(); //position of the actor { const auto halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); MWBase::Environment::get().getWorld()->updateActorPath(actor, mPathFinder.getPath(), halfExtents, - pos.asVec3(), dest); + position, dest); } /// Stops the actor when it gets too close to a unloaded cell //... At current time, this test is unnecessary. AI shuts down when actor is more than 7168 //... units from player, and exterior cells are 8192 units long and wide. //... But AI processing distance may increase in the future. - if (isNearInactiveCell(pos)) + if (isNearInactiveCell(position)) { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; return false; } - // handle path building and shortcutting - const osg::Vec3f start = pos.asVec3(); - - const float distToTarget = distance(start, dest); + const float distToTarget = distance(position, dest); const bool isDestReached = (distToTarget <= destTolerance); if (!isDestReached && mTimer > AI_REACTION_TIME) @@ -142,14 +139,14 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& // Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions. if (actorCanMoveByZ) - mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first + mIsShortcutting = shortcutPath(position, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first if (!mIsShortcutting) { if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path { const auto playerHalfExtents = world->getHalfExtents(world->getPlayerPtr()); - mPathFinder.buildPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()), + mPathFinder.buildPath(position, dest, actor.getCell(), getPathGridGraph(actor.getCell()), playerHalfExtents, getNavigatorFlags(actor)); mRotateOnTheRunChecks = 3; @@ -160,7 +157,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& auto pPointBeforeDest = mPathFinder.getPath().rbegin() + 1; // if start point is closer to the target then last point of path (excluding target itself) then go straight on the target - if (distance(start, dest) <= distance(dest, *pPointBeforeDest)) + if (distance(position, dest) <= distance(dest, *pPointBeforeDest)) { mPathFinder.clearPath(); mPathFinder.addPointToPath(dest); @@ -180,13 +177,13 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& mTimer = 0; } - mPathFinder.update(pos.asVec3(), std::max(destTolerance, DEFAULT_TOLERANCE)); + mPathFinder.update(position, std::max(destTolerance, DEFAULT_TOLERANCE)); if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished { // turn to destination point - zTurn(actor, getZAngleToPoint(start, dest)); - smoothTurn(actor, getXAngleToPoint(start, dest), 0); + zTurn(actor, getZAngleToPoint(position, dest)); + smoothTurn(actor, getXAngleToPoint(position, dest), 0); return true; } @@ -198,8 +195,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& } // turn to next path point by X,Z axes - zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); - smoothTurn(actor, mPathFinder.getXAngleToNext(pos.pos[0], pos.pos[1], pos.pos[2]), 0); + zTurn(actor, mPathFinder.getZAngleToNext(position.x(), position.y())); + smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0); mObstacleCheck.update(actor, duration); @@ -351,14 +348,13 @@ bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target) || (magicEffects.get(ESM::MagicEffect::Chameleon).getMagnitude() > 75); } -bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos) +bool MWMechanics::AiPackage::isNearInactiveCell(osg::Vec3f position) { const ESM::Cell* playerCell(getPlayer().getCell()->getCell()); if (playerCell->isExterior()) { // get actor's distance from origin of center cell - osg::Vec3f actorOffset(actorPos.asVec3()); - CoordinateConverter(playerCell).toLocal(actorOffset); + CoordinateConverter(playerCell).toLocal(position); // currently assumes 3 x 3 grid for exterior cells, with player at center cell. // ToDo: (Maybe) use "exterior cell load distance" setting to get count of actual active cells @@ -366,8 +362,8 @@ bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos) const float distanceFromEdge = 200.0; float minThreshold = (-1.0f * ESM::Land::REAL_SIZE) + distanceFromEdge; float maxThreshold = (2.0f * ESM::Land::REAL_SIZE) - distanceFromEdge; - return (actorOffset[0] < minThreshold) || (maxThreshold < actorOffset[0]) - || (actorOffset[1] < minThreshold) || (maxThreshold < actorOffset[1]); + return (position.x() < minThreshold) || (maxThreshold < position.x()) + || (position.y() < minThreshold) || (maxThreshold < position.y()); } else { diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 20f47480e7..b4188e4813 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -150,7 +150,7 @@ namespace MWMechanics osg::Vec3f mShortcutFailPos; // position of last shortcut fail private: - bool isNearInactiveCell(const ESM::Position& actorPos); + bool isNearInactiveCell(osg::Vec3f position); }; } From ab090108cb44a768450e736ab1674df14e8580e4 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 4 Sep 2018 00:23:25 +0300 Subject: [PATCH 079/133] Assign world to local variable once per function --- apps/openmw/mwmechanics/aipackage.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index c397901fb5..68169bae9e 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -102,11 +102,11 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& mTimer += duration; //Update timer const auto position = actor.getRefData().getPosition().asVec3(); //position of the actor + const auto world = MWBase::Environment::get().getWorld(); { - const auto halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); - MWBase::Environment::get().getWorld()->updateActorPath(actor, mPathFinder.getPath(), halfExtents, - position, dest); + const auto halfExtents = world->getHalfExtents(actor); + world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest); } /// Stops the actor when it gets too close to a unloaded cell @@ -131,10 +131,9 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& bool destInLOS = false; const MWWorld::Class& actorClass = actor.getClass(); - MWBase::World* world = MWBase::Environment::get().getWorld(); // check if actor can move along z-axis - bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + bool actorCanMoveByZ = (actorClass.canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor); // Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions. @@ -227,7 +226,8 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor) void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor) { - static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); + const auto world = MWBase::Environment::get().getWorld(); + static float distance = world->getMaxActivationDistance(); const MWWorld::Ptr door = getNearbyDoor(actor, distance); if (door == MWWorld::Ptr()) @@ -238,7 +238,7 @@ void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor) { if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 )) { - MWBase::Environment::get().getWorld()->activate(door, actor); + world->activate(door, actor); return; } @@ -250,7 +250,7 @@ void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor) MWWorld::Ptr keyPtr = invStore.search(keyId); if (!keyPtr.isEmpty()) - MWBase::Environment::get().getWorld()->activate(door, actor); + world->activate(door, actor); } } @@ -299,8 +299,9 @@ bool MWMechanics::AiPackage::shortcutPath(const osg::Vec3f& startPoint, const os bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor) { - const bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) - || MWBase::Environment::get().getWorld()->isFlying(actor); + const auto world = MWBase::Environment::get().getWorld(); + const bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && world->isSwimming(actor)) + || world->isFlying(actor); if (actorCanMoveByZ) return true; From bbd82a743a93bfaa7372cd2edc658ef59b65bca4 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 4 Sep 2018 00:31:03 +0300 Subject: [PATCH 080/133] Use different tolerance for path point and destination --- apps/openmw/mwmechanics/aipackage.cpp | 4 +++- apps/openmw/mwmechanics/pathfinding.cpp | 9 +++++++-- apps/openmw/mwmechanics/pathfinding.hpp | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 68169bae9e..82bb12966d 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -176,7 +176,9 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& mTimer = 0; } - mPathFinder.update(position, std::max(destTolerance, DEFAULT_TOLERANCE)); + const auto pointTolerance = std::min(actor.getClass().getSpeed(actor), DEFAULT_TOLERANCE); + + mPathFinder.update(position, pointTolerance, destTolerance); if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished { diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index ee091de4c3..3759335e5d 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -253,9 +253,14 @@ namespace MWMechanics return getXAngleToDir(dir); } - void PathFinder::update(const osg::Vec3f& position, const float tolerance) + void PathFinder::update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance) { - if (!mPath.empty() && sqrDistanceIgnoreZ(mPath.front(), position) < tolerance*tolerance) + if (mPath.empty()) + return; + + const auto tolerance = mPath.size() > 1 ? pointTolerance : destinationTolerance; + + if (sqrDistanceIgnoreZ(mPath.front(), position) < tolerance * tolerance) mPath.pop_front(); } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 543305ff62..1a35ef792e 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -79,7 +79,7 @@ namespace MWMechanics const DetourNavigator::Flags flags); /// Remove front point if exist and within tolerance - void update(const osg::Vec3f& position, const float tolerance = DEFAULT_TOLERANCE); + void update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance); bool checkPathCompleted() const { From 92b34e8bb4180b0e406f0ae14e328ec5349acbb9 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 4 Sep 2018 00:43:30 +0300 Subject: [PATCH 081/133] Check whether can actor move along z-axis in separate function --- apps/openmw/mwmechanics/aipackage.cpp | 14 ++++++++------ apps/openmw/mwmechanics/aipackage.hpp | 2 ++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 82bb12966d..42c18cc30c 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -129,12 +129,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const bool wasShortcutting = mIsShortcutting; bool destInLOS = false; - - const MWWorld::Class& actorClass = actor.getClass(); - - // check if actor can move along z-axis - bool actorCanMoveByZ = (actorClass.canSwim(actor) && world->isSwimming(actor)) - || world->isFlying(actor); + const bool actorCanMoveByZ = canActorMoveByZAxis(actor); // Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions. if (actorCanMoveByZ) @@ -418,3 +413,10 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld:: return result; } + +bool MWMechanics::AiPackage::canActorMoveByZAxis(const MWWorld::Ptr& actor) const +{ + const auto world = MWBase::Environment::get().getWorld(); + const auto& actorClass = actor.getClass(); + return (actorClass.canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor); +} diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index b4188e4813..5be858b0bf 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -134,6 +134,8 @@ namespace MWMechanics DetourNavigator::Flags getNavigatorFlags(const MWWorld::Ptr& actor) const; + bool canActorMoveByZAxis(const MWWorld::Ptr& actor) const; + // TODO: all this does not belong here, move into temporary storage PathFinder mPathFinder; ObstacleCheck mObstacleCheck; From 7c5bedc35a220176ec03a156c81ff3d9b0b38aba Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 8 Sep 2018 14:30:03 +0300 Subject: [PATCH 082/133] Reset shorcutting if actor can't move by z-axis on reaction time If actor was shortcutting because it was swimming, then when it started walking it still be shortcutting, but there will be no new path, because shortcut path builds only for actor able moving by z-axis and pathfinder path only for not shortcutting actor. --- apps/openmw/mwmechanics/aipackage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 42c18cc30c..1532cce018 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -132,8 +132,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& const bool actorCanMoveByZ = canActorMoveByZAxis(actor); // Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions. - if (actorCanMoveByZ) - mIsShortcutting = shortcutPath(position, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first + mIsShortcutting = actorCanMoveByZ + && shortcutPath(position, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first if (!mIsShortcutting) { From 7c80bb94116aee03a25b1b509befee59f096a040 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 22 Sep 2018 17:49:57 +0300 Subject: [PATCH 083/133] Support multiple threads for async nav mesh updater --- apps/openmw/mwworld/worldimp.cpp | 1 + apps/openmw_test_suite/detournavigator/navigator.cpp | 1 + components/detournavigator/asyncnavmeshupdater.cpp | 6 ++++-- components/detournavigator/asyncnavmeshupdater.hpp | 2 +- components/detournavigator/settings.hpp | 1 + files/settings-default.cfg | 3 +++ 6 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 6cfba1e260..83a2382803 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -215,6 +215,7 @@ namespace MWWorld navigatorSettings.mRegionMergeSize = Settings::Manager::getInt("region merge size", "Navigator"); navigatorSettings.mRegionMinSize = Settings::Manager::getInt("region min size", "Navigator"); navigatorSettings.mTileSize = Settings::Manager::getInt("tile size", "Navigator"); + navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast(Settings::Manager::getInt("async nav mesh updater threads", "Navigator")); navigatorSettings.mMaxPolygonPathSize = static_cast(Settings::Manager::getInt("max polygon path size", "Navigator")); navigatorSettings.mMaxSmoothPathSize = static_cast(Settings::Manager::getInt("max smooth path size", "Navigator")); navigatorSettings.mTrianglesPerChunk = static_cast(Settings::Manager::getInt("triangles per chunk", "Navigator")); diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 52e350572d..7e188f8fdc 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -55,6 +55,7 @@ namespace mSettings.mRegionMergeSize = 20; mSettings.mRegionMinSize = 8; mSettings.mTileSize = 64; + mSettings.mAsyncNavMeshUpdaterThreads = 1; mSettings.mMaxPolygonPathSize = 1024; mSettings.mMaxSmoothPathSize = 1024; mSettings.mTrianglesPerChunk = 256; diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 168ec8f417..ba3de35836 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -52,8 +52,9 @@ namespace DetourNavigator , mRecastMeshManager(recastMeshManager) , mOffMeshConnectionsManager(offMeshConnectionsManager) , mShouldStop() - , mThread([&] { process(); }) { + for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i) + mThreads.emplace_back([&] { process(); }); } AsyncNavMeshUpdater::~AsyncNavMeshUpdater() @@ -63,7 +64,8 @@ namespace DetourNavigator mJobs = decltype(mJobs)(); mHasJob.notify_all(); lock.unlock(); - mThread.join(); + for (auto& thread : mThreads) + thread.join(); } void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index d2d72a290a..8d31848873 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -71,7 +71,7 @@ namespace DetourNavigator TilePosition mPlayerTile; std::mutex mFirstStartMutex; boost::optional mFirstStart; - std::thread mThread; + std::vector mThreads; void process() throw(); diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index ccfece3dae..9f6ca97be1 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -27,6 +27,7 @@ namespace DetourNavigator int mRegionMergeSize; int mRegionMinSize; int mTileSize; + std::size_t mAsyncNavMeshUpdaterThreads; std::size_t mMaxPolygonPathSize; std::size_t mMaxSmoothPathSize; std::size_t mTrianglesPerChunk; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index c046a19b32..29333682e4 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -585,6 +585,9 @@ region merge size = 20 # The minimum number of cells allowed to form isolated island areas. (value >= 0) region min size = 8 +# Number of background threads to update nav mesh (value >= 1) +async nav mesh updater threads = 1 + # Maximum size of path over polygons (value > 0) max polygon path size = 1024 From 1a274899047bf1f60f8701f29cd2dd5a14ca85ad Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 22 Sep 2018 18:36:57 +0300 Subject: [PATCH 084/133] Add special type for object id --- apps/openmw/mwworld/scene.cpp | 12 ++--- apps/openmw/mwworld/worldimp.cpp | 2 +- .../detournavigator/navigator.cpp | 26 +++++----- .../cachedrecastmeshmanager.cpp | 6 +-- .../cachedrecastmeshmanager.hpp | 6 +-- components/detournavigator/navigator.cpp | 26 +++++----- components/detournavigator/navigator.hpp | 24 ++++----- components/detournavigator/navmeshmanager.cpp | 10 ++-- components/detournavigator/navmeshmanager.hpp | 10 ++-- components/detournavigator/objectid.hpp | 50 +++++++++++++++++++ .../offmeshconnectionsmanager.hpp | 13 ++--- .../detournavigator/recastmeshmanager.cpp | 6 +-- .../detournavigator/recastmeshmanager.hpp | 9 ++-- .../tilecachedrecastmeshmanager.cpp | 6 +-- .../tilecachedrecastmeshmanager.hpp | 8 +-- 15 files changed, 133 insertions(+), 81 deletions(-) create mode 100644 components/detournavigator/objectid.hpp diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 18ceffba42..49a603d1d8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -151,7 +151,7 @@ namespace const auto connectionEnd = endPoint.mHit ? endPoint.mHitPos : end; navigator.addObject( - reinterpret_cast(object), + DetourNavigator::ObjectId(object), DetourNavigator::DoorShapes( *shape, object->getShapeInstance()->getAvoidCollisionShape(), @@ -164,7 +164,7 @@ namespace else { navigator.addObject( - reinterpret_cast(object), + DetourNavigator::ObjectId(object), DetourNavigator::ObjectShapes { *object->getShapeInstance()->getCollisionShape(), object->getShapeInstance()->getAvoidCollisionShape() @@ -343,7 +343,7 @@ namespace MWWorld for (const auto& ptr : visitor.mObjects) { if (const auto object = mPhysics->getObject(ptr)) - navigator->removeObject(reinterpret_cast(object)); + navigator->removeObject(DetourNavigator::ObjectId(object)); else if (const auto actor = mPhysics->getActor(ptr)) { navigator->removeAgent(ptr.getCell()->isExterior() ? playerHalfExtents : actor->getHalfExtents()); @@ -365,7 +365,7 @@ namespace MWWorld if (land && land->mDataTypes&ESM::Land::DATA_VHGT) { if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->removeObject(reinterpret_cast(heightField)); + navigator->removeObject(DetourNavigator::ObjectId(heightField)); mPhysics->removeHeightField(cellX, cellY); } } @@ -420,7 +420,7 @@ namespace MWWorld } if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addObject(reinterpret_cast(heightField), *heightField->getShape(), + navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), heightField->getCollisionObject()->getWorldTransform()); } @@ -808,7 +808,7 @@ namespace MWWorld const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); if (const auto object = mPhysics->getObject(ptr)) { - navigator->removeObject(reinterpret_cast(object)); + navigator->removeObject(DetourNavigator::ObjectId(object)); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); navigator->update(player.getRefData().getPosition().asVec3()); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 83a2382803..c45d06c41e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1569,7 +1569,7 @@ namespace MWWorld *object->getShapeInstance()->getCollisionShape(), object->getShapeInstance()->getAvoidCollisionShape() }; - return mNavigator->updateObject(std::size_t(object), shapes, object->getCollisionObject()->getWorldTransform()); + return mNavigator->updateObject(DetourNavigator::ObjectId(object), shapes, object->getCollisionObject()->getWorldTransform()); } bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 7e188f8fdc..1b42b6d05c 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -102,7 +102,7 @@ namespace shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(1, shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(); @@ -152,7 +152,7 @@ namespace compoundShape.addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), &boxShape); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(1, heightfieldShape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&heightfieldShape), heightfieldShape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(); @@ -184,7 +184,7 @@ namespace osg::Vec3f(215, -215, 1.877177715301513671875), })) << mPath; - mNavigator->addObject(2, compoundShape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(); @@ -235,8 +235,8 @@ namespace compoundShape.addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), &boxShape); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(1, heightfieldShape, btTransform::getIdentity()); - mNavigator->addObject(2, compoundShape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&heightfieldShape), heightfieldShape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(); @@ -270,7 +270,7 @@ namespace compoundShape.updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0))); - mNavigator->updateObject(2, compoundShape, btTransform::getIdentity()); + mNavigator->updateObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(); @@ -327,8 +327,8 @@ namespace shape2.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(1, shape, btTransform::getIdentity()); - mNavigator->addObject(2, shape2, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape2), shape2, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(); @@ -384,7 +384,7 @@ namespace shapeAvoid.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(1, ObjectShapes {shape, &shapeAvoid}, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), ObjectShapes {shape, &shapeAvoid}, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(); @@ -432,7 +432,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, 300, btTransform::getIdentity()); - mNavigator->addObject(1, shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(); @@ -480,7 +480,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, -25, btTransform::getIdentity()); - mNavigator->addObject(1, shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(); @@ -525,7 +525,7 @@ namespace shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(1, shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); mNavigator->addWater(osg::Vec2i(0, 0), std::numeric_limits::max(), -25, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(); @@ -572,7 +572,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, -25, btTransform::getIdentity()); - mNavigator->addObject(1, shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); mNavigator->wait(); diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index 8a9fbd4ee8..5e145486b9 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -7,7 +7,7 @@ namespace DetourNavigator : mImpl(settings, bounds) {} - bool CachedRecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, + bool CachedRecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { if (!mImpl.addObject(id, shape, transform, areaType)) @@ -16,7 +16,7 @@ namespace DetourNavigator return true; } - bool CachedRecastMeshManager::updateObject(std::size_t id, const btTransform& transform, const AreaType areaType) + bool CachedRecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType) { if (!mImpl.updateObject(id, transform, areaType)) return false; @@ -24,7 +24,7 @@ namespace DetourNavigator return true; } - boost::optional CachedRecastMeshManager::removeObject(std::size_t id) + boost::optional CachedRecastMeshManager::removeObject(const ObjectId id) { const auto object = mImpl.removeObject(id); if (object) diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index 048d002701..528e8dabc1 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -12,16 +12,16 @@ namespace DetourNavigator public: CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds); - bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); - bool updateObject(std::size_t id, const btTransform& transform, const AreaType areaType); + bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform); boost::optional removeWater(const osg::Vec2i& cellPosition); - boost::optional removeObject(std::size_t id); + boost::optional removeObject(const ObjectId id); std::shared_ptr getMesh(); diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index 1c232a17c5..d185ad02fa 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -27,17 +27,17 @@ namespace DetourNavigator mNavMeshManager.reset(agentHalfExtents); } - bool Navigator::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) + bool Navigator::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) { return mNavMeshManager.addObject(id, shape, transform, AreaType_ground); } - bool Navigator::addObject(std::size_t id, const ObjectShapes& shapes, const btTransform& transform) + bool Navigator::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { bool result = addObject(id, shapes.mShape, transform); if (shapes.mAvoid) { - const auto avoidId = reinterpret_cast(shapes.mAvoid); + const ObjectId avoidId(shapes.mAvoid); if (mNavMeshManager.addObject(avoidId, *shapes.mAvoid, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); @@ -47,7 +47,7 @@ namespace DetourNavigator return result; } - bool Navigator::addObject(std::size_t id, const DoorShapes& shapes, const btTransform& transform) + bool Navigator::addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) { if (addObject(id, static_cast(shapes), transform)) { @@ -61,17 +61,17 @@ namespace DetourNavigator return false; } - bool Navigator::updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) + bool Navigator::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) { return mNavMeshManager.updateObject(id, shape, transform, AreaType_ground); } - bool Navigator::updateObject(std::size_t id, const ObjectShapes& shapes, const btTransform& transform) + bool Navigator::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { bool result = updateObject(id, shapes.mShape, transform); if (shapes.mAvoid) { - const auto avoidId = reinterpret_cast(shapes.mAvoid); + const ObjectId avoidId(shapes.mAvoid); if (mNavMeshManager.updateObject(avoidId, *shapes.mAvoid, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); @@ -81,12 +81,12 @@ namespace DetourNavigator return result; } - bool Navigator::updateObject(std::size_t id, const DoorShapes& shapes, const btTransform& transform) + bool Navigator::updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) { return updateObject(id, static_cast(shapes), transform); } - bool Navigator::removeObject(std::size_t id) + bool Navigator::removeObject(const ObjectId id) { bool result = mNavMeshManager.removeObject(id); const auto avoid = mAvoidIds.find(id); @@ -132,23 +132,23 @@ namespace DetourNavigator return mSettings; } - void Navigator::updateAvoidShapeId(const std::size_t id, const std::size_t avoidId) + void Navigator::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId) { updateId(id, avoidId, mWaterIds); } - void Navigator::updateWaterShapeId(const std::size_t id, const std::size_t waterId) + void Navigator::updateWaterShapeId(const ObjectId id, const ObjectId waterId) { updateId(id, waterId, mWaterIds); } - void Navigator::updateId(const std::size_t id, const std::size_t updateId, std::unordered_map& ids) + void Navigator::updateId(const ObjectId id, const ObjectId updateId, std::unordered_map& ids) { auto inserted = ids.insert(std::make_pair(id, updateId)); if (!inserted.second) { mNavMeshManager.removeObject(inserted.first->second); - inserted.second = updateId; + inserted.first->second = updateId; } } } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index e33f7c4abf..78857ad3e0 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -41,19 +41,19 @@ namespace DetourNavigator void removeAgent(const osg::Vec3f& agentHalfExtents); - bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); + bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform); - bool addObject(std::size_t id, const ObjectShapes& shapes, const btTransform& transform); + bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform); - bool addObject(std::size_t id, const DoorShapes& shapes, const btTransform& transform); + bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform); - bool updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform); + bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform); - bool updateObject(std::size_t id, const ObjectShapes& shapes, const btTransform& transform); + bool updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform); - bool updateObject(std::size_t id, const DoorShapes& shapes, const btTransform& transform); + bool updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform); - bool removeObject(std::size_t id); + bool removeObject(const ObjectId id); bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level, const btTransform& transform); @@ -82,12 +82,12 @@ namespace DetourNavigator Settings mSettings; NavMeshManager mNavMeshManager; std::map mAgents; - std::unordered_map mAvoidIds; - std::unordered_map mWaterIds; + std::unordered_map mAvoidIds; + std::unordered_map mWaterIds; - void updateAvoidShapeId(const std::size_t id, const std::size_t avoidId); - void updateWaterShapeId(const std::size_t id, const std::size_t waterId); - void updateId(const std::size_t id, const std::size_t waterId, std::unordered_map& ids); + void updateAvoidShapeId(const ObjectId id, const ObjectId avoidId); + void updateWaterShapeId(const ObjectId id, const ObjectId waterId); + void updateId(const ObjectId id, const ObjectId waterId, std::unordered_map& ids); }; } diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 67514b8b78..559aedebbf 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -32,7 +32,7 @@ namespace DetourNavigator , mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager) {} - bool NavMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + bool NavMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { if (!mRecastMeshManager.addObject(id, shape, transform, areaType)) @@ -41,7 +41,7 @@ namespace DetourNavigator return true; } - bool NavMeshManager::updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + bool NavMeshManager::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { if (!mRecastMeshManager.updateObject(id, transform, areaType)) @@ -50,7 +50,7 @@ namespace DetourNavigator return true; } - bool NavMeshManager::removeObject(std::size_t id) + bool NavMeshManager::removeObject(const ObjectId id) { const auto object = mRecastMeshManager.removeObject(id); if (!object) @@ -91,7 +91,7 @@ namespace DetourNavigator mCache.erase(agentHalfExtents); } - void NavMeshManager::addOffMeshConnection(std::size_t id, const osg::Vec3f& start, const osg::Vec3f& end) + void NavMeshManager::addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end) { if (!mOffMeshConnectionsManager.add(id, OffMeshConnection {start, end})) return; @@ -105,7 +105,7 @@ namespace DetourNavigator addChangedTile(endTilePosition, ChangeType::add); } - void NavMeshManager::removeOffMeshConnection(std::size_t id) + void NavMeshManager::removeOffMeshConnection(const ObjectId id) { if (const auto connection = mOffMeshConnectionsManager.remove(id)) { diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index cd8c55aa06..86d9ea5d48 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -22,13 +22,13 @@ namespace DetourNavigator public: NavMeshManager(const Settings& settings); - bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); - bool updateObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); - bool removeObject(std::size_t id); + bool removeObject(const ObjectId id); void addAgent(const osg::Vec3f& agentHalfExtents); @@ -38,9 +38,9 @@ namespace DetourNavigator void reset(const osg::Vec3f& agentHalfExtents); - void addOffMeshConnection(std::size_t id, const osg::Vec3f& start, const osg::Vec3f& end); + void addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end); - void removeOffMeshConnection(std::size_t id); + void removeOffMeshConnection(const ObjectId id); void update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents); diff --git a/components/detournavigator/objectid.hpp b/components/detournavigator/objectid.hpp new file mode 100644 index 0000000000..cbb933ba01 --- /dev/null +++ b/components/detournavigator/objectid.hpp @@ -0,0 +1,50 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTID_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTID_H + +#include +#include + +namespace DetourNavigator +{ + class ObjectId + { + public: + template + explicit ObjectId(const T* value) throw() + : mValue(reinterpret_cast(value)) + { + } + + std::size_t value() const throw() + { + return mValue; + } + + friend bool operator <(const ObjectId lhs, const ObjectId rhs) throw() + { + return lhs.mValue < rhs.mValue; + } + + friend bool operator ==(const ObjectId lhs, const ObjectId rhs) throw() + { + return lhs.mValue == rhs.mValue; + } + + private: + std::size_t mValue; + }; +} + +namespace std +{ + template <> + struct hash + { + std::size_t operator ()(const DetourNavigator::ObjectId value) const throw() + { + return value.value(); + } + }; +} + +#endif diff --git a/components/detournavigator/offmeshconnectionsmanager.hpp b/components/detournavigator/offmeshconnectionsmanager.hpp index 0b8f2010b9..0450724cfe 100644 --- a/components/detournavigator/offmeshconnectionsmanager.hpp +++ b/components/detournavigator/offmeshconnectionsmanager.hpp @@ -4,6 +4,7 @@ #include "settings.hpp" #include "settingsutils.hpp" #include "tileposition.hpp" +#include "objectid.hpp" #include @@ -30,7 +31,7 @@ namespace DetourNavigator : mSettings(settings) {} - bool add(const std::size_t id, const OffMeshConnection& value) + bool add(const ObjectId id, const OffMeshConnection& value) { const std::lock_guard lock(mMutex); @@ -48,7 +49,7 @@ namespace DetourNavigator return true; } - boost::optional remove(const std::size_t id) + boost::optional remove(const ObjectId id) { const std::lock_guard lock(mMutex); @@ -84,7 +85,7 @@ namespace DetourNavigator return result; std::for_each(itByTilePosition->second.begin(), itByTilePosition->second.end(), - [&] (const std::size_t v) + [&] (const ObjectId v) { const auto itById = mValuesById.find(v); if (itById != mValuesById.end()) @@ -97,10 +98,10 @@ namespace DetourNavigator private: const Settings& mSettings; std::mutex mMutex; - std::unordered_map mValuesById; - std::map> mValuesByTilePosition; + std::unordered_map mValuesById; + std::map> mValuesByTilePosition; - void removeByTilePosition(const TilePosition& tilePosition, const std::size_t id) + void removeByTilePosition(const TilePosition& tilePosition, const ObjectId id) { const auto it = mValuesByTilePosition.find(tilePosition); if (it != mValuesByTilePosition.end()) diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index cabde7fdf8..f9f81de962 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -11,7 +11,7 @@ namespace DetourNavigator { } - bool RecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + bool RecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { if (!mObjects.emplace(id, RecastMeshObject(shape, transform, areaType)).second) @@ -20,7 +20,7 @@ namespace DetourNavigator return mShouldRebuild; } - bool RecastMeshManager::updateObject(std::size_t id, const btTransform& transform, const AreaType areaType) + bool RecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType) { const auto object = mObjects.find(id); if (object == mObjects.end()) @@ -31,7 +31,7 @@ namespace DetourNavigator return mShouldRebuild; } - boost::optional RecastMeshManager::removeObject(std::size_t id) + boost::optional RecastMeshManager::removeObject(const ObjectId id) { const auto object = mObjects.find(id); if (object == mObjects.end()) diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index c4391b6821..be0fc35817 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -3,6 +3,7 @@ #include "recastmeshbuilder.hpp" #include "recastmeshobject.hpp" +#include "objectid.hpp" #include @@ -34,16 +35,16 @@ namespace DetourNavigator RecastMeshManager(const Settings& settings, const TileBounds& bounds); - bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); - bool updateObject(std::size_t id, const btTransform& transform, const AreaType areaType); + bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform); boost::optional removeWater(const osg::Vec2i& cellPosition); - boost::optional removeObject(std::size_t id); + boost::optional removeObject(const ObjectId id); std::shared_ptr getMesh(); @@ -52,7 +53,7 @@ namespace DetourNavigator private: bool mShouldRebuild; RecastMeshBuilder mMeshBuilder; - std::unordered_map mObjects; + std::unordered_map mObjects; std::map mWater; void rebuild(); diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 164253f856..67959e42c0 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -9,7 +9,7 @@ namespace DetourNavigator : mSettings(settings) {} - bool TileCachedRecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, + bool TileCachedRecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { bool result = false; @@ -39,7 +39,7 @@ namespace DetourNavigator return result; } - bool TileCachedRecastMeshManager::updateObject(std::size_t id, const btTransform& transform, + bool TileCachedRecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType) { const auto object = mObjectsTilesPositions.find(id); @@ -59,7 +59,7 @@ namespace DetourNavigator return result; } - boost::optional TileCachedRecastMeshManager::removeObject(std::size_t id) + boost::optional TileCachedRecastMeshManager::removeObject(const ObjectId id) { const auto object = mObjectsTilesPositions.find(id); if (object == mObjectsTilesPositions.end()) diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index f67d2c3922..2b237fddeb 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -14,12 +14,12 @@ namespace DetourNavigator public: TileCachedRecastMeshManager(const Settings& settings); - bool addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform, + bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); - bool updateObject(std::size_t id, const btTransform& transform, const AreaType areaType); + bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); - boost::optional removeObject(std::size_t id); + boost::optional removeObject(const ObjectId id); bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform); @@ -43,7 +43,7 @@ namespace DetourNavigator const Settings& mSettings; std::mutex mTilesMutex; std::map mTiles; - std::unordered_map> mObjectsTilesPositions; + std::unordered_map> mObjectsTilesPositions; std::map> mWaterTilesPositions; std::size_t mRevision = 0; }; From cf4066751c3ac3e999706bade95b22f08c1541af Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 29 Sep 2018 20:36:42 +0300 Subject: [PATCH 085/133] Add classes to encapsulate value guarded by mutex --- components/detournavigator/makenavmesh.hpp | 4 +- components/detournavigator/sharednavmesh.hpp | 42 +------------ components/misc/guarded.hpp | 65 ++++++++++++++++++++ 3 files changed, 69 insertions(+), 42 deletions(-) create mode 100644 components/misc/guarded.hpp diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index f9c304ee99..963e0daa8b 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -6,6 +6,7 @@ #include "navmeshcacheitem.hpp" #include "tileposition.hpp" #include "tilebounds.hpp" +#include "sharednavmesh.hpp" #include @@ -16,11 +17,8 @@ class dtNavMesh; namespace DetourNavigator { class RecastMesh; - class SharedNavMesh; struct Settings; - using NavMeshPtr = std::shared_ptr; - enum class UpdateNavMeshStatus { ignore, diff --git a/components/detournavigator/sharednavmesh.hpp b/components/detournavigator/sharednavmesh.hpp index 657973c2f6..8045434a18 100644 --- a/components/detournavigator/sharednavmesh.hpp +++ b/components/detournavigator/sharednavmesh.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H +#include + #include #include @@ -9,45 +11,7 @@ class dtNavMesh; namespace DetourNavigator { using NavMeshPtr = std::shared_ptr; - - class LockedSharedNavMesh - { - public: - LockedSharedNavMesh(std::mutex& mutex, const NavMeshPtr& value) - : mLock(new std::lock_guard(mutex)), mValue(value) - {} - - dtNavMesh* operator ->() const - { - return mValue.get(); - } - - dtNavMesh& operator *() const - { - return *mValue; - } - - private: - std::unique_ptr> mLock; - NavMeshPtr mValue; - }; - - class SharedNavMesh - { - public: - SharedNavMesh(const NavMeshPtr& value) - : mMutex(std::make_shared()), mValue(value) - {} - - LockedSharedNavMesh lock() const - { - return LockedSharedNavMesh(*mMutex, mValue); - } - - private: - std::shared_ptr mMutex; - NavMeshPtr mValue; - }; + using SharedNavMesh = Misc::SharedGuarded; } #endif diff --git a/components/misc/guarded.hpp b/components/misc/guarded.hpp new file mode 100644 index 0000000000..f70f5e4c5e --- /dev/null +++ b/components/misc/guarded.hpp @@ -0,0 +1,65 @@ +#ifndef OPENMW_COMPONENTS_MISC_GUARDED_H +#define OPENMW_COMPONENTS_MISC_GUARDED_H + +#include +#include + +namespace Misc +{ + template + class Locked + { + public: + Locked(std::mutex& mutex, T& value) + : mLock(mutex), mValue(value) + {} + + T& get() const + { + return mValue.get(); + } + + T* operator ->() const + { + return std::addressof(get()); + } + + T& operator *() const + { + return get(); + } + + private: + std::unique_lock mLock; + std::reference_wrapper mValue; + }; + + template + class SharedGuarded + { + public: + SharedGuarded() + : mMutex(std::make_shared()) + {} + + SharedGuarded(std::shared_ptr value) + : mMutex(std::make_shared()), mValue(std::move(value)) + {} + + Locked lock() const + { + return Locked(*mMutex, *mValue); + } + + Locked lockConst() const + { + return Locked(*mMutex, *mValue); + } + + private: + std::shared_ptr mMutex; + std::shared_ptr mValue; + }; +} + +#endif From ae7285e9600031973f6f2b0425ee7b489f08ec4f Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 29 Sep 2018 22:57:41 +0300 Subject: [PATCH 086/133] Use ScopeGuarded instead of raw mutex --- .../detournavigator/asyncnavmeshupdater.cpp | 37 +++-------- .../detournavigator/asyncnavmeshupdater.hpp | 16 +---- components/detournavigator/debug.hpp | 19 +++--- .../offmeshconnectionsmanager.hpp | 49 ++++++++------- .../tilecachedrecastmeshmanager.cpp | 62 +++++++++---------- .../tilecachedrecastmeshmanager.hpp | 8 +-- components/misc/guarded.hpp | 29 +++++++++ 7 files changed, 110 insertions(+), 110 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index ba3de35836..a61412a2a4 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -72,9 +72,7 @@ namespace DetourNavigator const std::shared_ptr& navMeshCacheItem, const TilePosition& playerTile, const std::map& changedTiles) { - log("post jobs playerTile=", playerTile); - - setPlayerTile(playerTile); + *mPlayerTile.lock() = playerTile; if (changedTiles.empty()) return; @@ -124,10 +122,10 @@ namespace DetourNavigator const auto start = std::chrono::steady_clock::now(); - setFirstStart(start); + const auto firstStart = setFirstStart(start); const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile); - const auto playerTile = getPlayerTile(); + const auto playerTile = *mPlayerTile.lockConst(); const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile, @@ -143,7 +141,7 @@ namespace DetourNavigator " generation=", job.mNavMeshCacheItem->mGeneration, " revision=", job.mNavMeshCacheItem->mNavMeshRevision, " time=", std::chrono::duration_cast(finish - start).count(), "ms", - " total_time=", std::chrono::duration_cast(finish - getFirstStart()).count(), "ms"); + " total_time=", std::chrono::duration_cast(finish - firstStart).count(), "ms"); } boost::optional AsyncNavMeshUpdater::getNextJob() @@ -188,28 +186,11 @@ namespace DetourNavigator writeToFile(*job.mNavMeshCacheItem->mValue.lock(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } - std::chrono::steady_clock::time_point AsyncNavMeshUpdater::getFirstStart() - { - const std::lock_guard lock(mFirstStartMutex); - return *mFirstStart; - } - - void AsyncNavMeshUpdater::setFirstStart(const std::chrono::steady_clock::time_point& value) - { - const std::lock_guard lock(mFirstStartMutex); - if (!mFirstStart) - mFirstStart = value; - } - - TilePosition AsyncNavMeshUpdater::getPlayerTile() - { - const std::lock_guard lock(mPlayerTileMutex); - return mPlayerTile; - } - - void AsyncNavMeshUpdater::setPlayerTile(const TilePosition& value) + std::chrono::steady_clock::time_point AsyncNavMeshUpdater::setFirstStart(const std::chrono::steady_clock::time_point& value) { - const std::lock_guard lock(mPlayerTileMutex); - mPlayerTile = value; + const auto locked = mFirstStart.lock(); + if (!*locked) + *locked = value; + return *locked.get(); } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 8d31848873..28d1952a79 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -67,10 +67,8 @@ namespace DetourNavigator std::condition_variable mDone; Jobs mJobs; std::map> mPushed; - std::mutex mPlayerTileMutex; - TilePosition mPlayerTile; - std::mutex mFirstStartMutex; - boost::optional mFirstStart; + Misc::ScopeGuarded mPlayerTile; + Misc::ScopeGuarded> mFirstStart; std::vector mThreads; void process() throw(); @@ -79,17 +77,9 @@ namespace DetourNavigator boost::optional getNextJob(); - void notifyHasJob(); - void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const; - std::chrono::steady_clock::time_point getFirstStart(); - - void setFirstStart(const std::chrono::steady_clock::time_point& value); - - TilePosition getPlayerTile(); - - void setPlayerTile(const TilePosition& value); + std::chrono::steady_clock::time_point setFirstStart(const std::chrono::steady_clock::time_point& value); }; } diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp index fb8d5bd219..460e224350 100644 --- a/components/detournavigator/debug.hpp +++ b/components/detournavigator/debug.hpp @@ -4,6 +4,7 @@ #include "tilebounds.hpp" #include +#include #include #include @@ -11,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -77,21 +77,19 @@ namespace DetourNavigator public: void setSink(std::unique_ptr sink) { - const std::lock_guard guard(mMutex); - mSink = std::move(sink); + *mSink.lock() = std::move(sink); } - bool isEnabled() const + bool isEnabled() { - const std::lock_guard guard(mMutex); - return bool(mSink); + return bool(*mSink.lockConst()); } void write(const std::string& text) { - const std::lock_guard guard(mMutex); - if (mSink) - mSink->write(text); + const auto sink = mSink.lock(); + if (*sink) + sink.get()->write(text); } static Log& instance() @@ -101,8 +99,7 @@ namespace DetourNavigator } private: - mutable std::mutex mMutex; - std::unique_ptr mSink; + Misc::ScopeGuarded> mSink; }; inline void write(std::ostream& stream) diff --git a/components/detournavigator/offmeshconnectionsmanager.hpp b/components/detournavigator/offmeshconnectionsmanager.hpp index 0450724cfe..212f821c90 100644 --- a/components/detournavigator/offmeshconnectionsmanager.hpp +++ b/components/detournavigator/offmeshconnectionsmanager.hpp @@ -6,6 +6,8 @@ #include "tileposition.hpp" #include "objectid.hpp" +#include + #include #include @@ -33,42 +35,42 @@ namespace DetourNavigator bool add(const ObjectId id, const OffMeshConnection& value) { - const std::lock_guard lock(mMutex); + const auto values = mValues.lock(); - if (!mValuesById.insert(std::make_pair(id, value)).second) + if (!values->mById.insert(std::make_pair(id, value)).second) return false; const auto startTilePosition = getTilePosition(mSettings, value.mStart); const auto endTilePosition = getTilePosition(mSettings, value.mEnd); - mValuesByTilePosition[startTilePosition].insert(id); + values->mByTilePosition[startTilePosition].insert(id); if (startTilePosition != endTilePosition) - mValuesByTilePosition[endTilePosition].insert(id); + values->mByTilePosition[endTilePosition].insert(id); return true; } boost::optional remove(const ObjectId id) { - const std::lock_guard lock(mMutex); + const auto values = mValues.lock(); - const auto itById = mValuesById.find(id); + const auto itById = values->mById.find(id); - if (itById == mValuesById.end()) + if (itById == values->mById.end()) return boost::none; const auto result = itById->second; - mValuesById.erase(itById); + values->mById.erase(itById); const auto startTilePosition = getTilePosition(mSettings, result.mStart); const auto endTilePosition = getTilePosition(mSettings, result.mEnd); - removeByTilePosition(startTilePosition, id); + removeByTilePosition(values->mByTilePosition, startTilePosition, id); if (startTilePosition != endTilePosition) - removeByTilePosition(endTilePosition, id); + removeByTilePosition(values->mByTilePosition, endTilePosition, id); return result; } @@ -77,18 +79,18 @@ namespace DetourNavigator { std::vector result; - const std::lock_guard lock(mMutex); + const auto values = mValues.lock(); - const auto itByTilePosition = mValuesByTilePosition.find(tilePosition); + const auto itByTilePosition = values->mByTilePosition.find(tilePosition); - if (itByTilePosition == mValuesByTilePosition.end()) + if (itByTilePosition == values->mByTilePosition.end()) return result; std::for_each(itByTilePosition->second.begin(), itByTilePosition->second.end(), [&] (const ObjectId v) { - const auto itById = mValuesById.find(v); - if (itById != mValuesById.end()) + const auto itById = values->mById.find(v); + if (itById != values->mById.end()) result.push_back(itById->second); }); @@ -96,15 +98,20 @@ namespace DetourNavigator } private: + struct Values + { + std::unordered_map mById; + std::map> mByTilePosition; + }; + const Settings& mSettings; - std::mutex mMutex; - std::unordered_map mValuesById; - std::map> mValuesByTilePosition; + Misc::ScopeGuarded mValues; - void removeByTilePosition(const TilePosition& tilePosition, const ObjectId id) + void removeByTilePosition(std::map>& valuesByTilePosition, + const TilePosition& tilePosition, const ObjectId id) { - const auto it = mValuesByTilePosition.find(tilePosition); - if (it != mValuesByTilePosition.end()) + const auto it = valuesByTilePosition.find(tilePosition); + if (it != valuesByTilePosition.end()) it->second.erase(id); } }; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 67959e42c0..d624800e92 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -17,19 +17,18 @@ namespace DetourNavigator const auto border = getBorderSize(mSettings); getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& tilePosition) { - std::unique_lock lock(mTilesMutex); - auto tile = mTiles.find(tilePosition); - if (tile == mTiles.end()) + const auto tiles = mTiles.lock(); + auto tile = tiles->find(tilePosition); + if (tile == tiles->end()) { auto tileBounds = makeTileBounds(mSettings, tilePosition); tileBounds.mMin -= osg::Vec2f(border, border); tileBounds.mMax += osg::Vec2f(border, border); - tile = mTiles.insert(std::make_pair(tilePosition, + tile = tiles->insert(std::make_pair(tilePosition, CachedRecastMeshManager(mSettings, tileBounds))).first; } if (tile->second.addObject(id, shape, transform, areaType)) { - lock.unlock(); tilesPositions.push_back(tilePosition); result = true; } @@ -46,14 +45,15 @@ namespace DetourNavigator if (object == mObjectsTilesPositions.end()) return false; bool result = false; - std::unique_lock lock(mTilesMutex); - for (const auto& tilePosition : object->second) { - const auto tile = mTiles.find(tilePosition); - if (tile != mTiles.end()) - result = tile->second.updateObject(id, transform, areaType) || result; + const auto tiles = mTiles.lock(); + for (const auto& tilePosition : object->second) + { + const auto tile = tiles->find(tilePosition); + if (tile != tiles->end()) + result = tile->second.updateObject(id, transform, areaType) || result; + } } - lock.unlock(); if (result) ++mRevision; return result; @@ -67,14 +67,13 @@ namespace DetourNavigator boost::optional result; for (const auto& tilePosition : object->second) { - std::unique_lock lock(mTilesMutex); - const auto tile = mTiles.find(tilePosition); - if (tile == mTiles.end()) + const auto tiles = mTiles.lock(); + const auto tile = tiles->find(tilePosition); + if (tile == tiles->end()) continue; const auto tileResult = tile->second.removeObject(id); if (tile->second.isEmpty()) - mTiles.erase(tile); - lock.unlock(); + tiles->erase(tile); if (tileResult && !result) result = tileResult; } @@ -94,8 +93,8 @@ namespace DetourNavigator if (cellSize == std::numeric_limits::max()) { - const std::lock_guard lock(mTilesMutex); - for (auto& tile : mTiles) + const auto tiles = mTiles.lock(); + for (auto& tile : *tiles) { if (tile.second.addWater(cellPosition, cellSize, transform)) { @@ -108,19 +107,18 @@ namespace DetourNavigator { getTilesPositions(cellSize, transform, mSettings, [&] (const TilePosition& tilePosition) { - std::unique_lock lock(mTilesMutex); - auto tile = mTiles.find(tilePosition); - if (tile == mTiles.end()) + const auto tiles = mTiles.lock(); + auto tile = tiles->find(tilePosition); + if (tile == tiles->end()) { auto tileBounds = makeTileBounds(mSettings, tilePosition); tileBounds.mMin -= osg::Vec2f(border, border); tileBounds.mMax += osg::Vec2f(border, border); - tile = mTiles.insert(std::make_pair(tilePosition, + tile = tiles->insert(std::make_pair(tilePosition, CachedRecastMeshManager(mSettings, tileBounds))).first; } if (tile->second.addWater(cellPosition, cellSize, transform)) { - lock.unlock(); tilesPositions.push_back(tilePosition); result = true; } @@ -141,14 +139,13 @@ namespace DetourNavigator boost::optional result; for (const auto& tilePosition : object->second) { - std::unique_lock lock(mTilesMutex); - const auto tile = mTiles.find(tilePosition); - if (tile == mTiles.end()) + const auto tiles = mTiles.lock(); + const auto tile = tiles->find(tilePosition); + if (tile == tiles->end()) continue; const auto tileResult = tile->second.removeWater(cellPosition); if (tile->second.isEmpty()) - mTiles.erase(tile); - lock.unlock(); + tiles->erase(tile); if (tileResult && !result) result = tileResult; } @@ -159,17 +156,16 @@ namespace DetourNavigator std::shared_ptr TileCachedRecastMeshManager::getMesh(const TilePosition& tilePosition) { - const std::lock_guard lock(mTilesMutex); - const auto it = mTiles.find(tilePosition); - if (it == mTiles.end()) + const auto tiles = mTiles.lock(); + const auto it = tiles->find(tilePosition); + if (it == tiles->end()) return nullptr; return it->second.getMesh(); } bool TileCachedRecastMeshManager::hasTile(const TilePosition& tilePosition) { - const std::lock_guard lock(mTilesMutex); - return mTiles.count(tilePosition); + return mTiles.lockConst()->count(tilePosition); } std::size_t TileCachedRecastMeshManager::getRevision() const diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 2b237fddeb..f82ef85c5c 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -4,6 +4,8 @@ #include "cachedrecastmeshmanager.hpp" #include "tileposition.hpp" +#include + #include #include @@ -32,8 +34,7 @@ namespace DetourNavigator template void forEachTilePosition(Function&& function) { - const std::lock_guard lock(mTilesMutex); - for (const auto& tile : mTiles) + for (const auto& tile : *mTiles.lock()) function(tile.first); } @@ -41,8 +42,7 @@ namespace DetourNavigator private: const Settings& mSettings; - std::mutex mTilesMutex; - std::map mTiles; + Misc::ScopeGuarded> mTiles; std::unordered_map> mObjectsTilesPositions; std::map> mWaterTilesPositions; std::size_t mRevision = 0; diff --git a/components/misc/guarded.hpp b/components/misc/guarded.hpp index f70f5e4c5e..841b9b8121 100644 --- a/components/misc/guarded.hpp +++ b/components/misc/guarded.hpp @@ -34,6 +34,35 @@ namespace Misc std::reference_wrapper mValue; }; + template + class ScopeGuarded + { + public: + ScopeGuarded() = default; + + ScopeGuarded(const T& value) + : mValue(value) + {} + + ScopeGuarded(T&& value) + : mValue(std::move(value)) + {} + + Locked lock() + { + return Locked(mMutex, mValue); + } + + Locked lockConst() + { + return Locked(mMutex, mValue); + } + + private: + std::mutex mMutex; + T mValue; + }; + template class SharedGuarded { From 69b5834c64d8523849c99785974a4e016fcca946 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 30 Sep 2018 01:20:42 +0300 Subject: [PATCH 087/133] Add doc for Navigator --- components/detournavigator/navigator.hpp | 111 +++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 78857ad3e0..9d7723b474 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -32,48 +32,159 @@ namespace DetourNavigator {} }; + /** + * @brief Top level class of detournavigator componenet. Navigator allows to build a scene with navmesh and find + * a path for an agent there. Scene contains agents, geometry objects and water. Agent are distinguished only by + * half extents. Each object has unique identifier and could be added, updated or removed. Water could be added once + * for each world cell at given level of height. Navmesh builds asynchronously in separate threads. To start build + * navmesh call update method. + */ class Navigator { public: + /** + * @brief Navigator constructor initializes all internal data. Constructed object is ready to build a scene. + * @param settings allows to customize navigator work. Constructor is only place to set navigator settings. + */ Navigator(const Settings& settings); + /** + * @brief addAgent should be called for each agent even if all of them has same half extents. + * @param agentHalfExtents allows to setup bounding cylinder for each agent, for each different half extents + * there is different navmesh. + */ void addAgent(const osg::Vec3f& agentHalfExtents); + /** + * @brief removeAgent should be called for each agent even if all of them has same half extents + * @param agentHalfExtents allows determine which agent to remove + */ void removeAgent(const osg::Vec3f& agentHalfExtents); + /** + * @brief addObject is used to add object represented by single btCollisionShape and btTransform. + * @param id is used to distinguish different objects. + * @param shape must live until object is updated by another shape removed from Navigator. + * @param transform allows to setup object geometry according to its world state. + * @return true if object is added, false if there is already object with given id. + */ bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform); + /** + * @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes + * @param id is used to distinguish different objects + * @param shape members must live until object is updated by another shape removed from Navigator + * @param transform allows to setup objects geometry according to its world state + * @return true if object is added, false if there is already object with given id + */ bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform); + /** + * @brief addObject is used to add doors. + * @param id is used to distinguish different objects. + * @param shape members must live until object is updated by another shape or removed from Navigator. + * @param transform allows to setup objects geometry according to its world state. + * @return true if object is added, false if there is already object with given id. + */ bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform); + /** + * @brief updateObject replace object geometry by given data. + * @param id is used to find object. + * @param shape must live until object is updated by another shape removed from Navigator. + * @param transform allows to setup objects geometry according to its world state. + * @return true if object is updated, false if there is no object with given id. + */ bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform); + /** + * @brief updateObject replace object geometry by given data. + * @param id is used to find object. + * @param shape members must live until object is updated by another shape removed from Navigator. + * @param transform allows to setup objects geometry according to its world state. + * @return true if object is updated, false if there is no object with given id. + */ bool updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform); + /** + * @brief updateObject replace object geometry by given data. + * @param id is used to find object. + * @param shape members must live until object is updated by another shape removed from Navigator. + * @param transform allows to setup objects geometry according to its world state. + * @return true if object is updated, false if there is no object with given id. + */ bool updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform); + /** + * @brief removeObject to make it no more available at the scene. + * @param id is used to find object. + * @return true if object is removed, false if there is no object with given id. + */ bool removeObject(const ObjectId id); + /** + * @brief addWater is used to set water level at given world cell. + * @param cellPosition allows to distinguish cells if there is many in current world. + * @param cellSize set cell borders. std::numeric_limits::max() disables cell borders. + * @param level set z coordinate of water surface at the scene. + * @param transform set global shift of cell center. + * @return true if there was no water at given cell if cellSize != std::numeric_limits::max() or there is + * at least single object is added to the scene, false if there is already water for given cell or there is no + * any other objects. + */ bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level, const btTransform& transform); + /** + * @brief removeWater to make it no more available at the scene. + * @param cellPosition allows to find cell. + * @return true if there was water at given cell. + */ bool removeWater(const osg::Vec2i& cellPosition); + /** + * @brief update start background navmesh update using current scene state. + * @param playerPosition setup initial point to order build tiles of navmesh. + */ void update(const osg::Vec3f& playerPosition); + /** + * @brief wait locks thread until all tiles are updated from last update call. + */ void wait(); + /** + * @brief findPath fills output iterator with points of scene surfaces to be used for actor to walk through. + * @param agentHalfExtents allows to find navmesh for given actor. + * @param start path from given point. + * @param end path at given point. + * @param includeFlags setup allowed surfaces for actor to walk. + * @param out the beginning of the destination range. + * @return Output iterator to the element in the destination range, one past the last element of found path. + * Equal to out if no path is found. + * @throws InvalidArgument if there is no navmesh for given agentHalfExtents. + */ template OutputIterator findPath(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, OutputIterator out) const { + static_assert( + std::is_same< + typename std::iterator_traits::iterator_category, + std::output_iterator_tag + >::value, + "out is not an OutputIterator" + ); const auto navMesh = mNavMeshManager.getNavMesh(agentHalfExtents); return findSmoothPath(*navMesh.lock(), toNavMeshCoordinates(mSettings, agentHalfExtents), toNavMeshCoordinates(mSettings, start), toNavMeshCoordinates(mSettings, end), includeFlags, mSettings, out); } + /** + * @brief getNavMeshes returns all current navmeshes + * @return map of agent half extents to navmesh + */ std::map> getNavMeshes() const; const Settings& getSettings() const; From ed73d130f9bdee36ec0996ef61715d6a4ca0e5f8 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 1 Oct 2018 01:33:25 +0300 Subject: [PATCH 088/133] Cache navmesh tiles Use LRU modification to hold currently used items. Use RecastMesh binary data for item key. Store original pointer of btCollisionShape in user pointer to make available it as an identifier within all duplicates. Use pointer to heights data array for btHeightfieldTerrainShape. --- apps/openmw/mwrender/navmesh.cpp | 4 +- apps/openmw/mwrender/navmesh.hpp | 5 +- apps/openmw/mwrender/renderingmanager.cpp | 5 +- apps/openmw/mwworld/worldimp.cpp | 1 + apps/openmw_test_suite/CMakeLists.txt | 1 + .../detournavigator/navigator.cpp | 55 +++ .../detournavigator/navmeshtilescache.cpp | 312 ++++++++++++++++++ .../detournavigator/recastmeshbuilder.cpp | 1 + .../nifloader/testbulletnifloader.cpp | 3 +- components/CMakeLists.txt | 1 + .../detournavigator/asyncnavmeshupdater.cpp | 13 +- .../detournavigator/asyncnavmeshupdater.hpp | 6 +- components/detournavigator/makenavmesh.cpp | 120 ++++--- components/detournavigator/makenavmesh.hpp | 3 +- components/detournavigator/navigator.cpp | 2 +- components/detournavigator/navigator.hpp | 4 +- .../detournavigator/navmeshcacheitem.hpp | 63 +++- components/detournavigator/navmeshdata.hpp | 35 ++ components/detournavigator/navmeshmanager.cpp | 35 +- components/detournavigator/navmeshmanager.hpp | 10 +- .../detournavigator/navmeshtilescache.cpp | 158 +++++++++ .../detournavigator/navmeshtilescache.hpp | 127 +++++++ components/detournavigator/objectid.hpp | 2 +- .../detournavigator/offmeshconnection.hpp | 15 + .../offmeshconnectionsmanager.hpp | 7 +- components/detournavigator/recastmesh.cpp | 7 +- components/detournavigator/recastmesh.hpp | 7 +- .../detournavigator/recastmeshbuilder.cpp | 2 +- .../detournavigator/recastmeshbuilder.hpp | 5 +- .../detournavigator/recastmeshmanager.cpp | 28 +- .../detournavigator/recastmeshmanager.hpp | 7 +- .../detournavigator/recastmeshobject.cpp | 1 + components/detournavigator/settings.hpp | 1 + components/detournavigator/sharednavmesh.hpp | 4 - components/misc/guarded.hpp | 15 + components/nifbullet/bulletnifloader.cpp | 9 +- files/settings-default.cfg | 3 + 37 files changed, 942 insertions(+), 135 deletions(-) create mode 100644 apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp create mode 100644 components/detournavigator/navmeshdata.hpp create mode 100644 components/detournavigator/navmeshtilescache.cpp create mode 100644 components/detournavigator/navmeshtilescache.hpp create mode 100644 components/detournavigator/offmeshconnection.hpp diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index 23f4a8d58b..331f506abb 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -31,7 +31,7 @@ namespace MWRender return mEnabled; } - void NavMesh::update(const DetourNavigator::SharedNavMesh& sharedNavMesh, const std::size_t id, + void NavMesh::update(const dtNavMesh& navMesh, const std::size_t id, const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings) { if (!mEnabled || (mId == id && mGeneration >= generation && mRevision >= revision)) @@ -42,7 +42,7 @@ namespace MWRender mRevision = revision; if (mGroup) mRootNode->removeChild(mGroup); - mGroup = SceneUtil::createNavMeshGroup(*sharedNavMesh.lock(), settings); + mGroup = SceneUtil::createNavMeshGroup(navMesh, settings); if (mGroup) { mGroup->setNodeMask(Mask_Debug); diff --git a/apps/openmw/mwrender/navmesh.hpp b/apps/openmw/mwrender/navmesh.hpp index 34e71a70c2..29205ca277 100644 --- a/apps/openmw/mwrender/navmesh.hpp +++ b/apps/openmw/mwrender/navmesh.hpp @@ -21,9 +21,8 @@ namespace MWRender bool toggle(); - void update(const DetourNavigator::SharedNavMesh& sharedNavMesh, - const std::size_t number, const std::size_t generation, const std::size_t revision, - const DetourNavigator::Settings& settings); + void update(const dtNavMesh& navMesh, const std::size_t number, const std::size_t generation, + const std::size_t revision, const DetourNavigator::Settings& settings); void reset(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index bc540d7e60..4d79fd3de2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -612,8 +612,9 @@ namespace MWRender { try { - mNavMesh->update(it->second->mValue, mNavMeshNumber, it->second->mGeneration, - it->second->mNavMeshRevision, mNavigator.getSettings()); + const auto locked = it->second.lockConst(); + mNavMesh->update(locked->getValue(), mNavMeshNumber, locked->getGeneration(), + locked->getNavMeshRevision(), mNavigator.getSettings()); } catch (const std::exception& e) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c45d06c41e..93babf120e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -216,6 +216,7 @@ namespace MWWorld navigatorSettings.mRegionMinSize = Settings::Manager::getInt("region min size", "Navigator"); navigatorSettings.mTileSize = Settings::Manager::getInt("tile size", "Navigator"); navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast(Settings::Manager::getInt("async nav mesh updater threads", "Navigator")); + navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast(Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator")); navigatorSettings.mMaxPolygonPathSize = static_cast(Settings::Manager::getInt("max polygon path size", "Navigator")); navigatorSettings.mMaxSmoothPathSize = static_cast(Settings::Manager::getInt("max smooth path size", "Navigator")); navigatorSettings.mTrianglesPerChunk = static_cast(Settings::Manager::getInt("triangles per chunk", "Navigator")); diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index b86f4b812c..acb33e1fa3 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -23,6 +23,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) detournavigator/recastmeshbuilder.cpp detournavigator/gettilespositions.cpp detournavigator/recastmeshobject.cpp + detournavigator/navmeshtilescache.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 1b42b6d05c..7926e208de 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -56,6 +56,7 @@ namespace mSettings.mRegionMinSize = 8; mSettings.mTileSize = 64; mSettings.mAsyncNavMeshUpdaterThreads = 1; + mSettings.mMaxNavMeshTilesCacheSize = 1024 * 1024; mSettings.mMaxPolygonPathSize = 1024; mSettings.mMaxSmoothPathSize = 1024; mSettings.mTrianglesPerChunk = 256; @@ -602,4 +603,58 @@ namespace osg::Vec3f(0, -215, -94.753631591796875), })) << mPath; } + + TEST_F(DetourNavigatorNavigatorTest, update_remove_and_update_then_find_path_should_return_path) + { + const std::array heightfieldData {{ + 0, 0, 0, 0, 0, + 0, -25, -25, -25, -25, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + }}; + btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + shape.setLocalScaling(btVector3(128, 128, 1)); + + mNavigator->addAgent(mAgentHalfExtents); + mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + mNavigator->removeObject(ObjectId(&shape)); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut); + + EXPECT_EQ(mPath, std::deque({ + osg::Vec3f(-215, 215, 1.85963428020477294921875), + osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.5760211944580078125), + osg::Vec3f(-174.930633544921875, 174.930633544921875, -15.01167774200439453125), + osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473323822021484375), + osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829898834228515625), + osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875), + osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.39907073974609375), + osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375), + osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625), + osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37929534912109375), + osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875), + osg::Vec3f(5.3815765380859375, -5.3815765380859375, -75.35065460205078125), + osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.96945953369140625), + osg::Vec3f(45.450958251953125, -45.450958251953125, -60.58824920654296875), + osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), + osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.825855255126953125), + osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), + osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625), + osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625), + osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875), + osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), + osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.53864824771881103515625), + osg::Vec3f(215, -215, 1.877177715301513671875), + })) << mPath; + } } diff --git a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp new file mode 100644 index 0000000000..93fc5fbaf6 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp @@ -0,0 +1,312 @@ +#include "operators.hpp" + +#include +#include +#include + +#include + +#include + +namespace DetourNavigator +{ + static inline bool operator ==(const NavMeshDataRef& lhs, const NavMeshDataRef& rhs) + { + return std::make_pair(lhs.mValue, lhs.mSize) == std::make_pair(rhs.mValue, rhs.mSize); + } +} + +namespace +{ + using namespace testing; + using namespace DetourNavigator; + + struct DetourNavigatorNavMeshTilesCacheTest : Test + { + const osg::Vec3f mAgentHalfExtents {1, 2, 3}; + const TilePosition mTilePosition {0, 0}; + const std::vector mIndices {{0, 1, 2}}; + const std::vector mVertices {{0, 0, 0, 1, 0, 0, 1, 1, 0}}; + const std::vector mAreaTypes {1, AreaType_ground}; + const std::vector mWater {}; + const std::size_t mTrianglesPerChunk {1}; + const RecastMesh mRecastMesh {mIndices, mVertices, mAreaTypes, mWater, mTrianglesPerChunk}; + const std::vector mOffMeshConnections {}; + unsigned char* const mData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData mNavMeshData {mData, 1}; + }; + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_empty_cache_should_return_empty_value) + { + const std::size_t maxSize = 0; + NavMeshTilesCache cache(maxSize); + + EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_for_not_enought_cache_size_should_return_empty_value) + { + const std::size_t maxSize = 0; + NavMeshTilesCache cache(maxSize); + + EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, + std::move(mNavMeshData))); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_return_cached_value) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + + const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, + std::move(mNavMeshData)); + EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_existing_element_should_throw_exception) + { + const std::size_t maxSize = 2; + NavMeshTilesCache cache(maxSize); + const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData anotherNavMeshData {anotherData, 1}; + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + EXPECT_THROW( + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(anotherNavMeshData)), + InvalidArgument + ); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_should_return_cached_value) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + const auto result = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); + ASSERT_TRUE(result); + EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_agent_half_extents_should_return_empty_value) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + const osg::Vec3f unexsistentAgentHalfExtents {1, 1, 1}; + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + EXPECT_FALSE(cache.get(unexsistentAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_tile_position_should_return_empty_value) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + const TilePosition unexistentTilePosition {1, 1}; + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + EXPECT_FALSE(cache.get(mAgentHalfExtents, unexistentTilePosition, mRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_recast_mesh_should_return_empty_value) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh unexistentRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_value) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + + const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData anotherNavMeshData {anotherData, 1}; + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + const auto result = cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, + std::move(anotherNavMeshData)); + ASSERT_TRUE(result); + EXPECT_EQ(result.get(), (NavMeshDataRef {anotherData, 1})); + EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_used_value) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + + const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData anotherNavMeshData {anotherData, 1}; + + const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, + std::move(mNavMeshData)); + EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, + std::move(anotherNavMeshData))); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_set_value) + { + const std::size_t maxSize = 2; + NavMeshTilesCache cache(maxSize); + + const std::vector leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh leastRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlySetWater, + mTrianglesPerChunk}; + const auto leastRecentlySetData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData leastRecentlySetNavMeshData {leastRecentlySetData, 1}; + + const std::vector mostRecentlySetWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; + const RecastMesh mostRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlySetWater, + mTrianglesPerChunk}; + const auto mostRecentlySetData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData mostRecentlySetNavMeshData {mostRecentlySetData, 1}; + + ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh, mOffMeshConnections, + std::move(leastRecentlySetNavMeshData))); + ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, mostRecentlySetRecastMesh, mOffMeshConnections, + std::move(mostRecentlySetNavMeshData))); + + const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, + std::move(mNavMeshData)); + EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); + + EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh, mOffMeshConnections)); + EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mostRecentlySetRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_used_value) + { + const std::size_t maxSize = 2; + NavMeshTilesCache cache(maxSize); + + const std::vector leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh leastRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlyUsedWater, + mTrianglesPerChunk}; + const auto leastRecentlyUsedData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData leastRecentlyUsedNavMeshData {leastRecentlyUsedData, 1}; + + const std::vector mostRecentlyUsedWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; + const RecastMesh mostRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlyUsedWater, + mTrianglesPerChunk}; + const auto mostRecentlyUsedData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData mostRecentlyUsedNavMeshData {mostRecentlyUsedData, 1}; + + cache.set(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections, + std::move(leastRecentlyUsedNavMeshData)); + cache.set(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections, + std::move(mostRecentlyUsedNavMeshData)); + + { + const auto value = cache.get(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections); + ASSERT_TRUE(value); + ASSERT_EQ(value.get(), (NavMeshDataRef {leastRecentlyUsedData, 1})); + } + + { + const auto value = cache.get(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections); + ASSERT_TRUE(value); + ASSERT_EQ(value.get(), (NavMeshDataRef {mostRecentlyUsedData, 1})); + } + + const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, + std::move(mNavMeshData)); + EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); + + EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections)); + EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_unused_least_recently_used_value_when_item_does_not_not_fit_cache_max_size) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + + const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh tooLargeRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const auto tooLargeData = reinterpret_cast(dtAlloc(2, DT_ALLOC_PERM)); + NavMeshData tooLargeNavMeshData {tooLargeData, 2}; + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, tooLargeRecastMesh, mOffMeshConnections, + std::move(tooLargeNavMeshData))); + EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_unused_least_recently_used_value_when_item_does_not_not_fit_size_of_unused_items) + { + const std::size_t maxSize = 2; + NavMeshTilesCache cache(maxSize); + + const std::vector anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, anotherWater, mTrianglesPerChunk}; + const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData anotherNavMeshData {anotherData, 1}; + + const std::vector tooLargeWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; + const RecastMesh tooLargeRecastMesh {mIndices, mVertices, mAreaTypes, tooLargeWater, mTrianglesPerChunk}; + const auto tooLargeData = reinterpret_cast(dtAlloc(2, DT_ALLOC_PERM)); + NavMeshData tooLargeNavMeshData {tooLargeData, 2}; + + const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, + std::move(mNavMeshData)); + ASSERT_TRUE(value); + ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, + std::move(anotherNavMeshData))); + EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, tooLargeRecastMesh, mOffMeshConnections, + std::move(tooLargeNavMeshData))); + EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); + EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_used_after_set_then_used_by_get_item_should_left_this_item_available) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + + const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData anotherNavMeshData {anotherData, 1}; + + const auto firstCopy = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + ASSERT_TRUE(firstCopy); + { + const auto secondCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); + ASSERT_TRUE(secondCopy); + } + EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, + std::move(anotherNavMeshData))); + EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); + } + + TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_twice_used_item_should_left_this_item_available) + { + const std::size_t maxSize = 1; + NavMeshTilesCache cache(maxSize); + + const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; + const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); + NavMeshData anotherNavMeshData {anotherData, 1}; + + cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); + const auto firstCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); + ASSERT_TRUE(firstCopy); + { + const auto secondCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); + ASSERT_TRUE(secondCopy); + } + EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, + std::move(anotherNavMeshData))); + EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); + } +} diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index ffef6114c7..1bf1163c70 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -56,6 +56,7 @@ namespace btTriangleMesh mesh; mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); btBvhTriangleMeshShape shape(&mesh, true); + RecastMeshBuilder builder(mSettings, mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); const auto recastMesh = builder.create(); diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index ceb035db8b..de1a6a7847 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -268,7 +268,8 @@ namespace value.target = Nif::ControlledPtr(nullptr); } - void copy(const btTransform& src, Nif::Transformation& dst) { + void copy(const btTransform& src, Nif::Transformation& dst) + { dst.pos = osg::Vec3f(src.getOrigin().x(), src.getOrigin().y(), src.getOrigin().z()); for (int row = 0; row < 3; ++row) for (int column = 0; column < 3; ++column) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index cf45d3212b..e70784f4bc 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -170,6 +170,7 @@ add_component_dir(detournavigator recastmesh tilecachedrecastmeshmanager recastmeshobject + navmeshtilescache ) set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index a61412a2a4..c8930e6b97 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -52,6 +52,7 @@ namespace DetourNavigator , mRecastMeshManager(recastMeshManager) , mOffMeshConnectionsManager(offMeshConnectionsManager) , mShouldStop() + , mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize) { for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i) mThreads.emplace_back([&] { process(); }); @@ -69,7 +70,7 @@ namespace DetourNavigator } void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, - const std::shared_ptr& navMeshCacheItem, const TilePosition& playerTile, + const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile, const std::map& changedTiles) { *mPlayerTile.lock() = playerTile; @@ -129,7 +130,7 @@ namespace DetourNavigator const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile, - offMeshConnections, mSettings, *job.mNavMeshCacheItem); + offMeshConnections, mSettings, job.mNavMeshCacheItem, mNavMeshTilesCache); const auto finish = std::chrono::steady_clock::now(); @@ -137,9 +138,10 @@ namespace DetourNavigator using FloatMs = std::chrono::duration; + const auto locked = job.mNavMeshCacheItem.lockConst(); log("cache updated for agent=", job.mAgentHalfExtents, " status=", status, - " generation=", job.mNavMeshCacheItem->mGeneration, - " revision=", job.mNavMeshCacheItem->mNavMeshRevision, + " generation=", locked->getGeneration(), + " revision=", locked->getNavMeshRevision(), " time=", std::chrono::duration_cast(finish - start).count(), "ms", " total_time=", std::chrono::duration_cast(finish - firstStart).count(), "ms"); } @@ -151,6 +153,7 @@ namespace DetourNavigator mHasJob.wait_for(lock, std::chrono::milliseconds(10)); if (mJobs.empty()) { + mFirstStart.lock()->reset(); mDone.notify_all(); return boost::none; } @@ -183,7 +186,7 @@ namespace DetourNavigator writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x()) + "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision); if (mSettings.get().mEnableWriteNavMeshToFile) - writeToFile(*job.mNavMeshCacheItem->mValue.lock(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); + writeToFile(job.mNavMeshCacheItem.lockConst()->getValue(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } std::chrono::steady_clock::time_point AsyncNavMeshUpdater::setFirstStart(const std::chrono::steady_clock::time_point& value) diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 28d1952a79..c9d7b1940e 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -5,6 +5,7 @@ #include "offmeshconnectionsmanager.hpp" #include "tilecachedrecastmeshmanager.hpp" #include "tileposition.hpp" +#include "navmeshtilescache.hpp" #include @@ -37,7 +38,7 @@ namespace DetourNavigator OffMeshConnectionsManager& offMeshConnectionsManager); ~AsyncNavMeshUpdater(); - void post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr& mNavMeshCacheItem, + void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem, const TilePosition& playerTile, const std::map& changedTiles); void wait(); @@ -46,7 +47,7 @@ namespace DetourNavigator struct Job { osg::Vec3f mAgentHalfExtents; - std::shared_ptr mNavMeshCacheItem; + SharedNavMeshCacheItem mNavMeshCacheItem; TilePosition mChangedTile; std::tuple mPriority; @@ -69,6 +70,7 @@ namespace DetourNavigator std::map> mPushed; Misc::ScopeGuarded mPlayerTile; Misc::ScopeGuarded> mFirstStart; + NavMeshTilesCache mNavMeshTilesCache; std::vector mThreads; void process() throw(); diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 07f2225046..cf6827b643 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -9,6 +9,7 @@ #include "sharednavmesh.hpp" #include "settingsutils.hpp" #include "flags.hpp" +#include "navmeshtilescache.hpp" #include #include @@ -23,6 +24,8 @@ namespace { using namespace DetourNavigator; + static const int doNotTransferOwnership = 0; + void initPolyMeshDetail(rcPolyMeshDetail& value) { value.meshes = nullptr; @@ -42,29 +45,6 @@ namespace using PolyMeshDetailStackPtr = std::unique_ptr; - struct NavMeshDataValueDeleter - { - void operator ()(unsigned char* value) const - { - dtFree(value); - } - }; - - using NavMeshDataValue = std::unique_ptr; - - struct NavMeshData - { - NavMeshDataValue mValue; - int mSize; - - NavMeshData() = default; - - NavMeshData(unsigned char* value, int size) - : mValue(value) - , mSize(size) - {} - }; - osg::Vec3f makeOsgVec3f(const btVector3& value) { return osg::Vec3f(value.x(), value.y(), value.z()); @@ -388,7 +368,7 @@ namespace DetourNavigator UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, const TilePosition& changedTile, const TilePosition& playerTile, const std::vector& offMeshConnections, const Settings& settings, - NavMeshCacheItem& navMeshCacheItem) + const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache) { log("update NavMesh with mutiple tiles:", " agentHeight=", std::setprecision(std::numeric_limits::max_exponent10), @@ -401,17 +381,19 @@ namespace DetourNavigator " playerTile=", playerTile, " changedTileDistance=", getDistance(changedTile, playerTile)); - auto& navMesh = navMeshCacheItem.mValue; - const auto& params = *navMesh.lock()->getParams(); + const auto params = *navMeshCacheItem.lockConst()->getValue().getParams(); const osg::Vec3f origin(params.orig[0], params.orig[1], params.orig[2]); const auto x = changedTile.x(); const auto y = changedTile.y(); const auto removeTile = [&] { - const auto locked = navMesh.lock(); - const auto removed = dtStatusSucceed(locked->removeTile(locked->getTileRefAt(x, y, 0), nullptr, nullptr)); - navMeshCacheItem.mNavMeshRevision += removed; + const auto locked = navMeshCacheItem.lock(); + auto& navMesh = locked->getValue(); + const auto tileRef = navMesh.getTileRefAt(x, y, 0); + const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr)); + if (removed) + locked->removeUsedTile(changedTile); return makeUpdateNavMeshStatus(removed, false); }; @@ -437,42 +419,82 @@ namespace DetourNavigator return removeTile(); } - const auto maxTiles = navMesh.lock()->getParams()->maxTiles; - if (!shouldAddTile(changedTile, playerTile, maxTiles)) + if (!shouldAddTile(changedTile, playerTile, params.maxTiles)) { log("ignore add tile: too far from player"); return removeTile(); } - const auto tileBounds = makeTileBounds(settings, changedTile); - const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y()); - const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y()); - - auto navMeshData = makeNavMeshTileData(agentHalfExtents, *recastMesh, offMeshConnections, x, y, - tileBorderMin, tileBorderMax, settings); + auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, offMeshConnections); - if (!navMeshData.mValue) + if (!cachedNavMeshData) { - log("ignore add tile: NavMeshData is null"); - return removeTile(); - } + const auto tileBounds = makeTileBounds(settings, changedTile); + const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y()); + const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y()); - dtStatus addStatus; - bool removed; - { - const auto locked = navMesh.lock(); - removed = dtStatusSucceed(locked->removeTile(locked->getTileRefAt(x, y, 0), nullptr, nullptr)); - addStatus = locked->addTile(navMeshData.mValue.get(), navMeshData.mSize, DT_TILE_FREE_DATA, 0, 0); + auto navMeshData = makeNavMeshTileData(agentHalfExtents, *recastMesh, offMeshConnections, x, y, + tileBorderMin, tileBorderMax, settings); + + if (!navMeshData.mValue) + { + log("ignore add tile: NavMeshData is null"); + return removeTile(); + } + + try + { + cachedNavMeshData = navMeshTilesCache.set(agentHalfExtents, changedTile, *recastMesh, + offMeshConnections, std::move(navMeshData)); + } + catch (const InvalidArgument&) + { + cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, + offMeshConnections); + } + + if (!cachedNavMeshData) + { + log("cache overflow"); + + const auto locked = navMeshCacheItem.lock(); + auto& navMesh = locked->getValue(); + const auto tileRef = navMesh.getTileRefAt(x, y, 0); + const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr)); + const auto addStatus = navMesh.addTile(navMeshData.mValue.get(), navMeshData.mSize, + doNotTransferOwnership, 0, 0); + + if (dtStatusSucceed(addStatus)) + { + locked->setUsedTile(changedTile, std::move(navMeshData)); + return makeUpdateNavMeshStatus(removed, true); + } + else + { + if (removed) + locked->removeUsedTile(changedTile); + log("failed to add tile with status=", WriteDtStatus {addStatus}); + return makeUpdateNavMeshStatus(removed, false); + } + } } + const auto locked = navMeshCacheItem.lock(); + auto& navMesh = locked->getValue(); + const auto tileRef = navMesh.getTileRefAt(x, y, 0); + const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr)); + const auto addStatus = navMesh.addTile(cachedNavMeshData.get().mValue, cachedNavMeshData.get().mSize, + doNotTransferOwnership, 0, 0); + if (dtStatusSucceed(addStatus)) { - ++navMeshCacheItem.mNavMeshRevision; - navMeshData.mValue.release(); + locked->setUsedTile(changedTile, std::move(cachedNavMeshData)); return makeUpdateNavMeshStatus(removed, true); } else { + if (removed) + locked->removeUsedTile(changedTile); log("failed to add tile with status=", WriteDtStatus {addStatus}); return makeUpdateNavMeshStatus(removed, false); } diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 963e0daa8b..55d3e261c5 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -7,6 +7,7 @@ #include "tileposition.hpp" #include "tilebounds.hpp" #include "sharednavmesh.hpp" +#include "navmeshtilescache.hpp" #include @@ -48,7 +49,7 @@ namespace DetourNavigator UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, const TilePosition& changedTile, const TilePosition& playerTile, const std::vector& offMeshConnections, const Settings& settings, - NavMeshCacheItem& navMeshCacheItem); + const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache); } #endif diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index d185ad02fa..73537ff6f6 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -122,7 +122,7 @@ namespace DetourNavigator mNavMeshManager.wait(); } - std::map> Navigator::getNavMeshes() const + std::map Navigator::getNavMeshes() const { return mNavMeshManager.getNavMeshes(); } diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 9d7723b474..351e0f9f84 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -176,7 +176,7 @@ namespace DetourNavigator "out is not an OutputIterator" ); const auto navMesh = mNavMeshManager.getNavMesh(agentHalfExtents); - return findSmoothPath(*navMesh.lock(), toNavMeshCoordinates(mSettings, agentHalfExtents), + return findSmoothPath(navMesh.lock()->getValue(), toNavMeshCoordinates(mSettings, agentHalfExtents), toNavMeshCoordinates(mSettings, start), toNavMeshCoordinates(mSettings, end), includeFlags, mSettings, out); } @@ -185,7 +185,7 @@ namespace DetourNavigator * @brief getNavMeshes returns all current navmeshes * @return map of agent half extents to navmesh */ - std::map> getNavMeshes() const; + std::map getNavMeshes() const; const Settings& getSettings() const; diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp index 537f6df5d7..e64b9d1383 100644 --- a/components/detournavigator/navmeshcacheitem.hpp +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -2,20 +2,69 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHCACHEITEM_H #include "sharednavmesh.hpp" +#include "tileposition.hpp" +#include "navmeshtilescache.hpp" -#include +#include + +#include namespace DetourNavigator { - struct NavMeshCacheItem + class NavMeshCacheItem { - SharedNavMesh mValue; - std::size_t mGeneration; - std::atomic_size_t mNavMeshRevision; - + public: NavMeshCacheItem(const NavMeshPtr& value, std::size_t generation) - : mValue(value), mGeneration(generation), mNavMeshRevision(0) {} + : mValue(value), mGeneration(generation), mNavMeshRevision(0) + { + } + + const dtNavMesh& getValue() const + { + return *mValue; + } + + dtNavMesh& getValue() + { + return *mValue; + } + + std::size_t getGeneration() const + { + return mGeneration; + } + + std::size_t getNavMeshRevision() const + { + return mNavMeshRevision; + } + + void setUsedTile(const TilePosition& tilePosition, NavMeshTilesCache::Value value) + { + mUsedTiles[tilePosition] = std::make_pair(std::move(value), NavMeshData()); + ++mNavMeshRevision; + } + + void setUsedTile(const TilePosition& tilePosition, NavMeshData value) + { + mUsedTiles[tilePosition] = std::make_pair(NavMeshTilesCache::Value(), std::move(value)); + ++mNavMeshRevision; + } + + void removeUsedTile(const TilePosition& tilePosition) + { + mUsedTiles.erase(tilePosition); + ++mNavMeshRevision; + } + + private: + NavMeshPtr mValue; + std::size_t mGeneration; + std::size_t mNavMeshRevision; + std::map> mUsedTiles; }; + + using SharedNavMeshCacheItem = Misc::SharedGuarded; } #endif diff --git a/components/detournavigator/navmeshdata.hpp b/components/detournavigator/navmeshdata.hpp new file mode 100644 index 0000000000..8ce79614b3 --- /dev/null +++ b/components/detournavigator/navmeshdata.hpp @@ -0,0 +1,35 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDATA_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDATA_H + +#include + +#include +#include + +namespace DetourNavigator +{ + struct NavMeshDataValueDeleter + { + void operator ()(unsigned char* value) const + { + dtFree(value); + } + }; + + using NavMeshDataValue = std::unique_ptr; + + struct NavMeshData + { + NavMeshDataValue mValue; + int mSize; + + NavMeshData() = default; + + NavMeshData(unsigned char* value, int size) + : mValue(value) + , mSize(size) + {} + }; +} + +#endif diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 559aedebbf..9afe105d9f 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -136,11 +136,12 @@ namespace DetourNavigator const auto& cached = getCached(agentHalfExtents); const auto changedTiles = mChangedTiles.find(agentHalfExtents); { - const auto locked = cached->mValue.lock(); + const auto locked = cached.lock(); + const auto& navMesh = locked->getValue(); if (changedTiles != mChangedTiles.end()) { for (const auto& tile : changedTiles->second) - if (locked->getTileAt(tile.first.x(), tile.first.y(), 0)) + if (navMesh.getTileAt(tile.first.x(), tile.first.y(), 0)) { auto tileToPost = tilesToPost.find(tile.first); if (tileToPost == tilesToPost.end()) @@ -153,13 +154,13 @@ namespace DetourNavigator if (changedTiles->second.empty()) mChangedTiles.erase(changedTiles); } - const auto maxTiles = locked->getParams()->maxTiles; + const auto maxTiles = navMesh.getParams()->maxTiles; mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile) { if (tilesToPost.count(tile)) return; const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles); - const auto presentInNavMesh = bool(locked->getTileAt(tile.x(), tile.y(), 0)); + const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0)); if (shouldAdd && !presentInNavMesh) tilesToPost.insert(std::make_pair(tile, ChangeType::add)); else if (!shouldAdd && presentInNavMesh) @@ -169,8 +170,7 @@ namespace DetourNavigator mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); log("cache update posted for agent=", agentHalfExtents, " playerTile=", lastPlayerTile->second, - " recastMeshManagerRevision=", lastRevision, - " changedTiles=", changedTiles->second.size()); + " recastMeshManagerRevision=", lastRevision); } void NavMeshManager::wait() @@ -178,12 +178,12 @@ namespace DetourNavigator mAsyncNavMeshUpdater.wait(); } - SharedNavMesh NavMeshManager::getNavMesh(const osg::Vec3f& agentHalfExtents) const + SharedNavMeshCacheItem NavMeshManager::getNavMesh(const osg::Vec3f& agentHalfExtents) const { - return getCached(agentHalfExtents)->mValue; + return getCached(agentHalfExtents); } - std::map> NavMeshManager::getNavMeshes() const + std::map NavMeshManager::getNavMeshes() const { return mCache; } @@ -209,19 +209,16 @@ namespace DetourNavigator { for (const auto& cached : mCache) { - if (cached.second) - { - auto& tiles = mChangedTiles[cached.first]; - auto tile = tiles.find(tilePosition); - if (tile == tiles.end()) - tiles.insert(std::make_pair(tilePosition, changeType)); - else - tile->second = addChangeType(tile->second, changeType); - } + auto& tiles = mChangedTiles[cached.first]; + auto tile = tiles.find(tilePosition); + if (tile == tiles.end()) + tiles.insert(std::make_pair(tilePosition, changeType)); + else + tile->second = addChangeType(tile->second, changeType); } } - const std::shared_ptr& NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const + const SharedNavMeshCacheItem& NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const { const auto cached = mCache.find(agentHalfExtents); if (cached != mCache.end()) diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 86d9ea5d48..f44cdd2515 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -46,17 +46,17 @@ namespace DetourNavigator void wait(); - SharedNavMesh getNavMesh(const osg::Vec3f& agentHalfExtents) const; + SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const; - std::map> getNavMeshes() const; + std::map getNavMeshes() const; private: const Settings& mSettings; TileCachedRecastMeshManager mRecastMeshManager; OffMeshConnectionsManager mOffMeshConnectionsManager; - std::map> mCache; - std::map> mChangedTiles; AsyncNavMeshUpdater mAsyncNavMeshUpdater; + std::map mCache; + std::map> mChangedTiles; std::size_t mGenerationCounter = 0; std::map mPlayerTile; std::map mLastRecastMeshManagerRevision; @@ -67,7 +67,7 @@ namespace DetourNavigator void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType); - const std::shared_ptr& getCached(const osg::Vec3f& agentHalfExtents) const; + const SharedNavMeshCacheItem& getCached(const osg::Vec3f& agentHalfExtents) const; }; } diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp new file mode 100644 index 0000000000..ff24b86f43 --- /dev/null +++ b/components/detournavigator/navmeshtilescache.cpp @@ -0,0 +1,158 @@ +#include "navmeshtilescache.hpp" +#include "exceptions.hpp" + +namespace DetourNavigator +{ + namespace + { + inline std::string makeNavMeshKey(const RecastMesh& recastMesh, + const std::vector& offMeshConnections) + { + std::string result; + result.reserve( + recastMesh.getIndices().size() * sizeof(int) + + recastMesh.getVertices().size() * sizeof(float) + + recastMesh.getAreaTypes().size() * sizeof(AreaType) + + recastMesh.getWater().size() * sizeof(RecastMesh::Water) + + offMeshConnections.size() * sizeof(OffMeshConnection) + ); + std::copy( + reinterpret_cast(recastMesh.getIndices().data()), + reinterpret_cast(recastMesh.getIndices().data() + recastMesh.getIndices().size()), + std::back_inserter(result) + ); + std::copy( + reinterpret_cast(recastMesh.getVertices().data()), + reinterpret_cast(recastMesh.getVertices().data() + recastMesh.getVertices().size()), + std::back_inserter(result) + ); + std::copy( + reinterpret_cast(recastMesh.getAreaTypes().data()), + reinterpret_cast(recastMesh.getAreaTypes().data() + recastMesh.getAreaTypes().size()), + std::back_inserter(result) + ); + std::copy( + reinterpret_cast(recastMesh.getWater().data()), + reinterpret_cast(recastMesh.getWater().data() + recastMesh.getWater().size()), + std::back_inserter(result) + ); + std::copy( + reinterpret_cast(offMeshConnections.data()), + reinterpret_cast(offMeshConnections.data() + offMeshConnections.size()), + std::back_inserter(result) + ); + return result; + } + } + + NavMeshTilesCache::NavMeshTilesCache(const std::size_t maxNavMeshDataSize) + : mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0) {} + + NavMeshTilesCache::Value NavMeshTilesCache::get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, + const RecastMesh& recastMesh, const std::vector& offMeshConnections) + { + const std::lock_guard lock(mMutex); + + const auto agentValues = mValues.find(agentHalfExtents); + if (agentValues == mValues.end()) + return Value(); + + const auto tileValues = agentValues->second.find(changedTile); + if (tileValues == agentValues->second.end()) + return Value(); + + const auto tile = tileValues->second.find(makeNavMeshKey(recastMesh, offMeshConnections)); + if (tile == tileValues->second.end()) + return Value(); + + acquireItemUnsafe(tile->second); + + return Value(*this, tile->second); + } + + NavMeshTilesCache::Value NavMeshTilesCache::set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, + const RecastMesh& recastMesh, const std::vector& offMeshConnections, + NavMeshData value) + { + const auto navMeshSize = static_cast(value.mSize); + + const std::lock_guard lock(mMutex); + + if (navMeshSize > mMaxNavMeshDataSize) + return Value(); + + if (navMeshSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) + return Value(); + + while (!mFreeItems.empty() && mUsedNavMeshDataSize + navMeshSize > mMaxNavMeshDataSize) + removeLeastRecentlyUsed(); + + const auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections); + const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, navMeshKey); + const auto emplaced = mValues[agentHalfExtents][changedTile].emplace(navMeshKey, iterator); + + if (!emplaced.second) + { + mFreeItems.erase(iterator); + throw InvalidArgument("Set existing cache value"); + } + + iterator->mNavMeshData = std::move(value); + mUsedNavMeshDataSize += navMeshSize; + mFreeNavMeshDataSize += navMeshSize; + + acquireItemUnsafe(iterator); + + return Value(*this, iterator); + } + + void NavMeshTilesCache::removeLeastRecentlyUsed() + { + const auto& item = mFreeItems.back(); + + const auto agentValues = mValues.find(item.mAgentHalfExtents); + if (agentValues == mValues.end()) + return; + + const auto tileValues = agentValues->second.find(item.mChangedTile); + if (tileValues == agentValues->second.end()) + return; + + const auto value = tileValues->second.find(item.mNavMeshKey); + if (value == tileValues->second.end()) + return; + + mUsedNavMeshDataSize -= static_cast(item.mNavMeshData.mSize); + mFreeItems.pop_back(); + + tileValues->second.erase(value); + if (!tileValues->second.empty()) + return; + + agentValues->second.erase(tileValues); + if (!agentValues->second.empty()) + return; + + mValues.erase(agentValues); + } + + void NavMeshTilesCache::acquireItemUnsafe(ItemIterator iterator) + { + if (++iterator->mUseCount > 1) + return; + + mBusyItems.splice(mBusyItems.end(), mFreeItems, iterator); + mFreeNavMeshDataSize -= static_cast(iterator->mNavMeshData.mSize); + } + + void NavMeshTilesCache::releaseItem(ItemIterator iterator) + { + if (--iterator->mUseCount > 0) + return; + + const std::lock_guard lock(mMutex); + + mFreeItems.splice(mFreeItems.begin(), mBusyItems, iterator); + mFreeNavMeshDataSize += static_cast(iterator->mNavMeshData.mSize); + } +} diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp new file mode 100644 index 0000000000..2ecd37bf48 --- /dev/null +++ b/components/detournavigator/navmeshtilescache.hpp @@ -0,0 +1,127 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H + +#include "offmeshconnection.hpp" +#include "navmeshdata.hpp" +#include "recastmesh.hpp" +#include "tileposition.hpp" + +#include +#include +#include +#include + +namespace DetourNavigator +{ + struct NavMeshDataRef + { + unsigned char* mValue; + int mSize; + }; + + class NavMeshTilesCache + { + public: + struct Item + { + std::atomic mUseCount; + osg::Vec3f mAgentHalfExtents; + TilePosition mChangedTile; + std::string mNavMeshKey; + NavMeshData mNavMeshData; + + Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, std::string navMeshKey) + : mUseCount(0) + , mAgentHalfExtents(agentHalfExtents) + , mChangedTile(changedTile) + , mNavMeshKey(std::move(navMeshKey)) + {} + }; + + using ItemIterator = std::list::iterator; + + class Value + { + public: + Value() + : mOwner(nullptr), mIterator() {} + + Value(NavMeshTilesCache& owner, ItemIterator iterator) + : mOwner(&owner), mIterator(iterator) + { + } + + Value(const Value& other) = delete; + + Value(Value&& other) + : mOwner(other.mOwner), mIterator(other.mIterator) + { + other.mIterator = ItemIterator(); + } + + ~Value() + { + if (mIterator != ItemIterator()) + mOwner->releaseItem(mIterator); + } + + Value& operator =(const Value& other) = delete; + + Value& operator =(Value&& other) + { + if (mIterator == other.mIterator) + return *this; + + if (mIterator != ItemIterator()) + mOwner->releaseItem(mIterator); + + mOwner = other.mOwner; + mIterator = other.mIterator; + + other.mIterator = ItemIterator(); + + return *this; + } + + NavMeshDataRef get() const + { + return NavMeshDataRef {mIterator->mNavMeshData.mValue.get(), mIterator->mNavMeshData.mSize}; + } + + operator bool() const + { + return mIterator != ItemIterator(); + } + + private: + NavMeshTilesCache* mOwner; + ItemIterator mIterator; + }; + + NavMeshTilesCache(const std::size_t maxNavMeshDataSize); + + Value get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, + const RecastMesh& recastMesh, const std::vector& offMeshConnections); + + Value set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, + const RecastMesh& recastMesh, const std::vector& offMeshConnections, + NavMeshData value); + + private: + std::mutex mMutex; + std::size_t mMaxNavMeshDataSize; + std::size_t mUsedNavMeshDataSize; + std::size_t mFreeNavMeshDataSize; + std::list mBusyItems; + std::list mFreeItems; + std::map>> mValues; + + void removeLeastRecentlyUsed(); + + void acquireItemUnsafe(ItemIterator iterator); + + void releaseItem(ItemIterator iterator); + }; +} + +#endif diff --git a/components/detournavigator/objectid.hpp b/components/detournavigator/objectid.hpp index cbb933ba01..3b56924b13 100644 --- a/components/detournavigator/objectid.hpp +++ b/components/detournavigator/objectid.hpp @@ -10,7 +10,7 @@ namespace DetourNavigator { public: template - explicit ObjectId(const T* value) throw() + explicit ObjectId(const T value) throw() : mValue(reinterpret_cast(value)) { } diff --git a/components/detournavigator/offmeshconnection.hpp b/components/detournavigator/offmeshconnection.hpp new file mode 100644 index 0000000000..60e8ecbbba --- /dev/null +++ b/components/detournavigator/offmeshconnection.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H + +#include + +namespace DetourNavigator +{ + struct OffMeshConnection + { + osg::Vec3f mStart; + osg::Vec3f mEnd; + }; +} + +#endif diff --git a/components/detournavigator/offmeshconnectionsmanager.hpp b/components/detournavigator/offmeshconnectionsmanager.hpp index 212f821c90..30d7976aec 100644 --- a/components/detournavigator/offmeshconnectionsmanager.hpp +++ b/components/detournavigator/offmeshconnectionsmanager.hpp @@ -5,6 +5,7 @@ #include "settingsutils.hpp" #include "tileposition.hpp" #include "objectid.hpp" +#include "offmeshconnection.hpp" #include @@ -20,12 +21,6 @@ namespace DetourNavigator { - struct OffMeshConnection - { - osg::Vec3f mStart; - osg::Vec3f mEnd; - }; - class OffMeshConnectionsManager { public: diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index b4897d14aa..50a3493f7a 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -1,18 +1,17 @@ #include "recastmesh.hpp" -#include "settings.hpp" #include "exceptions.hpp" #include namespace DetourNavigator { - RecastMesh::RecastMesh(std::vector indices, std::vector vertices, - std::vector areaTypes, std::vector water, const Settings& settings) + RecastMesh::RecastMesh(std::vector indices, std::vector vertices, std::vector areaTypes, + std::vector water, const std::size_t trianglesPerChunk) : mIndices(std::move(indices)) , mVertices(std::move(vertices)) , mAreaTypes(std::move(areaTypes)) , mWater(std::move(water)) - , mChunkyTriMesh(mVertices, mIndices, mAreaTypes, settings.mTrianglesPerChunk) + , mChunkyTriMesh(mVertices, mIndices, mAreaTypes, trianglesPerChunk) { if (getTrianglesCount() != mAreaTypes.size()) throw InvalidArgument("number of flags doesn't match number of triangles: triangles=" diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 12dc73e488..b3e158affa 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -5,6 +5,7 @@ #include "chunkytrimesh.hpp" #include +#include #include #include @@ -13,8 +14,6 @@ namespace DetourNavigator { - struct Settings; - class RecastMesh { public: @@ -24,8 +23,8 @@ namespace DetourNavigator btTransform mTransform; }; - RecastMesh(std::vector indices, std::vector vertices, - std::vector areaTypes, std::vector water, const Settings& settings); + RecastMesh(std::vector indices, std::vector vertices, std::vector areaTypes, + std::vector water, const std::size_t trianglesPerChunk); const std::vector& getIndices() const { diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 3e87aa34b7..8ba3309cee 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -119,7 +119,7 @@ namespace DetourNavigator std::shared_ptr RecastMeshBuilder::create() const { - return std::make_shared(mIndices, mVertices, mAreaTypes, mWater, mSettings); + return std::make_shared(mIndices, mVertices, mAreaTypes, mWater, mSettings.get().mTrianglesPerChunk); } void RecastMeshBuilder::reset() diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index af0565cabd..2f9d0373db 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -6,9 +6,6 @@ #include -#include -#include - class btBoxShape; class btCollisionShape; class btCompoundShape; @@ -18,6 +15,8 @@ class btTriangleCallback; namespace DetourNavigator { + struct Settings; + class RecastMeshBuilder { public: diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index f9f81de962..1c3a72b59c 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -14,8 +14,12 @@ namespace DetourNavigator bool RecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { - if (!mObjects.emplace(id, RecastMeshObject(shape, transform, areaType)).second) + const auto iterator = mObjectsOrder.emplace(mObjectsOrder.end(), RecastMeshObject(shape, transform, areaType)); + if (!mObjects.emplace(id, iterator).second) + { + mObjectsOrder.erase(iterator); return false; + } mShouldRebuild = true; return mShouldRebuild; } @@ -25,7 +29,7 @@ namespace DetourNavigator const auto object = mObjects.find(id); if (object == mObjects.end()) return false; - if (!object->second.update(transform, areaType)) + if (!object->second->update(transform, areaType)) return false; mShouldRebuild = true; return mShouldRebuild; @@ -36,7 +40,8 @@ namespace DetourNavigator const auto object = mObjects.find(id); if (object == mObjects.end()) return boost::none; - const RemovedRecastMeshObject result {object->second.getShape(), object->second.getTransform()}; + const RemovedRecastMeshObject result {object->second->getShape(), object->second->getTransform()}; + mObjectsOrder.erase(object->second); mObjects.erase(object); mShouldRebuild = true; return result; @@ -45,8 +50,12 @@ namespace DetourNavigator bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform) { - if (!mWater.insert(std::make_pair(cellPosition, Water {cellSize, transform})).second) + const auto iterator = mWaterOrder.emplace(mWaterOrder.end(), Water {cellSize, transform}); + if (!mWater.emplace(cellPosition, iterator).second) + { + mWaterOrder.erase(iterator); return false; + } mShouldRebuild = true; return true; } @@ -57,7 +66,8 @@ namespace DetourNavigator if (water == mWater.end()) return boost::none; mShouldRebuild = true; - const auto result = water->second; + const auto result = *water->second; + mWaterOrder.erase(water->second); mWater.erase(water); return result; } @@ -78,10 +88,10 @@ namespace DetourNavigator if (!mShouldRebuild) return; mMeshBuilder.reset(); - for (const auto& v : mWater) - mMeshBuilder.addWater(v.second.mCellSize, v.second.mTransform); - for (const auto& v : mObjects) - mMeshBuilder.addObject(v.second.getShape(), v.second.getTransform(), v.second.getAreaType()); + for (const auto& v : mWaterOrder) + mMeshBuilder.addWater(v.mCellSize, v.mTransform); + for (const auto& v : mObjectsOrder) + mMeshBuilder.addObject(v.getShape(), v.getTransform(), v.getAreaType()); mShouldRebuild = false; } } diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index be0fc35817..f8602e5145 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -13,6 +13,7 @@ #include #include +#include class btCollisionShape; @@ -53,8 +54,10 @@ namespace DetourNavigator private: bool mShouldRebuild; RecastMeshBuilder mMeshBuilder; - std::unordered_map mObjects; - std::map mWater; + std::list mObjectsOrder; + std::unordered_map::iterator> mObjects; + std::list mWaterOrder; + std::map::iterator> mWater; void rebuild(); }; diff --git a/components/detournavigator/recastmeshobject.cpp b/components/detournavigator/recastmeshobject.cpp index 1aac9fd81d..acaf398c16 100644 --- a/components/detournavigator/recastmeshobject.cpp +++ b/components/detournavigator/recastmeshobject.cpp @@ -4,6 +4,7 @@ #include +#include #include namespace DetourNavigator diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 9f6ca97be1..f2eb2be24b 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -28,6 +28,7 @@ namespace DetourNavigator int mRegionMinSize; int mTileSize; std::size_t mAsyncNavMeshUpdaterThreads; + std::size_t mMaxNavMeshTilesCacheSize; std::size_t mMaxPolygonPathSize; std::size_t mMaxSmoothPathSize; std::size_t mTrianglesPerChunk; diff --git a/components/detournavigator/sharednavmesh.hpp b/components/detournavigator/sharednavmesh.hpp index 8045434a18..beb0a3c412 100644 --- a/components/detournavigator/sharednavmesh.hpp +++ b/components/detournavigator/sharednavmesh.hpp @@ -1,9 +1,6 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H -#include - -#include #include class dtNavMesh; @@ -11,7 +8,6 @@ class dtNavMesh; namespace DetourNavigator { using NavMeshPtr = std::shared_ptr; - using SharedNavMesh = Misc::SharedGuarded; } #endif diff --git a/components/misc/guarded.hpp b/components/misc/guarded.hpp index 841b9b8121..db619569a8 100644 --- a/components/misc/guarded.hpp +++ b/components/misc/guarded.hpp @@ -48,6 +48,21 @@ namespace Misc : mValue(std::move(value)) {} + template + ScopeGuarded(Args&& ... args) + : mValue(std::forward(args) ...) + {} + + ScopeGuarded(const ScopeGuarded& other) + : mMutex() + , mValue(other.lock().get()) + {} + + ScopeGuarded(ScopeGuarded&& other) + : mMutex() + , mValue(std::move(other.lock().get())) + {} + Locked lock() { return Locked(mMutex, mValue); diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 991e9fff5e..9c1c926955 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -127,7 +127,9 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) { btTransform trans; trans.setIdentity(); - mCompoundShape->addChildShape(trans, new Resource::TriangleMeshShape(mStaticMesh.get(), true)); + std::unique_ptr child(new Resource::TriangleMeshShape(mStaticMesh.get(), true)); + mCompoundShape->addChildShape(trans, child.get()); + child.release(); mStaticMesh.release(); } mShape->mCollisionShape = mCompoundShape.release(); @@ -139,7 +141,10 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) } if (mAvoidStaticMesh) - mShape->mAvoidCollisionShape = new Resource::TriangleMeshShape(mAvoidStaticMesh.release(), false); + { + mShape->mAvoidCollisionShape = new Resource::TriangleMeshShape(mAvoidStaticMesh.get(), false); + mAvoidStaticMesh.release(); + } return mShape; } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 29333682e4..a76eaf1d6d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -588,6 +588,9 @@ region min size = 8 # Number of background threads to update nav mesh (value >= 1) async nav mesh updater threads = 1 +# Maximum total cached size of all nav mesh tiles in bytes (value >= 0) +max nav mesh tiles cache size = 268435456 + # Maximum size of path over polygons (value > 0) max polygon path size = 1024 From fb655cb04f07c53a7b0240135b37cb8e09d4b16a Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 13 Oct 2018 17:30:47 +0300 Subject: [PATCH 089/133] Remove macroses to check recastnavigation functions result --- components/detournavigator/dtstatus.hpp | 28 ----- components/detournavigator/findsmoothpath.hpp | 108 +++++++++++----- components/detournavigator/makenavmesh.cpp | 118 +++++++++++++++--- components/detournavigator/recastmesh.cpp | 2 +- 4 files changed, 179 insertions(+), 77 deletions(-) diff --git a/components/detournavigator/dtstatus.hpp b/components/detournavigator/dtstatus.hpp index 5360b5ce36..a73d33be1e 100644 --- a/components/detournavigator/dtstatus.hpp +++ b/components/detournavigator/dtstatus.hpp @@ -1,8 +1,6 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_DTSTATUS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_DTSTATUS_H -#include "exceptions.hpp" - #include #include @@ -35,32 +33,6 @@ namespace DetourNavigator stream << status.second << " "; return stream; } - - inline void checkDtStatus(dtStatus status, const char* call, int line) - { - if (!dtStatusSucceed(status)) - { - std::ostringstream message; - message << call << " failed with status=" << WriteDtStatus {status} << " at " __FILE__ ":" << line; - throw NavigatorException(message.str()); - } - } - - inline void checkDtResult(bool result, const char* call, int line) - { - if (!result) - { - std::ostringstream message; - message << call << " failed at " __FILE__ ":" << line; - throw NavigatorException(message.str()); - } - } } -#define OPENMW_CHECK_DT_STATUS(call) \ - do { DetourNavigator::checkDtStatus((call), #call, __LINE__); } while (false) - -#define OPENMW_CHECK_DT_RESULT(call) \ - do { DetourNavigator::checkDtResult((call), #call, __LINE__); } while (false) - #endif diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index 08bfa14da5..81b732b744 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -6,6 +6,7 @@ #include "flags.hpp" #include "settings.hpp" #include "settingsutils.hpp" +#include "debug.hpp" #include #include @@ -97,6 +98,73 @@ namespace DetourNavigator const Settings& mSettings; }; + inline void initNavMeshQuery(dtNavMeshQuery& value, const dtNavMesh& navMesh, const int maxNodes) + { + const auto status = value.init(&navMesh, maxNodes); + if (!dtStatusSucceed(status)) + throw NavigatorException("Failed to init navmesh query"); + } + + struct MoveAlongSurfaceResult + { + osg::Vec3f mResultPos; + std::vector mVisited; + }; + + inline MoveAlongSurfaceResult moveAlongSurface(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef, + const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& filter, + const std::size_t maxVisitedSize) + { + MoveAlongSurfaceResult result; + result.mVisited.resize(maxVisitedSize); + int visitedNumber = 0; + const auto status = navMeshQuery.moveAlongSurface(startRef, startPos.ptr(), endPos.ptr(), + &filter, result.mResultPos.ptr(), result.mVisited.data(), &visitedNumber, static_cast(maxVisitedSize)); + if (!dtStatusSucceed(status)) + { + std::ostringstream message; + message << "Failed to move along surface from " << startPos << " to " << endPos; + throw NavigatorException(message.str()); + } + assert(visitedNumber >= 0); + assert(visitedNumber <= static_cast(maxVisitedSize)); + result.mVisited.resize(static_cast(visitedNumber)); + return result; + } + + inline std::vector findPath(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef, + const dtPolyRef endRef, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& queryFilter, + const std::size_t maxSize) + { + int pathLen = 0; + std::vector result(maxSize); + const auto status = navMeshQuery.findPath(startRef, endRef, startPos.ptr(), endPos.ptr(), &queryFilter, + result.data(), &pathLen, static_cast(maxSize)); + if (!dtStatusSucceed(status)) + { + std::ostringstream message; + message << "Failed to find path over polygons from " << startRef << " to " << endRef; + throw NavigatorException(message.str()); + } + assert(pathLen >= 0); + assert(static_cast(pathLen) <= maxSize); + result.resize(static_cast(pathLen)); + return result; + } + + inline float getPolyHeight(const dtNavMeshQuery& navMeshQuery, const dtPolyRef ref, const osg::Vec3f& pos) + { + float result = 0.0f; + const auto status = navMeshQuery.getPolyHeight(ref, pos.ptr(), &result); + if (!dtStatusSucceed(status)) + { + std::ostringstream message; + message << "Failed to get polygon height ref=" << ref << " pos=" << pos; + throw NavigatorException(message.str()); + } + return result; + } + template OutputIterator makeSmoothPath(const dtNavMesh& navMesh, const dtNavMeshQuery& navMeshQuery, const dtQueryFilter& filter, const osg::Vec3f& start, const osg::Vec3f& end, @@ -139,25 +207,15 @@ namespace DetourNavigator len = STEP_SIZE / len; const osg::Vec3f moveTgt = iterPos + delta * len; + const auto result = moveAlongSurface(navMeshQuery, polygonPath.front(), iterPos, moveTgt, filter, 16); - // Move - osg::Vec3f result; - std::vector visited(16); - int nvisited = 0; - OPENMW_CHECK_DT_STATUS(navMeshQuery.moveAlongSurface(polygonPath.front(), iterPos.ptr(), moveTgt.ptr(), - &filter, result.ptr(), visited.data(), &nvisited, int(visited.size()))); - - assert(nvisited >= 0); - assert(nvisited <= int(visited.size())); - visited.resize(static_cast(nvisited)); - - polygonPath = fixupCorridor(polygonPath, visited); + polygonPath = fixupCorridor(polygonPath, result.mVisited); polygonPath = fixupShortcuts(polygonPath, navMeshQuery); float h = 0; - navMeshQuery.getPolyHeight(polygonPath.front(), result.ptr(), &h); - result.y() = h; - iterPos = result; + navMeshQuery.getPolyHeight(polygonPath.front(), result.mResultPos.ptr(), &h); + iterPos = result.mResultPos; + iterPos.y() = h; // Handle end of path and off-mesh links when close enough. if (endOfPath && inRange(iterPos, steerTarget->steerPos, SLOP, 1.0f)) @@ -203,9 +261,7 @@ namespace DetourNavigator // Move position at the other side of the off-mesh link. iterPos = endPos; - float eh = 0.0f; - OPENMW_CHECK_DT_STATUS(navMeshQuery.getPolyHeight(polygonPath.front(), iterPos.ptr(), &eh)); - iterPos.y() = eh; + iterPos.y() = getPolyHeight(navMeshQuery, polygonPath.front(), iterPos); } } @@ -223,7 +279,7 @@ namespace DetourNavigator const Settings& settings, OutputIterator out) { dtNavMeshQuery navMeshQuery; - OPENMW_CHECK_DT_STATUS(navMeshQuery.init(&navMesh, settings.mMaxNavMeshQueryNodes)); + initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes); dtQueryFilter queryFilter; queryFilter.setIncludeFlags(includeFlags); @@ -239,7 +295,7 @@ namespace DetourNavigator } if (startRef == 0) - throw NavigatorException("start polygon is not found at " __FILE__ ":" + std::to_string(__LINE__)); + throw NavigatorException("Navmesh polygon for start point is not found"); dtPolyRef endRef = 0; osg::Vec3f endPolygonPosition; @@ -252,16 +308,10 @@ namespace DetourNavigator } if (endRef == 0) - throw NavigatorException("end polygon is not found at " __FILE__ ":" + std::to_string(__LINE__)); - - std::vector polygonPath(settings.mMaxPolygonPathSize); - int pathLen = 0; - OPENMW_CHECK_DT_STATUS(navMeshQuery.findPath(startRef, endRef, start.ptr(), end.ptr(), &queryFilter, - polygonPath.data(), &pathLen, static_cast(polygonPath.size()))); - - assert(pathLen >= 0); + throw NavigatorException("Navmesh polygon for end polygon is not found"); - polygonPath.resize(static_cast(pathLen)); + const auto polygonPath = findPath(navMeshQuery, startRef, endRef, start, end, queryFilter, + settings.mMaxPolygonPathSize); if (polygonPath.empty() || polygonPath.back() != endRef) return out; diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index cf6827b643..31ea64f18e 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -102,6 +102,77 @@ namespace return result; } + void createHeightfield(rcContext& context, rcHeightfield& solid, int width, int height, const float* bmin, + const float* bmax, const float cs, const float ch) + { + const auto result = rcCreateHeightfield(&context, solid, width, height, bmin, bmax, cs, ch); + + if (!result) + throw NavigatorException("Failed to create heightfield for navmesh"); + } + + void buildCompactHeightfield(rcContext& context, const int walkableHeight, const int walkableClimb, + rcHeightfield& solid, rcCompactHeightfield& compact) + { + const auto result = rcBuildCompactHeightfield(&context, walkableHeight, + walkableClimb, solid, compact); + + if (!result) + throw NavigatorException("Failed to build compact heightfield for navmesh"); + } + + void erodeWalkableArea(rcContext& context, int walkableRadius, rcCompactHeightfield& compact) + { + const auto result = rcErodeWalkableArea(&context, walkableRadius, compact); + + if (!result) + throw NavigatorException("Failed to erode walkable area for navmesh"); + } + + void buildDistanceField(rcContext& context, rcCompactHeightfield& compact) + { + const auto result = rcBuildDistanceField(&context, compact); + + if (!result) + throw NavigatorException("Failed to build distance field for navmesh"); + } + + void buildRegions(rcContext& context, rcCompactHeightfield& compact, const int borderSize, + const int minRegionArea, const int mergeRegionArea) + { + const auto result = rcBuildRegions(&context, compact, borderSize, minRegionArea, mergeRegionArea); + + if (!result) + throw NavigatorException("Failed to build distance field for navmesh"); + } + + void buildContours(rcContext& context, rcCompactHeightfield& compact, const float maxError, const int maxEdgeLen, + rcContourSet& contourSet, const int buildFlags = RC_CONTOUR_TESS_WALL_EDGES) + { + const auto result = rcBuildContours(&context, compact, maxError, maxEdgeLen, contourSet, buildFlags); + + if (!result) + throw NavigatorException("Failed to build contours for navmesh"); + } + + void buildPolyMesh(rcContext& context, rcContourSet& contourSet, const int maxVertsPerPoly, rcPolyMesh& polyMesh) + { + const auto result = rcBuildPolyMesh(&context, contourSet, maxVertsPerPoly, polyMesh); + + if (!result) + throw NavigatorException("Failed to build poly mesh for navmesh"); + } + + void buildPolyMeshDetail(rcContext& context, const rcPolyMesh& polyMesh, const rcCompactHeightfield& compact, + const float sampleDist, const float sampleMaxError, rcPolyMeshDetail& polyMeshDetail) + { + const auto result = rcBuildPolyMeshDetail(&context, polyMesh, compact, sampleDist, sampleMaxError, + polyMeshDetail); + + if (!result) + throw NavigatorException("Failed to build detail poly mesh for navmesh"); + } + NavMeshData makeNavMeshTileData(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, const std::vector& offMeshConnections, const int tileX, const int tileY, const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax, const Settings& settings) @@ -133,8 +204,7 @@ namespace config.bmax[2] += getBorderSize(settings); rcHeightfield solid; - OPENMW_CHECK_DT_RESULT(rcCreateHeightfield(nullptr, solid, config.width, config.height, - config.bmin, config.bmax, config.cs, config.ch)); + createHeightfield(context, solid, config.width, config.height, config.bmin, config.bmax, config.cs, config.ch); { const auto& chunkyMesh = recastMesh.getChunkyTriMesh(); @@ -181,7 +251,7 @@ namespace areas.data() ); - OPENMW_CHECK_DT_RESULT(rcRasterizeTriangles( + const auto trianglesRasterized = rcRasterizeTriangles( &context, recastMesh.getVertices().data(), static_cast(recastMesh.getVerticesCount()), @@ -190,7 +260,10 @@ namespace static_cast(chunk.mSize), solid, config.walkableClimb - )); + ); + + if (!trianglesRasterized) + throw NavigatorException("Failed to create rasterize triangles from recast mesh for navmesh"); } } @@ -231,7 +304,7 @@ namespace 0, 2, 3, }}; - OPENMW_CHECK_DT_RESULT(rcRasterizeTriangles( + const auto trianglesRasterized = rcRasterizeTriangles( &context, convertedVertices.data(), static_cast(convertedVertices.size() / 3), @@ -240,7 +313,10 @@ namespace static_cast(areas.size()), solid, config.walkableClimb - )); + ); + + if (!trianglesRasterized) + throw NavigatorException("Failed to create rasterize water triangles for navmesh"); } } @@ -254,24 +330,22 @@ namespace const PolyMeshDetailStackPtr polyMeshDetailPtr(&polyMeshDetail); { rcCompactHeightfield compact; + buildCompactHeightfield(context, config.walkableHeight, config.walkableClimb, solid, compact); - OPENMW_CHECK_DT_RESULT(rcBuildCompactHeightfield(&context, config.walkableHeight, config.walkableClimb, - solid, compact)); - OPENMW_CHECK_DT_RESULT(rcErodeWalkableArea(&context, config.walkableRadius, compact)); - OPENMW_CHECK_DT_RESULT(rcBuildDistanceField(&context, compact)); - OPENMW_CHECK_DT_RESULT(rcBuildRegions(&context, compact, config.borderSize, config.minRegionArea, - config.mergeRegionArea)); + erodeWalkableArea(context, config.walkableRadius, compact); + buildDistanceField(context, compact); + buildRegions(context, compact, config.borderSize, config.minRegionArea, config.mergeRegionArea); rcContourSet contourSet; - OPENMW_CHECK_DT_RESULT(rcBuildContours(&context, compact, config.maxSimplificationError, config.maxEdgeLen, - contourSet)); + buildContours(context, compact, config.maxSimplificationError, config.maxEdgeLen, contourSet); if (contourSet.nconts == 0) return NavMeshData(); - OPENMW_CHECK_DT_RESULT(rcBuildPolyMesh(&context, contourSet, config.maxVertsPerPoly, polyMesh)); - OPENMW_CHECK_DT_RESULT(rcBuildPolyMeshDetail(&context, polyMesh, compact, config.detailSampleDist, - config.detailSampleMaxError, polyMeshDetail)); + buildPolyMesh(context, contourSet, config.maxVertsPerPoly, polyMesh); + + buildPolyMeshDetail(context, polyMesh, compact, config.detailSampleDist, config.detailSampleMaxError, + polyMeshDetail); } for (int i = 0; i < polyMesh.npolys; ++i) @@ -323,7 +397,10 @@ namespace unsigned char* navMeshData; int navMeshDataSize; - OPENMW_CHECK_DT_RESULT(dtCreateNavMeshData(¶ms, &navMeshData, &navMeshDataSize)); + const auto navMeshDataCreated = dtCreateNavMeshData(¶ms, &navMeshData, &navMeshDataSize); + + if (!navMeshDataCreated) + throw NavigatorException("Failed to create navmesh tile data"); return NavMeshData(navMeshData, navMeshDataSize); } @@ -360,7 +437,10 @@ namespace DetourNavigator params.maxPolys = maxPolysPerTile; NavMeshPtr navMesh(dtAllocNavMesh(), &dtFreeNavMesh); - OPENMW_CHECK_DT_STATUS(navMesh->init(¶ms)); + const auto status = navMesh->init(¶ms); + + if (!dtStatusSucceed(status)) + throw NavigatorException("Failed to init navmesh"); return navMesh; } diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index 50a3493f7a..f5c4559773 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -14,7 +14,7 @@ namespace DetourNavigator , mChunkyTriMesh(mVertices, mIndices, mAreaTypes, trianglesPerChunk) { if (getTrianglesCount() != mAreaTypes.size()) - throw InvalidArgument("number of flags doesn't match number of triangles: triangles=" + throw InvalidArgument("Number of flags doesn't match number of triangles: triangles=" + std::to_string(getTrianglesCount()) + ", areaTypes=" + std::to_string(mAreaTypes.size())); if (getVerticesCount()) rcCalcBounds(mVertices.data(), static_cast(getVerticesCount()), mBoundsMin.ptr(), mBoundsMax.ptr()); From 0f6a64ad54cb43472404c156d00a30d4eb5b7070 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 13 Oct 2018 20:59:00 +0300 Subject: [PATCH 090/133] Split makeNavMeshTileData --- components/detournavigator/bounds.hpp | 20 ++ components/detournavigator/makenavmesh.cpp | 391 +++++++++++---------- components/detournavigator/recastmesh.cpp | 2 +- components/detournavigator/recastmesh.hpp | 13 +- 4 files changed, 240 insertions(+), 186 deletions(-) create mode 100644 components/detournavigator/bounds.hpp diff --git a/components/detournavigator/bounds.hpp b/components/detournavigator/bounds.hpp new file mode 100644 index 0000000000..a31e410cb4 --- /dev/null +++ b/components/detournavigator/bounds.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_BOUNDS_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_BOUNDS_H + +#include + +namespace DetourNavigator +{ + struct Bounds + { + osg::Vec3f mMin; + osg::Vec3f mMax; + }; + + inline bool isEmpty(const Bounds& value) + { + return value.mMin == value.mMax; + } +} + +#endif diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 31ea64f18e..7f8a7b9d33 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -102,6 +102,37 @@ namespace return result; } + rcConfig makeConfig(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax, + const Settings& settings) + { + rcConfig config; + + config.cs = settings.mCellSize; + config.ch = settings.mCellHeight; + config.walkableSlopeAngle = settings.mMaxSlope; + config.walkableHeight = static_cast(std::ceil(getHeight(settings, agentHalfExtents) / config.ch)); + config.walkableClimb = static_cast(std::floor(getMaxClimb(settings) / config.ch)); + config.walkableRadius = static_cast(std::ceil(getRadius(settings, agentHalfExtents) / config.cs)); + config.maxEdgeLen = static_cast(std::round(settings.mMaxEdgeLen / config.cs)); + config.maxSimplificationError = settings.mMaxSimplificationError; + config.minRegionArea = settings.mRegionMinSize * settings.mRegionMinSize; + config.mergeRegionArea = settings.mRegionMergeSize * settings.mRegionMergeSize; + config.maxVertsPerPoly = settings.mMaxVertsPerPoly; + config.detailSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : config.cs * settings.mDetailSampleDist; + config.detailSampleMaxError = config.ch * settings.mDetailSampleMaxError; + config.borderSize = settings.mBorderSize; + config.width = settings.mTileSize + config.borderSize * 2; + config.height = settings.mTileSize + config.borderSize * 2; + rcVcopy(config.bmin, boundsMin.ptr()); + rcVcopy(config.bmax, boundsMax.ptr()); + config.bmin[0] -= getBorderSize(settings); + config.bmin[2] -= getBorderSize(settings); + config.bmax[0] += getBorderSize(settings); + config.bmax[2] += getBorderSize(settings); + + return config; + } + void createHeightfield(rcContext& context, rcHeightfield& solid, int width, int height, const float* bmin, const float* bmax, const float cs, const float ch) { @@ -111,6 +142,137 @@ namespace throw NavigatorException("Failed to create heightfield for navmesh"); } + bool rasterizeSolidObjectsTriangles(rcContext& context, const RecastMesh& recastMesh, const rcConfig& config, + rcHeightfield& solid) + { + const auto& chunkyMesh = recastMesh.getChunkyTriMesh(); + std::vector areas(chunkyMesh.getMaxTrisPerChunk(), AreaType_null); + const osg::Vec2f tileBoundsMin(config.bmin[0], config.bmin[2]); + const osg::Vec2f tileBoundsMax(config.bmax[0], config.bmax[2]); + std::vector cids; + chunkyMesh.getChunksOverlappingRect(Rect {tileBoundsMin, tileBoundsMax}, std::back_inserter(cids)); + + if (cids.empty()) + return false; + + for (const auto cid : cids) + { + const auto chunk = chunkyMesh.getChunk(cid); + + std::fill( + areas.begin(), + std::min(areas.begin() + static_cast(chunk.mSize), + areas.end()), + AreaType_null + ); + + rcMarkWalkableTriangles( + &context, + config.walkableSlopeAngle, + recastMesh.getVertices().data(), + static_cast(recastMesh.getVerticesCount()), + chunk.mIndices, + static_cast(chunk.mSize), + areas.data() + ); + + for (std::size_t i = 0; i < chunk.mSize; ++i) + areas[i] = chunk.mAreaTypes[i]; + + rcClearUnwalkableTriangles( + &context, + config.walkableSlopeAngle, + recastMesh.getVertices().data(), + static_cast(recastMesh.getVerticesCount()), + chunk.mIndices, + static_cast(chunk.mSize), + areas.data() + ); + + const auto trianglesRasterized = rcRasterizeTriangles( + &context, + recastMesh.getVertices().data(), + static_cast(recastMesh.getVerticesCount()), + chunk.mIndices, + areas.data(), + static_cast(chunk.mSize), + solid, + config.walkableClimb + ); + + if (!trianglesRasterized) + throw NavigatorException("Failed to create rasterize triangles from recast mesh for navmesh"); + } + + return true; + } + + void rasterizeWaterTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, + const Settings& settings, const rcConfig& config, rcHeightfield& solid) + { + const std::array areas {{AreaType_water, AreaType_water}}; + + for (const auto& water : recastMesh.getWater()) + { + const auto bounds = getWaterBounds(water, settings, agentHalfExtents); + + const osg::Vec2f tileBoundsMin( + std::min(config.bmax[0], std::max(config.bmin[0], bounds.mMin.x())), + std::min(config.bmax[2], std::max(config.bmin[2], bounds.mMin.z())) + ); + const osg::Vec2f tileBoundsMax( + std::min(config.bmax[0], std::max(config.bmin[0], bounds.mMax.x())), + std::min(config.bmax[2], std::max(config.bmin[2], bounds.mMax.z())) + ); + + if (tileBoundsMax == tileBoundsMin) + continue; + + const std::array vertices {{ + osg::Vec3f(tileBoundsMin.x(), bounds.mMin.y(), tileBoundsMin.y()), + osg::Vec3f(tileBoundsMin.x(), bounds.mMin.y(), tileBoundsMax.y()), + osg::Vec3f(tileBoundsMax.x(), bounds.mMin.y(), tileBoundsMax.y()), + osg::Vec3f(tileBoundsMax.x(), bounds.mMin.y(), tileBoundsMin.y()), + }}; + + std::array convertedVertices; + auto convertedVerticesIt = convertedVertices.begin(); + + for (const auto& vertex : vertices) + convertedVerticesIt = std::copy(vertex.ptr(), vertex.ptr() + 3, convertedVerticesIt); + + const std::array indices {{ + 0, 1, 2, + 0, 2, 3, + }}; + + const auto trianglesRasterized = rcRasterizeTriangles( + &context, + convertedVertices.data(), + static_cast(convertedVertices.size() / 3), + indices.data(), + areas.data(), + static_cast(areas.size()), + solid, + config.walkableClimb + ); + + if (!trianglesRasterized) + throw NavigatorException("Failed to create rasterize water triangles for navmesh"); + } + } + + bool rasterizeTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, + const rcConfig& config, const Settings& settings, rcHeightfield& solid) + { + if (!rasterizeSolidObjectsTriangles(context, recastMesh, config, solid)) + return false; + + rasterizeWaterTriangles(context, agentHalfExtents, recastMesh, settings, config, solid); + + return true; + } + void buildCompactHeightfield(rcContext& context, const int walkableHeight, const int walkableClimb, rcHeightfield& solid, rcCompactHeightfield& compact) { @@ -173,152 +335,55 @@ namespace throw NavigatorException("Failed to build detail poly mesh for navmesh"); } - NavMeshData makeNavMeshTileData(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, - const std::vector& offMeshConnections, const int tileX, const int tileY, - const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax, const Settings& settings) + void setPolyMeshFlags(rcPolyMesh& polyMesh) { - rcContext context; - rcConfig config; + for (int i = 0; i < polyMesh.npolys; ++i) + { + if (polyMesh.areas[i] == AreaType_ground) + polyMesh.flags[i] = Flag_walk; + else if (polyMesh.areas[i] == AreaType_water) + polyMesh.flags[i] = Flag_swim; + } + } - config.cs = settings.mCellSize; - config.ch = settings.mCellHeight; - config.walkableSlopeAngle = settings.mMaxSlope; - config.walkableHeight = static_cast(std::ceil(getHeight(settings, agentHalfExtents) / config.ch)); - config.walkableClimb = static_cast(std::floor(getMaxClimb(settings) / config.ch)); - config.walkableRadius = static_cast(std::ceil(getRadius(settings, agentHalfExtents) / config.cs)); - config.maxEdgeLen = static_cast(std::round(settings.mMaxEdgeLen / config.cs)); - config.maxSimplificationError = settings.mMaxSimplificationError; - config.minRegionArea = settings.mRegionMinSize * settings.mRegionMinSize; - config.mergeRegionArea = settings.mRegionMergeSize * settings.mRegionMergeSize; - config.maxVertsPerPoly = settings.mMaxVertsPerPoly; - config.detailSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : config.cs * settings.mDetailSampleDist; - config.detailSampleMaxError = config.ch * settings.mDetailSampleMaxError; - config.borderSize = settings.mBorderSize; - config.width = settings.mTileSize + config.borderSize * 2; - config.height = settings.mTileSize + config.borderSize * 2; - rcVcopy(config.bmin, boundsMin.ptr()); - rcVcopy(config.bmax, boundsMax.ptr()); - config.bmin[0] -= getBorderSize(settings); - config.bmin[2] -= getBorderSize(settings); - config.bmax[0] += getBorderSize(settings); - config.bmax[2] += getBorderSize(settings); + bool fillPolyMesh(rcContext& context, const rcConfig& config, rcHeightfield& solid, rcPolyMesh& polyMesh, + rcPolyMeshDetail& polyMeshDetail) + { + rcCompactHeightfield compact; + buildCompactHeightfield(context, config.walkableHeight, config.walkableClimb, solid, compact); - rcHeightfield solid; - createHeightfield(context, solid, config.width, config.height, config.bmin, config.bmax, config.cs, config.ch); + erodeWalkableArea(context, config.walkableRadius, compact); + buildDistanceField(context, compact); + buildRegions(context, compact, config.borderSize, config.minRegionArea, config.mergeRegionArea); - { - const auto& chunkyMesh = recastMesh.getChunkyTriMesh(); - std::vector areas(chunkyMesh.getMaxTrisPerChunk(), AreaType_null); - const osg::Vec2f tileBoundsMin(config.bmin[0], config.bmin[2]); - const osg::Vec2f tileBoundsMax(config.bmax[0], config.bmax[2]); - std::vector cids; - chunkyMesh.getChunksOverlappingRect(Rect {tileBoundsMin, tileBoundsMax}, std::back_inserter(cids)); + rcContourSet contourSet; + buildContours(context, compact, config.maxSimplificationError, config.maxEdgeLen, contourSet); - if (cids.empty()) - return NavMeshData(); + if (contourSet.nconts == 0) + return false; - for (const auto cid : cids) - { - const auto chunk = chunkyMesh.getChunk(cid); - - std::fill( - areas.begin(), - std::min(areas.begin() + static_cast(chunk.mSize), - areas.end()), - AreaType_null - ); - - rcMarkWalkableTriangles( - &context, - config.walkableSlopeAngle, - recastMesh.getVertices().data(), - static_cast(recastMesh.getVerticesCount()), - chunk.mIndices, - static_cast(chunk.mSize), - areas.data() - ); - - for (std::size_t i = 0; i < chunk.mSize; ++i) - areas[i] = chunk.mAreaTypes[i]; - - rcClearUnwalkableTriangles( - &context, - config.walkableSlopeAngle, - recastMesh.getVertices().data(), - static_cast(recastMesh.getVerticesCount()), - chunk.mIndices, - static_cast(chunk.mSize), - areas.data() - ); - - const auto trianglesRasterized = rcRasterizeTriangles( - &context, - recastMesh.getVertices().data(), - static_cast(recastMesh.getVerticesCount()), - chunk.mIndices, - areas.data(), - static_cast(chunk.mSize), - solid, - config.walkableClimb - ); - - if (!trianglesRasterized) - throw NavigatorException("Failed to create rasterize triangles from recast mesh for navmesh"); - } - } + buildPolyMesh(context, contourSet, config.maxVertsPerPoly, polyMesh); - { - const std::array areas {{AreaType_water, AreaType_water}}; + buildPolyMeshDetail(context, polyMesh, compact, config.detailSampleDist, config.detailSampleMaxError, + polyMeshDetail); - for (const auto& water : recastMesh.getWater()) - { - const auto bounds = getWaterBounds(water, settings, agentHalfExtents); - - const osg::Vec2f tileBoundsMin( - std::min(config.bmax[0], std::max(config.bmin[0], bounds.mMin.x())), - std::min(config.bmax[2], std::max(config.bmin[2], bounds.mMin.z())) - ); - const osg::Vec2f tileBoundsMax( - std::min(config.bmax[0], std::max(config.bmin[0], bounds.mMax.x())), - std::min(config.bmax[2], std::max(config.bmin[2], bounds.mMax.z())) - ); - - if (tileBoundsMax == tileBoundsMin) - continue; - - const std::array vertices {{ - osg::Vec3f(tileBoundsMin.x(), bounds.mMin.y(), tileBoundsMin.y()), - osg::Vec3f(tileBoundsMin.x(), bounds.mMin.y(), tileBoundsMax.y()), - osg::Vec3f(tileBoundsMax.x(), bounds.mMin.y(), tileBoundsMax.y()), - osg::Vec3f(tileBoundsMax.x(), bounds.mMin.y(), tileBoundsMin.y()), - }}; - - std::array convertedVertices; - auto convertedVerticesIt = convertedVertices.begin(); - - for (const auto& vertex : vertices) - convertedVerticesIt = std::copy(vertex.ptr(), vertex.ptr() + 3, convertedVerticesIt); - - const std::array indices {{ - 0, 1, 2, - 0, 2, 3, - }}; - - const auto trianglesRasterized = rcRasterizeTriangles( - &context, - convertedVertices.data(), - static_cast(convertedVertices.size() / 3), - indices.data(), - areas.data(), - static_cast(areas.size()), - solid, - config.walkableClimb - ); - - if (!trianglesRasterized) - throw NavigatorException("Failed to create rasterize water triangles for navmesh"); - } - } + setPolyMeshFlags(polyMesh); + + return true; + } + + NavMeshData makeNavMeshTileData(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, + const std::vector& offMeshConnections, const TilePosition& tile, + const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax, const Settings& settings) + { + rcContext context; + const auto config = makeConfig(agentHalfExtents, boundsMin, boundsMax, settings); + + rcHeightfield solid; + createHeightfield(context, solid, config.width, config.height, config.bmin, config.bmax, config.cs, config.ch); + + if (!rasterizeTriangles(context, agentHalfExtents, recastMesh, config, settings, solid)) + return NavMeshData(); rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, solid); rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, solid); @@ -328,33 +393,8 @@ namespace rcPolyMeshDetail polyMeshDetail; initPolyMeshDetail(polyMeshDetail); const PolyMeshDetailStackPtr polyMeshDetailPtr(&polyMeshDetail); - { - rcCompactHeightfield compact; - buildCompactHeightfield(context, config.walkableHeight, config.walkableClimb, solid, compact); - - erodeWalkableArea(context, config.walkableRadius, compact); - buildDistanceField(context, compact); - buildRegions(context, compact, config.borderSize, config.minRegionArea, config.mergeRegionArea); - - rcContourSet contourSet; - buildContours(context, compact, config.maxSimplificationError, config.maxEdgeLen, contourSet); - - if (contourSet.nconts == 0) - return NavMeshData(); - - buildPolyMesh(context, contourSet, config.maxVertsPerPoly, polyMesh); - - buildPolyMeshDetail(context, polyMesh, compact, config.detailSampleDist, config.detailSampleMaxError, - polyMeshDetail); - } - - for (int i = 0; i < polyMesh.npolys; ++i) - { - if (polyMesh.areas[i] == AreaType_ground) - polyMesh.flags[i] = Flag_walk; - else if (polyMesh.areas[i] == AreaType_water) - polyMesh.flags[i] = Flag_swim; - } + if (!fillPolyMesh(context, config, solid, polyMesh, polyMeshDetail)) + return NavMeshData(); const auto offMeshConVerts = getOffMeshVerts(offMeshConnections); const std::vector offMeshConRad(offMeshConnections.size(), getRadius(settings, agentHalfExtents)); @@ -391,8 +431,8 @@ namespace params.ch = config.ch; params.buildBvTree = true; params.userId = 0; - params.tileX = tileX; - params.tileY = tileY; + params.tileX = tile.x(); + params.tileY = tile.y(); params.tileLayer = 0; unsigned char* navMeshData; @@ -483,17 +523,16 @@ namespace DetourNavigator return removeTile(); } - auto boundsMin = recastMesh->getBoundsMin(); - auto boundsMax = recastMesh->getBoundsMax(); + auto recastMeshBounds = recastMesh->getBounds(); for (const auto& water : recastMesh->getWater()) { - const auto bounds = getWaterBounds(water, settings, agentHalfExtents); - boundsMin.y() = std::min(boundsMin.y(), bounds.mMin.y()); - boundsMax.y() = std::max(boundsMax.y(), bounds.mMax.y()); + const auto waterBounds = getWaterBounds(water, settings, agentHalfExtents); + recastMeshBounds.mMin.y() = std::min(recastMeshBounds.mMin.y(), waterBounds.mMin.y()); + recastMeshBounds.mMax.y() = std::max(recastMeshBounds.mMax.y(), waterBounds.mMax.y()); } - if (boundsMin == boundsMax) + if (isEmpty(recastMeshBounds)) { log("ignore add tile: recastMesh is empty"); return removeTile(); @@ -510,10 +549,10 @@ namespace DetourNavigator if (!cachedNavMeshData) { const auto tileBounds = makeTileBounds(settings, changedTile); - const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y()); - const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y()); + const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), recastMeshBounds.mMin.y() - 1, tileBounds.mMin.y()); + const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), recastMeshBounds.mMax.y() + 1, tileBounds.mMax.y()); - auto navMeshData = makeNavMeshTileData(agentHalfExtents, *recastMesh, offMeshConnections, x, y, + auto navMeshData = makeNavMeshTileData(agentHalfExtents, *recastMesh, offMeshConnections, changedTile, tileBorderMin, tileBorderMax, settings); if (!navMeshData.mValue) diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index f5c4559773..476799c1f5 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -17,6 +17,6 @@ namespace DetourNavigator throw InvalidArgument("Number of flags doesn't match number of triangles: triangles=" + std::to_string(getTrianglesCount()) + ", areaTypes=" + std::to_string(mAreaTypes.size())); if (getVerticesCount()) - rcCalcBounds(mVertices.data(), static_cast(getVerticesCount()), mBoundsMin.ptr(), mBoundsMax.ptr()); + rcCalcBounds(mVertices.data(), static_cast(getVerticesCount()), mBounds.mMin.ptr(), mBounds.mMax.ptr()); } } diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index b3e158affa..47d5f79637 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -3,6 +3,7 @@ #include "areatype.hpp" #include "chunkytrimesh.hpp" +#include "bounds.hpp" #include #include @@ -61,14 +62,9 @@ namespace DetourNavigator return mChunkyTriMesh; } - const osg::Vec3f& getBoundsMin() const + const Bounds& getBounds() const { - return mBoundsMin; - } - - const osg::Vec3f& getBoundsMax() const - { - return mBoundsMax; + return mBounds; } private: @@ -77,8 +73,7 @@ namespace DetourNavigator std::vector mAreaTypes; std::vector mWater; ChunkyTriMesh mChunkyTriMesh; - osg::Vec3f mBoundsMin; - osg::Vec3f mBoundsMax; + Bounds mBounds; }; } From e57504ae7c3ceca08bfbc9f9881c8e301ba176f2 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 13 Oct 2018 22:01:37 +0300 Subject: [PATCH 091/133] Lower log level --- apps/openmw/mwmechanics/pathfinding.cpp | 2 +- components/detournavigator/asyncnavmeshupdater.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 3759335e5d..cfa51c226b 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -302,7 +302,7 @@ namespace MWMechanics catch (const DetourNavigator::NavigatorException& exception) { DetourNavigator::log("PathFinder::buildPathByNavigator navigator exception: ", exception.what()); - Log(Debug::Error) << "Build path by navigator exception: " << exception.what(); + Log(Debug::Warning) << "Build path by navigator exception: " << exception.what(); } } } diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index c8930e6b97..505a579b27 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -111,7 +111,7 @@ namespace DetourNavigator catch (const std::exception& e) { DetourNavigator::log("AsyncNavMeshUpdater::process exception: ", e.what()); - ::Log(Debug::Error) << "Exception while process navmesh updated job: " << e.what(); + ::Log(Debug::Warning) << "Exception while process navmesh updated job: " << e.what(); } } log("stop process jobs"); From dc2eb2e16b4a9abf219e183d6e02fb59b021c8f0 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 15 Oct 2018 22:47:07 +0300 Subject: [PATCH 092/133] Do not write to global log async navmesh updater exceptions --- components/detournavigator/asyncnavmeshupdater.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 505a579b27..f4efc744b7 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -111,7 +111,6 @@ namespace DetourNavigator catch (const std::exception& e) { DetourNavigator::log("AsyncNavMeshUpdater::process exception: ", e.what()); - ::Log(Debug::Warning) << "Exception while process navmesh updated job: " << e.what(); } } log("stop process jobs"); From 03d4ce5e49040fc8a4bf07e716a39e272a82694d Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 15 Oct 2018 23:07:52 +0300 Subject: [PATCH 093/133] Log find path exception with level verbose, add more info to message --- apps/openmw/mwmechanics/aipackage.cpp | 2 +- apps/openmw/mwmechanics/aiwander.cpp | 6 ++-- apps/openmw/mwmechanics/pathfinding.cpp | 14 +++++--- apps/openmw/mwmechanics/pathfinding.hpp | 7 ++-- components/detournavigator/flags.hpp | 45 +++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 1532cce018..ac72068263 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -140,7 +140,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path { const auto playerHalfExtents = world->getHalfExtents(world->getPlayerPtr()); - mPathFinder.buildPath(position, dest, actor.getCell(), getPathGridGraph(actor.getCell()), + mPathFinder.buildPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()), playerHalfExtents, getNavigatorFlags(actor)); mRotateOnTheRunChecks = 3; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index e0822e8063..ad11f6d4e9 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -161,8 +161,8 @@ namespace MWMechanics { const auto world = MWBase::Environment::get().getWorld(); const auto playerHalfExtents = world->getHalfExtents(world->getPlayerPtr()); - mPathFinder.buildPath(pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(actor.getCell()), - playerHalfExtents, getNavigatorFlags(actor)); + mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(), + getPathGridGraph(actor.getCell()), playerHalfExtents, getNavigatorFlags(actor)); } if (mPathFinder.isPathConstructed()) @@ -323,7 +323,7 @@ namespace MWMechanics { const auto world = MWBase::Environment::get().getWorld();; const auto playerHalfExtents = world->getHalfExtents(world->getPlayerPtr()); - mPathFinder.buildPath(currentPosition, destinationPosition, actor.getCell(), + mPathFinder.buildPath(actor, currentPosition, destinationPosition, actor.getCell(), getPathGridGraph(actor.getCell()), playerHalfExtents, getNavigatorFlags(actor)); mPathFinder.addPointToPath(destinationPosition); diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index cfa51c226b..01c417b536 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -12,6 +12,7 @@ #include "../mwbase/environment.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" #include "pathgrid.hpp" #include "coordinateconverter.hpp" @@ -275,14 +276,14 @@ namespace MWMechanics mConstructed = true; } - void PathFinder::buildPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags) { mPath.clear(); mCell = cell; - buildPathByNavigatorImpl(startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath)); + buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath)); if (mPath.empty()) buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath)); @@ -290,8 +291,8 @@ namespace MWMechanics mConstructed = true; } - void PathFinder::buildPathByNavigatorImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, - const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, + void PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, + const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, std::back_insert_iterator> out) { try @@ -302,7 +303,10 @@ namespace MWMechanics catch (const DetourNavigator::NavigatorException& exception) { DetourNavigator::log("PathFinder::buildPathByNavigator navigator exception: ", exception.what()); - Log(Debug::Warning) << "Build path by navigator exception: " << exception.what(); + Log(Debug::Verbose) << "Build path by navigator exception: \"" << exception.what() + << "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase() + << ") from " << startPoint << " to " << endPoint << " with flags (" + << DetourNavigator::WriteFlags {flags} << ")"; } } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 1a35ef792e..567056fa5d 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -12,6 +12,7 @@ namespace MWWorld { class CellStore; + class ConstPtr; } namespace MWMechanics @@ -74,7 +75,7 @@ namespace MWMechanics void buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); - void buildPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, + void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags); @@ -180,8 +181,8 @@ namespace MWMechanics void buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const PathgridGraph& pathgridGraph, std::back_insert_iterator> out); - void buildPathByNavigatorImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, - const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, + void buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, + const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, std::back_insert_iterator> out); }; } diff --git a/components/detournavigator/flags.hpp b/components/detournavigator/flags.hpp index f52c0faf5a..df22209bbb 100644 --- a/components/detournavigator/flags.hpp +++ b/components/detournavigator/flags.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_FLAGS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_FLAGS_H +#include + namespace DetourNavigator { using Flags = unsigned short; @@ -12,6 +14,49 @@ namespace DetourNavigator Flag_swim = 1 << 1, Flag_openDoor = 1 << 2, }; + + inline std::ostream& operator <<(std::ostream& stream, const Flag value) + { + switch (value) { + case Flag_none: + return stream << "none"; + case Flag_walk: + return stream << "walk"; + case Flag_swim: + return stream << "swim"; + case Flag_openDoor: + return stream << "openDoor"; + } + } + + struct WriteFlags + { + Flags mValue; + + friend inline std::ostream& operator <<(std::ostream& stream, const WriteFlags& value) + { + if (value.mValue == Flag_none) + { + return stream << Flag_none; + } + else + { + bool first = true; + for (const auto flag : {Flag_walk, Flag_swim, Flag_openDoor}) + { + if (value.mValue & flag) + { + if (!first) + stream << " | "; + first = false; + stream << flag; + } + } + + return stream; + } + } + }; } #endif From 46bf45a6e2f0a30aa8f34c2a6c64406c36a5def7 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 19 Oct 2018 13:09:39 +0400 Subject: [PATCH 094/133] Remove redundant code --- apps/openmw/mwrender/animation.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6e2c76d1d8..f165b6bd3d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -190,12 +190,6 @@ namespace { } - RemoveFinishedCallbackVisitor(int effectId) - : RemoveVisitor() - , mHasMagicEffects(false) - { - } - virtual void apply(osg::Node &node) { traverse(node); @@ -228,9 +222,6 @@ namespace virtual void apply(osg::Geometry&) { } - - private: - int mEffectId; }; class RemoveCallbackVisitor : public RemoveVisitor From 41e90bd56c7e57f632d116f0f5cf10ec6c9906d8 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 19 Oct 2018 14:37:25 +0400 Subject: [PATCH 095/133] Unify random generator usage --- apps/opencs/editor.cpp | 3 +++ components/nifosg/particle.cpp | 12 +++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 450b434e66..6699ccaaf5 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include "model/doc/document.hpp" @@ -355,6 +356,8 @@ int CS::Editor::run() if (mLocal.empty()) return 1; + Misc::Rng::init(); + mStartup.show(); QApplication::setQuitOnLastWindowClosed (true); diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index f5c055a15a..c1f6a2819b 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -81,17 +82,17 @@ ParticleShooter::ParticleShooter(const ParticleShooter ©, const osg::CopyOp void ParticleShooter::shoot(osgParticle::Particle *particle) const { - float hdir = mHorizontalDir + mHorizontalAngle * (2.f * (std::rand() / static_cast(RAND_MAX)) - 1.f); - float vdir = mVerticalDir + mVerticalAngle * (2.f * (std::rand() / static_cast(RAND_MAX)) - 1.f); + float hdir = mHorizontalDir + mHorizontalAngle * (2.f * Misc::Rng::rollClosedProbability() - 1.f); + float vdir = mVerticalDir + mVerticalAngle * (2.f * Misc::Rng::rollClosedProbability() - 1.f); osg::Vec3f dir = (osg::Quat(vdir, osg::Vec3f(0,1,0)) * osg::Quat(hdir, osg::Vec3f(0,0,1))) * osg::Vec3f(0,0,1); - float vel = mMinSpeed + (mMaxSpeed - mMinSpeed) * std::rand() / static_cast(RAND_MAX); + float vel = mMinSpeed + (mMaxSpeed - mMinSpeed) * Misc::Rng::rollClosedProbability(); particle->setVelocity(dir * vel); // Not supposed to set this here, but there doesn't seem to be a better way of doing it - particle->setLifeTime(mLifetime + mLifetimeRandom * std::rand() / static_cast(RAND_MAX)); + particle->setLifeTime(mLifetime + mLifetimeRandom * Misc::Rng::rollClosedProbability()); } GrowFadeAffector::GrowFadeAffector(float growTime, float fadeTime) @@ -277,7 +278,8 @@ void Emitter::emitParticles(double dt) if (!mTargets.empty()) { - int randomRecIndex = mTargets[(std::rand() / (static_cast(RAND_MAX)+1.0)) * mTargets.size()]; + int randomIndex = Misc::Rng::rollClosedProbability() * (mTargets.size() - 1); + int randomRecIndex = mTargets[randomIndex]; // we could use a map here for faster lookup FindGroupByRecIndex visitor(randomRecIndex); From 229d1bb4258b48c274ca0315503f9b0e0812dbdc Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 19 Oct 2018 19:43:19 +0400 Subject: [PATCH 096/133] Backport loop from tinyxml 2.6 to avoid CVE --- extern/oics/tinyxml.cpp | 59 +++++++++++++---------------------------- 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/extern/oics/tinyxml.cpp b/extern/oics/tinyxml.cpp index f1cdc81925..b61df85c87 100644 --- a/extern/oics/tinyxml.cpp +++ b/extern/oics/tinyxml.cpp @@ -1046,58 +1046,35 @@ bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) return false; } - const char* lastPos = buf; - const char* p = buf; + const char* p = buf; // the read head + char* q = buf; // the write head + const char CR = 0x0d; + const char LF = 0x0a; buf[length] = 0; while( *p ) { assert( p < (buf+length) ); - if ( *p == 0xa ) { - // Newline character. No special rules for this. Append all the characters - // since the last string, and include the newline. - data.append( lastPos, (p-lastPos+1) ); // append, include the newline - ++p; // move past the newline - lastPos = p; // and point to the new buffer (may be 0) - assert( p <= (buf+length) ); - } - else if ( *p == 0xd ) { - // Carriage return. Append what we have so far, then - // handle moving forward in the buffer. - if ( (p-lastPos) > 0 ) { - data.append( lastPos, p-lastPos ); // do not add the CR - } - data += (char)0xa; // a proper newline - - if ( *(p+1) == 0xa ) { - // Carriage return - new line sequence - p += 2; - lastPos = p; - assert( p <= (buf+length) ); - } - else { - // it was followed by something else...that is presumably characters again. - ++p; - lastPos = p; - assert( p <= (buf+length) ); + assert( q <= (buf+length) ); + assert( q <= p ); + + if ( *p == CR ) { + *q++ = LF; + p++; + if ( *p == LF ) { // check for CR+LF (and skip LF) + p++; } } else { - ++p; + *q++ = *p++; } } - // Handle any left over characters. - if ( p-lastPos ) { - data.append( lastPos, p-lastPos ); - } - delete [] buf; - buf = 0; + assert( q <= (buf+length) ); + *q = 0; - Parse( data.c_str(), 0, encoding ); + Parse( buf, 0, encoding ); - if ( Error() ) - return false; - else - return true; + delete [] buf; + return !Error(); } From 9809eef18e8587969a787f7e057157592fb8e963 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 20 Oct 2018 22:14:15 +0300 Subject: [PATCH 097/133] Utilize the default soundgen entries when necessary (bug #4689) --- CHANGELOG.md | 1 + apps/openmw/mwclass/activator.cpp | 38 +++++++++++++++++++++---------- apps/openmw/mwclass/creature.cpp | 12 ++++++---- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b707851deb..150c294fc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -144,6 +144,7 @@ Bug #4677: Crash in ESM reader when NPC record has DNAM record without DODT one Bug #4678: Crash in ESP parser when SCVR has no variable names Bug #4685: Missing sound causes an exception inside Say command + Bug #4689: Default creature soundgen entries are not used Feature #912: Editor: Add missing icons to UniversalId tables Feature #1221: Editor: Creature/NPC rendering Feature #1617: Editor: Enchantment effect record verifier diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index e0e2013912..42ea99b007 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -138,7 +138,7 @@ namespace MWClass std::string Activator::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const { - std::string model = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise + const std::string model = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); std::string creatureId; @@ -151,21 +151,35 @@ namespace MWClass } } - if (creatureId.empty()) - return std::string(); - int type = getSndGenTypeFromName(name); - std::vector sounds; - for (auto sound = store.get().begin(); sound != store.get().end(); ++sound) - if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(creatureId, sound->mCreature))) - sounds.push_back(&*sound); + std::vector fallbacksounds; + if (!creatureId.empty()) + { + std::vector sounds; + for (auto sound = store.get().begin(); sound != store.get().end(); ++sound) + { + if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(creatureId, sound->mCreature))) + sounds.push_back(&*sound); + if (type == sound->mType && sound->mCreature.empty()) + fallbacksounds.push_back(&*sound); + } - if (!sounds.empty()) - return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; + if (!sounds.empty()) + return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; + if (!fallbacksounds.empty()) + return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound; + } + else + { + // The activator doesn't have a corresponding creature ID, but we can try to use the defaults + for (auto sound = store.get().begin(); sound != store.get().end(); ++sound) + if (type == sound->mType && sound->mCreature.empty()) + fallbacksounds.push_back(&*sound); - if (type == ESM::SoundGenerator::Land) - return "Body Fall Large"; + if (!fallbacksounds.empty()) + return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound; + } return std::string(); } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 788e5cd689..e03635b0c5 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -632,25 +632,27 @@ namespace MWClass if(type >= 0) { std::vector sounds; + std::vector fallbacksounds; MWWorld::LiveCellRef* ref = ptr.get(); const std::string& ourId = (ref->mBase->mOriginal.empty()) ? ptr.getCellRef().getRefId() : ref->mBase->mOriginal; MWWorld::Store::iterator sound = store.begin(); - while(sound != store.end()) + while (sound != store.end()) { if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(ourId, sound->mCreature))) sounds.push_back(&*sound); + if (type == sound->mType && sound->mCreature.empty()) + fallbacksounds.push_back(&*sound); ++sound; } - if(!sounds.empty()) + if (!sounds.empty()) return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; + if (!fallbacksounds.empty()) + return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound; } - if (type == ESM::SoundGenerator::Land) - return "Body Fall Large"; - return ""; } From 61e6e359c4595fd28f48dbb1a106931737c8d8c3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 22 Oct 2018 15:14:25 +0400 Subject: [PATCH 098/133] Allow creatures to use the autogenerated collision box (feature #2787) --- CHANGELOG.md | 1 + apps/openmw/mwphysics/actor.cpp | 26 ++++++++++++++++++++++++++ components/resource/bulletshape.hpp | 3 +-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b707851deb..a4e7364678 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -149,6 +149,7 @@ Feature #1617: Editor: Enchantment effect record verifier Feature #1645: Casting effects from objects Feature #2606: Editor: Implemented (optional) case sensitive global search + Feature #2787: Use the autogenerated collision box, if the creature mesh has no predefined one Feature #2847: Content selector: allow to copy the path to a file by using the context menu Feature #3083: Play animation when NPC is casting spell via script Feature #3103: Provide option for disposition to get increased by successful trade diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 79c6dcabfc..b55c20455a 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "../mwworld/class.hpp" @@ -28,6 +29,31 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr mHalfExtents = shape->mCollisionBoxHalfExtents; mMeshTranslation = shape->mCollisionBoxTranslate; + // We can not create actor without collisions - he will fall through the ground. + // In this case we should autogenerate collision box based on mesh shape + // (NPCs have bodyparts and use a different approach) + if (!ptr.getClass().isNpc() && mHalfExtents.length2() == 0.f) + { + const Resource::BulletShape* collisionShape = shape.get(); + if (collisionShape && collisionShape->mCollisionShape) + { + btTransform transform; + transform.setIdentity(); + btVector3 min; + btVector3 max; + + collisionShape->mCollisionShape->getAabb(transform, min, max); + mHalfExtents.x() = (max[0] - min[0])/2.f; + mHalfExtents.y() = (max[1] - min[1])/2.f; + mHalfExtents.z() = (max[2] - min[2])/2.f; + + mMeshTranslation = osg::Vec3f(0.f, 0.f, mHalfExtents.z()); + } + + if (mHalfExtents.length2() == 0.f) + Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\"."; + } + // Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it) if (std::abs(mHalfExtents.x()-mHalfExtents.y())= mHalfExtents.x()) { diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index a418bb28ce..b30b5045c2 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -26,8 +26,7 @@ namespace Resource btCollisionShape* mCollisionShape; - // Used for actors. Note, ideally actors would use a separate loader - as it is - // we have to keep a redundant copy of the actor model around in mCollisionShape, which isn't used. + // Used for actors. mCollisionShape is used for actors only when we need to autogenerate collision box for creatures. // For now, use one file <-> one resource for simplicity. osg::Vec3f mCollisionBoxHalfExtents; osg::Vec3f mCollisionBoxTranslate; From 67de61e1fb3bf14ef0b5f6bb1cecc3b6b4d3d363 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 24 Oct 2018 18:51:34 +0300 Subject: [PATCH 099/133] Avoid item condition and charge zero divisions --- apps/openmw/mwclass/armor.cpp | 2 +- apps/openmw/mwclass/npc.cpp | 11 +++++- apps/openmw/mwclass/weapon.cpp | 2 +- apps/openmw/mwgui/merchantrepair.cpp | 2 +- apps/openmw/mwgui/sortfilteritemmodel.cpp | 48 +++++++++++++++++++++-- apps/openmw/mwgui/tradewindow.cpp | 11 +++++- apps/openmw/mwgui/windowmanagerimp.cpp | 29 ++++++++++++-- apps/openmw/mwmechanics/combat.cpp | 11 +++++- 8 files changed, 100 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index b90c1ec581..ad64ad0d1e 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -297,7 +297,7 @@ namespace MWClass { const MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc); - if (ptr.getCellRef().getCharge() == 0) + if (getItemHealth(ptr) == 0) return std::make_pair(0, "#{sInventoryMessage1}"); // slots that this item can be equipped in diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index d6dafd2a25..f7172ac0b9 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1148,9 +1148,16 @@ namespace MWClass const bool hasHealth = it->getClass().hasItemHealth(*it); if (hasHealth) { - int armorHealth = it->getClass().getItemHealth(*it); int armorMaxHealth = it->getClass().getItemMaxHealth(*it); - ratings[i] *= (float(armorHealth) / armorMaxHealth); + if (armorMaxHealth == 0) + { + ratings[i] = 0; + } + else + { + int armorHealth = it->getClass().getItemHealth(*it); + ratings[i] *= (float(armorHealth) / armorMaxHealth); + } } } } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 78678f4617..7d28c89835 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -383,7 +383,7 @@ namespace MWClass std::pair Weapon::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { - if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0) + if (hasItemHealth(ptr) && getItemHealth(ptr) == 0) return std::make_pair(0, "#{sInventoryMessage1}"); // Do not allow equip weapons from inventory during attack diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index b9d4c80f4f..282c0e4ea2 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -51,7 +51,7 @@ void MerchantRepair::setPtr(const MWWorld::Ptr &actor) { int maxDurability = iter->getClass().getItemMaxHealth(*iter); int durability = iter->getClass().getItemHealth(*iter); - if (maxDurability == durability) + if (maxDurability == durability || maxDurability == 0) continue; int basePrice = iter->getClass().getValue(*iter); diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index fe7f619524..5172c85ee8 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -86,26 +86,66 @@ namespace if (!leftName.empty()) { const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(leftName); + if (ench) { if (ench->mData.mType == ESM::Enchantment::ConstantEffect) + { leftChargePercent = 101; + } else - leftChargePercent = (left.mBase.getCellRef().getEnchantmentCharge() == -1) ? 100 - : static_cast(left.mBase.getCellRef().getEnchantmentCharge() / static_cast(ench->mData.mCharge) * 100); + { + int maxEnchCharge = ench->mData.mCharge; + if (maxEnchCharge == 0) + { + leftChargePercent = 0; + } + else + { + float enchCharge = left.mBase.getCellRef().getEnchantmentCharge(); + if (enchCharge == -1) + { + leftChargePercent = 100; + } + else + { + leftChargePercent = static_cast(enchCharge / static_cast(maxEnchCharge) * 100); + } + } + } } } if (!rightName.empty()) { const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(rightName); + if (ench) { if (ench->mData.mType == ESM::Enchantment::ConstantEffect) + { rightChargePercent = 101; + } else - rightChargePercent = (right.mBase.getCellRef().getEnchantmentCharge() == -1) ? 100 - : static_cast(right.mBase.getCellRef().getEnchantmentCharge() / static_cast(ench->mData.mCharge) * 100); + { + int maxEnchCharge = ench->mData.mCharge; + if (maxEnchCharge == 0) + { + rightChargePercent = 0; + } + else + { + float enchCharge = right.mBase.getCellRef().getEnchantmentCharge(); + if (enchCharge == -1) + { + rightChargePercent = 100; + } + else + { + rightChargePercent = static_cast(enchCharge / static_cast(maxEnchCharge) * 100); + } + } + } } } diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 4404b2b1a6..2fd91fd4a0 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -35,8 +35,15 @@ namespace float price = static_cast(item.getClass().getValue(item)); if (item.getClass().hasItemHealth(item)) { - price *= item.getClass().getItemHealth(item); - price /= item.getClass().getItemMaxHealth(item); + if (item.getClass().getItemMaxHealth(item) == 0) + { + price = 0; + } + else + { + price *= item.getClass().getItemHealth(item); + price /= item.getClass().getItemMaxHealth(item); + } } return static_cast(price * count); } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index e4515fdc3f..3b26edecb8 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1369,8 +1369,22 @@ namespace MWGui const ESM::Enchantment* ench = mStore->get() .find(item.getClass().getEnchantment(item)); - int chargePercent = (item.getCellRef().getEnchantmentCharge() == -1) ? 100 - : static_cast(item.getCellRef().getEnchantmentCharge() / static_cast(ench->mData.mCharge) * 100); + int chargePercent = 100; + + int maxEnchCharge = ench->mData.mCharge; + if (maxEnchCharge == 0) + { + chargePercent = 0; + } + else + { + float enchCharge = item.getCellRef().getEnchantmentCharge(); + if (enchCharge != -1) + { + chargePercent = static_cast(enchCharge / static_cast(maxEnchCharge) * 100); + } + } + mHud->setSelectedEnchantItem(item, chargePercent); mSpellWindow->setTitle(item.getClass().getName(item)); } @@ -1386,7 +1400,16 @@ namespace MWGui int durabilityPercent = 100; if (item.getClass().hasItemHealth(item)) { - durabilityPercent = static_cast(item.getClass().getItemHealth(item) / static_cast(item.getClass().getItemMaxHealth(item)) * 100); + int weapmaxhealth = item.getClass().getItemMaxHealth(item); + if (weapmaxhealth == 0) + { + durabilityPercent = 0; + } + else + { + int weaphealth = item.getClass().getItemHealth(item); + durabilityPercent = static_cast(weaphealth / static_cast(weapmaxhealth) * 100); + } } mHud->setSelectedWeapon(item, durabilityPercent); mInventoryWindow->setTitle(item.getClass().getName(item)); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index be55b681f1..2a9ff5e84f 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -371,10 +371,17 @@ namespace MWMechanics return; const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); - if(weaphashealth) + if (weaphashealth) { - int weaphealth = weapon.getClass().getItemHealth(weapon); int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); + + if (weapmaxhealth == 0) + { + damage = 0; + return; + } + + int weaphealth = weapon.getClass().getItemHealth(weapon); damage *= (float(weaphealth) / weapmaxhealth); } From 54bd7b2dcff287451c2eea0b318b098ad5e90a35 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Thu, 25 Oct 2018 15:45:31 +0300 Subject: [PATCH 100/133] Implement getItemNormalizedHealth() method and use it --- apps/openmw/mwclass/npc.cpp | 11 +---------- apps/openmw/mwgui/tradewindow.cpp | 13 ++----------- apps/openmw/mwgui/windowmanagerimp.cpp | 12 ++---------- apps/openmw/mwmechanics/combat.cpp | 11 +---------- apps/openmw/mwworld/class.cpp | 12 ++++++++++++ apps/openmw/mwworld/class.hpp | 3 +++ 6 files changed, 21 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index f7172ac0b9..b8b1e9600d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1148,16 +1148,7 @@ namespace MWClass const bool hasHealth = it->getClass().hasItemHealth(*it); if (hasHealth) { - int armorMaxHealth = it->getClass().getItemMaxHealth(*it); - if (armorMaxHealth == 0) - { - ratings[i] = 0; - } - else - { - int armorHealth = it->getClass().getItemHealth(*it); - ratings[i] *= (float(armorHealth) / armorMaxHealth); - } + ratings[i] *= it->getClass().getItemNormalizedHealth(*it); } } } diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 2fd91fd4a0..ce8193da25 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -34,17 +34,8 @@ namespace { float price = static_cast(item.getClass().getValue(item)); if (item.getClass().hasItemHealth(item)) - { - if (item.getClass().getItemMaxHealth(item) == 0) - { - price = 0; - } - else - { - price *= item.getClass().getItemHealth(item); - price /= item.getClass().getItemMaxHealth(item); - } - } + price *= item.getClass().getItemNormalizedHealth(item); + return static_cast(price * count); } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 3b26edecb8..27ea534a74 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1400,17 +1400,9 @@ namespace MWGui int durabilityPercent = 100; if (item.getClass().hasItemHealth(item)) { - int weapmaxhealth = item.getClass().getItemMaxHealth(item); - if (weapmaxhealth == 0) - { - durabilityPercent = 0; - } - else - { - int weaphealth = item.getClass().getItemHealth(item); - durabilityPercent = static_cast(weaphealth / static_cast(weapmaxhealth) * 100); - } + durabilityPercent = static_cast(item.getClass().getItemNormalizedHealth(item)); } + mHud->setSelectedWeapon(item, durabilityPercent); mInventoryWindow->setTitle(item.getClass().getName(item)); } diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 2a9ff5e84f..41e2485ce0 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -373,16 +373,7 @@ namespace MWMechanics const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); if (weaphashealth) { - int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); - - if (weapmaxhealth == 0) - { - damage = 0; - return; - } - - int weaphealth = weapon.getClass().getItemHealth(weapon); - damage *= (float(weaphealth) / weapmaxhealth); + damage *= weapon.getClass().getItemNormalizedHealth(weapon); } static const float fDamageStrengthBase = MWBase::Environment::get().getWorld()->getStore().get() diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 68d5998ac9..70fde33cfb 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -83,6 +83,18 @@ namespace MWWorld return ptr.getCellRef().getCharge(); } + float Class::getItemNormalizedHealth (const ConstPtr& ptr) const + { + if (getItemMaxHealth(ptr) == 0) + { + return 0.f; + } + else + { + return getItemHealth(ptr) / static_cast(getItemMaxHealth(ptr)); + } + } + int Class::getItemMaxHealth (const ConstPtr& ptr) const { throw std::runtime_error ("class does not have item health"); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 132b7e9e87..af88b0dcca 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -112,6 +112,9 @@ namespace MWWorld virtual int getItemHealth (const ConstPtr& ptr) const; ///< Return current item health or throw an exception if class does not have item health + virtual float getItemNormalizedHealth (const ConstPtr& ptr) const; + ///< Return current item health re-scaled to maximum health + virtual int getItemMaxHealth (const ConstPtr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health /// (default implementation: throw an exception) From c3e8d536cdbfedd8dccdfac24b9fd10299376f1b Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Thu, 25 Oct 2018 16:09:07 +0300 Subject: [PATCH 101/133] Implement getNormalizedEnchantmentCharge() method and use it --- apps/openmw/mwgui/sortfilteritemmodel.cpp | 46 +---------------------- apps/openmw/mwgui/tradewindow.cpp | 3 +- apps/openmw/mwgui/windowmanagerimp.cpp | 18 +-------- apps/openmw/mwworld/cellref.cpp | 16 ++++++++ apps/openmw/mwworld/cellref.hpp | 3 ++ 5 files changed, 24 insertions(+), 62 deletions(-) diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 5172c85ee8..23f8a121b3 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -86,66 +86,24 @@ namespace if (!leftName.empty()) { const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(leftName); - if (ench) { if (ench->mData.mType == ESM::Enchantment::ConstantEffect) - { leftChargePercent = 101; - } else - { - int maxEnchCharge = ench->mData.mCharge; - if (maxEnchCharge == 0) - { - leftChargePercent = 0; - } - else - { - float enchCharge = left.mBase.getCellRef().getEnchantmentCharge(); - if (enchCharge == -1) - { - leftChargePercent = 100; - } - else - { - leftChargePercent = static_cast(enchCharge / static_cast(maxEnchCharge) * 100); - } - } - } + leftChargePercent = static_cast(left.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100); } } if (!rightName.empty()) { const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(rightName); - if (ench) { if (ench->mData.mType == ESM::Enchantment::ConstantEffect) - { rightChargePercent = 101; - } else - { - int maxEnchCharge = ench->mData.mCharge; - if (maxEnchCharge == 0) - { - rightChargePercent = 0; - } - else - { - float enchCharge = right.mBase.getCellRef().getEnchantmentCharge(); - if (enchCharge == -1) - { - rightChargePercent = 100; - } - else - { - rightChargePercent = static_cast(enchCharge / static_cast(maxEnchCharge) * 100); - } - } - } + rightChargePercent = static_cast(right.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100); } } diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index ce8193da25..90698dfc4f 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -34,8 +34,9 @@ namespace { float price = static_cast(item.getClass().getValue(item)); if (item.getClass().hasItemHealth(item)) + { price *= item.getClass().getItemNormalizedHealth(item); - + } return static_cast(price * count); } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 27ea534a74..5ffed48156 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1369,22 +1369,7 @@ namespace MWGui const ESM::Enchantment* ench = mStore->get() .find(item.getClass().getEnchantment(item)); - int chargePercent = 100; - - int maxEnchCharge = ench->mData.mCharge; - if (maxEnchCharge == 0) - { - chargePercent = 0; - } - else - { - float enchCharge = item.getCellRef().getEnchantmentCharge(); - if (enchCharge != -1) - { - chargePercent = static_cast(enchCharge / static_cast(maxEnchCharge) * 100); - } - } - + int chargePercent = static_cast(item.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100); mHud->setSelectedEnchantItem(item, chargePercent); mSpellWindow->setTitle(item.getClass().getName(item)); } @@ -1402,7 +1387,6 @@ namespace MWGui { durabilityPercent = static_cast(item.getClass().getItemNormalizedHealth(item)); } - mHud->setSelectedWeapon(item, durabilityPercent); mInventoryWindow->setTitle(item.getClass().getName(item)); } diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index 72ee56e6a1..094669bddf 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -70,6 +70,22 @@ namespace MWWorld return mCellRef.mEnchantmentCharge; } + float CellRef::getNormalizedEnchantmentCharge(int maxCharge) const + { + if (maxCharge == 0) + { + return 0; + } + else if (mCellRef.mEnchantmentCharge == -1) + { + return 1; + } + else + { + return mCellRef.mEnchantmentCharge / static_cast(maxCharge); + } + } + void CellRef::setEnchantmentCharge(float charge) { if (charge != mCellRef.mEnchantmentCharge) diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 7e27e6ef33..5646bafb04 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -56,6 +56,9 @@ namespace MWWorld // Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full). float getEnchantmentCharge() const; + // Remaining enchantment charge rescaled to the supplied maximum charge (such as one of the enchantment). + float getNormalizedEnchantmentCharge(int maxCharge) const; + void setEnchantmentCharge(float charge); // For weapon or armor, this is the remaining item health. From e7de6b974aac469adfa9bb23956ac961104133bf Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 21 Sep 2018 16:34:23 +0400 Subject: [PATCH 102/133] Optimize actors processing 1. Do not update physics and animations for actors outside processing range (bug #4647) 2. Do not render such actors 3. Add transparency to actors near processing border, so they will not pop up suddenly --- apps/openmw/mwbase/mechanicsmanager.hpp | 4 + apps/openmw/mwbase/world.hpp | 3 + apps/openmw/mwgui/settingswindow.cpp | 2 + apps/openmw/mwmechanics/actors.cpp | 97 ++++++++++++------- apps/openmw/mwmechanics/actors.hpp | 4 + apps/openmw/mwmechanics/aipackage.cpp | 4 +- apps/openmw/mwmechanics/aitravel.cpp | 6 +- apps/openmw/mwmechanics/character.cpp | 52 ++++++---- apps/openmw/mwmechanics/character.hpp | 3 +- .../mwmechanics/mechanicsmanagerimp.cpp | 19 ++++ .../mwmechanics/mechanicsmanagerimp.hpp | 6 ++ apps/openmw/mwphysics/physicssystem.cpp | 27 ++++++ apps/openmw/mwphysics/physicssystem.hpp | 2 + apps/openmw/mwrender/animation.cpp | 32 +++--- apps/openmw/mwworld/worldimp.cpp | 16 +++ apps/openmw/mwworld/worldimp.hpp | 3 + .../reference/modding/settings/game.rst | 15 ++- files/mygui/openmw_settings_window.layout | 33 ++++++- files/settings-default.cfg | 3 + 19 files changed, 258 insertions(+), 73 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 8137bad959..0566282118 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -233,6 +233,10 @@ namespace MWBase virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell) = 0; + virtual void processChangedSettings (const std::set< std::pair >& settings) = 0; + + virtual float getActorsProcessingRange() const = 0; + /// Check if the target actor was detected by an observer /// If the observer is a non-NPC, check all actors in AI processing distance as observers virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 027d1fd102..864b811583 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -302,6 +302,9 @@ namespace MWBase virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0; + virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled) = 0; + virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0; + virtual bool toggleCollisionMode() = 0; ///< Toggle collision mode for player. If disabled player object should ignore /// collisions and gravity. diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 80ed9202a0..6e6924f28e 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -20,6 +20,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "confirmationdialog.hpp" @@ -437,6 +438,7 @@ namespace MWGui MWBase::Environment::get().getSoundManager()->processChangedSettings(changed); MWBase::Environment::get().getWindowManager()->processChangedSettings(changed); MWBase::Environment::get().getInputManager()->processChangedSettings(changed); + MWBase::Environment::get().getMechanicsManager()->processChangedSettings(changed); } void SettingsWindow::onKeyboardSwitchClicked(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 2a68591a8f..d4350e07c9 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -147,9 +147,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float namespace MWMechanics { - const float aiProcessingDistance = 7168; - const float sqrAiProcessingDistance = aiProcessingDistance*aiProcessingDistance; - class SoulTrap : public MWMechanics::EffectSourceVisitor { MWWorld::Ptr mCreature; @@ -364,7 +361,8 @@ namespace MWMechanics const ESM::Position& actor1Pos = actor1.getRefData().getPosition(); const ESM::Position& actor2Pos = actor2.getRefData().getPosition(); float sqrDist = (actor1Pos.asVec3() - actor2Pos.asVec3()).length2(); - if (sqrDist > sqrAiProcessingDistance) + + if (sqrDist > mActorsProcessingRange*mActorsProcessingRange) return; // No combat for totally static creatures @@ -1130,8 +1128,11 @@ namespace MWMechanics } } - Actors::Actors() { + Actors::Actors() + { mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning + + updateProcessingRange(); } Actors::~Actors() @@ -1139,6 +1140,23 @@ namespace MWMechanics clear(); } + float Actors::getProcessingRange() const + { + return mActorsProcessingRange; + } + + void Actors::updateProcessingRange() + { + // We have to cap it since using high values (larger than 7168) will make some quests harder or impossible to complete (bug #1876) + static const float maxProcessingRange = 7168.f; + static const float minProcessingRange = maxProcessingRange / 2.f; + + float actorsProcessingRange = Settings::Manager::getFloat("actors processing range", "Game"); + actorsProcessingRange = std::min(actorsProcessingRange, maxProcessingRange); + actorsProcessingRange = std::max(actorsProcessingRange, minProcessingRange); + mActorsProcessingRange = actorsProcessingRange; + } + void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately) { removeActor(ptr); @@ -1184,7 +1202,7 @@ namespace MWMechanics // Otherwise check if any actor in AI processing range sees the target actor std::vector actors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); - getObjectsInRange(position, aiProcessingDistance, actors); + getObjectsInRange(position, mActorsProcessingRange, actors); for(std::vector::iterator it = actors.begin(); it != actors.end(); ++it) { if (*it == actor) @@ -1242,7 +1260,7 @@ namespace MWMechanics { if (iter->first == player) continue; - bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= sqrAiProcessingDistance; + bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= mActorsProcessingRange*mActorsProcessingRange; if (inProcessingRange) { MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); @@ -1288,7 +1306,8 @@ namespace MWMechanics if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0; // show torches only when there are darkness and no precipitations - bool showTorches = MWBase::Environment::get().getWorld()->useTorches(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + bool showTorches = world->useTorches(); MWWorld::Ptr player = getPlayer(); const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); @@ -1301,7 +1320,7 @@ namespace MWMechanics int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId(); if (attackedByPlayerId != -1) { - const MWWorld::Ptr playerHitAttemptActor = MWBase::Environment::get().getWorld()->searchPtrViaActorId(attackedByPlayerId); + const MWWorld::Ptr playerHitAttemptActor = world->searchPtrViaActorId(attackedByPlayerId); if (!playerHitAttemptActor.isInCell()) player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); @@ -1314,14 +1333,11 @@ namespace MWMechanics CharacterController* ctrl = iter->second->getCharacterController(); float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2(); - // AI processing is only done within distance of 7168 units to the player. Note the "AI distance" slider doesn't affect this - // (it only does some throttling for targets beyond the "AI distance", so doesn't give any guarantees as to whether AI will be enabled or not) - // This distance could be made configurable later, but the setting must be marked with a big warning: - // using higher values will make a quest in Bloodmoon harder or impossible to complete (bug #1876) - bool inProcessingRange = distSqr <= sqrAiProcessingDistance; + // AI processing is only done within given distance to the player. + bool inProcessingRange = distSqr <= mActorsProcessingRange*mActorsProcessingRange; if (isPlayer) - ctrl->setAttackingOrSpell(MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell()); + ctrl->setAttackingOrSpell(world->getPlayer().getAttackingOrSpell()); // If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player. if (iter->first != player && (iter->first.getClass().getCreatureStats(iter->first).isDead() @@ -1335,10 +1351,10 @@ namespace MWMechanics if (!iter->first.getClass().getCreatureStats(iter->first).isDead()) { - bool cellChanged = MWBase::Environment::get().getWorld()->hasCellChanged(); + bool cellChanged = world->hasCellChanged(); MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports updateActor(actor, duration); - if (!cellChanged && MWBase::Environment::get().getWorld()->hasCellChanged()) + if (!cellChanged && world->hasCellChanged()) { return; // for now abort update of the old cell when cell changes by teleportation magic effect // a better solution might be to apply cell changes at the end of the frame @@ -1363,7 +1379,7 @@ namespace MWMechanics MWWorld::Ptr headTrackTarget; MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); - bool firstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson(); + bool firstPersonPlayer = isPlayer && world->isFirstPerson(); // 1. Unconsious actor can not track target // 2. Actors in combat and pursue mode do not bother to headtrack @@ -1423,27 +1439,25 @@ namespace MWMechanics CharacterController* playerCharacter = nullptr; for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { - const float animationDistance = aiProcessingDistance + 400; // Slightly larger than AI distance so there is time to switch back to the idle animation. - const float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2(); + const float dist = (playerPos - iter->first.getRefData().getPosition().asVec3()).length(); bool isPlayer = iter->first == player; - bool inAnimationRange = isPlayer || (animationDistance == 0 || distSqr <= animationDistance*animationDistance); + bool inRange = isPlayer || dist <= mActorsProcessingRange; int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower) if (isPlayer) activeFlag = 2; - int active = inAnimationRange ? activeFlag : 0; - bool canFly = iter->first.getClass().canFly(iter->first); - if (canFly) - { - // Keep animating flying creatures so they don't just hover in-air - inAnimationRange = true; - active = std::max(1, active); - } + int active = inRange ? activeFlag : 0; CharacterController* ctrl = iter->second->getCharacterController(); ctrl->setActive(active); - if (!inAnimationRange) + if (!inRange) + { + iter->first.getRefData().getBaseNode()->setNodeMask(0); + world->setActorCollisionMode(iter->first, false); continue; + } + else if (!isPlayer) + iter->first.getRefData().getBaseNode()->setNodeMask(1<<3); if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) ctrl->skipAnim(); @@ -1455,11 +1469,28 @@ namespace MWMechanics playerCharacter = ctrl; continue; } + + world->setActorCollisionMode(iter->first, true); ctrl->update(duration); + + // Fade away actors on large distance (>90% of actor's processing distance) + float visibilityRatio = 1.0; + float fadeStartDistance = mActorsProcessingRange*0.9f; + float fadeEndDistance = mActorsProcessingRange; + float fadeRatio = (dist - fadeStartDistance)/(fadeEndDistance - fadeStartDistance); + if (fadeRatio > 0) + visibilityRatio -= std::max(0.f, fadeRatio); + + visibilityRatio = std::min(1.f, visibilityRatio); + + ctrl->setVisibility(visibilityRatio); } if (playerCharacter) + { playerCharacter->update(duration); + playerCharacter->setVisibility(1.f); + } for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { @@ -1671,7 +1702,7 @@ namespace MWMechanics restoreDynamicStats(iter->first, sleep); if ((!iter->first.getRefData().getBaseNode()) || - (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > sqrAiProcessingDistance) + (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange) continue; adjustMagicEffects (iter->first); @@ -1915,7 +1946,7 @@ namespace MWMechanics std::list list; std::vector neighbors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); - getObjectsInRange(position, aiProcessingDistance, neighbors); + getObjectsInRange(position, mActorsProcessingRange, neighbors); for(auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor) { if (*neighbor == actor) @@ -1936,7 +1967,7 @@ namespace MWMechanics std::list list; std::vector neighbors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); - getObjectsInRange(position, aiProcessingDistance, neighbors); + getObjectsInRange(position, mActorsProcessingRange, neighbors); std::set followers; getActorsFollowing(actor, followers); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index a1e0e511da..61879c432f 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -65,6 +65,9 @@ namespace MWMechanics /// paused we may want to do it manually (after equipping permanent enchantment) void updateMagicEffects (const MWWorld::Ptr& ptr); + void updateProcessingRange(); + float getProcessingRange() const; + void addActor (const MWWorld::Ptr& ptr, bool updateImmediately=false); ///< Register an actor for stats management /// @@ -168,6 +171,7 @@ namespace MWMechanics private: PtrActorMap mActors; float mTimerDisposeSummonsCorpses; + float mActorsProcessingRange; }; } diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 9a42f191ef..7045d28e54 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -103,7 +103,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr ESM::Position pos = actor.getRefData().getPosition(); //position of the actor /// Stops the actor when it gets too close to a unloaded cell - //... At current time, this test is unnecessary. AI shuts down when actor is more than 7168 + //... At current time, this test is unnecessary. AI shuts down when actor is more than "actors processing range" setting value //... units from player, and exterior cells are 8192 units long and wide. //... But AI processing distance may increase in the future. if (isNearInactiveCell(pos)) @@ -354,7 +354,7 @@ bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos) // currently assumes 3 x 3 grid for exterior cells, with player at center cell. // ToDo: (Maybe) use "exterior cell load distance" setting to get count of actual active cells - // While AI Process distance is 7168, AI shuts down actors before they reach edges of 3 x 3 grid. + // AI shuts down actors before they reach edges of 3 x 3 grid. const float distanceFromEdge = 200.0; float minThreshold = (-1.0f * ESM::Land::REAL_SIZE) + distanceFromEdge; float maxThreshold = (2.0f * ESM::Land::REAL_SIZE) - distanceFromEdge; diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 8b52b15a4b..90beb9ead5 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -3,8 +3,9 @@ #include #include -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" @@ -21,7 +22,8 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) // Maximum travel distance for vanilla compatibility. // Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well. // We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways. - return (pos1 - pos2).length2() <= 7168*7168; + bool aiDistance = MWBase::Environment::get().getMechanicsManager()->getActorsProcessingRange(); + return (pos1 - pos2).length2() <= aiDistance*aiDistance; } } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index ce844674f4..d5e22cb070 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -30,6 +30,7 @@ #include "../mwrender/animation.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" @@ -1849,7 +1850,7 @@ void CharacterController::updateAnimQueue() mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); } -void CharacterController::update(float duration) +void CharacterController::update(float duration, bool animationOnly) { MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Class &cls = mPtr.getClass(); @@ -2235,10 +2236,10 @@ void CharacterController::update(float duration) world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true); } - if (!mMovementAnimationControlled) + if (!animationOnly && !mMovementAnimationControlled) world->queueMovement(mPtr, vec); } - else + else if (!animationOnly) // We must always queue movement, even if there is none, to apply gravity. world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); @@ -2261,7 +2262,8 @@ void CharacterController::update(float duration) playDeath(1.f, mDeathState); } // We must always queue movement, even if there is none, to apply gravity. - world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); + if (!animationOnly) + world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); } bool isPersist = isPersistentAnimPlaying(); @@ -2295,7 +2297,7 @@ void CharacterController::update(float duration) moved.z() = 1.0; // Update movement - if(mMovementAnimationControlled && mPtr.getClass().isActor()) + if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor()) world->queueMovement(mPtr, moved); mSkipAnim = false; @@ -2544,20 +2546,6 @@ void CharacterController::updateMagicEffects() { if (!mPtr.getClass().isActor()) return; - float alpha = 1.f; - if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555). - { - if (mPtr == getPlayer()) - alpha = 0.4f; - else - alpha = 0.f; - } - float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); - if (chameleon) - { - alpha *= std::max(0.2f, (100.f - chameleon)/100.f); - } - mAnimation->setAlpha(alpha); bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f; mAnimation->setVampire(vampire); @@ -2566,6 +2554,32 @@ void CharacterController::updateMagicEffects() mAnimation->setLightEffect(light); } +void CharacterController::setVisibility(float visibility) +{ + // We should take actor's invisibility in account + if (mPtr.getClass().isActor()) + { + float alpha = 1.f; + if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555). + { + if (mPtr == getPlayer()) + alpha = 0.4f; + else + alpha = 0.f; + } + float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); + if (chameleon) + { + alpha *= std::max(0.2f, (100.f - chameleon)/100.f); + } + + visibility = std::min(visibility, alpha); + } + + // TODO: implement a dithering shader rather than just change object transparency. + mAnimation->setAlpha(visibility); +} + void CharacterController::setAttackTypeBasedOnMovement() { float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index f97614ef45..0f4b3aa90a 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -257,7 +257,7 @@ public: void updatePtr(const MWWorld::Ptr &ptr); - void update(float duration); + void update(float duration, bool animationOnly=false); void persistAnimationState(); void unpersistAnimationState(); @@ -292,6 +292,7 @@ public: bool isTurning() const; bool isAttackingOrSpell() const; + void setVisibility(float visibility); void setAttackingOrSpell(bool attackingOrSpell); void castSpell(const std::string spellId, bool manualSpell=false); void setAIAttackType(const std::string& attackType); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 619ed91b3e..1c75763167 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -431,6 +431,25 @@ namespace MWMechanics mObjects.update(duration, paused); } + void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector &changed) + { + for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) + { + if (it->first == "Game" && it->second == "actors processing range") + { + mActors.updateProcessingRange(); + + // Update mechanics for new processing range immediately + update(0.f, false); + } + } + } + + float MechanicsManager::getActorsProcessingRange() const + { + return mActors.getProcessingRange(); + } + bool MechanicsManager::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) { return mActors.isActorDetected(actor, observer); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 3682e97cec..26eaa968d8 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -1,6 +1,8 @@ #ifndef GAME_MWMECHANICS_MECHANICSMANAGERIMP_H #define GAME_MWMECHANICS_MECHANICSMANAGERIMP_H +#include + #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/ptr.hpp" @@ -206,6 +208,10 @@ namespace MWMechanics virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell=false); + void processChangedSettings(const Settings::CategorySettingVector& settings) override; + + virtual float getActorsProcessingRange() const; + /// Check if the target actor was detected by an observer /// If the observer is a non-NPC, check all actors in AI processing distance as observers virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 2ce498f377..9ee47f3f5c 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1366,6 +1366,33 @@ namespace MWPhysics return false; } + void PhysicsSystem::setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled) + { + ActorMap::iterator found = mActors.find(ptr); + if (found != mActors.end()) + { + bool cmode = found->second->getCollisionMode(); + if (cmode == enabled) + return; + + cmode = enabled; + found->second->enableCollisionMode(cmode); + found->second->enableCollisionBody(cmode); + } + } + + bool PhysicsSystem::isActorCollisionEnabled(const MWWorld::Ptr& ptr) + { + ActorMap::iterator found = mActors.find(ptr); + if (found != mActors.end()) + { + bool cmode = found->second->getCollisionMode(); + return cmode; + } + + return false; + } + void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &movement) { PtrVelocityList::iterator iter = mMovementQueue.begin(); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 3ef9990f53..2de869395d 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -86,6 +86,8 @@ namespace MWPhysics void removeHeightField (int x, int y); bool toggleCollisionMode(); + bool isActorCollisionEnabled(const MWWorld::Ptr& ptr); + void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled); void stepSimulation(float dt); void debugDraw(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6e2c76d1d8..81701797d7 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1748,21 +1748,31 @@ namespace MWRender if (alpha != 1.f) { - osg::StateSet* stateset (new osg::StateSet); + // If we have an existing material for alpha transparency, just override alpha level + osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet(); + osg::Material* material = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + if (material) + { + material->setAlpha(osg::Material::FRONT_AND_BACK, alpha); + } + else + { + osg::StateSet* stateset (new osg::StateSet); - osg::BlendFunc* blendfunc (new osg::BlendFunc); - stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + osg::BlendFunc* blendfunc (new osg::BlendFunc); + stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - // FIXME: overriding diffuse/ambient/emissive colors - osg::Material* material (new osg::Material); - material->setColorMode(osg::Material::OFF); - material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha)); - material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + // FIXME: overriding diffuse/ambient/emissive colors + material = new osg::Material; + material->setColorMode(osg::Material::OFF); + material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha)); + material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - mObjectRoot->setStateSet(stateset); + mObjectRoot->setStateSet(stateset); - mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); + mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); + } } else { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0f20fa05a1..3b1d84a66b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1582,6 +1582,16 @@ namespace MWWorld } } + void World::setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled) + { + mPhysics->setActorCollisionMode(ptr, enabled); + } + + bool World::isActorCollisionEnabled(const MWWorld::Ptr& ptr) + { + return mPhysics->isActorCollisionEnabled(ptr); + } + bool World::toggleCollisionMode() { if (mPhysics->toggleCollisionMode()) @@ -3559,7 +3569,13 @@ namespace MWWorld MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( origin, feetToGameUnits(static_cast(effectIt->mArea)), objects); for (std::vector::iterator affected = objects.begin(); affected != objects.end(); ++affected) + { + // Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing range. + if (affected->getClass().isActor() && !isActorCollisionEnabled(*affected)) + continue; + toApply[*affected].push_back(*effectIt); + } } // Now apply the appropriate effects to each actor in range diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1592453a20..9b027ec5a6 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -407,6 +407,9 @@ namespace MWWorld bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) override; + void setActorCollisionMode(const Ptr& ptr, bool enabled) override; + bool isActorCollisionEnabled(const Ptr& ptr) override; + bool toggleCollisionMode() override; ///< Toggle collision mode for player. If disabled player object should ignore /// collisions and gravity. diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 4c3f4579f7..254496d5b6 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -97,8 +97,21 @@ and values above 500 will result in the player inflicting no damage. This setting can be controlled in game with the Difficulty slider in the Prefs panel of the Options menu. +actors processing range +----------------------- + +:Type: integer +:Range: 3584 to 7168 +:Default: 7168 + +This setting allows to specify a distance from player in game units, in which OpenMW updates actor's state. +Actor state update includes AI, animations, and physics processing. +Actors near that border start softly fade out instead of just appearing/disapperaing. + +This setting can be controlled in game with the "Actors processing range slider" in the Prefs panel of the Options menu. + classic reflected absorb spells behavior ------------------------------------------ +---------------------------------------- :Type: boolean :Range: True/False diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 8ff850cae5..8e6e98612b 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -73,7 +73,32 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -83,7 +108,7 @@ - + @@ -93,7 +118,7 @@ - + @@ -103,7 +128,7 @@ - + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index ea6f6e7b66..66c6c65774 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -200,6 +200,9 @@ best attack = false # Difficulty. Expressed as damage dealt and received. (e.g. -100 to 100). difficulty = 0 +# The maximum range of actor AI, animations and physics updates. +actors processing range = 7168 + # Make reflected Absorb spells have no practical effect, like in Morrowind. classic reflected absorb spells behavior = true From bf9e8c4556e3f545239dfc3c061b8b8baaa98aef Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 26 Oct 2018 12:36:58 +0400 Subject: [PATCH 103/133] Make spell absorption multiplicative (bug #4684) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/spellcasting.cpp | 40 ++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07c0217da6..9fa330e778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,6 +143,7 @@ Bug #4674: Journal can be opened when settings window is open Bug #4677: Crash in ESM reader when NPC record has DNAM record without DODT one Bug #4678: Crash in ESP parser when SCVR has no variable names + Bug #4684: Spell Absorption is additive Bug #4685: Missing sound causes an exception inside Say command Bug #4689: Default creature soundgen entries are not used Feature #912: Editor: Add missing icons to UniversalId tables diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index a91aa2f31c..4a74e1647f 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -324,6 +324,34 @@ namespace MWMechanics return true; } + class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor + { + public: + float mProbability; + + GetAbsorptionProbability(const MWWorld::Ptr& actor) + : mProbability(0.f){} + + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, const std::string& sourceId, int casterActorId, + float magnitude, float remainingTime = -1, float totalTime = -1) + { + if (key.mId == ESM::MagicEffect::SpellAbsorption) + { + if (mProbability == 0.f) + mProbability = magnitude / 100; + else + { + // If there are different sources of SpellAbsorption effect, multiply failing probability for all effects. + // Real absorption probability will be the (1 - total fail chance) in this case. + float failProbability = 1.f - mProbability; + failProbability *= 1.f - magnitude / 100; + mProbability = 1.f - failProbability; + } + } + } + }; + CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell) : mCaster(caster) , mTarget(target) @@ -444,12 +472,18 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->setEnemy(target); // Try absorbing if it's a spell - // NOTE: Vanilla does this once per spell absorption effect source instead of adding the % from all sources together, not sure - // if that is worth replicating. + // NOTE: Vanilla does this once per spell absorption effect source instead of adding the % from all sources together, so use the same approach here bool absorbed = false; if (spell && caster != target && target.getClass().isActor()) { - float absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude(); + GetAbsorptionProbability check(target); + MWMechanics::CreatureStats& stats = target.getClass().getCreatureStats(target); + stats.getActiveSpells().visitEffectSources(check); + stats.getSpells().visitEffectSources(check); + if (target.getClass().hasInventoryStore(target)) + target.getClass().getInventoryStore(target).visitEffectSources(check); + + int absorb = check.mProbability * 100; absorbed = (Misc::Rng::roll0to99() < absorb); if (absorbed) { From 6d91fe69b268e6b2a8fc5da4a4432a5de346195d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 26 Oct 2018 12:51:04 +0400 Subject: [PATCH 104/133] Revert "Handle RootCollisionNode, attached to non-root node (bug #4311)" This reverts commit ec9a1b0d0526c9aba7d7cdd0e68789ab0e747d50. --- CHANGELOG.md | 1 - components/nifbullet/bulletnifloader.cpp | 42 ++++++++---------------- components/nifbullet/bulletnifloader.hpp | 4 +-- 3 files changed, 16 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07c0217da6..0f668010d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,7 +57,6 @@ Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4304: "Follow" not working as a second AI package Bug #4307: World cleanup should remove dead bodies only if death animation is finished - Bug #4311: OpenMW does not handle RootCollisionNode correctly Bug #4327: Missing animations during spell/weapon stance switching Bug #4333: Keyboard navigation in containers is not intuitive Bug #4358: Running animation is interrupted when magic mode is toggled diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 7b206e40c2..c18b96d395 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -96,17 +96,14 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) } else { + bool autogenerated = hasAutoGeneratedCollision(node); + // files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see Animation::addAnimSource). // assume all nodes in the file will be animated const auto filename = nif.getFilename(); const bool isAnimated = pathFileNameStartsWithX(filename); - // If the mesh has RootCollisionNode, attached to actual root node, use it as collision mesh - const Nif::Node* rootCollisionNode = getCollisionNode(node); - if (rootCollisionNode) - handleNode(filename, rootCollisionNode, 0, false, isAnimated, false); - else - handleNode(filename, node, 0, true, isAnimated, true); + handleNode(node, 0, autogenerated, isAnimated, autogenerated); if (mCompoundShape) { @@ -163,34 +160,25 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node, int flags) return false; } -const Nif::Node* BulletNifLoader::getCollisionNode(const Nif::Node* rootNode) +bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node* rootNode) { const Nif::NiNode *ninode = dynamic_cast(rootNode); if(ninode) { - // If root NiNode has only other NiNode as child, consider it as a wrapper, not as actual root node const Nif::NodeList &list = ninode->children; - if (list.length() == 1 && - rootNode->recType == Nif::RC_NiNode && - list[0].getPtr()->recType == Nif::RC_NiNode) - { - return getCollisionNode(list[0].getPtr()); - } - - for(size_t i = 0; i < list.length(); i++) + for(size_t i = 0;i < list.length();i++) { - if(list[i].empty()) - continue; - - const Nif::Node* childNode = list[i].getPtr(); - if(childNode->recType == Nif::RC_RootCollisionNode) - return childNode; + if(!list[i].empty()) + { + if(list[i].getPtr()->recType == Nif::RC_RootCollisionNode) + return false; + } } } - return nullptr; + return true; } -void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *node, int flags, +void BulletNifLoader::handleNode(const Nif::Node *node, int flags, bool isCollisionNode, bool isAnimated, bool autogenerated) { // Accumulate the flags from all the child nodes. This works for all @@ -203,9 +191,6 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode); - if (node->recType == Nif::RC_RootCollisionNode && autogenerated) - Log(Debug::Info) << "Found RootCollisionNode attached to non-root node in " << fileName << ". Treat it as a common NiTriShape."; - // Don't collide with AvoidNode shapes if(node->recType == Nif::RC_AvoidNode) flags |= 0x800; @@ -234,6 +219,7 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n // Marker can still have collision if the model explicitely specifies it via a RootCollisionNode. return; } + } } @@ -256,7 +242,7 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n for(size_t i = 0;i < list.length();i++) { if(!list[i].empty()) - handleNode(fileName, list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated); + handleNode(list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated); } } } diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index f970b7f3e5..cc9aaa844b 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -55,9 +55,9 @@ public: private: bool findBoundingBox(const Nif::Node* node, int flags = 0); - void handleNode(const std::string& fileName, Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false); + void handleNode(Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false); - const Nif::Node* getCollisionNode(const Nif::Node* rootNode); + bool hasAutoGeneratedCollision(const Nif::Node *rootNode); void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf& transform, bool isAnimated); From 19ce1abcdf40d196ae88d242e5b319f03d7a05ab Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Fri, 26 Oct 2018 12:53:15 +0300 Subject: [PATCH 105/133] Fix selected weapon HUD durability percentage --- apps/openmw/mwgui/windowmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 5ffed48156..3937518552 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1385,7 +1385,7 @@ namespace MWGui int durabilityPercent = 100; if (item.getClass().hasItemHealth(item)) { - durabilityPercent = static_cast(item.getClass().getItemNormalizedHealth(item)); + durabilityPercent = static_cast(item.getClass().getItemNormalizedHealth(item) * 100); } mHud->setSelectedWeapon(item, durabilityPercent); mInventoryWindow->setTitle(item.getClass().getName(item)); From 61da6b6ecfbeb08a405e2ebd620c3bcbc77ea37d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 26 Oct 2018 13:24:05 +0400 Subject: [PATCH 106/133] Print warning if the RootCollisionNode is attached to non-root node (bug #4311) --- CHANGELOG.md | 1 + components/nifbullet/bulletnifloader.cpp | 10 +++++++--- components/nifbullet/bulletnifloader.hpp | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f668010d5..07c0217da6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4304: "Follow" not working as a second AI package Bug #4307: World cleanup should remove dead bodies only if death animation is finished + Bug #4311: OpenMW does not handle RootCollisionNode correctly Bug #4327: Missing animations during spell/weapon stance switching Bug #4333: Keyboard navigation in containers is not intuitive Bug #4358: Running animation is interrupted when magic mode is toggled diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index c18b96d395..20307f2526 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -103,7 +103,7 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) const auto filename = nif.getFilename(); const bool isAnimated = pathFileNameStartsWithX(filename); - handleNode(node, 0, autogenerated, isAnimated, autogenerated); + handleNode(filename, node, 0, autogenerated, isAnimated, autogenerated); if (mCompoundShape) { @@ -178,7 +178,7 @@ bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node* rootNode) return true; } -void BulletNifLoader::handleNode(const Nif::Node *node, int flags, +void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *node, int flags, bool isCollisionNode, bool isAnimated, bool autogenerated) { // Accumulate the flags from all the child nodes. This works for all @@ -195,6 +195,10 @@ void BulletNifLoader::handleNode(const Nif::Node *node, int flags, if(node->recType == Nif::RC_AvoidNode) flags |= 0x800; + // We encountered a RootCollisionNode inside autogenerated mesh. It is not right. + if (node->recType == Nif::RC_RootCollisionNode && autogenerated) + Log(Debug::Info) << "Found RootCollisionNode attached to non-root node in " << fileName << ". Treat it as a common NiTriShape."; + // Check for extra data Nif::Extra const *e = node; while (!e->extra.empty()) @@ -242,7 +246,7 @@ void BulletNifLoader::handleNode(const Nif::Node *node, int flags, for(size_t i = 0;i < list.length();i++) { if(!list[i].empty()) - handleNode(list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated); + handleNode(fileName, list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated); } } } diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index cc9aaa844b..f67ab402ee 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -55,7 +55,7 @@ public: private: bool findBoundingBox(const Nif::Node* node, int flags = 0); - void handleNode(Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false); + void handleNode(const std::string& fileName, Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false); bool hasAutoGeneratedCollision(const Nif::Node *rootNode); From d2f3196ee861907b5b7048792726a64bf045fb9f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 26 Oct 2018 21:21:34 +0400 Subject: [PATCH 107/133] Fix testcase for RootCollisionNode with MRK data --- apps/openmw_test_suite/nifloader/testbulletnifloader.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index a2311be490..2361718b19 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -929,10 +929,8 @@ namespace mNiStringExtraData.string = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); - mNiNode3.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); - mNiNode3.recType = Nif::RC_RootCollisionNode; - mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(nullptr), Nif::NodePtr(&mNiNode3)})); - mNiNode2.recType = Nif::RC_NiNode; + mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode2.recType = Nif::RC_RootCollisionNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiNode2)})); mNiNode.recType = Nif::RC_NiNode; From f62ca24356a16c7148bef07bdaf2ba19464f2236 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Fri, 26 Oct 2018 19:33:04 +0200 Subject: [PATCH 108/133] [macOS, CI] Use dependencies with downgraded MyGUI, fixing #4665 --- CI/before_install.osx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 2ab996b10f..51b4b90be9 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -6,5 +6,5 @@ brew outdated cmake || brew upgrade cmake brew outdated pkgconfig || brew upgrade pkgconfig brew install qt -curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-100d2e0.zip -o ~/openmw-deps.zip +curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-4eec887.zip -o ~/openmw-deps.zip unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null From 4873d33642ca6973a266244e3b73c40da5f6c6a0 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 27 Oct 2018 01:25:16 +0300 Subject: [PATCH 109/133] Adjust magic light source linear attenuation (bug #3890) --- CHANGELOG.md | 1 + apps/openmw/mwrender/animation.cpp | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fa330e778..ed387eaf63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Bug #3788: GetPCInJail and GetPCTraveling do not work as in vanilla Bug #3836: Script fails to compile when command argument contains "\n" Bug #3876: Landscape texture painting is misaligned + Bug #3890: Magic light source attenuation is inaccurate Bug #3897: Have Goodbye give all choices the effects of Goodbye Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters Bug #3920: RemoveSpellEffects doesn't remove constant effects diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f165b6bd3d..8ae8800a6c 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1789,9 +1789,12 @@ namespace MWRender } else { - effect += 3; - float radius = effect * 66.f; - float linearAttenuation = 0.5f / effect; + // TODO: use global attenuation settings + + // 1 pt of Light effect magnitude corresponds to 1 foot of light source radius, which is about 21.33 game units, + // but Morrowind uses imprecise value of foot for magic effects. + float radius = effect * 22.f; + float linearAttenuation = 3.f / radius; if (!mGlowLight || linearAttenuation != mGlowLight->getLight(0)->getLinearAttenuation()) { @@ -1813,7 +1816,8 @@ namespace MWRender mGlowLight->setLight(light); } - mGlowLight->setRadius(radius); + // Make the obvious cut-off a bit less obvious + mGlowLight->setRadius(radius * 3); } } From 89f4ad18356353b2c1ddca2a7ef0a671ab624c81 Mon Sep 17 00:00:00 2001 From: Alec Nunn Date: Fri, 26 Oct 2018 21:52:56 -0700 Subject: [PATCH 110/133] Worked with formatting the documentation source to conform to standards desired. Also corrected numerous spelling errors and inconsistencies. --- docs/source/reference/documentationHowTo.rst | 106 ++++++++++++++---- docs/source/reference/modding/differences.rst | 26 +++-- docs/source/reference/modding/font.rst | 17 ++- docs/source/reference/modding/foreword.rst | 8 +- docs/source/reference/modding/mod-install.rst | 10 +- .../source/reference/modding/settings/HUD.rst | 3 +- .../reference/modding/settings/cells.rst | 20 +++- .../reference/modding/settings/game.rst | 22 ++-- .../source/reference/modding/settings/map.rst | 7 +- .../reference/modding/settings/saves.rst | 7 +- .../reference/modding/settings/shaders.rst | 5 +- .../reference/modding/settings/sound.rst | 4 +- .../reference/modding/settings/terrain.rst | 3 +- .../reference/modding/settings/video.rst | 6 +- .../reference/modding/settings/windows.rst | 5 +- .../convert-bump-mapped-mods.rst | 106 ++++++++++++++---- .../texture-modding/texture-basics.rst | 10 +- 17 files changed, 275 insertions(+), 90 deletions(-) diff --git a/docs/source/reference/documentationHowTo.rst b/docs/source/reference/documentationHowTo.rst index b27c122cb5..75dbe8dca2 100644 --- a/docs/source/reference/documentationHowTo.rst +++ b/docs/source/reference/documentationHowTo.rst @@ -8,9 +8,19 @@ Or a beginner's guide to writing docs without having to deal with more techie st Intro ===== -The premise of this guide is that you would like to help out the OpenMW project beyond play-testing for bugs and such, buuuuut you're like me and don't really know how to code. This has the rather pesky side effect of you not really knowing about all the tools like GitHub and such. While many of these tools are super handy and great to know how to use, not everyone has the actual need and desire to learn the ins and outs of them. Since we would like as much help fleshing out the user documentation as possible, I wrote this guide to lower the barrier of entry into contributing to the project. - -*However*, as much as I will try to guide you through all the tedious setup and day-to-day stuff, you will eventually have to learn to write using ReST (reStructuredText) formatting. Since you're probably like me when I started helping and don't know wtf ReST is, never fear. It's an incredibly simple language that is easy to read in plain text form that can then be converted automatically into different types of documents like PDFs and html for webpages. +The premise of this guide is that you would like to help out the OpenMW project beyond play-testing for bugs and such, +*buuuuut* you're like me and don't really know how to code. +This has the rather pesky side effect of you not really knowing about all the tools like GitHub and such. +While many of these tools are super handy and great to know how to use, +not everyone has the actual need and desire to learn the ins and outs of them. +Since we would like as much help fleshing out the user documentation as possible, +I wrote this guide to lower the barrier of entry into contributing to the project. + +*However*, as much as I will try to guide you through all the tedious setup and day-to-day stuff, +you will eventually have to learn to write using ReST (reStructuredText) formatting. +Since you're probably like me when I started helping and don't know wtf ReST is, never fear. +It's an incredibly simple language that is easy to read in plain text form that can then be converted automatically +into different types of documents like PDFs and html for webpages. Baby Steps ========== @@ -25,9 +35,11 @@ Choose Repository and Files in the menu on the left, then docs and source in the Don’t overlook the tutorial-style-guide.txt there for some tips to get you started. Open whichever file you want to tackle – probably within the manuals or reference directories. -There’s also a dropdown box to the right of edit, at the top of the left menu, which offers options such as new file or directory, or upload file, with “+” to close that dropdown box. +There’s also a dropdown box to the right of edit, at the top of the left menu, +which offers options such as new file or directory, or upload file, with “+” to close that dropdown box. -Click on "Edit" towards top right which will reveal the underlying version, rather than the version displayed to normal reaaders. Use "Write" and "Preview" to switch between the two views. +Click on "Edit" towards top right which will reveal the underlying version, +rather than the version displayed to normal readers. Use "Write" and "Preview" to switch between the two views. When you have made the appropriate changes, and checked them in Preview mode, click the Green "Commit changes" button at the bottom. This should add a branch, with a default name such as patch-1, to your own repository, and add a Merge Request to the main OpenMW Project. @@ -48,13 +60,24 @@ So here's what you're gonna be learning how to set up: GitHub ====== -GitHub is the website the OpenMW project is hosted on. It utilizes Git, which is a version control system, meaning it helps us all collaborate on the project without interfering with each others' work. The commands are a little annoying because there is a certain amount of undescriptive jargon, but for the most part, what you need to know is very simple and I'll walk you through it. There are three main parts that you should know: +GitHub is the website the OpenMW project is hosted on. It utilizes Git, which is a version control system, +meaning it helps us all collaborate on the project without interfering with each others' work. +The commands are a little annoying because there is a certain amount of undescriptive jargon, +but for the most part, what you need to know is very simple and I'll walk you through it. +There are three main parts that you should know: 1. The OpenMW repository 2. Your online repository 3. Your local repository -The master OpenMW respository is where all of our work comes together and where the most current version of the source code resides. A repository, also called repo, is a directory or the main folder that holds a project. You will need to create your own account on GitHub so you can *fork* the OpenMW repository. Forking is just when you clone a project into a repository on your own account so you can make changes however you like without accidentally messing up the original project. Now, you could add and edit files on GitHub.com directly through your online repository, however it's much easier to work on them on your own computer in your local repository. Local just refers to the fact that it's physically stored on your computer's hard drive. Here are the easy steps for doing all this: +The master OpenMW repository is where all of our work comes together and where the most current version of the source code resides. +A repository, also called repo, is a directory or the main folder that holds a project. +You will need to create your own account on GitHub so you can *fork* the OpenMW repository. +Forking is just when you clone a project into a repository on your own account so you can make changes however you like +without accidentally messing up the original project. +Now, you could add and edit files on GitHub.com directly through your online repository, +however it's much easier to work on them on your own computer in your local repository. +Local just refers to the fact that it's physically stored on your computer's hard drive. Here are the easy steps for doing all this: 1. Go to GitHub.com and sign up for a free account. 2. Navigate to the master OpenMW repo at: https://github.com/OpenMW/openmw @@ -67,7 +90,11 @@ If you want more info I recommend reading this guide: https://readwrite.com/2013 PyCharm ======= -PyCharm is what's known as an IDE, which stands for integrated development environment. All this means is that it's for writing code and has a bunch of built-in features that make it easier to do so. In this case, PyCharm is made for the language Python, which is what Sphinx is written in. We won't actually be touching any of the Python, but some of the built-in features are extremely useful. Let's start setting it up: +PyCharm is what's known as an IDE, which stands for integrated development environment. +All this means is that it's for writing code and has a bunch of built-in features that make it easier to do so. +In this case, PyCharm is made for the language Python, which is what Sphinx is written in. +We won't actually be touching any of the Python, but some of the built-in features are extremely useful. +Let's start setting it up: 1. Go to https://www.jetbrains.com/pycharm/download/ 2. Select your OS, then download the free Community version. @@ -81,31 +108,61 @@ PyCharm is what's known as an IDE, which stands for integrated development envir 10. Back in the welcome window, click "Check out from version control" and select GitHub. .. note:: - After this step, it should log in to your GitHub. If not, you probably messed up the Token creation. If you're on Mac, you may come across and error complaining about XCode and admin priviledges. If this happens, open Terminal and type: ``sudo xcodebuild -license`` Read through the license and agree. This should fix the error and allow you to log in. + After this step, it should log in to your GitHub. If not, you probably messed up the Token creation. + If you're on Mac, you may come across and error complaining about XCode and admin priviledges. If this happens, + open Terminal and type: ``sudo xcodebuild -license`` Read through the license and agree. + This should fix the error and allow you to log in. 11. In Git Repository URL, select your OpenMW repository and click Clone -Congrats! You now have the OpenMW sourcecode on your computer and you can begin making changes and contributing. If you're reading this guide though, you probably won't have any idea how to do that, so let's go through setting up Sphinx, then I'll go through it. +Congrats! You now have the OpenMW source code on your computer and you can begin making changes and contributing. +If you're reading this guide though, you probably won't have any idea how to do that, +so let's go through setting up Sphinx, then I'll go through it. Sphinx ====== -So far I've mentioned ReST (reStructuredText) a couple times, but what is it, and what is Sphinx? The most basic explanation is that ReST is the markup language (like HTML is the markup language for webpages) and Sphinx is the program that goes through and builds the actual document so you can read it in a more visually pleasing way. For a much more detailed explanation, I recommend: https://coderwall.com/p/vemncg/what-is-the-difference-rest-docutils-sphinx-readthedocs +So far I've mentioned ReST (reStructuredText) a couple times, but what is it, and what is Sphinx? +The most basic explanation is that ReST is the markup language (like HTML is the markup language for webpages) +and Sphinx is the program that goes through and builds the actual document so you can read it in a more visually pleasing way. +For a much more detailed explanation, I recommend: https://coderwall.com/p/vemncg/what-is-the-difference-rest-docutils-sphinx-readthedocs -This will be the most technical section as we have to use the command prompt or terminal to install Python and Sphinx. I had intended to give you a universal explanation on how to install both, but it would drastically increase the length of this guide. The tutorial on the Sphinx website is really just going to be better than anything I write here, so please refer to their guide here: https://www.sphinx-doc.org/en/stable/install.html +This will be the most technical section as we have to use the command prompt or terminal to install Python and Sphinx. +I had intended to give you a universal explanation on how to install both, +but it would drastically increase the length of this guide. +The tutorial on the Sphinx website is really just going to be better than anything I write here, +so please refer to their guide here: https://www.sphinx-doc.org/en/stable/install.html Hopefully you now have Python and Sphinx installed. ... -Now you should have everything installed and running so you can collaborate on documentation properly. Let's go through a few more brief GitHub basics. There are really only 4 things you will be using regularly: +Now you should have everything installed and running so you can collaborate on documentation properly. +Let's go through a few more brief GitHub basics. There are really only 4 things you will be using regularly: 1. Rebase 2. Commit 3. Push 4. Pull request (PR) -Rebasing means you're taking all changes in one branch and applying them directly on top of another branch. This is slightly different than a merge which compares the two branches and makes another state combining the two. The difference is slight, but we use the rebase because it keeps the history cleaner. You will always rebase your local repository from the OpenMW master repository. This ensures you have all the most up to date changes before working on stuff so there is less chance of conflicts that need to be resolved when your branch is merged back into the master. A commit is basically just stating which files you want to mark as ready to be "pushed" to your online repository. A push is just copying those "committed" changes to your online repo. (Commit and push can be combined in one step in PyCharm, so yay) Once you've pushed all the changes you need to contribute something to the project, you will then submit a pull request, so called because you are *requesting* that the project maintainers "pull" and merge the changes you've made into the project master repository. One of the project maintainers will probably ask you to make some corrections or clarifications. Go back and repeat this process to make those changes, and repeat until they're good enough to get merged. - -So to go over all that again. You rebase *every* time you start working on something to ensure you're working on the most updated version (I do literally every time I open PyCharm). Then make your edits. You commit and push from your local repo to your online repo. Then you submit a pull request and people can review your changes before they get merged into the project master! Or in list form: +Rebasing means you're taking all changes in one branch and applying them directly on top of another branch. +This is slightly different than a merge which compares the two branches and makes another state combining the two. +The difference is slight, but we use the rebase because it keeps the history cleaner. +You will always rebase your local repository from the OpenMW master repository. +This ensures you have all the most up to date changes before working on stuff so there is less chance of conflicts that +need to be resolved when your branch is merged back into the master. +A commit is basically just stating which files you want to mark as ready to be "pushed" to your online repository. +A push is just copying those "committed" changes to your online repo. +(Commit and push can be combined in one step in PyCharm, so yay) +Once you've pushed all the changes you need to contribute something to the project, you will then submit a pull request, +so called because you are *requesting* that the project maintainers "pull" + and merge the changes you've made into the project master repository. One of the project maintainers will probably ask + you to make some corrections or clarifications. Go back and repeat this process to make those changes, + and repeat until they're good enough to get merged. + +So to go over all that again. You rebase *every* time you start working on something to ensure you're working on the most +updated version (I do literally every time I open PyCharm). Then make your edits. +You commit and push from your local repo to your online repo. +Then you submit a pull request and people can review your changes before they get merged into the project master! +Or in list form: 1. Rebase local repo from OpenMW master 2. Make your edits @@ -116,7 +173,9 @@ So to go over all that again. You rebase *every* time you start working on somet Preview Documentation ********************* -You will probably find it helpful to be able to preview any documentation you've made. I often forget necessary syntax and this allows me to double check my work before submitting a PR. Luckily, PyCharm has a handy built-in feature that allows you to easily generate the docs. +You will probably find it helpful to be able to preview any documentation you've made. +I often forget necessary syntax and this allows me to double check my work before submitting a PR. +Luckily, PyCharm has a handy built-in feature that allows you to easily generate the docs. 1. In the top right corner of the PyCharm window, select the drop-down menu and select `Edit Configurations`. 2. In the `Run/Debug Configurations` dialogue, click the green plus button in the top left and select `Python Docs > Sphinx Tasks`. @@ -127,16 +186,21 @@ You will probably find it helpful to be able to preview any documentation you've :Output: 4. Click `Apply`, then `OK`. -Now in order to generate the documentation on your computer to preview them, just click the green play button in the top right, next to the drop down menu with the name you chose above selected. Sphinx will run and you can view the resulting documentation wherever you chose Output to be, above. The window that Sphinx runs in will also show any errors that occur during the build in red, which should help you find typos and missing/incorrect syntax. +Now in order to generate the documentation on your computer to preview them, +just click the green play button in the top right, next to the drop down menu with the name you chose above selected. +Sphinx will run and you can view the resulting documentation wherever you chose Output to be, above. +The window that Sphinx runs in will also show any errors that occur during the build in red, +which should help you find typos and missing/incorrect syntax. GitLab integration in PyCharm ============================= -As most of the hosting of OpenMW has moved to Gitlab, we should encourage the use of GitLab, though GitHub will continue to be supported. +As most of the hosting of OpenMW has moved to Gitlab, we should encourage the use of GitLab, +though GitHub will continue to be supported. -Add a couple of plugins to Pycharm - see general instructions at https://www.jetbrains.com/help/pycharm/installing-updating-and-uninstalling-repository-plugins.html +Add a couple of plugins to PyCharm - see general instructions at https://www.jetbrains.com/help/pycharm/installing-updating-and-uninstalling-repository-plugins.html -For Linux/Windows - (Macos is a little different) +For Linux/Windows - (MacOS is a little different) 1. File/Settings/Plugins 2. Browse Repositories diff --git a/docs/source/reference/modding/differences.rst b/docs/source/reference/modding/differences.rst index d492dc542a..5533a3aa37 100644 --- a/docs/source/reference/modding/differences.rst +++ b/docs/source/reference/modding/differences.rst @@ -4,15 +4,19 @@ Modding OpenMW vs Morrowind A brief overview of the differences between the two engines. ============================================================ -OpenMW is designed to be able to use all the normal Morrowind mod files such as ESM/ESP plugins, texture replacers, mesh replacers, etc. +OpenMW is designed to be able to use all the normal Morrowind mod files such as ESM/ESP plugins, texture replacers, +mesh replacers, etc. .. warning:: - All external programs and libraries that depend on ``morrowind.exe`` cannot function with OpenMW. This means you should assume mods dependent on Morrowind Graphics Extender, Morrowind Code Patch, Morrowind Script Extender, etc, will *not* work correctly, nor will the tools themselves. + All external programs and libraries that depend on ``morrowind.exe`` cannot function with OpenMW. + This means you should assume mods dependent on Morrowind Graphics Extender, Morrowind Code Patch, + Morrowind Script Extender, etc, will *not* work correctly, nor will the tools themselves. Multiple Data Folders --------------------- -The largest difference between OpenMW and Morrowind in terms of data structure is OpenMW's support of multiple data folders. This has many advantages, especially when it comes to uninstalling mods and preventing unintentional overwrites of files. +The largest difference between OpenMW and Morrowind in terms of data structure is OpenMW's support of multiple data folders. +This has many advantages, especially when it comes to uninstalling mods and preventing unintentional overwrites of files. .. warning:: Most mods can still be installed into the root OpenMW data folder, but this is not recommended. @@ -34,25 +38,33 @@ To uninstall these mods simply delete that mod's respective ``data=`` entry. The mods are loaded in the order of these entries, with the top being overwritten by mods added towards the bottom. .. note:: - Mods that depend on ESM/ESP plugins can be rearranged within the OpenMW Launcher, but mesh/texture replacer mods can only be reordered by moving their ``data=`` entry. + Mods that depend on ESM/ESP plugins can be rearranged within the OpenMW Launcher, + but mesh/texture replacer mods can only be reordered by moving their ``data=`` entry. OpenMW Launcher --------------- -The launcher included with OpenMW is similar to the original Morrowind Launcher. Go to the Data Files tab to enable and disable plugins. You can also drag list items to modify the load order. Content lists can be created at the bottom by clicking the New Content List button, creating a list name, then setting up a new modlist. This is helpful for different player profiles and testing out different load orders. +The launcher included with OpenMW is similar to the original Morrowind Launcher. +Go to the Data Files tab to enable and disable plugins. You can also drag list items to modify the load order. +Content lists can be created at the bottom by clicking the New Content List button, creating a list name, +then setting up a new modlist. This is helpful for different player profiles and testing out different load orders. .. TODO use a substitution image for the New Content List button. Settings.cfg ------------ -The ``settings.cfg`` file is essentially the same as the INI files for Morrowind. It is located in the same directory as ``openmw.cfg``. This is where many video, audio, GUI, input, etc. settings can be modified. Some are available in-game, but many are only available in this configuration file. Please see https://wiki.openmw.org/index.php?title=Settings for the complete listing. +The ``settings.cfg`` file is essentially the same as the INI files for Morrowind. +It is located in the same directory as ``openmw.cfg``. This is where many video, audio, GUI, input, etc. +settings can be modified. Some are available in-game, but many are only available in this configuration file. +Please see https://wiki.openmw.org/index.php?title=Settings for the complete listing. .. TODO Create a proper ReST document tree for all the settings rather than Wiki. Open Source Resources Support ----------------------------- -While OpenMW supports all of the original files that Morrowind supported, we've expanded support to many open source file formats. These are summarized below: +While OpenMW supports all of the original files that Morrowind supported, +we've expanded support to many open source file formats. These are summarized below: diff --git a/docs/source/reference/modding/font.rst b/docs/source/reference/modding/font.rst index 80d01c27f7..213cdf7600 100644 --- a/docs/source/reference/modding/font.rst +++ b/docs/source/reference/modding/font.rst @@ -4,14 +4,20 @@ Fonts Morrowind .fnt fonts -------------------- -Morrowind uses a custom ``.fnt`` file format. It is not compatible with the Windows Font File ``.fnt`` format, nor compatible with ``.fnt`` formats from any other Bethesda games. To our knowledge, the format is undocumented and no tools for viewing or editing these fonts exist. +Morrowind uses a custom ``.fnt`` file format. It is not compatible with the Windows Font File ``.fnt`` format, +nor compatible with ``.fnt`` formats from any other Bethesda games. To our knowledge, +the format is undocumented and no tools for viewing or editing these fonts exist. -OpenMW can load this format and convert it on the fly into something usable (see font loader `source code `_). In OpenMW 0.32, an --export-fonts command line option was added to write the converted font (a PNG image and an XML file describing the position of each glyph in the image) to the current directory. +OpenMW can load this format and convert it on the fly into something usable +(see font loader `source code `_). +In OpenMW 0.32, an --export-fonts command line option was added to write the converted font +(a PNG image and an XML file describing the position of each glyph in the image) to the current directory. TrueType fonts -------------- -Unlike vanilla Morrowind, OpenMW directly supports TrueType (``.ttf``) fonts. This is the recommended way to create new fonts. +Unlike vanilla Morrowind, OpenMW directly supports TrueType (``.ttf``) fonts. +This is the recommended way to create new fonts. - To replace the primary "Magic Cards" font: @@ -74,4 +80,7 @@ Unlike vanilla Morrowind, OpenMW directly supports TrueType (``.ttf``) fonts. Th Bitmap fonts ------------ -Morrowind ``.fnt`` files are essentially a bitmap font, but using them is discouraged because of no Unicode support. MyGUI has its own format for bitmap fonts. An example can be seen by using the --export-fonts command line option (see above), which converts Morrowind ``.fnt`` to a MyGUI bitmap font. This is the recommended format to use if you wish to edit Morrowind's bitmap font or create a new bitmap font. +Morrowind ``.fnt`` files are essentially a bitmap font, but using them is discouraged because of no Unicode support. +MyGUI has its own format for bitmap fonts. An example can be seen by using the --export-fonts command line option (see above), +which converts Morrowind ``.fnt`` to a MyGUI bitmap font. +This is the recommended format to use if you wish to edit Morrowind's bitmap font or create a new bitmap font. diff --git a/docs/source/reference/modding/foreword.rst b/docs/source/reference/modding/foreword.rst index cf72b2aa36..30b5fdb3be 100644 --- a/docs/source/reference/modding/foreword.rst +++ b/docs/source/reference/modding/foreword.rst @@ -1,4 +1,10 @@ Foreword ######## -OpenMW is a complete game engine built to be content agnostic. The majority of this guide is applicable to any non-Morrowind project using its engine. That being said, it was designed with the extensive modding community of Morrowind in mind. Therefore, if you are already familiar with modding in Morrowind, you will likely be able to start modding in OpenMW with little to no instruction. We do recommend you at least refer to :doc:`differences` to find out about what's different between OpenMW and the original Morrowind engine. For everyone else, or just a good refresher, read on! \ No newline at end of file +OpenMW is a complete game engine built to be content agnostic. +The majority of this guide is applicable to any non-Morrowind project using its engine. +That being said, it was designed with the extensive modding community of Morrowind in mind. +Therefore, if you are already familiar with modding in Morrowind, +you will likely be able to start modding in OpenMW with little to no instruction. +We do recommend you at least refer to :doc:`differences` to find out about what's different between OpenMW and the +original Morrowind engine. For everyone else, or just a good refresher, read on! \ No newline at end of file diff --git a/docs/source/reference/modding/mod-install.rst b/docs/source/reference/modding/mod-install.rst index e62c27fc18..2c883aa599 100644 --- a/docs/source/reference/modding/mod-install.rst +++ b/docs/source/reference/modding/mod-install.rst @@ -21,16 +21,20 @@ Install #. If your mod contains resources in a ``.bsa`` file, go to near the top of the file, locate the entries like ''fallback-archive=Morrowind.bsa'' and create a new line underneath and type: ``fallback-archive=.bsa''``. .. note:: - Some text editors, such as TextEdit on Mac, will autocorrect your double quotes to typographical "curly" quotes instead of leaving them as the proper neutral vertical quotes ``""``. + Some text editors, such as TextEdit on Mac, will auto-correct your double quotes to typographical "curly" + quotes instead of leaving them as the proper neutral vertical quotes ``""``. #. Save your ``openmw.cfg`` file. -You have now installed your mod. Any simple replacer mods that only contain resource files such as meshes or textures will now automatically be loaded in the order of their ``data=*`` entry. This is important to note because replacer mods that replace the same resource will overwrite previous ones as you go down the list. +You have now installed your mod. Any simple replacer mods that only contain resource files such as meshes or +textures will now automatically be loaded in the order of their ``data=*`` entry. +This is important to note because replacer mods that replace the same resource will overwrite previous ones as you go down the list. Enable ------ -Any mods that have plugin files must be enabled to work. Master game files and plugin files can only be enabled if they have been properly installed within a *data folder* as described above. +Any mods that have plugin files must be enabled to work. +Master game files and plugin files can only be enabled if they have been properly installed within a *data folder* as described above. #. Open the OpenMW Launcher. #. Click on the Data Files tab. diff --git a/docs/source/reference/modding/settings/HUD.rst b/docs/source/reference/modding/settings/HUD.rst index 83811420f7..07a3135c8d 100644 --- a/docs/source/reference/modding/settings/HUD.rst +++ b/docs/source/reference/modding/settings/HUD.rst @@ -8,7 +8,8 @@ crosshair :Range: True/False :Default: True -This setting determines whether the crosshair or reticle is displayed. Enabling the crosshair provides more immediate feedback about which object is currently the focus of actions. +This setting determines whether the crosshair or reticle is displayed. +Enabling the crosshair provides more immediate feedback about which object is currently the focus of actions. Some players perceive that disabling the crosshair provides a more immersive experience. Another common use is to disable the crosshair for screen shots. diff --git a/docs/source/reference/modding/settings/cells.rst b/docs/source/reference/modding/settings/cells.rst index 43a984ca7c..43e51eb7a4 100644 --- a/docs/source/reference/modding/settings/cells.rst +++ b/docs/source/reference/modding/settings/cells.rst @@ -11,7 +11,10 @@ exterior cell load distance This setting determines the number of exterior cells adjacent to the character that will be loaded for rendering. .. Warning:: - Values greater than 1 will significantly affect the frame rate and loading times. This setting is mainly intended for making screenshots of scenic vistas and not for real-time gameplay. Loading more cells can break certain scripts or quests in the game that expect cells to not be loaded until the player is there. These limitations will be addressed in a future version with a separate technique for rendering distant cells. + Values greater than 1 will significantly affect the frame rate and loading times. + This setting is mainly intended for making screenshots of scenic vistas and not for real-time gameplay. + Loading more cells can break certain scripts or quests in the game that expect cells to not be loaded until the player is there. + These limitations will be addressed in a future version with a separate technique for rendering distant cells. This setting interacts with viewing distance and field of view settings. @@ -163,11 +166,13 @@ prediction time :Range: >=0 :Default: 1 -The amount of time (in seconds) in the future to predict the player position for. This predicted position is used to preload any cells and/or distant terrain required at that position. +The amount of time (in seconds) in the future to predict the player position for. +This predicted position is used to preload any cells and/or distant terrain required at that position. This setting will only have an effect if 'preload enabled' is set or the 'distant terrain' in the Terrain section is set. -Increasing this setting from its default may help if your computer/hard disk is too slow to preload in time and you see loading screens and/or lag spikes. +Increasing this setting from its default may help if your computer/hard disk is too slow to preload in time and you see +loading screens and/or lag spikes. cache expiry delay ------------------ @@ -185,7 +190,10 @@ target framerate :Range: >0 :Default: 60 -Affects the time to be set aside each frame for graphics preloading operations. The game will distribute the preloading over several frames so as to not go under the specified framerate. For best results, set this value to the monitor's refresh rate. If you still experience stutters on turning around, you can try a lower value, although the framerate during loading will suffer a bit in that case. +Affects the time to be set aside each frame for graphics preloading operations. +The game will distribute the preloading over several frames so as to not go under the specified framerate. +For best results, set this value to the monitor's refresh rate. If you still experience stutters on turning around, +you can try a lower value, although the framerate during loading will suffer a bit in that case. pointers cache size ------------------- @@ -194,4 +202,6 @@ pointers cache size :Range: >0 :Default: 40 -The count of object pointers that will be saved for a faster search by object ID. This is a temporary setting that can be used to mitigate scripting performance issues with certain game files. If your profiler (press F3 twice) displays a large overhead for the Scripting section, try increasing this setting. +The count of object pointers that will be saved for a faster search by object ID. +This is a temporary setting that can be used to mitigate scripting performance issues with certain game files. +If your profiler (press F3 twice) displays a large overhead for the Scripting section, try increasing this setting. diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 4c3f4579f7..475a2f175c 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -9,11 +9,11 @@ show owned :Default: 0 Enable visual clues for items owned by NPCs when the crosshair is on the object. -If the setting is 0, no clues are provided which is the default Morrowind behavior. +If the setting is 0, no clues are provided which is the default Morrowind behaviour. If the setting is 1, the background of the tool tip for the object is highlighted -in the color specified by the color background owned setting in the GUI Settings Section. -If the setting is 2, the crosshair is the color of the color crosshair owned setting in the GUI Settings section. -If the setting is 3, both the tool tip background and the crosshair are colored. +in the colour specified by the colour background owned setting in the GUI Settings Section. +If the setting is 2, the crosshair is the colour of the colour crosshair owned setting in the GUI Settings section. +If the setting is 3, both the tool tip background and the crosshair are coloured. The crosshair is not visible if crosshair is false. This setting can be configured in Advanced tab of the launcher. @@ -71,12 +71,14 @@ can loot during death animation :Range: True/False :Default: True -If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. -However disposing corpses during death animation is not recommended - death counter may not be incremented, and this behaviour can break quests. +If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, +if they are not in combat. However disposing corpses during death animation is not recommended - +death counter may not be incremented, and this behaviour can break quests. This is how Morrowind behaves. If this setting is false, player has to wait until end of death animation in all cases. -This case is more safe, but makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. +This case is more safe, but makes using of summoned creatures exploit +(looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation. This setting can be toggled in Advanced tab of the launcher. @@ -167,7 +169,8 @@ use additional anim sources :Default: False Allow to load additional animation sources when enabled. -For example, if the main animation mesh has name Meshes/x.nif, an engine will load all KF-files from Animations/x folder and its child folders. +For example, if the main animation mesh has name Meshes/x.nif, +an engine will load all KF-files from Animations/x folder and its child folders. Can be useful if you want to use several animation replacers without merging them. Attention: animations from AnimKit have own format and are not supposed to be directly loaded in-game! This setting can only be configured by editing the settings configuration file. @@ -179,7 +182,8 @@ barter disposition change is permanent :Range: True/False :Default: False -If this setting is true, disposition change of merchants caused by trading will be permanent and won't be discarded upon exiting dialogue with them. +If this setting is true, +disposition change of merchants caused by trading will be permanent and won't be discarded upon exiting dialogue with them. This imitates the option Morrowind Code Patch offers. This setting can be toggled in Advanced tab of the launcher. diff --git a/docs/source/reference/modding/settings/map.rst b/docs/source/reference/modding/settings/map.rst index c973c26e50..2cf53f704f 100644 --- a/docs/source/reference/modding/settings/map.rst +++ b/docs/source/reference/modding/settings/map.rst @@ -8,7 +8,8 @@ global :Range: True/False :Default: False -If this value is true, the map window will display the world map, otherwise the local map. The setting updates automatically when pressing the local/world map switch button on the map window. +If this value is true, the map window will display the world map, otherwise the local map. +The setting updates automatically when pressing the local/world map switch button on the map window. global map cell size -------------------- @@ -110,4 +111,6 @@ local map cell distance :Range: >= 1 :Default: 1 -Similar to "exterior cell load distance" in the Cells section, controls how many cells are rendered on the local map. Values higher than the default may result in longer loading times. Please note that only loaded cells can be rendered, so this setting must be lower or equal to "exterior cell load distance" to work properly. +Similar to "exterior cell load distance" in the Cells section, controls how many cells are rendered on the local map. +Values higher than the default may result in longer loading times. Please note that only loaded cells can be rendered, +so this setting must be lower or equal to "exterior cell load distance" to work properly. diff --git a/docs/source/reference/modding/settings/saves.rst b/docs/source/reference/modding/settings/saves.rst index 5add36c0c7..0e58d66d84 100644 --- a/docs/source/reference/modding/settings/saves.rst +++ b/docs/source/reference/modding/settings/saves.rst @@ -29,7 +29,8 @@ timeplayed :Default: False This setting determines whether the amount of the time the player has spent playing will be displayed -for each saved game in the Load menu. Currently, the counter includes time spent in menus, including the pause menu, but does not include time spent with the game window minimized. +for each saved game in the Load menu. Currently, the counter includes time spent in menus, including the pause menu, +but does not include time spent with the game window minimized. This setting can only be configured by editing the settings configuration file. @@ -40,6 +41,8 @@ max quicksaves :Range: >0 :Default: 1 -This setting determines how many quicksave and autosave slots you can have at a time. If greater than 1, quicksaves will be sequentially created each time you quicksave. Once the maximum number of quicksaves has been reached, the oldest quicksave will be recycled the next time you perform a quicksave. +This setting determines how many quicksave and autosave slots you can have at a time. If greater than 1, +quicksaves will be sequentially created each time you quicksave. Once the maximum number of quicksaves has been reached, +the oldest quicksave will be recycled the next time you perform a quicksave. This setting can only be configured by editing the settings configuration file. diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index be1ecebf0c..b36f642852 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -39,7 +39,8 @@ Only affects objects that render with shaders (see 'force shaders' option). Always affects terrain. Leaving this option at its default makes the lighting compatible with Morrowind's fixed-function method, -but the lighting may appear dull and there might be color shifts. Setting this option to 'false' results in more dynamic lighting. +but the lighting may appear dull and there might be colour shifts. +Setting this option to 'false' results in more dynamic lighting. auto use object normal maps --------------------------- @@ -83,7 +84,7 @@ auto use terrain specular maps :Default: False If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. -The texture must contain the layer color in the RGB channel (as usual), and a specular multiplier in the alpha channel. +The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel. normal map pattern ------------------ diff --git a/docs/source/reference/modding/settings/sound.rst b/docs/source/reference/modding/settings/sound.rst index b9587a5c46..895b919fb1 100644 --- a/docs/source/reference/modding/settings/sound.rst +++ b/docs/source/reference/modding/settings/sound.rst @@ -67,7 +67,7 @@ voice volume :Range: 0.0 (silent) to 1.0 (maximum volume) :Default: 0.8 -This setting controls the volume for spoken dialog from NPCs. +This setting controls the volume for spoken dialogue from NPCs. This setting can be changed in game using the Voice slider from the Audio panel of the Options menu. @@ -122,7 +122,7 @@ hrtf This setting specifies which HRTF profile to use when HRTF is enabled. Blank means use the default. This setting has no effect if HRTF is not enabled based on the hrtf enable setting. -Allowed values for this field are enumerated in openmw.log file is an HRTF enabled ausio system is installed. +Allowed values for this field are enumerated in openmw.log file is an HRTF enabled audio system is installed. The default value is empty, which uses the default profile. This setting can only be configured by editing the settings configuration file. diff --git a/docs/source/reference/modding/settings/terrain.rst b/docs/source/reference/modding/settings/terrain.rst index 7f641888ac..687f55e5e3 100644 --- a/docs/source/reference/modding/settings/terrain.rst +++ b/docs/source/reference/modding/settings/terrain.rst @@ -12,7 +12,8 @@ Controls whether the engine will use paging and LOD algorithms to load the terra Otherwise, only the terrain of the surrounding cells is loaded. .. note:: - When enabling distant terrain, make sure the 'viewing distance' in the camera section is set to a larger value so that you can actually see the additional terrain. + When enabling distant terrain, make sure the 'viewing distance' in the camera section is set to a larger value so + that you can actually see the additional terrain. To avoid frame drops as the player moves around, nearby terrain pages are always preloaded in the background, regardless of the preloading settings in the 'Cells' section, diff --git a/docs/source/reference/modding/settings/video.rst b/docs/source/reference/modding/settings/video.rst index 3eb7c51289..a8a95739ea 100644 --- a/docs/source/reference/modding/settings/video.rst +++ b/docs/source/reference/modding/settings/video.rst @@ -170,7 +170,8 @@ contrast This setting controls the contrast correction for all video in the game. -This setting can only be configured by editing the settings configuration file. It has been reported to not work on some Linux systems. +This setting can only be configured by editing the settings configuration file. +It has been reported to not work on some Linux systems. gamma ----- @@ -183,4 +184,5 @@ This setting controls the gamma correction for all video in the game. Gamma is an exponent that makes colors brighter if greater than 1.0 and darker if less than 1.0. This setting can be changed in the Detail tab of the Video panel of the Options menu. -It has been reported to not work on some Linux systems, and therefore the in-game setting in the Options menu has been disabled on Linux systems. +It has been reported to not work on some Linux systems, +and therefore the in-game setting in the Options menu has been disabled on Linux systems. diff --git a/docs/source/reference/modding/settings/windows.rst b/docs/source/reference/modding/settings/windows.rst index 2fae9c3e31..2a60745c2e 100644 --- a/docs/source/reference/modding/settings/windows.rst +++ b/docs/source/reference/modding/settings/windows.rst @@ -25,7 +25,8 @@ Hand editing the configuration file might result in some fine tuning for alignme but the settings will be overwritten if a window is moved. .. note:: - To scale the windows, making the widgets proportionally larger, see the scaling factor setting in the GUI section instead. + To scale the windows, making the widgets proportionally larger, + see the scaling factor setting in the GUI section instead. :Type: boolean :Range: True/False @@ -210,7 +211,7 @@ dialogue w = 0.45 -The dialog window, for talking with NPCs. +The dialogue window, for talking with NPCs. Activated by clicking on a NPC. alchemy diff --git a/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst b/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst index 421fa67a70..5fbff38d4e 100644 --- a/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst +++ b/docs/source/reference/modding/texture-modding/convert-bump-mapped-mods.rst @@ -20,29 +20,43 @@ General introduction to normal map conversion :Authors: Joakim (Lysol) Berg :Updated: 2016-11-11 -This page has general information and tutorials on how normal mapping works in OpenMW and how you can make mods using the old fake normal mapping technique (such as `Netch Bump mapped`_ and `Hlaalu Bump mapped`_, and maybe the most (in)famous one to give shiny rocks in OpenMW, the mod `On the Rocks`_!, featured in MGSO and Morrowind Rebirth) work in OpenMW. +This page has general information and tutorials on how normal mapping works in OpenMW and how you can make mods using +the old fake normal mapping technique (such as `Netch Bump mapped`_ and `Hlaalu Bump mapped`_, and maybe the most +(in)famous one to give shiny rocks in OpenMW, the mod `On the Rocks`_!, featured in MGSO and Morrowind Rebirth) work in OpenMW. *Note:* The conversion made in the `Converting Apel's Various Things - Sacks`_-part of this tutorial require the use of the application NifSkope_. -*Another note:* I will use the terms bump mapping and normal mapping simultaneously. Normal mapping is one form of bump mapping. In other words, normal mapping is bump mapping, but bump mapping isn't necessarily normal mapping. There are several techniques for bump mapping, and normal mapping is the most common one today. +*Another note:* I will use the terms bump mapping and normal mapping simultaneously. +Normal mapping is one form of bump mapping. In other words, normal mapping is bump mapping, +but bump mapping isn't necessarily normal mapping. +There are several techniques for bump mapping, and normal mapping is the most common one today. So let's get on with it. Normal Mapping in OpenMW ************************ -Normal mapping in OpenMW works in a very simple way: The engine just looks for a texture with a *_n.dds* suffix, and you're done. +Normal mapping in OpenMW works in a very simple way: The engine just looks for a texture with a *_n.dds* suffix, +and you're done. So to expand on this a bit, let's take a look at how a model seeks for textures. -Let us assume we have the model *example.nif*. In this model file, there should be a tag (NiSourceTexture) that states what texture it should use and where to find it. Typically, it will point to something like *exampletexture_01.dds*. This texture is supposed to be located directly in the Textures folder since it does not state anything else. If the model is a custom made one, modders tend to group their textures in separate folders, just to easily keep track of them. It might be something like *./Textures/moddername/exampletexture_02.dds*. +Let us assume we have the model *example.nif*. In this model file, +there should be a tag (NiSourceTexture) that states what texture it should use and where to find it. Typically, +it will point to something like *exampletexture_01.dds*. This texture is supposed to be located directly in the +Textures folder since it does not state anything else. If the model is a custom made one, modders tend to group +their textures in separate folders, just to easily keep track of them. +It might be something like *./Textures/moddername/exampletexture_02.dds*. -When OpenMW finally adds normal mapping, it simply takes the NiSourceTexture file path, e.g., *exampletexture_01.dds*, and looks for a *exampletexture_01_n.dds*. If it can't find this file, no normal mapping is added. If it *does* find this file, the model will use this texture as a normal map. Simple. +When OpenMW finally adds normal mapping, it simply takes the NiSourceTexture file path, e.g., +*exampletexture_01.dds*, and looks for a *exampletexture_01_n.dds*. If it can't find this file, no normal mapping is added. +If it *does* find this file, the model will use this texture as a normal map. Simple. Activating normal mapping shaders in OpenMW ******************************************* -Before normal (and specular and parallax) maps will show up in OpenMW, you'll need to activate them in the settings.cfg_-file. Add these rows where it would make sense: +Before normal (and specular and parallax) maps will show up in OpenMW, you'll need to activate them in the +settings.cfg_-file. Add these rows where it would make sense: :: @@ -72,13 +86,26 @@ Normal mapping in Morrowind with Morrowind Code Patch **Conversion difficulty:** *Varies. Sometimes quick and easy, sometimes time-consuming and hard.* -You might have bumped (pun intended) on a few bump-mapped texture packs for Morrowind that require the Morrowind Code Patch (MCP). You might even be thinking: Why doesn't OpenMW just support these instead of reinventing the wheel? I know it sounds strange, but it will make sense. Here's how MCP handles normal maps: +You might have bumped (pun intended) on a few bump-mapped texture packs for Morrowind that require the +Morrowind Code Patch (MCP). You might even be thinking: Why doesn't OpenMW just support these instead of reinventing +the wheel? I know it sounds strange, but it will make sense. Here's how MCP handles normal maps: -Morrowind does not recognize normal maps (they weren't really a "thing" yet in 2002), so even if you have a normal map, Morrowind will not load and display it. MCP has a clever way to solve this issue, by using something Morrowind *does* support, namely environment maps. You could add a tag for an environment map and then add a normal map as the environment map, but you'd end up with a shiny ugly model in the game. MCP solves this by turning down the brightness of the environment maps, making the model look *kind of* as if it had a normal map applied to it. I say kind of because it does not really look as good as normal mapping usually does. It was a hacky way to do it, but it was the only way at the time, and therefore the best way. +Morrowind does not recognize normal maps (they weren't really a "thing" yet in 2002), so even if you have a normal map, +Morrowind will not load and display it. MCP has a clever way to solve this issue, by using something Morrowind *does* support, +namely environment maps. You could add a tag for an environment map and then add a normal map as the environment map, +but you'd end up with a shiny ugly model in the game. MCP solves this by turning down the brightness of the environment maps, +making the model look *kind of* as if it had a normal map applied to it. +I say kind of because it does not really look as good as normal mapping usually does. It was a hacky way to do it, +but it was the only way at the time, and therefore the best way. -The biggest problem with this is not that it doesn't look as good as it could – no, the biggest problem in my opinion is that it requires you to state the file paths for your normal map textures *in the models*! For buildings, which often use several textures for one single model file, it could take *ages* to do this, and you had to do it for dozens of model files too. You also had to ship your texture pack with model files, making your mod bigger in file size. +The biggest problem with this is not that it doesn't look as good as it could – no, +the biggest problem in my opinion is that it requires you to state the file paths for your normal map textures *in the models*! +For buildings, which often use several textures for one single model file, it could take *ages* to do this, +and you had to do it for dozens of model files too. You also had to ship your texture pack with model files, +making your mod bigger in file size. -These are basically the reasons why OpenMW does not support fake bump maps like MCP does. It is just a really bad way to enhance your models, all the more when you have the possibility to do it in a better way. +These are basically the reasons why OpenMW does not support fake bump maps like MCP does. +It is just a really bad way to enhance your models, all the more when you have the possibility to do it in a better way. Normal mapping in Morrowind with MGE XE *************************************** @@ -86,9 +113,18 @@ Normal mapping in Morrowind with MGE XE **Conversion difficulty:** *Easy* -The most recent feature on this topic is that the Morrowind Graphics Extender (MGE) finally started to support real normal mapping in an experimental version available here: `MGE XE`_ (you can't use MGE with OpenMW!). Not only this but it also adds full support for physically based rendering (PBR), making it one step ahead of OpenMW in terms of texturing techniques. However, OpenMW will probably have this feature in the future too – and let's hope that OpenMW and MGE will handle PBR in a similar fashion in the future so that mods can be used for both MGE and OpenMW without any hassle. +The most recent feature on this topic is that the Morrowind Graphics Extender (MGE) finally started to support real +normal mapping in an experimental version available here: `MGE XE`_ (you can't use MGE with OpenMW!). +Not only this but it also adds full support for physically based rendering (PBR), +making it one step ahead of OpenMW in terms of texturing techniques. However, +OpenMW will probably have this feature in the future too – and let's hope that OpenMW and MGE will handle PBR in a +similar fashion in the future so that mods can be used for both MGE and OpenMW without any hassle. -I haven't researched that much on the MGE variant yet but it does support real implementation of normal mapping, making it really easy to convert mods made for MGE into OpenMW (I'm only talking about the normal map textures though). There's some kind of text file if I understood it correctly that MGE uses to find the normal map. OpenMW does not need this, you just have to make sure the normal map has the same name as the diffuse texture but with the correct suffix after. +I haven't researched that much on the MGE variant yet but it does support real implementation of normal mapping, +making it really easy to convert mods made for MGE into OpenMW (I'm only talking about the normal map textures though). +There's some kind of text file if I understood it correctly that MGE uses to find the normal map. +OpenMW does not need this, you just have to make sure the normal map has the same name as the diffuse texture but with +the correct suffix after. Now, on to the tutorials. @@ -99,14 +135,21 @@ Converting PeterBitt's Scamp Replacer :Authors: Joakim (Lysol) Berg :Updated: 2016-11-11 -So, let's say you've found out that PeterBitt_ makes awesome models and textures featuring physically based rendering (PBR) and normal maps. Let's say that you tried to run his `PBR Scamp Replacer`_ in OpenMW and that you were greatly disappointed when the normal map didn't seem to work. Lastly, let's say you came here, looking for some answers. Am I right? Great. Because you've come to the right place! +So, let's say you've found out that PeterBitt_ makes awesome models and textures featuring physically based rendering +(PBR) and normal maps. Let's say that you tried to run his `PBR Scamp Replacer`_ in OpenMW and that you were greatly +disappointed when the normal map didn't seem to work. Lastly, let's say you came here, looking for some answers. +Am I right? Great. Because you've come to the right place! -*A quick note before we begin*: Please note that you can only use the normal map texture and not the rest of the materials, since PBR isn't implemented in OpenMW yet. Sometimes PBR textures can look dull without all of the texture files, so have that in mind. +*A quick note before we begin*: Please note that you can only use the normal map texture and not the rest of the materials, +since PBR isn't implemented in OpenMW yet. Sometimes PBR textures can look dull without all of the texture files, +so have that in mind. Tutorial - MGE ************** -In this tutorial, I will use PeterBitt's `PBR Scamp Replacer`_ as an example, but any mod featuring PBR that requires the PBR version of MGE will do, provided it also includes a normal map (which it probably does). +In this tutorial, I will use PeterBitt's `PBR Scamp Replacer`_ as an example, +but any mod featuring PBR that requires the PBR version of MGE will do, +provided it also includes a normal map (which it probably does). So, follow these steps: @@ -120,11 +163,14 @@ So, follow these steps: #. Rename your newly extracted file (``tx_Scamp_normals.dds``) to ``tx_Scamp_n.dds`` (which is exactly the same name as the diffuse texture file, except for the added *_n* suffix before the filename extention). #. You're actually done! -So as you might notice, converting these mods is very simple and takes just a couple of minutes. It's more or less just a matter of renaming and moving a few files. +So as you might notice, converting these mods is very simple and takes just a couple of minutes. +It's more or less just a matter of renaming and moving a few files. -I totally recommend you to also try this on PeterBitt's Nix Hound replacer and Flash3113's various replacers. It should be the same principle to get those to work. +I totally recommend you to also try this on PeterBitt's Nix Hound replacer and Flash3113's various replacers. +It should be the same principle to get those to work. -And let's hope that some one implements PBR shaders to OpenMW too, so that we can use all the material files of these mods in the future. +And let's hope that some one implements PBR shaders to OpenMW too, +so that we can use all the material files of these mods in the future. Converting Lougian's Hlaalu Bump mapped --------------------------------------- @@ -133,12 +179,17 @@ Converting Lougian's Hlaalu Bump mapped :Authors: Joakim (Lysol) Berg :Updated: 2016-11-11 -Converting textures made for the Morrowind Code Patch (MCP) fake bump mapping can be really easy or a real pain, depending on a few circumstances. In this tutorial, we will look at a very easy, although in some cases a bit time-consuming, example. +Converting textures made for the Morrowind Code Patch (MCP) fake bump mapping can be really easy or a real pain, +depending on a few circumstances. In this tutorial, we will look at a very easy, +although in some cases a bit time-consuming, example. Tutorial - MCP, Part 1 ********************** -We will be converting a quite popular texture replacer of the Hlaalu architecture, namely Lougian's `Hlaalu Bump mapped`_. Since this is just a texture pack and not a model replacer, we can convert the mod in a few minutes by just renaming a few dozen files and by *not* extracting the included model (``.nif``) files when installing the mod. +We will be converting a quite popular texture replacer of the Hlaalu architecture, namely Lougian's `Hlaalu Bump mapped`_. +Since this is just a texture pack and not a model replacer, +we can convert the mod in a few minutes by just renaming a few dozen files and by *not* extracting the included model +(``.nif``) files when installing the mod. #. Download Lougian's `Hlaalu Bump mapped`_. #. Install the mod by extracting the ``./Textures`` folder to a data folder the way you usually install mods (**Pro tip**: Install using OpenMW's `Multiple data folders`_ function!). @@ -149,7 +200,8 @@ We will be converting a quite popular texture replacer of the Hlaalu architectur - As a nice bonus to this tutorial, this pack actually included one specularity texture too. We should use it of course. It's the one called "``tx_glass_amber_02_reflection.dds``". For OpenMW to recognize this file and use it as a specular map, you need to change the *_reflection.dds* part to *_spec.dds*, resulting in the name ``tx_glass_amber_01_spec.dds``. #. That should be it. Really simple, but I do know that it takes a few minutes to rename all those files. -Now – if the mod you want to change includes custom made models it gets a bit more complicated I'm afraid. But that is for the next tutorial. +Now – if the mod you want to change includes custom made models it gets a bit more complicated I'm afraid. +But that is for the next tutorial. Converting Apel's Various Things - Sacks ---------------------------------------- @@ -158,12 +210,16 @@ Converting Apel's Various Things - Sacks :Authors: Joakim (Lysol) Berg :Updated: 2016-11-09 -In part one of this tutorial, we converted a mod that only included modified Morrowind model (``.nif``) files so that the normal maps could be loaded in Morrowind with MCP. We ignored those model files since they are not needed with OpenMW. In this tutorial however, we will convert a mod that includes new, custom made models. In other words, we cannot just ignore those files this time. +In part one of this tutorial, we converted a mod that only included modified Morrowind model (``.nif``) +files so that the normal maps could be loaded in Morrowind with MCP. +We ignored those model files since they are not needed with OpenMW. In this tutorial however, +we will convert a mod that includes new, custom made models. In other words, we cannot just ignore those files this time. Tutorial - MCP, Part 2 ********************** -The sacks included in Apel's `Various Things - Sacks`_ come in two versions – Without bump mapping, and with bump mapping. Since we want the glory of normal mapping in our OpenMW setup, we will go with the bump-mapped version. +The sacks included in Apel's `Various Things - Sacks`_ come in two versions – Without bump mapping, and with bump mapping. +Since we want the glory of normal mapping in our OpenMW setup, we will go with the bump-mapped version. #. Start by downloading Apel's `Various Things - Sacks`_ from Nexus. #. Once downloaded, install it the way you'd normally install your mods (**Pro tip**: Install using OpenMW's `Multiple data folders`_ function!). @@ -181,7 +237,9 @@ The sacks included in Apel's `Various Things - Sacks`_ come in two versions – #. OpenMW detects normal maps if they have the same name as the base diffuse texture, but with a *_n.dds* suffix. In this mod, the normal maps has a suffix of *_nm.dds*. Change all the files that ends with *_nm.dds* to instead end with *_n.dds*. #. Finally, `we are done`_! -Since these models have one or two textures applied to them, the fix was not that time-consuming. It gets worse when you have to fix a model that uses loads of textures. The principle is the same, it just requires more manual work which is annoying and takes time. +Since these models have one or two textures applied to them, the fix was not that time-consuming. +It gets worse when you have to fix a model that uses loads of textures. The principle is the same, +it just requires more manual work which is annoying and takes time. .. _`Netch Bump mapped`: https://www.nexusmods.com/morrowind/mods/42851/? .. _`Hlaalu Bump mapped`: https://www.nexusmods.com/morrowind/mods/42396/? diff --git a/docs/source/reference/modding/texture-modding/texture-basics.rst b/docs/source/reference/modding/texture-modding/texture-basics.rst index d9137c89d8..51523af3f3 100644 --- a/docs/source/reference/modding/texture-modding/texture-basics.rst +++ b/docs/source/reference/modding/texture-modding/texture-basics.rst @@ -19,10 +19,16 @@ OpenMW automatically uses shaders for objects with these mapping techniques. Normal Mapping ############## -To plug in a normal map, you name the normal map as the diffuse texture but with a specified suffix after. OpenMW will then recognise the file and load it as a normal map, provided you have set up your settings file correctly. See the section `Automatic use`_ further down below for detailed information. +To plug in a normal map, you name the normal map as the diffuse texture but with a specified suffix after. +OpenMW will then recognise the file and load it as a normal map, provided you have set up your settings file correctly. +See the section `Automatic use`_ further down below for detailed information. .. note:: - While the original Morrowind engine does support the loading of a BumpTexture slot in the NIF, it will not display it as a normal map. Morrowind Code Patch (MCP) added a way to hack normal maps into the engine by first enabling the engine to load the BumpTexture slot as an environment map and then turn down the brightness of the environment map. This will imitate how a real normal map shader would display a normal map, but it will not look exactly the same. + While the original Morrowind engine does support the loading of a BumpTexture slot in the NIF, + it will not display it as a normal map. Morrowind Code Patch (MCP) + added a way to hack normal maps into the engine by first enabling the engine to load the BumpTexture slot as an + environment map and then turn down the brightness of the environment map. + This will imitate how a real normal map shader would display a normal map, but it will not look exactly the same. OpenMW uses standard normal mapping, which achieves much better results. Unfortunately, this difference can result in incompatibilities. Some mods From b6f23cd3661b7195f1068a4aff162e7518130beb Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 27 Oct 2018 13:27:25 +0300 Subject: [PATCH 111/133] Make constants usage more obvious --- apps/openmw/mwrender/animation.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 8ae8800a6c..8463b7bfe5 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -15,13 +15,13 @@ #include -#include - #include #include #include #include +#include + #include // KeyframeHolder #include @@ -1791,10 +1791,10 @@ namespace MWRender { // TODO: use global attenuation settings - // 1 pt of Light effect magnitude corresponds to 1 foot of light source radius, which is about 21.33 game units, - // but Morrowind uses imprecise value of foot for magic effects. - float radius = effect * 22.f; - float linearAttenuation = 3.f / radius; + // 1 pt of Light magnitude corresponds to 1 foot of radius + float radius = effect * std::ceil(Constants::UnitsPerFoot); + const float linearValue = 3.f; // Currently hardcoded: unmodified Morrowind attenuation settings + float linearAttenuation = linearValue / radius; if (!mGlowLight || linearAttenuation != mGlowLight->getLight(0)->getLinearAttenuation()) { From abdf40e0d54bfc4d8970a5ca742ad998586ce355 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 27 Oct 2018 16:33:26 +0300 Subject: [PATCH 112/133] Avoid making expensive visitEffectSources calls if no spell absorption effect is active --- apps/openmw/mwmechanics/spellcasting.cpp | 39 +++++++++++++----------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 4a74e1647f..7c9e207e96 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -472,28 +472,31 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->setEnemy(target); // Try absorbing if it's a spell - // NOTE: Vanilla does this once per spell absorption effect source instead of adding the % from all sources together, so use the same approach here + // Unlike Reflect, this is done once per spell absorption effect source bool absorbed = false; if (spell && caster != target && target.getClass().isActor()) { - GetAbsorptionProbability check(target); - MWMechanics::CreatureStats& stats = target.getClass().getCreatureStats(target); - stats.getActiveSpells().visitEffectSources(check); - stats.getSpells().visitEffectSources(check); - if (target.getClass().hasInventoryStore(target)) - target.getClass().getInventoryStore(target).visitEffectSources(check); - - int absorb = check.mProbability * 100; - absorbed = (Misc::Rng::roll0to99() < absorb); - if (absorbed) + CreatureStats& stats = target.getClass().getCreatureStats(target); + if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f) { - const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); - MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( - "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, ""); - // Magicka is increased by cost of spell - DynamicStat magicka = target.getClass().getCreatureStats(target).getMagicka(); - magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); - target.getClass().getCreatureStats(target).setMagicka(magicka); + GetAbsorptionProbability check(target); + stats.getActiveSpells().visitEffectSources(check); + stats.getSpells().visitEffectSources(check); + if (target.getClass().hasInventoryStore(target)) + target.getClass().getInventoryStore(target).visitEffectSources(check); + + int absorb = check.mProbability * 100; + absorbed = (Misc::Rng::roll0to99() < absorb); + if (absorbed) + { + const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); + MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( + "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, ""); + // Magicka is increased by cost of spell + DynamicStat magicka = stats.getMagicka(); + magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); + stats.setMagicka(magicka); + } } } From 7a7b47b0ba6a6582ac9ac501f31b2a67bc83c7ea Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 27 Oct 2018 20:31:03 +0400 Subject: [PATCH 113/133] Add missing changelog entries --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed387eaf63..4d49672e00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug #2256: Landing sound not playing when jumping immediately after landing Bug #2274: Thin platform clips through player character instead of lifting Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped + Bug #2446: Restore Attribute/Skill should allow restoring drained attributes Bug #2455: Creatures attacks degrade armor Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash Bug #2626: Resurrecting the player does not resume the game @@ -46,6 +47,7 @@ Bug #4110: Fixed undo / redo menu text losing the assigned shortcuts Bug #4125: OpenMW logo cropped on bugtracker Bug #4215: OpenMW shows book text after last EOL tag + Bug #4217: Fixme implementation differs from Morrowind's Bug #4221: Characters get stuck in V-shaped terrain Bug #4230: AiTravel package issues break some Tribunal quests Bug #4231: Infected rats from the "Crimson Plague" quest rendered unconscious by change in Drain Fatigue functionality @@ -55,6 +57,7 @@ Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+ Bug #4286: Scripted animations can be interrupted Bug #4291: Non-persistent actors that started the game as dead do not play death animations + Bug #4292: CenterOnCell implementation differs from vanilla Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4304: "Follow" not working as a second AI package Bug #4307: World cleanup should remove dead bodies only if death animation is finished @@ -65,7 +68,7 @@ Bug #4368: Settings window ok button doesn't have key focus by default Bug #4378: On-self absorb spells restore stats Bug #4393: NPCs walk back to where they were after using ResetActors - Bug #4416: Handle exception if we try to play non-music file + Bug #4416: Non-music files crash the game when they are tried to be played Bug #4419: MRK NiStringExtraData is handled incorrectly Bug #4426: RotateWorld behavior is incorrect Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 @@ -81,6 +84,7 @@ Bug #4459: NotCell dialogue condition doesn't support partial matches Bug #4460: Script function "Equip" doesn't bypass beast restrictions Bug #4461: "Open" spell from non-player caster isn't a crime + Bug #4463: %g format doesn't return more digits Bug #4464: OpenMW keeps AiState cached storages even after we cancel AI packages Bug #4467: Content selector: cyrillic characters are decoded incorrectly in plugin descriptions Bug #4469: Abot Silt Striders – Model turn 90 degrees on horizontal From d27d48eb2259338bb1296bb67cacd0a53d839aa5 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 27 Oct 2018 19:45:40 +0300 Subject: [PATCH 114/133] Fix revert record icon --- files/opencs/resources.qrc | 1 + 1 file changed, 1 insertion(+) diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc index 93b47dbdda..4fc4bdf285 100644 --- a/files/opencs/resources.qrc +++ b/files/opencs/resources.qrc @@ -94,6 +94,7 @@ record-preview.png record-clone.png record-add.png + record-revert.png resources-icon.png resources-mesh.png resources-music.png From 8337423e6c273ed507c5178cc35c1065766bdc26 Mon Sep 17 00:00:00 2001 From: Alec Nunn Date: Sat, 27 Oct 2018 20:16:51 -0700 Subject: [PATCH 115/133] Fixed up grammar in the game settings documentation. --- docs/source/reference/modding/settings/game.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 475a2f175c..124b54f388 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -168,11 +168,11 @@ use additional anim sources :Range: True/False :Default: False -Allow to load additional animation sources when enabled. +Allow the engine to load additional animation sources when enabled. For example, if the main animation mesh has name Meshes/x.nif, -an engine will load all KF-files from Animations/x folder and its child folders. -Can be useful if you want to use several animation replacers without merging them. -Attention: animations from AnimKit have own format and are not supposed to be directly loaded in-game! +the engine will load all KF-files from Animations/x folder and its child folders. +This can be useful if you want to use several animation replacers without merging them. +Attention: animations from AnimKit have their own format and are not supposed to be directly loaded in-game! This setting can only be configured by editing the settings configuration file. barter disposition change is permanent @@ -184,6 +184,6 @@ barter disposition change is permanent If this setting is true, disposition change of merchants caused by trading will be permanent and won't be discarded upon exiting dialogue with them. -This imitates the option Morrowind Code Patch offers. +This imitates the option that Morrowind Code Patch offers. This setting can be toggled in Advanced tab of the launcher. From 6e05853478585f5eb2034771f1246dbc310c9de5 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 28 Oct 2018 11:44:14 +0400 Subject: [PATCH 116/133] Center progress bar when there are active messageboxes (bug #4691) --- CHANGELOG.md | 1 + apps/openmw/mwbase/windowmanager.hpp | 2 ++ apps/openmw/mwgui/loadingscreen.cpp | 8 ++++++-- apps/openmw/mwgui/loadingscreen.hpp | 2 +- apps/openmw/mwgui/messagebox.cpp | 5 +++++ apps/openmw/mwgui/messagebox.hpp | 2 ++ apps/openmw/mwgui/windowmanagerimp.cpp | 9 +++++++++ apps/openmw/mwgui/windowmanagerimp.hpp | 2 ++ apps/openmw/mwstate/statemanagerimp.cpp | 6 ++++-- apps/openmw/mwworld/scene.cpp | 6 ++++-- components/loadinglistener/loadinglistener.hpp | 2 +- 11 files changed, 37 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed387eaf63..2200bccb17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -147,6 +147,7 @@ Bug #4684: Spell Absorption is additive Bug #4685: Missing sound causes an exception inside Say command Bug #4689: Default creature soundgen entries are not used + Bug #4691: Loading bar for cell should be moved up when text is still active at bottom of screen Feature #912: Editor: Add missing icons to UniversalId tables Feature #1221: Editor: Creature/NPC rendering Feature #1617: Editor: Enchantment effect record verifier diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 36beb25fc8..2eec78526f 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -285,6 +285,8 @@ namespace MWBase virtual void setEnemy (const MWWorld::Ptr& enemy) = 0; + virtual int getMessagesCount() const = 0; + virtual const Translation::Storage& getTranslationDataStorage() const = 0; /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 3bb3ee260d..7f641d4088 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -96,7 +96,7 @@ namespace MWGui Log(Debug::Warning) << "Warning: no splash screens found!"; } - void LoadingScreen::setLabel(const std::string &label, bool important) + void LoadingScreen::setLabel(const std::string &label, bool important, bool center) { mImportantLabel = important; @@ -105,7 +105,11 @@ namespace MWGui MyGUI::IntSize size(mLoadingText->getTextSize().width+padding, mLoadingBox->getHeight()); size.width = std::max(300, size.width); mLoadingBox->setSize(size); - mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mLoadingBox->getTop()); + + if (center) + mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight()/2 - mLoadingBox->getHeight()/2); + else + mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight() - mLoadingBox->getHeight() - 8); } void LoadingScreen::setVisible(bool visible) diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index bdd210d00d..e74a602067 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -34,7 +34,7 @@ namespace MWGui virtual ~LoadingScreen(); /// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details - virtual void setLabel (const std::string& label, bool important); + virtual void setLabel (const std::string& label, bool important, bool center); virtual void loadingOn(bool visible=true); virtual void loadingOff(); virtual void setProgressRange (size_t range); diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index e83c4b238a..6c2d85dd9e 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -35,6 +35,11 @@ namespace MWGui } } + int MessageBoxManager::getMessagesCount() + { + return mMessageBoxes.size(); + } + void MessageBoxManager::clear() { if (mInterMessageBoxe) diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 156a17e678..e4e4b743c2 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -28,6 +28,8 @@ namespace MWGui bool createInteractiveMessageBox (const std::string& message, const std::vector& buttons); bool isInteractiveMessageBox (); + int getMessagesCount(); + const InteractiveMessageBox* getInteractiveMessageBox() const { return mInterMessageBoxe; } /// Remove all message boxes diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 3937518552..f02314de63 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1670,6 +1670,15 @@ namespace MWGui mHud->setEnemy(enemy); } + int WindowManager::getMessagesCount() const + { + int count = 0; + if (mMessageBoxManager) + count = mMessageBoxManager->getMessagesCount(); + + return count; + } + Loading::Listener* WindowManager::getLoadingScreen() { return mLoadingScreen; diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index dc3cc4b09b..050f1667d7 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -316,6 +316,8 @@ namespace MWGui virtual void setEnemy (const MWWorld::Ptr& enemy); + virtual int getMessagesCount() const; + virtual const Translation::Storage& getTranslationDataStorage() const; void onSoulgemDialogButtonPressed (int button); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 9804531d70..946c5eef7f 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -265,9 +265,10 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.save (stream); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); // Using only Cells for progress information, since they typically have the largest records by far listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells()); - listener.setLabel("#{sNotifyMessage4}", true); + listener.setLabel("#{sNotifyMessage4}", true, messagesCount > 0); Loading::ScopedLoad load(&listener); @@ -389,9 +390,10 @@ void MWState::StateManager::loadGame (const Character *character, const std::str std::map contentFileMap = buildContentFileIndexMap (reader); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); listener.setProgressRange(100); - listener.setLabel("#{sLoadingMessage14}"); + listener.setLabel("#{sLoadingMessage14}", false, messagesCount > 0); Loading::ScopedLoad load(&listener); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 30e73df588..4c6cf1962b 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -360,8 +360,9 @@ namespace MWWorld Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); + int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); std::string loadingExteriorText = "#{sLoadingMessage3}"; - loadingListener->setLabel(loadingExteriorText); + loadingListener->setLabel(loadingExteriorText, false, messagesCount > 0); CellStoreCollection::iterator active = mActiveCells.begin(); while (active!=mActiveCells.end()) @@ -526,8 +527,9 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); std::string loadingInteriorText = "#{sLoadingMessage2}"; - loadingListener->setLabel(loadingInteriorText); + loadingListener->setLabel(loadingInteriorText, false, messagesCount > 0); Loading::ScopedLoad load(loadingListener); if(!loadcell) diff --git a/components/loadinglistener/loadinglistener.hpp b/components/loadinglistener/loadinglistener.hpp index 6c7a3b090b..f5cfa5cf37 100644 --- a/components/loadinglistener/loadinglistener.hpp +++ b/components/loadinglistener/loadinglistener.hpp @@ -14,7 +14,7 @@ namespace Loading /// @note "non-important" labels may not show on screen if the loading process went so fast /// that the implementation decided not to show a loading screen at all. "important" labels /// will show in a separate message-box if the loading screen was not shown. - virtual void setLabel (const std::string& label, bool important=false) {} + virtual void setLabel (const std::string& label, bool important=false, bool center=false) {} /// Start a loading sequence. Must call loadingOff() when done. /// @note To get the loading screen to actually update, you must call setProgress / increaseProgress periodically. From 06d226a1b72841b97f73ad35c777ee856957aa98 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 28 Oct 2018 15:08:24 +0400 Subject: [PATCH 117/133] Minor tweaks for actors processing range setting --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 5 +++++ files/mygui/openmw_settings_window.layout | 6 +++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b707851deb..3ee1823d8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -178,6 +178,7 @@ Feature #4632: AI priority: utilize vanilla AI GMSTs for priority rating Feature #4636: Use sTo GMST in spellmaking menu Feature #4642: Batching potion creation + Feature #4647: Cull actors outside of AI processing range Feature #4682: Use the collision box from basic creature mesh if the X one have no collisions Task #2490: Don't open command prompt window on Release-mode builds automatically Task #4545: Enable is_pod string test diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 1c75763167..3c540a9d4e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -17,6 +17,7 @@ #include "../mwworld/ptr.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/dialoguemanager.hpp" @@ -437,6 +438,10 @@ namespace MWMechanics { if (it->first == "Game" && it->second == "actors processing range") { + int state = MWBase::Environment::get().getStateManager()->getState(); + if (state != MWBase::StateManager::State_Running) + continue; + mActors.updateProcessingRange(); // Update mechanics for new processing range immediately diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 8e6e98612b..3c26648572 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -75,7 +75,7 @@ - + @@ -87,7 +87,7 @@ - + @@ -224,7 +224,7 @@ - + From abc51a8a17922850a086c48d077b8f17e7c5d8d7 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 28 Oct 2018 14:59:12 +0300 Subject: [PATCH 118/133] Add settings option to set max number of polygons per navmesh tile --- apps/openmw/mwworld/worldimp.cpp | 1 + .../detournavigator/navigator.cpp | 1 + components/detournavigator/makenavmesh.cpp | 24 ++++++++++++++----- components/detournavigator/settings.hpp | 1 + files/settings-default.cfg | 5 ++++ 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 93babf120e..60c0395a07 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -211,6 +211,7 @@ namespace MWWorld navigatorSettings.mSwimHeightScale = mSwimHeightScale; navigatorSettings.mMaxEdgeLen = Settings::Manager::getInt("max edge len", "Navigator"); navigatorSettings.mMaxNavMeshQueryNodes = Settings::Manager::getInt("max nav mesh query nodes", "Navigator"); + navigatorSettings.mMaxPolys = Settings::Manager::getInt("max polygons per tile", "Navigator"); navigatorSettings.mMaxVertsPerPoly = Settings::Manager::getInt("max verts per poly", "Navigator"); navigatorSettings.mRegionMergeSize = Settings::Manager::getInt("region merge size", "Navigator"); navigatorSettings.mRegionMinSize = Settings::Manager::getInt("region min size", "Navigator"); diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 7926e208de..febbc03875 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -60,6 +60,7 @@ namespace mSettings.mMaxPolygonPathSize = 1024; mSettings.mMaxSmoothPathSize = 1024; mSettings.mTrianglesPerChunk = 256; + mSettings.mMaxPolys = 4096; mNavigator.reset(new Navigator(mSettings)); } }; diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 7f8a7b9d33..7072f2a230 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -456,6 +456,15 @@ namespace else return UpdateNavMeshStatus::ignore; } + + template + unsigned long getMinValuableBitsNumber(const T value) + { + unsigned long power = 0; + while (power < sizeof(T) * 8 && (static_cast(1) << power) < value) + ++power; + return power; + } } namespace DetourNavigator @@ -464,17 +473,20 @@ namespace DetourNavigator { // Max tiles and max polys affect how the tile IDs are caculated. // There are 22 bits available for identifying a tile and a polygon. - const auto tileBits = 10; - const auto polyBits = 22 - tileBits; - const auto maxTiles = 1 << tileBits; - const auto maxPolysPerTile = 1 << polyBits; + const int polysAndTilesBits = 22; + const auto polysBits = getMinValuableBitsNumber(settings.mMaxPolys); + + if (polysBits >= polysAndTilesBits) + throw InvalidArgument("Too many polygons per tile"); + + const auto tilesBits = polysAndTilesBits - polysBits; dtNavMeshParams params; std::fill_n(params.orig, 3, 0.0f); params.tileWidth = settings.mTileSize * settings.mCellSize; params.tileHeight = settings.mTileSize * settings.mCellSize; - params.maxTiles = maxTiles; - params.maxPolys = maxPolysPerTile; + params.maxTiles = 1 << tilesBits; + params.maxPolys = 1 << polysBits; NavMeshPtr navMesh(dtAllocNavMesh(), &dtFreeNavMesh); const auto status = navMesh->init(¶ms); diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index f2eb2be24b..3e537c2fb9 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -23,6 +23,7 @@ namespace DetourNavigator int mBorderSize; int mMaxEdgeLen; int mMaxNavMeshQueryNodes; + int mMaxPolys; int mMaxVertsPerPoly; int mRegionMergeSize; int mRegionMinSize; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index a76eaf1d6d..d8f5faaa9c 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -576,6 +576,11 @@ max edge len = 12 # Maximum number of search nodes. (0 < value <= 65535) max nav mesh query nodes = 2048 +# Maximum number of polygons per navmesh tile (value = 2^n, 0 < n < 22). Maximum number of navmesh tiles depends on +# this value. 22 bits is a limit to store both tile identifier and polygon identifier (tiles = 2^(22 - log2(polygons))). +# See recastnavigation for more details. +max polygons per tile = 4096 + # The maximum number of vertices allowed for polygons generated during the contour to polygon conversion process. (value >= 3) max verts per poly = 6 From af2f4e8424e09a49ab4af7eba405c189995fea79 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 28 Oct 2018 16:54:06 +0300 Subject: [PATCH 119/133] Allow to use zero cache size --- components/detournavigator/navmeshtilescache.cpp | 2 +- components/detournavigator/navmeshtilescache.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index ff24b86f43..8c5fc63815 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -72,7 +72,7 @@ namespace DetourNavigator NavMeshTilesCache::Value NavMeshTilesCache::set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, const RecastMesh& recastMesh, const std::vector& offMeshConnections, - NavMeshData value) + NavMeshData&& value) { const auto navMeshSize = static_cast(value.mSize); diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index 2ecd37bf48..1d0ecb5dc5 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -105,7 +105,7 @@ namespace DetourNavigator Value set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, const RecastMesh& recastMesh, const std::vector& offMeshConnections, - NavMeshData value); + NavMeshData&& value); private: std::mutex mMutex; From 50b6ae3e103a3e5c38a12adda873adcf6829ddc2 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 28 Oct 2018 16:54:47 +0300 Subject: [PATCH 120/133] Fix calculation for shape local AABB --- .../detournavigator/recastmeshbuilder.cpp | 2 +- .../detournavigator/recastmeshbuilder.cpp | 43 ++++++++++++------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index 1bf1163c70..38b1ab3614 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -297,7 +297,7 @@ namespace TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_x_bhv_triangle_shape_should_filter_by_bounds) { mBounds.mMin = osg::Vec2f(-5, -5); - mBounds.mMax = osg::Vec2f(5, -3); + mBounds.mMax = osg::Vec2f(5, -2); btTriangleMesh mesh; mesh.addTriangle(btVector3(0, -1, -1), btVector3(0, -1, -1), btVector3(0, 1, -1)); mesh.addTriangle(btVector3(0, -3, -3), btVector3(0, -3, -2), btVector3(0, -2, -3)); diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 8ba3309cee..e325b7eaf1 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -135,24 +135,35 @@ namespace DetourNavigator { btVector3 aabbMin; btVector3 aabbMax; + shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); - const btVector3 boundsMinMin(mBounds.mMin.x(), mBounds.mMin.y(), 0); - const btVector3 boundsMinMax(mBounds.mMin.x(), mBounds.mMax.y(), 0); - const btVector3 boundsMaxMin(mBounds.mMax.x(), mBounds.mMin.y(), 0); - const btVector3 boundsMaxMax(mBounds.mMax.x(), mBounds.mMax.y(), 0); + + aabbMin = transform(aabbMin); + aabbMax = transform(aabbMax); + + aabbMin.setX(std::max(mBounds.mMin.x(), aabbMin.x())); + aabbMin.setX(std::min(mBounds.mMax.x(), aabbMin.x())); + aabbMin.setY(std::max(mBounds.mMin.y(), aabbMin.y())); + aabbMin.setY(std::min(mBounds.mMax.y(), aabbMin.y())); + + aabbMax.setX(std::max(mBounds.mMin.x(), aabbMax.x())); + aabbMax.setX(std::min(mBounds.mMax.x(), aabbMax.x())); + aabbMax.setY(std::max(mBounds.mMin.y(), aabbMax.y())); + aabbMax.setY(std::min(mBounds.mMax.y(), aabbMax.y())); + const auto inversedTransform = transform.inverse(); - const auto localBoundsMinMin = inversedTransform(boundsMinMin); - const auto localBoundsMinMax = inversedTransform(boundsMinMax); - const auto localBoundsMaxMin = inversedTransform(boundsMaxMin); - const auto localBoundsMaxMax = inversedTransform(boundsMaxMax); - aabbMin.setX(std::min({localBoundsMinMin.x(), localBoundsMinMax.x(), - localBoundsMaxMin.x(), localBoundsMaxMax.x()})); - aabbMin.setY(std::min({localBoundsMinMin.y(), localBoundsMinMax.y(), - localBoundsMaxMin.y(), localBoundsMaxMax.y()})); - aabbMax.setX(std::max({localBoundsMinMin.x(), localBoundsMinMax.x(), - localBoundsMaxMin.x(), localBoundsMaxMax.x()})); - aabbMax.setY(std::max({localBoundsMinMin.y(), localBoundsMinMax.y(), - localBoundsMaxMin.y(), localBoundsMaxMax.y()})); + + aabbMin = inversedTransform(aabbMin); + aabbMax = inversedTransform(aabbMax); + + aabbMin.setX(std::min(aabbMin.x(), aabbMax.x())); + aabbMin.setY(std::min(aabbMin.y(), aabbMax.y())); + aabbMin.setZ(std::min(aabbMin.z(), aabbMax.z())); + + aabbMax.setX(std::max(aabbMin.x(), aabbMax.x())); + aabbMax.setY(std::max(aabbMin.y(), aabbMax.y())); + aabbMax.setZ(std::max(aabbMin.z(), aabbMax.z())); + shape.processAllTriangles(&callback, aabbMin, aabbMax); } From 4b1d287edaba1f257d5cacbf1b8cfdccc9d939ca Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 28 Oct 2018 17:03:38 +0300 Subject: [PATCH 121/133] Clean up redundant getClass calls in classes --- apps/openmw/mwclass/armor.cpp | 2 +- apps/openmw/mwclass/clothing.cpp | 2 +- apps/openmw/mwclass/creature.cpp | 19 +++--- apps/openmw/mwclass/npc.cpp | 104 +++++++++++++++---------------- apps/openmw/mwclass/weapon.cpp | 2 +- 5 files changed, 62 insertions(+), 67 deletions(-) diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index ad64ad0d1e..0a4958eb99 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -301,7 +301,7 @@ namespace MWClass return std::make_pair(0, "#{sInventoryMessage1}"); // slots that this item can be equipped in - std::pair, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr); + std::pair, bool> slots_ = getEquipmentSlots(ptr); if (slots_.first.empty()) return std::make_pair(0, ""); diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 027882304f..21c0d89c22 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -215,7 +215,7 @@ namespace MWClass std::pair Clothing::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const { // slots that this item can be equipped in - std::pair, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr); + std::pair, bool> slots_ = getEquipmentSlots(ptr); if (slots_.first.empty()) return std::make_pair(0, ""); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index e03635b0c5..8a6f7d23d6 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -137,7 +137,7 @@ namespace MWClass // Persistent actors with 0 health do not play death animation if (data->mCreatureStats.isDead()) - data->mCreatureStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr)); + data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr)); // spells for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); @@ -194,9 +194,9 @@ namespace MWClass models.push_back(model); // FIXME: use const version of InventoryStore functions once they are available - if (ptr.getClass().hasInventoryStore(ptr)) + if (hasInventoryStore(ptr)) { - const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); + const MWWorld::InventoryStore& invStore = getInventoryStore(ptr); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); @@ -237,7 +237,7 @@ namespace MWClass // Get the weapon used (if hand-to-hand, weapon = inv.end()) MWWorld::Ptr weapon; - if (ptr.getClass().hasInventoryStore(ptr)) + if (hasInventoryStore(ptr)) { MWWorld::InventoryStore &inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); @@ -253,8 +253,7 @@ namespace MWClass // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; - if (!ptr.isEmpty() && ptr.getClass().isActor()) - ptr.getClass().getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors); + stats.getAiSequence().getCombatTargets(targetActors); std::pair result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors); if (result.first.isEmpty()) @@ -332,7 +331,7 @@ namespace MWClass void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const { - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + MWMechanics::CreatureStats& stats = getCreatureStats(ptr); // NOTE: 'object' and/or 'attacker' may be empty. if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker)) @@ -346,7 +345,7 @@ namespace MWClass setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); // Attacker and target store each other as hitattemptactor if they have no one stored yet - if (!attacker.isEmpty() && attacker.getClass().isActor() && !ptr.isEmpty() && ptr.getClass().isActor()) + if (!attacker.isEmpty() && attacker.getClass().isActor()) { MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker); // First handle the attacked actor @@ -522,7 +521,7 @@ namespace MWClass const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); - bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run); + bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run); // The Run speed difference for creatures comes from the animation speed difference (see runStateToWalkState in character.cpp) float runSpeed = walkSpeed; @@ -808,7 +807,7 @@ namespace MWClass void Creature::respawn(const MWWorld::Ptr &ptr) const { - const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr); if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) return; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b8b1e9600d..ec0671afc7 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -357,7 +357,7 @@ namespace MWClass // Persistent actors with 0 health do not play death animation if (data->mNpcStats.isDead()) - data->mNpcStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr)); + data->mNpcStats.setDeathAnimationFinished(isPersistent(ptr)); // race powers const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); @@ -469,41 +469,38 @@ namespace MWClass // FIXME: use const version of InventoryStore functions once they are available // preload equipped items - if (ptr.getClass().hasInventoryStore(ptr)) + const MWWorld::InventoryStore& invStore = getInventoryStore(ptr); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { - const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); - for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); + if (equipped != invStore.end()) { - MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); - if (equipped != invStore.end()) + std::vector parts; + if(equipped->getTypeName() == typeid(ESM::Clothing).name()) { - std::vector parts; - if(equipped->getTypeName() == typeid(ESM::Clothing).name()) - { - const ESM::Clothing *clothes = equipped->get()->mBase; - parts = clothes->mParts.mParts; - } - else if(equipped->getTypeName() == typeid(ESM::Armor).name()) - { - const ESM::Armor *armor = equipped->get()->mBase; - parts = armor->mParts.mParts; - } - else - { - std::string model = equipped->getClass().getModel(*equipped); - if (!model.empty()) - models.push_back(model); - } + const ESM::Clothing *clothes = equipped->get()->mBase; + parts = clothes->mParts.mParts; + } + else if(equipped->getTypeName() == typeid(ESM::Armor).name()) + { + const ESM::Armor *armor = equipped->get()->mBase; + parts = armor->mParts.mParts; + } + else + { + std::string model = equipped->getClass().getModel(*equipped); + if (!model.empty()) + models.push_back(model); + } - for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) - { - std::string partname = female ? it->mFemale : it->mMale; - if (partname.empty()) - partname = female ? it->mMale : it->mFemale; - const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get().search(partname); - if (part && !part->mModel.empty()) - models.push_back("meshes/"+part->mModel); - } + for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) + { + std::string partname = female ? it->mFemale : it->mMale; + if (partname.empty()) + partname = female ? it->mMale : it->mFemale; + const ESM::BodyPart* part = MWBase::Environment::get().getWorld()->getStore().get().search(partname); + if (part && !part->mModel.empty()) + models.push_back("meshes/"+part->mModel); } } } @@ -573,8 +570,8 @@ namespace MWClass // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; - if (!ptr.isEmpty() && ptr.getClass().isActor() && ptr != MWMechanics::getPlayer()) - ptr.getClass().getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors); + if (ptr != MWMechanics::getPlayer()) + getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors); // TODO: Use second to work out the hit angle std::pair result = world->getHitContact(ptr, dist, targetActors); @@ -597,7 +594,7 @@ namespace MWClass if(!weapon.isEmpty()) weapskill = weapon.getClass().getEquipmentSkill(weapon); - float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.getClass().getSkill(ptr, weapskill)); + float hitchance = MWMechanics::getHitChance(ptr, victim, getSkill(ptr, weapskill)); if (Misc::Rng::roll0to99() >= hitchance) { @@ -667,7 +664,7 @@ namespace MWClass void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + MWMechanics::CreatureStats& stats = getCreatureStats(ptr); bool wasDead = stats.isDead(); // Note OnPcHitMe is not set for friendly hits. @@ -681,7 +678,7 @@ namespace MWClass } // Attacker and target store each other as hitattemptactor if they have no one stored yet - if (!attacker.isEmpty() && attacker.getClass().isActor() && !ptr.isEmpty() && ptr.getClass().isActor()) + if (!attacker.isEmpty() && attacker.getClass().isActor()) { MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker); // First handle the attacked actor @@ -702,7 +699,7 @@ namespace MWClass if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) { - const std::string &script = ptr.getClass().getScript(ptr); + const std::string &script = getScript(ptr); /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ if(!script.empty()) ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); @@ -891,12 +888,11 @@ namespace MWClass if(stats.getAiSequence().isInCombat()) return std::shared_ptr(new MWWorld::FailedAction("")); - if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak) - || ptr.getClass().getCreatureStats(ptr).getKnockedDown()) + if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak) || stats.getKnockedDown()) return std::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing // Can't talk to werewolfs - if(ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).isWerewolf()) + if(getNpcStats(ptr).isWerewolf()) return std::shared_ptr (new MWWorld::FailedAction("")); return std::shared_ptr(new MWWorld::ActionTalk(ptr)); @@ -927,7 +923,7 @@ namespace MWClass float Npc::getSpeed(const MWWorld::Ptr& ptr) const { - const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead()) return 0.f; @@ -993,7 +989,7 @@ namespace MWClass if(getEncumbrance(ptr) > getCapacity(ptr)) return 0.f; - const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead()) return 0.f; @@ -1213,13 +1209,13 @@ namespace MWClass return (name == "left") ? "FootWaterLeft" : "FootWaterRight"; if(world->isOnGround(ptr)) { - if (ptr.getClass().getNpcStats(ptr).isWerewolf() - && ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)) + if (getNpcStats(ptr).isWerewolf() + && getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)) { MWMechanics::WeaponType weaponType = MWMechanics::WeapType_None; - MWMechanics::getActiveWeapon(ptr.getClass().getCreatureStats(ptr), ptr.getClass().getInventoryStore(ptr), &weaponType); + MWMechanics::getActiveWeapon(getCreatureStats(ptr), getInventoryStore(ptr), &weaponType); if (weaponType == MWMechanics::WeapType_None) - return ""; + return std::string(); } const MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr); @@ -1237,12 +1233,12 @@ namespace MWClass return (name == "left") ? "FootHeavyLeft" : "FootHeavyRight"; } } - return ""; + return std::string(); } // Morrowind ignores land soundgen for NPCs if(name == "land") - return ""; + return std::string(); if(name == "swimleft") return "Swim Left"; if(name == "swimright") @@ -1252,11 +1248,11 @@ namespace MWClass // only for biped creatures? if(name == "moan") - return ""; + return std::string(); if(name == "roar") - return ""; + return std::string(); if(name == "scream") - return ""; + return std::string(); throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); } @@ -1270,7 +1266,7 @@ namespace MWClass int Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const { - return ptr.getClass().getNpcStats(ptr).getSkill(skill).getModified(); + return getNpcStats(ptr).getSkill(skill).getModified(); } int Npc::getBloodTexture(const MWWorld::ConstPtr &ptr) const @@ -1352,7 +1348,7 @@ namespace MWClass void Npc::respawn(const MWWorld::Ptr &ptr) const { - const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr); if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) return; diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 7d28c89835..b45e7cd2c2 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -391,7 +391,7 @@ namespace MWClass && MWBase::Environment::get().getWindowManager()->isGuiMode()) return std::make_pair(0, "#{sCantEquipWeapWarning}"); - std::pair, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr); + std::pair, bool> slots_ = getEquipmentSlots(ptr); if (slots_.first.empty()) return std::make_pair (0, ""); From 77b0ff7a752e858524e868940b7a44f8dd7fe95c Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 28 Oct 2018 18:28:36 +0300 Subject: [PATCH 122/133] Use real thrown weapon damage in tooltips and weapon rating (feature #4697) --- CHANGELOG.md | 1 + apps/openmw/mwclass/weapon.cpp | 12 ++++++++++-- apps/openmw/mwmechanics/weaponpriority.cpp | 10 +++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bc654e101..f373b375d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -189,6 +189,7 @@ Feature #4642: Batching potion creation Feature #4647: Cull actors outside of AI processing range Feature #4682: Use the collision box from basic creature mesh if the X one have no collisions + Feature #4697: Use the real thrown weapon damage in tooltips and AI Task #2490: Don't open command prompt window on Release-mode builds automatically Task #4545: Enable is_pod string test Task #4605: Optimize skinning diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 7d28c89835..b5035fb3c2 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -265,7 +265,7 @@ namespace MWClass std::string text; // weapon type & damage - if ((ref->mBase->mData.mType < 12 || Settings::Manager::getBool("show projectile damage", "Game")) && ref->mBase->mData.mType < 14) + if ((ref->mBase->mData.mType < ESM::Weapon::Arrow || Settings::Manager::getBool("show projectile damage", "Game")) && ref->mBase->mData.mType <= ESM::Weapon::Bolt) { text += "\n#{sType} "; @@ -295,7 +295,15 @@ namespace MWClass ((oneOrTwoHanded != "") ? ", " + store.get().find(oneOrTwoHanded)->mValue.getString() : ""); // weapon damage - if (ref->mBase->mData.mType >= 9) + if (ref->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + { + // Thrown weapons have 2x real damage applied + // as they're both the weapon and the ammo + text += "\n#{sAttack}: " + + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0] * 2)) + + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[1] * 2)); + } + else if (ref->mBase->mData.mType >= ESM::Weapon::MarksmanBow) { // marksman text += "\n#{sAttack}: " diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index aaa06f6e44..55fb2eaf09 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -58,8 +58,16 @@ namespace MWMechanics } const float chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f; - if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) + // We need to account for the fact that thrown weapons have 2x real damage applied to the target + // as they're both the weapon and the ammo of the hit + if (weapon->mData.mType == ESM::Weapon::MarksmanThrown) + { + rating = chop * 2; + } + else if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) + { rating = chop; + } else { const float slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1]) / 2.f; From 89b8eb029f6a21726fb426abda13ef5ee3f8615e Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 27 Oct 2018 19:35:19 +0300 Subject: [PATCH 123/133] Add icons for record context menu actions --- apps/opencs/view/world/table.cpp | 11 ++++++++++- files/opencs/record-down.png | Bin 0 -> 150 bytes files/opencs/record-up.png | Bin 0 -> 150 bytes files/opencs/resources.qrc | 2 ++ 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 files/opencs/record-down.png create mode 100644 files/opencs/record-up.png diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 986717cb21..f625fbe8dc 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -300,12 +300,14 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, { mCreateAction = new QAction (tr ("Add Record"), this); connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest())); + mCreateAction->setIcon(QIcon(":edit-add")); addAction (mCreateAction); CSMPrefs::Shortcut* createShortcut = new CSMPrefs::Shortcut("table-add", this); createShortcut->associateAction(mCreateAction); mCloneAction = new QAction (tr ("Clone Record"), this); connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord())); + mCloneAction->setIcon(QIcon(":edit-clone")); addAction(mCloneAction); CSMPrefs::Shortcut* cloneShortcut = new CSMPrefs::Shortcut("table-clone", this); cloneShortcut->associateAction(mCloneAction); @@ -322,49 +324,56 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, mRevertAction = new QAction (tr ("Revert Record"), this); connect (mRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeRevert())); + mRevertAction->setIcon(QIcon(":edit-undo")); addAction (mRevertAction); CSMPrefs::Shortcut* revertShortcut = new CSMPrefs::Shortcut("table-revert", this); revertShortcut->associateAction(mRevertAction); mDeleteAction = new QAction (tr ("Delete Record"), this); connect (mDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeDelete())); + mDeleteAction->setIcon(QIcon(":edit-delete")); addAction (mDeleteAction); CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("table-remove", this); deleteShortcut->associateAction(mDeleteAction); - mMoveUpAction = new QAction (tr ("Move Up"), this); connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord())); + mMoveUpAction->setIcon(QIcon(":record-up")); addAction (mMoveUpAction); CSMPrefs::Shortcut* moveUpShortcut = new CSMPrefs::Shortcut("table-moveup", this); moveUpShortcut->associateAction(mMoveUpAction); mMoveDownAction = new QAction (tr ("Move Down"), this); connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); + mMoveDownAction->setIcon(QIcon(":record-down")); addAction (mMoveDownAction); CSMPrefs::Shortcut* moveDownShortcut = new CSMPrefs::Shortcut("table-movedown", this); moveDownShortcut->associateAction(mMoveDownAction); mViewAction = new QAction (tr ("View"), this); connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord())); + mViewAction->setIcon(QIcon(":/cell.png")); addAction (mViewAction); CSMPrefs::Shortcut* viewShortcut = new CSMPrefs::Shortcut("table-view", this); viewShortcut->associateAction(mViewAction); mPreviewAction = new QAction (tr ("Preview"), this); connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord())); + mPreviewAction->setIcon(QIcon(":edit-preview")); addAction (mPreviewAction); CSMPrefs::Shortcut* previewShortcut = new CSMPrefs::Shortcut("table-preview", this); previewShortcut->associateAction(mPreviewAction); mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this); connect (mExtendedDeleteAction, SIGNAL (triggered()), this, SLOT (executeExtendedDelete())); + mExtendedDeleteAction->setIcon(QIcon(":edit-delete")); addAction (mExtendedDeleteAction); CSMPrefs::Shortcut* extendedDeleteShortcut = new CSMPrefs::Shortcut("table-extendeddelete", this); extendedDeleteShortcut->associateAction(mExtendedDeleteAction); mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this); connect (mExtendedRevertAction, SIGNAL (triggered()), this, SLOT (executeExtendedRevert())); + mExtendedRevertAction->setIcon(QIcon(":edit-undo")); addAction (mExtendedRevertAction); CSMPrefs::Shortcut* extendedRevertShortcut = new CSMPrefs::Shortcut("table-extendedrevert", this); extendedRevertShortcut->associateAction(mExtendedRevertAction); diff --git a/files/opencs/record-down.png b/files/opencs/record-down.png new file mode 100644 index 0000000000000000000000000000000000000000..92fe788602136102ed33598031e7493325540320 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6Vm)0PLp07OCrD@=n9jLfMwywJ zxjA1t`04)tJv}Rg{(my}cbakW(Qm$^Xa4W2d~t2d&$@4|8{>bAB^7(_ejmvO1ov;B xxTNvzSDAr@K~2Jq-8-eX*H_hev&U;NGbG-S&@#X6+Yhve!PC{xWt~$(695OAIXeIV literal 0 HcmV?d00001 diff --git a/files/opencs/record-up.png b/files/opencs/record-up.png new file mode 100644 index 0000000000000000000000000000000000000000..c8bfa4a34274561e236984ebc4d486d05017bd71 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6Vm)0PLp07OCrDHr=sETEZQ%9& zr98`-?(?x(vuT!khNyD9EW5v%2MG3UujSt8x&3F|x7LmE$r>MXKi0M%`u~15d!nIJ xiE-$^-{((stZA9}F#g{&PlK;MJkHV#46n~i6gX-JZv|S!;OXk;vd$@?2>`lgIN|^R literal 0 HcmV?d00001 diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc index 4fc4bdf285..c7be701209 100644 --- a/files/opencs/resources.qrc +++ b/files/opencs/resources.qrc @@ -90,6 +90,8 @@ multitype.png record-next.png record-previous.png + record-down.png + record-up.png record-delete.png record-preview.png record-clone.png From 8757b3edd1d438ef1bf522d099d8a79b68a28a4c Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 28 Oct 2018 20:44:03 +0300 Subject: [PATCH 124/133] Add icons for Edit and Touch actions (kudos to akortunov) --- apps/opencs/view/world/table.cpp | 2 ++ files/opencs/record-edit.png | Bin 0 -> 220 bytes files/opencs/record-touch.png | Bin 0 -> 289 bytes files/opencs/resources.qrc | 2 ++ 4 files changed, 4 insertions(+) create mode 100644 files/opencs/record-edit.png create mode 100644 files/opencs/record-touch.png diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index f625fbe8dc..14df4658d5 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -292,6 +292,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, mEditAction = new QAction (tr ("Edit Record"), this); connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord())); + mEditAction->setIcon(QIcon(":edit-edit")); addAction (mEditAction); CSMPrefs::Shortcut* editShortcut = new CSMPrefs::Shortcut("table-edit", this); editShortcut->associateAction(mEditAction); @@ -317,6 +318,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, { mTouchAction = new QAction(tr("Touch Record"), this); connect(mTouchAction, SIGNAL(triggered()), this, SLOT(touchRecord())); + mTouchAction->setIcon(QIcon(":edit-touch")); addAction(mTouchAction); CSMPrefs::Shortcut* touchShortcut = new CSMPrefs::Shortcut("table-touch", this); touchShortcut->associateAction(mTouchAction); diff --git a/files/opencs/record-edit.png b/files/opencs/record-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..8b94e163410880ebd3efbeb68b3aa23e595d2c65 GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67JIrlhG?8mPEcTwkd%;+`18+M zl81-q|HO#dKz@Y69AAULw*Lit|NRHa&fti2+{NUk5t6#=+QaYj4>GoMYWeW2EjKeX z`0+OVpyuBr?{BDSi`uizFn>6SN6Sa+Ezixm|B{PEE!@CU zBv}dg!@$fxGe5Jl3>zXWow}|Qgb-FXq?AD0w%-ev0ww6yZf*L6{}V{jEJd?F38ZM4 zqTL+!vbjHR_fj-0nP7}biZ0PdKl_uM`%+wiGu)x+a1GvZv37z`dBh>+9eR#~Precord-preview.png record-clone.png record-add.png + record-edit.png + record-touch.png record-revert.png resources-icon.png resources-mesh.png From 434b1fd0e351e569235b46fda5500d3e66845833 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 29 Oct 2018 01:53:49 +0300 Subject: [PATCH 125/133] Use Shift-C and Shift-V keybindings for View and Preview (feature #2845) --- CHANGELOG.md | 1 + apps/opencs/model/prefs/state.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bc654e101..891721f5ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -158,6 +158,7 @@ Feature #1645: Casting effects from objects Feature #2606: Editor: Implemented (optional) case sensitive global search Feature #2787: Use the autogenerated collision box, if the creature mesh has no predefined one + Feature #2845: Editor: add record view and preview default keybindings Feature #2847: Content selector: allow to copy the path to a file by using the context menu Feature #3083: Play animation when NPC is casting spell via script Feature #3103: Provide option for disposition to get increased by successful trade diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index b6a55ea862..57d47ebbcc 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -317,8 +317,8 @@ void CSMPrefs::State::declare() declareShortcut ("table-remove", "Remove Row/Record", QKeySequence(Qt::Key_Delete)); declareShortcut ("table-moveup", "Move Record Up", QKeySequence()); declareShortcut ("table-movedown", "Move Record Down", QKeySequence()); - declareShortcut ("table-view", "View Record", QKeySequence()); - declareShortcut ("table-preview", "Preview Record", QKeySequence()); + declareShortcut ("table-view", "View Record", QKeySequence(Qt::ShiftModifier | Qt::Key_C)); + declareShortcut ("table-preview", "Preview Record", QKeySequence(Qt::ShiftModifier | Qt::Key_V)); declareShortcut ("table-extendeddelete", "Extended Record Deletion", QKeySequence()); declareShortcut ("table-extendedrevert", "Extended Record Revertion", QKeySequence()); From 988914725ab81700e87b39ce84b88bffa969c231 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 25 Oct 2018 21:46:14 +0200 Subject: [PATCH 126/133] wip 1 wip2 add more Level_UP and QA text the rest of the questions --- files/openmw.cfg | 516 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 516 insertions(+) diff --git a/files/openmw.cfg b/files/openmw.cfg index 7a87bdc11e..354fdb4a19 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -11,3 +11,519 @@ script-blacklist=doortestwarp script-blacklist=WereChange2Script script-blacklist=wereDreamScript2 script-blacklist=wereDreamScript3 + +# Sane defaults + +# sound +no-sound=0 + +# archives +fallback-archive=OpenMW-Example-Suite.zip +fallback-archive=OpenMW-Template.zip + +# lighting +fallback=LightAttenuation_UseConstant,0 +fallback=LightAttenuation_ConstantValue,0.0 +fallback=LightAttenuation_UseLinear,1 +fallback=LightAttenuation_LinearMethod,1 +fallback=LightAttenuation_LinearValue,3.0 +fallback=LightAttenuation_LinearRadiusMult,1.0 +fallback=LightAttenuation_UseQuadratic,0 +fallback=LightAttenuation_QuadraticMethod,2 +fallback=LightAttenuation_QuadraticValue,16.0 +fallback=LightAttenuation_QuadraticRadiusMult,1.0 +fallback=LightAttenuation_OutQuadInLin,0 + +# inventory +fallback=Inventory_DirectionalDiffuseR,1.0 +fallback=Inventory_DirectionalDiffuseG,1.0 +fallback=Inventory_DirectionalDiffuseB,1.0 +fallback=Inventory_DirectionalAmbientR,0.0 +fallback=Inventory_DirectionalAmbientG,0.0 +fallback=Inventory_DirectionalAmbientB,0.0 +fallback=Inventory_DirectionalRotationX,110 +fallback=Inventory_DirectionalRotationY,90 +fallback=Inventory_UniformScaling,0 + +# fast travel +fallback=Map_Travel_Beast_Red,238 +fallback=Map_Travel_Beast_Green,230 +fallback=Map_Travel_Beast_Blue,30 +fallback=Map_Travel_Boat_Red,30 +fallback=Map_Travel_Boat_Green,30 +fallback=Map_Travel_Boat_Blue,230 +fallback=Map_Travel_Magic_Red,230 +fallback=Map_Travel_Magic_Green,30 +fallback=Map_Travel_Magic_Blue,30 +fallback=Map_Show_Travel_Lines,0 + +# water effects +fallback=Water_Map_Alpha,0.4 +fallback=Water_World_Alpha,0.75 +fallback=Water_SurfaceTextureSize,128 +fallback=Water_SurfaceTileCount,10 +fallback=Water_SurfaceFPS,12 +fallback=Water_SurfaceTexture,water +fallback=Water_SurfaceFrameCount,32 +fallback=Water_TileTextureDivisor,4.75 +fallback=Water_RippleTexture,ripple +fallback=Water_RippleFrameCount,4 +fallback=Water_RippleLifetime,3.0 +fallback=Water_MaxNumberRipples,75 +fallback=Water_RippleScale,0.15, 6.5 +fallback=Water_RippleRotSpeed,0.5 +fallback=Water_RippleAlphas,0.7, 0.1, 0.01 +fallback=Water_PSWaterReflectTerrain,1 +fallback=Water_PSWaterReflectUpdate,20.0 +fallback=Water_NearWaterRadius,1000 +fallback=Water_NearWaterPoints,8 +fallback=Water_NearWaterUnderwaterFreq,0.3 +fallback=Water_NearWaterUnderwaterVolume,0.9 +fallback=Water_NearWaterIndoorTolerance,512.0 +fallback=Water_NearWaterOutdoorTolerance,1024.0 +fallback=Water_NearWaterIndoorID,Water Layer +fallback=Water_NearWaterOutdoorID,Water Layer +fallback=Water_UnderwaterSunriseFog,3 +fallback=Water_UnderwaterDayFog,2.5 +fallback=Water_UnderwaterSunsetFog,3 +fallback=Water_UnderwaterNightFog,4 +fallback=Water_UnderwaterIndoorFog,3 +fallback=Water_UnderwaterColor,012,030,037 +fallback=Water_UnderwaterColorWeight,0.85 +fallback=PixelWater_SurfaceFPS,25 +fallback=PixelWater_TileCount,4 +fallback=PixelWater_Resolution,256 + +# fonts +fallback=Fonts_Font_0,magic_cards_regular +fallback=Fonts_Font_1,century_gothic_font_regular +fallback=Fonts_Font_2,daedric_font +fallback=FontColor_color_normal,202,165,96 +fallback=FontColor_color_normal_over,223,201,159 +fallback=FontColor_color_normal_pressed,243,237,221 +fallback=FontColor_color_active,96,112,202 +fallback=FontColor_color_active_over,159,169,223 +fallback=FontColor_color_active_pressed,223,226,244 +fallback=FontColor_color_disabled,179,168,135 +fallback=FontColor_color_disabled_over,223,201,159 +fallback=FontColor_color_disabled_pressed,243,237,221 +fallback=FontColor_color_link,112,126,207 +fallback=FontColor_color_link_over,143,155,218 +fallback=FontColor_color_link_pressed,175,184,228 +fallback=FontColor_color_journal_link,37,49,112 +fallback=FontColor_color_journal_link_over,58,77,175 +fallback=FontColor_color_journal_link_pressed,112,126,207 +fallback=FontColor_color_journal_topic,0,0,0 +fallback=FontColor_color_journal_topic_over,58,77,175 +fallback=FontColor_color_journal_topic_pressed,112,126,207 +fallback=FontColor_color_answer,150,50,30 +fallback=FontColor_color_answer_over,223,201,159 +fallback=FontColor_color_answer_pressed,243,237,221 +fallback=FontColor_color_header,223,201,159 +fallback=FontColor_color_notify,223,201,159 +fallback=FontColor_color_big_normal,202,165,96 +fallback=FontColor_color_big_normal_over,223,201,159 +fallback=FontColor_color_big_normal_pressed,243,237,221 +fallback=FontColor_color_big_link,112,126,207 +fallback=FontColor_color_big_link_over,143,155,218 +fallback=FontColor_color_big_link_pressed,175,184,228 +fallback=FontColor_color_big_answer,150,50,30 +fallback=FontColor_color_big_answer_over,223,201,159 +fallback=FontColor_color_big_answer_pressed,243,237,22 +fallback=FontColor_color_big_header,223,201,159 +fallback=FontColor_color_big_notify,223,201,159 +fallback=FontColor_color_background,0,0,0 +fallback=FontColor_color_focus,80,80,80 +fallback=FontColor_color_health,200,60,30 +fallback=FontColor_color_magic,53,69,159 +fallback=FontColor_color_fatigue,0,150,60 +fallback=FontColor_color_misc,0,205,205 +fallback=FontColor_color_weapon_fill,200,60,30 +fallback=FontColor_color_magic_fill,200,60,30 +fallback=FontColor_color_positive,223,201,159 +fallback=FontColor_color_negative,200,60,30 +fallback=FontColor_color_count,223,201,159 + +# leveling +fallback=Level_Up_Level2,You've just realized that there is more to the life than you thought, the last few days have opened your eyes. An uncertain future awaits you. +fallback=Level_Up_Level3,Hard work and determination have served you well, keep this up and you'll be bound for great things. +fallback=Level_Up_Level4,It's as clear as day to you now, all that time you pissed away in your youth. This... this is life and all the experience you've gained giving you the tools you need to succeed in life. +fallback=Level_Up_Level5,Things seem to be working out for you, the world is opening up as you become more capable. +fallback=Level_Up_Level6,There is so much more to learn but with every push you gain more knowledge, only increasing your thirst for it. +fallback=Level_Up_Level7,You've dug deep within yourself, thinking you'll finally be who you thought you would be... but there is more and you keep digging. +fallback=Level_Up_Level8,Success doesn't come easily, but you make it appear that way to others. +fallback=Level_Up_Level9,Everything is becoming second nature to you but also just a bit more dangerous. +fallback=Level_Up_Level10,Nothing in life is easy or it wouldn't be worth the blood, sweat and tears. +fallback=Level_Up_Level11,Working smarter, not harder is something even a barbarian could benefit from. +fallback=Level_Up_Level12,Some would call you crazy for keeping at it, but you know better. It'll all pay off, it already has. +fallback=Level_Up_Level13,One nights sleep was all the difference between between something being difficult and then it being easy. +fallback=Level_Up_Level14,Waking up today was the best day ever for you, you await more days like these in your future. +fallback=Level_Up_Level15,Ouch! You lean back feeling your whole body for what seems the first time. You'll be more mindful in the future, you only have by one life. +fallback=Level_Up_Level16,Trusting your instincts have gotten you this far, but you'll need to be smarter to keep from being dead. +fallback=Level_Up_Level17,You're forging your spirit in the crucible that is experience. Be ever vigilant. +fallback=Level_Up_Level18,The frustrations of the day before melt away as you wake up, today is a new day. +fallback=Level_Up_Level19,Today isn't yesterday, you feel it deep inside but you know that there is still more to this life. +fallback=Level_Up_Level20,Luck has never been a factor in your success, just look at the scars on your body and the trails you've endured. +fallback=Level_Up_Default,Through sheer force of will, you push onwards and upwards. The toil of your persistence has paid off. + +# character creation +fallback=Question_1_Question,Before you lies some kind of creation you never seen before, it's hind leg caught in a hunter's trap. You can tell that its leg is broken. +fallback=Question_1_AnswerOne,Pull out you knife for a short and merciful kill. +fallback=Question_1_AnswerTwo,Reach into your herbal pouch to find something to ease its suffering before putting it to sleep. +fallback=Question_1_AnswerThree,Leave it alone but take the time to observe and learn from this new creation +fallback=Question_1_Sound,Voice\CharGen\QA1.mp3 +fallback=Question_2_Question,Your mother has given everyone a list of choice, pick one. +fallback=Question_2_AnswerOne,The fence posts need replacing, help your father? +fallback=Question_2_AnswerTwo,Tonight's supper needs a few herbs, head out into the forest to collect them? +fallback=Question_2_AnswerThree,Fish aren't going find their way into the kitchen without your help. Go fishing? +fallback=Question_2_Sound,Voice\CharGen\QA2.mp3 +fallback=Question_3_Question,One of your brother teases you mercilessly in front of everyone about embarrassing details you would rather be forgotten. +fallback=Question_3_AnswerOne,Give him a black-eye and dare him to keep it up? +fallback=Question_3_AnswerTwo,Beat him to the punch and play it up, if you can control the negative then he can't embarrass you? +fallback=Question_3_AnswerThree,Wait until he sleeps, put his hand in a bowl of water and call everyone around the next day to see the results? +fallback=Question_3_Sound,Voice\CharGen\QA3.mp3 +fallback=Question_4_Question,Rumor has it that the King's security console has a new tool at their disposal for sniffing out the truth, people able to read minds. +fallback=Question_4_AnswerOne,You recoil at the thought of someone being able to read your thoughts. It's not that you think something that you'll ever act on it? +fallback=Question_4_AnswerTwo,For those who have done nothing, there is nothing to fear. This is just yet another tool for sniffing out thieves, murderers and plots against the crown? +fallback=Question_4_AnswerThree,While you loath the idea of someone reading your mind, you accept that it does have its uses if tempered by law and common sense. You wouldn't believe the mind of a madman for example? +fallback=Question_4_Sound,Voice\CharGen\QA4.mp3 +fallback=Question_5_Question,You're off to market for supplies. You notice that one of the merchants had given you too much back in change. +fallback=Question_5_AnswerOne,How dreadful, what if it was you. You head back to the merchant? +fallback=Question_5_AnswerTwo,Happy days indeed, you put that extra money towards the needs of your family? +fallback=Question_5_AnswerThree,You win some and you lose some. In this case you won and they lost, the oversight is the merchant's problem, not yours? +fallback=Question_5_Sound,Voice\CharGen\QA5.mp3 +fallback=Question_6_Question,While at market, a noble yells out to the city watch about a theft. As you turn around a man bumps into you and drops a sack, startled he darts into the crowd. +fallback=Question_6_AnswerOne,You pick up the sack and head over to the noble to return his property? +fallback=Question_6_AnswerTwo,Just walk away, the last thing you need is someone thinking you had anything to do with it? +fallback=Question_6_AnswerThree,Finders, keepers... you whistle as you walk away? +fallback=Question_6_Sound,Voice\CharGen\QA6.mp3 +fallback=Question_7_Question,If it's one thing you hate, it's cleaning out the stalls. It has to be done before sunset before the critters return. On your way there an old friend greets you and offers to help if you promise to help them in the future. +fallback=Question_7_AnswerOne,You thank him for the offer but would rather get it over with and not make promises you can't keep? +fallback=Question_7_AnswerTwo,You reason that two pairs of hands are better than one regardless of the task? +fallback=Question_7_AnswerThree,Sounds great, anything is better than cleaning the stalls. +fallback=Question_7_Sound,Voice\CharGen\QA7.mp3 +fallback=Question_8_Question,You just climbed down ladder from working on the roof. You're mother thanks you for the hard work but just at the moment you notice the hammer about to fall down on her head. +fallback=Question_8_AnswerOne,You lunge at your mother, pushing her out the way while the hammer falls on top of you? +fallback=Question_8_AnswerTwo,Use the ladder to intercept the hammer before it lands on her? +fallback=Question_8_AnswerThree,Warn her to take a step back? +fallback=Question_8_Sound,Voice\CharGen\QA8.mp3 +fallback=Question_9_Question,It's the end of the week and you just got your wages for your hard work. You decide to take the quick way back home, darting into a alley only to be confronted by ruffians who demand that you empty your pockets. +fallback=Question_9_AnswerOne,You tell them to go pack sand, planting your feet and raising your fists? +fallback=Question_9_AnswerTwo,Acting dejected, you turn your wages over. You know that you can count on your friends to help you track these brigands down and recover what's yours? +fallback=Question_9_AnswerThree,Tossing the sack into the air, you charge the leader who's attention is squarely focused on the coins in flight? +fallback=Question_9_Sound,Voice\CharGen\QA9.mp3 +fallback=Question_10_Question,Upon coming to town, you see a clown running at you with an angry mob following after him. +fallback=Question_10_AnswerOne,Stand your ground, you hate clowns? +fallback=Question_10_AnswerTwo,Move out of the way, letting fate decide the clown's fate? +fallback=Question_10_AnswerThree,Come to the clown's aid, clowns are people too? +fallback=Question_10_Sound,Voice\CharGen\QA10.mp3 + +# blood splatters +fallback=Blood_Model_0,BloodSplat.nif +fallback=Blood_Model_1,BloodSplat2.nif +fallback=Blood_Model_2,BloodSplat3.nif +fallback=Blood_Texture_0,Tx_Blood.dds +fallback=Blood_Texture_1,Tx_Blood_White.dds +fallback=Blood_Texture_2,Tx_Blood_Gold.dds +fallback=Blood_Texture_Name_0,Default (Red) +fallback=Blood_Texture_Name_1,Skeleton (White) +fallback=Blood_Texture_Name_2,Metal Sparks (Gold) + +# movies +fallback=Movies_Project_Logo,openmw_project_logo.webm +fallback=Movies_Game_Logo,openmw_example_suite_logo.webm +fallback=Movies_New_Game,new_game.webm +fallback=Movies_Loading,loading.webm +fallback=Movies_Options_Menu,options_menu.webm + +# weather +fallback=Weather_Thunderstorm_Thunder_Sound_ID_0,Thunder0 +fallback=Weather_Thunderstorm_Thunder_Sound_ID_1,Thunder1 +fallback=Weather_Thunderstorm_Thunder_Sound_ID_2,Thunder2 +fallback=Weather_Thunderstorm_Thunder_Sound_ID_3,Thunder3 +fallback=Weather_Sunrise_Time,6 +fallback=Weather_Sunset_Time,18 +fallback=Weather_Sunrise_Duration,2 +fallback=Weather_Sunset_Duration,2 +fallback=Weather_Hours_Between_Weather_Changes,20 +fallback=Weather_Thunderstorm_Thunder_Frequency,.4 +fallback=Weather_Thunderstorm_Thunder_Threshold,0.6 +fallback=Weather_EnvReduceColor,255,255,255,255 +fallback=Weather_LerpCloseColor,037,046,048,255 +fallback=Weather_BumpFadeColor,230,239,255,255 +fallback=Weather_AlphaReduce,0.35 +fallback=Weather_Minimum_Time_Between_Environmental_Sounds,1.0 +fallback=Weather_Maximum_Time_Between_Environmental_Sounds,5.0 +fallback=Weather_Sun_Glare_Fader_Max,0.5 +fallback=Weather_Sun_Glare_Fader_Angle_Max,30.0 +fallback=Weather_Sun_Glare_Fader_Color,222,095,039 +fallback=Weather_Timescale_Clouds,0 +fallback=Weather_Precip_Gravity,575 +fallback=Weather_Rain_Ripples,1 +fallback=Weather_Rain_Ripple_Radius,1024 +fallback=Weather_Rain_Ripples_Per_Drop,1 +fallback=Weather_Rain_Ripple_Scale,0.3 +fallback=Weather_Rain_Ripple_Speed,1.0 +fallback=Weather_Fog_Depth_Change_Speed,3 +fallback=Weather_Sky_Pre-Sunrise_Time,.5 +fallback=Weather_Sky_Post-Sunrise_Time,1 +fallback=Weather_Sky_Pre-Sunset_Time,1.5 +fallback=Weather_Sky_Post-Sunset_Time,.5 +fallback=Weather_Ambient_Pre-Sunrise_Time,.5 +fallback=Weather_Ambient_Post-Sunrise_Time,2 +fallback=Weather_Ambient_Pre-Sunset_Time,1 +fallback=Weather_Ambient_Post-Sunset_Time,1.25 +fallback=Weather_Fog_Pre-Sunrise_Time,.5 +fallback=Weather_Fog_Post-Sunrise_Time,1 +fallback=Weather_Fog_Pre-Sunset_Time,2 +fallback=Weather_Fog_Post-Sunset_Time,1 +fallback=Weather_Sun_Pre-Sunrise_Time,0 +fallback=Weather_Sun_Post-Sunrise_Time,0 +fallback=Weather_Sun_Pre-Sunset_Time,1 +fallback=Weather_Sun_Post-Sunset_Time,1.25 +fallback=Weather_Stars_Post-Sunset_Start,1 +fallback=Weather_Stars_Pre-Sunrise_Finish,2 +fallback=Weather_Stars_Fading_Duration,2 +fallback=Weather_Snow_Ripples,0 +fallback=Weather_Snow_Ripple_Radius,1024 +fallback=Weather_Snow_Ripples_Per_Flake,1 +fallback=Weather_Snow_Ripple_Scale,0.3 +fallback=Weather_Snow_Ripple_Speed,1.0 +fallback=Weather_Snow_Gravity_Scale,0.1 +fallback=Weather_Snow_High_Kill,700 +fallback=Weather_Snow_Low_Kill,150 +fallback=Weather_Clear_Cloud_Texture,Tx_Sky_Clear.dds +fallback=Weather_Clear_Clouds_Maximum_Percent,1.0 +fallback=Weather_Clear_Transition_Delta,.015 +fallback=Weather_Clear_Sky_Sunrise_Color,117,141,164 +fallback=Weather_Clear_Sky_Day_Color,095,135,203 +fallback=Weather_Clear_Sky_Sunset_Color,056,089,129 +fallback=Weather_Clear_Sky_Night_Color,009,010,011 +fallback=Weather_Clear_Fog_Sunrise_Color,255,189,157 +fallback=Weather_Clear_Fog_Day_Color,206,227,255 +fallback=Weather_Clear_Fog_Sunset_Color,255,189,157 +fallback=Weather_Clear_Fog_Night_Color,009,010,011 +fallback=Weather_Clear_Ambient_Sunrise_Color,047,066,096 +fallback=Weather_Clear_Ambient_Day_Color,137,140,160 +fallback=Weather_Clear_Ambient_Sunset_Color,068,075,096 +fallback=Weather_Clear_Ambient_Night_Color,032,035,042 +fallback=Weather_Clear_Sun_Sunrise_Color,242,159,119 +fallback=Weather_Clear_Sun_Day_Color,255,252,238 +fallback=Weather_Clear_Sun_Sunset_Color,255,114,079 +fallback=Weather_Clear_Sun_Night_Color,059,097,176 +fallback=Weather_Clear_Sun_Disc_Sunset_Color,255,189,157 +fallback=Weather_Clear_Land_Fog_Day_Depth,.69 +fallback=Weather_Clear_Land_Fog_Night_Depth,.69 +fallback=Weather_Clear_Wind_Speed,.1 +fallback=Weather_Clear_Cloud_Speed,1.25 +fallback=Weather_Clear_Glare_View,1 +fallback=Weather_Clear_Ambient_Loop_Sound_ID,None +fallback=Weather_Cloudy_Cloud_Texture,Tx_Sky_Cloudy.dds +fallback=Weather_Cloudy_Clouds_Maximum_Percent,1.0 +fallback=Weather_Cloudy_Transition_Delta,.015 +fallback=Weather_Cloudy_Sky_Sunrise_Color,126,158,173 +fallback=Weather_Cloudy_Sky_Day_Color,117,160,215 +fallback=Weather_Cloudy_Sky_Sunset_Color,111,114,159 +fallback=Weather_Cloudy_Sky_Night_Color,009,010,011 +fallback=Weather_Cloudy_Fog_Sunrise_Color,255,207,149 +fallback=Weather_Cloudy_Fog_Day_Color,245,235,224 +fallback=Weather_Cloudy_Fog_Sunset_Color,255,155,106 +fallback=Weather_Cloudy_Fog_Night_Color,009,010,011 +fallback=Weather_Cloudy_Ambient_Sunrise_Color,066,074,087 +fallback=Weather_Cloudy_Ambient_Day_Color,137,145,160 +fallback=Weather_Cloudy_Ambient_Sunset_Color,071,080,092 +fallback=Weather_Cloudy_Ambient_Night_Color,032,039,054 +fallback=Weather_Cloudy_Sun_Sunrise_Color,241,177,099 +fallback=Weather_Cloudy_Sun_Day_Color,255,236,221 +fallback=Weather_Cloudy_Sun_Sunset_Color,255,089,000 +fallback=Weather_Cloudy_Sun_Night_Color,077,091,124 +fallback=Weather_Cloudy_Sun_Disc_Sunset_Color,255,202,179 +fallback=Weather_Cloudy_Land_Fog_Day_Depth,.72 +fallback=Weather_Cloudy_Land_Fog_Night_Depth,.72 +fallback=Weather_Cloudy_Wind_Speed,.2 +fallback=Weather_Cloudy_Cloud_Speed,2 +fallback=Weather_Cloudy_Glare_View,1 +fallback=Weather_Cloudy_Ambient_Loop_Sound_ID,None +fallback=Weather_Foggy_Cloud_Texture,Tx_Sky_Foggy.dds +fallback=Weather_Foggy_Clouds_Maximum_Percent,1.0 +fallback=Weather_Foggy_Transition_Delta,.015 +fallback=Weather_Foggy_Sky_Sunrise_Color,197,190,180 +fallback=Weather_Foggy_Sky_Day_Color,184,211,228 +fallback=Weather_Foggy_Sky_Sunset_Color,142,159,176 +fallback=Weather_Foggy_Sky_Night_Color,018,023,028 +fallback=Weather_Foggy_Fog_Sunrise_Color,173,164,148 +fallback=Weather_Foggy_Fog_Day_Color,150,187,209 +fallback=Weather_Foggy_Fog_Sunset_Color,113,135,157 +fallback=Weather_Foggy_Fog_Night_Color,019,024,029 +fallback=Weather_Foggy_Ambient_Sunrise_Color,048,043,037 +fallback=Weather_Foggy_Ambient_Day_Color,092,109,120 +fallback=Weather_Foggy_Ambient_Sunset_Color,029,053,076 +fallback=Weather_Foggy_Ambient_Night_Color,028,033,039 +fallback=Weather_Foggy_Sun_Sunrise_Color,177,162,137 +fallback=Weather_Foggy_Sun_Day_Color,111,131,151 +fallback=Weather_Foggy_Sun_Sunset_Color,125,157,189 +fallback=Weather_Foggy_Sun_Night_Color,081,100,119 +fallback=Weather_Foggy_Sun_Disc_Sunset_Color,223,223,223 +fallback=Weather_Foggy_Land_Fog_Day_Depth,1.0 +fallback=Weather_Foggy_Land_Fog_Night_Depth,1.9 +fallback=Weather_Foggy_Wind_Speed,0 +fallback=Weather_Foggy_Cloud_Speed,1.25 +fallback=Weather_Foggy_Glare_View,0.25 +fallback=Weather_Foggy_Ambient_Loop_Sound_ID,None +fallback=Weather_Thunderstorm_Cloud_Texture,Tx_Sky_Thunder.dds +fallback=Weather_Thunderstorm_Clouds_Maximum_Percent,0.66 +fallback=Weather_Thunderstorm_Transition_Delta,.030 +fallback=Weather_Thunderstorm_Sky_Sunrise_Color,035,036,039 +fallback=Weather_Thunderstorm_Sky_Day_Color,097,104,115 +fallback=Weather_Thunderstorm_Sky_Sunset_Color,035,036,039 +fallback=Weather_Thunderstorm_Sky_Night_Color,019,020,022 +fallback=Weather_Thunderstorm_Fog_Sunrise_Color,070,074,085 +fallback=Weather_Thunderstorm_Fog_Day_Color,097,104,115 +fallback=Weather_Thunderstorm_Fog_Sunset_Color,070,074,085 +fallback=Weather_Thunderstorm_Fog_Night_Color,019,020,022 +fallback=Weather_Thunderstorm_Ambient_Sunrise_Color,054,054,054 +fallback=Weather_Thunderstorm_Ambient_Day_Color,090,090,090 +fallback=Weather_Thunderstorm_Ambient_Sunset_Color,054,054,054 +fallback=Weather_Thunderstorm_Ambient_Night_Color,049,051,054 +fallback=Weather_Thunderstorm_Sun_Sunrise_Color,091,099,122 +fallback=Weather_Thunderstorm_Sun_Day_Color,138,144,155 +fallback=Weather_Thunderstorm_Sun_Sunset_Color,096,101,117 +fallback=Weather_Thunderstorm_Sun_Night_Color,055,076,110 +fallback=Weather_Thunderstorm_Sun_Disc_Sunset_Color,128,128,128 +fallback=Weather_Thunderstorm_Land_Fog_Day_Depth,1 +fallback=Weather_Thunderstorm_Land_Fog_Night_Depth,1.15 +fallback=Weather_Thunderstorm_Wind_Speed,.5 +fallback=Weather_Thunderstorm_Cloud_Speed,3 +fallback=Weather_Thunderstorm_Glare_View,0 +fallback=Weather_Thunderstorm_Rain_Loop_Sound_ID,rain heavy +fallback=Weather_Thunderstorm_Using_Precip,1 +fallback=Weather_Thunderstorm_Rain_Diameter,600 +fallback=Weather_Thunderstorm_Rain_Height_Min,200 +fallback=Weather_Thunderstorm_Rain_Height_Max,700 +fallback=Weather_Thunderstorm_Rain_Threshold,0.6 +fallback=Weather_Thunderstorm_Max_Raindrops,650 +fallback=Weather_Thunderstorm_Rain_Entrance_Speed,5 +fallback=Weather_Thunderstorm_Ambient_Loop_Sound_ID,None +fallback=Weather_Thunderstorm_Flash_Decrement,4 +fallback=Weather_Rain_Cloud_Texture,Tx_Sky_Rainy.dds +fallback=Weather_Rain_Clouds_Maximum_Percent,0.66 +fallback=Weather_Rain_Transition_Delta,.015 +fallback=Weather_Rain_Sky_Sunrise_Color,071,074,075 +fallback=Weather_Rain_Sky_Day_Color,116,120,122 +fallback=Weather_Rain_Sky_Sunset_Color,073,073,073 +fallback=Weather_Rain_Sky_Night_Color,024,025,026 +fallback=Weather_Rain_Fog_Sunrise_Color,071,074,075 +fallback=Weather_Rain_Fog_Day_Color,116,120,122 +fallback=Weather_Rain_Fog_Sunset_Color,073,073,073 +fallback=Weather_Rain_Fog_Night_Color,024,025,026 +fallback=Weather_Rain_Ambient_Sunrise_Color,097,090,088 +fallback=Weather_Rain_Ambient_Day_Color,105,110,113 +fallback=Weather_Rain_Ambient_Sunset_Color,088,097,097 +fallback=Weather_Rain_Ambient_Night_Color,050,055,067 +fallback=Weather_Rain_Sun_Sunrise_Color,131,122,120 +fallback=Weather_Rain_Sun_Day_Color,149,157,170 +fallback=Weather_Rain_Sun_Sunset_Color,120,126,131 +fallback=Weather_Rain_Sun_Night_Color,050,062,101 +fallback=Weather_Rain_Sun_Disc_Sunset_Color,128,128,128 +fallback=Weather_Rain_Land_Fog_Day_Depth,.8 +fallback=Weather_Rain_Land_Fog_Night_Depth,.8 +fallback=Weather_Rain_Wind_Speed,.3 +fallback=Weather_Rain_Cloud_Speed,2 +fallback=Weather_Rain_Glare_View,0 +fallback=Weather_Rain_Rain_Loop_Sound_ID,Rain +fallback=Weather_Rain_Using_Precip,1 +fallback=Weather_Rain_Rain_Diameter,600 +fallback=Weather_Rain_Rain_Height_Min,200 +fallback=Weather_Rain_Rain_Height_Max,700 +fallback=Weather_Rain_Rain_Threshold,0.6 +fallback=Weather_Rain_Rain_Entrance_Speed,7 +fallback=Weather_Rain_Ambient_Loop_Sound_ID,None +fallback=Weather_Rain_Max_Raindrops,450 +fallback=Weather_Overcast_Cloud_Texture,Tx_Sky_Overcast.dds +fallback=Weather_Overcast_Clouds_Maximum_Percent,1.0 +fallback=Weather_Overcast_Transition_Delta,.015 +fallback=Weather_Overcast_Sky_Sunrise_Color,091,099,106 +fallback=Weather_Overcast_Sky_Day_Color,143,146,149 +fallback=Weather_Overcast_Sky_Sunset_Color,108,115,121 +fallback=Weather_Overcast_Sky_Night_Color,019,022,025 +fallback=Weather_Overcast_Fog_Sunrise_Color,091,099,106 +fallback=Weather_Overcast_Fog_Day_Color,143,146,149 +fallback=Weather_Overcast_Fog_Sunset_Color,108,115,121 +fallback=Weather_Overcast_Fog_Night_Color,019,022,025 +fallback=Weather_Overcast_Ambient_Sunrise_Color,084,088,092 +fallback=Weather_Overcast_Ambient_Day_Color,093,096,105 +fallback=Weather_Overcast_Ambient_Sunset_Color,083,077,075 +fallback=Weather_Overcast_Ambient_Night_Color,057,060,066 +fallback=Weather_Overcast_Sun_Sunrise_Color,087,125,163 +fallback=Weather_Overcast_Sun_Day_Color,163,169,183 +fallback=Weather_Overcast_Sun_Sunset_Color,085,103,157 +fallback=Weather_Overcast_Sun_Night_Color,032,054,100 +fallback=Weather_Overcast_Sun_Disc_Sunset_Color,128,128,128 +fallback=Weather_Overcast_Land_Fog_Day_Depth,.70 +fallback=Weather_Overcast_Land_Fog_Night_Depth,.70 +fallback=Weather_Overcast_Wind_Speed,.2 +fallback=Weather_Overcast_Cloud_Speed,1.5 +fallback=Weather_Overcast_Glare_View,0 +fallback=Weather_Overcast_Ambient_Loop_Sound_ID,None +fallback=Weather_Snow_Cloud_Texture,Tx_Sky_Snow.dds +fallback=Weather_Snow_Clouds_Maximum_Percent,1.0 +fallback=Weather_Snow_Transition_Delta,.015 +fallback=Weather_Snow_Sky_Sunrise_Color,106,091,091 +fallback=Weather_Snow_Sky_Day_Color,153,158,166 +fallback=Weather_Snow_Sky_Sunset_Color,096,115,134 +fallback=Weather_Snow_Sky_Night_Color,031,035,039 +fallback=Weather_Snow_Fog_Sunrise_Color,106,091,091 +fallback=Weather_Snow_Fog_Day_Color,153,158,166 +fallback=Weather_Snow_Fog_Sunset_Color,096,115,134 +fallback=Weather_Snow_Fog_Night_Color,031,035,039 +fallback=Weather_Snow_Ambient_Sunrise_Color,092,084,084 +fallback=Weather_Snow_Ambient_Day_Color,093,096,105 +fallback=Weather_Snow_Ambient_Sunset_Color,070,079,087 +fallback=Weather_Snow_Ambient_Night_Color,049,058,068 +fallback=Weather_Snow_Sun_Sunrise_Color,141,109,109 +fallback=Weather_Snow_Sun_Day_Color,163,169,183 +fallback=Weather_Snow_Sun_Sunset_Color,101,121,141 +fallback=Weather_Snow_Sun_Night_Color,055,066,077 +fallback=Weather_Snow_Sun_Disc_Sunset_Color,128,128,128 +fallback=Weather_Snow_Land_Fog_Day_Depth,1.0 +fallback=Weather_Snow_Land_Fog_Night_Depth,1.2 +fallback=Weather_Snow_Wind_Speed,0 +fallback=Weather_Snow_Cloud_Speed,1.5 +fallback=Weather_Snow_Glare_View,0 +fallback=Weather_Snow_Snow_Diameter,800 +fallback=Weather_Snow_Snow_Height_Min,400 +fallback=Weather_Snow_Snow_Height_Max,700 +fallback=Weather_Snow_Snow_Entrance_Speed,6 +fallback=Weather_Snow_Max_Snowflakes,750 +fallback=Weather_Snow_Ambient_Loop_Sound_ID,None +fallback=Weather_Snow_Snow_Threshold,0.5 +fallback=Weather_Blizzard_Cloud_Texture,Tx_Sky_Blizzard.dds +fallback=Weather_Blizzard_Clouds_Maximum_Percent,1.0 +fallback=Weather_Blizzard_Transition_Delta,.030 +fallback=Weather_Blizzard_Sky_Sunrise_Color,091,099,106 +fallback=Weather_Blizzard_Sky_Day_Color,121,133,145 +fallback=Weather_Blizzard_Sky_Sunset_Color,108,115,121 +fallback=Weather_Blizzard_Sky_Night_Color,027,029,031 +fallback=Weather_Blizzard_Fog_Sunrise_Color,091,099,106 +fallback=Weather_Blizzard_Fog_Day_Color,121,133,145 +fallback=Weather_Blizzard_Fog_Sunset_Color,108,115,121 +fallback=Weather_Blizzard_Fog_Night_Color,021,024,028 +fallback=Weather_Blizzard_Ambient_Sunrise_Color,084,088,092 +fallback=Weather_Blizzard_Ambient_Day_Color,093,096,105 +fallback=Weather_Blizzard_Ambient_Sunset_Color,083,077,075 +fallback=Weather_Blizzard_Ambient_Night_Color,053,062,070 +fallback=Weather_Blizzard_Sun_Sunrise_Color,114,128,146 +fallback=Weather_Blizzard_Sun_Day_Color,163,169,183 +fallback=Weather_Blizzard_Sun_Sunset_Color,106,114,136 +fallback=Weather_Blizzard_Sun_Night_Color,057,066,074 +fallback=Weather_Blizzard_Sun_Disc_Sunset_Color,128,128,128 +fallback=Weather_Blizzard_Land_Fog_Day_Depth,2.8 +fallback=Weather_Blizzard_Land_Fog_Night_Depth,3.0 +fallback=Weather_Blizzard_Wind_Speed,.9 +fallback=Weather_Blizzard_Cloud_Speed,7.5 +fallback=Weather_Blizzard_Glare_View,0 +fallback=Weather_Blizzard_Ambient_Loop_Sound_ID, Blizzard +fallback=Weather_Blizzard_Storm_Threshold,.50 + +# Misc +encoding=win1252 From e45c0c8f59f69703368233be714f5e13680de4dc Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 29 Oct 2018 16:28:24 +0000 Subject: [PATCH 127/133] Update to new packages --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 565feff141..904ab8ffb7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,9 +17,9 @@ Debian: # - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb - - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb - - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb - - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-2_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-2_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-2_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-2_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-2_amd64.deb -o libmygui-dev_3.2.2+dfsg-2_amd64.deb - dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb stage: build script: From cc1fd8c92e6fd206aaaa238e1ab5096688eb4e14 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 29 Oct 2018 17:35:04 +0000 Subject: [PATCH 128/133] Use https://http.kali.org/pool/main/m/mygui/ instead since debian doesn't keep old packages. --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 904ab8ffb7..79cb6957c0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,9 +17,9 @@ Debian: # - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb - - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-2_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-2_amd64.deb - - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-2_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-2_amd64.deb - - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-2_amd64.deb -o libmygui-dev_3.2.2+dfsg-2_amd64.deb + - curl https://http.kali.org/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb + - curl https://http.kali.org/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb + - curl https://http.kali.org/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb - dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb stage: build script: From 23d273b1d739ea007e726a5f3821142f750ecbcd Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 29 Oct 2018 17:43:08 +0000 Subject: [PATCH 129/133] Follow the redirects --- .gitlab-ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 79cb6957c0..06fab6cce0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -15,11 +15,11 @@ Debian: - apt-get update -yq - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev # - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old - - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb - - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb - - curl https://http.kali.org/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb - - curl https://http.kali.org/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb - - curl https://http.kali.org/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb + - curl -L http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb + - curl -L http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb + - curl -L https://http.kali.org/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb + - curl -L https://http.kali.org/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb + - curl -L https://http.kali.org/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb - dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb stage: build script: From f98fc8d7b4db55be7cfeea927649553537ff21b6 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 29 Oct 2018 22:47:04 +0400 Subject: [PATCH 130/133] Make sure we reset current weapon animation when resurrect actor (addition to bug #2626) --- apps/openmw/mwmechanics/character.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index d5e22cb070..cdb2fdc7c1 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2523,6 +2523,7 @@ void CharacterController::resurrect() mAnimation->disable(mCurrentDeath); mCurrentDeath.clear(); mDeathState = CharState_None; + mWeaponType = WeapType_None; } void CharacterController::updateContinuousVfx() From 9b83a2345b7a86974ed2173c386f5e27a93cefac Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 29 Oct 2018 20:54:21 +0100 Subject: [PATCH 131/133] remove unnecessary fallbacks --- files/openmw.cfg | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/files/openmw.cfg b/files/openmw.cfg index 354fdb4a19..11ea1ac150 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -18,12 +18,10 @@ script-blacklist=wereDreamScript3 no-sound=0 # archives -fallback-archive=OpenMW-Example-Suite.zip -fallback-archive=OpenMW-Template.zip +# fallback-archive=OpenMW-Example-Suite.zip +# fallback-archive=OpenMW-Template.zip # lighting -fallback=LightAttenuation_UseConstant,0 -fallback=LightAttenuation_ConstantValue,0.0 fallback=LightAttenuation_UseLinear,1 fallback=LightAttenuation_LinearMethod,1 fallback=LightAttenuation_LinearValue,3.0 @@ -45,18 +43,6 @@ fallback=Inventory_DirectionalRotationX,110 fallback=Inventory_DirectionalRotationY,90 fallback=Inventory_UniformScaling,0 -# fast travel -fallback=Map_Travel_Beast_Red,238 -fallback=Map_Travel_Beast_Green,230 -fallback=Map_Travel_Beast_Blue,30 -fallback=Map_Travel_Boat_Red,30 -fallback=Map_Travel_Boat_Green,30 -fallback=Map_Travel_Boat_Blue,230 -fallback=Map_Travel_Magic_Red,230 -fallback=Map_Travel_Magic_Green,30 -fallback=Map_Travel_Magic_Blue,30 -fallback=Map_Show_Travel_Lines,0 - # water effects fallback=Water_Map_Alpha,0.4 fallback=Water_World_Alpha,0.75 @@ -73,8 +59,6 @@ fallback=Water_MaxNumberRipples,75 fallback=Water_RippleScale,0.15, 6.5 fallback=Water_RippleRotSpeed,0.5 fallback=Water_RippleAlphas,0.7, 0.1, 0.01 -fallback=Water_PSWaterReflectTerrain,1 -fallback=Water_PSWaterReflectUpdate,20.0 fallback=Water_NearWaterRadius,1000 fallback=Water_NearWaterPoints,8 fallback=Water_NearWaterUnderwaterFreq,0.3 @@ -90,9 +74,6 @@ fallback=Water_UnderwaterNightFog,4 fallback=Water_UnderwaterIndoorFog,3 fallback=Water_UnderwaterColor,012,030,037 fallback=Water_UnderwaterColorWeight,0.85 -fallback=PixelWater_SurfaceFPS,25 -fallback=PixelWater_TileCount,4 -fallback=PixelWater_Resolution,256 # fonts fallback=Fonts_Font_0,magic_cards_regular From cbbe69f3f28960e5ed60938fb56d881c27d6e073 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 30 Oct 2018 12:17:10 +0100 Subject: [PATCH 132/133] additional cleanups for non-essential fallbacks --- files/openmw.cfg | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/files/openmw.cfg b/files/openmw.cfg index 11ea1ac150..9c51df3899 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -12,15 +12,6 @@ script-blacklist=WereChange2Script script-blacklist=wereDreamScript2 script-blacklist=wereDreamScript3 -# Sane defaults - -# sound -no-sound=0 - -# archives -# fallback-archive=OpenMW-Example-Suite.zip -# fallback-archive=OpenMW-Template.zip - # lighting fallback=LightAttenuation_UseLinear,1 fallback=LightAttenuation_LinearMethod,1 @@ -505,6 +496,3 @@ fallback=Weather_Blizzard_Cloud_Speed,7.5 fallback=Weather_Blizzard_Glare_View,0 fallback=Weather_Blizzard_Ambient_Loop_Sound_ID, Blizzard fallback=Weather_Blizzard_Storm_Threshold,.50 - -# Misc -encoding=win1252 From d1eb7a52a08666f621e8633023f9c0ce30845ef6 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 30 Oct 2018 20:49:59 +0000 Subject: [PATCH 133/133] Update CHANGELOG.md --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96baa5365f..524ce2cc24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +0.46.0 +------ + + Feature #2229: Improve pathfinding AI + Feature #3442: Default values for fallbacks from ini file + + 0.45.0 ------