1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-21 07:53:53 +00:00

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.
This commit is contained in:
fredzio 2020-11-01 17:14:59 +01:00
parent d9fcd3c768
commit e5fa457fe7
8 changed files with 91 additions and 94 deletions

View file

@ -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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(mPositionMutex);
return mPosition;
}
osg::Vec3f Actor::getPreviousPosition() const
{
std::unique_lock<std::mutex> lock(mPositionMutex);
return mPreviousPosition;
}
void Actor::updateRotation ()
{
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> lock(mPositionMutex);
std::scoped_lock lock(mPositionMutex);
return osg::componentMultiply(mHalfExtents, mScale);
}
osg::Vec3f Actor::getOriginalHalfExtents() const
{
std::unique_lock<std::mutex> lock(mPositionMutex);
std::scoped_lock lock(mPositionMutex);
return mHalfExtents;
}
osg::Vec3f Actor::getRenderingHalfExtents() const
{
std::unique_lock<std::mutex> 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<std::mutex> lock(mPositionMutex);
std::scoped_lock lock(mPositionMutex);
if (waterWalk != mCanWaterWalk)
{
mCanWaterWalk = waterWalk;

View file

@ -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;

View file

@ -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<Misc::Barrier>(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<WorldFrameData>();
// 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<Actor>(p))
{
actor->commitPositionChange();
actor->updateCollisionObjectPosition();
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
}
else if (const auto object = std::dynamic_pointer_cast<Object>(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();
}
}

View file

@ -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();

View file

@ -883,7 +883,7 @@ namespace MWPhysics
ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& 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)
{

View file

@ -78,14 +78,12 @@ namespace MWPhysics
struct ActorFrameData
{
ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
void updatePosition();
std::weak_ptr<Actor> 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;
};

View file

@ -32,7 +32,11 @@ namespace MWScript
std::vector<MWWorld::Ptr> 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<class R>

View file

@ -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());