mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-08-03 07:16:31 -04:00
Merge branch 'aiwander' into 'master'
Build checkpoints based path for wandering actors (#8433) Closes #8433 See merge request OpenMW/openmw!4652
This commit is contained in:
commit
0a8373987d
@ -139,7 +139,7 @@ namespace
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty)
|
||||
{
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::NavMeshNotFound);
|
||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>());
|
||||
}
|
||||
@ -147,7 +147,7 @@ namespace
|
||||
TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception)
|
||||
{
|
||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::StartPolygonNotFound);
|
||||
}
|
||||
|
||||
@ -156,7 +156,7 @@ namespace
|
||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||
mNavigator->removeAgent(mAgentBounds);
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::StartPolygonNotFound);
|
||||
}
|
||||
|
||||
@ -172,7 +172,7 @@ namespace
|
||||
updateGuard.reset();
|
||||
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -194,7 +194,7 @@ namespace
|
||||
updateGuard.reset();
|
||||
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mStart, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mStart, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath, ElementsAre(Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125))) << mPath;
|
||||
@ -218,7 +218,7 @@ namespace
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -237,7 +237,7 @@ namespace
|
||||
|
||||
mPath.clear();
|
||||
mOut = std::back_inserter(mPath);
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -265,7 +265,7 @@ namespace
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -285,7 +285,7 @@ namespace
|
||||
|
||||
mPath.clear();
|
||||
mOut = std::back_inserter(mPath);
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -318,7 +318,7 @@ namespace
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -386,7 +386,7 @@ namespace
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -421,7 +421,7 @@ namespace
|
||||
mEnd.x() = 256;
|
||||
mEnd.z() = 300;
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -453,8 +453,8 @@ namespace
|
||||
mStart.x() = 256;
|
||||
mEnd.x() = 256;
|
||||
|
||||
EXPECT_EQ(
|
||||
findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance,
|
||||
{}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -487,8 +487,8 @@ namespace
|
||||
mStart.x() = 256;
|
||||
mEnd.x() = 256;
|
||||
|
||||
EXPECT_EQ(
|
||||
findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mEndTolerance,
|
||||
{}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -520,7 +520,7 @@ namespace
|
||||
mStart.x() = 256;
|
||||
mEnd.x() = 256;
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -549,7 +549,7 @@ namespace
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -577,7 +577,7 @@ namespace
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -658,7 +658,7 @@ namespace
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -781,7 +781,7 @@ namespace
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -806,7 +806,7 @@ namespace
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::PartialPath);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -834,7 +834,7 @@ namespace
|
||||
|
||||
const float endTolerance = 1000.0f;
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, endTolerance, mOut),
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mEnd, Flag_walk, mAreaCosts, endTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
@ -979,6 +979,146 @@ namespace
|
||||
EXPECT_EQ(usedNavMeshTiles, 854);
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, find_path_should_return_path_around_steep_mountains)
|
||||
{
|
||||
const std::array<float, 5 * 5> heightfieldData{ {
|
||||
0, 0, 0, 0, 0, // row 0
|
||||
0, 0, 0, 0, 0, // row 1
|
||||
0, 0, 1000, 0, 0, // row 2
|
||||
0, 0, 0, 0, 0, // row 3
|
||||
0, 0, 0, 0, 0, // row 4
|
||||
} };
|
||||
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
|
||||
const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
|
||||
|
||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
const osg::Vec3f start(56, 56, 12);
|
||||
const osg::Vec3f end(464, 464, 12);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, start, end, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
ElementsAre( //
|
||||
Vec3fEq(56.66664886474609375, 56.66664886474609375, 11.33333301544189453125),
|
||||
Vec3fEq(396.666656494140625, 79.33331298828125, 11.33333301544189453125),
|
||||
Vec3fEq(430.666656494140625, 113.33331298828125, 11.33333301544189453125),
|
||||
Vec3fEq(463.999969482421875, 463.999969482421875, 11.33333301544189453125)))
|
||||
<< mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, find_path_should_return_path_around_steep_cliffs)
|
||||
{
|
||||
const std::array<float, 5 * 5> heightfieldData{ {
|
||||
0, 0, 0, 0, 0, // row 0
|
||||
0, 0, 0, 0, 0, // row 1
|
||||
0, 0, -1000, 0, 0, // row 2
|
||||
0, 0, 0, 0, 0, // row 3
|
||||
0, 0, 0, 0, 0, // row 4
|
||||
} };
|
||||
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
|
||||
const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
|
||||
|
||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
const osg::Vec3f start(56, 56, 12);
|
||||
const osg::Vec3f end(464, 464, 12);
|
||||
|
||||
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, start, end, Flag_walk, mAreaCosts, mEndTolerance, {}, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
ElementsAre( //
|
||||
Vec3fEq(56.66664886474609375, 56.66664886474609375, 8.66659259796142578125),
|
||||
Vec3fEq(385.33331298828125, 79.33331298828125, 8.66659259796142578125),
|
||||
Vec3fEq(430.666656494140625, 124.66664886474609375, 8.66659259796142578125),
|
||||
Vec3fEq(463.999969482421875, 463.999969482421875, 8.66659259796142578125)))
|
||||
<< mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, find_path_should_return_path_with_checkpoints)
|
||||
{
|
||||
const std::array<float, 5 * 5> heightfieldData{ {
|
||||
0, 0, 0, 0, 0, // row 0
|
||||
0, 0, 0, 0, 0, // row 1
|
||||
0, 0, 1000, 0, 0, // row 2
|
||||
0, 0, 0, 0, 0, // row 3
|
||||
0, 0, 0, 0, 0, // row 4
|
||||
} };
|
||||
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
|
||||
const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
|
||||
|
||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
const std::vector<osg::Vec3f> checkpoints = {
|
||||
osg::Vec3f(400, 70, 12),
|
||||
};
|
||||
|
||||
const osg::Vec3f start(56, 56, 12);
|
||||
const osg::Vec3f end(464, 464, 12);
|
||||
|
||||
EXPECT_EQ(
|
||||
findPath(*mNavigator, mAgentBounds, start, end, Flag_walk, mAreaCosts, mEndTolerance, checkpoints, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
ElementsAre( //
|
||||
Vec3fEq(56.66664886474609375, 56.66664886474609375, 11.33333301544189453125),
|
||||
Vec3fEq(400, 70, 11.33333301544189453125),
|
||||
Vec3fEq(430.666656494140625, 113.33331298828125, 11.33333301544189453125),
|
||||
Vec3fEq(463.999969482421875, 463.999969482421875, 11.33333301544189453125)))
|
||||
<< mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, find_path_should_skip_unreachable_checkpoints)
|
||||
{
|
||||
const std::array<float, 5 * 5> heightfieldData{ {
|
||||
0, 0, 0, 0, 0, // row 0
|
||||
0, 0, 0, 0, 0, // row 1
|
||||
0, 0, 1000, 0, 0, // row 2
|
||||
0, 0, 0, 0, 0, // row 3
|
||||
0, 0, 0, 0, 0, // row 4
|
||||
} };
|
||||
const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData);
|
||||
const int cellSize = heightfieldTileSize * static_cast<int>(surface.mSize - 1);
|
||||
|
||||
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
|
||||
mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
|
||||
mNavigator->update(mPlayerPosition, nullptr);
|
||||
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
|
||||
|
||||
const std::vector<osg::Vec3f> checkpoints = {
|
||||
osg::Vec3f(400, 70, 10000),
|
||||
osg::Vec3f(256, 256, 1000),
|
||||
osg::Vec3f(-1000, -1000, 0),
|
||||
};
|
||||
|
||||
const osg::Vec3f start(56, 56, 12);
|
||||
const osg::Vec3f end(464, 464, 12);
|
||||
|
||||
EXPECT_EQ(
|
||||
findPath(*mNavigator, mAgentBounds, start, end, Flag_walk, mAreaCosts, mEndTolerance, checkpoints, mOut),
|
||||
Status::Success);
|
||||
|
||||
EXPECT_THAT(mPath,
|
||||
ElementsAre( //
|
||||
Vec3fEq(56.66664886474609375, 56.66664886474609375, 11.33333301544189453125),
|
||||
Vec3fEq(396.666656494140625, 79.33331298828125, 11.33333301544189453125),
|
||||
Vec3fEq(430.666656494140625, 113.33331298828125, 11.33333301544189453125),
|
||||
Vec3fEq(463.999969482421875, 463.999969482421875, 11.33333301544189453125)))
|
||||
<< mPath;
|
||||
}
|
||||
|
||||
struct DetourNavigatorNavigatorNotSupportedAgentBoundsTest : TestWithParam<AgentBounds>
|
||||
{
|
||||
};
|
||||
|
@ -589,8 +589,7 @@ namespace MWBase
|
||||
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,
|
||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors = nullptr) const = 0;
|
||||
virtual bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& position) const = 0;
|
||||
|
||||
virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0;
|
||||
|
||||
|
@ -233,6 +233,7 @@ namespace MWLua
|
||||
DetourNavigator::Flags includeFlags = defaultIncludeFlags;
|
||||
DetourNavigator::AreaCosts areaCosts{};
|
||||
float destinationTolerance = 1;
|
||||
std::vector<osg::Vec3f> checkpoints;
|
||||
|
||||
if (options.has_value())
|
||||
{
|
||||
@ -258,13 +259,24 @@ namespace MWLua
|
||||
}
|
||||
if (const auto& v = options->get<sol::optional<float>>("destinationTolerance"))
|
||||
destinationTolerance = *v;
|
||||
if (const auto& t = options->get<sol::optional<sol::table>>("checkpoints"))
|
||||
{
|
||||
for (const auto& [k, v] : *t)
|
||||
{
|
||||
const int index = k.as<int>();
|
||||
const osg::Vec3f position = v.as<osg::Vec3f>();
|
||||
if (index != static_cast<int>(checkpoints.size() + 1))
|
||||
throw std::runtime_error("checkpoints is not an array");
|
||||
checkpoints.push_back(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<osg::Vec3f> path;
|
||||
|
||||
const DetourNavigator::Status status
|
||||
= DetourNavigator::findPath(*MWBase::Environment::get().getWorld()->getNavigator(), agentBounds,
|
||||
source, destination, includeFlags, areaCosts, destinationTolerance, std::back_inserter(path));
|
||||
const DetourNavigator::Status status = DetourNavigator::findPath(
|
||||
*MWBase::Environment::get().getWorld()->getNavigator(), agentBounds, source, destination,
|
||||
includeFlags, areaCosts, destinationTolerance, checkpoints, std::back_inserter(path));
|
||||
|
||||
sol::table result(lua, sol::create);
|
||||
LuaUtil::copyVectorToTable(path, result);
|
||||
|
@ -1,13 +1,11 @@
|
||||
#include "aicombat.hpp"
|
||||
|
||||
#include <components/misc/coordinateconverter.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
|
||||
#include <components/esm3/aisequence.hpp>
|
||||
|
||||
#include <components/misc/mathutil.hpp>
|
||||
|
||||
#include <components/detournavigator/navigatorutils.hpp>
|
||||
#include <components/esm3/aisequence.hpp>
|
||||
#include <components/misc/coordinateconverter.hpp>
|
||||
#include <components/misc/mathutil.hpp>
|
||||
#include <components/misc/pathgridutils.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
|
||||
#include "../mwphysics/raycasting.hpp"
|
||||
@ -303,8 +301,8 @@ namespace MWMechanics
|
||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||
const ESM::Pathgrid* pathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
||||
const auto& pathGridGraph = getPathGridGraph(pathgrid);
|
||||
mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, agentBounds,
|
||||
navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full);
|
||||
mPathFinder.buildPath(actor, vActorPos, vTargetPos, pathGridGraph, agentBounds, navigatorFlags, areaCosts,
|
||||
storage.mAttackRange, PathType::Full);
|
||||
|
||||
if (!mPathFinder.isPathConstructed())
|
||||
{
|
||||
@ -317,8 +315,8 @@ namespace MWMechanics
|
||||
if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack)
|
||||
{
|
||||
// If the point is close enough, try to find a path to that point.
|
||||
mPathFinder.buildPath(actor, vActorPos, *hit, actor.getCell(), pathGridGraph, agentBounds,
|
||||
navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full);
|
||||
mPathFinder.buildPath(actor, vActorPos, *hit, pathGridGraph, agentBounds, navigatorFlags, areaCosts,
|
||||
storage.mAttackRange, PathType::Full);
|
||||
if (mPathFinder.isPathConstructed())
|
||||
{
|
||||
// If path to that point is found use it as custom destination.
|
||||
@ -394,8 +392,8 @@ namespace MWMechanics
|
||||
osg::Vec3f localPos = actor.getRefData().getPosition().asVec3();
|
||||
coords.toLocal(localPos);
|
||||
|
||||
size_t closestPointIndex = PathFinder::getClosestPoint(pathgrid, localPos);
|
||||
for (size_t i = 0; i < pathgrid->mPoints.size(); i++)
|
||||
const std::size_t closestPointIndex = Misc::getClosestPoint(*pathgrid, localPos);
|
||||
for (std::size_t i = 0; i < pathgrid->mPoints.size(); i++)
|
||||
{
|
||||
if (i != closestPointIndex
|
||||
&& getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, i))
|
||||
@ -456,7 +454,8 @@ namespace MWMechanics
|
||||
float dist
|
||||
= (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length();
|
||||
if ((dist > fFleeDistance && !storage.mLOS)
|
||||
|| pathTo(actor, PathFinder::makeOsgVec3(storage.mFleeDest), duration, supportedMovementDirections))
|
||||
|| pathTo(
|
||||
actor, Misc::Convert::makeOsgVec3f(storage.mFleeDest), duration, supportedMovementDirections))
|
||||
{
|
||||
state = AiCombatStorage::FleeState_Idle;
|
||||
}
|
||||
|
@ -2,12 +2,13 @@
|
||||
#define GAME_MWMECHANICS_AICOMBAT_H
|
||||
|
||||
#include "aitemporarybase.hpp"
|
||||
#include "aitimer.hpp"
|
||||
#include "movement.hpp"
|
||||
#include "typedaipackage.hpp"
|
||||
|
||||
#include "../mwworld/cellstore.hpp" // for Doors
|
||||
|
||||
#include "aitimer.hpp"
|
||||
#include "movement.hpp"
|
||||
#include <components/esm3/loadpgrd.hpp>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
@ -180,8 +180,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
|
||||
= world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
||||
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||
mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(pathgrid),
|
||||
agentBounds, navigatorFlags, areaCosts, endTolerance, pathType);
|
||||
mPathFinder.buildLimitedPath(actor, position, dest, getPathGridGraph(pathgrid), agentBounds,
|
||||
navigatorFlags, areaCosts, endTolerance, pathType);
|
||||
mRotateOnTheRunChecks = 3;
|
||||
|
||||
// give priority to go directly on target if there is minimal opportunity
|
||||
@ -501,7 +501,11 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::
|
||||
result |= DetourNavigator::Flag_swim;
|
||||
|
||||
if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0)
|
||||
result |= DetourNavigator::Flag_walk | DetourNavigator::Flag_usePathgrid;
|
||||
{
|
||||
result |= DetourNavigator::Flag_walk;
|
||||
if (getTypeId() != AiPackageTypeId::Wander)
|
||||
result |= DetourNavigator::Flag_usePathgrid;
|
||||
}
|
||||
|
||||
if (canOpenDoors(actor) && getTypeId() != AiPackageTypeId::Wander)
|
||||
result |= DetourNavigator::Flag_openDoor;
|
||||
|
@ -16,6 +16,8 @@
|
||||
namespace ESM
|
||||
{
|
||||
struct Cell;
|
||||
struct Pathgrid;
|
||||
|
||||
namespace AiSequence
|
||||
{
|
||||
struct AiSequence;
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <components/detournavigator/navigatorutils.hpp>
|
||||
#include <components/esm3/aisequence.hpp>
|
||||
#include <components/misc/coordinateconverter.hpp>
|
||||
#include <components/misc/pathgridutils.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
@ -29,17 +30,6 @@
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
static const int COUNT_BEFORE_RESET = 10;
|
||||
static const float IDLE_POSITION_CHECK_INTERVAL = 1.5f;
|
||||
|
||||
// to prevent overcrowding
|
||||
static const int DESTINATION_TOLERANCE = 64;
|
||||
|
||||
// distance must be long enough that NPC will need to move to get there.
|
||||
static const int MINIMUM_WANDER_DISTANCE = DESTINATION_TOLERANCE * 2;
|
||||
|
||||
static const std::size_t MAX_IDLE_SIZE = 8;
|
||||
|
||||
const std::string_view AiWander::sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1] = {
|
||||
"idle2",
|
||||
"idle3",
|
||||
@ -53,11 +43,22 @@ namespace MWMechanics
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr int countBeforeReset = 10;
|
||||
constexpr float idlePositionCheckInterval = 1.5f;
|
||||
|
||||
// to prevent overcrowding
|
||||
constexpr unsigned destinationTolerance = 64;
|
||||
|
||||
// distance must be long enough that NPC will need to move to get there.
|
||||
constexpr unsigned minimumWanderDistance = destinationTolerance * 2;
|
||||
|
||||
constexpr std::size_t maxIdleSize = 8;
|
||||
|
||||
inline int getCountBeforeReset(const MWWorld::ConstPtr& actor)
|
||||
{
|
||||
if (actor.getClass().isPureWaterCreature(actor) || actor.getClass().isPureFlyingCreature(actor))
|
||||
return 1;
|
||||
return COUNT_BEFORE_RESET;
|
||||
return countBeforeReset;
|
||||
}
|
||||
|
||||
osg::Vec3f getRandomPointAround(const osg::Vec3f& position, const float distance)
|
||||
@ -99,16 +100,42 @@ namespace MWMechanics
|
||||
|
||||
std::vector<unsigned char> getInitialIdle(const std::vector<unsigned char>& idle)
|
||||
{
|
||||
std::vector<unsigned char> result(MAX_IDLE_SIZE, 0);
|
||||
std::copy_n(idle.begin(), std::min(MAX_IDLE_SIZE, idle.size()), result.begin());
|
||||
std::vector<unsigned char> result(maxIdleSize, 0);
|
||||
std::copy_n(idle.begin(), std::min(maxIdleSize, idle.size()), result.begin());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> getInitialIdle(const unsigned char (&idle)[MAX_IDLE_SIZE])
|
||||
std::vector<unsigned char> getInitialIdle(const unsigned char (&idle)[maxIdleSize])
|
||||
{
|
||||
return std::vector<unsigned char>(std::begin(idle), std::end(idle));
|
||||
}
|
||||
|
||||
void trimAllowedPositions(const std::deque<osg::Vec3f>& path, std::vector<osg::Vec3f>& allowedPositions)
|
||||
{
|
||||
// TODO: how to add these back in once the door opens?
|
||||
// Idea: keep a list of detected closed doors (see aicombat.cpp)
|
||||
// Every now and then check whether one of the doors is opened. (maybe
|
||||
// at the end of playing idle?) If the door is opened then re-calculate
|
||||
// allowed positions starting from the spawn point.
|
||||
std::vector<osg::Vec3f> points(path.begin(), path.end());
|
||||
while (points.size() >= 2)
|
||||
{
|
||||
const osg::Vec3f point = points.back();
|
||||
for (std::size_t j = 0; j < allowedPositions.size(); j++)
|
||||
{
|
||||
// FIXME: doesn't handle a door with the same X/Y
|
||||
// coordinates but with a different Z
|
||||
if (std::abs(allowedPositions[j].x() - point.x()) <= 0.5
|
||||
&& std::abs(allowedPositions[j].y() - point.y()) <= 0.5)
|
||||
{
|
||||
allowedPositions.erase(allowedPositions.begin() + j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
points.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AiWanderStorage::AiWanderStorage()
|
||||
@ -118,9 +145,9 @@ namespace MWMechanics
|
||||
, mCanWanderAlongPathGrid(true)
|
||||
, mIdleAnimation(0)
|
||||
, mBadIdles()
|
||||
, mPopulateAvailableNodes(true)
|
||||
, mAllowedNodes()
|
||||
, mTrimCurrentNode(false)
|
||||
, mPopulateAvailablePositions(true)
|
||||
, mAllowedPositions()
|
||||
, mTrimCurrentPosition(false)
|
||||
, mCheckIdlePositionTimer(0)
|
||||
, mStuckCount(0)
|
||||
{
|
||||
@ -128,8 +155,8 @@ namespace MWMechanics
|
||||
|
||||
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat)
|
||||
: TypedAiPackage<AiWander>(repeat)
|
||||
, mDistance(std::max(0, distance))
|
||||
, mDuration(std::max(0, duration))
|
||||
, mDistance(static_cast<unsigned>(std::max(0, distance)))
|
||||
, mDuration(static_cast<unsigned>(std::max(0, duration)))
|
||||
, mRemainingDuration(duration)
|
||||
, mTimeOfDay(timeOfDay)
|
||||
, mIdle(getInitialIdle(idle))
|
||||
@ -215,20 +242,12 @@ namespace MWMechanics
|
||||
{
|
||||
const ESM::Pathgrid* pathgrid
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
||||
if (mUsePathgrid)
|
||||
{
|
||||
mPathFinder.buildPathByPathgrid(
|
||||
pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(pathgrid));
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(actor);
|
||||
constexpr float endTolerance = 0;
|
||||
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||
mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(pathgrid),
|
||||
agentBounds, navigatorFlags, areaCosts, endTolerance, PathType::Full);
|
||||
}
|
||||
const auto agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(actor);
|
||||
constexpr float endTolerance = 0;
|
||||
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||
mPathFinder.buildPath(actor, pos.asVec3(), mDestination, getPathGridGraph(pathgrid), agentBounds,
|
||||
navigatorFlags, areaCosts, endTolerance, PathType::Full);
|
||||
|
||||
if (mPathFinder.isPathConstructed())
|
||||
storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid);
|
||||
@ -259,9 +278,6 @@ namespace MWMechanics
|
||||
|
||||
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)
|
||||
{
|
||||
if (mDistance <= 0)
|
||||
storage.mCanWanderAlongPathGrid = false;
|
||||
|
||||
if (isPackageCompleted())
|
||||
{
|
||||
stopWalking(actor);
|
||||
@ -276,13 +292,15 @@ namespace MWMechanics
|
||||
mStoredInitialActorPosition = true;
|
||||
}
|
||||
|
||||
// Initialization to discover & store allowed node points for this actor.
|
||||
if (storage.mPopulateAvailableNodes)
|
||||
// Initialization to discover & store allowed positions points for this actor.
|
||||
if (storage.mPopulateAvailablePositions)
|
||||
{
|
||||
getAllowedNodes(actor, storage);
|
||||
fillAllowedPositions(actor, storage);
|
||||
}
|
||||
|
||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
||||
MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||
|
||||
auto& prng = world.getPrng();
|
||||
if (canActorMoveByZAxis(actor) && mDistance > 0)
|
||||
{
|
||||
// Typically want to idle for a short time before the next wander
|
||||
@ -295,7 +313,7 @@ namespace MWMechanics
|
||||
}
|
||||
// If the package has a wander distance but no pathgrid is available,
|
||||
// randomly idle or wander near spawn point
|
||||
else if (storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually)
|
||||
else if (storage.mAllowedPositions.empty() && mDistance > 0 && !storage.mIsWanderingManually)
|
||||
{
|
||||
// Typically want to idle for a short time before the next wander
|
||||
if (Misc::Rng::rollDice(100, prng) >= 96)
|
||||
@ -307,7 +325,7 @@ namespace MWMechanics
|
||||
storage.setState(AiWanderStorage::Wander_IdleNow);
|
||||
}
|
||||
}
|
||||
else if (storage.mAllowedNodes.empty() && !storage.mIsWanderingManually)
|
||||
else if (storage.mAllowedPositions.empty() && !storage.mIsWanderingManually)
|
||||
{
|
||||
storage.mCanWanderAlongPathGrid = false;
|
||||
}
|
||||
@ -323,9 +341,9 @@ namespace MWMechanics
|
||||
// Construct a new path if there isn't one
|
||||
if (!mPathFinder.isPathConstructed())
|
||||
{
|
||||
if (!storage.mAllowedNodes.empty())
|
||||
if (!storage.mAllowedPositions.empty())
|
||||
{
|
||||
setPathToAnAllowedNode(actor, storage, pos);
|
||||
setPathToAnAllowedPosition(actor, storage, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -336,7 +354,7 @@ namespace MWMechanics
|
||||
|
||||
if (storage.mIsWanderingManually && storage.mState == AiWanderStorage::Wander_Walking
|
||||
&& (mPathFinder.getPathSize() == 0 || isDestinationHidden(actor, mPathFinder.getPath().back())
|
||||
|| isAreaOccupiedByOtherActor(actor, mPathFinder.getPath().back())))
|
||||
|| world.isAreaOccupiedByOtherActor(actor, mPathFinder.getPath().back())))
|
||||
completeManualWalking(actor, storage);
|
||||
|
||||
return false; // AiWander package not yet completed
|
||||
@ -366,12 +384,12 @@ namespace MWMechanics
|
||||
std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here
|
||||
const bool isWaterCreature = actor.getClass().isPureWaterCreature(actor);
|
||||
const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor);
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
const auto agentBounds = world->getPathfindingAgentBounds(actor);
|
||||
const auto navigator = world->getNavigator();
|
||||
MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||
const auto agentBounds = world.getPathfindingAgentBounds(actor);
|
||||
const auto navigator = world.getNavigator();
|
||||
const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor);
|
||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags);
|
||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
||||
Misc::Rng::Generator& prng = world.getPrng();
|
||||
|
||||
do
|
||||
{
|
||||
@ -411,7 +429,7 @@ namespace MWMechanics
|
||||
if (isDestinationHidden(actor, mDestination))
|
||||
continue;
|
||||
|
||||
if (isAreaOccupiedByOtherActor(actor, mDestination))
|
||||
if (world.isAreaOccupiedByOtherActor(actor, mDestination))
|
||||
continue;
|
||||
|
||||
constexpr float endTolerance = 0;
|
||||
@ -491,17 +509,17 @@ namespace MWMechanics
|
||||
|
||||
void AiWander::onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
||||
{
|
||||
// Check if an idle actor is too far from all allowed nodes or too close to a door - if so start walking.
|
||||
// Check if an idle actor is too far from all allowed positions or too close to a door - if so start walking.
|
||||
storage.mCheckIdlePositionTimer += duration;
|
||||
|
||||
if (storage.mCheckIdlePositionTimer >= IDLE_POSITION_CHECK_INTERVAL && !isStationary())
|
||||
if (storage.mCheckIdlePositionTimer >= idlePositionCheckInterval && !isStationary())
|
||||
{
|
||||
storage.mCheckIdlePositionTimer = 0; // restart timer
|
||||
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance() * 1.6f;
|
||||
if (proximityToDoor(actor, distance) || !isNearAllowedNode(actor, storage, distance))
|
||||
if (proximityToDoor(actor, distance) || !isNearAllowedPosition(actor, storage, distance))
|
||||
{
|
||||
storage.setState(AiWanderStorage::Wander_MoveNow);
|
||||
storage.mTrimCurrentNode = false; // just in case
|
||||
storage.mTrimCurrentPosition = false; // just in case
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -517,16 +535,14 @@ namespace MWMechanics
|
||||
}
|
||||
}
|
||||
|
||||
bool AiWander::isNearAllowedNode(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const
|
||||
bool AiWander::isNearAllowedPosition(
|
||||
const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const
|
||||
{
|
||||
const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
|
||||
for (const ESM::Pathgrid::Point& node : storage.mAllowedNodes)
|
||||
{
|
||||
osg::Vec3f point(node.mX, node.mY, node.mZ);
|
||||
if ((actorPos - point).length2() < distance * distance)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
const float squaredDistance = distance * distance;
|
||||
return std::ranges::find_if(storage.mAllowedPositions, [&](const osg::Vec3& v) {
|
||||
return (actorPos - v).length2() < squaredDistance;
|
||||
}) != storage.mAllowedPositions.end();
|
||||
}
|
||||
|
||||
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration,
|
||||
@ -535,7 +551,7 @@ namespace MWMechanics
|
||||
// Is there no destination or are we there yet?
|
||||
if ((!mPathFinder.isPathConstructed())
|
||||
|| pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, supportedMovementDirections,
|
||||
DESTINATION_TOLERANCE))
|
||||
destinationTolerance))
|
||||
{
|
||||
stopWalking(actor);
|
||||
storage.setState(AiWanderStorage::Wander_ChooseAction);
|
||||
@ -586,8 +602,8 @@ namespace MWMechanics
|
||||
if (proximityToDoor(actor, distance))
|
||||
{
|
||||
// remove allowed points then select another random destination
|
||||
storage.mTrimCurrentNode = true;
|
||||
trimAllowedNodes(storage.mAllowedNodes, mPathFinder);
|
||||
storage.mTrimCurrentPosition = true;
|
||||
trimAllowedPositions(mPathFinder.getPath(), storage.mAllowedPositions);
|
||||
mObstacleCheck.clear();
|
||||
stopWalking(actor);
|
||||
storage.setState(AiWanderStorage::Wander_MoveNow);
|
||||
@ -606,67 +622,67 @@ namespace MWMechanics
|
||||
}
|
||||
}
|
||||
|
||||
void AiWander::setPathToAnAllowedNode(
|
||||
void AiWander::setPathToAnAllowedPosition(
|
||||
const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos)
|
||||
{
|
||||
auto world = MWBase::Environment::get().getWorld();
|
||||
auto& prng = world->getPrng();
|
||||
unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng);
|
||||
const ESM::Pathgrid::Point& dest = storage.mAllowedNodes[randNode];
|
||||
MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||
Misc::Rng::Generator& prng = world.getPrng();
|
||||
const std::size_t randomAllowedPositionIndex
|
||||
= static_cast<std::size_t>(Misc::Rng::rollDice(storage.mAllowedPositions.size(), prng));
|
||||
const osg::Vec3f randomAllowedPosition = storage.mAllowedPositions[randomAllowedPositionIndex];
|
||||
|
||||
const osg::Vec3f start = actorPos.asVec3();
|
||||
|
||||
// don't take shortcuts for wandering
|
||||
const ESM::Pathgrid* pathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
||||
const osg::Vec3f destVec3f = PathFinder::makeOsgVec3(dest);
|
||||
mPathFinder.buildPathByPathgrid(start, destVec3f, actor.getCell(), getPathGridGraph(pathgrid));
|
||||
const MWWorld::Cell& cell = *actor.getCell()->getCell();
|
||||
const ESM::Pathgrid* pathgrid = world.getStore().get<ESM::Pathgrid>().search(cell);
|
||||
const PathgridGraph& pathgridGraph = getPathGridGraph(pathgrid);
|
||||
|
||||
if (mPathFinder.isPathConstructed())
|
||||
const Misc::CoordinateConverter converter = Misc::makeCoordinateConverter(cell);
|
||||
std::deque<ESM::Pathgrid::Point> path
|
||||
= pathgridGraph.aStarSearch(Misc::getClosestPoint(*pathgrid, converter.toLocalVec3(start)),
|
||||
Misc::getClosestPoint(*pathgrid, converter.toLocalVec3(randomAllowedPosition)));
|
||||
|
||||
// Choose a different position and delete this one from possible positions because it is uncreachable:
|
||||
if (path.empty())
|
||||
{
|
||||
mDestination = destVec3f;
|
||||
mHasDestination = true;
|
||||
mUsePathgrid = true;
|
||||
// Remove this node as an option and add back the previously used node (stops NPC from picking the same
|
||||
// node):
|
||||
ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode];
|
||||
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode);
|
||||
// check if mCurrentNode was taken out of mAllowedNodes
|
||||
if (storage.mTrimCurrentNode && storage.mAllowedNodes.size() > 1)
|
||||
storage.mTrimCurrentNode = false;
|
||||
else
|
||||
storage.mAllowedNodes.push_back(storage.mCurrentNode);
|
||||
storage.mCurrentNode = temp;
|
||||
|
||||
storage.setState(AiWanderStorage::Wander_Walking);
|
||||
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + randomAllowedPositionIndex);
|
||||
return;
|
||||
}
|
||||
// Choose a different node and delete this one from possible nodes because it is uncreachable:
|
||||
|
||||
// Drop nearest pathgrid point.
|
||||
path.pop_front();
|
||||
|
||||
std::vector<osg::Vec3f> checkpoints(path.size());
|
||||
for (std::size_t i = 0; i < path.size(); ++i)
|
||||
checkpoints[i] = Misc::Convert::makeOsgVec3f(converter.toWorldPoint(path[i]));
|
||||
|
||||
const DetourNavigator::AgentBounds agentBounds = world.getPathfindingAgentBounds(actor);
|
||||
const DetourNavigator::Flags flags = getNavigatorFlags(actor);
|
||||
const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, flags);
|
||||
constexpr float endTolerance = 0;
|
||||
mPathFinder.buildPath(actor, start, randomAllowedPosition, pathgridGraph, agentBounds, flags, areaCosts,
|
||||
endTolerance, PathType::Full, checkpoints);
|
||||
|
||||
if (!mPathFinder.isPathConstructed())
|
||||
{
|
||||
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + randomAllowedPositionIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
mDestination = randomAllowedPosition;
|
||||
mHasDestination = true;
|
||||
mUsePathgrid = true;
|
||||
// Remove this position as an option and add back the previously used position (stops NPC from picking the
|
||||
// same position):
|
||||
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + randomAllowedPositionIndex);
|
||||
// check if mCurrentPosition was taken out of mAllowedPositions
|
||||
if (storage.mTrimCurrentPosition && storage.mAllowedPositions.size() > 1)
|
||||
storage.mTrimCurrentPosition = false;
|
||||
else
|
||||
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode);
|
||||
}
|
||||
storage.mAllowedPositions.push_back(storage.mCurrentPosition);
|
||||
storage.mCurrentPosition = randomAllowedPosition;
|
||||
|
||||
void AiWander::trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes, const PathFinder& pathfinder)
|
||||
{
|
||||
// TODO: how to add these back in once the door opens?
|
||||
// Idea: keep a list of detected closed doors (see aicombat.cpp)
|
||||
// Every now and then check whether one of the doors is opened. (maybe
|
||||
// at the end of playing idle?) If the door is opened then re-calculate
|
||||
// allowed nodes starting from the spawn point.
|
||||
auto paths = pathfinder.getPath();
|
||||
while (paths.size() >= 2)
|
||||
{
|
||||
const auto pt = paths.back();
|
||||
for (unsigned int j = 0; j < nodes.size(); j++)
|
||||
{
|
||||
// FIXME: doesn't handle a door with the same X/Y
|
||||
// coordinates but with a different Z
|
||||
if (std::abs(nodes[j].mX - pt.x()) <= 0.5 && std::abs(nodes[j].mY - pt.y()) <= 0.5)
|
||||
{
|
||||
nodes.erase(nodes.begin() + j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
paths.pop_back();
|
||||
}
|
||||
storage.setState(AiWanderStorage::Wander_Walking);
|
||||
}
|
||||
|
||||
void AiWander::stopWalking(const MWWorld::Ptr& actor)
|
||||
@ -742,20 +758,20 @@ namespace MWMechanics
|
||||
return;
|
||||
|
||||
AiWanderStorage& storage = state.get<AiWanderStorage>();
|
||||
if (storage.mPopulateAvailableNodes)
|
||||
getAllowedNodes(actor, storage);
|
||||
if (storage.mPopulateAvailablePositions)
|
||||
fillAllowedPositions(actor, storage);
|
||||
|
||||
if (storage.mAllowedNodes.empty())
|
||||
if (storage.mAllowedPositions.empty())
|
||||
return;
|
||||
|
||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
||||
int index = Misc::Rng::rollDice(storage.mAllowedNodes.size(), prng);
|
||||
ESM::Pathgrid::Point worldDest = storage.mAllowedNodes[index];
|
||||
int index = Misc::Rng::rollDice(storage.mAllowedPositions.size(), prng);
|
||||
const osg::Vec3f worldDest = storage.mAllowedPositions[index];
|
||||
const Misc::CoordinateConverter converter = Misc::makeCoordinateConverter(*actor.getCell()->getCell());
|
||||
ESM::Pathgrid::Point dest = converter.toLocalPoint(worldDest);
|
||||
osg::Vec3f dest = converter.toLocalVec3(worldDest);
|
||||
|
||||
bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(
|
||||
PathFinder::makeOsgVec3(worldDest), 60);
|
||||
const bool isPathGridOccupied
|
||||
= MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(worldDest, 60);
|
||||
|
||||
// add offset only if the selected pathgrid is occupied by another actor
|
||||
if (isPathGridOccupied)
|
||||
@ -775,19 +791,17 @@ namespace MWMechanics
|
||||
const ESM::Pathgrid::Point& connDest = points[randomIndex];
|
||||
|
||||
// add an offset towards random neighboring node
|
||||
osg::Vec3f dir = PathFinder::makeOsgVec3(connDest) - PathFinder::makeOsgVec3(dest);
|
||||
float length = dir.length();
|
||||
osg::Vec3f dir = Misc::Convert::makeOsgVec3f(connDest) - dest;
|
||||
const float length = dir.length();
|
||||
dir.normalize();
|
||||
|
||||
for (int j = 1; j <= 3; j++)
|
||||
{
|
||||
// move for 5-15% towards random neighboring node
|
||||
dest
|
||||
= PathFinder::makePathgridPoint(PathFinder::makeOsgVec3(dest) + dir * (j * 5 * length / 100.f));
|
||||
worldDest = converter.toWorldPoint(dest);
|
||||
dest = dest + dir * (j * 5 * length / 100.f);
|
||||
|
||||
isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(
|
||||
PathFinder::makeOsgVec3(worldDest), 60);
|
||||
converter.toWorldVec3(dest), 60);
|
||||
|
||||
if (!isOccupied)
|
||||
break;
|
||||
@ -807,19 +821,18 @@ namespace MWMechanics
|
||||
|
||||
// place above to prevent moving inside objects, e.g. stairs, because a vector between pathgrids can be
|
||||
// underground. Adding 20 in adjustPosition() is not enough.
|
||||
dest.mZ += 60;
|
||||
dest.z() += 60;
|
||||
|
||||
converter.toWorld(dest);
|
||||
|
||||
state.reset();
|
||||
|
||||
osg::Vec3f pos(static_cast<float>(dest.mX), static_cast<float>(dest.mY), static_cast<float>(dest.mZ));
|
||||
MWBase::Environment::get().getWorld()->moveObject(actor, pos);
|
||||
MWBase::Environment::get().getWorld()->moveObject(actor, dest);
|
||||
actor.getClass().adjustPosition(actor, false);
|
||||
}
|
||||
|
||||
void AiWander::getNeighbouringNodes(
|
||||
ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points)
|
||||
const osg::Vec3f& dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points)
|
||||
{
|
||||
const ESM::Pathgrid* pathgrid
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*currentCell->getCell());
|
||||
@ -827,19 +840,19 @@ namespace MWMechanics
|
||||
if (pathgrid == nullptr || pathgrid->mPoints.empty())
|
||||
return;
|
||||
|
||||
size_t index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest));
|
||||
const size_t index = Misc::getClosestPoint(*pathgrid, dest);
|
||||
|
||||
getPathGridGraph(pathgrid).getNeighbouringPoints(index, points);
|
||||
}
|
||||
|
||||
void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
||||
void AiWander::fillAllowedPositions(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
||||
{
|
||||
// infrequently used, therefore no benefit in caching it as a member
|
||||
const MWWorld::CellStore* cellStore = actor.getCell();
|
||||
const ESM::Pathgrid* pathgrid
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::Pathgrid>().search(*cellStore->getCell());
|
||||
|
||||
storage.mAllowedNodes.clear();
|
||||
storage.mAllowedPositions.clear();
|
||||
|
||||
// If there is no path this actor doesn't go anywhere. See:
|
||||
// https://forum.openmw.org/viewtopic.php?t=1556
|
||||
@ -860,34 +873,35 @@ namespace MWMechanics
|
||||
const osg::Vec3f npcPos = converter.toLocalVec3(mInitialActorPosition);
|
||||
|
||||
// Find closest pathgrid point
|
||||
size_t closestPointIndex = PathFinder::getClosestPoint(pathgrid, npcPos);
|
||||
const std::size_t closestPointIndex = Misc::getClosestPoint(*pathgrid, npcPos);
|
||||
|
||||
// mAllowedNodes for this actor with pathgrid point indexes based on mDistance
|
||||
// mAllowedPositions for this actor with pathgrid point indexes based on mDistance
|
||||
// and if the point is connected to the closest current point
|
||||
// NOTE: mPoints is in local coordinates
|
||||
size_t pointIndex = 0;
|
||||
for (size_t counter = 0; counter < pathgrid->mPoints.size(); counter++)
|
||||
{
|
||||
osg::Vec3f nodePos(PathFinder::makeOsgVec3(pathgrid->mPoints[counter]));
|
||||
const osg::Vec3f nodePos = Misc::Convert::makeOsgVec3f(pathgrid->mPoints[counter]);
|
||||
if ((npcPos - nodePos).length2() <= mDistance * mDistance
|
||||
&& getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, counter))
|
||||
{
|
||||
storage.mAllowedNodes.push_back(converter.toWorldPoint(pathgrid->mPoints[counter]));
|
||||
storage.mAllowedPositions.push_back(
|
||||
Misc::Convert::makeOsgVec3f(converter.toWorldPoint(pathgrid->mPoints[counter])));
|
||||
pointIndex = counter;
|
||||
}
|
||||
}
|
||||
if (storage.mAllowedNodes.size() == 1)
|
||||
if (storage.mAllowedPositions.size() == 1)
|
||||
{
|
||||
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(mInitialActorPosition));
|
||||
storage.mAllowedPositions.push_back(mInitialActorPosition);
|
||||
addNonPathGridAllowedPoints(pathgrid, pointIndex, storage, converter);
|
||||
}
|
||||
if (!storage.mAllowedNodes.empty())
|
||||
if (!storage.mAllowedPositions.empty())
|
||||
{
|
||||
setCurrentNodeToClosestAllowedNode(storage);
|
||||
setCurrentPositionToClosestAllowedPosition(storage);
|
||||
}
|
||||
}
|
||||
|
||||
storage.mPopulateAvailableNodes = false;
|
||||
storage.mPopulateAvailablePositions = false;
|
||||
}
|
||||
|
||||
// When only one path grid point in wander distance,
|
||||
@ -901,44 +915,44 @@ namespace MWMechanics
|
||||
{
|
||||
if (edge.mV0 == pointIndex)
|
||||
{
|
||||
AddPointBetweenPathGridPoints(converter.toWorldPoint(pathGrid->mPoints[edge.mV0]),
|
||||
addPositionBetweenPathgridPoints(converter.toWorldPoint(pathGrid->mPoints[edge.mV0]),
|
||||
converter.toWorldPoint(pathGrid->mPoints[edge.mV1]), storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AiWander::AddPointBetweenPathGridPoints(
|
||||
void AiWander::addPositionBetweenPathgridPoints(
|
||||
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage)
|
||||
{
|
||||
osg::Vec3f vectorStart = PathFinder::makeOsgVec3(start);
|
||||
osg::Vec3f delta = PathFinder::makeOsgVec3(end) - vectorStart;
|
||||
osg::Vec3f vectorStart = Misc::Convert::makeOsgVec3f(start);
|
||||
osg::Vec3f delta = Misc::Convert::makeOsgVec3f(end) - vectorStart;
|
||||
float length = delta.length();
|
||||
delta.normalize();
|
||||
|
||||
int distance = std::max(mDistance / 2, MINIMUM_WANDER_DISTANCE);
|
||||
unsigned distance = std::max(mDistance / 2, minimumWanderDistance);
|
||||
|
||||
// must not travel longer than distance between waypoints or NPC goes past waypoint
|
||||
distance = std::min(distance, static_cast<int>(length));
|
||||
distance = std::min(distance, static_cast<unsigned>(length));
|
||||
delta *= distance;
|
||||
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(vectorStart + delta));
|
||||
storage.mAllowedPositions.push_back(vectorStart + delta);
|
||||
}
|
||||
|
||||
void AiWander::setCurrentNodeToClosestAllowedNode(AiWanderStorage& storage)
|
||||
void AiWander::setCurrentPositionToClosestAllowedPosition(AiWanderStorage& storage)
|
||||
{
|
||||
float distanceToClosestNode = std::numeric_limits<float>::max();
|
||||
float distanceToClosestPosition = std::numeric_limits<float>::max();
|
||||
size_t index = 0;
|
||||
for (size_t i = 0; i < storage.mAllowedNodes.size(); ++i)
|
||||
for (size_t i = 0; i < storage.mAllowedPositions.size(); ++i)
|
||||
{
|
||||
osg::Vec3f nodePos(PathFinder::makeOsgVec3(storage.mAllowedNodes[i]));
|
||||
float tempDist = (mInitialActorPosition - nodePos).length2();
|
||||
if (tempDist < distanceToClosestNode)
|
||||
const osg::Vec3f position = storage.mAllowedPositions[i];
|
||||
const float tempDist = (mInitialActorPosition - position).length2();
|
||||
if (tempDist < distanceToClosestPosition)
|
||||
{
|
||||
index = i;
|
||||
distanceToClosestNode = tempDist;
|
||||
distanceToClosestPosition = tempDist;
|
||||
}
|
||||
}
|
||||
storage.mCurrentNode = storage.mAllowedNodes[index];
|
||||
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + index);
|
||||
storage.mCurrentPosition = storage.mAllowedPositions[index];
|
||||
storage.mAllowedPositions.erase(storage.mAllowedPositions.begin() + index);
|
||||
}
|
||||
|
||||
void AiWander::writeState(ESM::AiSequence::AiSequence& sequence) const
|
||||
@ -970,8 +984,8 @@ namespace MWMechanics
|
||||
|
||||
AiWander::AiWander(const ESM::AiSequence::AiWander* wander)
|
||||
: TypedAiPackage<AiWander>(makeDefaultOptions().withRepeat(wander->mData.mShouldRepeat != 0))
|
||||
, mDistance(std::max(static_cast<short>(0), wander->mData.mDistance))
|
||||
, mDuration(std::max(static_cast<short>(0), wander->mData.mDuration))
|
||||
, mDistance(static_cast<unsigned>(std::max(static_cast<short>(0), wander->mData.mDistance)))
|
||||
, mDuration(static_cast<unsigned>(std::max(static_cast<short>(0), wander->mData.mDuration)))
|
||||
, mRemainingDuration(wander->mDurationData.mRemainingDuration)
|
||||
, mTimeOfDay(wander->mData.mTimeOfDay)
|
||||
, mIdle(getInitialIdle(wander->mData.mIdle))
|
||||
|
@ -1,14 +1,15 @@
|
||||
#ifndef GAME_MWMECHANICS_AIWANDER_H
|
||||
#define GAME_MWMECHANICS_AIWANDER_H
|
||||
|
||||
#include "typedaipackage.hpp"
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "aitemporarybase.hpp"
|
||||
#include "aitimer.hpp"
|
||||
#include "pathfinding.hpp"
|
||||
#include "typedaipackage.hpp"
|
||||
|
||||
#include <components/esm3/loadpgrd.hpp>
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
@ -51,14 +52,13 @@ namespace MWMechanics
|
||||
unsigned short mIdleAnimation;
|
||||
std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors
|
||||
|
||||
// do we need to calculate allowed nodes based on mDistance
|
||||
bool mPopulateAvailableNodes;
|
||||
bool mPopulateAvailablePositions;
|
||||
|
||||
// allowed pathgrid nodes based on mDistance from the spawn point
|
||||
std::vector<ESM::Pathgrid::Point> mAllowedNodes;
|
||||
// allowed destination positions based on mDistance from the spawn point
|
||||
std::vector<osg::Vec3f> mAllowedPositions;
|
||||
|
||||
ESM::Pathgrid::Point mCurrentNode;
|
||||
bool mTrimCurrentNode;
|
||||
osg::Vec3f mCurrentPosition;
|
||||
bool mTrimCurrentPosition;
|
||||
|
||||
float mCheckIdlePositionTimer;
|
||||
int mStuckCount;
|
||||
@ -132,7 +132,8 @@ namespace MWMechanics
|
||||
bool playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
||||
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
|
||||
int getRandomIdle() const;
|
||||
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
||||
void setPathToAnAllowedPosition(
|
||||
const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
||||
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration,
|
||||
MWWorld::MovementDirectionFlags supportedMovementDirections, AiWanderStorage& storage);
|
||||
@ -145,28 +146,27 @@ namespace MWMechanics
|
||||
void wanderNearStart(const MWWorld::Ptr& actor, AiWanderStorage& storage, int wanderDistance);
|
||||
bool destinationIsAtWater(const MWWorld::Ptr& actor, const osg::Vec3f& destination);
|
||||
void completeManualWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
bool isNearAllowedNode(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const;
|
||||
bool isNearAllowedPosition(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const;
|
||||
|
||||
const int mDistance; // how far the actor can wander from the spawn point
|
||||
const int mDuration;
|
||||
// how far the actor can wander from the spawn point
|
||||
const unsigned mDistance;
|
||||
const unsigned mDuration;
|
||||
float mRemainingDuration;
|
||||
const int mTimeOfDay;
|
||||
const std::vector<unsigned char> mIdle;
|
||||
|
||||
bool mStoredInitialActorPosition;
|
||||
osg::Vec3f
|
||||
mInitialActorPosition; // Note: an original engine does not reset coordinates even when actor changes a cell
|
||||
// Note: an original engine does not reset coordinates even when actor changes a cell
|
||||
osg::Vec3f mInitialActorPosition;
|
||||
|
||||
bool mHasDestination;
|
||||
osg::Vec3f mDestination;
|
||||
bool mUsePathgrid;
|
||||
|
||||
void getNeighbouringNodes(
|
||||
ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);
|
||||
const osg::Vec3f& dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);
|
||||
|
||||
void getAllowedNodes(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
|
||||
void trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes, const PathFinder& pathfinder);
|
||||
void fillAllowedPositions(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
|
||||
// constants for converting idleSelect values into groupNames
|
||||
enum GroupIndex
|
||||
@ -175,12 +175,12 @@ namespace MWMechanics
|
||||
GroupIndex_MaxIdle = 9
|
||||
};
|
||||
|
||||
void setCurrentNodeToClosestAllowedNode(AiWanderStorage& storage);
|
||||
void setCurrentPositionToClosestAllowedPosition(AiWanderStorage& storage);
|
||||
|
||||
void addNonPathGridAllowedPoints(const ESM::Pathgrid* pathGrid, size_t pointIndex, AiWanderStorage& storage,
|
||||
const Misc::CoordinateConverter& converter);
|
||||
|
||||
void AddPointBetweenPathGridPoints(
|
||||
void addPositionBetweenPathgridPoints(
|
||||
const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage);
|
||||
|
||||
/// lookup table for converting idleSelect value to groupName
|
||||
|
@ -106,21 +106,6 @@ namespace MWMechanics
|
||||
return visitor.mResult;
|
||||
}
|
||||
|
||||
bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, bool ignorePlayer,
|
||||
std::vector<MWWorld::Ptr>* occupyingActors)
|
||||
{
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
const osg::Vec3f halfExtents = world->getPathfindingAgentBounds(actor).mHalfExtents;
|
||||
const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z()));
|
||||
if (ignorePlayer)
|
||||
{
|
||||
const std::array ignore{ actor, world->getPlayerConstPtr() };
|
||||
return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, ignore, occupyingActors);
|
||||
}
|
||||
const std::array ignore{ actor };
|
||||
return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, ignore, occupyingActors);
|
||||
}
|
||||
|
||||
ObstacleCheck::ObstacleCheck()
|
||||
: mEvadeDirectionIndex(std::size(evadeDirections) - 1)
|
||||
{
|
||||
|
@ -5,8 +5,6 @@
|
||||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
@ -24,9 +22,6 @@ namespace MWMechanics
|
||||
/** \return Pointer to the door, or empty pointer if none exists **/
|
||||
const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist);
|
||||
|
||||
bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination,
|
||||
bool ignorePlayer = false, std::vector<MWWorld::Ptr>* occupyingActors = nullptr);
|
||||
|
||||
class ObstacleCheck
|
||||
{
|
||||
public:
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <components/detournavigator/navigatorutils.hpp>
|
||||
#include <components/misc/coordinateconverter.hpp>
|
||||
#include <components/misc/math.hpp>
|
||||
#include <components/misc/pathgridutils.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
@ -38,7 +39,7 @@ namespace
|
||||
// points to a quadtree may help
|
||||
for (size_t counter = 0; counter < grid->mPoints.size(); counter++)
|
||||
{
|
||||
float potentialDistBetween = MWMechanics::PathFinder::distanceSquared(grid->mPoints[counter], pos);
|
||||
float potentialDistBetween = Misc::distanceSquared(grid->mPoints[counter], pos);
|
||||
if (potentialDistBetween < closestDistanceReachable)
|
||||
{
|
||||
// found a closer one
|
||||
@ -197,7 +198,7 @@ namespace MWMechanics
|
||||
// point right behind the wall that is closer than any pathgrid
|
||||
// point outside the wall
|
||||
osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint));
|
||||
size_t startNode = getClosestPoint(pathgrid, startPointInLocalCoords);
|
||||
const size_t startNode = Misc::getClosestPoint(*pathgrid, startPointInLocalCoords);
|
||||
|
||||
osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint));
|
||||
std::pair<size_t, bool> endNode
|
||||
@ -206,8 +207,8 @@ namespace MWMechanics
|
||||
// if it's shorter for actor to travel from start to end, than to travel from either
|
||||
// start or end to nearest pathgrid point, just travel from start to end.
|
||||
float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2();
|
||||
float endTolastNodeLength2 = distanceSquared(pathgrid->mPoints[endNode.first], endPointInLocalCoords);
|
||||
float startTo1stNodeLength2 = distanceSquared(pathgrid->mPoints[startNode], startPointInLocalCoords);
|
||||
float endTolastNodeLength2 = Misc::distanceSquared(pathgrid->mPoints[endNode.first], endPointInLocalCoords);
|
||||
float startTo1stNodeLength2 = Misc::distanceSquared(pathgrid->mPoints[startNode], startPointInLocalCoords);
|
||||
if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2))
|
||||
{
|
||||
*out++ = endPoint;
|
||||
@ -223,7 +224,7 @@ namespace MWMechanics
|
||||
{
|
||||
ESM::Pathgrid::Point temp(pathgrid->mPoints[startNode]);
|
||||
converter.toWorld(temp);
|
||||
*out++ = makeOsgVec3(temp);
|
||||
*out++ = Misc::Convert::makeOsgVec3f(temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -234,8 +235,8 @@ namespace MWMechanics
|
||||
if (path.size() > 1)
|
||||
{
|
||||
ESM::Pathgrid::Point secondNode = *(++path.begin());
|
||||
osg::Vec3f firstNodeVec3f = makeOsgVec3(pathgrid->mPoints[startNode]);
|
||||
osg::Vec3f secondNodeVec3f = makeOsgVec3(secondNode);
|
||||
osg::Vec3f firstNodeVec3f = Misc::Convert::makeOsgVec3f(pathgrid->mPoints[startNode]);
|
||||
osg::Vec3f secondNodeVec3f = Misc::Convert::makeOsgVec3f(secondNode);
|
||||
osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f;
|
||||
osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f;
|
||||
if (toSecondNodeVec3f * toStartPointVec3f > 0)
|
||||
@ -259,7 +260,7 @@ namespace MWMechanics
|
||||
// convert supplied path to world coordinates
|
||||
std::transform(path.begin(), path.end(), out, [&](ESM::Pathgrid::Point& point) {
|
||||
converter.toWorld(point);
|
||||
return makeOsgVec3(point);
|
||||
return Misc::Convert::makeOsgVec3f(point);
|
||||
});
|
||||
}
|
||||
|
||||
@ -359,26 +360,16 @@ namespace MWMechanics
|
||||
mConstructed = true;
|
||||
}
|
||||
|
||||
void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
|
||||
{
|
||||
mPath.clear();
|
||||
mCell = cell;
|
||||
|
||||
buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath));
|
||||
|
||||
mConstructed = !mPath.empty();
|
||||
}
|
||||
|
||||
void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||
const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType)
|
||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType,
|
||||
std::span<const osg::Vec3f> checkpoints)
|
||||
{
|
||||
mPath.clear();
|
||||
|
||||
// If it's not possible to build path over navmesh due to disabled navmesh generation fallback to straight path
|
||||
DetourNavigator::Status status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds, flags,
|
||||
areaCosts, endTolerance, pathType, std::back_inserter(mPath));
|
||||
areaCosts, endTolerance, pathType, checkpoints, std::back_inserter(mPath));
|
||||
|
||||
if (status != DetourNavigator::Status::Success)
|
||||
mPath.clear();
|
||||
@ -390,19 +381,19 @@ namespace MWMechanics
|
||||
}
|
||||
|
||||
void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType)
|
||||
const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||
PathType pathType, std::span<const osg::Vec3f> checkpoints)
|
||||
{
|
||||
mPath.clear();
|
||||
mCell = cell;
|
||||
mCell = actor.getCell();
|
||||
|
||||
DetourNavigator::Status status = DetourNavigator::Status::NavMeshNotFound;
|
||||
|
||||
if (!actor.getClass().isPureWaterCreature(actor) && !actor.getClass().isPureFlyingCreature(actor))
|
||||
{
|
||||
status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds, flags, areaCosts, endTolerance,
|
||||
pathType, std::back_inserter(mPath));
|
||||
pathType, checkpoints, std::back_inserter(mPath));
|
||||
if (status != DetourNavigator::Status::Success)
|
||||
mPath.clear();
|
||||
}
|
||||
@ -411,7 +402,7 @@ namespace MWMechanics
|
||||
&& (flags & DetourNavigator::Flag_usePathgrid) == 0)
|
||||
{
|
||||
status = buildPathByNavigatorImpl(actor, startPoint, endPoint, agentBounds,
|
||||
flags | DetourNavigator::Flag_usePathgrid, areaCosts, endTolerance, pathType,
|
||||
flags | DetourNavigator::Flag_usePathgrid, areaCosts, endTolerance, pathType, checkpoints,
|
||||
std::back_inserter(mPath));
|
||||
if (status != DetourNavigator::Status::Success)
|
||||
mPath.clear();
|
||||
@ -429,12 +420,13 @@ namespace MWMechanics
|
||||
DetourNavigator::Status PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor,
|
||||
const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||
PathType pathType, std::back_insert_iterator<std::deque<osg::Vec3f>> out)
|
||||
PathType pathType, std::span<const osg::Vec3f> checkpoints,
|
||||
std::back_insert_iterator<std::deque<osg::Vec3f>> out)
|
||||
{
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
const auto navigator = world->getNavigator();
|
||||
const auto status = DetourNavigator::findPath(
|
||||
*navigator, agentBounds, startPoint, endPoint, flags, areaCosts, endTolerance, out);
|
||||
const MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||
const DetourNavigator::Navigator& navigator = *world.getNavigator();
|
||||
const DetourNavigator::Status status = DetourNavigator::findPath(
|
||||
navigator, agentBounds, startPoint, endPoint, flags, areaCosts, endTolerance, checkpoints, out);
|
||||
|
||||
if (pathType == PathType::Partial && status == DetourNavigator::Status::PartialPath)
|
||||
return DetourNavigator::Status::Success;
|
||||
@ -451,9 +443,9 @@ namespace MWMechanics
|
||||
}
|
||||
|
||||
void PathFinder::buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||
const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType)
|
||||
const osg::Vec3f& endPoint, const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||
PathType pathType)
|
||||
{
|
||||
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
||||
const auto maxDistance
|
||||
@ -461,9 +453,9 @@ namespace MWMechanics
|
||||
const auto startToEnd = endPoint - startPoint;
|
||||
const auto distance = startToEnd.length();
|
||||
if (distance <= maxDistance)
|
||||
return buildPath(actor, startPoint, endPoint, cell, pathgridGraph, agentBounds, flags, areaCosts,
|
||||
endTolerance, pathType);
|
||||
return buildPath(
|
||||
actor, startPoint, endPoint, pathgridGraph, agentBounds, flags, areaCosts, endTolerance, pathType);
|
||||
const auto end = startPoint + startToEnd * maxDistance / distance;
|
||||
buildPath(actor, startPoint, end, cell, pathgridGraph, agentBounds, flags, areaCosts, endTolerance, pathType);
|
||||
buildPath(actor, startPoint, end, pathgridGraph, agentBounds, flags, areaCosts, endTolerance, pathType);
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,13 @@
|
||||
#include <cassert>
|
||||
#include <deque>
|
||||
#include <iterator>
|
||||
#include <span>
|
||||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
#include <components/detournavigator/areatype.hpp>
|
||||
#include <components/detournavigator/flags.hpp>
|
||||
#include <components/detournavigator/status.hpp>
|
||||
#include <components/esm/position.hpp>
|
||||
#include <components/esm3/loadpgrd.hpp>
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
@ -102,23 +103,20 @@ namespace MWMechanics
|
||||
|
||||
void buildStraightPath(const osg::Vec3f& endPoint);
|
||||
|
||||
void buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
|
||||
|
||||
void buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||
const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||
PathType pathType);
|
||||
PathType pathType, std::span<const osg::Vec3f> checkpoints = {});
|
||||
|
||||
void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType);
|
||||
const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||
PathType pathType, std::span<const osg::Vec3f> checkpoints = {});
|
||||
|
||||
void buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph,
|
||||
const DetourNavigator::AgentBounds& agentBounds, const DetourNavigator::Flags flags,
|
||||
const DetourNavigator::AreaCosts& areaCosts, float endTolerance, PathType pathType);
|
||||
const PathgridGraph& pathgridGraph, const DetourNavigator::AgentBounds& agentBounds,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||
PathType pathType);
|
||||
|
||||
/// Remove front point if exist and within tolerance
|
||||
void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
|
||||
@ -145,61 +143,6 @@ namespace MWMechanics
|
||||
mPath.push_back(point);
|
||||
}
|
||||
|
||||
/// utility function to convert a osg::Vec3f to a Pathgrid::Point
|
||||
static ESM::Pathgrid::Point makePathgridPoint(const osg::Vec3f& v)
|
||||
{
|
||||
return ESM::Pathgrid::Point(static_cast<int>(v[0]), static_cast<int>(v[1]), static_cast<int>(v[2]));
|
||||
}
|
||||
|
||||
/// utility function to convert an ESM::Position to a Pathgrid::Point
|
||||
static ESM::Pathgrid::Point makePathgridPoint(const ESM::Position& p)
|
||||
{
|
||||
return ESM::Pathgrid::Point(
|
||||
static_cast<int>(p.pos[0]), static_cast<int>(p.pos[1]), static_cast<int>(p.pos[2]));
|
||||
}
|
||||
|
||||
static osg::Vec3f makeOsgVec3(const ESM::Pathgrid::Point& p)
|
||||
{
|
||||
return osg::Vec3f(static_cast<float>(p.mX), static_cast<float>(p.mY), static_cast<float>(p.mZ));
|
||||
}
|
||||
|
||||
// Slightly cheaper version for comparisons.
|
||||
// Caller needs to be careful for very short distances (i.e. less than 1)
|
||||
// or when accumuating the results i.e. (a + b)^2 != a^2 + b^2
|
||||
//
|
||||
static float distanceSquared(const ESM::Pathgrid::Point& point, const osg::Vec3f& pos)
|
||||
{
|
||||
return (MWMechanics::PathFinder::makeOsgVec3(point) - pos).length2();
|
||||
}
|
||||
|
||||
// Return the closest pathgrid point index from the specified position
|
||||
// coordinates. NOTE: Does not check if there is a sensible way to get there
|
||||
// (e.g. a cliff in front).
|
||||
//
|
||||
// NOTE: pos is expected to be in local coordinates, as is grid->mPoints
|
||||
//
|
||||
static size_t getClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos)
|
||||
{
|
||||
assert(grid && !grid->mPoints.empty());
|
||||
|
||||
float distanceBetween = distanceSquared(grid->mPoints[0], pos);
|
||||
size_t closestIndex = 0;
|
||||
|
||||
// TODO: if this full scan causes performance problems mapping pathgrid
|
||||
// points to a quadtree may help
|
||||
for (size_t counter = 1; counter < grid->mPoints.size(); counter++)
|
||||
{
|
||||
float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos);
|
||||
if (potentialDistBetween < distanceBetween)
|
||||
{
|
||||
distanceBetween = potentialDistBetween;
|
||||
closestIndex = counter;
|
||||
}
|
||||
}
|
||||
|
||||
return closestIndex;
|
||||
}
|
||||
|
||||
private:
|
||||
bool mConstructed = false;
|
||||
std::deque<osg::Vec3f> mPath;
|
||||
@ -211,7 +154,8 @@ namespace MWMechanics
|
||||
[[nodiscard]] DetourNavigator::Status buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor,
|
||||
const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const DetourNavigator::AgentBounds& agentBounds,
|
||||
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, float endTolerance,
|
||||
PathType pathType, std::back_insert_iterator<std::deque<osg::Vec3f>> out);
|
||||
PathType pathType, std::span<const osg::Vec3f> checkpoints,
|
||||
std::back_insert_iterator<std::deque<osg::Vec3f>> out);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
namespace MWPhysics
|
||||
{
|
||||
// https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection
|
||||
bool testAabbAgainstSphere(
|
||||
inline bool testAabbAgainstSphere(
|
||||
const btVector3& aabbMin, const btVector3& aabbMax, const btVector3& position, const btScalar radius)
|
||||
{
|
||||
const btVector3 nearest(std::clamp(position.x(), aabbMin.x(), aabbMax.x()),
|
||||
@ -18,35 +18,28 @@ namespace MWPhysics
|
||||
return nearest.distance(position) < radius;
|
||||
}
|
||||
|
||||
template <class Ignore, class OnCollision>
|
||||
class HasSphereCollisionCallback final : public btBroadphaseAabbCallback
|
||||
{
|
||||
public:
|
||||
HasSphereCollisionCallback(const btVector3& position, const btScalar radius, const int mask, const int group,
|
||||
const Ignore& ignore, OnCollision* onCollision)
|
||||
explicit HasSphereCollisionCallback(const btVector3& position, const btScalar radius, const int mask,
|
||||
const int group, const btCollisionObject* ignore)
|
||||
: mPosition(position)
|
||||
, mRadius(radius)
|
||||
, mIgnore(ignore)
|
||||
, mCollisionFilterMask(mask)
|
||||
, mCollisionFilterGroup(group)
|
||||
, mOnCollision(onCollision)
|
||||
{
|
||||
}
|
||||
|
||||
bool process(const btBroadphaseProxy* proxy) override
|
||||
{
|
||||
if (mResult && mOnCollision == nullptr)
|
||||
if (mResult)
|
||||
return false;
|
||||
const auto collisionObject = static_cast<btCollisionObject*>(proxy->m_clientObject);
|
||||
if (mIgnore(collisionObject) || !needsCollision(*proxy)
|
||||
if (mIgnore == collisionObject || !needsCollision(*proxy)
|
||||
|| !testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius))
|
||||
return true;
|
||||
mResult = true;
|
||||
if (mOnCollision != nullptr)
|
||||
{
|
||||
(*mOnCollision)(collisionObject);
|
||||
return true;
|
||||
}
|
||||
return !mResult;
|
||||
}
|
||||
|
||||
@ -55,10 +48,9 @@ namespace MWPhysics
|
||||
private:
|
||||
btVector3 mPosition;
|
||||
btScalar mRadius;
|
||||
Ignore mIgnore;
|
||||
const btCollisionObject* mIgnore;
|
||||
int mCollisionFilterMask;
|
||||
int mCollisionFilterGroup;
|
||||
OnCollision* mOnCollision;
|
||||
bool mResult = false;
|
||||
|
||||
bool needsCollision(const btBroadphaseProxy& proxy) const
|
||||
|
@ -849,36 +849,18 @@ namespace MWPhysics
|
||||
mWaterCollisionObject.get(), CollisionType_Water, CollisionType_Actor | CollisionType_Projectile);
|
||||
}
|
||||
|
||||
bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) const
|
||||
bool PhysicsSystem::isAreaOccupiedByOtherActor(
|
||||
const MWWorld::LiveCellRefBase* actor, const osg::Vec3f& position, const float radius) const
|
||||
{
|
||||
std::vector<const btCollisionObject*> ignoredObjects;
|
||||
ignoredObjects.reserve(ignore.size());
|
||||
for (const auto& v : ignore)
|
||||
if (const auto it = mActors.find(v.mRef); it != mActors.end())
|
||||
ignoredObjects.push_back(it->second->getCollisionObject());
|
||||
std::sort(ignoredObjects.begin(), ignoredObjects.end());
|
||||
ignoredObjects.erase(std::unique(ignoredObjects.begin(), ignoredObjects.end()), ignoredObjects.end());
|
||||
const auto ignoreFilter = [&](const btCollisionObject* v) {
|
||||
return std::binary_search(ignoredObjects.begin(), ignoredObjects.end(), v);
|
||||
};
|
||||
const auto bulletPosition = Misc::Convert::toBullet(position);
|
||||
const auto aabbMin = bulletPosition - btVector3(radius, radius, radius);
|
||||
const auto aabbMax = bulletPosition + btVector3(radius, radius, radius);
|
||||
const btCollisionObject* ignoredObject = nullptr;
|
||||
if (const auto it = mActors.find(actor); it != mActors.end())
|
||||
ignoredObject = it->second->getCollisionObject();
|
||||
const btVector3 bulletPosition = Misc::Convert::toBullet(position);
|
||||
const btVector3 aabbMin = bulletPosition - btVector3(radius, radius, radius);
|
||||
const btVector3 aabbMax = bulletPosition + btVector3(radius, radius, radius);
|
||||
const int mask = MWPhysics::CollisionType_Actor;
|
||||
const int group = MWPhysics::CollisionType_AnyPhysical;
|
||||
if (occupyingActors == nullptr)
|
||||
{
|
||||
HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter,
|
||||
static_cast<void (*)(const btCollisionObject*)>(nullptr));
|
||||
mTaskScheduler->aabbTest(aabbMin, aabbMax, callback);
|
||||
return callback.getResult();
|
||||
}
|
||||
const auto onCollision = [&](const btCollisionObject* object) {
|
||||
if (PtrHolder* holder = static_cast<PtrHolder*>(object->getUserPointer()))
|
||||
occupyingActors->push_back(holder->getPtr());
|
||||
};
|
||||
HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter, &onCollision);
|
||||
HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoredObject);
|
||||
mTaskScheduler->aabbTest(aabbMin, aabbMax, callback);
|
||||
return callback.getResult();
|
||||
}
|
||||
|
@ -281,8 +281,8 @@ namespace MWPhysics
|
||||
std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function);
|
||||
}
|
||||
|
||||
bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) const;
|
||||
bool isAreaOccupiedByOtherActor(
|
||||
const MWWorld::LiveCellRefBase* actor, const osg::Vec3f& position, float radius) const;
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
||||
void reportCollision(const btVector3& position, const btVector3& normal);
|
||||
|
@ -3871,10 +3871,11 @@ namespace MWWorld
|
||||
return btRayAabb(localFrom, localTo, aabbMin, aabbMax, hitDistance, hitNormal);
|
||||
}
|
||||
|
||||
bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius,
|
||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) const
|
||||
bool World::isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& position) const
|
||||
{
|
||||
return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore, occupyingActors);
|
||||
const osg::Vec3f halfExtents = getPathfindingAgentBounds(actor).mHalfExtents;
|
||||
const float maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z()));
|
||||
return mPhysics->isAreaOccupiedByOtherActor(actor.mRef, position, 2 * maxHalfExtent);
|
||||
}
|
||||
|
||||
void World::reportStats(unsigned int frameNumber, osg::Stats& stats) const
|
||||
|
@ -664,8 +664,7 @@ namespace MWWorld
|
||||
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,
|
||||
std::span<const MWWorld::ConstPtr> ignore, std::vector<MWWorld::Ptr>* occupyingActors) const override;
|
||||
bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& position) const override;
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const override;
|
||||
|
||||
|
@ -13,7 +13,6 @@
|
||||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
@ -64,6 +63,25 @@ namespace DetourNavigator
|
||||
std::reference_wrapper<const RecastSettings> mSettings;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class ToNavMeshCoordinatesSpan
|
||||
{
|
||||
public:
|
||||
explicit ToNavMeshCoordinatesSpan(std::span<T> span, const RecastSettings& settings)
|
||||
: mSpan(span)
|
||||
, mSettings(settings)
|
||||
{
|
||||
}
|
||||
|
||||
std::size_t size() const noexcept { return mSpan.size(); }
|
||||
|
||||
T operator[](std::size_t i) const noexcept { return toNavMeshCoordinates(mSettings, mSpan[i]); }
|
||||
|
||||
private:
|
||||
std::span<T> mSpan;
|
||||
const RecastSettings& mSettings;
|
||||
};
|
||||
|
||||
inline std::optional<std::size_t> findPolygonPath(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef,
|
||||
const dtPolyRef endRef, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& queryFilter,
|
||||
std::span<dtPolyRef> pathBuffer)
|
||||
@ -79,7 +97,7 @@ namespace DetourNavigator
|
||||
}
|
||||
|
||||
Status makeSmoothPath(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& start, const osg::Vec3f& end,
|
||||
std::span<dtPolyRef> polygonPath, std::size_t polygonPathSize, std::size_t maxSmoothPathSize,
|
||||
std::span<dtPolyRef> polygonPath, std::size_t polygonPathSize, std::size_t maxSmoothPathSize, bool skipFirst,
|
||||
std::output_iterator<osg::Vec3f> auto& out)
|
||||
{
|
||||
assert(polygonPathSize <= polygonPath.size());
|
||||
@ -95,7 +113,7 @@ namespace DetourNavigator
|
||||
dtStatusFailed(status))
|
||||
return Status::FindStraightPathFailed;
|
||||
|
||||
for (int i = 0; i < cornersCount; ++i)
|
||||
for (int i = skipFirst ? 1 : 0; i < cornersCount; ++i)
|
||||
*out++ = Misc::Convert::makeOsgVec3f(&cornerVertsBuffer[static_cast<std::size_t>(i) * 3]);
|
||||
|
||||
return Status::Success;
|
||||
@ -103,7 +121,8 @@ namespace DetourNavigator
|
||||
|
||||
Status findSmoothPath(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& halfExtents, const osg::Vec3f& start,
|
||||
const osg::Vec3f& end, const Flags includeFlags, const AreaCosts& areaCosts, const DetourSettings& settings,
|
||||
float endTolerance, std::output_iterator<osg::Vec3f> auto out)
|
||||
float endTolerance, const ToNavMeshCoordinatesSpan<const osg::Vec3f>& checkpoints,
|
||||
std::output_iterator<osg::Vec3f> auto out)
|
||||
{
|
||||
dtQueryFilter queryFilter;
|
||||
queryFilter.setIncludeFlags(includeFlags);
|
||||
@ -131,29 +150,66 @@ namespace DetourNavigator
|
||||
return Status::EndPolygonNotFound;
|
||||
|
||||
std::vector<dtPolyRef> polygonPath(settings.mMaxPolygonPathSize);
|
||||
const auto polygonPathSize
|
||||
= findPolygonPath(navMeshQuery, startRef, endRef, startNavMeshPos, endNavMeshPos, queryFilter, polygonPath);
|
||||
std::span<dtPolyRef> polygonPathBuffer = polygonPath;
|
||||
dtPolyRef currentRef = startRef;
|
||||
osg::Vec3f currentNavMeshPos = startNavMeshPos;
|
||||
bool skipFirst = false;
|
||||
|
||||
if (!polygonPathSize.has_value())
|
||||
for (std::size_t i = 0; i < checkpoints.size(); ++i)
|
||||
{
|
||||
const osg::Vec3f checkpointPos = checkpoints[i];
|
||||
osg::Vec3f checkpointNavMeshPos;
|
||||
dtPolyRef checkpointRef;
|
||||
if (const dtStatus status = navMeshQuery.findNearestPoly(checkpointPos.ptr(), polyHalfExtents.ptr(),
|
||||
&queryFilter, &checkpointRef, checkpointNavMeshPos.ptr());
|
||||
dtStatusFailed(status) || checkpointRef == 0)
|
||||
continue;
|
||||
|
||||
const std::optional<std::size_t> toCheckpointPathSize = findPolygonPath(navMeshQuery, currentRef,
|
||||
checkpointRef, currentNavMeshPos, checkpointNavMeshPos, queryFilter, polygonPath);
|
||||
|
||||
if (!toCheckpointPathSize.has_value())
|
||||
continue;
|
||||
|
||||
if (*toCheckpointPathSize == 0)
|
||||
continue;
|
||||
|
||||
if (polygonPath[*toCheckpointPathSize - 1] != checkpointRef)
|
||||
continue;
|
||||
|
||||
const Status smoothStatus = makeSmoothPath(navMeshQuery, currentNavMeshPos, checkpointNavMeshPos,
|
||||
polygonPath, *toCheckpointPathSize, settings.mMaxSmoothPathSize, skipFirst, out);
|
||||
|
||||
if (smoothStatus != Status::Success)
|
||||
return smoothStatus;
|
||||
|
||||
currentRef = checkpointRef;
|
||||
currentNavMeshPos = checkpointNavMeshPos;
|
||||
skipFirst = true;
|
||||
}
|
||||
|
||||
const std::optional<std::size_t> toEndPathSize = findPolygonPath(
|
||||
navMeshQuery, currentRef, endRef, currentNavMeshPos, endNavMeshPos, queryFilter, polygonPathBuffer);
|
||||
|
||||
if (!toEndPathSize.has_value())
|
||||
return Status::FindPathOverPolygonsFailed;
|
||||
|
||||
if (*polygonPathSize == 0)
|
||||
return Status::Success;
|
||||
if (*toEndPathSize == 0)
|
||||
return currentRef == endRef ? Status::Success : Status::PartialPath;
|
||||
|
||||
osg::Vec3f targetNavMeshPos;
|
||||
if (const dtStatus status = navMeshQuery.closestPointOnPoly(
|
||||
polygonPath[*polygonPathSize - 1], end.ptr(), targetNavMeshPos.ptr(), nullptr);
|
||||
polygonPath[*toEndPathSize - 1], end.ptr(), targetNavMeshPos.ptr(), nullptr);
|
||||
dtStatusFailed(status))
|
||||
return Status::TargetPolygonNotFound;
|
||||
|
||||
const bool partialPath = polygonPath[*polygonPathSize - 1] != endRef;
|
||||
const Status smoothStatus = makeSmoothPath(navMeshQuery, startNavMeshPos, targetNavMeshPos, polygonPath,
|
||||
*polygonPathSize, settings.mMaxSmoothPathSize, out);
|
||||
const Status smoothStatus = makeSmoothPath(navMeshQuery, currentNavMeshPos, targetNavMeshPos, polygonPath,
|
||||
*toEndPathSize, settings.mMaxSmoothPathSize, skipFirst, out);
|
||||
|
||||
if (smoothStatus != Status::Success)
|
||||
return smoothStatus;
|
||||
|
||||
return partialPath ? Status::PartialPath : Status::Success;
|
||||
return polygonPath[*toEndPathSize - 1] == endRef ? Status::Success : Status::PartialPath;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
@ -21,13 +22,13 @@ namespace DetourNavigator
|
||||
* @param end path at given point.
|
||||
* @param includeFlags setup allowed navmesh areas.
|
||||
* @param out the beginning of the destination range.
|
||||
* @param endTolerance defines maximum allowed distance to end path point in addition to agentHalfExtents
|
||||
* @param endTolerance defines maximum allowed distance to end path point in addition to agentHalfExtents.
|
||||
* @param checkpoints is a sequence of positions the path should go over if possible.
|
||||
* @return Status.
|
||||
* Equal to out if no path is found.
|
||||
*/
|
||||
inline Status findPath(const Navigator& navigator, const AgentBounds& agentBounds, const osg::Vec3f& start,
|
||||
const osg::Vec3f& end, const Flags includeFlags, const AreaCosts& areaCosts, float endTolerance,
|
||||
std::output_iterator<osg::Vec3f> auto out)
|
||||
std::span<const osg::Vec3f> checkpoints, std::output_iterator<osg::Vec3f> auto out)
|
||||
{
|
||||
const auto navMesh = navigator.getNavMesh(agentBounds);
|
||||
if (navMesh == nullptr)
|
||||
@ -37,7 +38,8 @@ namespace DetourNavigator
|
||||
const auto locked = navMesh->lock();
|
||||
return findSmoothPath(locked->getQuery(), toNavMeshCoordinates(settings.mRecast, agentBounds.mHalfExtents),
|
||||
toNavMeshCoordinates(settings.mRecast, start), toNavMeshCoordinates(settings.mRecast, end), includeFlags,
|
||||
areaCosts, settings.mDetour, endTolerance, outTransform);
|
||||
areaCosts, settings.mDetour, endTolerance, ToNavMeshCoordinatesSpan(checkpoints, settings.mRecast),
|
||||
outTransform);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,10 +59,18 @@ namespace Misc
|
||||
point.y() -= static_cast<float>(mCellY);
|
||||
}
|
||||
|
||||
osg::Vec3f toWorldVec3(const osg::Vec3f& point) const
|
||||
{
|
||||
osg::Vec3f result = point;
|
||||
toWorld(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
osg::Vec3f toLocalVec3(const osg::Vec3f& point) const
|
||||
{
|
||||
return osg::Vec3f(
|
||||
point.x() - static_cast<float>(mCellX), point.y() - static_cast<float>(mCellY), point.z());
|
||||
osg::Vec3f result = point;
|
||||
toLocal(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
|
53
components/misc/pathgridutils.hpp
Normal file
53
components/misc/pathgridutils.hpp
Normal file
@ -0,0 +1,53 @@
|
||||
#ifndef OPENMW_COMPONENTS_MISC_PATHGRIDUTILS_H
|
||||
#define OPENMW_COMPONENTS_MISC_PATHGRIDUTILS_H
|
||||
|
||||
#include "convert.hpp"
|
||||
|
||||
#include <components/esm3/loadpgrd.hpp>
|
||||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Misc
|
||||
{
|
||||
// Slightly cheaper version for comparisons.
|
||||
// Caller needs to be careful for very short distances (i.e. less than 1)
|
||||
// or when accumuating the results i.e. (a + b)^2 != a^2 + b^2
|
||||
//
|
||||
inline float distanceSquared(const ESM::Pathgrid::Point& point, const osg::Vec3f& pos)
|
||||
{
|
||||
return (Misc::Convert::makeOsgVec3f(point) - pos).length2();
|
||||
}
|
||||
|
||||
// Return the closest pathgrid point index from the specified position
|
||||
// coordinates. NOTE: Does not check if there is a sensible way to get there
|
||||
// (e.g. a cliff in front).
|
||||
//
|
||||
// NOTE: pos is expected to be in local coordinates, as is grid->mPoints
|
||||
//
|
||||
inline std::size_t getClosestPoint(const ESM::Pathgrid& grid, const osg::Vec3f& pos)
|
||||
{
|
||||
if (grid.mPoints.empty())
|
||||
throw std::invalid_argument("Pathgrid has no points");
|
||||
|
||||
float minDistance = distanceSquared(grid.mPoints[0], pos);
|
||||
std::size_t closestIndex = 0;
|
||||
|
||||
// TODO: if this full scan causes performance problems mapping pathgrid
|
||||
// points to a quadtree may help
|
||||
for (std::size_t i = 1; i < grid.mPoints.size(); ++i)
|
||||
{
|
||||
const float distance = distanceSquared(grid.mPoints[i], pos);
|
||||
if (minDistance > distance)
|
||||
{
|
||||
minDistance = distance;
|
||||
closestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return closestIndex;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -180,6 +180,7 @@
|
||||
-- @field [parent=#FindPathOptions] #AreaCosts areaCosts a table defining relative cost for each type of area.
|
||||
-- @field [parent=#FindPathOptions] #number destinationTolerance a floating point number representing maximum allowed
|
||||
-- distance between destination and a nearest point on the navigation mesh in addition to agent size (default: 1).
|
||||
-- @field [parent=#FindPathOptions] #table checkpoints an array of positions to build path over if possible.
|
||||
|
||||
---
|
||||
-- A table of parameters for @{#nearby.findRandomPointAroundCircle} and @{#nearby.castNavigationRay}
|
||||
|
@ -315,6 +315,7 @@ registerPlayerTest('player rotation')
|
||||
registerPlayerTest('player forward running')
|
||||
registerPlayerTest('player diagonal walking')
|
||||
registerPlayerTest('findPath')
|
||||
registerPlayerTest('findPath with checkpoints')
|
||||
registerPlayerTest('findRandomPointAroundCircle')
|
||||
registerPlayerTest('castNavigationRay')
|
||||
registerPlayerTest('findNearestNavMeshPosition')
|
||||
|
@ -82,6 +82,7 @@ registerGlobalTest('player rotation', 'rotating player should not lead to nan ro
|
||||
registerGlobalTest('player forward running')
|
||||
registerGlobalTest('player diagonal walking')
|
||||
registerGlobalTest('findPath')
|
||||
registerGlobalTest('findPath with checkpoints')
|
||||
registerGlobalTest('findRandomPointAroundCircle')
|
||||
registerGlobalTest('castNavigationRay')
|
||||
registerGlobalTest('findNearestNavMeshPosition')
|
||||
|
@ -179,6 +179,39 @@ testing.registerLocalTest('findPath',
|
||||
testing.expectEqual(status, nearby.FIND_PATH_STATUS.Success, 'Status')
|
||||
testing.expectLessOrEqual((path[#path] - dst):length(), 1,
|
||||
'Last path point ' .. testing.formatActualExpected(path[#path], dst))
|
||||
testing.expectThat(path, matchers.equalTo({
|
||||
matchers.closeToVector(util.vector3(4096, 4096, 1746.27099609375), 1e-1),
|
||||
matchers.closeToVector(util.vector3(4500, 4500, 1745.95263671875), 1e-1),
|
||||
}))
|
||||
end)
|
||||
|
||||
testing.registerLocalTest('findPath with checkpoints',
|
||||
function()
|
||||
local src = util.vector3(4096, 4096, 1745)
|
||||
local dst = util.vector3(4500, 4500, 1745.95263671875)
|
||||
local options = {
|
||||
agentBounds = types.Actor.getPathfindingAgentBounds(self),
|
||||
includeFlags = nearby.NAVIGATOR_FLAGS.Walk + nearby.NAVIGATOR_FLAGS.Swim,
|
||||
areaCosts = {
|
||||
water = 1,
|
||||
door = 2,
|
||||
ground = 1,
|
||||
pathgrid = 1,
|
||||
},
|
||||
destinationTolerance = 1,
|
||||
checkpoints = {
|
||||
util.vector3(4200, 4100, 1750),
|
||||
},
|
||||
}
|
||||
local status, path = nearby.findPath(src, dst, options)
|
||||
testing.expectEqual(status, nearby.FIND_PATH_STATUS.Success, 'Status')
|
||||
testing.expectLessOrEqual((path[#path] - dst):length(), 1,
|
||||
'Last path point ' .. testing.formatActualExpected(path[#path], dst))
|
||||
testing.expectThat(path, matchers.equalTo({
|
||||
matchers.closeToVector(util.vector3(4096, 4096, 1746.27099609375), 1e-1),
|
||||
matchers.closeToVector(util.vector3(4200, 4100, 1749.5076904296875), 1e-1),
|
||||
matchers.closeToVector(util.vector3(4500, 4500, 1745.95263671875), 1e-1),
|
||||
}))
|
||||
end)
|
||||
|
||||
testing.registerLocalTest('findRandomPointAroundCircle',
|
||||
|
Loading…
x
Reference in New Issue
Block a user