From c4b5a41ac396fc0e69df339513217ee0d5ba5ba9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 17 Sep 2015 01:08:16 +0200 Subject: [PATCH] Improve combat AI vertical aiming (Fixes #1366, Fixes #1330) --- apps/openmw/mwbase/world.hpp | 4 +++ apps/openmw/mwmechanics/aicombat.cpp | 29 ++++++++-------- apps/openmw/mwphysics/physicssystem.hpp | 1 + apps/openmw/mwworld/worldimp.cpp | 44 ++++++++++++------------- apps/openmw/mwworld/worldimp.hpp | 4 +++ 5 files changed, 45 insertions(+), 37 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index a3d293d95..1622e1537 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -541,6 +541,10 @@ namespace MWBase virtual void resetActors() = 0; virtual bool isWalkingOnWater (const MWWorld::Ptr& actor) = 0; + + /// Return a vector aiming the actor's weapon towards a target. + /// @note The length of the vector is the distance between actor and target. + virtual osg::Vec3f aimToTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target) = 0; }; } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 58f74a87f..93b4e0ee7 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -34,10 +34,9 @@ namespace return std::atan2(dir.x(), dir.y()); } - float getXAngleToDir(const osg::Vec3f& dir, float dirLen = 0.0f) + float getXAngleToDir(const osg::Vec3f& dir) { - float len = (dirLen > 0.0f)? dirLen : dir.length(); - return -std::asin(dir.z() / len); + return -std::asin(dir.z() / dir.length()); } const float REACTION_INTERVAL = 0.25f; @@ -346,8 +345,9 @@ namespace MWMechanics ESM::Position pos = actor.getRefData().getPosition(); osg::Vec3f vActorPos(pos.asVec3()); osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); - osg::Vec3f vDirToTarget = vTargetPos - vActorPos; - float distToTarget = vDirToTarget.length(); + + osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); + float distToTarget = (vTargetPos - vActorPos).length(); osg::Vec3f& lastActorPos = storage.mLastActorPos; bool& followTarget = storage.mFollowTarget; @@ -388,7 +388,7 @@ namespace MWMechanics if (distantCombat) { osg::Vec3f& lastTargetPos = storage.mLastTargetPos; - osg::Vec3f vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, REACTION_INTERVAL, weaptype, + vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, REACTION_INTERVAL, weaptype, storage.mStrength); lastTargetPos = vTargetPos; movement.mRotation[0] = getXAngleToDir(vAimDir); @@ -396,8 +396,8 @@ namespace MWMechanics } else { - movement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); - movement.mRotation[2] = getZAngleToDir(vDirToTarget); + movement.mRotation[0] = getXAngleToDir(vAimDir); + movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated } // (not quite attack dist while following) @@ -431,19 +431,19 @@ namespace MWMechanics if(speed == 0.0f) speed = actorClass.getSpeed(actor); // maximum dist before pit/obstacle for actor to avoid them depending on his speed float maxAvoidDist = REACTION_INTERVAL * speed + speed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability - preferShortcut = checkWayIsClear(vActorPos, vTargetPos, osg::Vec3f(vDirToTarget.x(), vDirToTarget.y(), 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); + preferShortcut = checkWayIsClear(vActorPos, vTargetPos, osg::Vec3f(vAimDir.x(), vAimDir.y(), 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); } // don't use pathgrid when actor can move in 3 dimensions if (canMoveByZ) { preferShortcut = true; - movement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); + movement.mRotation[0] = getXAngleToDir(vAimDir); } if(preferShortcut) { - movement.mRotation[2] = getZAngleToDir(vDirToTarget); + movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); forceNoShortcut = false; shortcutFailPos.pos[0] = shortcutFailPos.pos[1] = shortcutFailPos.pos[2] = 0; mPathFinder.clearPath(); @@ -478,7 +478,7 @@ namespace MWMechanics // if there is no new path, then go straight on target if (!mPathFinder.isPathConstructed()) { - movement.mRotation[2] = getZAngleToDir(vDirToTarget); + movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); } } @@ -779,9 +779,8 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t // idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same - osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3(); - osg::Vec3f vDirToTarget = vTargetPos - vActorPos; + osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); float distToTarget = vDirToTarget.length(); osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos; @@ -812,7 +811,7 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t t_collision = projDistDiff / (std::sqrt(projVelDirSquared) - velDir); else t_collision = 0; // speed of projectile is not enough to reach moving target - return vTargetPos + vTargetMoveDir * t_collision - vActorPos; + return vDirToTarget + vTargetMoveDir * t_collision; } } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index ee9378ea4..db8da2886 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -107,6 +107,7 @@ namespace MWPhysics bool isOnGround (const MWWorld::Ptr& actor); + /// Get physical half extents (scaled) of the given actor. osg::Vec3f getHalfExtents(const MWWorld::Ptr& actor); /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f50991e24..b3e1c6ecb 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1018,14 +1018,11 @@ namespace MWWorld return facedObject; } - std::pair World::getHitContact(const MWWorld::Ptr &ptr, float distance) + osg::Vec3f getActorHeadPosition(const MWWorld::Ptr& actor, MWRender::RenderingManager* rendering) { - const ESM::Position &posdata = ptr.getRefData().getPosition(); - - osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1)); - osg::Vec3f pos (posdata.asVec3()); + osg::Vec3f origin(actor.getRefData().getPosition().asVec3()); - MWRender::Animation* anim = mRendering->getAnimation(ptr); + MWRender::Animation* anim = rendering->getAnimation(actor); if (anim != NULL) { const osg::Node* node = anim->getNode("Head"); @@ -1035,9 +1032,18 @@ namespace MWWorld { osg::MatrixList mats = node->getWorldMatrices(); if (mats.size()) - pos = mats[0].getTrans(); + origin = mats[0].getTrans(); } } + return origin; + } + + std::pair World::getHitContact(const MWWorld::Ptr &ptr, float distance) + { + const ESM::Position &posdata = ptr.getRefData().getPosition(); + + osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1)); + osg::Vec3f pos = getActorHeadPosition(ptr, mRendering); std::pair result = mPhysics->getHitContact(ptr, pos, rot, distance); if(result.first.isEmpty()) @@ -2608,21 +2614,7 @@ namespace MWWorld MWWorld::Ptr target; float distance = 192.f; // ?? osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); - osg::Vec3f origin(actor.getRefData().getPosition().asVec3()); - - MWRender::Animation* anim = mRendering->getAnimation(actor); - if (anim != NULL) - { - const osg::Node* node = anim->getNode("Head"); - if (node == NULL) - node = anim->getNode("Bip01 Head"); - if (node != NULL) - { - osg::MatrixList mats = node->getWorldMatrices(); - if (mats.size()) - origin = mats[0].getTrans(); - } - } + osg::Vec3f origin = getActorHeadPosition(actor, mRendering); osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); @@ -3262,4 +3254,12 @@ namespace MWWorld return true; return false; } + + osg::Vec3f World::aimToTarget(const Ptr &actor, const MWWorld::Ptr& target) + { + osg::Vec3f weaponPos = getActorHeadPosition(actor, mRendering); + osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); + targetPos.z() += mPhysics->getHalfExtents(target).z(); + return (targetPos - weaponPos); + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 241dacc8a..26153086a 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -630,6 +630,10 @@ namespace MWWorld virtual void resetActors(); virtual bool isWalkingOnWater (const MWWorld::Ptr& actor); + + /// Return a vector aiming the actor's weapon towards a target. + /// @note The length of the vector is the distance between actor and target. + virtual osg::Vec3f aimToTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); }; }