17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 12:09:19 +02:00
Files
maszyna/editor/plan_solution.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

182 lines
7.6 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 <string>
#include <vector>
export module eu07.editor.plan_solution;
export import eu07.editor.plan_segment;
export import eu07.editor.plan_turnout_geometry;
export import eu07.editor.plan_model;
export {
namespace editor::plan {
/// A point in the project's CRS (metres for EPSG:2180).
struct PlanPoint {
double x{0.0};
double y{0.0};
};
// ---------------------------------------------------------------------------
// Solved geometry
// ---------------------------------------------------------------------------
/// What one authored element works out to on the ground. Curvatures rather than
/// radii, because that is what `geometry::layout_segment` takes and what makes a
/// straight (0, 0), an arc (k, k) and a clothoid (k0, k1) one shape.
struct SolvedElement {
ElementId id{ElementId::none};
Kind kind{Kind::Line};
geometry::Pose start{}; ///< pose the element is laid from
geometry::Pose end{}; ///< pose it ends on = the next element's start
double k0{0.0}; ///< signed curvature at the start (1/m)
double k1{0.0}; ///< signed curvature at the end
double length{0.0};
std::vector<PlanPoint> points; ///< sampled axis, first point == start
};
/// A solved track: its elements in order, plus the axis flattened into one
/// polyline with no duplicated join points, which is what a station along the
/// track is resolved against.
struct SolvedTrack {
TrackId id{TrackId::none};
std::vector<SolvedElement> elements;
std::vector<PlanPoint> centreline;
geometry::Pose start{};
geometry::Pose end{};
double length{0.0};
/// True when the chain was laid whole. False means it was cut short by a
/// diagnostic — the elements up to the cut are still here and still drawable.
bool complete{true};
};
/// One laid segment of a turnout's diverging path.
struct SolvedTurnoutSegment {
geometry::Pose start{};
double k0{0.0}, k1{0.0}, length{0.0};
plan::TurnoutPart part{plan::TurnoutPart::Curve};
std::vector<PlanPoint> points;
};
/// A construction point of a turnout: where two of its parts meet, or a point
/// the catalogue drawing is dimensioned from. Reported rather than left to be
/// eyeballed off the drawn curve.
struct TurnoutMark {
double x{0.0}, y{0.0};
/// 0 PR, 1 ostrze iglicy (where the blade begins, the odcinek przediglicowy
/// behind it), 2 pięta iglicy, 3 joint inside the curve, 4 end of curve /
/// start of the frog rail, 5 KR, 6 punkt teoretyczny, 7 koniec dzioba iglicy
/// (A, where the blade is u thick), 8 koniec strugania, 9 koniec rozjazdu on
/// the through track (the frog projected back onto the straight).
int kind{0};
double station{0.0}; ///< along the diverging path from PR; 0 for the theoretical point
};
struct SolvedTurnout {
TurnoutId id{TurnoutId::none};
bool valid{false};
geometry::Pose start{}; ///< PR, on the through track
geometry::Pose frog{}; ///< KR, where a branch anchors
double tangent_front{0.0}, tangent_back{0.0};
double diverging_length{0.0};
double through_length{0.0}; ///< PR to the frog's projection, along the through track
std::vector<SolvedTurnoutSegment> path;
std::vector<TurnoutMark> marks;
/// The blade as dimensioned: where its tip stands (the odcinek przediglicowy
/// is everything before it), how long the planed nose and the planing run,
/// the kąt przylegania, and whether the type's own a/d/u agree with the curve.
/// Łukowanie, as laid: the signed curvature of the through track under it, the
/// one the branch came out at, and how far the through track turns over the
/// turnout. All zero for a rozjazd zwyczajny.
double bend{0.0};
double diverging_curvature{0.0};
double bend_angle{0.0};
bool has_blade{false};
double blade_station{0.0};
double nose_end{0.0};
double planing_length{0.0};
double blade_angle{0.0};
double blade_residual{0.0};
/// What the laid path works out to, against what the catalogue promised:
/// deflection minus the crossing angle, and through-length minus the
/// catalogue length. Reported, never fitted away.
double angle_residual{0.0};
double length_residual{0.0};
};
// ---------------------------------------------------------------------------
// Diagnostics
// ---------------------------------------------------------------------------
/// Why something could not be laid. Reported, never silently dropped: the user
/// gets the reason along with whatever part of the drawing did stand up.
enum class Code {
UnknownReference, ///< an anchor points at an id that is not there
CyclicDependency, ///< tracks/turnouts depend on each other in a circle
DegenerateElement, ///< non-positive radius or length, a clothoid that eases nothing
TurnoutInvalid, ///< catalogue numbers admit no geometry
TurnoutNotClosed, ///< the laid path misses the crossing angle or the catalogue length
StationOffTrack, ///< a turnout sits past the end of its through track
TouchingStraights, ///< two straights end to end, which is one straight
HoldConflict, ///< two elements of one movable track both hold it parallel
OffsetNotHeld, ///< a pinned track cannot move, so its offset is what it is
};
[[nodiscard]] const char* code_name(Code code);
struct Diagnostic {
Code code{Code::DegenerateElement};
TrackId track{TrackId::none};
ElementId element{ElementId::none};
TurnoutId turnout{TurnoutId::none};
/// Human-readable, with the number that matters in it ("R=300 m nie mieści
/// się, maksimum 214,4 m"). The host shows this; it does not compose it.
std::string text;
};
// ---------------------------------------------------------------------------
// The whole solution
// ---------------------------------------------------------------------------
/// Everything derived. Rebuilt whole on each solve, never fed back into the
/// document. Vectors rather than maps so iteration order is the document's and
/// two solves of the same document compare equal.
struct Solution {
std::vector<SolvedTrack> tracks;
std::vector<SolvedTurnout> turnouts;
std::vector<Diagnostic> diagnostics;
};
[[nodiscard]] const SolvedTrack* find_track(const Solution& solution, TrackId id);
[[nodiscard]] const SolvedTurnout* find_turnout(const Solution& solution, TurnoutId id);
[[nodiscard]] const SolvedElement* find_element(const Solution& solution, ElementId id);
/// Pose at @p station metres along a solved track's centreline. Clamped to the
/// ends. Returns false for an empty track.
[[nodiscard]] bool pose_at(const SolvedTrack& track, double station,
geometry::Pose& out);
/// How much track of one steady curvature there is around @p station: @p behind
/// back to where the element begins, @p ahead on to where it ends, and
/// @p curvature what it runs at (signed, positive turns left; zero on a
/// straight). A turnout is laid as one piece of geometry, bent to the one radius
/// under it, so it belongs on a single element and has to fit on it whole.
/// False on a clothoid, where there is no one radius to bend to, and past the
/// end of the track. Nothing is written then.
[[nodiscard]] bool steady_room(const SolvedTrack& track, double station, double& curvature,
double& behind, double& ahead);
} // namespace editor::plan
} // export