mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-29 22:45:34 +00:00
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/
This commit is contained in:
parent
d1375cd3a3
commit
383524c688
8 changed files with 117 additions and 66 deletions
|
@ -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)
|
||||
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 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;
|
||||
|
||||
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));
|
||||
|
||||
osg::Vec3f position = physicActor->getPosition();
|
||||
float oldHeight = position.z();
|
||||
for (int i=0; i<numSteps; ++i)
|
||||
{
|
||||
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();
|
||||
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
float heightDiff = newpos.z() - oldHeight;
|
||||
|
||||
if (heightDiff < 0)
|
||||
iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff);
|
||||
|
||||
mMovementResults.push_back(std::make_pair(iter->first, newpos));
|
||||
position = MovementSolver::move(position, physicActor->getPtr(), physicActor, iter->second, physicsDt,
|
||||
world->isFlying(iter->first),
|
||||
waterlevel, slowFall, mCollisionWorld, mStandingCollisions);
|
||||
physicActor->setPosition(position);
|
||||
}
|
||||
|
||||
mTimeAccum = 0.0f;
|
||||
float interpolationFactor = mTimeAccum / physicsDt;
|
||||
osg::Vec3f interpolated = position * interpolationFactor + physicActor->getPreviousPosition() * (1.f - interpolationFactor);
|
||||
|
||||
float heightDiff = position.z() - oldHeight;
|
||||
|
||||
if (heightDiff < 0)
|
||||
iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff);
|
||||
|
||||
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…
Reference in a new issue