mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 22:39:19 +02:00
What the plan tool was missing was not geometry but the ordinary business of editing: taking a mistake back, dividing what is drawn, and seeing what the next click will do before making it. Cofanie. The document is plain data, so a step is a copy of it. What counts as one step is decided by watching the document - a fingerprint over the tracks, turnouts and catalogue - rather than by dressing every edit with a call: a frame that changes anything opens a step, and the step closes when the drawing settles. That is what makes a drag, or a number typed digit by digit, one step and not thirty, and nothing can be forgotten on the way in. Ctrl+Z / Ctrl+Y; the scene editor listens for the same keys, so it hands them over while the plan has the keyboard. Złącze i wycięcie. `insert_joint` divides whichever element a station lands in and leaves one chain: the ground does not move, the curvature at the new joint is the one that was always there, and the fit reads the chain the same way afterwards. It is refused on a straight, because two straights end to end are one straight and the skeleton will not read that, and under a turnout, which is one piece of geometry on one element. `cut_track` takes a piece out and does leave two tracks - a gap is the ground between them, not a hole in a chain. Przejście rozjazdowe. A turnout may now stand against another one: where it stands is then worked out from that one's frog, not authored, so moving either carries the other with it and a trapez stays shut. The frog has to land on the line leaving the other frog - not on the track's axis, which is 1,469 m further on for a 1:9 - so the station is settled by laying the turnout and walking it along until it does. Drawing is a tool now, not what a click happens to do: until Rysuj is on, a click picks what is under it. With it on, the cursor at the growing end shows the corner and the straight the next click would lay, worked out by the same fit_corner the click itself uses. A chain only grows at a loose end, and the first element of a track is put down by two clicks instead of landing on the plan's zero. Also here: the curvature strip under the panel, pikietaż in the rows and at the cursor, how far the laid axis runs from the clicks it was drawn from, the file format saying which frame and which units it is in (v9), plans chosen from the directory instead of typed, and editor/samples/demo.m0s - two parallel tracks and a trapez - with the generator that wrote it and a test that opens it and demands it lays clean.
305 lines
14 KiB
C++
305 lines
14 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 <deque>
|
|
#include <unordered_map>
|
|
#include <string>
|
|
#include <variant>
|
|
#include <vector>
|
|
#include "imgui/imgui.h"
|
|
|
|
export module eu07.application.planpanel;
|
|
import eu07.editor.plan_tilecache;
|
|
import eu07.editor.plan_layout;
|
|
import eu07.editor.plan_model;
|
|
import eu07.editor.plan_render;
|
|
import eu07.editor.plan_solution;
|
|
import eu07.editor.plan_scn_export;
|
|
import eu07.application.uilayer;
|
|
import eu07.editor.orthophoto;
|
|
|
|
export {
|
|
|
|
// 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:
|
|
// the plan library 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
|
|
{
|
|
|
|
public:
|
|
plan_panel(std::string const &Name, bool const Isopen);
|
|
|
|
void update() override;
|
|
void render_contents() override;
|
|
// whether Ctrl+Z / Ctrl+Y belong to the plan while this panel has the keyboard. the editor's
|
|
// scene undo listens for the same keys, and cofniecie ma dotyczyc tego, na co sie patrzy
|
|
[[nodiscard]] bool takes_history_shortcuts() const;
|
|
|
|
private:
|
|
using Document = editor::plan::Document;
|
|
using Element = editor::plan::Element;
|
|
using ElementId = editor::plan::ElementId;
|
|
using Solution = editor::plan::Solution;
|
|
using Track = editor::plan::Track;
|
|
using TrackId = editor::plan::TrackId;
|
|
using TurnoutId = editor::plan::TurnoutId;
|
|
|
|
// panels
|
|
void render_toolbar();
|
|
void render_elements();
|
|
void render_turnouts();
|
|
// joining two tracks into one: the radius a join may bend at, and the button that starts
|
|
// pointing at the ends to join. everything else about it happens on the map
|
|
void render_join();
|
|
// dzielenie i wycinanie: the buttons that start pointing at the ground. what a cut costs is
|
|
// worked out by the plan library, so there is nothing here but the errand and its answer
|
|
void render_cutting();
|
|
void split_at(TrackId const Track_, double const Station);
|
|
void cut_between(TrackId const Track_, double const From, double const To);
|
|
void render_turnout_type(editor::plan::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();
|
|
// krzywizna po pikietazu: the strip under the lists. it is the one picture that shows a chain's
|
|
// structure at a glance - a straight is on the axis, an arc is a step, a transition is a ramp,
|
|
// and a joint where the curvature jumps is a wall
|
|
void render_curvature_strip();
|
|
// how far the laid axis runs from the points that were clicked for it. only for a track that is
|
|
// being drawn: the trace is the intent behind it, and it is not part of the document
|
|
void measure_trace();
|
|
void render_storage();
|
|
// stamps the view and the georeference into the document and writes it where m_path points
|
|
void save_plan();
|
|
void export_scn(bool Run_);
|
|
void run_scn();
|
|
void seed_catalogue();
|
|
void render_newmap_dialog();
|
|
void render_location_dialog();
|
|
|
|
// scene
|
|
void handle_scene();
|
|
void draw_on_scene();
|
|
void go_to_plan();
|
|
|
|
// cofanie. the document is plain data, so one step is a copy of it. what counts as a step is
|
|
// decided by watching the document rather than by dressing every edit with a call: a frame that
|
|
// changes anything opens a step, and the step closes when the drawing settles - which is what
|
|
// makes a drag, or a number typed digit by digit, one step and not thirty
|
|
void watch_history();
|
|
[[nodiscard]] std::uint64_t document_print() const;
|
|
void undo();
|
|
void redo();
|
|
// after stepping through history: the selection may name something that is no longer there,
|
|
// and every transient errand belongs to the state that was undone
|
|
void settle_after_history();
|
|
|
|
// 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(editor::plan::Kind const Kind);
|
|
// whether the chain may grow at all, and why not. a chain grows at its far end, so that end has
|
|
// to be loose: pushing it into a track it already stands on, or off a turnout's frog, would move
|
|
// what is already connected
|
|
bool can_grow(std::string &Why) const;
|
|
// where the next element will go, and which way it will run. false when the track is empty or
|
|
// not laid - there is no growing end then
|
|
bool growth_pose(editor::plan::geometry::Pose &Out) const;
|
|
// the element the buttons would append right now, laid from the growing end, as a dashed ghost
|
|
void draw_growth_cursor();
|
|
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;
|
|
// the loose end of any track: OutEnd 0 for the one it starts on, 1 for the one it runs out to.
|
|
// a track hanging on a turnout's port has no free start, so that one is not offered
|
|
bool hit_track_end(ImVec2 const &Mouse, float const Tolerance, TrackId &OutTrack, int &OutEnd) const;
|
|
// whether that end is loose at all: one that already stands on another track's end is joined
|
|
// to it, and there is nothing to join twice
|
|
bool loose_end(editor::plan::SolvedTrack const &Track_, int const End_) const;
|
|
// how an end reads for a join laid out of it or into it: one facing the wrong way for that
|
|
// is taken backwards, which is what turning the track round would do to it anyway
|
|
static editor::plan::geometry::Pose join_pose(editor::plan::SolvedTrack const &Track_, int const End_, bool const Leaving_);
|
|
// lays the join and leaves one track, saying what it cost
|
|
void join_ends(TrackId const A, int const Aend, TrackId const B, int const Bend);
|
|
// 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
|
|
Document m_document;
|
|
Solution m_solution;
|
|
// history: the states to go back to, the states to come forward to, the document as of the last
|
|
// step boundary, and the fingerprint that says whether anything has changed since
|
|
std::deque<Document> m_undo;
|
|
std::deque<Document> m_redo;
|
|
Document m_settled;
|
|
std::uint64_t m_print{0};
|
|
bool m_step_open{false};
|
|
bool m_focused{false};
|
|
std::vector<editor::plan::PlanPolyline> m_rails;
|
|
|
|
// the clicks a track was drawn from, per track, and what the laid geometry cost against them:
|
|
// the worst and the root-mean-square distance, per element and per track. none of it is saved -
|
|
// a trace is what the user pointed at, not what the document says
|
|
std::unordered_map<std::uint32_t, std::vector<editor::plan::PlanPoint>> m_trace;
|
|
std::unordered_map<std::uint32_t, std::pair<double, double>> m_deviation;
|
|
std::unordered_map<std::uint32_t, std::pair<double, double>> m_track_deviation;
|
|
// joints where the curvature jumps - an arc taken without a transition curve. normal on a
|
|
// drawing of an existing line, worth counting rather than warning about one by one
|
|
std::unordered_map<std::uint32_t, int> m_jumps;
|
|
|
|
TrackId m_track{};
|
|
ElementId m_sel_element{};
|
|
TurnoutId m_sel_turnout{};
|
|
|
|
// 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};
|
|
|
|
// 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};
|
|
|
|
// 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_startx{0.0};
|
|
double m_starty{0.0};
|
|
|
|
// 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};
|
|
// scalanie by pointing at it: 0 idle, 1 waiting for the first end, 2 for the second. a track
|
|
// joins onto another end to start, so which of the two ends is clicked says which way round
|
|
int m_pick_join{0};
|
|
TrackId m_join_first{};
|
|
int m_join_first_end{0};
|
|
// dzielenie i wycinanie: 0 idle, 1 waiting for a click on a track's axis; the cut wants two of
|
|
// them, so it also remembers the first and which track it belongs to
|
|
// rysowanie po terenie is a tool of its own, not what a click happens to do: off, a click in the
|
|
// ground selects what is under it and nothing more. on, every click carries the edited track on
|
|
// - which is why it has to be asked for and why Esc leaves it
|
|
int m_pick_draw{0};
|
|
|
|
// putting down the first element of an empty track: 0 idle, 1 waiting for the point it starts
|
|
// on, 2 for the direction. what it will be is remembered from the button that started it -
|
|
// a track has to begin somewhere on the ground, and 0,0 is not an answer
|
|
int m_pick_start{0};
|
|
editor::plan::Kind m_start_kind{editor::plan::Kind::Line};
|
|
|
|
// sparowanie rozjazdow w przejscie rozjazdowe: 0 idle, 1 waiting for the turnout the selected
|
|
// one is to stand against
|
|
int m_pick_pair{0};
|
|
|
|
int m_pick_split{0};
|
|
int m_pick_cut{0};
|
|
TrackId m_cut_track{};
|
|
double m_cut_from{0.0};
|
|
|
|
// 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};
|
|
// the radius a join may bend at
|
|
double m_join_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;
|
|
// the plan on screen, and the name being typed for a new one. the path is not typed any more:
|
|
// it is picked from what is in the directory
|
|
char m_path[256]{"editor/plan.m0s"};
|
|
char m_saveas[128]{};
|
|
char m_scn_path[256]{"scenery/plan_export.scn"};
|
|
std::vector<std::string> m_scn_warnings;
|
|
// the exported scenery is opened in the editor by default: it stands up without a locomotive,
|
|
// and looking at what was drawn is what the export is usually for
|
|
bool m_scn_run_editor{true};
|
|
bool m_scn_trainset{true};
|
|
|
|
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};
|
|
|
|
editor::wms_image m_topomap{editor::plan::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{editor::plan::WmsConfig::geoportal_topo(), 1000, "topo"};
|
|
bool m_showortho{true};
|
|
};
|
|
|
|
} // export
|