mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-15 23:49:55 +00:00
5bd921fa3a
One of the issue since the introduction of async physics is the quirky handling of scripted moves. Previous attempt to account for them was based on detecting changes in actor position while the physics thread is running. To this end, semantics of Actor::updatePosition() (which is responsible for set the absolute position of an actor in the world) was toned down to merely store the desired position, with the physics system actually responsible for moving the actor. For the cases were complete override of the physics simulation was needed, I introduced Actor::resetPosition(), which actually have same semantics as original updatePosition(). This in turn introduced a loads of new bugs when the weakened semantics broke key assumptions inside the engine (spawning, summoning, teleport, etc). Instead of tracking them down, count on the newly introduced support for object relative movements in the engine (World::moveObjectBy) to register relative movements and restore original handling of absolute positionning. Changes are relatively small: - move resetPosition() content into updatePosition() - call updatePosition() everywhere it was called before - remove all added calls to the now non-existing resetPosition() tldr; ditch last month worth of bug introduction and eradication and redo it properly
289 lines
7.9 KiB
C++
289 lines
7.9 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)
|
|
: mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false)
|
|
, mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents)
|
|
, 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(this);
|
|
|
|
updateRotation();
|
|
updateScale();
|
|
updatePosition();
|
|
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);
|
|
updateWorldPosition();
|
|
mPreviousPosition = mWorldPosition;
|
|
mPosition = mWorldPosition;
|
|
mSimulationPosition = mWorldPosition;
|
|
mStandingOnPtr = nullptr;
|
|
mSkipSimulation = true;
|
|
}
|
|
|
|
void Actor::updateWorldPosition()
|
|
{
|
|
if (mWorldPosition != mPtr.getRefData().getPosition().asVec3())
|
|
mWorldPositionChanged = true;
|
|
mWorldPosition = mPtr.getRefData().getPosition().asVec3();
|
|
}
|
|
|
|
osg::Vec3f Actor::getWorldPosition() const
|
|
{
|
|
return mWorldPosition;
|
|
}
|
|
|
|
void Actor::setSimulationPosition(const osg::Vec3f& position)
|
|
{
|
|
if (!mSkipSimulation)
|
|
mSimulationPosition = position;
|
|
mSkipSimulation = false;
|
|
}
|
|
|
|
osg::Vec3f Actor::getSimulationPosition() const
|
|
{
|
|
return mSimulationPosition;
|
|
}
|
|
|
|
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);
|
|
mWorldPositionChanged = false;
|
|
}
|
|
|
|
osg::Vec3f Actor::getCollisionObjectPosition() const
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
return Misc::Convert::toOsg(mLocalTransform.getOrigin());
|
|
}
|
|
|
|
bool Actor::setPosition(const osg::Vec3f& position)
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
|
|
mPreviousPosition = mPosition + mPositionOffset;
|
|
mPosition = position + mPositionOffset;
|
|
mPositionOffset = osg::Vec3f();
|
|
return hasChanged;
|
|
}
|
|
|
|
void Actor::adjustPosition(const osg::Vec3f& offset)
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
mPositionOffset += offset;
|
|
}
|
|
|
|
osg::Vec3f Actor::getPosition() const
|
|
{
|
|
return mPosition;
|
|
}
|
|
|
|
osg::Vec3f Actor::getPreviousPosition() const
|
|
{
|
|
return mPreviousPosition;
|
|
}
|
|
|
|
void Actor::updateRotation ()
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
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
|
|
{
|
|
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)
|
|
{
|
|
if (waterWalk != mCanWaterWalk)
|
|
{
|
|
mCanWaterWalk = waterWalk;
|
|
updateCollisionMask();
|
|
}
|
|
}
|
|
|
|
MWWorld::Ptr Actor::getStandingOnPtr() const
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
return mStandingOnPtr;
|
|
}
|
|
|
|
void Actor::setStandingOnPtr(const MWWorld::Ptr& ptr)
|
|
{
|
|
std::scoped_lock lock(mPositionMutex);
|
|
mStandingOnPtr = ptr;
|
|
}
|
|
|
|
}
|