From 8b8e4f78b616b97a13a51cb7e35dab7eda6d93fe Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 4 Jun 2022 00:44:42 +0200 Subject: [PATCH] Support cylinder and rotating box collision shape types for actors Cylinder collision shape should give the best consistency between physics simulation and pathfinding. Rotating box is already used by some actors, so add it to have the same collision shape type for all actors. --- apps/launcher/advancedpage.cpp | 5 + apps/navmeshtool/main.cpp | 2 +- apps/openmw/mwlua/nearbybindings.cpp | 3 +- apps/openmw/mwphysics/actor.cpp | 27 ++++- apps/openmw/mwphysics/actor.hpp | 3 +- apps/openmw/mwphysics/physicssystem.cpp | 7 +- apps/openmw/mwphysics/physicssystem.hpp | 3 + apps/openmw/mwworld/worldimp.cpp | 3 +- apps/openmw/mwworld/worldimp.hpp | 2 + components/CMakeLists.txt | 1 + .../detournavigator/collisionshapetype.cpp | 22 ++++ .../detournavigator/collisionshapetype.hpp | 3 +- components/detournavigator/debug.cpp | 1 + components/detournavigator/recastparams.hpp | 2 + .../reference/modding/settings/game.rst | 18 ++- files/lua_api/openmw/nearby.lua | 3 +- files/settings-default.cfg | 6 + files/ui/advancedpage.ui | 112 +++++++++--------- 18 files changed, 152 insertions(+), 71 deletions(-) create mode 100644 components/detournavigator/collisionshapetype.cpp diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index c4e438edae..ff04a846ff 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "utils/openalutil.hpp" @@ -109,6 +110,9 @@ bool Launcher::AdvancedPage::loadSettings() physicsThreadsSpinBox->setValue(numPhysicsThreads); loadSettingBool(allowNPCToFollowOverWaterSurfaceCheckBox, "allow actors to follow over water surface", "Game"); loadSettingBool(unarmedCreatureAttacksDamageArmorCheckBox, "unarmed creature attacks damage armor", "Game"); + const int actorCollisionShapeType = Settings::Manager::getInt("actor collision shape type", "Game"); + if (0 <= actorCollisionShapeType && actorCollisionShapeType < actorCollisonShapeTypeComboBox->count()) + actorCollisonShapeTypeComboBox->setCurrentIndex(actorCollisionShapeType); } // Visuals @@ -264,6 +268,7 @@ void Launcher::AdvancedPage::saveSettings() saveSettingInt(physicsThreadsSpinBox, "async num threads", "Physics"); saveSettingBool(allowNPCToFollowOverWaterSurfaceCheckBox, "allow actors to follow over water surface", "Game"); saveSettingBool(unarmedCreatureAttacksDamageArmorCheckBox, "unarmed creature attacks damage armor", "Game"); + saveSettingInt(actorCollisonShapeTypeComboBox, "actor collision shape type", "Game"); } // Visuals diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index ea0046b1ae..a40a4770bf 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -174,7 +174,7 @@ namespace NavMeshTool Settings::Manager settings; settings.load(config); - const DetourNavigator::CollisionShapeType agentCollisionShape = DetourNavigator::defaultCollisionShapeType; + const auto agentCollisionShape = DetourNavigator::toCollisionShapeType(Settings::Manager::getInt("actor collision shape type", "Game")); const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game"); const DetourNavigator::AgentBounds agentBounds {agentCollisionShape, agentHalfExtents}; const std::uint64_t maxDbFileSize = static_cast(Settings::Manager::getInt64("max navmeshdb file size", "Navigator")); diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 8e09284518..7a40bdc40c 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -138,6 +138,7 @@ namespace MWLua context.mLua->tableFromPairs({ {"Aabb", DetourNavigator::CollisionShapeType::Aabb}, {"RotatingBox", DetourNavigator::CollisionShapeType::RotatingBox}, + {"Cylinder", DetourNavigator::CollisionShapeType::Cylinder}, })); api["FIND_PATH_STATUS"] = LuaUtil::makeStrictReadOnly( @@ -154,7 +155,7 @@ namespace MWLua })); static const DetourNavigator::AgentBounds defaultAgentBounds { - DetourNavigator::defaultCollisionShapeType, + DetourNavigator::toCollisionShapeType(Settings::Manager::getInt("actor collision shape type", "Game")), Settings::Manager::getVector3("default actor pathfind half extents", "Game"), }; static const float defaultStepSize = 2 * std::max(defaultAgentBounds.mHalfExtents.x(), defaultAgentBounds.mHalfExtents.y()); diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 64e38559f6..a23a2e56f4 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -1,6 +1,6 @@ #include "actor.hpp" -#include +#include #include #include @@ -20,7 +20,8 @@ namespace MWPhysics { -Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk) +Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, + bool canWaterWalk, DetourNavigator::CollisionShapeType collisionShapeType) : mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false) , mMeshTranslation(shape->mCollisionBox.mCenter), mOriginalHalfExtents(shape->mCollisionBox.mExtents) , mStuckFrames(0), mLastStuckPosition{0, 0, 0} @@ -56,16 +57,30 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\"."; } - mShape = std::make_unique(Misc::Convert::toBullet(mOriginalHalfExtents)); - + const btVector3 halfExtents = Misc::Convert::toBullet(mOriginalHalfExtents); if ((mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mOriginalHalfExtents.x() - mOriginalHalfExtents.y()) < 2.2) { - mRotationallyInvariant = true; - mCollisionShapeType = DetourNavigator::CollisionShapeType::Aabb; + switch (collisionShapeType) + { + case DetourNavigator::CollisionShapeType::Aabb: + mShape = std::make_unique(halfExtents); + mRotationallyInvariant = true; + break; + case DetourNavigator::CollisionShapeType::RotatingBox: + mShape = std::make_unique(halfExtents); + mRotationallyInvariant = false; + break; + case DetourNavigator::CollisionShapeType::Cylinder: + mShape = std::make_unique(halfExtents); + mRotationallyInvariant = true; + break; + } + mCollisionShapeType = collisionShapeType; } else { + mShape = std::make_unique(halfExtents); mRotationallyInvariant = false; mCollisionShapeType = DetourNavigator::CollisionShapeType::RotatingBox; } diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 322ad74d7c..b333d2915a 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -29,7 +29,8 @@ namespace MWPhysics class Actor final : public PtrHolder { public: - Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk); + Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, + bool canWaterWalk, DetourNavigator::CollisionShapeType collisionShapeType); ~Actor() override; /** diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index f4718e0d8f..352cb55221 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -26,7 +26,7 @@ #include #include #include - +#include #include // FindRecIndexVisitor #include "../mwbase/world.hpp" @@ -104,6 +104,7 @@ namespace MWPhysics , mWaterEnabled(false) , mParentNode(parentNode) , mPhysicsDt(1.f / 60.f) + , mActorCollisionShapeType(DetourNavigator::toCollisionShapeType(Settings::Manager::getInt("actor collision shape type", "Game"))) { mResourceSystem->addResourceManager(mShapeManager.get()); @@ -646,8 +647,8 @@ namespace MWPhysics const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); const bool canWaterWalk = effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0; - auto actor = std::make_shared(ptr, shape, mTaskScheduler.get(), canWaterWalk); - + auto actor = std::make_shared(ptr, shape, mTaskScheduler.get(), canWaterWalk, mActorCollisionShapeType); + mActors.emplace(ptr.mRef, std::move(actor)); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 4afafede4f..7843b6a9a3 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -17,6 +17,7 @@ #include #include +#include #include "../mwworld/ptr.hpp" @@ -330,6 +331,8 @@ namespace MWPhysics float mPhysicsDt; + DetourNavigator::CollisionShapeType mActorCollisionShapeType; + PhysicsSystem (const PhysicsSystem&); PhysicsSystem& operator= (const PhysicsSystem&); }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 911d00b788..12df39fb28 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -152,6 +152,7 @@ namespace MWWorld mGodMode(false), mScriptsEnabled(true), mDiscardMovements(true), mContentFiles (contentFiles), mUserDataPath(userDataPath), mDefaultHalfExtents(Settings::Manager::getVector3("default actor pathfind half extents", "Game")), + mDefaultActorCollisionShapeType(DetourNavigator::toCollisionShapeType(Settings::Manager::getInt("actor collision shape type", "Game"))), mShouldUpdateNavigator(false), mActivationDistanceOverride (activationDistanceOverride), mStartCell(startCell), mDistanceToFacedObject(-1.f), mTeleportEnabled(true), @@ -3945,7 +3946,7 @@ namespace MWWorld { const MWPhysics::Actor* physicsActor = mPhysics->getActor(actor); if (physicsActor == nullptr || !actor.isInCell() || actor.getCell()->isExterior()) - return DetourNavigator::AgentBounds {DetourNavigator::defaultCollisionShapeType, mDefaultHalfExtents}; + return DetourNavigator::AgentBounds {mDefaultActorCollisionShapeType, mDefaultHalfExtents}; else return DetourNavigator::AgentBounds {physicsActor->getCollisionShapeType(), physicsActor->getHalfExtents()}; } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 6cb278814d..3e5b3d7731 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "../mwbase/world.hpp" @@ -111,6 +112,7 @@ namespace MWWorld std::string mUserDataPath; osg::Vec3f mDefaultHalfExtents; + DetourNavigator::CollisionShapeType mDefaultActorCollisionShapeType; bool mShouldUpdateNavigator; int mActivationDistanceOverride; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 93901b6f83..29940cef6f 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -315,6 +315,7 @@ add_component_dir(detournavigator navmeshdbutils recast gettilespositions + collisionshapetype ) add_component_dir(loadinglistener diff --git a/components/detournavigator/collisionshapetype.cpp b/components/detournavigator/collisionshapetype.cpp new file mode 100644 index 0000000000..b20ae6147f --- /dev/null +++ b/components/detournavigator/collisionshapetype.cpp @@ -0,0 +1,22 @@ +#include "collisionshapetype.hpp" + +#include +#include + +namespace DetourNavigator +{ + CollisionShapeType toCollisionShapeType(int value) + { + switch (static_cast(value)) + { + case CollisionShapeType::Aabb: + case CollisionShapeType::RotatingBox: + case CollisionShapeType::Cylinder: + return static_cast(value); + } + std::string error("Invalid CollisionShapeType value: \""); + error += value; + error += '"'; + throw std::invalid_argument(error); + } +} diff --git a/components/detournavigator/collisionshapetype.hpp b/components/detournavigator/collisionshapetype.hpp index 3964840ecf..cf47623f2b 100644 --- a/components/detournavigator/collisionshapetype.hpp +++ b/components/detournavigator/collisionshapetype.hpp @@ -9,9 +9,10 @@ namespace DetourNavigator { Aabb = 0, RotatingBox = 1, + Cylinder = 2, }; - inline constexpr CollisionShapeType defaultCollisionShapeType = CollisionShapeType::Aabb; + CollisionShapeType toCollisionShapeType(int value); } #endif diff --git a/components/detournavigator/debug.cpp b/components/detournavigator/debug.cpp index c76e2bc562..d274023e78 100644 --- a/components/detournavigator/debug.cpp +++ b/components/detournavigator/debug.cpp @@ -79,6 +79,7 @@ namespace DetourNavigator { case CollisionShapeType::Aabb: return s << "AgentShapeType::Aabb"; case CollisionShapeType::RotatingBox: return s << "AgentShapeType::RotatingBox"; + case CollisionShapeType::Cylinder: return s << "AgentShapeType::Cylinder"; } return s << "AgentShapeType::" << static_cast>(v); } diff --git a/components/detournavigator/recastparams.hpp b/components/detournavigator/recastparams.hpp index e765623431..02799950a6 100644 --- a/components/detournavigator/recastparams.hpp +++ b/components/detournavigator/recastparams.hpp @@ -24,6 +24,8 @@ namespace DetourNavigator return std::max(agentBounds.mHalfExtents.x(), agentBounds.mHalfExtents.y()) * std::sqrt(2); case CollisionShapeType::RotatingBox: return agentBounds.mHalfExtents.x(); + case CollisionShapeType::Cylinder: + return std::max(agentBounds.mHalfExtents.x(), agentBounds.mHalfExtents.y()); } assert(false && "Unsupported agent shape type"); return 0; diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 2f8cbbf91d..3df13904ae 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -476,7 +476,7 @@ day night switches Some mods add models which change visuals based on time of day. When this setting is enabled, supporting models will automatically make use of Day/night state. unarmed creature attacks damage armor ------------------------------------------ +------------------------------------- :Type: boolean :Range: True/False @@ -487,3 +487,19 @@ If disabled unarmed creature attacks do not reduce armor condition, just as with If enabled unarmed creature attacks reduce armor condition, the same as attacks from NPCs and armed creatures. This setting can be controlled in Advanced tab of the launcher, under Game Mechanics. + +actor collision shape type +-------------------------- + +:Type: integer +:Range: 0, 1, 2 +:Default: 0 (Axis-aligned bounding box) + +Collision is used for both physics simulation and navigation mesh generation for pathfinding. +Cylinder gives the best consistency bewtween available navigation paths and ability to move by them. +Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value +will not be useful with another. + +* 0: Axis-aligned bounding box +* 1: Rotating box +* 2: Cylinder diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index 47ed77f5ef..8f19f2b3c2 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -100,7 +100,8 @@ -- @field [parent=#CCOLLISION_SHAPE_TYPE] #number Aabb Axis-Aligned Bounding Box is used for NPC and symmetric -- Creatures; -- @field [parent=#COLLISION_SHAPE_TYPE] #number RotatingBox is used for Creatures with big difference in width and --- height. +-- height; +-- @field [parent=#COLLISION_SHAPE_TYPE] #number Cylinder is used for NPC and symmetric Creatures. --- -- @type FIND_PATH_STATUS diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 27a1063279..89090fa262 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -363,6 +363,12 @@ day night switches = true # Enables degradation of NPC's armor from unarmed creature attacks. unarmed creature attacks damage armor = false +# Collision is used for both physics simulation and navigation mesh generation for pathfinding: +# 0 = Axis-aligned bounding box +# 1 = Rotating box +# 2 = Cylinder +actor collision shape type = 0 + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 42dfbb2bfa..bcf710ffea 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -43,7 +43,7 @@ - + <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> @@ -143,28 +143,28 @@ - - - - <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - - - Always allow stealing from knocked out actors - - - - - - - Give NPC an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled. - - - Always allow NPC to follow over water surface - - - + + + + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> + + + Always allow stealing from knocked out actors + + + + + + + Give NPC an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled. + + + Always allow NPC to follow over water surface + + + @@ -186,15 +186,15 @@ - - + + Factor strength into hand-to-hand combat: - + 0 @@ -216,53 +216,55 @@ - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - + <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - Background physics threads + Background physics threads: - + - - - - Qt::Horizontal + + + + Actor collision shape type: - - - 40 - 20 - + + + + + + Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency bewtween available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. - + + Axis-aligned bounding box + + + + Axis-aligned bounding box + + + + + Rotating box + + + + + Cylinder + + + - + Qt::Vertical