Merge branch 'custom_actor_collision_shape_type' into 'master'

Support cylinder and rotating box collision shape types for actors (#6138)

Closes #6138

See merge request OpenMW/openmw!2043
check_span
psi29a 2 years ago
commit 454684bad3

@ -11,6 +11,7 @@
#include <components/config/gamesettings.hpp>
#include <components/contentselector/view/contentselector.hpp>
#include <components/contentselector/model/esmfile.hpp>
#include <components/detournavigator/collisionshapetype.hpp>
#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

@ -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<std::uint64_t>(Settings::Manager::getInt64("max navmeshdb file size", "Navigator"));

@ -138,6 +138,7 @@ namespace MWLua
context.mLua->tableFromPairs<std::string_view, DetourNavigator::CollisionShapeType>({
{"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());

@ -1,6 +1,6 @@
#include "actor.hpp"
#include <BulletCollision/CollisionShapes/btBoxShape.h>
#include <BulletCollision/CollisionShapes/btCylinderShape.h>
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include <components/sceneutil/positionattitudetransform.hpp>
@ -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<btBoxShape>(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<btBoxShape>(halfExtents);
mRotationallyInvariant = true;
break;
case DetourNavigator::CollisionShapeType::RotatingBox:
mShape = std::make_unique<btBoxShape>(halfExtents);
mRotationallyInvariant = false;
break;
case DetourNavigator::CollisionShapeType::Cylinder:
mShape = std::make_unique<btCylinderShapeZ>(halfExtents);
mRotationallyInvariant = true;
break;
}
mCollisionShapeType = collisionShapeType;
}
else
{
mShape = std::make_unique<btBoxShape>(halfExtents);
mRotationallyInvariant = false;
mCollisionShapeType = DetourNavigator::CollisionShapeType::RotatingBox;
}

@ -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;
/**

@ -26,7 +26,7 @@
#include <components/esm3/loadgmst.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/misc/convert.hpp>
#include <components/settings/settings.hpp>
#include <components/nifosg/particle.hpp> // 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<Actor>(ptr, shape, mTaskScheduler.get(), canWaterWalk);
auto actor = std::make_shared<Actor>(ptr, shape, mTaskScheduler.get(), canWaterWalk, mActorCollisionShapeType);
mActors.emplace(ptr.mRef, std::move(actor));
}

@ -17,6 +17,7 @@
#include <osg/Timer>
#include <components/misc/span.hpp>
#include <components/detournavigator/collisionshapetype.hpp>
#include "../mwworld/ptr.hpp"
@ -330,6 +331,8 @@ namespace MWPhysics
float mPhysicsDt;
DetourNavigator::CollisionShapeType mActorCollisionShapeType;
PhysicsSystem (const PhysicsSystem&);
PhysicsSystem& operator= (const PhysicsSystem&);
};

@ -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()};
}

@ -6,6 +6,7 @@
#include <components/settings/settings.hpp>
#include <components/misc/rng.hpp>
#include <components/esm3/readerscache.hpp>
#include <components/detournavigator/collisionshapetype.hpp>
#include "../mwbase/world.hpp"
@ -111,6 +112,7 @@ namespace MWWorld
std::string mUserDataPath;
osg::Vec3f mDefaultHalfExtents;
DetourNavigator::CollisionShapeType mDefaultActorCollisionShapeType;
bool mShouldUpdateNavigator;
int mActivationDistanceOverride;

@ -315,6 +315,7 @@ add_component_dir(detournavigator
navmeshdbutils
recast
gettilespositions
collisionshapetype
)
add_component_dir(loadinglistener

@ -0,0 +1,22 @@
#include "collisionshapetype.hpp"
#include <stdexcept>
#include <string>
namespace DetourNavigator
{
CollisionShapeType toCollisionShapeType(int value)
{
switch (static_cast<CollisionShapeType>(value))
{
case CollisionShapeType::Aabb:
case CollisionShapeType::RotatingBox:
case CollisionShapeType::Cylinder:
return static_cast<CollisionShapeType>(value);
}
std::string error("Invalid CollisionShapeType value: \"");
error += value;
error += '"';
throw std::invalid_argument(error);
}
}

@ -9,9 +9,10 @@ namespace DetourNavigator
{
Aabb = 0,
RotatingBox = 1,
Cylinder = 2,
};
inline constexpr CollisionShapeType defaultCollisionShapeType = CollisionShapeType::Aabb;
CollisionShapeType toCollisionShapeType(int value);
}
#endif

@ -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<std::underlying_type_t<CollisionShapeType>>(v);
}

@ -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;

@ -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

@ -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

@ -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).

@ -43,7 +43,7 @@
</property>
</widget>
</item>
<item row="6" column="0">
<item row="5" column="1">
<widget class="QCheckBox" name="avoidCollisionsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If enabled NPCs apply evasion maneuver to avoid collisions with others.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -143,28 +143,28 @@
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="stealingFromKnockedOutCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make stealing items from NPCs that were knocked down possible during combat.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Always allow stealing from knocked out actors</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="allowNPCToFollowOverWaterSurfaceCheckBox">
<property name="toolTip">
<string>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.</string>
</property>
<property name="text">
<string>Always allow NPC to follow over water surface</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="stealingFromKnockedOutCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make stealing items from NPCs that were knocked down possible during combat.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Always allow stealing from knocked out actors</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="allowNPCToFollowOverWaterSurfaceCheckBox">
<property name="toolTip">
<string>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.</string>
</property>
<property name="text">
<string>Always allow NPC to follow over water surface</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="requireAppropriateAmmunitionCheckBox">
<property name="toolTip">
@ -186,15 +186,15 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalUnarmedStrengthLayout">
<item>
<layout class="QGridLayout" name="gridLayoutGameMechanics2">
<item row="0" column="0">
<widget class="QLabel" name="unarmedFactorsStrengthLabel">
<property name="text">
<string>Factor strength into hand-to-hand combat:</string>
</property>
</widget>
</item>
<item>
<item row="0" column="1">
<widget class="QComboBox" name="unarmedFactorsStrengthComboBox">
<property name="currentIndex">
<number>0</number>
@ -216,53 +216,55 @@
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalPhysicsThreadsLayout">
<item>
<item row="1" column="0">
<widget class="QLabel" name="physicsThreadsLabel">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;A value greater than 1 requires the Bullet library be compiled with multithreading support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Background physics threads</string>
<string>Background physics threads:</string>
</property>
</widget>
</item>
<item>
<item row="1" column="1">
<widget class="QSpinBox" name="physicsThreadsSpinBox"/>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<item row="2" column="0">
<widget class="QLabel" name="labelActorCollisionShapeType">
<property name="text">
<string>Actor collision shape type:</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="actorCollisonShapeTypeComboBox">
<property name="toolTip">
<string>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.</string>
</property>
</spacer>
<property name="currentText">
<string>Axis-aligned bounding box</string>
</property>
<item>
<property name="text">
<string>Axis-aligned bounding box</string>
</property>
</item>
<item>
<property name="text">
<string>Rotating box</string>
</property>
</item>
<item>
<property name="text">
<string>Cylinder</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_3">
<spacer name="verticalSpacerGameMechanics">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>

Loading…
Cancel
Save