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: