diff --git a/CHANGELOG.md b/CHANGELOG.md index 47cfd00310..bd74e4e1d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index f96ddb27f8..c01cbe60c7 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -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 diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 81b3a353df..142914c358 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -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().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::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); } } diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/actorconvexcallback.cpp similarity index 92% rename from apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp rename to apps/openmw/mwphysics/actorconvexcallback.cpp index 27e7a390c4..f99b706d74 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/actorconvexcallback.cpp @@ -1,6 +1,6 @@ #include -#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); diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp b/apps/openmw/mwphysics/actorconvexcallback.hpp similarity index 54% rename from apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp rename to apps/openmw/mwphysics/actorconvexcallback.hpp index 538721ad80..1c28ee6cc4 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp +++ b/apps/openmw/mwphysics/actorconvexcallback.hpp @@ -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 @@ -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; diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index c3104f8603..32d97d6c75 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -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 targets, const btVector3& from, const btVector3& to, Projectile* proj) + ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector 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(rayResult.m_collisionObject->getUserPointer()); - mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); - break; - } - case CollisionType_Projectile: - { - auto* target = static_cast(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); } } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index b86427165a..1fa32ef686 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -14,13 +14,13 @@ namespace MWPhysics class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to, Projectile* proj=nullptr); + ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, const btVector3& from, const btVector3& to); btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; + private: const btCollisionObject* mMe; const std::vector mTargets; - Projectile* mProjectile; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 95d1ec70aa..ed0e0c915d 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -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 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 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(mProjectileId, caster, position, mTaskScheduler.get(), this); + auto projectile = std::make_shared(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 diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 4492099743..6f901067a2 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -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 targets = std::vector(), - 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; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 1b9beca5ff..a931219975 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -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(radius); mConvexShape = static_cast(mShape.get()); mCollisionObject = std::make_unique(); @@ -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 Projectile::getWaterHitPosition() +{ + return std::exchange(mWaterHitPosition, std::nullopt); +} + +void Projectile::setWaterHitPosition(btVector3 pos) +{ + if (mCrossedWaterSurface) + return; + mCrossedWaterSurface = true; + mWaterHitPosition = pos; } } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index 5ae963b9a8..fb50eebde8 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #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& targets); bool isValidTarget(const MWWorld::Ptr& target) const; + std::optional getWaterHitPosition(); + void setWaterHitPosition(btVector3 pos); + private: std::unique_ptr mShape; @@ -85,9 +80,12 @@ namespace MWPhysics std::unique_ptr mCollisionObject; btTransform mLocalTransform; bool mTransformUpdatePending; + bool mCanCrossWaterSurface; + bool mCrossedWaterSurface; std::atomic mActive; MWWorld::Ptr mCaster; MWWorld::Ptr mHitTarget; + std::optional 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; }; } diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp new file mode 100644 index 0000000000..0d0ac87202 --- /dev/null +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -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(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(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; + } + +} + diff --git a/apps/openmw/mwphysics/projectileconvexcallback.hpp b/apps/openmw/mwphysics/projectileconvexcallback.hpp new file mode 100644 index 0000000000..c687de36c0 --- /dev/null +++ b/apps/openmw/mwphysics/projectileconvexcallback.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H +#define OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H + +#include + +#include + +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 diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index b176e83304..7c8375cb5a 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -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 targets = std::vector(), - 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; diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index f50b6100a6..049d026e8e 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -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; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 08c9e46627..c87f625053 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -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()->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) diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 2cd570847b..e088dd7011 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -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);