diff --git a/application/editormode.cpp b/application/editormode.cpp index 6f54100f..db791846 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -675,23 +675,19 @@ void editor_mode::start_focus(scene::basic_node *node, double duration) double const len = glm::length(dir); dir = len > 1e-3 ? dir / len : glm::normalize(glm::dvec3(1.0, 0.5, 1.0)); + // looking from there straight at the object + fly_to(center, -dir, distance, duration); +} + +void editor_mode::fly_to(glm::dvec3 const &Target, glm::dvec3 const &Look, double const Distance, double const Duration) +{ m_focus_start_pos = Camera.Pos; m_focus_start_angle = Camera.Angle; - m_focus_target_pos = center + dir * distance; - - // target orientation looks from the target position straight at the object - glm::dvec3 look = center - m_focus_target_pos; - double const looklen = glm::length(look); - if (looklen > 1e-6) - look /= looklen; - m_focus_target_angle = glm::vec3( - static_cast(std::asin(glm::clamp(look.y, -1.0, 1.0))), // pitch - static_cast(std::atan2(-look.x, -look.z)), // yaw - 0.0f); // roll - + m_focus_target_pos = Target - Look * Distance; + m_focus_target_angle = glm::vec3(static_cast(std::asin(std::clamp(Look.y, -1.0, 1.0))), static_cast(std::atan2(-Look.x, -Look.z)), 0.0f); m_focus_active = true; m_focus_time = 0.0; - m_focus_duration = duration; + m_focus_duration = Duration; } void editor_mode::snap_to_ground(scene::basic_node *node, bool const Alone, bool const Joined) diff --git a/application/editormode.h b/application/editormode.h index 4418d035..f696249d 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -1374,7 +1374,7 @@ class editor_mode : public application_mode, private editor_track::observer 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 }; + 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 }; kind what{kind::none}; TTrack *track{nullptr}; int path{0}; @@ -1447,6 +1447,8 @@ class editor_mode : public application_mode, private editor_track::observer void render_track_context(); void split_track_at(TTrack &Track, glm::dvec3 const &Point); void focus_track(TTrack &Track, int const Path, glm::dvec3 const &Point); + // the camera flies over Duration seconds to the place Distance back from the target along Look, looking along it + void fly_to(glm::dvec3 const &Target, glm::dvec3 const &Look, double const Distance, double const Duration = 0.6); // paths found by their names struct track_search { @@ -1737,7 +1739,7 @@ class editor_mode : public application_mode, private editor_track::observer { bool open{false}; bool expand{false}; - int scope{0}; // 0: the selected switch, 1: around the camera, 2: the whole scenery + 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 @@ -1756,8 +1758,13 @@ class editor_mode : public application_mode, private editor_track::observer }; fouling_tool m_fouling; void fouling_update(); - void fouling_place(); - void fouling_fix(); + // 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; diff --git a/application/editormodegauge.cpp b/application/editormodegauge.cpp index 298724e7..8ccdb39f 100644 --- a/application/editormodegauge.cpp +++ b/application/editormodegauge.cpp @@ -677,13 +677,7 @@ void editor_mode::gauge_focus(int const Index) look = glm::length(look) > 1e-6 ? glm::normalize(look) : glm::dvec3(0.0, 0.0, 1.0); if (glm::dot(Camera.Pos - hit.point, look) > 0.0) look = -look; - m_focus_start_pos = Camera.Pos; - m_focus_start_angle = Camera.Angle; - m_focus_target_pos = hit.point - look * gauge_view_distance; - m_focus_target_angle = glm::vec3(static_cast(std::asin(std::clamp(look.y, -1.0, 1.0))), static_cast(std::atan2(-look.x, -look.z)), 0.0f); - m_focus_active = true; - m_focus_time = 0.0; - m_focus_duration = 0.6; + fly_to(hit.point, look, gauge_view_distance); } void editor_mode::gauge_spot(gauge_hit const &Hit) diff --git a/application/editormodelineside.cpp b/application/editormodelineside.cpp index f28ff72a..500d5018 100644 --- a/application/editormodelineside.cpp +++ b/application/editormodelineside.cpp @@ -38,6 +38,7 @@ using geometry::plan_of; double const kSampleStep{2.0}; // m between the samples of the line double const kLineCorridor{15.0}; // m from the axis, posts farther away belong to another line double const kOverlayRange{3000.0}; +float const kFoulingPick{12.0f}; // px from a point of the fouling overlay a click takes it double const kFoulingTrace{250.0}; // m from the start of a switch the tracks are followed for double const kRailHeight{0.18}; // m from the points of a track to the heads of its rails int const kRailCategory{1}; @@ -666,14 +667,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())) { - glm::dvec3 const look{glm::normalize(glm::dvec3{-1.0, -0.8, -1.0})}; - m_focus_start_pos = Camera.Pos; - m_focus_start_angle = Camera.Angle; - m_focus_target_pos = post.post.location - look * 20.0; - m_focus_target_angle = glm::vec3(static_cast(std::asin(look.y)), static_cast(std::atan2(-look.x, -look.z)), 0.0f); - m_focus_active = true; - m_focus_time = 0.0; - m_focus_duration = 0.6; + fly_to(post.post.location, glm::normalize(glm::dvec3{-1.0, -0.8, -1.0}), 20.0); } } ImGui::EndChild(); @@ -832,14 +826,15 @@ 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() +void editor_mode::fouling_place(int const Only) { auto &tool{m_fouling}; fouling_update(); std::vector items; - for (auto const &point : tool.points) + for (int i = 0; i < static_cast(tool.points.size()); ++i) { - if (point.standing >= 0) + auto const &point{tool.points[i]}; + if (point.standing >= 0 || (Only >= 0 && i != Only)) continue; template_item item; item.file = kFoulingModels[std::clamp(tool.model, 0, 3)]; @@ -856,14 +851,15 @@ void editor_mode::fouling_place() tool.dirty = true; } -void editor_mode::fouling_fix() +void editor_mode::fouling_fix(int const Only) { auto &tool{m_fouling}; fouling_update(); std::vector> moves; - for (auto const &found : tool.found) + for (int i = 0; i < static_cast(tool.found.size()); ++i) { - if (false == found.wrong) + auto const &found{tool.found[i]}; + if (false == found.wrong || (Only >= 0 && i != Only)) continue; template_item item; auto const &point{tool.points[found.point]}; @@ -886,10 +882,37 @@ void editor_mode::fouling_fix() tool.dirty = true; } +std::optional> editor_mode::fouling_hit() const +{ + auto const &tool{m_fouling}; + screen_projection const projection; + auto const &mouse{ImGui::GetIO().MousePos}; + glm::dvec3 const camera{Global.pCamera.Pos}; + std::optional> result; + auto best{kFoulingPick * kFoulingPick}; + auto const consider = [&](glm::dvec3 const &Position, int const Index, bool const Marker) { + ImVec2 screen; + if (glm::distance(Position, camera) > kOverlayRange || false == projection.project(Position, screen)) + return; + auto const distance2{(screen.x - mouse.x) * (screen.x - mouse.x) + (screen.y - mouse.y) * (screen.y - mouse.y)}; + if (distance2 < best) + { + best = distance2; + result = std::make_pair(Index, Marker); + } + }; + for (int i = 0; i < static_cast(tool.points.size()); ++i) + if (tool.points[i].standing < 0) + consider(tool.points[i].position, i, false); + for (int i = 0; i < static_cast(tool.found.size()); ++i) + if (tool.found[i].wrong) + consider(tool.found[i].marker.location, i, true); + return result; +} + void editor_mode::render_fouling_ui() { auto &tool{m_fouling}; - ImGui::TextDisabled("%s", STR_C("W17 where the axes of the tracks meeting at a switch are the spacing apart,\nhalf way between them")); auto const changed = [&](bool const Changed) { if (Changed) tool.dirty = true; @@ -899,54 +922,92 @@ void editor_mode::render_fouling_ui() changed(ImGui::RadioButton(STR_C("Around the camera"), &tool.scope, 1)); ImGui::SameLine(); changed(ImGui::RadioButton(STR_C("Whole scenery"), &tool.scope, 2)); - ImGui::PushItemWidth(110.0f); - if (tool.scope == 1) - changed(ImGui::InputDouble(STR_C("Around the camera (m)"), &tool.reach, 0.0, 0.0, "%.0f")); - changed(ImGui::InputDouble(STR_C("Spacing of the axes (m)"), &tool.spacing, 0.0, 0.0, "%.2f")); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", STR_C("3.5 m on the straight track; more on the curves, where the cars lean out")); - changed(ImGui::InputDouble(STR_C("A marker off by more (m) is wrong"), &tool.tolerance, 0.0, 0.0, "%.1f")); - changed(ImGui::RadioButton(STR_C("On the ground"), &tool.height_mode, 0)); - ImGui::SameLine(); - changed(ImGui::RadioButton(STR_C("Over the rail heads"), &tool.height_mode, 1)); - if (tool.height_mode == 1) + fouling_update(); + if (false == tool.error.empty()) + ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.3f, 1.0f), "%s", tool.error.c_str()); + else { + ImGui::TextWrapped("%s", tool.status.c_str()); + auto const missing{std::count_if(tool.points.begin(), tool.points.end(), [](fouling_point const &Point) { return Point.standing < 0; })}; + auto const wrong{std::count_if(tool.found.begin(), tool.found.end(), [](fouling_found const &Found) { return Found.wrong; })}; + if (missing > 0 && ImGui::Button(format(STR_C("Place %d markers"), static_cast(missing)).c_str())) + fouling_place(); + if (wrong > 0) + { + if (missing > 0) + ImGui::SameLine(); + if (ImGui::Button(format(STR_C("Move %d wrong markers to their places"), static_cast(wrong)).c_str())) + fouling_fix(); + } + if (missing + wrong > 0) + { + // each one: a click flies there, the button fixes it alone; in the view a click on its point does the same + ImGui::TextDisabled("%s", STR_C("Click the green or red point in the view: that one only")); + ImGui::BeginChild("##foulinglist", ImVec2(0.0f, std::min(8, static_cast(missing + wrong)) * ImGui::GetFrameHeightWithSpacing() + 6.0f), true); + auto const look{glm::normalize(glm::dvec3{-1.0, -0.8, -1.0})}; + for (int i = 0; i < static_cast(tool.points.size()); ++i) + { + auto const &point{tool.points[i]}; + if (point.standing >= 0) + continue; + ImGui::PushID(i); + if (ImGui::SmallButton(STR_C("Place"))) + fouling_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); + ImGui::PopID(); + } + for (int i = 0; i < static_cast(tool.found.size()); ++i) + { + auto const &found{tool.found[i]}; + if (false == found.wrong) + continue; + ImGui::PushID(1000000 + i); + if (ImGui::SmallButton(STR_C("Fix"))) + fouling_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); + ImGui::PopID(); + } + ImGui::EndChild(); + } + } + if (ImGui::CollapsingHeader(STR_C("Settings##fouling"))) + { + ImGui::TextDisabled("%s", STR_C("W17 where the axes of the tracks meeting at a switch are the spacing apart,\nhalf way between them")); + ImGui::PushItemWidth(110.0f); + if (tool.scope == 1) + changed(ImGui::InputDouble(STR_C("Around the camera (m)"), &tool.reach, 0.0, 0.0, "%.0f")); + changed(ImGui::InputDouble(STR_C("Spacing of the axes (m)"), &tool.spacing, 0.0, 0.0, "%.2f")); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", STR_C("3.5 m on the straight track; more on the curves, where the cars lean out")); + changed(ImGui::InputDouble(STR_C("A marker off by more (m) is wrong"), &tool.tolerance, 0.0, 0.0, "%.1f")); + changed(ImGui::RadioButton(STR_C("On the ground"), &tool.height_mode, 0)); ImGui::SameLine(); - changed(ImGui::InputDouble(STR_C("by (m)"), &tool.height, 0.0, 0.0, "%.3f")); - changed(ImGui::InputDouble(STR_C("A marker higher or lower by more (m) is wrong"), &tool.height_tolerance, 0.0, 0.0, "%.3f")); - tool.height_tolerance = std::clamp(tool.height_tolerance, 0.005, 1.0); + changed(ImGui::RadioButton(STR_C("Over the rail heads"), &tool.height_mode, 1)); + if (tool.height_mode == 1) + { + ImGui::SameLine(); + changed(ImGui::InputDouble(STR_C("by (m)"), &tool.height, 0.0, 0.0, "%.3f")); + changed(ImGui::InputDouble(STR_C("A marker higher or lower by more (m) is wrong"), &tool.height_tolerance, 0.0, 0.0, "%.3f")); + tool.height_tolerance = std::clamp(tool.height_tolerance, 0.005, 1.0); + } + char const *models[] = {"w17", "w17_new", "w17_old", "w17_dwuteo"}; + ImGui::Combo(STR_C("Marker"), &tool.model, models, IM_ARRAYSIZE(models)); + char const *turns[] = {"0", "90", "180", "270"}; + int turn{tool.turn / 90}; + if (ImGui::Combo(STR_C("Turned by (deg)"), &turn, turns, IM_ARRAYSIZE(turns))) + { + tool.turn = turn * 90; + tool.dirty = true; + } + ImGui::PopItemWidth(); } - char const *models[] = {"w17", "w17_new", "w17_old", "w17_dwuteo"}; - ImGui::Combo(STR_C("Marker"), &tool.model, models, IM_ARRAYSIZE(models)); - char const *turns[] = {"0", "90", "180", "270"}; - int turn{tool.turn / 90}; - if (ImGui::Combo(STR_C("Turned by (deg)"), &turn, turns, IM_ARRAYSIZE(turns))) - { - tool.turn = turn * 90; - tool.dirty = true; - } - ImGui::PopItemWidth(); tool.reach = std::clamp(tool.reach, 50.0, 50000.0); tool.spacing = std::clamp(tool.spacing, 2.0, 10.0); tool.tolerance = std::clamp(tool.tolerance, 0.2, 20.0); - fouling_update(); - if (false == tool.error.empty()) - { - ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.3f, 1.0f), "%s", tool.error.c_str()); - return; - } - ImGui::TextWrapped("%s", tool.status.c_str()); - auto const missing{std::count_if(tool.points.begin(), tool.points.end(), [](fouling_point const &Point) { return Point.standing < 0; })}; - auto const wrong{std::count_if(tool.found.begin(), tool.found.end(), [](fouling_found const &Found) { return Found.wrong; })}; - if (missing > 0 && ImGui::Button(format(STR_C("Place %d markers"), static_cast(missing)).c_str())) - fouling_place(); - if (wrong > 0) - { - if (missing > 0) - ImGui::SameLine(); - if (ImGui::Button(format(STR_C("Move %d wrong markers to their places"), static_cast(wrong)).c_str())) - fouling_fix(); - } } void editor_mode::draw_fouling_overlay() const diff --git a/application/editormodeselftest.cpp b/application/editormodeselftest.cpp index f2b0af08..878ffbd0 100644 --- a/application/editormodeselftest.cpp +++ b/application/editormodeselftest.cpp @@ -783,6 +783,11 @@ editor_mode::selftest_result editor_mode::selftest_sweep(std::string const &comm 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"); @@ -793,6 +798,19 @@ editor_mode::selftest_result editor_mode::selftest_sweep(std::string const &comm 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(); diff --git a/application/editormodetrackux.cpp b/application/editormodetrackux.cpp index 1bd84371..5257e097 100644 --- a/application/editormodetrackux.cpp +++ b/application/editormodetrackux.cpp @@ -437,6 +437,14 @@ 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()) + { + 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; + return make(kind::fouling, hit->second ? "Click: move this W17 to its place" : "Click: put the W17 here"); + } if (m_hover.track != nullptr) { intent.track = m_hover.track; @@ -552,6 +560,12 @@ void editor_mode::track_press(track_intent const &Intent) handle_drag_start(Intent.position); return; case kind::switch_at: start_switch_placement(); return; + case kind::fouling: + if (Intent.path == 1) + fouling_fix(Intent.index); + else + fouling_place(Intent.index); + return; case kind::extend_end: if (false == start_extend(Intent.snap)) point_drag_start(Intent.snap.point); @@ -700,13 +714,7 @@ void editor_mode::focus_track(TTrack &Track, int const Path, glm::dvec3 const &P if (glm::dot(Camera.Pos - Point, look) > 0.0) look = -look; look = glm::normalize(look - glm::dvec3(0.0, 0.25, 0.0)); - m_focus_start_pos = Camera.Pos; - m_focus_start_angle = Camera.Angle; - m_focus_target_pos = Point - look * kFocusDistance; - m_focus_target_angle = glm::vec3(static_cast(std::asin(std::clamp(look.y, -1.0, 1.0))), static_cast(std::atan2(-look.x, -look.z)), 0.0f); - m_focus_active = true; - m_focus_time = 0.0; - m_focus_duration = 0.6; + fly_to(Point, look, kFocusDistance); } void editor_mode::render_track_context() diff --git a/lang/editor_pl.po b/lang/editor_pl.po index c9e8eea5..13de5644 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -7034,3 +7034,21 @@ msgstr "Wciśnij na nim i przeciągnij: wzdłuż kierunek, w bok strona" msgid "Connection: drag from a straight onto the other track" msgstr "Połączenie: przeciągnij z prostej na drugi tor" + +msgid "Click the green or red point in the view: that one only" +msgstr "Kliknij zielony albo czerwony punkt w widoku: tylko ten" + +msgid "%s: no W17" +msgstr "%s: brak W17" + +msgid "%s: W17 %.1f m off, %+.2f m high" +msgstr "%s: W17 %.1f m od miejsca, %+.2f m w pionie" + +msgid "Settings##fouling" +msgstr "Ustawienia##fouling" + +msgid "Click: move this W17 to its place" +msgstr "Klik: przesuń ten W17 na miejsce" + +msgid "Click: put the W17 here" +msgstr "Klik: postaw tu W17"