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:
commit
8c81191d09
8 changed files with 287 additions and 253 deletions
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue