1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-04-01 22:36:39 +00:00

Dejank movement solver vs animation movement accumulation

This commit is contained in:
Mads Buvik Sandvei 2023-12-01 19:24:20 +01:00
parent 102d2c4b43
commit cedc5289d7
14 changed files with 183 additions and 34 deletions

View file

@ -295,7 +295,7 @@ namespace MWBase
/// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is
/// obstructed). /// 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 ///< Queues movement for \a ptr (in local space), to be applied in the next call to
/// doPhysics. /// doPhysics.

View file

@ -2420,7 +2420,7 @@ namespace MWMechanics
} }
if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) if (!isMovementAnimationControlled() && !isScriptedAnimPlaying())
world->queueMovement(mPtr, vec); world->queueMovement(mPtr, vec, duration);
} }
movement = vec; movement = vec;
@ -2493,7 +2493,7 @@ namespace MWMechanics
} }
// Update movement // Update movement
world->queueMovement(mPtr, movement); world->queueMovement(mPtr, movement, duration);
} }
mSkipAnim = false; mSkipAnim = false;

View file

@ -170,6 +170,8 @@ namespace MWPhysics
} }
// Now that we have the effective movement vector, apply wind forces to it // 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) if (worldData.mIsInStorm && velocity.length() > 0)
{ {
osg::Vec3f stormDirection = worldData.mStormDirection; osg::Vec3f stormDirection = worldData.mStormDirection;

View file

@ -25,6 +25,7 @@
#include "../mwrender/bulletdebugdraw.hpp" #include "../mwrender/bulletdebugdraw.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -35,6 +36,7 @@
#include "object.hpp" #include "object.hpp"
#include "physicssystem.hpp" #include "physicssystem.hpp"
#include "projectile.hpp" #include "projectile.hpp"
#include "ptrholder.hpp"
namespace MWPhysics namespace MWPhysics
{ {
@ -195,6 +197,51 @@ namespace
void operator()(MWPhysics::ProjectileSimulation& /*sim*/) const {} 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 struct PreStep
{ {
btCollisionWorld* mCollisionWorld; btCollisionWorld* mCollisionWorld;
@ -501,18 +548,18 @@ namespace MWPhysics
return std::make_tuple(numSteps, actualDelta); return std::make_tuple(numSteps, actualDelta);
} }
void PhysicsTaskScheduler::applyQueuedMovements(float& timeAccum, std::vector<Simulation>& simulations, void PhysicsTaskScheduler::applyQueuedMovements(float& timeAccum, float simulationTime,
osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) std::vector<Simulation>& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{ {
assert(mSimulations != &simulations); assert(mSimulations != &simulations);
waitForWorkers(); waitForWorkers();
prepareWork(timeAccum, simulations, frameStart, frameNumber, stats); prepareWork(timeAccum, simulationTime, simulations, frameStart, frameNumber, stats);
if (mWorkersSync != nullptr) if (mWorkersSync != nullptr)
mWorkersSync->wakeUpWorkers(); mWorkersSync->wakeUpWorkers();
} }
void PhysicsTaskScheduler::prepareWork(float& timeAccum, std::vector<Simulation>& simulations, void PhysicsTaskScheduler::prepareWork(float& timeAccum, float simulationTime, std::vector<Simulation>& simulations,
osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{ {
// This function run in the main thread. // This function run in the main thread.
@ -522,6 +569,9 @@ namespace MWPhysics
double timeStart = mTimer->tick(); double timeStart = mTimer->tick();
// The simulation time when the movement solving begins.
float simulationTimeStart = simulationTime - timeAccum;
// start by finishing previous background computation // start by finishing previous background computation
if (mNumThreads != 0) if (mNumThreads != 0)
{ {
@ -536,10 +586,15 @@ namespace MWPhysics
timeAccum -= numSteps * newDelta; timeAccum -= numSteps * newDelta;
// init // init
const Visitors::InitPosition vis{ mCollisionWorld }; const Visitors::InitPosition initPositionVisitor{ mCollisionWorld };
for (auto& sim : simulations) 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; mPrevStepCount = numSteps;
mRemainingSteps = numSteps; mRemainingSteps = numSteps;
@ -552,10 +607,11 @@ namespace MWPhysics
mNextJob.store(0, std::memory_order_release); mNextJob.store(0, std::memory_order_release);
if (mAdvanceSimulation) if (mAdvanceSimulation)
{
mNextJobSimTime = simulationTimeStart + (numSteps * newDelta);
mWorldFrameData = std::make_unique<WorldFrameData>(); mWorldFrameData = std::make_unique<WorldFrameData>();
if (mAdvanceSimulation)
mBudgetCursor += 1; mBudgetCursor += 1;
}
if (mNumThreads == 0) if (mNumThreads == 0)
{ {
@ -756,6 +812,7 @@ namespace MWPhysics
mLockingPolicy }; mLockingPolicy };
for (Simulation& sim : *mSimulations) for (Simulation& sim : *mSimulations)
std::visit(vis, sim); std::visit(vis, sim);
mCurrentJobSimTime += mPhysicsDt;
} }
bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2) 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; }), std::remove_if(mLOSCache.begin(), mLOSCache.end(), [](const LOSRequest& req) { return req.mStale; }),
mLOSCache.end()); 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(); mTimeEnd = mTimer->tick();
if (mWorkersSync != nullptr) if (mWorkersSync != nullptr)
mWorkersSync->workIsDone(); mWorkersSync->workIsDone();
@ -878,6 +939,9 @@ namespace MWPhysics
std::visit(vis, sim); std::visit(vis, sim);
mSimulations->clear(); mSimulations->clear();
mSimulations = nullptr; 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 // Attempt to acquire unique lock on mSimulationMutex while not all worker

View file

@ -46,7 +46,7 @@ namespace MWPhysics
/// @param timeAccum accumulated time from previous run to interpolate movements /// @param timeAccum accumulated time from previous run to interpolate movements
/// @param actorsData per actor data needed to compute new positions /// @param actorsData per actor data needed to compute new positions
/// @return new position of each actor /// @return new position of each actor
void applyQueuedMovements(float& timeAccum, std::vector<Simulation>& simulations, osg::Timer_t frameStart, void applyQueuedMovements(float& timeAccum, float simulationTime, std::vector<Simulation>& simulations, osg::Timer_t frameStart,
unsigned int frameNumber, osg::Stats& stats); unsigned int frameNumber, osg::Stats& stats);
void resetSimulation(const ActorMap& actors); void resetSimulation(const ActorMap& actors);
@ -87,7 +87,7 @@ namespace MWPhysics
void afterPostSim(); void afterPostSim();
void syncWithMainThread(); void syncWithMainThread();
void waitForWorkers(); void waitForWorkers();
void prepareWork(float& timeAccum, std::vector<Simulation>& simulations, osg::Timer_t frameStart, void prepareWork(float& timeAccum, float simulationTime, std::vector<Simulation>& simulations, osg::Timer_t frameStart,
unsigned int frameNumber, osg::Stats& stats); unsigned int frameNumber, osg::Stats& stats);
std::unique_ptr<WorldFrameData> mWorldFrameData; std::unique_ptr<WorldFrameData> mWorldFrameData;
@ -96,6 +96,9 @@ namespace MWPhysics
float mDefaultPhysicsDt; float mDefaultPhysicsDt;
float mPhysicsDt; float mPhysicsDt;
float mTimeAccum; float mTimeAccum;
float mNextJobSimTime = 0.f;
float mCurrentJobSimTime = 0.f;
float mPreviousJobSimTime = 0.f;
btCollisionWorld* mCollisionWorld; btCollisionWorld* mCollisionWorld;
MWRender::DebugDrawer* mDebugDrawer; MWRender::DebugDrawer* mDebugDrawer;
std::vector<LOSRequest> mLOSCache; std::vector<LOSRequest> mLOSCache;

View file

@ -43,6 +43,7 @@
#include "../mwrender/bulletdebugdraw.hpp" #include "../mwrender/bulletdebugdraw.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "actor.hpp" #include "actor.hpp"
#include "collisiontype.hpp" #include "collisiontype.hpp"
@ -623,18 +624,19 @@ namespace MWPhysics
return false; 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); ActorMap::iterator found = mActors.find(ptr.mRef);
if (found != mActors.end()) if (found != mActors.end())
found->second->setVelocity(velocity); found->second->queueMovement(velocity, start, start + duration);
} }
void PhysicsSystem::clearQueuedMovement() void PhysicsSystem::clearQueuedMovement()
{ {
for (const auto& [_, actor] : mActors) for (const auto& [_, actor] : mActors)
{ {
actor->setVelocity(osg::Vec3f()); actor->clearMovement();
actor->setInertialForce(osg::Vec3f()); actor->setInertialForce(osg::Vec3f());
} }
} }
@ -722,8 +724,10 @@ namespace MWPhysics
{ {
std::vector<Simulation>& simulations = mSimulations[mSimulationsCounter++ % mSimulations.size()]; std::vector<Simulation>& simulations = mSimulations[mSimulationsCounter++ % mSimulations.size()];
prepareSimulation(mTimeAccum >= mPhysicsDt, simulations); prepareSimulation(mTimeAccum >= mPhysicsDt, simulations);
float simulationTime = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime() + dt;
// modifies mTimeAccum // 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())) ->mValue.getFloat()))
, mSlowFall(slowFall) , mSlowFall(slowFall)
, mRotation() , mRotation()
, mMovement(actor.velocity()) , mMovement()
, mWaterlevel(waterlevel) , mWaterlevel(waterlevel)
, mHalfExtentsZ(actor.getHalfExtents().z()) , mHalfExtentsZ(actor.getHalfExtents().z())
, mOldHeight(0) , mOldHeight(0)
@ -922,7 +926,7 @@ namespace MWPhysics
ProjectileFrameData::ProjectileFrameData(Projectile& projectile) ProjectileFrameData::ProjectileFrameData(Projectile& projectile)
: mPosition(projectile.getPosition()) : mPosition(projectile.getPosition())
, mMovement(projectile.velocity()) , mMovement()
, mCaster(projectile.getCasterCollisionObject()) , mCaster(projectile.getCasterCollisionObject())
, mCollisionObject(projectile.getCollisionObject()) , mCollisionObject(projectile.getCollisionObject())
, mProjectile(&projectile) , mProjectile(&projectile)

View file

@ -245,7 +245,7 @@ namespace MWPhysics
/// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will
/// be overwritten. Valid until the next call to stepSimulation /// 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. /// Clear the queued movements list without applying.
void clearQueuedMovement(); void clearQueuedMovement();

View file

@ -4,6 +4,7 @@
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <utility> #include <utility>
#include <deque>
#include <osg/Vec3d> #include <osg/Vec3d>
@ -13,6 +14,13 @@
namespace MWPhysics 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 class PtrHolder
{ {
public: public:
@ -32,9 +40,10 @@ namespace MWPhysics
btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } 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>& movement() { return mMovement; }
void setSimulationPosition(const osg::Vec3f& position) { mSimulationPosition = position; } void setSimulationPosition(const osg::Vec3f& position) { mSimulationPosition = position; }
@ -53,7 +62,7 @@ namespace MWPhysics
protected: protected:
MWWorld::Ptr mPtr; MWWorld::Ptr mPtr;
std::unique_ptr<btCollisionObject> mCollisionObject; std::unique_ptr<btCollisionObject> mCollisionObject;
osg::Vec3f mVelocity; std::deque<Movement> mMovement;
osg::Vec3f mSimulationPosition; osg::Vec3f mSimulationPosition;
osg::Vec3d mPosition; osg::Vec3d mPosition;
osg::Vec3d mPreviousPosition; osg::Vec3d mPreviousPosition;

View file

@ -50,6 +50,7 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp" #include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority
@ -489,12 +490,21 @@ namespace MWRender
class ResetAccumRootCallback : public SceneUtil::NodeCallback<ResetAccumRootCallback, osg::MatrixTransform*> class ResetAccumRootCallback : public SceneUtil::NodeCallback<ResetAccumRootCallback, osg::MatrixTransform*>
{ {
struct AccumulatedMovement
{
osg::Vec3f mMovement = osg::Vec3f();
float mSimStartTime = 0.f;
float mSimStopTime = 0.f;
};
public: public:
void operator()(osg::MatrixTransform* transform, osg::NodeVisitor* nv) void operator()(osg::MatrixTransform* transform, osg::NodeVisitor* nv)
{ {
osg::Matrix mat = transform->getMatrix(); osg::Matrix mat = transform->getMatrix();
osg::Vec3f position = mat.getTrans(); osg::Vec3f position = mat.getTrans();
position = osg::componentMultiply(mResetAxes, position); position = osg::componentMultiply(mResetAxes, position);
// Add back the offset that the movement solver has not consumed yet
position += computeRemainder();
mat.setTrans(position); mat.setTrans(position);
transform->setMatrix(mat); transform->setMatrix(mat);
@ -509,7 +519,35 @@ namespace MWRender
mResetAxes.z() = accumulate.z() != 0.f ? 0.f : 1.f; 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: private:
std::deque<AccumulatedMovement> mMovement;
osg::Vec3f mResetAxes; osg::Vec3f mResetAxes;
}; };
@ -1129,11 +1167,27 @@ namespace MWRender
return velocity; 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 // Get the difference from the last update, and move the position
osg::Vec3f off = osg::componentMultiply(mAccumCtrl->getTranslation(newtime), mAccumulate); osg::Vec3f offset = osg::componentMultiply(mAccumCtrl->getTranslation(newtime), mAccumulate);
position += off - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), 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) osg::Vec3f Animation::runAnimation(float duration)
@ -1151,6 +1205,7 @@ namespace MWRender
const SceneUtil::TextKeyMap& textkeys = state.mSource->getTextKeys(); const SceneUtil::TextKeyMap& textkeys = state.mSource->getTextKeys();
auto textkey = textkeys.upperBound(state.getTime()); auto textkey = textkeys.upperBound(state.getTime());
bool hasMovement = getVelocity(stateiter->first) > 0.001f;
float timepassed = duration * state.mSpeedMult; float timepassed = duration * state.mSpeedMult;
while (state.mPlaying) while (state.mPlaying)
@ -1161,13 +1216,13 @@ namespace MWRender
if (textkey == textkeys.end() || textkey->first > targetTime) if (textkey == textkeys.end() || textkey->first > targetTime)
{ {
if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) 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)); state.setTime(std::min(targetTime, state.mStopTime));
} }
else else
{ {
if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr())
updatePosition(state.getTime(), textkey->first, movement); updatePosition(state.getTime(), textkey->first, movement, hasMovement);
state.setTime(textkey->first); 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))); osg::Quat(mHeadPitchRadians, osg::Vec3f(1, 0, 0)) * osg::Quat(yaw, osg::Vec3f(0, 0, 1)));
} }
if (mResetAccumRootCallback)
mResetAccumRootCallback->accumulate(movement, duration);
return movement; return movement;
} }

View file

@ -276,6 +276,7 @@ namespace MWRender
float mUpperBodyYawRadians; float mUpperBodyYawRadians;
float mLegsYawRadians; float mLegsYawRadians;
float mBodyPitchRadians; float mBodyPitchRadians;
osg::Vec3f mPreviousPosition;
osg::ref_ptr<RotateController> addRotateController(std::string_view bone); osg::ref_ptr<RotateController> addRotateController(std::string_view bone);
@ -304,7 +305,7 @@ namespace MWRender
/* Updates the position of the accum root node for the given time, and /* Updates the position of the accum root node for the given time, and
* returns the wanted movement vector from the previous time. */ * 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 /* 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 * moving anything, and set the end time to the specified stop marker. If

View file

@ -29,6 +29,10 @@ namespace MWWorld
float getGameTimeScale() const { return mGameTimeScale; } float getGameTimeScale() const { return mGameTimeScale; }
void setGameTimeScale(float scale); // game time to simulation time ratio 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). // Rendering simulation time (summary simulation time of rendering frames since application start).
double getRenderingSimulationTime() const { return mRenderingSimulationTime; } double getRenderingSimulationTime() const { return mRenderingSimulationTime; }
void setRenderingSimulationTime(double t) { mRenderingSimulationTime = t; } void setRenderingSimulationTime(double t) { mRenderingSimulationTime = t; }
@ -70,6 +74,7 @@ namespace MWWorld
float mSimulationTimeScale = 1.0; float mSimulationTimeScale = 1.0;
double mRenderingSimulationTime = 0.0; double mRenderingSimulationTime = 0.0;
double mSimulationTime = 0.0; double mSimulationTime = 0.0;
double mPhysicsSimulationTime = 0.0;
bool mPaused = false; bool mPaused = false;
std::set<std::string, std::less<>> mPausedTags; std::set<std::string, std::less<>> mPausedTags;
}; };

View file

@ -33,6 +33,7 @@
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "../mwworld/manualref.hpp" #include "../mwworld/manualref.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
@ -457,7 +458,8 @@ namespace MWWorld
} }
osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0);
direction.normalize(); direction.normalize();
projectile->setVelocity(direction * speed); float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime();
projectile->queueMovement(direction * speed, start, start + duration);
update(magicBoltState, duration); update(magicBoltState, duration);
@ -485,7 +487,8 @@ namespace MWWorld
projectileState.mVelocity projectileState.mVelocity
-= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; -= 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 // rotation does not work well for throwing projectiles - their roll angle will depend on shooting
// direction. // direction.

View file

@ -1448,9 +1448,9 @@ namespace MWWorld
return placed; 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) void World::updateAnimatedCollisionShape(const Ptr& ptr)

View file

@ -383,7 +383,7 @@ namespace MWWorld
float getMaxActivationDistance() const override; 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 ///< Queues movement for \a ptr (in local space), to be applied in the next call to
/// doPhysics. /// doPhysics.