17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 03:59:19 +02:00

feat: GPL-1/GPL-2 structure gauges, widening run-in ahead of the curves

This commit is contained in:
maj00r
2026-10-04 01:40:25 +02:00
parent b0c363b586
commit cf8062ea96
3 changed files with 161 additions and 60 deletions

View File

@@ -30,7 +30,7 @@ using geometry::plan_of;
namespace 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}; std::size_t const gauge_triangle_limit{200000};
// samples of the route, found by the position in the plan // samples of the route, found by the position in the plan
@@ -122,16 +122,16 @@ private:
std::vector<std::vector<std::size_t>> m_cells; std::vector<std::vector<std::size_t>> m_cells;
}; };
gauge::section section_of(editor_track::route_sample const &Sample) std::vector<gauge::section> sections_of(std::vector<editor_track::route_sample> const &Samples)
{ {
gauge::section section; std::vector<double> chainage, curvature, cant;
if (std::abs(Sample.curvature) > 1e-5) for (auto const &sample : Samples)
{ {
section.radius = 1.0 / std::abs(Sample.curvature); chainage.push_back(sample.chainage);
section.inner = Sample.curvature > 0.0 ? 1 : -1; curvature.push_back(sample.curvature);
cant.push_back(sample.cant);
} }
section.cant = Sample.cant; return gauge::sections(chainage, curvature, cant);
return section;
} }
glm::dmat4 placement(TAnimModel const &Instance) 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) if (samples.size() < 2 || profile.outline.size() < 2)
return; return;
double widest{0.0}, top{0.0}; auto const sections{sections_of(samples)};
double widest{0.0};
for (auto const &point : profile.outline) for (auto const &point : profile.outline)
{
widest = std::max(widest, point.half_width); widest = std::max(widest, point.half_width);
top = std::max(top, point.height); // room for the widening in the sharpest curves on the largest cant
} corridor const space{samples, widest + 1.2};
corridor const space{samples, widest + gauge::curve_widening(150.0) + gauge::cant_widening(0.2, top) + 0.5};
auto const origin{samples.front().position}; auto const origin{samples.front().position};
EditorOverlay.origin = origin; EditorOverlay.origin = origin;
auto const relative{[&](glm::dvec3 const &Point) { return glm::vec3(Point - 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 // 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 ring{[&](std::size_t const Index) {
auto const section{section_of(Sample)};
std::vector<glm::vec3> points; std::vector<glm::vec3> points;
for (int side : {-1, 1}) for (int side : {-1, 1})
for (std::size_t j = 0; j < profile.outline.size(); ++j) 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 const &point{profile.outline[side < 0 ? j : profile.outline.size() - 1 - j]};
auto width{point.half_width + gauge::curve_widening(section.radius)}; auto const width{point.half_width + gauge::widening(sections[Index], point.height, side)};
if (side == section.inner) points.push_back(relative(lift(samples[Index], side * width, point.height)));
width += gauge::cant_widening(section.cant, point.height);
points.push_back(relative(lift(Sample, side * width, point.height)));
} }
return points; return points;
}}; }};
@@ -255,7 +251,7 @@ void editor_mode::scan_gauge(TTrack &Track)
double lastring{-1e9}; double lastring{-1e9};
for (std::size_t i = 0; i < samples.size(); i += 2) 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()) if (false == previous.empty())
for (std::size_t j = 1; j < current.size(); ++j) 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 // triangle enters the gauge when any point of a grid spread over it does
auto const inside{[&](glm::dvec3 const &Point) { auto const inside{[&](glm::dvec3 const &Point) {
auto const local{space.locate(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()) 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)) if (ImGui::Checkbox("Show in the 3D view", &m_gauge.enabled))
m_gauge.pending = true; m_gauge.pending = true;
if (ImGui::IsItemHovered()) 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(); gauge_load();
auto const previous{m_gauge.profile}; auto const previous{m_gauge.profile};
for (int i = 0; i < static_cast<int>(m_gauge.profiles.size()); ++i) for (int i = 0; i < static_cast<int>(m_gauge.profiles.size()); ++i)
{ {
if (i > 0) if (i % 2 == 1)
ImGui::SameLine(); ImGui::SameLine(160.0f);
ImGui::RadioButton(m_gauge.profiles[i].name.c_str(), &m_gauge.profile, i); ImGui::RadioButton(m_gauge.profiles[i].name.c_str(), &m_gauge.profile, i);
} }
bool changed{previous != m_gauge.profile}; bool changed{previous != m_gauge.profile};
@@ -338,7 +334,7 @@ void editor_mode::render_gauge_ui()
if (ImGui::TreeNode("Outline (right half)")) 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]}; auto &profile{m_gauge.profiles[m_gauge.profile]};
bool edited{false}; bool edited{false};
bool inserted{false}; bool inserted{false};

View File

@@ -16,14 +16,94 @@ http://mozilla.org/MPL/2.0/.
namespace gauge 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<double> const &Chainage, std::vector<double> 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::size_t>(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<double> const &m_chainage;
std::vector<double> m_values;
std::vector<double> m_integral;
};
} // namespace
std::vector<profile> default_profiles() std::vector<profile> default_profiles()
{ {
std::vector<profile::point> 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}}; // limit installation gauge up to 1170 mm, then the outline of the unified gauge
profile plain{"A (bez sieci)", lower}; std::vector<profile::point> const lower{{1.211, 0.055}, {1.585, 0.380}, {1.675, 0.380}, {1.675, 1.170}, {2.000, 1.170}};
plain.outline.insert(plain.outline.end(), {{1.400, 4.85}, {0.0, 4.85}}); std::vector<profile::point> const gpl1{{2.000, 3.050}, {1.900, 3.850}, {1.800, 4.250}, {1.600, 4.500}, {1.450, 4.632}};
profile electrified{"C (sieć trakcyjna)", lower}; std::vector<profile::point> const gpl2{{2.000, 3.625}, {1.920, 4.900}};
electrified.outline.insert(electrified.outline.end(), {{1.700, 4.85}, {1.400, 5.60}, {1.000, 6.00}, {0.0, 6.00}}); std::vector<profile::point> const pantograph{{1.450, 6.600}, {1.150, 6.900}, {0.0, 6.900}};
return {plain, electrified}; auto const make{[&](std::string const &Name, std::vector<std::vector<profile::point>> 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 // 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<profile> const &Profiles
} }
} }
double curve_widening(double const Radius) std::vector<section> sections(std::vector<double> const &Chainage, std::vector<double> const &Curvature, std::vector<double> const &Cant)
{ {
// smallest radius of the class and its widening in mm; the radius between the classes takes the larger one auto const count{Chainage.size()};
static constexpr std::pair<double, double> table[]{{3500, 10}, {2500, 15}, {1800, 20}, {1500, 25}, {1200, 30}, {1000, 35}, std::vector<section> result(count);
{900, 40}, {800, 45}, {700, 50}, {600, 60}, {500, 75}, {450, 80}, if (count == 0)
{400, 90}, {350, 105}, {300, 120}, {280, 130}, {260, 140}, {250, 145}, return result;
{240, 150}, {220, 165}, {200, 180}, {190, 190}, {180, 200}}; // per side, the parts when it's the inner side of the curve and when it's the outer one
if (Radius <= 0.0 || Radius >= 4000.0) for (int side = 0; side < 2; ++side)
return 0.0; {
for (auto const &[radius, widening] : table) std::vector<double> upperinner(count), upperouter(count), lowerinner(count), lowerouter(count), cantinner(count), cantouter(count);
if (Radius >= radius) for (std::size_t i = 0; i < count; ++i)
return widening * 0.001; {
return 36.0 / Radius; 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) 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) if (width < 0.0)
return width; return width;
width += curve_widening(Section.radius); return width + widening(Section, Height, Side);
if (Section.inner != 0 && Side == Section.inner)
width += cant_widening(Section.cant, Height);
return width;
} }
double intrusion(profile const &Profile, section const &Section, double const Lateral, double const Height) double intrusion(profile const &Profile, section const &Section, double const Lateral, double const Height)

View File

@@ -9,10 +9,12 @@ http://mozilla.org/MPL/2.0/.
#pragma once #pragma once
#include <array>
#include <string> #include <string>
#include <vector> #include <vector>
// 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 namespace gauge
{ {
@@ -29,25 +31,29 @@ struct profile
std::vector<point> outline; std::vector<point> 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<profile> default_profiles(); std::vector<profile> default_profiles();
// outlines kept in the file, the defaults when there's none // outlines kept in the file, the defaults when there's none
std::vector<profile> load_profiles(std::string const &File); std::vector<profile> load_profiles(std::string const &File);
void save_profiles(std::string const &File, std::vector<profile> const &Profiles); void save_profiles(std::string const &File, std::vector<profile> const &Profiles);
// widening of both sides in a curve, from the radius (table 1 of the D1 annex 11), metres // below this height the outline is the limit installation gauge, which leaves the room for the platforms
double curve_widening(double Radius); double constexpr lower_part{1.17};
// 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);
// 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 struct section
{ {
double radius{0.0}; // 0: straight std::array<double, 2> upper{}; // from the radius, above the lower part
int inner{0}; // side of the curve centre, +1 left or -1 right, 0 on a straight std::array<double, 2> lower{}; // from the radius, in the lower part
double cant{0.0}; // metres std::array<double, 2> 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<section> sections(std::vector<double> const &Chainage, std::vector<double> const &Curvature, std::vector<double> 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); 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 // 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); double intrusion(profile const &Profile, section const &Section, double Lateral, double Height);