mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-02-01 18:15:32 +00:00
Merge branch 'projectiles_collisions_mk2' into 'master'
Projectiles collisions See merge request OpenMW/openmw!369
This commit is contained in:
commit
8d8a4a890c
17 changed files with 505 additions and 95 deletions
|
@ -8,6 +8,7 @@
|
||||||
Bug #2473: Unable to overstock merchants
|
Bug #2473: Unable to overstock merchants
|
||||||
Bug #2798: Mutable ESM records
|
Bug #2798: Mutable ESM records
|
||||||
Bug #2976 [reopened]: Issues combining settings from the command line and both config files
|
Bug #2976 [reopened]: Issues combining settings from the command line and both config files
|
||||||
|
Bug #3372: Projectiles and magic bolts go through moving targets
|
||||||
Bug #3676: NiParticleColorModifier isn't applied properly
|
Bug #3676: NiParticleColorModifier isn't applied properly
|
||||||
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
|
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
|
||||||
Bug #3789: Crash in visitEffectSources while in battle
|
Bug #3789: Crash in visitEffectSources while in battle
|
||||||
|
|
|
@ -66,13 +66,13 @@ add_openmw_dir (mwworld
|
||||||
cells localscripts customdata inventorystore ptr actionopen actionread actionharvest
|
cells localscripts customdata inventorystore ptr actionopen actionread actionharvest
|
||||||
actionequip timestamp actionalchemy cellstore actionapply actioneat
|
actionequip timestamp actionalchemy cellstore actionapply actioneat
|
||||||
store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor
|
store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor
|
||||||
contentloader esmloader actiontrap cellreflist cellref physicssystem weather projectilemanager
|
contentloader esmloader actiontrap cellreflist cellref weather projectilemanager
|
||||||
cellpreloader datetimemanager
|
cellpreloader datetimemanager
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile
|
||||||
closestnotmeconvexresultcallback raycasting mtphysics
|
closestnotmeconvexresultcallback raycasting mtphysics
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
|
||||||
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
|
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
|
||||||
mCollisionObject->setActivationState(DISABLE_DEACTIVATION);
|
mCollisionObject->setActivationState(DISABLE_DEACTIVATION);
|
||||||
mCollisionObject->setCollisionShape(mShape.get());
|
mCollisionObject->setCollisionShape(mShape.get());
|
||||||
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
|
mCollisionObject->setUserPointer(this);
|
||||||
|
|
||||||
updateRotation();
|
updateRotation();
|
||||||
updateScale();
|
updateScale();
|
||||||
|
|
|
@ -2,6 +2,14 @@
|
||||||
|
|
||||||
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||||
|
|
||||||
|
#include <components/misc/convert.hpp>
|
||||||
|
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
|
||||||
|
#include "collisiontype.hpp"
|
||||||
|
#include "projectile.hpp"
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot)
|
ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot)
|
||||||
|
@ -10,11 +18,26 @@ namespace MWPhysics
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace)
|
btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
|
||||||
{
|
{
|
||||||
if (convexResult.m_hitCollisionObject == mMe)
|
if (convexResult.m_hitCollisionObject == mMe)
|
||||||
return btScalar(1);
|
return btScalar(1);
|
||||||
|
|
||||||
|
if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile)
|
||||||
|
{
|
||||||
|
auto* projectileHolder = static_cast<Projectile*>(convexResult.m_hitCollisionObject->getUserPointer());
|
||||||
|
if (!projectileHolder->isActive())
|
||||||
|
return btScalar(1);
|
||||||
|
auto* targetHolder = static_cast<PtrHolder*>(mMe->getUserPointer());
|
||||||
|
const MWWorld::Ptr target = targetHolder->getPtr();
|
||||||
|
// do nothing if we hit the caster. Sometimes the launching origin is inside of caster collision shape
|
||||||
|
if (projectileHolder->getCaster() != target)
|
||||||
|
{
|
||||||
|
projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal);
|
||||||
|
return btScalar(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
btVector3 hitNormalWorld;
|
btVector3 hitNormalWorld;
|
||||||
if (normalInWorldSpace)
|
if (normalInWorldSpace)
|
||||||
hitNormalWorld = convexResult.m_hitNormalLocal;
|
hitNormalWorld = convexResult.m_hitNormalLocal;
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
#include "closestnotmerayresultcallback.hpp"
|
#include "closestnotmerayresultcallback.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||||
|
|
||||||
#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, const std::vector<const btCollisionObject*>& targets, const btVector3& from, const btVector3& to)
|
ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to, Projectile* proj)
|
||||||
: btCollisionWorld::ClosestRayResultCallback(from, to)
|
: btCollisionWorld::ClosestRayResultCallback(from, to)
|
||||||
, mMe(me), mTargets(targets)
|
, mMe(me), mTargets(std::move(targets)), mProjectile(proj)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,11 +28,27 @@ namespace MWPhysics
|
||||||
{
|
{
|
||||||
if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end()))
|
if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end()))
|
||||||
{
|
{
|
||||||
PtrHolder* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer());
|
auto* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer());
|
||||||
if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor())
|
if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor())
|
||||||
return 1.f;
|
return 1.f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
|
|
||||||
|
btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
|
||||||
|
if (mProjectile)
|
||||||
|
{
|
||||||
|
auto* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer());
|
||||||
|
if (auto* target = dynamic_cast<Actor*>(holder))
|
||||||
|
{
|
||||||
|
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
|
||||||
|
}
|
||||||
|
else if (auto* target = dynamic_cast<Projectile*>(holder))
|
||||||
|
{
|
||||||
|
target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld);
|
||||||
|
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rayResult.m_hitFraction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,18 @@ class btCollisionObject;
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
|
class Projectile;
|
||||||
|
|
||||||
class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
|
class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector<const btCollisionObject*>& targets, const btVector3& from, const btVector3& to);
|
ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to, Projectile* proj=nullptr);
|
||||||
|
|
||||||
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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace MWPhysics
|
||||||
mCollisionObject.reset(new btCollisionObject);
|
mCollisionObject.reset(new btCollisionObject);
|
||||||
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
|
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
|
||||||
|
|
||||||
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
|
mCollisionObject->setUserPointer(this);
|
||||||
|
|
||||||
setScale(ptr.getCellRef().getScale());
|
setScale(ptr.getCellRef().getScale());
|
||||||
setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
|
setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
|
||||||
|
|
|
@ -46,6 +46,8 @@
|
||||||
|
|
||||||
#include "collisiontype.hpp"
|
#include "collisiontype.hpp"
|
||||||
#include "actor.hpp"
|
#include "actor.hpp"
|
||||||
|
|
||||||
|
#include "projectile.hpp"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#include "object.hpp"
|
#include "object.hpp"
|
||||||
#include "heightfield.hpp"
|
#include "heightfield.hpp"
|
||||||
|
@ -64,6 +66,7 @@ namespace MWPhysics
|
||||||
, mResourceSystem(resourceSystem)
|
, mResourceSystem(resourceSystem)
|
||||||
, mDebugDrawEnabled(false)
|
, mDebugDrawEnabled(false)
|
||||||
, mTimeAccum(0.0f)
|
, mTimeAccum(0.0f)
|
||||||
|
, mProjectileId(0)
|
||||||
, mWaterHeight(0)
|
, mWaterHeight(0)
|
||||||
, mWaterEnabled(false)
|
, mWaterEnabled(false)
|
||||||
, mParentNode(parentNode)
|
, mParentNode(parentNode)
|
||||||
|
@ -112,7 +115,7 @@ namespace MWPhysics
|
||||||
|
|
||||||
mObjects.clear();
|
mObjects.clear();
|
||||||
mActors.clear();
|
mActors.clear();
|
||||||
|
mProjectiles.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue)
|
void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue)
|
||||||
|
@ -248,7 +251,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) 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)
|
||||||
{
|
{
|
||||||
|
@ -285,7 +288,7 @@ namespace MWPhysics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo);
|
ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo, getProjectile(projId));
|
||||||
resultCallback.m_collisionFilterGroup = group;
|
resultCallback.m_collisionFilterGroup = group;
|
||||||
resultCallback.m_collisionFilterMask = mask;
|
resultCallback.m_collisionFilterMask = mask;
|
||||||
|
|
||||||
|
@ -502,6 +505,13 @@ namespace MWPhysics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PhysicsSystem::removeProjectile(const int projectileId)
|
||||||
|
{
|
||||||
|
ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId);
|
||||||
|
if (foundProjectile != mProjectiles.end())
|
||||||
|
mProjectiles.erase(foundProjectile);
|
||||||
|
}
|
||||||
|
|
||||||
void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
|
void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
|
||||||
{
|
{
|
||||||
ObjectMap::iterator found = mObjects.find(old);
|
ObjectMap::iterator found = mObjects.find(old);
|
||||||
|
@ -553,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);
|
||||||
|
@ -572,6 +590,17 @@ namespace MWPhysics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position)
|
||||||
|
{
|
||||||
|
ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId);
|
||||||
|
if (foundProjectile != mProjectiles.end())
|
||||||
|
{
|
||||||
|
foundProjectile->second->setPosition(position);
|
||||||
|
mTaskScheduler->updateSingleAabb(foundProjectile->second);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr)
|
void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr)
|
||||||
{
|
{
|
||||||
ObjectMap::iterator found = mObjects.find(ptr);
|
ObjectMap::iterator found = mObjects.find(ptr);
|
||||||
|
@ -632,6 +661,15 @@ 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)
|
||||||
|
{
|
||||||
|
mProjectileId++;
|
||||||
|
auto projectile = std::make_shared<Projectile>(mProjectileId, caster, position, mTaskScheduler.get(), this);
|
||||||
|
mProjectiles.emplace(mProjectileId, std::move(projectile));
|
||||||
|
|
||||||
|
return mProjectileId;
|
||||||
|
}
|
||||||
|
|
||||||
bool PhysicsSystem::toggleCollisionMode()
|
bool PhysicsSystem::toggleCollisionMode()
|
||||||
{
|
{
|
||||||
ActorMap::iterator found = mActors.find(MWMechanics::getPlayer());
|
ActorMap::iterator found = mActors.find(MWMechanics::getPlayer());
|
||||||
|
|
|
@ -56,6 +56,7 @@ namespace MWPhysics
|
||||||
class Object;
|
class Object;
|
||||||
class Actor;
|
class Actor;
|
||||||
class PhysicsTaskScheduler;
|
class PhysicsTaskScheduler;
|
||||||
|
class Projectile;
|
||||||
|
|
||||||
using ActorMap = std::map<MWWorld::ConstPtr, std::shared_ptr<Actor>>;
|
using ActorMap = std::map<MWWorld::ConstPtr, std::shared_ptr<Actor>>;
|
||||||
|
|
||||||
|
@ -127,6 +128,10 @@ 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);
|
||||||
|
void updateProjectile(const int projectileId, const osg::Vec3f &position);
|
||||||
|
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);
|
||||||
|
|
||||||
Actor* getActor(const MWWorld::Ptr& ptr);
|
Actor* getActor(const MWWorld::Ptr& ptr);
|
||||||
|
@ -134,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);
|
||||||
|
|
||||||
|
@ -141,7 +148,6 @@ namespace MWPhysics
|
||||||
void updateRotation (const MWWorld::Ptr& ptr);
|
void updateRotation (const MWWorld::Ptr& ptr);
|
||||||
void updatePosition (const MWWorld::Ptr& ptr);
|
void updatePosition (const MWWorld::Ptr& ptr);
|
||||||
|
|
||||||
|
|
||||||
void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject);
|
void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject);
|
||||||
|
|
||||||
void removeHeightField (int x, int y);
|
void removeHeightField (int x, int y);
|
||||||
|
@ -172,7 +178,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) const override;
|
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) 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;
|
||||||
|
|
||||||
|
@ -269,6 +275,9 @@ namespace MWPhysics
|
||||||
|
|
||||||
ActorMap mActors;
|
ActorMap mActors;
|
||||||
|
|
||||||
|
using ProjectileMap = std::map<int, std::shared_ptr<Projectile>>;
|
||||||
|
ProjectileMap mProjectiles;
|
||||||
|
|
||||||
using HeightFieldMap = std::map<std::pair<int, int>, HeightField *>;
|
using HeightFieldMap = std::map<std::pair<int, int>, HeightField *>;
|
||||||
HeightFieldMap mHeightFields;
|
HeightFieldMap mHeightFields;
|
||||||
|
|
||||||
|
@ -279,6 +288,8 @@ namespace MWPhysics
|
||||||
|
|
||||||
float mTimeAccum;
|
float mTimeAccum;
|
||||||
|
|
||||||
|
unsigned int mProjectileId;
|
||||||
|
|
||||||
float mWaterHeight;
|
float mWaterHeight;
|
||||||
bool mWaterEnabled;
|
bool mWaterEnabled;
|
||||||
|
|
||||||
|
|
89
apps/openmw/mwphysics/projectile.cpp
Normal file
89
apps/openmw/mwphysics/projectile.cpp
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <BulletCollision/CollisionShapes/btSphereShape.h>
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||||
|
|
||||||
|
#include <LinearMath/btVector3.h>
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
#include <components/misc/convert.hpp>
|
||||||
|
#include <components/resource/bulletshape.hpp>
|
||||||
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||||
|
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
|
||||||
|
#include "collisiontype.hpp"
|
||||||
|
#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)
|
||||||
|
, mCaster(caster)
|
||||||
|
, mPhysics(physicssystem)
|
||||||
|
, mTaskScheduler(scheduler)
|
||||||
|
, mProjectileId(projectileId)
|
||||||
|
{
|
||||||
|
mShape.reset(new btSphereShape(1.f));
|
||||||
|
mConvexShape = static_cast<btConvexShape*>(mShape.get());
|
||||||
|
|
||||||
|
mCollisionObject.reset(new btCollisionObject);
|
||||||
|
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
|
||||||
|
mCollisionObject->setActivationState(DISABLE_DEACTIVATION);
|
||||||
|
mCollisionObject->setCollisionShape(mShape.get());
|
||||||
|
mCollisionObject->setUserPointer(this);
|
||||||
|
|
||||||
|
setPosition(position);
|
||||||
|
|
||||||
|
const int collisionMask = CollisionType_World | CollisionType_HeightMap |
|
||||||
|
CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile;
|
||||||
|
mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask);
|
||||||
|
|
||||||
|
commitPositionChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
Projectile::~Projectile()
|
||||||
|
{
|
||||||
|
if (mCollisionObject)
|
||||||
|
{
|
||||||
|
if (!mActive)
|
||||||
|
mPhysics->reportCollision(mHitPosition, mHitNormal);
|
||||||
|
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Projectile::commitPositionChange()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||||
|
if (mTransformUpdatePending)
|
||||||
|
{
|
||||||
|
mCollisionObject->setWorldTransform(mLocalTransform);
|
||||||
|
mTransformUpdatePending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Projectile::setPosition(const osg::Vec3f &position)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||||
|
mLocalTransform.setOrigin(Misc::Convert::toBullet(position));
|
||||||
|
mTransformUpdatePending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal)
|
||||||
|
{
|
||||||
|
if (!mActive.load(std::memory_order_acquire))
|
||||||
|
return;
|
||||||
|
std::unique_lock<std::mutex> lock(mPositionMutex);
|
||||||
|
mHitTarget = target;
|
||||||
|
mHitPosition = pos;
|
||||||
|
mHitNormal = normal;
|
||||||
|
mActive.store(false, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Projectile::activate()
|
||||||
|
{
|
||||||
|
assert(!mActive);
|
||||||
|
mActive.store(true, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
106
apps/openmw/mwphysics/projectile.hpp
Normal file
106
apps/openmw/mwphysics/projectile.hpp
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
#ifndef OPENMW_MWPHYSICS_PROJECTILE_H
|
||||||
|
#define OPENMW_MWPHYSICS_PROJECTILE_H
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "components/misc/convert.hpp"
|
||||||
|
|
||||||
|
#include "ptrholder.hpp"
|
||||||
|
|
||||||
|
class btCollisionObject;
|
||||||
|
class btCollisionShape;
|
||||||
|
class btConvexShape;
|
||||||
|
class btVector3;
|
||||||
|
|
||||||
|
namespace osg
|
||||||
|
{
|
||||||
|
class Vec3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Resource
|
||||||
|
{
|
||||||
|
class BulletShape;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
class PhysicsTaskScheduler;
|
||||||
|
class PhysicsSystem;
|
||||||
|
|
||||||
|
class Projectile final : public PtrHolder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Projectile(const int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem);
|
||||||
|
~Projectile() override;
|
||||||
|
|
||||||
|
btConvexShape* getConvexShape() const { return mConvexShape; }
|
||||||
|
|
||||||
|
void commitPositionChange();
|
||||||
|
|
||||||
|
void setPosition(const osg::Vec3f& position);
|
||||||
|
|
||||||
|
btCollisionObject* getCollisionObject() const
|
||||||
|
{
|
||||||
|
return mCollisionObject.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getProjectileId() const
|
||||||
|
{
|
||||||
|
return mProjectileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isActive() const
|
||||||
|
{
|
||||||
|
return mActive.load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
MWWorld::Ptr getTarget() const
|
||||||
|
{
|
||||||
|
assert(!mActive);
|
||||||
|
return mHitTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
MWWorld::Ptr getCaster() const { return mCaster; }
|
||||||
|
|
||||||
|
osg::Vec3f getHitPos() const
|
||||||
|
{
|
||||||
|
assert(!mActive);
|
||||||
|
return Misc::Convert::toOsg(mHitPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal);
|
||||||
|
void activate();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::unique_ptr<btCollisionShape> mShape;
|
||||||
|
btConvexShape* mConvexShape;
|
||||||
|
|
||||||
|
std::unique_ptr<btCollisionObject> mCollisionObject;
|
||||||
|
btTransform mLocalTransform;
|
||||||
|
bool mTransformUpdatePending;
|
||||||
|
std::atomic<bool> mActive;
|
||||||
|
MWWorld::Ptr mCaster;
|
||||||
|
MWWorld::Ptr mHitTarget;
|
||||||
|
btVector3 mHitPosition;
|
||||||
|
btVector3 mHitNormal;
|
||||||
|
|
||||||
|
mutable std::mutex mPositionMutex;
|
||||||
|
|
||||||
|
osg::Vec3f mPosition;
|
||||||
|
|
||||||
|
PhysicsSystem *mPhysics;
|
||||||
|
PhysicsTaskScheduler *mTaskScheduler;
|
||||||
|
|
||||||
|
Projectile(const Projectile&);
|
||||||
|
Projectile& operator=(const Projectile&);
|
||||||
|
|
||||||
|
int mProjectileId;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#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) 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;
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||||
#include <BulletCollision/CollisionShapes/btConvexShape.h>
|
#include <BulletCollision/CollisionShapes/btConvexShape.h>
|
||||||
|
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
|
||||||
#include "collisiontype.hpp"
|
#include "collisiontype.hpp"
|
||||||
#include "actor.hpp"
|
#include "actor.hpp"
|
||||||
#include "closestnotmeconvexresultcallback.hpp"
|
#include "closestnotmeconvexresultcallback.hpp"
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
@ -302,6 +304,7 @@ namespace MWWorld
|
||||||
MWWorld::Ptr ptr = ref.getPtr();
|
MWWorld::Ptr ptr = ref.getPtr();
|
||||||
|
|
||||||
osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects);
|
osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects);
|
||||||
|
|
||||||
createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture);
|
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();
|
||||||
|
@ -313,6 +316,8 @@ namespace MWWorld
|
||||||
state.mSounds.push_back(sound);
|
state.mSounds.push_back(sound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.mProjectileId = mPhysics->addProjectile(caster, pos);
|
||||||
|
state.mToDelete = false;
|
||||||
mMagicBolts.push_back(state);
|
mMagicBolts.push_back(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,7 +330,6 @@ namespace MWWorld
|
||||||
state.mIdArrow = projectile.getCellRef().getRefId();
|
state.mIdArrow = projectile.getCellRef().getRefId();
|
||||||
state.mCasterHandle = actor;
|
state.mCasterHandle = actor;
|
||||||
state.mAttackStrength = attackStrength;
|
state.mAttackStrength = attackStrength;
|
||||||
|
|
||||||
int type = projectile.get<ESM::Weapon>()->mBase->mData.mType;
|
int type = projectile.get<ESM::Weapon>()->mBase->mData.mType;
|
||||||
state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown;
|
state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown;
|
||||||
|
|
||||||
|
@ -336,6 +340,8 @@ 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.mToDelete = false;
|
||||||
mProjectiles.push_back(state);
|
mProjectiles.push_back(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,63 +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);
|
||||||
|
|
||||||
update(*it, duration);
|
mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos);
|
||||||
|
|
||||||
|
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;
|
||||||
|
@ -425,7 +426,7 @@ namespace MWWorld
|
||||||
|
|
||||||
// Check for impact
|
// Check for impact
|
||||||
// TODO: use a proper btRigidBody / btGhostObject?
|
// TODO: use a proper btRigidBody / btGhostObject?
|
||||||
MWPhysics::RayCastingResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile);
|
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)
|
||||||
|
@ -433,16 +434,16 @@ namespace MWWorld
|
||||||
hit = true;
|
hit = true;
|
||||||
if (result.mHitObject.isEmpty())
|
if (result.mHitObject.isEmpty())
|
||||||
{
|
{
|
||||||
// terrain
|
// terrain or projectile
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -453,47 +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);
|
||||||
|
|
||||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
cleanupMagicBolt(magicBoltState);
|
||||||
for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++)
|
|
||||||
sndMgr->stopSound(it->mSounds.at(soundIter));
|
|
||||||
|
|
||||||
mParent->removeChild(it->mNode);
|
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
update(*it, duration);
|
mPhysics->updateProjectile(projectileState.mProjectileId, newPos);
|
||||||
|
|
||||||
MWWorld::Ptr caster = it->getCaster();
|
update(projectileState, duration);
|
||||||
|
|
||||||
|
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;
|
||||||
|
@ -502,50 +502,149 @@ namespace MWWorld
|
||||||
|
|
||||||
// Check for impact
|
// Check for impact
|
||||||
// TODO: use a proper btRigidBody / btGhostObject?
|
// TODO: use a proper btRigidBody / btGhostObject?
|
||||||
MWPhysics::RayCastingResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile);
|
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)
|
||||||
{
|
{
|
||||||
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow);
|
|
||||||
|
|
||||||
// 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(), 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);
|
||||||
|
|
||||||
mParent->removeChild(it->mNode);
|
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();
|
||||||
|
assert(target != caster);
|
||||||
|
if (!isValidTarget(caster, target))
|
||||||
|
{
|
||||||
|
projectile->activate();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
++it;
|
if (caster.isEmpty())
|
||||||
|
caster = target;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
projectileState.mHitPosition = pos;
|
||||||
|
cleanupProjectile(projectileState);
|
||||||
|
MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength);
|
||||||
}
|
}
|
||||||
|
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();
|
||||||
|
assert(target != caster);
|
||||||
|
if (!isValidTarget(caster, target))
|
||||||
|
{
|
||||||
|
projectile->activate();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
magicBoltState.mHitPosition = pos;
|
||||||
|
cleanupMagicBolt(magicBoltState);
|
||||||
|
|
||||||
|
MWMechanics::CastSpell cast(caster, target);
|
||||||
|
cast.mHitPosition = pos;
|
||||||
|
cast.mId = magicBoltState.mSpellId;
|
||||||
|
cast.mSourceName = magicBoltState.mSourceName;
|
||||||
|
cast.mStack = false;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
|
||||||
|
{
|
||||||
|
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
|
||||||
|
if (!targetActors.empty())
|
||||||
|
{
|
||||||
|
bool validTarget = false;
|
||||||
|
for (MWWorld::Ptr& targetActor : targetActors)
|
||||||
|
{
|
||||||
|
if (targetActor == target)
|
||||||
|
{
|
||||||
|
validTarget = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validTarget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state)
|
void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state)
|
||||||
{
|
{
|
||||||
mParent->removeChild(state.mNode);
|
mParent->removeChild(state.mNode);
|
||||||
|
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);
|
||||||
|
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));
|
||||||
|
@ -554,15 +653,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -619,6 +715,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
|
||||||
|
@ -626,9 +723,10 @@ namespace MWWorld
|
||||||
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId);
|
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId);
|
||||||
MWWorld::Ptr ptr = ref.getPtr();
|
MWWorld::Ptr ptr = ref.getPtr();
|
||||||
model = ptr.getClass().getModel(ptr);
|
model = ptr.getClass().getModel(ptr);
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
catch(...)
|
catch(...)
|
||||||
{
|
{
|
||||||
|
@ -640,7 +738,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);
|
||||||
|
@ -649,7 +747,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
|
||||||
{
|
{
|
||||||
|
@ -672,6 +771,7 @@ 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(...)
|
||||||
{
|
{
|
||||||
|
|
|
@ -56,6 +56,8 @@ namespace MWWorld
|
||||||
|
|
||||||
void update(float dt);
|
void update(float dt);
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
@ -76,6 +78,9 @@ namespace MWWorld
|
||||||
std::shared_ptr<MWRender::EffectAnimationTime> mEffectAnimationTime;
|
std::shared_ptr<MWRender::EffectAnimationTime> mEffectAnimationTime;
|
||||||
|
|
||||||
int mActorId;
|
int mActorId;
|
||||||
|
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.
|
||||||
|
@ -88,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
|
||||||
|
@ -125,6 +132,8 @@ namespace MWWorld
|
||||||
void moveProjectiles(float dt);
|
void moveProjectiles(float dt);
|
||||||
void moveMagicBolts(float dt);
|
void moveMagicBolts(float dt);
|
||||||
|
|
||||||
|
bool isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
|
||||||
|
|
||||||
void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient,
|
void 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);
|
||||||
|
|
|
@ -1493,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)
|
||||||
|
|
Loading…
Reference in a new issue