summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Goodliffe <dan@randomdan.homeip.net>2025-05-10 13:50:08 +0100
committerDan Goodliffe <dan@randomdan.homeip.net>2025-05-10 13:50:08 +0100
commitdf9b9bc264e0bf12e99b447be7903050c822692d (patch)
treee92ddbfb0c61cc94131512ed2d757df798157f72
parentNew genCurveDef for 2 directions (diff)
downloadilt-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.cpp25
-rw-r--r--game/network/network.h21
-rw-r--r--game/network/network.impl.h36
-rw-r--r--test/test-network.cpp238
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