From b986cc899005607654872f27e09f9b2863be244a Mon Sep 17 00:00:00 2001 From: maj00r Date: Fri, 2 Oct 2026 22:59:55 +0200 Subject: [PATCH] feat(editor): move straights keeping parallel --- application/editormode.h | 4 ++ application/editormodetrack.cpp | 111 ++++++++++++++++++++++++++------ editor/editorTrack.cpp | 28 ++++++-- editor/editorTrack.hpp | 6 +- 4 files changed, 123 insertions(+), 26 deletions(-) diff --git a/application/editormode.h b/application/editormode.h index 42eb590f..8f7107a1 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -334,6 +334,8 @@ class editor_mode : public application_mode int handle{-1}; bool dragging{false}; editor_track::straight drag_line; + std::vector set; + std::vector drag_lines; std::vector> drag_states; glm::dvec3 preview_start{0.0}; glm::dvec3 preview_end{0.0}; @@ -352,6 +354,8 @@ class editor_mode : public application_mode bool pick_straight_handle(); void render_straight_gizmo(); void straight_apply(editor_track::straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End); + void straights_apply(std::vector const &Lines, std::vector> const &Ends); + bool in_straight_set(editor_track::straight const &Line) const; void straight_refresh(); straights_state m_straights; enum class track_tab { straights, route, path }; diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index f02ca018..41804b44 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -24,6 +24,7 @@ http: #include #include #include +#include namespace { @@ -1158,10 +1159,19 @@ void editor_mode::draw_straights_overlay() const if (nearby(line)) draw(line, i == m_straights.listed ? IM_COL32(40, 220, 255, 255) : IM_COL32(255, 255, 255, 110), i == m_straights.listed ? 4.0f : 2.0f); } + for (auto const &member : m_straights.set) + draw(member, IM_COL32(255, 150, 30, 255), 4.0f); draw(m_straights.current, IM_COL32(40, 220, 255, 255), 4.0f); if (m_straights.dragging) { - projection.line(drawlist, m_straights.preview_start, m_straights.preview_end, IM_COL32(255, 210, 60, 255), 3.0f); + for (auto const &member : m_straights.drag_lines) + { + for (auto const *track : member.tracks) + { + auto const &path{track->m_paths.front()}; + projection.line(drawlist, path.points[segment_data::point::start], path.points[segment_data::point::end], IM_COL32(255, 210, 60, 255), 3.0f); + } + } } else if (false == m_straights.current.tracks.empty()) { @@ -1262,6 +1272,16 @@ void editor_mode::render_straights_ui() ImGui::SameLine(); if (ImGui::Button("Set as B")) state.b = line; + ImGui::SameLine(); + if (in_straight_set(line)) + { + if (ImGui::Button("Remove from the set")) + state.set.erase(std::remove_if(state.set.begin(), state.set.end(), [&](editor_track::straight const &Member) { return std::any_of(line.tracks.begin(), line.tracks.end(), [&](TTrack const *Track) { return std::find(Member.tracks.begin(), Member.tracks.end(), Track) != Member.tracks.end(); }); }), state.set.end()); + } + else if (ImGui::Button("Add to the set")) + { + state.set.push_back(line); + } if (false == ImGui::IsAnyItemActive()) { @@ -1298,6 +1318,14 @@ void editor_mode::render_straights_ui() if (false == state.status.empty()) ImGui::TextColored(ImVec4(1.0f, 0.35f, 0.3f, 1.0f), "%s", state.status.c_str()); + if (false == state.set.empty()) + { + ImGui::Text("Set: %zu straights, dragging any of them moves the whole set, keeping it parallel", state.set.size()); + ImGui::SameLine(); + if (ImGui::SmallButton("Clear the set")) + state.set.clear(); + } + ImGui::Separator(); auto const &a{state.a}; auto const &b{state.b}; @@ -1388,13 +1416,30 @@ void editor_mode::straight_refresh() for (auto &line : state.found) if (false == line.tracks.empty()) line = editor_track::find_straight(*line.tracks.front(), state.tolerance); + for (auto &line : state.set) + if (false == line.tracks.empty()) + line = editor_track::find_straight(*line.tracks.front(), state.tolerance); +} + +bool editor_mode::in_straight_set(editor_track::straight const &Line) const +{ + for (auto const &member : m_straights.set) + for (auto const *track : Line.tracks) + if (std::find(member.tracks.begin(), member.tracks.end(), track) != member.tracks.end()) + return true; + return false; } void editor_mode::straight_apply(editor_track::straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End) +{ + straights_apply({Line}, {{Start, End}}); +} + +void editor_mode::straights_apply(std::vector const &Lines, std::vector> const &Ends) { auto &state{m_straights}; state.status.clear(); - auto const tracks{editor_track::straight_affected(Line)}; + auto const tracks{editor_track::straight_affected(Lines)}; for (auto *track : tracks) { if (false == track->Dynamics.empty()) @@ -1407,7 +1452,7 @@ void editor_mode::straight_apply(editor_track::straight const &Line, glm::dvec3 for (auto *track : tracks) states.emplace_back(track, editor_track::capture(*track)); push_track_snapshot(std::move(states)); - editor_track::move_straight(Line, Start, End); + editor_track::move_straights(Lines, Ends); editor_track::commit(tracks); straight_refresh(); } @@ -1443,39 +1488,68 @@ void editor_mode::render_straight_gizmo() { if (false == state.dragging) { - auto const tracks{editor_track::straight_affected(line)}; + std::vector lines; + if (in_straight_set(line)) + { + for (auto const &member : state.set) + if (false == member.tracks.empty()) + lines.push_back(editor_track::find_straight(*member.tracks.front(), state.tolerance)); + } + else + { + lines.push_back(line); + } + auto const tracks{editor_track::straight_affected(lines)}; if (std::any_of(tracks.begin(), tracks.end(), [](TTrack const *Track) { return false == Track->Dynamics.empty(); })) { - state.status = "There are vehicles placed on the straight or the paths attached to it"; + state.status = "There are vehicles placed on the straights or the paths attached to them"; return; } state.dragging = true; state.drag_line = line; + state.drag_lines = lines; state.drag_states.clear(); for (auto *track : tracks) state.drag_states.emplace_back(track, editor_track::capture(*track)); push_track_snapshot(state.drag_states); m_track_drag = tracks; } - auto const &original{state.drag_line}; + auto const &grabbed{state.drag_line}; glm::dvec3 const moved{camerapos + glm::dvec3(state.gizmo[3])}; - auto start{original.start}; - auto end{original.end}; - if (state.handle == 0) - start = moved; - else if (state.handle == 1) - end = moved; + std::function transform; + if (state.handle == 2) + { + auto const offset{moved - (grabbed.start + grabbed.end) * 0.5}; + transform = [offset](glm::dvec3 const &Point) { return Point + offset; }; + } else { - auto const offset{moved - (original.start + original.end) * 0.5}; - start += offset; - end += offset; + auto const pivot{state.handle == 0 ? grabbed.end : grabbed.start}; + auto const original{state.handle == 0 ? grabbed.start : grabbed.end}; + glm::dvec2 const before{original.x - pivot.x, original.z - pivot.z}; + glm::dvec2 const after{moved.x - pivot.x, moved.z - pivot.z}; + auto const lengthbefore{glm::length(before)}; + auto const lengthafter{std::max(0.1, glm::length(after))}; + auto const axis{before / std::max(1e-9, lengthbefore)}; + auto const newaxis{after / lengthafter}; + auto const scale{lengthafter / std::max(1e-9, lengthbefore)}; + auto const rise{moved.y - original.y}; + transform = [=](glm::dvec3 const &Point) { + glm::dvec2 const offset{Point.x - pivot.x, Point.z - pivot.z}; + auto const along{glm::dot(offset, axis)}; + auto const across{axis.x * offset.y - axis.y * offset.x}; + auto const planar{glm::dvec2{pivot.x, pivot.z} + newaxis * (along * scale) + glm::dvec2{-newaxis.y, newaxis.x} * across}; + return glm::dvec3{planar.x, Point.y + rise * along / std::max(1e-9, lengthbefore), planar.y}; + }; } - state.preview_start = start; - state.preview_end = end; + std::vector> ends; + for (auto const &member : state.drag_lines) + ends.emplace_back(transform(member.start), transform(member.end)); + state.preview_start = transform(grabbed.start); + state.preview_end = transform(grabbed.end); for (auto const &entry : state.drag_states) editor_track::apply(*entry.first, entry.second); - editor_track::move_straight(original, start, end); + editor_track::move_straights(state.drag_lines, ends); m_track_dirty = true; commit_track_drag(false); } @@ -1485,6 +1559,7 @@ void editor_mode::render_straight_gizmo() state.dragging = false; m_track_drag.clear(); state.drag_states.clear(); + state.drag_lines.clear(); straight_refresh(); } } diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index 81ed916f..e8d7edde 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -22,6 +22,7 @@ http: #include #include #include +#include #include #include @@ -970,11 +971,15 @@ std::vector editor_track::find_straights(double const Mi return result; } -std::vector editor_track::straight_affected(straight const &Line) +std::vector editor_track::straight_affected(std::vector const &Lines) { - std::vector result(Line.tracks.begin(), Line.tracks.end()); - auto const member = [&](TTrack const *Track) { return std::find(Line.tracks.begin(), Line.tracks.end(), Track) != Line.tracks.end(); }; - for (auto *track : Line.tracks) + std::vector result; + for (auto const &line : Lines) + for (auto *track : line.tracks) + add_unique(result, track); + auto const members{result}; + auto const member = [&](TTrack const *Track) { return std::find(members.begin(), members.end(), Track) != members.end(); }; + for (auto *track : members) { for (int i = 0; i < static_cast(track->m_paths.size()); ++i) { @@ -995,7 +1000,18 @@ std::vector editor_track::straight_affected(straight const &Line) return result; } -void editor_track::move_straight(straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End) +void editor_track::move_straights(std::vector const &Lines, std::vector> const &Ends) +{ + std::vector members; + for (auto const &line : Lines) + for (auto *track : line.tracks) + add_unique(members, track); + auto const member = [&](TTrack const *Track) { return std::find(members.begin(), members.end(), Track) != members.end(); }; + for (std::size_t i = 0; i < Lines.size() && i < Ends.size(); ++i) + move_straight(Lines[i], Ends[i].first, Ends[i].second, member); +} + +void editor_track::move_straight(straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End, std::function const &Member) { if (Line.tracks.empty() || Line.length < 1e-6) return; @@ -1053,7 +1069,7 @@ void editor_track::move_straight(straight const &Line, glm::dvec3 const &Start, return glm::dvec3{moved.x, Point.y - oldheight + newheight, moved.y}; }; - auto const member = [&](TTrack const *Track) { return std::find(Line.tracks.begin(), Line.tracks.end(), Track) != Line.tracks.end(); }; + auto const &member{Member}; std::vector> joints; for (std::size_t i = 0; i < Line.tracks.size(); ++i) { diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index d58e7f36..58a6c2f1 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -9,6 +9,7 @@ http: #pragma once +#include #include #include #include @@ -115,8 +116,8 @@ class editor_track static bool is_straight(TTrack const &Track, straight_tolerance const &Tolerance); static straight find_straight(TTrack &Track, straight_tolerance const &Tolerance); static std::vector find_straights(double const Minimumlength, straight_tolerance const &Tolerance); - static std::vector straight_affected(straight const &Line); - static void move_straight(straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End); + static std::vector straight_affected(std::vector const &Lines); + static void move_straights(std::vector const &Lines, std::vector> const &Ends); static std::vector relay(chain const &Chain, std::vector const &Pieces); static void retire(TTrack &Track); static void revive(TTrack &Track); @@ -139,4 +140,5 @@ class editor_track static void store_switch_path(TTrack &Switch, int const Path); static void rebuild_geometry(TTrack &Track); static TTrack *clone(TTrack const &Template); + static void move_straight(straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End, std::function const &Member); };