1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-31 15:32:45 +00:00
fredzio 0bce6c09e1 Change projectile behaviour to be like in vanilla wrt. water plane:
- enchanted arrow explode upon hit the water plane
- non enchanted arrow disappear (or more accurately, they hit nothingness)
- enchanted arrow shot underwater explode immediately
- non enchanted arrow disappear immediately

Also, solve a bug that occured previously and could theoritically still happens where we use the last tested collision position for instead of the last registered hit:
Use the hit position as saved inside Projectile::hit() instead of the last position saved inside the callback.
If a projectile collides with several objects (bottom of the sea and water surface for instance), the last collision tested won't necessarily be the impact position as we have no control over the order in which the tests are performed.
2021-09-03 10:24:05 +02:00

140 lines
3.9 KiB
C++

#include <memory>
#include <BulletCollision/CollisionShapes/btSphereShape.h>
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include <components/misc/convert.hpp>
#include "../mwworld/class.hpp"
#include "actor.hpp"
#include "collisiontype.hpp"
#include "mtphysics.hpp"
#include "object.hpp"
#include "projectile.hpp"
namespace MWPhysics
{
Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem)
: mHitWater(false)
, mActive(true)
, mHitTarget(nullptr)
, mPhysics(physicssystem)
, mTaskScheduler(scheduler)
{
mShape = std::make_unique<btSphereShape>(radius);
mConvexShape = static_cast<btConvexShape*>(mShape.get());
mCollisionObject = std::make_unique<btCollisionObject>();
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
mCollisionObject->setActivationState(DISABLE_DEACTIVATION);
mCollisionObject->setCollisionShape(mShape.get());
mCollisionObject->setUserPointer(this);
setPosition(position);
setCaster(caster);
const int collisionMask = CollisionType_World | CollisionType_HeightMap |
CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile;
mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask);
commitPositionChange();
}
Projectile::~Projectile()
{
if (!mActive)
mPhysics->reportCollision(mHitPosition, mHitNormal);
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
}
void Projectile::commitPositionChange()
{
std::scoped_lock lock(mMutex);
if (mTransformUpdatePending)
{
auto& trans = mCollisionObject->getWorldTransform();
trans.setOrigin(Misc::Convert::toBullet(mPosition));
mCollisionObject->setWorldTransform(trans);
mTransformUpdatePending = false;
}
}
void Projectile::setPosition(const osg::Vec3f &position)
{
std::scoped_lock lock(mMutex);
mPosition = position;
mTransformUpdatePending = true;
}
osg::Vec3f Projectile::getPosition() const
{
std::scoped_lock lock(mMutex);
return mPosition;
}
void Projectile::hit(const btCollisionObject* target, btVector3 pos, btVector3 normal)
{
bool active = true;
if (!mActive.compare_exchange_strong(active, false, std::memory_order_relaxed) || !active)
return;
mHitTarget = target;
mHitPosition = pos;
mHitNormal = normal;
}
MWWorld::Ptr Projectile::getTarget() const
{
assert(!mActive);
auto* target = static_cast<PtrHolder*>(mHitTarget->getUserPointer());
return target ? target->getPtr() : MWWorld::Ptr();
}
MWWorld::Ptr Projectile::getCaster() const
{
return mCaster;
}
void Projectile::setCaster(const MWWorld::Ptr& caster)
{
mCaster = caster;
mCasterColObj = [this,&caster]() -> const btCollisionObject*
{
const Actor* actor = mPhysics->getActor(caster);
if (actor)
return actor->getCollisionObject();
const Object* object = mPhysics->getObject(caster);
if (object)
return object->getCollisionObject();
return nullptr;
}();
}
void Projectile::setValidTargets(const std::vector<MWWorld::Ptr>& targets)
{
std::scoped_lock lock(mMutex);
mValidTargets.clear();
for (const auto& ptr : targets)
{
const auto* physicActor = mPhysics->getActor(ptr);
if (physicActor)
mValidTargets.push_back(physicActor->getCollisionObject());
}
}
bool Projectile::isValidTarget(const btCollisionObject* target) const
{
assert(target);
std::scoped_lock lock(mMutex);
if (mCasterColObj == target)
return false;
if (mValidTargets.empty())
return true;
return std::any_of(mValidTargets.begin(), mValidTargets.end(),
[target](const btCollisionObject* actor) { return target == actor; });
}
}