From e5fa457fe7bcd4d346b8d10c33e4ee6601bac37b Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 1 Nov 2020 17:14:59 +0100 Subject: [PATCH] Properly account for interleaved move of actors. Before this change, if an actor position was changed while the physics simulation was running, the simulation result would be discarded. It is fine in case of one off event such as teleport, but in the case of scripts making use of this functionality to make lifts or conveyor (such as Sotha Sil Expanded mod) it broke actor movement. To alleviate this issue, at the end of the simulation, the position of the Actor in the world is compared to the position it had at the beginning of the simulation. A difference indicate a force move occured. In this case, the Actor mPosition and mPreviousPosition are translated by the difference of position. Since the Actor position will be really set while the next simulation runs, we save it in the mNextPosition field. --- apps/openmw/mwphysics/actor.cpp | 90 ++++++++----------- apps/openmw/mwphysics/actor.hpp | 19 ++-- apps/openmw/mwphysics/mtphysics.cpp | 53 +++++------ apps/openmw/mwphysics/mtphysics.hpp | 1 - apps/openmw/mwphysics/physicssystem.cpp | 7 +- apps/openmw/mwphysics/physicssystem.hpp | 3 +- .../mwscript/transformationextensions.cpp | 6 +- apps/openmw/mwworld/worldimp.cpp | 6 +- 8 files changed, 91 insertions(+), 94 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 5caaba5c9..760e21cce 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -75,9 +75,10 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic updateRotation(); updateScale(); updatePosition(); + setPosition(mWorldPosition, true); addCollisionMask(getCollisionMask()); - commitPositionChange(); + updateCollisionObjectPosition(); } Actor::~Actor() @@ -122,88 +123,77 @@ int Actor::getCollisionMask() const void Actor::updatePosition() { - std::unique_lock lock(mPositionMutex); - osg::Vec3f position = mPtr.getRefData().getPosition().asVec3(); + std::scoped_lock lock(mPositionMutex); + mWorldPosition = mPtr.getRefData().getPosition().asVec3(); +} - mPosition = position; - mPreviousPosition = position; +osg::Vec3f Actor::getWorldPosition() const +{ + std::scoped_lock lock(mPositionMutex); + return mWorldPosition; +} - mTransformUpdatePending = true; - updateCollisionObjectPosition(); +void Actor::setNextPosition(const osg::Vec3f& position) +{ + mNextPosition = position; +} + +osg::Vec3f Actor::getNextPosition() const +{ + return mNextPosition; } void Actor::updateCollisionObjectPosition() { + std::scoped_lock lock(mPositionMutex); + mShape->setLocalScaling(Misc::Convert::toBullet(mScale)); osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale); osg::Vec3f newPosition = scaledTranslation + mPosition; mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition)); mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation)); - -} - -void Actor::commitPositionChange() -{ - std::unique_lock lock(mPositionMutex); - if (mScaleUpdatePending) - { - mShape->setLocalScaling(Misc::Convert::toBullet(mScale)); - mScaleUpdatePending = false; - } - if (mTransformUpdatePending) - { - mCollisionObject->setWorldTransform(mLocalTransform); - mTransformUpdatePending = false; - } + mCollisionObject->setWorldTransform(mLocalTransform); } osg::Vec3f Actor::getCollisionObjectPosition() const { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mPositionMutex); return Misc::Convert::toOsg(mLocalTransform.getOrigin()); } -void Actor::setPosition(const osg::Vec3f &position, bool updateCollisionObject) +void Actor::setPosition(const osg::Vec3f& position, bool reset) { - std::unique_lock lock(mPositionMutex); - if (mTransformUpdatePending) + if (reset) { - mCollisionObject->setWorldTransform(mLocalTransform); - mTransformUpdatePending = false; + mPreviousPosition = position; + mNextPosition = position; } else - { mPreviousPosition = mPosition; + mPosition = position; +} - mPosition = position; - if (updateCollisionObject) - { - updateCollisionObjectPosition(); - mCollisionObject->setWorldTransform(mLocalTransform); - } - } +void Actor::adjustPosition(const osg::Vec3f& offset) +{ + mPosition += offset; + mPreviousPosition += offset; } osg::Vec3f Actor::getPosition() const { - std::unique_lock lock(mPositionMutex); return mPosition; } osg::Vec3f Actor::getPreviousPosition() const { - std::unique_lock lock(mPositionMutex); return mPreviousPosition; } void Actor::updateRotation () { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mPositionMutex); if (mRotation == mPtr.getRefData().getBaseNode()->getAttitude()) return; mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); - - mTransformUpdatePending = true; - updateCollisionObjectPosition(); } bool Actor::isRotationallyInvariant() const @@ -213,37 +203,33 @@ bool Actor::isRotationallyInvariant() const void Actor::updateScale() { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mPositionMutex); float scale = mPtr.getCellRef().getScale(); osg::Vec3f scaleVec(scale,scale,scale); mPtr.getClass().adjustScale(mPtr, scaleVec, false); mScale = scaleVec; - mScaleUpdatePending = true; scaleVec = osg::Vec3f(scale,scale,scale); mPtr.getClass().adjustScale(mPtr, scaleVec, true); mRenderingScale = scaleVec; - - mTransformUpdatePending = true; - updateCollisionObjectPosition(); } osg::Vec3f Actor::getHalfExtents() const { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mPositionMutex); return osg::componentMultiply(mHalfExtents, mScale); } osg::Vec3f Actor::getOriginalHalfExtents() const { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mPositionMutex); return mHalfExtents; } osg::Vec3f Actor::getRenderingHalfExtents() const { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mPositionMutex); return osg::componentMultiply(mHalfExtents, mRenderingScale); } @@ -274,7 +260,7 @@ void Actor::setWalkingOnWater(bool walkingOnWater) void Actor::setCanWaterWalk(bool waterWalk) { - std::unique_lock lock(mPositionMutex); + std::scoped_lock lock(mPositionMutex); if (waterWalk != mCanWaterWalk) { mCanWaterWalk = waterWalk; diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index ef7b368b9..00ba162af 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -57,13 +57,20 @@ namespace MWPhysics bool isRotationallyInvariant() const; /** - * Set mPosition and mPreviousPosition to the position in the Ptr's RefData. This should be used + * Set mWorldPosition to the position in the Ptr's RefData. This is used by the physics simulation to account for * when an object is "instantly" moved/teleported as opposed to being moved by the physics simulation. */ void updatePosition(); + osg::Vec3f getWorldPosition() const; + + /** + * Used by the physics simulation to store the simulation result. Used in conjunction with mWorldPosition + * to account for e.g. scripted movements + */ + void setNextPosition(const osg::Vec3f& position); + osg::Vec3f getNextPosition() const; void updateCollisionObjectPosition(); - void commitPositionChange(); /** * Returns the half extents of the collision body (scaled according to collision scale) @@ -83,9 +90,9 @@ namespace MWPhysics /** * Store the current position into mPreviousPosition, then move to this position. - * Optionally, inform the physics engine about the change of position. */ - void setPosition(const osg::Vec3f& position, bool updateCollisionObject=true); + void setPosition(const osg::Vec3f& position, bool reset=false); + void adjustPosition(const osg::Vec3f& offset); osg::Vec3f getPosition() const; @@ -159,11 +166,11 @@ namespace MWPhysics osg::Vec3f mScale; osg::Vec3f mRenderingScale; + osg::Vec3f mWorldPosition; + osg::Vec3f mNextPosition; osg::Vec3f mPosition; osg::Vec3f mPreviousPosition; btTransform mLocalTransform; - bool mScaleUpdatePending; - bool mTransformUpdatePending; mutable std::mutex mPositionMutex; osg::Vec3f mForce; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index f105efce5..1b99b4c3f 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -102,9 +102,18 @@ namespace stats.addToFallHeight(-actorData.mFallHeight); } - osg::Vec3f interpolateMovements(const MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) + osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) { const float interpolationFactor = timeAccum / physicsDt; + + // account for force change of actor's position in the main thread + const auto correction = actorData.mActorRaw->getWorldPosition() - actorData.mOrigin; + if (correction.length() != 0) + { + actorData.mActorRaw->adjustPosition(correction); + actorData.mPosition = actorData.mActorRaw->getPosition(); + } + return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); } @@ -182,7 +191,6 @@ namespace MWPhysics mPostSimBarrier = std::make_unique(mNumThreads, [&]() { - udpateActorsAabbs(); mNewFrame = false; if (mLOSCacheExpiry >= 0) { @@ -229,6 +237,9 @@ namespace MWPhysics updateMechanics(data); if (mAdvanceSimulation) updateStandingCollision(data, standingCollisions); + + if (mMovementResults.find(data.mPtr) != mMovementResults.end()) + data.mActorRaw->setNextPosition(mMovementResults[data.mPtr]); } } @@ -245,10 +256,6 @@ namespace MWPhysics if (mAdvanceSimulation) mWorldFrameData = std::make_unique(); - // update each actor position based on latest data - for (auto& data : mActorsFrameData) - data.updatePosition(); - // we are asked to skip the simulation (load a savegame for instance) // just return the actors' reference position without applying the movements if (skipSimulation) @@ -256,7 +263,10 @@ namespace MWPhysics standingCollisions.clear(); mMovementResults.clear(); for (const auto& m : mActorsFrameData) - mMovementResults[m.mPtr] = m.mPosition; + { + m.mActorRaw->setPosition(m.mActorRaw->getWorldPosition(), true); + mMovementResults[m.mPtr] = m.mActorRaw->getWorldPosition(); + } return mMovementResults; } @@ -271,6 +281,11 @@ namespace MWPhysics for (auto& data : mActorsFrameData) updateStandingCollision(data, standingCollisions); } + for (auto& data : mActorsFrameData) + { + if (mMovementResults.find(data.mPtr) != mMovementResults.end()) + data.mActorRaw->setNextPosition(mMovementResults[data.mPtr]); + } return mMovementResults; } @@ -427,7 +442,7 @@ namespace MWPhysics { if (const auto actor = std::dynamic_pointer_cast(p)) { - actor->commitPositionChange(); + actor->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } else if (const auto object = std::dynamic_pointer_cast(p)) @@ -485,28 +500,17 @@ namespace MWPhysics { if(const auto actor = actorData.mActor.lock()) { - if (actorData.mPosition == actor->getPosition()) - actor->setPosition(actorData.mPosition, false); // update previous position to make sure interpolation is correct - else + bool positionChanged = actorData.mPosition != actorData.mActorRaw->getPosition(); + actorData.mActorRaw->setPosition(actorData.mPosition); + if (positionChanged) { - actorData.mPositionChanged = true; - actor->setPosition(actorData.mPosition); + actor->updateCollisionObjectPosition(); + mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); } } } } - void PhysicsTaskScheduler::udpateActorsAabbs() - { - std::unique_lock lock(mCollisionWorldMutex); - for (const auto& actorData : mActorsFrameData) - if (actorData.mPositionChanged) - { - if(const auto actor = actorData.mActor.lock()) - mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); - } - } - bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2) { btVector3 pos1 = Misc::Convert::toBullet(actor1->getCollisionObjectPosition() + osg::Vec3f(0,0,actor1->getHalfExtents().z() * 0.9)); // eye level @@ -538,6 +542,5 @@ namespace MWPhysics mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt); updateMechanics(actorData); } - udpateActorsAabbs(); } } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 100e71a90..84ea93c08 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -49,7 +49,6 @@ namespace MWPhysics void syncComputation(); void worker(); void updateActorsPositions(); - void udpateActorsAabbs(); bool hasLineOfSight(const Actor* actor1, const Actor* actor2); void refreshLOSCache(); void updateAabbs(); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 9a777bd45..6b94ef43b 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -883,7 +883,7 @@ namespace MWPhysics ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel) : mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn), - mPositionChanged(false), mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface), + mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface), mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos() { const MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -893,10 +893,9 @@ namespace MWPhysics mWantJump = mPtr.getClass().getMovementSettings(mPtr).mPosition[2] != 0; mIsDead = mPtr.getClass().getCreatureStats(mPtr).isDead(); mWasOnGround = actor->getOnGround(); - } - void ActorFrameData::updatePosition() - { + mActorRaw->updatePosition(); + mOrigin = mActorRaw->getNextPosition(); mPosition = mActorRaw->getPosition(); if (mMoveToWaterSurface) { diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 36ef762d3..4844b5e8e 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -78,14 +78,12 @@ namespace MWPhysics struct ActorFrameData { ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel); - void updatePosition(); std::weak_ptr mActor; Actor* mActorRaw; MWWorld::Ptr mPtr; MWWorld::Ptr mStandingOn; bool mFlying; bool mSwimming; - bool mPositionChanged; bool mWasOnGround; bool mWantJump; bool mDidJump; @@ -97,6 +95,7 @@ namespace MWPhysics float mOldHeight; float mFallHeight; osg::Vec3f mMovement; + osg::Vec3f mOrigin; osg::Vec3f mPosition; ESM::Position mRefpos; }; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 41df1870c..ce45729b3 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -32,7 +32,11 @@ namespace MWScript std::vector actors; MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); for (auto& actor : actors) - MWBase::Environment::get().getWorld()->queueMovement(actor, diff); + { + osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); + actorPos += diff; + MWBase::Environment::get().getWorld()->moveObject(actor, actorPos.x(), actorPos.y(), actorPos.z()); + } } template diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index be32765ad..ad6c33790 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1504,11 +1504,11 @@ namespace MWWorld const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements); mDiscardMovements = false; - for(const auto& result : results) + for(const auto& [actor, position]: results) { // Handle player last, in case a cell transition occurs - if(result.first != getPlayerPtr()) - moveObjectImp(result.first, result.second.x(), result.second.y(), result.second.z(), false); + if(actor != getPlayerPtr()) + moveObjectImp(actor, position.x(), position.y(), position.z(), false); } const auto player = results.find(getPlayerPtr());