From ca276ca5471a4e7a137f68a81feb150282eae62f Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Wed, 7 Feb 2024 23:50:50 +0000 Subject: Add stripiter A generic iterator wrapper returning a tuple of 3 references to the original values, as processed in the fashion of an OpenGL triangle strip. --- test/test-lib.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'test') diff --git a/test/test-lib.cpp b/test/test-lib.cpp index 58b769a..5f0b5e5 100644 --- a/test/test-lib.cpp +++ b/test/test-lib.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -49,3 +50,22 @@ BOOST_AUTO_TEST_CASE(generate_move_and_delete) } BOOST_CHECK(active.empty()); } + +constexpr std::array TRIANGLE_STRIP_IN {0, 1, 2, 3, 4, 5}; +static_assert(std::distance(strip_begin(TRIANGLE_STRIP_IN), strip_end(TRIANGLE_STRIP_IN)) == 4); + +BOOST_AUTO_TEST_CASE(triangle_strip_iter) +{ + constexpr std::array TRIANGLE_STRIP_EXPECTED {0, 1, 2, 2, 1, 3, 2, 3, 4, 4, 3, 5}; + + std::vector out; + std::for_each(strip_begin(TRIANGLE_STRIP_IN), strip_end(TRIANGLE_STRIP_IN), [&out](const auto & t) { + const auto [a, b, c] = t; + out.push_back(a); + out.push_back(b); + out.push_back(c); + }); + BOOST_REQUIRE_EQUAL(out.size(), (TRIANGLE_STRIP_IN.size() - 2) * 3); + BOOST_CHECK_EQUAL_COLLECTIONS( + out.begin(), out.end(), TRIANGLE_STRIP_EXPECTED.begin(), TRIANGLE_STRIP_EXPECTED.end()); +} -- cgit v1.2.3 From 849f4aa735352704995ffb51bf23dadf795bb119 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Tue, 13 Feb 2024 01:36:42 +0000 Subject: Include face handle in intersectRay result --- game/geoData.cpp | 8 ++++---- game/geoData.h | 6 ++++-- test/test-geoData.cpp | 2 +- ui/builders/freeExtend.cpp | 6 +++--- ui/builders/straight.cpp | 6 +++--- 5 files changed, 15 insertions(+), 13 deletions(-) (limited to 'test') diff --git a/game/geoData.cpp b/game/geoData.cpp index ae43f3f..0dbc0ac 100644 --- a/game/geoData.cpp +++ b/game/geoData.cpp @@ -213,16 +213,16 @@ GeoData::positionAt(const PointFace & p) const return positionOnTriangle(p.point, triangle<3>(p.face(this))); } -[[nodiscard]] std::optional +[[nodiscard]] GeoData::IntersectionResult GeoData::intersectRay(const Ray & ray) const { return intersectRay(ray, findPoint(ray.start)); } -[[nodiscard]] std::optional +[[nodiscard]] GeoData::IntersectionResult GeoData::intersectRay(const Ray & ray, FaceHandle face) const { - std::optional out; + GeoData::IntersectionResult out; walkUntil(PointFace {ray.start, face}, ray.start.xy() + GlobalPosition2D(ray.direction.xy() * RelativePosition2D(upperExtent.xy() - lowerExtent.xy())), @@ -231,7 +231,7 @@ GeoData::intersectRay(const Ray & ray, FaceHandle face) const RelativeDistance dist {}; const auto t = triangle<3>(face); if (ray.intersectTriangle(t.x, t.y, t.z, bari, dist)) { - out = t * bari; + out.emplace(t * bari, face); return true; } return false; diff --git a/game/geoData.h b/game/geoData.h index 2fd3aa5..f599552 100644 --- a/game/geoData.h +++ b/game/geoData.h @@ -74,8 +74,10 @@ public: [[nodiscard]] FaceHandle findPoint(GlobalPosition2D, FaceHandle start) const; [[nodiscard]] GlobalPosition3D positionAt(const PointFace &) const; - [[nodiscard]] std::optional intersectRay(const Ray &) const; - [[nodiscard]] std::optional intersectRay(const Ray &, FaceHandle start) const; + using IntersectionLocation = std::pair; + using IntersectionResult = std::optional; + [[nodiscard]] IntersectionResult intersectRay(const Ray &) const; + [[nodiscard]] IntersectionResult intersectRay(const Ray &, FaceHandle start) const; void walk(const PointFace & from, const GlobalPosition2D to, const std::function & op) const; void walkUntil(const PointFace & from, const GlobalPosition2D to, const std::function & op) const; diff --git a/test/test-geoData.cpp b/test/test-geoData.cpp index efe845c..f8437c3 100644 --- a/test/test-geoData.cpp +++ b/test/test-geoData.cpp @@ -114,7 +114,7 @@ BOOST_DATA_TEST_CASE(findRayIntersect, }), p, d, i) { - BOOST_CHECK_EQUAL(fixedTerrtain.intersectRay({p, d}).value(), i); + BOOST_CHECK_EQUAL(fixedTerrtain.intersectRay({p, d})->first, i); } BOOST_AUTO_TEST_CASE(boundaryWalk) diff --git a/ui/builders/freeExtend.cpp b/ui/builders/freeExtend.cpp index 47356c3..db127e6 100644 --- a/ui/builders/freeExtend.cpp +++ b/ui/builders/freeExtend.cpp @@ -19,7 +19,7 @@ BuilderFreeExtend::move( candidateLinks.objects = network->candidateJoins(*p1, p->pos); } else if (const auto p = geoData->intersectRay(ray)) { - candidateLinks.objects = network->candidateExtend(*p1, *p); + candidateLinks.objects = network->candidateExtend(*p1, p->first); } else { candidateLinks.removeAll(); @@ -42,8 +42,8 @@ BuilderFreeExtend::click( p1 = p->pos; } else if (const auto p = geoData->intersectRay(ray)) { - network->addExtend(*p1, *p); - p1 = *p; + network->addExtend(*p1, p->first); + p1 = p->first; } } else { diff --git a/ui/builders/straight.cpp b/ui/builders/straight.cpp index 0c4a3e2..43f5ec8 100644 --- a/ui/builders/straight.cpp +++ b/ui/builders/straight.cpp @@ -16,7 +16,7 @@ BuilderStraight::move( { if (p1) { if (const auto p = geoData->intersectRay(ray)) { - candidateLinks.objects = network->candidateStraight(*p1, *p); + candidateLinks.objects = network->candidateStraight(*p1, p->first); } else { candidateLinks.removeAll(); @@ -32,12 +32,12 @@ BuilderStraight::click( case SDL_BUTTON_LEFT: if (const auto p = geoData->intersectRay(ray)) { if (p1) { - create(network, *p1, *p); + create(network, *p1, p->first); candidateLinks.removeAll(); p1.reset(); } else { - p1 = p; + p1 = p->first; } } return; -- cgit v1.2.3 From 3770b101cfc5ad37a069850b422f0098ade7fc08 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Thu, 22 Feb 2024 01:16:29 +0000 Subject: First cut of terrain deformation This "works" for some definition of works... But it's flawed in many ways, not least the following: * It's glitchy for no obvious reason * It's unreliable; fails if you tweak the parameters * The sides of the modified area are triangulated in the dumbest possible fashion, which results in ill-formed polygons * Probably other things, but... It works, remember... --- game/geoData.cpp | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++ game/geoData.h | 2 + test/test-geoData.cpp | 60 ++++++++++++++++++ 3 files changed, 228 insertions(+) (limited to 'test') diff --git a/game/geoData.cpp b/game/geoData.cpp index 0dbc0ac..86b9152 100644 --- a/game/geoData.cpp +++ b/game/geoData.cpp @@ -1,7 +1,10 @@ #include "geoData.h" +#include "collections.h" #include #include +#include #include +#include GeoData GeoData::loadFromAsciiGrid(const std::filesystem::path & input) @@ -375,3 +378,166 @@ GeoData::triangleContainsTriangle(const Triangle<2> & a, const Triangle<2> & b) { return triangleContainsPoint(a.x, b) && triangleContainsPoint(a.y, b) && triangleContainsPoint(a.z, b); } + +void +GeoData::setHeights(const std::span triangleStrip) +{ + std::set nosplit; + // Create new vertices + std::vector newVerts; + newVerts.reserve(newVerts.size()); + std::transform(triangleStrip.begin(), triangleStrip.end(), std::back_inserter(newVerts), [this](const auto tsVert) { + return add_vertex(tsVert); + }); + // Create new faces + std::vector newFaces; + newFaces.reserve(newVerts.size() - 2); + std::transform(strip_begin(newVerts), strip_end(newVerts), std::back_inserter(newFaces), + [this, &nosplit](const auto & newVert) { + const auto [a, b, c] = newVert; + auto faceHandle = add_face(a, b, c); + std::copy(fe_begin(faceHandle), fe_end(faceHandle), std::inserter(nosplit, nosplit.end())); + return faceHandle; + }); + std::vector boundary; + boundaryWalk( + [out = std::back_inserter(boundary)](const auto boundaryHeh) mutable { + out = boundaryHeh; + }, + *voh_begin(newVerts.front())); + + // Extrude corners + std::set cutpoints; + std::vector> extrusionExtents; + std::vector extrusionVertices; + std::transform(boundary.begin(), boundary.end(), std::back_inserter(extrusionExtents), + [this, &nosplit, &cutpoints, &extrusionVertices](const auto boundaryHeh) { + const auto vectorNormal + = [](const glm::vec<2, T, Q> & v) -> glm::vec<2, T, Q> { + return {-v.y, v.x}; + }; + + const auto p0 = point(from_vertex_handle(prev_halfedge_handle(boundaryHeh))); + const auto p1 = point(from_vertex_handle(boundaryHeh)); + const auto p2 = point(to_vertex_handle(boundaryHeh)); + const auto e0 = glm::normalize(vectorNormal(RelativePosition2D(p1 - p0))); + const auto e1 = glm::normalize(vectorNormal(RelativePosition2D(p2 - p1))); + const auto mid = glm::normalize((e0 + e1) / 2.F); + const auto doExtrusion = [mid, p1, this, &nosplit, &cutpoints, &extrusionVertices]( + RelativeDistance vert, GlobalDistance limit) { + const auto extrusionDir = glm::normalize(mid || vert); + + if (const auto intersect = intersectRay({p1, extrusionDir})) { + auto splitVertex = split(intersect->second, intersect->first); + cutpoints.insert(splitVertex); + extrusionVertices.push_back(splitVertex); + std::copy(ve_begin(splitVertex), ve_end(splitVertex), std::inserter(nosplit, nosplit.end())); + } + + const auto extrusion + = extrusionDir * std::max(0.F, RelativeDistance(limit - p1.z) / extrusionDir.z); + return p1 + GlobalPosition3D(extrusion); + }; + return std::make_pair(doExtrusion(-2, lowerExtent.z - 100), doExtrusion(2, upperExtent.z + 100)); + }); + + // Cut existing terrain + extrusionExtents.emplace_back(extrusionExtents.front()); // Circular next + extrusionVertices.emplace_back(extrusionVertices.front()); + std::vector> boundaryFaces; + std::transform(boundary.begin(), boundary.end(), std ::back_inserter(boundaryFaces), + [ex = extrusionExtents.begin(), exv = extrusionVertices.begin(), this, &nosplit]( + const auto boundaryHeh) mutable { + const auto fromVertex = from_vertex_handle(boundaryHeh); + const auto p0 = point(fromVertex); + auto toVertex = to_vertex_handle(boundaryHeh); + const auto p1 = point(toVertex); + const auto nex = ex + 1; + const auto nexv = exv + 1; + const std::array, 4> triangles {{ + {p0, ex->first, nex->first}, + {p0, p1, nex->first}, + {p0, ex->second, nex->second}, + {p0, p1, nex->second}, + }}; + + std::vector sideVerts {fromVertex, *exv}; + for (auto currentVertex = *exv; + std::any_of(voh_begin(currentVertex), voh_end(currentVertex), [&](const auto currentVertexOut) { + const auto next = next_halfedge_handle(currentVertexOut); + const auto nextVertex = to_vertex_handle(next); + const auto startVertex = from_vertex_handle(next); + if (nextVertex == *++sideVerts.rbegin()) { + // This half edge goes back to the previous vertex + return false; + } + if (nextVertex == *nexv) { + // The next half edge goes to the termination point + return false; + } + const auto edge = edge_handle(next); + if (nosplit.contains(edge)) { + // This edge should not be split (for some reason, maybe no longer applies) + return false; + } + const auto ep0 = point(startVertex); + const auto ep1 = point(nextVertex); + const auto diff = RelativePosition3D(ep1 - ep0); + const auto length = glm::length(diff); + const auto dir = diff / length; + const Ray r {ep0, dir}; + return std::any_of(triangles.begin(), triangles.end(), [&](const auto & triangle) { + BaryPosition bary; + RelativeDistance dist {}; + + if (r.intersectTriangle(triangle.x, triangle.y, triangle.z, bary, dist) + && dist <= length - 1 && dist >= 1) { + const auto splitPos = triangle * bary; + currentVertex = sideVerts.emplace_back(split(edge, splitPos)); + return true; + } + return false; + }); + });) { + ; + } + sideVerts.emplace_back(*nexv); + sideVerts.emplace_back(toVertex); + ex = nex; + exv++; + return sideVerts; + }); + + // Remove old faces + std::set visited; + auto removeOld = [&](auto & self, const auto face) -> void { + if (visited.insert(face).second) { + std::vector neighbourFaces; + std::for_each(fh_begin(face), fh_end(face), [&](const auto fh) { + if (std::none_of(boundaryFaces.begin(), boundaryFaces.end(), [fh, this](const auto & bf) { + return std::find(bf.begin(), bf.end(), from_vertex_handle(fh)) != bf.end() + && std::find(bf.begin(), bf.end(), to_vertex_handle(fh)) != bf.end(); + })) { + neighbourFaces.emplace_back(opposite_face_handle(fh)); + } + }); + + delete_face(face, false); + std::for_each(neighbourFaces.begin(), neighbourFaces.end(), [&self](const auto nextFace) { + if (nextFace.is_valid()) { + self(self, nextFace); + } + }); + } + }; + removeOld(removeOld, findPoint(triangleStrip.front())); + + std::for_each(boundaryFaces.begin(), boundaryFaces.end(), [&](auto & boundaryFace) { + std::reverse(boundaryFace.begin(), boundaryFace.end()); + add_face(boundaryFace); + }); + + // Tidy up + garbage_collection(); + update_vertex_normals_only(); +} diff --git a/game/geoData.h b/game/geoData.h index 6c6ae26..4d8e11c 100644 --- a/game/geoData.h +++ b/game/geoData.h @@ -89,6 +89,8 @@ public: [[nodiscard]] HalfedgeHandle findEntry(const GlobalPosition2D from, const GlobalPosition2D to) const; + void setHeights(const std::span triangleStrip); + [[nodiscard]] auto getExtents() const { diff --git a/test/test-geoData.cpp b/test/test-geoData.cpp index f8437c3..6602b05 100644 --- a/test/test-geoData.cpp +++ b/test/test-geoData.cpp @@ -1,7 +1,12 @@ #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 @@ -199,3 +204,58 @@ BOOST_DATA_TEST_CASE(findEntries, { BOOST_CHECK_EQUAL(fixedTerrtain.findEntry(from, to).idx(), heh); } + +BOOST_AUTO_TEST_CASE(setTriangle, *boost::unit_test::timeout(5)) +{ + auto gd = std::make_shared(GeoData::createFlat({0, 0}, {1000000, 1000000}, 100)); + std::array points { + GlobalPosition3D {70100, 123000, 6000}, + GlobalPosition3D {50100, 52300, 6000}, + GlobalPosition3D {191000, 283000, 8000}, + GlobalPosition3D {241000, 123330, -2000}, + }; + BOOST_CHECK_NO_THROW(gd->setHeights(points)); + + ApplicationBase ab; + TestMainWindow tmw; + TestRenderOutput tro {{1792, 1024}}; + + SceneRenderer ss {tro.size, tro.output}; + ss.camera.setView({-90000, -90000, 300000}, glm::normalize(glm::vec3 {1, 1, -1.5F})); + + struct TestTerrain : public SceneProvider { + explicit TestTerrain(std::shared_ptr gd) : terrain(std::move(gd)) { } + + const Terrain terrain; + + void + content(const SceneShader & shader) const override + { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + terrain.render(shader); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + + void + environment(const SceneShader &, const SceneRenderer & sr) const override + { + sr.setAmbientLight({0.1, 0.1, 0.1}); + sr.setDirectionalLight({1, 1, 1}, south + down, *this); + } + + void + lights(const SceneShader &) const override + { + } + + void + shadows(const ShadowMapper & shadowMapper) const override + { + terrain.shadows(shadowMapper); + } + }; + + TestTerrain t {gd}; + BOOST_CHECK_NO_THROW(ss.render(t)); + Texture::save(tro.outImage, "/tmp/geoData.tga"); +} -- cgit v1.2.3 From 848fc7fe1349c36c839b3642f9607ba80ffe4e8e Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Mon, 26 Feb 2024 01:42:26 +0000 Subject: Make terrain deformation test a data test Easily test multiple deformations and view them from different angles --- test/test-geoData.cpp | 48 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-) (limited to 'test') diff --git a/test/test-geoData.cpp b/test/test-geoData.cpp index ac1d668..e1f8fce 100644 --- a/test/test-geoData.cpp +++ b/test/test-geoData.cpp @@ -205,23 +205,41 @@ BOOST_DATA_TEST_CASE(findEntries, BOOST_CHECK_EQUAL(fixedTerrtain.findEntry(from, to).idx(), heh); } -BOOST_AUTO_TEST_CASE(setTriangle, *boost::unit_test::timeout(5)) +using DeformTerrainData + = std::tuple, std::vector, const char *>>>; + +template +std::ostream & +operator<<(std::ostream & s, const Ray & ray) +{ + return s << "Ray" << std::make_pair(ray.start, ray.direction); +} + +BOOST_DATA_TEST_CASE(setTriangle, + boost::unit_test::data::make({ + {{ + {70100, 123000, 6000}, + {50100, 52300, 6000}, + {191000, 283000, 8000}, + {241000, 123330, -2000}, + }, + { + {{{20000, 20000, 90000}, glm::normalize(Direction3D {1, 1, -1.5F})}, + "/tmp/geoData0.tga"}, + {{{30000, 164000, 90000}, glm::normalize(Direction3D {1, -1, -1.5F})}, + "/tmp/geoData1.tga"}, + {{{288000, 162000, 90000}, glm::normalize(Direction3D {-1, -1, -1.5F})}, + "/tmp/geoData2.tga"}, + }}, + }), + points, cams) { auto gd = std::make_shared(GeoData::createFlat({0, 0}, {1000000, 1000000}, 100)); - std::array points { - GlobalPosition3D {70100, 123000, 6000}, - GlobalPosition3D {50100, 52300, 6000}, - GlobalPosition3D {191000, 283000, 8000}, - GlobalPosition3D {241000, 123330, -2000}, - }; BOOST_CHECK_NO_THROW(gd->setHeights(points)); ApplicationBase ab; TestMainWindow tmw; - TestRenderOutput tro {{1792, 1024}}; - - SceneRenderer ss {tro.size, tro.output}; - ss.camera.setView({-90000, -90000, 300000}, glm::normalize(glm::vec3 {1, 1, -1.5F})); + TestRenderOutput tro {{640, 480}}; struct TestTerrain : public SceneProvider { explicit TestTerrain(std::shared_ptr gd) : terrain(std::move(gd)) { } @@ -256,6 +274,10 @@ BOOST_AUTO_TEST_CASE(setTriangle, *boost::unit_test::timeout(5)) }; TestTerrain t {gd}; - BOOST_CHECK_NO_THROW(ss.render(t)); - Texture::save(tro.outImage, "/tmp/geoData.tga"); + SceneRenderer ss {tro.size, tro.output}; + std::for_each(cams.begin(), cams.end(), [&ss, &t, &tro](const auto & cam) { + ss.camera.setView(cam.first.start, cam.first.direction); + BOOST_CHECK_NO_THROW(ss.render(t)); + Texture::save(tro.outImage, cam.second); + }); } -- cgit v1.2.3 From 6c13527020d65c410ee8daef18ee8273cdf8e827 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Mon, 26 Feb 2024 22:48:37 +0000 Subject: Add helper for loading fixtures for data tests from fixture JSON --- test/testHelpers.h | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'test') diff --git a/test/testHelpers.h b/test/testHelpers.h index f2b0901..58e4372 100644 --- a/test/testHelpers.h +++ b/test/testHelpers.h @@ -2,11 +2,22 @@ #include #include +#include +#include #include // IWYU pragma: keep std::setprecision +#include #include std::unique_ptr uasprintf(const char * fmt, ...) __attribute__((format(printf, 1, 2))); +template +decltype(auto) +loadFixtureJson(const std::filesystem::path & path) +{ + std::ifstream in {FIXTURESDIR / path}; + return Persistence::JsonParsePersistence {}.loadState>(in); +} + #define BOOST_CHECK_CLOSE_VEC(a_, b_) \ { \ const auto a {a_}, b {b_}; \ -- cgit v1.2.3 From aa07f1bdd607a6dead9cdec59ff950f6e9a5c28c Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Mon, 26 Feb 2024 23:47:39 +0000 Subject: Load terrain deform fixture data from JSON --- test/Jamfile.jam | 2 +- test/fixtures/geoData/deform/1.json | 73 +++++++++++++++++++++++++++++++++++++ test/test-geoData.cpp | 34 +++-------------- 3 files changed, 79 insertions(+), 30 deletions(-) create mode 100644 test/fixtures/geoData/deform/1.json (limited to 'test') diff --git a/test/Jamfile.jam b/test/Jamfile.jam index 733ef05..1b07b5a 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -46,7 +46,7 @@ lib test : [ glob *.cpp : test-*.cpp perf-*.cpp ] ; run test-collection.cpp ; run test-maths.cpp ; run test-lib.cpp ; -run test-geoData.cpp : -- : fixtures/height/SD19.asc : test ; +run test-geoData.cpp : -- : [ sequence.insertion-sort [ glob-tree $(fixtures)/geoData : *.json ] fixtures/height/SD19.asc ] : test ; run test-network.cpp : : : test ; run test-persistence.cpp : -- : [ sequence.insertion-sort [ glob-tree $(fixtures)/json : *.json ] ] : test ; run test-text.cpp : -- : test-glContainer : test ; diff --git a/test/fixtures/geoData/deform/1.json b/test/fixtures/geoData/deform/1.json new file mode 100644 index 0000000..33ac86d --- /dev/null +++ b/test/fixtures/geoData/deform/1.json @@ -0,0 +1,73 @@ +[ + [ + [ + [ + 70100, + 123000, + 6000 + ], + [ + 50100, + 52300, + 6000 + ], + [ + 191000, + 283000, + 8000 + ], + [ + 241000, + 123330, + -2000 + ] + ], + [ + [ + [ + [ + 20000, + 20000, + 90000 + ], + [ + 1, + 1, + -1.5 + ] + ], + "/tmp/geoData0.tga" + ], + [ + [ + [ + 30000, + 164000, + 90000 + ], + [ + 1, + -1, + -1.5 + ] + ], + "/tmp/geoData1.tga" + ], + [ + [ + [ + 288000, + 162000, + 90000 + ], + [ + -1, + -1, + -1.5 + ] + ], + "/tmp/geoData2.tga" + ] + ] + ] +] diff --git a/test/test-geoData.cpp b/test/test-geoData.cpp index e1f8fce..36d008b 100644 --- a/test/test-geoData.cpp +++ b/test/test-geoData.cpp @@ -205,34 +205,10 @@ BOOST_DATA_TEST_CASE(findEntries, BOOST_CHECK_EQUAL(fixedTerrtain.findEntry(from, to).idx(), heh); } -using DeformTerrainData - = std::tuple, std::vector, const char *>>>; +using DeformTerrainData = std::tuple, + std::vector, std::string>>>; -template -std::ostream & -operator<<(std::ostream & s, const Ray & ray) -{ - return s << "Ray" << std::make_pair(ray.start, ray.direction); -} - -BOOST_DATA_TEST_CASE(setTriangle, - boost::unit_test::data::make({ - {{ - {70100, 123000, 6000}, - {50100, 52300, 6000}, - {191000, 283000, 8000}, - {241000, 123330, -2000}, - }, - { - {{{20000, 20000, 90000}, glm::normalize(Direction3D {1, 1, -1.5F})}, - "/tmp/geoData0.tga"}, - {{{30000, 164000, 90000}, glm::normalize(Direction3D {1, -1, -1.5F})}, - "/tmp/geoData1.tga"}, - {{{288000, 162000, 90000}, glm::normalize(Direction3D {-1, -1, -1.5F})}, - "/tmp/geoData2.tga"}, - }}, - }), - points, cams) +BOOST_DATA_TEST_CASE(deform, loadFixtureJson("geoData/deform/1.json"), points, cams) { auto gd = std::make_shared(GeoData::createFlat({0, 0}, {1000000, 1000000}, 100)); BOOST_CHECK_NO_THROW(gd->setHeights(points)); @@ -276,8 +252,8 @@ BOOST_DATA_TEST_CASE(setTriangle, 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.start, cam.first.direction); + ss.camera.setView(cam.first.first, glm::normalize(cam.first.second)); BOOST_CHECK_NO_THROW(ss.render(t)); - Texture::save(tro.outImage, cam.second); + Texture::save(tro.outImage, cam.second.c_str()); }); } -- cgit v1.2.3 From 421ae75fa94a05b71c03255af4ad597a60fc8ed9 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Fri, 8 Mar 2024 01:17:11 +0000 Subject: Rework stream support to work with any collection --- lib/stream_support.h | 16 ++++------------ test/test-static-stream_support.cpp | 21 +++++++++++++++------ 2 files changed, 19 insertions(+), 18 deletions(-) (limited to 'test') diff --git a/lib/stream_support.h b/lib/stream_support.h index fa536f1..57d82a1 100644 --- a/lib/stream_support.h +++ b/lib/stream_support.h @@ -11,17 +11,16 @@ template concept stringlike = requires(const S & s) { s.substr(0); }; template -concept spanable = std::is_constructible_v, T> && !stringlike - && !std::is_same_v, T>; +concept NonStringIterableCollection + = std::is_same_v().begin()), decltype(std::declval().end())> && !stringlike; namespace std { - template std::ostream & - operator<<(std::ostream & s, const span v) + operator<<(std::ostream & s, const NonStringIterableCollection auto & v) { s << '('; for (const auto & i : v) { - if (&i != &v.front()) { + if (&i != &*v.begin()) { s << ", "; } s << i; @@ -43,13 +42,6 @@ namespace std { return (s << std::span {&v[0], L}); } - template - std::ostream & - operator<<(std::ostream & s, const T & v) - { - return (s << std::span {v}); - } - template std::ostream & operator<<(std::ostream & s, const std::pair & v) diff --git a/test/test-static-stream_support.cpp b/test/test-static-stream_support.cpp index 3002ccc..6bf9ea4 100644 --- a/test/test-static-stream_support.cpp +++ b/test/test-static-stream_support.cpp @@ -1,11 +1,20 @@ #include "stream_support.h" #include +#include +#include #include -static_assert(spanable>); -static_assert(spanable>); -static_assert(spanable>); -static_assert(spanable>); -static_assert(!spanable); -static_assert(!spanable); +static_assert(NonStringIterableCollection>); +static_assert(NonStringIterableCollection>); +static_assert(NonStringIterableCollection>); +static_assert(NonStringIterableCollection>); +static_assert(NonStringIterableCollection>); +static_assert(NonStringIterableCollection>); +static_assert(!NonStringIterableCollection); +static_assert(!NonStringIterableCollection); + +static_assert(requires(std::vector i, std::ostream & o) { o << i; }); +static_assert(requires(std::array i, std::ostream & o) { o << i; }); +static_assert(requires(std::set i, std::ostream & o) { o << i; }); +static_assert(requires(std::map i, std::ostream & o) { o << i; }); -- cgit v1.2.3 From 567477ab8b4b09111d942039c4b2587b25fc2fe2 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Fri, 8 Mar 2024 20:41:37 +0000 Subject: Rewrite most of setHeights Accounts for sensible corners and fixes quite a few bugs and edge cases... introduces a few more, is still a bit glitchy in places --- game/geoData.cpp | 236 ++++++++++++++++++++---------------- test/fixtures/geoData/deform/1.json | 53 +++++++- 2 files changed, 186 insertions(+), 103 deletions(-) (limited to 'test') diff --git a/game/geoData.cpp b/game/geoData.cpp index 9a08a89..ac2f035 100644 --- a/game/geoData.cpp +++ b/game/geoData.cpp @@ -2,7 +2,6 @@ #include "collections.h" #include #include -#include #include #include @@ -381,6 +380,8 @@ GeoData::triangleContainsTriangle(const Triangle<2> & a, const Triangle<2> & b) void GeoData::setHeights(const std::span triangleStrip) { + static const RelativeDistance MAX_SLOPE = 1.5F; + if (triangleStrip.size() < 3) { return; } @@ -391,7 +392,7 @@ GeoData::setHeights(const std::span triangleStrip) std::transform(triangleStrip.begin(), triangleStrip.end(), std::back_inserter(newVerts), [this](const auto tsVert) { return add_vertex(tsVert); }); - // Create new faces + // Create new faces std::vector newFaces; newFaces.reserve(newVerts.size() - 2); std::transform( @@ -408,121 +409,152 @@ GeoData::setHeights(const std::span triangleStrip) *voh_begin(newVerts.front())); // Extrude corners - std::set cutpoints; - std::vector> extrusionExtents; - std::vector extrusionVertices; - std::transform(boundary.begin(), boundary.end(), std::back_inserter(extrusionExtents), - [this, &cutpoints, &extrusionVertices](const auto boundaryHeh) { - const auto vectorNormal - = [](const glm::vec<2, T, Q> & v) -> glm::vec<2, T, Q> { - return {-v.y, v.x}; - }; - - const auto p0 = point(from_vertex_handle(prev_halfedge_handle(boundaryHeh))); - const auto p1 = point(from_vertex_handle(boundaryHeh)); - const auto p2 = point(to_vertex_handle(boundaryHeh)); - const auto e0 = glm::normalize(vectorNormal(RelativePosition2D(p1 - p0))); - const auto e1 = glm::normalize(vectorNormal(RelativePosition2D(p2 - p1))); - const auto mid = glm::normalize((e0 + e1) / 2.F); - const auto doExtrusion - = [mid, p1, this, &cutpoints, &extrusionVertices](RelativeDistance vert, GlobalDistance limit) { - const auto extrusionDir = glm::normalize(mid || vert); - - if (const auto intersect = intersectRay({p1, extrusionDir})) { - auto splitVertex = split(intersect->second, intersect->first); - cutpoints.insert(splitVertex); - extrusionVertices.push_back(splitVertex); - } - - const auto extrusion - = extrusionDir * std::max(0.F, RelativeDistance(limit - p1.z) / extrusionDir.z); - return p1 + extrusion; - }; - return std::make_pair(doExtrusion(-2, lowerExtent.z - 100), doExtrusion(2, upperExtent.z + 100)); - }); + struct Extrusion { + VertexHandle boundaryVertex, extrusionVertex; + GlobalPosition3D lowerLimit, upperLimit; + }; + + std::vector extrusionExtents; + std::for_each(boundary.begin(), boundary.end(), [this, &extrusionExtents](const auto boundaryHeh) { + const auto vectorNormal = [](const glm::vec<2, T, Q> & v) -> glm::vec<2, T, Q> { + return {-v.y, v.x}; + }; + + const auto boundaryVertex = from_vertex_handle(boundaryHeh); + const auto p0 = point(from_vertex_handle(prev_halfedge_handle(boundaryHeh))); + const auto p1 = point(boundaryVertex); + const auto p2 = point(to_vertex_handle(boundaryHeh)); + const auto e0 = glm::normalize(vectorNormal(RelativePosition2D(p1 - p0))); + const auto e1 = glm::normalize(vectorNormal(RelativePosition2D(p2 - p1))); + + const auto doExtrusion = [this](VertexHandle & extrusionVertex, Direction2D direction, + GlobalPosition3D boundaryVertex, RelativeDistance vert, GlobalDistance limit) { + const auto extrusionDir = glm::normalize(direction || vert); + + if (!extrusionVertex.is_valid()) { + if (const auto intersect = intersectRay({boundaryVertex, extrusionDir})) { + auto splitVertex = split(intersect->second, intersect->first); + extrusionVertex = splitVertex; + } + else if (const auto intersect + = intersectRay({boundaryVertex + GlobalPosition3D {1, 1, 0}, extrusionDir})) { + auto splitVertex = split(intersect->second, intersect->first); + extrusionVertex = splitVertex; + } + else if (const auto intersect + = intersectRay({boundaryVertex + GlobalPosition3D {1, 0, 0}, extrusionDir})) { + auto splitVertex = split(intersect->second, intersect->first); + extrusionVertex = splitVertex; + } + } + + const auto extrusion + = extrusionDir * std::max(0.F, RelativeDistance(limit - boundaryVertex.z) / extrusionDir.z); + return boundaryVertex + extrusion; + }; + // Previous half edge end to current half end start arc tangents + const Arc arc {p1, p1 + (e0 || 0.F), p1 + (e1 || 0.F)}; + const auto limit = std::floor((arc.second - arc.first) * 5.F / pi); + const auto inc = (arc.second - arc.first) / limit; + for (float step = 1; step < limit; step += 1.F) { + const auto direction = sincosf(arc.first + (step * inc)); + VertexHandle extrusionVertex; + extrusionExtents.emplace_back(boundaryVertex, extrusionVertex, + doExtrusion(extrusionVertex, direction, p1, -MAX_SLOPE, lowerExtent.z - 10), + doExtrusion(extrusionVertex, direction, p1, MAX_SLOPE, upperExtent.z + 10)); + assert(extrusionVertex.is_valid()); + } + // Half edge start/end tangents + for (const auto p : {p1, p2}) { + VertexHandle extrusionVertex; + extrusionExtents.emplace_back(boundaryVertex, extrusionVertex, + doExtrusion(extrusionVertex, e1, p, -MAX_SLOPE, lowerExtent.z - 10), + doExtrusion(extrusionVertex, e1, p, MAX_SLOPE, upperExtent.z + 10)); + assert(extrusionVertex.is_valid()); + } + }); - // Cut existing terrain + // Cut existing terrain extrusionExtents.emplace_back(extrusionExtents.front()); // Circular next - extrusionVertices.emplace_back(extrusionVertices.front()); std::vector> boundaryFaces; - std::transform(boundary.begin(), boundary.end(), std ::back_inserter(boundaryFaces), - [ex = extrusionExtents.begin(), exv = extrusionVertices.begin(), this](const auto boundaryHeh) mutable { - const auto fromVertex = from_vertex_handle(boundaryHeh); - const auto p0 = point(fromVertex); - auto toVertex = to_vertex_handle(boundaryHeh); - const auto p1 = point(toVertex); - const auto nex = ex + 1; - const auto nexv = exv + 1; - const std::array, 4> triangles {{ - {p0, ex->first, nex->first}, - {p0, p1, nex->first}, - {p0, ex->second, nex->second}, - {p0, p1, nex->second}, - }}; - - std::vector sideVerts {fromVertex, *exv}; - for (auto currentVertex = *exv; - std::any_of(voh_begin(currentVertex), voh_end(currentVertex), [&](const auto currentVertexOut) { - const auto next = next_halfedge_handle(currentVertexOut); - const auto nextVertex = to_vertex_handle(next); - const auto startVertex = from_vertex_handle(next); - if (nextVertex == *++sideVerts.rbegin()) { - // This half edge goes back to the previous vertex - return false; - } - if (nextVertex == *nexv) { - // The next half edge goes to the termination point - return false; - } - const auto edge = edge_handle(next); - const auto ep0 = point(startVertex); - const auto ep1 = point(nextVertex); - const auto diff = RelativePosition3D(ep1 - ep0); - const auto length = glm::length(diff); - const auto dir = diff / length; - const Ray r {ep0, dir}; - return std::any_of(triangles.begin(), triangles.end(), [&](const auto & triangle) { - BaryPosition bary; - RelativeDistance dist {}; - - if (r.intersectTriangle(triangle.x, triangle.y, triangle.z, bary, dist) - && dist <= length - 1 && dist >= 1) { - const auto splitPos = triangle * bary; - currentVertex = sideVerts.emplace_back(split(edge, splitPos)); - return true; - } - return false; - }); - });) { + std::adjacent_find(extrusionExtents.begin(), extrusionExtents.end(), + [this, &boundaryFaces](const auto & first, const auto & second) { + const auto p0 = point(first.boundaryVertex); + std::vector> triangles { + {p0, first.lowerLimit, second.lowerLimit}, {p0, first.upperLimit, second.upperLimit}}; + const auto p1 = point(second.boundaryVertex); + if (first.boundaryVertex != second.boundaryVertex) { + triangles.emplace_back(p0, p1, second.lowerLimit); + triangles.emplace_back(p0, p1, second.upperLimit); + } + + auto & out = boundaryFaces.emplace_back(); + out.emplace_back(first.boundaryVertex); + out.emplace_back(first.extrusionVertex); + for (auto currentVertex = first.extrusionVertex; + std::none_of(voh_begin(currentVertex), voh_end(currentVertex), + [&](const auto currentVertexOut) { + // The next half edge goes to the termination point + const auto next = next_halfedge_handle(currentVertexOut); + const auto nextVertex = to_vertex_handle(next); + return (nextVertex == second.extrusionVertex); + }) + && std::any_of( + voh_begin(currentVertex), voh_end(currentVertex), [&](const auto currentVertexOut) { + const auto next = next_halfedge_handle(currentVertexOut); + const auto nextVertex = to_vertex_handle(next); + const auto startVertex = from_vertex_handle(next); + if (nextVertex == *++out.rbegin()) { + // This half edge goes back to the previous vertex + return false; + } + const auto edge = edge_handle(next); + const auto ep0 = point(startVertex); + const auto ep1 = point(nextVertex); + const auto diff = RelativePosition3D(ep1 - ep0); + const auto length = glm::length(diff); + const auto dir = diff / length; + const Ray r {ep0, dir}; + return std::any_of(triangles.begin(), triangles.end(), [&](const auto & triangle) { + BaryPosition bary; + RelativeDistance dist {}; + + if (r.intersectTriangle(triangle.x, triangle.y, triangle.z, bary, dist) + && dist <= length - 1 && dist >= 1) { + const auto splitPos = triangle * bary; + currentVertex = out.emplace_back(split(edge, splitPos)); + return true; + } + return false; + }); + });) { ; } - sideVerts.emplace_back(*nexv); - sideVerts.emplace_back(toVertex); - ex = nex; - exv++; - return sideVerts; + out.emplace_back(second.extrusionVertex); + if (first.boundaryVertex != second.boundaryVertex) { + out.emplace_back(second.boundaryVertex); + } + return false; }); - // Remove old faces + // Remove old faces std::set visited; auto removeOld = [&](auto & self, const auto face) -> void { if (visited.insert(face).second) { - std::vector neighbourFaces; std::for_each(fh_begin(face), fh_end(face), [&](const auto fh) { - if (std::none_of(boundaryFaces.begin(), boundaryFaces.end(), [fh, this](const auto & bf) { - return std::find(bf.begin(), bf.end(), from_vertex_handle(fh)) != bf.end() - && std::find(bf.begin(), bf.end(), to_vertex_handle(fh)) != bf.end(); - })) { - neighbourFaces.emplace_back(opposite_face_handle(fh)); + const auto b1 = to_vertex_handle(fh); + const auto b2 = from_vertex_handle(fh); + if (opposite_face_handle(fh).is_valid() + && std::none_of(boundaryFaces.begin(), boundaryFaces.end(), [b2, b1](const auto & bf) { + return std::adjacent_find(bf.begin(), bf.end(), [b2, b1](const auto v1, const auto v2) { + return b1 == v1 && b2 == v2; + }) != bf.end(); + })) { + self(self, opposite_face_handle(fh)); } }); delete_face(face, false); - std::for_each(neighbourFaces.begin(), neighbourFaces.end(), [&self](const auto nextFace) { - if (nextFace.is_valid()) { - self(self, nextFace); - } - }); } }; removeOld(removeOld, findPoint(triangleStrip.front())); diff --git a/test/fixtures/geoData/deform/1.json b/test/fixtures/geoData/deform/1.json index 33ac86d..2854e64 100644 --- a/test/fixtures/geoData/deform/1.json +++ b/test/fixtures/geoData/deform/1.json @@ -19,7 +19,7 @@ [ 241000, 123330, - -2000 + 2000 ] ], [ @@ -69,5 +69,56 @@ "/tmp/geoData2.tga" ] ] + ], + [ + [ + [ + 3000, + 1000, + 500 + ], + [ + 3000, + 2000, + 500 + ], + [ + 2000, + 1000, + 500 + ] + ], + [ + [ + [ + [ + -1000, + -3000, + 7000 + ], + [ + 1, + 1, + -1.5 + ] + ], + "/tmp/geoData3.tga" + ], + [ + [ + [ + 1800, + 2500, + 800 + ], + [ + 1, + -0.2, + -0.5 + ] + ], + "/tmp/geoData4.tga" + ] + ] ] ] -- cgit v1.2.3 From 0275b4d1ce7c2ea3dbbe31ebc71db5395722bdc9 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sat, 9 Mar 2024 12:05:37 +0000 Subject: Add timeout to deformation unit test --- test/test-geoData.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'test') diff --git a/test/test-geoData.cpp b/test/test-geoData.cpp index 36d008b..2d6b1aa 100644 --- a/test/test-geoData.cpp +++ b/test/test-geoData.cpp @@ -208,6 +208,8 @@ BOOST_DATA_TEST_CASE(findEntries, 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) { auto gd = std::make_shared(GeoData::createFlat({0, 0}, {1000000, 1000000}, 100)); -- cgit v1.2.3 From 4c05a12cf1c30eadcd5edd61f75b8fc92d3dbd7c Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sat, 9 Mar 2024 12:10:17 +0000 Subject: Add deformation test case with lower spec --- test/fixtures/geoData/deform/1.json | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'test') diff --git a/test/fixtures/geoData/deform/1.json b/test/fixtures/geoData/deform/1.json index 2854e64..fb45ce4 100644 --- a/test/fixtures/geoData/deform/1.json +++ b/test/fixtures/geoData/deform/1.json @@ -120,5 +120,41 @@ "/tmp/geoData4.tga" ] ] + ], + [ + [ + [ + 3000, + 1000, + 10 + ], + [ + 3000, + 2000, + 10 + ], + [ + 2000, + 1000, + 10 + ] + ], + [ + [ + [ + [ + -500, + -1500, + 3000 + ], + [ + 1, + 1, + -1.5 + ] + ], + "/tmp/geoData5.tga" + ] + ] ] ] -- cgit v1.2.3 From 4571ce1b4cac2ab9bf2454ed06d0d787604d62fa Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Thu, 21 Mar 2024 20:23:44 +0000 Subject: Add helper constructors to Arc * Two angles, wraps logic ensuring b after a * Two vector directions * Centre and two endpoints, in at least 2 dimensions, uses .xy() --- game/geoData.cpp | 2 +- game/network/rail.cpp | 10 ++++------ lib/maths.cpp | 5 +++++ lib/maths.h | 28 +++++++++------------------- test/Jamfile.jam | 1 + 5 files changed, 20 insertions(+), 26 deletions(-) (limited to 'test') diff --git a/game/geoData.cpp b/game/geoData.cpp index f4d9f65..aaa2548 100644 --- a/game/geoData.cpp +++ b/game/geoData.cpp @@ -453,7 +453,7 @@ GeoData::setHeights(const std::span triangleStrip) return extrusionDir; }; // Previous half edge end to current half end start arc tangents - const Arc arc {p1, p1 + (e0 || 0.F), p1 + (e1 || 0.F)}; + const Arc arc {e0, e1}; const auto limit = std::floor((arc.second - arc.first) * 5.F / pi); const auto inc = (arc.second - arc.first) / limit; for (float step = 1; step < limit; step += 1.F) { diff --git a/game/network/rail.cpp b/game/network/rail.cpp index e342224..fd07ace 100644 --- a/game/network/rail.cpp +++ b/game/network/rail.cpp @@ -95,9 +95,8 @@ round_sleepers(const float v) return round_frac(v, sleepers); } -RailLinkStraight::RailLinkStraight( - NetworkLinkHolder & instances, const Node::Ptr & a, const Node::Ptr & b) : - RailLinkStraight(instances, a, b, b->pos - a->pos) +RailLinkStraight::RailLinkStraight(NetworkLinkHolder & instances, const Node::Ptr & a, + const Node::Ptr & b) : RailLinkStraight(instances, a, b, b->pos - a->pos) { } @@ -109,9 +108,8 @@ RailLinkStraight::RailLinkStraight( { } -RailLinkCurve::RailLinkCurve( - NetworkLinkHolder & instances, const Node::Ptr & a, const Node::Ptr & b, GlobalPosition2D c) : - RailLinkCurve(instances, a, b, c || a->pos.z, {c || 0, a->pos, b->pos}) +RailLinkCurve::RailLinkCurve(NetworkLinkHolder & instances, const Node::Ptr & a, const Node::Ptr & b, + GlobalPosition2D c) : RailLinkCurve(instances, a, b, c || a->pos.z, {c, a->pos, b->pos}) { } diff --git a/lib/maths.cpp b/lib/maths.cpp index bf17204..51e27fe 100644 --- a/lib/maths.cpp +++ b/lib/maths.cpp @@ -4,6 +4,11 @@ #include #include +Arc::Arc(const RelativePosition2D & dir0, const RelativePosition2D & dir1) : + Arc {vector_yaw(dir0), vector_yaw(dir1)} { } + +Arc::Arc(const Angle anga, const Angle angb) : pair {anga, (angb < anga) ? angb + two_pi : angb} { } + glm::mat4 flat_orientation(const Direction3D & diff) { diff --git a/lib/maths.h b/lib/maths.h index 20a397b..656fefd 100644 --- a/lib/maths.h +++ b/lib/maths.h @@ -8,11 +8,16 @@ #include #include -struct Arc : public std::pair { - using std::pair::pair; +struct Arc : public std::pair { + template + requires(Lc >= 2, Le >= 2) + Arc(const glm::vec & centre, const glm::vec & e0p, const glm::vec & e1p) : + Arc {RelativePosition2D {e0p.xy() - centre.xy()}, RelativePosition2D {e1p.xy() - centre.xy()}} + { + } - template - Arc(const glm::vec<3, T, Q> & centre3, const glm::vec<3, T, Q> & e0p, const glm::vec<3, T, Q> & e1p); + Arc(const RelativePosition2D & dir0, const RelativePosition2D & dir1); + Arc(const Angle angb, const Angle anga); auto operator[](bool i) const @@ -244,21 +249,6 @@ midpoint(const std::pair & v) return std::midpoint(v.first, v.second); } -template -Arc::Arc(const glm::vec<3, T, Q> & centre3, const glm::vec<3, T, Q> & e0p, const glm::vec<3, T, Q> & e1p) : - Arc([&]() -> Arc { - const auto diffa = e0p - centre3; - const auto diffb = e1p - centre3; - const auto anga = vector_yaw(diffa); - const auto angb = [&diffb, &anga]() { - const auto angb = vector_yaw(diffb); - return (angb < anga) ? angb + two_pi : angb; - }(); - return {anga, angb}; - }()) -{ -} - // Conversions template inline constexpr auto diff --git a/test/Jamfile.jam b/test/Jamfile.jam index 1b07b5a..cce4513 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -12,6 +12,7 @@ project : requirements BOOST_TEST_DYN_LINK RESDIR=\\\"$(res)/\\\" FIXTURESDIR=\\\"$(fixtures)/\\\" + GLM_FORCE_SWIZZLE debug:pedantic debug:on debug:-Wnon-virtual-dtor -- cgit v1.2.3 From f380b20ddf1ead6447a6ee6e137a14c4aa226c12 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sat, 23 Mar 2024 21:41:10 +0000 Subject: Handle and test concave surface boundaries --- game/geoData.cpp | 19 ++++++++++++----- test/fixtures/geoData/deform/1.json | 41 +++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) (limited to 'test') diff --git a/game/geoData.cpp b/game/geoData.cpp index 80a4eba..30c382c 100644 --- a/game/geoData.cpp +++ b/game/geoData.cpp @@ -446,11 +446,20 @@ GeoData::setHeights(const std::span triangleStrip) return extrusionDir; }; // Previous half edge end to current half end start arc tangents - const Arc arc {e0, e1}; - const auto limit = std::ceil(arc.length() * 5.F / pi); - const auto inc = arc.length() / limit; - for (float step = 0; step <= limit; step += 1.F) { - const auto direction = sincosf(arc.first + (step * inc)); + if (const Arc arc {e0, e1}; arc.length() < pi) { + const auto limit = std::ceil(arc.length() * 5.F / pi); + const auto inc = arc.length() / limit; + for (float step = 0; step <= limit; step += 1.F) { + const auto direction = sincosf(arc.first + (step * inc)); + VertexHandle extrusionVertex; + extrusionExtents.emplace_back(boundaryVertex, extrusionVertex, + doExtrusion(extrusionVertex, direction, p1, -MAX_SLOPE), + doExtrusion(extrusionVertex, direction, p1, MAX_SLOPE)); + assert(extrusionVertex.is_valid()); + } + } + else { + const auto direction = normalize(e0 + e1) / sinf((arc.length() - pi) / 2.F); VertexHandle extrusionVertex; extrusionExtents.emplace_back(boundaryVertex, extrusionVertex, doExtrusion(extrusionVertex, direction, p1, -MAX_SLOPE), diff --git a/test/fixtures/geoData/deform/1.json b/test/fixtures/geoData/deform/1.json index fb45ce4..6930238 100644 --- a/test/fixtures/geoData/deform/1.json +++ b/test/fixtures/geoData/deform/1.json @@ -156,5 +156,46 @@ "/tmp/geoData5.tga" ] ] + ], + [ + [ + [ + 1500, + 2000, + 800 + ], + [ + 3000, + 2000, + 800 + ], + [ + 5000, + 4000, + 800 + ], + [ + 3500, + 700, + 800 + ] + ], + [ + [ + [ + [ + -1000, + -3000, + 7000 + ], + [ + 1, + 1, + -1.5 + ] + ], + "/tmp/geoData6.tga" + ] + ] ] ] -- cgit v1.2.3 From 2865ce32bc00e6c05c2686215880085b23aecabf Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Tue, 2 Apr 2024 20:37:23 +0100 Subject: Tests for triangle helpers --- test/test-geoData.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'test') diff --git a/test/test-geoData.cpp b/test/test-geoData.cpp index 2d6b1aa..0a2de8d 100644 --- a/test/test-geoData.cpp +++ b/test/test-geoData.cpp @@ -192,6 +192,23 @@ BOOST_DATA_TEST_CASE(walkTerrainUntil, BOOST_CHECK_EQUAL_COLLECTIONS(visited.begin(), visited.end(), visits.begin(), visits.end()); } +BOOST_AUTO_TEST_CASE(triangle_helpers) +{ + constexpr static GeoData::Triangle<3> t {{0, 0, 0}, {5, 0, 0}, {5, 5, 0}}; + + BOOST_CHECK_EQUAL(t.nnormal(), up); + BOOST_CHECK_CLOSE(t.angle(0), quarter_pi, 0.01F); + BOOST_CHECK_CLOSE(t.angleAt({0, 0, 0}), quarter_pi, 0.01F); + BOOST_CHECK_CLOSE(t.angle(1), half_pi, 0.01F); + BOOST_CHECK_CLOSE(t.angleAt({5, 0, 0}), half_pi, 0.01F); + BOOST_CHECK_CLOSE(t.angle(2), quarter_pi, 0.01F); + BOOST_CHECK_CLOSE(t.angleAt({5, 5, 0}), quarter_pi, 0.01F); + + BOOST_CHECK_CLOSE(t.angleAt({0, 1, 0}), 0.F, 0.01F); + + BOOST_CHECK_CLOSE(t.area(), 12.5F, 0.01F); +} + using FindEntiesData = std::tuple; BOOST_DATA_TEST_CASE(findEntries, -- cgit v1.2.3 From 1fd3c4baf8b8acf97e343a0cd01455d23f2e9daf Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Thu, 4 Apr 2024 20:06:27 +0100 Subject: Remove wireframe mode from test renders --- test/test-geoData.cpp | 2 -- 1 file changed, 2 deletions(-) (limited to 'test') diff --git a/test/test-geoData.cpp b/test/test-geoData.cpp index 0a2de8d..fb9aba0 100644 --- a/test/test-geoData.cpp +++ b/test/test-geoData.cpp @@ -244,9 +244,7 @@ BOOST_DATA_TEST_CASE(deform, loadFixtureJson("geoData/deform/ void content(const SceneShader & shader) const override { - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); terrain.render(shader); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } void -- cgit v1.2.3