Run physics in fixed timesteps, use the remainder to interpolate between current and previous state

Based on http://gafferongames.com/game-physics/fix-your-timestep/
move
scrawl 9 years ago
parent d1375cd3a3
commit 383524c688

@ -270,7 +270,7 @@ namespace MWBase
virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0;
///< @return an updated Ptr in case the Ptr's cell changes ///< @return an updated Ptr in case the Ptr's cell changes
virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z) = 0; virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0;
///< @return an updated Ptr ///< @return an updated Ptr
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;

@ -45,8 +45,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape>
updateRotation(); updateRotation();
updateScale(); updateScale();
// already called by updateScale() updatePosition();
//updatePosition();
updateCollisionMask(); updateCollisionMask();
} }
@ -86,19 +85,44 @@ void Actor::updatePosition()
{ {
osg::Vec3f position = mPtr.getRefData().getPosition().asVec3(); osg::Vec3f position = mPtr.getRefData().getPosition().asVec3();
mPosition = position;
mPreviousPosition = position;
updateCollisionObjectPosition();
}
void Actor::updateCollisionObjectPosition()
{
btTransform tr = mCollisionObject->getWorldTransform(); btTransform tr = mCollisionObject->getWorldTransform();
osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale); osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale);
osg::Vec3f newPosition = scaledTranslation + position; osg::Vec3f newPosition = scaledTranslation + mPosition;
tr.setOrigin(toBullet(newPosition)); tr.setOrigin(toBullet(newPosition));
mCollisionObject->setWorldTransform(tr); mCollisionObject->setWorldTransform(tr);
} }
osg::Vec3f Actor::getPosition() const osg::Vec3f Actor::getCollisionObjectPosition() const
{ {
return toOsg(mCollisionObject->getWorldTransform().getOrigin()); return toOsg(mCollisionObject->getWorldTransform().getOrigin());
} }
void Actor::setPosition(const osg::Vec3f &position)
{
mPreviousPosition = mPosition;
mPosition = position;
updateCollisionObjectPosition();
}
osg::Vec3f Actor::getPosition() const
{
return mPosition;
}
osg::Vec3f Actor::getPreviousPosition() const
{
return mPreviousPosition;
}
void Actor::updateRotation () void Actor::updateRotation ()
{ {
btTransform tr = mCollisionObject->getWorldTransform(); btTransform tr = mCollisionObject->getWorldTransform();
@ -106,7 +130,7 @@ void Actor::updateRotation ()
tr.setRotation(toBullet(mRotation)); tr.setRotation(toBullet(mRotation));
mCollisionObject->setWorldTransform(tr); mCollisionObject->setWorldTransform(tr);
updatePosition(); updateCollisionObjectPosition();
} }
void Actor::updateScale() void Actor::updateScale()
@ -122,7 +146,7 @@ void Actor::updateScale()
mPtr.getClass().adjustScale(mPtr, scaleVec, true); mPtr.getClass().adjustScale(mPtr, scaleVec, true);
mRenderingScale = scaleVec; mRenderingScale = scaleVec;
updatePosition(); updateCollisionObjectPosition();
} }
osg::Vec3f Actor::getHalfExtents() const osg::Vec3f Actor::getHalfExtents() const

@ -68,8 +68,15 @@ namespace MWPhysics
void updateScale(); void updateScale();
void updateRotation(); void updateRotation();
/**
* Set mPosition and mPreviousPosition to the position in the Ptr's RefData. This should be used
* when an object is "instantly" moved/teleported as opposed to being moved by the physics simulation.
*/
void updatePosition(); void updatePosition();
void updateCollisionObjectPosition();
/** /**
* Returns the half extents of the collision body (scaled according to collision scale) * Returns the half extents of the collision body (scaled according to collision scale)
*/ */
@ -79,8 +86,17 @@ namespace MWPhysics
* Returns the position of the collision body * Returns the position of the collision body
* @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. * @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space.
*/ */
osg::Vec3f getCollisionObjectPosition() const;
/**
* Store the current position into mPreviousPosition, then move to this position.
*/
void setPosition(const osg::Vec3f& position);
osg::Vec3f getPosition() const; osg::Vec3f getPosition() const;
osg::Vec3f getPreviousPosition() const;
/** /**
* Returns the half extents of the collision body (scaled according to rendering scale) * Returns the half extents of the collision body (scaled according to rendering scale)
* @note The reason we need this extra method is because of an inconsistency in MW - NPC race scales aren't applied to the collision shape, * @note The reason we need this extra method is because of an inconsistency in MW - NPC race scales aren't applied to the collision shape,
@ -138,6 +154,7 @@ namespace MWPhysics
osg::Vec3f mScale; osg::Vec3f mScale;
osg::Vec3f mRenderingScale; osg::Vec3f mRenderingScale;
osg::Vec3f mPosition; osg::Vec3f mPosition;
osg::Vec3f mPreviousPosition;
osg::Vec3f mForce; osg::Vec3f mForce;
bool mOnGround; bool mOnGround;

@ -234,13 +234,11 @@ namespace MWPhysics
} }
} }
static osg::Vec3f move(const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time, static osg::Vec3f move(osg::Vec3f position, const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time,
bool isFlying, float waterlevel, float slowFall, btCollisionWorld* collisionWorld, bool isFlying, float waterlevel, float slowFall, btCollisionWorld* collisionWorld,
std::map<MWWorld::Ptr, MWWorld::Ptr>& standingCollisionTracker) std::map<MWWorld::Ptr, MWWorld::Ptr>& standingCollisionTracker)
{ {
const ESM::Position& refpos = ptr.getRefData().getPosition(); const ESM::Position& refpos = ptr.getRefData().getPosition();
osg::Vec3f position(refpos.asVec3());
// Early-out for totally static creatures // Early-out for totally static creatures
// (Not sure if gravity should still apply?) // (Not sure if gravity should still apply?)
if (!ptr.getClass().isMobile(ptr)) if (!ptr.getClass().isMobile(ptr))
@ -944,8 +942,8 @@ namespace MWPhysics
if (!physactor1 || !physactor2) if (!physactor1 || !physactor2)
return false; return false;
osg::Vec3f pos1 (physactor1->getPosition() + osg::Vec3f(0,0,physactor1->getHalfExtents().z() * 0.8)); // eye level osg::Vec3f pos1 (physactor1->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor1->getHalfExtents().z() * 0.8)); // eye level
osg::Vec3f pos2 (physactor2->getPosition() + osg::Vec3f(0,0,physactor2->getHalfExtents().z() * 0.8)); osg::Vec3f pos2 (physactor2->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor2->getHalfExtents().z() * 0.8));
RayResult result = castRay(pos1, pos2, MWWorld::Ptr(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door); RayResult result = castRay(pos1, pos2, MWWorld::Ptr(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door);
@ -1007,11 +1005,11 @@ namespace MWPhysics
return osg::Vec3f(); return osg::Vec3f();
} }
osg::Vec3f PhysicsSystem::getPosition(const MWWorld::ConstPtr &actor) const osg::Vec3f PhysicsSystem::getCollisionObjectPosition(const MWWorld::ConstPtr &actor) const
{ {
const Actor* physactor = getActor(actor); const Actor* physactor = getActor(actor);
if (physactor) if (physactor)
return physactor->getPosition(); return physactor->getCollisionObjectPosition();
else else
return osg::Vec3f(); return osg::Vec3f();
} }
@ -1296,54 +1294,65 @@ namespace MWPhysics
mMovementResults.clear(); mMovementResults.clear();
mTimeAccum += dt; mTimeAccum += dt;
if(mTimeAccum >= 1.0f/60.0f) const float physicsDt = 1.f/60.0f;
int numSteps = mTimeAccum / (physicsDt);
mTimeAccum -= numSteps * physicsDt;
if (numSteps)
{ {
// Collision events should be available on every frame // Collision events should be available on every frame
mStandingCollisions.clear(); mStandingCollisions.clear();
}
const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWBase::World *world = MWBase::Environment::get().getWorld();
PtrVelocityList::iterator iter = mMovementQueue.begin(); PtrVelocityList::iterator iter = mMovementQueue.begin();
for(;iter != mMovementQueue.end();++iter) for(;iter != mMovementQueue.end();++iter)
{ {
float waterlevel = -std::numeric_limits<float>::max(); float waterlevel = -std::numeric_limits<float>::max();
const MWWorld::CellStore *cell = iter->first.getCell(); const MWWorld::CellStore *cell = iter->first.getCell();
if(cell->getCell()->hasWater()) if(cell->getCell()->hasWater())
waterlevel = cell->getWaterLevel(); waterlevel = cell->getWaterLevel();
float oldHeight = iter->first.getRefData().getPosition().pos[2];
const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects(); const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects();
bool waterCollision = false; bool waterCollision = false;
if (effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() if (effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()
&& cell->getCell()->hasWater() && cell->getCell()->hasWater()
&& !world->isUnderwater(iter->first.getCell(), && !world->isUnderwater(iter->first.getCell(),
osg::Vec3f(iter->first.getRefData().getPosition().asVec3()))) osg::Vec3f(iter->first.getRefData().getPosition().asVec3())))
waterCollision = true; waterCollision = true;
ActorMap::iterator foundActor = mActors.find(iter->first); ActorMap::iterator foundActor = mActors.find(iter->first);
if (foundActor == mActors.end()) // actor was already removed from the scene if (foundActor == mActors.end()) // actor was already removed from the scene
continue; continue;
Actor* physicActor = foundActor->second; Actor* physicActor = foundActor->second;
physicActor->setCanWaterWalk(waterCollision); physicActor->setCanWaterWalk(waterCollision);
// Slow fall reduces fall speed by a factor of (effect magnitude / 200) // Slow fall reduces fall speed by a factor of (effect magnitude / 200)
float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f));
osg::Vec3f newpos = MovementSolver::move(physicActor->getPtr(), physicActor, iter->second, mTimeAccum, osg::Vec3f position = physicActor->getPosition();
world->isFlying(iter->first), float oldHeight = position.z();
waterlevel, slowFall, mCollisionWorld, mStandingCollisions); for (int i=0; i<numSteps; ++i)
{
position = MovementSolver::move(position, physicActor->getPtr(), physicActor, iter->second, physicsDt,
world->isFlying(iter->first),
waterlevel, slowFall, mCollisionWorld, mStandingCollisions);
physicActor->setPosition(position);
}
float heightDiff = newpos.z() - oldHeight; float interpolationFactor = mTimeAccum / physicsDt;
osg::Vec3f interpolated = position * interpolationFactor + physicActor->getPreviousPosition() * (1.f - interpolationFactor);
if (heightDiff < 0) float heightDiff = position.z() - oldHeight;
iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff);
mMovementResults.push_back(std::make_pair(iter->first, newpos)); if (heightDiff < 0)
} iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff);
mTimeAccum = 0.0f; mMovementResults.push_back(std::make_pair(iter->first, interpolated));
} }
mMovementQueue.clear(); mMovementQueue.clear();
return mMovementResults; return mMovementResults;

@ -129,7 +129,7 @@ namespace MWPhysics
/// Get the position of the collision shape for the actor. Use together with getHalfExtents() to get the collision bounds in world space. /// Get the position of the collision shape for the actor. Use together with getHalfExtents() to get the collision bounds in world space.
/// @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. /// @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space.
osg::Vec3f getPosition(const MWWorld::ConstPtr& actor) const; osg::Vec3f getCollisionObjectPosition(const MWWorld::ConstPtr& actor) const;
/// 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 applyQueuedMovement. /// be overwritten. Valid until the next call to applyQueuedMovement.

@ -117,7 +117,7 @@ namespace MWWorld
const ESM::EffectList &effects, const Ptr &caster, const std::string &sourceName, const ESM::EffectList &effects, const Ptr &caster, const std::string &sourceName,
const osg::Vec3f& fallbackDirection) const osg::Vec3f& fallbackDirection)
{ {
osg::Vec3f pos = mPhysics->getPosition(caster) + osg::Vec3f(0,0,mPhysics->getHalfExtents(caster).z() * 0.5); // Spawn at 0.75 * ActorHeight osg::Vec3f pos = mPhysics->getCollisionObjectPosition(caster) + osg::Vec3f(0,0,mPhysics->getHalfExtents(caster).z() * 0.5); // Spawn at 0.75 * ActorHeight
if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible
return; return;

@ -1113,7 +1113,7 @@ namespace MWWorld
} }
} }
MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z) MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z, bool movePhysics)
{ {
ESM::Position pos = ptr.getRefData().getPosition(); ESM::Position pos = ptr.getRefData().getPosition();
@ -1201,7 +1201,8 @@ namespace MWWorld
if (haveToMove && newPtr.getRefData().getBaseNode()) if (haveToMove && newPtr.getRefData().getBaseNode())
{ {
mRendering->moveObject(newPtr, vec); mRendering->moveObject(newPtr, vec);
mPhysics->updatePosition(newPtr); if (movePhysics)
mPhysics->updatePosition(newPtr);
} }
if (isPlayer) if (isPlayer)
{ {
@ -1210,7 +1211,7 @@ namespace MWWorld
return newPtr; return newPtr;
} }
MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z) MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z, bool movePhysics)
{ {
CellStore *cell = ptr.getCell(); CellStore *cell = ptr.getCell();
@ -1221,7 +1222,7 @@ namespace MWWorld
cell = getExterior(cellX, cellY); cell = getExterior(cellX, cellY);
} }
return moveObject(ptr, cell, x, y, z); return moveObject(ptr, cell, x, y, z, movePhysics);
} }
MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z) MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z)
@ -1373,10 +1374,10 @@ namespace MWWorld
player = iter; player = iter;
continue; continue;
} }
moveObjectImp(iter->first, iter->second.x(), iter->second.y(), iter->second.z()); moveObjectImp(iter->first, iter->second.x(), iter->second.y(), iter->second.z(), false);
} }
if(player != results.end()) if(player != results.end())
moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z()); moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false);
mPhysics->debugDraw(); mPhysics->debugDraw();
} }
@ -3206,7 +3207,7 @@ namespace MWWorld
osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target) osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target)
{ {
osg::Vec3f weaponPos = getActorHeadTransform(actor).getTrans(); osg::Vec3f weaponPos = getActorHeadTransform(actor).getTrans();
osg::Vec3f targetPos = mPhysics->getPosition(target); osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target);
return (targetPos - weaponPos); return (targetPos - weaponPos);
} }

@ -119,7 +119,7 @@ namespace MWWorld
void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, bool adjust); void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, bool adjust);
Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z); Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z, bool movePhysics=true);
///< @return an updated Ptr in case the Ptr's cell changes ///< @return an updated Ptr in case the Ptr's cell changes
Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos);
@ -355,7 +355,7 @@ namespace MWWorld
virtual MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z); virtual MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z);
///< @return an updated Ptr in case the Ptr's cell changes ///< @return an updated Ptr in case the Ptr's cell changes
virtual MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z); virtual MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true);
///< @return an updated Ptr ///< @return an updated Ptr
virtual void scaleObject (const Ptr& ptr, float scale); virtual void scaleObject (const Ptr& ptr, float scale);

Loading…
Cancel
Save