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;
///< @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
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();
updateScale();
// already called by updateScale()
//updatePosition();
updatePosition();
updateCollisionMask();
}
@ -86,19 +85,44 @@ void Actor::updatePosition()
{
osg::Vec3f position = mPtr.getRefData().getPosition().asVec3();
mPosition = position;
mPreviousPosition = position;
updateCollisionObjectPosition();
}
void Actor::updateCollisionObjectPosition()
{
btTransform tr = mCollisionObject->getWorldTransform();
osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale);
osg::Vec3f newPosition = scaledTranslation + position;
osg::Vec3f newPosition = scaledTranslation + mPosition;
tr.setOrigin(toBullet(newPosition));
mCollisionObject->setWorldTransform(tr);
}
osg::Vec3f Actor::getPosition() const
osg::Vec3f Actor::getCollisionObjectPosition() const
{
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 ()
{
btTransform tr = mCollisionObject->getWorldTransform();
@ -106,7 +130,7 @@ void Actor::updateRotation ()
tr.setRotation(toBullet(mRotation));
mCollisionObject->setWorldTransform(tr);
updatePosition();
updateCollisionObjectPosition();
}
void Actor::updateScale()
@ -122,7 +146,7 @@ void Actor::updateScale()
mPtr.getClass().adjustScale(mPtr, scaleVec, true);
mRenderingScale = scaleVec;
updatePosition();
updateCollisionObjectPosition();
}
osg::Vec3f Actor::getHalfExtents() const

@ -68,8 +68,15 @@ namespace MWPhysics
void updateScale();
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 updateCollisionObjectPosition();
/**
* 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
* @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 getPreviousPosition() const;
/**
* 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,
@ -138,6 +154,7 @@ namespace MWPhysics
osg::Vec3f mScale;
osg::Vec3f mRenderingScale;
osg::Vec3f mPosition;
osg::Vec3f mPreviousPosition;
osg::Vec3f mForce;
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,
std::map<MWWorld::Ptr, MWWorld::Ptr>& standingCollisionTracker)
{
const ESM::Position& refpos = ptr.getRefData().getPosition();
osg::Vec3f position(refpos.asVec3());
// Early-out for totally static creatures
// (Not sure if gravity should still apply?)
if (!ptr.getClass().isMobile(ptr))
@ -944,8 +942,8 @@ namespace MWPhysics
if (!physactor1 || !physactor2)
return false;
osg::Vec3f pos1 (physactor1->getPosition() + 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 pos1 (physactor1->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor1->getHalfExtents().z() * 0.8)); // eye level
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);
@ -1007,11 +1005,11 @@ namespace MWPhysics
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);
if (physactor)
return physactor->getPosition();
return physactor->getCollisionObjectPosition();
else
return osg::Vec3f();
}
@ -1296,54 +1294,65 @@ namespace MWPhysics
mMovementResults.clear();
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
mStandingCollisions.clear();
}
const MWBase::World *world = MWBase::Environment::get().getWorld();
PtrVelocityList::iterator iter = mMovementQueue.begin();
for(;iter != mMovementQueue.end();++iter)
{
float waterlevel = -std::numeric_limits<float>::max();
const MWWorld::CellStore *cell = iter->first.getCell();
if(cell->getCell()->hasWater())
waterlevel = cell->getWaterLevel();
const MWBase::World *world = MWBase::Environment::get().getWorld();
PtrVelocityList::iterator iter = mMovementQueue.begin();
for(;iter != mMovementQueue.end();++iter)
{
float waterlevel = -std::numeric_limits<float>::max();
const MWWorld::CellStore *cell = iter->first.getCell();
if(cell->getCell()->hasWater())
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;
if (effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()
&& cell->getCell()->hasWater()
&& !world->isUnderwater(iter->first.getCell(),
osg::Vec3f(iter->first.getRefData().getPosition().asVec3())))
waterCollision = true;
bool waterCollision = false;
if (effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()
&& cell->getCell()->hasWater()
&& !world->isUnderwater(iter->first.getCell(),
osg::Vec3f(iter->first.getRefData().getPosition().asVec3())))
waterCollision = true;
ActorMap::iterator foundActor = mActors.find(iter->first);
if (foundActor == mActors.end()) // actor was already removed from the scene
continue;
Actor* physicActor = foundActor->second;
physicActor->setCanWaterWalk(waterCollision);
ActorMap::iterator foundActor = mActors.find(iter->first);
if (foundActor == mActors.end()) // actor was already removed from the scene
continue;
Actor* physicActor = foundActor->second;
physicActor->setCanWaterWalk(waterCollision);
// 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));
// 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));
osg::Vec3f newpos = MovementSolver::move(physicActor->getPtr(), physicActor, iter->second, mTimeAccum,
world->isFlying(iter->first),
waterlevel, slowFall, mCollisionWorld, mStandingCollisions);
osg::Vec3f position = physicActor->getPosition();
float oldHeight = position.z();
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)
iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff);
float heightDiff = position.z() - oldHeight;
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();
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.
/// @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
/// 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 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
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();
@ -1201,7 +1201,8 @@ namespace MWWorld
if (haveToMove && newPtr.getRefData().getBaseNode())
{
mRendering->moveObject(newPtr, vec);
mPhysics->updatePosition(newPtr);
if (movePhysics)
mPhysics->updatePosition(newPtr);
}
if (isPlayer)
{
@ -1210,7 +1211,7 @@ namespace MWWorld
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();
@ -1221,7 +1222,7 @@ namespace MWWorld
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)
@ -1373,10 +1374,10 @@ namespace MWWorld
player = iter;
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())
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();
}
@ -3206,7 +3207,7 @@ namespace MWWorld
osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target)
{
osg::Vec3f weaponPos = getActorHeadTransform(actor).getTrans();
osg::Vec3f targetPos = mPhysics->getPosition(target);
osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target);
return (targetPos - weaponPos);
}

@ -119,7 +119,7 @@ namespace MWWorld
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
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);
///< @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
virtual void scaleObject (const Ptr& ptr, float scale);

Loading…
Cancel
Save