From 6a5f0d735db30d8dd7fbb60e939ac8776aceb581 Mon Sep 17 00:00:00 2001 From: maj00r Date: Wed, 21 Oct 2026 17:47:42 +0200 Subject: [PATCH] feat(editor): the tracks of the track mode can be coloured by their radius, speed or grade, with a legend View > Track colours draws the rail tracks over the view in bands of the radius of their paths, of their speed or of the grade along them, from above over the whole view and in the 3D view within 1.5 km of the camera, with the legend of the bands in the bottom right corner. Plain keeps the lines of the top view as they were. Self-test command trackcolours. --- application/editormode.cpp | 13 ++++ application/editormode.h | 4 ++ application/editormodeselftesttrack.cpp | 7 ++ application/editormodetrackux.cpp | 87 +++++++++++++++++++++++-- lang/editor_pl.po | 18 +++++ 5 files changed, 125 insertions(+), 4 deletions(-) diff --git a/application/editormode.cpp b/application/editormode.cpp index 9a7da798..b0bc1214 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -1458,7 +1458,10 @@ bool editor_mode::update() render_track_inspector(); if (ui()->mode() == nodebank_panel::TRACK) + { draw_track_lines(); + draw_track_legend(); + } if (selected_track()) draw_track_overlay(); if (route_active()) @@ -3579,6 +3582,16 @@ void editor_mode::render_view_menu() } if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", STR_C("Off: the top view draws the rails and sleepers of the tracks without the ballast, so the ground under them shows")); + if (ImGui::BeginMenu(STR_C("Track colours"))) + { + char const *const names[]{"Plain", "By the radius", "By the speed", "By the grade"}; + for (int i = 0; i < 4; ++i) + if (ImGui::MenuItem(STR_C(names[i]), nullptr, static_cast(m_track_colouring) == i)) + m_track_colouring = static_cast(i); + ImGui::EndMenu(); + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", STR_C("The tracks of the track mode coloured by their radius, speed or grade, with the legend in the corner")); ImGui::MenuItem(STR_C("Show lanes"), nullptr, &m_roadtool.lanes); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", STR_C("The lanes of the roads drawn as the paths the vehicles take")); diff --git a/application/editormode.h b/application/editormode.h index 19114605..15be6dac 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -1570,6 +1570,10 @@ class editor_mode : public application_mode, private editor_track::observer, pri track_hover split_spot() const; void update_track_hover(); void draw_track_lines() const; + // what the colours of the tracks in the view tell + enum class track_colouring { plain, radius, speed, grade }; + track_colouring m_track_colouring{track_colouring::plain}; + void draw_track_legend() const; void draw_track_hover() const; struct track_intent { diff --git a/application/editormodeselftesttrack.cpp b/application/editormodeselftesttrack.cpp index 9068b20a..021669ae 100644 --- a/application/editormodeselftesttrack.cpp +++ b/application/editormodeselftesttrack.cpp @@ -396,6 +396,13 @@ editor_mode::selftest_result editor_mode::selftest_track(std::string const &comm WriteLog(format("SELFTEST vprofile zone %.3f %.3f %s", zone.from, zone.to, zone.name.c_str())); return selftest_result::handled; } + if (command == "trackcolours") + { + int colouring{0}; + words >> colouring; + m_track_colouring = static_cast(std::clamp(colouring, 0, 3)); + return selftest_result::handled; + } if (command == "vprofilerun" || command == "vprofiledel" || command == "vprofileput") { // the grade line of the line through a track, a point of it taken out as with Del, the line applied diff --git a/application/editormodetrackux.cpp b/application/editormodetrackux.cpp index 8b210d33..f8c151dc 100644 --- a/application/editormodetrackux.cpp +++ b/application/editormodetrackux.cpp @@ -36,6 +36,7 @@ http://mozilla.org/MPL/2.0/. #include #include #include +#include namespace { @@ -82,6 +83,31 @@ double ortho_metres_per_pixel() return Global.EditorOrtho && height > 0.0f ? 2.0 * static_cast(Global.EditorOrthoExtent) / static_cast(height) : 0.0; } +// the bands of the colouring of the tracks: up to the value, the colour and its label in the legend +struct colour_band +{ + double below; + ImU32 colour; + char const *label; +}; +double constexpr kBeyond{std::numeric_limits::max()}; +ImU32 const kUncoloured{IM_COL32(200, 200, 200, 170)}; +double const kColouredReach{1500.0}; // m around the camera in the 3D view +std::vector const kRadiusBands{{300.0, IM_COL32(235, 60, 60, 235), "R < 300 m"}, {600.0, IM_COL32(245, 140, 50, 235), "R 300-600 m"}, {1200.0, IM_COL32(240, 220, 60, 235), "R 600-1200 m"}, + {3000.0, IM_COL32(110, 210, 80, 235), "R 1200-3000 m"}, {kBeyond, IM_COL32(70, 190, 235, 235), "R > 3000 m"}}; +std::vector const kSpeedBands{{40.5, IM_COL32(235, 60, 60, 235), "<= 40 km/h"}, {60.5, IM_COL32(245, 140, 50, 235), "50-60 km/h"}, {80.5, IM_COL32(240, 220, 60, 235), "70-80 km/h"}, + {100.5, IM_COL32(110, 210, 80, 235), "90-100 km/h"}, {120.5, IM_COL32(70, 190, 235, 235), "110-120 km/h"}, {kBeyond, IM_COL32(170, 120, 255, 235), "> 120 km/h"}}; +std::vector const kGradeBands{{2.0, kUncoloured, "0-2 \xe2\x80\xb0"}, {5.0, IM_COL32(110, 210, 80, 235), "2-5 \xe2\x80\xb0"}, {10.0, IM_COL32(240, 220, 60, 235), "5-10 \xe2\x80\xb0"}, + {20.0, IM_COL32(245, 140, 50, 235), "10-20 \xe2\x80\xb0"}, {kBeyond, IM_COL32(235, 60, 60, 235), "> 20 \xe2\x80\xb0"}}; + +ImU32 band_colour(std::vector const &Bands, double const Value) +{ + for (auto const &band : Bands) + if (Value < band.below) + return band.colour; + return kUncoloured; +} + bool plan_near(bezier const &Curve, glm::dvec2 const &Point, double const Reach) { auto const centre{(plan_of(Curve.p0) + plan_of(Curve.p1) + plan_of(Curve.p2) + plan_of(Curve.p3)) * 0.25}; @@ -172,34 +198,87 @@ void editor_mode::update_track_hover() void editor_mode::draw_track_lines() const { auto const metres{ortho_metres_per_pixel()}; - if (metres < kLinesFrom) + auto const colouring{m_track_colouring}; + if (metres < kLinesFrom && colouring == track_colouring::plain) return; auto const &io{ImGui::GetIO()}; screen_projection const projection; glm::dvec2 const centre{Global.pCamera.Pos.x, Global.pCamera.Pos.z}; - auto const reach{0.5 * metres * std::hypot(static_cast(io.DisplaySize.x), static_cast(io.DisplaySize.y))}; + auto const reach{Global.EditorOrtho ? 0.5 * metres * std::hypot(static_cast(io.DisplaySize.x), static_cast(io.DisplaySize.y)) : kColouredReach}; + auto const width{colouring == track_colouring::plain ? 1.5f : 3.0f}; auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())}; for (auto *track : simulation::Paths.sequence()) { if (track == nullptr || false == rail(*track)) continue; + auto const velocity{editor_track::velocity(*track)}; for (auto const &path : track->m_paths) { bezier const curve{path}; if (false == plan_near(curve, centre, reach)) continue; - auto const count{std::clamp(static_cast(curve.plan_length(4) / metres / 8.0), 1, 32)}; + auto colour{IM_COL32(235, 235, 235, 150)}; + if (colouring == track_colouring::radius) + { + auto const radius{::track_mode::piece_plan_radius(path)}; + colour = radius >= 0.5 ? band_colour(kRadiusBands, radius) : kUncoloured; + } + else if (colouring == track_colouring::speed) + colour = velocity > 0.0 ? band_colour(kSpeedBands, velocity) : kUncoloured; + auto const length{curve.plan_length(4)}; + auto const count{std::clamp(static_cast(length / std::max(metres, 0.5) / 8.0), colouring == track_colouring::grade ? std::clamp(static_cast(length / 10.0), 1, 8) : 1, 32)}; auto previous{curve.point(0.0)}; for (int k = 1; k <= count; ++k) { auto const next{curve.point(static_cast(k) / count)}; - projection.line(drawlist, previous, next, IM_COL32(235, 235, 235, 150), 1.5f); + if (colouring == track_colouring::grade) + { + auto const run{geometry::plan_distance(previous, next)}; + colour = run > 1e-3 ? band_colour(kGradeBands, std::abs(next.y - previous.y) / run * 1000.0) : kUncoloured; + } + projection.line(drawlist, previous, next, colour, width); previous = next; } } } } +void editor_mode::draw_track_legend() const +{ + if (m_track_colouring == track_colouring::plain) + return; + auto const &bands{m_track_colouring == track_colouring::radius ? kRadiusBands : m_track_colouring == track_colouring::speed ? kSpeedBands : kGradeBands}; + std::vector> rows; + for (auto const &band : bands) + rows.emplace_back(band.colour, band.label); + if (m_track_colouring == track_colouring::radius) + rows.emplace_back(kUncoloured, STR("straight")); + else if (m_track_colouring == track_colouring::speed) + rows.emplace_back(kUncoloured, STR("no limit")); + char const *const titles[]{"", "By the radius", "By the speed", "By the grade"}; + std::string const title{STR_C(titles[static_cast(m_track_colouring)])}; + auto const line{ImGui::GetTextLineHeightWithSpacing()}; + auto const swatch{ImGui::GetTextLineHeight()}; + float width{ImGui::CalcTextSize(title.c_str()).x}; + for (auto const &row : rows) + width = std::max(width, swatch + 6.0f + ImGui::CalcTextSize(row.second.c_str()).x); + auto const pad{8.0f}; + // in the bottom right corner, over the profile strip when it's open + auto const bottom{m_profile.strip_top > editor_ui::view_min().y + 4.0f * line ? std::min(editor_ui::view_max().y, m_profile.strip_top) : editor_ui::view_max().y}; + ImVec2 const to{editor_ui::view_max().x - 12.0f, bottom - 12.0f}; + ImVec2 const from{to.x - width - pad * 2.0f, to.y - pad * 2.0f - line * static_cast(rows.size() + 1)}; + auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())}; + drawlist->AddRectFilled(from, to, IM_COL32(0, 0, 0, 170), 4.0f); + ImVec2 at{from.x + pad, from.y + pad}; + drawlist->AddText(at, IM_COL32(255, 255, 255, 235), title.c_str()); + for (auto const &row : rows) + { + at.y += line; + drawlist->AddRectFilled(ImVec2(at.x, at.y + 2.0f), ImVec2(at.x + swatch, at.y + swatch - 2.0f), row.first, 2.0f); + drawlist->AddText(ImVec2(at.x + swatch + 6.0f, at.y), IM_COL32(235, 235, 235, 235), row.second.c_str()); + } +} + void editor_mode::draw_track_hover() const { using kind = track_intent::kind; diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 8cebb007..0b6ca255 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -10105,3 +10105,21 @@ msgstr "pochylenie %+.1f‰" msgid "Switched off in this build of the simulator" msgstr "Wyłączone w tej wersji symulatora" + +msgid "Track colours" +msgstr "Kolory torów" + +msgid "Plain" +msgstr "Zwykłe" + +msgid "By the radius" +msgstr "Według promienia" + +msgid "By the speed" +msgstr "Według prędkości" + +msgid "By the grade" +msgstr "Według pochylenia" + +msgid "The tracks of the track mode coloured by their radius, speed or grade, with the legend in the corner" +msgstr "Tory w dziale Tory pokolorowane według promienia, prędkości albo pochylenia, z legendą w rogu"