mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-19 21:23:52 +00:00
Merge branch 'async-physics' into 'master'
Async physics See merge request OpenMW/openmw!248
This commit is contained in:
commit
c16fa27407
21 changed files with 1277 additions and 302 deletions
|
@ -67,6 +67,7 @@
|
|||
Feature #5524: Resume failed script execution after reload
|
||||
Feature #5525: Search fields tweaks (utf-8)
|
||||
Feature #5545: Option to allow stealing from an unconscious NPC during combat
|
||||
Feature #5563: Run physics update in background thread
|
||||
Feature #5579: MCP SetAngle enhancement
|
||||
Feature #5610: Actors movement should be smoother
|
||||
Feature #5642: Ability to attach arrows to actor skeleton instead of bow mesh
|
||||
|
|
|
@ -94,6 +94,9 @@ bool Launcher::AdvancedPage::loadSettings()
|
|||
unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex);
|
||||
loadSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game");
|
||||
loadSettingBool(enableNavigatorCheckBox, "enable", "Navigator");
|
||||
int numPhysicsThreads = mEngineSettings.getInt("async num threads", "Physics");
|
||||
if (numPhysicsThreads >= 0)
|
||||
physicsThreadsSpinBox->setValue(numPhysicsThreads);
|
||||
}
|
||||
|
||||
// Visuals
|
||||
|
@ -208,6 +211,9 @@ void Launcher::AdvancedPage::saveSettings()
|
|||
mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex);
|
||||
saveSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game");
|
||||
saveSettingBool(enableNavigatorCheckBox, "enable", "Navigator");
|
||||
int numPhysicsThreads = physicsThreadsSpinBox->value();
|
||||
if (numPhysicsThreads != mEngineSettings.getInt("async num threads", "Physics"))
|
||||
mEngineSettings.setInt("async num threads", "Physics", numPhysicsThreads);
|
||||
}
|
||||
|
||||
// Visuals
|
||||
|
|
|
@ -73,7 +73,7 @@ add_openmw_dir (mwworld
|
|||
add_openmw_dir (mwphysics
|
||||
physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback
|
||||
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver
|
||||
closestnotmeconvexresultcallback raycasting
|
||||
closestnotmeconvexresultcallback raycasting mtphysics
|
||||
)
|
||||
|
||||
add_openmw_dir (mwclass
|
||||
|
|
|
@ -12,37 +12,35 @@
|
|||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "collisiontype.hpp"
|
||||
#include "mtphysics.hpp"
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
|
||||
|
||||
Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape> shape, btCollisionWorld* world)
|
||||
Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler)
|
||||
: mCanWaterWalk(false), mWalkingOnWater(false)
|
||||
, mCollisionObject(nullptr), mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
|
||||
, mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBoxTranslate), mHalfExtents(shape->mCollisionBoxHalfExtents)
|
||||
, mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
|
||||
, mInternalCollisionMode(true)
|
||||
, mExternalCollisionMode(true)
|
||||
, mCollisionWorld(world)
|
||||
, mTaskScheduler(scheduler)
|
||||
{
|
||||
mPtr = 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)
|
||||
if (shape->mCollisionShape)
|
||||
{
|
||||
btTransform transform;
|
||||
transform.setIdentity();
|
||||
btVector3 min;
|
||||
btVector3 max;
|
||||
|
||||
collisionShape->mCollisionShape->getAabb(transform, min, max);
|
||||
shape->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;
|
||||
|
@ -79,17 +77,18 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape>
|
|||
updatePosition();
|
||||
|
||||
addCollisionMask(getCollisionMask());
|
||||
commitPositionChange();
|
||||
}
|
||||
|
||||
Actor::~Actor()
|
||||
{
|
||||
if (mCollisionObject.get())
|
||||
mCollisionWorld->removeCollisionObject(mCollisionObject.get());
|
||||
if (mCollisionObject)
|
||||
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
||||
}
|
||||
|
||||
void Actor::enableCollisionMode(bool collision)
|
||||
{
|
||||
mInternalCollisionMode = collision;
|
||||
mInternalCollisionMode.store(collision, std::memory_order_release);
|
||||
}
|
||||
|
||||
void Actor::enableCollisionBody(bool collision)
|
||||
|
@ -103,16 +102,15 @@ void Actor::enableCollisionBody(bool collision)
|
|||
|
||||
void Actor::addCollisionMask(int collisionMask)
|
||||
{
|
||||
mCollisionWorld->addCollisionObject(mCollisionObject.get(), CollisionType_Actor, collisionMask);
|
||||
mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Actor, collisionMask);
|
||||
}
|
||||
|
||||
void Actor::updateCollisionMask()
|
||||
{
|
||||
mCollisionWorld->removeCollisionObject(mCollisionObject.get());
|
||||
addCollisionMask(getCollisionMask());
|
||||
mTaskScheduler->setCollisionFilterMask(mCollisionObject.get(), getCollisionMask());
|
||||
}
|
||||
|
||||
int Actor::getCollisionMask()
|
||||
int Actor::getCollisionMask() const
|
||||
{
|
||||
int collisionMask = CollisionType_World | CollisionType_HeightMap;
|
||||
if (mExternalCollisionMode)
|
||||
|
@ -120,58 +118,91 @@ int Actor::getCollisionMask()
|
|||
if (mCanWaterWalk)
|
||||
collisionMask |= CollisionType_Water;
|
||||
return collisionMask;
|
||||
|
||||
}
|
||||
|
||||
void Actor::updatePosition()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
osg::Vec3f position = mPtr.getRefData().getPosition().asVec3();
|
||||
|
||||
mPosition = position;
|
||||
mPreviousPosition = position;
|
||||
|
||||
mTransformUpdatePending = true;
|
||||
updateCollisionObjectPosition();
|
||||
}
|
||||
|
||||
void Actor::updateCollisionObjectPosition()
|
||||
{
|
||||
btTransform tr = mCollisionObject->getWorldTransform();
|
||||
osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale);
|
||||
osg::Vec3f newPosition = scaledTranslation + mPosition;
|
||||
tr.setOrigin(Misc::Convert::toBullet(newPosition));
|
||||
mCollisionObject->setWorldTransform(tr);
|
||||
mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition));
|
||||
mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation));
|
||||
|
||||
}
|
||||
|
||||
void Actor::commitPositionChange()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
if (mScaleUpdatePending)
|
||||
{
|
||||
mShape->setLocalScaling(Misc::Convert::toBullet(mScale));
|
||||
mScaleUpdatePending = false;
|
||||
}
|
||||
if (mTransformUpdatePending)
|
||||
{
|
||||
mCollisionObject->setWorldTransform(mLocalTransform);
|
||||
mTransformUpdatePending = false;
|
||||
}
|
||||
}
|
||||
|
||||
osg::Vec3f Actor::getCollisionObjectPosition() const
|
||||
{
|
||||
return Misc::Convert::toOsg(mCollisionObject->getWorldTransform().getOrigin());
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
return Misc::Convert::toOsg(mLocalTransform.getOrigin());
|
||||
}
|
||||
|
||||
void Actor::setPosition(const osg::Vec3f &position)
|
||||
void Actor::setPosition(const osg::Vec3f &position, bool updateCollisionObject)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
if (mTransformUpdatePending)
|
||||
{
|
||||
mCollisionObject->setWorldTransform(mLocalTransform);
|
||||
mTransformUpdatePending = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
mPreviousPosition = mPosition;
|
||||
|
||||
mPosition = position;
|
||||
if (updateCollisionObject)
|
||||
{
|
||||
updateCollisionObjectPosition();
|
||||
mCollisionObject->setWorldTransform(mLocalTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
osg::Vec3f Actor::getPosition() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
return mPosition;
|
||||
}
|
||||
|
||||
osg::Vec3f Actor::getPreviousPosition() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
return mPreviousPosition;
|
||||
}
|
||||
|
||||
void Actor::updateRotation ()
|
||||
{
|
||||
btTransform tr = mCollisionObject->getWorldTransform();
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
if (mRotation == mPtr.getRefData().getBaseNode()->getAttitude())
|
||||
return;
|
||||
mRotation = mPtr.getRefData().getBaseNode()->getAttitude();
|
||||
tr.setRotation(Misc::Convert::toBullet(mRotation));
|
||||
mCollisionObject->setWorldTransform(tr);
|
||||
|
||||
mTransformUpdatePending = true;
|
||||
updateCollisionObjectPosition();
|
||||
}
|
||||
|
||||
|
@ -182,32 +213,37 @@ bool Actor::isRotationallyInvariant() const
|
|||
|
||||
void Actor::updateScale()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
float scale = mPtr.getCellRef().getScale();
|
||||
osg::Vec3f scaleVec(scale,scale,scale);
|
||||
|
||||
mPtr.getClass().adjustScale(mPtr, scaleVec, false);
|
||||
mScale = scaleVec;
|
||||
mShape->setLocalScaling(Misc::Convert::toBullet(mScale));
|
||||
mScaleUpdatePending = true;
|
||||
|
||||
scaleVec = osg::Vec3f(scale,scale,scale);
|
||||
mPtr.getClass().adjustScale(mPtr, scaleVec, true);
|
||||
mRenderingScale = scaleVec;
|
||||
|
||||
mTransformUpdatePending = true;
|
||||
updateCollisionObjectPosition();
|
||||
}
|
||||
|
||||
osg::Vec3f Actor::getHalfExtents() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
return osg::componentMultiply(mHalfExtents, mScale);
|
||||
}
|
||||
|
||||
osg::Vec3f Actor::getOriginalHalfExtents() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
return mHalfExtents;
|
||||
}
|
||||
|
||||
osg::Vec3f Actor::getRenderingHalfExtents() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
return osg::componentMultiply(mHalfExtents, mRenderingScale);
|
||||
}
|
||||
|
||||
|
@ -218,26 +254,27 @@ void Actor::setInertialForce(const osg::Vec3f &force)
|
|||
|
||||
void Actor::setOnGround(bool grounded)
|
||||
{
|
||||
mOnGround = grounded;
|
||||
mOnGround.store(grounded, std::memory_order_release);
|
||||
}
|
||||
|
||||
void Actor::setOnSlope(bool slope)
|
||||
{
|
||||
mOnSlope = slope;
|
||||
mOnSlope.store(slope, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool Actor::isWalkingOnWater() const
|
||||
{
|
||||
return mWalkingOnWater;
|
||||
return mWalkingOnWater.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void Actor::setWalkingOnWater(bool walkingOnWater)
|
||||
{
|
||||
mWalkingOnWater = walkingOnWater;
|
||||
mWalkingOnWater.store(walkingOnWater, std::memory_order_release);
|
||||
}
|
||||
|
||||
void Actor::setCanWaterWalk(bool waterWalk)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
if (waterWalk != mCanWaterWalk)
|
||||
{
|
||||
mCanWaterWalk = waterWalk;
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
#ifndef OPENMW_MWPHYSICS_ACTOR_H
|
||||
#define OPENMW_MWPHYSICS_ACTOR_H
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "ptrholder.hpp"
|
||||
|
||||
#include <LinearMath/btTransform.h>
|
||||
#include <osg/Vec3f>
|
||||
#include <osg/Quat>
|
||||
#include <osg/ref_ptr>
|
||||
|
||||
class btCollisionWorld;
|
||||
class btCollisionShape;
|
||||
class btCollisionObject;
|
||||
class btConvexShape;
|
||||
|
@ -21,12 +23,13 @@ namespace Resource
|
|||
|
||||
namespace MWPhysics
|
||||
{
|
||||
class PhysicsTaskScheduler;
|
||||
|
||||
class Actor : public PtrHolder
|
||||
class Actor final : public PtrHolder
|
||||
{
|
||||
public:
|
||||
Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape> shape, btCollisionWorld* world);
|
||||
~Actor();
|
||||
Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler);
|
||||
~Actor() override;
|
||||
|
||||
/**
|
||||
* Sets the collisionMode for this actor. If disabled, the actor can fly and clip geometry.
|
||||
|
@ -35,7 +38,7 @@ namespace MWPhysics
|
|||
|
||||
bool getCollisionMode() const
|
||||
{
|
||||
return mInternalCollisionMode;
|
||||
return mInternalCollisionMode.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
btConvexShape* getConvexShape() const { return mConvexShape; }
|
||||
|
@ -60,6 +63,7 @@ namespace MWPhysics
|
|||
void updatePosition();
|
||||
|
||||
void updateCollisionObjectPosition();
|
||||
void commitPositionChange();
|
||||
|
||||
/**
|
||||
* Returns the half extents of the collision body (scaled according to collision scale)
|
||||
|
@ -79,8 +83,9 @@ namespace MWPhysics
|
|||
|
||||
/**
|
||||
* Store the current position into mPreviousPosition, then move to this position.
|
||||
* Optionally, inform the physics engine about the change of position.
|
||||
*/
|
||||
void setPosition(const osg::Vec3f& position);
|
||||
void setPosition(const osg::Vec3f& position, bool updateCollisionObject=true);
|
||||
|
||||
osg::Vec3f getPosition() const;
|
||||
|
||||
|
@ -110,14 +115,14 @@ namespace MWPhysics
|
|||
|
||||
bool getOnGround() const
|
||||
{
|
||||
return mInternalCollisionMode && mOnGround;
|
||||
return mInternalCollisionMode.load(std::memory_order_acquire) && mOnGround.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void setOnSlope(bool slope);
|
||||
|
||||
bool getOnSlope() const
|
||||
{
|
||||
return mInternalCollisionMode && mOnSlope;
|
||||
return mInternalCollisionMode.load(std::memory_order_acquire) && mOnSlope.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
btCollisionObject* getCollisionObject() const
|
||||
|
@ -136,10 +141,10 @@ namespace MWPhysics
|
|||
/// Removes then re-adds the collision object to the dynamics world
|
||||
void updateCollisionMask();
|
||||
void addCollisionMask(int collisionMask);
|
||||
int getCollisionMask();
|
||||
int getCollisionMask() const;
|
||||
|
||||
bool mCanWaterWalk;
|
||||
bool mWalkingOnWater;
|
||||
std::atomic<bool> mWalkingOnWater;
|
||||
|
||||
bool mRotationallyInvariant;
|
||||
|
||||
|
@ -156,14 +161,18 @@ namespace MWPhysics
|
|||
osg::Vec3f mRenderingScale;
|
||||
osg::Vec3f mPosition;
|
||||
osg::Vec3f mPreviousPosition;
|
||||
btTransform mLocalTransform;
|
||||
bool mScaleUpdatePending;
|
||||
bool mTransformUpdatePending;
|
||||
mutable std::mutex mPositionMutex;
|
||||
|
||||
osg::Vec3f mForce;
|
||||
bool mOnGround;
|
||||
bool mOnSlope;
|
||||
bool mInternalCollisionMode;
|
||||
std::atomic<bool> mOnGround;
|
||||
std::atomic<bool> mOnSlope;
|
||||
std::atomic<bool> mInternalCollisionMode;
|
||||
bool mExternalCollisionMode;
|
||||
|
||||
btCollisionWorld* mCollisionWorld;
|
||||
PhysicsTaskScheduler* mTaskScheduler;
|
||||
|
||||
Actor(const Actor&);
|
||||
Actor& operator=(const Actor&);
|
||||
|
|
|
@ -10,18 +10,14 @@
|
|||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/movement.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/refdata.hpp"
|
||||
|
||||
#include "actor.hpp"
|
||||
#include "collisiontype.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "physicssystem.hpp"
|
||||
#include "stepper.hpp"
|
||||
#include "trace.h"
|
||||
|
||||
|
@ -78,24 +74,26 @@ namespace MWPhysics
|
|||
return tracer.mEndPos-offset + osg::Vec3f(0.f, 0.f, sGroundOffset);
|
||||
}
|
||||
|
||||
osg::Vec3f MovementSolver::move(osg::Vec3f position, const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time,
|
||||
bool isFlying, float waterlevel, float slowFall, const btCollisionWorld* collisionWorld,
|
||||
std::map<MWWorld::Ptr, MWWorld::Ptr>& standingCollisionTracker)
|
||||
void MovementSolver::move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld,
|
||||
WorldFrameData& worldData)
|
||||
{
|
||||
const ESM::Position& refpos = ptr.getRefData().getPosition();
|
||||
auto* physicActor = actor.mActorRaw;
|
||||
auto ptr = actor.mPtr;
|
||||
const ESM::Position& refpos = actor.mRefpos;
|
||||
// Early-out for totally static creatures
|
||||
// (Not sure if gravity should still apply?)
|
||||
if (!ptr.getClass().isMobile(ptr))
|
||||
return position;
|
||||
return;
|
||||
|
||||
// Reset per-frame data
|
||||
physicActor->setWalkingOnWater(false);
|
||||
// Anything to collide with?
|
||||
if(!physicActor->getCollisionMode())
|
||||
{
|
||||
return position + (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) *
|
||||
actor.mPosition += (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) *
|
||||
osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))
|
||||
) * movement * time;
|
||||
) * actor.mMovement * time;
|
||||
return;
|
||||
}
|
||||
|
||||
const btCollisionObject *colobj = physicActor->getCollisionObject();
|
||||
|
@ -105,23 +103,23 @@ namespace MWPhysics
|
|||
// That means the collision shape used for moving this actor is in a different spot than the collision shape
|
||||
// other actors are using to collide against this actor.
|
||||
// While this is strictly speaking wrong, it's needed for MW compatibility.
|
||||
position.z() += halfExtents.z();
|
||||
actor.mPosition.z() += halfExtents.z();
|
||||
|
||||
static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
|
||||
float swimlevel = waterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
|
||||
float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
|
||||
|
||||
ActorTracer tracer;
|
||||
|
||||
osg::Vec3f inertia = physicActor->getInertialForce();
|
||||
osg::Vec3f velocity;
|
||||
|
||||
if (position.z() < swimlevel || isFlying)
|
||||
if (actor.mPosition.z() < swimlevel || actor.mFlying)
|
||||
{
|
||||
velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement;
|
||||
velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
|
||||
}
|
||||
else
|
||||
{
|
||||
velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement;
|
||||
velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
|
||||
|
||||
if ((velocity.z() > 0.f && physicActor->getOnGround() && !physicActor->getOnSlope())
|
||||
|| (velocity.z() > 0.f && velocity.z() + inertia.z() <= -velocity.z() && physicActor->getOnSlope()))
|
||||
|
@ -131,38 +129,16 @@ namespace MWPhysics
|
|||
}
|
||||
|
||||
// dead actors underwater will float to the surface, if the CharacterController tells us to do so
|
||||
if (movement.z() > 0 && ptr.getClass().getCreatureStats(ptr).isDead() && position.z() < swimlevel)
|
||||
if (actor.mMovement.z() > 0 && actor.mIsDead && actor.mPosition.z() < swimlevel)
|
||||
velocity = osg::Vec3f(0,0,1) * 25;
|
||||
|
||||
if (ptr.getClass().getMovementSettings(ptr).mPosition[2])
|
||||
{
|
||||
const bool isPlayer = (ptr == MWMechanics::getPlayer());
|
||||
// Advance acrobatics and set flag for GetPCJumping
|
||||
if (isPlayer)
|
||||
{
|
||||
ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0);
|
||||
MWBase::Environment::get().getWorld()->getPlayer().setJumping(true);
|
||||
}
|
||||
|
||||
// Decrease fatigue
|
||||
if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState())
|
||||
{
|
||||
const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||
const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat();
|
||||
const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat();
|
||||
const float normalizedEncumbrance = std::min(1.f, ptr.getClass().getNormalizedEncumbrance(ptr));
|
||||
const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult;
|
||||
MWMechanics::DynamicStat<float> fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue();
|
||||
fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease);
|
||||
ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue);
|
||||
}
|
||||
ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0;
|
||||
}
|
||||
if (actor.mWantJump)
|
||||
actor.mDidJump = true;
|
||||
|
||||
// Now that we have the effective movement vector, apply wind forces to it
|
||||
if (MWBase::Environment::get().getWorld()->isInStorm())
|
||||
if (worldData.mIsInStorm)
|
||||
{
|
||||
osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection();
|
||||
osg::Vec3f stormDirection = worldData.mStormDirection;
|
||||
float angleDegrees = osg::RadiansToDegrees(std::acos(stormDirection * velocity / (stormDirection.length() * velocity.length())));
|
||||
static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fStromWalkMult")->mValue.getFloat();
|
||||
velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f));
|
||||
|
@ -170,7 +146,7 @@ namespace MWPhysics
|
|||
|
||||
Stepper stepper(collisionWorld, colobj);
|
||||
osg::Vec3f origVelocity = velocity;
|
||||
osg::Vec3f newPosition = position;
|
||||
osg::Vec3f newPosition = actor.mPosition;
|
||||
/*
|
||||
* A loop to find newPosition using tracer, if successful different from the starting position.
|
||||
* nextpos is the local variable used to find potential newPosition, using velocity and remainingTime
|
||||
|
@ -182,7 +158,7 @@ namespace MWPhysics
|
|||
osg::Vec3f nextpos = newPosition + velocity * remainingTime;
|
||||
|
||||
// If not able to fly, don't allow to swim up into the air
|
||||
if(!isFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel)
|
||||
if(!actor.mFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel)
|
||||
{
|
||||
const osg::Vec3f down(0,0,-1);
|
||||
velocity = slide(velocity, down);
|
||||
|
@ -235,7 +211,7 @@ namespace MWPhysics
|
|||
if (result)
|
||||
{
|
||||
// don't let pure water creatures move out of water after stepMove
|
||||
if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > waterlevel)
|
||||
if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel)
|
||||
newPosition = oldPosition;
|
||||
}
|
||||
else
|
||||
|
@ -245,7 +221,7 @@ namespace MWPhysics
|
|||
|
||||
// Do not allow sliding upward if there is gravity.
|
||||
// Stepping will have taken care of that.
|
||||
if(!(newPosition.z() < swimlevel || isFlying))
|
||||
if(!(newPosition.z() < swimlevel || actor.mFlying))
|
||||
newVelocity.z() = std::min(newVelocity.z(), 0.0f);
|
||||
|
||||
if ((newVelocity-velocity).length2() < 0.01)
|
||||
|
@ -269,11 +245,11 @@ namespace MWPhysics
|
|||
const btCollisionObject* standingOn = tracer.mHitObject;
|
||||
PtrHolder* ptrHolder = static_cast<PtrHolder*>(standingOn->getUserPointer());
|
||||
if (ptrHolder)
|
||||
standingCollisionTracker[ptr] = ptrHolder->getPtr();
|
||||
actor.mStandingOn = ptrHolder->getPtr();
|
||||
|
||||
if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
|
||||
physicActor->setWalkingOnWater(true);
|
||||
if (!isFlying)
|
||||
if (!actor.mFlying)
|
||||
newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
|
||||
|
||||
isOnGround = true;
|
||||
|
@ -292,7 +268,7 @@ namespace MWPhysics
|
|||
btVector3 aabbMin, aabbMax;
|
||||
tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax);
|
||||
btVector3 center = (aabbMin + aabbMax) / 2.f;
|
||||
inertia = osg::Vec3f(position.x() - center.x(), position.y() - center.y(), 0);
|
||||
inertia = osg::Vec3f(actor.mPosition.x() - center.x(), actor.mPosition.y() - center.y(), 0);
|
||||
inertia.normalize();
|
||||
inertia *= 100;
|
||||
}
|
||||
|
@ -302,16 +278,16 @@ namespace MWPhysics
|
|||
}
|
||||
}
|
||||
|
||||
if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || isFlying)
|
||||
if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying)
|
||||
physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f));
|
||||
else
|
||||
{
|
||||
inertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter;
|
||||
if (inertia.z() < 0)
|
||||
inertia.z() *= slowFall;
|
||||
if (slowFall < 1.f) {
|
||||
inertia.x() *= slowFall;
|
||||
inertia.y() *= slowFall;
|
||||
inertia.z() *= actor.mSlowFall;
|
||||
if (actor.mSlowFall < 1.f) {
|
||||
inertia.x() *= actor.mSlowFall;
|
||||
inertia.y() *= actor.mSlowFall;
|
||||
}
|
||||
physicActor->setInertialForce(inertia);
|
||||
}
|
||||
|
@ -319,6 +295,6 @@ namespace MWPhysics
|
|||
physicActor->setOnSlope(isOnSlope);
|
||||
|
||||
newPosition.z() -= halfExtents.z(); // remove what was added at the beginning
|
||||
return newPosition;
|
||||
actor.mPosition = newPosition;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,18 @@
|
|||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
class btCollisionWorld;
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
}
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
class Actor;
|
||||
struct ActorFrameData;
|
||||
struct WorldFrameData;
|
||||
|
||||
class MovementSolver
|
||||
{
|
||||
|
@ -31,9 +36,7 @@ namespace MWPhysics
|
|||
|
||||
public:
|
||||
static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight);
|
||||
static osg::Vec3f move(osg::Vec3f position, const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time,
|
||||
bool isFlying, float waterlevel, float slowFall, const btCollisionWorld* collisionWorld,
|
||||
std::map<MWWorld::Ptr, MWWorld::Ptr>& standingCollisionTracker);
|
||||
static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
607
apps/openmw/mwphysics/mtphysics.cpp
Normal file
607
apps/openmw/mwphysics/mtphysics.cpp
Normal file
|
@ -0,0 +1,607 @@
|
|||
#include <BulletCollision/CollisionShapes/btCollisionShape.h>
|
||||
#include <LinearMath/btThreads.h>
|
||||
|
||||
#include "components/debug/debuglog.hpp"
|
||||
#include <components/misc/barrier.hpp>
|
||||
#include "components/misc/convert.hpp"
|
||||
#include "components/settings/settings.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/movement.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
|
||||
#include "actor.hpp"
|
||||
#include "movementsolver.hpp"
|
||||
#include "mtphysics.hpp"
|
||||
#include "object.hpp"
|
||||
#include "physicssystem.hpp"
|
||||
|
||||
class btIParallelSumBody; // needed to compile with bullet < 2.88
|
||||
|
||||
namespace
|
||||
{
|
||||
/// @brief A scoped lock that is either shared or exclusive depending on configuration
|
||||
template<class Mutex>
|
||||
class MaybeSharedLock
|
||||
{
|
||||
public:
|
||||
/// @param mutex a shared mutex
|
||||
/// @param canBeSharedLock decide wether the lock will be shared or exclusive
|
||||
MaybeSharedLock(Mutex& mutex, bool canBeSharedLock) : mMutex(mutex), mCanBeSharedLock(canBeSharedLock)
|
||||
{
|
||||
if (mCanBeSharedLock)
|
||||
mMutex.lock_shared();
|
||||
else
|
||||
mMutex.lock();
|
||||
}
|
||||
|
||||
~MaybeSharedLock()
|
||||
{
|
||||
if (mCanBeSharedLock)
|
||||
mMutex.unlock_shared();
|
||||
else
|
||||
mMutex.unlock();
|
||||
}
|
||||
private:
|
||||
Mutex& mMutex;
|
||||
bool mCanBeSharedLock;
|
||||
};
|
||||
|
||||
void handleFall(MWPhysics::ActorFrameData& actorData, bool simulationPerformed)
|
||||
{
|
||||
const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight;
|
||||
|
||||
const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mActorRaw->getOnGround());
|
||||
|
||||
if (isStillOnGround || actorData.mFlying || actorData.mSwimming || actorData.mSlowFall < 1)
|
||||
actorData.mNeedLand = true;
|
||||
else if (heightDiff < 0)
|
||||
actorData.mFallHeight += heightDiff;
|
||||
}
|
||||
|
||||
void handleJump(const MWWorld::Ptr &ptr)
|
||||
{
|
||||
const bool isPlayer = (ptr == MWMechanics::getPlayer());
|
||||
// Advance acrobatics and set flag for GetPCJumping
|
||||
if (isPlayer)
|
||||
{
|
||||
ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0);
|
||||
MWBase::Environment::get().getWorld()->getPlayer().setJumping(true);
|
||||
}
|
||||
|
||||
// Decrease fatigue
|
||||
if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState())
|
||||
{
|
||||
const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||
const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat();
|
||||
const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat();
|
||||
const float normalizedEncumbrance = std::min(1.f, ptr.getClass().getNormalizedEncumbrance(ptr));
|
||||
const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult;
|
||||
MWMechanics::DynamicStat<float> fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue();
|
||||
fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease);
|
||||
ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue);
|
||||
}
|
||||
ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0;
|
||||
}
|
||||
|
||||
void updateStandingCollision(MWPhysics::ActorFrameData& actorData, MWPhysics::CollisionMap& standingCollisions)
|
||||
{
|
||||
if (!actorData.mStandingOn.isEmpty())
|
||||
standingCollisions[actorData.mPtr] = actorData.mStandingOn;
|
||||
else
|
||||
standingCollisions.erase(actorData.mPtr);
|
||||
}
|
||||
|
||||
void updateMechanics(MWPhysics::ActorFrameData& actorData)
|
||||
{
|
||||
if (actorData.mDidJump)
|
||||
handleJump(actorData.mPtr);
|
||||
|
||||
MWMechanics::CreatureStats& stats = actorData.mPtr.getClass().getCreatureStats(actorData.mPtr);
|
||||
if (actorData.mNeedLand)
|
||||
stats.land(actorData.mPtr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming));
|
||||
else if (actorData.mFallHeight < 0)
|
||||
stats.addToFallHeight(-actorData.mFallHeight);
|
||||
}
|
||||
|
||||
osg::Vec3f interpolateMovements(const MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
|
||||
{
|
||||
const float interpolationFactor = timeAccum / physicsDt;
|
||||
return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
|
||||
}
|
||||
|
||||
struct WorldFrameData
|
||||
{
|
||||
WorldFrameData() : mIsInStorm(MWBase::Environment::get().getWorld()->isInStorm())
|
||||
, mStormDirection(MWBase::Environment::get().getWorld()->getStormDirection())
|
||||
{}
|
||||
|
||||
bool mIsInStorm;
|
||||
osg::Vec3f mStormDirection;
|
||||
};
|
||||
|
||||
namespace Config
|
||||
{
|
||||
/* The purpose of these 2 classes is to make OpenMW works with Bullet compiled with either single or multithread support.
|
||||
At runtime, Bullet resolve the call to btParallelFor() to:
|
||||
- btITaskScheduler::parallelFor() if bullet is multithreaded
|
||||
- btIParallelForBody::forLoop() if bullet is singlethreaded.
|
||||
|
||||
NOTE: From Bullet 2.88, there is a btDefaultTaskScheduler(), that returns NULL if multithreading is not supported.
|
||||
It might be worth considering to simplify the API once OpenMW stops supporting 2.87.
|
||||
*/
|
||||
|
||||
template<class ...>
|
||||
using void_t = void;
|
||||
|
||||
/// @brief for Bullet <= 2.87
|
||||
template <class T, class = void>
|
||||
class MultiThreadedBulletImpl : public T
|
||||
{
|
||||
public:
|
||||
MultiThreadedBulletImpl(): T("") {};
|
||||
~MultiThreadedBulletImpl() override = default;
|
||||
int getMaxNumThreads() const override { return 1; };
|
||||
int getNumThreads() const override { return 1; };
|
||||
void setNumThreads(int numThreads) override {};
|
||||
|
||||
/// @brief will be called by Bullet if threading is supported
|
||||
void parallelFor(int iBegin, int iEnd, int batchsize, const btIParallelForBody& body) override {};
|
||||
};
|
||||
|
||||
/// @brief for Bullet >= 2.88
|
||||
template <class T>
|
||||
class MultiThreadedBulletImpl<T, void_t<decltype(&T::parallelSum)>> : public T
|
||||
{
|
||||
public:
|
||||
MultiThreadedBulletImpl(): T("") {};
|
||||
~MultiThreadedBulletImpl() override = default;
|
||||
int getMaxNumThreads() const override { return 1; };
|
||||
int getNumThreads() const override { return 1; };
|
||||
void setNumThreads(int numThreads) override {};
|
||||
|
||||
/// @brief will be called by Bullet if threading is supported
|
||||
void parallelFor(int iBegin, int iEnd, int batchsize, const btIParallelForBody& body) override {};
|
||||
|
||||
btScalar parallelSum(int iBegin, int iEnd, int grainSize, const btIParallelSumBody& body) override { return {}; };
|
||||
};
|
||||
|
||||
using MultiThreadedBullet = MultiThreadedBulletImpl<btITaskScheduler>;
|
||||
|
||||
class SingleThreadedBullet : public btIParallelForBody
|
||||
{
|
||||
public:
|
||||
explicit SingleThreadedBullet(bool &threadingSupported): mThreadingSupported(threadingSupported) {};
|
||||
/// @brief will be called by Bullet if threading is NOT supported
|
||||
void forLoop(int iBegin, int iEnd) const override
|
||||
{
|
||||
mThreadingSupported = false;
|
||||
}
|
||||
private:
|
||||
bool &mThreadingSupported;
|
||||
};
|
||||
|
||||
/// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading
|
||||
int computeNumThreads(bool& threadSafeBullet)
|
||||
{
|
||||
int wantedThread = Settings::Manager::getInt("async num threads", "Physics");
|
||||
|
||||
auto bulletScheduler = std::make_unique<MultiThreadedBullet>();
|
||||
btSetTaskScheduler(bulletScheduler.get());
|
||||
bool threadingSupported = true;
|
||||
btParallelFor(0, 0, 0, SingleThreadedBullet(threadingSupported));
|
||||
|
||||
threadSafeBullet = threadingSupported;
|
||||
if (!threadingSupported && wantedThread > 1)
|
||||
{
|
||||
Log(Debug::Warning) << "Bullet was not compiled with multithreading support, 1 async thread will be used";
|
||||
return 1;
|
||||
}
|
||||
return std::max(0, wantedThread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld)
|
||||
: mPhysicsDt(physicsDt)
|
||||
, mCollisionWorld(std::move(collisionWorld))
|
||||
, mNumJobs(0)
|
||||
, mRemainingSteps(0)
|
||||
, mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics"))
|
||||
, mDeferAabbUpdate(Settings::Manager::getBool("defer aabb update", "Physics"))
|
||||
, mNewFrame(false)
|
||||
, mAdvanceSimulation(false)
|
||||
, mQuit(false)
|
||||
, mNextJob(0)
|
||||
, mNextLOS(0)
|
||||
{
|
||||
mNumThreads = Config::computeNumThreads(mThreadSafeBullet);
|
||||
|
||||
if (mNumThreads >= 1)
|
||||
{
|
||||
for (int i = 0; i < mNumThreads; ++i)
|
||||
mThreads.emplace_back([&] { worker(); } );
|
||||
}
|
||||
else
|
||||
{
|
||||
mLOSCacheExpiry = -1;
|
||||
mDeferAabbUpdate = false;
|
||||
}
|
||||
|
||||
mPreStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
|
||||
{
|
||||
updateAabbs();
|
||||
});
|
||||
|
||||
mPostStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
|
||||
{
|
||||
if (mRemainingSteps)
|
||||
--mRemainingSteps;
|
||||
mNextJob.store(0, std::memory_order_release);
|
||||
updateActorsPositions();
|
||||
});
|
||||
|
||||
mPostSimBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
|
||||
{
|
||||
udpateActorsAabbs();
|
||||
mNewFrame = false;
|
||||
if (mLOSCacheExpiry >= 0)
|
||||
{
|
||||
std::unique_lock<std::shared_timed_mutex> lock(mLOSCacheMutex);
|
||||
mLOSCache.erase(
|
||||
std::remove_if(mLOSCache.begin(), mLOSCache.end(),
|
||||
[](const LOSRequest& req) { return req.mStale; }),
|
||||
mLOSCache.end());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
PhysicsTaskScheduler::~PhysicsTaskScheduler()
|
||||
{
|
||||
std::unique_lock<std::shared_timed_mutex> lock(mSimulationMutex);
|
||||
mQuit = true;
|
||||
mNumJobs = 0;
|
||||
mRemainingSteps = 0;
|
||||
lock.unlock();
|
||||
mHasJob.notify_all();
|
||||
for (auto& thread : mThreads)
|
||||
thread.join();
|
||||
}
|
||||
|
||||
const PtrPositionList& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, CollisionMap& standingCollisions, bool skipSimulation)
|
||||
{
|
||||
// This function run in the main thread.
|
||||
// While the mSimulationMutex is held, background physics threads can't run.
|
||||
|
||||
std::unique_lock<std::shared_timed_mutex> lock(mSimulationMutex);
|
||||
|
||||
// start by finishing previous background computation
|
||||
if (mNumThreads != 0)
|
||||
{
|
||||
if (mAdvanceSimulation)
|
||||
standingCollisions.clear();
|
||||
|
||||
for (auto& data : mActorsFrameData)
|
||||
{
|
||||
// Ignore actors that were deleted while the background thread was running
|
||||
if (!data.mActor.lock())
|
||||
continue;
|
||||
|
||||
updateMechanics(data);
|
||||
if (mAdvanceSimulation)
|
||||
updateStandingCollision(data, standingCollisions);
|
||||
}
|
||||
}
|
||||
|
||||
// init
|
||||
mRemainingSteps = numSteps;
|
||||
mTimeAccum = timeAccum;
|
||||
mActorsFrameData = std::move(actorsData);
|
||||
mAdvanceSimulation = (mRemainingSteps != 0);
|
||||
mNewFrame = true;
|
||||
mNumJobs = mActorsFrameData.size();
|
||||
mNextLOS.store(0, std::memory_order_relaxed);
|
||||
mNextJob.store(0, std::memory_order_release);
|
||||
|
||||
if (mAdvanceSimulation)
|
||||
mWorldFrameData = std::make_unique<WorldFrameData>();
|
||||
|
||||
// update each actor position based on latest data
|
||||
for (auto& data : mActorsFrameData)
|
||||
data.updatePosition();
|
||||
|
||||
// we are asked to skip the simulation (load a savegame for instance)
|
||||
// just return the actors' reference position without applying the movements
|
||||
if (skipSimulation)
|
||||
{
|
||||
standingCollisions.clear();
|
||||
mMovementResults.clear();
|
||||
for (const auto& m : mActorsFrameData)
|
||||
mMovementResults[m.mPtr] = m.mPosition;
|
||||
return mMovementResults;
|
||||
}
|
||||
|
||||
if (mNumThreads == 0)
|
||||
{
|
||||
mMovementResults.clear();
|
||||
syncComputation();
|
||||
|
||||
if (mAdvanceSimulation)
|
||||
{
|
||||
standingCollisions.clear();
|
||||
for (auto& data : mActorsFrameData)
|
||||
updateStandingCollision(data, standingCollisions);
|
||||
}
|
||||
return mMovementResults;
|
||||
}
|
||||
|
||||
// Remove actors that were deleted while the background thread was running
|
||||
for (auto& data : mActorsFrameData)
|
||||
{
|
||||
if (!data.mActor.lock())
|
||||
mMovementResults.erase(data.mPtr);
|
||||
}
|
||||
std::swap(mMovementResults, mPreviousMovementResults);
|
||||
|
||||
// mMovementResults is shared between all workers instance
|
||||
// pre-allocate all nodes so that we don't need synchronization
|
||||
mMovementResults.clear();
|
||||
for (const auto& m : mActorsFrameData)
|
||||
mMovementResults[m.mPtr] = m.mPosition;
|
||||
|
||||
lock.unlock();
|
||||
mHasJob.notify_all();
|
||||
return mPreviousMovementResults;
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const
|
||||
{
|
||||
MaybeSharedLock<std::shared_timed_mutex> lock(mCollisionWorldMutex, mThreadSafeBullet);
|
||||
mCollisionWorld->rayTest(rayFromWorld, rayToWorld, resultCallback);
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const
|
||||
{
|
||||
MaybeSharedLock<std::shared_timed_mutex> lock(mCollisionWorldMutex, mThreadSafeBullet);
|
||||
mCollisionWorld->convexSweepTest(castShape, from, to, resultCallback);
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
|
||||
{
|
||||
std::shared_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
|
||||
mCollisionWorld->contactTest(colObj, resultCallback);
|
||||
}
|
||||
|
||||
boost::optional<btVector3> PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target)
|
||||
{
|
||||
MaybeSharedLock<std::shared_timed_mutex> lock(mCollisionWorldMutex, mThreadSafeBullet);
|
||||
// target the collision object's world origin, this should be the center of the collision object
|
||||
btTransform rayTo;
|
||||
rayTo.setIdentity();
|
||||
rayTo.setOrigin(target->getWorldTransform().getOrigin());
|
||||
|
||||
btCollisionWorld::ClosestRayResultCallback cb(from.getOrigin(), rayTo.getOrigin());
|
||||
|
||||
mCollisionWorld->rayTestSingle(from, rayTo, target, target->getCollisionShape(), target->getWorldTransform(), cb);
|
||||
if (!cb.hasHit())
|
||||
// didn't hit the target. this could happen if point is already inside the collision box
|
||||
return boost::none;
|
||||
return {cb.m_hitPointWorld};
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::aabbTest(const btVector3& aabbMin, const btVector3& aabbMax, btBroadphaseAabbCallback& callback)
|
||||
{
|
||||
std::shared_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
|
||||
mCollisionWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, callback);
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::getAabb(const btCollisionObject* obj, btVector3& min, btVector3& max)
|
||||
{
|
||||
std::shared_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
|
||||
obj->getCollisionShape()->getAabb(obj->getWorldTransform(), min, max);
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask)
|
||||
{
|
||||
std::unique_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
|
||||
collisionObject->getBroadphaseHandle()->m_collisionFilterMask = collisionFilterMask;
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask)
|
||||
{
|
||||
std::unique_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
|
||||
mCollisionWorld->addCollisionObject(collisionObject, collisionFilterGroup, collisionFilterMask);
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::removeCollisionObject(btCollisionObject* collisionObject)
|
||||
{
|
||||
std::unique_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
|
||||
mCollisionWorld->removeCollisionObject(collisionObject);
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr<PtrHolder> ptr)
|
||||
{
|
||||
if (mDeferAabbUpdate)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mUpdateAabbMutex);
|
||||
mUpdateAabb.insert(std::move(ptr));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unique_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
|
||||
updatePtrAabb(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2)
|
||||
{
|
||||
std::unique_lock<std::shared_timed_mutex> lock(mLOSCacheMutex);
|
||||
|
||||
auto actorPtr1 = actor1.lock();
|
||||
auto actorPtr2 = actor2.lock();
|
||||
if (!actorPtr1 || !actorPtr2)
|
||||
return false;
|
||||
|
||||
auto req = LOSRequest(actor1, actor2);
|
||||
auto result = std::find(mLOSCache.begin(), mLOSCache.end(), req);
|
||||
if (result == mLOSCache.end())
|
||||
{
|
||||
req.mResult = hasLineOfSight(actorPtr1.get(), actorPtr2.get());
|
||||
if (mLOSCacheExpiry >= 0)
|
||||
mLOSCache.push_back(req);
|
||||
return req.mResult;
|
||||
}
|
||||
result->mAge = 0;
|
||||
return result->mResult;
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::refreshLOSCache()
|
||||
{
|
||||
std::shared_lock<std::shared_timed_mutex> lock(mLOSCacheMutex);
|
||||
int job = 0;
|
||||
int numLOS = mLOSCache.size();
|
||||
while ((job = mNextLOS.fetch_add(1, std::memory_order_relaxed)) < numLOS)
|
||||
{
|
||||
auto& req = mLOSCache[job];
|
||||
auto actorPtr1 = req.mActors[0].lock();
|
||||
auto actorPtr2 = req.mActors[1].lock();
|
||||
|
||||
if (req.mAge++ > mLOSCacheExpiry || !actorPtr1 || !actorPtr2)
|
||||
req.mStale = true;
|
||||
else
|
||||
req.mResult = hasLineOfSight(actorPtr1.get(), actorPtr2.get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::updateAabbs()
|
||||
{
|
||||
std::unique_lock<std::shared_timed_mutex> lock1(mCollisionWorldMutex, std::defer_lock);
|
||||
std::unique_lock<std::mutex> lock2(mUpdateAabbMutex, std::defer_lock);
|
||||
std::lock(lock1, lock2);
|
||||
std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(),
|
||||
[this](const std::weak_ptr<PtrHolder>& ptr) { updatePtrAabb(ptr); });
|
||||
mUpdateAabb.clear();
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr)
|
||||
{
|
||||
if (const auto p = ptr.lock())
|
||||
{
|
||||
if (const auto actor = std::dynamic_pointer_cast<Actor>(p))
|
||||
{
|
||||
actor->commitPositionChange();
|
||||
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
|
||||
}
|
||||
else if (const auto object = std::dynamic_pointer_cast<Object>(p))
|
||||
{
|
||||
object->commitPositionChange();
|
||||
mCollisionWorld->updateSingleAabb(object->getCollisionObject());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::worker()
|
||||
{
|
||||
std::shared_lock<std::shared_timed_mutex> lock(mSimulationMutex);
|
||||
while (!mQuit)
|
||||
{
|
||||
if (!mNewFrame)
|
||||
mHasJob.wait(lock, [&]() { return mQuit || mNewFrame; });
|
||||
|
||||
if (mDeferAabbUpdate)
|
||||
mPreStepBarrier->wait();
|
||||
|
||||
int job = 0;
|
||||
while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
|
||||
{
|
||||
MaybeSharedLock<std::shared_timed_mutex> lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
|
||||
if(const auto actor = mActorsFrameData[job].mActor.lock())
|
||||
{
|
||||
if (mRemainingSteps)
|
||||
MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
|
||||
else
|
||||
{
|
||||
auto& actorData = mActorsFrameData[job];
|
||||
handleFall(actorData, mAdvanceSimulation);
|
||||
mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mPostStepBarrier->wait();
|
||||
|
||||
if (!mRemainingSteps)
|
||||
{
|
||||
if (mLOSCacheExpiry >= 0)
|
||||
refreshLOSCache();
|
||||
mPostSimBarrier->wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::updateActorsPositions()
|
||||
{
|
||||
std::unique_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
|
||||
for (auto& actorData : mActorsFrameData)
|
||||
{
|
||||
if(const auto actor = actorData.mActor.lock())
|
||||
{
|
||||
if (actorData.mPosition == actor->getPosition())
|
||||
actor->setPosition(actorData.mPosition, false); // update previous position to make sure interpolation is correct
|
||||
else
|
||||
{
|
||||
actorData.mPositionChanged = true;
|
||||
actor->setPosition(actorData.mPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::udpateActorsAabbs()
|
||||
{
|
||||
std::unique_lock<std::shared_timed_mutex> lock(mCollisionWorldMutex);
|
||||
for (const auto& actorData : mActorsFrameData)
|
||||
if (actorData.mPositionChanged)
|
||||
{
|
||||
if(const auto actor = actorData.mActor.lock())
|
||||
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
|
||||
}
|
||||
}
|
||||
|
||||
bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2)
|
||||
{
|
||||
btVector3 pos1 = Misc::Convert::toBullet(actor1->getCollisionObjectPosition() + osg::Vec3f(0,0,actor1->getHalfExtents().z() * 0.9)); // eye level
|
||||
btVector3 pos2 = Misc::Convert::toBullet(actor2->getCollisionObjectPosition() + osg::Vec3f(0,0,actor2->getHalfExtents().z() * 0.9));
|
||||
|
||||
btCollisionWorld::ClosestRayResultCallback resultCallback(pos1, pos2);
|
||||
resultCallback.m_collisionFilterGroup = 0xFF;
|
||||
resultCallback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door;
|
||||
|
||||
MaybeSharedLock<std::shared_timed_mutex> lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
|
||||
mCollisionWorld->rayTest(pos1, pos2, resultCallback);
|
||||
|
||||
return !resultCallback.hasHit();
|
||||
}
|
||||
|
||||
void PhysicsTaskScheduler::syncComputation()
|
||||
{
|
||||
while (mRemainingSteps--)
|
||||
{
|
||||
for (auto& actorData : mActorsFrameData)
|
||||
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
|
||||
|
||||
updateActorsPositions();
|
||||
}
|
||||
|
||||
for (auto& actorData : mActorsFrameData)
|
||||
{
|
||||
handleFall(actorData, mAdvanceSimulation);
|
||||
mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt);
|
||||
updateMechanics(actorData);
|
||||
}
|
||||
udpateActorsAabbs();
|
||||
}
|
||||
}
|
94
apps/openmw/mwphysics/mtphysics.hpp
Normal file
94
apps/openmw/mwphysics/mtphysics.hpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
#ifndef OPENMW_MWPHYSICS_MTPHYSICS_H
|
||||
#define OPENMW_MWPHYSICS_MTPHYSICS_H
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
#include <shared_mutex>
|
||||
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||
|
||||
#include "physicssystem.hpp"
|
||||
#include "ptrholder.hpp"
|
||||
|
||||
namespace Misc
|
||||
{
|
||||
class Barrier;
|
||||
}
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
class PhysicsTaskScheduler
|
||||
{
|
||||
public:
|
||||
PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld);
|
||||
~PhysicsTaskScheduler();
|
||||
|
||||
/// @brief move actors taking into account desired movements and collisions
|
||||
/// @param numSteps how much simulation step to run
|
||||
/// @param timeAccum accumulated time from previous run to interpolate movements
|
||||
/// @param actorsData per actor data needed to compute new positions
|
||||
/// @return new position of each actor
|
||||
const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, CollisionMap& standingCollisions, bool skip);
|
||||
|
||||
// Thread safe wrappers
|
||||
void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const;
|
||||
void convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const;
|
||||
void contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback);
|
||||
boost::optional<btVector3> getHitPoint(const btTransform& from, btCollisionObject* target);
|
||||
void aabbTest(const btVector3& aabbMin, const btVector3& aabbMax, btBroadphaseAabbCallback& callback);
|
||||
void getAabb(const btCollisionObject* obj, btVector3& min, btVector3& max);
|
||||
void setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask);
|
||||
void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask);
|
||||
void removeCollisionObject(btCollisionObject* collisionObject);
|
||||
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr);
|
||||
bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2);
|
||||
|
||||
private:
|
||||
void syncComputation();
|
||||
void worker();
|
||||
void updateActorsPositions();
|
||||
void udpateActorsAabbs();
|
||||
bool hasLineOfSight(const Actor* actor1, const Actor* actor2);
|
||||
void refreshLOSCache();
|
||||
void updateAabbs();
|
||||
void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr);
|
||||
|
||||
std::unique_ptr<WorldFrameData> mWorldFrameData;
|
||||
std::vector<ActorFrameData> mActorsFrameData;
|
||||
PtrPositionList mMovementResults;
|
||||
PtrPositionList mPreviousMovementResults;
|
||||
const float mPhysicsDt;
|
||||
float mTimeAccum;
|
||||
std::shared_ptr<btCollisionWorld> mCollisionWorld;
|
||||
std::vector<LOSRequest> mLOSCache;
|
||||
std::set<std::weak_ptr<PtrHolder>, std::owner_less<std::weak_ptr<PtrHolder>>> mUpdateAabb;
|
||||
|
||||
// TODO: use std::experimental::flex_barrier or std::barrier once it becomes a thing
|
||||
std::unique_ptr<Misc::Barrier> mPreStepBarrier;
|
||||
std::unique_ptr<Misc::Barrier> mPostStepBarrier;
|
||||
std::unique_ptr<Misc::Barrier> mPostSimBarrier;
|
||||
|
||||
int mNumThreads;
|
||||
int mNumJobs;
|
||||
int mRemainingSteps;
|
||||
int mLOSCacheExpiry;
|
||||
bool mDeferAabbUpdate;
|
||||
bool mNewFrame;
|
||||
bool mAdvanceSimulation;
|
||||
bool mThreadSafeBullet;
|
||||
bool mQuit;
|
||||
std::atomic<int> mNextJob;
|
||||
std::atomic<int> mNextLOS;
|
||||
std::vector<std::thread> mThreads;
|
||||
|
||||
mutable std::shared_timed_mutex mSimulationMutex;
|
||||
mutable std::shared_timed_mutex mCollisionWorldMutex;
|
||||
mutable std::shared_timed_mutex mLOSCacheMutex;
|
||||
mutable std::mutex mUpdateAabbMutex;
|
||||
std::condition_variable_any mHasJob;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
|
@ -1,4 +1,5 @@
|
|||
#include "object.hpp"
|
||||
#include "mtphysics.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/nifosg/particle.hpp>
|
||||
|
@ -8,15 +9,15 @@
|
|||
|
||||
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||
|
||||
#include <LinearMath/btTransform.h>
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance)
|
||||
Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance, PhysicsTaskScheduler* scheduler)
|
||||
: mShapeInstance(shapeInstance)
|
||||
, mSolid(true)
|
||||
, mTaskScheduler(scheduler)
|
||||
{
|
||||
mPtr = ptr;
|
||||
|
||||
|
@ -29,6 +30,13 @@ namespace MWPhysics
|
|||
setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
|
||||
const float* pos = ptr.getRefData().getPosition().pos;
|
||||
setOrigin(btVector3(pos[0], pos[1], pos[2]));
|
||||
commitPositionChange();
|
||||
}
|
||||
|
||||
Object::~Object()
|
||||
{
|
||||
if (mCollisionObject)
|
||||
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
||||
}
|
||||
|
||||
const Resource::BulletShapeInstance* Object::getShapeInstance() const
|
||||
|
@ -38,17 +46,38 @@ namespace MWPhysics
|
|||
|
||||
void Object::setScale(float scale)
|
||||
{
|
||||
mShapeInstance->setLocalScaling(btVector3(scale, scale, scale));
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
mScale = { scale,scale,scale };
|
||||
mScaleUpdatePending = true;
|
||||
}
|
||||
|
||||
void Object::setRotation(const btQuaternion& quat)
|
||||
{
|
||||
mCollisionObject->getWorldTransform().setRotation(quat);
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
mLocalTransform.setRotation(quat);
|
||||
mTransformUpdatePending = true;
|
||||
}
|
||||
|
||||
void Object::setOrigin(const btVector3& vec)
|
||||
{
|
||||
mCollisionObject->getWorldTransform().setOrigin(vec);
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
mLocalTransform.setOrigin(vec);
|
||||
mTransformUpdatePending = true;
|
||||
}
|
||||
|
||||
void Object::commitPositionChange()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
if (mScaleUpdatePending)
|
||||
{
|
||||
mShapeInstance->setLocalScaling(mScale);
|
||||
mScaleUpdatePending = false;
|
||||
}
|
||||
if (mTransformUpdatePending)
|
||||
{
|
||||
mCollisionObject->setWorldTransform(mLocalTransform);
|
||||
mTransformUpdatePending = false;
|
||||
}
|
||||
}
|
||||
|
||||
btCollisionObject* Object::getCollisionObject()
|
||||
|
@ -61,6 +90,12 @@ namespace MWPhysics
|
|||
return mCollisionObject.get();
|
||||
}
|
||||
|
||||
btTransform Object::getTransform() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||
return mLocalTransform;
|
||||
}
|
||||
|
||||
bool Object::isSolid() const
|
||||
{
|
||||
return mSolid;
|
||||
|
@ -76,10 +111,10 @@ namespace MWPhysics
|
|||
return !mShapeInstance->mAnimatedShapes.empty();
|
||||
}
|
||||
|
||||
void Object::animateCollisionShapes(btCollisionWorld* collisionWorld)
|
||||
bool Object::animateCollisionShapes()
|
||||
{
|
||||
if (mShapeInstance->mAnimatedShapes.empty())
|
||||
return;
|
||||
return false;
|
||||
|
||||
assert (mShapeInstance->getCollisionShape()->isCompound());
|
||||
|
||||
|
@ -100,7 +135,7 @@ namespace MWPhysics
|
|||
|
||||
// Remove nonexistent nodes from animated shapes map and early out
|
||||
mShapeInstance->mAnimatedShapes.erase(recIndex);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
osg::NodePath nodePath = visitor.mFoundPath;
|
||||
nodePath.erase(nodePath.begin());
|
||||
|
@ -122,7 +157,6 @@ namespace MWPhysics
|
|||
if (!(transform == compound->getChildTransform(shapeIndex)))
|
||||
compound->updateChildTransform(shapeIndex, transform);
|
||||
}
|
||||
|
||||
collisionWorld->updateSingleAabb(mCollisionObject.get());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
|
||||
#include "ptrholder.hpp"
|
||||
|
||||
#include <LinearMath/btTransform.h>
|
||||
#include <osg/Node>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
|
@ -14,34 +16,46 @@ namespace Resource
|
|||
}
|
||||
|
||||
class btCollisionObject;
|
||||
class btCollisionWorld;
|
||||
class btQuaternion;
|
||||
class btVector3;
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
class Object : public PtrHolder
|
||||
class PhysicsTaskScheduler;
|
||||
|
||||
class Object final : public PtrHolder
|
||||
{
|
||||
public:
|
||||
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance);
|
||||
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance, PhysicsTaskScheduler* scheduler);
|
||||
~Object() override;
|
||||
|
||||
const Resource::BulletShapeInstance* getShapeInstance() const;
|
||||
void setScale(float scale);
|
||||
void setRotation(const btQuaternion& quat);
|
||||
void setOrigin(const btVector3& vec);
|
||||
void commitPositionChange();
|
||||
btCollisionObject* getCollisionObject();
|
||||
const btCollisionObject* getCollisionObject() const;
|
||||
btTransform getTransform() 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);
|
||||
/// @brief update object shape
|
||||
/// @return true if shape changed
|
||||
bool animateCollisionShapes();
|
||||
|
||||
private:
|
||||
std::unique_ptr<btCollisionObject> mCollisionObject;
|
||||
osg::ref_ptr<Resource::BulletShapeInstance> mShapeInstance;
|
||||
std::map<int, osg::NodePath> mRecIndexToNodePath;
|
||||
bool mSolid;
|
||||
btVector3 mScale;
|
||||
btTransform mLocalTransform;
|
||||
bool mScaleUpdatePending;
|
||||
bool mTransformUpdatePending;
|
||||
mutable std::mutex mPositionMutex;
|
||||
PhysicsTaskScheduler* mTaskScheduler;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/movement.hpp"
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
@ -50,6 +51,7 @@
|
|||
#include "contacttestresultcallback.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "movementsolver.hpp"
|
||||
#include "mtphysics.hpp"
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
|
@ -65,11 +67,11 @@ namespace MWPhysics
|
|||
{
|
||||
mResourceSystem->addResourceManager(mShapeManager.get());
|
||||
|
||||
mCollisionConfiguration = new btDefaultCollisionConfiguration();
|
||||
mDispatcher = new btCollisionDispatcher(mCollisionConfiguration);
|
||||
mBroadphase = new btDbvtBroadphase();
|
||||
mCollisionConfiguration = std::make_unique<btDefaultCollisionConfiguration>();
|
||||
mDispatcher = std::make_unique<btCollisionDispatcher>(mCollisionConfiguration.get());
|
||||
mBroadphase = std::make_unique<btDbvtBroadphase>();
|
||||
|
||||
mCollisionWorld = new btCollisionWorld(mDispatcher, mBroadphase, mCollisionConfiguration);
|
||||
mCollisionWorld = std::make_shared<btCollisionWorld>(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get());
|
||||
|
||||
// Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this.
|
||||
// Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb.
|
||||
|
@ -86,36 +88,26 @@ namespace MWPhysics
|
|||
Log(Debug::Warning) << "Warning: using custom physics framerate (" << physFramerate << " FPS).";
|
||||
}
|
||||
}
|
||||
|
||||
mTaskScheduler = std::make_unique<PhysicsTaskScheduler>(mPhysicsDt, mCollisionWorld);
|
||||
}
|
||||
|
||||
PhysicsSystem::~PhysicsSystem()
|
||||
{
|
||||
mResourceSystem->removeResourceManager(mShapeManager.get());
|
||||
|
||||
if (mWaterCollisionObject.get())
|
||||
mCollisionWorld->removeCollisionObject(mWaterCollisionObject.get());
|
||||
if (mWaterCollisionObject)
|
||||
mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get());
|
||||
|
||||
for (auto& heightField : mHeightFields)
|
||||
{
|
||||
mCollisionWorld->removeCollisionObject(heightField.second->getCollisionObject());
|
||||
mTaskScheduler->removeCollisionObject(heightField.second->getCollisionObject());
|
||||
delete heightField.second;
|
||||
}
|
||||
|
||||
for (auto& object : mObjects)
|
||||
{
|
||||
mCollisionWorld->removeCollisionObject(object.second->getCollisionObject());
|
||||
delete object.second;
|
||||
}
|
||||
mObjects.clear();
|
||||
mActors.clear();
|
||||
|
||||
for (auto& actor : mActors)
|
||||
{
|
||||
delete actor.second;
|
||||
}
|
||||
|
||||
delete mCollisionWorld;
|
||||
delete mCollisionConfiguration;
|
||||
delete mDispatcher;
|
||||
delete mBroadphase;
|
||||
}
|
||||
|
||||
void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue)
|
||||
|
@ -132,13 +124,13 @@ namespace MWPhysics
|
|||
{
|
||||
mDebugDrawEnabled = !mDebugDrawEnabled;
|
||||
|
||||
if (mDebugDrawEnabled && !mDebugDrawer.get())
|
||||
if (mDebugDrawEnabled && !mDebugDrawer)
|
||||
{
|
||||
mDebugDrawer.reset(new MWRender::DebugDrawer(mParentNode, mCollisionWorld));
|
||||
mDebugDrawer.reset(new MWRender::DebugDrawer(mParentNode, mCollisionWorld.get()));
|
||||
mCollisionWorld->setDebugDrawer(mDebugDrawer.get());
|
||||
mDebugDrawer->setDebugMode(mDebugDrawEnabled);
|
||||
}
|
||||
else if (mDebugDrawer.get())
|
||||
else if (mDebugDrawer)
|
||||
mDebugDrawer->setDebugMode(mDebugDrawEnabled);
|
||||
return mDebugDrawEnabled;
|
||||
}
|
||||
|
@ -175,7 +167,7 @@ namespace MWPhysics
|
|||
std::pair<MWWorld::Ptr, osg::Vec3f> PhysicsSystem::getHitContact(const MWWorld::ConstPtr& actor,
|
||||
const osg::Vec3f &origin,
|
||||
const osg::Quat &orient,
|
||||
float queryDistance, std::vector<MWWorld::Ptr> targets)
|
||||
float queryDistance, std::vector<MWWorld::Ptr>& targets)
|
||||
{
|
||||
// First of all, try to hit where you aim to
|
||||
int hitmask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor;
|
||||
|
@ -221,7 +213,7 @@ namespace MWPhysics
|
|||
DeepestNotMeContactTestResultCallback resultCallback(me, targetCollisionObjects, Misc::Convert::toBullet(origin));
|
||||
resultCallback.m_collisionFilterGroup = CollisionType_Actor;
|
||||
resultCallback.m_collisionFilterMask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor;
|
||||
mCollisionWorld->contactTest(&object, resultCallback);
|
||||
mTaskScheduler->contactTest(&object, resultCallback);
|
||||
|
||||
if (resultCallback.mObject)
|
||||
{
|
||||
|
@ -245,22 +237,13 @@ namespace MWPhysics
|
|||
rayFrom.setIdentity();
|
||||
rayFrom.setOrigin(Misc::Convert::toBullet(point));
|
||||
|
||||
// target the collision object's world origin, this should be the center of the collision object
|
||||
btTransform rayTo;
|
||||
rayTo.setIdentity();
|
||||
rayTo.setOrigin(targetCollisionObj->getWorldTransform().getOrigin());
|
||||
auto hitpoint = mTaskScheduler->getHitPoint(rayFrom, targetCollisionObj);
|
||||
if (hitpoint)
|
||||
return (point - Misc::Convert::toOsg(hitpoint.get())).length();
|
||||
|
||||
btCollisionWorld::ClosestRayResultCallback cb(rayFrom.getOrigin(), rayTo.getOrigin());
|
||||
|
||||
btCollisionWorld::rayTestSingle(rayFrom, rayTo, targetCollisionObj, targetCollisionObj->getCollisionShape(), targetCollisionObj->getWorldTransform(), cb);
|
||||
if (!cb.hasHit())
|
||||
{
|
||||
// didn't hit the target. this could happen if point is already inside the collision box
|
||||
return 0.f;
|
||||
}
|
||||
else
|
||||
return (point - Misc::Convert::toOsg(cb.m_hitPointWorld)).length();
|
||||
}
|
||||
|
||||
RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr> targets, int mask, int group) const
|
||||
{
|
||||
|
@ -303,7 +286,7 @@ namespace MWPhysics
|
|||
resultCallback.m_collisionFilterGroup = group;
|
||||
resultCallback.m_collisionFilterMask = mask;
|
||||
|
||||
mCollisionWorld->rayTest(btFrom, btTo, resultCallback);
|
||||
mTaskScheduler->rayTest(btFrom, btTo, resultCallback);
|
||||
|
||||
RayCastingResult result;
|
||||
result.mHit = resultCallback.hasHit();
|
||||
|
@ -329,7 +312,7 @@ namespace MWPhysics
|
|||
btTransform from_ (btrot, Misc::Convert::toBullet(from));
|
||||
btTransform to_ (btrot, Misc::Convert::toBullet(to));
|
||||
|
||||
mCollisionWorld->convexSweepTest(&shape, from_, to_, callback);
|
||||
mTaskScheduler->convexSweepTest(&shape, from_, to_, callback);
|
||||
|
||||
RayCastingResult result;
|
||||
result.mHit = callback.hasHit();
|
||||
|
@ -343,18 +326,15 @@ namespace MWPhysics
|
|||
|
||||
bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const
|
||||
{
|
||||
const Actor* physactor1 = getActor(actor1);
|
||||
const Actor* physactor2 = getActor(actor2);
|
||||
const auto getWeakPtr = [&](const MWWorld::ConstPtr &ptr) -> std::weak_ptr<Actor>
|
||||
{
|
||||
const auto found = mActors.find(ptr);
|
||||
if (found != mActors.end())
|
||||
return { found->second };
|
||||
return {};
|
||||
};
|
||||
|
||||
if (!physactor1 || !physactor2)
|
||||
return false;
|
||||
|
||||
osg::Vec3f pos1 (physactor1->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor1->getHalfExtents().z() * 0.9)); // eye level
|
||||
osg::Vec3f pos2 (physactor2->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor2->getHalfExtents().z() * 0.9));
|
||||
|
||||
RayCastingResult result = castRay(pos1, pos2, MWWorld::ConstPtr(), std::vector<MWWorld::Ptr>(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door);
|
||||
|
||||
return !result.mHit;
|
||||
return mTaskScheduler->getLineOfSight(getWeakPtr(actor1), getWeakPtr(actor2));
|
||||
}
|
||||
|
||||
bool PhysicsSystem::isOnGround(const MWWorld::Ptr &actor)
|
||||
|
@ -373,7 +353,7 @@ namespace MWPhysics
|
|||
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
|
||||
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
|
||||
ActorTracer tracer;
|
||||
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, mCollisionWorld);
|
||||
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, mCollisionWorld.get());
|
||||
return (tracer.mFraction >= 1.0f);
|
||||
}
|
||||
|
||||
|
@ -408,7 +388,7 @@ namespace MWPhysics
|
|||
const Object * physobject = getObject(object);
|
||||
if (!physobject) return osg::BoundingBox();
|
||||
btVector3 min, max;
|
||||
physobject->getCollisionObject()->getCollisionShape()->getAabb(physobject->getCollisionObject()->getWorldTransform(), min, max);
|
||||
mTaskScheduler->getAabb(physobject->getCollisionObject(), min, max);
|
||||
return osg::BoundingBox(Misc::Convert::toOsg(min), Misc::Convert::toOsg(max));
|
||||
}
|
||||
|
||||
|
@ -434,7 +414,7 @@ namespace MWPhysics
|
|||
ContactTestResultCallback resultCallback (me);
|
||||
resultCallback.m_collisionFilterGroup = collisionGroup;
|
||||
resultCallback.m_collisionFilterMask = collisionMask;
|
||||
mCollisionWorld->contactTest(me, resultCallback);
|
||||
mTaskScheduler->contactTest(me, resultCallback);
|
||||
return resultCallback.mResult;
|
||||
}
|
||||
|
||||
|
@ -444,7 +424,7 @@ namespace MWPhysics
|
|||
if (found == mActors.end())
|
||||
return ptr.getRefData().getPosition().asVec3();
|
||||
else
|
||||
return MovementSolver::traceDown(ptr, position, found->second, mCollisionWorld, maxHeight);
|
||||
return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight);
|
||||
}
|
||||
|
||||
void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject)
|
||||
|
@ -452,7 +432,7 @@ namespace MWPhysics
|
|||
HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject);
|
||||
mHeightFields[std::make_pair(x,y)] = heightfield;
|
||||
|
||||
mCollisionWorld->addCollisionObject(heightfield->getCollisionObject(), CollisionType_HeightMap,
|
||||
mTaskScheduler->addCollisionObject(heightfield->getCollisionObject(), CollisionType_HeightMap,
|
||||
CollisionType_Actor|CollisionType_Projectile);
|
||||
}
|
||||
|
||||
|
@ -461,7 +441,7 @@ namespace MWPhysics
|
|||
HeightFieldMap::iterator heightfield = mHeightFields.find(std::make_pair(x,y));
|
||||
if(heightfield != mHeightFields.end())
|
||||
{
|
||||
mCollisionWorld->removeCollisionObject(heightfield->second->getCollisionObject());
|
||||
mTaskScheduler->removeCollisionObject(heightfield->second->getCollisionObject());
|
||||
delete heightfield->second;
|
||||
mHeightFields.erase(heightfield);
|
||||
}
|
||||
|
@ -481,13 +461,13 @@ namespace MWPhysics
|
|||
if (!shapeInstance || !shapeInstance->getCollisionShape())
|
||||
return;
|
||||
|
||||
Object *obj = new Object(ptr, shapeInstance);
|
||||
auto obj = std::make_shared<Object>(ptr, shapeInstance, mTaskScheduler.get());
|
||||
mObjects.emplace(ptr, obj);
|
||||
|
||||
if (obj->isAnimated())
|
||||
mAnimatedObjects.insert(obj);
|
||||
mAnimatedObjects.insert(obj.get());
|
||||
|
||||
mCollisionWorld->addCollisionObject(obj->getCollisionObject(), collisionType,
|
||||
mTaskScheduler->addCollisionObject(obj->getCollisionObject(), collisionType,
|
||||
CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile);
|
||||
}
|
||||
|
||||
|
@ -496,21 +476,17 @@ namespace MWPhysics
|
|||
ObjectMap::iterator found = mObjects.find(ptr);
|
||||
if (found != mObjects.end())
|
||||
{
|
||||
mCollisionWorld->removeCollisionObject(found->second->getCollisionObject());
|
||||
|
||||
if (mUnrefQueue.get())
|
||||
mUnrefQueue->push(found->second->getShapeInstance());
|
||||
|
||||
mAnimatedObjects.erase(found->second);
|
||||
mAnimatedObjects.erase(found->second.get());
|
||||
|
||||
delete found->second;
|
||||
mObjects.erase(found);
|
||||
}
|
||||
|
||||
ActorMap::iterator foundActor = mActors.find(ptr);
|
||||
if (foundActor != mActors.end())
|
||||
{
|
||||
delete foundActor->second;
|
||||
mActors.erase(foundActor);
|
||||
}
|
||||
}
|
||||
|
@ -536,19 +512,19 @@ namespace MWPhysics
|
|||
ObjectMap::iterator found = mObjects.find(old);
|
||||
if (found != mObjects.end())
|
||||
{
|
||||
Object* obj = found->second;
|
||||
auto obj = found->second;
|
||||
obj->updatePtr(updated);
|
||||
mObjects.erase(found);
|
||||
mObjects.emplace(updated, obj);
|
||||
mObjects.emplace(updated, std::move(obj));
|
||||
}
|
||||
|
||||
ActorMap::iterator foundActor = mActors.find(old);
|
||||
if (foundActor != mActors.end())
|
||||
{
|
||||
Actor* actor = foundActor->second;
|
||||
auto actor = foundActor->second;
|
||||
actor->updatePtr(updated);
|
||||
mActors.erase(foundActor);
|
||||
mActors.emplace(updated, actor);
|
||||
mActors.emplace(updated, std::move(actor));
|
||||
}
|
||||
|
||||
updateCollisionMapPtr(mStandingCollisions, old, updated);
|
||||
|
@ -558,7 +534,7 @@ namespace MWPhysics
|
|||
{
|
||||
ActorMap::iterator found = mActors.find(ptr);
|
||||
if (found != mActors.end())
|
||||
return found->second;
|
||||
return found->second.get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -566,7 +542,7 @@ namespace MWPhysics
|
|||
{
|
||||
ActorMap::const_iterator found = mActors.find(ptr);
|
||||
if (found != mActors.end())
|
||||
return found->second;
|
||||
return found->second.get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -574,7 +550,7 @@ namespace MWPhysics
|
|||
{
|
||||
ObjectMap::const_iterator found = mObjects.find(ptr);
|
||||
if (found != mObjects.end())
|
||||
return found->second;
|
||||
return found->second.get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -585,14 +561,14 @@ namespace MWPhysics
|
|||
{
|
||||
float scale = ptr.getCellRef().getScale();
|
||||
found->second->setScale(scale);
|
||||
mCollisionWorld->updateSingleAabb(found->second->getCollisionObject());
|
||||
mTaskScheduler->updateSingleAabb(found->second);
|
||||
return;
|
||||
}
|
||||
ActorMap::iterator foundActor = mActors.find(ptr);
|
||||
if (foundActor != mActors.end())
|
||||
{
|
||||
foundActor->second->updateScale();
|
||||
mCollisionWorld->updateSingleAabb(foundActor->second->getCollisionObject());
|
||||
mTaskScheduler->updateSingleAabb(foundActor->second);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -603,7 +579,7 @@ namespace MWPhysics
|
|||
if (found != mObjects.end())
|
||||
{
|
||||
found->second->setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
|
||||
mCollisionWorld->updateSingleAabb(found->second->getCollisionObject());
|
||||
mTaskScheduler->updateSingleAabb(found->second);
|
||||
return;
|
||||
}
|
||||
ActorMap::iterator foundActor = mActors.find(ptr);
|
||||
|
@ -612,7 +588,7 @@ namespace MWPhysics
|
|||
if (!foundActor->second->isRotationallyInvariant())
|
||||
{
|
||||
foundActor->second->updateRotation();
|
||||
mCollisionWorld->updateSingleAabb(foundActor->second->getCollisionObject());
|
||||
mTaskScheduler->updateSingleAabb(foundActor->second);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -624,14 +600,14 @@ namespace MWPhysics
|
|||
if (found != mObjects.end())
|
||||
{
|
||||
found->second->setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3()));
|
||||
mCollisionWorld->updateSingleAabb(found->second->getCollisionObject());
|
||||
mTaskScheduler->updateSingleAabb(found->second);
|
||||
return;
|
||||
}
|
||||
ActorMap::iterator foundActor = mActors.find(ptr);
|
||||
if (foundActor != mActors.end())
|
||||
{
|
||||
foundActor->second->updatePosition();
|
||||
mCollisionWorld->updateSingleAabb(foundActor->second->getCollisionObject());
|
||||
mTaskScheduler->updateSingleAabb(foundActor->second);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -639,11 +615,9 @@ namespace MWPhysics
|
|||
void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh)
|
||||
{
|
||||
osg::ref_ptr<const Resource::BulletShape> shape = mShapeManager->getShape(mesh);
|
||||
if (!shape)
|
||||
return;
|
||||
|
||||
// Try to get shape from basic model as fallback for creatures
|
||||
if (!ptr.getClass().isNpc() && shape->mCollisionBoxHalfExtents.length2() == 0)
|
||||
if (!ptr.getClass().isNpc() && shape && shape->mCollisionBoxHalfExtents.length2() == 0)
|
||||
{
|
||||
const std::string fallbackModel = ptr.getClass().getModel(ptr);
|
||||
if (fallbackModel != mesh)
|
||||
|
@ -652,8 +626,11 @@ namespace MWPhysics
|
|||
}
|
||||
}
|
||||
|
||||
Actor* actor = new Actor(ptr, shape, mCollisionWorld);
|
||||
mActors.emplace(ptr, actor);
|
||||
if (!shape)
|
||||
return;
|
||||
|
||||
auto actor = std::make_shared<Actor>(ptr, shape, mTaskScheduler.get());
|
||||
mActors.emplace(ptr, std::move(actor));
|
||||
}
|
||||
|
||||
bool PhysicsSystem::toggleCollisionMode()
|
||||
|
@ -691,99 +668,76 @@ namespace MWPhysics
|
|||
mStandingCollisions.clear();
|
||||
}
|
||||
|
||||
const PtrVelocityList& PhysicsSystem::applyQueuedMovement(float dt)
|
||||
const PtrPositionList& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation)
|
||||
{
|
||||
mMovementResults.clear();
|
||||
|
||||
mTimeAccum += dt;
|
||||
|
||||
const int maxAllowedSteps = 20;
|
||||
int numSteps = mTimeAccum / (mPhysicsDt);
|
||||
int numSteps = mTimeAccum / mPhysicsDt;
|
||||
numSteps = std::min(numSteps, maxAllowedSteps);
|
||||
|
||||
mTimeAccum -= numSteps * mPhysicsDt;
|
||||
|
||||
if (numSteps)
|
||||
{
|
||||
// Collision events should be available on every frame
|
||||
mStandingCollisions.clear();
|
||||
return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(), mStandingCollisions, skipSimulation);
|
||||
}
|
||||
|
||||
const MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
for(auto& movementItem : mMovementQueue)
|
||||
std::vector<ActorFrameData> PhysicsSystem::prepareFrameData()
|
||||
{
|
||||
ActorMap::iterator foundActor = mActors.find(movementItem.first);
|
||||
std::vector<ActorFrameData> actorsFrameData;
|
||||
actorsFrameData.reserve(mMovementQueue.size());
|
||||
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
for (const auto& m : mMovementQueue)
|
||||
{
|
||||
const auto& character = m.first;
|
||||
const auto& movement = m.second;
|
||||
const auto foundActor = mActors.find(character);
|
||||
if (foundActor == mActors.end()) // actor was already removed from the scene
|
||||
{
|
||||
mStandingCollisions.erase(character);
|
||||
continue;
|
||||
Actor* physicActor = foundActor->second;
|
||||
}
|
||||
auto physicActor = foundActor->second;
|
||||
|
||||
float waterlevel = -std::numeric_limits<float>::max();
|
||||
const MWWorld::CellStore *cell = movementItem.first.getCell();
|
||||
const MWWorld::CellStore *cell = character.getCell();
|
||||
if(cell->getCell()->hasWater())
|
||||
waterlevel = cell->getWaterLevel();
|
||||
|
||||
const MWMechanics::MagicEffects& effects = movementItem.first.getClass().getCreatureStats(movementItem.first).getMagicEffects();
|
||||
const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects();
|
||||
|
||||
bool waterCollision = false;
|
||||
bool moveToWaterSurface = false;
|
||||
if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude())
|
||||
{
|
||||
if (!world->isUnderwater(movementItem.first.getCell(), osg::Vec3f(movementItem.first.getRefData().getPosition().asVec3())))
|
||||
if (!world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3())))
|
||||
waterCollision = true;
|
||||
else if (physicActor->getCollisionMode() && canMoveToWaterSurface(movementItem.first, waterlevel))
|
||||
else if (physicActor->getCollisionMode() && canMoveToWaterSurface(character, waterlevel))
|
||||
{
|
||||
const osg::Vec3f actorPosition = physicActor->getPosition();
|
||||
physicActor->setPosition(osg::Vec3f(actorPosition.x(), actorPosition.y(), waterlevel));
|
||||
moveToWaterSurface = true;
|
||||
waterCollision = true;
|
||||
}
|
||||
}
|
||||
|
||||
physicActor->setCanWaterWalk(waterCollision);
|
||||
|
||||
// Slow fall reduces fall speed by a factor of (effect magnitude / 200)
|
||||
float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f));
|
||||
const float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f));
|
||||
|
||||
bool flying = world->isFlying(movementItem.first);
|
||||
bool swimming = world->isSwimming(movementItem.first);
|
||||
|
||||
bool wasOnGround = physicActor->getOnGround();
|
||||
osg::Vec3f position = physicActor->getPosition();
|
||||
float oldHeight = position.z();
|
||||
bool positionChanged = false;
|
||||
for (int i=0; i<numSteps; ++i)
|
||||
{
|
||||
position = MovementSolver::move(position, physicActor->getPtr(), physicActor, movementItem.second, mPhysicsDt,
|
||||
flying, waterlevel, slowFall, mCollisionWorld, mStandingCollisions);
|
||||
if (position != physicActor->getPosition())
|
||||
positionChanged = true;
|
||||
physicActor->setPosition(position); // always set even if unchanged to make sure interpolation is correct
|
||||
actorsFrameData.emplace_back(std::move(physicActor), character, mStandingCollisions[character], moveToWaterSurface, movement, slowFall, waterlevel);
|
||||
}
|
||||
if (positionChanged)
|
||||
mCollisionWorld->updateSingleAabb(physicActor->getCollisionObject());
|
||||
|
||||
float interpolationFactor = mTimeAccum / mPhysicsDt;
|
||||
osg::Vec3f interpolated = position * interpolationFactor + physicActor->getPreviousPosition() * (1.f - interpolationFactor);
|
||||
|
||||
float heightDiff = position.z() - oldHeight;
|
||||
|
||||
MWMechanics::CreatureStats& stats = movementItem.first.getClass().getCreatureStats(movementItem.first);
|
||||
bool isStillOnGround = (numSteps > 0 && wasOnGround && physicActor->getOnGround());
|
||||
if (isStillOnGround || flying || swimming || slowFall < 1)
|
||||
stats.land(movementItem.first == player && (flying || swimming));
|
||||
else if (heightDiff < 0)
|
||||
stats.addToFallHeight(-heightDiff);
|
||||
|
||||
mMovementResults.emplace_back(movementItem.first, interpolated);
|
||||
}
|
||||
|
||||
mMovementQueue.clear();
|
||||
|
||||
return mMovementResults;
|
||||
return actorsFrameData;
|
||||
}
|
||||
|
||||
void PhysicsSystem::stepSimulation(float dt)
|
||||
void PhysicsSystem::stepSimulation()
|
||||
{
|
||||
for (Object* animatedObject : mAnimatedObjects)
|
||||
animatedObject->animateCollisionShapes(mCollisionWorld);
|
||||
if (animatedObject->animateCollisionShapes())
|
||||
{
|
||||
auto obj = mObjects.find(animatedObject->getPtr());
|
||||
assert(obj != mObjects.end());
|
||||
mTaskScheduler->updateSingleAabb(obj->second);
|
||||
}
|
||||
|
||||
#ifndef BT_NO_PROFILE
|
||||
CProfileManager::Reset();
|
||||
|
@ -795,12 +749,13 @@ namespace MWPhysics
|
|||
{
|
||||
ObjectMap::iterator found = mObjects.find(object);
|
||||
if (found != mObjects.end())
|
||||
found->second->animateCollisionShapes(mCollisionWorld);
|
||||
if (found->second->animateCollisionShapes())
|
||||
mTaskScheduler->updateSingleAabb(found->second);
|
||||
}
|
||||
|
||||
void PhysicsSystem::debugDraw()
|
||||
{
|
||||
if (mDebugDrawer.get())
|
||||
if (mDebugDrawer)
|
||||
mDebugDrawer->step();
|
||||
}
|
||||
|
||||
|
@ -865,9 +820,9 @@ namespace MWPhysics
|
|||
|
||||
void PhysicsSystem::updateWater()
|
||||
{
|
||||
if (mWaterCollisionObject.get())
|
||||
if (mWaterCollisionObject)
|
||||
{
|
||||
mCollisionWorld->removeCollisionObject(mWaterCollisionObject.get());
|
||||
mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get());
|
||||
}
|
||||
|
||||
if (!mWaterEnabled)
|
||||
|
@ -879,7 +834,7 @@ namespace MWPhysics
|
|||
mWaterCollisionObject.reset(new btCollisionObject());
|
||||
mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight));
|
||||
mWaterCollisionObject->setCollisionShape(mWaterCollisionShape.get());
|
||||
mCollisionWorld->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water,
|
||||
mTaskScheduler->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water,
|
||||
CollisionType_Actor);
|
||||
}
|
||||
|
||||
|
@ -895,7 +850,7 @@ namespace MWPhysics
|
|||
const int mask = MWPhysics::CollisionType_Actor;
|
||||
const int group = 0xff;
|
||||
HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group);
|
||||
mCollisionWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, callback);
|
||||
mTaskScheduler->aabbTest(aabbMin, aabbMax, callback);
|
||||
return callback.getResult();
|
||||
}
|
||||
|
||||
|
@ -905,4 +860,61 @@ namespace MWPhysics
|
|||
stats.setAttribute(frameNumber, "Physics Objects", mObjects.size());
|
||||
stats.setAttribute(frameNumber, "Physics HeightFields", mHeightFields.size());
|
||||
}
|
||||
|
||||
ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn,
|
||||
bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel)
|
||||
: mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn),
|
||||
mPositionChanged(false), mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface),
|
||||
mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos()
|
||||
{
|
||||
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
mPtr = actor->getPtr();
|
||||
mFlying = world->isFlying(character);
|
||||
mSwimming = world->isSwimming(character);
|
||||
mWantJump = mPtr.getClass().getMovementSettings(mPtr).mPosition[2] != 0;
|
||||
mIsDead = mPtr.getClass().getCreatureStats(mPtr).isDead();
|
||||
mWasOnGround = actor->getOnGround();
|
||||
}
|
||||
|
||||
void ActorFrameData::updatePosition()
|
||||
{
|
||||
mPosition = mActorRaw->getPosition();
|
||||
if (mMoveToWaterSurface)
|
||||
{
|
||||
mPosition.z() = mWaterlevel;
|
||||
mActorRaw->setPosition(mPosition);
|
||||
}
|
||||
mOldHeight = mPosition.z();
|
||||
mRefpos = mPtr.getRefData().getPosition();
|
||||
}
|
||||
|
||||
WorldFrameData::WorldFrameData()
|
||||
: mIsInStorm(MWBase::Environment::get().getWorld()->isInStorm())
|
||||
, mStormDirection(MWBase::Environment::get().getWorld()->getStormDirection())
|
||||
{}
|
||||
|
||||
LOSRequest::LOSRequest(const std::weak_ptr<Actor>& a1, const std::weak_ptr<Actor>& a2)
|
||||
: mResult(false), mStale(false), mAge(0)
|
||||
{
|
||||
// we use raw actor pointer pair to uniquely identify request
|
||||
// sort the pointer value in ascending order to not duplicate equivalent requests, eg. getLOS(A, B) and getLOS(B, A)
|
||||
auto* raw1 = a1.lock().get();
|
||||
auto* raw2 = a2.lock().get();
|
||||
assert(raw1 != raw2);
|
||||
if (raw1 < raw2)
|
||||
{
|
||||
mActors = {a1, a2};
|
||||
mRawActors = {raw1, raw2};
|
||||
}
|
||||
else
|
||||
{
|
||||
mActors = {a2, a1};
|
||||
mRawActors = {raw2, raw1};
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const LOSRequest& lhs, const LOSRequest& rhs) noexcept
|
||||
{
|
||||
return lhs.mRawActors == rhs.mRawActors;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef OPENMW_MWPHYSICS_PHYSICSSYSTEM_H
|
||||
#define OPENMW_MWPHYSICS_PHYSICSSYSTEM_H
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
@ -47,11 +48,57 @@ class btCollisionShape;
|
|||
|
||||
namespace MWPhysics
|
||||
{
|
||||
typedef std::vector<std::pair<MWWorld::Ptr,osg::Vec3f> > PtrVelocityList;
|
||||
using PtrPositionList = std::map<MWWorld::Ptr, osg::Vec3f>;
|
||||
using CollisionMap = std::map<MWWorld::Ptr, MWWorld::Ptr>;
|
||||
|
||||
class HeightField;
|
||||
class Object;
|
||||
class Actor;
|
||||
class PhysicsTaskScheduler;
|
||||
|
||||
struct LOSRequest
|
||||
{
|
||||
LOSRequest(const std::weak_ptr<Actor>& a1, const std::weak_ptr<Actor>& a2);
|
||||
std::array<std::weak_ptr<Actor>, 2> mActors;
|
||||
std::array<const Actor*, 2> mRawActors;
|
||||
bool mResult;
|
||||
bool mStale;
|
||||
int mAge;
|
||||
};
|
||||
bool operator==(const LOSRequest& lhs, const LOSRequest& rhs) noexcept;
|
||||
|
||||
struct ActorFrameData
|
||||
{
|
||||
ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
|
||||
void updatePosition();
|
||||
std::weak_ptr<Actor> mActor;
|
||||
Actor* mActorRaw;
|
||||
MWWorld::Ptr mPtr;
|
||||
MWWorld::Ptr mStandingOn;
|
||||
bool mFlying;
|
||||
bool mSwimming;
|
||||
bool mPositionChanged;
|
||||
bool mWasOnGround;
|
||||
bool mWantJump;
|
||||
bool mDidJump;
|
||||
bool mIsDead;
|
||||
bool mNeedLand;
|
||||
bool mMoveToWaterSurface;
|
||||
float mWaterlevel;
|
||||
float mSlowFall;
|
||||
float mOldHeight;
|
||||
float mFallHeight;
|
||||
osg::Vec3f mMovement;
|
||||
osg::Vec3f mPosition;
|
||||
ESM::Position mRefpos;
|
||||
};
|
||||
|
||||
struct WorldFrameData
|
||||
{
|
||||
WorldFrameData();
|
||||
bool mIsInStorm;
|
||||
osg::Vec3f mStormDirection;
|
||||
};
|
||||
|
||||
class PhysicsSystem : public RayCastingInterface
|
||||
{
|
||||
|
@ -93,7 +140,7 @@ namespace MWPhysics
|
|||
|
||||
bool toggleCollisionMode();
|
||||
|
||||
void stepSimulation(float dt);
|
||||
void stepSimulation();
|
||||
void debugDraw();
|
||||
|
||||
std::vector<MWWorld::Ptr> getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const; ///< get handles this object collides with
|
||||
|
@ -102,7 +149,7 @@ namespace MWPhysics
|
|||
std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::ConstPtr& actor,
|
||||
const osg::Vec3f &origin,
|
||||
const osg::Quat &orientation,
|
||||
float queryDistance, std::vector<MWWorld::Ptr> targets = std::vector<MWWorld::Ptr>());
|
||||
float queryDistance, std::vector<MWWorld::Ptr>& targets);
|
||||
|
||||
|
||||
/// Get distance from \a point to the collision shape of \a target. Uses a raycast to find where the
|
||||
|
@ -146,7 +193,7 @@ namespace MWPhysics
|
|||
void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity);
|
||||
|
||||
/// Apply all queued movements, then clear the list.
|
||||
const PtrVelocityList& applyQueuedMovement(float dt);
|
||||
const PtrPositionList& applyQueuedMovement(float dt, bool skipSimulation);
|
||||
|
||||
/// Clear the queued movements list without applying.
|
||||
void clearQueuedMovement();
|
||||
|
@ -190,39 +237,41 @@ namespace MWPhysics
|
|||
|
||||
void updateWater();
|
||||
|
||||
std::vector<ActorFrameData> prepareFrameData();
|
||||
|
||||
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
|
||||
|
||||
btBroadphaseInterface* mBroadphase;
|
||||
btDefaultCollisionConfiguration* mCollisionConfiguration;
|
||||
btCollisionDispatcher* mDispatcher;
|
||||
btCollisionWorld* mCollisionWorld;
|
||||
std::unique_ptr<btBroadphaseInterface> mBroadphase;
|
||||
std::unique_ptr<btDefaultCollisionConfiguration> mCollisionConfiguration;
|
||||
std::unique_ptr<btCollisionDispatcher> mDispatcher;
|
||||
std::shared_ptr<btCollisionWorld> mCollisionWorld;
|
||||
std::unique_ptr<PhysicsTaskScheduler> mTaskScheduler;
|
||||
|
||||
std::unique_ptr<Resource::BulletShapeManager> mShapeManager;
|
||||
Resource::ResourceSystem* mResourceSystem;
|
||||
|
||||
typedef std::map<MWWorld::ConstPtr, Object*> ObjectMap;
|
||||
using ObjectMap = std::map<MWWorld::ConstPtr, std::shared_ptr<Object>>;
|
||||
ObjectMap mObjects;
|
||||
|
||||
std::set<Object*> mAnimatedObjects; // stores pointers to elements in mObjects
|
||||
|
||||
typedef std::map<MWWorld::ConstPtr, Actor*> ActorMap;
|
||||
using ActorMap = std::map<MWWorld::ConstPtr, std::shared_ptr<Actor>>;
|
||||
ActorMap mActors;
|
||||
|
||||
typedef std::map<std::pair<int, int>, HeightField*> HeightFieldMap;
|
||||
using HeightFieldMap = std::map<std::pair<int, int>, HeightField *>;
|
||||
HeightFieldMap mHeightFields;
|
||||
|
||||
bool mDebugDrawEnabled;
|
||||
|
||||
// Tracks standing collisions happening during a single frame. <actor handle, collided handle>
|
||||
// This will detect standing on an object, but won't detect running e.g. against a wall.
|
||||
typedef std::map<MWWorld::Ptr, MWWorld::Ptr> CollisionMap;
|
||||
CollisionMap mStandingCollisions;
|
||||
|
||||
// replaces all occurrences of 'old' in the map by 'updated', no matter if it's a key or value
|
||||
void updateCollisionMapPtr(CollisionMap& map, const MWWorld::Ptr &old, const MWWorld::Ptr &updated);
|
||||
|
||||
using PtrVelocityList = std::vector<std::pair<MWWorld::Ptr, osg::Vec3f>>;
|
||||
PtrVelocityList mMovementQueue;
|
||||
PtrVelocityList mMovementResults;
|
||||
|
||||
float mTimeAccum;
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@ namespace
|
|||
? btVector3(distanceFromDoor, 0, 0)
|
||||
: btVector3(0, distanceFromDoor, 0);
|
||||
|
||||
const auto& transform = object->getCollisionObject()->getWorldTransform();
|
||||
const auto transform = object->getTransform();
|
||||
const btTransform closedDoorTransform(
|
||||
Misc::Convert::toBullet(makeObjectOsgQuat(ptr.getCellRef().getPosition())),
|
||||
transform.getOrigin()
|
||||
|
@ -187,7 +187,7 @@ namespace
|
|||
*object->getShapeInstance()->getCollisionShape(),
|
||||
object->getShapeInstance()->getAvoidCollisionShape()
|
||||
},
|
||||
object->getCollisionObject()->getWorldTransform()
|
||||
object->getTransform()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,7 +144,7 @@ namespace MWWorld
|
|||
const std::string& resourcePath, const std::string& userDataPath)
|
||||
: mResourceSystem(resourceSystem), mLocalScripts (mStore),
|
||||
mCells (mStore, mEsm), mSky (true),
|
||||
mGodMode(false), mScriptsEnabled(true), mContentFiles (contentFiles),
|
||||
mGodMode(false), mScriptsEnabled(true), mDiscardMovements(true), mContentFiles (contentFiles),
|
||||
mUserDataPath(userDataPath), mShouldUpdateNavigator(false),
|
||||
mActivationDistanceOverride (activationDistanceOverride),
|
||||
mStartCell(startCell), mDistanceToFacedObject(-1.f), mTeleportEnabled(true),
|
||||
|
@ -932,6 +932,7 @@ namespace MWWorld
|
|||
void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
|
||||
{
|
||||
mPhysics->clearQueuedMovement();
|
||||
mDiscardMovements = true;
|
||||
|
||||
if (changeEvent && mCurrentWorldSpace != cellName)
|
||||
{
|
||||
|
@ -951,6 +952,7 @@ namespace MWWorld
|
|||
void World::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
|
||||
{
|
||||
mPhysics->clearQueuedMovement();
|
||||
mDiscardMovements = true;
|
||||
|
||||
if (changeEvent && mCurrentWorldSpace != ESM::CellId::sDefaultWorldspace)
|
||||
{
|
||||
|
@ -1494,24 +1496,23 @@ namespace MWWorld
|
|||
|
||||
void World::doPhysics(float duration)
|
||||
{
|
||||
mPhysics->stepSimulation(duration);
|
||||
mPhysics->stepSimulation();
|
||||
processDoors(duration);
|
||||
|
||||
mProjectileManager->update(duration);
|
||||
|
||||
const MWPhysics::PtrVelocityList &results = mPhysics->applyQueuedMovement(duration);
|
||||
MWPhysics::PtrVelocityList::const_iterator player(results.end());
|
||||
for(MWPhysics::PtrVelocityList::const_iterator iter(results.begin());iter != results.end();++iter)
|
||||
{
|
||||
if(iter->first == getPlayerPtr())
|
||||
const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements);
|
||||
mDiscardMovements = false;
|
||||
|
||||
for(const auto& result : results)
|
||||
{
|
||||
// Handle player last, in case a cell transition occurs
|
||||
player = iter;
|
||||
continue;
|
||||
if(result.first != getPlayerPtr())
|
||||
moveObjectImp(result.first, result.second.x(), result.second.y(), result.second.z(), false);
|
||||
}
|
||||
moveObjectImp(iter->first, iter->second.x(), iter->second.y(), iter->second.z(), false);
|
||||
}
|
||||
if(player != results.end())
|
||||
|
||||
const auto player = results.find(getPlayerPtr());
|
||||
if (player != results.end())
|
||||
moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false);
|
||||
}
|
||||
|
||||
|
@ -1539,7 +1540,7 @@ namespace MWWorld
|
|||
*object->getShapeInstance()->getCollisionShape(),
|
||||
object->getShapeInstance()->getAvoidCollisionShape()
|
||||
};
|
||||
return mNavigator->updateObject(DetourNavigator::ObjectId(object), shapes, object->getCollisionObject()->getWorldTransform());
|
||||
return mNavigator->updateObject(DetourNavigator::ObjectId(object), shapes, object->getTransform());
|
||||
}
|
||||
|
||||
const MWPhysics::RayCastingInterface* World::getRayCasting() const
|
||||
|
@ -3882,7 +3883,7 @@ namespace MWWorld
|
|||
btVector3 aabbMax;
|
||||
object->getShapeInstance()->getCollisionShape()->getAabb(btTransform::getIdentity(), aabbMin, aabbMax);
|
||||
|
||||
const auto toLocal = object->getCollisionObject()->getWorldTransform().inverse();
|
||||
const auto toLocal = object->getTransform().inverse();
|
||||
const auto localFrom = toLocal(Misc::Convert::toBullet(position));
|
||||
const auto localTo = toLocal(Misc::Convert::toBullet(destination));
|
||||
|
||||
|
|
|
@ -102,6 +102,7 @@ namespace MWWorld
|
|||
bool mSky;
|
||||
bool mGodMode;
|
||||
bool mScriptsEnabled;
|
||||
bool mDiscardMovements;
|
||||
std::vector<std::string> mContentFiles;
|
||||
|
||||
std::string mUserDataPath;
|
||||
|
|
51
components/misc/barrier.hpp
Normal file
51
components/misc/barrier.hpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#ifndef OPENMW_BARRIER_H
|
||||
#define OPENMW_BARRIER_H
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
namespace Misc
|
||||
{
|
||||
/// @brief Synchronize several threads
|
||||
class Barrier
|
||||
{
|
||||
public:
|
||||
using BarrierCallback = std::function<void(void)>;
|
||||
/// @param count number of threads to wait on
|
||||
/// @param func callable to be executed once after all threads have met
|
||||
Barrier(int count, BarrierCallback&& func) : mThreadCount(count), mRendezvousCount(0), mGeneration(0)
|
||||
, mFunc(std::forward<BarrierCallback>(func))
|
||||
{}
|
||||
|
||||
/// @brief stop execution of threads until count distinct threads reach this point
|
||||
void wait()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
|
||||
++mRendezvousCount;
|
||||
const int currentGeneration = mGeneration;
|
||||
if (mRendezvousCount == mThreadCount)
|
||||
{
|
||||
++mGeneration;
|
||||
mRendezvousCount = 0;
|
||||
mFunc();
|
||||
mRendezvous.notify_all();
|
||||
}
|
||||
else
|
||||
{
|
||||
mRendezvous.wait(lock, [&]() { return mGeneration != currentGeneration; });
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int mThreadCount;
|
||||
int mRendezvousCount;
|
||||
int mGeneration;
|
||||
mutable std::mutex mMutex;
|
||||
std::condition_variable mRendezvous;
|
||||
BarrierCallback mFunc;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -57,3 +57,4 @@ The ranges included with each setting are the physically possible ranges, not re
|
|||
water
|
||||
windows
|
||||
navigator
|
||||
physics
|
||||
|
|
37
docs/source/reference/modding/settings/physics.rst
Normal file
37
docs/source/reference/modding/settings/physics.rst
Normal file
|
@ -0,0 +1,37 @@
|
|||
Physics Settings
|
||||
################
|
||||
|
||||
async num threads
|
||||
-----------------
|
||||
|
||||
:Type: integer
|
||||
:Range: >= 0
|
||||
:Default: 0
|
||||
|
||||
Determines how many threads will be spawned to compute physics update in the background (that is, process actors movement). A value of 0 means that the update will be performed in the main thread.
|
||||
A value greater than 1 requires the Bullet library be compiled with multithreading support. If that's not the case, a warning will be written in ``openmw.log`` and a value of 1 will be used.
|
||||
|
||||
lineofsight keep inactive cache
|
||||
-------------------------------
|
||||
|
||||
:Type: integer
|
||||
:Range: >= -1
|
||||
:Default: 0
|
||||
|
||||
The line of sight determines if 2 actors can see each other (without taking into account game mechanics such as invisibility or sneaking). It is used by some scripts (the getLOS function), by the AI (to determine if an actor should start combat or chase an opponent) and for functionnalities such as greetings or turning NPC head toward an object.
|
||||
This parameters determine for how long a cache of request should be kept warm. It depends on :ref:`async num threads` being > 0, otherwise a value of -1 will be used. If a request is not found in the cache, it is always fulfilled immediately. In case Bullet is compiled without multithreading support, non-cached requests involve blocking the async thread(s), which might hurt performance.
|
||||
A value of -1 means no caching.
|
||||
A value of 0 means that for as long as a request is made (after the first one), it will be preemptively "refreshed" in the async thread, without blocking neither the main thread nor the async thread.
|
||||
Any value > 0 is the number of frames for which the values are kept in cache even if the results was not requested again.
|
||||
If Bullet is compiled with multithreading support, requests are non blocking, it is better to set this parameter to -1.
|
||||
|
||||
defer aabb update
|
||||
-----------------
|
||||
|
||||
:Type: boolean
|
||||
:Range: True/False
|
||||
:Default: True
|
||||
|
||||
Axis-aligned bounding box (aabb for short) are used by Bullet for collision detection. They should be updated anytime a physical object is modified (for instance moved) for collision detection to be correct.
|
||||
This parameter control wether the update should be done as soon as the object is modified (the default), which involves blocking the async thread(s), or queue the modifications to update them as a batch before the collision detections. It depends on :ref:`async num threads` being > 0, otherwise it will be disabled.
|
||||
Disabling this parameter is intended as an aid for debugging collisions detection issues.
|
|
@ -916,3 +916,15 @@ object shadows = false
|
|||
|
||||
# Allow shadows indoors. Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.
|
||||
enable indoor shadows = true
|
||||
|
||||
[Physics]
|
||||
# how much background thread to use in the physics solver. 0 to disable (i.e solver run in the main thread)
|
||||
async num threads = 0
|
||||
|
||||
# maintain a cache of lineofsight request in the bacground physics thread
|
||||
# determines for how much frames an inactive lineofsight request should be kept updated in the cache
|
||||
# -1 to disable (i.e the LOS will be calculated only on request)
|
||||
lineofsight keep inactive cache = 0
|
||||
|
||||
# wether to defer aabb update till before collision detection
|
||||
defer aabb update = true
|
||||
|
|
|
@ -211,6 +211,36 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalPhysicsThreadsLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="physicsThreadsLabel">
|
||||
<property name="toolTip">
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Background physics threads</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="physicsThreadsSpinBox"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<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>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
|
|
Loading…
Reference in a new issue