mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-29 00:32:49 +00:00
50fd913058
to be able to reuse up stepper results for successive movement solver iterations. This can reduce the number of convex casts almost by half in some cases.
1511 lines
59 KiB
C++
1511 lines
59 KiB
C++
#include "physicssystem.hpp"
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <osg/Group>
|
|
|
|
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
|
|
#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/CollisionDispatch/btCollisionWorld.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/esm/loadgmst.hpp>
|
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
|
#include <components/sceneutil/unrefqueue.hpp>
|
|
|
|
#include <components/nifosg/particle.hpp> // FindRecIndexVisitor
|
|
|
|
#include "../mwbase/world.hpp"
|
|
#include "../mwbase/environment.hpp"
|
|
|
|
#include "../mwmechanics/creaturestats.hpp"
|
|
#include "../mwmechanics/movement.hpp"
|
|
#include "../mwmechanics/actorutil.hpp"
|
|
|
|
#include "../mwworld/esmstore.hpp"
|
|
#include "../mwworld/cellstore.hpp"
|
|
|
|
#include "../mwrender/bulletdebugdraw.hpp"
|
|
|
|
#include "../mwbase/world.hpp"
|
|
#include "../mwbase/environment.hpp"
|
|
|
|
#include "../mwworld/class.hpp"
|
|
|
|
#include "collisiontype.hpp"
|
|
#include "actor.hpp"
|
|
#include "convert.hpp"
|
|
#include "trace.h"
|
|
|
|
namespace MWPhysics
|
|
{
|
|
|
|
static const float sMaxSlope = 49.0f;
|
|
static const float sStepSizeUp = 34.0f;
|
|
static const float sStepSizeDown = 62.0f;
|
|
static const float sMinStep = 10.f;
|
|
|
|
// Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
|
|
static const int sMaxIterations = 8;
|
|
|
|
static bool isActor(const btCollisionObject *obj)
|
|
{
|
|
assert(obj);
|
|
return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor;
|
|
}
|
|
|
|
template <class Vec3>
|
|
static bool isWalkableSlope(const Vec3 &normal)
|
|
{
|
|
static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope));
|
|
return (normal.z() > sMaxSlopeCos);
|
|
}
|
|
|
|
static bool canStepDown(const ActorTracer &stepper)
|
|
{
|
|
return stepper.mHitObject && isWalkableSlope(stepper.mPlaneNormal) && !isActor(stepper.mHitObject);
|
|
}
|
|
|
|
class Stepper
|
|
{
|
|
private:
|
|
const btCollisionWorld *mColWorld;
|
|
const btCollisionObject *mColObj;
|
|
|
|
ActorTracer mTracer, mUpStepper, mDownStepper;
|
|
bool mHaveMoved;
|
|
|
|
public:
|
|
Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj)
|
|
: mColWorld(colWorld)
|
|
, mColObj(colObj)
|
|
, mHaveMoved(true)
|
|
{}
|
|
|
|
bool step(osg::Vec3f &position, const osg::Vec3f &toMove, float &remainingTime)
|
|
{
|
|
/*
|
|
* Slide up an incline or set of stairs. Should be called only after a
|
|
* collision detection otherwise unnecessary tracing will be performed.
|
|
*
|
|
* NOTE: with a small change this method can be used to step over an obstacle
|
|
* of height sStepSize.
|
|
*
|
|
* If successful return 'true' and update 'position' to the new possible
|
|
* location and adjust 'remainingTime'.
|
|
*
|
|
* If not successful return 'false'. May fail for these reasons:
|
|
* - can't move directly up from current position
|
|
* - having moved up by between epsilon() and sStepSize, can't move forward
|
|
* - having moved forward by between epsilon() and toMove,
|
|
* = moved down between 0 and just under sStepSize but slope was too steep, or
|
|
* = moved the full sStepSize down (FIXME: this could be a bug)
|
|
*
|
|
*
|
|
*
|
|
* Starting position. Obstacle or stairs with height upto sStepSize in front.
|
|
*
|
|
* +--+ +--+ |XX
|
|
* | | -------> toMove | | +--+XX
|
|
* | | | | |XXXXX
|
|
* | | +--+ | | +--+XXXXX
|
|
* | | |XX| | | |XXXXXXXX
|
|
* +--+ +--+ +--+ +--------
|
|
* ==============================================
|
|
*/
|
|
|
|
/*
|
|
* Try moving up sStepSize using stepper.
|
|
* FIXME: does not work in case there is no front obstacle but there is one above
|
|
*
|
|
* +--+ +--+
|
|
* | | | |
|
|
* | | | | |XX
|
|
* | | | | +--+XX
|
|
* | | | | |XXXXX
|
|
* +--+ +--+ +--+ +--+XXXXX
|
|
* |XX| |XXXXXXXX
|
|
* +--+ +--------
|
|
* ==============================================
|
|
*/
|
|
if (mHaveMoved)
|
|
{
|
|
mHaveMoved = false;
|
|
mUpStepper.doTrace(mColObj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), mColWorld);
|
|
if(mUpStepper.mFraction < std::numeric_limits<float>::epsilon())
|
|
return false; // didn't even move the smallest representable amount
|
|
// (TODO: shouldn't this be larger? Why bother with such a small amount?)
|
|
}
|
|
|
|
/*
|
|
* Try moving from the elevated position using tracer.
|
|
*
|
|
* +--+ +--+
|
|
* | | |YY| FIXME: collision with object YY
|
|
* | | +--+
|
|
* | |
|
|
* <------------------->| |
|
|
* +--+ +--+
|
|
* |XX| the moved amount is toMove*tracer.mFraction
|
|
* +--+
|
|
* ==============================================
|
|
*/
|
|
osg::Vec3f tracerPos = mUpStepper.mEndPos;
|
|
mTracer.doTrace(mColObj, tracerPos, tracerPos + toMove, mColWorld);
|
|
if(mTracer.mFraction < std::numeric_limits<float>::epsilon())
|
|
return false; // didn't even move the smallest representable amount
|
|
|
|
/*
|
|
* Try moving back down sStepSizeDown using stepper.
|
|
* NOTE: if there is an obstacle below (e.g. stairs), we'll be "stepping up".
|
|
* Below diagram is the case where we "stepped over" an obstacle in front.
|
|
*
|
|
* +--+
|
|
* |YY|
|
|
* +--+ +--+
|
|
* | |
|
|
* | |
|
|
* +--+ | |
|
|
* |XX| | |
|
|
* +--+ +--+
|
|
* ==============================================
|
|
*/
|
|
mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld);
|
|
if (!canStepDown(mDownStepper))
|
|
{
|
|
// Try again with increased step length
|
|
if (mTracer.mFraction < 1.0f || toMove.length2() > sMinStep*sMinStep)
|
|
return false;
|
|
|
|
osg::Vec3f direction = toMove;
|
|
direction.normalize();
|
|
mTracer.doTrace(mColObj, tracerPos, tracerPos + direction*sMinStep, mColWorld);
|
|
if (mTracer.mFraction < 0.001f)
|
|
return false;
|
|
|
|
mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld);
|
|
if (!canStepDown(mDownStepper))
|
|
return false;
|
|
}
|
|
if (mDownStepper.mFraction < 1.0f)
|
|
{
|
|
// only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall.
|
|
// TODO: stepper.mPlaneNormal does not appear to be reliable - needs more testing
|
|
// NOTE: caller's variables 'position' & 'remainingTime' are modified here
|
|
position = mDownStepper.mEndPos;
|
|
remainingTime *= (1.0f-mTracer.mFraction); // remaining time is proportional to remaining distance
|
|
mHaveMoved = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class MovementSolver
|
|
{
|
|
private:
|
|
///Project a vector u on another vector v
|
|
static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v)
|
|
{
|
|
return v * (u * v);
|
|
// ^ dot product
|
|
}
|
|
|
|
///Helper for computing the character sliding
|
|
static inline osg::Vec3f slide(const osg::Vec3f& direction, const osg::Vec3f &planeNormal)
|
|
{
|
|
return direction - project(direction, planeNormal);
|
|
}
|
|
|
|
static inline osg::Vec3f reflect(const osg::Vec3& velocity, const osg::Vec3f& normal)
|
|
{
|
|
return velocity - (normal * (normal * velocity)) * 2;
|
|
// ^ dot product
|
|
}
|
|
|
|
|
|
public:
|
|
static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight)
|
|
{
|
|
osg::Vec3f position(ptr.getRefData().getPosition().asVec3());
|
|
|
|
ActorTracer tracer;
|
|
tracer.findGround(actor, position, position-osg::Vec3f(0,0,maxHeight), collisionWorld);
|
|
if(tracer.mFraction >= 1.0f)
|
|
{
|
|
actor->setOnGround(false);
|
|
return position;
|
|
}
|
|
else
|
|
{
|
|
// Check if we actually found a valid spawn point (use an infinitely thin ray this time).
|
|
// Required for some broken door destinations in Morrowind.esm, where the spawn point
|
|
// intersects with other geometry if the actor's base is taken into account
|
|
btVector3 from = toBullet(position);
|
|
btVector3 to = from - btVector3(0,0,maxHeight);
|
|
|
|
btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to);
|
|
resultCallback1.m_collisionFilterGroup = 0xff;
|
|
resultCallback1.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap;
|
|
|
|
collisionWorld->rayTest(from, to, resultCallback1);
|
|
|
|
if (resultCallback1.hasHit() &&
|
|
( (toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos).length2() > 35*35
|
|
|| !isWalkableSlope(tracer.mPlaneNormal)))
|
|
{
|
|
actor->setOnGround(isWalkableSlope(resultCallback1.m_hitNormalWorld));
|
|
return toOsg(resultCallback1.m_hitPointWorld) + osg::Vec3f(0.f, 0.f, 1.f);
|
|
}
|
|
|
|
actor->setOnGround(isWalkableSlope(tracer.mPlaneNormal));
|
|
|
|
return tracer.mEndPos;
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
const ESM::Position& refpos = ptr.getRefData().getPosition();
|
|
// Early-out for totally static creatures
|
|
// (Not sure if gravity should still apply?)
|
|
if (!ptr.getClass().isMobile(ptr))
|
|
return position;
|
|
|
|
// 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)) *
|
|
osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))
|
|
) * movement * time;
|
|
}
|
|
|
|
const btCollisionObject *colobj = physicActor->getCollisionObject();
|
|
osg::Vec3f halfExtents = physicActor->getHalfExtents();
|
|
|
|
// NOTE: here we don't account for the collision box translation (i.e. physicActor->getPosition() - refpos.pos).
|
|
// 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();
|
|
|
|
static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
|
|
.find("fSwimHeightScale")->getFloat();
|
|
float swimlevel = waterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
|
|
|
|
ActorTracer tracer;
|
|
osg::Vec3f inertia = physicActor->getInertialForce();
|
|
osg::Vec3f velocity;
|
|
|
|
if(position.z() < swimlevel || isFlying)
|
|
{
|
|
velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) *
|
|
osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement;
|
|
}
|
|
else
|
|
{
|
|
velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement;
|
|
|
|
if (velocity.z() > 0.f && physicActor->getOnGround())
|
|
inertia = velocity;
|
|
else if(!physicActor->getOnGround())
|
|
{
|
|
velocity = velocity + physicActor->getInertialForce();
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
velocity = osg::Vec3f(0,0,1) * 25;
|
|
|
|
ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0;
|
|
|
|
// Now that we have the effective movement vector, apply wind forces to it
|
|
if (MWBase::Environment::get().getWorld()->isInStorm())
|
|
{
|
|
osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection();
|
|
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")->getFloat();
|
|
velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f));
|
|
}
|
|
|
|
Stepper stepper(collisionWorld, colobj);
|
|
osg::Vec3f origVelocity = velocity;
|
|
osg::Vec3f newPosition = position;
|
|
/*
|
|
* 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
|
|
* The initial velocity was set earlier (see above).
|
|
*/
|
|
float remainingTime = time;
|
|
for(int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations)
|
|
{
|
|
osg::Vec3f nextpos = newPosition + velocity * remainingTime;
|
|
|
|
// If not able to fly, don't allow to swim up into the air
|
|
if(newPosition.z() < swimlevel &&
|
|
!isFlying && // can't fly
|
|
nextpos.z() > swimlevel && // but about to go above water
|
|
newPosition.z() <= swimlevel)
|
|
{
|
|
const osg::Vec3f down(0,0,-1);
|
|
float movelen = velocity.normalize();
|
|
osg::Vec3f reflectdir = reflect(velocity, down);
|
|
reflectdir.normalize();
|
|
velocity = slide(reflectdir, down)*movelen;
|
|
// NOTE: remainingTime is unchanged before the loop continues
|
|
continue; // velocity updated, calculate nextpos again
|
|
}
|
|
|
|
if((newPosition - nextpos).length2() > 0.0001)
|
|
{
|
|
// trace to where character would go if there were no obstructions
|
|
tracer.doTrace(colobj, newPosition, nextpos, collisionWorld);
|
|
|
|
// check for obstructions
|
|
if(tracer.mFraction >= 1.0f)
|
|
{
|
|
newPosition = tracer.mEndPos; // ok to move, so set newPosition
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The current position and next position are nearly the same, so just exit.
|
|
// Note: Bullet can trigger an assert in debug modes if the positions
|
|
// are the same, since that causes it to attempt to normalize a zero
|
|
// length vector (which can also happen with nearly identical vectors, since
|
|
// precision can be lost due to any math Bullet does internally). Since we
|
|
// aren't performing any collision detection, we want to reject the next
|
|
// position, so that we don't slowly move inside another object.
|
|
break;
|
|
}
|
|
|
|
|
|
osg::Vec3f oldPosition = newPosition;
|
|
// We hit something. Try to step up onto it. (NOTE: stepMove does not allow stepping over)
|
|
// NOTE: stepMove modifies newPosition if successful
|
|
bool result = stepper.step(newPosition, velocity*remainingTime, remainingTime);
|
|
if (result)
|
|
{
|
|
// don't let pure water creatures move out of water after stepMove
|
|
if (ptr.getClass().isPureWaterCreature(ptr)
|
|
&& newPosition.z() + halfExtents.z() > waterlevel)
|
|
newPosition = oldPosition;
|
|
}
|
|
else
|
|
{
|
|
// Can't move this way, try to find another spot along the plane
|
|
osg::Vec3f direction = velocity;
|
|
float movelen = direction.normalize();
|
|
osg::Vec3f reflectdir = reflect(velocity, tracer.mPlaneNormal);
|
|
reflectdir.normalize();
|
|
|
|
osg::Vec3f newVelocity = slide(reflectdir, tracer.mPlaneNormal)*movelen;
|
|
|
|
// Do not allow sliding upward if there is gravity.
|
|
// Stepping will have taken care of that.
|
|
if(!(newPosition.z() < swimlevel || isFlying))
|
|
newVelocity.z() = std::min(newVelocity.z(), 0.0f);
|
|
|
|
if ((newVelocity-velocity).length2() < 0.01)
|
|
break;
|
|
if ((velocity * origVelocity) <= 0.f)
|
|
break; // ^ dot product
|
|
|
|
velocity = newVelocity;
|
|
}
|
|
}
|
|
|
|
bool isOnGround = false;
|
|
if (!(inertia.z() > 0.f) && !(newPosition.z() < swimlevel))
|
|
{
|
|
osg::Vec3f from = newPosition;
|
|
osg::Vec3f to = newPosition - (physicActor->getOnGround() ?
|
|
osg::Vec3f(0,0,sStepSizeDown+2.f) : osg::Vec3f(0,0,2.f));
|
|
tracer.doTrace(colobj, from, to, collisionWorld);
|
|
if(tracer.mFraction < 1.0f && isWalkableSlope(tracer.mPlaneNormal)
|
|
&& tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != CollisionType_Actor)
|
|
{
|
|
const btCollisionObject* standingOn = tracer.mHitObject;
|
|
PtrHolder* ptrHolder = static_cast<PtrHolder*>(standingOn->getUserPointer());
|
|
if (ptrHolder)
|
|
standingCollisionTracker[ptr] = ptrHolder->getPtr();
|
|
|
|
if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
|
|
physicActor->setWalkingOnWater(true);
|
|
if (!isFlying)
|
|
newPosition.z() = tracer.mEndPos.z() + 1.0f;
|
|
|
|
isOnGround = true;
|
|
}
|
|
else
|
|
{
|
|
// standing on actors is not allowed (see above).
|
|
// in addition to that, apply a sliding effect away from the center of the actor,
|
|
// so that we do not stay suspended in air indefinitely.
|
|
if (tracer.mFraction < 1.0f && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor)
|
|
{
|
|
if (osg::Vec3f(velocity.x(), velocity.y(), 0).length2() < 100.f*100.f)
|
|
{
|
|
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.normalize();
|
|
inertia *= 100;
|
|
}
|
|
}
|
|
|
|
isOnGround = false;
|
|
}
|
|
}
|
|
|
|
if(isOnGround || newPosition.z() < swimlevel || isFlying)
|
|
physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f));
|
|
else
|
|
{
|
|
inertia.z() += time * -627.2f;
|
|
if (inertia.z() < 0)
|
|
inertia.z() *= slowFall;
|
|
if (slowFall < 1.f) {
|
|
inertia.x() *= slowFall;
|
|
inertia.y() *= slowFall;
|
|
}
|
|
physicActor->setInertialForce(inertia);
|
|
}
|
|
physicActor->setOnGround(isOnGround);
|
|
|
|
newPosition.z() -= halfExtents.z(); // remove what was added at the beginning
|
|
return newPosition;
|
|
}
|
|
};
|
|
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
class HeightField
|
|
{
|
|
public:
|
|
HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts)
|
|
{
|
|
// find the minimum and maximum heights (needed for bullet)
|
|
float minh = heights[0];
|
|
float maxh = heights[0];
|
|
for(int i = 1;i < sqrtVerts*sqrtVerts;++i)
|
|
{
|
|
float h = heights[i];
|
|
if(h > maxh) maxh = h;
|
|
if(h < minh) minh = h;
|
|
}
|
|
|
|
mShape = new btHeightfieldTerrainShape(
|
|
sqrtVerts, sqrtVerts, heights, 1,
|
|
minh, maxh, 2,
|
|
PHY_FLOAT, true
|
|
);
|
|
mShape->setUseDiamondSubdivision(true);
|
|
mShape->setLocalScaling(btVector3(triSize, triSize, 1));
|
|
|
|
btTransform transform(btQuaternion::getIdentity(),
|
|
btVector3((x+0.5f) * triSize * (sqrtVerts-1),
|
|
(y+0.5f) * triSize * (sqrtVerts-1),
|
|
(maxh+minh)*0.5f));
|
|
|
|
mCollisionObject = new btCollisionObject;
|
|
mCollisionObject->setCollisionShape(mShape);
|
|
mCollisionObject->setWorldTransform(transform);
|
|
}
|
|
~HeightField()
|
|
{
|
|
delete mCollisionObject;
|
|
delete mShape;
|
|
}
|
|
btCollisionObject* getCollisionObject()
|
|
{
|
|
return mCollisionObject;
|
|
}
|
|
|
|
private:
|
|
btHeightfieldTerrainShape* mShape;
|
|
btCollisionObject* mCollisionObject;
|
|
|
|
void operator=(const HeightField&);
|
|
HeightField(const HeightField&);
|
|
};
|
|
|
|
// --------------------------------------------------------------
|
|
|
|
class Object : public PtrHolder
|
|
{
|
|
public:
|
|
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance)
|
|
: mShapeInstance(shapeInstance)
|
|
, mSolid(true)
|
|
{
|
|
mPtr = ptr;
|
|
|
|
mCollisionObject.reset(new btCollisionObject);
|
|
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
|
|
|
|
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
|
|
|
|
setScale(ptr.getCellRef().getScale());
|
|
setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
|
|
const float* pos = ptr.getRefData().getPosition().pos;
|
|
setOrigin(btVector3(pos[0], pos[1], pos[2]));
|
|
}
|
|
|
|
const Resource::BulletShapeInstance* getShapeInstance() const
|
|
{
|
|
return mShapeInstance.get();
|
|
}
|
|
|
|
void setScale(float scale)
|
|
{
|
|
mShapeInstance->getCollisionShape()->setLocalScaling(btVector3(scale,scale,scale));
|
|
}
|
|
|
|
void setRotation(const btQuaternion& quat)
|
|
{
|
|
mCollisionObject->getWorldTransform().setRotation(quat);
|
|
}
|
|
|
|
void setOrigin(const btVector3& vec)
|
|
{
|
|
mCollisionObject->getWorldTransform().setOrigin(vec);
|
|
}
|
|
|
|
btCollisionObject* getCollisionObject()
|
|
{
|
|
return mCollisionObject.get();
|
|
}
|
|
|
|
const btCollisionObject* getCollisionObject() const
|
|
{
|
|
return mCollisionObject.get();
|
|
}
|
|
|
|
/// Return solid flag. Not used by the object itself, true by default.
|
|
bool isSolid() const
|
|
{
|
|
return mSolid;
|
|
}
|
|
|
|
void setSolid(bool solid)
|
|
{
|
|
mSolid = solid;
|
|
}
|
|
|
|
bool isAnimated() const
|
|
{
|
|
return !mShapeInstance->mAnimatedShapes.empty();
|
|
}
|
|
|
|
void animateCollisionShapes(btCollisionWorld* collisionWorld)
|
|
{
|
|
if (mShapeInstance->mAnimatedShapes.empty())
|
|
return;
|
|
|
|
assert (mShapeInstance->getCollisionShape()->isCompound());
|
|
|
|
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->getCollisionShape());
|
|
|
|
for (std::map<int, int>::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it)
|
|
{
|
|
int recIndex = it->first;
|
|
int shapeIndex = it->second;
|
|
|
|
std::map<int, osg::NodePath>::iterator nodePathFound = mRecIndexToNodePath.find(recIndex);
|
|
if (nodePathFound == mRecIndexToNodePath.end())
|
|
{
|
|
NifOsg::FindGroupByRecIndex visitor(recIndex);
|
|
mPtr.getRefData().getBaseNode()->accept(visitor);
|
|
if (!visitor.mFound)
|
|
{
|
|
std::cerr << "animateCollisionShapes: Can't find node " << recIndex << std::endl;
|
|
return;
|
|
}
|
|
osg::NodePath nodePath = visitor.mFoundPath;
|
|
nodePath.erase(nodePath.begin());
|
|
nodePathFound = mRecIndexToNodePath.insert(std::make_pair(recIndex, nodePath)).first;
|
|
}
|
|
|
|
osg::NodePath& nodePath = nodePathFound->second;
|
|
osg::Matrixf matrix = osg::computeLocalToWorld(nodePath);
|
|
osg::Vec3f scale = matrix.getScale();
|
|
matrix.orthoNormalize(matrix);
|
|
|
|
btTransform transform;
|
|
transform.setOrigin(toBullet(matrix.getTrans()) * compound->getLocalScaling());
|
|
for (int i=0; i<3; ++i)
|
|
for (int j=0; j<3; ++j)
|
|
transform.getBasis()[i][j] = matrix(j,i); // NB column/row major difference
|
|
|
|
if (compound->getLocalScaling() * toBullet(scale) != compound->getChildShape(shapeIndex)->getLocalScaling())
|
|
compound->getChildShape(shapeIndex)->setLocalScaling(compound->getLocalScaling() * toBullet(scale));
|
|
if (!(transform == compound->getChildTransform(shapeIndex)))
|
|
compound->updateChildTransform(shapeIndex, transform);
|
|
}
|
|
|
|
collisionWorld->updateSingleAabb(mCollisionObject.get());
|
|
}
|
|
|
|
private:
|
|
std::auto_ptr<btCollisionObject> mCollisionObject;
|
|
osg::ref_ptr<Resource::BulletShapeInstance> mShapeInstance;
|
|
std::map<int, osg::NodePath> mRecIndexToNodePath;
|
|
bool mSolid;
|
|
};
|
|
|
|
// ---------------------------------------------------------------
|
|
|
|
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)
|
|
{
|
|
mResourceSystem->addResourceManager(mShapeManager.get());
|
|
|
|
mCollisionConfiguration = new btDefaultCollisionConfiguration();
|
|
mDispatcher = new btCollisionDispatcher(mCollisionConfiguration);
|
|
mBroadphase = new btDbvtBroadphase();
|
|
|
|
mCollisionWorld = new btCollisionWorld(mDispatcher, mBroadphase, mCollisionConfiguration);
|
|
|
|
// 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);
|
|
}
|
|
|
|
PhysicsSystem::~PhysicsSystem()
|
|
{
|
|
mResourceSystem->removeResourceManager(mShapeManager.get());
|
|
|
|
if (mWaterCollisionObject.get())
|
|
mCollisionWorld->removeCollisionObject(mWaterCollisionObject.get());
|
|
|
|
for (HeightFieldMap::iterator it = mHeightFields.begin(); it != mHeightFields.end(); ++it)
|
|
{
|
|
mCollisionWorld->removeCollisionObject(it->second->getCollisionObject());
|
|
delete it->second;
|
|
}
|
|
|
|
for (ObjectMap::iterator it = mObjects.begin(); it != mObjects.end(); ++it)
|
|
{
|
|
mCollisionWorld->removeCollisionObject(it->second->getCollisionObject());
|
|
delete it->second;
|
|
}
|
|
|
|
for (ActorMap::iterator it = mActors.begin(); it != mActors.end(); ++it)
|
|
{
|
|
delete it->second;
|
|
}
|
|
|
|
delete mCollisionWorld;
|
|
delete mCollisionConfiguration;
|
|
delete mDispatcher;
|
|
delete mBroadphase;
|
|
}
|
|
|
|
void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue)
|
|
{
|
|
mUnrefQueue = unrefQueue;
|
|
}
|
|
|
|
Resource::BulletShapeManager *PhysicsSystem::getShapeManager()
|
|
{
|
|
return mShapeManager.get();
|
|
}
|
|
|
|
bool PhysicsSystem::toggleDebugRendering()
|
|
{
|
|
mDebugDrawEnabled = !mDebugDrawEnabled;
|
|
|
|
if (mDebugDrawEnabled && !mDebugDrawer.get())
|
|
{
|
|
mDebugDrawer.reset(new MWRender::DebugDrawer(mParentNode, mCollisionWorld));
|
|
mCollisionWorld->setDebugDrawer(mDebugDrawer.get());
|
|
mDebugDrawer->setDebugMode(mDebugDrawEnabled);
|
|
}
|
|
else if (mDebugDrawer.get())
|
|
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;
|
|
}
|
|
|
|
class DeepestNotMeContactTestResultCallback : public btCollisionWorld::ContactResultCallback
|
|
{
|
|
const btCollisionObject* mMe;
|
|
|
|
// Store the real origin, since the shape's origin is its center
|
|
btVector3 mOrigin;
|
|
|
|
public:
|
|
const btCollisionObject *mObject;
|
|
btVector3 mContactPoint;
|
|
btScalar mLeastDistSqr;
|
|
|
|
DeepestNotMeContactTestResultCallback(const btCollisionObject* me, const btVector3 &origin)
|
|
: mMe(me), mOrigin(origin), mObject(NULL), mContactPoint(0,0,0),
|
|
mLeastDistSqr(std::numeric_limits<float>::max())
|
|
{ }
|
|
|
|
virtual btScalar addSingleResult(btManifoldPoint& cp,
|
|
const btCollisionObjectWrapper* col0Wrap,int partId0,int index0,
|
|
const btCollisionObjectWrapper* col1Wrap,int partId1,int index1)
|
|
{
|
|
const btCollisionObject* collisionObject = col1Wrap->m_collisionObject;
|
|
if (collisionObject != mMe)
|
|
{
|
|
btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA());
|
|
if(!mObject || distsqr < mLeastDistSqr)
|
|
{
|
|
mObject = collisionObject;
|
|
mLeastDistSqr = distsqr;
|
|
mContactPoint = cp.getPositionWorldOnA();
|
|
}
|
|
}
|
|
|
|
return 0.f;
|
|
}
|
|
};
|
|
|
|
std::pair<MWWorld::Ptr, osg::Vec3f> PhysicsSystem::getHitContact(const MWWorld::ConstPtr& actor,
|
|
const osg::Vec3f &origin,
|
|
const osg::Quat &orient,
|
|
float queryDistance)
|
|
{
|
|
const MWWorld::Store<ESM::GameSetting> &store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
|
|
|
btConeShape shape (osg::DegreesToRadians(store.find("fCombatAngleXY")->getFloat()/2.0f), queryDistance);
|
|
shape.setLocalScaling(btVector3(1, 1, osg::DegreesToRadians(store.find("fCombatAngleZ")->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(toBullet(orient), toBullet(center)));
|
|
|
|
const btCollisionObject* me = NULL;
|
|
const Actor* physactor = getActor(actor);
|
|
if (physactor)
|
|
me = physactor->getCollisionObject();
|
|
|
|
DeepestNotMeContactTestResultCallback resultCallback(me, toBullet(origin));
|
|
resultCallback.m_collisionFilterGroup = CollisionType_Actor;
|
|
resultCallback.m_collisionFilterMask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor;
|
|
mCollisionWorld->contactTest(&object, resultCallback);
|
|
|
|
if (resultCallback.mObject)
|
|
{
|
|
PtrHolder* holder = static_cast<PtrHolder*>(resultCallback.mObject->getUserPointer());
|
|
if (holder)
|
|
return std::make_pair(holder->getPtr(), 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 = NULL;
|
|
const Actor* actor = getActor(target);
|
|
if (actor)
|
|
targetCollisionObj = actor->getCollisionObject();
|
|
if (!targetCollisionObj)
|
|
return 0.f;
|
|
|
|
btTransform rayFrom;
|
|
rayFrom.setIdentity();
|
|
rayFrom.setOrigin(toBullet(point));
|
|
|
|
// target the collision object's world origin, this should be the center of the collision object
|
|
btTransform rayTo;
|
|
rayTo.setIdentity();
|
|
rayTo.setOrigin(targetCollisionObj->getWorldTransform().getOrigin());
|
|
|
|
btCollisionWorld::ClosestRayResultCallback cb(rayFrom.getOrigin(), rayTo.getOrigin());
|
|
|
|
btCollisionWorld::rayTestSingle(rayFrom, rayTo, targetCollisionObj, targetCollisionObj->getCollisionShape(), targetCollisionObj->getWorldTransform(), cb);
|
|
if (!cb.hasHit())
|
|
{
|
|
// didn't hit the target. this could happen if point is already inside the collision box
|
|
return 0.f;
|
|
}
|
|
else
|
|
return (point - toOsg(cb.m_hitPointWorld)).length();
|
|
}
|
|
|
|
class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
|
|
{
|
|
public:
|
|
ClosestNotMeRayResultCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to)
|
|
: btCollisionWorld::ClosestRayResultCallback(from, to)
|
|
, mMe(me)
|
|
{
|
|
}
|
|
|
|
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace)
|
|
{
|
|
if (rayResult.m_collisionObject == mMe)
|
|
return 1.f;
|
|
return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
|
|
}
|
|
private:
|
|
const btCollisionObject* mMe;
|
|
};
|
|
|
|
PhysicsSystem::RayResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, MWWorld::ConstPtr ignore, int mask, int group) const
|
|
{
|
|
btVector3 btFrom = toBullet(from);
|
|
btVector3 btTo = toBullet(to);
|
|
|
|
const btCollisionObject* me = NULL;
|
|
if (!ignore.isEmpty())
|
|
{
|
|
const Actor* actor = getActor(ignore);
|
|
if (actor)
|
|
me = actor->getCollisionObject();
|
|
else
|
|
{
|
|
const Object* object = getObject(ignore);
|
|
if (object)
|
|
me = object->getCollisionObject();
|
|
}
|
|
}
|
|
|
|
ClosestNotMeRayResultCallback resultCallback(me, btFrom, btTo);
|
|
resultCallback.m_collisionFilterGroup = group;
|
|
resultCallback.m_collisionFilterMask = mask;
|
|
|
|
mCollisionWorld->rayTest(btFrom, btTo, resultCallback);
|
|
|
|
RayResult result;
|
|
result.mHit = resultCallback.hasHit();
|
|
if (resultCallback.hasHit())
|
|
{
|
|
result.mHitPos = toOsg(resultCallback.m_hitPointWorld);
|
|
result.mHitNormal = toOsg(resultCallback.m_hitNormalWorld);
|
|
if (PtrHolder* ptrHolder = static_cast<PtrHolder*>(resultCallback.m_collisionObject->getUserPointer()))
|
|
result.mHitObject = ptrHolder->getPtr();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
PhysicsSystem::RayResult PhysicsSystem::castSphere(const osg::Vec3f &from, const osg::Vec3f &to, float radius)
|
|
{
|
|
btCollisionWorld::ClosestConvexResultCallback callback(toBullet(from), 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, toBullet(from));
|
|
btTransform to_ (btrot, toBullet(to));
|
|
|
|
mCollisionWorld->convexSweepTest(&shape, from_, to_, callback);
|
|
|
|
RayResult result;
|
|
result.mHit = callback.hasHit();
|
|
if (result.mHit)
|
|
{
|
|
result.mHitPos = toOsg(callback.m_hitPointWorld);
|
|
result.mHitNormal = toOsg(callback.m_hitNormalWorld);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool PhysicsSystem::getLineOfSight(const MWWorld::ConstPtr &actor1, const MWWorld::ConstPtr &actor2) const
|
|
{
|
|
const Actor* physactor1 = getActor(actor1);
|
|
const Actor* physactor2 = getActor(actor2);
|
|
|
|
if (!physactor1 || !physactor2)
|
|
return false;
|
|
|
|
osg::Vec3f pos1 (physactor1->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor1->getHalfExtents().z() * 0.9)); // eye level
|
|
osg::Vec3f pos2 (physactor2->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor2->getHalfExtents().z() * 0.9));
|
|
|
|
RayResult result = castRay(pos1, pos2, MWWorld::Ptr(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door);
|
|
|
|
return !result.mHit;
|
|
}
|
|
|
|
// physactor->getOnGround() is not a reliable indicator of whether the actor
|
|
// is on the ground (defaults to false, which means code blocks such as
|
|
// CharacterController::update() may falsely detect "falling").
|
|
//
|
|
// Also, collisions can move z position slightly off zero, giving a false
|
|
// indication. In order to reduce false detection of jumping, small distance
|
|
// below the actor is detected and ignored. A value of 1.5 is used here, but
|
|
// something larger may be more suitable. This change should resolve Bug#1271.
|
|
//
|
|
// TODO: There might be better places to update PhysicActor::mOnGround.
|
|
bool PhysicsSystem::isOnGround(const MWWorld::Ptr &actor)
|
|
{
|
|
Actor* physactor = getActor(actor);
|
|
if(!physactor)
|
|
return false;
|
|
if(physactor->getOnGround())
|
|
return true;
|
|
else
|
|
{
|
|
osg::Vec3f pos(actor.getRefData().getPosition().asVec3());
|
|
|
|
ActorTracer tracer;
|
|
// a small distance above collision object is considered "on ground"
|
|
tracer.findGround(physactor,
|
|
pos,
|
|
pos - osg::Vec3f(0, 0, 1.5f), // trace a small amount down
|
|
mCollisionWorld);
|
|
if(tracer.mFraction < 1.0f) // collision, must be close to something below
|
|
{
|
|
physactor->setOnGround(true);
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel)
|
|
{
|
|
const Actor* physicActor = getActor(actor);
|
|
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);
|
|
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::getRenderingHalfExtents(const MWWorld::ConstPtr &actor) const
|
|
{
|
|
const Actor* physactor = getActor(actor);
|
|
if (physactor)
|
|
return physactor->getRenderingHalfExtents();
|
|
else
|
|
return osg::Vec3f();
|
|
}
|
|
|
|
osg::Vec3f PhysicsSystem::getCollisionObjectPosition(const MWWorld::ConstPtr &actor) const
|
|
{
|
|
const Actor* physactor = getActor(actor);
|
|
if (physactor)
|
|
return physactor->getCollisionObjectPosition();
|
|
else
|
|
return osg::Vec3f();
|
|
}
|
|
|
|
class ContactTestResultCallback : public btCollisionWorld::ContactResultCallback
|
|
{
|
|
public:
|
|
ContactTestResultCallback(const btCollisionObject* testedAgainst)
|
|
: mTestedAgainst(testedAgainst)
|
|
{
|
|
}
|
|
|
|
const btCollisionObject* mTestedAgainst;
|
|
|
|
std::vector<MWWorld::Ptr> mResult;
|
|
|
|
virtual btScalar addSingleResult(btManifoldPoint& cp,
|
|
const btCollisionObjectWrapper* col0Wrap,int partId0,int index0,
|
|
const btCollisionObjectWrapper* col1Wrap,int partId1,int index1)
|
|
{
|
|
const btCollisionObject* collisionObject = col0Wrap->m_collisionObject;
|
|
if (collisionObject == mTestedAgainst)
|
|
collisionObject = col1Wrap->m_collisionObject;
|
|
PtrHolder* holder = static_cast<PtrHolder*>(collisionObject->getUserPointer());
|
|
if (holder)
|
|
mResult.push_back(holder->getPtr());
|
|
return 0.f;
|
|
}
|
|
};
|
|
|
|
std::vector<MWWorld::Ptr> PhysicsSystem::getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const
|
|
{
|
|
btCollisionObject* me = NULL;
|
|
|
|
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;
|
|
mCollisionWorld->contactTest(me, resultCallback);
|
|
return resultCallback.mResult;
|
|
}
|
|
|
|
osg::Vec3f PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, float maxHeight)
|
|
{
|
|
ActorMap::iterator found = mActors.find(ptr);
|
|
if (found == mActors.end())
|
|
return ptr.getRefData().getPosition().asVec3();
|
|
else
|
|
return MovementSolver::traceDown(ptr, found->second, mCollisionWorld, maxHeight);
|
|
}
|
|
|
|
void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts)
|
|
{
|
|
HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts);
|
|
mHeightFields[std::make_pair(x,y)] = heightfield;
|
|
|
|
mCollisionWorld->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())
|
|
{
|
|
mCollisionWorld->removeCollisionObject(heightfield->second->getCollisionObject());
|
|
delete heightfield->second;
|
|
mHeightFields.erase(heightfield);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
Object *obj = new Object(ptr, shapeInstance);
|
|
mObjects.insert(std::make_pair(ptr, obj));
|
|
|
|
if (obj->isAnimated())
|
|
mAnimatedObjects.insert(obj);
|
|
|
|
mCollisionWorld->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())
|
|
{
|
|
mCollisionWorld->removeCollisionObject(found->second->getCollisionObject());
|
|
|
|
if (mUnrefQueue.get())
|
|
mUnrefQueue->push(found->second->getShapeInstance());
|
|
|
|
mAnimatedObjects.erase(found->second);
|
|
|
|
delete found->second;
|
|
mObjects.erase(found);
|
|
}
|
|
|
|
ActorMap::iterator foundActor = mActors.find(ptr);
|
|
if (foundActor != mActors.end())
|
|
{
|
|
delete foundActor->second;
|
|
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 (CollisionMap::iterator it = map.begin(); it != map.end(); ++it)
|
|
{
|
|
if (it->second == old)
|
|
it->second = updated;
|
|
}
|
|
}
|
|
|
|
void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
|
|
{
|
|
ObjectMap::iterator found = mObjects.find(old);
|
|
if (found != mObjects.end())
|
|
{
|
|
Object* obj = found->second;
|
|
obj->updatePtr(updated);
|
|
mObjects.erase(found);
|
|
mObjects.insert(std::make_pair(updated, obj));
|
|
}
|
|
|
|
ActorMap::iterator foundActor = mActors.find(old);
|
|
if (foundActor != mActors.end())
|
|
{
|
|
Actor* actor = foundActor->second;
|
|
actor->updatePtr(updated);
|
|
mActors.erase(foundActor);
|
|
mActors.insert(std::make_pair(updated, 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;
|
|
return NULL;
|
|
}
|
|
|
|
const Actor *PhysicsSystem::getActor(const MWWorld::ConstPtr &ptr) const
|
|
{
|
|
ActorMap::const_iterator found = mActors.find(ptr);
|
|
if (found != mActors.end())
|
|
return found->second;
|
|
return NULL;
|
|
}
|
|
|
|
const Object* PhysicsSystem::getObject(const MWWorld::ConstPtr &ptr) const
|
|
{
|
|
ObjectMap::const_iterator found = mObjects.find(ptr);
|
|
if (found != mObjects.end())
|
|
return found->second;
|
|
return NULL;
|
|
}
|
|
|
|
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);
|
|
mCollisionWorld->updateSingleAabb(found->second->getCollisionObject());
|
|
return;
|
|
}
|
|
ActorMap::iterator foundActor = mActors.find(ptr);
|
|
if (foundActor != mActors.end())
|
|
{
|
|
foundActor->second->updateScale();
|
|
mCollisionWorld->updateSingleAabb(foundActor->second->getCollisionObject());
|
|
return;
|
|
}
|
|
}
|
|
|
|
void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr)
|
|
{
|
|
ObjectMap::iterator found = mObjects.find(ptr);
|
|
if (found != mObjects.end())
|
|
{
|
|
found->second->setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
|
|
mCollisionWorld->updateSingleAabb(found->second->getCollisionObject());
|
|
return;
|
|
}
|
|
ActorMap::iterator foundActor = mActors.find(ptr);
|
|
if (foundActor != mActors.end())
|
|
{
|
|
foundActor->second->updateRotation();
|
|
mCollisionWorld->updateSingleAabb(foundActor->second->getCollisionObject());
|
|
return;
|
|
}
|
|
}
|
|
|
|
void PhysicsSystem::updatePosition(const MWWorld::Ptr &ptr)
|
|
{
|
|
ObjectMap::iterator found = mObjects.find(ptr);
|
|
if (found != mObjects.end())
|
|
{
|
|
found->second->setOrigin(toBullet(ptr.getRefData().getPosition().asVec3()));
|
|
mCollisionWorld->updateSingleAabb(found->second->getCollisionObject());
|
|
return;
|
|
}
|
|
ActorMap::iterator foundActor = mActors.find(ptr);
|
|
if (foundActor != mActors.end())
|
|
{
|
|
foundActor->second->updatePosition();
|
|
mCollisionWorld->updateSingleAabb(foundActor->second->getCollisionObject());
|
|
return;
|
|
}
|
|
}
|
|
|
|
void PhysicsSystem::addActor (const MWWorld::Ptr& ptr, const std::string& mesh) {
|
|
osg::ref_ptr<const Resource::BulletShape> shape = mShapeManager->getShape(mesh);
|
|
if (!shape)
|
|
return;
|
|
|
|
Actor* actor = new Actor(ptr, shape, mCollisionWorld);
|
|
mActors.insert(std::make_pair(ptr, 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);
|
|
return cmode;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &movement)
|
|
{
|
|
PtrVelocityList::iterator iter = mMovementQueue.begin();
|
|
for(;iter != mMovementQueue.end();++iter)
|
|
{
|
|
if(iter->first == ptr)
|
|
{
|
|
iter->second = movement;
|
|
return;
|
|
}
|
|
}
|
|
|
|
mMovementQueue.push_back(std::make_pair(ptr, movement));
|
|
}
|
|
|
|
void PhysicsSystem::clearQueuedMovement()
|
|
{
|
|
mMovementQueue.clear();
|
|
mStandingCollisions.clear();
|
|
}
|
|
|
|
const PtrVelocityList& PhysicsSystem::applyQueuedMovement(float dt)
|
|
{
|
|
mMovementResults.clear();
|
|
|
|
mTimeAccum += dt;
|
|
const float physicsDt = 1.f/60.0f;
|
|
|
|
const int maxAllowedSteps = 20;
|
|
int numSteps = mTimeAccum / (physicsDt);
|
|
numSteps = std::min(numSteps, maxAllowedSteps);
|
|
|
|
mTimeAccum -= numSteps * physicsDt;
|
|
|
|
if (numSteps)
|
|
{
|
|
// Collision events should be available on every frame
|
|
mStandingCollisions.clear();
|
|
}
|
|
|
|
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
PtrVelocityList::iterator iter = mMovementQueue.begin();
|
|
for(;iter != mMovementQueue.end();++iter)
|
|
{
|
|
ActorMap::iterator foundActor = mActors.find(iter->first);
|
|
if (foundActor == mActors.end()) // actor was already removed from the scene
|
|
continue;
|
|
Actor* physicActor = foundActor->second;
|
|
|
|
float waterlevel = -std::numeric_limits<float>::max();
|
|
const MWWorld::CellStore *cell = iter->first.getCell();
|
|
if(cell->getCell()->hasWater())
|
|
waterlevel = cell->getWaterLevel();
|
|
|
|
const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects();
|
|
|
|
bool waterCollision = false;
|
|
if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude())
|
|
{
|
|
if (!world->isUnderwater(iter->first.getCell(), osg::Vec3f(iter->first.getRefData().getPosition().asVec3())))
|
|
waterCollision = true;
|
|
else if (physicActor->getCollisionMode() && canMoveToWaterSurface(iter->first, waterlevel))
|
|
{
|
|
const osg::Vec3f actorPosition = physicActor->getPosition();
|
|
physicActor->setPosition(osg::Vec3f(actorPosition.x(), actorPosition.y(), waterlevel));
|
|
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));
|
|
|
|
osg::Vec3f position = physicActor->getPosition();
|
|
float oldHeight = position.z();
|
|
for (int i=0; i<numSteps; ++i)
|
|
{
|
|
position = MovementSolver::move(position, physicActor->getPtr(), physicActor, iter->second, physicsDt,
|
|
world->isFlying(iter->first),
|
|
waterlevel, slowFall, mCollisionWorld, mStandingCollisions);
|
|
physicActor->setPosition(position);
|
|
}
|
|
|
|
float interpolationFactor = mTimeAccum / physicsDt;
|
|
osg::Vec3f interpolated = position * interpolationFactor + physicActor->getPreviousPosition() * (1.f - interpolationFactor);
|
|
|
|
float heightDiff = position.z() - oldHeight;
|
|
|
|
if (heightDiff < 0)
|
|
iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff);
|
|
|
|
mMovementResults.push_back(std::make_pair(iter->first, interpolated));
|
|
}
|
|
|
|
mMovementQueue.clear();
|
|
|
|
return mMovementResults;
|
|
}
|
|
|
|
void PhysicsSystem::stepSimulation(float dt)
|
|
{
|
|
for (std::set<Object*>::iterator it = mAnimatedObjects.begin(); it != mAnimatedObjects.end(); ++it)
|
|
(*it)->animateCollisionShapes(mCollisionWorld);
|
|
|
|
#ifndef BT_NO_PROFILE
|
|
CProfileManager::Reset();
|
|
CProfileManager::Increment_Frame_Counter();
|
|
#endif
|
|
}
|
|
|
|
void PhysicsSystem::debugDraw()
|
|
{
|
|
if (mDebugDrawer.get())
|
|
mDebugDrawer->step();
|
|
}
|
|
|
|
bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const
|
|
{
|
|
for (CollisionMap::const_iterator it = mStandingCollisions.begin(); it != mStandingCollisions.end(); ++it)
|
|
{
|
|
if (it->first == actor && it->second == object)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void PhysicsSystem::getActorsStandingOn(const MWWorld::ConstPtr &object, std::vector<MWWorld::Ptr> &out) const
|
|
{
|
|
for (CollisionMap::const_iterator it = mStandingCollisions.begin(); it != mStandingCollisions.end(); ++it)
|
|
{
|
|
if (it->second == object)
|
|
out.push_back(it->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.get())
|
|
{
|
|
mCollisionWorld->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());
|
|
mCollisionWorld->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water,
|
|
CollisionType_Actor);
|
|
}
|
|
}
|