mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-29 01:45:33 +00:00
3c2504b442
Before movement calculation, the main thread prepare a vector of ActorFrameData, which contains all data necessary to perform the simulation, and feed it to the solver. At the same time it fetches the result from the previous background simulation, which in turn is used by the game mechanics. Other functions of the physics system (weapon hit for instance) interrupt the background simulation, with some exceptions described below. The number of threads is controlled by the numeric setting [Physics] async num threads In case 'async num threads' > 1 and Bullet doesn't support multiple threads, 1 async thread will be used. 0 means synchronous solver. Additional settings (will be silently switched off if async num threads = 0) [Physics] defer aabb update Update AABBs of actors and objects in the background thread(s). It is not an especially costly operation, but it needs exclusive access to the collision world, which blocks other operations. Since AABB needs to be updated for collision detection, one can queue them to defer update before start of the movement solver. Extensive tests on as much as one installation (mine) show no drawback having that switched on. [Physics] lineofsight keep inactive cache Control for how long (how many frames) the line of sight (LOS) request will be kept updated. When a request for LOS is made for the first time, the background threads are stopped to service it. From now on, the LOS will be refreshed preemptively as part of the background routine until it is not required for lineofsight keep inactive cache frames. This mean that subsequent request will not interrupt the background computation.
920 lines
34 KiB
C++
920 lines
34 KiB
C++
#include "physicssystem.hpp"
|
|
|
|
#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);
|
|
}
|
|
|
|
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;
|
|
|
|
if (mDebugDrawEnabled && !mDebugDrawer)
|
|
{
|
|
mDebugDrawer.reset(new MWRender::DebugDrawer(mParentNode, mCollisionWorld.get()));
|
|
mCollisionWorld->setDebugDrawer(mDebugDrawer.get());
|
|
mDebugDrawer->setDebugMode(mDebugDrawEnabled);
|
|
}
|
|
else if (mDebugDrawer)
|
|
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)
|
|
{
|
|
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)
|
|
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.get())).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<MWWorld::Ptr> PhysicsSystem::getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const
|
|
{
|
|
btCollisionObject* me = nullptr;
|
|
|
|
ObjectMap::const_iterator found = mObjects.find(ptr);
|
|
if (found != mObjects.end())
|
|
me = found->second->getCollisionObject();
|
|
else
|
|
return std::vector<MWWorld::Ptr>();
|
|
|
|
ContactTestResultCallback resultCallback (me);
|
|
resultCallback.m_collisionFilterGroup = collisionGroup;
|
|
resultCallback.m_collisionFilterMask = collisionMask;
|
|
mTaskScheduler->contactTest(me, resultCallback);
|
|
return resultCallback.mResult;
|
|
}
|
|
|
|
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 &movement)
|
|
{
|
|
for(auto& movementItem : mMovementQueue)
|
|
{
|
|
if (movementItem.first == ptr)
|
|
{
|
|
movementItem.second = movement;
|
|
return;
|
|
}
|
|
}
|
|
|
|
mMovementQueue.emplace_back(ptr, movement);
|
|
}
|
|
|
|
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(), mStandingCollisions, skipSimulation);
|
|
}
|
|
|
|
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)
|
|
{
|
|
const auto& character = m.first;
|
|
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();
|
|
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));
|
|
|
|
actorsFrameData.emplace_back(std::move(physicActor), character, mStandingCollisions[character], 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 (mDebugDrawer)
|
|
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());
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|