Use convexSweepTest for projectile movement to solve any

imprecision issue with projectile collision detection.
Simplify the mechanics: manage hits in one spot.
Give magic projectiles a collision shape similar in size to their visible
model.

Rename the 2 convex result callback to clearly state their purpose.
pull/593/head
fredzio 4 years ago
parent 9eba086c34
commit 1f4c85520f

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

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

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

@ -1,6 +1,6 @@
#include <mutex> #include <mutex>
#include "closestnotmeconvexresultcallback.hpp" #include "actorconvexcallback.hpp"
#include "collisiontype.hpp" #include "collisiontype.hpp"
#include "contacttestwrapper.h" #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)), : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)),
mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot), mWorld(world) 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) if (convexResult.m_hitCollisionObject == mMe)
return btScalar(1); return btScalar(1);

@ -1,5 +1,5 @@
#ifndef OPENMW_MWPHYSICS_CLOSESTNOTMECONVEXRESULTCALLBACK_H #ifndef OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H
#define OPENMW_MWPHYSICS_CLOSESTNOTMECONVEXRESULTCALLBACK_H #define OPENMW_MWPHYSICS_ACTORCONVEXCALLBACK_H
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h> #include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
@ -7,10 +7,10 @@ class btCollisionObject;
namespace MWPhysics namespace MWPhysics
{ {
class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback class ActorConvexCallback : public btCollisionWorld::ClosestConvexResultCallback
{ {
public: 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; btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) override;

@ -7,16 +7,13 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "actor.hpp"
#include "collisiontype.hpp"
#include "projectile.hpp"
#include "ptrholder.hpp" #include "ptrholder.hpp"
namespace MWPhysics 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) : 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) if (rayResult.m_collisionObject == mMe)
return 1.f; return 1.f;
if (mProjectile && rayResult.m_collisionObject == mProjectile->getCollisionObject())
return 1.f;
if (!mTargets.empty()) if (!mTargets.empty())
{ {
if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end()))
@ -38,27 +32,6 @@ namespace MWPhysics
} }
} }
btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); return 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;
} }
} }

@ -14,13 +14,13 @@ namespace MWPhysics
class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
{ {
public: 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; btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override;
private: private:
const btCollisionObject* mMe; const btCollisionObject* mMe;
const std::vector<const btCollisionObject*> mTargets; const std::vector<const btCollisionObject*> mTargets;
Projectile* mProjectile;
}; };
} }

@ -55,6 +55,7 @@
#include "deepestnotmecontacttestresultcallback.hpp" #include "deepestnotmecontacttestresultcallback.hpp"
#include "closestnotmerayresultcallback.hpp" #include "closestnotmerayresultcallback.hpp"
#include "contacttestresultcallback.hpp" #include "contacttestresultcallback.hpp"
#include "projectileconvexcallback.hpp"
#include "constants.hpp" #include "constants.hpp"
#include "movementsolver.hpp" #include "movementsolver.hpp"
#include "mtphysics.hpp" #include "mtphysics.hpp"
@ -246,7 +247,7 @@ namespace MWPhysics
return 0.f; 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) 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_collisionFilterGroup = group;
resultCallback.m_collisionFilterMask = mask; 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); const auto foundProjectile = mProjectiles.find(projectileId);
if (foundProjectile != mProjectiles.end()) 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*
{ {
foundProjectile->second->setPosition(position); 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); mTaskScheduler->updateSingleAabb(foundProjectile->second);
return;
}
} }
void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr)
@ -651,10 +681,10 @@ namespace MWPhysics
mActors.emplace(ptr, std::move(actor)); 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++; 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)); mProjectiles.emplace(mProjectileId, std::move(projectile));
return mProjectileId; return mProjectileId;
@ -863,7 +893,7 @@ namespace MWPhysics
mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight)); mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight));
mWaterCollisionObject->setCollisionShape(mWaterCollisionShape.get()); mWaterCollisionObject->setCollisionShape(mWaterCollisionShape.get());
mTaskScheduler->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water, 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 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 addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World);
void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh);
int addProjectile(const MWWorld::Ptr& caster, 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); void updateProjectile(const int projectileId, const osg::Vec3f &position) const;
void removeProjectile(const int projectileId); void removeProjectile(const int projectileId);
void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); 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. /// @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(), 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>(), 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; RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override;

@ -13,19 +13,22 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "collisiontype.hpp" #include "collisiontype.hpp"
#include "memory"
#include "mtphysics.hpp" #include "mtphysics.hpp"
#include "projectile.hpp" #include "projectile.hpp"
namespace MWPhysics namespace MWPhysics
{ {
Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem)
: mActive(true) : mCanCrossWaterSurface(canCrossWaterSurface)
, mCrossedWaterSurface(false)
, mActive(true)
, mCaster(caster) , mCaster(caster)
, mWaterHitPosition(std::nullopt)
, mPhysics(physicssystem) , mPhysics(physicssystem)
, mTaskScheduler(scheduler) , mTaskScheduler(scheduler)
, mProjectileId(projectileId)
{ {
mShape.reset(new btSphereShape(1.f)); mShape = std::make_unique<btSphereShape>(radius);
mConvexShape = static_cast<btConvexShape*>(mShape.get()); mConvexShape = static_cast<btConvexShape*>(mShape.get());
mCollisionObject = std::make_unique<btCollisionObject>(); mCollisionObject = std::make_unique<btCollisionObject>();
@ -67,6 +70,17 @@ void Projectile::setPosition(const osg::Vec3f &position)
mTransformUpdatePending = true; 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) void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal)
{ {
if (!mActive.load(std::memory_order_acquire)) 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); mActive.store(false, std::memory_order_release);
} }
void Projectile::activate()
{
assert(!mActive);
mActive.store(true, std::memory_order_release);
}
MWWorld::Ptr Projectile::getCaster() const MWWorld::Ptr Projectile::getCaster() const
{ {
std::scoped_lock lock(mMutex); std::scoped_lock lock(mMutex);
@ -108,8 +116,9 @@ bool Projectile::isValidTarget(const MWWorld::Ptr& target) const
if (mCaster == target) if (mCaster == target)
return false; return false;
if (!mValidTargets.empty()) if (target.isEmpty() || mValidTargets.empty())
{ return true;
bool validTarget = false; bool validTarget = false;
for (const auto& targetActor : mValidTargets) for (const auto& targetActor : mValidTargets)
{ {
@ -119,10 +128,20 @@ bool Projectile::isValidTarget(const MWWorld::Ptr& target) const
break; break;
} }
} }
return validTarget; return validTarget;
} }
return true;
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 <atomic>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <optional>
#include "components/misc/convert.hpp" #include "components/misc/convert.hpp"
@ -32,7 +33,7 @@ namespace MWPhysics
class Projectile final : public PtrHolder class Projectile final : public PtrHolder
{ {
public: 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; ~Projectile() override;
btConvexShape* getConvexShape() const { return mConvexShape; } btConvexShape* getConvexShape() const { return mConvexShape; }
@ -40,17 +41,13 @@ namespace MWPhysics
void commitPositionChange(); void commitPositionChange();
void setPosition(const osg::Vec3f& position); void setPosition(const osg::Vec3f& position);
osg::Vec3f getPosition() const;
btCollisionObject* getCollisionObject() const btCollisionObject* getCollisionObject() const
{ {
return mCollisionObject.get(); return mCollisionObject.get();
} }
int getProjectileId() const
{
return mProjectileId;
}
bool isActive() const bool isActive() const
{ {
return mActive.load(std::memory_order_acquire); return mActive.load(std::memory_order_acquire);
@ -65,18 +62,16 @@ namespace MWPhysics
MWWorld::Ptr getCaster() const; MWWorld::Ptr getCaster() const;
void setCaster(MWWorld::Ptr caster); void setCaster(MWWorld::Ptr caster);
osg::Vec3f getHitPos() const bool canTraverseWater() const;
{
assert(!mActive);
return Misc::Convert::toOsg(mHitPosition);
}
void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal); void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal);
void activate();
void setValidTargets(const std::vector<MWWorld::Ptr>& targets); void setValidTargets(const std::vector<MWWorld::Ptr>& targets);
bool isValidTarget(const MWWorld::Ptr& target) const; bool isValidTarget(const MWWorld::Ptr& target) const;
std::optional<btVector3> getWaterHitPosition();
void setWaterHitPosition(btVector3 pos);
private: private:
std::unique_ptr<btCollisionShape> mShape; std::unique_ptr<btCollisionShape> mShape;
@ -85,9 +80,12 @@ namespace MWPhysics
std::unique_ptr<btCollisionObject> mCollisionObject; std::unique_ptr<btCollisionObject> mCollisionObject;
btTransform mLocalTransform; btTransform mLocalTransform;
bool mTransformUpdatePending; bool mTransformUpdatePending;
bool mCanCrossWaterSurface;
bool mCrossedWaterSurface;
std::atomic<bool> mActive; std::atomic<bool> mActive;
MWWorld::Ptr mCaster; MWWorld::Ptr mCaster;
MWWorld::Ptr mHitTarget; MWWorld::Ptr mHitTarget;
std::optional<btVector3> mWaterHitPosition;
btVector3 mHitPosition; btVector3 mHitPosition;
btVector3 mHitNormal; btVector3 mHitNormal;
@ -95,15 +93,11 @@ namespace MWPhysics
mutable std::mutex mMutex; mutable std::mutex mMutex;
osg::Vec3f mPosition;
PhysicsSystem *mPhysics; PhysicsSystem *mPhysics;
PhysicsTaskScheduler *mTaskScheduler; PhysicsTaskScheduler *mTaskScheduler;
Projectile(const Projectile&); Projectile(const Projectile&);
Projectile& operator=(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. /// @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(), 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>(), 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; virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0;

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

@ -3,6 +3,7 @@
#include <iomanip> #include <iomanip>
#include <memory> #include <memory>
#include <optional>
#include <osg/PositionAttitudeTransform> #include <osg/PositionAttitudeTransform>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
@ -43,6 +44,7 @@
#include "../mwsound/sound.hpp" #include "../mwsound/sound.hpp"
#include "../mwphysics/collisiontype.hpp"
#include "../mwphysics/physicssystem.hpp" #include "../mwphysics/physicssystem.hpp"
#include "../mwphysics/projectile.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) bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture)
{ {
state.mNode = new osg::PositionAttitudeTransform; state.mNode = new osg::PositionAttitudeTransform;
@ -251,6 +253,7 @@ namespace MWWorld
state.mNode->accept(assignVisitor); state.mNode->accept(assignVisitor);
MWRender::overrideFirstRootTexture(texture, mResourceSystem, projectile); MWRender::overrideFirstRootTexture(texture, mResourceSystem, projectile);
return projectile->getBound().radius();
} }
void ProjectileManager::update(State& state, float duration) void ProjectileManager::update(State& state, float duration)
@ -305,7 +308,7 @@ namespace MWWorld
osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); 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(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
for (const std::string &soundid : state.mSoundIds) for (const std::string &soundid : state.mSoundIds)
@ -316,7 +319,7 @@ namespace MWWorld
state.mSounds.push_back(sound); state.mSounds.push_back(sound);
} }
state.mProjectileId = mPhysics->addProjectile(caster, pos); state.mProjectileId = mPhysics->addProjectile(caster, pos, radius, false);
state.mToDelete = false; state.mToDelete = false;
mMagicBolts.push_back(state); mMagicBolts.push_back(state);
} }
@ -340,7 +343,7 @@ namespace MWWorld
if (!ptr.getClass().getEnchantment(ptr).empty()) if (!ptr.getClass().getEnchantment(ptr).empty())
SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); 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; state.mToDelete = false;
mProjectiles.push_back(state); mProjectiles.push_back(state);
} }
@ -407,15 +410,7 @@ namespace MWWorld
float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed; float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed;
osg::Vec3f direction = orient * osg::Vec3f(0,1,0); osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
direction.normalize(); direction.normalize();
osg::Vec3f pos(magicBoltState.mNode->getPosition()); osg::Vec3f newPos = projectile->getPosition() + direction * duration * speed;
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);
update(magicBoltState, duration); update(magicBoltState, duration);
@ -425,41 +420,7 @@ namespace MWWorld
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
projectile->setValidTargets(targetActors); projectile->setValidTargets(targetActors);
// Check for impact mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos);
// 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);
}
} }
} }
@ -467,9 +428,6 @@ namespace MWWorld
{ {
for (auto& projectileState : mProjectiles) for (auto& projectileState : mProjectiles)
{ {
if (projectileState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
if (!projectile->isActive()) if (!projectile->isActive())
continue; continue;
@ -477,8 +435,7 @@ namespace MWWorld
// simulating aerodynamics at all // simulating aerodynamics at all
projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration;
osg::Vec3f pos(projectileState.mNode->getPosition()); osg::Vec3f newPos = projectile->getPosition() + projectileState.mVelocity * duration;
osg::Vec3f newPos = pos + projectileState.mVelocity * duration;
// rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction. // rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction.
if (!projectileState.mThrown) if (!projectileState.mThrown)
@ -488,10 +445,6 @@ namespace MWWorld
projectileState.mNode->setAttitude(orient); projectileState.mNode->setAttitude(orient);
} }
projectileState.mNode->setPosition(newPos);
mPhysics->updateProjectile(projectileState.mProjectileId, newPos);
update(projectileState, duration); update(projectileState, duration);
MWWorld::Ptr caster = projectileState.getCaster(); MWWorld::Ptr caster = projectileState.getCaster();
@ -502,36 +455,7 @@ namespace MWWorld
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
projectile->setValidTargets(targetActors); projectile->setValidTargets(targetActors);
// Check for impact mPhysics->updateProjectile(projectileState.mProjectileId, newPos);
// 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);
}
} }
} }
@ -543,17 +467,19 @@ namespace MWWorld
continue; continue;
auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); 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()) if (projectile->isActive())
continue; continue;
const auto target = projectile->getTarget(); const auto target = projectile->getTarget();
const auto pos = projectile->getHitPos(); auto caster = projectileState.getCaster();
MWWorld::Ptr caster = projectileState.getCaster();
assert(target != caster); assert(target != caster);
if (!projectile->isValidTarget(target))
{
projectile->activate();
continue;
}
if (caster.isEmpty()) if (caster.isEmpty())
caster = target; caster = target;
@ -569,9 +495,8 @@ namespace MWWorld
bow = *invIt; bow = *invIt;
} }
projectileState.mHitPosition = pos;
cleanupProjectile(projectileState);
MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength);
cleanupProjectile(projectileState);
} }
for (auto& magicBoltState : mMagicBolts) for (auto& magicBoltState : mMagicBolts)
{ {
@ -579,20 +504,18 @@ namespace MWWorld
continue; continue;
auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId); 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()) if (projectile->isActive())
continue; continue;
const auto target = projectile->getTarget(); const auto target = projectile->getTarget();
const auto pos = projectile->getHitPos(); const auto caster = magicBoltState.getCaster();
MWWorld::Ptr caster = magicBoltState.getCaster();
assert(target != caster); assert(target != caster);
if (!projectile->isValidTarget(target))
{
projectile->activate();
continue;
}
magicBoltState.mHitPosition = pos;
cleanupMagicBolt(magicBoltState);
MWMechanics::CastSpell cast(caster, target); MWMechanics::CastSpell cast(caster, target);
cast.mHitPosition = pos; cast.mHitPosition = pos;
@ -602,6 +525,7 @@ namespace MWWorld
cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); 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); 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.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }),
mProjectiles.end()); mProjectiles.end());
@ -702,7 +626,7 @@ namespace MWWorld
int weaponType = ptr.get<ESM::Weapon>()->mBase->mData.mType; int weaponType = ptr.get<ESM::Weapon>()->mBase->mData.mType;
state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; 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(...) catch(...)
{ {
@ -747,7 +671,6 @@ namespace MWWorld
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0));
MWWorld::Ptr ptr = ref.getPtr(); MWWorld::Ptr ptr = ref.getPtr();
model = ptr.getClass().getModel(ptr); model = ptr.getClass().getModel(ptr);
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition));
} }
catch(...) catch(...)
{ {
@ -755,7 +678,8 @@ namespace MWWorld
} }
osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); 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(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
for (const std::string &soundid : state.mSoundIds) for (const std::string &soundid : state.mSoundIds)

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

Loading…
Cancel
Save