1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-22 04:53:52 +00:00

Modify projectile collision to work with async physics

This commit is contained in:
fredzio 2020-10-23 20:27:07 +02:00 committed by Frederic Chardon
parent dc7b48e92e
commit 66fe3b0d38
12 changed files with 245 additions and 209 deletions

View file

@ -610,8 +610,6 @@ namespace MWBase
virtual bool isPlayerInJail() const = 0; virtual bool isPlayerInJail() const = 0;
virtual void manualProjectileHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos) = 0;
virtual void rest(double hours) = 0; virtual void rest(double hours) = 0;
virtual void rechargeItems(double duration, bool activeOnly) = 0; virtual void rechargeItems(double duration, bool activeOnly) = 0;

View file

@ -26,12 +26,13 @@ namespace MWPhysics
if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile) if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile)
{ {
Projectile* projectileHolder = static_cast<Projectile*>(convexResult.m_hitCollisionObject->getUserPointer()); Projectile* projectileHolder = static_cast<Projectile*>(convexResult.m_hitCollisionObject->getUserPointer());
int projectileId = projectileHolder->getProjectileId(); if (!projectileHolder->isActive())
return btScalar(1);
PtrHolder* targetHolder = static_cast<PtrHolder*>(mMe->getUserPointer()); PtrHolder* targetHolder = static_cast<PtrHolder*>(mMe->getUserPointer());
const MWWorld::Ptr target = targetHolder->getPtr(); const MWWorld::Ptr target = targetHolder->getPtr();
osg::Vec3f pos = Misc::Convert::makeOsgVec3f(convexResult.m_hitPointLocal); osg::Vec3f pos = Misc::Convert::makeOsgVec3f(convexResult.m_hitPointLocal);
MWBase::Environment::get().getWorld()->manualProjectileHit(projectileId, target, pos); projectileHolder->hit(target, pos);
return btScalar(1); return btScalar(1);
} }

View file

@ -17,6 +17,7 @@
#include "mtphysics.hpp" #include "mtphysics.hpp"
#include "object.hpp" #include "object.hpp"
#include "physicssystem.hpp" #include "physicssystem.hpp"
#include "projectile.hpp"
namespace namespace
{ {
@ -455,6 +456,11 @@ namespace MWPhysics
object->commitPositionChange(); object->commitPositionChange();
mCollisionWorld->updateSingleAabb(object->getCollisionObject()); mCollisionWorld->updateSingleAabb(object->getCollisionObject());
} }
else if (const auto projectile = std::dynamic_pointer_cast<Projectile>(p))
{
projectile->commitPositionChange();
mCollisionWorld->updateSingleAabb(projectile->getCollisionObject());
}
}; };
} }

View file

@ -115,11 +115,7 @@ namespace MWPhysics
mObjects.clear(); mObjects.clear();
mActors.clear(); mActors.clear();
mProjectiles.clear();
for (ProjectileMap::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it)
{
delete it->second;
}
} }
void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue) void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue)
@ -255,7 +251,7 @@ namespace MWPhysics
return 0.f; return 0.f;
} }
PhysicsSystem::RayResult 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, int projId) const
{ {
if (from == to) if (from == to)
{ {
@ -305,17 +301,7 @@ namespace MWPhysics
result.mHitPos = Misc::Convert::toOsg(resultCallback.m_hitPointWorld); result.mHitPos = Misc::Convert::toOsg(resultCallback.m_hitPointWorld);
result.mHitNormal = Misc::Convert::toOsg(resultCallback.m_hitNormalWorld); result.mHitNormal = Misc::Convert::toOsg(resultCallback.m_hitNormalWorld);
if (PtrHolder* ptrHolder = static_cast<PtrHolder*>(resultCallback.m_collisionObject->getUserPointer())) if (PtrHolder* ptrHolder = static_cast<PtrHolder*>(resultCallback.m_collisionObject->getUserPointer()))
{
result.mHitObject = ptrHolder->getPtr(); result.mHitObject = ptrHolder->getPtr();
if (group == CollisionType_Projectile)
{
Projectile* projectile = static_cast<Projectile*>(ptrHolder);
result.mProjectileId = projectile->getProjectileId();
}
else
result.mProjectileId = -1;
}
} }
return result; return result;
} }
@ -523,13 +509,7 @@ namespace MWPhysics
{ {
ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId); ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId);
if (foundProjectile != mProjectiles.end()) if (foundProjectile != mProjectiles.end())
{
delete foundProjectile->second;
mProjectiles.erase(foundProjectile); mProjectiles.erase(foundProjectile);
if (projectileId == mProjectileId)
mProjectileId--;
}
} }
void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
@ -583,6 +563,14 @@ namespace MWPhysics
return nullptr; return nullptr;
} }
Projectile* PhysicsSystem::getProjectile(int projectileId) const
{
ProjectileMap::const_iterator found = mProjectiles.find(projectileId);
if (found != mProjectiles.end())
return found->second.get();
return nullptr;
}
void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr) void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr)
{ {
ObjectMap::iterator found = mObjects.find(ptr); ObjectMap::iterator found = mObjects.find(ptr);
@ -608,7 +596,7 @@ namespace MWPhysics
if (foundProjectile != mProjectiles.end()) if (foundProjectile != mProjectiles.end())
{ {
foundProjectile->second->setPosition(position); foundProjectile->second->setPosition(position);
mCollisionWorld->updateSingleAabb(foundProjectile->second->getCollisionObject()); mTaskScheduler->updateSingleAabb(foundProjectile->second);
return; return;
} }
} }
@ -676,8 +664,8 @@ namespace MWPhysics
int PhysicsSystem::addProjectile (const osg::Vec3f& position) int PhysicsSystem::addProjectile (const osg::Vec3f& position)
{ {
mProjectileId++; mProjectileId++;
Projectile* projectile = new Projectile(mProjectileId, position, mCollisionWorld); auto projectile = std::make_shared<Projectile>(mProjectileId, position, mTaskScheduler.get());
mProjectiles.insert(std::make_pair(mProjectileId, projectile)); mProjectiles.emplace(mProjectileId, std::move(projectile));
return mProjectileId; return mProjectileId;
} }

View file

@ -139,6 +139,8 @@ namespace MWPhysics
const Object* getObject(const MWWorld::ConstPtr& ptr) const; const Object* getObject(const MWWorld::ConstPtr& ptr) const;
Projectile* getProjectile(int projectileId) const;
// Object or Actor // Object or Actor
void remove (const MWWorld::Ptr& ptr); void remove (const MWWorld::Ptr& ptr);
@ -173,15 +175,6 @@ namespace MWPhysics
/// \note Only Actor targets are supported at the moment. /// \note Only Actor targets are supported at the moment.
float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const override; float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const override;
struct RayResult
{
bool mHit;
osg::Vec3f mHitPos;
osg::Vec3f mHitNormal;
MWWorld::Ptr mHitObject;
int mProjectileId;
};
/// @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>(),
@ -282,7 +275,7 @@ namespace MWPhysics
ActorMap mActors; ActorMap mActors;
using ProjectileMap = std::map<int, Projectile *>; using ProjectileMap = std::map<int, std::shared_ptr<Projectile>>;
ProjectileMap mProjectiles; ProjectileMap mProjectiles;
using HeightFieldMap = std::map<std::pair<int, int>, HeightField *>; using HeightFieldMap = std::map<std::pair<int, int>, HeightField *>;
@ -295,7 +288,7 @@ namespace MWPhysics
float mTimeAccum; float mTimeAccum;
int mProjectileId; unsigned int mProjectileId;
float mWaterHeight; float mWaterHeight;
bool mWaterEnabled; bool mWaterEnabled;

View file

@ -1,24 +1,28 @@
#include "projectile.hpp" #include <memory>
#include <BulletCollision/CollisionShapes/btSphereShape.h> #include <BulletCollision/CollisionShapes/btSphereShape.h>
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h> #include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include <components/sceneutil/positionattitudetransform.hpp> #include <LinearMath/btVector3.h>
#include <components/resource/bulletshape.hpp>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/misc/convert.hpp> #include <components/misc/convert.hpp>
#include <components/resource/bulletshape.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "collisiontype.hpp" #include "collisiontype.hpp"
#include "mtphysics.hpp"
#include "projectile.hpp"
namespace MWPhysics namespace MWPhysics
{ {
Projectile::Projectile(int projectileId, const osg::Vec3f& position, btCollisionWorld* world) Projectile::Projectile(int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler)
: mCollisionWorld(world) : mActive(true)
, mTaskScheduler(scheduler)
, mProjectileId(projectileId)
{ {
mProjectileId = projectileId;
mShape.reset(new btSphereShape(1.f)); mShape.reset(new btSphereShape(1.f));
mConvexShape = static_cast<btConvexShape*>(mShape.get()); mConvexShape = static_cast<btConvexShape*>(mShape.get());
@ -32,33 +36,47 @@ Projectile::Projectile(int projectileId, const osg::Vec3f& position, btCollision
const int collisionMask = CollisionType_World | CollisionType_HeightMap | const int collisionMask = CollisionType_World | CollisionType_HeightMap |
CollisionType_Actor | CollisionType_Door | CollisionType_Water; CollisionType_Actor | CollisionType_Door | CollisionType_Water;
mCollisionWorld->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask); mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask);
commitPositionChange();
} }
Projectile::~Projectile() Projectile::~Projectile()
{ {
if (mCollisionObject.get()) if (mCollisionObject)
mCollisionWorld->removeCollisionObject(mCollisionObject.get()); mTaskScheduler->removeCollisionObject(mCollisionObject.get());
} }
void Projectile::updateCollisionObjectPosition() void Projectile::commitPositionChange()
{ {
btTransform tr = mCollisionObject->getWorldTransform(); std::unique_lock<std::mutex> lock(mPositionMutex);
// osg::Vec3f scaledTranslation = mRotation * mMeshTranslation; if (mTransformUpdatePending)
// osg::Vec3f newPosition = scaledTranslation + mPosition; {
tr.setOrigin(Misc::Convert::toBullet(mPosition)); mCollisionObject->setWorldTransform(mLocalTransform);
mCollisionObject->setWorldTransform(tr); mTransformUpdatePending = false;
}
} }
void Projectile::setPosition(const osg::Vec3f &position) void Projectile::setPosition(const osg::Vec3f &position)
{ {
mPosition = position; std::unique_lock<std::mutex> lock(mPositionMutex);
updateCollisionObjectPosition(); mLocalTransform.setOrigin(Misc::Convert::toBullet(position));
mTransformUpdatePending = true;
} }
osg::Vec3f Projectile::getPosition() const void Projectile::hit(MWWorld::Ptr target, osg::Vec3f pos)
{ {
return mPosition; if (!mActive.load(std::memory_order_acquire))
return;
std::unique_lock<std::mutex> lock(mPositionMutex);
mHitTarget = target;
mHitPosition = pos;
mActive.store(false, std::memory_order_release);
} }
void Projectile::activate()
{
assert(!mActive);
mActive.store(true, std::memory_order_release);
}
} }

View file

@ -1,18 +1,23 @@
#ifndef OPENMW_MWPHYSICS_PROJECTILE_H #ifndef OPENMW_MWPHYSICS_PROJECTILE_H
#define OPENMW_MWPHYSICS_PROJECTILE_H #define OPENMW_MWPHYSICS_PROJECTILE_H
#include <atomic>
#include <memory> #include <memory>
#include <mutex>
#include <components/misc/convert.hpp>
#include "ptrholder.hpp" #include "ptrholder.hpp"
#include <osg/Vec3f>
#include <osg/Quat>
#include <osg/ref_ptr>
class btCollisionWorld;
class btCollisionShape;
class btCollisionObject; class btCollisionObject;
class btCollisionShape;
class btConvexShape; class btConvexShape;
class btVector3;
namespace osg
{
class Vec3f;
}
namespace Resource namespace Resource
{ {
@ -21,20 +26,21 @@ namespace Resource
namespace MWPhysics namespace MWPhysics
{ {
class Projectile : public PtrHolder class PhysicsTaskScheduler;
class PhysicsSystem;
class Projectile final : public PtrHolder
{ {
public: public:
Projectile(const int projectileId, const osg::Vec3f& position, btCollisionWorld* world); Projectile(const int projectileId, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler);
~Projectile(); ~Projectile() override;
btConvexShape* getConvexShape() const { return mConvexShape; } btConvexShape* getConvexShape() const { return mConvexShape; }
void updateCollisionObjectPosition(); 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();
@ -45,16 +51,43 @@ namespace MWPhysics
return mProjectileId; return mProjectileId;
} }
bool isActive() const
{
return mActive.load(std::memory_order_acquire);
}
MWWorld::Ptr getTarget() const
{
assert(!mActive);
return mHitTarget;
}
osg::Vec3f getHitPos() const
{
assert(!mActive);
return Misc::Convert::toOsg(mHitPosition);
}
void hit(MWWorld::Ptr target, osg::Vec3f pos);
void activate();
private: private:
std::unique_ptr<btCollisionShape> mShape; std::unique_ptr<btCollisionShape> mShape;
btConvexShape* mConvexShape; btConvexShape* mConvexShape;
std::unique_ptr<btCollisionObject> mCollisionObject; std::unique_ptr<btCollisionObject> mCollisionObject;
btTransform mLocalTransform;
bool mTransformUpdatePending;
std::atomic<bool> mActive;
MWWorld::Ptr mHitTarget;
osg::Vec3f mHitPosition;
mutable std::mutex mPositionMutex;
osg::Vec3f mPosition; osg::Vec3f mPosition;
btCollisionWorld* mCollisionWorld; PhysicsTaskScheduler *mTaskScheduler;
Projectile(const Projectile&); Projectile(const Projectile&);
Projectile& operator=(const Projectile&); Projectile& operator=(const Projectile&);

View file

@ -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) const = 0; int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) 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;

View file

@ -2,6 +2,7 @@
#include <iomanip> #include <iomanip>
#include <memory>
#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/physicssystem.hpp" #include "../mwphysics/physicssystem.hpp"
#include "../mwphysics/projectile.hpp"
namespace namespace
{ {
@ -284,7 +286,7 @@ namespace MWWorld
else else
state.mActorId = -1; state.mActorId = -1;
std::string texture = ""; std::string texture;
state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId); state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId);
@ -315,6 +317,7 @@ namespace MWWorld
} }
state.mProjectileId = mPhysics->addProjectile(pos); state.mProjectileId = mPhysics->addProjectile(pos);
state.mToDelete = false;
mMagicBolts.push_back(state); mMagicBolts.push_back(state);
} }
@ -338,6 +341,7 @@ namespace MWWorld
SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr));
state.mProjectileId = mPhysics->addProjectile(pos); state.mProjectileId = mPhysics->addProjectile(pos);
state.mToDelete = false;
mProjectiles.push_back(state); mProjectiles.push_back(state);
} }
@ -362,65 +366,58 @@ namespace MWWorld
return (state.mNode->getPosition() - playerPos).length2() >= farawayThreshold*farawayThreshold; return (state.mNode->getPosition() - playerPos).length2() >= farawayThreshold*farawayThreshold;
}; };
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end();) for (auto& projectileState : mProjectiles)
{ {
if (isCleanable(*it)) if (isCleanable(projectileState))
{ cleanupProjectile(projectileState);
cleanupProjectile(*it);
it = mProjectiles.erase(it);
}
else
++it;
} }
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) for (auto& magicBoltState : mMagicBolts)
{ {
if (isCleanable(*it)) if (isCleanable(magicBoltState))
{ cleanupMagicBolt(magicBoltState);
cleanupMagicBolt(*it);
it = mMagicBolts.erase(it);
}
else
++it;
} }
} }
} }
void ProjectileManager::moveMagicBolts(float duration) void ProjectileManager::moveMagicBolts(float duration)
{ {
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) for (auto& magicBoltState : mMagicBolts)
{ {
if (magicBoltState.mToDelete)
continue;
const auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
if (!projectile->isActive())
continue;
// If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame. // If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame.
MWWorld::Ptr caster = it->getCaster(); MWWorld::Ptr caster = magicBoltState.getCaster();
if (!caster.isEmpty() && caster.getClass().isActor()) if (!caster.isEmpty() && caster.getClass().isActor())
{ {
if (caster.getRefData().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead()) if (caster.getRefData().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead())
{ {
cleanupMagicBolt(*it); cleanupMagicBolt(magicBoltState);
it = mMagicBolts.erase(it);
continue; continue;
} }
} }
osg::Quat orient = it->mNode->getAttitude(); osg::Quat orient = magicBoltState.mNode->getAttitude();
static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>() static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("fTargetSpellMaxSpeed")->mValue.getFloat(); .find("fTargetSpellMaxSpeed")->mValue.getFloat();
float speed = fTargetSpellMaxSpeed * it->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(it->mNode->getPosition()); osg::Vec3f pos(magicBoltState.mNode->getPosition());
osg::Vec3f newPos = pos + direction * duration * speed; osg::Vec3f newPos = pos + direction * duration * speed;
for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++) for (const auto& sound : magicBoltState.mSounds)
{ sound->setPosition(newPos);
it->mSounds.at(soundIter)->setPosition(newPos);
}
it->mNode->setPosition(newPos); magicBoltState.mNode->setPosition(newPos);
mPhysics->updateProjectile(it->mProjectileId, newPos); mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos);
update(*it, duration); update(magicBoltState, duration);
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors; std::vector<MWWorld::Ptr> targetActors;
@ -429,7 +426,7 @@ namespace MWWorld
// Check for impact // Check for impact
// TODO: use a proper btRigidBody / btGhostObject? // TODO: use a proper btRigidBody / btGhostObject?
MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, it->mProjectileId); const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, magicBoltState.mProjectileId);
bool hit = false; bool hit = false;
if (result.mHit) if (result.mHit)
@ -443,10 +440,10 @@ namespace MWWorld
{ {
MWMechanics::CastSpell cast(caster, result.mHitObject); MWMechanics::CastSpell cast(caster, result.mHitObject);
cast.mHitPosition = pos; cast.mHitPosition = pos;
cast.mId = it->mSpellId; cast.mId = magicBoltState.mSpellId;
cast.mSourceName = it->mSourceName; cast.mSourceName = magicBoltState.mSourceName;
cast.mStack = false; cast.mStack = false;
cast.inflict(result.mHitObject, caster, it->mEffects, ESM::RT_Target, false, true); cast.inflict(result.mHitObject, caster, magicBoltState.mEffects, ESM::RT_Target, false, true);
mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal)); mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal));
} }
} }
@ -457,45 +454,46 @@ namespace MWWorld
if (hit) if (hit)
{ {
MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, result.mHitObject, MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, result.mHitObject,
ESM::RT_Target, it->mSpellId, it->mSourceName); ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName);
cleanupMagicBolt(*it); cleanupMagicBolt(magicBoltState);
it = mMagicBolts.erase(it);
continue;
} }
else
++it;
} }
} }
void ProjectileManager::moveProjectiles(float duration) void ProjectileManager::moveProjectiles(float duration)
{ {
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end();) for (auto& projectileState : mProjectiles)
{ {
if (projectileState.mToDelete)
continue;
const auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
if (!projectile->isActive())
continue;
// gravity constant - must be way lower than the gravity affecting actors, since we're not // gravity constant - must be way lower than the gravity affecting actors, since we're not
// simulating aerodynamics at all // simulating aerodynamics at all
it->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(it->mNode->getPosition()); osg::Vec3f pos(projectileState.mNode->getPosition());
osg::Vec3f newPos = pos + it->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 (!it->mThrown) if (!projectileState.mThrown)
{ {
osg::Quat orient; osg::Quat orient;
orient.makeRotate(osg::Vec3f(0,1,0), it->mVelocity); orient.makeRotate(osg::Vec3f(0,1,0), projectileState.mVelocity);
it->mNode->setAttitude(orient); projectileState.mNode->setAttitude(orient);
} }
it->mNode->setPosition(newPos); projectileState.mNode->setPosition(newPos);
mPhysics->updateProjectile(it->mProjectileId, newPos); mPhysics->updateProjectile(projectileState.mProjectileId, newPos);
update(*it, duration); update(projectileState, duration);
MWWorld::Ptr caster = it->getCaster(); MWWorld::Ptr caster = projectileState.getCaster();
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors; std::vector<MWWorld::Ptr> targetActors;
@ -504,103 +502,107 @@ namespace MWWorld
// Check for impact // Check for impact
// TODO: use a proper btRigidBody / btGhostObject? // TODO: use a proper btRigidBody / btGhostObject?
MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, it->mProjectileId); 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); bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos);
if (result.mHit || underwater) if (result.mHit || underwater)
{ {
// Try to get a Ptr to the bow that was used. It might no longer exist. // Try to get a Ptr to the bow that was used. It might no longer exist.
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow); MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow);
MWWorld::Ptr bow = projectileRef.getPtr(); MWWorld::Ptr bow = projectileRef.getPtr();
if (!caster.isEmpty() && it->mIdArrow != it->mBowId) if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId)
{ {
MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster);
MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId))
bow = *invIt; bow = *invIt;
} }
if (caster.isEmpty()) if (caster.isEmpty())
caster = result.mHitObject; caster = result.mHitObject;
MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, it->mAttackStrength); 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)); mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal));
if (underwater) if (underwater)
mRendering->emitWaterRipple(newPos); mRendering->emitWaterRipple(newPos);
cleanupProjectile(*it); cleanupProjectile(projectileState);
it = mProjectiles.erase(it); }
}
}
void ProjectileManager::processHits()
{
for (auto& projectileState : mProjectiles)
{
if (projectileState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
if (projectile->isActive())
continue;
const auto target = projectile->getTarget();
const auto pos = projectile->getHitPos();
MWWorld::Ptr caster = projectileState.getCaster();
if (caster == target || !isValidTarget(caster, target))
{
projectile->activate();
continue; continue;
} }
++it;
}
}
void ProjectileManager::manualHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos)
{
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it)
{
if (it->mProjectileId == projectileId)
{
MWWorld::Ptr caster = it->getCaster();
if (caster == target)
return;
if (!isValidTarget(caster, target))
return;
if (caster.isEmpty()) if (caster.isEmpty())
caster = target; caster = target;
// Try to get a Ptr to the bow that was used. It might no longer exist. // Try to get a Ptr to the bow that was used. It might no longer exist.
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow); MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow);
MWWorld::Ptr bow = projectileRef.getPtr(); MWWorld::Ptr bow = projectileRef.getPtr();
if (!caster.isEmpty() && it->mIdArrow != it->mBowId) if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId)
{ {
MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster);
MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId))
bow = *invIt; bow = *invIt;
} }
it->mHitPosition = pos; projectileState.mHitPosition = pos;
cleanupProjectile(*it); cleanupProjectile(projectileState);
MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, it->mAttackStrength); MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength);
mProjectiles.erase(it);
return;
} }
for (auto& magicBoltState : mMagicBolts)
{
if (magicBoltState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
if (projectile->isActive())
continue;
const auto target = projectile->getTarget();
const auto pos = projectile->getHitPos();
MWWorld::Ptr caster = magicBoltState.getCaster();
if (caster == target || !isValidTarget(caster, target))
{
projectile->activate();
continue;
} }
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it)
{
if (it->mProjectileId == projectileId)
{
MWWorld::Ptr caster = it->getCaster();
if (caster == target)
return;
if (!isValidTarget(caster, target)) magicBoltState.mHitPosition = pos;
return; cleanupMagicBolt(magicBoltState);
it->mHitPosition = pos;
cleanupMagicBolt(*it);
MWMechanics::CastSpell cast(caster, target); MWMechanics::CastSpell cast(caster, target);
cast.mHitPosition = pos; cast.mHitPosition = pos;
cast.mId = it->mSpellId; cast.mId = magicBoltState.mSpellId;
cast.mSourceName = it->mSourceName; cast.mSourceName = magicBoltState.mSourceName;
cast.mStack = false; cast.mStack = false;
cast.inflict(target, caster, it->mEffects, ESM::RT_Target, false, true); cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true);
MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, target, ESM::RT_Target, it->mSpellId, it->mSourceName); MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName);
mMagicBolts.erase(it);
return;
}
} }
mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }),
mProjectiles.end());
mMagicBolts.erase(std::remove_if(mMagicBolts.begin(), mMagicBolts.end(), [](const State& state) { return state.mToDelete; }),
mMagicBolts.end());
} }
bool ProjectileManager::isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target) bool ProjectileManager::isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
@ -633,12 +635,14 @@ namespace MWWorld
{ {
mParent->removeChild(state.mNode); mParent->removeChild(state.mNode);
mPhysics->removeProjectile(state.mProjectileId); mPhysics->removeProjectile(state.mProjectileId);
state.mToDelete = true;
} }
void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state) void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state)
{ {
mParent->removeChild(state.mNode); mParent->removeChild(state.mNode);
mPhysics->removeProjectile(state.mProjectileId); mPhysics->removeProjectile(state.mProjectileId);
state.mToDelete = true;
for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++) for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++)
{ {
MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter)); MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter));
@ -647,15 +651,12 @@ namespace MWWorld
void ProjectileManager::clear() void ProjectileManager::clear()
{ {
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) for (auto& mProjectile : mProjectiles)
{ cleanupProjectile(mProjectile);
cleanupProjectile(*it);
}
mProjectiles.clear(); mProjectiles.clear();
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it)
{ for (auto& mMagicBolt : mMagicBolts)
cleanupMagicBolt(*it); cleanupMagicBolt(mMagicBolt);
}
mMagicBolts.clear(); mMagicBolts.clear();
} }
@ -712,6 +713,7 @@ namespace MWWorld
state.mVelocity = esm.mVelocity; state.mVelocity = esm.mVelocity;
state.mIdArrow = esm.mId; state.mIdArrow = esm.mId;
state.mAttackStrength = esm.mAttackStrength; state.mAttackStrength = esm.mAttackStrength;
state.mToDelete = false;
std::string model; std::string model;
try try
@ -734,7 +736,7 @@ namespace MWWorld
mProjectiles.push_back(state); mProjectiles.push_back(state);
return true; return true;
} }
else if (type == ESM::REC_MPRJ) if (type == ESM::REC_MPRJ)
{ {
ESM::MagicBoltState esm; ESM::MagicBoltState esm;
esm.load(reader); esm.load(reader);
@ -743,7 +745,8 @@ namespace MWWorld
state.mIdMagic.push_back(esm.mId); state.mIdMagic.push_back(esm.mId);
state.mSpellId = esm.mSpellId; state.mSpellId = esm.mSpellId;
state.mActorId = esm.mActorId; state.mActorId = esm.mActorId;
std::string texture = ""; state.mToDelete = false;
std::string texture;
try try
{ {

View file

@ -56,7 +56,7 @@ namespace MWWorld
void update(float dt); void update(float dt);
void manualHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos); void processHits();
/// Removes all current projectiles. Should be called when switching to a new worldspace. /// Removes all current projectiles. Should be called when switching to a new worldspace.
void clear(); void clear();
@ -93,6 +93,8 @@ namespace MWWorld
// MW-id of an arrow projectile // MW-id of an arrow projectile
std::string mIdArrow; std::string mIdArrow;
bool mToDelete;
}; };
struct MagicBoltState : public State struct MagicBoltState : public State

View file

@ -843,11 +843,6 @@ namespace MWWorld
} }
} }
void World::manualProjectileHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos)
{
mProjectileManager->manualHit(projectileId, target, pos);
}
void World::disable (const Ptr& reference) void World::disable (const Ptr& reference)
{ {
if (!reference.getRefData().isEnabled()) if (!reference.getRefData().isEnabled())
@ -1498,6 +1493,7 @@ namespace MWWorld
mProjectileManager->update(duration); mProjectileManager->update(duration);
const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats); const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats);
mProjectileManager->processHits();
mDiscardMovements = false; mDiscardMovements = false;
for(const auto& [actor, position]: results) for(const auto& [actor, position]: results)

View file

@ -584,8 +584,6 @@ namespace MWWorld
RestPermitted canRest() const override; RestPermitted canRest() const override;
///< check if the player is allowed to rest ///< check if the player is allowed to rest
void manualProjectileHit(int projectileId, const MWWorld::Ptr& target, const osg::Vec3f& pos);
void rest(double hours) override; void rest(double hours) override;
void rechargeItems(double duration, bool activeOnly) override; void rechargeItems(double duration, bool activeOnly) override;