mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-02-03 17:54:06 +00:00
e5fa457fe7
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.
272 lines
7.3 KiB
C++
272 lines
7.3 KiB
C++
#include "actor.hpp"
|
|
|
|
#include <BulletCollision/CollisionShapes/btCapsuleShape.h>
|
|
#include <BulletCollision/CollisionShapes/btBoxShape.h>
|
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
|
|
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
|
#include <components/resource/bulletshape.hpp>
|
|
#include <components/debug/debuglog.hpp>
|
|
#include <components/misc/convert.hpp>
|
|
|
|
#include "../mwworld/class.hpp"
|
|
|
|
#include "collisiontype.hpp"
|
|
#include "mtphysics.hpp"
|
|
|
|
namespace MWPhysics
|
|
{
|
|
|
|
|
|
Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler)
|
|
: mCanWaterWalk(false), mWalkingOnWater(false)
|
|
, mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBoxTranslate), mHalfExtents(shape->mCollisionBoxHalfExtents)
|
|
, mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
|
|
, mInternalCollisionMode(true)
|
|
, mExternalCollisionMode(true)
|
|
, mTaskScheduler(scheduler)
|
|
{
|
|
mPtr = ptr;
|
|
|
|
// We can not create actor without collisions - he will fall through the ground.
|
|
// In this case we should autogenerate collision box based on mesh shape
|
|
// (NPCs have bodyparts and use a different approach)
|
|
if (!ptr.getClass().isNpc() && mHalfExtents.length2() == 0.f)
|
|
{
|
|
if (shape->mCollisionShape)
|
|
{
|
|
btTransform transform;
|
|
transform.setIdentity();
|
|
btVector3 min;
|
|
btVector3 max;
|
|
|
|
shape->mCollisionShape->getAabb(transform, min, max);
|
|
mHalfExtents.x() = (max[0] - min[0])/2.f;
|
|
mHalfExtents.y() = (max[1] - min[1])/2.f;
|
|
mHalfExtents.z() = (max[2] - min[2])/2.f;
|
|
|
|
mMeshTranslation = osg::Vec3f(0.f, 0.f, mHalfExtents.z());
|
|
}
|
|
|
|
if (mHalfExtents.length2() == 0.f)
|
|
Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\".";
|
|
}
|
|
|
|
// Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it)
|
|
if (std::abs(mHalfExtents.x()-mHalfExtents.y())<mHalfExtents.x()*0.05 && mHalfExtents.z() >= mHalfExtents.x())
|
|
{
|
|
mShape.reset(new btCapsuleShapeZ(mHalfExtents.x(), 2*mHalfExtents.z() - 2*mHalfExtents.x()));
|
|
mRotationallyInvariant = true;
|
|
}
|
|
else
|
|
{
|
|
mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents)));
|
|
mRotationallyInvariant = false;
|
|
}
|
|
|
|
mConvexShape = static_cast<btConvexShape*>(mShape.get());
|
|
|
|
mCollisionObject.reset(new btCollisionObject);
|
|
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
|
|
mCollisionObject->setActivationState(DISABLE_DEACTIVATION);
|
|
mCollisionObject->setCollisionShape(mShape.get());
|
|
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
|
|
|
|
updateRotation();
|
|
updateScale();
|
|
updatePosition();
|
|
setPosition(mWorldPosition, true);
|
|
|
|
addCollisionMask(getCollisionMask());
|
|
updateCollisionObjectPosition();
|
|
}
|
|
|
|
Actor::~Actor()
|
|
{
|
|
if (mCollisionObject)
|
|
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
|
}
|
|
|
|
void Actor::enableCollisionMode(bool collision)
|
|
{
|
|
mInternalCollisionMode.store(collision, std::memory_order_release);
|
|
}
|
|
|
|
void Actor::enableCollisionBody(bool collision)
|
|
{
|
|
if (mExternalCollisionMode != collision)
|
|
{
|
|
mExternalCollisionMode = collision;
|
|
updateCollisionMask();
|
|
}
|
|
}
|
|
|
|
void Actor::addCollisionMask(int collisionMask)
|
|
{
|
|
mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Actor, collisionMask);
|
|
}
|
|
|
|
void Actor::updateCollisionMask()
|
|
{
|
|
mTaskScheduler->setCollisionFilterMask(mCollisionObject.get(), getCollisionMask());
|
|
}
|
|
|
|
int Actor::getCollisionMask() const
|
|
{
|
|
int collisionMask = CollisionType_World | CollisionType_HeightMap;
|
|
if (mExternalCollisionMode)
|
|
collisionMask |= CollisionType_Actor | CollisionType_Projectile | CollisionType_Door;
|
|
if (mCanWaterWalk)
|
|
collisionMask |= CollisionType_Water;
|
|
return collisionMask;
|
|
}
|
|
|
|
void Actor::updatePosition()
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
mWorldPosition = mPtr.getRefData().getPosition().asVec3();
|
|
}
|
|
|
|
osg::Vec3f Actor::getWorldPosition() const
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
return mWorldPosition;
|
|
}
|
|
|
|
void Actor::setNextPosition(const osg::Vec3f& position)
|
|
{
|
|
mNextPosition = position;
|
|
}
|
|
|
|
osg::Vec3f Actor::getNextPosition() const
|
|
{
|
|
return mNextPosition;
|
|
}
|
|
|
|
void Actor::updateCollisionObjectPosition()
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
mShape->setLocalScaling(Misc::Convert::toBullet(mScale));
|
|
osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale);
|
|
osg::Vec3f newPosition = scaledTranslation + mPosition;
|
|
mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition));
|
|
mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation));
|
|
mCollisionObject->setWorldTransform(mLocalTransform);
|
|
}
|
|
|
|
osg::Vec3f Actor::getCollisionObjectPosition() const
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
return Misc::Convert::toOsg(mLocalTransform.getOrigin());
|
|
}
|
|
|
|
void Actor::setPosition(const osg::Vec3f& position, bool reset)
|
|
{
|
|
if (reset)
|
|
{
|
|
mPreviousPosition = position;
|
|
mNextPosition = position;
|
|
}
|
|
else
|
|
mPreviousPosition = mPosition;
|
|
mPosition = position;
|
|
}
|
|
|
|
void Actor::adjustPosition(const osg::Vec3f& offset)
|
|
{
|
|
mPosition += offset;
|
|
mPreviousPosition += offset;
|
|
}
|
|
|
|
osg::Vec3f Actor::getPosition() const
|
|
{
|
|
return mPosition;
|
|
}
|
|
|
|
osg::Vec3f Actor::getPreviousPosition() const
|
|
{
|
|
return mPreviousPosition;
|
|
}
|
|
|
|
void Actor::updateRotation ()
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
if (mRotation == mPtr.getRefData().getBaseNode()->getAttitude())
|
|
return;
|
|
mRotation = mPtr.getRefData().getBaseNode()->getAttitude();
|
|
}
|
|
|
|
bool Actor::isRotationallyInvariant() const
|
|
{
|
|
return mRotationallyInvariant;
|
|
}
|
|
|
|
void Actor::updateScale()
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
float scale = mPtr.getCellRef().getScale();
|
|
osg::Vec3f scaleVec(scale,scale,scale);
|
|
|
|
mPtr.getClass().adjustScale(mPtr, scaleVec, false);
|
|
mScale = scaleVec;
|
|
|
|
scaleVec = osg::Vec3f(scale,scale,scale);
|
|
mPtr.getClass().adjustScale(mPtr, scaleVec, true);
|
|
mRenderingScale = scaleVec;
|
|
}
|
|
|
|
osg::Vec3f Actor::getHalfExtents() const
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
return osg::componentMultiply(mHalfExtents, mScale);
|
|
}
|
|
|
|
osg::Vec3f Actor::getOriginalHalfExtents() const
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
return mHalfExtents;
|
|
}
|
|
|
|
osg::Vec3f Actor::getRenderingHalfExtents() const
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
return osg::componentMultiply(mHalfExtents, mRenderingScale);
|
|
}
|
|
|
|
void Actor::setInertialForce(const osg::Vec3f &force)
|
|
{
|
|
mForce = force;
|
|
}
|
|
|
|
void Actor::setOnGround(bool grounded)
|
|
{
|
|
mOnGround.store(grounded, std::memory_order_release);
|
|
}
|
|
|
|
void Actor::setOnSlope(bool slope)
|
|
{
|
|
mOnSlope.store(slope, std::memory_order_release);
|
|
}
|
|
|
|
bool Actor::isWalkingOnWater() const
|
|
{
|
|
return mWalkingOnWater.load(std::memory_order_acquire);
|
|
}
|
|
|
|
void Actor::setWalkingOnWater(bool walkingOnWater)
|
|
{
|
|
mWalkingOnWater.store(walkingOnWater, std::memory_order_release);
|
|
}
|
|
|
|
void Actor::setCanWaterWalk(bool waterWalk)
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
if (waterWalk != mCanWaterWalk)
|
|
{
|
|
mCanWaterWalk = waterWalk;
|
|
updateCollisionMask();
|
|
}
|
|
}
|
|
|
|
}
|