From cf8062ea966f0fd84f8053f917a248a2a487315f Mon Sep 17 00:00:00 2001 From: maj00r Date: Sun, 4 Oct 2026 01:40:25 +0200 Subject: [PATCH] feat: GPL-1/GPL-2 structure gauges, widening run-in ahead of the curves --- application/editormodegauge.cpp | 46 +++++----- editor/editorGauge.cpp | 147 ++++++++++++++++++++++++++------ editor/editorGauge.hpp | 28 +++--- 3 files changed, 161 insertions(+), 60 deletions(-) diff --git a/application/editormodegauge.cpp b/application/editormodegauge.cpp index 2fbd9192..731cf6cc 100644 --- a/application/editormodegauge.cpp +++ b/application/editormodegauge.cpp @@ -30,7 +30,7 @@ using geometry::plan_of; namespace { -char const *const gauge_file{"editor_gauges.txt"}; +char const *const gauge_file{"editor_structure_gauges.txt"}; std::size_t const gauge_triangle_limit{200000}; // samples of the route, found by the position in the plan @@ -122,16 +122,16 @@ private: std::vector> m_cells; }; -gauge::section section_of(editor_track::route_sample const &Sample) +std::vector sections_of(std::vector const &Samples) { - gauge::section section; - if (std::abs(Sample.curvature) > 1e-5) + std::vector chainage, curvature, cant; + for (auto const &sample : Samples) { - section.radius = 1.0 / std::abs(Sample.curvature); - section.inner = Sample.curvature > 0.0 ? 1 : -1; + chainage.push_back(sample.chainage); + curvature.push_back(sample.curvature); + cant.push_back(sample.cant); } - section.cant = Sample.cant; - return section; + return gauge::sections(chainage, curvature, cant); } glm::dmat4 placement(TAnimModel const &Instance) @@ -217,13 +217,12 @@ void editor_mode::scan_gauge(TTrack &Track) if (samples.size() < 2 || profile.outline.size() < 2) return; - double widest{0.0}, top{0.0}; + auto const sections{sections_of(samples)}; + double widest{0.0}; for (auto const &point : profile.outline) - { widest = std::max(widest, point.half_width); - top = std::max(top, point.height); - } - corridor const space{samples, widest + gauge::curve_widening(150.0) + gauge::cant_widening(0.2, top) + 0.5}; + // room for the widening in the sharpest curves on the largest cant + corridor const space{samples, widest + 1.2}; auto const origin{samples.front().position}; EditorOverlay.origin = origin; auto const relative{[&](glm::dvec3 const &Point) { return glm::vec3(Point - origin); }}; @@ -233,17 +232,14 @@ void editor_mode::scan_gauge(TTrack &Track) }}; // outline across the track, from the bottom right up and down to the bottom left - auto const ring{[&](editor_track::route_sample const &Sample) { - auto const section{section_of(Sample)}; + auto const ring{[&](std::size_t const Index) { std::vector points; for (int side : {-1, 1}) for (std::size_t j = 0; j < profile.outline.size(); ++j) { auto const &point{profile.outline[side < 0 ? j : profile.outline.size() - 1 - j]}; - auto width{point.half_width + gauge::curve_widening(section.radius)}; - if (side == section.inner) - width += gauge::cant_widening(section.cant, point.height); - points.push_back(relative(lift(Sample, side * width, point.height))); + auto const width{point.half_width + gauge::widening(sections[Index], point.height, side)}; + points.push_back(relative(lift(samples[Index], side * width, point.height))); } return points; }}; @@ -255,7 +251,7 @@ void editor_mode::scan_gauge(TTrack &Track) double lastring{-1e9}; for (std::size_t i = 0; i < samples.size(); i += 2) { - auto const current{ring(samples[i])}; + auto const current{ring(i)}; if (false == previous.empty()) for (std::size_t j = 1; j < current.size(); ++j) { @@ -274,7 +270,7 @@ void editor_mode::scan_gauge(TTrack &Track) // triangle enters the gauge when any point of a grid spread over it does auto const inside{[&](glm::dvec3 const &Point) { auto const local{space.locate(Point)}; - return local && gauge::intrusion(profile, section_of(samples[local->sample]), local->lateral, local->height) > 0.0; + return local && gauge::intrusion(profile, sections[local->sample], local->lateral, local->height) > 0.0; }}; for (auto *instance : simulation::Instances.sequence()) { @@ -314,13 +310,13 @@ void editor_mode::render_gauge_ui() if (ImGui::Checkbox("Show in the 3D view", &m_gauge.enabled)) m_gauge.pending = true; if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Translucent tunnel of the gauge along the line of the selected path,\nwith the triangles of the models which enter it in red. Widened in curves and on the inner side of a cant."); + ImGui::SetTooltip("Translucent tunnel of the gauge along the line of the selected path,\nwith the triangles of the models which enter it in red.\nWidened in the curves under 250 m and tilted on the cant, from 20 m (inner side)\nand 26 m (outer side) ahead of the curve, as in the PKP PLK standard, volume II."); gauge_load(); auto const previous{m_gauge.profile}; for (int i = 0; i < static_cast(m_gauge.profiles.size()); ++i) { - if (i > 0) - ImGui::SameLine(); + if (i % 2 == 1) + ImGui::SameLine(160.0f); ImGui::RadioButton(m_gauge.profiles[i].name.c_str(), &m_gauge.profile, i); } bool changed{previous != m_gauge.profile}; @@ -338,7 +334,7 @@ void editor_mode::render_gauge_ui() if (ImGui::TreeNode("Outline (right half)")) { - ImGui::TextDisabled("Approximate starting points, check against PN-69/K-02057.\nMetres from the track axis and above the rail top, bottom up."); + ImGui::TextDisabled("PKP PLK standard, volume II; below 1170 mm the limit installation gauge.\nMetres from the track axis and above the rail top, bottom up."); auto &profile{m_gauge.profiles[m_gauge.profile]}; bool edited{false}; bool inserted{false}; diff --git a/editor/editorGauge.cpp b/editor/editorGauge.cpp index 937feb5b..59acae31 100644 --- a/editor/editorGauge.cpp +++ b/editor/editorGauge.cpp @@ -16,14 +16,94 @@ http://mozilla.org/MPL/2.0/. namespace gauge { +namespace +{ + +// the widening runs up ahead of the change: on the inner side from 20 m before it, on the outer side from 26 m +// to 6 m before it (figures 3 to 5 of the standard) +double constexpr inner_lead{20.0}; +double constexpr outer_lead{26.0}; +double constexpr outer_full{6.0}; + +struct side_widening +{ + double inner{0.0}; + double outer{0.0}; +}; + +// above the lower part the gauge includes the curves of R >= 250 m already +side_widening upper_widening(double const Radius) +{ + if (Radius >= 250.0) + return {}; + return {std::max(0.0, 50.0 / Radius - 0.185), std::max(0.0, 60.0 / Radius - 0.225)}; +} + +side_widening lower_widening(double const Radius) +{ + if (Radius >= 250.0) + return {3.75 / Radius, 3.75 / Radius}; + return upper_widening(Radius); +} + +// values at the points of a route, read as linear between them and continued past the ends +class field +{ +public: + field(std::vector const &Chainage, std::vector Values) : m_chainage{Chainage}, m_values{std::move(Values)}, m_integral(m_values.size(), 0.0) + { + for (std::size_t i = 1; i < m_values.size(); ++i) + m_integral[i] = m_integral[i - 1] + 0.5 * (m_values[i - 1] + m_values[i]) * (m_chainage[i] - m_chainage[i - 1]); + } + + double mean(double const From, double const To) const + { + return (integral(To) - integral(From)) / (To - From); + } + + // larger of the means over the window placed ahead of the point in both directions + double ahead(double const Chainage, double const Near, double const Far) const + { + return std::max(mean(Chainage + Near, Chainage + Far), mean(Chainage - Far, Chainage - Near)); + } + +private: + double integral(double const Chainage) const + { + if (Chainage <= m_chainage.front()) + return m_values.front() * (Chainage - m_chainage.front()); + if (Chainage >= m_chainage.back()) + return m_integral.back() + m_values.back() * (Chainage - m_chainage.back()); + auto const next{static_cast(std::upper_bound(m_chainage.begin(), m_chainage.end(), Chainage) - m_chainage.begin())}; + auto const i{next - 1}; + auto const span{m_chainage[next] - m_chainage[i]}; + auto const offset{Chainage - m_chainage[i]}; + auto const value{span > 0.0 ? m_values[i] + (m_values[next] - m_values[i]) * offset / span : m_values[i]}; + return m_integral[i] + 0.5 * (m_values[i] + value) * offset; + } + + std::vector const &m_chainage; + std::vector m_values; + std::vector m_integral; +}; + +} // namespace + std::vector default_profiles() { - std::vector const lower{{1.645, 0.05}, {1.645, 0.30}, {1.720, 0.30}, {1.720, 1.10}, {2.200, 1.10}, {2.200, 3.85}, {1.900, 4.25}}; - profile plain{"A (bez sieci)", lower}; - plain.outline.insert(plain.outline.end(), {{1.400, 4.85}, {0.0, 4.85}}); - profile electrified{"C (sieć trakcyjna)", lower}; - electrified.outline.insert(electrified.outline.end(), {{1.700, 4.85}, {1.400, 5.60}, {1.000, 6.00}, {0.0, 6.00}}); - return {plain, electrified}; + // limit installation gauge up to 1170 mm, then the outline of the unified gauge + std::vector const lower{{1.211, 0.055}, {1.585, 0.380}, {1.675, 0.380}, {1.675, 1.170}, {2.000, 1.170}}; + std::vector const gpl1{{2.000, 3.050}, {1.900, 3.850}, {1.800, 4.250}, {1.600, 4.500}, {1.450, 4.632}}; + std::vector const gpl2{{2.000, 3.625}, {1.920, 4.900}}; + std::vector const pantograph{{1.450, 6.600}, {1.150, 6.900}, {0.0, 6.900}}; + auto const make{[&](std::string const &Name, std::vector> const &Parts) { + profile result{Name, lower}; + for (auto const &part : Parts) + result.outline.insert(result.outline.end(), part.begin(), part.end()); + return result; + }}; + return {make("GPL-1", {gpl1, {{1.260, 4.800}, {1.180, 4.850}, {0.0, 4.850}}}), make("GPL-1 + pantograf", {gpl1, pantograph}), + make("GPL-2", {gpl2, {{0.0, 4.900}}}), make("GPL-2 + pantograf", {gpl2, {{1.450, 4.900}}, pantograph})}; } // one outline per line: the name in quotes, then pairs of half-width and height @@ -59,24 +139,46 @@ void save_profiles(std::string const &File, std::vector const &Profiles } } -double curve_widening(double const Radius) +std::vector
sections(std::vector const &Chainage, std::vector const &Curvature, std::vector const &Cant) { - // smallest radius of the class and its widening in mm; the radius between the classes takes the larger one - static constexpr std::pair table[]{{3500, 10}, {2500, 15}, {1800, 20}, {1500, 25}, {1200, 30}, {1000, 35}, - {900, 40}, {800, 45}, {700, 50}, {600, 60}, {500, 75}, {450, 80}, - {400, 90}, {350, 105}, {300, 120}, {280, 130}, {260, 140}, {250, 145}, - {240, 150}, {220, 165}, {200, 180}, {190, 190}, {180, 200}}; - if (Radius <= 0.0 || Radius >= 4000.0) - return 0.0; - for (auto const &[radius, widening] : table) - if (Radius >= radius) - return widening * 0.001; - return 36.0 / Radius; + auto const count{Chainage.size()}; + std::vector
result(count); + if (count == 0) + return result; + // per side, the parts when it's the inner side of the curve and when it's the outer one + for (int side = 0; side < 2; ++side) + { + std::vector upperinner(count), upperouter(count), lowerinner(count), lowerouter(count), cantinner(count), cantouter(count); + for (std::size_t i = 0; i < count; ++i) + { + if (std::abs(Curvature[i]) < 1e-6) + continue; + auto const radius{1.0 / std::abs(Curvature[i])}; + bool const inner{(Curvature[i] > 0.0) == (side == 1)}; + auto const upper{upper_widening(radius)}; + auto const lower{lower_widening(radius)}; + (inner ? upperinner : upperouter)[i] = inner ? upper.inner : upper.outer; + (inner ? lowerinner : lowerouter)[i] = inner ? lower.inner : lower.outer; + (inner ? cantinner : cantouter)[i] = Cant[i]; + } + field const fields[]{{Chainage, std::move(upperinner)}, {Chainage, std::move(upperouter)}, {Chainage, std::move(lowerinner)}, + {Chainage, std::move(lowerouter)}, {Chainage, std::move(cantinner)}, {Chainage, std::move(cantouter)}}; + for (std::size_t i = 0; i < count; ++i) + { + auto const inner{[&](field const &Field) { return Field.ahead(Chainage[i], 0.0, inner_lead); }}; + auto const outer{[&](field const &Field) { return Field.ahead(Chainage[i], outer_full, outer_lead); }}; + result[i].upper[side] = inner(fields[0]) + outer(fields[1]); + result[i].lower[side] = inner(fields[2]) + outer(fields[3]); + result[i].cant[side] = inner(fields[4]) - outer(fields[5]); + } + } + return result; } -double cant_widening(double const Cant, double const Height) +double widening(section const &Section, double const Height, int const Side) { - return Height * Cant / 1.5; + auto const i{Side > 0 ? 1 : 0}; + return (Height < lower_part ? Section.lower[i] : Section.upper[i]) + Section.cant[i] * Height / 1.5; } double half_width(profile const &Profile, section const &Section, double const Height, int const Side) @@ -93,10 +195,7 @@ double half_width(profile const &Profile, section const &Section, double const H } if (width < 0.0) return width; - width += curve_widening(Section.radius); - if (Section.inner != 0 && Side == Section.inner) - width += cant_widening(Section.cant, Height); - return width; + return width + widening(Section, Height, Side); } double intrusion(profile const &Profile, section const &Section, double const Lateral, double const Height) diff --git a/editor/editorGauge.hpp b/editor/editorGauge.hpp index e4d235bc..7ef65bbf 100644 --- a/editor/editorGauge.hpp +++ b/editor/editorGauge.hpp @@ -9,10 +9,12 @@ http://mozilla.org/MPL/2.0/. #pragma once +#include #include #include -// structure gauge (skrajnia budowli): the space along the track which has to stay clear of the objects +// structure gauge (skrajnia budowli) of the PKP PLK technical standards, volume II: the space along the track +// which has to stay clear of the objects namespace gauge { @@ -29,25 +31,29 @@ struct profile std::vector outline; }; -// starting points of the outlines, approximations to verify against PN-69/K-02057 +// GPL-1 and GPL-2 over the limit installation gauge below 1170 mm, with and without the pantograph gauge std::vector default_profiles(); // outlines kept in the file, the defaults when there's none std::vector load_profiles(std::string const &File); void save_profiles(std::string const &File, std::vector const &Profiles); -// widening of both sides in a curve, from the radius (table 1 of the D1 annex 11), metres -double curve_widening(double Radius); -// widening of the inner side of a curve from the tilt of the vehicles on the cant, at the height above the rail top, metres -double cant_widening(double Cant, double Height); +// below this height the outline is the limit installation gauge, which leaves the room for the platforms +double constexpr lower_part{1.17}; -// place of the gauge across the track +// widening of the sides of the gauge at a place of the track, metres; index 0 is the right side, 1 the left one struct section { - double radius{0.0}; // 0: straight - int inner{0}; // side of the curve centre, +1 left or -1 right, 0 on a straight - double cant{0.0}; // metres + std::array upper{}; // from the radius, above the lower part + std::array lower{}; // from the radius, in the lower part + std::array cant{}; // cant in metres, positive on the inner side of the curve and negative on the outer one }; -// half-width of the gauge at the height on the side (+1 left, -1 right), negative above or below the outline +// sections at the points of a route, from their chainage, curvature (1/m, positive turning left) and cant (m). +// The widening begins and ends ahead of the changes of the curvature and cant, as the vehicles enter a curve +std::vector
sections(std::vector const &Chainage, std::vector const &Curvature, std::vector const &Cant); + +// widening of the side (+1 left, -1 right) at the height +double widening(section const &Section, double Height, int Side); +// half-width of the gauge at the height on the side, negative above or below the outline double half_width(profile const &Profile, section const &Section, double Height, int Side); // depth of the point inside the gauge, metres, not positive outside of it. Lateral positive to the left double intrusion(profile const &Profile, section const &Section, double Lateral, double Height);