From 9198840d0f6d5059e656670ed802274c18459bf3 Mon Sep 17 00:00:00 2001 From: maj00r Date: Fri, 2 Oct 2026 22:52:25 +0200 Subject: [PATCH] feat(editor): straights include switches, drag to edit --- application/editormode.cpp | 9 +- application/editormode.h | 18 ++ application/editormodetrack.cpp | 216 +++++++++++++++++++++ editor/editorTrack.cpp | 326 +++++++++++++++++++++++++++----- editor/editorTrack.hpp | 3 + 5 files changed, 528 insertions(+), 44 deletions(-) diff --git a/application/editormode.cpp b/application/editormode.cpp index 4e7fb1c2..624285c3 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -2252,6 +2252,12 @@ void editor_mode::render_gizmo() return; } + if (straights_active()) + { + render_straight_gizmo(); + return; + } + if (route_active()) { render_route_gizmo(); @@ -2738,6 +2744,7 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in { m_track_point = {}; m_route.vertex = -1; + m_straights.handle = -1; } break; @@ -2831,7 +2838,7 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const if (mode == nodebank_panel::TRACK) { - if (false == ImGuizmo::IsOver() && false == pick_route_vertex() && false == pick_track_handle()) + if (false == ImGuizmo::IsOver() && false == pick_route_vertex() && false == pick_straight_handle() && false == pick_track_handle()) { GfxRenderer->Pick_Node_Callback([this](scene::basic_node *node) { if (viewport_click()) diff --git a/application/editormode.h b/application/editormode.h index 67019ad6..42eb590f 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -331,10 +331,28 @@ class editor_mode : public application_mode editor_track::straight a; editor_track::straight b; char filter[32]{}; + int handle{-1}; + bool dragging{false}; + editor_track::straight drag_line; + std::vector> drag_states; + glm::dvec3 preview_start{0.0}; + glm::dvec3 preview_end{0.0}; + glm::mat4 gizmo{1.0f}; + glm::dvec3 edit_start{0.0}; + glm::dvec3 edit_end{0.0}; + double edit_length{0.0}; + double edit_azimuth{0.0}; + double distance{4.0}; + std::string status; }; void render_straights_ui(); void draw_straights_overlay() const; editor_track::straight const ¤t_straight(); + bool straights_active(); + 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 straight_refresh(); straights_state m_straights; enum class track_tab { straights, route, path }; track_tab m_track_tab{track_tab::straights}; diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 1c8c55e8..5e412fa8 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1124,6 +1124,7 @@ editor_track::straight const &editor_mode::current_straight() if (track != m_straights.current_for) { m_straights.current_for = track; + m_straights.handle = -1; m_straights.current = track != nullptr ? editor_track::find_straight(*track, m_straights.tolerance) : editor_track::straight{}; } return m_straights.current; @@ -1158,6 +1159,27 @@ void editor_mode::draw_straights_overlay() const 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); } 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); + } + else if (false == m_straights.current.tracks.empty()) + { + auto const &line{m_straights.current}; + glm::dvec3 const handles[] = {line.start, line.end, (line.start + line.end) * 0.5}; + for (int i = 0; i < 3; ++i) + { + ImVec2 screen; + if (false == projection.project(handles[i], screen)) + continue; + if (i < 2) + drawlist->AddRectFilled(ImVec2(screen.x - 6.0f, screen.y - 6.0f), ImVec2(screen.x + 6.0f, screen.y + 6.0f), IM_COL32(40, 220, 255, 255)); + else + drawlist->AddCircleFilled(screen, 6.0f, IM_COL32(40, 220, 255, 255), 4); + if (i == m_straights.handle) + drawlist->AddCircle(screen, 12.0f, IM_COL32(255, 255, 255, 255), 16, 2.5f); + } + } draw(m_straights.a, IM_COL32(60, 230, 90, 255), 5.0f); draw(m_straights.b, IM_COL32(255, 60, 255, 255), 5.0f); if (false == m_straights.a.tracks.empty() && false == m_straights.b.tracks.empty()) @@ -1240,7 +1262,41 @@ void editor_mode::render_straights_ui() ImGui::SameLine(); if (ImGui::Button("Set as B")) state.b = line; + + if (false == ImGui::IsAnyItemActive()) + { + state.edit_start = line.start; + state.edit_end = line.end; + state.edit_length = line.length; + state.edit_azimuth = line.azimuth; + } + ImGui::TextDisabled("Drag the ends or the middle of the straight with the gizmo, or enter the values"); + ImGui::PushItemWidth(260.0f); + ImGui::InputScalarN("Start (x, y, z)", ImGuiDataType_Double, glm::value_ptr(state.edit_start), 3, nullptr, nullptr, "%.3f"); + ImGui::InputScalarN("End (x, y, z)", ImGuiDataType_Double, glm::value_ptr(state.edit_end), 3, nullptr, nullptr, "%.3f"); + ImGui::PopItemWidth(); + if (ImGui::Button("Apply ends")) + straight_apply(line, state.edit_start, state.edit_end); + ImGui::PushItemWidth(120.0f); + ImGui::InputDouble("Length (m)", &state.edit_length, 0.0, 0.0, "%.3f"); + ImGui::SameLine(); + if (ImGui::Button("Set length from the start")) + { + glm::dvec3 const direction{line.direction.x, line.grade, line.direction.y}; + straight_apply(line, line.start, line.start + direction * std::max(0.1, state.edit_length)); + } + ImGui::InputDouble("Azimuth (deg)", &state.edit_azimuth, 0.0, 0.0, "%.5f"); + ImGui::SameLine(); + if (ImGui::Button("Turn around the start")) + { + auto const azimuth{glm::radians(state.edit_azimuth)}; + glm::dvec3 const direction{std::sin(azimuth), line.grade, std::cos(azimuth)}; + straight_apply(line, line.start, line.start + direction * line.length); + } + ImGui::PopItemWidth(); } + if (false == state.status.empty()) + ImGui::TextColored(ImVec4(1.0f, 0.35f, 0.3f, 1.0f), "%s", state.status.c_str()); ImGui::Separator(); auto const &a{state.a}; @@ -1270,4 +1326,164 @@ void editor_mode::render_straights_ui() ImGui::Text("Height of B above A: start %.3f m, end %.3f m", height(b.start), height(b.end)); ImGui::Text("Grade difference: %.2f per mille", (b.grade * (dot < 0.0 ? -1.0 : 1.0) - a.grade) * 1000.0); ImGui::Text("Overlap along A: %.2f m", std::max(0.0, to - from)); + + ImGui::PushItemWidth(120.0f); + ImGui::InputDouble("Distance (m)", &state.distance, 0.0, 0.0, "%.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(); + if (ImGui::Button("Make B parallel to A at this distance")) + { + auto const side{(startoffset + endoffset) >= 0.0 ? 1.0 : -1.0}; + glm::dvec2 const direction{a.direction * (dot < 0.0 ? -1.0 : 1.0)}; + glm::dvec2 const normal{-a.direction.y, a.direction.x}; + auto const startalong{along(b.start)}; + auto const start2d{glm::dvec2{a.start.x, a.start.z} + a.direction * startalong + normal * (side * std::abs(state.distance))}; + auto const end2d{start2d + direction * b.length}; + straight_apply(b, {start2d.x, b.start.y, start2d.y}, {end2d.x, b.end.y, end2d.y}); + } +} + +bool editor_mode::straights_active() +{ + return ui()->mode() == nodebank_panel::TRACK && m_track_tab == track_tab::straights && false == current_straight().tracks.empty(); +} + +bool editor_mode::pick_straight_handle() +{ + if (false == straights_active()) + return false; + auto const &line{current_straight()}; + glm::dvec3 const handles[] = {line.start, line.end, (line.start + line.end) * 0.5}; + screen_projection const projection; + ImVec2 const mouse = ImGui::GetIO().MousePos; + float best = kHandleRadius * kHandleRadius; + int hit{-1}; + for (int i = 0; i < 3; ++i) + { + ImVec2 screen; + if (false == projection.project(handles[i], screen)) + continue; + float const dx = screen.x - mouse.x; + float const dy = screen.y - mouse.y; + if (dx * dx + dy * dy < best) + { + best = dx * dx + dy * dy; + hit = i; + } + } + if (hit < 0) + return false; + m_straights.handle = hit; + return true; +} + +void editor_mode::straight_refresh() +{ + auto &state{m_straights}; + state.current_for = nullptr; + for (auto *line : {&state.a, &state.b}) + if (false == line->tracks.empty()) + *line = editor_track::find_straight(*line->tracks.front(), state.tolerance); + for (auto &line : state.found) + if (false == line.tracks.empty()) + line = editor_track::find_straight(*line.tracks.front(), state.tolerance); +} + +void editor_mode::straight_apply(editor_track::straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End) +{ + auto &state{m_straights}; + state.status.clear(); + auto const tracks{editor_track::straight_affected(Line)}; + for (auto *track : tracks) + { + if (false == track->Dynamics.empty()) + { + state.status = "There are vehicles placed on path " + track->name(); + return; + } + } + std::vector> states; + 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::commit(tracks); + straight_refresh(); +} + +void editor_mode::render_straight_gizmo() +{ + auto &state{m_straights}; + auto const &line{state.dragging ? state.drag_line : current_straight()}; + if (state.handle < 0 || line.tracks.empty() || false == m_gizmo_enabled) + { + state.dragging = false; + return; + } + + ImGuizmo::BeginFrame(); + ImGuizmo::SetOrthographic(Global.EditorOrtho); + ImGuiIO const &io = ImGui::GetIO(); + ImGuizmo::SetRect(0.0f, 0.0f, io.DisplaySize.x, io.DisplaySize.y); + glm::mat4 const view = GfxRenderer->Camera_View_Matrix(); + glm::dvec3 const camerapos = GfxRenderer->Camera_Position(); + float const aspect = io.DisplaySize.y > 0.0f ? io.DisplaySize.x / io.DisplaySize.y : 1.0f; + glm::mat4 const projection = editor_mode::projection_matrix(aspect); + + glm::dvec3 const anchors[] = {line.start, line.end, (line.start + line.end) * 0.5}; + auto const anchor{anchors[state.handle]}; + if (false == state.dragging) + state.gizmo = glm::translate(glm::mat4(1.0f), glm::vec3(anchor - camerapos)); + glm::vec3 snapvalue(m_gizmo_snap); + float const *snap = Global.ctrlState && snapvalue.x > 0.0f ? glm::value_ptr(snapvalue) : nullptr; + ImGuizmo::Manipulate(glm::value_ptr(view), glm::value_ptr(projection), ImGuizmo::TRANSLATE, ImGuizmo::WORLD, glm::value_ptr(state.gizmo), nullptr, snap); + + if (ImGuizmo::IsUsing()) + { + if (false == state.dragging) + { + auto const tracks{editor_track::straight_affected(line)}; + 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"; + return; + } + state.dragging = true; + state.drag_line = line; + 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}; + 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; + else + { + auto const offset{moved - (original.start + original.end) * 0.5}; + start += offset; + end += offset; + } + state.preview_start = start; + state.preview_end = end; + for (auto const &entry : state.drag_states) + editor_track::apply(*entry.first, entry.second); + editor_track::move_straight(original, start, end); + m_track_dirty = true; + commit_track_drag(false); + } + else if (state.dragging) + { + commit_track_drag(true); + state.dragging = false; + m_track_drag.clear(); + state.drag_states.clear(); + straight_refresh(); + } } diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index ca17787a..81ed916f 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -773,76 +773,161 @@ bool editor_track::find_chain(TTrack *From, TTrack *To, chain &Chain, std::strin return true; } -bool editor_track::is_straight(TTrack const &Track, straight_tolerance const &Tolerance) +namespace { - if (Track.eType != tt_Normal || false == is_supported(Track) || Track.m_paths.empty()) - return false; - auto const &path{Track.m_paths.front()}; - glm::dvec2 const start{path.points[segment_data::point::start].x, path.points[segment_data::point::start].z}; - glm::dvec2 const end{path.points[segment_data::point::end].x, path.points[segment_data::point::end].z}; - auto const length{glm::distance(start, end)}; + +glm::dvec2 plan_of(glm::dvec3 const &Point) +{ + return {Point.x, Point.z}; +} + +glm::dvec2 turned(glm::dvec2 const &Vector, double const Angle) +{ + auto const c{std::cos(Angle)}; + auto const s{std::sin(Angle)}; + return {Vector.x * c - Vector.y * s, Vector.x * s + Vector.y * c}; +} + +double lateral_of(glm::dvec2 const &Direction, glm::dvec2 const &Offset) +{ + return Direction.x * Offset.y - Direction.y * Offset.x; +} + +bool plan_straight(segment_data const &Path, double const Tolerance) +{ + auto const chord{plan_of(Path.points[segment_data::point::end] - Path.points[segment_data::point::start])}; + auto const length{glm::length(chord)}; if (length < 1e-3) return false; - auto const direction{(end - start) / length}; + auto const direction{chord / length}; for (auto const index : {segment_data::point::control1, segment_data::point::control2}) - { - auto const &control{path.points[index]}; - glm::dvec2 const offset{control.x, control.z}; - if (std::abs(direction.x * offset.y - direction.y * offset.x) > Tolerance.offset) + if (std::abs(lateral_of(direction, plan_of(Path.points[index]))) > Tolerance) return false; - } return true; } +TTrack *neighbour_at(TTrack const &Track, int const Path, bool const Atend) +{ + if (has_switch_paths(Track)) + return Atend ? Track.SwitchExtension->pNexts[Path & 1] : Track.SwitchExtension->pPrevs[Path & 1]; + return Atend ? Track.trNext : Track.trPrev; +} + +void rigid_move(TTrack &Track, glm::dvec3 const &Pivot, glm::dvec3 const &Target, double const Angle) +{ + for (auto &path : Track.m_paths) + { + for (auto const index : {segment_data::point::start, segment_data::point::end}) + { + auto &point{path.points[index]}; + auto const moved{plan_of(Target) + turned(plan_of(point - Pivot), Angle)}; + point = {moved.x, point.y + Target.y - Pivot.y, moved.y}; + } + for (auto const index : {segment_data::point::control1, segment_data::point::control2}) + { + auto &control{path.points[index]}; + auto const moved{turned(plan_of(control), Angle)}; + control = {moved.x, control.y, moved.y}; + } + } +} + +void follow_joint(TTrack &Track, glm::dvec3 const &From, glm::dvec3 const &To, double const Angle) +{ + for (auto &path : Track.m_paths) + { + for (auto const atend : {false, true}) + { + auto &point{path.points[atend ? segment_data::point::end : segment_data::point::start]}; + if (glm::distance(point, From) > kSamePoint) + continue; + point = To; + auto &control{path.points[atend ? segment_data::point::control2 : segment_data::point::control1]}; + auto const moved{turned(plan_of(control), Angle)}; + control = {moved.x, control.y, moved.y}; + } + } +} + +} // namespace + +bool editor_track::is_straight(TTrack const &Track, straight_tolerance const &Tolerance) +{ + if (false == is_supported(Track) || Track.m_editorremoved) + return false; + return std::any_of(Track.m_paths.begin(), Track.m_paths.end(), [&](segment_data const &Path) { return plan_straight(Path, Tolerance.offset); }); +} + editor_track::straight editor_track::find_straight(TTrack &Track, straight_tolerance const &Tolerance) { straight result; if (false == is_straight(Track, Tolerance)) return result; - auto const plan = [](glm::dvec3 const &Point) { return glm::dvec2{Point.x, Point.z}; }; - auto const &first{Track.m_paths.front()}; + int firstpath{0}; + while (false == plan_straight(Track.m_paths[firstpath], Tolerance.offset)) + ++firstpath; + auto const &first{Track.m_paths[firstpath]}; auto const origin{first.points[segment_data::point::start]}; - auto const direction{glm::normalize(plan(first.points[segment_data::point::end] - origin))}; - auto const lateral = [&](glm::dvec3 const &Point) { - auto const offset{plan(Point - origin)}; - return std::abs(direction.x * offset.y - direction.y * offset.x); - }; - auto const continues = [&](TTrack const *Other) { - if (Other == nullptr || false == is_straight(*Other, Tolerance)) - return false; - auto const &path{Other->m_paths.front()}; - auto const otherdirection{glm::normalize(plan(path.points[segment_data::point::end] - path.points[segment_data::point::start]))}; - if (std::abs(direction.x * otherdirection.y - direction.y * otherdirection.x) > Tolerance.angle) - return false; - return lateral(path.points[segment_data::point::start]) <= Tolerance.offset && lateral(path.points[segment_data::point::end]) <= Tolerance.offset; + auto const direction{glm::normalize(plan_of(first.points[segment_data::point::end] - origin))}; + auto const lateral = [&](glm::dvec3 const &Point) { return std::abs(lateral_of(direction, plan_of(Point - origin))); }; + auto const accepts = [&](TTrack const *Other, glm::dvec3 const &Joint) { + if (Other == nullptr || false == is_supported(*Other) || Other->m_editorremoved) + return -1; + for (int i = 0; i < static_cast(Other->m_paths.size()); ++i) + { + auto const &path{Other->m_paths[i]}; + if (false == plan_straight(path, Tolerance.offset)) + continue; + auto const &start{path.points[segment_data::point::start]}; + auto const &end{path.points[segment_data::point::end]}; + if (glm::distance(start, Joint) > kSamePoint && glm::distance(end, Joint) > kSamePoint) + continue; + auto const otherdirection{glm::normalize(plan_of(end - start))}; + if (std::abs(lateral_of(direction, otherdirection)) > Tolerance.angle) + continue; + if (lateral(start) > Tolerance.offset || lateral(end) > Tolerance.offset) + continue; + return i; + } + return -1; }; - std::deque run{&Track}; - for (auto const throughend : {true, false}) + std::deque> run{{&Track, firstpath}}; + for (auto const forward : {true, false}) { - TTrack *previous{&Track}; - TTrack *current{throughend ? Track.trNext : Track.trPrev}; - while (continues(current) && std::find(run.begin(), run.end(), current) == run.end()) + TTrack *current{&Track}; + int path{firstpath}; + bool atend{forward}; + while (true) { - if (throughend) - run.push_back(current); + auto const &exit{current->m_paths[path].points[atend ? segment_data::point::end : segment_data::point::start]}; + auto *next{neighbour_at(*current, path, atend)}; + if (std::any_of(run.begin(), run.end(), [&](auto const &Member) { return Member.first == next; })) + break; + auto const nextpath{accepts(next, exit)}; + if (nextpath < 0) + break; + if (forward) + run.emplace_back(next, nextpath); else - run.push_front(current); - auto *next{current->trPrev == previous ? current->trNext : current->trPrev}; - previous = current; + run.emplace_front(next, nextpath); + atend = glm::distance(next->m_paths[nextpath].points[segment_data::point::start], exit) <= kSamePoint; current = next; + path = nextpath; } } double low{0.0}, high{0.0}; glm::dvec3 lowpoint{origin}, highpoint{origin}; - for (auto *track : run) + for (auto const &member : run) { + result.tracks.push_back(member.first); + result.paths.push_back(member.second); for (auto const index : {segment_data::point::start, segment_data::point::end}) { - auto const &point{track->m_paths.front().points[index]}; - auto const along{glm::dot(plan(point - origin), direction)}; + auto const &point{member.first->m_paths[member.second].points[index]}; + auto const along{glm::dot(plan_of(point - origin), direction)}; if (along < low) { low = along; @@ -855,7 +940,6 @@ editor_track::straight editor_track::find_straight(TTrack &Track, straight_toler } } } - result.tracks.assign(run.begin(), run.end()); result.start = lowpoint; result.end = highpoint; result.direction = direction; @@ -874,7 +958,7 @@ std::vector editor_track::find_straights(double const Mi std::unordered_set visited; for (auto *track : simulation::Paths.sequence()) { - if (track == nullptr || track->m_editorremoved || visited.count(track) > 0 || false == is_straight(*track, Tolerance)) + if (track == nullptr || visited.count(track) > 0 || false == is_straight(*track, Tolerance)) continue; auto line{find_straight(*track, Tolerance)}; for (auto const *member : line.tracks) @@ -886,6 +970,162 @@ std::vector editor_track::find_straights(double const Mi return result; } +std::vector editor_track::straight_affected(straight const &Line) +{ + 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) + { + for (int i = 0; i < static_cast(track->m_paths.size()); ++i) + { + for (auto const atend : {false, true}) + { + auto *neighbour{neighbour_at(*track, i, atend)}; + if (neighbour == nullptr || member(neighbour)) + continue; + add_unique(result, neighbour); + if (false == has_switch_paths(*neighbour)) + continue; + for (auto *next : neighbours(*neighbour)) + if (false == member(next)) + add_unique(result, next); + } + } + } + return result; +} + +void editor_track::move_straight(straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End) +{ + if (Line.tracks.empty() || Line.length < 1e-6) + return; + auto const &direction{Line.direction}; + auto const newplan{plan_of(End - Start)}; + auto const newlength{glm::length(newplan)}; + if (newlength < 1e-3) + return; + auto const newdirection{newplan / newlength}; + auto const angle{std::atan2(lateral_of(direction, newdirection), glm::dot(direction, newdirection))}; + auto const along = [&](glm::dvec3 const &Point) { return glm::dot(plan_of(Point - Line.start), direction); }; + + struct interval + { + double from, to; + bool rigid; + }; + std::vector intervals; + double rigidlength{0.0}; + for (std::size_t i = 0; i < Line.tracks.size(); ++i) + { + auto const &path{Line.tracks[i]->m_paths[Line.paths[i]]}; + auto const a{along(path.points[segment_data::point::start])}; + auto const b{along(path.points[segment_data::point::end])}; + auto const rigid{has_switch_paths(*Line.tracks[i])}; + intervals.push_back({std::min(a, b), std::max(a, b), rigid}); + if (rigid) + rigidlength += std::abs(b - a); + } + std::sort(intervals.begin(), intervals.end(), [](interval const &A, interval const &B) { return A.from < B.from; }); + auto const scale{Line.length - rigidlength > 1e-6 ? (newlength - rigidlength) / (Line.length - rigidlength) : 1.0}; + auto const remap = [&](double const Along) { + double mapped{0.0}, previous{0.0}; + for (auto const &span : intervals) + { + if (Along <= span.from) + return mapped + (Along - previous) * scale; + mapped += (span.from - previous) * scale; + previous = span.from; + auto const factor{span.rigid ? 1.0 : scale}; + if (Along <= span.to) + return mapped + (Along - previous) * factor; + mapped += (span.to - previous) * factor; + previous = span.to; + } + return mapped + (Along - previous) * scale; + }; + auto const transform = [&](glm::dvec3 const &Point) { + auto const a{along(Point)}; + auto const offset{lateral_of(direction, plan_of(Point - Line.start))}; + auto const mapped{remap(a)}; + auto const oldheight{Line.start.y + (Line.end.y - Line.start.y) * a / Line.length}; + auto const newheight{Start.y + (End.y - Start.y) * mapped / newlength}; + auto const moved{plan_of(Start) + newdirection * mapped + glm::dvec2{-newdirection.y, newdirection.x} * offset}; + 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(); }; + std::vector> joints; + for (std::size_t i = 0; i < Line.tracks.size(); ++i) + { + auto &track{*Line.tracks[i]}; + auto const before{track.m_paths}; + if (has_switch_paths(track)) + { + auto const pivot{before[Line.paths[i]].points[segment_data::point::start]}; + rigid_move(track, pivot, transform(pivot), angle); + } + else + { + for (auto &path : track.m_paths) + { + for (auto const index : {segment_data::point::start, segment_data::point::end}) + path.points[index] = transform(path.points[index]); + for (auto const index : {segment_data::point::control1, segment_data::point::control2}) + { + auto &control{path.points[index]}; + auto const moved{turned(plan_of(control), angle) * scale}; + control = {moved.x, control.y * scale, moved.y}; + } + } + } + for (std::size_t j = 0; j < before.size(); ++j) + for (auto const index : {segment_data::point::start, segment_data::point::end}) + joints.emplace_back(before[j].points[index], track.m_paths[j].points[index]); + } + + std::vector moved; + for (auto *track : Line.tracks) + { + for (int i = 0; i < static_cast(track->m_paths.size()); ++i) + { + for (auto const atend : {false, true}) + { + auto *neighbour{neighbour_at(*track, i, atend)}; + if (neighbour == nullptr || member(neighbour) || std::find(moved.begin(), moved.end(), neighbour) != moved.end()) + continue; + moved.push_back(neighbour); + for (auto const &joint : joints) + { + bool touches{false}; + for (auto const &path : neighbour->m_paths) + for (auto const index : {segment_data::point::start, segment_data::point::end}) + touches |= glm::distance(path.points[index], joint.first) <= kSamePoint; + if (false == touches) + continue; + if (has_switch_paths(*neighbour)) + { + auto const before{neighbour->m_paths}; + rigid_move(*neighbour, joint.first, joint.second, angle); + for (auto *next : neighbours(*neighbour)) + { + if (member(next) || next == neighbour) + continue; + for (std::size_t j = 0; j < before.size(); ++j) + for (auto const index : {segment_data::point::start, segment_data::point::end}) + follow_joint(*next, before[j].points[index], neighbour->m_paths[j].points[index], angle); + } + } + else + { + follow_joint(*neighbour, joint.first, joint.second, angle); + } + break; + } + } + } + } +} + std::vector editor_track::relay(chain const &Chain, std::vector const &Pieces) { std::vector created; diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index 1ed25cae..d58e7f36 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -99,6 +99,7 @@ class editor_track struct straight { std::vector tracks; + std::vector paths; glm::dvec3 start{0.0}; glm::dvec3 end{0.0}; glm::dvec2 direction{0.0, 1.0}; @@ -114,6 +115,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 relay(chain const &Chain, std::vector const &Pieces); static void retire(TTrack &Track); static void revive(TTrack &Track);