1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-06-29 08:41:34 +00:00

Merge branch 'schneller' into 'master'

Optimize actors physics simulation

See merge request OpenMW/openmw!1048
This commit is contained in:
psi29a 2021-08-08 12:27:53 +00:00
commit 8c81191d09
8 changed files with 287 additions and 253 deletions

View file

@ -1831,7 +1831,7 @@ namespace MWMechanics
osg::Vec2f baseSpeed = origMovement * maxSpeed;
osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3();
float baseRotZ = ptr.getRefData().getPosition().rot[2];
osg::Vec3f halfExtents = world->getHalfExtents(ptr);
const osg::Vec3f halfExtents = world->getHalfExtents(ptr);
float maxDistToCheck = isMoving ? maxDistForPartialAvoiding : maxDistForStrictAvoiding;
float timeToCollision = maxTimeToCheck;
@ -1849,7 +1849,7 @@ namespace MWMechanics
if (otherPtr == ptr || otherPtr == currentTarget)
continue;
osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr);
const osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr);
osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos;
osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ);
float dist = deltaPos.length();
@ -1867,7 +1867,7 @@ namespace MWMechanics
float rotZ = otherPtr.getRefData().getPosition().rot[2];
osg::Vec2f relSpeed = Misc::rotateVec2f(osg::Vec2f(speed.x(), speed.y()), baseRotZ - rotZ) - baseSpeed;
float collisionDist = minGap + world->getHalfExtents(ptr).x() + world->getHalfExtents(otherPtr).x();
float collisionDist = minGap + halfExtents.x() + otherHalfExtents.x();
collisionDist = std::min(collisionDist, relPos.length());
// Find the earliest `t` when |relPos + relSpeed * t| == collisionDist.

View file

@ -21,7 +21,7 @@ namespace MWPhysics
Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk)
: mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false)
, mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents)
, mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mOriginalHalfExtents(shape->mCollisionBox.extents)
, mVelocity(0,0,0), mStuckFrames(0), mLastStuckPosition{0, 0, 0}
, mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
, mInternalCollisionMode(true)
@ -33,7 +33,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
// We can not create actor without collisions - he will fall through the ground.
// In this case we should autogenerate collision box based on mesh shape
// (NPCs have bodyparts and use a different approach)
if (!ptr.getClass().isNpc() && mHalfExtents.length2() == 0.f)
if (!ptr.getClass().isNpc() && mOriginalHalfExtents.length2() == 0.f)
{
if (shape->mCollisionShape)
{
@ -43,19 +43,19 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
btVector3 max;
shape->mCollisionShape->getAabb(transform, min, max);
mHalfExtents.x() = (max[0] - min[0])/2.f;
mHalfExtents.y() = (max[1] - min[1])/2.f;
mHalfExtents.z() = (max[2] - min[2])/2.f;
mOriginalHalfExtents.x() = (max[0] - min[0])/2.f;
mOriginalHalfExtents.y() = (max[1] - min[1])/2.f;
mOriginalHalfExtents.z() = (max[2] - min[2])/2.f;
mMeshTranslation = osg::Vec3f(0.f, 0.f, mHalfExtents.z());
mMeshTranslation = osg::Vec3f(0.f, 0.f, mOriginalHalfExtents.z());
}
if (mHalfExtents.length2() == 0.f)
if (mOriginalHalfExtents.length2() == 0.f)
Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\".";
}
mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents)));
mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mHalfExtents.x() - mHalfExtents.y()) < 2.2;
mShape.reset(new btBoxShape(Misc::Convert::toBullet(mOriginalHalfExtents)));
mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mOriginalHalfExtents.x() - mOriginalHalfExtents.y()) < 2.2;
mConvexShape = static_cast<btConvexShape*>(mShape.get());
@ -82,7 +82,7 @@ Actor::~Actor()
void Actor::enableCollisionMode(bool collision)
{
mInternalCollisionMode.store(collision, std::memory_order_release);
mInternalCollisionMode = collision;
}
void Actor::enableCollisionBody(bool collision)
@ -220,27 +220,26 @@ void Actor::updateScale()
mPtr.getClass().adjustScale(mPtr, scaleVec, false);
mScale = scaleVec;
mHalfExtents = osg::componentMultiply(mOriginalHalfExtents, scaleVec);
scaleVec = osg::Vec3f(scale,scale,scale);
mPtr.getClass().adjustScale(mPtr, scaleVec, true);
mRenderingScale = scaleVec;
mRenderingHalfExtents = osg::componentMultiply(mOriginalHalfExtents, scaleVec);
}
osg::Vec3f Actor::getHalfExtents() const
{
std::scoped_lock lock(mPositionMutex);
return osg::componentMultiply(mHalfExtents, mScale);
return mHalfExtents;
}
osg::Vec3f Actor::getOriginalHalfExtents() const
{
return mHalfExtents;
return mOriginalHalfExtents;
}
osg::Vec3f Actor::getRenderingHalfExtents() const
{
std::scoped_lock lock(mPositionMutex);
return osg::componentMultiply(mHalfExtents, mRenderingScale);
return mRenderingHalfExtents;
}
void Actor::setInertialForce(const osg::Vec3f &force)
@ -250,22 +249,22 @@ void Actor::setInertialForce(const osg::Vec3f &force)
void Actor::setOnGround(bool grounded)
{
mOnGround.store(grounded, std::memory_order_release);
mOnGround = grounded;
}
void Actor::setOnSlope(bool slope)
{
mOnSlope.store(slope, std::memory_order_release);
mOnSlope = slope;
}
bool Actor::isWalkingOnWater() const
{
return mWalkingOnWater.load(std::memory_order_acquire);
return mWalkingOnWater;
}
void Actor::setWalkingOnWater(bool walkingOnWater)
{
mWalkingOnWater.store(walkingOnWater, std::memory_order_release);
mWalkingOnWater = walkingOnWater;
}
void Actor::setCanWaterWalk(bool waterWalk)

View file

@ -1,7 +1,6 @@
#ifndef OPENMW_MWPHYSICS_ACTOR_H
#define OPENMW_MWPHYSICS_ACTOR_H
#include <atomic>
#include <memory>
#include <mutex>
@ -37,7 +36,7 @@ namespace MWPhysics
bool getCollisionMode() const
{
return mInternalCollisionMode.load(std::memory_order_acquire);
return mInternalCollisionMode;
}
btConvexShape* getConvexShape() const { return mConvexShape; }
@ -123,14 +122,14 @@ namespace MWPhysics
bool getOnGround() const
{
return mInternalCollisionMode.load(std::memory_order_acquire) && mOnGround.load(std::memory_order_acquire);
return mInternalCollisionMode && mOnGround;
}
void setOnSlope(bool slope);
bool getOnSlope() const
{
return mInternalCollisionMode.load(std::memory_order_acquire) && mOnSlope.load(std::memory_order_acquire);
return mInternalCollisionMode && mOnSlope;
}
btCollisionObject* getCollisionObject() const
@ -182,7 +181,7 @@ namespace MWPhysics
osg::Vec3f getScaledMeshTranslation() const;
bool mCanWaterWalk;
std::atomic<bool> mWalkingOnWater;
bool mWalkingOnWater;
bool mRotationallyInvariant;
@ -192,11 +191,12 @@ namespace MWPhysics
std::unique_ptr<btCollisionObject> mCollisionObject;
osg::Vec3f mMeshTranslation;
osg::Vec3f mOriginalHalfExtents;
osg::Vec3f mHalfExtents;
osg::Vec3f mRenderingHalfExtents;
osg::Quat mRotation;
osg::Vec3f mScale;
osg::Vec3f mRenderingScale;
osg::Vec3f mSimulationPosition;
osg::Vec3f mPosition;
osg::Vec3f mPreviousPosition;
@ -211,9 +211,9 @@ namespace MWPhysics
osg::Vec3f mLastStuckPosition;
osg::Vec3f mForce;
std::atomic<bool> mOnGround;
std::atomic<bool> mOnSlope;
std::atomic<bool> mInternalCollisionMode;
bool mOnGround;
bool mOnSlope;
bool mInternalCollisionMode;
bool mExternalCollisionMode;
PhysicsTaskScheduler* mTaskScheduler;

View file

@ -118,66 +118,49 @@ namespace MWPhysics
void MovementSolver::move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld,
WorldFrameData& worldData)
{
auto* physicActor = actor.mActorRaw;
const ESM::Position& refpos = actor.mRefpos;
// Early-out for totally static creatures
// (Not sure if gravity should still apply?)
{
const auto ptr = physicActor->getPtr();
if (!ptr.getClass().isMobile(ptr))
return;
}
// Reset per-frame data
physicActor->setWalkingOnWater(false);
actor.mWalkingOnWater = false;
// Anything to collide with?
if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection)
if(actor.mSkipCollisionDetection)
{
actor.mPosition += (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) *
osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))
actor.mPosition += (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) *
osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))
) * actor.mMovement * time;
return;
}
const btCollisionObject *colobj = physicActor->getCollisionObject();
// Adjust for collision mesh offset relative to actor's "location"
// (doTrace doesn't take local/interior collision shape translation into account, so we have to do it on our own)
// for compatibility with vanilla assets, we have to derive this from the vertical half extent instead of from internal hull translation
// if not for this hack, the "correct" collision hull position would be physicActor->getScaledMeshTranslation()
osg::Vec3f halfExtents = physicActor->getHalfExtents();
actor.mPosition.z() += halfExtents.z(); // vanilla-accurate
actor.mPosition.z() += actor.mHalfExtentsZ; // vanilla-accurate
static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
float swimlevel = actor.mSwimLevel + actor.mHalfExtentsZ;
ActorTracer tracer;
osg::Vec3f inertia = physicActor->getInertialForce();
osg::Vec3f velocity;
if (actor.mPosition.z() < swimlevel || actor.mFlying)
{
velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
}
else
{
velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
if ((velocity.z() > 0.f && physicActor->getOnGround() && !physicActor->getOnSlope())
|| (velocity.z() > 0.f && velocity.z() + inertia.z() <= -velocity.z() && physicActor->getOnSlope()))
inertia = velocity;
else if (!physicActor->getOnGround() || physicActor->getOnSlope())
velocity = velocity + inertia;
}
// Dead and paralyzed actors underwater will float to the surface,
// if the CharacterController tells us to do so
if (actor.mMovement.z() > 0 && actor.mFloatToSurface && actor.mPosition.z() < swimlevel)
if (actor.mMovement.z() > 0 && actor.mInert && actor.mPosition.z() < swimlevel)
{
velocity = osg::Vec3f(0,0,1) * 25;
}
else if (actor.mPosition.z() < swimlevel || actor.mFlying)
{
velocity = (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement;
}
else
{
velocity = (osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement;
if (actor.mWantJump)
actor.mDidJump = true;
if ((velocity.z() > 0.f && actor.mIsOnGround && !actor.mIsOnSlope)
|| (velocity.z() > 0.f && velocity.z() + actor.mInertia.z() <= -velocity.z() && actor.mIsOnSlope))
actor.mInertia = velocity;
else if (!actor.mIsOnGround || actor.mIsOnSlope)
velocity = velocity + actor.mInertia;
}
// Now that we have the effective movement vector, apply wind forces to it
if (worldData.mIsInStorm)
@ -188,7 +171,7 @@ namespace MWPhysics
velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f));
}
Stepper stepper(collisionWorld, colobj);
Stepper stepper(collisionWorld, actor.mCollisionObject);
osg::Vec3f origVelocity = velocity;
osg::Vec3f newPosition = actor.mPosition;
/*
@ -197,7 +180,6 @@ namespace MWPhysics
* The initial velocity was set earlier (see above).
*/
float remainingTime = time;
bool seenGround = physicActor->getOnGround() && !physicActor->getOnSlope() && !actor.mFlying;
int numTimesSlid = 0;
osg::Vec3f lastSlideNormal(0,0,1);
@ -207,9 +189,10 @@ namespace MWPhysics
for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.0001f; ++iterations)
{
osg::Vec3f nextpos = newPosition + velocity * remainingTime;
bool underwater = newPosition.z() < swimlevel;
// If not able to fly, don't allow to swim up into the air
if(!actor.mFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel)
if(!actor.mFlying && nextpos.z() > swimlevel && underwater)
{
const osg::Vec3f down(0,0,-1);
velocity = reject(velocity, down);
@ -220,7 +203,7 @@ namespace MWPhysics
if((newPosition - nextpos).length2() > 0.0001)
{
// trace to where character would go if there were no obstructions
tracer.doTrace(colobj, newPosition, nextpos, collisionWorld);
tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld);
// check for obstructions
if(tracer.mFraction >= 1.0f)
@ -241,11 +224,10 @@ namespace MWPhysics
break;
}
if (isWalkableSlope(tracer.mPlaneNormal) && !actor.mFlying && newPosition.z() >= swimlevel)
seenGround = true;
bool seenGround = !actor.mFlying && !underwater && ((actor.mIsOnGround && !actor.mIsOnSlope) || isWalkableSlope(tracer.mPlaneNormal));
// We hit something. Check if we can step up.
float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z();
float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + actor.mHalfExtentsZ;
osg::Vec3f oldPosition = newPosition;
bool usedStepLogic = false;
if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject))
@ -256,9 +238,7 @@ namespace MWPhysics
}
if (usedStepLogic)
{
// don't let pure water creatures move out of water after stepMove
const auto ptr = physicActor->getPtr();
if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel)
if (actor.mIsAquatic && newPosition.z() + actor.mHalfExtentsZ > actor.mWaterlevel)
newPosition = oldPosition;
else if(!actor.mFlying && actor.mPosition.z() >= swimlevel)
forceGroundTest = true;
@ -317,7 +297,7 @@ namespace MWPhysics
// version of surface rejection for acute crevices/seams
auto averageNormal = bestNormal + planeNormal;
averageNormal.normalize();
tracer.doTrace(colobj, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld);
tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld);
newPosition = (newPosition + tracer.mEndPos)/2.0;
usedSeamLogic = true;
@ -333,7 +313,7 @@ namespace MWPhysics
// but this is along the collision normal
if(!usedSeamLogic && (iterations > 0 || remainingTime < 0.01f))
{
tracer.doTrace(colobj, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld);
tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld);
newPosition = (newPosition + tracer.mEndPos)/2.0;
}
@ -353,26 +333,22 @@ namespace MWPhysics
bool isOnGround = false;
bool isOnSlope = false;
if (forceGroundTest || (inertia.z() <= 0.f && newPosition.z() >= swimlevel))
if (forceGroundTest || (actor.mInertia.z() <= 0.f && newPosition.z() >= swimlevel))
{
osg::Vec3f from = newPosition;
auto dropDistance = 2*sGroundOffset + (physicActor->getOnGround() ? sStepSizeDown : 0);
auto dropDistance = 2*sGroundOffset + (actor.mIsOnGround ? sStepSizeDown : 0);
osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance);
tracer.doTrace(colobj, from, to, collisionWorld);
tracer.doTrace(actor.mCollisionObject, from, to, collisionWorld);
if(tracer.mFraction < 1.0f)
{
if (!isActor(tracer.mHitObject))
{
isOnGround = true;
isOnSlope = !isWalkableSlope(tracer.mPlaneNormal);
actor.mStandingOn = tracer.mHitObject;
const btCollisionObject* standingOn = tracer.mHitObject;
PtrHolder* ptrHolder = static_cast<PtrHolder*>(standingOn->getUserPointer());
if (ptrHolder)
actor.mStandingOn = ptrHolder->getPtr();
if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
physicActor->setWalkingOnWater(true);
if (actor.mStandingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
actor.mWalkingOnWater = true;
if (!actor.mFlying && !isOnSlope)
{
if (tracer.mFraction*dropDistance > sGroundOffset)
@ -380,7 +356,7 @@ namespace MWPhysics
else
{
newPosition.z() = tracer.mEndPos.z();
tracer.doTrace(colobj, newPosition, newPosition + osg::Vec3f(0, 0, 2*sGroundOffset), collisionWorld);
tracer.doTrace(actor.mCollisionObject, newPosition, newPosition + osg::Vec3f(0, 0, 2*sGroundOffset), collisionWorld);
newPosition = (newPosition+tracer.mEndPos)/2.0;
}
}
@ -395,7 +371,7 @@ namespace MWPhysics
}
}
// forcibly treat stuck actors as if they're on flat ground because buggy collisions when inside of things can/will break ground detection
if(physicActor->getStuckFrames() > 0)
if(actor.mStuckFrames > 0)
{
isOnGround = true;
isOnSlope = false;
@ -403,24 +379,23 @@ namespace MWPhysics
}
if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying)
physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f));
actor.mInertia = osg::Vec3f(0.f, 0.f, 0.f);
else
{
inertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter;
if (inertia.z() < 0)
inertia.z() *= actor.mSlowFall;
actor.mInertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter;
if (actor.mInertia.z() < 0)
actor.mInertia.z() *= actor.mSlowFall;
if (actor.mSlowFall < 1.f) {
inertia.x() *= actor.mSlowFall;
inertia.y() *= actor.mSlowFall;
actor.mInertia.x() *= actor.mSlowFall;
actor.mInertia.y() *= actor.mSlowFall;
}
physicActor->setInertialForce(inertia);
}
physicActor->setOnGround(isOnGround);
physicActor->setOnSlope(isOnSlope);
actor.mIsOnGround = isOnGround;
actor.mIsOnSlope = isOnSlope;
actor.mPosition = newPosition;
// remove what was added earlier in compensating for doTrace not taking interior transformation into account
actor.mPosition.z() -= halfExtents.z(); // vanilla-accurate
actor.mPosition.z() -= actor.mHalfExtentsZ; // vanilla-accurate
}
btVector3 addMarginToDelta(btVector3 delta)
@ -432,53 +407,47 @@ namespace MWPhysics
void MovementSolver::unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld)
{
const auto& ptr = actor.mActorRaw->getPtr();
if (!ptr.getClass().isMobile(ptr))
if(actor.mSkipCollisionDetection) // noclipping/tcl
return;
auto* physicActor = actor.mActorRaw;
if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection) // noclipping/tcl
return;
auto* collisionObject = physicActor->getCollisionObject();
auto tempPosition = actor.mPosition;
if(physicActor->getStuckFrames() >= 10)
if(actor.mStuckFrames >= 10)
{
if((physicActor->getLastStuckPosition() - actor.mPosition).length2() < 100)
if((actor.mLastStuckPosition - actor.mPosition).length2() < 100)
return;
else
{
physicActor->setStuckFrames(0);
physicActor->setLastStuckPosition({0, 0, 0});
actor.mStuckFrames = 0;
actor.mLastStuckPosition = {0, 0, 0};
}
}
// use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver)
// if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation()
const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z());
const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, actor.mHalfExtentsZ);
// use a 3d approximation of the movement vector to better judge player intent
auto velocity = (osg::Quat(actor.mRefpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRefpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
auto velocity = (osg::Quat(actor.mRotation.x(), osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRotation.y(), osg::Vec3f(0, 0, -1))) * actor.mMovement;
// try to pop outside of the world before doing anything else if we're inside of it
if (!physicActor->getOnGround() || physicActor->getOnSlope())
velocity += physicActor->getInertialForce();
if (!actor.mIsOnGround || actor.mIsOnSlope)
velocity += actor.mInertia;
// because of the internal collision box offset hack, and the fact that we're moving the collision box manually,
// we need to replicate part of the collision box's transform process from scratch
osg::Vec3f refPosition = tempPosition + verticalHalfExtent;
osg::Vec3f goodPosition = refPosition;
const btTransform oldTransform = collisionObject->getWorldTransform();
const btTransform oldTransform = actor.mCollisionObject->getWorldTransform();
btTransform newTransform = oldTransform;
auto gatherContacts = [&](btVector3 newOffset) -> ContactCollectionCallback
{
goodPosition = refPosition + Misc::Convert::toOsg(addMarginToDelta(newOffset));
newTransform.setOrigin(Misc::Convert::toBullet(goodPosition));
collisionObject->setWorldTransform(newTransform);
actor.mCollisionObject->setWorldTransform(newTransform);
ContactCollectionCallback callback{collisionObject, velocity};
ContactTestWrapper::contactTest(const_cast<btCollisionWorld*>(collisionWorld), collisionObject, callback);
ContactCollectionCallback callback{actor.mCollisionObject, velocity};
ContactTestWrapper::contactTest(const_cast<btCollisionWorld*>(collisionWorld), actor.mCollisionObject, callback);
return callback;
};
@ -486,8 +455,8 @@ namespace MWPhysics
auto contactCallback = gatherContacts({0.0, 0.0, 0.0});
if(contactCallback.mDistance < -sAllowedPenetration)
{
physicActor->setStuckFrames(physicActor->getStuckFrames() + 1);
physicActor->setLastStuckPosition(actor.mPosition);
++actor.mStuckFrames;
actor.mLastStuckPosition = actor.mPosition;
// we are; try moving it out of the world
auto positionDelta = contactCallback.mContactSum;
// limit rejection delta to the largest known individual rejections
@ -522,11 +491,11 @@ namespace MWPhysics
}
else
{
physicActor->setStuckFrames(0);
physicActor->setLastStuckPosition({0, 0, 0});
actor.mStuckFrames = 0;
actor.mLastStuckPosition = {0, 0, 0};
}
collisionObject->setWorldTransform(oldTransform);
actor.mCollisionObject->setWorldTransform(oldTransform);
actor.mPosition = tempPosition;
}
}

View file

@ -11,7 +11,6 @@
#include "../mwmechanics/movement.hpp"
#include "../mwrender/bulletdebugdraw.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/player.hpp"
#include "actor.hpp"
#include "contacttestwrapper.h"
@ -50,60 +49,38 @@ namespace
bool mCanBeSharedLock;
};
bool isUnderWater(const MWPhysics::ActorFrameData& actorData)
{
return actorData.mPosition.z() < actorData.mSwimLevel;
}
void handleFall(MWPhysics::ActorFrameData& actorData, bool simulationPerformed)
{
const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight;
const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mActorRaw->getOnGround());
const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mIsOnGround);
if (isStillOnGround || actorData.mFlying || actorData.mSwimming || actorData.mSlowFall < 1)
if (isStillOnGround || actorData.mFlying || isUnderWater(actorData) || actorData.mSlowFall < 1)
actorData.mNeedLand = true;
else if (heightDiff < 0)
actorData.mFallHeight += heightDiff;
}
void handleJump(const MWWorld::Ptr &ptr)
void updateMechanics(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData)
{
const bool isPlayer = (ptr == MWMechanics::getPlayer());
// Advance acrobatics and set flag for GetPCJumping
if (isPlayer)
{
ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0);
MWBase::Environment::get().getWorld()->getPlayer().setJumping(true);
}
// Decrease fatigue
if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState())
{
const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat();
const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat();
const float normalizedEncumbrance = std::min(1.f, ptr.getClass().getNormalizedEncumbrance(ptr));
const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult;
MWMechanics::DynamicStat<float> fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue();
fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease);
ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue);
}
ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0;
}
void updateMechanics(MWPhysics::ActorFrameData& actorData)
{
auto ptr = actorData.mActorRaw->getPtr();
if (actorData.mDidJump)
handleJump(ptr);
auto ptr = actor.getPtr();
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
if (actorData.mNeedLand)
stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming));
stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || isUnderWater(actorData)));
else if (actorData.mFallHeight < 0)
stats.addToFallHeight(-actorData.mFallHeight);
}
osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
osg::Vec3f interpolateMovements(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
{
const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f);
return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
return actorData.mPosition * interpolationFactor + actor.getPreviousPosition() * (1.f - interpolationFactor);
}
namespace Config
@ -230,7 +207,7 @@ namespace MWPhysics
return std::make_tuple(numSteps, actualDelta);
}
void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
void PhysicsTaskScheduler::applyQueuedMovements(float & timeAccum, std::vector<std::weak_ptr<Actor>>&& actors, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{
// This function run in the main thread.
// While the mSimulationMutex is held, background physics threads can't run.
@ -242,22 +219,12 @@ namespace MWPhysics
// start by finishing previous background computation
if (mNumThreads != 0)
{
for (auto& data : mActorsFrameData)
for (size_t i = 0; i < mActors.size(); ++i)
{
const auto actorActive = [&data](const auto& newFrameData) -> bool
if (auto actor = mActors[i].lock())
{
const auto actor = data.mActor.lock();
return actor && actor->getPtr() == newFrameData.mActorRaw->getPtr();
};
// Only return actors that are still part of the scene
if (std::any_of(actorsData.begin(), actorsData.end(), actorActive))
{
updateMechanics(data);
// these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values
if (mAdvanceSimulation)
data.mActorRaw->setStandingOnPtr(data.mStandingOn);
data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt));
updateMechanics(*actor, mActorsFrameData[i]);
updateActor(*actor, mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt);
}
}
if(mAdvanceSimulation)
@ -269,12 +236,16 @@ namespace MWPhysics
timeAccum -= numSteps*newDelta;
// init
for (auto& data : actorsData)
data.updatePosition(mCollisionWorld);
for (size_t i = 0; i < actors.size(); ++i)
{
assert(actors[i].lock());
actorsData[i].updatePosition(*actors[i].lock(), mCollisionWorld);
}
mPrevStepCount = numSteps;
mRemainingSteps = numSteps;
mTimeAccum = timeAccum;
mPhysicsDt = newDelta;
mActors = std::move(actors);
mActorsFrameData = std::move(actorsData);
mAdvanceSimulation = (mRemainingSteps != 0);
mNewFrame = true;
@ -371,12 +342,14 @@ namespace MWPhysics
void PhysicsTaskScheduler::addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask)
{
mCollisionObjects.insert(collisionObject);
std::unique_lock lock(mCollisionWorldMutex);
mCollisionWorld->addCollisionObject(collisionObject, collisionFilterGroup, collisionFilterMask);
}
void PhysicsTaskScheduler::removeCollisionObject(btCollisionObject* collisionObject)
{
mCollisionObjects.erase(collisionObject);
std::unique_lock lock(mCollisionWorldMutex);
mCollisionWorld->removeCollisionObject(collisionObject);
}
@ -478,7 +451,7 @@ namespace MWPhysics
int job = 0;
while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
{
if(const auto actor = mActorsFrameData[job].mActor.lock())
if(const auto actor = mActors[job].lock())
{
MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData);
@ -491,10 +464,9 @@ namespace MWPhysics
{
while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
{
if(const auto actor = mActorsFrameData[job].mActor.lock())
if(const auto actor = mActors[job].lock())
{
auto& actorData = mActorsFrameData[job];
handleFall(actorData, mAdvanceSimulation);
handleFall(mActorsFrameData[job], mAdvanceSimulation);
}
}
@ -506,14 +478,14 @@ namespace MWPhysics
void PhysicsTaskScheduler::updateActorsPositions()
{
for (auto& actorData : mActorsFrameData)
for (size_t i = 0; i < mActors.size(); ++i)
{
if(const auto actor = actorData.mActor.lock())
if(const auto actor = mActors[i].lock())
{
if (actor->setPosition(actorData.mPosition))
if (actor->setPosition(mActorsFrameData[i].mPosition))
{
std::scoped_lock lock(mCollisionWorldMutex);
actorData.mPosition = actor->getPosition(); // account for potential position change made by script
mActorsFrameData[i].mPosition = actor->getPosition(); // account for potential position change made by script
actor->updateCollisionObjectPosition();
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
}
@ -521,6 +493,27 @@ namespace MWPhysics
}
}
void PhysicsTaskScheduler::updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const
{
actor.setSimulationPosition(interpolateMovements(actor, actorData, timeAccum, dt));
actor.setLastStuckPosition(actorData.mLastStuckPosition);
actor.setStuckFrames(actorData.mStuckFrames);
if (simulationPerformed)
{
MWWorld::Ptr standingOn;
auto* ptrHolder = static_cast<MWPhysics::PtrHolder*>(getUserPointer(actorData.mStandingOn));
if (ptrHolder)
standingOn = ptrHolder->getPtr();
actor.setStandingOnPtr(standingOn);
// the "on ground" state of an actor might have been updated by a traceDown, don't overwrite the change
if (actor.getOnGround() == actorData.mWasOnGround)
actor.setOnGround(actorData.mIsOnGround);
actor.setOnSlope(actorData.mIsOnSlope);
actor.setWalkingOnWater(actorData.mWalkingOnWater);
actor.setInertialForce(actorData.mInertia);
}
}
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
@ -549,13 +542,13 @@ namespace MWPhysics
updateActorsPositions();
}
for (auto& actorData : mActorsFrameData)
for (size_t i = 0; i < mActors.size(); ++i)
{
handleFall(actorData, mAdvanceSimulation);
actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt));
updateMechanics(actorData);
if (mAdvanceSimulation)
actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn);
auto actor = mActors[i].lock();
assert(actor);
handleFall(mActorsFrameData[i], mAdvanceSimulation);
updateMechanics(*actor, mActorsFrameData[i]);
updateActor(*actor, mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt);
}
refreshLOSCache();
}
@ -581,17 +574,27 @@ namespace MWPhysics
mDebugDrawer->step();
}
void* PhysicsTaskScheduler::getUserPointer(const btCollisionObject* object) const
{
auto it = mCollisionObjects.find(object);
if (it == mCollisionObjects.end())
return nullptr;
return (*it)->getUserPointer();
}
void PhysicsTaskScheduler::afterPreStep()
{
updateAabbs();
if (!mRemainingSteps)
return;
for (auto& data : mActorsFrameData)
if (const auto actor = data.mActor.lock())
for (size_t i = 0; i < mActors.size(); ++i)
{
if (auto actor = mActors[i].lock())
{
std::unique_lock lock(mCollisionWorldMutex);
MovementSolver::unstuck(data, mCollisionWorld);
MovementSolver::unstuck(mActorsFrameData[i], mCollisionWorld);
}
}
}
void PhysicsTaskScheduler::afterPostStep()

View file

@ -6,6 +6,7 @@
#include <optional>
#include <shared_mutex>
#include <thread>
#include <unordered_set>
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
@ -38,7 +39,7 @@ namespace MWPhysics
/// @param timeAccum accumulated time from previous run to interpolate movements
/// @param actorsData per actor data needed to compute new positions
/// @return new position of each actor
void applyQueuedMovements(float & timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
void applyQueuedMovements(float & timeAccum, std::vector<std::weak_ptr<Actor>>&& actors, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
void resetSimulation(const ActorMap& actors);
@ -55,11 +56,13 @@ namespace MWPhysics
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate=false);
bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2);
void debugDraw();
void* getUserPointer(const btCollisionObject* object) const;
private:
void syncComputation();
void worker();
void updateActorsPositions();
void updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const;
bool hasLineOfSight(const Actor* actor1, const Actor* actor2);
void refreshLOSCache();
void updateAabbs();
@ -71,7 +74,9 @@ namespace MWPhysics
void afterPostSim();
std::unique_ptr<WorldFrameData> mWorldFrameData;
std::vector<std::weak_ptr<Actor>> mActors;
std::vector<ActorFrameData> mActorsFrameData;
std::unordered_set<const btCollisionObject*> mCollisionObjects;
float mDefaultPhysicsDt;
float mPhysicsDt;
float mTimeAccum;

View file

@ -38,6 +38,7 @@
#include "../mwworld/esmstore.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/player.hpp"
#include "../mwrender/bulletdebugdraw.hpp"
@ -72,6 +73,36 @@ namespace
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, world);
return (tracer.mFraction >= 1.0f);
}
void handleJump(const MWWorld::Ptr &ptr)
{
if (!ptr.getClass().isActor())
return;
if (ptr.getClass().getMovementSettings(ptr).mPosition[2] == 0)
return;
const bool isPlayer = (ptr == MWMechanics::getPlayer());
// Advance acrobatics and set flag for GetPCJumping
if (isPlayer)
{
ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0);
MWBase::Environment::get().getWorld()->getPlayer().setJumping(true);
}
// Decrease fatigue
if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState())
{
const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat();
const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat();
const float normalizedEncumbrance = std::min(1.f, ptr.getClass().getNormalizedEncumbrance(ptr));
const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult;
MWMechanics::DynamicStat<float> fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue();
fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease);
ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue);
}
ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0;
}
}
namespace MWPhysics
@ -750,24 +781,29 @@ namespace MWPhysics
actor->setVelocity(osg::Vec3f());
}
std::vector<ActorFrameData> PhysicsSystem::prepareFrameData(bool willSimulate)
std::pair<std::vector<std::weak_ptr<Actor>>, std::vector<ActorFrameData>> PhysicsSystem::prepareFrameData(bool willSimulate)
{
std::vector<ActorFrameData> actorsFrameData;
actorsFrameData.reserve(mActors.size());
std::pair<std::vector<std::weak_ptr<Actor>>, std::vector<ActorFrameData>> framedata;
framedata.first.reserve(mActors.size());
framedata.second.reserve(mActors.size());
const MWBase::World *world = MWBase::Environment::get().getWorld();
for (const auto& [ptr, physicActor] : mActors)
for (const auto& [actor, physicActor] : mActors)
{
auto ptr = physicActor->getPtr();
if (!actor.getClass().isMobile(ptr))
continue;
float waterlevel = -std::numeric_limits<float>::max();
const MWWorld::CellStore *cell = ptr.getCell();
const MWWorld::CellStore *cell = actor.getCell();
if(cell->getCell()->hasWater())
waterlevel = cell->getWaterLevel();
const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(physicActor->getPtr()).getMagicEffects();
const auto& stats = ptr.getClass().getCreatureStats(ptr);
const MWMechanics::MagicEffects& effects = stats.getMagicEffects();
bool waterCollision = false;
if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude())
{
if (physicActor->getCollisionMode() || !world->isUnderwater(ptr.getCell(), osg::Vec3f(ptr.getRefData().getPosition().asVec3())))
if (physicActor->getCollisionMode() || !world->isUnderwater(actor.getCell(), actor.getRefData().getPosition().asVec3()))
waterCollision = true;
}
@ -775,15 +811,17 @@ namespace MWPhysics
// Slow fall reduces fall speed by a factor of (effect magnitude / 200)
const float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f));
const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState();
const bool inert = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0);
// Ue current value only if we don't advance the simulation. Otherwise we might get a stale value.
MWWorld::Ptr standingOn;
if (!willSimulate)
standingOn = physicActor->getStandingOnPtr();
framedata.first.emplace_back(physicActor);
framedata.second.emplace_back(*physicActor, inert, waterCollision, slowFall, waterlevel);
actorsFrameData.emplace_back(physicActor, standingOn, waterCollision, slowFall, waterlevel);
// if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly.
if (willSimulate)
handleJump(ptr);
}
return actorsFrameData;
return framedata;
}
void PhysicsSystem::stepSimulation(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
@ -808,8 +846,11 @@ namespace MWPhysics
if (skipSimulation)
mTaskScheduler->resetSimulation(mActors);
else
{
auto [actors, framedata] = prepareFrameData(mTimeAccum >= mPhysicsDt);
// modifies mTimeAccum
mTaskScheduler->applyQueuedMovements(mTimeAccum, prepareFrameData(mTimeAccum >= mPhysicsDt), frameStart, frameNumber, stats);
mTaskScheduler->applyQueuedMovements(mTimeAccum, std::move(actors), std::move(framedata), frameStart, frameNumber, stats);
}
}
void PhysicsSystem::moveActors()
@ -946,34 +987,46 @@ namespace MWPhysics
mDebugDrawer->addCollision(position, normal);
}
ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn,
bool waterCollision, float slowFall, float waterlevel)
: mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn),
mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision), mSkipCollisionDetection(actor->skipCollisions()),
mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(actor->velocity()), mPosition(), mRefpos()
ActorFrameData::ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel)
: mPosition()
, mStandingOn(nullptr)
, mIsOnGround(actor.getOnGround())
, mIsOnSlope(actor.getOnSlope())
, mWalkingOnWater(false)
, mInert(inert)
, mCollisionObject(actor.getCollisionObject())
, mSwimLevel(waterlevel - (actor.getRenderingHalfExtents().z() * 2 * MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat()))
, mSlowFall(slowFall)
, mRotation()
, mMovement(actor.velocity())
, mWaterlevel(waterlevel)
, mHalfExtentsZ(actor.getHalfExtents().z())
, mOldHeight(0)
, mFallHeight(0)
, mFlying(MWBase::Environment::get().getWorld()->isFlying(actor.getPtr()))
, mWasOnGround(actor.getOnGround())
, mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr()))
, mWaterCollision(waterCollision)
, mSkipCollisionDetection(actor.skipCollisions() || !actor.getCollisionMode())
, mNeedLand(false)
{
const MWBase::World *world = MWBase::Environment::get().getWorld();
const auto ptr = actor->getPtr();
mFlying = world->isFlying(ptr);
mSwimming = world->isSwimming(ptr);
mWantJump = ptr.getClass().getMovementSettings(ptr).mPosition[2] != 0;
auto& stats = ptr.getClass().getCreatureStats(ptr);
const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState();
mFloatToSurface = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0);
mWasOnGround = actor->getOnGround();
}
void ActorFrameData::updatePosition(btCollisionWorld* world)
void ActorFrameData::updatePosition(Actor& actor, btCollisionWorld* world)
{
mActorRaw->applyOffsetChange();
mPosition = mActorRaw->getPosition();
if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world))
actor.applyOffsetChange();
mPosition = actor.getPosition();
if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(&actor, mWaterlevel, world))
{
mPosition.z() = mWaterlevel;
MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition, false);
MWBase::Environment::get().getWorld()->moveObject(actor.getPtr(), mPosition, false);
}
mOldHeight = mPosition.z();
mRefpos = mActorRaw->getPtr().getRefData().getPosition();
const auto rotation = actor.getPtr().getRefData().getPosition().asRotationVec3();
mRotation = osg::Vec2f(rotation.x(), rotation.z());
mInertia = actor.getInertialForce();
mStuckFrames = actor.getStuckFrames();
mLastStuckPosition = actor.getLastStuckPosition();
}
WorldFrameData::WorldFrameData()

View file

@ -78,27 +78,32 @@ namespace MWPhysics
struct ActorFrameData
{
ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, float slowFall, float waterlevel);
void updatePosition(btCollisionWorld* world);
std::weak_ptr<Actor> mActor;
Actor* mActorRaw;
MWWorld::Ptr mStandingOn;
bool mFlying;
bool mSwimming;
bool mWasOnGround;
bool mWantJump;
bool mDidJump;
bool mFloatToSurface;
bool mNeedLand;
bool mWaterCollision;
bool mSkipCollisionDetection;
float mWaterlevel;
float mSlowFall;
ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel);
void updatePosition(Actor& actor, btCollisionWorld* world);
osg::Vec3f mPosition;
osg::Vec3f mInertia;
const btCollisionObject* mStandingOn;
bool mIsOnGround;
bool mIsOnSlope;
bool mWalkingOnWater;
const bool mInert;
btCollisionObject* mCollisionObject;
const float mSwimLevel;
const float mSlowFall;
osg::Vec2f mRotation;
osg::Vec3f mMovement;
osg::Vec3f mLastStuckPosition;
const float mWaterlevel;
const float mHalfExtentsZ;
float mOldHeight;
float mFallHeight;
osg::Vec3f mMovement;
osg::Vec3f mPosition;
ESM::Position mRefpos;
unsigned int mStuckFrames;
const bool mFlying;
const bool mWasOnGround;
const bool mIsAquatic;
const bool mWaterCollision;
const bool mSkipCollisionDetection;
bool mNeedLand;
};
struct WorldFrameData
@ -254,7 +259,7 @@ namespace MWPhysics
void updateWater();
std::vector<ActorFrameData> prepareFrameData(bool willSimulate);
std::pair<std::vector<std::weak_ptr<Actor>>, std::vector<ActorFrameData>> prepareFrameData(bool willSimulate);
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;