17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 02:12:27 +01:00

feat(editor): a switch laid into a curve is checked by ST-T1-A6 as it's placed: the radii of its main and diverging tracks at its start and its end shown at it; warnings where the curvature of a track turns from one side to the other within it (KZW II, 9.7 pkt 12), where the diverging track is sharper or the main track bent more than table 11.9 allows the basic switch (the values allowed by way of exception named), where a switch of 1:9.403 or steeper is bent (11.4 pkt 4), where the cant changes within it (11.4 pkt 7), where a cant ramp starts or ends within it or under 6 m off it (9.6 pkt 16-17) and where the cant is over 100 mm (table 9.6); a note on a transition curve, which only an exception allows. The ends of the cant ramps are marked on the curve, and the start of the switch pressed within 3 m of where it starts or ends at an end of a ramp, or keeps the 6 m off it, is drawn there. The warnings stay in the status once it's laid. The checks stand on their own in editor/editorSwitchCurve, with unit tests

This commit is contained in:
maj00r
2026-10-25 00:29:05 +02:00
parent aeef2cebb3
commit ece61056ff
9 changed files with 633 additions and 12 deletions

View File

@@ -192,6 +192,7 @@ set(SOURCES
"editor/editorGroundFollow.cpp"
"editor/editorAlignmentStore.cpp"
"editor/editorSwitchStore.cpp"
"editor/editorSwitchCurve.cpp"
"editor/editorKilometrage.cpp"
"editor/editorStation.cpp"
"editor/editorSwitchDesign.cpp"

View File

@@ -35,6 +35,7 @@ http://mozilla.org/MPL/2.0/.
#include "editor/editorAlignment.hpp"
#include "editor/editorAlignmentStore.hpp"
#include "editor/editorSwitchStore.hpp"
#include "editor/editorSwitchCurve.hpp"
#include "editor/editorKilometrage.hpp"
#include "editor/editorSpeed.hpp"
#include "editor/editorRoad.hpp"
@@ -1240,6 +1241,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri
int last{0}; // template armed the last time
bool placing{false};
double along{0.0};
double pressed{0.0}; // along a curve: where the press was, the start of the switch drawn from it to the ends of the cant ramps
glm::dvec3 point{0.0};
glm::dvec3 mouse{0.0};
editor_track::straight line;
@@ -1528,6 +1530,15 @@ class editor_mode : public application_mode, private editor_track::observer, pri
void switch_placed(std::vector<TTrack *> const &Created);
static std::vector<curve_sample> sample_chain(editor_track::chain const &Chain);
static curve_sample sample_at(std::vector<curve_sample> const &Frame, double const Station);
// the curve a switch goes into as the norms look at it: its headings and its cant along it
std::vector<switch_curve::track_point> frame_points(std::vector<curve_sample> const &Frame) const;
// the way the switch being placed into a curve goes along it and the side its diverging track leaves to, from the drag
void curved_switch_pose(int &Direction, int &Side) const;
// the start of the switch being placed into a curve: where it was pressed, drawn to where the switch meets an end of a cant ramp or
// keeps 6 m off it; What receives which
double curved_switch_start(int const Direction, std::string *What = nullptr) const;
// the switch made of the paths, laid into the curve at the station, as ST-T1-A6 sees it
switch_curve::switch_report curved_switch_report(std::vector<segment_data> const &Paths, double const Station, int const Direction) const;
static double nearest_station(std::vector<curve_sample> const &Frame, glm::dvec3 const &Point);
static std::vector<segment_data> curved_switch_paths(editor_track::switch_template const &Shape, std::vector<curve_sample> const &Frame, double const Station, int const Direction, int const Side);
void insert_curved_switch(int const Direction, int const Side);

View File

@@ -51,12 +51,10 @@ std::vector<segment_data> editor_mode::switch_preview() const
return {};
if (tool.curved)
{
auto const sample{sample_at(tool.frame, tool.along)};
glm::dvec2 const offset{tool.mouse.x - tool.point.x, tool.mouse.z - tool.point.z};
auto const forward{glm::dot(offset, sample.tangent) >= 0.0 ? 1 : -1};
glm::dvec2 const direction{sample.tangent * static_cast<double>(forward)};
auto const side{direction.x * offset.y - direction.y * offset.x >= 0.0 ? 1 : -1};
return curved_switch_paths(tool.templates[tool.armed], tool.frame, tool.along, forward, side);
int forward;
int side;
curved_switch_pose(forward, side);
return curved_switch_paths(tool.templates[tool.armed], tool.frame, curved_switch_start(forward), forward, side);
}
if (switch_link plan; plan_switch_link(plan))
{
@@ -116,6 +114,7 @@ bool editor_mode::start_switch_placement()
return false;
tool.curved = true;
tool.along = nearest_station(tool.frame, ground);
tool.pressed = tool.along;
tool.point = sample_at(tool.frame, tool.along).position;
if (plan_distance(tool.point, ground) > kSwitchReach)
return false;
@@ -167,11 +166,10 @@ void editor_mode::finish_switch_placement()
}
if (tool.curved)
{
auto const sample{sample_at(tool.frame, tool.along)};
glm::dvec2 const offset{tool.mouse.x - tool.point.x, tool.mouse.z - tool.point.z};
auto const forward{glm::dot(offset, sample.tangent) >= 0.0 ? 1 : -1};
glm::dvec2 const direction{sample.tangent * static_cast<double>(forward)};
auto const side{direction.x * offset.y - direction.y * offset.x >= 0.0 ? 1 : -1};
int forward;
int side;
curved_switch_pose(forward, side);
tool.along = curved_switch_start(forward);
insert_curved_switch(forward, side);
return;
}
@@ -1256,6 +1254,89 @@ double editor_mode::nearest_station(std::vector<curve_sample> const &Frame, glm:
return station;
}
std::vector<switch_curve::track_point> editor_mode::frame_points(std::vector<curve_sample> const &Frame) const
{
auto const gauge{m_route.design.norms.gauge};
std::vector<switch_curve::track_point> points;
for (auto const &sample : Frame)
{
switch_curve::track_point point;
point.station = sample.station;
point.heading = std::atan2(sample.tangent.y, sample.tangent.x);
// the headings go on through a half turn, so that their differences are the turns between them
if (false == points.empty())
point.heading = points.back().heading + std::remainder(point.heading - points.back().heading, 2.0 * glm::pi<double>());
point.cant = gauge * std::sin(glm::radians(static_cast<double>(sample.roll)));
points.push_back(point);
}
return points;
}
void editor_mode::curved_switch_pose(int &Direction, int &Side) const
{
auto const &tool{m_switch};
auto const sample{sample_at(tool.frame, tool.pressed)};
glm::dvec2 const offset{tool.mouse.x - tool.point.x, tool.mouse.z - tool.point.z};
Direction = glm::dot(offset, sample.tangent) >= 0.0 ? 1 : -1;
glm::dvec2 const direction{sample.tangent * static_cast<double>(Direction)};
Side = direction.x * offset.y - direction.y * offset.x >= 0.0 ? 1 : -1;
}
double editor_mode::curved_switch_start(int const Direction, std::string *What) const
{
auto const &tool{m_switch};
if (What != nullptr)
What->clear();
if (tool.frame.size() < 2 || tool.armed < 0 || tool.armed >= static_cast<int>(tool.templates.size()))
return tool.pressed;
auto const length{tool.templates[tool.armed].length};
std::string what;
auto const start{switch_curve::snapped_start(switch_curve::ramp_ends(frame_points(tool.frame)), tool.pressed, length, Direction, 3.0, &what)};
// a start which would put the switch off the curve isn't one
auto const total{tool.frame.back().station};
if (Direction > 0 ? start < 0.0 || start > total - length : start < length || start > total)
return tool.pressed;
if (What != nullptr)
*What = what;
return start;
}
switch_curve::switch_report editor_mode::curved_switch_report(std::vector<segment_data> const &Paths, double const Station, int const Direction) const
{
auto const &tool{m_switch};
if (Paths.size() < 2 || tool.armed < 0 || tool.armed >= static_cast<int>(tool.templates.size()))
return {};
// a path of the switch from its start, in the plan
auto const along = [](segment_data const &Path) {
std::vector<switch_curve::track_point> points;
auto const add = [&](segment_data const &Piece) {
bezier const curve{Piece};
auto const steps{std::max(4, static_cast<int>(std::ceil(curve.plan_length() / 0.25)))};
for (int k = points.empty() ? 0 : 1; k <= steps; ++k)
{
auto const t{static_cast<double>(k) / steps};
auto const position{plan_of(curve.point(t))};
auto const tangent{plan_of(curve.first(t))};
switch_curve::track_point point;
point.heading = std::atan2(tangent.y, tangent.x);
if (false == points.empty())
{
point.station = points.back().station + glm::distance(position, plan_of(curve.point(static_cast<double>(k - 1) / steps)));
point.heading = points.back().heading + std::remainder(point.heading - points.back().heading, 2.0 * glm::pi<double>());
}
points.push_back(point);
}
};
if (Path.pieces.empty())
add(Path);
for (auto const &piece : Path.pieces)
add(piece);
return points;
};
auto const &shape{tool.templates[tool.armed]};
return switch_curve::check(along(Paths[0]), along(Paths[1]), frame_points(tool.frame), Station, Station + Direction * shape.length, shape.radius, shape.ratio);
}
std::vector<segment_data> editor_mode::curved_switch_paths(editor_track::switch_template const &Shape, std::vector<curve_sample> const &Frame, double const Station, int const Direction, int const Side)
{
auto const map = [&](glm::dvec3 const &Local, glm::dvec2 &Tangent, glm::dvec2 const &Localtangent) {
@@ -1393,10 +1474,16 @@ void editor_mode::insert_curved_switch(int const Direction, int const Side)
}
}
auto const paths{curved_switch_paths(shape, tool.frame, station, Direction, Side)};
auto const report{curved_switch_report(paths, station, Direction)};
auto const made{create_switch_set(shape, paths, *tool.frame_tracks.front(), switch_name_for_new())};
created.insert(created.end(), made.begin(), made.end());
if (made.size() == 1)
tool.status = format(STR_C("Switch %s inserted into the curve, Ctrl+Z takes it back"), made.front()->name().c_str());
// what the norms say of it stays in the status
if (report.transition)
tool.status += "\n" + report.notes.front();
for (auto const &warning : report.warnings)
tool.status += "\n" + warning;
auto const placed{created};
push_track_snapshot(std::move(states), std::move(created), std::move(removed));
straight_refresh();

View File

@@ -2354,8 +2354,64 @@ void editor_mode::draw_build_overlay() const
}
else if (m_switch.placing)
{
for (auto const &piece : switch_preview())
auto const preview{switch_preview()};
for (auto const &piece : preview)
drawpath(piece, overlay_color::marked);
// into a curve: the ends of the cant ramps, what the start was drawn to, the radii at the ends of the switch and the norms
if (auto const &tool{m_switch}; tool.curved && tool.connection < 0 && preview.size() >= 2)
{
auto const points{frame_points(tool.frame)};
for (auto const end : switch_curve::ramp_ends(points))
{
auto const sample{sample_at(tool.frame, end)};
glm::dvec3 const across{-sample.tangent.y * 2.5, 0.0, sample.tangent.x * 2.5};
projection.line(drawlist, sample.position - across, sample.position + across, IM_COL32(255, 170, 40, 255), 3.0f);
ImVec2 screen;
if (projection.project(sample.position + across, screen))
drawlist->AddText({screen.x + 4.0f, screen.y - 7.0f}, IM_COL32(255, 170, 40, 255), STR_C("ramp"));
}
int forward;
int side;
curved_switch_pose(forward, side);
std::string snapped;
auto const station{curved_switch_start(forward, &snapped)};
auto const report{curved_switch_report(preview, station, forward)};
// a radius counted positive the way the line turns, the other way with a minus
auto const radius = [](double const Curvature) { return std::abs(Curvature) < 2e-5 ? std::string{STR_C("straight")} : format("%.0f m", 1.0 / Curvature); };
std::vector<std::pair<std::string, ImU32>> lines;
if (false == snapped.empty())
lines.emplace_back(snapped, IM_COL32(255, 170, 40, 255));
if (report.curved)
{
lines.emplace_back(format(STR_C("main track R %s at the start, %s at the end"), radius(report.main_start).c_str(), radius(report.main_end).c_str()), IM_COL32(255, 255, 255, 235));
lines.emplace_back(format(STR_C("diverging track R %s at the start, %s at the end"), radius(report.branch_start).c_str(), radius(report.branch_end).c_str()), IM_COL32(255, 255, 255, 235));
}
lines.emplace_back(format(STR_C("cant %.0f mm at the start, %.0f mm at the end"), report.cant_start, report.cant_end), IM_COL32(255, 255, 255, 235));
for (auto const &note : report.notes)
lines.emplace_back(note, IM_COL32(170, 200, 230, 235));
for (auto const &warning : report.warnings)
lines.emplace_back(warning, overlay_color::invalid);
ImVec2 screen;
if (projection.project(sample_at(tool.frame, station).position, screen))
{
auto const wrap{460.0f};
auto height{0.0f};
auto width{0.0f};
for (auto const &[text, colour] : lines)
{
auto const size{ImGui::CalcTextSize(text.c_str(), nullptr, false, wrap)};
height += size.y + 2.0f;
width = std::max(width, size.x);
}
ImVec2 corner{screen.x + 24.0f, screen.y + 24.0f};
drawlist->AddRectFilled({corner.x - 6.0f, corner.y - 4.0f}, {corner.x + width + 6.0f, corner.y + height + 4.0f}, IM_COL32(10, 20, 30, 215), 4.0f);
for (auto const &[text, colour] : lines)
{
drawlist->AddText(ImGui::GetFont(), ImGui::GetFontSize(), corner, colour, text.c_str(), nullptr, wrap);
corner.y += ImGui::CalcTextSize(text.c_str(), nullptr, false, wrap).y + 2.0f;
}
}
}
switch_link plan;
auto const linked{plan_switch_link(plan)};
ImVec2 screen;

View File

@@ -0,0 +1,229 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "editor/editorSwitchCurve.hpp"
#include "editor/editorFormat.hpp"
#include "utilities/translation.h"
#include <algorithm>
#include <cmath>
#include <limits>
namespace switch_curve
{
namespace
{
// 1/m: a curvature below it is a straight, the curvature measured over a few metres never quite 0
constexpr double kStraight{2e-5};
// m: an end of a ramp this close to an end of the switch is at it
constexpr double kAtEnd{0.3};
// m: how far off the switch a ramp keeps, 9.6 pkt 16
constexpr double kRampClear{6.0};
// the curved switches bent from the basic ones, table 11.9: the radius of the basic switch, the limiting curvature of the
// diverging track and the least radius of the main track, the values allowed by way of exception in the brackets, 0 for none
struct bent_limit
{
double radius;
double branch;
double branch_exception;
double main;
double main_exception;
};
constexpr bent_limit kBent[]{
{300.0, 4.000e-3, 4.673e-3, 1505.522, 749.722}, {500.0, 4.000e-3, 4.673e-3, 501.730, 375.638}, {760.0, 3.333e-3, 0.0, 497.250, 0.0}, {1200.0, 2.169e-3, 0.0, 750.001, 0.0}, {2500.0, 1.063e-3, 0.0, 1510.0, 0.0}};
double heading_at(std::vector<track_point> const &Track, double const Station)
{
if (Station <= Track.front().station)
return Track.front().heading;
if (Station >= Track.back().station)
return Track.back().heading;
auto const next{std::lower_bound(Track.begin(), Track.end(), Station, [](track_point const &Point, double const Value) { return Point.station < Value; })};
auto const previous{std::prev(next)};
auto const span{std::max(1e-9, next->station - previous->station)};
return previous->heading + (next->heading - previous->heading) * (Station - previous->station) / span;
}
double cant_at(std::vector<track_point> const &Track, double const Station)
{
if (Station <= Track.front().station)
return Track.front().cant;
if (Station >= Track.back().station)
return Track.back().cant;
auto const next{std::lower_bound(Track.begin(), Track.end(), Station, [](track_point const &Point, double const Value) { return Point.station < Value; })};
auto const previous{std::prev(next)};
auto const span{std::max(1e-9, next->station - previous->station)};
return previous->cant + (next->cant - previous->cant) * (Station - previous->station) / span;
}
} // namespace
double curvature_at(std::vector<track_point> const &Track, double const Station)
{
if (Track.size() < 2)
return 0.0;
auto const from{std::max(Track.front().station, Station - 1.0)};
auto const to{std::min(Track.back().station, Station + 1.0)};
if (to - from < 0.2)
return 0.0;
return (heading_at(Track, to) - heading_at(Track, from)) / (to - from);
}
std::vector<double> ramp_ends(std::vector<track_point> const &Track)
{
std::vector<double> ends;
auto ramping{false};
for (std::size_t i = 0; i + 1 < Track.size(); ++i)
{
auto const span{Track[i + 1].station - Track[i].station};
if (span < 1e-6)
continue;
// a change of the cant of 1 mm over 20 m and more
auto const changing{std::abs(Track[i + 1].cant - Track[i].cant) / span > 0.05};
if (changing == ramping)
continue;
ramping = changing;
// a ramp going on after a break of under a metre, where the paths meet, is the same ramp
if (changing && false == ends.empty() && Track[i].station - ends.back() < 1.0)
ends.pop_back();
else
ends.push_back(Track[i].station);
}
if (ramping)
ends.push_back(Track.back().station);
return ends;
}
switch_report check(std::vector<track_point> const &Main, std::vector<track_point> const &Branch, std::vector<track_point> const &Line, double const From, double const To, double const Radius,
double const Ratio)
{
switch_report report;
if (Main.size() < 2 || Branch.size() < 2 || Line.size() < 2)
return report;
// the curvatures counted positive the way the line turns, as the switch goes along it
auto const direction{To >= From ? 1.0 : -1.0};
auto const sign{(Line.back().heading - Line.front().heading < 0.0 ? -1.0 : 1.0) * direction};
struct span
{
double first{0.0};
double last{0.0};
double low{std::numeric_limits<double>::max()};
double high{std::numeric_limits<double>::lowest()};
double sharpest{0.0};
};
auto const along = [&](std::vector<track_point> const &Track) {
span result;
auto const length{Track.back().station - Track.front().station};
auto const margin{std::min(1.0, length * 0.25)};
result.first = sign * curvature_at(Track, Track.front().station + margin);
result.last = sign * curvature_at(Track, Track.back().station - margin);
for (auto s{Track.front().station + margin}; s <= Track.back().station - margin + 1e-9; s += 0.5)
{
auto const curvature{sign * curvature_at(Track, s)};
result.low = std::min(result.low, curvature);
result.high = std::max(result.high, curvature);
result.sharpest = std::max(result.sharpest, std::abs(curvature));
}
return result;
};
auto const main{along(Main)};
auto const branch{along(Branch)};
report.main_start = main.first;
report.main_end = main.last;
report.branch_start = branch.first;
report.branch_end = branch.last;
report.cant_start = std::abs(cant_at(Line, From));
report.cant_end = std::abs(cant_at(Line, To));
report.curved = main.sharpest > kStraight;
report.transition = report.curved && std::abs(main.last - main.first) > std::max(2e-6, 0.05 * std::max(std::abs(main.first), std::abs(main.last)));
if (report.transition)
report.notes.emplace_back(STR_C("On a transition curve: only by way of exception, by consent of the Zaklad Linii Kolejowych (9.7 pkt 12); the plan gives the radii of both tracks at the start and at the end of the switch"));
// the curvature of each track of one sign all along it, the cases KZW I and KZW III of 9.7 pkt 12, not KZW II
if (branch.low < -kStraight && branch.high > kStraight)
report.warnings.emplace_back(STR_C("The curvature of the diverging track turns from one side to the other within the switch (KZW II), which isn't allowed (9.7 pkt 12)"));
if (main.low < -kStraight && main.high > kStraight)
report.warnings.emplace_back(STR_C("The curvature of the main track turns from one side to the other within the switch, which isn't allowed (9.7 pkt 12)"));
if (report.curved)
{
auto const limit{std::find_if(std::begin(kBent), std::end(kBent), [&](bent_limit const &Limit) { return std::abs(Limit.radius - Radius) < 2.0; })};
if (limit == std::end(kBent))
report.notes.emplace_back(format(STR_C("The basic switch of R %.0f m isn't in table 11.9, its limiting curvature isn't checked"), Radius));
else
{
if (branch.sharpest > limit->branch + 1e-9)
report.warnings.emplace_back(limit->branch_exception > 0.0 && branch.sharpest <= limit->branch_exception + 1e-9 ?
format(STR_C("The curvature of the diverging track %.3f 1/km is over the limiting %.3f 1/km of table 11.9, up to %.3f 1/km only by consent of the Zaklad Linii Kolejowych"), branch.sharpest * 1000.0,
limit->branch * 1000.0, limit->branch_exception * 1000.0) :
format(STR_C("The curvature of the diverging track %.3f 1/km is over the limiting %.3f 1/km for the switch of R %.0f m (table 11.9)"), branch.sharpest * 1000.0, limit->branch * 1000.0, Radius));
if (main.sharpest > kStraight && 1.0 / main.sharpest < limit->main - 1e-6)
report.warnings.emplace_back(limit->main_exception > 0.0 && 1.0 / main.sharpest >= limit->main_exception - 1e-6 ?
format(STR_C("The main track bent to R %.0f m, below the least %.0f m of table 11.9, down to %.0f m only by consent of the Zaklad Linii Kolejowych"), 1.0 / main.sharpest, limit->main, limit->main_exception) :
format(STR_C("The main track bent to R %.0f m, below the least %.0f m for the switch of R %.0f m (table 11.9)"), 1.0 / main.sharpest, limit->main, Radius));
}
if (Ratio > 0.0 && Ratio < 9.41)
report.warnings.emplace_back(format(STR_C("A switch of 1:%.3f isn't bent in the running and the main tracks: none of 1:9.403 nor steeper than 1:9 (11.4 pkt 4)"), Ratio));
}
// the cant: the same all along the switch, a ramp off it by 6 m at least or ending at its ends (11.4 pkt 7, 9.6 pkt 16-17)
if (std::abs(report.cant_end - report.cant_start) > 1.0)
report.warnings.emplace_back(format(STR_C("The cant changes within the switch, %.0f to %.0f mm: it's to stay the same over it (11.4 pkt 7); a straight ramp through the switch only by consent of the Zaklad Linii Kolejowych"),
report.cant_start, report.cant_end));
auto const low{std::min(From, To)};
auto const high{std::max(From, To)};
for (auto const end : ramp_ends(Line))
{
if (end > low + kAtEnd && end < high - kAtEnd)
report.warnings.emplace_back(format(STR_C("A cant ramp starts or ends within the switch, %.1f m from its start: at the start or the end of the switch, or 6 m off it at least (9.6 pkt 16-17)"), std::abs(end - From)));
else if (std::abs(end - low) <= kAtEnd || std::abs(end - high) <= kAtEnd)
report.notes.emplace_back(STR_C("A cant ramp starts or ends at an end of the switch"));
else if (auto const off{end < low ? low - end : end - high}; off < kRampClear)
report.warnings.emplace_back(format(STR_C("A cant ramp ends %.1f m off the switch, 6 m at least (9.6 pkt 16)"), off));
}
auto const cant{std::max(report.cant_start, report.cant_end)};
if (report.curved && cant > 100.0 + 1e-6)
report.warnings.emplace_back(format(STR_C("Cant %.0f mm in a bent switch, over 100 mm (table 9.6)"), cant));
else if (report.curved && cant > 60.0 + 1e-6)
report.notes.emplace_back(format(STR_C("Cant %.0f mm: over the 60 mm recommended for a bent switch, up to 100 mm (table 9.6)"), cant));
else if (false == report.curved && cant > 1.0)
report.notes.emplace_back(format(STR_C("Cant %.0f mm in a straight switch: none recommended, up to 100 mm (table 9.6)"), cant));
return report;
}
double snapped_start(std::vector<double> const &Ramps, double const Pressed, double const Length, int const Direction, double const Reach, std::string *What)
{
auto const d{Direction < 0 ? -1.0 : 1.0};
auto best{Pressed};
auto nearest{Reach};
std::string name;
auto const consider = [&](double const Start, char const *Name) {
if (auto const distance{std::abs(Start - Pressed)}; distance <= nearest)
{
nearest = distance;
best = Start;
name = Name;
}
};
for (auto const end : Ramps)
{
consider(end, STR_C("the switch starts at an end of the cant ramp"));
consider(end - d * Length, STR_C("the switch ends at an end of the cant ramp"));
consider(end + d * kRampClear, STR_C("the switch starts 6 m off the cant ramp"));
consider(end - d * (kRampClear + Length), STR_C("the switch ends 6 m off the cant ramp"));
}
if (What != nullptr)
*What = name;
return best;
}
} // namespace switch_curve

View File

@@ -0,0 +1,61 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <string>
#include <vector>
// a switch laid into a curve or into a transition curve, as the standard of PKP PLK ST-T1-A6 sees it: the curvatures of its tracks
// (9.7 pkt 12, 11.4, table 11.9) and the cant along it, which is to stay the same over it or be ramped from its ends (9.6 pkt
// 16-17, 11.4 pkt 7, table 9.6)
namespace switch_curve
{
// a point of a track in the plan: how far along it, which way it heads (radians, growing as it turns to the left), its cant (mm)
struct track_point
{
double station{0.0};
double heading{0.0};
double cant{0.0};
};
// the curvature of the track at the station, from its headings a metre either side of it, 1/m positive to the left
double curvature_at(std::vector<track_point> const &Track, double const Station);
// the stations where the cant of the track starts or stops changing: the ends of its cant ramps
std::vector<double> ramp_ends(std::vector<track_point> const &Track);
struct switch_report
{
// at the start and at the end of the switch, 1/m, positive turning the way the line does
double main_start{0.0};
double main_end{0.0};
double branch_start{0.0};
double branch_end{0.0};
// of the main track, mm
double cant_start{0.0};
double cant_end{0.0};
bool curved{false}; // the main track bent
bool transition{false}; // its curvature changing along the switch
std::vector<std::string> notes;
std::vector<std::string> warnings;
};
// Main, Branch: the tracks of the switch from its start, the stations from 0 there; Line: the track it's laid into, From and To the
// stations of the start and the end of the switch along it, From after To where it goes back along the line. Radius and Ratio:
// of the diverging track of the basic switch it's bent from
switch_report check(std::vector<track_point> const &Main, std::vector<track_point> const &Branch, std::vector<track_point> const &Line, double const From, double const To, double const Radius,
double const Ratio);
// the start of a switch Length long, going Direction along the line, drawn to where its start or its end meets an end of a cant ramp,
// or to where it keeps the 6 m off it, when the station pressed is within Reach of that; What receives which of them, empty for none
double snapped_start(std::vector<double> const &Ramps, double const Pressed, double const Length, int const Direction, double const Reach, std::string *What = nullptr);
} // namespace switch_curve

View File

@@ -11974,3 +11974,75 @@ msgstr "W%d R%d %.0f m przechyłka %.0f mm"
msgid "W%d R %.0f m cant %.0f mm"
msgstr "W%d R %.0f m przechyłka %.0f mm"
msgid "On a transition curve: only by way of exception, by consent of the Zaklad Linii Kolejowych (9.7 pkt 12); the plan gives the radii of both tracks at the start and at the end of the switch"
msgstr "Na krzywej przejściowej: tylko wyjątkowo, za zgodą Zakładu Linii Kolejowych (9.7 pkt 12); na planie podaje się promienie obu torów na początku i na końcu rozjazdu"
msgid "The curvature of the diverging track turns from one side to the other within the switch (KZW II), which isn't allowed (9.7 pkt 12)"
msgstr "Krzywizna toru zwrotnego zmienia znak w obrębie rozjazdu (KZW II), co jest niedozwolone (9.7 pkt 12)"
msgid "The curvature of the main track turns from one side to the other within the switch, which isn't allowed (9.7 pkt 12)"
msgstr "Krzywizna toru zasadniczego zmienia znak w obrębie rozjazdu, co jest niedozwolone (9.7 pkt 12)"
msgid "The basic switch of R %.0f m isn't in table 11.9, its limiting curvature isn't checked"
msgstr "Rozjazdu podstawowego R %.0f m nie ma w tabeli 11.9, jego krzywizna graniczna nie jest sprawdzana"
msgid "The curvature of the diverging track %.3f 1/km is over the limiting %.3f 1/km of table 11.9, up to %.3f 1/km only by consent of the Zaklad Linii Kolejowych"
msgstr "Krzywizna toru zwrotnego %.3f 1/km ponad graniczną %.3f 1/km z tabeli 11.9; do %.3f 1/km tylko za zgodą Zakładu Linii Kolejowych"
msgid "The curvature of the diverging track %.3f 1/km is over the limiting %.3f 1/km for the switch of R %.0f m (table 11.9)"
msgstr "Krzywizna toru zwrotnego %.3f 1/km ponad graniczną %.3f 1/km dla rozjazdu R %.0f m (tabela 11.9)"
msgid "The main track bent to R %.0f m, below the least %.0f m of table 11.9, down to %.0f m only by consent of the Zaklad Linii Kolejowych"
msgstr "Tor zasadniczy wygięty do R %.0f m, poniżej najmniejszego %.0f m z tabeli 11.9; do %.0f m tylko za zgodą Zakładu Linii Kolejowych"
msgid "The main track bent to R %.0f m, below the least %.0f m for the switch of R %.0f m (table 11.9)"
msgstr "Tor zasadniczy wygięty do R %.0f m, poniżej najmniejszego %.0f m dla rozjazdu R %.0f m (tabela 11.9)"
msgid "A switch of 1:%.3f isn't bent in the running and the main tracks: none of 1:9.403 nor steeper than 1:9 (11.4 pkt 4)"
msgstr "Rozjazdu 1:%.3f nie łukuje się w torach szlakowych i głównych: ani 1:9,403, ani o skosie większym niż 1:9 (11.4 pkt 4)"
msgid "The cant changes within the switch, %.0f to %.0f mm: it's to stay the same over it (11.4 pkt 7); a straight ramp through the switch only by consent of the Zaklad Linii Kolejowych"
msgstr "Przechyłka zmienia się w obrębie rozjazdu, z %.0f na %.0f mm: ma być stała na całej jego długości (11.4 pkt 7); prostoliniowa rampa w rozjeździe tylko za zgodą Zakładu Linii Kolejowych"
msgid "A cant ramp starts or ends within the switch, %.1f m from its start: at the start or the end of the switch, or 6 m off it at least (9.6 pkt 16-17)"
msgstr "Rampa przechyłkowa zaczyna się albo kończy w obrębie rozjazdu, %.1f m od jego początku: ma się zaczynać na początku albo końcu rozjazdu, albo co najmniej 6 m od niego (9.6 pkt 16-17)"
msgid "A cant ramp starts or ends at an end of the switch"
msgstr "Rampa przechyłkowa zaczyna się albo kończy na początku lub końcu rozjazdu"
msgid "A cant ramp ends %.1f m off the switch, 6 m at least (9.6 pkt 16)"
msgstr "Koniec rampy przechyłkowej %.1f m od rozjazdu, ma być co najmniej 6 m (9.6 pkt 16)"
msgid "Cant %.0f mm in a bent switch, over 100 mm (table 9.6)"
msgstr "Przechyłka %.0f mm w rozjeździe łukowym, ponad 100 mm (tabela 9.6)"
msgid "Cant %.0f mm: over the 60 mm recommended for a bent switch, up to 100 mm (table 9.6)"
msgstr "Przechyłka %.0f mm: ponad zalecane 60 mm dla rozjazdu łukowego, dopuszczalne do 100 mm (tabela 9.6)"
msgid "Cant %.0f mm in a straight switch: none recommended, up to 100 mm (table 9.6)"
msgstr "Przechyłka %.0f mm w rozjeździe zwyczajnym: zalecana żadna, dopuszczalne do 100 mm (tabela 9.6)"
msgid "the switch starts at an end of the cant ramp"
msgstr "początek rozjazdu na krańcu rampy przechyłkowej"
msgid "the switch ends at an end of the cant ramp"
msgstr "koniec rozjazdu na krańcu rampy przechyłkowej"
msgid "the switch starts 6 m off the cant ramp"
msgstr "początek rozjazdu 6 m od rampy przechyłkowej"
msgid "the switch ends 6 m off the cant ramp"
msgstr "koniec rozjazdu 6 m od rampy przechyłkowej"
msgid "ramp"
msgstr "rampa"
msgid "main track R %s at the start, %s at the end"
msgstr "tor zasadniczy R %s na początku, %s na końcu"
msgid "diverging track R %s at the start, %s at the end"
msgstr "tor zwrotny R %s na początku, %s na końcu"
msgid "cant %.0f mm at the start, %.0f mm at the end"
msgstr "przechyłka %.0f mm na początku, %.0f mm na końcu"

View File

@@ -20,6 +20,8 @@ add_executable(${UNIT_TESTS_NAME}
# and the heights of the tracks laid
"${CMAKE_SOURCE_DIR}/editor/editorGroundFollow.cpp"
"${CMAKE_SOURCE_DIR}/editor/editorProfile.cpp"
# and the switches laid into the curves
"${CMAKE_SOURCE_DIR}/editor/editorSwitchCurve.cpp"
${UNIT_TEST_CASES})
# the core library brings the game's build settings along

View File

@@ -0,0 +1,102 @@
#include "stdafx.h"
#include "doctest.h"
#include "editor/editorSwitchCurve.hpp"
#include <algorithm>
#include <cmath>
#include <string>
namespace {
// a line every half metre: a straight, a transition curve from it to the arc of the radius, with a straight cant ramp along it, and
// the arc, each of the lengths given
std::vector<switch_curve::track_point> line(double const Straight, double const Transition, double const Arc, double const Radius, double const Cant) {
std::vector<switch_curve::track_point> points;
double heading{0.0};
auto const curvature = [&](double const S) { return S <= Straight ? 0.0 : S <= Straight + Transition ? (S - Straight) / Transition / Radius : 1.0 / Radius; };
auto const cant = [&](double const S) { return S <= Straight ? 0.0 : S <= Straight + Transition ? Cant * (S - Straight) / Transition : Cant; };
for (double s{0.0}; s <= Straight + Transition + Arc + 1e-9; s += 0.5) {
if (s > 0.0)
heading += 0.5 * (curvature(s - 0.5) + curvature(s)) * 0.5;
points.push_back({s, heading, cant(s)});
}
return points;
}
// the tracks of a switch Length long laid into the line from the station: the main one along it, the diverging one turning off it
// to the side by the curvature of the basic switch
std::pair<std::vector<switch_curve::track_point>, std::vector<switch_curve::track_point>> laid(std::vector<switch_curve::track_point> const &Line, double const From, double const Length, double const Radius, int const Side) {
std::vector<switch_curve::track_point> main;
std::vector<switch_curve::track_point> branch;
auto const first{std::lower_bound(Line.begin(), Line.end(), From, [](auto const &Point, double const Value) { return Point.station < Value; })};
for (auto point{first}; point != Line.end() && point->station <= From + Length + 1e-9; ++point) {
auto const s{point->station - From};
main.push_back({s, point->heading - first->heading, 0.0});
branch.push_back({s, point->heading - first->heading + Side * s / Radius, 0.0});
}
return {main, branch};
}
bool says(std::vector<std::string> const &Lines, char const *Text) {
return std::any_of(Lines.begin(), Lines.end(), [&](std::string const &Line) { return Line.find(Text) != std::string::npos; });
}
} // namespace
TEST_CASE("switch on a curve: the curvatures of its tracks on a transition curve, read the way the line turns") {
auto const track{line(100.0, 120.0, 200.0, 1000.0, 60.0)};
auto const [main, branch] {laid(track, 150.0, 30.0, 500.0, -1)};
auto const report{switch_curve::check(main, branch, track, 150.0, 180.0, 500.0, 12.0)};
CHECK(report.curved);
CHECK(report.transition);
CHECK(report.main_start == doctest::Approx(51.0 / 120.0 / 1000.0).epsilon(0.02));
CHECK(report.main_end == doctest::Approx(79.0 / 120.0 / 1000.0).epsilon(0.02));
// turning off to the outside of the curve: against it all along, the case KZW I
CHECK(report.branch_start < 0.0);
CHECK(report.branch_end < 0.0);
CHECK(false == says(report.warnings, "KZW II"));
CHECK(says(report.notes, "transition curve"));
// laid on the ramp, the cant changing under it
CHECK(report.cant_start == doctest::Approx(25.0).epsilon(0.02));
CHECK(report.cant_end == doctest::Approx(40.0).epsilon(0.02));
CHECK(says(report.warnings, "The cant changes within the switch"));
}
TEST_CASE("switch on a curve: the diverging track turning from one side to the other is the case KZW II") {
// a transition growing past the curvature of the basic switch along the switch
auto const track{line(100.0, 60.0, 100.0, 300.0, 0.0)};
auto const [main, branch] {laid(track, 125.0, 30.0, 500.0, -1)};
auto const report{switch_curve::check(main, branch, track, 125.0, 155.0, 500.0, 12.0)};
CHECK(report.branch_start < 0.0);
CHECK(report.branch_end > 0.0);
CHECK(says(report.warnings, "KZW II"));
}
TEST_CASE("switch on a curve: the ends of a cant ramp at the ends of the switch or 6 m off it") {
auto const track{line(100.0, 120.0, 200.0, 1000.0, 60.0)};
auto const ramps{switch_curve::ramp_ends(track)};
REQUIRE(ramps.size() == 2);
CHECK(ramps[0] == doctest::Approx(100.0).epsilon(0.01));
CHECK(ramps[1] == doctest::Approx(220.0).epsilon(0.01));
auto const at = [&](double const From) {
auto const [main, branch] {laid(track, From, 30.0, 500.0, 1)};
return switch_curve::check(main, branch, track, From, From + 30.0, 500.0, 12.0);
};
CHECK(says(at(210.0).warnings, "within the switch"));
CHECK(says(at(222.0).warnings, "6 m at least"));
CHECK(false == says(at(220.0).warnings, "within the switch"));
CHECK(says(at(220.0).notes, "at an end of the switch"));
CHECK(false == says(at(230.0).warnings, "cant ramp"));
// the start pressed near those places drawn to them
std::string what;
CHECK(switch_curve::snapped_start(ramps, 218.5, 30.0, 1, 3.0, &what) == doctest::Approx(220.0));
CHECK(what.find("starts at an end") != std::string::npos);
CHECK(switch_curve::snapped_start(ramps, 228.0, 30.0, 1, 3.0, &what) == doctest::Approx(226.0));
CHECK(what.find("6 m off") != std::string::npos);
CHECK(switch_curve::snapped_start(ramps, 72.0, 30.0, 1, 3.0, &what) == doctest::Approx(70.0));
CHECK(what.find("ends at an end") != std::string::npos);
CHECK(switch_curve::snapped_start(ramps, 160.0, 30.0, 1, 3.0, &what) == doctest::Approx(160.0));
CHECK(what.empty());
}