From 9472728fa49a8e7820d3e7543205d1dc2dcfebb0 Mon Sep 17 00:00:00 2001 From: fredzio Date: Tue, 20 Jul 2021 05:38:06 +0200 Subject: [PATCH 01/12] Do not generate data for immobile actors instead of early out from the solver --- apps/openmw/mwphysics/movementsolver.cpp | 11 ----------- apps/openmw/mwphysics/physicssystem.cpp | 2 ++ 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index fd0e090fcb..66530e37ca 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -120,13 +120,6 @@ namespace MWPhysics { auto* physicActor = actor.mActorRaw; const ESM::Position& refpos = actor.mRefpos; - // Early-out for totally static creatures - // (Not sure if gravity should still apply?) - { - const auto ptr = physicActor->getPtr(); - if (!ptr.getClass().isMobile(ptr)) - return; - } // Reset per-frame data physicActor->setWalkingOnWater(false); @@ -432,10 +425,6 @@ namespace MWPhysics void MovementSolver::unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld) { - const auto& ptr = actor.mActorRaw->getPtr(); - if (!ptr.getClass().isMobile(ptr)) - return; - auto* physicActor = actor.mActorRaw; if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection) // noclipping/tcl return; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 85fb463a95..1028c0062e 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -757,6 +757,8 @@ namespace MWPhysics const MWBase::World *world = MWBase::Environment::get().getWorld(); for (const auto& [ptr, physicActor] : mActors) { + if (!ptr.getClass().isMobile(physicActor->getPtr())) + continue; float waterlevel = -std::numeric_limits::max(); const MWWorld::CellStore *cell = ptr.getCell(); if(cell->getCell()->hasWater()) From 1bfaf353bec713636ab927cc5b4c21cd60ada0d2 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 21 Jul 2021 19:04:36 +0200 Subject: [PATCH 02/12] Explicitely store all the potential states an Actor can have into the ActActorFrameData structure. It makes it easier to reason about the simulation (and hopefully simplify it). Remove atomics from Actor class as a side effect. Rename mFloatToSurface to mInert to make is explicit what it represent, not what it is used for Store the Actor rotation (1 Vec2) instead of the whole ESM::Position (2 Vec3) --- apps/openmw/mwphysics/actor.cpp | 10 +++---- apps/openmw/mwphysics/actor.hpp | 15 +++++----- apps/openmw/mwphysics/movementsolver.cpp | 37 ++++++++++++------------ apps/openmw/mwphysics/mtphysics.cpp | 12 +++++++- apps/openmw/mwphysics/physicssystem.cpp | 27 +++++++++++++---- apps/openmw/mwphysics/physicssystem.hpp | 7 +++-- 6 files changed, 67 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 67bfb4dffe..5416223264 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -82,7 +82,7 @@ Actor::~Actor() void Actor::enableCollisionMode(bool collision) { - mInternalCollisionMode.store(collision, std::memory_order_release); + mInternalCollisionMode = collision; } void Actor::enableCollisionBody(bool collision) @@ -250,22 +250,22 @@ void Actor::setInertialForce(const osg::Vec3f &force) void Actor::setOnGround(bool grounded) { - mOnGround.store(grounded, std::memory_order_release); + mOnGround = grounded; } void Actor::setOnSlope(bool slope) { - mOnSlope.store(slope, std::memory_order_release); + mOnSlope = slope; } bool Actor::isWalkingOnWater() const { - return mWalkingOnWater.load(std::memory_order_acquire); + return mWalkingOnWater; } void Actor::setWalkingOnWater(bool walkingOnWater) { - mWalkingOnWater.store(walkingOnWater, std::memory_order_release); + mWalkingOnWater = walkingOnWater; } void Actor::setCanWaterWalk(bool waterWalk) diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 703d8a191b..eb81064937 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -1,7 +1,6 @@ #ifndef OPENMW_MWPHYSICS_ACTOR_H #define OPENMW_MWPHYSICS_ACTOR_H -#include #include #include @@ -37,7 +36,7 @@ namespace MWPhysics bool getCollisionMode() const { - return mInternalCollisionMode.load(std::memory_order_acquire); + return mInternalCollisionMode; } btConvexShape* getConvexShape() const { return mConvexShape; } @@ -123,14 +122,14 @@ namespace MWPhysics bool getOnGround() const { - return mInternalCollisionMode.load(std::memory_order_acquire) && mOnGround.load(std::memory_order_acquire); + return mInternalCollisionMode && mOnGround; } void setOnSlope(bool slope); bool getOnSlope() const { - return mInternalCollisionMode.load(std::memory_order_acquire) && mOnSlope.load(std::memory_order_acquire); + return mInternalCollisionMode && mOnSlope; } btCollisionObject* getCollisionObject() const @@ -182,7 +181,7 @@ namespace MWPhysics osg::Vec3f getScaledMeshTranslation() const; bool mCanWaterWalk; - std::atomic mWalkingOnWater; + bool mWalkingOnWater; bool mRotationallyInvariant; @@ -211,9 +210,9 @@ namespace MWPhysics osg::Vec3f mLastStuckPosition; osg::Vec3f mForce; - std::atomic mOnGround; - std::atomic mOnSlope; - std::atomic mInternalCollisionMode; + bool mOnGround; + bool mOnSlope; + bool mInternalCollisionMode; bool mExternalCollisionMode; PhysicsTaskScheduler* mTaskScheduler; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 66530e37ca..76f7a8d5a2 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -119,15 +119,14 @@ namespace MWPhysics WorldFrameData& worldData) { auto* physicActor = actor.mActorRaw; - const ESM::Position& refpos = actor.mRefpos; // Reset per-frame data - physicActor->setWalkingOnWater(false); + actor.mWalkingOnWater = false; // Anything to collide with? - if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection) + if(actor.mSkipCollisionDetection) { - actor.mPosition += (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * - osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1)) + actor.mPosition += (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) * + osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1)) ) * actor.mMovement * time; return; } @@ -151,22 +150,22 @@ namespace MWPhysics 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))) * actor.mMovement; + velocity = (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement; } else { - velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement; + velocity = (osg::Quat(actor.mRotation.y(), 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())) + if ((velocity.z() > 0.f && actor.mIsOnGround && !actor.mIsOnSlope) + || (velocity.z() > 0.f && velocity.z() + inertia.z() <= -velocity.z() && actor.mIsOnSlope)) inertia = velocity; - else if (!physicActor->getOnGround() || physicActor->getOnSlope()) + else if (!actor.mIsOnGround || actor.mIsOnSlope) velocity = velocity + inertia; } // Dead and paralyzed actors underwater will float to the surface, // if the CharacterController tells us to do so - if (actor.mMovement.z() > 0 && actor.mFloatToSurface && actor.mPosition.z() < swimlevel) + if (actor.mMovement.z() > 0 && actor.mInert && actor.mPosition.z() < swimlevel) velocity = osg::Vec3f(0,0,1) * 25; if (actor.mWantJump) @@ -190,7 +189,7 @@ namespace MWPhysics * The initial velocity was set earlier (see above). */ float remainingTime = time; - bool seenGround = physicActor->getOnGround() && !physicActor->getOnSlope() && !actor.mFlying; + bool seenGround = actor.mIsOnGround && !actor.mIsOnSlope && !actor.mFlying; int numTimesSlid = 0; osg::Vec3f lastSlideNormal(0,0,1); @@ -349,7 +348,7 @@ namespace MWPhysics if (forceGroundTest || (inertia.z() <= 0.f && newPosition.z() >= swimlevel)) { osg::Vec3f from = newPosition; - auto dropDistance = 2*sGroundOffset + (physicActor->getOnGround() ? sStepSizeDown : 0); + auto dropDistance = 2*sGroundOffset + (actor.mIsOnGround ? sStepSizeDown : 0); osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance); tracer.doTrace(colobj, from, to, collisionWorld); if(tracer.mFraction < 1.0f) @@ -365,7 +364,7 @@ namespace MWPhysics actor.mStandingOn = ptrHolder->getPtr(); if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) - physicActor->setWalkingOnWater(true); + actor.mWalkingOnWater = true; if (!actor.mFlying && !isOnSlope) { if (tracer.mFraction*dropDistance > sGroundOffset) @@ -408,8 +407,8 @@ namespace MWPhysics } physicActor->setInertialForce(inertia); } - physicActor->setOnGround(isOnGround); - physicActor->setOnSlope(isOnSlope); + actor.mIsOnGround = isOnGround; + actor.mIsOnSlope = isOnSlope; actor.mPosition = newPosition; // remove what was added earlier in compensating for doTrace not taking interior transformation into account @@ -426,7 +425,7 @@ namespace MWPhysics void MovementSolver::unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld) { auto* physicActor = actor.mActorRaw; - if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection) // noclipping/tcl + if(actor.mSkipCollisionDetection) // noclipping/tcl return; auto* collisionObject = physicActor->getCollisionObject(); @@ -448,9 +447,9 @@ namespace MWPhysics const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z()); // use a 3d approximation of the movement vector to better judge player intent - auto velocity = (osg::Quat(actor.mRefpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRefpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement; + auto velocity = (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement; // try to pop outside of the world before doing anything else if we're inside of it - if (!physicActor->getOnGround() || physicActor->getOnSlope()) + if (!actor.mIsOnGround || actor.mIsOnSlope) velocity += physicActor->getInertialForce(); // because of the internal collision box offset hack, and the fact that we're moving the collision box manually, diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 81b1dc34d6..eaa1948cba 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -54,7 +54,7 @@ namespace { const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight; - const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mActorRaw->getOnGround()); + const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mIsOnGround); if (isStillOnGround || actorData.mFlying || actorData.mSwimming || actorData.mSlowFall < 1) actorData.mNeedLand = true; @@ -256,7 +256,12 @@ namespace MWPhysics // these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values if (mAdvanceSimulation) + { data.mActorRaw->setStandingOnPtr(data.mStandingOn); + data.mActorRaw->setOnGround(data.mIsOnGround); + data.mActorRaw->setOnSlope(data.mIsOnSlope); + data.mActorRaw->setWalkingOnWater(data.mWalkingOnWater); + } data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt)); } } @@ -555,7 +560,12 @@ namespace MWPhysics actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt)); updateMechanics(actorData); if (mAdvanceSimulation) + { actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn); + actorData.mActorRaw->setOnGround(actorData.mIsOnGround); + actorData.mActorRaw->setOnSlope(actorData.mIsOnSlope); + actorData.mActorRaw->setWalkingOnWater(actorData.mWalkingOnWater); + } } refreshLOSCache(); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 1028c0062e..3130f90bd0 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -950,9 +950,24 @@ namespace MWPhysics ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool waterCollision, float slowFall, float waterlevel) - : mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn), - mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision), mSkipCollisionDetection(actor->skipCollisions()), - mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(actor->velocity()), mPosition(), mRefpos() + : mActor(actor) + , mActorRaw(actor.get()) + , mStandingOn(standingOn) + , mWasOnGround(actor->getOnGround()) + , mIsOnGround(actor->getOnGround()) + , mIsOnSlope(actor->getOnSlope()) + , mDidJump(false) + , mNeedLand(false) + , mWaterCollision(waterCollision) + , mWalkingOnWater(false) + , mSkipCollisionDetection(actor->skipCollisions() || !actor->getCollisionMode()) + , mWaterlevel(waterlevel) + , mSlowFall(slowFall) + , mOldHeight(0) + , mFallHeight(0) + , mMovement(actor->velocity()) + , mPosition() + , mRotation() { const MWBase::World *world = MWBase::Environment::get().getWorld(); const auto ptr = actor->getPtr(); @@ -961,8 +976,7 @@ namespace MWPhysics mWantJump = ptr.getClass().getMovementSettings(ptr).mPosition[2] != 0; auto& stats = ptr.getClass().getCreatureStats(ptr); const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); - mFloatToSurface = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); - mWasOnGround = actor->getOnGround(); + mInert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); } void ActorFrameData::updatePosition(btCollisionWorld* world) @@ -975,7 +989,8 @@ namespace MWPhysics MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition, false); } mOldHeight = mPosition.z(); - mRefpos = mActorRaw->getPtr().getRefData().getPosition(); + const auto rotation = mActorRaw->getPtr().getRefData().getPosition().asRotationVec3(); + mRotation = osg::Vec2f(rotation.x(), rotation.z()); } WorldFrameData::WorldFrameData() diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index ee62ab6ace..59f4de10ea 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -86,11 +86,14 @@ namespace MWPhysics bool mFlying; bool mSwimming; bool mWasOnGround; + bool mIsOnGround; + bool mIsOnSlope; bool mWantJump; bool mDidJump; - bool mFloatToSurface; + bool mInert; bool mNeedLand; bool mWaterCollision; + bool mWalkingOnWater; bool mSkipCollisionDetection; float mWaterlevel; float mSlowFall; @@ -98,7 +101,7 @@ namespace MWPhysics float mFallHeight; osg::Vec3f mMovement; osg::Vec3f mPosition; - ESM::Position mRefpos; + osg::Vec2f mRotation; }; struct WorldFrameData From 51514e44cc137ee362c0e4f2166848fec444a163 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 21 Jul 2021 19:40:22 +0200 Subject: [PATCH 03/12] Handle jump as part of the simulation preparation (inside of PhysicsSystem) instead of inside the simulaiton. For mechanics, we don't care how the jump is handled, just that it will be. --- apps/openmw/mwphysics/movementsolver.cpp | 3 -- apps/openmw/mwphysics/mtphysics.cpp | 28 ------------- apps/openmw/mwphysics/physicssystem.cpp | 50 ++++++++++++++++++++---- apps/openmw/mwphysics/physicssystem.hpp | 2 - 4 files changed, 42 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 76f7a8d5a2..4eb1417cdd 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -168,9 +168,6 @@ namespace MWPhysics if (actor.mMovement.z() > 0 && actor.mInert && actor.mPosition.z() < swimlevel) velocity = osg::Vec3f(0,0,1) * 25; - if (actor.mWantJump) - actor.mDidJump = true; - // Now that we have the effective movement vector, apply wind forces to it if (worldData.mIsInStorm) { diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index eaa1948cba..68a69a5c6d 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -11,7 +11,6 @@ #include "../mwmechanics/movement.hpp" #include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" #include "actor.hpp" #include "contacttestwrapper.h" @@ -62,36 +61,9 @@ namespace 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 &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - 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 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 updateMechanics(MWPhysics::ActorFrameData& actorData) { auto ptr = actorData.mActorRaw->getPtr(); - if (actorData.mDidJump) - handleJump(ptr); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); if (actorData.mNeedLand) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 3130f90bd0..7757b3eb65 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -38,6 +38,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/player.hpp" #include "../mwrender/bulletdebugdraw.hpp" @@ -72,6 +73,36 @@ namespace tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, world); return (tracer.mFraction >= 1.0f); } + + void handleJump(const MWWorld::Ptr &ptr) + { + if (!ptr.getClass().isActor()) + return; + if (ptr.getClass().getMovementSettings(ptr).mPosition[2] == 0) + return; + 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 &gmst = MWBase::Environment::get().getWorld()->getStore().get(); + 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 fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue(); + fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); + ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue); + } + ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; + } + } namespace MWPhysics @@ -755,21 +786,22 @@ namespace MWPhysics std::vector actorsFrameData; actorsFrameData.reserve(mActors.size()); const MWBase::World *world = MWBase::Environment::get().getWorld(); - for (const auto& [ptr, physicActor] : mActors) + for (const auto& [actor, physicActor] : mActors) { - if (!ptr.getClass().isMobile(physicActor->getPtr())) + auto ptr = physicActor->getPtr(); + if (!actor.getClass().isMobile(ptr)) continue; float waterlevel = -std::numeric_limits::max(); - const MWWorld::CellStore *cell = ptr.getCell(); + const MWWorld::CellStore *cell = actor.getCell(); if(cell->getCell()->hasWater()) waterlevel = cell->getWaterLevel(); - const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(physicActor->getPtr()).getMagicEffects(); + const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(ptr).getMagicEffects(); bool waterCollision = false; if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) { - if (physicActor->getCollisionMode() || !world->isUnderwater(ptr.getCell(), osg::Vec3f(ptr.getRefData().getPosition().asVec3()))) + if (physicActor->getCollisionMode() || !world->isUnderwater(actor.getCell(), actor.getRefData().getPosition().asVec3())) waterCollision = true; } @@ -784,6 +816,10 @@ namespace MWPhysics standingOn = physicActor->getStandingOnPtr(); actorsFrameData.emplace_back(physicActor, standingOn, waterCollision, slowFall, waterlevel); + + // if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly. + if (willSimulate) + handleJump(ptr); } return actorsFrameData; } @@ -956,7 +992,6 @@ namespace MWPhysics , mWasOnGround(actor->getOnGround()) , mIsOnGround(actor->getOnGround()) , mIsOnSlope(actor->getOnSlope()) - , mDidJump(false) , mNeedLand(false) , mWaterCollision(waterCollision) , mWalkingOnWater(false) @@ -973,8 +1008,7 @@ namespace MWPhysics const auto ptr = actor->getPtr(); mFlying = world->isFlying(ptr); mSwimming = world->isSwimming(ptr); - mWantJump = ptr.getClass().getMovementSettings(ptr).mPosition[2] != 0; - auto& stats = ptr.getClass().getCreatureStats(ptr); + const auto& stats = ptr.getClass().getCreatureStats(ptr); const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); mInert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 59f4de10ea..e5e1aa0c06 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -88,8 +88,6 @@ namespace MWPhysics bool mWasOnGround; bool mIsOnGround; bool mIsOnSlope; - bool mWantJump; - bool mDidJump; bool mInert; bool mNeedLand; bool mWaterCollision; From 6e51a9a512f360d9eed83b45d045145c6d17d53d Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 21 Jul 2021 23:19:17 +0200 Subject: [PATCH 04/12] Simplify a bit the solver --- apps/openmw/mwphysics/movementsolver.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 4eb1417cdd..fd72ea3220 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -148,7 +148,13 @@ namespace MWPhysics osg::Vec3f inertia = physicActor->getInertialForce(); osg::Vec3f velocity; - if (actor.mPosition.z() < swimlevel || actor.mFlying) + // Dead and paralyzed actors underwater will float to the surface, + // if the CharacterController tells us to do so + if (actor.mMovement.z() > 0 && actor.mInert && actor.mPosition.z() < swimlevel) + { + velocity = osg::Vec3f(0,0,1) * 25; + } + else if (actor.mPosition.z() < swimlevel || actor.mFlying) { velocity = (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement; } @@ -163,11 +169,6 @@ namespace MWPhysics velocity = velocity + inertia; } - // Dead and paralyzed actors underwater will float to the surface, - // if the CharacterController tells us to do so - if (actor.mMovement.z() > 0 && actor.mInert && actor.mPosition.z() < swimlevel) - velocity = osg::Vec3f(0,0,1) * 25; - // Now that we have the effective movement vector, apply wind forces to it if (worldData.mIsInStorm) { @@ -186,7 +187,6 @@ namespace MWPhysics * The initial velocity was set earlier (see above). */ float remainingTime = time; - bool seenGround = actor.mIsOnGround && !actor.mIsOnSlope && !actor.mFlying; int numTimesSlid = 0; osg::Vec3f lastSlideNormal(0,0,1); @@ -196,9 +196,10 @@ namespace MWPhysics for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.0001f; ++iterations) { osg::Vec3f nextpos = newPosition + velocity * remainingTime; + bool underwater = newPosition.z() < swimlevel; // If not able to fly, don't allow to swim up into the air - if(!actor.mFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel) + if(!actor.mFlying && nextpos.z() > swimlevel && underwater) { const osg::Vec3f down(0,0,-1); velocity = reject(velocity, down); @@ -230,8 +231,7 @@ namespace MWPhysics break; } - if (isWalkableSlope(tracer.mPlaneNormal) && !actor.mFlying && newPosition.z() >= swimlevel) - seenGround = true; + bool seenGround = !actor.mFlying && !underwater && ((actor.mIsOnGround && !actor.mIsOnSlope) || isWalkableSlope(tracer.mPlaneNormal)); // We hit something. Check if we can step up. float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z(); From b04c9584105781bb475c01aee5366421a544aa91 Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 22 Jul 2021 00:08:44 +0200 Subject: [PATCH 05/12] Modify the way swimming is handled: - compute the swimming state instead of storing it, it changes as part of the simulation and was not updated, so it was wrong anyway. - store the swim level in ActorFrameData, it is constant per Actor so no need to compute it inside the simulation --- apps/openmw/mwphysics/movementsolver.cpp | 3 +-- apps/openmw/mwphysics/mtphysics.cpp | 9 +++++++-- apps/openmw/mwphysics/physicssystem.cpp | 3 ++- apps/openmw/mwphysics/physicssystem.hpp | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index fd72ea3220..ccb9105147 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -140,8 +140,7 @@ namespace MWPhysics osg::Vec3f halfExtents = physicActor->getHalfExtents(); actor.mPosition.z() += halfExtents.z(); // vanilla-accurate - static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat(); - float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); + float swimlevel = actor.mSwimLevel + halfExtents.z(); ActorTracer tracer; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 68a69a5c6d..de390aee1f 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -49,13 +49,18 @@ namespace bool mCanBeSharedLock; }; + bool isUnderWater(const MWPhysics::ActorFrameData& actorData) + { + return actorData.mPosition.z() < actorData.mSwimLevel; + } + void handleFall(MWPhysics::ActorFrameData& actorData, bool simulationPerformed) { const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight; const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mIsOnGround); - if (isStillOnGround || actorData.mFlying || actorData.mSwimming || actorData.mSlowFall < 1) + if (isStillOnGround || actorData.mFlying || isUnderWater(actorData) || actorData.mSlowFall < 1) actorData.mNeedLand = true; else if (heightDiff < 0) actorData.mFallHeight += heightDiff; @@ -67,7 +72,7 @@ namespace MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); if (actorData.mNeedLand) - stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming)); + stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || isUnderWater(actorData))); else if (actorData.mFallHeight < 0) stats.addToFallHeight(-actorData.mFallHeight); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 7757b3eb65..f67e732751 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1007,10 +1007,11 @@ namespace MWPhysics const MWBase::World *world = MWBase::Environment::get().getWorld(); const auto ptr = actor->getPtr(); mFlying = world->isFlying(ptr); - mSwimming = world->isSwimming(ptr); const auto& stats = ptr.getClass().getCreatureStats(ptr); const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); mInert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); + static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat(); + mSwimLevel = mWaterlevel - (actor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); } void ActorFrameData::updatePosition(btCollisionWorld* world) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index e5e1aa0c06..e4c67ed188 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -84,7 +84,6 @@ namespace MWPhysics Actor* mActorRaw; MWWorld::Ptr mStandingOn; bool mFlying; - bool mSwimming; bool mWasOnGround; bool mIsOnGround; bool mIsOnSlope; @@ -94,6 +93,7 @@ namespace MWPhysics bool mWalkingOnWater; bool mSkipCollisionDetection; float mWaterlevel; + float mSwimLevel; float mSlowFall; float mOldHeight; float mFallHeight; From 9e911cc8b5a6d069ef6f98474debbbcf3efcc84a Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 23 Jul 2021 19:07:01 +0200 Subject: [PATCH 06/12] Introduce helper function to write back updated values inside parent Actor class --- apps/openmw/mwphysics/mtphysics.cpp | 32 +++++++++++++---------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index de390aee1f..be94638301 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -83,6 +83,18 @@ namespace return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); } + void updateActor(MWPhysics::ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) + { + actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, timeAccum, dt)); + if (simulationPerformed) + { + actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn); + actorData.mActorRaw->setOnGround(actorData.mIsOnGround); + actorData.mActorRaw->setOnSlope(actorData.mIsOnSlope); + actorData.mActorRaw->setWalkingOnWater(actorData.mWalkingOnWater); + } + } + namespace Config { /// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading @@ -230,16 +242,7 @@ namespace MWPhysics if (std::any_of(actorsData.begin(), actorsData.end(), actorActive)) { updateMechanics(data); - - // these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values - if (mAdvanceSimulation) - { - data.mActorRaw->setStandingOnPtr(data.mStandingOn); - data.mActorRaw->setOnGround(data.mIsOnGround); - data.mActorRaw->setOnSlope(data.mIsOnSlope); - data.mActorRaw->setWalkingOnWater(data.mWalkingOnWater); - } - data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt)); + updateActor(data, mAdvanceSimulation, mTimeAccum, mPhysicsDt); } } if(mAdvanceSimulation) @@ -534,15 +537,8 @@ namespace MWPhysics for (auto& actorData : mActorsFrameData) { handleFall(actorData, mAdvanceSimulation); - actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt)); updateMechanics(actorData); - if (mAdvanceSimulation) - { - actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn); - actorData.mActorRaw->setOnGround(actorData.mIsOnGround); - actorData.mActorRaw->setOnSlope(actorData.mIsOnSlope); - actorData.mActorRaw->setWalkingOnWater(actorData.mWalkingOnWater); - } + updateActor(actorData, mAdvanceSimulation, mTimeAccum, mPhysicsDt); } refreshLOSCache(); } From f68273c3c05d6f1d71d0334db2acf651ad252a71 Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 22 Jul 2021 19:29:20 +0200 Subject: [PATCH 07/12] Remove Actor* from ActorFrameData --- apps/openmw/mwphysics/movementsolver.cpp | 85 +++++++++++------------- apps/openmw/mwphysics/mtphysics.cpp | 46 +++++++------ apps/openmw/mwphysics/physicssystem.cpp | 19 ++++-- apps/openmw/mwphysics/physicssystem.hpp | 9 ++- 4 files changed, 80 insertions(+), 79 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index ccb9105147..990000b257 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -118,8 +118,6 @@ namespace MWPhysics void MovementSolver::move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData) { - auto* physicActor = actor.mActorRaw; - // Reset per-frame data actor.mWalkingOnWater = false; // Anything to collide with? @@ -131,20 +129,16 @@ namespace MWPhysics return; } - const btCollisionObject *colobj = physicActor->getCollisionObject(); - // Adjust for collision mesh offset relative to actor's "location" // (doTrace doesn't take local/interior collision shape translation into account, so we have to do it on our own) // for compatibility with vanilla assets, we have to derive this from the vertical half extent instead of from internal hull translation // if not for this hack, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() - osg::Vec3f halfExtents = physicActor->getHalfExtents(); - actor.mPosition.z() += halfExtents.z(); // vanilla-accurate + actor.mPosition.z() += actor.mHalfExtentsZ; // vanilla-accurate - float swimlevel = actor.mSwimLevel + halfExtents.z(); + float swimlevel = actor.mSwimLevel + actor.mHalfExtentsZ; ActorTracer tracer; - osg::Vec3f inertia = physicActor->getInertialForce(); osg::Vec3f velocity; // Dead and paralyzed actors underwater will float to the surface, @@ -162,10 +156,10 @@ namespace MWPhysics velocity = (osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement; if ((velocity.z() > 0.f && actor.mIsOnGround && !actor.mIsOnSlope) - || (velocity.z() > 0.f && velocity.z() + inertia.z() <= -velocity.z() && actor.mIsOnSlope)) - inertia = velocity; + || (velocity.z() > 0.f && velocity.z() + actor.mInertia.z() <= -velocity.z() && actor.mIsOnSlope)) + actor.mInertia = velocity; else if (!actor.mIsOnGround || actor.mIsOnSlope) - velocity = velocity + inertia; + velocity = velocity + actor.mInertia; } // Now that we have the effective movement vector, apply wind forces to it @@ -177,7 +171,7 @@ namespace MWPhysics velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f)); } - Stepper stepper(collisionWorld, colobj); + Stepper stepper(collisionWorld, actor.mCollisionObject); osg::Vec3f origVelocity = velocity; osg::Vec3f newPosition = actor.mPosition; /* @@ -209,7 +203,7 @@ namespace MWPhysics if((newPosition - nextpos).length2() > 0.0001) { // trace to where character would go if there were no obstructions - tracer.doTrace(colobj, newPosition, nextpos, collisionWorld); + tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld); // check for obstructions if(tracer.mFraction >= 1.0f) @@ -233,7 +227,7 @@ namespace MWPhysics bool seenGround = !actor.mFlying && !underwater && ((actor.mIsOnGround && !actor.mIsOnSlope) || isWalkableSlope(tracer.mPlaneNormal)); // We hit something. Check if we can step up. - float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z(); + float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + actor.mHalfExtentsZ; osg::Vec3f oldPosition = newPosition; bool usedStepLogic = false; if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject)) @@ -244,9 +238,7 @@ namespace MWPhysics } if (usedStepLogic) { - // don't let pure water creatures move out of water after stepMove - const auto ptr = physicActor->getPtr(); - if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel) + if (actor.mIsAquatic && newPosition.z() + actor.mHalfExtentsZ > actor.mWaterlevel) newPosition = oldPosition; else if(!actor.mFlying && actor.mPosition.z() >= swimlevel) forceGroundTest = true; @@ -305,7 +297,7 @@ namespace MWPhysics // version of surface rejection for acute crevices/seams auto averageNormal = bestNormal + planeNormal; averageNormal.normalize(); - tracer.doTrace(colobj, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld); + tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld); newPosition = (newPosition + tracer.mEndPos)/2.0; usedSeamLogic = true; @@ -321,7 +313,7 @@ namespace MWPhysics // but this is along the collision normal if(!usedSeamLogic && (iterations > 0 || remainingTime < 0.01f)) { - tracer.doTrace(colobj, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld); + tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld); newPosition = (newPosition + tracer.mEndPos)/2.0; } @@ -341,12 +333,12 @@ namespace MWPhysics bool isOnGround = false; bool isOnSlope = false; - if (forceGroundTest || (inertia.z() <= 0.f && newPosition.z() >= swimlevel)) + if (forceGroundTest || (actor.mInertia.z() <= 0.f && newPosition.z() >= swimlevel)) { osg::Vec3f from = newPosition; auto dropDistance = 2*sGroundOffset + (actor.mIsOnGround ? sStepSizeDown : 0); osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance); - tracer.doTrace(colobj, from, to, collisionWorld); + tracer.doTrace(actor.mCollisionObject, from, to, collisionWorld); if(tracer.mFraction < 1.0f) { if (!isActor(tracer.mHitObject)) @@ -368,7 +360,7 @@ namespace MWPhysics else { newPosition.z() = tracer.mEndPos.z(); - tracer.doTrace(colobj, newPosition, newPosition + osg::Vec3f(0, 0, 2*sGroundOffset), collisionWorld); + tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + osg::Vec3f(0, 0, 2*sGroundOffset), collisionWorld); newPosition = (newPosition+tracer.mEndPos)/2.0; } } @@ -383,7 +375,7 @@ namespace MWPhysics } } // forcibly treat stuck actors as if they're on flat ground because buggy collisions when inside of things can/will break ground detection - if(physicActor->getStuckFrames() > 0) + if(actor.mStuckFrames > 0) { isOnGround = true; isOnSlope = false; @@ -391,24 +383,23 @@ namespace MWPhysics } if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying) - physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f)); + actor.mInertia = osg::Vec3f(0.f, 0.f, 0.f); else { - inertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter; - if (inertia.z() < 0) - inertia.z() *= actor.mSlowFall; + actor.mInertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter; + if (actor.mInertia.z() < 0) + actor.mInertia.z() *= actor.mSlowFall; if (actor.mSlowFall < 1.f) { - inertia.x() *= actor.mSlowFall; - inertia.y() *= actor.mSlowFall; + actor.mInertia.x() *= actor.mSlowFall; + actor.mInertia.y() *= actor.mSlowFall; } - physicActor->setInertialForce(inertia); } actor.mIsOnGround = isOnGround; actor.mIsOnSlope = isOnSlope; actor.mPosition = newPosition; // remove what was added earlier in compensating for doTrace not taking interior transformation into account - actor.mPosition.z() -= halfExtents.z(); // vanilla-accurate + actor.mPosition.z() -= actor.mHalfExtentsZ; // vanilla-accurate } btVector3 addMarginToDelta(btVector3 delta) @@ -420,49 +411,47 @@ namespace MWPhysics void MovementSolver::unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld) { - auto* physicActor = actor.mActorRaw; if(actor.mSkipCollisionDetection) // noclipping/tcl return; - auto* collisionObject = physicActor->getCollisionObject(); auto tempPosition = actor.mPosition; - if(physicActor->getStuckFrames() >= 10) + if(actor.mStuckFrames >= 10) { - if((physicActor->getLastStuckPosition() - actor.mPosition).length2() < 100) + if((actor.mLastStuckPosition - actor.mPosition).length2() < 100) return; else { - physicActor->setStuckFrames(0); - physicActor->setLastStuckPosition({0, 0, 0}); + actor.mStuckFrames = 0; + actor.mLastStuckPosition = {0, 0, 0}; } } // use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver) // if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation() - const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z()); + const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, actor.mHalfExtentsZ); // use a 3d approximation of the movement vector to better judge player intent auto velocity = (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement; // try to pop outside of the world before doing anything else if we're inside of it if (!actor.mIsOnGround || actor.mIsOnSlope) - velocity += physicActor->getInertialForce(); + velocity += actor.mInertia; // because of the internal collision box offset hack, and the fact that we're moving the collision box manually, // we need to replicate part of the collision box's transform process from scratch osg::Vec3f refPosition = tempPosition + verticalHalfExtent; osg::Vec3f goodPosition = refPosition; - const btTransform oldTransform = collisionObject->getWorldTransform(); + const btTransform oldTransform = actor.mCollisionObject->getWorldTransform(); btTransform newTransform = oldTransform; auto gatherContacts = [&](btVector3 newOffset) -> ContactCollectionCallback { goodPosition = refPosition + Misc::Convert::toOsg(addMarginToDelta(newOffset)); newTransform.setOrigin(Misc::Convert::toBullet(goodPosition)); - collisionObject->setWorldTransform(newTransform); + actor.mCollisionObject->setWorldTransform(newTransform); - ContactCollectionCallback callback{collisionObject, velocity}; - ContactTestWrapper::contactTest(const_cast(collisionWorld), collisionObject, callback); + ContactCollectionCallback callback{actor.mCollisionObject, velocity}; + ContactTestWrapper::contactTest(const_cast(collisionWorld), actor.mCollisionObject, callback); return callback; }; @@ -470,8 +459,8 @@ namespace MWPhysics auto contactCallback = gatherContacts({0.0, 0.0, 0.0}); if(contactCallback.mDistance < -sAllowedPenetration) { - physicActor->setStuckFrames(physicActor->getStuckFrames() + 1); - physicActor->setLastStuckPosition(actor.mPosition); + ++actor.mStuckFrames; + actor.mLastStuckPosition = actor.mPosition; // we are; try moving it out of the world auto positionDelta = contactCallback.mContactSum; // limit rejection delta to the largest known individual rejections @@ -506,11 +495,11 @@ namespace MWPhysics } else { - physicActor->setStuckFrames(0); - physicActor->setLastStuckPosition({0, 0, 0}); + actor.mStuckFrames = 0; + actor.mLastStuckPosition = {0, 0, 0}; } - collisionObject->setWorldTransform(oldTransform); + actor.mCollisionObject->setWorldTransform(oldTransform); actor.mPosition = tempPosition; } } diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index be94638301..9fc7b08675 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -66,9 +66,9 @@ namespace actorData.mFallHeight += heightDiff; } - void updateMechanics(MWPhysics::ActorFrameData& actorData) + void updateMechanics(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData) { - auto ptr = actorData.mActorRaw->getPtr(); + auto ptr = actor.getPtr(); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); if (actorData.mNeedLand) @@ -77,21 +77,24 @@ namespace stats.addToFallHeight(-actorData.mFallHeight); } - osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) + osg::Vec3f interpolateMovements(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) { const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f); - return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); + return actorData.mPosition * interpolationFactor + actor.getPreviousPosition() * (1.f - interpolationFactor); } - void updateActor(MWPhysics::ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) + void updateActor(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) { - actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, timeAccum, dt)); + actor.setSimulationPosition(interpolateMovements(actor, actorData, timeAccum, dt)); + actor.setLastStuckPosition(actorData.mLastStuckPosition); + actor.setStuckFrames(actorData.mStuckFrames); if (simulationPerformed) { - actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn); - actorData.mActorRaw->setOnGround(actorData.mIsOnGround); - actorData.mActorRaw->setOnSlope(actorData.mIsOnSlope); - actorData.mActorRaw->setWalkingOnWater(actorData.mWalkingOnWater); + actor.setStandingOnPtr(actorData.mStandingOn); + actor.setOnGround(actorData.mIsOnGround); + actor.setOnSlope(actorData.mIsOnSlope); + actor.setWalkingOnWater(actorData.mWalkingOnWater); + actor.setInertialForce(actorData.mInertia); } } @@ -233,16 +236,10 @@ namespace MWPhysics { for (auto& data : mActorsFrameData) { - const auto actorActive = [&data](const auto& newFrameData) -> bool + if (auto actor = data.mActor.lock()) { - const auto actor = data.mActor.lock(); - return actor && actor->getPtr() == newFrameData.mActorRaw->getPtr(); - }; - // Only return actors that are still part of the scene - if (std::any_of(actorsData.begin(), actorsData.end(), actorActive)) - { - updateMechanics(data); - updateActor(data, mAdvanceSimulation, mTimeAccum, mPhysicsDt); + updateMechanics(*actor, data); + updateActor(*actor, data, mAdvanceSimulation, mTimeAccum, mPhysicsDt); } } if(mAdvanceSimulation) @@ -255,7 +252,10 @@ namespace MWPhysics // init for (auto& data : actorsData) - data.updatePosition(mCollisionWorld); + { + assert(data.mActor.lock()); + data.updatePosition(*data.mActor.lock(), mCollisionWorld); + } mPrevStepCount = numSteps; mRemainingSteps = numSteps; mTimeAccum = timeAccum; @@ -536,9 +536,11 @@ namespace MWPhysics for (auto& actorData : mActorsFrameData) { + auto actor = actorData.mActor.lock(); + assert(actor); handleFall(actorData, mAdvanceSimulation); - updateMechanics(actorData); - updateActor(actorData, mAdvanceSimulation, mTimeAccum, mPhysicsDt); + updateMechanics(*actor, actorData); + updateActor(*actor, actorData, mAdvanceSimulation, mTimeAccum, mPhysicsDt); } refreshLOSCache(); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index f67e732751..11fbf74e4b 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -987,7 +987,7 @@ namespace MWPhysics ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool waterCollision, float slowFall, float waterlevel) : mActor(actor) - , mActorRaw(actor.get()) + , mCollisionObject(actor->getCollisionObject()) , mStandingOn(standingOn) , mWasOnGround(actor->getOnGround()) , mIsOnGround(actor->getOnGround()) @@ -1000,6 +1000,7 @@ namespace MWPhysics , mSlowFall(slowFall) , mOldHeight(0) , mFallHeight(0) + , mHalfExtentsZ(actor->getHalfExtents().z()) , mMovement(actor->velocity()) , mPosition() , mRotation() @@ -1007,6 +1008,7 @@ namespace MWPhysics const MWBase::World *world = MWBase::Environment::get().getWorld(); const auto ptr = actor->getPtr(); mFlying = world->isFlying(ptr); + mIsAquatic = ptr.getClass().isPureWaterCreature(ptr); const auto& stats = ptr.getClass().getCreatureStats(ptr); const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); mInert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); @@ -1014,18 +1016,21 @@ namespace MWPhysics mSwimLevel = mWaterlevel - (actor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); } - void ActorFrameData::updatePosition(btCollisionWorld* world) + void ActorFrameData::updatePosition(Actor& actor, btCollisionWorld* world) { - mActorRaw->applyOffsetChange(); - mPosition = mActorRaw->getPosition(); - if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world)) + actor.applyOffsetChange(); + mPosition = actor.getPosition(); + if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(&actor, mWaterlevel, world)) { mPosition.z() = mWaterlevel; - MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition, false); + MWBase::Environment::get().getWorld()->moveObject(actor.getPtr(), mPosition, false); } mOldHeight = mPosition.z(); - const auto rotation = mActorRaw->getPtr().getRefData().getPosition().asRotationVec3(); + const auto rotation = actor.getPtr().getRefData().getPosition().asRotationVec3(); mRotation = osg::Vec2f(rotation.x(), rotation.z()); + mInertia = actor.getInertialForce(); + mStuckFrames = actor.getStuckFrames(); + mLastStuckPosition = actor.getLastStuckPosition(); } WorldFrameData::WorldFrameData() diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index e4c67ed188..52e72c69e8 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -79,9 +79,9 @@ namespace MWPhysics struct ActorFrameData { ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, float slowFall, float waterlevel); - void updatePosition(btCollisionWorld* world); + void updatePosition(Actor& actor, btCollisionWorld* world); std::weak_ptr mActor; - Actor* mActorRaw; + btCollisionObject* mCollisionObject; MWWorld::Ptr mStandingOn; bool mFlying; bool mWasOnGround; @@ -89,17 +89,22 @@ namespace MWPhysics bool mIsOnSlope; bool mInert; bool mNeedLand; + bool mIsAquatic; bool mWaterCollision; bool mWalkingOnWater; bool mSkipCollisionDetection; + unsigned int mStuckFrames; float mWaterlevel; float mSwimLevel; float mSlowFall; float mOldHeight; float mFallHeight; + float mHalfExtentsZ; osg::Vec3f mMovement; osg::Vec3f mPosition; osg::Vec2f mRotation; + osg::Vec3f mInertia; + osg::Vec3f mLastStuckPosition; }; struct WorldFrameData From 26d9052b8ca79210b9898dfcf773602fc387a347 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 23 Jul 2021 20:39:26 +0200 Subject: [PATCH 08/12] Move the weak_ptr outside of ActorFrameData. --- apps/openmw/mwphysics/mtphysics.cpp | 50 +++++++++++++------------ apps/openmw/mwphysics/mtphysics.hpp | 3 +- apps/openmw/mwphysics/physicssystem.cpp | 38 ++++++++++--------- apps/openmw/mwphysics/physicssystem.hpp | 5 +-- 4 files changed, 51 insertions(+), 45 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 9fc7b08675..8c2250c05c 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -222,7 +222,7 @@ namespace MWPhysics return std::make_tuple(numSteps, actualDelta); } - void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector>&& actors, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. @@ -234,12 +234,12 @@ namespace MWPhysics // start by finishing previous background computation if (mNumThreads != 0) { - for (auto& data : mActorsFrameData) + for (size_t i = 0; i < mActors.size(); ++i) { - if (auto actor = data.mActor.lock()) + if (auto actor = mActors[i].lock()) { - updateMechanics(*actor, data); - updateActor(*actor, data, mAdvanceSimulation, mTimeAccum, mPhysicsDt); + updateMechanics(*actor, mActorsFrameData[i]); + updateActor(*actor, mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); } } if(mAdvanceSimulation) @@ -251,15 +251,16 @@ namespace MWPhysics timeAccum -= numSteps*newDelta; // init - for (auto& data : actorsData) + for (size_t i = 0; i < actors.size(); ++i) { - assert(data.mActor.lock()); - data.updatePosition(*data.mActor.lock(), mCollisionWorld); + assert(actors[i].lock()); + actorsData[i].updatePosition(*actors[i].lock(), mCollisionWorld); } mPrevStepCount = numSteps; mRemainingSteps = numSteps; mTimeAccum = timeAccum; mPhysicsDt = newDelta; + mActors = std::move(actors); mActorsFrameData = std::move(actorsData); mAdvanceSimulation = (mRemainingSteps != 0); mNewFrame = true; @@ -463,7 +464,7 @@ namespace MWPhysics int job = 0; while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { - if(const auto actor = mActorsFrameData[job].mActor.lock()) + if(const auto actor = mActors[job].lock()) { MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData); @@ -476,10 +477,9 @@ namespace MWPhysics { while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { - if(const auto actor = mActorsFrameData[job].mActor.lock()) + if(const auto actor = mActors[job].lock()) { - auto& actorData = mActorsFrameData[job]; - handleFall(actorData, mAdvanceSimulation); + handleFall(mActorsFrameData[job], mAdvanceSimulation); } } @@ -491,14 +491,14 @@ namespace MWPhysics void PhysicsTaskScheduler::updateActorsPositions() { - for (auto& actorData : mActorsFrameData) + for (size_t i = 0; i < mActors.size(); ++i) { - if(const auto actor = actorData.mActor.lock()) + if(const auto actor = mActors[i].lock()) { - if (actor->setPosition(actorData.mPosition)) + if (actor->setPosition(mActorsFrameData[i].mPosition)) { std::scoped_lock lock(mCollisionWorldMutex); - actorData.mPosition = actor->getPosition(); // account for potential position change made by script + mActorsFrameData[i].mPosition = actor->getPosition(); // account for potential position change made by script actor->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } @@ -534,13 +534,13 @@ namespace MWPhysics updateActorsPositions(); } - for (auto& actorData : mActorsFrameData) + for (size_t i = 0; i < mActors.size(); ++i) { - auto actor = actorData.mActor.lock(); + auto actor = mActors[i].lock(); assert(actor); - handleFall(actorData, mAdvanceSimulation); - updateMechanics(*actor, actorData); - updateActor(*actor, actorData, mAdvanceSimulation, mTimeAccum, mPhysicsDt); + handleFall(mActorsFrameData[i], mAdvanceSimulation); + updateMechanics(*actor, mActorsFrameData[i]); + updateActor(*actor, mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); } refreshLOSCache(); } @@ -571,12 +571,14 @@ namespace MWPhysics updateAabbs(); if (!mRemainingSteps) return; - for (auto& data : mActorsFrameData) - if (const auto actor = data.mActor.lock()) + for (size_t i = 0; i < mActors.size(); ++i) + { + if (auto actor = mActors[i].lock()) { std::unique_lock lock(mCollisionWorldMutex); - MovementSolver::unstuck(data, mCollisionWorld); + MovementSolver::unstuck(mActorsFrameData[i], mCollisionWorld); } + } } void PhysicsTaskScheduler::afterPostStep() diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 21698e52bd..6c2116f18f 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -38,7 +38,7 @@ namespace MWPhysics /// @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 - void applyQueuedMovements(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + void applyQueuedMovements(float & timeAccum, std::vector>&& actors, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); void resetSimulation(const ActorMap& actors); @@ -71,6 +71,7 @@ namespace MWPhysics void afterPostSim(); std::unique_ptr mWorldFrameData; + std::vector> mActors; std::vector mActorsFrameData; float mDefaultPhysicsDt; float mPhysicsDt; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 11fbf74e4b..c7d19a3b5c 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -781,10 +781,11 @@ namespace MWPhysics actor->setVelocity(osg::Vec3f()); } - std::vector PhysicsSystem::prepareFrameData(bool willSimulate) + std::pair>, std::vector> PhysicsSystem::prepareFrameData(bool willSimulate) { - std::vector actorsFrameData; - actorsFrameData.reserve(mActors.size()); + std::pair>, std::vector> framedata; + framedata.first.reserve(mActors.size()); + framedata.second.reserve(mActors.size()); const MWBase::World *world = MWBase::Environment::get().getWorld(); for (const auto& [actor, physicActor] : mActors) { @@ -815,13 +816,14 @@ namespace MWPhysics if (!willSimulate) standingOn = physicActor->getStandingOnPtr(); - actorsFrameData.emplace_back(physicActor, standingOn, waterCollision, slowFall, waterlevel); + framedata.first.emplace_back(physicActor); + framedata.second.emplace_back(*physicActor, standingOn, waterCollision, slowFall, waterlevel); // if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly. if (willSimulate) handleJump(ptr); } - return actorsFrameData; + return framedata; } void PhysicsSystem::stepSimulation(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) @@ -846,8 +848,11 @@ namespace MWPhysics if (skipSimulation) mTaskScheduler->resetSimulation(mActors); else + { + auto [actors, framedata] = prepareFrameData(mTimeAccum >= mPhysicsDt); // modifies mTimeAccum - mTaskScheduler->applyQueuedMovements(mTimeAccum, prepareFrameData(mTimeAccum >= mPhysicsDt), frameStart, frameNumber, stats); + mTaskScheduler->applyQueuedMovements(mTimeAccum, std::move(actors), std::move(framedata), frameStart, frameNumber, stats); + } } void PhysicsSystem::moveActors() @@ -984,36 +989,35 @@ namespace MWPhysics mDebugDrawer->addCollision(position, normal); } - ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, + ActorFrameData::ActorFrameData(Actor& actor, const MWWorld::Ptr standingOn, bool waterCollision, float slowFall, float waterlevel) - : mActor(actor) - , mCollisionObject(actor->getCollisionObject()) + : mCollisionObject(actor.getCollisionObject()) , mStandingOn(standingOn) - , mWasOnGround(actor->getOnGround()) - , mIsOnGround(actor->getOnGround()) - , mIsOnSlope(actor->getOnSlope()) + , mWasOnGround(actor.getOnGround()) + , mIsOnGround(actor.getOnGround()) + , mIsOnSlope(actor.getOnSlope()) , mNeedLand(false) , mWaterCollision(waterCollision) , mWalkingOnWater(false) - , mSkipCollisionDetection(actor->skipCollisions() || !actor->getCollisionMode()) + , mSkipCollisionDetection(actor.skipCollisions() || !actor.getCollisionMode()) , mWaterlevel(waterlevel) , mSlowFall(slowFall) , mOldHeight(0) , mFallHeight(0) - , mHalfExtentsZ(actor->getHalfExtents().z()) - , mMovement(actor->velocity()) + , mHalfExtentsZ(actor.getHalfExtents().z()) + , mMovement(actor.velocity()) , mPosition() , mRotation() { const MWBase::World *world = MWBase::Environment::get().getWorld(); - const auto ptr = actor->getPtr(); + const auto ptr = actor.getPtr(); mFlying = world->isFlying(ptr); mIsAquatic = ptr.getClass().isPureWaterCreature(ptr); const auto& stats = ptr.getClass().getCreatureStats(ptr); const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); mInert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat(); - mSwimLevel = mWaterlevel - (actor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); + mSwimLevel = mWaterlevel - (actor.getRenderingHalfExtents().z() * 2 * fSwimHeightScale); } void ActorFrameData::updatePosition(Actor& actor, btCollisionWorld* world) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 52e72c69e8..9d0d661d22 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -78,9 +78,8 @@ namespace MWPhysics struct ActorFrameData { - ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, float slowFall, float waterlevel); + ActorFrameData(Actor& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, float slowFall, float waterlevel); void updatePosition(Actor& actor, btCollisionWorld* world); - std::weak_ptr mActor; btCollisionObject* mCollisionObject; MWWorld::Ptr mStandingOn; bool mFlying; @@ -260,7 +259,7 @@ namespace MWPhysics void updateWater(); - std::vector prepareFrameData(bool willSimulate); + std::pair>, std::vector> prepareFrameData(bool willSimulate); osg::ref_ptr mUnrefQueue; From 0c5cf6ec19cf9ee28895031bbee72af707841cc0 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 23 Jul 2021 22:37:55 +0200 Subject: [PATCH 09/12] Store the btCollisionObject* of the object we're standing on instead of MWWorld::Ptr: - they are equivalent - btCollisionObject* is readily available from the simulation, it saves a call to a mutex - btCollisionObject* is smaller --- apps/openmw/mwphysics/movementsolver.cpp | 8 ++--- apps/openmw/mwphysics/mtphysics.cpp | 46 ++++++++++++++++-------- apps/openmw/mwphysics/mtphysics.hpp | 4 +++ apps/openmw/mwphysics/physicssystem.cpp | 12 ++----- apps/openmw/mwphysics/physicssystem.hpp | 4 +-- 5 files changed, 42 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 990000b257..2401f78367 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -345,13 +345,9 @@ namespace MWPhysics { isOnGround = true; isOnSlope = !isWalkableSlope(tracer.mPlaneNormal); + actor.mStandingOn = tracer.mHitObject; - const btCollisionObject* standingOn = tracer.mHitObject; - PtrHolder* ptrHolder = static_cast(standingOn->getUserPointer()); - if (ptrHolder) - actor.mStandingOn = ptrHolder->getPtr(); - - if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) + if (actor.mStandingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water) actor.mWalkingOnWater = true; if (!actor.mFlying && !isOnSlope) { diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 8c2250c05c..41cec6facc 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -83,21 +83,6 @@ namespace return actorData.mPosition * interpolationFactor + actor.getPreviousPosition() * (1.f - interpolationFactor); } - void updateActor(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) - { - actor.setSimulationPosition(interpolateMovements(actor, actorData, timeAccum, dt)); - actor.setLastStuckPosition(actorData.mLastStuckPosition); - actor.setStuckFrames(actorData.mStuckFrames); - if (simulationPerformed) - { - actor.setStandingOnPtr(actorData.mStandingOn); - actor.setOnGround(actorData.mIsOnGround); - actor.setOnSlope(actorData.mIsOnSlope); - actor.setWalkingOnWater(actorData.mWalkingOnWater); - actor.setInertialForce(actorData.mInertia); - } - } - namespace Config { /// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading @@ -357,12 +342,14 @@ namespace MWPhysics void PhysicsTaskScheduler::addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask) { + mCollisionObjects.insert(collisionObject); std::unique_lock lock(mCollisionWorldMutex); mCollisionWorld->addCollisionObject(collisionObject, collisionFilterGroup, collisionFilterMask); } void PhysicsTaskScheduler::removeCollisionObject(btCollisionObject* collisionObject) { + mCollisionObjects.erase(collisionObject); std::unique_lock lock(mCollisionWorldMutex); mCollisionWorld->removeCollisionObject(collisionObject); } @@ -506,6 +493,27 @@ namespace MWPhysics } } + void PhysicsTaskScheduler::updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const + { + actor.setSimulationPosition(interpolateMovements(actor, actorData, timeAccum, dt)); + actor.setLastStuckPosition(actorData.mLastStuckPosition); + actor.setStuckFrames(actorData.mStuckFrames); + if (simulationPerformed) + { + MWWorld::Ptr standingOn; + auto* ptrHolder = static_cast(getUserPointer(actorData.mStandingOn)); + if (ptrHolder) + standingOn = ptrHolder->getPtr(); + actor.setStandingOnPtr(standingOn); + // the "on ground" state of an actor might have been updated by a traceDown, don't overwrite the change + if (actor.getOnGround() == actorData.mWasOnGround) + actor.setOnGround(actorData.mIsOnGround); + actor.setOnSlope(actorData.mIsOnSlope); + actor.setWalkingOnWater(actorData.mWalkingOnWater); + actor.setInertialForce(actorData.mInertia); + } + } + 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 @@ -566,6 +574,14 @@ namespace MWPhysics mDebugDrawer->step(); } + void* PhysicsTaskScheduler::getUserPointer(const btCollisionObject* object) const + { + auto it = mCollisionObjects.find(object); + if (it == mCollisionObjects.end()) + return nullptr; + return (*it)->getUserPointer(); + } + void PhysicsTaskScheduler::afterPreStep() { updateAabbs(); diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 6c2116f18f..e758c557e2 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -55,11 +56,13 @@ namespace MWPhysics void updateSingleAabb(std::weak_ptr ptr, bool immediate=false); bool getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2); void debugDraw(); + void* getUserPointer(const btCollisionObject* object) const; private: void syncComputation(); void worker(); void updateActorsPositions(); + void updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const; bool hasLineOfSight(const Actor* actor1, const Actor* actor2); void refreshLOSCache(); void updateAabbs(); @@ -73,6 +76,7 @@ namespace MWPhysics std::unique_ptr mWorldFrameData; std::vector> mActors; std::vector mActorsFrameData; + std::unordered_set mCollisionObjects; float mDefaultPhysicsDt; float mPhysicsDt; float mTimeAccum; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index c7d19a3b5c..d65231322a 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -811,13 +811,8 @@ namespace MWPhysics // Slow fall reduces fall speed by a factor of (effect magnitude / 200) const float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); - // Ue current value only if we don't advance the simulation. Otherwise we might get a stale value. - MWWorld::Ptr standingOn; - if (!willSimulate) - standingOn = physicActor->getStandingOnPtr(); - framedata.first.emplace_back(physicActor); - framedata.second.emplace_back(*physicActor, standingOn, waterCollision, slowFall, waterlevel); + framedata.second.emplace_back(*physicActor, waterCollision, slowFall, waterlevel); // if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly. if (willSimulate) @@ -989,10 +984,9 @@ namespace MWPhysics mDebugDrawer->addCollision(position, normal); } - ActorFrameData::ActorFrameData(Actor& actor, const MWWorld::Ptr standingOn, - bool waterCollision, float slowFall, float waterlevel) + ActorFrameData::ActorFrameData(Actor& actor, bool waterCollision, float slowFall, float waterlevel) : mCollisionObject(actor.getCollisionObject()) - , mStandingOn(standingOn) + , mStandingOn(nullptr) , mWasOnGround(actor.getOnGround()) , mIsOnGround(actor.getOnGround()) , mIsOnSlope(actor.getOnSlope()) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 9d0d661d22..208e6cc71d 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -78,10 +78,10 @@ namespace MWPhysics struct ActorFrameData { - ActorFrameData(Actor& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, float slowFall, float waterlevel); + ActorFrameData(Actor& actor, bool moveToWaterSurface, float slowFall, float waterlevel); void updatePosition(Actor& actor, btCollisionWorld* world); btCollisionObject* mCollisionObject; - MWWorld::Ptr mStandingOn; + const btCollisionObject* mStandingOn; bool mFlying; bool mWasOnGround; bool mIsOnGround; From bcd6541d3e259e9998b9c43fec10b6bb21788c10 Mon Sep 17 00:00:00 2001 From: fredzio Date: Fri, 23 Jul 2021 23:08:35 +0200 Subject: [PATCH 10/12] Reorganize ActorFrameData members: - constify all read-only variables - order them so that all variables modified as aprt of the simulation fits in one cache line --- apps/openmw/mwphysics/physicssystem.cpp | 42 ++++++++++++------------- apps/openmw/mwphysics/physicssystem.hpp | 38 +++++++++++----------- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index d65231322a..f8862ea400 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -797,7 +797,8 @@ namespace MWPhysics if(cell->getCell()->hasWater()) waterlevel = cell->getWaterLevel(); - const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(ptr).getMagicEffects(); + const auto& stats = ptr.getClass().getCreatureStats(ptr); + const MWMechanics::MagicEffects& effects = stats.getMagicEffects(); bool waterCollision = false; if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) @@ -810,9 +811,11 @@ namespace MWPhysics // Slow fall reduces fall speed by a factor of (effect magnitude / 200) const float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); + const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); + const bool inert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); framedata.first.emplace_back(physicActor); - framedata.second.emplace_back(*physicActor, waterCollision, slowFall, waterlevel); + framedata.second.emplace_back(*physicActor, inert, waterCollision, slowFall, waterlevel); // if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly. if (willSimulate) @@ -984,34 +987,29 @@ namespace MWPhysics mDebugDrawer->addCollision(position, normal); } - ActorFrameData::ActorFrameData(Actor& actor, bool waterCollision, float slowFall, float waterlevel) - : mCollisionObject(actor.getCollisionObject()) + ActorFrameData::ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel) + : mPosition() , mStandingOn(nullptr) - , mWasOnGround(actor.getOnGround()) , mIsOnGround(actor.getOnGround()) , mIsOnSlope(actor.getOnSlope()) - , mNeedLand(false) - , mWaterCollision(waterCollision) , mWalkingOnWater(false) - , mSkipCollisionDetection(actor.skipCollisions() || !actor.getCollisionMode()) - , mWaterlevel(waterlevel) + , mInert(inert) + , mCollisionObject(actor.getCollisionObject()) + , mSwimLevel(waterlevel - (actor.getRenderingHalfExtents().z() * 2 * MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat())) , mSlowFall(slowFall) + , mRotation() + , mMovement(actor.velocity()) + , mWaterlevel(waterlevel) + , mHalfExtentsZ(actor.getHalfExtents().z()) , mOldHeight(0) , mFallHeight(0) - , mHalfExtentsZ(actor.getHalfExtents().z()) - , mMovement(actor.velocity()) - , mPosition() - , mRotation() + , mFlying(MWBase::Environment::get().getWorld()->isFlying(actor.getPtr())) + , mWasOnGround(actor.getOnGround()) + , mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr())) + , mWaterCollision(waterCollision) + , mSkipCollisionDetection(actor.skipCollisions() || !actor.getCollisionMode()) + , mNeedLand(false) { - const MWBase::World *world = MWBase::Environment::get().getWorld(); - const auto ptr = actor.getPtr(); - mFlying = world->isFlying(ptr); - mIsAquatic = ptr.getClass().isPureWaterCreature(ptr); - const auto& stats = ptr.getClass().getCreatureStats(ptr); - const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); - mInert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); - static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat(); - mSwimLevel = mWaterlevel - (actor.getRenderingHalfExtents().z() * 2 * fSwimHeightScale); } void ActorFrameData::updatePosition(Actor& actor, btCollisionWorld* world) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 208e6cc71d..f1b6bc147a 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -78,32 +78,32 @@ namespace MWPhysics struct ActorFrameData { - ActorFrameData(Actor& actor, bool moveToWaterSurface, float slowFall, float waterlevel); + ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel); void updatePosition(Actor& actor, btCollisionWorld* world); - btCollisionObject* mCollisionObject; + osg::Vec3f mPosition; + osg::Vec3f mInertia; const btCollisionObject* mStandingOn; - bool mFlying; - bool mWasOnGround; bool mIsOnGround; bool mIsOnSlope; - bool mInert; - bool mNeedLand; - bool mIsAquatic; - bool mWaterCollision; bool mWalkingOnWater; - bool mSkipCollisionDetection; - unsigned int mStuckFrames; - float mWaterlevel; - float mSwimLevel; - float mSlowFall; + const bool mInert; + btCollisionObject* mCollisionObject; + const float mSwimLevel; + const float mSlowFall; + osg::Vec2f mRotation; + osg::Vec3f mMovement; + osg::Vec3f mLastStuckPosition; + const float mWaterlevel; + const float mHalfExtentsZ; float mOldHeight; float mFallHeight; - float mHalfExtentsZ; - osg::Vec3f mMovement; - osg::Vec3f mPosition; - osg::Vec2f mRotation; - osg::Vec3f mInertia; - osg::Vec3f mLastStuckPosition; + unsigned int mStuckFrames; + const bool mFlying; + const bool mWasOnGround; + const bool mIsAquatic; + const bool mWaterCollision; + const bool mSkipCollisionDetection; + bool mNeedLand; }; struct WorldFrameData From 7dd5e715d727f8ce38aaf2727d01b64388adbbd7 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 24 Jul 2021 16:44:21 +0200 Subject: [PATCH 11/12] Compute and store actor half extents. This is faster for 2 reasons: - half extents changes when actor scale is modified, which is very rare - we don't need to protect them by the actor mutex. --- apps/openmw/mwphysics/actor.cpp | 29 ++++++++++++++--------------- apps/openmw/mwphysics/actor.hpp | 3 ++- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 5416223264..c566c3a1ed 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -21,7 +21,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk) : mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false) - , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents) + , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mOriginalHalfExtents(shape->mCollisionBox.extents) , mVelocity(0,0,0), mStuckFrames(0), mLastStuckPosition{0, 0, 0} , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) @@ -33,7 +33,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic // 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) + if (!ptr.getClass().isNpc() && mOriginalHalfExtents.length2() == 0.f) { if (shape->mCollisionShape) { @@ -43,19 +43,19 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic btVector3 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; + mOriginalHalfExtents.x() = (max[0] - min[0])/2.f; + mOriginalHalfExtents.y() = (max[1] - min[1])/2.f; + mOriginalHalfExtents.z() = (max[2] - min[2])/2.f; - mMeshTranslation = osg::Vec3f(0.f, 0.f, mHalfExtents.z()); + mMeshTranslation = osg::Vec3f(0.f, 0.f, mOriginalHalfExtents.z()); } - if (mHalfExtents.length2() == 0.f) + if (mOriginalHalfExtents.length2() == 0.f) Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\"."; } - mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents))); - mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mHalfExtents.x() - mHalfExtents.y()) < 2.2; + mShape.reset(new btBoxShape(Misc::Convert::toBullet(mOriginalHalfExtents))); + mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mOriginalHalfExtents.x() - mOriginalHalfExtents.y()) < 2.2; mConvexShape = static_cast(mShape.get()); @@ -220,27 +220,26 @@ void Actor::updateScale() mPtr.getClass().adjustScale(mPtr, scaleVec, false); mScale = scaleVec; + mHalfExtents = osg::componentMultiply(mOriginalHalfExtents, scaleVec); scaleVec = osg::Vec3f(scale,scale,scale); mPtr.getClass().adjustScale(mPtr, scaleVec, true); - mRenderingScale = scaleVec; + mRenderingHalfExtents = osg::componentMultiply(mOriginalHalfExtents, scaleVec); } osg::Vec3f Actor::getHalfExtents() const { - std::scoped_lock lock(mPositionMutex); - return osg::componentMultiply(mHalfExtents, mScale); + return mHalfExtents; } osg::Vec3f Actor::getOriginalHalfExtents() const { - return mHalfExtents; + return mOriginalHalfExtents; } osg::Vec3f Actor::getRenderingHalfExtents() const { - std::scoped_lock lock(mPositionMutex); - return osg::componentMultiply(mHalfExtents, mRenderingScale); + return mRenderingHalfExtents; } void Actor::setInertialForce(const osg::Vec3f &force) diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index eb81064937..e52a4caca7 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -191,11 +191,12 @@ namespace MWPhysics std::unique_ptr mCollisionObject; osg::Vec3f mMeshTranslation; + osg::Vec3f mOriginalHalfExtents; osg::Vec3f mHalfExtents; + osg::Vec3f mRenderingHalfExtents; osg::Quat mRotation; osg::Vec3f mScale; - osg::Vec3f mRenderingScale; osg::Vec3f mSimulationPosition; osg::Vec3f mPosition; osg::Vec3f mPreviousPosition; From 5fc3b80406a16930f412d3266bdb9ca8152681a2 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sat, 24 Jul 2021 21:12:02 +0200 Subject: [PATCH 12/12] Don't query actor halfextent in a tight loop, use the already existing variable. These repeated calls become costly with > 150 actors. --- apps/openmw/mwmechanics/actors.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 3669fd9391..c5b46c3764 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1831,7 +1831,7 @@ namespace MWMechanics osg::Vec2f baseSpeed = origMovement * maxSpeed; osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3(); float baseRotZ = ptr.getRefData().getPosition().rot[2]; - osg::Vec3f halfExtents = world->getHalfExtents(ptr); + const osg::Vec3f halfExtents = world->getHalfExtents(ptr); float maxDistToCheck = isMoving ? maxDistForPartialAvoiding : maxDistForStrictAvoiding; float timeToCollision = maxTimeToCheck; @@ -1849,7 +1849,7 @@ namespace MWMechanics if (otherPtr == ptr || otherPtr == currentTarget) continue; - osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr); + const osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr); osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos; osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ); float dist = deltaPos.length(); @@ -1867,7 +1867,7 @@ namespace MWMechanics float rotZ = otherPtr.getRefData().getPosition().rot[2]; osg::Vec2f relSpeed = Misc::rotateVec2f(osg::Vec2f(speed.x(), speed.y()), baseRotZ - rotZ) - baseSpeed; - float collisionDist = minGap + world->getHalfExtents(ptr).x() + world->getHalfExtents(otherPtr).x(); + float collisionDist = minGap + halfExtents.x() + otherHalfExtents.x(); collisionDist = std::min(collisionDist, relPos.length()); // Find the earliest `t` when |relPos + relSpeed * t| == collisionDist.