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