1
0
forked from 0ad/0ad
0ad/source/simulation2/components/tests/test_HierPathfinder.h
Freagarach 645e053fd2 Remove executable bit on some source files.
Reported by: Ralph Sennhauser
Fixes: #6325

This was SVN commit r25917.
2021-09-12 18:41:51 +00:00

524 lines
20 KiB
C++

/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "simulation2/system/ComponentTest.h"
#define TEST
#include "maths/Vector2D.h"
#include "simulation2/helpers/Grid.h"
#include "simulation2/helpers/HierarchicalPathfinder.h"
class TestHierarchicalPathfinder : public CxxTest::TestSuite
{
public:
void setUp()
{
}
void tearDown()
{
}
const pass_class_t PASS_1 = 1;
const pass_class_t PASS_2 = 2;
const pass_class_t NON_PASS_1 = 4;
const u16 mapSize = 240;
std::map<std::string, pass_class_t> pathClassMask;
std::map<std::string, pass_class_t> nonPathClassMask;
void debug_grid(Grid<NavcellData>& grid)
{
for (size_t i = 0; i < grid.m_W; ++i)
{
for (size_t j = 0; j < grid.m_H; ++j)
printf("%i", grid.get(i, j));
printf("\n");
}
}
void debug_grid_points(Grid<NavcellData>& grid, u16 i1, u16 j1, u16 i2, u16 j2)
{
for (size_t i = 0; i < grid.m_W; ++i)
{
for (size_t j = 0; j < grid.m_H; ++j)
{
if (i == i1 && j == j1)
printf("A");
else if (i == i2 && j == j2)
printf("B");
else
printf("%i", grid.get(i, j));
}
printf("\n");
}
}
void assert_blank(HierarchicalPathfinder& hierPath)
{
// test that the map has the same global region everywhere
HierarchicalPathfinder::GlobalRegionID globalRegionID = hierPath.GetGlobalRegion(35, 23, PASS_1);
for (u16 i = 0; i < mapSize; ++i)
for (u16 j = 0; j < mapSize; ++j)
{
TS_ASSERT(globalRegionID == hierPath.GetGlobalRegion(i, j, PASS_1));
TS_ASSERT(hierPath.GetGlobalRegion(i, j, PASS_2) == 0);
}
u16 i = 89;
u16 j = 34;
hierPath.FindNearestPassableNavcell(i, j, PASS_1);
TS_ASSERT(i == 89 && j == 34);
for (auto& chunk : hierPath.m_Chunks[PASS_1])
TS_ASSERT(chunk.m_RegionsID.size() == 1);
// number of connected regions: 4 in the middle, 2 in the corners.
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(120, 120, PASS_1)].size() == 4);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(20, 20, PASS_1)].size() == 2);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(220, 220, PASS_1)].size() == 2);
std::set<HierarchicalPathfinder::RegionID> reachables;
hierPath.FindReachableRegions(hierPath.Get(120, 120, PASS_1), reachables, PASS_1);
TS_ASSERT(reachables.size() == 9);
reachables.clear();
hierPath.FindReachableRegions(hierPath.Get(20, 20, PASS_1), reachables, PASS_1);
TS_ASSERT(reachables.size() == 9);
}
void test_reachability_and_update()
{
pathClassMask = std::map<std::string, pass_class_t> {
{ "1", 1 },
{ "2", 2 },
};
nonPathClassMask = std::map<std::string, pass_class_t> {
{ "3", 4 }
};
HierarchicalPathfinder hierPath;
Grid<NavcellData> grid(mapSize, mapSize);
Grid<u8> dirtyGrid(mapSize, mapSize);
// Entirely passable for PASS_1, not for others;
for (size_t i = 0; i < mapSize; ++i)
for (size_t j = 0; j < mapSize; ++j)
grid.set(i, j, 6);
hierPath.Recompute(&grid, nonPathClassMask, pathClassMask);
assert_blank(hierPath);
//////////////////////////////////////////////////////
// Split the map in two in the middle.
for (u16 j = 0; j < mapSize; ++j)
{
grid.set(125, j, 7);
dirtyGrid.set(125, j, 1);
}
hierPath.Update(&grid, dirtyGrid);
// Global region: check we are now split in two.
TS_ASSERT(hierPath.GetGlobalRegion(50, 50, PASS_1) != hierPath.GetGlobalRegion(150, 50, PASS_1));
for (u16 j = 0; j < mapSize; ++j)
{
TS_ASSERT(hierPath.Get(125, j, PASS_1).r == 0);
TS_ASSERT(hierPath.GetGlobalRegion(125, j, PASS_1) == 0);
}
for (u16 i = 0; i < 125; ++i)
for (u16 j = 0; j < mapSize; ++j)
{
TS_ASSERT(hierPath.GetGlobalRegion(50, 50, PASS_1) == hierPath.GetGlobalRegion(i, j, PASS_1));
TS_ASSERT(hierPath.GetGlobalRegion(i, j, PASS_2) == 0);
}
for (u16 i = 126; i < mapSize; ++i)
for (u16 j = 0; j < mapSize; ++j)
{
TS_ASSERT(hierPath.GetGlobalRegion(150, 50, PASS_1) == hierPath.GetGlobalRegion(i, j, PASS_1));
TS_ASSERT(hierPath.GetGlobalRegion(i, j, PASS_2) == 0);
}
// number of connected regions: 3 in the middle (both sides), 2 in the corners.
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(120, 120, PASS_1)].size() == 3);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(170, 120, PASS_1)].size() == 3);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(20, 20, PASS_1)].size() == 2);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(220, 220, PASS_1)].size() == 2);
std::set<HierarchicalPathfinder::RegionID> reachables;
hierPath.FindReachableRegions(hierPath.Get(120, 120, PASS_1), reachables, PASS_1);
TS_ASSERT(reachables.size() == 6);
reachables.clear();
hierPath.FindReachableRegions(hierPath.Get(170, 120, PASS_1), reachables, PASS_1);
TS_ASSERT(reachables.size() == 6);
//////////////////////////////////////////////////////
// Un-split the map in two in the middle.
for (u16 j = 0; j < mapSize; ++j)
{
grid.set(125, j, 6);
dirtyGrid.set(125, j, 1);
}
hierPath.Update(&grid, dirtyGrid);
assert_blank(hierPath);
//////////////////////////////////////////////////////
// Partial split in the middle chunk - no actual connectivity change
for (u16 j = 120; j < 150; ++j)
{
grid.set(125, j, 7);
dirtyGrid.set(125, j, 1);
}
hierPath.Update(&grid, dirtyGrid);
reachables.clear();
hierPath.FindReachableRegions(hierPath.Get(170, 120, PASS_1), reachables, PASS_1);
TS_ASSERT(reachables.size() == 9);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(170, 120, PASS_1)].size() == 4);
//////////////////////////////////////////////////////
// Block a strip along the edge, but regions are still connected.
for (u16 j = 70; j < 200; ++j)
{
grid.set(96, j, 7);
dirtyGrid.set(96, j, 1);
}
hierPath.Update(&grid, dirtyGrid);
reachables.clear();
hierPath.FindReachableRegions(hierPath.Get(170, 120, PASS_1), reachables, PASS_1);
TS_ASSERT(reachables.size() == 9);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(20, 120, PASS_1)].size() == 2);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(170, 120, PASS_1)].size() == 3);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(200, 120, PASS_1)].size() == 3);
//////////////////////////////////////////////////////
// Block the other edge
for (u16 j = 70; j < 200; ++j)
{
grid.set(192, j, 7);
dirtyGrid.set(192, j, 1);
}
hierPath.Update(&grid, dirtyGrid);
reachables.clear();
hierPath.FindReachableRegions(hierPath.Get(170, 120, PASS_1), reachables, PASS_1);
TS_ASSERT(reachables.size() == 9);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(20, 120, PASS_1)].size() == 2);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(170, 120, PASS_1)].size() == 2);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(200, 120, PASS_1)].size() == 2);
//////////////////////////////////////////////////////
// Create an isolated region in the middle chunk
for (u16 i = 96; i < 140; ++i)
{
grid.set(i, 110, 7);
dirtyGrid.set(i, 110, 1);
}
for (u16 i = 96; i < 140; ++i)
{
grid.set(i, 140, 7);
dirtyGrid.set(i, 140, 1);
}
for (u16 j = 110; j < 141; ++j)
{
grid.set(140, j, 7);
dirtyGrid.set(140, j, 1);
}
hierPath.Update(&grid, dirtyGrid);
TS_ASSERT(hierPath.GetGlobalRegion(120, 120, PASS_1) != hierPath.GetGlobalRegion(150, 50, PASS_1));
reachables.clear();
hierPath.FindReachableRegions(hierPath.Get(170, 120, PASS_1), reachables, PASS_1);
TS_ASSERT(reachables.size() == 9);
reachables.clear();
hierPath.FindReachableRegions(hierPath.Get(120, 120, PASS_1), reachables, PASS_1);
TS_ASSERT(reachables.size() == 1);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(120, 120, PASS_1)].size() == 0);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(20, 120, PASS_1)].size() == 2);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(170, 120, PASS_1)].size() == 2);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(200, 120, PASS_1)].size() == 2);
//////////////////////////////////////////////////////
// Open it
for (u16 j = 110; j < 141; ++j)
{
grid.set(140, j, 6);
dirtyGrid.set(140, j, 1);
}
hierPath.Update(&grid, dirtyGrid);
TS_ASSERT(hierPath.GetGlobalRegion(120, 120, PASS_1) == hierPath.GetGlobalRegion(150, 50, PASS_1));
reachables.clear();
hierPath.FindReachableRegions(hierPath.Get(170, 120, PASS_1), reachables, PASS_1);
TS_ASSERT(reachables.size() == 9);
reachables.clear();
hierPath.FindReachableRegions(hierPath.Get(120, 120, PASS_1), reachables, PASS_1);
TS_ASSERT(reachables.size() == 9);
TS_ASSERT(hierPath.m_Edges[PASS_1][hierPath.Get(120, 120, PASS_1)].size() == 2);
}
u16 manhattan(u16 i, u16 j, u16 gi, u16 gj)
{
return abs(i - gi) + abs(j - gj);
}
double euclidian(u16 i, u16 j, u16 gi, u16 gj)
{
return sqrt((i - gi) * (i - gi) + (j - gj) * (j - gj));
}
void test_passability()
{
pathClassMask = std::map<std::string, pass_class_t> {
{ "1", 1 },
{ "2", 2 },
};
nonPathClassMask = std::map<std::string, pass_class_t> {
{ "3", 4 }
};
// 0 is passable, 1 is not.
// i is vertical, j is horizontal;
#define _ 0
#define X 1
NavcellData gridDef[40][40] = {
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,X,X,X,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,X,X,X,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,X,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,X,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,X,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,X,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,X,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,X,X,X,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,X,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,X,X,X,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,X,_,X,X,X,X,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,X,_,X,_,_,X,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,X,_,X,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,X,_,X,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,X,_,X,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,X,X,X,_,_,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,X,_,X,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,X,_,X,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,X,_,X,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,X,X,X,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,X,X,X,X,X,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,X,X,X,X,X,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,X,X,X,X,X,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,X,X,X,X,X,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,X,X,X,X,X,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,X,X,X,X,X,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,X,X,X,X,X,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,X,X,X,X,X,X,X,X,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_},
{_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_}
};
#undef _
#undef X
// upscaled n times
const int scale = 6;
HierarchicalPathfinder hierPath;
Grid<NavcellData> grid(40*scale, 40*scale);
Grid<u8> dirtyGrid(40*scale, 40*scale);
for (size_t i = 0; i < 40; ++i)
for (size_t j = 0; j < 40; ++j)
for (size_t ii = 0; ii < scale; ++ii)
for (size_t jj = 0; jj < scale; ++jj)
grid.set(i * scale + ii, j * scale + jj, gridDef[i][j]);
hierPath.Recompute(&grid, nonPathClassMask, pathClassMask);
u16 i = 5, j = 5;
hierPath.FindNearestPassableNavcell(i, j, PASS_1);
TS_ASSERT(i == 5 && j == 5);
// use a macro so the lines reported by tests are accurate
#define check_closest_passable(i, j, expected_manhattan) \
oi = i; oj = j; \
pi = i; pj = j; \
TS_ASSERT(!IS_PASSABLE(grid.get(pi, pj), PASS_1)); \
hierPath.FindNearestPassableNavcell(pi, pj, PASS_1); \
\
if (expected_manhattan == -1) \
{ \
TS_ASSERT(oi == pi && oj == pj); \
} \
else \
{ \
TS_ASSERT(IS_PASSABLE(grid.get(pi, pj), PASS_1)); \
TS_ASSERT_EQUALS(manhattan(pi, pj, oi, oj), expected_manhattan); \
}
u16 oi, oj, pi, pj;
check_closest_passable(4 * scale, 4 * scale, 1);
check_closest_passable(4 * scale + 1, 4 * scale + 1, 2);
check_closest_passable(14 * scale + 2, 7 * scale + 2, 9);
check_closest_passable(14 * scale + 2, 7 * scale + 4, 8);
check_closest_passable(14 * scale + 2, 7 * scale + 5, 7);
check_closest_passable(14 * scale + 2, 7 * scale + 6, 6);
check_closest_passable(5 * scale + 3, 7 * scale + 2, 3);
#undef check_closest_passable
PathGoal goal;
goal.type = PathGoal::POINT;
// from the left of the C, goal is unreachable, expect closest navcell to goal
oi = 5 * scale + 3; oj = 3 * scale + 3;
pi = 5 * scale + 3; pj = 6 * scale + 2; goal.x = fixed::FromInt(pi); goal.z = fixed::FromInt(pj);
hierPath.MakeGoalReachable(oi, oj, goal, PASS_1);
hierPath.FindNearestPassableNavcell(pi, pj, PASS_1);
TS_ASSERT_EQUALS(pi, goal.x.ToInt_RoundToNegInfinity());
TS_ASSERT_EQUALS(pj, goal.z.ToInt_RoundToNegInfinity());
// random reachable point.
oi = 5 * scale + 3; oj = 3 * scale + 3;
pi = 26 * scale + 3; pj = 5 * scale + 2; goal.x = fixed::FromInt(pi) + fixed::FromInt(1)/3; goal.z = fixed::FromInt(pj) + fixed::FromInt(1)/3;
hierPath.MakeGoalReachable(oi, oj, goal, PASS_1);
TS_ASSERT(pi == goal.x.ToInt_RoundToNegInfinity() && pj == goal.z.ToInt_RoundToNegInfinity());
// top-left corner
goal.x = fixed::FromInt(pi); goal.z = fixed::FromInt(pj);
hierPath.MakeGoalReachable(oi, oj, goal, PASS_1);
TS_ASSERT(pi == goal.x.ToInt_RoundToNegInfinity() && pj == goal.z.ToInt_RoundToNegInfinity());
// near bottom-right corner
goal.x = fixed::FromInt(pi) + fixed::FromInt(3)/4; goal.z = fixed::FromInt(pj) + fixed::FromInt(3)/4;
hierPath.MakeGoalReachable(oi, oj, goal, PASS_1);
TS_ASSERT(pi == goal.x.ToInt_RoundToNegInfinity() && pj == goal.z.ToInt_RoundToNegInfinity());
// Circle
goal.type = PathGoal::CIRCLE;
goal.hw = fixed::FromInt(1) / 2;
// from the left of the C, goal is unreachable, expect closest navcell to goal
oi = 5 * scale + 3; oj = 3 * scale + 3;
pi = 5 * scale + 3; pj = 7 * scale + 2; goal.x = fixed::FromInt(pi); goal.z = fixed::FromInt(pj);
hierPath.MakeGoalReachable(oi, oj, goal, PASS_1);
hierPath.FindNearestPassableNavcell(pi, pj, PASS_1);
TS_ASSERT(pi == goal.x.ToInt_RoundToNegInfinity() && pj == goal.z.ToInt_RoundToNegInfinity());
// same position, goal is reachable, expect closest navcell to goal
goal.hw = fixed::FromInt(3);
goal.x = fixed::FromInt(pi); goal.z = fixed::FromInt(pj);
hierPath.MakeGoalReachable(oi, oj, goal, PASS_1);
hierPath.FindNearestPassableNavcell(pi, pj, PASS_1);
TS_ASSERT(pi == goal.x.ToInt_RoundToNegInfinity() && pj == goal.z.ToInt_RoundToNegInfinity());
// Same position, but goal is unreachable and much farther away.
goal.type = PathGoal::POINT;
oi = 5 * scale + 3; oj = 3 * scale + 3;
pi = 34 * scale + 3; pj = 6 * scale + 2; goal.x = fixed::FromInt(pi); goal.z = fixed::FromInt(pj);
hierPath.MakeGoalReachable(oi, oj, goal, PASS_1);
hierPath.FindNearestPassableNavcell(pi, pj, PASS_1);
TS_ASSERT_EQUALS(pi, goal.x.ToInt_RoundToNegInfinity())
TS_ASSERT_EQUALS(pj, goal.z.ToInt_RoundToNegInfinity());
// Square
goal.type = PathGoal::SQUARE;
goal.hw = fixed::FromInt(1) / 2;
goal.hh = fixed::FromInt(1) / 2;
// from the left of the C, goal is unreachable, expect closest navcell to goal
oi = 5 * scale + 3; oj = 3 * scale + 3;
pi = 5 * scale + 3; pj = 7 * scale + 2; goal.x = fixed::FromInt(pi); goal.z = fixed::FromInt(pj);
hierPath.MakeGoalReachable(oi, oj, goal, PASS_1);
hierPath.FindNearestPassableNavcell(pi, pj, PASS_1);
TS_ASSERT(pi == goal.x.ToInt_RoundToNegInfinity() && pj == goal.z.ToInt_RoundToNegInfinity());
// same position, goal is reachable, expect closest navcell to goal
goal.hw = fixed::FromInt(3);
goal.hh = fixed::FromInt(3);
goal.x = fixed::FromInt(pi); goal.z = fixed::FromInt(pj);
hierPath.MakeGoalReachable(oi, oj, goal, PASS_1);
hierPath.FindNearestPassableNavcell(pi, pj, PASS_1);
TS_ASSERT(pi == goal.x.ToInt_RoundToNegInfinity() && pj == goal.z.ToInt_RoundToNegInfinity());
// Goal is reachable diagonally (1 cell away)
goal.hw = fixed::FromInt(1);
goal.hh = fixed::FromInt(1);
oi = 5 * scale + 3; oj = 3 * scale + 3;
pi = 5 * scale - 1; pj = 7 * scale + 3; goal.x = fixed::FromInt(pi); goal.z = fixed::FromInt(pj);
hierPath.MakeGoalReachable(oi, oj, goal, PASS_1);
hierPath.FindNearestPassableNavcell(pi, pj, PASS_1);
TS_ASSERT(pi == goal.x.ToInt_RoundToNegInfinity() && pj == goal.z.ToInt_RoundToNegInfinity());
// Huge Circle goal, expect point closest to us.
goal.type = PathGoal::CIRCLE;
goal.hw = fixed::FromInt(20);
oi = 5 * scale + 3; oj = 3 * scale + 3;
pi = 36 * scale + 3; pj = 7 * scale + 2; goal.x = fixed::FromInt(pi); goal.z = fixed::FromInt(pj);
hierPath.MakeGoalReachable(oi, oj, goal, PASS_1);
// bit of leeway for cell placement
TS_ASSERT(std::fabs(euclidian(goal.x.ToInt_RoundToNegInfinity(), goal.z.ToInt_RoundToNegInfinity(), pi, pj)-20) < 1.5f);
TS_ASSERT(std::fabs(euclidian(goal.x.ToInt_RoundToNegInfinity(), goal.z.ToInt_RoundToNegInfinity(), oi, oj) - euclidian(pi, pj, oi, oj)) < 22.0f);
}
void test_regions_flood_fill()
{
// Partial test of region inner flood filling.
// This highlights that internal region IDs can become higher than the number of regions.
pathClassMask = std::map<std::string, pass_class_t> {
{ "1", 1 },
{ "2", 2 },
};
nonPathClassMask = std::map<std::string, pass_class_t> {
{ "3", 4 }
};
// 0 is passable, 1 is not.
// i is vertical, j is horizontal;
#define _ 0
#define X 1
NavcellData gridDef[5][5] = {
{X,_,X,_,_},
{_,_,X,X,_},
{X,_,X,_,_},
{_,_,X,X,_},
{X,_,X,_,_}
};
#undef _
#undef X
HierarchicalPathfinder hierPath;
Grid<NavcellData> grid(5, 5);
Grid<u8> dirtyGrid(5, 5);
for (size_t i = 0; i < 5; ++i)
for (size_t j = 0; j < 5; ++j)
grid.set(i, j, gridDef[i][j]);
hierPath.Recompute(&grid, nonPathClassMask, pathClassMask);
TS_ASSERT_EQUALS(hierPath.m_Chunks[pathClassMask["1"]][0].m_RegionsID.size(), 2);
TS_ASSERT_EQUALS(hierPath.m_Chunks[pathClassMask["1"]][0].m_RegionsID.back(), 4);
}
};