Merge branch 'projectile_physics' into 'master'

Precise projectile physics (closes #4201)

Closes #4201

See merge request OpenMW/openmw!550
pull/3044/head
psi29a 4 years ago
commit d2c5de5211

@ -21,6 +21,7 @@
Bug #4039: Multiple followers should have the same following distance
Bug #4055: Local scripts don't inherit variables from their base record
Bug #4083: Door animation freezes when colliding with actors
Bug #4201: Projectile-projectile collision
Bug #4247: Cannot walk up stairs in Ebonheart docks
Bug #4363: Editor: Defect in Clone Function for Dialogue Info records
Bug #4447: Actor collision capsule shape allows looking through some walls

@ -73,7 +73,7 @@ add_openmw_dir (mwworld
add_openmw_dir (mwphysics
physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile
closestnotmeconvexresultcallback raycasting mtphysics contacttestwrapper
actorconvexcallback raycasting mtphysics contacttestwrapper projectileconvexcallback
)
add_openmw_dir (mwclass

@ -60,7 +60,10 @@ namespace MWMechanics
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded)
{
if (!target.isEmpty() && target.getClass().isActor())
if (target.isEmpty())
return;
if (target.getClass().isActor())
{
// Early-out for characters that have departed.
const auto& stats = target.getClass().getCreatureStats(target);
@ -82,7 +85,7 @@ namespace MWMechanics
return;
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (mId);
if (spell && !target.isEmpty() && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight))
if (spell && target.getClass().isActor() && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight))
{
int requiredResistance = (spell->mData.mType == ESM::Spell::ST_Disease) ?
ESM::MagicEffect::ResistCommonDisease
@ -105,13 +108,13 @@ namespace MWMechanics
// This is required for Weakness effects in a spell to apply to any subsequent effects in the spell.
// Otherwise, they'd only apply after the whole spell was added.
MagicEffects targetEffects;
if (!target.isEmpty() && target.getClass().isActor())
if (target.getClass().isActor())
targetEffects += target.getClass().getCreatureStats(target).getMagicEffects();
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
ActiveSpells targetSpells;
if (!target.isEmpty() && target.getClass().isActor())
if (target.getClass().isActor())
targetSpells = target.getClass().getCreatureStats(target).getActiveSpells();
bool canCastAnEffect = false; // For bound equipment.If this remains false
@ -123,7 +126,7 @@ namespace MWMechanics
int currentEffectIndex = 0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex)
effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex)
{
if (effectIt->mRange != range)
continue;
@ -267,7 +270,7 @@ namespace MWMechanics
}
// Re-casting a summon effect will remove the creature from previous castings of that effect.
if (isSummoningEffect(effectIt->mEffectID) && !target.isEmpty() && target.getClass().isActor())
if (isSummoningEffect(effectIt->mEffectID) && target.getClass().isActor())
{
CreatureStats& targetStats = target.getClass().getCreatureStats(target);
ESM::SummonKey key(effectIt->mEffectID, mId, currentEffectIndex);
@ -310,18 +313,16 @@ namespace MWMechanics
if (!exploded)
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile);
if (!target.isEmpty()) {
if (!reflectedEffects.mList.empty())
inflict(caster, target, reflectedEffects, range, true, exploded);
if (!reflectedEffects.mList.empty())
inflict(caster, target, reflectedEffects, range, true, exploded);
if (!appliedLastingEffects.empty())
{
int casterActorId = -1;
if (!caster.isEmpty() && caster.getClass().isActor())
casterActorId = caster.getClass().getCreatureStats(caster).getActorId();
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects,
mSourceName, casterActorId);
}
if (!appliedLastingEffects.empty())
{
int casterActorId = -1;
if (!caster.isEmpty() && caster.getClass().isActor())
casterActorId = caster.getClass().getCreatureStats(caster).getActorId();
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects,
mSourceName, casterActorId);
}
}

@ -1,6 +1,6 @@
#include <mutex>
#include "closestnotmeconvexresultcallback.hpp"
#include "actorconvexcallback.hpp"
#include "collisiontype.hpp"
#include "contacttestwrapper.h"
@ -31,13 +31,13 @@ namespace MWPhysics
}
};
ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world)
ActorConvexCallback::ActorConvexCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world)
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)),
mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot), mWorld(world)
{
}
btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
btScalar ActorConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
{
if (convexResult.m_hitCollisionObject == mMe)
return btScalar(1);

@ -1,5 +1,5 @@
#ifndef OPENMW_MWPHYSICS_CLOSESTNOTMECONVEXRESULTCALLBACK_H
#define OPENMW_MWPHYSICS_CLOSESTNOTMECONVEXRESULTCALLBACK_H
#ifndef OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H
#define OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
@ -7,10 +7,10 @@ class btCollisionObject;
namespace MWPhysics
{
class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
class ActorConvexCallback : public btCollisionWorld::ClosestConvexResultCallback
{
public:
ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world);
ActorConvexCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world);
btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) override;

@ -7,16 +7,13 @@
#include "../mwworld/class.hpp"
#include "actor.hpp"
#include "collisiontype.hpp"
#include "projectile.hpp"
#include "ptrholder.hpp"
namespace MWPhysics
{
ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to, Projectile* proj)
ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to)
: btCollisionWorld::ClosestRayResultCallback(from, to)
, mMe(me), mTargets(std::move(targets)), mProjectile(proj)
, mMe(me), mTargets(std::move(targets))
{
}
@ -25,9 +22,6 @@ namespace MWPhysics
if (rayResult.m_collisionObject == mMe)
return 1.f;
if (mProjectile && rayResult.m_collisionObject == mProjectile->getCollisionObject())
return 1.f;
if (!mTargets.empty())
{
if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end()))
@ -38,27 +32,6 @@ namespace MWPhysics
}
}
btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
if (mProjectile)
{
switch (rayResult.m_collisionObject->getBroadphaseHandle()->m_collisionFilterGroup)
{
case CollisionType_Actor:
{
auto* target = static_cast<Actor*>(rayResult.m_collisionObject->getUserPointer());
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
break;
}
case CollisionType_Projectile:
{
auto* target = static_cast<Projectile*>(rayResult.m_collisionObject->getUserPointer());
target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld);
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
break;
}
}
}
return rayResult.m_hitFraction;
return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
}
}

@ -14,13 +14,13 @@ namespace MWPhysics
class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
{
public:
ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to, Projectile* proj=nullptr);
ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to);
btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override;
private:
const btCollisionObject* mMe;
const std::vector<const btCollisionObject*> mTargets;
Projectile* mProjectile;
};
}

@ -55,6 +55,7 @@
#include "deepestnotmecontacttestresultcallback.hpp"
#include "closestnotmerayresultcallback.hpp"
#include "contacttestresultcallback.hpp"
#include "projectileconvexcallback.hpp"
#include "constants.hpp"
#include "movementsolver.hpp"
#include "mtphysics.hpp"
@ -246,7 +247,7 @@ namespace MWPhysics
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, int projId) const
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)
{
@ -283,7 +284,7 @@ namespace MWPhysics
}
}
ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo, getProjectile(projId));
ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo);
resultCallback.m_collisionFilterGroup = group;
resultCallback.m_collisionFilterMask = mask;
@ -580,15 +581,44 @@ namespace MWPhysics
}
}
void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position)
void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position) const
{
ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId);
if (foundProjectile != mProjectiles.end())
{
foundProjectile->second->setPosition(position);
mTaskScheduler->updateSingleAabb(foundProjectile->second);
const auto foundProjectile = mProjectiles.find(projectileId);
assert(foundProjectile != mProjectiles.end());
auto* projectile = foundProjectile->second.get();
btVector3 btFrom = Misc::Convert::toBullet(projectile->getPosition());
btVector3 btTo = Misc::Convert::toBullet(position);
if (btFrom == btTo)
return;
}
const auto casterPtr = projectile->getCaster();
const auto* caster = [this,&casterPtr]() -> const btCollisionObject*
{
const Actor* actor = getActor(casterPtr);
if (actor)
return actor->getCollisionObject();
const Object* object = getObject(casterPtr);
if (object)
return object->getCollisionObject();
return nullptr;
}();
assert(caster);
ProjectileConvexCallback resultCallback(caster, btFrom, btTo, projectile);
resultCallback.m_collisionFilterMask = 0xff;
resultCallback.m_collisionFilterGroup = CollisionType_Projectile;
const btQuaternion btrot = btQuaternion::getIdentity();
btTransform from_ (btrot, btFrom);
btTransform to_ (btrot, btTo);
mTaskScheduler->convexSweepTest(projectile->getConvexShape(), from_, to_, resultCallback);
const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(resultCallback.m_hitPointWorld);
projectile->setPosition(newpos);
mTaskScheduler->updateSingleAabb(foundProjectile->second);
}
void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr)
@ -651,10 +681,10 @@ namespace MWPhysics
mActors.emplace(ptr, std::move(actor));
}
int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position)
int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater)
{
mProjectileId++;
auto projectile = std::make_shared<Projectile>(mProjectileId, caster, position, mTaskScheduler.get(), this);
auto projectile = std::make_shared<Projectile>(caster, position, radius, canTraverseWater, mTaskScheduler.get(), this);
mProjectiles.emplace(mProjectileId, std::move(projectile));
return mProjectileId;
@ -863,7 +893,7 @@ namespace MWPhysics
mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight));
mWaterCollisionObject->setCollisionShape(mWaterCollisionShape.get());
mTaskScheduler->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water,
CollisionType_Actor);
CollisionType_Actor|CollisionType_Projectile);
}
bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const

@ -124,8 +124,8 @@ namespace MWPhysics
void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World);
void addActor (const MWWorld::Ptr& ptr, const std::string& mesh);
int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position);
void updateProjectile(const int projectileId, const osg::Vec3f &position);
int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater);
void updateProjectile(const int projectileId, const osg::Vec3f &position) const;
void removeProjectile(const int projectileId);
void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated);
@ -174,7 +174,7 @@ namespace MWPhysics
/// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors.
RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(),
std::vector<MWWorld::Ptr> targets = std::vector<MWWorld::Ptr>(),
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const override;
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const override;
RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override;

@ -13,19 +13,22 @@
#include "../mwworld/class.hpp"
#include "collisiontype.hpp"
#include "memory"
#include "mtphysics.hpp"
#include "projectile.hpp"
namespace MWPhysics
{
Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem)
: mActive(true)
Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem)
: mCanCrossWaterSurface(canCrossWaterSurface)
, mCrossedWaterSurface(false)
, mActive(true)
, mCaster(caster)
, mWaterHitPosition(std::nullopt)
, mPhysics(physicssystem)
, mTaskScheduler(scheduler)
, mProjectileId(projectileId)
{
mShape.reset(new btSphereShape(1.f));
mShape = std::make_unique<btSphereShape>(radius);
mConvexShape = static_cast<btConvexShape*>(mShape.get());
mCollisionObject = std::make_unique<btCollisionObject>();
@ -67,6 +70,17 @@ void Projectile::setPosition(const osg::Vec3f &position)
mTransformUpdatePending = true;
}
osg::Vec3f Projectile::getPosition() const
{
std::scoped_lock lock(mMutex);
return Misc::Convert::toOsg(mLocalTransform.getOrigin());
}
bool Projectile::canTraverseWater() const
{
return mCanCrossWaterSurface;
}
void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal)
{
if (!mActive.load(std::memory_order_acquire))
@ -78,12 +92,6 @@ void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal)
mActive.store(false, std::memory_order_release);
}
void Projectile::activate()
{
assert(!mActive);
mActive.store(true, std::memory_order_release);
}
MWWorld::Ptr Projectile::getCaster() const
{
std::scoped_lock lock(mMutex);
@ -108,21 +116,32 @@ bool Projectile::isValidTarget(const MWWorld::Ptr& target) const
if (mCaster == target)
return false;
if (!mValidTargets.empty())
if (target.isEmpty() || mValidTargets.empty())
return true;
bool validTarget = false;
for (const auto& targetActor : mValidTargets)
{
bool validTarget = false;
for (const auto& targetActor : mValidTargets)
if (targetActor == target)
{
if (targetActor == target)
{
validTarget = true;
break;
}
validTarget = true;
break;
}
return validTarget;
}
return true;
return validTarget;
}
std::optional<btVector3> Projectile::getWaterHitPosition()
{
return std::exchange(mWaterHitPosition, std::nullopt);
}
void Projectile::setWaterHitPosition(btVector3 pos)
{
if (mCrossedWaterSurface)
return;
mCrossedWaterSurface = true;
mWaterHitPosition = pos;
}
}

@ -4,6 +4,7 @@
#include <atomic>
#include <memory>
#include <mutex>
#include <optional>
#include "components/misc/convert.hpp"
@ -32,7 +33,7 @@ namespace MWPhysics
class Projectile final : public PtrHolder
{
public:
Projectile(const int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem);
Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem);
~Projectile() override;
btConvexShape* getConvexShape() const { return mConvexShape; }
@ -40,17 +41,13 @@ namespace MWPhysics
void commitPositionChange();
void setPosition(const osg::Vec3f& position);
osg::Vec3f getPosition() const;
btCollisionObject* getCollisionObject() const
{
return mCollisionObject.get();
}
int getProjectileId() const
{
return mProjectileId;
}
bool isActive() const
{
return mActive.load(std::memory_order_acquire);
@ -65,18 +62,16 @@ namespace MWPhysics
MWWorld::Ptr getCaster() const;
void setCaster(MWWorld::Ptr caster);
osg::Vec3f getHitPos() const
{
assert(!mActive);
return Misc::Convert::toOsg(mHitPosition);
}
bool canTraverseWater() const;
void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal);
void activate();
void setValidTargets(const std::vector<MWWorld::Ptr>& targets);
bool isValidTarget(const MWWorld::Ptr& target) const;
std::optional<btVector3> getWaterHitPosition();
void setWaterHitPosition(btVector3 pos);
private:
std::unique_ptr<btCollisionShape> mShape;
@ -85,9 +80,12 @@ namespace MWPhysics
std::unique_ptr<btCollisionObject> mCollisionObject;
btTransform mLocalTransform;
bool mTransformUpdatePending;
bool mCanCrossWaterSurface;
bool mCrossedWaterSurface;
std::atomic<bool> mActive;
MWWorld::Ptr mCaster;
MWWorld::Ptr mHitTarget;
std::optional<btVector3> mWaterHitPosition;
btVector3 mHitPosition;
btVector3 mHitNormal;
@ -95,15 +93,11 @@ namespace MWPhysics
mutable std::mutex mMutex;
osg::Vec3f mPosition;
PhysicsSystem *mPhysics;
PhysicsTaskScheduler *mTaskScheduler;
Projectile(const Projectile&);
Projectile& operator=(const Projectile&);
int mProjectileId;
};
}

@ -0,0 +1,67 @@
#include "../mwworld/class.hpp"
#include "actor.hpp"
#include "collisiontype.hpp"
#include "projectile.hpp"
#include "projectileconvexcallback.hpp"
#include "ptrholder.hpp"
namespace MWPhysics
{
ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj)
: btCollisionWorld::ClosestConvexResultCallback(from, to)
, mMe(me), mProjectile(proj)
{
assert(mProjectile);
}
btScalar ProjectileConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace)
{
// don't hit the caster
if (result.m_hitCollisionObject == mMe)
return 1.f;
// don't hit the projectile
if (result.m_hitCollisionObject == mProjectile->getCollisionObject())
return 1.f;
btCollisionWorld::ClosestConvexResultCallback::addSingleResult(result, normalInWorldSpace);
switch (result.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup)
{
case CollisionType_Actor:
{
auto* target = static_cast<Actor*>(result.m_hitCollisionObject->getUserPointer());
if (!mProjectile->isValidTarget(target->getPtr()))
return 1.f;
mProjectile->hit(target->getPtr(), result.m_hitPointLocal, result.m_hitNormalLocal);
break;
}
case CollisionType_Projectile:
{
auto* target = static_cast<Projectile*>(result.m_hitCollisionObject->getUserPointer());
if (!mProjectile->isValidTarget(target->getCaster()))
return 1.f;
target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld);
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
break;
}
case CollisionType_Water:
{
mProjectile->setWaterHitPosition(m_hitPointWorld);
if (mProjectile->canTraverseWater())
return 1.f;
mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld);
break;
}
default:
{
mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld);
break;
}
}
return result.m_hitFraction;
}
}

@ -0,0 +1,27 @@
#ifndef OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H
#define OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H
#include <vector>
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
class btCollisionObject;
namespace MWPhysics
{
class Projectile;
class ProjectileConvexCallback : public btCollisionWorld::ClosestConvexResultCallback
{
public:
ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj);
btScalar addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) override;
private:
const btCollisionObject* mMe;
Projectile* mProjectile;
};
}
#endif

@ -29,7 +29,7 @@ namespace MWPhysics
/// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors.
virtual RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(),
std::vector<MWWorld::Ptr> targets = std::vector<MWWorld::Ptr>(),
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const = 0;
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const = 0;
virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0;

@ -7,7 +7,7 @@
#include "collisiontype.hpp"
#include "actor.hpp"
#include "closestnotmeconvexresultcallback.hpp"
#include "actorconvexcallback.hpp"
namespace MWPhysics
{
@ -24,7 +24,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star
to.setOrigin(btend);
const btVector3 motion = btstart-btend;
ClosestNotMeConvexResultCallback newTraceCallback(actor, motion, btScalar(0.0), world);
ActorConvexCallback newTraceCallback(actor, motion, btScalar(0.0), world);
// Inherit the actor's collision group and mask
newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup;
newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask;
@ -62,7 +62,7 @@ void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const
btTransform to(trans.getBasis(), btend);
const btVector3 motion = btstart-btend;
ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world);
ActorConvexCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0), world);
// Inherit the actor's collision group and mask
newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup;
newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask;

@ -3,6 +3,7 @@
#include <iomanip>
#include <memory>
#include <optional>
#include <osg/PositionAttitudeTransform>
#include <components/debug/debuglog.hpp>
@ -43,6 +44,7 @@
#include "../mwsound/sound.hpp"
#include "../mwphysics/collisiontype.hpp"
#include "../mwphysics/physicssystem.hpp"
#include "../mwphysics/projectile.hpp"
@ -187,7 +189,7 @@ namespace MWWorld
};
void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient,
float ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient,
bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture)
{
state.mNode = new osg::PositionAttitudeTransform;
@ -251,6 +253,7 @@ namespace MWWorld
state.mNode->accept(assignVisitor);
MWRender::overrideFirstRootTexture(texture, mResourceSystem, projectile);
return projectile->getBound().radius();
}
void ProjectileManager::update(State& state, float duration)
@ -305,7 +308,7 @@ namespace MWWorld
osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects);
createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture);
const auto radius = createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
for (const std::string &soundid : state.mSoundIds)
@ -316,7 +319,7 @@ namespace MWWorld
state.mSounds.push_back(sound);
}
state.mProjectileId = mPhysics->addProjectile(caster, pos);
state.mProjectileId = mPhysics->addProjectile(caster, pos, radius, false);
state.mToDelete = false;
mMagicBolts.push_back(state);
}
@ -340,7 +343,7 @@ namespace MWWorld
if (!ptr.getClass().getEnchantment(ptr).empty())
SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr));
state.mProjectileId = mPhysics->addProjectile(actor, pos);
state.mProjectileId = mPhysics->addProjectile(actor, pos, 1.f, true);
state.mToDelete = false;
mProjectiles.push_back(state);
}
@ -407,15 +410,7 @@ namespace MWWorld
float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed;
osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
direction.normalize();
osg::Vec3f pos(magicBoltState.mNode->getPosition());
osg::Vec3f newPos = pos + direction * duration * speed;
for (const auto& sound : magicBoltState.mSounds)
sound->setPosition(newPos);
magicBoltState.mNode->setPosition(newPos);
mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos);
osg::Vec3f newPos = projectile->getPosition() + direction * duration * speed;
update(magicBoltState, duration);
@ -425,41 +420,7 @@ namespace MWWorld
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
projectile->setValidTargets(targetActors);
// Check for impact
// TODO: use a proper btRigidBody / btGhostObject?
const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, magicBoltState.mProjectileId);
bool hit = false;
if (result.mHit)
{
hit = true;
if (result.mHitObject.isEmpty())
{
// terrain or projectile
}
else
{
MWMechanics::CastSpell cast(caster, result.mHitObject);
cast.mHitPosition = pos;
cast.mId = magicBoltState.mSpellId;
cast.mSourceName = magicBoltState.mSourceName;
cast.mStack = false;
cast.inflict(result.mHitObject, caster, magicBoltState.mEffects, ESM::RT_Target, false, true);
mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal));
}
}
// Explodes when hitting water
if (MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos))
hit = true;
if (hit)
{
MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, result.mHitObject,
ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName);
cleanupMagicBolt(magicBoltState);
}
mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos);
}
}
@ -467,9 +428,6 @@ namespace MWWorld
{
for (auto& projectileState : mProjectiles)
{
if (projectileState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
if (!projectile->isActive())
continue;
@ -477,8 +435,7 @@ namespace MWWorld
// simulating aerodynamics at all
projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration;
osg::Vec3f pos(projectileState.mNode->getPosition());
osg::Vec3f newPos = pos + projectileState.mVelocity * duration;
osg::Vec3f newPos = projectile->getPosition() + projectileState.mVelocity * duration;
// rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction.
if (!projectileState.mThrown)
@ -488,10 +445,6 @@ namespace MWWorld
projectileState.mNode->setAttitude(orient);
}
projectileState.mNode->setPosition(newPos);
mPhysics->updateProjectile(projectileState.mProjectileId, newPos);
update(projectileState, duration);
MWWorld::Ptr caster = projectileState.getCaster();
@ -502,36 +455,7 @@ namespace MWWorld
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
projectile->setValidTargets(targetActors);
// Check for impact
// TODO: use a proper btRigidBody / btGhostObject?
const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, projectileState.mProjectileId);
bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos);
if (result.mHit || underwater)
{
// Try to get a Ptr to the bow that was used. It might no longer exist.
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow);
MWWorld::Ptr bow = projectileRef.getPtr();
if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId)
{
MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster);
MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId))
bow = *invIt;
}
if (caster.isEmpty())
caster = result.mHitObject;
MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, projectileState.mAttackStrength);
mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal));
if (underwater)
mRendering->emitWaterRipple(newPos);
cleanupProjectile(projectileState);
}
mPhysics->updateProjectile(projectileState.mProjectileId, newPos);
}
}
@ -543,17 +467,19 @@ namespace MWWorld
continue;
auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
if (const auto hitWaterPos = projectile->getWaterHitPosition())
mRendering->emitWaterRipple(Misc::Convert::toOsg(*hitWaterPos));
const auto pos = projectile->getPosition();
projectileState.mNode->setPosition(pos);
if (projectile->isActive())
continue;
const auto target = projectile->getTarget();
const auto pos = projectile->getHitPos();
MWWorld::Ptr caster = projectileState.getCaster();
auto caster = projectileState.getCaster();
assert(target != caster);
if (!projectile->isValidTarget(target))
{
projectile->activate();
continue;
}
if (caster.isEmpty())
caster = target;
@ -569,9 +495,8 @@ namespace MWWorld
bow = *invIt;
}
projectileState.mHitPosition = pos;
cleanupProjectile(projectileState);
MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength);
cleanupProjectile(projectileState);
}
for (auto& magicBoltState : mMagicBolts)
{
@ -579,20 +504,18 @@ namespace MWWorld
continue;
auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
const auto pos = projectile->getPosition();
magicBoltState.mNode->setPosition(pos);
for (const auto& sound : magicBoltState.mSounds)
sound->setPosition(pos);
if (projectile->isActive())
continue;
const auto target = projectile->getTarget();
const auto pos = projectile->getHitPos();
MWWorld::Ptr caster = magicBoltState.getCaster();
const auto caster = magicBoltState.getCaster();
assert(target != caster);
if (!projectile->isValidTarget(target))
{
projectile->activate();
continue;
}
magicBoltState.mHitPosition = pos;
cleanupMagicBolt(magicBoltState);
MWMechanics::CastSpell cast(caster, target);
cast.mHitPosition = pos;
@ -602,6 +525,7 @@ namespace MWWorld
cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true);
MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName);
cleanupMagicBolt(magicBoltState);
}
mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }),
mProjectiles.end());
@ -702,7 +626,7 @@ namespace MWWorld
int weaponType = ptr.get<ESM::Weapon>()->mBase->mData.mType;
state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown;
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition));
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), 1.f, true);
}
catch(...)
{
@ -747,7 +671,6 @@ namespace MWWorld
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0));
MWWorld::Ptr ptr = ref.getPtr();
model = ptr.getClass().getModel(ptr);
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition));
}
catch(...)
{
@ -755,7 +678,8 @@ namespace MWWorld
}
osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects);
createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture);
const auto radius = createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture);
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), radius, false);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
for (const std::string &soundid : state.mSoundIds)

@ -80,8 +80,6 @@ namespace MWWorld
int mActorId;
int mProjectileId;
osg::Vec3f mHitPosition;
// TODO: this will break when the game is saved and reloaded, since there is currently
// no way to write identifiers for non-actors to a savegame.
MWWorld::Ptr mCasterHandle;
@ -132,7 +130,7 @@ namespace MWWorld
void moveProjectiles(float dt);
void moveMagicBolts(float dt);
void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient,
float createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient,
bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = "");
void update (State& state, float duration);

Loading…
Cancel
Save