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:
parent
d9fcd3c768
commit
e5fa457fe7
8 changed files with 91 additions and 94 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in a new issue