From 27ea2a74564e5231968b067309b6509fc58f6d90 Mon Sep 17 00:00:00 2001 From: maj00r Date: Wed, 21 Oct 2026 17:41:51 +0200 Subject: [PATCH] feat(editor): the joints of the tracks just changed are checked at once and their faults marked in the view After a change of the tracks, once the mouse is let go, the joints check looks at the joints of the tracks changed and of the tracks they join, with the limits of the Joints panel: ends almost meeting, steps, kinks, jumps of the cant and of the grade get a marker with the value in the view while the panel is closed, and go away once a later change or an undo puts them right. The check of a track is shared with the panel (joints_check takes the list and the pairs seen), the pairs already looked at are kept by the tool instead of a global. Self-test command jointslive. --- application/editormodeinfra.cpp | 1 + application/editormodejoints.cpp | 88 ++++++++++++++++++++++++++------ application/editormodejoints.h | 17 +++++- 3 files changed, 88 insertions(+), 18 deletions(-) diff --git a/application/editormodeinfra.cpp b/application/editormodeinfra.cpp index 4e3dc7da..1fef9e8c 100644 --- a/application/editormodeinfra.cpp +++ b/application/editormodeinfra.cpp @@ -1195,6 +1195,7 @@ void editor_mode::track_captured(TTrack const &Track) void editor_mode::tracks_committed(std::vector const &Tracks) { nmt_track_changed(Tracks); + m_joints.tracks_committed(Tracks); sweeps_committed(Tracks); switch_drives_committed(Tracks); if (m_infra_suspended || false == m_infra.follow) diff --git a/application/editormodejoints.cpp b/application/editormodejoints.cpp index 6b135db1..5b46320e 100644 --- a/application/editormodejoints.cpp +++ b/application/editormodejoints.cpp @@ -85,16 +85,11 @@ double plan_angle(glm::dvec3 const &A, glm::dvec3 const &B) return glm::degrees(std::acos(std::clamp(glm::dot(glm::normalize(a), glm::normalize(b)), -1.0, 1.0))); } -using end_key = std::tuple; - -end_key key_of(TTrack const *Track, editor_track::point_ref const &Point) +std::tuple key_of(TTrack const *Track, editor_track::point_ref const &Point) { return {Track, Point.path, static_cast(Point.kind)}; } -// pairs of the ends already looked at in the check under way -std::set> reported; - char const *kind_name(kind const Kind) { switch (Kind) @@ -152,7 +147,7 @@ void joints_tool::joints_start() state.issues.clear(); state.selected = -1; state.status.clear(); - reported.clear(); + m_reported.clear(); glm::dvec3 const camera{Global.pCamera.Pos}; for (auto *track : simulation::Paths.sequence()) { @@ -273,10 +268,10 @@ bool joints_tool::joints_model() return true; } -void joints_tool::joints_check(TTrack &Track) +void joints_tool::joints_check(TTrack &Track, std::vector &Issues, std::set> &Seen, bool const Open) const { - auto &state{m_joints}; - auto const once = [&](end_key const &A, end_key const &B) { return reported.insert(A < B ? std::make_pair(A, B) : std::make_pair(B, A)).second; }; + auto const &state{m_joints}; + auto const once = [&](end_key const &A, end_key const &B) { return Seen.insert(A < B ? std::make_pair(A, B) : std::make_pair(B, A)).second; }; for (int i = 0; i < static_cast(Track.m_paths.size()); ++i) { for (auto const kind_of_end : {editor_track::point_kind::start, editor_track::point_kind::end}) @@ -314,17 +309,17 @@ void joints_tool::joints_check(TTrack &Track) { nearest.type = kind::gap; nearest.position = position; - state.issues.push_back(nearest); + Issues.push_back(nearest); } } - else if (state.free_ends) + else if (Open) { joint_issue open; open.type = kind::open; open.track = &Track; open.point = point; open.position = position; - state.issues.push_back(open); + Issues.push_back(open); } continue; } @@ -349,7 +344,7 @@ void joints_tool::joints_check(TTrack &Track) auto const add = [&](kind const Kind, double const Value) { issue.type = Kind; issue.value = Value; - state.issues.push_back(issue); + Issues.push_back(issue); }; if (step > state.step) add(kind::step, step); @@ -364,9 +359,44 @@ void joints_tool::joints_check(TTrack &Track) } } +void joints_tool::tracks_committed(std::vector const &Tracks) +{ + for (auto *track : Tracks) + if (track != nullptr) + m_live.pending.insert(track); +} + +void joints_tool::live_update() +{ + auto &live{m_live}; + auto const gone = [](TTrack const *Track) { return Track != nullptr && Track->m_editorremoved; }; + std::erase_if(live.issues, [&](joint_issue const &Issue) { return gone(Issue.track) || gone(Issue.other); }); + if (live.pending.empty() || ImGui::IsMouseDown(ImGuiMouseButton_Left) || ImGui::IsMouseDown(ImGuiMouseButton_Right)) + return; + // the joints of a track changed are looked at again, from its side and from the side of the tracks it joins + std::set checked; + for (auto *track : live.pending) + { + if (track->m_editorremoved || false == rail(*track)) + continue; + checked.insert(track); + for (auto const &path : track->m_paths) + for (auto const index : {segment_data::point::start, segment_data::point::end}) + for (auto const &connection : editor_track::connected_points(*track, path.points[index])) + if (rail(*connection.first)) + checked.insert(connection.first); + } + live.pending.clear(); + std::erase_if(live.issues, [&](joint_issue const &Issue) { return checked.contains(Issue.track) || checked.contains(Issue.other); }); + std::set> seen; + for (auto *track : checked) + joints_check(*track, live.issues, seen, false); +} + void joints_tool::update() { auto &state{m_joints}; + live_update(); if (state.next >= state.queue.size() && false == state.model_waiting) return; auto const deadline{std::chrono::steady_clock::now() + std::chrono::milliseconds(15)}; @@ -375,7 +405,7 @@ void joints_tool::update() auto *track{state.queue[state.next++]}; if (track->m_editorremoved) continue; - joints_check(*track); + joints_check(*track, state.issues, m_reported, state.free_ends); } if (state.next < state.queue.size()) return; @@ -649,11 +679,26 @@ void joints_tool::render_options() void joints_tool::draw_overlay() const { auto const &state{m_joints}; - if (false == state.open || state.issues.empty()) - return; screen_projection const projection; auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())}; glm::dvec3 const camera{Global.pCamera.Pos}; + if (false == state.open) + { + for (auto const &issue : m_live.issues) + { + ImVec2 screen; + if ((false == Global.EditorOrtho && glm::distance(issue.position, camera) > kOverlayRange) || false == projection.project(issue.position, screen)) + continue; + auto const colour{kind_colour(issue.type)}; + drawlist->AddCircle(screen, 9.0f, colour, 16, 2.0f); + auto const text{std::string{STR_C(kind_name(issue.type))} + " " + value_text(issue)}; + drawlist->AddRectFilled(ImVec2(screen.x + 11.0f, screen.y - 9.0f), ImVec2(screen.x + 17.0f + ImGui::CalcTextSize(text.c_str()).x, screen.y + 9.0f), IM_COL32(0, 0, 0, 150), 3.0f); + drawlist->AddText(ImVec2(screen.x + 14.0f, screen.y - 7.0f), colour, text.c_str()); + } + return; + } + if (state.issues.empty()) + return; for (int i = 0; i < static_cast(state.issues.size()); ++i) { auto const &issue{state.issues[i]}; @@ -696,6 +741,15 @@ editor_selftest_result joints_tool::selftest(std::string const &Command, std::is WriteLog("SELFTEST jointsmodel: " + m_joints.status); return editor_selftest_result::handled; } + if (Command == "jointslive") + { + live_update(); + std::string text; + for (auto const &issue : m_live.issues) + text += format(" [%s %s at %.1f %.1f]", kind_name(issue.type), value_text(issue).c_str(), issue.position.x, issue.position.z); + WriteLog(format("SELFTEST jointslive: %zu pending, %zu marked", m_live.pending.size(), m_live.issues.size()) + text); + return editor_selftest_result::handled; + } if (Command == "joints") { std::vector points; diff --git a/application/editormodejoints.h b/application/editormodejoints.h index 626846e7..01617d88 100644 --- a/application/editormodejoints.h +++ b/application/editormodejoints.h @@ -11,7 +11,9 @@ http://mozilla.org/MPL/2.0/. #include "application/editortool.h" +#include #include +#include #include #include @@ -37,6 +39,8 @@ class joints_tool final : public editor_tool void render_options() override; editor_selftest_result selftest(std::string const &Command, std::istringstream &Words) override; void set_open(bool const Open) { m_joints.open = Open; } + // the tracks just changed, checked at their joints once the change is over + void tracks_committed(std::vector const &Tracks); double kink_angle() const { return m_joints.angle; } private: @@ -75,8 +79,19 @@ class joints_tool final : public editor_tool std::string status; }; joints_state m_joints; + using end_key = std::tuple; + // pairs of the ends already looked at in the check under way + std::set> m_reported; + // the joints of the tracks changed last, marked while the panel is closed + struct live_state + { + std::set pending; + std::vector issues; + }; + live_state m_live; + void live_update(); void joints_start(); - void joints_check(TTrack &Track); + void joints_check(TTrack &Track, std::vector &Issues, std::set> &Seen, bool const Open) const; // the paths lying off the terrain model by more than the threshold. returns: false while the model under them is coming bool joints_model(); void joints_focus(int const Index);