From 5009b66ef5bccc447603cc0d58592106cf0cd1a0 Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Thu, 7 Oct 2021 21:47:05 +0200 Subject: [PATCH] Use std::variant in the physics simulation for the different types of objects. For now only support only for actors. --- apps/openmw/mwphysics/actor.cpp | 12 ++ apps/openmw/mwphysics/actor.hpp | 3 + apps/openmw/mwphysics/movementsolver.cpp | 2 +- apps/openmw/mwphysics/movementsolver.hpp | 2 +- apps/openmw/mwphysics/mtphysics.cpp | 175 ++++++++++++++++------- apps/openmw/mwphysics/mtphysics.hpp | 8 +- apps/openmw/mwphysics/physicssystem.cpp | 34 ++--- apps/openmw/mwphysics/physicssystem.hpp | 6 +- 8 files changed, 155 insertions(+), 87 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index e140141e32..c7e2308e2f 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -12,6 +12,7 @@ #include "collisiontype.hpp" #include "mtphysics.hpp" +#include "trace.h" #include @@ -303,4 +304,15 @@ osg::Vec3f Actor::velocity() return std::exchange(mVelocity, osg::Vec3f()); } +bool Actor::canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const +{ + const float halfZ = getHalfExtents().z(); + const osg::Vec3f actorPosition = getPosition(); + const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); + const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ); + MWPhysics::ActorTracer tracer; + tracer.doTrace(getCollisionObject(), startingPosition, destinationPosition, world); + return (tracer.mFraction >= 1.0f); +} + } diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 0846401c1d..d2ebd78379 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -12,6 +12,7 @@ class btCollisionShape; class btCollisionObject; +class btCollisionWorld; class btConvexShape; namespace Resource @@ -165,6 +166,8 @@ namespace MWPhysics void setVelocity(osg::Vec3f velocity); osg::Vec3f velocity(); + bool canMoveToWaterSurface(float waterlevel, const btCollisionWorld* world) const; + private: MWWorld::Ptr mStandingOnPtr; /// Removes then re-adds the collision object to the dynamics world diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 44a5391f0d..29810e9085 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -116,7 +116,7 @@ namespace MWPhysics } void MovementSolver::move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, - WorldFrameData& worldData) + const WorldFrameData& worldData) { // Reset per-frame data actor.mWalkingOnWater = false; diff --git a/apps/openmw/mwphysics/movementsolver.hpp b/apps/openmw/mwphysics/movementsolver.hpp index 30733eeec8..837004f232 100644 --- a/apps/openmw/mwphysics/movementsolver.hpp +++ b/apps/openmw/mwphysics/movementsolver.hpp @@ -43,7 +43,7 @@ namespace MWPhysics { public: static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight); - static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData); + static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, const WorldFrameData& worldData); static void unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld); }; } diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index a0dde67c2e..ad11878369 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -111,6 +111,106 @@ namespace return actorData.mPosition * interpolationFactor + actor.getPreviousPosition() * (1.f - interpolationFactor); } + namespace Visitors + { + struct InitPosition + { + const btCollisionWorld* mCollisionWorld; + void operator()(MWPhysics::ActorSimulation& sim) const + { + auto& [actor, frameData] = sim; + actor->applyOffsetChange(); + frameData.mPosition = actor->getPosition(); + if (frameData.mWaterCollision && frameData.mPosition.z() < frameData.mWaterlevel && actor->canMoveToWaterSurface(frameData.mWaterlevel, mCollisionWorld)) + { + frameData.mPosition.z() = frameData.mWaterlevel; + MWBase::Environment::get().getWorld()->moveObject(actor->getPtr(), frameData.mPosition, false); + } + frameData.mOldHeight = frameData.mPosition.z(); + const auto rotation = actor->getPtr().getRefData().getPosition().asRotationVec3(); + frameData.mRotation = osg::Vec2f(rotation.x(), rotation.z()); + frameData.mInertia = actor->getInertialForce(); + frameData.mStuckFrames = actor->getStuckFrames(); + frameData.mLastStuckPosition = actor->getLastStuckPosition(); + } + }; + + struct PreStep + { + btCollisionWorld* mCollisionWorld; + void operator()(MWPhysics::ActorSimulation& sim) const + { + MWPhysics::MovementSolver::unstuck(sim.second, mCollisionWorld); + } + }; + + struct UpdatePosition + { + btCollisionWorld* mCollisionWorld; + void operator()(MWPhysics::ActorSimulation& sim) const + { + auto& [actor, frameData] = sim; + if (actor->setPosition(frameData.mPosition)) + { + frameData.mPosition = actor->getPosition(); // account for potential position change made by script + actor->updateCollisionObjectPosition(); + mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); + } + } + }; + + struct Move + { + const float mPhysicsDt; + const btCollisionWorld* mCollisionWorld; + const MWPhysics::WorldFrameData& mWorldFrameData; + void operator()(MWPhysics::ActorSimulation& sim) const + { + MWPhysics::MovementSolver::move(sim.second, mPhysicsDt, mCollisionWorld, mWorldFrameData); + } + }; + + struct Sync + { + const bool mAdvanceSimulation; + const float mTimeAccum; + const float mPhysicsDt; + const MWPhysics::PhysicsTaskScheduler* scheduler; + void operator()(MWPhysics::ActorSimulation& sim) const + { + auto& [actor, frameData] = sim; + auto ptr = actor->getPtr(); + + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + const float heightDiff = frameData.mPosition.z() - frameData.mOldHeight; + const bool isStillOnGround = (mAdvanceSimulation && frameData.mWasOnGround && frameData.mIsOnGround); + + if (isStillOnGround || frameData.mFlying || isUnderWater(frameData) || frameData.mSlowFall < 1) + stats.land(ptr == MWMechanics::getPlayer() && (frameData.mFlying || isUnderWater(frameData))); + else if (heightDiff < 0) + stats.addToFallHeight(-heightDiff); + + actor->setSimulationPosition(::interpolateMovements(*actor, frameData, mTimeAccum, mPhysicsDt)); + actor->setLastStuckPosition(frameData.mLastStuckPosition); + actor->setStuckFrames(frameData.mStuckFrames); + if (mAdvanceSimulation) + { + MWWorld::Ptr standingOn; + auto* ptrHolder = static_cast(scheduler->getUserPointer(frameData.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() == frameData.mWasOnGround) + actor->setOnGround(frameData.mIsOnGround); + actor->setOnSlope(frameData.mIsOnSlope); + actor->setWalkingOnWater(frameData.mWalkingOnWater); + actor->setInertialForce(frameData.mInertia); + } + } + }; + } + namespace Config { /// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading and user requested more than 1 background threads @@ -235,13 +335,12 @@ namespace MWPhysics return std::make_tuple(numSteps, actualDelta); } - void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector>&& actors, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector&& simulations, 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. MaybeExclusiveLock lock(mSimulationMutex, mNumThreads); - assert(actors.size() == actorsData.size()); double timeStart = mTimer->tick(); @@ -259,19 +358,19 @@ namespace MWPhysics timeAccum -= numSteps*newDelta; // init - for (size_t i = 0; i < actors.size(); ++i) + const Visitors::InitPosition vis{mCollisionWorld}; + for (auto& sim : simulations) { - actorsData[i].updatePosition(*actors[i], mCollisionWorld); + std::visit(vis, sim); } mPrevStepCount = numSteps; mRemainingSteps = numSteps; mTimeAccum = timeAccum; mPhysicsDt = newDelta; - mActors = std::move(actors); - mActorsFrameData = std::move(actorsData); + mSimulations = std::move(simulations); mAdvanceSimulation = (mRemainingSteps != 0); mNewFrame = true; - mNumJobs = mActorsFrameData.size(); + mNumJobs = mSimulations.size(); mNextLOS.store(0, std::memory_order_relaxed); mNextJob.store(0, std::memory_order_release); @@ -301,8 +400,7 @@ namespace MWPhysics MaybeExclusiveLock lock(mSimulationMutex, mNumThreads); mBudget.reset(mDefaultPhysicsDt); mAsyncBudget.reset(0.0f); - mActors.clear(); - mActorsFrameData.clear(); + mSimulations.clear(); for (const auto& [_, actor] : actors) { actor->updatePosition(); @@ -467,47 +565,11 @@ namespace MWPhysics void PhysicsTaskScheduler::updateActorsPositions() { - for (size_t i = 0; i < mActors.size(); ++i) + const Visitors::UpdatePosition vis{mCollisionWorld}; + for (auto& sim : mSimulations) { - if (mActors[i]->setPosition(mActorsFrameData[i].mPosition)) - { - MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); - mActorsFrameData[i].mPosition = mActors[i]->getPosition(); // account for potential position change made by script - mActors[i]->updateCollisionObjectPosition(); - mCollisionWorld->updateSingleAabb(mActors[i]->getCollisionObject()); - } - } - } - - void PhysicsTaskScheduler::updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const - { - auto ptr = actor.getPtr(); - - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight; - const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mIsOnGround); - - if (isStillOnGround || actorData.mFlying || isUnderWater(actorData) || actorData.mSlowFall < 1) - stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || isUnderWater(actorData))); - else if (heightDiff < 0) - stats.addToFallHeight(-heightDiff); - - 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); + MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); + std::visit(vis, sim); } } @@ -532,10 +594,11 @@ namespace MWPhysics { mPreStepBarrier->wait([this] { afterPreStep(); }); int job = 0; + const Visitors::Move vis{mPhysicsDt, mCollisionWorld, *mWorldFrameData}; while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { MaybeLock lockColWorld(mCollisionWorldMutex, mNumThreads); - MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData); + std::visit(vis, mSimulations[job]); } mPostStepBarrier->wait([this] { afterPostStep(); }); @@ -577,7 +640,7 @@ namespace MWPhysics void PhysicsTaskScheduler::releaseSharedStates() { std::scoped_lock lock(mSimulationMutex, mUpdateAabbMutex); - mActors.clear(); + mSimulations.clear(); mUpdateAabb.clear(); } @@ -586,10 +649,11 @@ namespace MWPhysics updateAabbs(); if (!mRemainingSteps) return; - for (size_t i = 0; i < mActors.size(); ++i) + const Visitors::PreStep vis{mCollisionWorld}; + for (auto& sim : mSimulations) { MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); - MovementSolver::unstuck(mActorsFrameData[i], mCollisionWorld); + std::visit(vis, sim); } } @@ -618,7 +682,8 @@ namespace MWPhysics void PhysicsTaskScheduler::syncWithMainThread() { - for (size_t i = 0; i < mActors.size(); ++i) - updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); + const Visitors::Sync vis{mAdvanceSimulation, mTimeAccum, mPhysicsDt, this}; + for (auto& sim : mSimulations) + std::visit(vis, sim); } } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 08997947e4..44330b2cc6 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -39,7 +40,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>&& actors, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + void applyQueuedMovements(float & timeAccum, std::vector&& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); void resetSimulation(const ActorMap& actors); @@ -57,14 +58,12 @@ namespace MWPhysics bool getLineOfSight(const std::shared_ptr& actor1, const std::shared_ptr& actor2); void debugDraw(); void* getUserPointer(const btCollisionObject* object) const; - void releaseSharedStates(); // destroy all objects whose destructor can't be safely called from ~PhysicsTaskScheduler() private: void doSimulation(); 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(); @@ -77,8 +76,7 @@ namespace MWPhysics void syncWithMainThread(); std::unique_ptr mWorldFrameData; - std::vector> mActors; - std::vector mActorsFrameData; + std::vector mSimulations; std::unordered_set mCollisionObjects; float mDefaultPhysicsDt; float mPhysicsDt; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 2a5bcb58bd..f6fc3902e7 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -60,19 +60,6 @@ namespace { - bool canMoveToWaterSurface(const MWPhysics::Actor* physicActor, const float waterlevel, btCollisionWorld* world) - { - if (!physicActor) - return false; - const float halfZ = physicActor->getHalfExtents().z(); - const osg::Vec3f actorPosition = physicActor->getPosition(); - const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ); - const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ); - MWPhysics::ActorTracer tracer; - tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, world); - return (tracer.mFraction >= 1.0f); - } - void handleJump(const MWWorld::Ptr &ptr) { if (!ptr.getClass().isActor()) @@ -386,7 +373,8 @@ namespace MWPhysics bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) { - return ::canMoveToWaterSurface(getActor(actor), waterlevel, mCollisionWorld.get()); + const auto* physactor = getActor(actor); + return physactor && physactor->canMoveToWaterSurface(waterlevel, mCollisionWorld.get()); } osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const @@ -727,11 +715,10 @@ namespace MWPhysics actor->setVelocity(osg::Vec3f()); } - std::pair>, std::vector> PhysicsSystem::prepareFrameData(bool willSimulate) + std::vector PhysicsSystem::prepareSimulation(bool willSimulate) { - std::pair>, std::vector> framedata; - framedata.first.reserve(mActors.size()); - framedata.second.reserve(mActors.size()); + std::vector simulations; + simulations.reserve(mActors.size()); const MWBase::World *world = MWBase::Environment::get().getWorld(); for (const auto& [ref, physicActor] : mActors) { @@ -760,14 +747,13 @@ namespace MWPhysics 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, inert, waterCollision, slowFall, waterlevel); + simulations.emplace_back(ActorSimulation{physicActor, ActorFrameData{*physicActor, inert, waterCollision, slowFall, waterlevel}}); // if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly. if (willSimulate) handleJump(ptr); } - return framedata; + return simulations; } void PhysicsSystem::stepSimulation(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) @@ -793,9 +779,9 @@ namespace MWPhysics mTaskScheduler->resetSimulation(mActors); else { - auto [actors, framedata] = prepareFrameData(mTimeAccum >= mPhysicsDt); + auto simulations = prepareSimulation(mTimeAccum >= mPhysicsDt); // modifies mTimeAccum - mTaskScheduler->applyQueuedMovements(mTimeAccum, std::move(actors), std::move(framedata), frameStart, frameNumber, stats); + mTaskScheduler->applyQueuedMovements(mTimeAccum, std::move(simulations), frameStart, frameNumber, stats); } } @@ -982,7 +968,7 @@ namespace MWPhysics { actor.applyOffsetChange(); mPosition = actor.getPosition(); - if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(&actor, mWaterlevel, world)) + if (mWaterCollision && mPosition.z() < mWaterlevel && actor.canMoveToWaterSurface(mWaterlevel, world)) { mPosition.z() = mWaterlevel; MWBase::Environment::get().getWorld()->moveObject(actor.getPtr(), mPosition, false); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 6ec4ebfda9..be9fa10aa6 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -107,6 +108,9 @@ namespace MWPhysics osg::Vec3f mStormDirection; }; + using ActorSimulation = std::pair, ActorFrameData>; + using Simulation = std::variant; + class PhysicsSystem : public RayCastingInterface { public: @@ -253,7 +257,7 @@ namespace MWPhysics void updateWater(); - std::pair>, std::vector> prepareFrameData(bool willSimulate); + std::vector prepareSimulation(bool willSimulate); std::unique_ptr mBroadphase; std::unique_ptr mCollisionConfiguration;