mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-28 19:17:28 +01:00
Editor: rebuild the plan library around a solved document, and bend turnouts onto curves
The library was a heap of half-built services - fitting, parallelism, niwelety, an SVG writer - that never met each other. In their place is one authored Document and one solver that lays it whole: tracks as element chains, turnouts placed on them, and a Solution that is rebuilt from scratch every time and never fed back into the document. What cannot be laid comes back as a diagnostic with the number in it rather than as silence or a fitted-away error. Turnouts are dimensioned the way the drawings dimension them. The blade follows Koc's rys. 5.9 - the curve is theoretically tangent to the opornica at PR, the blade starts at a with a planed nose of d, and A is where the rail stands u thick - and a + d derived that way lands on his tablica 5.1. Bending follows 5.3.6: it rotates the triangle OBC about the centre of the turnout, so the tangent t and the crossing angle stand and both radii follow from them. That is Koc's (5.7) and (5.8) without coding either, and it puts a symmetrically bent Rz 49E1-190-1:9 on the catalogue's Rlus 49E1-380,292-1:9. A rozjazd standing on a curve is bent onto it; one on a track drawn straight can carry a bend of its own. The szyny laczace take up the difference, because the switch and the frog are only bent, not re-cut. The plan panel draws all of it: the catalogue drawing of a type with its construction points, the theoretical arc dashed where no rail runs, and the blade nose as the straight it is cut as. Dragging a straight now stops where it would pull the track out from under a turnout. File format goes to m0s 6. Versions 3 to 5 still open.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -12,19 +12,27 @@ 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/editor/layout.hpp"
|
||||
#include "maj0sted/editor/model.hpp"
|
||||
#include "maj0sted/editor/render.hpp"
|
||||
#include "maj0sted/editor/solution.hpp"
|
||||
#include "maj0sted/io/scn_export.hpp"
|
||||
#include "maj0sted/editor/editor.hpp"
|
||||
#include "maj0sted/editor/ribbon.hpp"
|
||||
|
||||
#include <string>
|
||||
#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 plan view. none of the geometry is computed
|
||||
// here: maj0sted solves the whole document and the panel paints what comes back.
|
||||
// track layout tool. the drawing happens in the viewport, which the tool puts into a top-down
|
||||
// orthographic plan view; the panel holds only the controls. none of the geometry is computed here:
|
||||
// maj0sted solves the whole document and the panel paints what comes back.
|
||||
//
|
||||
// a track is a chain of elements, and every one of them says outright what it is: prosta of so many
|
||||
// metres, luk of radius R turning so far, krzywa przejsciowa easing to R over so many metres. there
|
||||
// is no second language of "run on until you reach that line" - laying by clicking along the ground
|
||||
// works out the corner at the click and writes the numbers down.
|
||||
//
|
||||
// what one track keeps against another is said as a relation, not redrawn by hand: an element may be
|
||||
// held parallel to another track's element at a given miedzytorze, and the solver moves the whole
|
||||
// track until it really is.
|
||||
class plan_panel : public ui_panel
|
||||
{
|
||||
|
||||
@@ -35,131 +43,133 @@ class plan_panel : public ui_panel
|
||||
void render_contents() override;
|
||||
|
||||
private:
|
||||
// methods
|
||||
using Document = maj0sted::editor::Document;
|
||||
using Element = maj0sted::editor::Element;
|
||||
using ElementId = maj0sted::editor::ElementId;
|
||||
using Solution = maj0sted::editor::Solution;
|
||||
using Track = maj0sted::editor::Track;
|
||||
using TrackId = maj0sted::editor::TrackId;
|
||||
using TurnoutId = maj0sted::editor::TurnoutId;
|
||||
|
||||
// panels
|
||||
void render_toolbar();
|
||||
void render_draft();
|
||||
void render_gaps();
|
||||
void render_compound(maj0sted::editor::GapFit &Fit, std::size_t const Gap, bool &Dirty);
|
||||
void fit_compound_to_guides();
|
||||
void clear_fit_guides();
|
||||
void render_elements();
|
||||
void render_turnouts();
|
||||
void render_turnout_type(maj0sted::editor::TurnoutType &Type_);
|
||||
// the catalogue drawing of the chosen type, in its own window: the diverging path piece by
|
||||
// piece, both tracks' rails, and the points a drawing is dimensioned from
|
||||
void render_template_window();
|
||||
void render_diagnostics();
|
||||
void render_storage();
|
||||
void export_scn();
|
||||
void seed_catalogue();
|
||||
void render_newmap_dialog();
|
||||
void render_location_dialog();
|
||||
void start_map(bool const Georeferenced, double const Originx, double const Originy);
|
||||
|
||||
// scene
|
||||
void handle_scene();
|
||||
void draw_on_scene();
|
||||
void solve();
|
||||
void apply_constraints();
|
||||
maj0sted::editor::NiweletaSpec *current_niweleta();
|
||||
maj0sted::editor::NiweletaSpec const *current_niweleta() const;
|
||||
void align_fits();
|
||||
bool fit_applied(std::size_t const Gap) const;
|
||||
// independent anchored straights: each endpoint is its own handle
|
||||
maj0sted::editor::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 drop_last_vertex();
|
||||
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;
|
||||
// nearest niweleta by its solved axis (curves included), for a forgiving double-click select
|
||||
int nearest_niweleta_axis(ImVec2 const &Mouse, float const Tolerance) const;
|
||||
// nearest station (arc length) on a through niweleta's axis to a plan point; for placing/moving switches
|
||||
bool station_on_through(std::size_t const Through, double const Wx, double const Wy, double &OutStation) 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();
|
||||
|
||||
// document
|
||||
void solve();
|
||||
void start_map(bool const Georeferenced, double const Originx, double const Originy);
|
||||
Track *current_track();
|
||||
Track const *current_track() const;
|
||||
void new_track();
|
||||
void delete_current_track();
|
||||
void clear_selection();
|
||||
// carries the track on to (X,Y). the track ends where it was last clicked, so that end is the
|
||||
// corner: it gets rounded with an arc of m_corner_radius, the straight behind it gives up the
|
||||
// tangent, and the new straight runs out to (X,Y) exactly. all of it lands in the document as
|
||||
// ordinary elements with ordinary lengths
|
||||
void append_point_to(double const X, double const Y);
|
||||
// takes back one click: the trailing straight, its corner arc, and the tangent the arc had
|
||||
// taken off the straight before it - which leaves the track ending on the click before
|
||||
void drop_last_vertex();
|
||||
// appends one element to the end of the current track, exactly as asked for
|
||||
void append_element(maj0sted::editor::Kind const Kind);
|
||||
void drop_last_element();
|
||||
void delete_selected_element();
|
||||
void place_turnout_at(TrackId const On, double const Wx, double const Wy);
|
||||
// re-lays the edited track along the lines its straights sat on before the change, so a new
|
||||
// radius (or a new transition length) is absorbed by the corner it belongs to and nothing
|
||||
// further down the track moves. false, with a reason, when it cannot be laid that way
|
||||
bool refit_current_track(std::string &Why);
|
||||
// swings the grabbed straight onto the line through its far end and (X,Y), then re-fits the
|
||||
// arcs on either side of it. leaves the track untouched when that cannot be laid
|
||||
void drag_straight_to(double const X, double const Y);
|
||||
|
||||
// hit tests, in screen pixels
|
||||
// the end of a straight of the edited track: OutIndex is its element index, OutEnd 0 for the
|
||||
// end it starts on and 1 for the end it runs out to
|
||||
bool hit_straight_end(ImVec2 const &Mouse, std::size_t &OutIndex, int &OutEnd) const;
|
||||
bool hit_element(ImVec2 const &Mouse, ElementId &OutElement, TrackId &OutTrack) const;
|
||||
bool hit_turnout(ImVec2 const &Mouse, TurnoutId &OutTurnout) const;
|
||||
// nearest station along a solved track's axis, for placing and sliding turnouts
|
||||
bool station_on(TrackId const Track, double const Wx, double const Wy, double &OutStation) const;
|
||||
TrackId nearest_track_axis(ImVec2 const &Mouse, float const Tolerance) const;
|
||||
|
||||
// members
|
||||
maj0sted::app::EditorDocument m_document;
|
||||
std::vector<maj0sted::editor::NiweletaPolys> m_solved;
|
||||
std::vector<maj0sted::editor::JunctionGeom> m_junctions;
|
||||
std::size_t m_niweleta{0};
|
||||
Document m_document;
|
||||
Solution m_solution;
|
||||
std::vector<maj0sted::editor::PlanPolyline> m_rails;
|
||||
|
||||
// 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)
|
||||
double m_sw_length{0.0}; // catalogue length from the chosen template (0 = just the curve)
|
||||
double m_sw_pre_blade{0.0}; // odcinek przediglicowy from the template, metres
|
||||
double m_sw_blade_angle{0.0}; // kąt nagięcia iglicy (beta) from the template, radians
|
||||
double m_sw_blade_length{0.0};// the straight blade from the template, metres
|
||||
int m_drag_junction{-1}; // index of the switch being slid along its through track, or -1
|
||||
TrackId m_track{};
|
||||
ElementId m_sel_element{};
|
||||
TurnoutId m_sel_turnout{};
|
||||
|
||||
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};
|
||||
// a press on the ground does whatever is under it: picks up what is drawn there, or carries the
|
||||
// current track on to there. every transient errand (placing a turnout, picking the element to
|
||||
// run parallel to) is started from its own button, and Esc drops back out of it
|
||||
bool m_dragging_turnout{false};
|
||||
|
||||
// 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};
|
||||
// grabbing a straight by one of its ends: which one, and the far end it swings about. the far
|
||||
// end is captured once, at the grab, because re-fitting the arc behind it moves it
|
||||
bool m_dragging_straight{false};
|
||||
std::size_t m_drag_index{0};
|
||||
int m_drag_end{0};
|
||||
double m_pivot_x{0.0};
|
||||
double m_pivot_y{0.0};
|
||||
|
||||
bool m_drawing{true};
|
||||
// an empty track needs two clicks before there is anything to carry on: the first only marks
|
||||
// where it starts. after that the track always ends on the last click, which is the corner the
|
||||
// next one is rounded at
|
||||
bool m_pending{false};
|
||||
double m_pendingx{0.0};
|
||||
double m_pendingy{0.0};
|
||||
double m_startx{0.0};
|
||||
double m_starty{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;
|
||||
// 0 idle, 1 waiting for a click on the through track
|
||||
int m_pick_turnout{0};
|
||||
// 0 idle, 1 waiting for the element the selected one is to be held parallel to
|
||||
int m_pick_parallel{0};
|
||||
|
||||
maj0sted::app::BasketDraft m_draft;
|
||||
std::vector<maj0sted::editor::PlanPolyline> 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};
|
||||
// what the next appended element is made of
|
||||
double m_new_radius{300.0};
|
||||
double m_new_length{100.0};
|
||||
int m_new_hand{1};
|
||||
// the radius a corner is rounded with when laying by clicking
|
||||
double m_corner_radius{300.0};
|
||||
// miedzytorze a new hold is given, metres
|
||||
double m_new_offset{4.75};
|
||||
|
||||
int m_turnout_type{0};
|
||||
// which piece of the chosen type's geometry list is open for editing; -1 = none
|
||||
int m_sel_piece{-1};
|
||||
|
||||
// the template drawing. a switch is thirty metres long and a metre and a half wide, so like
|
||||
// every catalogue drawing this one is stretched across the track - the ratio is said on screen
|
||||
bool m_show_template{false};
|
||||
float m_tpl_zoom{1.0f};
|
||||
float m_tpl_pan{0.0f}; // metres off centre, along the through track
|
||||
float m_tpl_exagg{6.0f}; // przewyzszenie poprzeczne
|
||||
bool m_tpl_rails{true};
|
||||
bool m_tpl_marks{true};
|
||||
// lukowanie in the drawing: the radius the tor zasadniczy is bent to, and which way
|
||||
double m_tpl_bend_radius{0.0};
|
||||
int m_tpl_bend_hand{0};
|
||||
|
||||
char m_namebuf[128]{};
|
||||
|
||||
std::string m_status;
|
||||
char m_path[256]{"editor/plan.m0s"};
|
||||
char m_scn_path[256]{"scenery/plan_export.scn"};
|
||||
|
||||
Reference in New Issue
Block a user