mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 21:34:08 -07:00
Avoid cases of filenames Update years in terms and other legal(ish) documents Don't update years in license headers, since change is not meaningful Will add linter rule in seperate commit Happy recompiling everyone! Original Patch By: Nescio Comment By: Gallaecio Differential Revision: D2620 This was SVN commit r27786.
523 lines
20 KiB
C++
523 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);
|
||
}
|
||
};
|