From 32798a32bb17a39329f2b1f97284f5088d125e24 Mon Sep 17 00:00:00 2001 From: maj00r Date: Fri, 2 Oct 2026 23:56:19 +0200 Subject: [PATCH] feat(editor): switch templates, auto distances with snapping --- application/editormode.h | 7 +- application/editormodetrack.cpp | 178 +++++++++++++++++++---------- editor/editorTrack.cpp | 195 ++++++++++++++++++++++++++++++-- editor/editorTrack.hpp | 18 +++ 4 files changed, 326 insertions(+), 72 deletions(-) diff --git a/application/editormode.h b/application/editormode.h index 2d7e2520..2545c5e8 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -332,8 +332,8 @@ class editor_mode : public application_mode int listed{-1}; TTrack *current_for{nullptr}; editor_track::straight current; - editor_track::straight a; - editor_track::straight b; + std::vector neighbours; + std::vector spacings{3.5, 4.0, 4.5, 4.75, 5.0, 5.5, 6.0}; char filter[32]{}; int handle{-1}; bool dragging{false}; @@ -378,6 +378,9 @@ class editor_mode : public application_mode double straight_tool_radius(editor_track::straight const &Line) const; void straight_reshape(editor_track::straight const &Line, double const From, double const To, std::function const &Tail, double const Radius); void straight_refresh(); + void find_neighbour_straights(); + glm::dvec3 snap_straight_offset(editor_track::straight const &Line, glm::dvec3 const &Offset) const; + glm::dvec3 snap_straight_direction(glm::dvec3 const &Pivot, glm::dvec3 const &Moved) const; 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 fccc54cc..fc4796b9 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -15,6 +15,8 @@ http: #include "utilities/Globals.h" #include "rendering/renderer.h" #include "world/Track.h" +#include "scene/scene.h" +#include "simulation/simulation.h" #include "utilities/Logs.h" #include "imgui/imgui.h" @@ -1232,6 +1234,7 @@ editor_track::straight const &editor_mode::current_straight() 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{}; + find_neighbour_straights(); } return m_straights.current; } @@ -1326,20 +1329,119 @@ void editor_mode::draw_straights_overlay() const 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()) + auto const &line{m_straights.current}; + if (line.tracks.empty() || m_straights.dragging) + return; + glm::dvec2 const normal{-line.direction.y, line.direction.x}; + for (auto const &other : m_straights.neighbours) { - auto const &a{m_straights.a}; - for (auto const &point : {m_straights.b.start, m_straights.b.end}) + auto const along = [&](glm::dvec3 const &Point) { return glm::dot(glm::dvec2{Point.x - line.start.x, Point.z - line.start.z}, line.direction); }; + auto const across = [&](glm::dvec3 const &Point) { return glm::dot(glm::dvec2{Point.x - line.start.x, Point.z - line.start.z}, normal); }; + auto const a0{along(other.start)}, a1{along(other.end)}; + auto const from{std::max(0.0, std::min(a0, a1))}; + auto const to{std::min(line.length, std::max(a0, a1))}; + if (to <= from) + continue; + auto const middle{(from + to) * 0.5}; + auto const fraction{std::abs(a1 - a0) > 1e-6 ? (middle - a0) / (a1 - a0) : 0.0}; + auto const lateral{across(other.start) + (across(other.end) - across(other.start)) * fraction}; + glm::dvec3 const foot{line.start.x + line.direction.x * middle, line.start.y + line.grade * middle, line.start.z + line.direction.y * middle}; + glm::dvec3 const target{foot.x + normal.x * lateral, foot.y, foot.z + normal.y * lateral}; + projection.line(drawlist, foot, target, IM_COL32(255, 230, 120, 230), 1.5f); + ImVec2 screen; + if (projection.project((foot + target) * 0.5, screen)) { - auto const along{glm::dot(glm::dvec2{point.x - a.start.x, point.z - a.start.z}, a.direction)}; - auto const foot{a.start + glm::dvec3{a.direction.x, 0.0, a.direction.y} * along + glm::dvec3{0.0, a.grade * along, 0.0}}; - projection.line(drawlist, foot, point, IM_COL32(255, 210, 60, 220), 1.5f); + auto const angle{glm::degrees(std::abs(std::asin(std::clamp(line.direction.x * other.direction.y - line.direction.y * other.direction.x, -1.0, 1.0))))}; + char label[48]; + if (angle > 0.001) + std::snprintf(label, sizeof(label), "%.3f m %.3f deg", std::abs(lateral), angle); + else + std::snprintf(label, sizeof(label), "%.3f m", std::abs(lateral)); + auto const size{ImGui::CalcTextSize(label)}; + drawlist->AddRectFilled(ImVec2(screen.x - 3.0f, screen.y - 2.0f), ImVec2(screen.x + size.x + 3.0f, screen.y + size.y + 2.0f), IM_COL32(0, 0, 0, 170), 3.0f); + drawlist->AddText(screen, IM_COL32(255, 230, 120, 255), label); } } } +void editor_mode::find_neighbour_straights() +{ + auto &state{m_straights}; + state.neighbours.clear(); + auto const &line{state.current}; + if (line.tracks.empty()) + return; + glm::dvec2 const normal{-line.direction.y, line.direction.x}; + auto const middle{(line.start + line.end) * 0.5}; + std::vector visited(line.tracks.begin(), line.tracks.end()); + auto const sections{simulation::Region->sections(middle, static_cast(line.length * 0.5 + 60.0))}; + for (auto *section : sections) + { + for (auto const &cell : section->m_cells) + { + for (auto *track : cell.m_paths) + { + if (std::find(visited.begin(), visited.end(), track) != visited.end() || false == editor_track::is_straight(*track, state.tolerance)) + continue; + auto other{editor_track::find_straight(*track, state.tolerance)}; + visited.insert(visited.end(), other.tracks.begin(), other.tracks.end()); + if (other.tracks.empty() || std::abs(line.direction.x * other.direction.y - line.direction.y * other.direction.x) > std::sin(glm::radians(10.0))) + continue; + auto const offset{glm::dvec2{(other.start + other.end).x * 0.5 - line.start.x, (other.start + other.end).z * 0.5 - line.start.z}}; + if (std::abs(glm::dot(offset, normal)) > 60.0) + continue; + auto const a0{glm::dot(glm::dvec2{other.start.x - line.start.x, other.start.z - line.start.z}, line.direction)}; + auto const a1{glm::dot(glm::dvec2{other.end.x - line.start.x, other.end.z - line.start.z}, line.direction)}; + if (std::max(a0, a1) <= 0.0 || std::min(a0, a1) >= line.length) + continue; + state.neighbours.push_back(std::move(other)); + } + } + } +} + +glm::dvec3 editor_mode::snap_straight_offset(editor_track::straight const &Line, glm::dvec3 const &Offset) const +{ + glm::dvec2 const normal{-Line.direction.y, Line.direction.x}; + auto const middle{(Line.start + Line.end) * 0.5 + Offset}; + double best{0.2}; + double correction{0.0}; + for (auto const &other : m_straights.neighbours) + { + if (std::abs(Line.direction.x * other.direction.y - Line.direction.y * other.direction.x) > std::sin(glm::radians(0.5))) + continue; + auto const distance{glm::dot(glm::dvec2{other.start.x - middle.x, other.start.z - middle.z}, normal)}; + for (auto const spacing : m_straights.spacings) + { + auto const error{std::abs(distance) - spacing}; + if (std::abs(error) < best) + { + best = std::abs(error); + correction = distance > 0.0 ? error : -error; + } + } + } + return Offset + glm::dvec3{normal.x, 0.0, normal.y} * correction; +} + +glm::dvec3 editor_mode::snap_straight_direction(glm::dvec3 const &Pivot, glm::dvec3 const &Moved) const +{ + glm::dvec2 const offset{Moved.x - Pivot.x, Moved.z - Pivot.z}; + auto const length{glm::length(offset)}; + if (length < 1e-3) + return Moved; + auto const direction{offset / length}; + for (auto const &other : m_straights.neighbours) + { + auto const sine{direction.x * other.direction.y - direction.y * other.direction.x}; + if (std::abs(sine) > std::sin(glm::radians(0.5))) + continue; + auto const aligned{other.direction * (glm::dot(direction, other.direction) < 0.0 ? -1.0 : 1.0)}; + return {Pivot.x + aligned.x * length, Moved.y, Pivot.z + aligned.y * length}; + } + return Moved; +} + void editor_mode::render_straights_ui() { auto &state{m_straights}; @@ -1403,12 +1505,6 @@ void editor_mode::render_straights_ui() ImGui::Text("To (%.3f, %.3f, %.3f)", line.end.x, line.end.y, line.end.z); ImGui::Text("Paths: %s ... %s", line.tracks.front()->name().c_str(), line.tracks.back()->name().c_str()); ImGui::Unindent(); - if (ImGui::Button("Set as A")) - state.a = line; - 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")) @@ -1496,48 +1592,11 @@ void editor_mode::render_straights_ui() state.set.clear(); } - ImGui::Separator(); - auto const &a{state.a}; - auto const &b{state.b}; - ImGui::TextColored(ImVec4(0.25f, 0.9f, 0.35f, 1.0f), "A: %s", a.tracks.empty() ? "-" : describe(a).c_str()); - ImGui::TextColored(ImVec4(1.0f, 0.25f, 1.0f, 1.0f), "B: %s", b.tracks.empty() ? "-" : describe(b).c_str()); - if (a.tracks.empty() || b.tracks.empty()) - return; - - auto const cross{a.direction.x * b.direction.y - a.direction.y * b.direction.x}; - auto const dot{glm::dot(a.direction, b.direction)}; - auto const between{glm::degrees(std::atan2(std::abs(cross), std::abs(dot)))}; - auto const offset = [&](glm::dvec3 const &Point) { - glm::dvec2 const relative{Point.x - a.start.x, Point.z - a.start.z}; - return a.direction.x * relative.y - a.direction.y * relative.x; - }; - auto const along = [&](glm::dvec3 const &Point) { return glm::dot(glm::dvec2{Point.x - a.start.x, Point.z - a.start.z}, a.direction); }; - auto const height = [&](glm::dvec3 const &Point) { return Point.y - (a.start.y + a.grade * along(Point)); }; - auto const startoffset{offset(b.start)}; - auto const endoffset{offset(b.end)}; - auto const from{std::max(0.0, std::min(along(b.start), along(b.end)))}; - auto const to{std::min(a.length, std::max(along(b.start), along(b.end)))}; - - ImGui::Text("Angle between A and B: %.5f deg%s", between, between < 0.001 ? " (parallel)" : ""); - ImGui::Text("Distance of B from A: start %.3f m, end %.3f m, change %.3f m", std::abs(startoffset), std::abs(endoffset), std::abs(endoffset - startoffset)); - ImGui::Text("B lies on the %s of A", (startoffset + endoffset) > 0.0 ? "left" : "right"); - 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")) + if (false == state.neighbours.empty()) { - 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}); + ImGui::Separator(); + ImGui::Text("Neighbouring straights: %zu (distances shown in the view)", state.neighbours.size()); + ImGui::TextDisabled("Dragging the middle snaps the distance to: 3.5, 4.0, 4.5, 4.75, 5.0, 5.5, 6.0 m; dragging an end snaps parallel"); } } @@ -1580,9 +1639,7 @@ void editor_mode::straight_refresh() auto &state{m_straights}; state.current_for = selected_track(); state.current = state.current_for != nullptr ? editor_track::find_straight(*state.current_for, state.tolerance) : editor_track::straight{}; - for (auto *line : {&state.a, &state.b}) - if (false == line->tracks.empty()) - *line = editor_track::find_straight(*line->tracks.front(), state.tolerance); + find_neighbour_straights(); for (auto &line : state.found) if (false == line.tracks.empty()) line = editor_track::find_straight(*line.tracks.front(), state.tolerance); @@ -1696,17 +1753,18 @@ void editor_mode::render_straight_gizmo() m_track_drag = tracks; } auto const &grabbed{state.drag_line}; - glm::dvec3 const moved{camerapos + glm::dvec3(state.gizmo[3])}; + glm::dvec3 moved{camerapos + glm::dvec3(state.gizmo[3])}; std::function transform; if (state.handle == 2) { - auto const offset{moved - (grabbed.start + grabbed.end) * 0.5}; + auto const offset{snap_straight_offset(grabbed, moved - (grabbed.start + grabbed.end) * 0.5)}; transform = [offset](glm::dvec3 const &Point) { return Point + offset; }; } else { auto const pivot{state.handle == 0 ? grabbed.end : grabbed.start}; auto const original{state.handle == 0 ? grabbed.start : grabbed.end}; + moved = snap_straight_direction(pivot, moved); 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)}; diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index 32542f32..f7a4f778 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -22,6 +22,7 @@ http: #include #include #include +#include #include #include #include @@ -1043,6 +1044,176 @@ bool editor_track::find_curve(TTrack &Track, straight_tolerance const &Tolerance return true; } +std::vector editor_track::find_switch_templates() +{ + std::vector result; + std::map lookup; + for (auto *track : simulation::Paths.sequence()) + { + if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch || track->m_paths.size() < 2 || (track->iCategoryFlag & 0x80) != 0) + continue; + auto const &main{track->m_paths[0]}; + auto const origin{main.points[segment_data::point::start]}; + auto const chord{plan_of(main.points[segment_data::point::end] - origin)}; + if (glm::length(chord) < 1.0) + continue; + auto const axis{glm::normalize(chord)}; + glm::dvec2 const normal{-axis.y, axis.x}; + auto const tolocal = [&](glm::dvec3 const &Vector) { + auto const planar{plan_of(Vector)}; + return glm::dvec3{glm::dot(planar, normal), Vector.y, glm::dot(planar, axis)}; + }; + switch_template entry; + entry.source = track; + for (int i = 0; i < 2; ++i) + { + auto const &path{track->m_paths[i]}; + auto &local{entry.local[i]}; + local = path; + local.points[segment_data::point::start] = tolocal(path.points[segment_data::point::start] - origin); + local.points[segment_data::point::end] = tolocal(path.points[segment_data::point::end] - origin); + local.points[segment_data::point::control1] = tolocal(path.points[segment_data::point::control1]); + local.points[segment_data::point::control2] = tolocal(path.points[segment_data::point::control2]); + } + if (glm::length(glm::dvec2{entry.local[1].points[segment_data::point::start].x, entry.local[1].points[segment_data::point::start].z}) > kSamePoint) + continue; + if (entry.local[1].points[segment_data::point::end].x < 0.0) + { + for (auto &path : entry.local) + { + for (auto &point : path.points) + point.x = -point.x; + path.rolls = {-path.rolls[0], -path.rolls[1]}; + } + } + entry.length = entry.local[0].points[segment_data::point::end].z; + auto const tangent{path_tangent(entry.local[1], true)}; + auto const angle{std::atan2(std::abs(tangent.x), tangent.z)}; + entry.ratio = angle > 1e-6 ? 1.0 / std::tan(angle) : 0.0; + entry.radius = path_radius(entry.local[1], 0.25); + char key[256]; + std::snprintf(key, sizeof(key), "%.2f|%.2f|%.2f|%s|%s", entry.length, entry.local[1].points[segment_data::point::end].x, entry.local[1].points[segment_data::point::end].z, material_name(track->m_material1).c_str(), material_name(track->m_material2).c_str()); + auto const found{lookup.find(key)}; + if (found != lookup.end()) + { + ++result[found->second].count; + continue; + } + entry.count = 1; + char label[256]; + std::snprintf(label, sizeof(label), "L %.2f m 1:%.1f R %.0f m %s", entry.length, entry.ratio, entry.radius, material_name(track->m_material1).c_str()); + entry.label = label; + lookup.emplace(key, result.size()); + result.push_back(entry); + } + std::sort(result.begin(), result.end(), [](switch_template const &A, switch_template const &B) { return A.count > B.count; }); + return result; +} + +std::vector editor_track::place_switch(switch_template const &Template, glm::dvec3 const &Origin, glm::dvec2 const &Direction, int const Side, double const Grade) +{ + std::vector result; + glm::dvec2 const normal{-Direction.y, Direction.x}; + auto const side{static_cast(Side)}; + auto const vector = [&](glm::dvec3 const &Local) { + auto const planar{Direction * Local.z + normal * (Local.x * side)}; + return glm::dvec3{planar.x, Local.y + Grade * Local.z, planar.y}; + }; + for (auto const &local : Template.local) + { + segment_data path{local}; + path.points[segment_data::point::start] = Origin + vector(local.points[segment_data::point::start]); + path.points[segment_data::point::end] = Origin + vector(local.points[segment_data::point::end]); + path.points[segment_data::point::control1] = vector(local.points[segment_data::point::control1]); + path.points[segment_data::point::control2] = vector(local.points[segment_data::point::control2]); + if (Side < 0) + path.rolls = {-local.rolls[0], -local.rolls[1]}; + result.push_back(path); + } + return result; +} + +std::vector editor_track::standard_switch_templates() +{ + struct definition + { + char const *name; + double radius; + double ratio; + double a; + double b; + }; + definition const definitions[] = { + {"Rz 1:9 R190", 190.0, 9.0, 10.523, 16.615}, + {"Rz 1:9 R300", 300.0, 9.0, 16.615, 16.615}, + {"Rz 1:12 R500", 500.0, 12.0, 20.797, 0.0}, + {"Rz 1:14 R760", 760.0, 14.0, 0.0, 0.0}, + {"Rz 1:18,5 R1200", 1200.0, 18.5, 32.409, 34.943}, + {"Rz 1:26,5 R2500", 2500.0, 26.5, 0.0, 0.0}, + }; + std::vector result; + for (auto const &definition : definitions) + { + auto const angle{std::atan(1.0 / definition.ratio)}; + auto const a{definition.a > 0.0 ? definition.a : definition.radius * std::tan(angle * 0.5)}; + auto const estimated{definition.b <= 0.0}; + auto const b{estimated ? 1.87 * definition.ratio : definition.b}; + auto const offset{b * std::tan(angle)}; + switch_template entry; + entry.a = a; + entry.b = b; + entry.length = a + b; + entry.ratio = definition.ratio; + entry.radius = definition.radius; + auto &main{entry.local[0]}; + main.points[segment_data::point::end] = {0.0, 0.0, a + b}; + auto &diverging{entry.local[1]}; + diverging.points[segment_data::point::end] = {offset, 0.0, a + b}; + diverging.points[segment_data::point::control1] = {0.0, 0.0, a * 2.0 / 3.0}; + diverging.points[segment_data::point::control2] = {-offset * 2.0 / 3.0, 0.0, -b * 2.0 / 3.0}; + diverging.radius = static_cast(definition.radius); + entry.label = std::string{definition.name} + " a " + std::to_string(a).substr(0, std::to_string(a).find('.') + 4) + " b " + std::to_string(b).substr(0, std::to_string(b).find('.') + 4) + (estimated ? " (b est.)" : ""); + result.push_back(entry); + } + return result; +} + +TTrack *editor_track::create_switch(switch_template const &Template, std::vector const &Paths, TTrack const &Style) +{ + if (Paths.size() < 2) + return nullptr; + TTrack *track{nullptr}; + if (Template.source != nullptr) + { + track = clone(*Template.source); + } + else + { + char const *environments[] = {"flat", "mountains", "canyon", "tunnel", "bridge", "bank"}; + auto const environment{Style.eEnvironment >= e_flat && Style.eEnvironment <= e_bank ? environments[Style.eEnvironment] : "flat"}; + std::ostringstream text; + text.precision(std::numeric_limits::digits10); + text << "switch " << Template.length << ' ' << Style.fTrackWidth << ' ' << Style.fFriction << ' ' << 10.0 << ' ' << Style.iQualityFlag << ' ' << 0 << ' ' << environment << ' '; + if (Style.m_visible) + text << "vis " << material_name(Style.m_material1) << ' ' << Style.fTexLength << ' ' << material_name(Style.m_material2) << ' ' << texture_height(Style) << ' ' << Style.fTexWidth << ' ' << Style.fTexSlope << ' '; + else + text << "unvis "; + for (int i = 0; i < 2; ++i) + { + auto const &path{Paths[i]}; + auto const &start{path.points[segment_data::point::start]}; + auto const &control1{path.points[segment_data::point::control1]}; + auto const &control2{path.points[segment_data::point::control2]}; + auto const &end{path.points[segment_data::point::end]}; + text << start.x << ' ' << start.y << ' ' << start.z << ' ' << path.rolls[0] << ' ' << control1.x << ' ' << control1.y << ' ' << control1.z << ' ' << control2.x << ' ' << control2.y << ' ' << control2.z << ' ' << end.x << ' ' << end.y << ' ' << end.z << ' ' << path.rolls[1] << ' ' << path.radius << ' '; + } + text << "endtrack\n"; + track = load_path(text.str(), Style); + } + track->m_paths.assign(Paths.begin(), Paths.begin() + 2); + return track; +} + std::vector editor_track::straight_affected(std::vector const &Lines) { std::vector result; @@ -1252,18 +1423,23 @@ TTrack *editor_track::clone(TTrack const &Template) { std::ostringstream text; Template.export_as_text_(text); - cParser parser(text.str(), cParser::buffer_TEXT); - parser.getTokens(); - std::string token; - parser >> token; + auto content{text.str()}; + auto const type{content.find("track ")}; + if (type == 0) + content.erase(0, 6); + auto *track{load_path(content, Template)}; + track->m_friction = Template.m_friction; + return track; +} +TTrack *editor_track::load_path(std::string const &Text, TTrack const &Template) +{ + cParser parser(Text, cParser::buffer_TEXT); scene::node_data data; data.type = "track"; - if (false == Template.name().empty() && Template.name() != "none") - { - for (int i = 1; data.name.empty() || simulation::Paths.find(data.name) != nullptr; ++i) - data.name = Template.name() + "_" + std::to_string(i); - } + auto const base{Template.name().empty() || Template.name() == "none" ? std::string{"editor_track"} : Template.name()}; + for (int i = 1; data.name.empty() || simulation::Paths.find(data.name) != nullptr; ++i) + data.name = base + "_" + std::to_string(i); auto *track = new TTrack(data); track->m_rangesquaredmin = Template.m_rangesquaredmin; track->m_rangesquaredmax = Template.m_rangesquaredmax; @@ -1271,7 +1447,6 @@ TTrack *editor_track::clone(TTrack const &Template) for (auto *events : {&track->m_events0, &track->m_events1, &track->m_events2, &track->m_events0all, &track->m_events1all, &track->m_events2all}) events->clear(); track->m_events = false; - track->m_friction = Template.m_friction; simulation::Paths.insert(track); return track; } diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index 27b9ac92..ad828284 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -9,6 +9,7 @@ http: #pragma once +#include #include #include #include @@ -129,6 +130,22 @@ class editor_track double cant{0.0}; }; static bool find_curve(TTrack &Track, straight_tolerance const &Tolerance, double const Gauge, curve &Curve); + struct switch_template + { + TTrack *source{nullptr}; + std::array local; + double length{0.0}; + double ratio{0.0}; + double radius{0.0}; + int count{0}; + std::string label; + double a{0.0}; + double b{0.0}; + }; + static std::vector standard_switch_templates(); + static std::vector find_switch_templates(); + static std::vector place_switch(switch_template const &Template, glm::dvec3 const &Origin, glm::dvec2 const &Direction, int const Side, double const Grade); + static TTrack *create_switch(switch_template const &Template, std::vector const &Paths, TTrack const &Style); 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); @@ -152,5 +169,6 @@ 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 TTrack *load_path(std::string const &Text, TTrack const &Template); static void move_straight(straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End, std::function const &Member); };