From cedc5289d785e44bad706133dfc80a988b32fb66 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Fri, 1 Dec 2023 19:24:20 +0100 Subject: [PATCH] Dejank movement solver vs animation movement accumulation --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/character.cpp | 4 +- apps/openmw/mwphysics/movementsolver.cpp | 2 + apps/openmw/mwphysics/mtphysics.cpp | 80 ++++++++++++++++++++--- apps/openmw/mwphysics/mtphysics.hpp | 7 +- apps/openmw/mwphysics/physicssystem.cpp | 16 +++-- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwphysics/ptrholder.hpp | 15 ++++- apps/openmw/mwrender/animation.cpp | 68 +++++++++++++++++-- apps/openmw/mwrender/animation.hpp | 3 +- apps/openmw/mwworld/datetimemanager.hpp | 5 ++ apps/openmw/mwworld/projectilemanager.cpp | 7 +- apps/openmw/mwworld/worldimp.cpp | 4 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 14 files changed, 183 insertions(+), 34 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 14e3b2b3b7..f67b9a0e05 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -295,7 +295,7 @@ namespace MWBase /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is /// obstructed). - virtual void queueMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity) = 0; + virtual void queueMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration) = 0; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 77bc51423e..9a9f62f9ac 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2420,7 +2420,7 @@ namespace MWMechanics } if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) - world->queueMovement(mPtr, vec); + world->queueMovement(mPtr, vec, duration); } movement = vec; @@ -2493,7 +2493,7 @@ namespace MWMechanics } // Update movement - world->queueMovement(mPtr, movement); + world->queueMovement(mPtr, movement, duration); } mSkipAnim = false; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index c0b5014b31..ae958c60c7 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -170,6 +170,8 @@ namespace MWPhysics } // Now that we have the effective movement vector, apply wind forces to it + // TODO: This will cause instability in idle animations and other in-place animations. Should include a flag for + // this when queueing up movement if (worldData.mIsInStorm && velocity.length() > 0) { osg::Vec3f stormDirection = worldData.mStormDirection; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 52b96d9d13..5df9a5c7a9 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -25,6 +25,7 @@ #include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/datetimemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -35,6 +36,7 @@ #include "object.hpp" #include "physicssystem.hpp" #include "projectile.hpp" +#include "ptrholder.hpp" namespace MWPhysics { @@ -195,6 +197,51 @@ namespace void operator()(MWPhysics::ProjectileSimulation& /*sim*/) const {} }; + struct InitMovement + { + float mSteps = 1.f; + float mDelta = 0.f; + float mSimulationTime = 0.f; + + // Returns how the actor or projectile wants to move between startTime and endTime + osg::Vec3f takeMovement(MWPhysics::PtrHolder& actor, float startTime, float endTime) const + { + osg::Vec3f movement = osg::Vec3f(); + auto it = actor.movement().begin(); + while (it != actor.movement().end()) + { + float start = std::max(it->simulationTimeStart, startTime); + float stop = std::min(it->simulationTimeStop, endTime); + movement += it->velocity * (stop - start); + if (std::abs(stop - it->simulationTimeStop) < 0.0001f) + it = actor.movement().erase(it); + else + it++; + } + + return movement; + } + + void operator()(auto& sim) const + { + if (mSteps == 0 || mDelta < 0.00001f) + return; + + auto locked = sim.lock(); + if (!locked.has_value()) + return; + + auto& [ptrHolder, frameDataRef] = *locked; + + // Because takeMovement() returns movement instead of velocity, convert it back to velocity for the + // movement solver + osg::Vec3f velocity + = takeMovement(*ptrHolder, mSimulationTime, mSimulationTime + mDelta * mSteps) / (mSteps * mDelta); + + frameDataRef.get().mMovement += velocity; + } + }; + struct PreStep { btCollisionWorld* mCollisionWorld; @@ -501,18 +548,18 @@ namespace MWPhysics return std::make_tuple(numSteps, actualDelta); } - void PhysicsTaskScheduler::applyQueuedMovements(float& timeAccum, std::vector& simulations, - osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + void PhysicsTaskScheduler::applyQueuedMovements(float& timeAccum, float simulationTime, + std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { assert(mSimulations != &simulations); waitForWorkers(); - prepareWork(timeAccum, simulations, frameStart, frameNumber, stats); + prepareWork(timeAccum, simulationTime, simulations, frameStart, frameNumber, stats); if (mWorkersSync != nullptr) mWorkersSync->wakeUpWorkers(); } - void PhysicsTaskScheduler::prepareWork(float& timeAccum, std::vector& simulations, + void PhysicsTaskScheduler::prepareWork(float& timeAccum, float simulationTime, std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. @@ -522,6 +569,9 @@ namespace MWPhysics double timeStart = mTimer->tick(); + // The simulation time when the movement solving begins. + float simulationTimeStart = simulationTime - timeAccum; + // start by finishing previous background computation if (mNumThreads != 0) { @@ -536,10 +586,15 @@ namespace MWPhysics timeAccum -= numSteps * newDelta; // init - const Visitors::InitPosition vis{ mCollisionWorld }; + const Visitors::InitPosition initPositionVisitor{ mCollisionWorld }; for (auto& sim : simulations) { - std::visit(vis, sim); + std::visit(initPositionVisitor, sim); + } + const Visitors::InitMovement initMovementVisitor{ numSteps, newDelta, simulationTimeStart }; + for (auto& sim : simulations) + { + std::visit(initMovementVisitor, sim); } mPrevStepCount = numSteps; mRemainingSteps = numSteps; @@ -552,10 +607,11 @@ namespace MWPhysics mNextJob.store(0, std::memory_order_release); if (mAdvanceSimulation) + { + mNextJobSimTime = simulationTimeStart + (numSteps * newDelta); mWorldFrameData = std::make_unique(); - - if (mAdvanceSimulation) mBudgetCursor += 1; + } if (mNumThreads == 0) { @@ -756,6 +812,7 @@ namespace MWPhysics mLockingPolicy }; for (Simulation& sim : *mSimulations) std::visit(vis, sim); + mCurrentJobSimTime += mPhysicsDt; } bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2) @@ -864,6 +921,10 @@ namespace MWPhysics std::remove_if(mLOSCache.begin(), mLOSCache.end(), [](const LOSRequest& req) { return req.mStale; }), mLOSCache.end()); } + + // On paper, mCurrentJobSimTime should have added up to mNextJobSimTime already + // But to avoid accumulating floating point errors, assign this anyway. + mCurrentJobSimTime = mNextJobSimTime; mTimeEnd = mTimer->tick(); if (mWorkersSync != nullptr) mWorkersSync->workIsDone(); @@ -878,6 +939,9 @@ namespace MWPhysics std::visit(vis, sim); mSimulations->clear(); mSimulations = nullptr; + const float interpolationFactor = std::clamp(mTimeAccum / mPhysicsDt, 0.0f, 1.0f); + MWBase::Environment::get().getWorld()->getTimeManager()->setPhysicsSimulationTime( + mCurrentJobSimTime - mPhysicsDt * (1.f - interpolationFactor)); } // Attempt to acquire unique lock on mSimulationMutex while not all worker diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 57f3711096..d2eb320ea4 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -46,7 +46,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& simulations, osg::Timer_t frameStart, + void applyQueuedMovements(float& timeAccum, float simulationTime, std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); void resetSimulation(const ActorMap& actors); @@ -87,7 +87,7 @@ namespace MWPhysics void afterPostSim(); void syncWithMainThread(); void waitForWorkers(); - void prepareWork(float& timeAccum, std::vector& simulations, osg::Timer_t frameStart, + void prepareWork(float& timeAccum, float simulationTime, std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); std::unique_ptr mWorldFrameData; @@ -96,6 +96,9 @@ namespace MWPhysics float mDefaultPhysicsDt; float mPhysicsDt; float mTimeAccum; + float mNextJobSimTime = 0.f; + float mCurrentJobSimTime = 0.f; + float mPreviousJobSimTime = 0.f; btCollisionWorld* mCollisionWorld; MWRender::DebugDrawer* mDebugDrawer; std::vector mLOSCache; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 2196834a50..3c451497e1 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -43,6 +43,7 @@ #include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/datetimemanager.hpp" #include "actor.hpp" #include "collisiontype.hpp" @@ -623,18 +624,19 @@ namespace MWPhysics return false; } - void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity) + void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration) { + float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); ActorMap::iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) - found->second->setVelocity(velocity); + found->second->queueMovement(velocity, start, start + duration); } void PhysicsSystem::clearQueuedMovement() { for (const auto& [_, actor] : mActors) { - actor->setVelocity(osg::Vec3f()); + actor->clearMovement(); actor->setInertialForce(osg::Vec3f()); } } @@ -722,8 +724,10 @@ namespace MWPhysics { std::vector& simulations = mSimulations[mSimulationsCounter++ % mSimulations.size()]; prepareSimulation(mTimeAccum >= mPhysicsDt, simulations); + float simulationTime = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime() + dt; // modifies mTimeAccum - mTaskScheduler->applyQueuedMovements(mTimeAccum, simulations, frameStart, frameNumber, stats); + mTaskScheduler->applyQueuedMovements( + mTimeAccum, simulationTime, simulations, frameStart, frameNumber, stats); } } @@ -907,7 +911,7 @@ namespace MWPhysics ->mValue.getFloat())) , mSlowFall(slowFall) , mRotation() - , mMovement(actor.velocity()) + , mMovement() , mWaterlevel(waterlevel) , mHalfExtentsZ(actor.getHalfExtents().z()) , mOldHeight(0) @@ -922,7 +926,7 @@ namespace MWPhysics ProjectileFrameData::ProjectileFrameData(Projectile& projectile) : mPosition(projectile.getPosition()) - , mMovement(projectile.velocity()) + , mMovement() , mCaster(projectile.getCasterCollisionObject()) , mCollisionObject(projectile.getCollisionObject()) , mProjectile(&projectile) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index ad56581eb3..3babdef9aa 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -245,7 +245,7 @@ namespace MWPhysics /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to stepSimulation - void queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity); + void queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration); /// Clear the queued movements list without applying. void clearQueuedMovement(); diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index fc8fd94c30..270d2af37b 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -13,6 +14,13 @@ namespace MWPhysics { + struct Movement + { + osg::Vec3f velocity = osg::Vec3f(); + float simulationTimeStart = 0.f; // The time at which this movement begun + float simulationTimeStop = 0.f; // The time at which this movement finished + }; + class PtrHolder { public: @@ -32,9 +40,10 @@ namespace MWPhysics btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } - void setVelocity(osg::Vec3f velocity) { mVelocity = velocity; } + void clearMovement() { mMovement = { }; } + void queueMovement(osg::Vec3f velocity, float simulationTimeStart, float simulationTimeStop) { mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop }); } - osg::Vec3f velocity() { return std::exchange(mVelocity, osg::Vec3f()); } + std::deque& movement() { return mMovement; } void setSimulationPosition(const osg::Vec3f& position) { mSimulationPosition = position; } @@ -53,7 +62,7 @@ namespace MWPhysics protected: MWWorld::Ptr mPtr; std::unique_ptr mCollisionObject; - osg::Vec3f mVelocity; + std::deque mMovement; osg::Vec3f mSimulationPosition; osg::Vec3d mPosition; osg::Vec3d mPreviousPosition; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 5b0e1f82bd..b9d6759dc7 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -50,6 +50,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/datetimemanager.hpp" #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority @@ -489,12 +490,21 @@ namespace MWRender class ResetAccumRootCallback : public SceneUtil::NodeCallback { + struct AccumulatedMovement + { + osg::Vec3f mMovement = osg::Vec3f(); + float mSimStartTime = 0.f; + float mSimStopTime = 0.f; + }; + public: void operator()(osg::MatrixTransform* transform, osg::NodeVisitor* nv) { osg::Matrix mat = transform->getMatrix(); osg::Vec3f position = mat.getTrans(); position = osg::componentMultiply(mResetAxes, position); + // Add back the offset that the movement solver has not consumed yet + position += computeRemainder(); mat.setTrans(position); transform->setMatrix(mat); @@ -509,7 +519,35 @@ namespace MWRender mResetAxes.z() = accumulate.z() != 0.f ? 0.f : 1.f; } + void accumulate(const osg::Vec3f& movement, float dt) + { + if (dt < 0.00001f) + return; + + float simTime = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); + mMovement.emplace_back(AccumulatedMovement{ movement, simTime, simTime + dt }); + } + + const osg::Vec3f computeRemainder() + { + float physSimTime = MWBase::Environment::get().getWorld()->getTimeManager()->getPhysicsSimulationTime(); + // Start by erasing all movement that has been fully consumed by the physics code + std::erase_if(mMovement, + [physSimTime](const AccumulatedMovement& movement) { return movement.mSimStopTime <= physSimTime; }); + + // Accumulate all the movement that hasn't been consumed. + osg::Vec3f movement; + for (const auto& m : mMovement) + { + float startTime = std::max(physSimTime, m.mSimStartTime); + float fraction = (m.mSimStopTime - startTime) / (m.mSimStopTime - m.mSimStartTime); + movement += m.mMovement * fraction; + } + return movement; + } + private: + std::deque mMovement; osg::Vec3f mResetAxes; }; @@ -1129,11 +1167,27 @@ namespace MWRender return velocity; } - void Animation::updatePosition(float oldtime, float newtime, osg::Vec3f& position) + void Animation::updatePosition(float oldtime, float newtime, osg::Vec3f& position, bool hasMovement) { // Get the difference from the last update, and move the position - osg::Vec3f off = osg::componentMultiply(mAccumCtrl->getTranslation(newtime), mAccumulate); - position += off - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), mAccumulate); + osg::Vec3f offset = osg::componentMultiply(mAccumCtrl->getTranslation(newtime), mAccumulate); + if (!hasMovement) + { + // When animations have no velocity, the character should have zero net movement through a complete loop or + // animation sequence. Although any subsequence of the animation may move. This works because each sequence + // starts and stops with Bip01 at the same position, totaling 0 movement. This allows us to accurately move + // the character by just moving it from the position Bip01 was last frame to where it is this frame, without + // needing to accumulate anything in-between. + position += offset - mPreviousPosition; + mPreviousPosition = offset; + } + else + { + // When animations have velocity, net movement is expected. The above block would negate that movement every time + // the animation resets. Therefore we have to accumulate from oldtime to newtime instead, which works because + // oldtime < newtime is a guarantee even when the animation has looped. + position += offset - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), mAccumulate); + } } osg::Vec3f Animation::runAnimation(float duration) @@ -1151,6 +1205,7 @@ namespace MWRender const SceneUtil::TextKeyMap& textkeys = state.mSource->getTextKeys(); auto textkey = textkeys.upperBound(state.getTime()); + bool hasMovement = getVelocity(stateiter->first) > 0.001f; float timepassed = duration * state.mSpeedMult; while (state.mPlaying) @@ -1161,13 +1216,13 @@ namespace MWRender if (textkey == textkeys.end() || textkey->first > targetTime) { if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) - updatePosition(state.getTime(), targetTime, movement); + updatePosition(state.getTime(), targetTime, movement, hasMovement); state.setTime(std::min(targetTime, state.mStopTime)); } else { if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) - updatePosition(state.getTime(), textkey->first, movement); + updatePosition(state.getTime(), textkey->first, movement, hasMovement); state.setTime(textkey->first); } @@ -1249,6 +1304,9 @@ namespace MWRender osg::Quat(mHeadPitchRadians, osg::Vec3f(1, 0, 0)) * osg::Quat(yaw, osg::Vec3f(0, 0, 1))); } + + if (mResetAccumRootCallback) + mResetAccumRootCallback->accumulate(movement, duration); return movement; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 24366889c4..990910fc50 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -276,6 +276,7 @@ namespace MWRender float mUpperBodyYawRadians; float mLegsYawRadians; float mBodyPitchRadians; + osg::Vec3f mPreviousPosition; osg::ref_ptr addRotateController(std::string_view bone); @@ -304,7 +305,7 @@ namespace MWRender /* Updates the position of the accum root node for the given time, and * returns the wanted movement vector from the previous time. */ - void updatePosition(float oldtime, float newtime, osg::Vec3f& position); + void updatePosition(float oldtime, float newtime, osg::Vec3f& position, bool hasMovement); /* Resets the animation to the time of the specified start marker, without * moving anything, and set the end time to the specified stop marker. If diff --git a/apps/openmw/mwworld/datetimemanager.hpp b/apps/openmw/mwworld/datetimemanager.hpp index af62d9ba3f..20d968f4ea 100644 --- a/apps/openmw/mwworld/datetimemanager.hpp +++ b/apps/openmw/mwworld/datetimemanager.hpp @@ -29,6 +29,10 @@ namespace MWWorld float getGameTimeScale() const { return mGameTimeScale; } void setGameTimeScale(float scale); // game time to simulation time ratio + // Physics simulation time + double getPhysicsSimulationTime() const { return mPhysicsSimulationTime; } + void setPhysicsSimulationTime(double t) { mPhysicsSimulationTime = t; } + // Rendering simulation time (summary simulation time of rendering frames since application start). double getRenderingSimulationTime() const { return mRenderingSimulationTime; } void setRenderingSimulationTime(double t) { mRenderingSimulationTime = t; } @@ -70,6 +74,7 @@ namespace MWWorld float mSimulationTimeScale = 1.0; double mRenderingSimulationTime = 0.0; double mSimulationTime = 0.0; + double mPhysicsSimulationTime = 0.0; bool mPaused = false; std::set> mPausedTags; }; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index d873f16a59..3496b5dab5 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -33,6 +33,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/manualref.hpp" +#include "../mwworld/datetimemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" @@ -457,7 +458,8 @@ namespace MWWorld } osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); direction.normalize(); - projectile->setVelocity(direction * speed); + float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); + projectile->queueMovement(direction * speed, start, start + duration); update(magicBoltState, duration); @@ -485,7 +487,8 @@ namespace MWWorld projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; - projectile->setVelocity(projectileState.mVelocity); + float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); + projectile->queueMovement(projectileState.mVelocity, start, start + duration); // rotation does not work well for throwing projectiles - their roll angle will depend on shooting // direction. diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d945aa5848..9db72d1604 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1448,9 +1448,9 @@ namespace MWWorld return placed; } - void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity) + void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration) { - mPhysics->queueObjectMovement(ptr, velocity); + mPhysics->queueObjectMovement(ptr, velocity, duration); } void World::updateAnimatedCollisionShape(const Ptr& ptr) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4b9a0ccb98..486e240fdc 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -383,7 +383,7 @@ namespace MWWorld float getMaxActivationDistance() const override; - void queueMovement(const Ptr& ptr, const osg::Vec3f& velocity) override; + void queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration) override; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics.