diff --git a/application/editormode.h b/application/editormode.h index b46c1978..aeb5377c 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -1792,6 +1792,9 @@ class editor_mode : public application_mode, private editor_track::observer, pri // the tracks the laying under way goes on from and to std::pair laying_ends() const; std::string design_speed_of(TTrack const *From, TTrack const *To) const; + // what ST-T1-A6 says of the sudden changes of the cant deficiency right behind a switch the pieces laid come out of at Start (From), + // or go into at End (To): of the end of its curve and the first sudden change of the pieces (9.11, tables 9.15, 9.16) + std::vector switch_exit_warnings(TTrack const *From, glm::dvec3 const &Start, TTrack const *To, glm::dvec3 const &End, std::vector const &Pieces) const; void give_own_speed(std::vector const &Created) const; std::vector join_pieces(TTrack const *From, editor_track::point_ref const &Frompoint, glm::dvec3 const &Start, glm::dvec2 const &Direction, editor_track::snap_target const &End, double &Length, std::string &Error) const; diff --git a/application/editormodelay.cpp b/application/editormodelay.cpp index 25ea4052..4b3e6d04 100644 --- a/application/editormodelay.cpp +++ b/application/editormodelay.cpp @@ -612,6 +612,61 @@ std::string editor_mode::design_speed_of(TTrack const *From, TTrack const *To) c return format(STR_C("design speed %.0f km/h, set for the laying"), speed); } +std::vector editor_mode::switch_exit_warnings(TTrack const *From, glm::dvec3 const &Start, TTrack const *To, glm::dvec3 const &End, std::vector const &Pieces) const +{ + auto const *switched{From != nullptr && From->eType == tt_Switch ? From : To != nullptr && To->eType == tt_Switch ? To : nullptr}; + if (switched == nullptr || Pieces.empty()) + return {}; + auto const &joint{switched == From ? Start : End}; + // 1/m, positive to the left, as the piece runs + auto const curvature_at = [](segment_data const &Piece, double const T) { + bezier const curve{Piece}; + auto const first{curve.first(T)}; + auto const second{curve.second(T)}; + auto const speed{std::hypot(first.x, first.z)}; + return speed < 1e-9 ? 0.0 : (first.x * second.z - first.z * second.x) / (speed * speed * speed); + }; + // the path of the switch ending at the joint, its pieces from the joint back into the switch, each with its curvature at its ends as + // the line runs on beyond the joint + std::vector> back; + for (auto const &path : switched->m_paths) + for (auto const atend : {false, true}) + { + if (false == back.empty() || glm::distance(path.points[atend ? segment_data::point::end : segment_data::point::start], joint) > 0.05) + continue; + std::vector pieces{path.pieces.empty() ? std::vector{path} : path.pieces}; + if (atend) + std::reverse(pieces.begin(), pieces.end()); + for (auto const &piece : pieces) + back.push_back({bezier{piece}.plan_length(), atend ? curvature_at(piece, 1.0) : -curvature_at(piece, 0.0), atend ? curvature_at(piece, 0.0) : -curvature_at(piece, 1.0)}); + } + // the curve the switch ends with, and the straight of a straight frog after it + double straight{0.0}; + std::optional curvature; + for (auto const &[length, inner, outer] : back) + { + if (std::abs(inner) > 1e-4 || std::abs(outer) > 1e-4) + { + curvature = inner; + break; + } + straight += length; + } + if (false == curvature.has_value()) + return {}; + // the pieces laid as the line runs on from the switch + std::vector> line; + if (switched == From) + for (auto const &piece : Pieces) + line.push_back({bezier{piece}.plan_length(), curvature_at(piece, 0.0), curvature_at(piece, 1.0)}); + else + for (auto piece{Pieces.rbegin()}; piece != Pieces.rend(); ++piece) + line.push_back({bezier{*piece}.plan_length(), -curvature_at(*piece, 1.0), -curvature_at(*piece, 0.0)}); + // no faster than the diverging track of the switch lets + auto const speed{std::min(design_speed(From, To), editor_track::diverging_speed(1.0 / std::abs(*curvature)))}; + return switch_curve::check_exit(*curvature, straight, line, speed, laying_norms().gauge).warnings; +} + void editor_mode::give_own_speed(std::vector const &Created) const { if (false == m_lay.own_speed || m_lay.style.velocity <= 0.0) @@ -725,6 +780,8 @@ void editor_mode::finish_extend() first_step_done(first_step::laid); if (target.track != nullptr) first_step_done(first_step::join); + if (auto const warnings{switch_exit_warnings(style, pieces.front().points[segment_data::point::start], target.track, target.position, pieces)}; false == warnings.empty()) + ui()->set_status(lines_of(warnings), true); m_node = created.back(); ui()->set_node(m_node); nmt_fit_laid(from, created.back()->m_paths.front().points[segment_data::point::end]); @@ -1610,6 +1667,8 @@ void editor_mode::lay_finish(editor_track::snap_target const &End) tool.status += "\n" + format(STR_C("Curve %d %s"), curve.vertex + 1, on.c_str()); if (eased.has_value() && false == eased->note.empty()) tool.status += "\n" + eased->note; + for (auto const &warning : switch_exit_warnings(tool.start.track, tool.points.front(), End.track, End.position, pieces)) + tool.status += "\n" + warning; { std::vector> run; if (tool.start.track != nullptr && tool.start.track->eType == tt_Normal) diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index e9ba721f..074c9e55 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1980,6 +1980,8 @@ std::string editor_mode::track_readout() const text += "\n" + vertical; if (snapped) text += "\n" + format(STR_C("joins %s"), name(target.track).c_str()); + for (auto const &warning : switch_exit_warnings(m_extend.track, m_extend.point, snapped ? target.track : nullptr, target.position, pieces)) + text += "\n" + warning; if (false == reason.empty()) text += "\n" + reason; return text + "\n" + track_shaping(snapped); @@ -2052,6 +2054,8 @@ std::string editor_mode::track_readout() const if (auto const vertical{vertical_of(lay.preview)}; false == vertical.empty()) text += "\n" + vertical; } + for (auto const &warning : switch_exit_warnings(lay.start.track, lay.points.empty() ? lay.mouse : lay.points.front(), lay.mouse_snap.track, lay.mouse_snap.position, lay.preview)) + text += "\n" + warning; if (false == lay.points.empty() && lay.mouse_snap.track != nullptr) text += "\n" + format(STR_C("joins %s"), name(lay.mouse_snap.track).c_str()); if (false == lay.preview_error.empty()) diff --git a/editor/editorSwitchCurve.cpp b/editor/editorSwitchCurve.cpp index 5142c885..e770a647 100644 --- a/editor/editorSwitchCurve.cpp +++ b/editor/editorSwitchCurve.cpp @@ -254,6 +254,68 @@ slip_report check_slip(std::vector> const &Tracks, std: return report; } +exit_report check_exit(double const Curvature, double const Straight, std::vector> const &Line, double const Speed, double const Gauge) +{ + exit_report report; + // table 9.15 and table 9.16, track connections + report.allowed = Speed <= 60.0 ? 130.0 : Speed <= 100.0 ? 100.0 : Speed <= 160.0 ? 80.0 : 60.0; + report.element = Speed <= 60.0 ? 6.0 : Speed <= 100.0 ? 0.15 * Speed : 0.25 * Speed; + // mm of the cant deficiency the change of the curvature makes at once: 11.8 V^2 / R at the standard gauge + auto const sudden = [&](double const Before, double const After) { return Gauge * Speed * Speed / (9.81 * 3.6 * 3.6) * std::abs(After - Before); }; + // 1/m, a change of the curvature as large at once is a sudden one + constexpr double kSudden{1e-4}; + struct change + { + double along; // from the end of the switch + double before; + double after; + }; + std::vector changes; + auto curvature{Curvature}; + if (Straight > 0.0 && std::abs(Curvature) > kSudden) + { + changes.push_back({-Straight, Curvature, 0.0}); + curvature = 0.0; + } + double along{0.0}; + for (auto const &[length, start, end] : Line) + { + if (std::abs(start - curvature) > kSudden) + changes.push_back({along, curvature, start}); + curvature = end; + along += length; + } + // the curve of the switch going on into the line: nothing of the switch to judge + if (changes.empty() || changes.front().along > 1e-6) + return report; + auto const &first{changes.front()}; + auto const alone = [&](change const &Change) { + auto const value{sudden(Change.before, Change.after)}; + report.change = std::max(report.change, value); + if (value > report.allowed) + report.warnings.emplace_back(format(STR_C("Behind the switch the curvature changes at once by a sudden change of the cant deficiency of %.0f mm, over the %.0f mm allowed at %.0f km/h (table 9.15)"), value, report.allowed, Speed)); + }; + if (changes.size() > 1) + { + auto const &next{changes[1]}; + report.apart = next.along - first.along; + if (report.apart < report.element) + { + // taken together, from before the first one to after the next one (9.11 pkt 8b, 9) + report.change = sudden(first.before, next.after); + if (report.change > report.allowed) + report.warnings.emplace_back(format(STR_C("The curve behind the switch begins %.1f m after the end of its curve, closer than the %.1f m which take the sudden changes of the cant deficiency apart: together %.0f mm, over the %.0f mm allowed at %.0f km/h (tables 9.15, 9.16)"), + report.apart, report.element, report.change, report.allowed, Speed)); + return report; + } + alone(next); + } + // the end of the curve of the switch at its end, not within it over a straight frog: that one is the switch's own + if (first.along > -1e-6) + alone(first); + return report; +} + double snapped_start(std::vector 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}; diff --git a/editor/editorSwitchCurve.hpp b/editor/editorSwitchCurve.hpp index d61f942b..82789fcf 100644 --- a/editor/editorSwitchCurve.hpp +++ b/editor/editorSwitchCurve.hpp @@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/. #pragma once +#include #include #include @@ -76,6 +77,21 @@ struct slip_report // From and To the stations of the ends of the first track through the slip along it slip_report check_slip(std::vector> const &Tracks, std::vector> const &Curves, std::vector const &Line, double const From, double const To); +// a line laid on from the end of the diverging track of a switch, as ST-T1-A6 sees the sudden changes of the cant deficiency right behind +// the switch (9.11): Curvature the switch ends with (1/m, positive to the left as the line runs on), Straight metres before its end +// over a straight frog, then the pieces of the line, each its length and its curvature at its start and at its end. The first two +// sudden changes nearer than the intermediate element of table 9.16 taken together, else each one alone, against table 9.15 for track +// connections at the Speed (km/h); Gauge between the axes of the rails, mm +struct exit_report +{ + double apart{-1.0}; // m, from the end of the curve of the switch to the next sudden change, -1 none + double change{0.0}; // mm, the largest sudden change of the cant deficiency judged + double allowed{0.0}; // mm, table 9.15 + double element{0.0}; // m, the intermediate element of table 9.16 + std::vector warnings; +}; +exit_report check_exit(double const Curvature, double const Straight, std::vector> const &Line, double const Speed, double const Gauge = 1500.0); + // 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 const &Ramps, double const Pressed, double const Length, int const Direction, double const Reach, std::string *What = nullptr); diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 41365363..8b8b50bb 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -12444,3 +12444,9 @@ msgstr "Tor przecinający rozjazdu krzyżowego jest dalej połączony: rozjazd z msgid "Slip %s moved %.2f m along its track, its crossing track with it" msgstr "Rozjazd krzyżowy %s przesunięty o %.2f m wzdłuż toru, razem z torem przecinającym" + +msgid "Behind the switch the curvature changes at once by a sudden change of the cant deficiency of %.0f mm, over the %.0f mm allowed at %.0f km/h (table 9.15)" +msgstr "Za rozjazdem krzywizna zmienia się skokowo: nagła zmiana niedomiaru przechyłki %.0f mm, ponad dopuszczalne %.0f mm przy %.0f km/h (tab. 9.15)" + +msgid "The curve behind the switch begins %.1f m after the end of its curve, closer than the %.1f m which take the sudden changes of the cant deficiency apart: together %.0f mm, over the %.0f mm allowed at %.0f km/h (tables 9.15, 9.16)" +msgstr "Łuk za rozjazdem zaczyna się %.1f m za końcem jego łuku, bliżej niż %.1f m odcinka pośredniego, który rozdziela nagłe zmiany niedomiaru przechyłki: łącznie %.0f mm, ponad dopuszczalne %.0f mm przy %.0f km/h (tab. 9.15, 9.16)" diff --git a/tests/unit/test_switch_curve.cpp b/tests/unit/test_switch_curve.cpp index 206d32e1..0a05a89d 100644 --- a/tests/unit/test_switch_curve.cpp +++ b/tests/unit/test_switch_curve.cpp @@ -167,3 +167,33 @@ TEST_CASE("slip bent into a curve: the cant of the side track, a track through i auto const report{switch_curve::check_slip({first, arc(33.2, angle, 1.0 / 1000.0)}, {arc(21.0, 0.0, 1.0 / 190.0)}, curve, 150.0, 183.2)}; CHECK(says(report.warnings, "9.7 pkt 12")); } + +TEST_CASE("switch exit: a curve right behind a straight frog, its sudden changes of the cant deficiency apart or together") { + double const sw{1.0 / 190.0}; + // R 190 on behind a frog straight for 6.09 m, at 40 km/h: apart, 99 mm at the end, within the 130 mm of table 9.15 + auto const same{switch_curve::check_exit(sw, 6.092, {{40.0, sw, sw}}, 40.0)}; + CHECK(same.apart == doctest::Approx(6.092)); + CHECK(same.element == doctest::Approx(6.0)); + CHECK(same.change == doctest::Approx(99.4).epsilon(0.01)); + CHECK(same.warnings.empty()); + // the other way behind a frog straight of 1.66 m only: together, 199 mm, over the 130 mm + auto const reverse{switch_curve::check_exit(sw, 1.662, {{40.0, -sw, -sw}}, 40.0)}; + CHECK(reverse.apart == doctest::Approx(1.662)); + CHECK(reverse.change == doctest::Approx(198.8).epsilon(0.01)); + REQUIRE(reverse.warnings.size() == 1); + CHECK(says(reverse.warnings, "tables 9.15, 9.16")); + // the same way behind it: together the curvature hardly changes + CHECK(switch_curve::check_exit(sw, 1.662, {{40.0, sw, sw}}, 40.0).warnings.empty()); + // a straight of 5 m laid on first: 6.66 m apart, the curve the other way alone, 99 mm + auto const later{switch_curve::check_exit(sw, 1.662, {{5.0, 0.0, 0.0}, {40.0, -sw, -sw}}, 40.0)}; + CHECK(later.apart == doctest::Approx(6.662)); + CHECK(later.warnings.empty()); + // a sharp curve of R 100 alone: 189 mm + CHECK(says(switch_curve::check_exit(sw, 6.092, {{30.0, 0.01, 0.01}}, 40.0).warnings, "table 9.15")); + // a transition out of the frog straight: no sudden change behind it + CHECK(switch_curve::check_exit(sw, 1.662, {{20.0, 0.0, -sw}, {20.0, -sw, -sw}}, 40.0).warnings.empty()); + // the curve of a switch with a curved frog going on: nothing of the switch to judge + auto const on{switch_curve::check_exit(1.0 / 300.0, 0.0, {{30.0, 1.0 / 300.0, 1.0 / 300.0}}, 50.0)}; + CHECK(on.apart < 0.0); + CHECK(on.warnings.empty()); +}