17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-28 14:37:29 +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:
maj00r
2026-08-01 07:44:58 +02:00
committed by maj00r
parent d4b1d73338
commit d069743265
9 changed files with 3381 additions and 214 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -12,13 +12,19 @@ http://mozilla.org/MPL/2.0/.
#include "application/uilayer.h"
#include "editor/orthophoto.h"
#include "maj0sted/app/basket_draft.hpp"
#include "maj0sted/app/editor_document.hpp"
#include "maj0sted/app/straight_constraints.hpp"
#include "maj0sted/io/scn_export.hpp"
#include "maj0sted/web/editor.hpp"
#include "maj0sted/web/ribbon.hpp"
#include <vector>
// track layout tool: straights laid down by hand on the scenery itself and the curves fitted into
// the gaps between them. the panel holds only the controls - the drawing happens in the viewport,
// which the tool puts into a top-down orthographic view for the purpose. none of the geometry is
// computed here: maj0sted solves the whole document and the panel paints what comes back.
// which the tool puts into a top-down orthographic plan view. none of the geometry is computed
// here: maj0sted solves the whole document and the panel paints what comes back.
class plan_panel : public ui_panel
{
@@ -31,56 +37,134 @@ class plan_panel : public ui_panel
private:
// methods
void render_toolbar();
void render_draft();
void render_gaps();
// parameters of a basket curve: the end clothoids and the chain of arcs between them
void render_compound(maj0sted::web::GapFit &Fit);
void render_compound(maj0sted::web::GapFit &Fit, std::size_t const Gap, bool &Dirty);
void fit_compound_to_guides();
void clear_fit_guides();
void render_storage();
// asks what kind of scenery is being started, and where on the map it sits
void export_scn();
void render_newmap_dialog();
void render_location_dialog();
// clears the drawing and sets the point of EPSG:2180 that the scenery's zero stands for
void start_map(bool const Georeferenced, double const Originx, double const Originy);
// paints the solved plan over the rendered scenery and takes the clicks that build it
void handle_scene();
void draw_on_scene();
// re-runs the solver over the whole document; cheap enough to do on every edit
void solve();
void apply_constraints();
maj0sted::web::NiweletaSpec *current_niweleta();
maj0sted::web::NiweletaSpec const *current_niweleta() const;
// keeps exactly one fit slot per gap, so a gap's parameters survive edits to its neighbours
void align_fits();
// true when the solver actually produced the fit requested for the given gap
bool fit_applied(std::size_t const Gap) const;
// the panel edits a chained polyline, so a vertex is shared by the two straights meeting at it.
// the library allows fully independent straights; that freedom just isn't exposed here yet
std::size_t vertex_count() const;
void vertex_position(std::size_t const Index, double &X, double &Y) const;
// independent anchored straights: each endpoint is its own handle
maj0sted::web::StraightSpec const *handle_straight(std::size_t const Niw, std::size_t const Index) const;
void append_vertex(double const X, double const Y);
void move_vertex(std::size_t const Index, double const X, double const Y);
void drop_last_vertex();
// takes the camera to the middle of the drawing
void delete_selected_straight();
void delete_current_niweleta();
void clear_selection();
// hit tests (screen pixels). niw=-1 means "current niweleta only"
bool hit_endpoint(ImVec2 const &Mouse, int &OutNiw, int &OutStr, int &OutEnd) const;
bool hit_straight(ImVec2 const &Mouse, int &OutNiw, int &OutStr, bool const AllNiwelety) const;
// compound basket: draggable ends of entry/exit KP, free arcs and between-KP
bool hit_compound_station(ImVec2 const &Mouse, int &OutGap, int &OutKind, int &OutArc) const;
void drag_compound_station(double const X, double const Y);
// arc-first draft: continue from a straight end, click appends luk/KP by length
void begin_draft();
void discard_draft();
void start_draft_from_end(std::size_t const Str, int const End);
void append_draft_at(double const X, double const Y);
void undo_draft_fragment();
bool finish_draft();
bool reopen_draft_from_gap(std::size_t const Gap);
void rebuild_draft_preview();
bool draft_tip(double &OutX, double &OutY) const;
bool draft_end_pose(double &OutX, double &OutY, double &OutAz) const;
bool hit_draft_station(ImVec2 const &Mouse, int &OutKind, int &OutArc) const;
void drag_draft_station(double const X, double const Y);
bool anchor_draft_to_gap(std::size_t const Gap);
// switches (rozjazdy): a branch leaves a through niweleta without splitting it. clicking the
// through track drops a switch there and starts a branch niweleta pinned to its frog
void add_switch_on(std::size_t const Through, double const Wx, double const Wy);
void render_switches();
void go_to_plan();
// members
maj0sted::app::EditorDocument m_document;
std::vector<maj0sted::web::NiweletaPolys> m_solved;
std::size_t m_niweleta{0}; // niweleta being drawn into
int m_draggedvertex{-1};
bool m_drawing{true}; // whether a click on the scenery lays track or only picks existing points
// first corner of a niweleta has nothing to attach to, so it waits for the second click
std::vector<maj0sted::web::JunctionGeom> m_junctions;
std::size_t m_niweleta{0};
// switch placement: 0 idle, 1 waiting for a click on the through track
int m_pick_switch{0};
int m_sw_side{0}; // 0 left, 1 right
double m_sw_crossing{9.0}; // skos 1:n
double m_sw_radius{190.0}; // internal arc radius (metres)
int m_sel_straight{-1};
int m_sel_end{-1};
bool m_dragging{false};
// captured at drag start so Shift keeps the angle while the length changes
double m_drag_fixed_x{0.0};
double m_drag_fixed_y{0.0};
double m_drag_ux{1.0};
double m_drag_uy{0.0};
// compound station drag: kind 0=entry KP end, 1=free-arc end, 2=between-KP end, 3=exit KP start
bool m_dragging_cmp{false};
int m_cmp_gap{-1};
int m_cmp_kind{-1};
int m_cmp_arc{-1};
bool m_drawing{true};
bool m_pending{false};
double m_pendingx{0.0};
double m_pendingy{0.0};
// 0 = idle, 1 = picking parallel reference, 2 = picking skew reference
int m_pick_rel{0};
// after a reference is chosen, ask for the numeric parameter
bool m_ask_rel_param{false};
double m_ask_rel_value{4.5};
// compound: gap being edited; pick mode accumulates guide points, then Fit runs the solver
int m_sel_gap{-1};
bool m_pick_compound_point{false};
struct FitGuide
{
double x{0.0};
double y{0.0};
};
std::vector<FitGuide> m_fit_guides;
maj0sted::app::BasketDraft m_draft;
std::vector<maj0sted::web::WebPolyline> m_draft_polys;
// 0 idle, 1 pick straight end to continue from
int m_draft_place{0};
// next click appends: 0 = luk, 1 = KP (wej/między), 2 = KP wyjście (kończy: prosta + kotwica)
int m_draft_append_kind{0};
double m_draft_r{300.0};
double m_draft_finish_straight{100.0};
// 0 idle, 1 click a straight to choose the gap to anchor into
int m_anchor_pick{0};
bool m_dragging_draft{false};
int m_draft_kind{-1};
int m_draft_arc{-1};
char m_namebuf[128]{};
std::string m_status;
char m_path[256]{"editor/plan.m0s"};
// location picker: the point being considered, and the view over the schematic map of Poland
char m_scn_path[256]{"scenery/plan_export.scn"};
bool m_pickingplace{false};
double m_pickx{0.0};
double m_picky{0.0};
double m_mapviewx{0.0};
double m_mapviewy{0.0};
double m_mapscale{0.0};
// topographic base map behind the picker, and the orthophoto under the plan itself
editor::wms_image m_topomap{maj0sted::web::WmsConfig::geoportal_topo()};
editor::orthophoto_source m_ortho;
// 1 km topo cells for views wider than a kilometre (orto's 100 m grid is too dense there)
editor::orthophoto_source m_topo{maj0sted::web::WmsConfig::geoportal_topo(), 1000, "topo"};
bool m_showortho{true};
};