1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-29 01:45:33 +00:00
openmw-tes3mp/apps/openmw/mwphysics/physicssystem.cpp
fredzio e5fa457fe7 Properly account for interleaved move of actors.
Before this change, if an actor position was changed while the physics
simulation was running, the simulation result would be discarded. It is
fine in case of one off event such as teleport, but in the case of
scripts making use of this functionality to make lifts or conveyor (such
as Sotha Sil Expanded mod) it broke actor movement.

To alleviate this issue, at the end of the simulation, the position of the Actor
in the world is compared to the position it had at the beginning of the
simulation. A difference indicate a force move occured. In this case,
the Actor mPosition and mPreviousPosition are translated by the difference of position.

Since the Actor position will be really set while the next simulation runs, we
save it in the mNextPosition field.
2020-11-14 20:39:16 +01:00

938 lines
34 KiB
C++

#include "physicssystem.hpp"
#include <LinearMath/btIDebugDraw.h>
#include <LinearMath/btVector3.h>
#include <memory>
#include <osg/Group>
#include <osg/Stats>
#include <BulletCollision/CollisionShapes/btConeShape.h>
#include <BulletCollision/CollisionShapes/btSphereShape.h>
#include <BulletCollision/CollisionShapes/btStaticPlaneShape.h>
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include <BulletCollision/CollisionDispatch/btDefaultCollisionConfiguration.h>
#include <BulletCollision/BroadphaseCollision/btDbvtBroadphase.h>
#include <LinearMath/btQuickprof.h>
#include <components/nifbullet/bulletnifloader.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/bulletshapemanager.hpp>
#include <components/debug/debuglog.hpp>
#include <components/esm/loadgmst.hpp>
#include <components/misc/constants.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/sceneutil/unrefqueue.hpp>
#include <components/misc/convert.hpp>
#include <components/nifosg/particle.hpp> // FindRecIndexVisitor
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwrender/bulletdebugdraw.hpp"
#include "../mwworld/class.hpp"
#include "collisiontype.hpp"
#include "actor.hpp"
#include "trace.h"
#include "object.hpp"
#include "heightfield.hpp"
#include "hasspherecollisioncallback.hpp"
#include "deepestnotmecontacttestresultcallback.hpp"
#include "closestnotmerayresultcallback.hpp"
#include "contacttestresultcallback.hpp"
#include "constants.hpp"
#include "movementsolver.hpp"
#include "mtphysics.hpp"
namespace MWPhysics
{
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
: mShapeManager(new Resource::BulletShapeManager(resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager()))
, mResourceSystem(resourceSystem)
, mDebugDrawEnabled(false)
, mTimeAccum(0.0f)
, mWaterHeight(0)
, mWaterEnabled(false)
, mParentNode(parentNode)
, mPhysicsDt(1.f / 60.f)
{
mResourceSystem->addResourceManager(mShapeManager.get());
mCollisionConfiguration = std::make_unique<btDefaultCollisionConfiguration>();
mDispatcher = std::make_unique<btCollisionDispatcher>(mCollisionConfiguration.get());
mBroadphase = std::make_unique<btDbvtBroadphase>();
mCollisionWorld = std::make_shared<btCollisionWorld>(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get());
// Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this.
// Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb.
mCollisionWorld->setForceUpdateAllAabbs(false);
// Check if a user decided to override a physics system FPS
const char* env = getenv("OPENMW_PHYSICS_FPS");
if (env)
{
float physFramerate = std::atof(env);
if (physFramerate > 0)
{
mPhysicsDt = 1.f / physFramerate;
Log(Debug::Warning) << "Warning: using custom physics framerate (" << physFramerate << " FPS).";
}
}
mTaskScheduler = std::make_unique<PhysicsTaskScheduler>(mPhysicsDt, mCollisionWorld);
mDebugDrawer = std::make_unique<MWRender::DebugDrawer>(mParentNode, mCollisionWorld.get());
}
PhysicsSystem::~PhysicsSystem()
{
mResourceSystem->removeResourceManager(mShapeManager.get());
if (mWaterCollisionObject)
mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get());
for (auto& heightField : mHeightFields)
{
mTaskScheduler->removeCollisionObject(heightField.second->getCollisionObject());
delete heightField.second;
}
mObjects.clear();
mActors.clear();
}
void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue)
{
mUnrefQueue = unrefQueue;
}
Resource::BulletShapeManager *PhysicsSystem::getShapeManager()
{
return mShapeManager.get();
}
bool PhysicsSystem::toggleDebugRendering()
{
mDebugDrawEnabled = !mDebugDrawEnabled;
mCollisionWorld->setDebugDrawer(mDebugDrawEnabled ? mDebugDrawer.get() : nullptr);
mDebugDrawer->setDebugMode(mDebugDrawEnabled);
return mDebugDrawEnabled;
}
void PhysicsSystem::markAsNonSolid(const MWWorld::ConstPtr &ptr)
{
ObjectMap::iterator found = mObjects.find(ptr);
if (found == mObjects.end())
return;
found->second->setSolid(false);
}
bool PhysicsSystem::isOnSolidGround (const MWWorld::Ptr& actor) const
{
const Actor* physactor = getActor(actor);
if (!physactor || !physactor->getOnGround())
return false;
CollisionMap::const_iterator found = mStandingCollisions.find(actor);
if (found == mStandingCollisions.end())
return true; // assume standing on terrain (which is a non-object, so not collision tracked)
ObjectMap::const_iterator foundObj = mObjects.find(found->second);
if (foundObj == mObjects.end())
return false;
if (!foundObj->second->isSolid())
return false;
return true;
}
std::pair<MWWorld::Ptr, osg::Vec3f> PhysicsSystem::getHitContact(const MWWorld::ConstPtr& actor,
const osg::Vec3f &origin,
const osg::Quat &orient,
float queryDistance, std::vector<MWWorld::Ptr>& targets)
{
// First of all, try to hit where you aim to
int hitmask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor;
RayCastingResult result = castRay(origin, origin + (orient * osg::Vec3f(0.0f, queryDistance, 0.0f)), actor, targets, hitmask, CollisionType_Actor);
if (result.mHit)
{
reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal));
return std::make_pair(result.mHitObject, result.mHitPos);
}
// Use cone shape as fallback
const MWWorld::Store<ESM::GameSetting> &store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
btConeShape shape (osg::DegreesToRadians(store.find("fCombatAngleXY")->mValue.getFloat()/2.0f), queryDistance);
shape.setLocalScaling(btVector3(1, 1, osg::DegreesToRadians(store.find("fCombatAngleZ")->mValue.getFloat()/2.0f) /
shape.getRadius()));
// The shape origin is its center, so we have to move it forward by half the length. The
// real origin will be provided to getFilteredContact to find the closest.
osg::Vec3f center = origin + (orient * osg::Vec3f(0.0f, queryDistance*0.5f, 0.0f));
btCollisionObject object;
object.setCollisionShape(&shape);
object.setWorldTransform(btTransform(Misc::Convert::toBullet(orient), Misc::Convert::toBullet(center)));
const btCollisionObject* me = nullptr;
std::vector<const btCollisionObject*> targetCollisionObjects;
const Actor* physactor = getActor(actor);
if (physactor)
me = physactor->getCollisionObject();
if (!targets.empty())
{
for (MWWorld::Ptr& target : targets)
{
const Actor* targetActor = getActor(target);
if (targetActor)
targetCollisionObjects.push_back(targetActor->getCollisionObject());
}
}
DeepestNotMeContactTestResultCallback resultCallback(me, targetCollisionObjects, Misc::Convert::toBullet(origin));
resultCallback.m_collisionFilterGroup = CollisionType_Actor;
resultCallback.m_collisionFilterMask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor;
mTaskScheduler->contactTest(&object, resultCallback);
if (resultCallback.mObject)
{
PtrHolder* holder = static_cast<PtrHolder*>(resultCallback.mObject->getUserPointer());
if (holder)
{
reportCollision(resultCallback.mContactPoint, resultCallback.mContactNormal);
return std::make_pair(holder->getPtr(), Misc::Convert::toOsg(resultCallback.mContactPoint));
}
}
return std::make_pair(MWWorld::Ptr(), osg::Vec3f());
}
float PhysicsSystem::getHitDistance(const osg::Vec3f &point, const MWWorld::ConstPtr &target) const
{
btCollisionObject* targetCollisionObj = nullptr;
const Actor* actor = getActor(target);
if (actor)
targetCollisionObj = actor->getCollisionObject();
if (!targetCollisionObj)
return 0.f;
btTransform rayFrom;
rayFrom.setIdentity();
rayFrom.setOrigin(Misc::Convert::toBullet(point));
auto hitpoint = mTaskScheduler->getHitPoint(rayFrom, targetCollisionObj);
if (hitpoint)
return (point - Misc::Convert::toOsg(*hitpoint)).length();
// didn't hit the target. this could happen if point is already inside the collision box
return 0.f;
}
RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr> targets, int mask, int group) const
{
if (from == to)
{
RayCastingResult result;
result.mHit = false;
return result;
}
btVector3 btFrom = Misc::Convert::toBullet(from);
btVector3 btTo = Misc::Convert::toBullet(to);
const btCollisionObject* me = nullptr;
std::vector<const btCollisionObject*> targetCollisionObjects;
if (!ignore.isEmpty())
{
const Actor* actor = getActor(ignore);
if (actor)
me = actor->getCollisionObject();
else
{
const Object* object = getObject(ignore);
if (object)
me = object->getCollisionObject();
}
}
if (!targets.empty())
{
for (MWWorld::Ptr& target : targets)
{
const Actor* actor = getActor(target);
if (actor)
targetCollisionObjects.push_back(actor->getCollisionObject());
}
}
ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo);
resultCallback.m_collisionFilterGroup = group;
resultCallback.m_collisionFilterMask = mask;
mTaskScheduler->rayTest(btFrom, btTo, resultCallback);
RayCastingResult result;
result.mHit = resultCallback.hasHit();
if (resultCallback.hasHit())
{
result.mHitPos = Misc::Convert::toOsg(resultCallback.m_hitPointWorld);
result.mHitNormal = Misc::Convert::toOsg(resultCallback.m_hitNormalWorld);
if (PtrHolder* ptrHolder = static_cast<PtrHolder*>(resultCallback.m_collisionObject->getUserPointer()))
result.mHitObject = ptrHolder->getPtr();
}
return result;
}
RayCastingResult PhysicsSystem::castSphere(const osg::Vec3f &from, const osg::Vec3f &to, float radius) const
{
btCollisionWorld::ClosestConvexResultCallback callback(Misc::Convert::toBullet(from), Misc::Convert::toBullet(to));
callback.m_collisionFilterGroup = 0xff;
callback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door;
btSphereShape shape(radius);
const btQuaternion btrot = btQuaternion::getIdentity();
btTransform from_ (btrot, Misc::Convert::toBullet(from));
btTransform to_ (btrot, Misc::Convert::toBullet(to));
mTaskScheduler->convexSweepTest(&shape, from_, to_, callback);
RayCastingResult result;
result.mHit = callback.hasHit();
if (result.mHit)
{
result.mHitPos = Misc::Convert::toOsg(callback.m_hitPointWorld);
result.mHitNormal = Misc::Convert::toOsg(callback.m_hitNormalWorld);
}
return result;
}
bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const
{
const auto getWeakPtr = [&](const MWWorld::ConstPtr &ptr) -> std::weak_ptr<Actor>
{
const auto found = mActors.find(ptr);
if (found != mActors.end())
return { found->second };
return {};
};
return mTaskScheduler->getLineOfSight(getWeakPtr(actor1), getWeakPtr(actor2));
}
bool PhysicsSystem::isOnGround(const MWWorld::Ptr &actor)
{
Actor* physactor = getActor(actor);
return physactor && physactor->getOnGround();
}
bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel)
{
const Actor* physicActor = getActor(actor);
if (!physicActor)
return false;
const float halfZ = physicActor->getHalfExtents().z();
const osg::Vec3f actorPosition = physicActor->getPosition();
const osg::Vec3f startingPosition(actorPosition.x(), actorPosition.y(), actorPosition.z() + halfZ);
const osg::Vec3f destinationPosition(actorPosition.x(), actorPosition.y(), waterlevel + halfZ);
ActorTracer tracer;
tracer.doTrace(physicActor->getCollisionObject(), startingPosition, destinationPosition, mCollisionWorld.get());
return (tracer.mFraction >= 1.0f);
}
osg::Vec3f PhysicsSystem::getHalfExtents(const MWWorld::ConstPtr &actor) const
{
const Actor* physactor = getActor(actor);
if (physactor)
return physactor->getHalfExtents();
else
return osg::Vec3f();
}
osg::Vec3f PhysicsSystem::getOriginalHalfExtents(const MWWorld::ConstPtr &actor) const
{
if (const Actor* physactor = getActor(actor))
return physactor->getOriginalHalfExtents();
else
return osg::Vec3f();
}
osg::Vec3f PhysicsSystem::getRenderingHalfExtents(const MWWorld::ConstPtr &actor) const
{
const Actor* physactor = getActor(actor);
if (physactor)
return physactor->getRenderingHalfExtents();
else
return osg::Vec3f();
}
osg::BoundingBox PhysicsSystem::getBoundingBox(const MWWorld::ConstPtr &object) const
{
const Object * physobject = getObject(object);
if (!physobject) return osg::BoundingBox();
btVector3 min, max;
mTaskScheduler->getAabb(physobject->getCollisionObject(), min, max);
return osg::BoundingBox(Misc::Convert::toOsg(min), Misc::Convert::toOsg(max));
}
osg::Vec3f PhysicsSystem::getCollisionObjectPosition(const MWWorld::ConstPtr &actor) const
{
const Actor* physactor = getActor(actor);
if (physactor)
return physactor->getCollisionObjectPosition();
else
return osg::Vec3f();
}
std::vector<ContactPoint> PhysicsSystem::getCollisionsPoints(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const
{
btCollisionObject* me = nullptr;
auto found = mObjects.find(ptr);
if (found != mObjects.end())
me = found->second->getCollisionObject();
else
return {};
ContactTestResultCallback resultCallback (me);
resultCallback.m_collisionFilterGroup = collisionGroup;
resultCallback.m_collisionFilterMask = collisionMask;
mTaskScheduler->contactTest(me, resultCallback);
return resultCallback.mResult;
}
std::vector<MWWorld::Ptr> PhysicsSystem::getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const
{
std::vector<MWWorld::Ptr> actors;
for (auto& [actor, point, normal] : getCollisionsPoints(ptr, collisionGroup, collisionMask))
actors.emplace_back(actor);
return actors;
}
osg::Vec3f PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, float maxHeight)
{
ActorMap::iterator found = mActors.find(ptr);
if (found == mActors.end())
return ptr.getRefData().getPosition().asVec3();
else
return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight);
}
void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject)
{
HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject);
mHeightFields[std::make_pair(x,y)] = heightfield;
mTaskScheduler->addCollisionObject(heightfield->getCollisionObject(), CollisionType_HeightMap,
CollisionType_Actor|CollisionType_Projectile);
}
void PhysicsSystem::removeHeightField (int x, int y)
{
HeightFieldMap::iterator heightfield = mHeightFields.find(std::make_pair(x,y));
if(heightfield != mHeightFields.end())
{
mTaskScheduler->removeCollisionObject(heightfield->second->getCollisionObject());
delete heightfield->second;
mHeightFields.erase(heightfield);
}
}
const HeightField* PhysicsSystem::getHeightField(int x, int y) const
{
const auto heightField = mHeightFields.find(std::make_pair(x, y));
if (heightField == mHeightFields.end())
return nullptr;
return heightField->second;
}
void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType)
{
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh);
if (!shapeInstance || !shapeInstance->getCollisionShape())
return;
auto obj = std::make_shared<Object>(ptr, shapeInstance, mTaskScheduler.get());
mObjects.emplace(ptr, obj);
if (obj->isAnimated())
mAnimatedObjects.insert(obj.get());
mTaskScheduler->addCollisionObject(obj->getCollisionObject(), collisionType,
CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile);
}
void PhysicsSystem::remove(const MWWorld::Ptr &ptr)
{
ObjectMap::iterator found = mObjects.find(ptr);
if (found != mObjects.end())
{
if (mUnrefQueue.get())
mUnrefQueue->push(found->second->getShapeInstance());
mAnimatedObjects.erase(found->second.get());
mObjects.erase(found);
}
ActorMap::iterator foundActor = mActors.find(ptr);
if (foundActor != mActors.end())
{
mActors.erase(foundActor);
}
}
void PhysicsSystem::updateCollisionMapPtr(CollisionMap& map, const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
{
CollisionMap::iterator found = map.find(old);
if (found != map.end())
{
map[updated] = found->second;
map.erase(found);
}
for (auto& collision : map)
{
if (collision.second == old)
collision.second = updated;
}
}
void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
{
ObjectMap::iterator found = mObjects.find(old);
if (found != mObjects.end())
{
auto obj = found->second;
obj->updatePtr(updated);
mObjects.erase(found);
mObjects.emplace(updated, std::move(obj));
}
ActorMap::iterator foundActor = mActors.find(old);
if (foundActor != mActors.end())
{
auto actor = foundActor->second;
actor->updatePtr(updated);
mActors.erase(foundActor);
mActors.emplace(updated, std::move(actor));
}
updateCollisionMapPtr(mStandingCollisions, old, updated);
}
Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr)
{
ActorMap::iterator found = mActors.find(ptr);
if (found != mActors.end())
return found->second.get();
return nullptr;
}
const Actor *PhysicsSystem::getActor(const MWWorld::ConstPtr &ptr) const
{
ActorMap::const_iterator found = mActors.find(ptr);
if (found != mActors.end())
return found->second.get();
return nullptr;
}
const Object* PhysicsSystem::getObject(const MWWorld::ConstPtr &ptr) const
{
ObjectMap::const_iterator found = mObjects.find(ptr);
if (found != mObjects.end())
return found->second.get();
return nullptr;
}
void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr)
{
ObjectMap::iterator found = mObjects.find(ptr);
if (found != mObjects.end())
{
float scale = ptr.getCellRef().getScale();
found->second->setScale(scale);
mTaskScheduler->updateSingleAabb(found->second);
return;
}
ActorMap::iterator foundActor = mActors.find(ptr);
if (foundActor != mActors.end())
{
foundActor->second->updateScale();
mTaskScheduler->updateSingleAabb(foundActor->second);
return;
}
}
void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr)
{
ObjectMap::iterator found = mObjects.find(ptr);
if (found != mObjects.end())
{
found->second->setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
mTaskScheduler->updateSingleAabb(found->second);
return;
}
ActorMap::iterator foundActor = mActors.find(ptr);
if (foundActor != mActors.end())
{
if (!foundActor->second->isRotationallyInvariant())
{
foundActor->second->updateRotation();
mTaskScheduler->updateSingleAabb(foundActor->second);
}
return;
}
}
void PhysicsSystem::updatePosition(const MWWorld::Ptr &ptr)
{
ObjectMap::iterator found = mObjects.find(ptr);
if (found != mObjects.end())
{
found->second->setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3()));
mTaskScheduler->updateSingleAabb(found->second);
return;
}
ActorMap::iterator foundActor = mActors.find(ptr);
if (foundActor != mActors.end())
{
foundActor->second->updatePosition();
mTaskScheduler->updateSingleAabb(foundActor->second);
return;
}
}
void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh)
{
osg::ref_ptr<const Resource::BulletShape> shape = mShapeManager->getShape(mesh);
// Try to get shape from basic model as fallback for creatures
if (!ptr.getClass().isNpc() && shape && shape->mCollisionBoxHalfExtents.length2() == 0)
{
const std::string fallbackModel = ptr.getClass().getModel(ptr);
if (fallbackModel != mesh)
{
shape = mShapeManager->getShape(fallbackModel);
}
}
if (!shape)
return;
auto actor = std::make_shared<Actor>(ptr, shape, mTaskScheduler.get());
mActors.emplace(ptr, std::move(actor));
}
bool PhysicsSystem::toggleCollisionMode()
{
ActorMap::iterator found = mActors.find(MWMechanics::getPlayer());
if (found != mActors.end())
{
bool cmode = found->second->getCollisionMode();
cmode = !cmode;
found->second->enableCollisionMode(cmode);
// NB: Collision body isn't disabled for vanilla TCL compatibility
return cmode;
}
return false;
}
void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity)
{
for(auto& movementItem : mMovementQueue)
{
if (movementItem.first == ptr)
{
movementItem.second = velocity;
return;
}
}
mMovementQueue.emplace_back(ptr, velocity);
}
void PhysicsSystem::clearQueuedMovement()
{
mMovementQueue.clear();
mStandingCollisions.clear();
}
const PtrPositionList& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation)
{
mTimeAccum += dt;
const int maxAllowedSteps = 20;
int numSteps = mTimeAccum / mPhysicsDt;
numSteps = std::min(numSteps, maxAllowedSteps);
mTimeAccum -= numSteps * mPhysicsDt;
return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), mStandingCollisions, skipSimulation);
}
std::vector<ActorFrameData> PhysicsSystem::prepareFrameData(int numSteps)
{
std::vector<ActorFrameData> actorsFrameData;
actorsFrameData.reserve(mMovementQueue.size());
const MWBase::World *world = MWBase::Environment::get().getWorld();
for (const auto& [character, movement] : mMovementQueue)
{
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();
const MWWorld::CellStore *cell = character.getCell();
if(cell->getCell()->hasWater())
waterlevel = cell->getWaterLevel();
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))
{
moveToWaterSurface = true;
waterCollision = true;
}
}
physicActor->setCanWaterWalk(waterCollision);
// 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));
// Ue current value only if we don't advance the simulation. Otherwise we might get a stale value.
MWWorld::Ptr standingOn;
if (numSteps == 0)
standingOn = mStandingCollisions[character];
actorsFrameData.emplace_back(std::move(physicActor), character, standingOn, moveToWaterSurface, movement, slowFall, waterlevel);
}
mMovementQueue.clear();
return actorsFrameData;
}
void PhysicsSystem::stepSimulation()
{
for (Object* animatedObject : mAnimatedObjects)
if (animatedObject->animateCollisionShapes())
{
auto obj = mObjects.find(animatedObject->getPtr());
assert(obj != mObjects.end());
mTaskScheduler->updateSingleAabb(obj->second);
}
#ifndef BT_NO_PROFILE
CProfileManager::Reset();
CProfileManager::Increment_Frame_Counter();
#endif
}
void PhysicsSystem::updateAnimatedCollisionShape(const MWWorld::Ptr& object)
{
ObjectMap::iterator found = mObjects.find(object);
if (found != mObjects.end())
if (found->second->animateCollisionShapes())
mTaskScheduler->updateSingleAabb(found->second);
}
void PhysicsSystem::debugDraw()
{
if (mDebugDrawEnabled)
mDebugDrawer->step();
}
bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const
{
for (const auto& standingActor : mStandingCollisions)
{
if (standingActor.first == actor && standingActor.second == object)
return true;
}
return false;
}
void PhysicsSystem::getActorsStandingOn(const MWWorld::ConstPtr &object, std::vector<MWWorld::Ptr> &out) const
{
for (const auto& standingActor : mStandingCollisions)
{
if (standingActor.second == object)
out.push_back(standingActor.first);
}
}
bool PhysicsSystem::isActorCollidingWith(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const
{
std::vector<MWWorld::Ptr> collisions = getCollisions(object, CollisionType_World, CollisionType_Actor);
return (std::find(collisions.begin(), collisions.end(), actor) != collisions.end());
}
void PhysicsSystem::getActorsCollidingWith(const MWWorld::ConstPtr &object, std::vector<MWWorld::Ptr> &out) const
{
std::vector<MWWorld::Ptr> collisions = getCollisions(object, CollisionType_World, CollisionType_Actor);
out.insert(out.end(), collisions.begin(), collisions.end());
}
void PhysicsSystem::disableWater()
{
if (mWaterEnabled)
{
mWaterEnabled = false;
updateWater();
}
}
void PhysicsSystem::enableWater(float height)
{
if (!mWaterEnabled || mWaterHeight != height)
{
mWaterEnabled = true;
mWaterHeight = height;
updateWater();
}
}
void PhysicsSystem::setWaterHeight(float height)
{
if (mWaterHeight != height)
{
mWaterHeight = height;
updateWater();
}
}
void PhysicsSystem::updateWater()
{
if (mWaterCollisionObject)
{
mTaskScheduler->removeCollisionObject(mWaterCollisionObject.get());
}
if (!mWaterEnabled)
{
mWaterCollisionObject.reset();
return;
}
mWaterCollisionObject.reset(new btCollisionObject());
mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight));
mWaterCollisionObject->setCollisionShape(mWaterCollisionShape.get());
mTaskScheduler->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water,
CollisionType_Actor);
}
bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const
{
btCollisionObject* object = nullptr;
const auto it = mActors.find(ignore);
if (it != mActors.end())
object = it->second->getCollisionObject();
const auto bulletPosition = Misc::Convert::toBullet(position);
const auto aabbMin = bulletPosition - btVector3(radius, radius, radius);
const auto aabbMax = bulletPosition + btVector3(radius, radius, radius);
const int mask = MWPhysics::CollisionType_Actor;
const int group = 0xff;
HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group);
mTaskScheduler->aabbTest(aabbMin, aabbMax, callback);
return callback.getResult();
}
void PhysicsSystem::reportStats(unsigned int frameNumber, osg::Stats& stats) const
{
stats.setAttribute(frameNumber, "Physics Actors", mActors.size());
stats.setAttribute(frameNumber, "Physics Objects", mObjects.size());
stats.setAttribute(frameNumber, "Physics HeightFields", mHeightFields.size());
}
void PhysicsSystem::reportCollision(const btVector3& position, const btVector3& normal)
{
if (mDebugDrawEnabled)
mDebugDrawer->addCollision(position, normal);
}
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),
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();
mActorRaw->updatePosition();
mOrigin = mActorRaw->getNextPosition();
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;
}
}