#define BOOST_TEST_MODULE terrain #include "game/terrain.h" #include "test/testMainWindow.h" #include "test/testRenderOutput.h" #include "testHelpers.h" #include "ui/applicationBase.h" #include #include #include #include #include class TestTerrainMesh : public GeoData { public: TestTerrainMesh() : GeoData {GeoData::loadFromAsciiGrid(FIXTURESDIR "height/SD19.asc")} { } }; constexpr size_t ncols = 200, nrows = 200, xllcorner = 310000000, yllcorner = 490000000, cellsize = 50000; BOOST_FIXTURE_TEST_SUITE(ttm, TestTerrainMesh); BOOST_AUTO_TEST_CASE(loadSuccess) { BOOST_CHECK_EQUAL(ncols * nrows, n_vertices()); BOOST_CHECK_EQUAL(2 * (ncols - 1) * (nrows - 1), n_faces()); const auto [lower, upper] = getExtents(); BOOST_CHECK_EQUAL(lower, GlobalPosition3D(310000000, 490000000, -2600)); BOOST_CHECK_EQUAL(upper, GlobalPosition3D(319950000, 499950000, 571600)); } BOOST_AUTO_TEST_CASE(normalsAllPointUp) { BOOST_CHECK(std::ranges::all_of(vertices(), [this](auto && vertex) { return normal(vertex).z > 0; })); } BOOST_AUTO_TEST_CASE(trianglesContainsPoints) { const auto face = face_handle(0); BOOST_TEST_CONTEXT(this->triangle<2>(face)) { BOOST_CHECK(triangleContainsPoint(GlobalPosition2D {xllcorner, yllcorner}, face)); BOOST_CHECK(triangleContainsPoint(GlobalPosition2D {xllcorner + cellsize, yllcorner + cellsize}, face)); BOOST_CHECK(triangleContainsPoint(GlobalPosition2D {xllcorner, yllcorner + cellsize}, face)); BOOST_CHECK(triangleContainsPoint(GlobalPosition2D {xllcorner + 1, yllcorner + 1}, face)); BOOST_CHECK(triangleContainsPoint(GlobalPosition2D {xllcorner + 1, yllcorner + 2}, face)); BOOST_CHECK(!triangleContainsPoint(GlobalPosition2D {xllcorner + 3, yllcorner + 2}, face)); BOOST_CHECK(!triangleContainsPoint(GlobalPosition2D {xllcorner + cellsize, yllcorner}, face)); } } BOOST_AUTO_TEST_SUITE_END(); static const TestTerrainMesh fixedTerrtain; BOOST_AUTO_TEST_CASE(locatePointFace) { const GeoData::PointFace pf {{310002000, 490003000}}; BOOST_CHECK(!pf.isLocated()); BOOST_CHECK(pf.face(&fixedTerrtain).is_valid()); BOOST_CHECK_EQUAL(pf.face(&fixedTerrtain).idx(), 0); } BOOST_AUTO_TEST_CASE(preLocatePointFace) { const GeoData::PointFace pf {{310002000, 490003000}, &fixedTerrtain}; BOOST_CHECK(pf.isLocated()); BOOST_CHECK_EQUAL(pf.face(&fixedTerrtain).idx(), 0); } using FindPointData = std::tuple; // No boundary cases as these can produce different valid results depending on starting point BOOST_DATA_TEST_CASE(findPointOnTerrain, boost::unit_test::data::make({ {{0, 0}, -1}, {{xllcorner, 0}, -1}, {{0, yllcorner}, -1}, {{xllcorner + 1, yllcorner + 2}, 0}, {{xllcorner + (cellsize * (nrows - 1)) - 2, yllcorner + (cellsize * (ncols - 1)) - 1}, 79200}, {{315555000, 495556000}, 44400}, // perf test target }) * boost::unit_test::data::make( {0, 1, 2, 3, 4, 5, 6, 10, 100, 150, 200, 1000, 1234, 17439, 79201, 79200, 79199}), p, fh, start) { BOOST_CHECK_EQUAL(fh, fixedTerrtain.findPoint(p, GeoData::FaceHandle(start)).idx()); } using FindPositionData = std::tuple; BOOST_DATA_TEST_CASE(findPositionAt, boost::unit_test::data::make({ // corners {{310000000, 490000000}, 32800}, {{310050000, 490050000}, 33000}, {{310000000, 490050000}, 32700}, {{310050000, 490000000}, 33200}, {{310750000, 490150000}, 58400}, // midpoints {{310025000, 490025000}, 32900}, {{310025000, 490050000}, 32850}, {{310000000, 490025000}, 32750}, // other {{310751000, 490152000}, 58326}, }), p, h) { BOOST_CHECK_EQUAL(fixedTerrtain.positionAt(p), GlobalPosition3D(p, h)); } using FindRayIntersectData = std::tuple; BOOST_DATA_TEST_CASE(findRayIntersect, boost::unit_test::data::make({ {{310000000, 490000000, 50000}, {1, 1, -2}, {310008583, 490008583, 32834}}, {{310000000, 490000000, 50000}, {1, 1, -1}, {310017131, 490017131, 32869}}, }), p, d, i) { BOOST_CHECK_EQUAL(fixedTerrtain.intersectRay({p, d})->first, i); } BOOST_AUTO_TEST_CASE(boundaryWalk) { size_t count {}; fixedTerrtain.boundaryWalk([&count](auto heh) { BOOST_CHECK(fixedTerrtain.is_boundary(heh)); count++; }); BOOST_CHECK_EQUAL(count, 2 * (ncols + nrows - 2)); } using WalkTerrainData = std::tuple>; BOOST_DATA_TEST_CASE(walkTerrain, boost::unit_test::data::make({ {{310002000, 490003000}, {310002000, 490003000}, {0}}, {{310003000, 490002000}, {310003000, 490002000}, {1}}, {{310002000, 490003000}, {310003000, 490002000}, {0, 1}}, {{310003000, 490002000}, {310002000, 490003000}, {1, 0}}, {{310002000, 490003000}, {310202000, 490003000}, {0, 1, 2, 3, 4, 5, 6, 7, 8}}, {{310202000, 490003000}, {310002000, 490003000}, {8, 7, 6, 5, 4, 3, 2, 1, 0}}, {{310002000, 490003000}, {310002000, 490203000}, {0, 399, 398, 797, 796, 1195, 1194, 1593, 1592}}, {{310002000, 490003000}, {309999000, 489999000}, {0}}, {{309999000, 489999000}, {310002000, 490003000}, {0}}, {{320002000, 500003000}, {319949000, 499948000}, {79201}}, {{309999000, 490003000}, {310004000, 489997000}, {0, 1}}, {{310004000, 489997000}, {309999000, 490003000}, {1, 0}}, }), from, to, visits) { std::vector visited; BOOST_CHECK_NO_THROW(fixedTerrtain.walk(from, to, [&visited](auto fh) { visited.emplace_back(fh.idx()); })); BOOST_CHECK_EQUAL_COLLECTIONS(visited.begin(), visited.end(), visits.begin(), visits.end()); } BOOST_DATA_TEST_CASE(walkTerrainSetsFromFace, boost::unit_test::data::make({ {{310002000, 490003000}, {310002000, 490003000}, {0}}, {{310003000, 490002000}, {310003000, 490002000}, {1}}, {{310002000, 490003000}, {310003000, 490002000}, {0, 1}}, {{310003000, 490002000}, {310002000, 490003000}, {1, 0}}, }), from, to, visits) { GeoData::PointFace pf {from}; BOOST_CHECK_NO_THROW(fixedTerrtain.walk(pf, to, [](auto) { })); BOOST_CHECK_EQUAL(pf.face(&fixedTerrtain).idx(), visits.front()); } BOOST_DATA_TEST_CASE(walkTerrainUntil, boost::unit_test::data::make({ {{310002000, 490003000}, {310002000, 490003000}, {0}}, {{310003000, 490002000}, {310003000, 490002000}, {1}}, {{310002000, 490003000}, {310003000, 490002000}, {0, 1}}, {{310003000, 490002000}, {310002000, 490003000}, {1, 0}}, {{310002000, 490003000}, {310202000, 490003000}, {0, 1, 2, 3, 4}}, {{310202000, 490003000}, {310002000, 490003000}, {8, 7, 6, 5, 4}}, {{310002000, 490003000}, {310002000, 490203000}, {0, 399, 398, 797, 796}}, }), from, to, visits) { std::vector visited; BOOST_CHECK_NO_THROW(fixedTerrtain.walkUntil(from, to, [&visited](auto fh) { visited.emplace_back(fh.idx()); return visited.size() >= 5; })); BOOST_CHECK_EQUAL_COLLECTIONS(visited.begin(), visited.end(), visits.begin(), visits.end()); } using FindEntiesData = std::tuple; BOOST_DATA_TEST_CASE(findEntries, boost::unit_test::data::make({ {{307739360, 494851616}, {314056992, 500079744}, 160667}, {{308597952, 498417056}, {315154144, 504671456}, 233623}, {{302690592, 502270912}, {311585184, 497868064}, 207311}, }), from, to, heh) { BOOST_CHECK_EQUAL(fixedTerrtain.findEntry(from, to).idx(), heh); } using DeformTerrainData = std::tuple, std::vector, std::string>>>; BOOST_TEST_DECORATOR(*boost::unit_test::timeout(2)); BOOST_DATA_TEST_CASE(deform, loadFixtureJson("geoData/deform/1.json"), points, cams) { Surface surface; surface.colorBias = RGB {0, 0, 1}; auto gd = std::make_shared(GeoData::createFlat({0, 0}, {1000000, 1000000}, 100)); BOOST_CHECK_NO_THROW(gd->setHeights(points, {.surface = surface})); BOOST_CHECK(std::ranges::all_of(gd->vertices(), [&gd](auto && vertex) { return gd->normal(vertex).z > 0; })); ApplicationBase ab; TestMainWindow tmw; TestRenderOutput tro {{640, 480}}; struct TestTerrain : public SceneProvider { explicit TestTerrain(std::shared_ptr gd) : terrain(std::move(gd)) { } const Terrain terrain; void content(const SceneShader & shader) const override { terrain.render(shader); } void environment(const SceneShader &, const SceneRenderer & sr) const override { sr.setAmbientLight({0.1, 0.1, 0.1}); sr.setDirectionalLight({1, 1, 1}, {{quarter_pi, -3 * half_pi}}, *this); } void lights(const SceneShader &) const override { } void shadows(const ShadowMapper & shadowMapper) const override { terrain.shadows(shadowMapper); } }; TestTerrain t {gd}; SceneRenderer ss {tro.size, tro.output}; std::for_each(cams.begin(), cams.end(), [&ss, &t, &tro](const auto & cam) { ss.camera.setView(cam.first.first, glm::normalize(cam.first.second)); BOOST_CHECK_NO_THROW(ss.render(t)); Texture::save(tro.outImage, cam.second.c_str()); }); } BOOST_DATA_TEST_CASE( deformMulti, loadFixtureJson>>("geoData/deform/multi1.json"), points) { BOOST_REQUIRE(!points.empty()); Surface surface; auto gd = std::make_shared(GeoData::createFlat({0, 0}, {1000000, 1000000}, 100)); for (const auto & strip : points) { BOOST_REQUIRE_GE(strip.size(), 3); BOOST_CHECK_NO_THROW(gd->setHeights(strip, {.surface = surface, .nearNodeTolerance = 50})); } }