diff options
author | Dan Goodliffe <dan@randomdan.homeip.net> | 2025-05-10 13:50:08 +0100 |
---|---|---|
committer | Dan Goodliffe <dan@randomdan.homeip.net> | 2025-05-10 13:50:08 +0100 |
commit | df9b9bc264e0bf12e99b447be7903050c822692d (patch) | |
tree | e92ddbfb0c61cc94131512ed2d757df798157f72 | |
parent | New genCurveDef for 2 directions (diff) | |
download | ilt-df9b9bc264e0bf12e99b447be7903050c822692d.tar.bz2 ilt-df9b9bc264e0bf12e99b447be7903050c822692d.tar.xz ilt-df9b9bc264e0bf12e99b447be7903050c822692d.zip |
Add new network building interface
Imperfect, matches some legacy interface in places, has some TODO notes.
-rw-r--r-- | game/network/network.cpp | 25 | ||||
-rw-r--r-- | game/network/network.h | 21 | ||||
-rw-r--r-- | game/network/network.impl.h | 36 | ||||
-rw-r--r-- | test/test-network.cpp | 238 |
4 files changed, 298 insertions, 22 deletions
diff --git a/game/network/network.cpp b/game/network/network.cpp index 403975c..295e6ec 100644 --- a/game/network/network.cpp +++ b/game/network/network.cpp @@ -142,3 +142,28 @@ Network::genCurveDef(const GlobalPosition3D & start, const GlobalPosition3D & en return {genCurveDef(start, joint, startDir), genCurveDef(end, joint, endDir)}; } + +Link::Collection +Network::create(const CreationDefinition & def) +{ + // TODO + // Where to make a straight to join because angles align? + // Where to drop part of S curve pair if a single curve works? + + if (!def.fromEnd.direction && !def.toEnd.direction) { + // No specific directions at either end, straight link + return {create(GenStraightDef {def.fromEnd.position, def.toEnd.position})}; + } + if (def.fromEnd.direction) { + if (def.toEnd.direction) { + // Two specific directions at both ends, S curves + const auto curves = genCurveDef( + def.fromEnd.position, def.toEnd.position, *def.fromEnd.direction, *def.toEnd.direction); + return {create(curves.first), create(curves.second)}; + } + // One specific direction, single curve from there + return {create(genCurveDef(def.fromEnd.position, def.toEnd.position, *def.fromEnd.direction))}; + } + // One specific direction, single curve from the other + return {create(genCurveDef(def.toEnd.position, def.fromEnd.position, *def.toEnd.direction))}; +} diff --git a/game/network/network.h b/game/network/network.h index 4f5d2b0..114f42b 100644 --- a/game/network/network.h +++ b/game/network/network.h @@ -19,8 +19,19 @@ class GeoData; template<typename> class Ray; template<size_t... N> using GenDef = std::tuple<glm::vec<N, GlobalDistance>...>; +using GenStraightDef = GenDef<3, 3>; using GenCurveDef = GenDef<3, 3, 2>; +struct CreationDefinitionEnd { + GlobalPosition3D position; + std::optional<Angle> direction; +}; + +struct CreationDefinition { + CreationDefinitionEnd fromEnd; + CreationDefinitionEnd toEnd; +}; + class Network { public: using LinkEnd = std::pair<Link::Ptr, unsigned char>; @@ -49,6 +60,9 @@ public: [[nodiscard]] virtual float findNodeDirection(Node::AnyCPtr) const = 0; + [[nodiscard]] Link::Collection create(const CreationDefinition &); + virtual void add(const GeoData *, const Link::Ptr &) = 0; + [[nodiscard]] virtual const Surface * getBaseSurface() const = 0; [[nodiscard]] virtual RelativeDistance getBaseWidth() const = 0; @@ -58,6 +72,9 @@ protected: static std::pair<GenCurveDef, GenCurveDef> genCurveDef( const GlobalPosition3D & start, const GlobalPosition3D & end, float startDir, float endDir); + [[nodiscard]] virtual Link::Ptr create(const GenStraightDef &) = 0; + [[nodiscard]] virtual Link::Ptr create(const GenCurveDef &) = 0; + using Nodes = std::set<Node::Ptr, PtrMemberSorter<Node::Ptr, &Node::pos>>; Nodes nodes; Texture::Ptr texture; @@ -111,6 +128,10 @@ public: Link::CCollection addExtend(const GeoData *, GlobalPosition3D, GlobalPosition3D) override; [[nodiscard]] float findNodeDirection(Node::AnyCPtr) const override; + using Network::create; + [[nodiscard]] Link::Ptr create(const GenStraightDef &) override; + [[nodiscard]] Link::Ptr create(const GenCurveDef &) override; + void add(const GeoData *, const Link::Ptr &) override; protected: Link::CCollection addCurve(const GeoData *, const GenCurveDef &); diff --git a/game/network/network.impl.h b/game/network/network.impl.h index e339922..54a5f4b 100644 --- a/game/network/network.impl.h +++ b/game/network/network.impl.h @@ -143,3 +143,39 @@ NetworkOf<T, Links...>::addExtend(const GeoData * geoData, GlobalPosition3D star { return addCurve(geoData, genCurveDef(start, end, findNodeDirection(nodeAt(start)))); } + +template<typename T, typename... Links> +Link::Ptr +NetworkOf<T, Links...>::create(const GenStraightDef & def) +{ + return std::make_shared<typename T::StraightLink>( + *this, candidateNodeAt(std::get<0>(def)).first, candidateNodeAt(std::get<1>(def)).first); +} + +template<typename T, typename... Links> +Link::Ptr +NetworkOf<T, Links...>::create(const GenCurveDef & def) +{ + return std::make_shared<typename T::CurveLink>( + *this, candidateNodeAt(std::get<0>(def)).first, candidateNodeAt(std::get<1>(def)).first, std::get<2>(def)); +} + +template<typename T, typename... Links> +void +NetworkOf<T, Links...>::add(const GeoData *, const Link::Ptr & link) +{ + const auto addIf = [this](auto && lptr) { + if (lptr) { + links.emplace(lptr); + return true; + } + return false; + }; + for (auto & end : link->ends) { + end.node = nodeAt(end.node->pos); + } + if (!(addIf(std::dynamic_pointer_cast<Links>(link)) || ...)) { + throw std::logic_error("Unsupported link type for network"); + } + joinLinks(link); +} diff --git a/test/test-network.cpp b/test/test-network.cpp index 1c91981..2b4b958 100644 --- a/test/test-network.cpp +++ b/test/test-network.cpp @@ -23,10 +23,13 @@ BOOST_GLOBAL_FIXTURE(TestMainWindowAppBase); namespace { struct TestLinkS; + struct TestLinkC; struct TestLink : public virtual Link { using StraightLink = TestLinkS; - using CurveLink = TestLinkS; + using CurveLink = TestLinkC; + + struct Vertex { }; }; struct TestLinkS : public TestLink, public LinkStraight { @@ -35,15 +38,34 @@ namespace { { } - TestLinkS(NetworkLinkHolder<TestLinkS> &, Node::Ptr nodeA, Node::Ptr nodeB, RelativePosition2D difference) : - Link {{.node = std::move(nodeA), .dir = 0}, {.node = std::move(nodeB), .dir = pi}, glm::length(difference)} + TestLinkS(NetworkLinkHolder<TestLinkS> & network, Node::Ptr nodeA, Node::Ptr nodeB, + RelativePosition2D difference) : + TestLinkS {network, std::move(nodeA), std::move(nodeB), glm::length(difference), vector_yaw(difference)} { } - struct Vertex { }; + TestLinkS(NetworkLinkHolder<TestLinkS> &, Node::Ptr nodeA, Node::Ptr nodeB, float length, Angle dirForward) : + Link {{.node = std::move(nodeA), .dir = dirForward}, + {.node = std::move(nodeB), .dir = normalize(pi + dirForward)}, length} + { + } + }; - TestLinkS(NetworkLinkHolder<TestLinkS> &, Node::Ptr nodeA, Node::Ptr nodeB, float length) : - Link {{.node = std::move(nodeA), .dir = 0}, {.node = std::move(nodeB), .dir = pi}, length} + struct TestLinkC : public TestLink, public LinkCurve { + TestLinkC(NetworkLinkHolder<TestLinkC> & network, const Node::Ptr & nodeA, const Node::Ptr & nodeB, + GlobalPosition2D centre) : + TestLinkC {network, nodeA, nodeB, centre, + {vector_yaw(difference(nodeA->pos.xy(), centre)), vector_yaw(difference(nodeB->pos.xy(), centre))}} + { + } + + TestLinkC(NetworkLinkHolder<TestLinkC> &, const Node::Ptr & nodeA, const Node::Ptr & nodeB, + GlobalPosition2D centre, Arc arc) : + Link {{.node = nodeA, .dir = normalize(arc.first + half_pi)}, + {.node = nodeB, .dir = normalize(arc.second - half_pi)}, + // Wrong, but OK for testing, just 10% longer than a straight line + (glm::length(difference(nodeA->pos, nodeB->pos)) * 1.1F)}, + LinkCurve {centre || nodeA->pos.z, glm::length(difference(centre, nodeA->pos.xy())), arc} { } }; @@ -53,24 +75,11 @@ namespace { } template<> NetworkLinkHolder<TestLinkS>::NetworkLinkHolder() = default; +template<> NetworkLinkHolder<TestLinkC>::NetworkLinkHolder() = default; namespace { - struct TestNetwork : public NetworkOf<TestLink, TestLinkS> { - TestNetwork() : NetworkOf<TestLink, TestLinkS> {RESDIR "rails.jpg"} - { - // 0 1 2 - // p000 <-> p100 <-> p200 <-> p300 - // \ | / - // \ 5 / - // 3 | 4 - // \-> p110 <-/ - addLink<TestLinkS>(P000, P100, 1.F); - addLink<TestLinkS>(P100, P200, 1.F); - addLink<TestLinkS>(P200, P300, 1.F); - addLink<TestLinkS>(P000, P110, 2.F); - addLink<TestLinkS>(P200, P110, 2.F); - addLink<TestLinkS>(P100, P110, 1.F); - } + struct EmptyNetwork : public NetworkOf<TestLink, TestLinkS, TestLinkC> { + EmptyNetwork() : NetworkOf<TestLink, TestLinkS, TestLinkC> {RESDIR "rails.jpg"} { } void render(const SceneShader &, const Frustum &) const override @@ -90,6 +99,24 @@ namespace { } }; + struct TestNetwork : public EmptyNetwork { + TestNetwork() + { + // 0 1 2 + // p000 <-> p100 <-> p200 <-> p300 + // \ | / + // \ 5 / + // 3 | 4 + // \-> p110 <-/ + addLink<TestLinkS>(P000, P100, 1.F, 0); + addLink<TestLinkS>(P100, P200, 1.F, 0); + addLink<TestLinkS>(P200, P300, 1.F, 0); + addLink<TestLinkS>(P000, P110, 2.F, 0); + addLink<TestLinkS>(P200, P110, 2.F, 0); + addLink<TestLinkS>(P100, P110, 1.F, 0); + } + }; + constexpr auto VALID_NODES = std::array<GlobalPosition3D, 4>({ P000, P100, @@ -224,6 +251,173 @@ BOOST_AUTO_TEST_CASE(RouteToDownStream3to300) BOOST_AUTO_TEST_SUITE_END() +BOOST_FIXTURE_TEST_SUITE(en, EmptyNetwork) + +using GenCurveDefsData = std::tuple<GlobalPosition3D, GlobalPosition3D, Angle, GenCurveDef>; + +BOOST_DATA_TEST_CASE(GenCurveDefs, + boost::unit_test::data::make<GenCurveDefsData>({ + {{0, 0, 0}, {1000, 1000, 0}, 0, {{1000, 1000, 0}, {0, 0, 0}, {1000, 0}}}, + {{0, 0, 0}, {-1000, 1000, 0}, 0, {{0, 0, 0}, {-1000, 1000, 0}, {-1000, 0}}}, + {{0, 0, 0}, {0, 1000, 0}, half_pi, {{0, 0, 0}, {0, 1000, 0}, {0, 500}}}, + {{0, 0, 0}, {0, 1000, 0}, -half_pi, {{0, 1000, 0}, {0, 0, 0}, {0, 500}}}, + }), + start, end, startDir, exp) +{ + BOOST_CHECK_EQUAL(genCurveDef(start, end, startDir), exp); +} + +BOOST_AUTO_TEST_CASE(NetworkCreateStraight) +{ + const auto link = create(CreationDefinition { + .fromEnd = {.position = {0, 0, 0}, .direction = {}}, + .toEnd = {.position = {0, 1000, 0}, .direction = {}}, + }); + BOOST_REQUIRE_EQUAL(link.size(), 1); + BOOST_CHECK(links.empty()); + BOOST_CHECK(nodes.empty()); + + BOOST_CHECK_IF(straight, std::dynamic_pointer_cast<TestLinkS>(link.front())) { + BOOST_CHECK_CLOSE(straight->length, 1000, 1); + BOOST_CHECK_CLOSE_VECI(straight->ends.front().node->pos, GlobalPosition3D(0, 0, 0)); + BOOST_CHECK_CLOSE_VECI(straight->ends.back().node->pos, GlobalPosition3D(0, 1000, 0)); + } + + add(nullptr, link.front()); + BOOST_CHECK_EQUAL(links.size(), 1); + BOOST_CHECK_EQUAL(nodes.size(), 2); +} + +BOOST_AUTO_TEST_CASE(NetworkCreateExtendingCurve) +{ + const auto link = create(CreationDefinition { + .fromEnd = {.position = {0, 0, 0}, .direction = half_pi}, + .toEnd = {.position = {0, 1000, 0}, .direction = {}}, + }); + BOOST_REQUIRE_EQUAL(link.size(), 1); + BOOST_CHECK(links.empty()); + BOOST_CHECK(nodes.empty()); + + BOOST_CHECK_IF(curve, std::dynamic_pointer_cast<TestLinkC>(link.front())) { + BOOST_CHECK_CLOSE(curve->length, 1100, 1); + BOOST_CHECK_CLOSE(curve->radius, 500, 1); + BOOST_CHECK_CLOSE_VECI(curve->centreBase, GlobalPosition3D(0, 500, 0)); + BOOST_CHECK_CLOSE_VECI(curve->ends.front().node->pos, GlobalPosition3D(0, 0, 0)); + BOOST_CHECK_CLOSE(curve->ends.front().dir, -half_pi, 1); + BOOST_CHECK_CLOSE_VECI(curve->ends.back().node->pos, GlobalPosition3D(0, 1000, 0)); + BOOST_CHECK_CLOSE(curve->ends.back().dir, -half_pi, 1); + } + + add(nullptr, link.front()); + BOOST_CHECK_EQUAL(links.size(), 1); + BOOST_CHECK_EQUAL(nodes.size(), 2); +} + +BOOST_AUTO_TEST_CASE(NetworkCreateExtendeeCurve) +{ + const auto link = create(CreationDefinition { + .fromEnd = {.position = {0, 0, 0}, .direction = {}}, + .toEnd = {.position = {0, 1000, 0}, .direction = half_pi}, + }); + BOOST_REQUIRE_EQUAL(link.size(), 1); + BOOST_CHECK(links.empty()); + BOOST_CHECK(nodes.empty()); + + BOOST_CHECK_IF(curve, std::dynamic_pointer_cast<TestLinkC>(link.front())) { + BOOST_CHECK_CLOSE(curve->length, 1100, 1); + BOOST_CHECK_CLOSE(curve->radius, 500, 1); + BOOST_CHECK_CLOSE_VECI(curve->centreBase, GlobalPosition3D(0, 500, 0)); + BOOST_CHECK_CLOSE_VECI(curve->ends.front().node->pos, GlobalPosition3D(0, 0, 0)); + BOOST_CHECK_CLOSE(curve->ends.front().dir, -half_pi, 1); + BOOST_CHECK_CLOSE_VECI(curve->ends.back().node->pos, GlobalPosition3D(0, 1000, 0)); + BOOST_CHECK_CLOSE(curve->ends.back().dir, -half_pi, 1); + } + + add(nullptr, link.front()); + BOOST_CHECK_EQUAL(links.size(), 1); + BOOST_CHECK_EQUAL(nodes.size(), 2); +} + +BOOST_AUTO_TEST_CASE(NetworkCreateBiarcPair) +{ + const auto link = create(CreationDefinition { + .fromEnd = {.position = {0, 0, 0}, .direction = pi}, + .toEnd = {.position = {1000, 1000, 0}, .direction = 0}, + }); + BOOST_REQUIRE_EQUAL(link.size(), 2); + BOOST_CHECK(links.empty()); + BOOST_CHECK(nodes.empty()); + + BOOST_CHECK_IF(firstCurve, std::dynamic_pointer_cast<TestLinkC>(link.front())) { + BOOST_CHECK_CLOSE(firstCurve->length, 777, 1); + BOOST_CHECK_CLOSE(firstCurve->radius, 500, 1); + BOOST_CHECK_CLOSE_VECI(firstCurve->centreBase, GlobalPosition3D(500, 0, 0)); + BOOST_CHECK_CLOSE_VECI(firstCurve->ends.front().node->pos, GlobalPosition3D(0, 0, 0)); + BOOST_CHECK_CLOSE(firstCurve->ends.front().dir, 0, 1); + BOOST_CHECK_CLOSE_VECI(firstCurve->ends.back().node->pos, GlobalPosition3D(500, 500, 0)); + BOOST_CHECK_CLOSE(firstCurve->ends.back().dir, -half_pi, 1); + } + + BOOST_CHECK_IF(secondCurve, std::dynamic_pointer_cast<TestLinkC>(link.back())) { + BOOST_CHECK_CLOSE(secondCurve->length, 777, 1); + BOOST_CHECK_CLOSE(secondCurve->radius, 500, 1); + BOOST_CHECK_CLOSE_VECI(secondCurve->centreBase, GlobalPosition3D(500, 1000, 0)); + BOOST_CHECK_CLOSE_VECI(secondCurve->ends.front().node->pos, GlobalPosition3D(1000, 1000, 0)); + BOOST_CHECK_CLOSE(secondCurve->ends.front().dir, pi, 1); + BOOST_CHECK_CLOSE_VECI(secondCurve->ends.back().node->pos, GlobalPosition3D(500, 500, 0)); + BOOST_CHECK_CLOSE(secondCurve->ends.back().dir, half_pi, 1); + } + + add(nullptr, link.front()); + add(nullptr, link.back()); + BOOST_CHECK_EQUAL(links.size(), 2); + BOOST_CHECK_EQUAL(nodes.size(), 3); + BOOST_CHECK_EQUAL(link.front()->ends.back().nexts.front().first.lock(), link.back()); + BOOST_CHECK_EQUAL(link.back()->ends.back().nexts.front().first.lock(), link.front()); +} + +BOOST_AUTO_TEST_CASE(NetworkCreateBiarcPairEqTan) +{ + // This could be achieved with a single curve, but not there yet + const auto link = create(CreationDefinition { + .fromEnd = {.position = {0, 0, 0}, .direction = 0}, + .toEnd = {.position = {1000, 0, 0}, .direction = 0}, + }); + BOOST_REQUIRE_EQUAL(link.size(), 2); + BOOST_CHECK(links.empty()); + BOOST_CHECK(nodes.empty()); + + BOOST_CHECK_IF(firstCurve, std::dynamic_pointer_cast<TestLinkC>(link.front())) { + BOOST_CHECK_CLOSE_VECI(firstCurve->centreBase, GlobalPosition3D(500, 0, 0)); + } + + BOOST_CHECK_IF(secondCurve, std::dynamic_pointer_cast<TestLinkC>(link.back())) { + BOOST_CHECK_CLOSE_VECI(secondCurve->centreBase, GlobalPosition3D(500, 0, 0)); + } +} + +BOOST_AUTO_TEST_CASE(NetworkCreateBiarcPairEqTanPerp) +{ + // This creates an equal pair of semi-circle arcs + const auto link = create(CreationDefinition { + .fromEnd = {.position = {0, 0, 0}, .direction = 0}, + .toEnd = {.position = {1000, 0, 0}, .direction = pi}, + }); + BOOST_REQUIRE_EQUAL(link.size(), 2); + BOOST_CHECK(links.empty()); + BOOST_CHECK(nodes.empty()); + + BOOST_CHECK_IF(firstCurve, std::dynamic_pointer_cast<TestLinkC>(link.front())) { + BOOST_CHECK_CLOSE_VECI(firstCurve->centreBase, GlobalPosition3D(250, 0, 0)); + } + + BOOST_CHECK_IF(secondCurve, std::dynamic_pointer_cast<TestLinkC>(link.back())) { + BOOST_CHECK_CLOSE_VECI(secondCurve->centreBase, GlobalPosition3D(750, 0, 0)); + } +} + +BOOST_AUTO_TEST_SUITE_END() + BOOST_FIXTURE_TEST_CASE(TestRailNetwork, RailLinks) { // 0 1 2 |