17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 06:19:19 +02:00
Files
maszyna/editor/plan_model.cppm
maj00r 321c9dd7ab Editor: fold the plan library into the tree as modules, drop its own name
The track layout library came in as a subproject of its own -- its own
CMakeLists, its own install and export rules, its own include tree -- which only
made the build harder to follow now that everything else is a module. It is part
of the editor, so it lives where the editor lives.

- editor/plan_*.cppm / plan_*.cpp: the fifteen interface units and twelve
  implementation units, flat in editor/ beside editorTerrain and orthophoto, with
  the plan_ prefix keeping them clear of what was already there (editor/turnouts
  is the preset catalogue, so the geometry is plan_turnout_geometry).
- modules are named eu07.editor.plan_*, like every other module in the tree.
- namespaces move under the editor's own: editor::plan, editor::plan::geometry,
  ::io, ::render. The library's old name is gone from the sources and the docs.
- every moved file carries the MPL header the rest of the tree carries; it had
  none. version.hpp went with the subproject: macros only, and unused.
- the umbrella header is dropped -- nothing imported it.
- the unit tests survive the fold: editor/tests, behind EU07_EDITOR_PLAN_TESTS
  (off by default), compiles the interfaces a second time into a small library of
  its own, because a test can only import modules from a target it links and
  linking the game would drag the whole simulator in. The code under test needs
  nothing but a compiler. 7/7 pass.
- a switch may now be written with two dashes as well: --editor beside -editor.

Builds with clang 22 (-DWITH_BETTER_RENDERER=OFF); -editor and --editor both come
up in the editor on empty ground.
2026-09-13 13:31:01 +02:00

236 lines
10 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
module;
#include <cstdint>
#include <string>
#include <variant>
#include <vector>
export module eu07.editor.plan_model;
export {
namespace editor::plan {
// ---------------------------------------------------------------------------
// Identity
// ---------------------------------------------------------------------------
/// Stable handles. Every reference in a document points at one of these, never
/// at a position in a vector: inserting or deleting anything must not silently
/// re-target a reference somewhere else. Ids are minted from one counter per
/// document (Document::next_id), so they are unique across all four spaces —
/// which makes a stray reference a lookup miss rather than a wrong hit.
enum class ElementId : std::uint32_t { none = 0 };
enum class TrackId : std::uint32_t { none = 0 };
enum class TurnoutId : std::uint32_t { none = 0 };
// ---------------------------------------------------------------------------
// The element
// ---------------------------------------------------------------------------
/// What an element is, geometrically. These are the three shapes
/// `geometry::layout_segment` lays: constant zero curvature, constant non-zero
/// curvature, linearly varying curvature.
enum class Kind {
Line,
Arc,
Clothoid,
};
// ---------------------------------------------------------------------------
// What holds an element
// ---------------------------------------------------------------------------
/// Nothing holds it: its shape and its place are its own.
struct Free {};
/// Held parallel to another track's element at a signed offset (+ = left of
/// *that* element's travel, whichever way this one runs).
///
/// This is międzytorze, said once: a straight beside a straight, an arc
/// concentric with an arc at R∓d. It fixes two things — the shape (kind, radius,
/// hand, taken from the reference) and where the track runs across the
/// reference. It fixes nothing along it, so the track stays where it was put and
/// slides nowhere when the reference is edited.
///
/// A track that can move — one anchored at a pose of its own — is moved as one
/// rigid piece until the held element really lies at the offset. A track pinned
/// to a turnout's port cannot move: there the reference still fixes the shape,
/// and the offset that actually came out is reported rather than forced.
struct Parallel {
ElementId ref{ElementId::none};
double offset{0.0};
};
using Hold = std::variant<Free, Parallel>;
/// One element of a track. It starts where the previous element ends — by pose,
/// not by coordinates — so tangency is a property of the representation and not
/// a rule anyone has to check or restore.
///
/// Everything about it is stated outright: what shape it is, how long it is, and
/// how tightly it turns. Nothing here is left for a solver to decide — there is
/// no "run on until you reach that line". Laying track by clicking along the
/// ground still works, but the corner is worked out at the click and written
/// down as numbers, so what the document says is what stands on the ground.
struct Element {
ElementId id{ElementId::none};
Kind kind{Kind::Line};
/// Arc: the radius, metres, > 0. Clothoid: the radius at its far end, with
/// 0 meaning infinite (a clothoid running out to a straight); its near-end
/// radius is whatever the previous element ends on. Line: ignored.
double radius{0.0};
/// +1 turns left, -1 turns right, 0 for a straight. Signed curvature is
/// exactly `hand / radius`, and positive curvature turns left in
/// `geometry::layout_segment` — so no sign is flipped anywhere downstream.
int hand{0};
/// Along the axis, metres, > 0.
double length{0.0};
/// What holds it to something else. Never changes its length.
Hold hold{Free{}};
};
// ---------------------------------------------------------------------------
// Anchors
// ---------------------------------------------------------------------------
/// Start at an explicit pose. @c az is an azimuth: radians clockwise from north,
/// i.e. atan2(dx, dy), matching the rest of the editor.
struct AtPose {
double x{0.0}, y{0.0}, az{0.0};
};
/// Which end of a turnout a track leaves from.
enum class Port {
Frog, ///< KR — koniec rozjazdu, the diverging track's end
Start, ///< PR — początek rozjazdu
};
/// Start at a turnout's port. This is what used to be `frog_of` plus the pinning
/// pass: the branch no longer gets moved back into place after the fact, it was
/// never anywhere else.
struct AtPort {
TurnoutId turnout{TurnoutId::none};
Port port{Port::Frog};
};
using Anchor = std::variant<AtPose, AtPort>;
// ---------------------------------------------------------------------------
// Track
// ---------------------------------------------------------------------------
/// A niweleta: an anchor plus an ordered chain of elements. Straights, arcs and
/// clothoids are equal citizens here — a chain may start with an arc, put two
/// arcs back to back, or simply stop without closing onto anything.
struct Track {
TrackId id{TrackId::none};
std::string name;
Anchor anchor{AtPose{}};
std::vector<Element> elements;
};
// ---------------------------------------------------------------------------
// Turnouts: catalogue type + placement
// ---------------------------------------------------------------------------
/// One piece of a catalogue turnout's diverging path. Mirrors
/// plan::TurnoutPiece; kept here so the document does not depend on the
/// geometry header.
struct TurnoutPieceSpec {
int part{2}; ///< 0 przediglicowy, 1 iglica, 2 łuk, 3 krzyżownicowa
double length{0.0}; ///< always explicit; nothing is fitted
double radius_start{0.0}; ///< 0 = straight
double radius_end{0.0}; ///< != radius_start = clothoid
double turn_in{0.0}; ///< kąt nagięcia iglicy, radians, unsigned
};
/// The blade's own dimensions, which place its cut tip. Mirrors plan::Blade.
struct BladeSpec {
double tip_thickness{0.0}; ///< u: width where the planed nose ends
double nose{0.0}; ///< d: the planed nose, from the tip to that point
double railtop_width{0.0}; ///< w: railhead width
};
/// A catalogue turnout: the list of pieces its diverging path is laid from, plus
/// the crossing mark and length the catalogue says it should work out to — which
/// the solver checks the list against and never fits it to. Every number that
/// belongs to the *type* lives here, so adding one is a catalogue change and
/// never touches the file format for placements.
struct TurnoutType {
std::string name;
double crossing_n{9.0}; ///< skos 1:n, checked against the laid deflection
double length{0.0}; ///< catalogue length PR->KR along the through track, checked
std::vector<TurnoutPieceSpec> pieces;
BladeSpec blade{};
};
/// An instance of a type, laid on a through track. There is nothing here to
/// edit but where it sits — the geometry belongs to the type.
struct TurnoutPlacement {
TurnoutId id{TurnoutId::none};
std::string type; ///< catalogue name
TrackId on{TrackId::none}; ///< the through track, which is not split
double station{0.0}; ///< PR, as arc length along the through centreline
int hand{1}; ///< +1 diverges left, -1 diverges right
bool facing{true}; ///< opens toward increasing station
/// Łukowanie. A turnout standing on a curve is bent onto it, and normally
/// that is all there is to say: the tor zasadniczy is whatever the track does
/// under the PR. Clearing this asks for a bend of its own instead — a rozjazd
/// łukowy on a track drawn straight — and then `bend` is the signed curvature
/// (1/m, positive turns left) the through track is taken to have. What the
/// bend works out to is the solver's business and is never written back here.
bool bend_from_track{true};
double bend{0.0};
};
// ---------------------------------------------------------------------------
// Document
// ---------------------------------------------------------------------------
/// Everything that is authored. Nothing in here is ever written by the solver.
struct Document {
std::vector<TurnoutType> turnout_types;
std::vector<TurnoutPlacement> turnouts;
std::vector<Track> tracks;
/// Where this plan is exported to, and so which MaSzyna it is exported into:
/// the scenery folder names the game the file lands in, and running what was
/// drawn means running the simulator there. Empty until it is said once.
std::string scn_path;
double view_x{0.0}, view_y{0.0}, view_extent{0.0};
bool origin_set{false};
bool georeferenced{false};
double origin_x{0.0}, origin_y{0.0};
/// Next handle to hand out. One counter for every id space.
std::uint32_t next_id{1};
};
/// Mints the next handle. The four overloads exist so a caller states which kind
/// of thing it is naming.
[[nodiscard]] ElementId mint_element(Document& document);
[[nodiscard]] TrackId mint_track(Document& document);
[[nodiscard]] TurnoutId mint_turnout(Document& document);
[[nodiscard]] const Track* find_track(const Document& document, TrackId id);
[[nodiscard]] Track* find_track(Document& document, TrackId id);
[[nodiscard]] const TurnoutPlacement* find_turnout(const Document& document, TurnoutId id);
[[nodiscard]] TurnoutPlacement* find_turnout(Document& document, TurnoutId id);
[[nodiscard]] const TurnoutType* find_type(const Document& document, const std::string& name);
/// The element itself, wherever it is; and the track it belongs to. Null when
/// unknown.
[[nodiscard]] const Element* find_element(const Document& document, ElementId id);
[[nodiscard]] const Track* find_element_track(const Document& document, ElementId id);
} // namespace editor::plan
} // export