diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 0529140f4..31d4a1b3c 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -313,6 +313,8 @@ namespace MWBase virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0; + virtual bool castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) = 0; + virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0; virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0; @@ -623,6 +625,8 @@ namespace MWBase virtual osg::Vec3f getPathfindingHalfExtents(const MWWorld::ConstPtr& actor) const = 0; virtual bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0; + + virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const = 0; }; } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 442ba0499..ff213b219 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -61,6 +61,34 @@ namespace MWMechanics rotation.makeRotate(randomDirection, osg::Vec3f(0.0, 0.0, 1.0)); return position + osg::Vec3f(distance, 0.0, 0.0) * rotation; } + + bool isDestinationHidden(const MWWorld::ConstPtr &actor, const osg::Vec3f& destination) + { + const auto position = actor.getRefData().getPosition().asVec3(); + const bool isWaterCreature = actor.getClass().isPureWaterCreature(actor); + const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor); + const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); + osg::Vec3f direction = destination - position; + direction.normalize(); + const auto visibleDestination = ( + isWaterCreature || isFlyingCreature + ? destination + : destination + osg::Vec3f(0, 0, halfExtents.z()) + ) + direction * std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); + const int mask = MWPhysics::CollisionType_World + | MWPhysics::CollisionType_HeightMap + | MWPhysics::CollisionType_Door + | MWPhysics::CollisionType_Actor; + return MWBase::Environment::get().getWorld()->castRay(position, visibleDestination, mask, actor); + } + + bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr &actor, const osg::Vec3f& destination) + { + const auto world = MWBase::Environment::get().getWorld(); + const osg::Vec3f halfExtents = world->getPathfindingHalfExtents(actor); + const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); + return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, actor); + } } AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): @@ -265,6 +293,11 @@ namespace MWMechanics completeManualWalking(actor, storage); } + if (wanderState == AiWanderStorage::Wander_Walking + && (isDestinationHidden(actor, mPathFinder.getPath().back()) + || isAreaOccupiedByOtherActor(actor, mPathFinder.getPath().back()))) + completeManualWalking(actor, storage); + return false; // AiWander package not yet completed } @@ -328,7 +361,10 @@ namespace MWMechanics if (!isWaterCreature && destinationIsAtWater(actor, mDestination)) continue; - if ((isWaterCreature || isFlyingCreature) && destinationThroughGround(currentPosition, mDestination)) + if (isDestinationHidden(actor, mDestination)) + continue; + + if (isAreaOccupiedByOtherActor(actor, mDestination)) continue; if (isWaterCreature || isFlyingCreature) @@ -357,16 +393,6 @@ namespace MWMechanics return MWBase::Environment::get().getWorld()->isUnderwater(actor.getCell(), positionBelowSurface); } - /* - * Returns true if the start to end point travels through a collision point (land). - */ - bool AiWander::destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination) { - const int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door; - return MWBase::Environment::get().getWorld()->castRay(startPoint.x(), startPoint.y(), startPoint.z(), - destination.x(), destination.y(), destination.z(), - mask); - } - void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) { stopWalking(actor, storage); mObstacleCheck.clear(); diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 767c8c2e3..376be3a25 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -136,7 +136,6 @@ namespace MWMechanics bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance); bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination); - bool destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination); void completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage); int mDistance; // how far the actor can wander from the spawn point diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 6268eaddf..e30a2947f 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -120,6 +120,7 @@ namespace MWMechanics mWalkState = WalkState::Norm; mStateDuration = 0; mPrev = position; + mInitialDistance = (destination - position).length(); return; } @@ -129,10 +130,11 @@ namespace MWMechanics const float prevDistance = (destination - mPrev).length(); const float currentDistance = (destination - position).length(); const float movedDistance = prevDistance - currentDistance; + const float movedFromInitialDistance = mInitialDistance - currentDistance; mPrev = position; - if (movedDistance >= distSameSpot) + if (movedDistance >= distSameSpot && movedFromInitialDistance >= distSameSpot) { mWalkState = WalkState::Norm; mStateDuration = 0; @@ -143,6 +145,7 @@ namespace MWMechanics { mWalkState = WalkState::CheckStuck; mStateDuration = duration; + mInitialDistance = (destination - position).length(); return; } diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index 8314031ea..6c2197d81 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -54,6 +54,7 @@ namespace MWMechanics float mStateDuration; int mEvadeDirectionIndex; + float mInitialDistance = 0; void chooseEvasionDirection(); }; diff --git a/apps/openmw/mwphysics/hasspherecollisioncallback.hpp b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp new file mode 100644 index 000000000..58e7373e5 --- /dev/null +++ b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp @@ -0,0 +1,72 @@ +#ifndef OPENMW_MWPHYSICS_HASSPHERECOLLISIONCALLBACK_H +#define OPENMW_MWPHYSICS_HASSPHERECOLLISIONCALLBACK_H + +#include +#include +#include +#include + +#include + +namespace MWPhysics +{ + // https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection + bool testAabbAgainstSphere(const btVector3& aabbMin, const btVector3& aabbMax, + const btVector3& position, const btScalar radius) + { + const btVector3 nearest( + std::max(aabbMin.x(), std::min(aabbMax.x(), position.x())), + std::max(aabbMin.y(), std::min(aabbMax.y(), position.y())), + std::max(aabbMin.z(), std::min(aabbMax.z(), position.z())) + ); + return nearest.distance(position) < radius; + } + + class HasSphereCollisionCallback final : public btBroadphaseAabbCallback + { + public: + HasSphereCollisionCallback(const btVector3& position, const btScalar radius, btCollisionObject* object, + const int mask, const int group) + : mPosition(position), + mRadius(radius), + mCollisionObject(object), + mCollisionFilterMask(mask), + mCollisionFilterGroup(group) + { + } + + bool process(const btBroadphaseProxy* proxy) final + { + if (mResult) + return false; + const auto collisionObject = static_cast(proxy->m_clientObject); + if (collisionObject == mCollisionObject) + return true; + if (needsCollision(*proxy)) + mResult = testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius); + return !mResult; + } + + bool getResult() const + { + return mResult; + } + + private: + btVector3 mPosition; + btScalar mRadius; + btCollisionObject* mCollisionObject; + int mCollisionFilterMask; + int mCollisionFilterGroup; + bool mResult = false; + + bool needsCollision(const btBroadphaseProxy& proxy) const + { + bool collides = (proxy.m_collisionFilterGroup & mCollisionFilterMask) != 0; + collides = collides && (mCollisionFilterGroup & proxy.m_collisionFilterMask); + return collides; + } + }; +} + +#endif diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 31325cf21..69177d95d 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -45,6 +45,7 @@ #include "trace.h" #include "object.hpp" #include "heightfield.hpp" +#include "hasspherecollisioncallback.hpp" namespace MWPhysics { @@ -1444,4 +1445,20 @@ namespace MWPhysics mCollisionWorld->addCollisionObject(mWaterCollisionObject.get(), CollisionType_Water, CollisionType_Actor); } + + bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const + { + btCollisionObject* object = nullptr; + const auto it = mActors.find(ignore); + if (it != mActors.end()) + object = it->second->getCollisionObject(); + const auto bulletPosition = Misc::Convert::toBullet(position); + const auto aabbMin = bulletPosition - btVector3(radius, radius, radius); + const auto aabbMax = bulletPosition + btVector3(radius, radius, radius); + const int mask = MWPhysics::CollisionType_Actor; + const int group = 0xff; + HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group); + mCollisionWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, callback); + return callback.getResult(); + } } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 364a59ab1..d74e2de16 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -187,6 +187,8 @@ namespace MWPhysics std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function); } + bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const; + private: void updateWater(); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 06676d006..7d779e62f 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1254,6 +1254,7 @@ namespace MWScript msg << "[Deleted]" << std::endl; msg << "RefID: " << ptr.getCellRef().getRefId() << std::endl; + msg << "Memory address: " << ptr.getBase() << std::endl; if (ptr.isInCell()) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4b9213cc9..7cc7fef65 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1640,6 +1640,11 @@ namespace MWWorld return result.mHit; } + bool World::castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) + { + return mPhysics->castRay(from, to, ignore, std::vector(), mask).mHit; + } + bool World::rotateDoor(const Ptr door, MWWorld::DoorState state, float duration) { const ESM::Position& objPos = door.getRefData().getPosition(); @@ -3896,4 +3901,9 @@ namespace MWWorld btVector3 hitNormal; return btRayAabb(localFrom, localTo, aabbMin, aabbMax, hitDistance, hitNormal); } + + bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const + { + return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore); + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index ed622b5b8..fd36e5ba2 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -426,6 +426,8 @@ namespace MWWorld bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) override; + bool castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) override; + void setActorCollisionMode(const Ptr& ptr, bool internal, bool external) override; bool isActorCollisionEnabled(const Ptr& ptr) override; @@ -726,6 +728,8 @@ namespace MWWorld osg::Vec3f getPathfindingHalfExtents(const MWWorld::ConstPtr& actor) const override; bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const override; + + bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const override; }; }