diff --git a/application/editormode.cpp b/application/editormode.cpp index 4176d9e2..43ad3783 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -1494,12 +1494,14 @@ bool editor_mode::update() } render_track_context(); update_build_tools(); + tape_update(); update_track_intent(); draw_track_hover(); draw_track_set(); draw_track_spread(); draw_build_overlay(); draw_track_intent(); + draw_tape(); m_speed.update(); m_joints.update(); draw_track_hints(); @@ -6447,7 +6449,9 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in // the first Esc drops what is going on in the track mode, the next one goes back to the selection if (is_press(Action) && ui()->mode() == nodebank_panel::TRACK) { - if (m_lay.active && false == m_lay.points.empty()) + if (m_tape.active) + m_tape = {}; + else if (m_lay.active && false == m_lay.points.empty()) lay_cancel(); else if (m_track_style.picking) m_track_style.picking = false; diff --git a/application/editormode.h b/application/editormode.h index 20d334df..dd841175 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -1580,7 +1580,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri void draw_track_hover() const; struct track_intent { - enum class kind { none, gizmo, select, point, box, straight_set, straight_break, detour, straight_tool, straight_handle, route_vertex, route_grip, switch_at, lay, lay_end, sweep, turntable_place, turntable_exit, turntable_fit, spread, extend_end, signal_place, signal_move, switch_slide, pick_style, fouling }; + enum class kind { none, gizmo, select, point, box, straight_set, straight_break, detour, straight_tool, straight_handle, route_vertex, route_grip, switch_at, lay, lay_end, sweep, turntable_place, turntable_exit, turntable_fit, spread, extend_end, signal_place, signal_move, switch_slide, pick_style, fouling, measure }; kind what{kind::none}; TTrack *track{nullptr}; int path{0}; @@ -1591,6 +1591,17 @@ class editor_mode : public application_mode, private editor_track::observer, pri std::string action; }; track_intent m_intent; + // the measure between two points (M): on the ground, at a free end or on the axis of the track under the cursor + struct tape_state + { + bool active{false}; + std::vector points; + glm::dvec3 mouse{0.0}; + }; + tape_state m_tape; + void tape_update(); + std::string tape_readout() const; + void draw_tape() const; track_intent track_intent_at(int const Mods); bool track_gesture_on() const; void update_track_intent(); diff --git a/application/editormodeselftesttrack.cpp b/application/editormodeselftesttrack.cpp index 021669ae..845e9fd2 100644 --- a/application/editormodeselftesttrack.cpp +++ b/application/editormodeselftesttrack.cpp @@ -396,6 +396,23 @@ editor_mode::selftest_result editor_mode::selftest_track(std::string const &comm WriteLog(format("SELFTEST vprofile zone %.3f %.3f %s", zone.from, zone.to, zone.name.c_str())); return selftest_result::handled; } + if (command == "tape") + { + // the measure between two points on the ground, as two clicks of the measuring tape take it + std::vector points; + for (glm::dvec3 point{0.0, 5000.0, 0.0}; words >> point.x >> point.z;) + points.push_back(point); + std::vector found; + auto const heights{ground_heights(points, true, &found)}; + for (std::size_t i = 0; i < points.size(); ++i) + points[i].y = found[i] != 0 ? heights[i] : 0.0; + m_tape = {true, points, points.empty() ? glm::dvec3{0.0} : points.back()}; + auto readout{tape_readout()}; + std::replace(readout.begin(), readout.end(), '\n', '|'); + WriteLog("SELFTEST tape: " + readout); + m_tape = {}; + return selftest_result::handled; + } if (command == "trackcolours") { int colouring{0}; diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 1da2fb0c..406924ec 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -501,6 +501,11 @@ void editor_mode::arm_switch() bool editor_mode::track_shortcut(int const Key) { + if (Key == GLFW_KEY_M) + { + m_tape = {false == m_tape.active, {}, m_tape.mouse}; + return true; + } auto const key{Key == GLFW_KEY_C ? GLFW_KEY_G : Key}; for (auto const &mode : track_modes()) { @@ -531,6 +536,8 @@ bool editor_mode::track_busy() const std::string editor_mode::track_mode_name() const { + if (m_tape.active) + return STR_C("Measure"); if (false == m_track_window_open) return STR_C("Select"); switch (m_track_tab) @@ -1750,6 +1757,8 @@ std::vector editor_mode::track_key_hints(bool const All) // values of the change in progress, shown next to the cursor std::string editor_mode::track_readout() const { + if (m_tape.active) + return tape_readout(); auto const name = [](TTrack const *Track) { return name_of(*Track); }; auto const azimuth = [](glm::dvec3 const &From, glm::dvec3 const &To) { auto const degrees{glm::degrees(std::atan2(To.x - From.x, To.z - From.z))}; diff --git a/application/editormodetrackux.cpp b/application/editormodetrackux.cpp index 26919c39..0869faba 100644 --- a/application/editormodetrackux.cpp +++ b/application/editormodetrackux.cpp @@ -392,6 +392,11 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods) }; auto *selected{selected_track()}; + if (m_tape.active) + { + intent.position = m_tape.mouse; + return make(kind::measure, m_tape.points.size() == 1 ? "Click: the end of the measure" : "Click: the start of the measure. M or Esc: done"); + } if (m_lay.active) { auto const &lay{m_lay}; @@ -637,11 +642,75 @@ void editor_mode::update_track_intent() } } +void editor_mode::tape_update() +{ + auto &tape{m_tape}; + if (false == tape.active || ImGui::GetIO().WantCaptureMouse) + return; + glm::vec2 const cursor{ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y}; + auto const ground{cursor_ground()}; + if (auto const end{snap_free_end(ground, cursor, nullptr, kRailCategory, {})}; end.track != nullptr) + tape.mouse = end.position; + else if (auto const under{track_under_cursor()}; under.track != nullptr) + tape.mouse = under.point; + else + tape.mouse = ground; +} + +std::string editor_mode::tape_readout() const +{ + auto const &tape{m_tape}; + if (tape.points.empty()) + return {}; + auto const &from{tape.points.front()}; + auto const &to{tape.points.size() >= 2 ? tape.points[1] : tape.mouse}; + auto const plan{geometry::plan_distance(from, to)}; + auto const rise{to.y - from.y}; + auto azimuth{glm::degrees(std::atan2(to.x - from.x, to.z - from.z))}; + if (azimuth < 0.0) + azimuth += 360.0; + auto text{format(STR_C("%.2f m in plan, %.2f m in space"), plan, glm::distance(from, to))}; + text += "\n" + format(STR_C("height %+.2f m"), rise) + (plan > 0.01 ? format(STR_C(" grade %+.1f per mille"), rise / plan * 1000.0) : std::string{}); + text += "\n" + format(STR_C("azimuth %.1f deg"), azimuth); + if (tape.points.size() >= 2) + text += "\n" + STR("Click: a new measure. M or Esc: done"); + return text; +} + +void editor_mode::draw_tape() const +{ + auto const &tape{m_tape}; + if (false == tape.active) + return; + screen_projection const projection; + auto *drawlist{ImGui::GetForegroundDrawList(ImGui::GetMainViewport())}; + auto const colour{IM_COL32(255, 230, 60, 255)}; + std::vector points{tape.points}; + if (points.size() < 2) + points.push_back(tape.mouse); + for (std::size_t i = 0; i + 1 < points.size(); ++i) + projection.line(drawlist, points[i], points[i + 1], colour, 2.0f); + for (auto const &point : points) + { + ImVec2 screen; + if (projection.project(point, screen)) + { + drawlist->AddCircleFilled(screen, 4.0f, colour); + drawlist->AddCircle(screen, 7.0f, IM_COL32(0, 0, 0, 200), 12, 1.5f); + } + } +} + void editor_mode::track_press(track_intent const &Intent) { using kind = track_intent::kind; switch (Intent.what) { + case kind::measure: + if (m_tape.points.size() >= 2) + m_tape.points.clear(); + m_tape.points.push_back(m_tape.mouse); + return; case kind::lay_end: if (m_lay.points.empty() && start_extend(Intent.snap)) return; diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 3280d254..ca1a37ee 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -10129,3 +10129,24 @@ msgstr "Opisy torów w widoku z góry" msgid "Zoomed in from above, the curves get their radius and length and the switches their name and type, as on a plan; where they'd crowd, some are left out" msgstr "Po przybliżeniu z góry łuki dostają promień i długość, a rozjazdy nazwę i typ, jak na planie; gdzie byłoby ich za gęsto, część się chowa" + +msgid "%.2f m in plan, %.2f m in space" +msgstr "%.2f m w planie, %.2f m w przestrzeni" + +msgid "height %+.2f m" +msgstr "różnica wysokości %+.2f m" + +msgid "azimuth %.1f deg" +msgstr "azymut %.1f°" + +msgid "Click: a new measure. M or Esc: done" +msgstr "LPM: nowy pomiar. M albo Esc: koniec" + +msgid "Click: the end of the measure" +msgstr "LPM: koniec pomiaru" + +msgid "Click: the start of the measure. M or Esc: done" +msgstr "LPM: początek pomiaru. M albo Esc: koniec" + +msgid "Measure" +msgstr "Miarka"