We need to update the collision world after each step.

Change order of traversal simulation step to make it rare enough to be parallelizable

Before:
for actor in actors:
    repeat numstep:
        solve(actor)
After:
repeat numstep:
    for actor in actors:
        solve(actor)

Introduce struct ActorFrameData to pack all data that is necessary for
the solver
pull/3013/head
fredzio 4 years ago
parent d76cc5d0a9
commit 91b3926a49

@ -10,18 +10,14 @@
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/player.hpp"
#include "../mwworld/refdata.hpp"
#include "actor.hpp"
#include "collisiontype.hpp"
#include "constants.hpp"
#include "physicssystem.hpp"
#include "stepper.hpp"
#include "trace.h"
@ -78,24 +74,26 @@ namespace MWPhysics
return tracer.mEndPos-offset + osg::Vec3f(0.f, 0.f, sGroundOffset);
}
osg::Vec3f MovementSolver::move(osg::Vec3f position, const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time,
bool isFlying, float waterlevel, float slowFall, const btCollisionWorld* collisionWorld,
std::map<MWWorld::Ptr, MWWorld::Ptr>& standingCollisionTracker)
void MovementSolver::move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld,
WorldFrameData& worldData)
{
const ESM::Position& refpos = ptr.getRefData().getPosition();
auto* physicActor = actor.mActorRaw;
auto ptr = actor.mPtr;
const ESM::Position& refpos = actor.mRefpos;
// Early-out for totally static creatures
// (Not sure if gravity should still apply?)
if (!ptr.getClass().isMobile(ptr))
return position;
return;
// Reset per-frame data
physicActor->setWalkingOnWater(false);
// Anything to collide with?
if(!physicActor->getCollisionMode())
{
return position + (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) *
actor.mPosition += (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) *
osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))
) * movement * time;
) * actor.mMovement * time;
return;
}
const btCollisionObject *colobj = physicActor->getCollisionObject();
@ -105,23 +103,23 @@ namespace MWPhysics
// That means the collision shape used for moving this actor is in a different spot than the collision shape
// other actors are using to collide against this actor.
// While this is strictly speaking wrong, it's needed for MW compatibility.
position.z() += halfExtents.z();
actor.mPosition.z() += halfExtents.z();
static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
float swimlevel = waterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
ActorTracer tracer;
osg::Vec3f inertia = physicActor->getInertialForce();
osg::Vec3f velocity;
if (position.z() < swimlevel || isFlying)
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))) * movement;
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))) * movement;
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()))
@ -131,38 +129,16 @@ namespace MWPhysics
}
// dead actors underwater will float to the surface, if the CharacterController tells us to do so
if (movement.z() > 0 && ptr.getClass().getCreatureStats(ptr).isDead() && position.z() < swimlevel)
if (actor.mMovement.z() > 0 && actor.mIsDead && actor.mPosition.z() < swimlevel)
velocity = osg::Vec3f(0,0,1) * 25;
if (ptr.getClass().getMovementSettings(ptr).mPosition[2])
{
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;
}
if (actor.mWantJump)
actor.mDidJump = true;
// Now that we have the effective movement vector, apply wind forces to it
if (MWBase::Environment::get().getWorld()->isInStorm())
if (worldData.mIsInStorm)
{
osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection();
osg::Vec3f stormDirection = worldData.mStormDirection;
float angleDegrees = osg::RadiansToDegrees(std::acos(stormDirection * velocity / (stormDirection.length() * velocity.length())));
static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fStromWalkMult")->mValue.getFloat();
velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f));
@ -170,7 +146,7 @@ namespace MWPhysics
Stepper stepper(collisionWorld, colobj);
osg::Vec3f origVelocity = velocity;
osg::Vec3f newPosition = position;
osg::Vec3f newPosition = actor.mPosition;
/*
* A loop to find newPosition using tracer, if successful different from the starting position.
* nextpos is the local variable used to find potential newPosition, using velocity and remainingTime
@ -182,7 +158,7 @@ namespace MWPhysics
osg::Vec3f nextpos = newPosition + velocity * remainingTime;
// If not able to fly, don't allow to swim up into the air
if(!isFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel)
if(!actor.mFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel)
{
const osg::Vec3f down(0,0,-1);
velocity = slide(velocity, down);
@ -235,7 +211,7 @@ namespace MWPhysics
if (result)
{
// don't let pure water creatures move out of water after stepMove
if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > waterlevel)
if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel)
newPosition = oldPosition;
}
else
@ -245,7 +221,7 @@ namespace MWPhysics
// Do not allow sliding upward if there is gravity.
// Stepping will have taken care of that.
if(!(newPosition.z() < swimlevel || isFlying))
if(!(newPosition.z() < swimlevel || actor.mFlying))
newVelocity.z() = std::min(newVelocity.z(), 0.0f);
if ((newVelocity-velocity).length2() < 0.01)
@ -269,11 +245,11 @@ namespace MWPhysics
const btCollisionObject* standingOn = tracer.mHitObject;
PtrHolder* ptrHolder = static_cast<PtrHolder*>(standingOn->getUserPointer());
if (ptrHolder)
standingCollisionTracker[ptr] = ptrHolder->getPtr();
actor.mStandingOn = ptrHolder->getPtr();
if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
physicActor->setWalkingOnWater(true);
if (!isFlying)
if (!actor.mFlying)
newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
isOnGround = true;
@ -292,7 +268,7 @@ namespace MWPhysics
btVector3 aabbMin, aabbMax;
tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax);
btVector3 center = (aabbMin + aabbMax) / 2.f;
inertia = osg::Vec3f(position.x() - center.x(), position.y() - center.y(), 0);
inertia = osg::Vec3f(actor.mPosition.x() - center.x(), actor.mPosition.y() - center.y(), 0);
inertia.normalize();
inertia *= 100;
}
@ -302,16 +278,16 @@ namespace MWPhysics
}
}
if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || isFlying)
if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying)
physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f));
else
{
inertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter;
if (inertia.z() < 0)
inertia.z() *= slowFall;
if (slowFall < 1.f) {
inertia.x() *= slowFall;
inertia.y() *= slowFall;
inertia.z() *= actor.mSlowFall;
if (actor.mSlowFall < 1.f) {
inertia.x() *= actor.mSlowFall;
inertia.y() *= actor.mSlowFall;
}
physicActor->setInertialForce(inertia);
}
@ -319,6 +295,6 @@ namespace MWPhysics
physicActor->setOnSlope(isOnSlope);
newPosition.z() -= halfExtents.z(); // remove what was added at the beginning
return newPosition;
actor.mPosition = newPosition;
}
}

@ -5,13 +5,18 @@
#include <osg/Vec3f>
#include "../mwworld/ptr.hpp"
class btCollisionWorld;
namespace MWWorld
{
class Ptr;
}
namespace MWPhysics
{
class Actor;
struct ActorFrameData;
struct WorldFrameData;
class MovementSolver
{
@ -31,9 +36,7 @@ namespace MWPhysics
public:
static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight);
static osg::Vec3f move(osg::Vec3f position, const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time,
bool isFlying, float waterlevel, float slowFall, const btCollisionWorld* collisionWorld,
std::map<MWWorld::Ptr, MWWorld::Ptr>& standingCollisionTracker);
static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData);
};
}

@ -31,9 +31,11 @@
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/player.hpp"
#include "../mwrender/bulletdebugdraw.hpp"
@ -687,14 +689,106 @@ namespace MWPhysics
numSteps = std::min(numSteps, maxAllowedSteps);
mTimeAccum -= numSteps * mPhysicsDt;
mActorsFrameData = prepareFrameData();
bool advanceSimulation = (numSteps != 0);
if (advanceSimulation)
mWorldFrameData = std::make_unique<WorldFrameData>();
if (numSteps)
// update each actor position based on latest data
for (auto& data : mActorsFrameData)
data.updatePosition();
mMovementResults.clear();
while (numSteps--)
{
for (auto& actorData : mActorsFrameData)
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
// update actors position
for (auto& actorData : mActorsFrameData)
{
if(const auto actor = actorData.mActor.lock())
{
if (actorData.mPosition != actorData.mActorRaw->getPosition())
actorData.mPositionChanged = true;
actorData.mActorRaw->setPosition(actorData.mPosition);
}
}
}
for (auto& actorData : mActorsFrameData)
{
// handle fall of actor
const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight;
const bool isStillOnGround = (advanceSimulation && actorData.mWasOnGround && actorData.mActorRaw->getOnGround());
if (isStillOnGround || actorData.mFlying || actorData.mSwimming || actorData.mSlowFall < 1)
actorData.mNeedLand = true;
else if (heightDiff < 0)
actorData.mFallHeight += heightDiff;
// interpolate position
const float interpolationFactor = mTimeAccum / mPhysicsDt;
mMovementResults[actorData.mPtr] = actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
// update mechanics if actor jumped
if (actorData.mDidJump)
{
const bool isPlayer = (actorData.mPtr == MWMechanics::getPlayer());
// Advance acrobatics and set flag for GetPCJumping
if (isPlayer)
{
actorData.mPtr.getClass().skillUsageSucceeded(actorData.mPtr, 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, actorData.mPtr.getClass().getNormalizedEncumbrance(actorData.mPtr));
const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult;
MWMechanics::DynamicStat<float> fatigue = actorData.mPtr.getClass().getCreatureStats(actorData.mPtr).getFatigue();
fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease);
actorData.mPtr.getClass().getCreatureStats(actorData.mPtr).setFatigue(fatigue);
}
actorData.mPtr.getClass().getMovementSettings(actorData.mPtr).mPosition[2] = 0;
}
MWMechanics::CreatureStats& stats = actorData.mPtr.getClass().getCreatureStats(actorData.mPtr);
if (actorData.mNeedLand)
stats.land(actorData.mPtr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming));
else if (actorData.mFallHeight < 0)
stats.addToFallHeight(-actorData.mFallHeight);
}
// update actors aabb
for (const auto& actorData : mActorsFrameData)
if (actorData.mPositionChanged)
mCollisionWorld->updateSingleAabb(actorData.mActorRaw->getCollisionObject());
// update standing collisions map
if (advanceSimulation)
{
// Collision events should be available on every frame
mStandingCollisions.clear();
for (auto& actorData : mActorsFrameData)
{
if (!actorData.mStandingOn.isEmpty())
mStandingCollisions[actorData.mPtr] = actorData.mStandingOn;
else
mStandingCollisions.erase(actorData.mPtr);
}
}
return mMovementResults;
}
const MWWorld::Ptr player = MWMechanics::getPlayer();
std::vector<ActorFrameData> PhysicsSystem::prepareFrameData()
{
std::vector<ActorFrameData> actorsFrameData;
actorsFrameData.reserve(mMovementQueue.size());
const MWBase::World *world = MWBase::Environment::get().getWorld();
for (const auto& m : mMovementQueue)
{
@ -702,7 +796,10 @@ namespace MWPhysics
const auto& movement = m.second;
const auto foundActor = mActors.find(character);
if (foundActor == mActors.end()) // actor was already removed from the scene
{
mStandingCollisions.erase(character);
continue;
}
auto physicActor = foundActor->second;
float waterlevel = -std::numeric_limits<float>::max();
@ -713,56 +810,27 @@ namespace MWPhysics
const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects();
bool waterCollision = false;
bool moveToWaterSurface = false;
if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude())
{
if (!world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3())))
waterCollision = true;
else if (physicActor->getCollisionMode() && canMoveToWaterSurface(character, waterlevel))
{
const osg::Vec3f actorPosition = physicActor->getPosition();
physicActor->setPosition(osg::Vec3f(actorPosition.x(), actorPosition.y(), waterlevel));
moveToWaterSurface = true;
waterCollision = true;
}
}
physicActor->setCanWaterWalk(waterCollision);
// Slow fall reduces fall speed by a factor of (effect magnitude / 200)
float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f));
bool flying = world->isFlying(character);
bool swimming = world->isSwimming(character);
bool wasOnGround = physicActor->getOnGround();
osg::Vec3f position = physicActor->getPosition();
float oldHeight = position.z();
bool positionChanged = false;
for (int i=0; i<numSteps; ++i)
{
position = MovementSolver::move(position, physicActor->getPtr(), physicActor.get(), movement, mPhysicsDt,
flying, waterlevel, slowFall, mCollisionWorld.get(), mStandingCollisions);
if (position != physicActor->getPosition())
positionChanged = true;
physicActor->setPosition(position); // always set even if unchanged to make sure interpolation is correct
}
if (positionChanged)
mCollisionWorld->updateSingleAabb(physicActor->getCollisionObject());
float interpolationFactor = mTimeAccum / mPhysicsDt;
osg::Vec3f interpolated = position * interpolationFactor + physicActor->getPreviousPosition() * (1.f - interpolationFactor);
float heightDiff = position.z() - oldHeight;
const float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f));
MWMechanics::CreatureStats& stats = character.getClass().getCreatureStats(character);
bool isStillOnGround = (numSteps > 0 && wasOnGround && physicActor->getOnGround());
if (isStillOnGround || flying || swimming || slowFall < 1)
stats.land(character == player && (flying || swimming));
else if (heightDiff < 0)
stats.addToFallHeight(-heightDiff);
mMovementResults.emplace(character, interpolated);
actorsFrameData.emplace_back(std::move(physicActor), character, mStandingCollisions[character], moveToWaterSurface, movement, slowFall, waterlevel);
}
mMovementQueue.clear();
return mMovementResults;
return actorsFrameData;
}
void PhysicsSystem::stepSimulation()
@ -896,4 +964,61 @@ namespace MWPhysics
stats.setAttribute(frameNumber, "Physics Objects", mObjects.size());
stats.setAttribute(frameNumber, "Physics HeightFields", mHeightFields.size());
}
ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn,
bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel)
: mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn),
mPositionChanged(false), mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface),
mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos()
{
const MWBase::World *world = MWBase::Environment::get().getWorld();
mPtr = actor->getPtr();
mFlying = world->isFlying(character);
mSwimming = world->isSwimming(character);
mWantJump = mPtr.getClass().getMovementSettings(mPtr).mPosition[2] != 0;
mIsDead = mPtr.getClass().getCreatureStats(mPtr).isDead();
mWasOnGround = actor->getOnGround();
}
void ActorFrameData::updatePosition()
{
mPosition = mActorRaw->getPosition();
if (mMoveToWaterSurface)
{
mPosition.z() = mWaterlevel;
mActorRaw->setPosition(mPosition);
}
mOldHeight = mPosition.z();
mRefpos = mPtr.getRefData().getPosition();
}
WorldFrameData::WorldFrameData()
: mIsInStorm(MWBase::Environment::get().getWorld()->isInStorm())
, mStormDirection(MWBase::Environment::get().getWorld()->getStormDirection())
{}
LOSRequest::LOSRequest(const std::weak_ptr<Actor>& a1, const std::weak_ptr<Actor>& a2)
: mResult(false), mStale(false), mAge(0)
{
// we use raw actor pointer pair to uniquely identify request
// sort the pointer value in ascending order to not duplicate equivalent requests, eg. getLOS(A, B) and getLOS(B, A)
auto* raw1 = a1.lock().get();
auto* raw2 = a2.lock().get();
assert(raw1 != raw2);
if (raw1 < raw2)
{
mActors = {a1, a2};
mRawActors = {raw1, raw2};
}
else
{
mActors = {a2, a1};
mRawActors = {raw2, raw1};
}
}
bool operator==(const LOSRequest& lhs, const LOSRequest& rhs) noexcept
{
return lhs.mRawActors == rhs.mRawActors;
}
}

@ -1,6 +1,7 @@
#ifndef OPENMW_MWPHYSICS_PHYSICSSYSTEM_H
#define OPENMW_MWPHYSICS_PHYSICSSYSTEM_H
#include <array>
#include <memory>
#include <map>
#include <set>
@ -53,6 +54,51 @@ namespace MWPhysics
class HeightField;
class Object;
class Actor;
class PhysicsTaskScheduler;
struct LOSRequest
{
LOSRequest(const std::weak_ptr<Actor>& a1, const std::weak_ptr<Actor>& a2);
std::array<std::weak_ptr<Actor>, 2> mActors;
std::array<const Actor*, 2> mRawActors;
bool mResult;
bool mStale;
int mAge;
};
bool operator==(const LOSRequest& lhs, const LOSRequest& rhs) noexcept;
struct ActorFrameData
{
ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
void updatePosition();
std::weak_ptr<Actor> mActor;
Actor* mActorRaw;
MWWorld::Ptr mPtr;
MWWorld::Ptr mStandingOn;
bool mFlying;
bool mSwimming;
bool mPositionChanged;
bool mWasOnGround;
bool mWantJump;
bool mDidJump;
bool mIsDead;
bool mNeedLand;
bool mMoveToWaterSurface;
float mWaterlevel;
float mSlowFall;
float mOldHeight;
float mFallHeight;
osg::Vec3f mMovement;
osg::Vec3f mPosition;
ESM::Position mRefpos;
};
struct WorldFrameData
{
WorldFrameData();
bool mIsInStorm;
osg::Vec3f mStormDirection;
};
class PhysicsSystem : public RayCastingInterface
{
@ -191,6 +237,8 @@ namespace MWPhysics
void updateWater();
std::vector<ActorFrameData> prepareFrameData();
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
std::unique_ptr<btBroadphaseInterface> mBroadphase;
@ -224,6 +272,8 @@ namespace MWPhysics
using PtrVelocityList = std::vector<std::pair<MWWorld::Ptr, osg::Vec3f>>;
PtrVelocityList mMovementQueue;
PtrPositionList mMovementResults;
std::unique_ptr<WorldFrameData> mWorldFrameData;
std::vector<ActorFrameData> mActorsFrameData;
float mTimeAccum;

Loading…
Cancel
Save