From 9625b4b3d9cdd5de1a0db0f6b4b5bffc935e949c Mon Sep 17 00:00:00 2001 From: maj00r Date: Sat, 24 Oct 2026 18:41:25 +0200 Subject: [PATCH] feat(editor): K on a track is a break of the grade, as a point of a road: the track split at the cursor, the point between the two at the height of the ground and the ballast there, each of them an even grade from its other end up to it Selftest kbreak: K on the track nearest a point, logs the joint against the ground. --- application/editormode.cpp | 2 +- application/editormodeselftest.cpp | 1 + application/editormodeselftestscene.cpp | 31 +++++++++++++++++++++++++ application/editormodetrack.cpp | 2 +- application/editormodetrackux.cpp | 18 +++++++++++++- lang/editor_pl.po | 12 +++++----- 6 files changed, 57 insertions(+), 9 deletions(-) diff --git a/application/editormode.cpp b/application/editormode.cpp index 061f2fb1..04cfc07a 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -5583,7 +5583,7 @@ void editor_mode::render_shortcuts_window() {"Ctrl+F", "Find a track by its name"}, {"Shift+LMB", "Add a track to the selection, drag: a box"}, {"RMB on a track", "What can be done with it"}, - {"K", "Split the selected track at the cursor"}, + {"K", "Split the selected track at the cursor, a break of the grade at the height of the ground"}, {"L, LMB, LMB...", "Lay track: each click lays a piece and goes on from its end"}, {"Drag from a free end", "Lead a track out of it; onto another free end: join the two"}, {"Ctrl+wheel", "Laying: radius"}, diff --git a/application/editormodeselftest.cpp b/application/editormodeselftest.cpp index b74389cb..f1e529ad 100644 --- a/application/editormodeselftest.cpp +++ b/application/editormodeselftest.cpp @@ -65,6 +65,7 @@ editor_mode::selftest_result editor_mode::selftest_dispatch(std::string const &c // camera from the first point, looking at the second // select // selectat the path nearest the point, within 3 m, as a click on it +// kbreak K on the track nearest the point: split there, a break of the grade at the height of the ground // blade [name] logs the blades of each switch cut from the rails: tip, 50 mm head, full head, heel, drop of the head, open flangeway, the gauge lines apart against x^2/2R, vertices in both positions // frog [name] logs where the inner rails of each switch cross: distance on both paths, 1:n, throat, nose, check rails // guards [name] logs each guard rail and what each of its ends and breaks is cut along; a fault when a cut is not along a rail or a glued end not on the guard rail of the other track diff --git a/application/editormodeselftestscene.cpp b/application/editormodeselftestscene.cpp index b939d597..49e550d2 100644 --- a/application/editormodeselftestscene.cpp +++ b/application/editormodeselftestscene.cpp @@ -93,6 +93,37 @@ editor_mode::selftest_result editor_mode::selftest_scene(std::string const &comm WriteLog(format("SELFTEST selectat %.1f %.1f: %s", point.x, point.z, m_node != nullptr ? m_node->name().c_str() : "nothing")); return selftest_result::handled; } + if (command == "kbreak") + { + // K on the track nearest the point: split there, the point between at the height of the ground and the ballast + glm::dvec3 point{0.0}; + words >> point.x >> point.z; + TTrack *nearest{nullptr}; + double distance{1e9}; + for (auto *track : simulation::Paths.sequence()) + if (track != nullptr && false == track->m_editorremoved && false == track->m_paths.empty() && track->eType == tt_Normal) + if (auto const d{geometry::plan_distance(editor_track::point_at(*track, editor_track::nearest_parameter(*track, point)), point)}; d < distance) + { + distance = d; + nearest = track; + } + if (nearest == nullptr || distance > 3.0) + { + WriteLog(format("SELFTEST kbreak %.1f %.1f: no track", point.x, point.z)); + return selftest_result::handled; + } + auto const tracks{simulation::Paths.sequence().size()}; + split_track_at(*nearest, point); + auto const &path{nearest->m_paths.front()}; + auto const joint{path.points[segment_data::point::end]}; + std::vector found; + auto const ground{ground_heights({glm::dvec3{joint.x, joint.y + 50.0, joint.z}}, true, &found).front()}; + geometry::bezier const curve{path}; + auto const rise = [](glm::dvec3 const &Tangent) { return 1000.0 * Tangent.y / std::max(1e-9, glm::length(geometry::plan_of(Tangent))); }; + WriteLog(format("SELFTEST kbreak %s: %zu tracks more, joint %.2f %.3f %.2f, ground %.3f (found %d), grade %.2f -> %.2f permille", nearest->name().c_str(), simulation::Paths.sequence().size() - tracks, joint.x, joint.y, joint.z, ground, found[0], rise(curve.first(0.0)), + rise(curve.first(1.0)))); + return selftest_result::handled; + } if (command == "select") { std::string name; diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 8b800cd9..df0c751e 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1800,7 +1800,7 @@ std::vector editor_mode::track_key_hints(bool const All) { if (gizmo && m_gizmo_snap > 0.0f) hints.push_back({"Ctrl", format("gizmo snaps by %.2f m", m_gizmo_snap)}); - hints.push_back({"K", "split the path under the cursor"}); + hints.push_back({"K", "split the path under the cursor, a break of the grade at the height of the ground"}); hints.push_back({"O", "top view"}); hints.push_back({"Esc", "back to Select"}); if (Global.EditorOrtho) diff --git a/application/editormodetrackux.cpp b/application/editormodetrackux.cpp index 64fe2d8c..4e522a90 100644 --- a/application/editormodetrackux.cpp +++ b/application/editormodetrackux.cpp @@ -1076,6 +1076,22 @@ void editor_mode::split_track_at(TTrack &Track, glm::dvec3 const &Point) auto const before{editor_track::capture(Track)}; if (auto *created{editor_track::split_path(Track, parameter)}) { + // a break of the grade, as a point of a road: the point between the two takes the height of the ground and the ballast + // there, each of them an even grade from its other end up to it + auto const &path{Track.m_paths.front()}; + auto const joint{path.points[segment_data::point::end]}; + auto const height{bed_height(joint, geometry::plan_of(geometry::bezier{path}.first(1.0)), &Track)}; + auto const none{std::numeric_limits::quiet_NaN()}; + auto const unknown = [](std::vector const &Points, std::vector const &) { return std::vector(Points.size(), std::numeric_limits::quiet_NaN()); }; + for (auto *part : {&Track, created}) + { + std::vector pieces{part->m_paths.front()}; + auto &points{pieces.front().points}; + points[part == &Track ? segment_data::point::end : segment_data::point::start].y = height; + ground_follow::follow(pieces, {points[segment_data::point::start].y, none, points[segment_data::point::end].y, none}, unknown); + part->m_paths.front() = pieces.front(); + } + editor_track::commit({&Track, created}); push_track_snapshot({{&Track, before}}, {created}); straight_refresh(); } @@ -1147,7 +1163,7 @@ void editor_mode::render_track_context() ImGui::EndMenu(); } ImGui::Separator(); - if (ImGui::MenuItem(STR_C("Split here"), "K", false, normal)) + if (ImGui::MenuItem(STR_C("Split here, a break of the grade"), "K", false, normal)) split_track_at(*track, point); ImGui::Separator(); if (ImGui::MenuItem(STR_C("Switch"), "T", false, normal || track->eType == tt_Switch)) diff --git a/lang/editor_pl.po b/lang/editor_pl.po index aeaf3758..69b45229 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -240,8 +240,8 @@ msgstr "zastosuj" msgid "release the vertex" msgstr "puszcza wierzchołek" -msgid "split the path under the cursor" -msgstr "podziel tor pod kursorem" +msgid "split the path under the cursor, a break of the grade at the height of the ground" +msgstr "podziel tor pod kursorem, załamanie niwelety na wysokości terenu" msgid "top view" msgstr "widok z góry" @@ -1743,8 +1743,8 @@ msgstr "Rozstaw torów (m)" msgid "Speed check of the line" msgstr "Sprawdź prędkość linii" -msgid "Split here" -msgstr "Podziel tutaj" +msgid "Split here, a break of the grade" +msgstr "Podziel tutaj, załamanie niwelety" msgid "Step (m)" msgstr "Uskok (m)" @@ -4083,8 +4083,8 @@ msgstr "Przyciąganie do kroku z ustawień gizma" msgid "Split the road at the cursor, or make a point there" msgstr "Podziel drogę przy kursorze albo zrób tam punkt" -msgid "Split the selected track at the cursor" -msgstr "Podziel zaznaczony tor przy kursorze" +msgid "Split the selected track at the cursor, a break of the grade at the height of the ground" +msgstr "Podziel zaznaczony tor przy kursorze, załamanie niwelety na wysokości terenu" msgid "Takes the last change back" msgstr "Cofa ostatnią zmianę"