From 6ee771bb223415ad91103805cdcdb064e3752654 Mon Sep 17 00:00:00 2001 From: maj00r Date: Wed, 21 Oct 2026 09:50:38 +0200 Subject: [PATCH] refactor(editor): the hectometre posts and the fouling points are tools of their own The fifth and the sixth tool out of editor_mode, the two lineside ones sharing their file. Placing, moving and taking out the templates which stand by the track become services of the editor, with the template types of their own, and so do the tab of the track tools shown, the ground under a point and the first step done. The context menu, the click on a fouling point in the view and the lineside tabs go through the public calls of the tools. The self-test commands hekto and fouling move with them and log the same as before. --- application/editormode.cpp | 4 +- application/editormode.h | 146 ++--------------- application/editormodelineside.cpp | 209 +++++++++++++++++++----- application/editormodelineside.h | 142 ++++++++++++++++ application/editormodeselftestsweep.cpp | 106 +----------- application/editormodeselftesttrack.cpp | 4 +- application/editormodetrack.cpp | 4 +- application/editormodetrackux.cpp | 20 +-- application/editortool.h | 37 ++++- 9 files changed, 372 insertions(+), 300 deletions(-) create mode 100644 application/editormodelineside.h diff --git a/application/editormode.cpp b/application/editormode.cpp index 29f6347f..389d3b58 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -1453,8 +1453,8 @@ bool editor_mode::update() draw_parallel_preview(); if (m_track_tab == track_tab::lineside) { - draw_hekto_overlay(); - draw_fouling_overlay(); + m_hekto.draw_overlay(); + m_fouling.draw_overlay(); m_vehicle.draw_overlay(); } render_track_context(); diff --git a/application/editormode.h b/application/editormode.h index 3f6708b7..c9881b6c 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -16,6 +16,7 @@ http://mozilla.org/MPL/2.0/. #include "vehicle/Camera.h" #include "application/editormodegauge.h" #include "application/editormodejoints.h" +#include "application/editormodelineside.h" #include "application/editormodespeed.h" #include "application/editormodevehicle.h" #include "application/editortool.h" @@ -225,7 +226,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri int m_selected_history_idx{-1}; glm::dvec3 clamp_mouse_offset_to_max(const glm::dvec3 &offset); // the ground under a point the cursor landed on, for things which are to stand on the ground - glm::dvec3 placement_on_ground(glm::dvec3 Location); + glm::dvec3 placement_on_ground(glm::dvec3 Location) override; // puts every instance of the model the selected node shows on the ground under it void drop_model_instances(); void render_object_menu(); @@ -1431,7 +1432,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri void update_build_tools(); void draw_build_overlay() const; // modes of the track editor, one at a time: path is the plain selection - enum class track_tab { straights, route, path, turnout, profile, speed, joints, infra, lay, lineside, turntable, signals, parallel }; + using track_tab = editor_track_tab; struct track_mode { char const *label; @@ -1477,7 +1478,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri // the track tools in the tool options window, and the selected path in the inspector void render_track_tool_options(); void render_track_selection(); - void show_track_tab(track_tab const Wanted); + void show_track_tab(track_tab const Wanted) override; bool track_analysis_tab() const { return m_track_tab == track_tab::profile || m_track_tab == track_tab::speed || m_track_tab == track_tab::joints || m_track_tab == track_tab::infra; } bool track_shortcut(int const Key); bool track_busy() const; // something going on in the mode, which Esc drops before it leaves the mode @@ -1769,28 +1770,10 @@ class editor_mode : public application_mode, private editor_track::observer, pri bool parallel_preview_visible() const; void draw_parallel_preview() const; - // scenery templates standing by the track, placed and checked by the editor - struct template_item - { - std::string file; - glm::dvec3 location{0.0}; // world space - double yaw{0.0}; - std::vector rest; // parameters after the rotation - bool described{false}; - std::string track; - glm::dvec2 tilt{0.0}; // rot.x and rot.z of a described template, degrees - std::map values; - }; - struct standing_template - { - scene::instance_handle instance{0}; - std::string file; // lower case, forward slashes - glm::dvec3 location{0.0}; - double yaw{0.0}; - std::vector values; - }; + using template_item = ::template_item; + using standing_template = ::standing_template; // includes of the templates whose file names the test accepts, with the location in p2..p4 and the rotation in p5 - std::vector standing_templates(std::function const &Accept) const; + std::vector standing_templates(std::function const &Accept) const override; struct switch_drive { standing_template standing; @@ -1804,116 +1787,11 @@ class editor_mode : public application_mode, private editor_track::observer, pri void switch_drive_captured(TTrack const &Track); void switch_drives_committed(std::vector const &Tracks); // places them in the active layer, as one step of the undo. returns: how many were placed - std::size_t place_templates(std::vector const &Items, std::string &Error); + std::size_t place_templates(std::vector const &Items, std::string &Error) override; // moves them, as one step of the undo - std::size_t move_templates(std::vector> const &Moves); + std::size_t move_templates(std::vector> const &Moves) override; // takes them out, as one step of the undo - std::size_t remove_templates(std::vector const &Instances); - - // hectometre posts along the line, at every 100 m of the kilometrage - struct hekto_post - { - int hectometres{0}; - double chainage{0.0}; - glm::dvec3 position{0.0}; - double yaw{0.0}; - int standing{-1}; // post already there with this number - }; - struct hekto_found - { - standing_template post; - int hectometres{0}; - double chainage{0.0}; // where it stands along the line - double expected{0.0}; // where its number belongs - double error{0.0}; // m along the line - double distance{0.0}; // m from where it belongs, in the plan - bool wrong{false}; - bool duplicate{false}; // another post with the same number stands nearer to its place - bool outside{false}; // its number belongs past the ends of the line - }; - struct hekto_tool - { - bool open{false}; - bool expand{false}; - TTrack *track{nullptr}; // the line runs through it - glm::dvec3 point{0.0}; // where the kilometrage is given - double km{0.0}; - bool km_read{false}; // the kilometrage was read from the posts already standing - int growth{1}; // 1: grows along the line, -1: against - int side{1}; // 1: to the right of the growing kilometrage, -1: to the left, 0: alternately - double offset{3.0}; // m from the axis - int style{0}; // nowy, stary, zgnity, mixed - int turn{0}; // degrees added to the rotation - double reach{10000.0}; // m each way - double tolerance{5.0}; // m along the line, for the posts standing - bool dirty{true}; - std::size_t history{0}; - editor_track::route route; - std::vector samples; - double origin{0.0}; // chainage of the point - std::vector posts; - std::vector found; - std::string status; - std::string error; - }; - hekto_tool m_hekto; - void hekto_start(TTrack &Track, glm::dvec3 const &Point); - void hekto_update(); - void hekto_read_kilometrage(); - void hekto_place(); - void hekto_fix(); - void hekto_remove_duplicates(); - void render_hekto_ui(); - void draw_hekto_overlay() const; - - // fouling point markers (W17) where the tracks meeting at a switch are the spacing apart - struct fouling_point - { - TTrack *track{nullptr}; // the switch - glm::dvec3 position{0.0}; - double yaw{0.0}; - int standing{-1}; // marker already there - }; - struct fouling_found - { - standing_template marker; - int point{-1}; // the fouling point it belongs to - double error{0.0}; // m from where it belongs - double height_error{0.0}; // m over where it belongs - bool wrong{false}; - }; - struct fouling_tool - { - bool open{false}; - bool expand{false}; - int scope{1}; // 0: the selected switch, 1: around the camera, 2: the whole scenery - double reach{1500.0}; - double spacing{3.5}; // m between the axes - double tolerance{2.0}; // m, a marker farther than this from its point is wrong - int height_mode{1}; // 0: on the ground, 1: at the height given over the rail heads - double height{0.0}; // m over the rail heads - double height_tolerance{0.03}; // m, a marker standing higher or lower than this is wrong - int model{0}; // w17, w17_new, w17_old, w17_dwuteo - int turn{0}; - bool dirty{true}; - std::size_t history{0}; - TTrack *selected{nullptr}; - std::vector points; - std::vector found; - std::string status; - std::string error; - }; - fouling_tool m_fouling; - void fouling_update(); - // the markers put at the fouling points without one, or at the one only - void fouling_place(int const Only = -1); - // the markers standing wrong moved to their places, or the one found only - void fouling_fix(int const Only = -1); - // the fouling point without a marker or the marker standing wrong under the cursor: the index of the point or of the - // marker found, and whether it's a marker - std::optional> fouling_hit() const; - void render_fouling_ui(); - void draw_fouling_overlay() const; + std::size_t remove_templates(std::vector const &Instances) override; struct signal_template { @@ -2393,7 +2271,9 @@ class editor_mode : public application_mode, private editor_track::observer, pri speed_tool m_speed{*this}; joints_tool m_joints{*this}; vehicle_tool m_vehicle{*this}; - std::vector m_tools{&m_gauge, &m_speed, &m_joints, &m_vehicle}; + hekto_tool m_hekto{*this}; + fouling_tool m_fouling{*this}; + std::vector m_tools{&m_gauge, &m_speed, &m_joints, &m_vehicle, &m_hekto, &m_fouling}; void infra_rebase(); void render_infra_search(); diff --git a/application/editormodelineside.cpp b/application/editormodelineside.cpp index e96858d8..f6de2967 100644 --- a/application/editormodelineside.cpp +++ b/application/editormodelineside.cpp @@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "application/editormode.h" +#include "application/editormodelineside.h" #include "application/editormodeshared.h" #include "application/editoruilayer.h" #include "application/editorprojection.h" @@ -365,18 +366,18 @@ std::size_t editor_mode::remove_templates(std::vector co return count; } -void editor_mode::hekto_start(TTrack &Track, glm::dvec3 const &Point) +void hekto_tool::start(TTrack &Track, glm::dvec3 const &Point) { auto &tool{m_hekto}; tool.track = &Track; tool.point = Point; tool.km_read = false; tool.dirty = true; - tool.expand = true; + m_tab.expand = true; tool.status.clear(); } -void editor_mode::hekto_read_kilometrage() +void hekto_tool::hekto_read_kilometrage() { auto &tool{m_hekto}; auto const &found{tool.found}; @@ -417,13 +418,13 @@ void editor_mode::hekto_read_kilometrage() tool.status = format(STR_C("Kilometrage read from %zu posts standing along the line"), found.size()); } -void editor_mode::hekto_update() +void hekto_tool::hekto_update() { auto &tool{m_hekto}; - if (false == tool.dirty && tool.history == m_history.size()) + if (false == tool.dirty && tool.history == m_services.history_size()) return; tool.dirty = false; - tool.history = m_history.size(); + tool.history = m_services.history_size(); tool.posts.clear(); tool.found.clear(); tool.error.clear(); @@ -448,7 +449,7 @@ void editor_mode::hekto_update() double distance; tool.origin = project(tool.samples, tool.point, distance); auto const length{tool.samples.back().chainage}; - for (auto const &standing : standing_templates([](std::string const &File) { return File.find("hekto") != std::string::npos; })) + for (auto const &standing : m_services.standing_templates([](std::string const &File) { return File.find("hekto") != std::string::npos; })) { hekto_found post; post.post = standing; @@ -487,7 +488,7 @@ void editor_mode::hekto_update() // alternately: the even hectometres on the right, the odd ones on the left of the growing kilometrage auto const side{tool.side != 0 ? tool.side : (hectometres % 2 == 0 ? 1 : -1)}; auto const planar{plan_of(axis) + left * (side * tool.offset)}; - post.position = placement_on_ground({planar.x, axis.y, planar.y}); + post.position = m_services.placement_on_ground({planar.x, axis.y, planar.y}); post.yaw = std::fmod(heading_of(growing) + tool.turn, 360.0); // of the posts with this number the one nearest to the place stands for it, the others are duplicates. it stands wrong // when it's farther from the place than allowed: along the line, across it, or on the other side @@ -517,7 +518,7 @@ void editor_mode::hekto_update() } } -void editor_mode::hekto_remove_duplicates() +void hekto_tool::hekto_remove_duplicates() { auto &tool{m_hekto}; hekto_update(); @@ -525,13 +526,13 @@ void editor_mode::hekto_remove_duplicates() for (auto const &found : tool.found) if (found.duplicate) instances.push_back(found.post.instance); - auto const removed{remove_templates(instances)}; + auto const removed{m_services.remove_templates(instances)}; tool.status = format(STR_P("%zu duplicate hectometre post removed, Ctrl+Z brings it back", "%zu duplicate hectometre posts removed, Ctrl+Z brings them back", static_cast(removed)), removed); WriteLog("Editor: " + tool.status); tool.dirty = true; } -void editor_mode::hekto_place() +void hekto_tool::hekto_place() { auto &tool{m_hekto}; hekto_update(); @@ -548,15 +549,15 @@ void editor_mode::hekto_place() items.push_back(std::move(item)); } std::string error; - auto const placed{place_templates(items, error)}; + auto const placed{m_services.place_templates(items, error)}; tool.status = placed > 0 ? format(STR_P("%zu hectometre post placed, Ctrl+Z takes it back", "%zu hectometre posts placed, Ctrl+Z takes them back", static_cast(placed)), placed) : error; if (placed > 0) - first_step_done(first_step::posts); + m_services.first_step_done(editor_first_step::posts); WriteLog("Editor: " + tool.status, logtype::generic); tool.dirty = true; } -void editor_mode::hekto_fix() +void hekto_tool::hekto_fix() { auto &tool{m_hekto}; hekto_update(); @@ -570,16 +571,16 @@ void editor_mode::hekto_fix() item.yaw = post.yaw; moves.emplace_back(tool.found[post.standing].post, item); } - auto const moved{move_templates(moves)}; + auto const moved{m_services.move_templates(moves)}; tool.status = format(STR_P("%zu hectometre post moved to its place, Ctrl+Z takes it back", "%zu hectometre posts moved to their places, Ctrl+Z takes it back", static_cast(moved)), moved); WriteLog("Editor: " + tool.status, logtype::generic); tool.dirty = true; } -void editor_mode::render_hekto_ui() +void hekto_tool::render_options() { auto &tool{m_hekto}; - auto *selected{selected_track()}; + auto *selected{m_services.selected_track()}; if (tool.track == nullptr || (selected != nullptr && selected != tool.track && ImGui::SmallButton(STR_C("Along the line of the selected path")))) { if (selected == nullptr) @@ -587,7 +588,7 @@ void editor_mode::render_hekto_ui() ImGui::TextDisabled("%s", STR_C("Select a path of the line, or RMB on it: Hectometre posts")); return; } - hekto_start(*selected, bezier{selected->m_paths.front()}.point(0.0)); + start(*selected, bezier{selected->m_paths.front()}.point(0.0)); } auto const changed = [&](bool const Changed) { if (Changed) @@ -678,7 +679,7 @@ void editor_mode::render_hekto_ui() auto const label{post.duplicate ? format(STR_C("km %s duplicate##%u"), km_text(post.hectometres).c_str(), post.post.instance) : format(STR_C("km %s stands %.1f m from its place##%u"), km_text(post.hectometres).c_str(), post.distance, post.post.instance)}; if (ImGui::Selectable(label.c_str())) { - fly_to(post.post.location, glm::normalize(glm::dvec3{-1.0, -0.8, -1.0}), 20.0); + m_services.fly_to(post.post.location, glm::normalize(glm::dvec3{-1.0, -0.8, -1.0}), 20.0); } } ImGui::EndChild(); @@ -687,10 +688,10 @@ void editor_mode::render_hekto_ui() ImGui::TextWrapped("%s", tool.status.c_str()); } -void editor_mode::draw_hekto_overlay() const +void hekto_tool::draw_overlay() const { auto const &tool{m_hekto}; - if (false == tool.open || tool.track == nullptr) + if (false == m_tab.open || tool.track == nullptr) return; screen_projection const projection; auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())}; @@ -731,16 +732,16 @@ void editor_mode::draw_hekto_overlay() const } } -void editor_mode::fouling_update() +void fouling_tool::fouling_update() { auto &tool{m_fouling}; - auto *selected{selected_track()}; + auto *selected{m_services.selected_track()}; if (tool.scope == 0 && selected != tool.selected) tool.dirty = true; - if (false == tool.dirty && tool.history == m_history.size()) + if (false == tool.dirty && tool.history == m_services.history_size()) return; tool.dirty = false; - tool.history = m_history.size(); + tool.history = m_services.history_size(); tool.selected = selected; tool.points.clear(); tool.found.clear(); @@ -791,7 +792,7 @@ void editor_mode::fouling_update() point.track = track; auto const middle{(onbranch + onmain) * 0.5}; // the points of the paths are at the rail foot, the heads of the rails are the height of the rail over them - point.position = tool.height_mode == 1 ? glm::dvec3{middle.x, 0.5 * (onbranch.y + onmain.y) + kRailHeight + tool.height, middle.z} : placement_on_ground({middle.x, std::max(onbranch.y, onmain.y), middle.z}); + point.position = tool.height_mode == 1 ? glm::dvec3{middle.x, 0.5 * (onbranch.y + onmain.y) + kRailHeight + tool.height, middle.z} : m_services.placement_on_ground({middle.x, std::max(onbranch.y, onmain.y), middle.z}); auto const back{plan_of(previous - branch[i])}; point.yaw = std::fmod(heading_of(glm::length(back) > 1e-9 ? glm::normalize(back) : glm::dvec2{0.0, 1.0}) + tool.turn, 360.0); tool.points.push_back(point); @@ -799,7 +800,7 @@ void editor_mode::fouling_update() } } // markers standing: each goes to the nearest fouling point, the others are left alone - auto const markers{standing_templates([](std::string const &File) { return File.find("w17") != std::string::npos || File.find("ukres") != std::string::npos; })}; + auto const markers{m_services.standing_templates([](std::string const &File) { return File.find("w17") != std::string::npos || File.find("ukres") != std::string::npos; })}; for (auto const &marker : markers) { int nearest{-1}; @@ -837,7 +838,7 @@ void editor_mode::fouling_update() tool.status = format(STR_C("%zu switches, %zu fouling points: %d without a marker, %d markers standing wrong"), switches.size(), tool.points.size(), static_cast(missing), static_cast(wrong)); } -void editor_mode::fouling_place(int const Only) +void fouling_tool::place(int const Only) { auto &tool{m_fouling}; fouling_update(); @@ -856,13 +857,13 @@ void editor_mode::fouling_place(int const Only) items.push_back(std::move(item)); } std::string error; - auto const placed{place_templates(items, error)}; + auto const placed{m_services.place_templates(items, error)}; tool.status = placed > 0 ? format(STR_P("%zu fouling point marker placed, Ctrl+Z takes it back", "%zu fouling point markers placed, Ctrl+Z takes them back", static_cast(placed)), placed) : error; WriteLog("Editor: " + tool.status, logtype::generic); tool.dirty = true; } -void editor_mode::fouling_fix(int const Only) +void fouling_tool::fix(int const Only) { auto &tool{m_fouling}; fouling_update(); @@ -887,13 +888,13 @@ void editor_mode::fouling_fix(int const Only) } moves.emplace_back(found.marker, item); } - auto const moved{move_templates(moves)}; + auto const moved{m_services.move_templates(moves)}; tool.status = format(STR_P("%zu fouling point marker moved to its place, Ctrl+Z takes it back", "%zu fouling point markers moved to their places, Ctrl+Z takes it back", static_cast(moved)), moved); WriteLog("Editor: " + tool.status, logtype::generic); tool.dirty = true; } -std::optional> editor_mode::fouling_hit() const +std::optional> fouling_tool::hit() const { auto const &tool{m_fouling}; screen_projection const projection; @@ -921,7 +922,7 @@ std::optional> editor_mode::fouling_hit() const return result; } -void editor_mode::render_fouling_ui() +void fouling_tool::render_options() { auto &tool{m_fouling}; auto const changed = [&](bool const Changed) { @@ -943,7 +944,7 @@ void editor_mode::render_fouling_ui() ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.55f, 0.25f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25f, 0.65f, 0.3f, 1.0f)); if (ImGui::Button(format(STR_P("Place %d marker", "Place %d markers", static_cast(missing)), static_cast(missing)).c_str(), ImVec2(-1.0f, tall))) - fouling_place(); + place(); ImGui::PopStyleColor(2); } if (wrong > 0) @@ -951,7 +952,7 @@ void editor_mode::render_fouling_ui() ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.4f, 0.1f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.48f, 0.15f, 1.0f)); if (ImGui::Button(format(STR_P("Move %d wrong marker to its place", "Move %d wrong markers to their places", static_cast(wrong)), static_cast(wrong)).c_str(), ImVec2(-1.0f, tall))) - fouling_fix(); + fix(); ImGui::PopStyleColor(2); } if (missing + wrong > 0) @@ -967,10 +968,10 @@ void editor_mode::render_fouling_ui() continue; ImGui::PushID(i); if (ImGui::SmallButton(STR_C("Place"))) - fouling_place(i); + place(i); ImGui::SameLine(); if (ImGui::Selectable(format(STR_C("%s: no W17"), point.track->name().c_str()).c_str())) - fly_to(point.position, look, 25.0); + m_services.fly_to(point.position, look, 25.0); ImGui::PopID(); } for (int i = 0; i < static_cast(tool.found.size()); ++i) @@ -980,10 +981,10 @@ void editor_mode::render_fouling_ui() continue; ImGui::PushID(1000000 + i); if (ImGui::SmallButton(STR_C("Fix"))) - fouling_fix(i); + fix(i); ImGui::SameLine(); if (ImGui::Selectable(format(STR_C("%s: W17 %.1f m off, %+.2f m high"), tool.points[found.point].track->name().c_str(), found.error, found.height_error).c_str())) - fly_to(found.marker.location, look, 25.0); + m_services.fly_to(found.marker.location, look, 25.0); ImGui::PopID(); } ImGui::EndChild(); @@ -1031,10 +1032,10 @@ void editor_mode::render_fouling_ui() tool.tolerance = std::clamp(tool.tolerance, 0.2, 20.0); } -void editor_mode::draw_fouling_overlay() const +void fouling_tool::draw_overlay() const { auto const &tool{m_fouling}; - if (false == tool.open) + if (false == m_tab.open) return; screen_projection const projection; auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())}; @@ -1063,3 +1064,129 @@ void editor_mode::draw_fouling_overlay() const projection.line(drawlist, found.marker.location, tool.points[found.point].position, IM_COL32(255, 70, 60, 160), 1.5f); } } + +editor_selftest_result hekto_tool::selftest(std::string const &Command, std::istringstream &Words) +{ + if (Command == "hekto") + { + std::map values; + std::string pair; + while (Words >> pair) + { + auto const equals{pair.find('=')}; + if (equals != std::string::npos) + values[pair.substr(0, equals)] = pair.substr(equals + 1); + } + auto *track{simulation::Paths.find(values["track"])}; + if (track == nullptr) + { + WriteLog("SELFTEST hekto: no such track"); + return editor_selftest_result::handled; + } + start(*track, track->m_paths.front().points[segment_data::point::start]); + auto &tool{m_hekto}; + tool.dirty = true; + hekto_update(); + if (values.count("km")) + { + tool.km = std::stod(values["km"]); + tool.growth = values["growth"] == "-1" ? -1 : 1; + } + tool.side = values["side"] == "alt" ? 0 : values["side"] == "left" ? -1 : 1; + tool.dirty = true; + hekto_update(); + auto const missing{std::count_if(tool.posts.begin(), tool.posts.end(), [](hekto_post const &Post) { return Post.standing < 0; })}; + WriteLog(format("SELFTEST hekto: km %.3f growth %d, %zu posts along %.0f m, %d to place, %zu standing. %s", tool.km, tool.growth, tool.posts.size(), tool.samples.empty() ? 0.0 : tool.samples.back().chainage, static_cast(missing), tool.found.size(), tool.status.c_str())); + for (std::size_t i = 0; i < tool.posts.size() && i < 4; ++i) + { + auto const &post{tool.posts[i]}; + double lateral; + glm::dvec3 const axis{editor_track::sampled_position(tool.samples, post.chainage)}; + auto const direction{editor_track::sampled_position(tool.samples, post.chainage + 1.0) - axis}; + glm::dvec2 const offset{post.position.x - axis.x, post.position.z - axis.z}; + lateral = glm::length(offset); + auto const right{(direction.x * offset.y - direction.z * offset.x) * tool.growth >= 0.0}; + WriteLog(format("SELFTEST hekto post %d.%d at %.1f m, %.2f m from the axis, %s of the growing kilometrage, at %.1f %.1f %.1f", post.hectometres / 10, post.hectometres % 10, post.chainage, lateral, right ? "right" : "left", post.position.x, post.position.y, + post.position.z)); + } + if (values["place"] == "1") + { + hekto_place(); + WriteLog("SELFTEST hekto: " + tool.status); + } + return editor_selftest_result::handled; + } + return editor_selftest_result::unhandled; +} + +glm::dvec3 fouling_tool::position_of(std::pair const &Hit) const +{ + return Hit.second ? m_fouling.found[Hit.first].marker.location : m_fouling.points[Hit.first].position; +} + +void fouling_tool::check_selected_switch() +{ + m_fouling.scope = 0; + m_fouling.dirty = true; + m_tab.expand = true; +} + +editor_selftest_result fouling_tool::selftest(std::string const &Command, std::istringstream &Words) +{ + if (Command == "fouling") + { + std::map values; + std::string pair; + while (Words >> pair) + { + auto const equals{pair.find('=')}; + if (equals != std::string::npos) + values[pair.substr(0, equals)] = pair.substr(equals + 1); + } + auto &tool{m_fouling}; + if (values["show"] == "1") + { + m_services.show_track_tab(editor_track_tab::lineside); + m_tab.expand = true; + } + tool.scope = std::stoi(values.count("scope") ? values["scope"] : "2"); + tool.height_mode = values["mode"] == "ground" ? 0 : 1; + tool.height = std::stod(values.count("height") ? values["height"] : "0"); + tool.dirty = true; + fouling_update(); + WriteLog("SELFTEST fouling: " + tool.status); + int shown{0}; + for (auto const &found : tool.found) + if (found.wrong && shown++ < 5) + WriteLog(format("SELFTEST fouling: %s %.2f m off, %+.3f m high", found.marker.file.c_str(), found.error, found.height_error)); + if (values["place"] == "one") + { + // the first fouling point without a marker alone, as a click on it in the view + for (int index = 0; index < static_cast(tool.points.size()); ++index) + if (tool.points[index].standing < 0) + { + place(index); + break; + } + tool.dirty = true; + fouling_update(); + WriteLog("SELFTEST fouling one: " + tool.status); + } + if (values["place"] == "1") + { + place(); + tool.dirty = true; + fouling_update(); + WriteLog("SELFTEST fouling after placing: " + tool.status); + } + if (values["fix"] == "1") + { + fix(); + tool.dirty = true; + fouling_update(); + WriteLog("SELFTEST fouling after the fix: " + tool.status); + } + return editor_selftest_result::handled; + } + return editor_selftest_result::unhandled; +} diff --git a/application/editormodelineside.h b/application/editormodelineside.h new file mode 100644 index 00000000..d007cd54 --- /dev/null +++ b/application/editormodelineside.h @@ -0,0 +1,142 @@ +/* +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/. +*/ + +#pragma once + +#include "application/editortool.h" + +#include +#include +#include +#include + +// hectometre posts along the line, at every 100 m of the kilometrage +class hekto_tool final : public editor_tool +{ + public: + explicit hekto_tool(editor_services &Services) : m_services{Services} {} + void draw_overlay() const override; + void render_options() override; + editor_selftest_result selftest(std::string const &Command, std::istringstream &Words) override; + tool_tab &tab() { return m_tab; } + void start(TTrack &Track, glm::dvec3 const &Point); + + private: + editor_services &m_services; + tool_tab m_tab; + struct hekto_post + { + int hectometres{0}; + double chainage{0.0}; + glm::dvec3 position{0.0}; + double yaw{0.0}; + int standing{-1}; // post already there with this number + }; + struct hekto_found + { + standing_template post; + int hectometres{0}; + double chainage{0.0}; // where it stands along the line + double expected{0.0}; // where its number belongs + double error{0.0}; // m along the line + double distance{0.0}; // m from where it belongs, in the plan + bool wrong{false}; + bool duplicate{false}; // another post with the same number stands nearer to its place + bool outside{false}; // its number belongs past the ends of the line + }; + struct hekto_state + { + TTrack *track{nullptr}; // the line runs through it + glm::dvec3 point{0.0}; // where the kilometrage is given + double km{0.0}; + bool km_read{false}; // the kilometrage was read from the posts already standing + int growth{1}; // 1: grows along the line, -1: against + int side{1}; // 1: to the right of the growing kilometrage, -1: to the left, 0: alternately + double offset{3.0}; // m from the axis + int style{0}; // nowy, stary, zgnity, mixed + int turn{0}; // degrees added to the rotation + double reach{10000.0}; // m each way + double tolerance{5.0}; // m along the line, for the posts standing + bool dirty{true}; + std::size_t history{0}; + editor_track::route route; + std::vector samples; + double origin{0.0}; // chainage of the point + std::vector posts; + std::vector found; + std::string status; + std::string error; + }; + hekto_state m_hekto; + void hekto_update(); + void hekto_read_kilometrage(); + void hekto_place(); + void hekto_fix(); + void hekto_remove_duplicates(); +}; + +// fouling point markers (W17) where the tracks meeting at a switch are the spacing apart +class fouling_tool final : public editor_tool +{ + public: + explicit fouling_tool(editor_services &Services) : m_services{Services} {} + void draw_overlay() const override; + void render_options() override; + editor_selftest_result selftest(std::string const &Command, std::istringstream &Words) override; + tool_tab &tab() { return m_tab; } + // the markers put at the fouling points without one, or at the one only + void place(int const Only = -1); + // the markers standing wrong moved to their places, or the one found only + void fix(int const Only = -1); + // the fouling point without a marker or the marker standing wrong under the cursor: the index of the point or of the + // marker found, and whether it's a marker + std::optional> hit() const; + glm::dvec3 position_of(std::pair const &Hit) const; + void check_selected_switch(); + + private: + editor_services &m_services; + tool_tab m_tab; + struct fouling_point + { + TTrack *track{nullptr}; // the switch + glm::dvec3 position{0.0}; + double yaw{0.0}; + int standing{-1}; // marker already there + }; + struct fouling_found + { + standing_template marker; + int point{-1}; // the fouling point it belongs to + double error{0.0}; // m from where it belongs + double height_error{0.0}; // m over where it belongs + bool wrong{false}; + }; + struct fouling_state + { + int scope{1}; // 0: the selected switch, 1: around the camera, 2: the whole scenery + double reach{1500.0}; + double spacing{3.5}; // m between the axes + double tolerance{2.0}; // m, a marker farther than this from its point is wrong + int height_mode{1}; // 0: on the ground, 1: at the height given over the rail heads + double height{0.0}; // m over the rail heads + double height_tolerance{0.03}; // m, a marker standing higher or lower than this is wrong + int model{0}; // w17, w17_new, w17_old, w17_dwuteo + int turn{0}; + bool dirty{true}; + std::size_t history{0}; + TTrack *selected{nullptr}; + std::vector points; + std::vector found; + std::string status; + std::string error; + }; + fouling_state m_fouling; + void fouling_update(); +}; diff --git a/application/editormodeselftestsweep.cpp b/application/editormodeselftestsweep.cpp index 084994a2..fd37edb9 100644 --- a/application/editormodeselftestsweep.cpp +++ b/application/editormodeselftestsweep.cpp @@ -126,110 +126,6 @@ editor_mode::selftest_result editor_mode::selftest_sweep(std::string const &comm WriteLog("SELFTEST drag: " + m_sweep.status); return selftest_result::handled; } - if (command == "fouling") - { - std::map values; - std::string pair; - while (words >> pair) - { - auto const equals{pair.find('=')}; - if (equals != std::string::npos) - values[pair.substr(0, equals)] = pair.substr(equals + 1); - } - auto &tool{m_fouling}; - if (values["show"] == "1") - { - show_track_tab(track_tab::lineside); - tool.expand = true; - } - tool.scope = std::stoi(values.count("scope") ? values["scope"] : "2"); - tool.height_mode = values["mode"] == "ground" ? 0 : 1; - tool.height = std::stod(values.count("height") ? values["height"] : "0"); - tool.dirty = true; - fouling_update(); - WriteLog("SELFTEST fouling: " + tool.status); - int shown{0}; - for (auto const &found : tool.found) - if (found.wrong && shown++ < 5) - WriteLog(format("SELFTEST fouling: %s %.2f m off, %+.3f m high", found.marker.file.c_str(), found.error, found.height_error)); - if (values["place"] == "one") - { - // the first fouling point without a marker alone, as a click on it in the view - for (int index = 0; index < static_cast(tool.points.size()); ++index) - if (tool.points[index].standing < 0) - { - fouling_place(index); - break; - } - tool.dirty = true; - fouling_update(); - WriteLog("SELFTEST fouling one: " + tool.status); - } - if (values["place"] == "1") - { - fouling_place(); - tool.dirty = true; - fouling_update(); - WriteLog("SELFTEST fouling after placing: " + tool.status); - } - if (values["fix"] == "1") - { - fouling_fix(); - tool.dirty = true; - fouling_update(); - WriteLog("SELFTEST fouling after the fix: " + tool.status); - } - return selftest_result::handled; - } - if (command == "hekto") - { - std::map values; - std::string pair; - while (words >> pair) - { - auto const equals{pair.find('=')}; - if (equals != std::string::npos) - values[pair.substr(0, equals)] = pair.substr(equals + 1); - } - auto *track{simulation::Paths.find(values["track"])}; - if (track == nullptr) - { - WriteLog("SELFTEST hekto: no such track"); - return selftest_result::handled; - } - hekto_start(*track, track->m_paths.front().points[segment_data::point::start]); - auto &tool{m_hekto}; - tool.dirty = true; - hekto_update(); - if (values.count("km")) - { - tool.km = std::stod(values["km"]); - tool.growth = values["growth"] == "-1" ? -1 : 1; - } - tool.side = values["side"] == "alt" ? 0 : values["side"] == "left" ? -1 : 1; - tool.dirty = true; - hekto_update(); - auto const missing{std::count_if(tool.posts.begin(), tool.posts.end(), [](hekto_post const &Post) { return Post.standing < 0; })}; - WriteLog(format("SELFTEST hekto: km %.3f growth %d, %zu posts along %.0f m, %d to place, %zu standing. %s", tool.km, tool.growth, tool.posts.size(), tool.samples.empty() ? 0.0 : tool.samples.back().chainage, static_cast(missing), tool.found.size(), tool.status.c_str())); - for (std::size_t i = 0; i < tool.posts.size() && i < 4; ++i) - { - auto const &post{tool.posts[i]}; - double lateral; - glm::dvec3 const axis{editor_track::sampled_position(tool.samples, post.chainage)}; - auto const direction{editor_track::sampled_position(tool.samples, post.chainage + 1.0) - axis}; - glm::dvec2 const offset{post.position.x - axis.x, post.position.z - axis.z}; - lateral = glm::length(offset); - auto const right{(direction.x * offset.y - direction.z * offset.x) * tool.growth >= 0.0}; - WriteLog(format("SELFTEST hekto post %d.%d at %.1f m, %.2f m from the axis, %s of the growing kilometrage, at %.1f %.1f %.1f", post.hectometres / 10, post.hectometres % 10, post.chainage, lateral, right ? "right" : "left", post.position.x, post.position.y, - post.position.z)); - } - if (values["place"] == "1") - { - hekto_place(); - WriteLog("SELFTEST hekto: " + tool.status); - } - return selftest_result::handled; - } if (command == "spread") { std::string name; @@ -388,7 +284,7 @@ editor_mode::selftest_result editor_mode::selftest_sweep(std::string const &comm std::string tab; words >> tab; show_track_tab(tab == "parallel" ? track_tab::parallel : track_tab::lineside); - (tab == "vehicle" ? m_vehicle.tab().expand : tab == "hekto" ? m_hekto.expand : tab == "fouling" ? m_fouling.expand : m_parallel.expand) = true; + (tab == "vehicle" ? m_vehicle.tab().expand : tab == "hekto" ? m_hekto.tab().expand : tab == "fouling" ? m_fouling.tab().expand : m_parallel.expand) = true; return selftest_result::handled; } if (command == "bendpanel") diff --git a/application/editormodeselftesttrack.cpp b/application/editormodeselftesttrack.cpp index dad7a6a5..7c1c5eac 100644 --- a/application/editormodeselftesttrack.cpp +++ b/application/editormodeselftesttrack.cpp @@ -139,8 +139,8 @@ editor_mode::selftest_result editor_mode::selftest_track(std::string const &comm words >> tab; show_track_tab(track_tab::lineside); m_sweep.expand = tab == 0; - m_hekto.expand = tab == 1; - m_fouling.expand = tab == 2; + m_hekto.tab().expand = tab == 1; + m_fouling.tab().expand = tab == 2; m_parallel.expand = tab == 3; m_vehicle.tab().expand = tab == 4; return selftest_result::handled; diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 4866c4fd..c1a4fcf3 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -556,8 +556,8 @@ void editor_mode::render_track_inspector() if (false == m_track_window_open || m_track_tab != track_tab::lineside) { m_sweep.open = false; - m_hekto.open = false; - m_fouling.open = false; + m_hekto.tab().open = false; + m_fouling.tab().open = false; m_parallel.open = false; m_vehicle.tab().open = false; } diff --git a/application/editormodetrackux.cpp b/application/editormodetrackux.cpp index cf67bba7..21629482 100644 --- a/application/editormodetrackux.cpp +++ b/application/editormodetrackux.cpp @@ -445,12 +445,12 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods) return make(kind::switch_at, "Press and drag: a switch here, along sets its direction, sideways its side"); } } - if (m_track_tab == track_tab::lineside && m_fouling.open) - if (auto const hit{fouling_hit()}; hit.has_value()) + if (m_track_tab == track_tab::lineside && m_fouling.tab().open) + if (auto const hit{m_fouling.hit()}; hit.has_value()) { intent.index = hit->first; intent.path = hit->second ? 1 : 0; - intent.position = hit->second ? m_fouling.found[hit->first].marker.location : m_fouling.points[hit->first].position; + intent.position = m_fouling.position_of(*hit); return make(kind::fouling, hit->second ? "Click: move this W17 to its place" : "Click: put the W17 here"); } if (m_hover.track != nullptr) @@ -570,9 +570,9 @@ void editor_mode::track_press(track_intent const &Intent) case kind::switch_at: start_switch_placement(); return; case kind::fouling: if (Intent.path == 1) - fouling_fix(Intent.index); + m_fouling.fix(Intent.index); else - fouling_place(Intent.index); + m_fouling.place(Intent.index); return; case kind::extend_end: if (false == start_extend(Intent.snap)) @@ -798,14 +798,12 @@ void editor_mode::render_track_context() if (ImGui::MenuItem(STR_C("Hectometre posts from here"))) { show_track_tab(track_tab::lineside); - hekto_start(*track, point); + m_hekto.start(*track, point); } if (ImGui::MenuItem(STR_C("Fouling point markers"), nullptr, false, track->eType == tt_Switch)) { show_track_tab(track_tab::lineside); - m_fouling.scope = 0; - m_fouling.dirty = true; - m_fouling.expand = true; + m_fouling.check_selected_switch(); } if (ImGui::MenuItem(STR_C("Vertical profile of the line"), "P")) { @@ -1477,8 +1475,8 @@ void editor_mode::render_lineside_ui() std::function render; }; std::array const tabs{{ - {"Hectometre posts", &m_hekto.open, &m_hekto.expand, [this]() { render_hekto_ui(); }}, - {"Fouling points", &m_fouling.open, &m_fouling.expand, [this]() { render_fouling_ui(); }}, + {"Hectometre posts", &m_hekto.tab().open, &m_hekto.tab().expand, [this]() { m_hekto.render_options(); }}, + {"Fouling points", &m_fouling.tab().open, &m_fouling.tab().expand, [this]() { m_fouling.render_options(); }}, {"Trainset", &m_vehicle.tab().open, &m_vehicle.tab().expand, [this]() { m_vehicle.render_options(); }}, }}; if (selected_track() == nullptr) diff --git a/application/editortool.h b/application/editortool.h index c4bbaa43..791d7424 100644 --- a/application/editortool.h +++ b/application/editortool.h @@ -10,6 +10,9 @@ http://mozilla.org/MPL/2.0/. #pragma once #include +#include +#include +#include #include #include #include @@ -18,13 +21,10 @@ http://mozilla.org/MPL/2.0/. #include #include "editor/editorTrack.hpp" +#include "utilities/Classes.h" class TTrack; class editor_nmt; -namespace scene -{ -class basic_node; -} enum class editor_selftest_result { @@ -36,6 +36,8 @@ enum class editor_selftest_result enum class editor_first_step { tracks, lay, laid, wheel, join, turnout, undo, save, drive, look, top, select, shape, profile, profile_point, profile_apply, terrain, nmt, plan, plan_band, plan_apply, signal, posts, count }; +enum class editor_track_tab { straights, route, path, turnout, profile, speed, joints, infra, lay, lineside, turntable, signals, parallel }; + struct track_hover { TTrack *track{nullptr}; @@ -50,6 +52,27 @@ struct tool_tab bool expand{false}; }; +// scenery templates standing by the track, placed and checked by the editor +struct template_item +{ + std::string file; + glm::dvec3 location{0.0}; // world space + double yaw{0.0}; + std::vector rest; // parameters after the rotation + bool described{false}; + std::string track; + glm::dvec2 tilt{0.0}; // rot.x and rot.z of a described template, degrees + std::map values; +}; +struct standing_template +{ + scene::instance_handle instance{0}; + std::string file; // lower case, forward slashes + glm::dvec3 location{0.0}; + double yaw{0.0}; + std::vector values; +}; + struct nmt_place { glm::dvec3 position{0.0}; // on the axis of the path @@ -70,6 +93,8 @@ class editor_services virtual glm::dvec3 camera_position() const = 0; virtual track_hover const &hovered_track() const = 0; virtual void first_step_done(editor_first_step const Step) = 0; + virtual void show_track_tab(editor_track_tab const Wanted) = 0; + virtual glm::dvec3 placement_on_ground(glm::dvec3 Location) = 0; virtual void fly_to(glm::dvec3 const &Target, glm::dvec3 const &Look, double const Distance, double const Duration = 0.6) = 0; virtual void focus_track(TTrack &Track, int const Path, glm::dvec3 const &Point) = 0; virtual void set_current_node(scene::basic_node *Node) = 0; @@ -80,6 +105,10 @@ class editor_services virtual editor_nmt &terrain_model() = 0; virtual double terrain_model_offset() const = 0; virtual bool terrain_model_fitted() const = 0; + virtual std::vector standing_templates(std::function const &Accept) const = 0; + virtual std::size_t place_templates(std::vector const &Items, std::string &Error) = 0; + virtual std::size_t move_templates(std::vector> const &Moves) = 0; + virtual std::size_t remove_templates(std::vector const &Instances) = 0; virtual void push_track_snapshot(std::vector> States, std::vector Created = {}, std::vector Removed = {}) = 0; protected: