mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-03 19:00:22 +01:00
Switches: enforced branch tangency, internal basket, Polish turnout templates
A switch (rozjazd) is a relation on a through niweleta, not a niweleta - the through track is never split. It marks the theoretical point on the through centreline; two tangent lines cross there (the through heading and that heading turned by the crossing mark 1:n), and the internal diverging curve is fitted to round the corner between them, reusing the same GapFitter the gap fits go through. So the curve is free: a plain arc, or a łuk koszowy right inside the switch. The theoretical tangent lengths and the curve length follow from it and are read off, not set. The branch niweleta's leaving straight is now locked to the frog: it starts at the frog and runs along the frog heading every solve, only its length is the user's. That enforces tangency no matter what is drawn, and because the first straight is pinned tangent, any fit in the branch's first gap - including a basket - meets the switch tangentially by construction. solve_layout solves the niwelety and the switches together in two passes: solve, read each frog, pin the branches, solve again. Switches serialize as an ejuncs / ejunc section; older files without it still load. Typical Polish turnouts (rozjazdy zwyczajne) are offered as templates that set the crossing mark and radius together; the length each spans is computed from them (arc = R*atan(1/n)) and shown, so how long the switch is is known.
This commit is contained in:
@@ -5,52 +5,29 @@
|
||||
#include <vector>
|
||||
|
||||
#include "maj0sted/domain/map_project.hpp"
|
||||
#include "maj0sted/web/editor.hpp" // NiweletaSpec (the editor's GUI model)
|
||||
#include "maj0sted/web/editor.hpp"
|
||||
|
||||
namespace maj0sted::app {
|
||||
|
||||
/// The editor's persistable document: the GUI model exactly as the user built
|
||||
/// it — independent anchored straights plus the parametric fits between them.
|
||||
///
|
||||
/// This is the source of truth for save/restore because it is lossless: it keeps
|
||||
/// the untrimmed straights and the fit *parameters* (radius, transition lengths,
|
||||
/// compound arcs, ...), which the resolved domain geometry alone cannot express.
|
||||
struct EditorDocument {
|
||||
std::vector<maj0sted::web::NiweletaSpec> niwelety;
|
||||
std::vector<maj0sted::web::Junction> junctions;
|
||||
double view_x{0.0};
|
||||
double view_y{0.0};
|
||||
double view_extent{0.0};
|
||||
bool origin_set{false};
|
||||
bool georeferenced{false};
|
||||
double origin_x{0.0};
|
||||
double origin_y{0.0};
|
||||
};
|
||||
|
||||
/// Best-effort resolved-domain view of @p document: one Niweleta per editor
|
||||
/// niweleta, keeping the anchored straights (a floating placeholder arc sits in
|
||||
/// each gap so the alignment stays valid). Used for interop / inspection and so
|
||||
/// the on-disk file embeds a real maj0sted project. Never throws.
|
||||
[[nodiscard]] maj0sted::domain::MapProject to_map_project(const EditorDocument& document);
|
||||
|
||||
/// Imports a plain domain project into the editor model: every Straight in each
|
||||
/// niweleta's plan becomes an editor straight (fits are left empty — resolved
|
||||
/// geometry carries no fit parameters to recover).
|
||||
[[nodiscard]] EditorDocument from_map_project(const maj0sted::domain::MapProject& project);
|
||||
|
||||
/// Serializes @p document to the library's textual format. The document embeds a
|
||||
/// real maj0sted project (via maj0sted::io::serialize of to_map_project) followed
|
||||
/// by an `editor` section that captures the straights and fit parameters
|
||||
/// losslessly. @c deserialize_document restores it exactly.
|
||||
[[nodiscard]] std::string serialize_document(const EditorDocument& document);
|
||||
|
||||
/// Rebuilds an EditorDocument from text produced by @c serialize_document.
|
||||
/// @throws std::runtime_error on malformed input.
|
||||
[[nodiscard]] EditorDocument deserialize_document(const std::string& text);
|
||||
|
||||
/// The default on-disk project path. Under Emscripten this lives beneath the
|
||||
/// IDBFS mount point (see web/index.html), so it survives reloads once flushed.
|
||||
[[nodiscard]] std::string default_project_path();
|
||||
|
||||
/// Serializes @p document and writes it to @p path (creating parent directories).
|
||||
/// @returns true on success; never throws.
|
||||
bool save_project(const EditorDocument& document,
|
||||
const std::string& path = default_project_path());
|
||||
|
||||
/// Reads and deserializes the project at @p path, or std::nullopt if the file is
|
||||
/// absent or unreadable/malformed. Never throws.
|
||||
[[nodiscard]] std::optional<EditorDocument> load_project(
|
||||
const std::string& path = default_project_path());
|
||||
|
||||
|
||||
@@ -13,6 +13,17 @@ struct StraightSpec {
|
||||
/// Internal tangent guide used to solve a terminal curve. It participates
|
||||
/// in fitting but is not rendered as track or exposed as an editor handle.
|
||||
bool hidden{false};
|
||||
|
||||
/// Live constraint the host reapplies before each solve (same model as the
|
||||
/// web editor's `par` / `skew`). 0 = none, 1 = parallel offset from another
|
||||
/// straight, 2 = skew 1:n from another straight.
|
||||
int rel_kind{0};
|
||||
int rel_niw{-1}; ///< index of the reference niweleta
|
||||
int rel_str{-1}; ///< index of the reference straight
|
||||
double rel_offset{0.0}; ///< parallel: signed metres (+ = left of travel)
|
||||
double rel_cot{0.0}; ///< skew: n of a 1:n turnout
|
||||
int rel_side{1}; ///< skew: +1 left / -1 right of the reference
|
||||
double rel_length{0.0}; ///< skew: length of the dependent arm
|
||||
};
|
||||
|
||||
/// One arc of a compound fit: its radius, its fixed length (ignored for the last
|
||||
@@ -61,6 +72,9 @@ struct NiweletaPolys {
|
||||
/// parameters or geometry) is simply absent here — the library, not the host,
|
||||
/// decides that a failed fit becomes "no fit". The host mirrors this list.
|
||||
std::vector<GapFit> applied_fits;
|
||||
/// The solved axis as one polyline, in order, with no duplicate join points.
|
||||
/// Lets a station along the niweleta be resolved to a pose (see pose_at).
|
||||
std::vector<WebPoint> centreline;
|
||||
};
|
||||
|
||||
/// Builds each niweleta, fits every requested gap, and returns the sampled plan
|
||||
@@ -69,4 +83,53 @@ struct NiweletaPolys {
|
||||
[[nodiscard]] std::vector<NiweletaPolys> solve_project(
|
||||
const std::vector<NiweletaSpec>& niwelety);
|
||||
|
||||
/// A switch (rozjazd): the point where a branch leaves a through niweleta. It is
|
||||
/// a relation, not a niweleta — the through track is not split. The switch is
|
||||
/// described the surveyor's way: a crossing mark 1:n (skos) that fixes the angle
|
||||
/// the branch leaves at, and an interior curve that is free to be anything — a
|
||||
/// single arc, or a compound basket (łuk koszowy). @c station marks the switch's
|
||||
/// theoretical point (środek rozjazdu, where the through and diverging tangent
|
||||
/// lines cross); the curve is fitted around it and the branch niweleta is pinned
|
||||
/// to the frog it ends on. The theoretical tangent lengths follow from the curve
|
||||
/// (see JunctionGeom) — they are read off, not set.
|
||||
struct Junction {
|
||||
int through{-1}; ///< index of the through niweleta (not modified)
|
||||
double station{0.0}; ///< theoretical point: arc length along the through centreline
|
||||
int side{0}; ///< 0 = diverges left, 1 = diverges right
|
||||
bool facing{true}; ///< true opens toward increasing station, false back
|
||||
double crossing_n{9.0}; ///< crossing mark 1:n (skos) → leaving angle atan(1/n)
|
||||
GapFit curve; ///< the internal diverging curve: arc / compound / basket
|
||||
int branch{-1}; ///< index of the branch niweleta, pinned to the frog
|
||||
};
|
||||
|
||||
/// The solved geometry of one switch: the switch point where the branch leaves
|
||||
/// the through track, the frog it ends on, the diverging curve between them and
|
||||
/// the theoretical tangent lengths that curve works out to.
|
||||
struct JunctionGeom {
|
||||
bool valid{false};
|
||||
double px{0.0}, py{0.0}; ///< switch point (diverging leaves the through here)
|
||||
double fx{0.0}, fy{0.0}; ///< frog: where the branch niweleta is pinned
|
||||
double fhx{0.0}, fhy{0.0}; ///< unit heading the branch leaves the frog on
|
||||
double tangent_front{0.0}; ///< theoretical tangent length before the curve
|
||||
double tangent_back{0.0}; ///< theoretical tangent length after the curve
|
||||
double length{0.0}; ///< diverging curve length (switch point to frog), metres
|
||||
/// The diverging curve, as rails ready to draw.
|
||||
std::vector<WebPolyline> polylines;
|
||||
};
|
||||
|
||||
/// A whole layout solved at once: the niwelety plus every switch. Returned by
|
||||
/// solve_layout, which additionally pins each branch's start to its frog.
|
||||
struct LayoutSolution {
|
||||
std::vector<NiweletaPolys> niwelety;
|
||||
std::vector<JunctionGeom> junctions; ///< index-aligned with the input switches
|
||||
};
|
||||
|
||||
/// Solves @p niwelety together with @p junctions: each branch niweleta's start is
|
||||
/// pinned to its switch's frog before solving, and every switch's diverging arc
|
||||
/// is computed from the through niweleta's centreline. Never throws; a switch
|
||||
/// whose through/branch is out of range or whose geometry is degenerate comes
|
||||
/// back with valid == false. Takes @p niwelety by value because it repins them.
|
||||
[[nodiscard]] LayoutSolution solve_layout(std::vector<NiweletaSpec> niwelety,
|
||||
const std::vector<Junction>& junctions);
|
||||
|
||||
} // namespace maj0sted::web
|
||||
|
||||
Reference in New Issue
Block a user