From 7b11db566ea6ced542293519dd49f1a1e57994e0 Mon Sep 17 00:00:00 2001 From: maj00r Date: Tue, 27 Oct 2026 19:39:38 +0100 Subject: [PATCH] feat(editor): a slip Rkp or Rkpd bent into a curve of a side track: dragged on a curve with a slip armed it's laid out on the tangent to the curve where it was pressed and bent onto the curve as a switch laid into one is, editor_mode::frame_path laying a path of a template into the curve for both, its crossing track coming along with its ends free, ahead to the side of the drag, the slip curve of a single slip on that side, the straights of both tracks out to the joints of its template. A crossing goes only where two straights cross, a curve as sharp as the slip curves is refused. As it's dragged the view tells the radii of its tracks and slip curves, its cant and what switch_curve::check_slip says, the 11.4 pkt 4b of ST-T1-A6 among it; laid, the status the same. A slip bent into a curve isn't replaced by one laid out of straights. Selftests switchcurve, bladetips: a Rkpd 49E1-190-1:9 in R 800 m with its slip curves of R 154 and 249 m and the warnings of 11.4 pkt 4b and table 9.2, one of R 300 m in R 800 m with no warning of table 9.2, a Rkp in R 4000 m, the blades from 1.248 m on all of them; refused in R 150 m, a Skrz refused --- application/editormode.h | 39 +- application/editormodeselftestswitch.cpp | 38 +- application/editormodeswitchplace.cpp | 540 ++++++++++++++++------- application/editormodeswitchtool.cpp | 4 +- application/editormodetrack.cpp | 51 ++- lang/editor_pl.po | 45 ++ 6 files changed, 538 insertions(+), 179 deletions(-) diff --git a/application/editormode.h b/application/editormode.h index 459eea99..335c44c4 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -1598,6 +1598,9 @@ class editor_mode : public application_mode, private editor_track::observer, pri switch_curve::switch_report curved_switch_report(std::vector const &Paths, double const Station, int const Direction) const; static double nearest_station(std::vector const &Frame, glm::dvec3 const &Point); static std::vector curved_switch_paths(editor_track::switch_template const &Shape, std::vector const &Frame, double const Station, int const Direction, int const Side); + // the path of a switch template laid into the curve: x of the template across the main track to the Side, z along it from the + // Station on, going Direction along the curve. The heights and the cant are those of the line where the point is along it + static segment_data frame_path(segment_data const &Local, std::vector const &Frame, double const Station, int const Direction, int const Side); void insert_curved_switch(int const Direction, int const Side); // a crossover between two tracks running side by side along a curve (editormodeconnectioncurve.cpp) struct curve_mapping; @@ -1681,7 +1684,41 @@ class editor_mode : public application_mode, private editor_track::observer, pri glm::dvec3 cursor_on_straight(editor_track::straight const &Line) const; bool insert_double_slip(editor_track::straight const &Line, glm::dvec3 const &Point, editor_track::switch_template const &Shape); bool replace_double_slip(TTrack &Part, editor_track::switch_template const &Shape); - std::vector build_double_slip(glm::dvec3 const &PointA, glm::dvec3 const &PointB, glm::dvec3 const &PointC, glm::dvec3 const &PointD, glm::dvec2 const &Crossing, double const Height, glm::dvec2 const &First, glm::dvec2 const &Second, double const Angle, editor_track::slip_curve const &Curve, TTrack &Style, std::string const &Type = "Rkpd"); + // Bend: what a path of the slip laid out as on straights turns into where it's bent into a curve + std::vector build_double_slip(glm::dvec3 const &PointA, glm::dvec3 const &PointB, glm::dvec3 const &PointC, glm::dvec3 const &PointD, glm::dvec2 const &Crossing, double const Height, glm::dvec2 const &First, glm::dvec2 const &Second, double const Angle, editor_track::slip_curve const &Curve, TTrack &Style, std::string const &Type = "Rkpd", + std::function const &Bend = {}); + // a slip switch bent into a curve of a side track (ST-T1-A6 11.4 pkt 4b): laid out on the tangent to the curve at its crossing, where + // it was pressed, and bent onto the curve as a switch laid into it is, its crossing track coming along with its ends free. Direction + // and Side as of a switch dragged into a curve: the crossing track goes ahead to that side, through the quarter of the cursor, and the + // slip curve of a single slip lies on that side + struct curved_slip + { + std::string problem; + std::string snapped; // what the ends of the slip were drawn to + double middle{0.0}; // the station of the crossing along the curve + double half{0.0}; // from the crossing to the ends of the slip + double reach{0.0}; // from the crossing to the joints of the template, the straights of both tracks laid out that far + int direction{1}; + double angle{0.0}; + editor_track::slip_curve curve; + // as the slip is laid out on the tangent, before it's bent: its crossing, its tracks going Direction along the curve, its ends + glm::dvec2 crossing{0.0}; + glm::dvec2 first{0.0, 1.0}; + glm::dvec2 second{0.0, 1.0}; + std::array points{}; + }; + bool plan_curved_slip(editor_track::switch_template const &Shape, int const Direction, int const Side, curved_slip &Plan) const; + // the path of the slip laid out on the tangent, bent onto the curve + segment_data bent_slip_path(curved_slip const &Plan, segment_data const &Path) const; + // the two tracks through the slip and its slip curves, each from end to end, bent: the first track, the slip curve leaving it at its + // start, the second track, the other slip curve of a double slip + std::vector curved_slip_lines(curved_slip const &Plan, std::string const &Type) const; + // the straights of both tracks from the ends of the slip out to the joints of the template, bent + std::vector curved_slip_reaches(curved_slip const &Plan) const; + switch_curve::slip_report curved_slip_report(curved_slip const &Plan, std::string const &Type) const; + // the radii of the slip and its cant, as the view and the status tell them + static std::vector slip_report_lines(switch_curve::slip_report const &Report); + void insert_curved_slip(int const Direction, int const Side); bool start_extend(editor_track::snap_target const &End); void finish_extend(); std::vector extend_pieces() const; diff --git a/application/editormodeselftestswitch.cpp b/application/editormodeselftestswitch.cpp index ab881fbc..5ba0f2b5 100644 --- a/application/editormodeselftestswitch.cpp +++ b/application/editormodeselftestswitch.cpp @@ -68,6 +68,20 @@ editor_mode::selftest_result editor_mode::selftest_switch(std::string const &com text += format(", drive %.3f m from the joint", geometry::plan_distance(found->standing.location, start)); else text += ", no drive"; + // how far apart the rail of the blade and the one it lies at are along the path, where the blades of a slip are shaped + if (track->DoubleSlip()) + for (int path = 0; path < 2; ++path) + { + auto const &shape{track->blade(path)}; + auto const gap = [&](double const At) { + auto const &gauge{shape.gauge}; + auto const next{std::find_if(gauge.begin(), gauge.end(), [&](glm::dvec2 const &Sample) { return Sample.x >= At; })}; + return next == gauge.end() ? (gauge.empty() ? -1.0 : gauge.back().y) : next->y; + }; + text += format(", path %d %s side %d gaps", path, shape.valid ? "shaped" : "not shaped", track->SwitchExtension->RightSwitch == (path == 0) ? 1 : -1); + for (auto const at : {0.354, 1.0, 1.248, 2.0, 4.0, 6.0, 8.0, 9.4}) + text += format(" %.1f:%.4f", at, gap(at)); + } WriteLog(text); } return selftest_result::handled; @@ -575,7 +589,29 @@ editor_mode::selftest_result editor_mode::selftest_switch(std::string const &com tool.frame_tracks = chain.tracks; tool.curved = true; tool.along = nearest_station(tool.frame, point); - insert_curved_switch(direction, side); + tool.pressed = tool.along; + // a slip bent into the curve with its crossing track, its paths and its record logged + if (tool.templates[tool.armed].double_slip) + { + insert_curved_slip(direction, side); + if (auto const *part{selected_track()}; part != nullptr && part->DoubleSlip()) + if (auto const known{switch_store::named(*part)}) + { + WriteLog(format("SELFTEST switchcurve slip %s: %s, %zu parts", known->name.c_str(), switch_store::label(*known).c_str(), known->parts.size())); + for (auto const &name : known->parts) + if (auto const *track{simulation::Paths.find(name)}) + for (std::size_t i = 0; i < track->m_paths.size(); ++i) + { + auto const &path{track->m_paths[i]}; + auto const &a{path.points[segment_data::point::start]}; + auto const &b{path.points[segment_data::point::end]}; + WriteLog(format("SELFTEST switchcurve slip path %s/%zu: %.3f %.3f %.3f -> %.3f %.3f %.3f, R %.1f, %zu pieces, rolls %.2f %.2f", name.c_str(), i, a.x, a.y, a.z, b.x, b.y, b.z, static_cast(path.radius), path.pieces.size(), + static_cast(path.rolls[0]), static_cast(path.rolls[1]))); + } + } + } + else + insert_curved_switch(direction, side); } else { diff --git a/application/editormodeswitchplace.cpp b/application/editormodeswitchplace.cpp index 3024f427..408663a6 100644 --- a/application/editormodeswitchplace.cpp +++ b/application/editormodeswitchplace.cpp @@ -59,6 +59,16 @@ std::vector editor_mode::switch_preview() const int forward; int side; curved_switch_pose(forward, side); + if (tool.templates[tool.armed].double_slip) + { + curved_slip plan; + if (false == plan_curved_slip(tool.templates[tool.armed], forward, side, plan)) + return {}; + auto lines{curved_slip_lines(plan, tool.templates[tool.armed].type)}; + auto const reaches{curved_slip_reaches(plan)}; + lines.insert(lines.end(), reaches.begin(), reaches.end()); + return lines; + } return curved_switch_paths(tool.templates[tool.armed], tool.frame, curved_switch_start(forward), forward, side); } if (tool.templates[tool.armed].double_slip) @@ -104,9 +114,10 @@ bool editor_mode::start_switch_placement() if (current_straight().tracks.empty()) { auto *track{selected}; - if (tool.templates[tool.armed].double_slip) + // a slip is bent into the curve, in a side track only, a crossing goes only where two straights cross + if (tool.templates[tool.armed].double_slip && tool.templates[tool.armed].type == "Skrz") { - tool.status = refused(STR_C("A double slip goes only into a straight crossing another one")); + tool.status = refused(STR_C("A crossing goes only where two straights cross")); return false; } editor_track::curve curve; @@ -216,6 +227,11 @@ void editor_mode::finish_switch_placement() int forward; int side; curved_switch_pose(forward, side); + if (tool.templates[tool.armed].double_slip) + { + insert_curved_slip(forward, side); + return; + } tool.along = curved_switch_start(forward); insert_curved_switch(forward, side); return; @@ -832,83 +848,97 @@ std::vector editor_mode::link_tracks(editor_track::snap_target const & return pieces; } -std::vector editor_mode::build_double_slip(glm::dvec3 const &PointA, glm::dvec3 const &PointB, glm::dvec3 const &PointC, glm::dvec3 const &PointD, glm::dvec2 const &Crossing, double const Height, glm::dvec2 const &First, glm::dvec2 const &Second, double const Angle, editor_track::slip_curve const &Curve, TTrack &Style, std::string const &Type) +namespace { - auto const at = [](glm::dvec2 const &Planar, double const Y) { return glm::dvec3{Planar.x, Y, Planar.y}; }; - auto const straight = [](glm::dvec3 const &Start, glm::dvec3 const &End) { + +segment_data straight_path(glm::dvec3 const &Start, glm::dvec3 const &End) +{ + segment_data path; + path.points[segment_data::point::start] = Start; + path.points[segment_data::point::end] = End; + return path; +} + +// the part of the slip curve from Start along Heading, turning by Turn to Finish, between the distances From and To along it; where it +// changes the radius made of pieces, with one curve from end to end for the versions which know no pieces +segment_data slip_arc(editor_track::slip_curve const &Curve, glm::dvec3 const &Start, glm::dvec2 const &Heading, double const Turn, glm::dvec3 const &Finish, double const From, double const To) +{ + auto const whole{Curve.length()}; + auto const place = [&](double const Along) { + auto const local{Curve.point(Along)}; + auto const planar{plan_of(Start) + Heading * local.x + glm::dvec2{-Heading.y, Heading.x} * (Turn * local.y)}; + return glm::dvec3{planar.x, Start.y + (Finish.y - Start.y) * Along / whole, planar.y}; + }; + auto const bend = [&](double const Begin, double const End, double const Radius, double const Handle0, double const Handle1) { + auto const d0{turned(Heading, Turn * Curve.heading(Begin))}; + auto const d3{turned(Heading, Turn * Curve.heading(End))}; segment_data path; - path.points[segment_data::point::start] = Start; - path.points[segment_data::point::end] = End; + path.points[segment_data::point::start] = place(Begin); + path.points[segment_data::point::end] = place(End); + if (Radius > 0.0) + { + path.points[segment_data::point::control1] = {d0.x * Handle0, 0.0, d0.y * Handle0}; + path.points[segment_data::point::control2] = {-d3.x * Handle1, 0.0, -d3.y * Handle1}; + } + path.radius = static_cast(Radius); return path; }; + std::vector> zones; + for (auto const &zone : {std::pair{Curve.lead, 0.0}, {Curve.lead + Curve.ends, Curve.radius}, {Curve.lead + Curve.ends + Curve.between, Curve.middle}, {whole - Curve.lead, Curve.radius}, {whole, 0.0}}) + { + if (zone.first <= (zones.empty() ? 0.0 : zones.back().first) + 1e-9) + continue; + if (false == zones.empty() && zone.second == zones.back().second) + zones.back().first = zone.first; + else + zones.push_back(zone); + } + // broken at the tips of the blades of the tables, so the model of the switch finds them there + if (Curve.tip > 0.0) + for (auto const cut : {Curve.tip, whole - Curve.tip}) + for (std::size_t i = 0; i < zones.size(); ++i) + if (cut > (i > 0 ? zones[i - 1].first : 0.0) + 0.05 && cut < zones[i].first - 0.05) + { + zones.insert(zones.begin() + i, {cut, zones[i].second}); + break; + } + std::vector pieces; + auto from{From}; + for (auto const &[to, radius] : zones) + { + auto const upto{std::min(To, to)}; + if (upto <= from + 1e-6) + continue; + auto const handle{radius > 0.0 ? 4.0 / 3.0 * std::tan((upto - from) / radius / 4.0) * radius : 0.0}; + pieces.push_back(bend(from, upto, radius, handle, handle)); + from = upto; + } + if (pieces.size() == 1) + return pieces.front(); + // the handles of the curve from end to end reach 2/3 of the way to the crossing of the tangents + auto const chord{plan_of(place(To) - place(From))}; + auto const d0{turned(Heading, Turn * Curve.heading(From))}; + auto const d3{turned(Heading, Turn * Curve.heading(To))}; + auto const sine{cross(d0, d3)}; + auto path{bend(From, To, Curve.radius, 2.0 / 3.0 * cross(chord, d3) / sine, 2.0 / 3.0 * cross(d0, chord) / sine)}; + path.pieces = std::move(pieces); + return path; +} + +} // namespace + +std::vector editor_mode::build_double_slip(glm::dvec3 const &PointA, glm::dvec3 const &PointB, glm::dvec3 const &PointC, glm::dvec3 const &PointD, glm::dvec2 const &Crossing, double const Height, glm::dvec2 const &First, glm::dvec2 const &Second, double const Angle, editor_track::slip_curve const &Curve, TTrack &Style, std::string const &Type, std::function const &Bend) +{ + auto const at = [](glm::dvec2 const &Planar, double const Y) { return glm::dvec3{Planar.x, Y, Planar.y}; }; + auto const straight = [](glm::dvec3 const &Start, glm::dvec3 const &End) { return straight_path(Start, End); }; // the ends of the two diagonals at the crossing are kept apart further than the simulation joins the ends, 2 cm auto const halfsine{std::sqrt(std::max(1e-6, (1.0 - glm::dot(First, Second)) * 0.5))}; auto const gap{std::max(0.25, 0.06 / halfsine)}; auto const side{cross(First, Second) > 0.0 ? 1.0 : -1.0}; auto const whole{Curve.length()}; - // the part of the slip curve from Start along Heading, turning by Turn to Finish, between the distances From and To along it; - // where it changes the radius made of pieces, with one curve from end to end for the versions which know no pieces - auto const arc = [&](glm::dvec3 const &Start, glm::dvec2 const &Heading, double const Turn, glm::dvec3 const &Finish, double const From, double const To) { - auto const place = [&](double const Along) { - auto const local{Curve.point(Along)}; - auto const planar{plan_of(Start) + Heading * local.x + glm::dvec2{-Heading.y, Heading.x} * (Turn * local.y)}; - return glm::dvec3{planar.x, Start.y + (Finish.y - Start.y) * Along / whole, planar.y}; - }; - auto const bend = [&](double const Begin, double const End, double const Radius, double const Handle0, double const Handle1) { - auto const d0{turned(Heading, Turn * Curve.heading(Begin))}; - auto const d3{turned(Heading, Turn * Curve.heading(End))}; - segment_data path; - path.points[segment_data::point::start] = place(Begin); - path.points[segment_data::point::end] = place(End); - if (Radius > 0.0) - { - path.points[segment_data::point::control1] = {d0.x * Handle0, 0.0, d0.y * Handle0}; - path.points[segment_data::point::control2] = {-d3.x * Handle1, 0.0, -d3.y * Handle1}; - } - path.radius = static_cast(Radius); - return path; - }; - std::vector> zones; - for (auto const &zone : {std::pair{Curve.lead, 0.0}, {Curve.lead + Curve.ends, Curve.radius}, {Curve.lead + Curve.ends + Curve.between, Curve.middle}, {whole - Curve.lead, Curve.radius}, {whole, 0.0}}) - { - if (zone.first <= (zones.empty() ? 0.0 : zones.back().first) + 1e-9) - continue; - if (false == zones.empty() && zone.second == zones.back().second) - zones.back().first = zone.first; - else - zones.push_back(zone); - } - // broken at the tips of the blades of the tables, so the model of the switch finds them there - if (Curve.tip > 0.0) - for (auto const cut : {Curve.tip, whole - Curve.tip}) - for (std::size_t i = 0; i < zones.size(); ++i) - if (cut > (i > 0 ? zones[i - 1].first : 0.0) + 0.05 && cut < zones[i].first - 0.05) - { - zones.insert(zones.begin() + i, {cut, zones[i].second}); - break; - } - std::vector pieces; - auto from{From}; - for (auto const &[to, radius] : zones) - { - auto const upto{std::min(To, to)}; - if (upto <= from + 1e-6) - continue; - auto const handle{radius > 0.0 ? 4.0 / 3.0 * std::tan((upto - from) / radius / 4.0) * radius : 0.0}; - pieces.push_back(bend(from, upto, radius, handle, handle)); - from = upto; - } - if (pieces.size() == 1) - return pieces.front(); - // the handles of the curve from end to end reach 2/3 of the way to the crossing of the tangents - auto const chord{plan_of(place(To) - place(From))}; - auto const d0{turned(Heading, Turn * Curve.heading(From))}; - auto const d3{turned(Heading, Turn * Curve.heading(To))}; - auto const sine{cross(d0, d3)}; - auto path{bend(From, To, Curve.radius, 2.0 / 3.0 * cross(chord, d3) / sine, 2.0 / 3.0 * cross(d0, chord) / sine)}; - path.pieces = std::move(pieces); - return path; - }; + auto const arc = [&](glm::dvec3 const &Start, glm::dvec2 const &Heading, double const Turn, glm::dvec3 const &Finish, double const From, double const To) { return slip_arc(Curve, Start, Heading, Turn, Finish, From, To); }; + // laid out on a straight, bent where it goes into a curve + auto const laid = [&](segment_data const &Path) { return Bend ? Bend(Path) : Path; }; auto const back = [](segment_data const &Path) { auto result{reversed(Path)}; std::reverse(result.pieces.begin(), result.pieces.end()); @@ -935,8 +965,8 @@ std::vector editor_mode::build_double_slip(glm::dvec3 const &PointA, g if (Type == "Skrz") { // the two tracks just cross, their ends far from the crossing - parts.push_back(editor_track::create_path(Style, straight(PointA, PointB))); - parts.push_back(editor_track::create_path(Style, straight(PointC, PointD))); + parts.push_back(editor_track::create_path(Style, laid(straight(PointA, PointB)))); + parts.push_back(editor_track::create_path(Style, laid(straight(PointC, PointD)))); parts.erase(std::remove(parts.begin(), parts.end(), nullptr), parts.end()); record_slip(parts, "crossing", Type, 0.0, Angle, base); return parts; @@ -944,21 +974,21 @@ std::vector editor_mode::build_double_slip(glm::dvec3 const &PointA, g editor_track::switch_template shape; shape.length = glm::distance(plan_of(PointA), Crossing); bool const single{Type == "Rkp"}; - parts.push_back(editor_track::create_switch(shape, {straight(PointA, centreA), arc(PointA, First, side, PointD, 0.0, middle - split)}, Style, base + "_a")); + parts.push_back(editor_track::create_switch(shape, {laid(straight(PointA, centreA)), laid(arc(PointA, First, side, PointD, 0.0, middle - split))}, Style, base + "_a")); if (single) - parts.push_back(editor_track::create_path(Style, straight(PointB, centreB))); + parts.push_back(editor_track::create_path(Style, laid(straight(PointB, centreB)))); else - parts.push_back(editor_track::create_switch(shape, {straight(PointB, centreB), back(arc(PointC, Second, -side, PointB, middle + split, whole))}, Style, base + "_b")); + parts.push_back(editor_track::create_switch(shape, {laid(straight(PointB, centreB)), laid(back(arc(PointC, Second, -side, PointB, middle + split, whole)))}, Style, base + "_b")); if (single) - parts.push_back(editor_track::create_path(Style, straight(PointC, centreC))); + parts.push_back(editor_track::create_path(Style, laid(straight(PointC, centreC)))); else - parts.push_back(editor_track::create_switch(shape, {straight(PointC, centreC), arc(PointC, Second, -side, PointB, 0.0, middle - split)}, Style, base + "_c")); - parts.push_back(editor_track::create_switch(shape, {straight(PointD, centreD), back(arc(PointA, First, side, PointD, middle + split, whole))}, Style, base + "_d")); - parts.push_back(editor_track::create_path(Style, straight(centreA, centreB))); - parts.push_back(editor_track::create_path(Style, straight(centreC, centreD))); - parts.push_back(editor_track::create_path(Style, arc(PointA, First, side, PointD, middle - split, middle + split))); + parts.push_back(editor_track::create_switch(shape, {laid(straight(PointC, centreC)), laid(arc(PointC, Second, -side, PointB, 0.0, middle - split))}, Style, base + "_c")); + parts.push_back(editor_track::create_switch(shape, {laid(straight(PointD, centreD)), laid(back(arc(PointA, First, side, PointD, middle + split, whole)))}, Style, base + "_d")); + parts.push_back(editor_track::create_path(Style, laid(straight(centreA, centreB)))); + parts.push_back(editor_track::create_path(Style, laid(straight(centreC, centreD)))); + parts.push_back(editor_track::create_path(Style, laid(arc(PointA, First, side, PointD, middle - split, middle + split)))); if (false == single) - parts.push_back(editor_track::create_path(Style, arc(PointC, Second, -side, PointB, middle - split, middle + split))); + parts.push_back(editor_track::create_path(Style, laid(arc(PointC, Second, -side, PointB, middle - split, middle + split)))); parts.erase(std::remove(parts.begin(), parts.end(), nullptr), parts.end()); record_slip(parts, "slip", Type, Curve.radius, Angle, base, Curve.middle); return parts; @@ -1017,15 +1047,20 @@ bool editor_mode::plan_double_slip(editor_track::straight const &Line, glm::dvec } } } - // a double slip nearer the cursor than any crossing is replaced by the one chosen + // a double slip nearer the cursor than any crossing is replaced by the one chosen, one bent into a curve isn't: it's laid out of + // straights if (Shape.type == "Rkpd") { TTrack *slip{nullptr}; double closest{25.0}; + auto const bent = [](TTrack const &Track) { + auto const known{switch_store::named(Track)}; + return known.has_value() && known->main_radius != 0.0; + }; for (auto *section : simulation::Region->sections(Point, 40.f)) for (auto const &cell : section->m_cells) for (auto *track : cell.m_paths) - if (track->eType == tt_Switch && false == track->m_editorremoved && track->DoubleSlip() && glm::distance(plan_of(track->location()), plan_of(Point)) < closest) + if (track->eType == tt_Switch && false == track->m_editorremoved && track->DoubleSlip() && glm::distance(plan_of(track->location()), plan_of(Point)) < closest && false == bent(*track)) { closest = glm::distance(plan_of(track->location()), plan_of(Point)); slip = track; @@ -1486,53 +1521,60 @@ double editor_mode::curved_switch_start(int const Direction, std::string *What) return start; } +namespace +{ + +// a path from its start, in the plan, a point every quarter of a metre +std::vector plan_points(segment_data const &Path) +{ + std::vector points; + auto const add = [&](segment_data const &Piece) { + bezier const curve{Piece}; + auto const steps{std::max(4, static_cast(std::ceil(curve.plan_length() / 0.25)))}; + for (int k = points.empty() ? 0 : 1; k <= steps; ++k) + { + auto const t{static_cast(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(k - 1) / steps))); + point.heading = points.back().heading + std::remainder(point.heading - points.back().heading, 2.0 * glm::pi()); + } + points.push_back(point); + } + }; + if (Path.pieces.empty()) + add(Path); + for (auto const &piece : Path.pieces) + add(piece); + return points; +} + +} // namespace + switch_curve::switch_report editor_mode::curved_switch_report(std::vector 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(tool.templates.size())) return {}; - // a path of the switch from its start, in the plan - auto const along = [](segment_data const &Path) { - std::vector points; - auto const add = [&](segment_data const &Piece) { - bezier const curve{Piece}; - auto const steps{std::max(4, static_cast(std::ceil(curve.plan_length() / 0.25)))}; - for (int k = points.empty() ? 0 : 1; k <= steps; ++k) - { - auto const t{static_cast(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(k - 1) / steps))); - point.heading = points.back().heading + std::remainder(point.heading - points.back().heading, 2.0 * glm::pi()); - } - 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); + return switch_curve::check(plan_points(Paths[0]), plan_points(Paths[1]), frame_points(tool.frame), Station, Station + Direction * shape.length, shape.radius, shape.ratio); } -std::vector editor_mode::curved_switch_paths(editor_track::switch_template const &Shape, std::vector const &Frame, double const Station, int const Direction, int const Side) +segment_data editor_mode::frame_path(segment_data const &Local, std::vector 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) { - auto const sample{sample_at(Frame, Station + Direction * Local.z)}; + auto const map = [&](glm::dvec3 const &Point, glm::dvec2 &Tangent, glm::dvec2 const &Localtangent) { + auto const sample{sample_at(Frame, Station + Direction * Point.z)}; auto const along{sample.tangent * static_cast(Direction)}; glm::dvec2 const across{glm::dvec2{-along.y, along.x} * static_cast(Side)}; Tangent = glm::normalize(along * Localtangent.y + across * Localtangent.x); - auto const planar{plan_of(sample.position) + across * Local.x}; + auto const planar{plan_of(sample.position) + across * Point.x}; auto const cant{glm::radians(static_cast(sample.roll) * Direction)}; - auto const cone{-Local.x * Side * std::tan(cant)}; - return glm::dvec3{planar.x, sample.position.y + Local.y + cone, planar.y}; + auto const cone{-Point.x * Side * std::tan(cant)}; + return glm::dvec3{planar.x, sample.position.y + Point.y + cone, planar.y}; }; auto const hermite = [](glm::dvec3 const &Start, glm::dvec2 const &Startdirection, glm::dvec3 const &End, glm::dvec2 const &Enddirection) { auto const handle{plan_distance(Start, End) / 3.0}; @@ -1543,47 +1585,49 @@ std::vector editor_mode::curved_switch_paths(editor_track::switch_ path.points[segment_data::point::control2] = {-Enddirection.x * handle, -(End.y - Start.y) / 3.0, -Enddirection.y * handle}; return path; }; - auto const bend = [&](segment_data const &Local) { - auto const &start{Local.points[segment_data::point::start]}; - auto const &end{Local.points[segment_data::point::end]}; - auto const &control1{Local.points[segment_data::point::control1]}; - auto const &control2{Local.points[segment_data::point::control2]}; - glm::dvec2 const chord{end.x - start.x, end.z - start.z}; - auto const localstart{glm::normalize(control1 != glm::dvec3{} ? plan_of(control1) : chord)}; - auto const localend{glm::normalize(control2 != glm::dvec3{} ? -plan_of(control2) : chord)}; - glm::dvec2 startdirection; - glm::dvec2 enddirection; - auto const p0{map(start, startdirection, localstart)}; - auto const p3{map(end, enddirection, localend)}; - auto path{hermite(p0, startdirection, p3, enddirection)}; - { - auto const cone = [&](double const T) { - auto const point{bezier{path}.point(T)}; - auto const sample{sample_at(Frame, nearest_station(Frame, point))}; - auto const lateral{glm::dot(plan_of(point) - plan_of(sample.position), glm::dvec2{-sample.tangent.y, sample.tangent.x})}; - return sample.position.y - lateral * std::tan(glm::radians(static_cast(sample.roll))); - }; - auto const h1{cone(1.0 / 3.0)}; - auto const h2{cone(2.0 / 3.0)}; - auto const y1{3.0 * h1 - 1.5 * h2 - 5.0 / 6.0 * p0.y + p3.y / 3.0}; - auto const y2{3.0 * h2 - 1.5 * h1 + p0.y / 3.0 - 5.0 / 6.0 * p3.y}; - path.points[segment_data::point::control1].y = y1 - p0.y; - path.points[segment_data::point::control2].y = y2 - p3.y; - } - auto const rollstart{sample_at(Frame, Station + Direction * start.z).roll * Direction}; - auto const rollend{sample_at(Frame, Station + Direction * end.z).roll * Direction}; - path.rolls = {static_cast(rollstart), static_cast(rollend)}; - path.radius = Local.radius; - { - bezier const curve{path}; - auto const begin{plan_of(curve.first(0.0))}; - auto const finish{plan_of(curve.first(1.0))}; - auto const turn{glm::length(begin) > 1e-9 && glm::length(finish) > 1e-9 ? geometry::signed_angle(begin, finish) : 0.0}; - if (Local.radius != 0.f && std::abs(turn) > 1e-6) - path.radius = static_cast(std::copysign(curve.plan_length() / std::abs(turn), static_cast(Local.radius))); - } - return path; - }; + auto const &start{Local.points[segment_data::point::start]}; + auto const &end{Local.points[segment_data::point::end]}; + auto const &control1{Local.points[segment_data::point::control1]}; + auto const &control2{Local.points[segment_data::point::control2]}; + glm::dvec2 const chord{end.x - start.x, end.z - start.z}; + auto const localstart{glm::normalize(control1 != glm::dvec3{} ? plan_of(control1) : chord)}; + auto const localend{glm::normalize(control2 != glm::dvec3{} ? -plan_of(control2) : chord)}; + glm::dvec2 startdirection; + glm::dvec2 enddirection; + auto const p0{map(start, startdirection, localstart)}; + auto const p3{map(end, enddirection, localend)}; + auto path{hermite(p0, startdirection, p3, enddirection)}; + { + auto const cone = [&](double const T) { + auto const point{bezier{path}.point(T)}; + auto const sample{sample_at(Frame, nearest_station(Frame, point))}; + auto const lateral{glm::dot(plan_of(point) - plan_of(sample.position), glm::dvec2{-sample.tangent.y, sample.tangent.x})}; + return sample.position.y - lateral * std::tan(glm::radians(static_cast(sample.roll))); + }; + auto const h1{cone(1.0 / 3.0)}; + auto const h2{cone(2.0 / 3.0)}; + auto const y1{3.0 * h1 - 1.5 * h2 - 5.0 / 6.0 * p0.y + p3.y / 3.0}; + auto const y2{3.0 * h2 - 1.5 * h1 + p0.y / 3.0 - 5.0 / 6.0 * p3.y}; + path.points[segment_data::point::control1].y = y1 - p0.y; + path.points[segment_data::point::control2].y = y2 - p3.y; + } + auto const rollstart{sample_at(Frame, Station + Direction * start.z).roll * Direction}; + auto const rollend{sample_at(Frame, Station + Direction * end.z).roll * Direction}; + path.rolls = {static_cast(rollstart), static_cast(rollend)}; + path.radius = Local.radius; + { + bezier const curve{path}; + auto const begin{plan_of(curve.first(0.0))}; + auto const finish{plan_of(curve.first(1.0))}; + auto const turn{glm::length(begin) > 1e-9 && glm::length(finish) > 1e-9 ? geometry::signed_angle(begin, finish) : 0.0}; + if (Local.radius != 0.f && std::abs(turn) > 1e-6) + path.radius = static_cast(std::copysign(curve.plan_length() / std::abs(turn), static_cast(Local.radius))); + } + return path; +} + +std::vector editor_mode::curved_switch_paths(editor_track::switch_template const &Shape, std::vector const &Frame, double const Station, int const Direction, int const Side) +{ std::vector locals{Shape.local.begin(), Shape.local.end()}; if (Shape.twofold()) { @@ -1594,9 +1638,9 @@ std::vector editor_mode::curved_switch_paths(editor_track::switch_ for (std::size_t i = 0; i < locals.size(); ++i) { auto const &local{locals[i]}; - auto path{bend(local)}; + auto path{frame_path(local, Frame, Station, Direction, Side)}; for (auto const &piece : local.pieces) - path.pieces.push_back(bend(piece)); + path.pieces.push_back(frame_path(piece, Frame, Station, Direction, Side)); // the main tracks follow the curve they are laid into if (i % 2 == 0) path.radius = sample_at(Frame, Station + Direction * 0.5 * (local.points[segment_data::point::start].z + local.points[segment_data::point::end].z)).radius; @@ -1672,3 +1716,177 @@ void editor_mode::insert_curved_switch(int const Direction, int const Side) straight_refresh(); switch_placed(placed); } + +bool editor_mode::plan_curved_slip(editor_track::switch_template const &Shape, int const Direction, int const Side, curved_slip &Plan) const +{ + auto const &tool{m_switch}; + Plan = curved_slip{}; + // a crossing has no slip curves to bend, it goes only where two straights cross + if (Shape.type == "Skrz") + { + Plan.problem = STR_C("A crossing goes only where two straights cross"); + return false; + } + if (tool.frame.size() < 2 || tool.frame_tracks.empty()) + return false; + auto const *gauged{tool.frame_tracks.front()}; + auto const angle{std::atan(1.0 / (Shape.ratio > 0.0 ? Shape.ratio : 9.0))}; + auto const blade{switch_design::secant_blade(Shape.rail, Shape.radius, Shape.ratio)}; + auto curve{editor_track::slip_of(Shape.radius, Shape.middle, angle, 0.5 * std::abs(gauged->fTrackWidth) + gauged->rail_dimensions().head + 0.002, blade.first, blade.second)}; + curve.tip = switch_design::cut_tip(Shape.rail, curve.radius); + auto const half{curve.tangent()}; + auto const total{tool.frame.back().station}; + if (2.0 * half > total - 0.02) + { + Plan.problem = format(STR_C("The curve is %.1f m long, too short for the %.1f m switch"), total, 2.0 * half); + return false; + } + // the crossing where it was pressed, the ends of the slip drawn to the ends of the cant ramps as the ends of a switch are, the + // whole of it on the curve + auto const points{frame_points(tool.frame)}; + auto const start{switch_curve::snapped_start(switch_curve::ramp_ends(points), tool.pressed - Direction * half, 2.0 * half, Direction, 3.0, &Plan.snapped)}; + auto const middle{std::clamp(start + Direction * half, half, total - half)}; + if (std::abs(middle - (start + Direction * half)) > 1e-6) + Plan.snapped.clear(); + // bent with a curve as sharp as its slip curves, the outer one would turn the other way + double sharpest{0.0}; + for (auto s{middle - half}; s <= middle + half + 1e-9; s += 0.5) + sharpest = std::max(sharpest, std::abs(switch_curve::curvature_at(points, s))); + if (sharpest * curve.radius > 1.0 - 1e-6) + { + Plan.problem = format(STR_C("The curve of R %.0f m is as sharp as the slip curves of R %.0f m or sharper, the slip isn't bent into it"), 1.0 / sharpest, curve.radius); + return false; + } + auto const sample{sample_at(tool.frame, middle)}; + Plan.middle = middle; + Plan.half = half; + // the straights of both tracks out to the joints of the template, as far as the curve goes on + Plan.reach = std::max(half, std::min(Shape.a, std::min(middle, total - middle) - 0.02)); + Plan.direction = Direction; + Plan.angle = angle; + Plan.curve = curve; + Plan.crossing = plan_of(sample.position); + Plan.first = sample.tangent * static_cast(Direction); + glm::dvec2 const left{-Plan.first.y, Plan.first.x}; + Plan.second = Plan.first * std::cos(angle) + left * (static_cast(Side) * std::sin(angle)); + auto const at = [](glm::dvec2 const &Planar) { return glm::dvec3{Planar.x, 0.0, Planar.y}; }; + Plan.points = {at(Plan.crossing - Plan.first * half), at(Plan.crossing + Plan.first * half), at(Plan.crossing - Plan.second * half), at(Plan.crossing + Plan.second * half)}; + return true; +} + +segment_data editor_mode::bent_slip_path(curved_slip const &Plan, segment_data const &Path) const +{ + auto const &frame{m_switch.frame}; + // in the plan of a template: x across the tangent, to the left of the way the slip goes along the curve, z along it from the crossing + glm::dvec2 const left{-Plan.first.y, Plan.first.x}; + auto const local = [&](segment_data const &Piece) { + auto result{Piece}; + result.pieces.clear(); + for (auto const index : {segment_data::point::start, segment_data::point::end}) + { + auto const offset{plan_of(Piece.points[index]) - Plan.crossing}; + result.points[index] = {glm::dot(offset, left), Piece.points[index].y, glm::dot(offset, Plan.first)}; + } + for (auto const index : {segment_data::point::control1, segment_data::point::control2}) + if (auto const &handle{Piece.points[index]}; handle != glm::dvec3{}) + result.points[index] = {glm::dot(plan_of(handle), left), handle.y, glm::dot(plan_of(handle), Plan.first)}; + return result; + }; + auto const bent = [&](segment_data const &Piece) { + auto const flat{local(Piece)}; + auto result{frame_path(flat, frame, Plan.middle, Plan.direction, 1)}; + // the straights of the slip follow the curve they're bent with + if (Piece.radius == 0.f) + result.radius = sample_at(frame, Plan.middle + Plan.direction * 0.5 * (flat.points[segment_data::point::start].z + flat.points[segment_data::point::end].z)).radius; + return result; + }; + auto result{bent(Path)}; + for (auto const &piece : Path.pieces) + result.pieces.push_back(bent(piece)); + return result; +} + +std::vector editor_mode::curved_slip_lines(curved_slip const &Plan, std::string const &Type) const +{ + auto const &[a, b, c, d]{Plan.points}; + auto const side{cross(Plan.first, Plan.second) > 0.0 ? 1.0 : -1.0}; + auto const whole{Plan.curve.length()}; + std::vector lines{bent_slip_path(Plan, straight_path(a, b)), bent_slip_path(Plan, slip_arc(Plan.curve, a, Plan.first, side, d, 0.0, whole)), bent_slip_path(Plan, straight_path(c, d))}; + if (Type != "Rkp") + lines.push_back(bent_slip_path(Plan, slip_arc(Plan.curve, c, Plan.second, -side, b, 0.0, whole))); + return lines; +} + +std::vector editor_mode::curved_slip_reaches(curved_slip const &Plan) const +{ + if (Plan.reach < Plan.half + 0.01) + return {}; + auto const at = [](glm::dvec2 const &Planar) { return glm::dvec3{Planar.x, 0.0, Planar.y}; }; + auto const &[a, b, c, d]{Plan.points}; + return {bent_slip_path(Plan, straight_path(at(Plan.crossing - Plan.first * Plan.reach), a)), bent_slip_path(Plan, straight_path(b, at(Plan.crossing + Plan.first * Plan.reach))), + bent_slip_path(Plan, straight_path(at(Plan.crossing - Plan.second * Plan.reach), c)), bent_slip_path(Plan, straight_path(d, at(Plan.crossing + Plan.second * Plan.reach)))}; +} + +switch_curve::slip_report editor_mode::curved_slip_report(curved_slip const &Plan, std::string const &Type) const +{ + auto const lines{curved_slip_lines(Plan, Type)}; + std::vector> curves{plan_points(lines[1])}; + if (lines.size() > 3) + curves.push_back(plan_points(lines[3])); + return switch_curve::check_slip({plan_points(lines[0]), plan_points(lines[2])}, curves, frame_points(m_switch.frame), Plan.middle - Plan.direction * Plan.half, Plan.middle + Plan.direction * Plan.half); +} + +std::vector editor_mode::slip_report_lines(switch_curve::slip_report const &Report) +{ + // 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 lines{format(STR_C("tracks through the slip R %s and %s"), radius(Report.first).c_str(), radius(Report.second).c_str())}; + std::string curves; + for (auto const curvature : Report.curves) + curves += (curves.empty() ? "" : ", ") + radius(curvature); + if (false == curves.empty()) + lines.push_back(format(STR_C("slip curves R %s at their sharpest"), curves.c_str())); + lines.push_back(format(STR_C("cant %.0f mm at the start, %.0f mm at the end"), Report.cant_start, Report.cant_end)); + return lines; +} + +void editor_mode::insert_curved_slip(int const Direction, int const Side) +{ + auto &tool{m_switch}; + auto const &shape{tool.templates[tool.armed]}; + curved_slip plan; + if (false == plan_curved_slip(shape, Direction, Side, plan)) + { + tool.status = refused(plan.problem); + return; + } + auto const report{curved_slip_report(plan, shape.type)}; + std::vector> states; + std::vector created; + std::vector removed; + std::vector candidates{tool.frame_tracks}; + auto &style{*tool.frame_tracks.front()}; + // the curve taken out up to the joints of the template, the straights of both tracks laid out there + cut_curve(candidates, tool.frame, sample_at(tool.frame, plan.middle - plan.reach).position, sample_at(tool.frame, plan.middle + plan.reach).position, states, created, removed); + auto const parts{build_double_slip(plan.points[0], plan.points[1], plan.points[2], plan.points[3], plan.crossing, 0.0, plan.first, plan.second, plan.angle, plan.curve, style, shape.type, + [&](segment_data const &Path) { return bent_slip_path(plan, Path); })}; + std::vector made{parts}; + for (auto const &path : curved_slip_reaches(plan)) + if (auto *straight{editor_track::create_path(style, path)}) + made.push_back(straight); + editor_track::commit(made); + created.insert(created.end(), made.begin(), made.end()); + push_track_snapshot(std::move(states), std::move(created), std::move(removed)); + straight_refresh(); + switch_placed(parts); + // after what the drives placed with it tell: its radii and what the norms say of it + std::string text{STR_C("Slip bent into the curve, the ends of its crossing track free: lay on from them")}; + for (auto const &line : slip_report_lines(report)) + text += "\n" + line; + for (auto const &warning : report.warnings) + text += "\n" + warning; + for (auto const ¬e : report.notes) + text += "\n" + note; + tool.status += (tool.status.empty() ? "" : "\n") + text; + ui()->set_status(tool.status, false); +} diff --git a/application/editormodeswitchtool.cpp b/application/editormodeswitchtool.cpp index ecdf8822..47ac9fbc 100644 --- a/application/editormodeswitchtool.cpp +++ b/application/editormodeswitchtool.cpp @@ -231,7 +231,7 @@ void editor_mode::render_switch_ui() char const *tooltip; }; choice const choices[] = {{switch_kind::single, "Switch", "A switch in a straight or a curve"}, - {switch_kind::slip, "Slip switch", "Rkp or Rkpd at a crossing of two straights at its skos"}, + {switch_kind::slip, "Slip switch", "Rkp or Rkpd at a crossing of two straights at its skos, or bent into a curve of a side track"}, {switch_kind::crossing, "Crossing", "A crossing of two straights"}, {switch_kind::connection, "Connection", "Crossover, double, scissors, ladder"}}; { @@ -277,7 +277,7 @@ void editor_mode::render_switch_ui() std::vector> how; switch (kind) { - case switch_kind::slip: how = {{"Drag at a crossing", "of two straights at the skos of the slip: it goes there"}}; break; + case switch_kind::slip: how = {{"Drag at a crossing", "of two straights at the skos of the slip: it goes there"}, {"Drag on a curve", "bent into it, in a side track only (11.4 pkt 4b)"}}; break; case switch_kind::crossing: how = {{"Drag at a crossing", "of two straights: it goes there"}}; break; case switch_kind::connection: how = {{"Drag", "from a straight onto the other track"}, {"Shift+wheel", "the kind"}, {"Ctrl+wheel", "R of the aligning curve"}}; diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 5ea94092..14753778 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1730,6 +1730,8 @@ std::vector editor_mode::track_key_hints(bool const All) else if (m_switch.placing && m_switch.free) hints = m_switch.free_end.track != nullptr ? std::vector{{"Drag sideways", "side of the diverging track"}, {"Esc", "cancel"}} : std::vector{{"Drag along", "the direction of the main track, held from 4 m on"}, {"Drag sideways", "side of the diverging track"}, {"Back to the start", "the direction free again"}, {"Esc", "cancel"}}; + else if (m_switch.placing && m_switch.curved && current_switch_kind() == switch_kind::slip) + hints = {{"Drag", "the crossing track ahead through the quarter of the cursor, the slip curve of a single slip on its side"}, {"Esc", "cancel"}}; else if (m_switch.placing) hints = m_switch.connection >= 0 && false == m_switch.curved ? std::vector{{"Drag onto the other track", "the connection there"}, {"Shift+wheel", "another kind"}, {"Ctrl+wheel", "R of the aligning curve"}, {"Alt+wheel", "where a double crossover meets"}, {"Esc", "cancel"}} : @@ -1741,7 +1743,7 @@ std::vector editor_mode::track_key_hints(bool const All) switch (current_switch_kind()) { case switch_kind::connection: hints = {{"Press on a straight", "and drag onto the other track"}, {"Shift+wheel", "another kind"}, {"Ctrl+wheel", "R of the aligning curve"}}; break; - case switch_kind::slip: + case switch_kind::slip: hints = {{"Press+drag at a crossing", "of two straights"}, {"Press+drag on a curve", "the slip bent into it, in a side track only (11.4 pkt 4b)"}}; break; case switch_kind::crossing: hints = {{"Press+drag at a crossing", "of two straights"}}; break; default: hints = {{"LMB on a track", "the straight or the curve for the switch"}, {"Press+drag on the ground", "a switch with no track, on from a free end near it"}}; break; } @@ -1755,7 +1757,7 @@ std::vector editor_mode::track_key_hints(bool const All) else if (m_track_tab == track_tab::turnout && m_switch.armed < 0) hints = {{"List in the panel", "the type of the switch"}, {"LMB on a track", "another track"}}; else if (m_track_tab == track_tab::turnout && current_switch_kind() == switch_kind::slip) - hints = {{"Press+drag at a crossing", "of two straights at the skos of the slip"}}; + hints = {{"Press+drag at a crossing", "of two straights at the skos of the slip"}, {"Press+drag on a curve", "the slip bent into it, in a side track only (11.4 pkt 4b)"}}; else if (m_track_tab == track_tab::turnout) hints = {{"Press+drag on the track", "along: direction, sideways: side"}, {"List in the panel", "the type of the switch"}, {"LMB on another track", "choose it"}}; else if (m_track_tab == track_tab::profile && m_profile.route.spans.empty()) @@ -2522,22 +2524,43 @@ void editor_mode::draw_build_overlay() const 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 notes; + std::vector warnings; std::vector> lines; - if (false == snapped.empty()) - lines.emplace_back(snapped, IM_COL32(255, 170, 40, 255)); - if (report.curved) + double station; + if (auto const &shape{tool.templates[tool.armed]}; shape.double_slip) { - 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)); + // a slip bent: the radii of its tracks and its slip curves, its middle where the station is + curved_slip plan; + plan_curved_slip(shape, forward, side, plan); + snapped = plan.snapped; + station = plan.middle; + auto const report{curved_slip_report(plan, shape.type)}; + for (auto const &line : slip_report_lines(report)) + lines.emplace_back(line, IM_COL32(255, 255, 255, 235)); + notes = report.notes; + warnings = report.warnings; } - 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 ¬e : report.notes) + else + { + 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); }; + 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)); + notes = report.notes; + warnings = report.warnings; + } + if (false == snapped.empty()) + lines.emplace(lines.begin(), snapped, IM_COL32(255, 170, 40, 255)); + for (auto const ¬e : notes) lines.emplace_back(note, IM_COL32(170, 200, 230, 235)); - for (auto const &warning : report.warnings) + for (auto const &warning : warnings) lines.emplace_back(warning, overlay_color::invalid); ImVec2 screen; if (projection.project(sample_at(tool.frame, station).position, screen)) diff --git a/lang/editor_pl.po b/lang/editor_pl.po index e3957fd7..81d50a8c 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -12387,3 +12387,48 @@ msgstr "dwóch prostych pod skosem rozjazdu: tam stanie" msgid "of two straights at the skos of the slip" msgstr "dwóch prostych pod skosem rozjazdu" + +msgid "The curvature of a track through the slip turns from one side to the other within it, which isn't allowed (9.7 pkt 12)" +msgstr "Krzywizna toru przez rozjazd krzyżowy zmienia stronę w jego obrębie, co jest niedopuszczalne (9.7 pkt 12)" + +msgid "A slip isn't bent in the running nor in the main tracks, a bent one goes into a side track only (11.4 pkt 4b)" +msgstr "Rozjazdów krzyżowych nie łukuje się w torach szlakowych ani głównych, łukowany kładzie się tylko w torze bocznym (11.4 pkt 4b)" + +msgid "A slip curve bent to R %.0f m, below the least %.0f m of a side track (table 9.2)" +msgstr "Łuk rozjazdu krzyżowego wygięty do R %.0f m, poniżej najmniejszego %.0f m w torze bocznym (tabela 9.2)" + +msgid "In a side track the layouts needing no cant are recommended (9.3 pkt 5)" +msgstr "W torze bocznym zaleca się układy niewymagające przechyłki (9.3 pkt 5)" + +msgid "A crossing goes only where two straights cross" +msgstr "Skrzyżowanie stawia się tylko na przecięciu dwóch prostych" + +msgid "The curve of R %.0f m is as sharp as the slip curves of R %.0f m or sharper, the slip isn't bent into it" +msgstr "Łuk R %.0f m jest tak ostry jak łuki rozjazdu krzyżowego R %.0f m albo ostrzejszy, rozjazdu nie da się w niego wygiąć" + +msgid "tracks through the slip R %s and %s" +msgstr "tory przez rozjazd R %s i %s" + +msgid "slip curves R %s at their sharpest" +msgstr "łuki rozjazdu R %s w najostrzejszym miejscu" + +msgid "Slip bent into the curve, the ends of its crossing track free: lay on from them" +msgstr "Rozjazd krzyżowy wygięty w łuk, końce toru przecinającego wolne: kładź od nich dalej" + +msgid "the crossing track ahead through the quarter of the cursor, the slip curve of a single slip on its side" +msgstr "tor przecinający w przód przez ćwiartkę kursora, łuk rozjazdu pojedynczego po jej stronie" + +msgid "Press+drag on a curve" +msgstr "Wciśnij i przeciągnij na łuku" + +msgid "the slip bent into it, in a side track only (11.4 pkt 4b)" +msgstr "rozjazd krzyżowy wygięty w łuk, tylko w torze bocznym (11.4 pkt 4b)" + +msgid "Drag on a curve" +msgstr "Przeciągnij na łuku" + +msgid "bent into it, in a side track only (11.4 pkt 4b)" +msgstr "wygięty w łuk, tylko w torze bocznym (11.4 pkt 4b)" + +msgid "Rkp or Rkpd at a crossing of two straights at its skos, or bent into a curve of a side track" +msgstr "Rozjazd krzyżowy pojedynczy (Rkp) albo podwójny (Rkpd) na skrzyżowaniu dwóch prostych pod jego skosem albo wygięty w łuk toru bocznego"