diff --git a/application/editormoderoute.cpp b/application/editormoderoute.cpp index 768898c7..447a88df 100644 --- a/application/editormoderoute.cpp +++ b/application/editormoderoute.cpp @@ -1227,6 +1227,22 @@ void editor_mode::draw_route_overlay() const previous = next; } } + // the cant ramps: their ends across the track, the ones moved off the transitions brighter, with how the cant goes along them + for (auto const &ramp : result.ramps) + { + if (ramp.cant < 0.5) + continue; + auto const colour{ramp.own ? IM_COL32(255, 170, 40, 255) : IM_COL32(255, 170, 40, 120)}; + for (auto const end : {ramp.from, ramp.to}) + { + auto const station{alignment::evaluate(result, end)}; + glm::dvec3 const across{-station.direction.z * 2.0, 0.0, station.direction.x * 2.0}; + projection.line(drawlist, station.position - across, station.position + across, colour, 3.0f); + } + ImVec2 screen; + if (ramp.own && projection.project(alignment::evaluate(result, (ramp.from + ramp.to) * 0.5).position, screen)) + drawlist->AddText({screen.x + 8.0f, screen.y + 6.0f}, colour, format(STR_C("ramp %.0f m, %.0f mm"), ramp.to - ramp.from, ramp.cant).c_str()); + } // the part of the curve whose fields are pointed at or edited in the panel, lit up and named if (auto const focused{route_focused()}; false == focused.empty()) { @@ -2161,6 +2177,47 @@ bool editor_mode::render_route_vertex(int const Index) mark(part::whole, 0); } } + // the cant ramps at the ends of the curve: along its transitions, or where they're put, off a switch laid on a transition say + if (false == vertex.spirals && ImGui::TreeNode(STR_C("Cant ramps"))) + { + std::vector ramps; + for (auto const &ramp : route.result.ramps) + if (ramp.vertex == Index) + ramps.push_back(ramp); + auto const ramp = [&](char const *Id, char const *Label, double &Offset, double &Length, std::size_t const Which, part const What) { + ImGui::PushID(Id); + auto along{Length < 0.0}; + if (ImGui::Checkbox(Label, &along)) + { + // let go of the transition where it lay along it, to be moved from there + Length = along ? -1.0 : Which < ramps.size() ? ramps[Which].to - ramps[Which].from : 0.0; + Offset = 0.0; + changed = true; + } + mark(What, 0); + item_tooltip("The norms recommend the cant ramp along the transition curve; off it, it may lie on the straight before the curve or on the arc, a switch on the transition keeping the same cant all along"); + if (false == along) + { + ImGui::SetNextItemWidth(half); + changed |= ImGui::DragScalar("##offset", ImGuiDataType_Double, &Offset, 0.5f, nullptr, nullptr, STR_C("into the curve %.1f m")); + mark(What, 0); + item_tooltip("How far the outer end of the ramp lies into the curve from its end, below 0 out along the straight before it"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(half); + changed |= ImGui::DragScalar("##length", ImGuiDataType_Double, &Length, 0.5f, nullptr, nullptr, STR_C("length %.1f m")); + mark(What, 0); + item_tooltip("The length of the ramp, the cant rising evenly along it"); + Length = std::max(0.0, Length); + } + if (Which < ramps.size() && ramps[Which].to > ramps[Which].from) + ImGui::TextDisabled(STR_C("km %s - %s, %.2f mm/m"), profile::format_chainage(route.origin + ramps[Which].from).c_str(), profile::format_chainage(route.origin + ramps[Which].to).c_str(), + ramps[Which].cant / (ramps[Which].to - ramps[Which].from)); + ImGui::PopID(); + }; + ramp("in", STR_C("Ramp in along the transition curve"), vertex.ramp_in_offset, vertex.ramp_in_length, 0, part::entry); + ramp("out", STR_C("Ramp out along the transition curve"), vertex.ramp_out_offset, vertex.ramp_out_length, 1, part::exit); + ImGui::TreePop(); + } if (ImGui::TreeNode(STR_C("Details"))) { for (auto const &curve : route.result.curves) diff --git a/application/editormodeswitchplace.cpp b/application/editormodeswitchplace.cpp index 5b87c716..a108f90c 100644 --- a/application/editormodeswitchplace.cpp +++ b/application/editormodeswitchplace.cpp @@ -1480,8 +1480,6 @@ void editor_mode::insert_curved_switch(int const Direction, int const Side) 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}; diff --git a/editor/editorAlignment.cpp b/editor/editorAlignment.cpp index 66410010..74bfffff 100644 --- a/editor/editorAlignment.cpp +++ b/editor/editorAlignment.cpp @@ -228,6 +228,25 @@ element const &element_at(result const &Result, double const Chainage) return lookup == Result.elements.begin() ? Result.elements.front() : *std::prev(lookup); } +// the cant of the line at the chainage, signed with the turn of its curve; where it changes all at once there, the one before the +// chainage or the one after it +double signed_cant(result const &Result, element const &Element, double const Distance, bool const Before) +{ + auto const &cants{Result.cants}; + if (cants.empty()) + return Element.turn * element_cant(Element, Distance); + auto const chainage{Element.chainage + Distance}; + auto const next{Before ? std::lower_bound(cants.begin(), cants.end(), chainage, [](glm::dvec2 const &Point, double const Value) { return Point.x < Value; }) + : std::upper_bound(cants.begin(), cants.end(), chainage, [](double const Value, glm::dvec2 const &Point) { return Value < Point.x; })}; + if (next == cants.begin()) + return cants.front().y; + if (next == cants.end()) + return cants.back().y; + auto const previous{std::prev(next)}; + auto const span{next->x - previous->x}; + return span < 1e-9 ? next->y : previous->y + (next->y - previous->y) * (chainage - previous->x) / span; +} + glm::dvec2 blossom(std::array const &Points, double const A, double const B, double const C) { glm::dvec2 r[3], s[2]; @@ -377,6 +396,126 @@ bool meets_norms(curve_report const &Curve, design const &Design) namespace { +// the length of a straight cant ramp the norms ask for to raise the cant at the speed: slow enough in time and along the track +double ramp_length(double const Speed, double const Cant, limits const &Limits) +{ + if (Cant <= 0.0) + return 0.0; + return ceil_to(std::max(Speed * Cant / (3.6 * Limits.cant_rate), Cant / 1000.0 * Limits.ramp_factor * Speed), 5.0); +} + +// the cant along the line: in each curve as its elements carry it, raised from 0 and lowered back to it on its ramps, which go along +// its transition curves unless the vertex gives them a place and a length of their own +void make_cants(design const &Design, result &R) +{ + R.cants.clear(); + R.ramps.clear(); + auto const push = [&](double const Chainage, double const Cant) { + if (false == R.cants.empty() && std::abs(R.cants.back().x - Chainage) < 1e-6 && std::abs(R.cants.back().y - Cant) < 1e-6) + return; + R.cants.push_back({Chainage, Cant}); + }; + double reached{0.0}; // where the cant of the curve before is down to 0 again + for (std::size_t k = 0; k < Design.vertices.size(); ++k) + { + auto const &vertex{Design.vertices[k]}; + int first{-1}; + int last{-1}; + for (std::size_t i = 0; i < R.elements.size(); ++i) + if (R.elements[i].vertex == static_cast(k) && R.elements[i].length > 1e-6) + { + first = first < 0 ? static_cast(i) : first; + last = static_cast(i); + } + if (first < 0) + continue; + auto const &head{R.elements[first]}; + auto const &tail{R.elements[last]}; + auto const start{head.chainage}; + auto const end{tail.chainage + tail.length}; + auto const turn{static_cast(head.turn)}; + // the transitions at the ends: the first and the last element, where the curvature grows from 0 and goes back to it + auto const entry{head.kind == element_kind::transition_in || (head.kind == element_kind::spiral && head.curvature_start == 0.0 && head.curvature_end > 0.0) ? head.length : 0.0}; + auto const exit{tail.kind == element_kind::transition_out || (tail.kind == element_kind::spiral && tail.curvature_end == 0.0 && tail.curvature_start > 0.0) ? tail.length : 0.0}; + auto const cantin{element_cant(head, entry)}; + auto const cantout{element_cant(tail, tail.length - exit)}; + if (std::max(cantin, cantout) < 0.5) + { + reached = end; + continue; + } + result::cant_ramp in{static_cast(k), start, start + entry, cantin, vertex.ramp_in_length >= 0.0}; + result::cant_ramp out{static_cast(k), end - exit, end, cantout, vertex.ramp_out_length >= 0.0}; + if (in.own) + { + in.from = start + vertex.ramp_in_offset; + in.to = in.from + vertex.ramp_in_length; + } + if (out.own) + { + out.to = end - vertex.ramp_out_offset; + out.from = out.to - vertex.ramp_out_length; + } + // the ramps kept off the curve before, within the line and off each other + if (in.from < reached - 1e-6) + { + R.warnings.emplace_back(R.ramps.empty() ? format(STR_C("Vertex %zu: the cant ramp in reaches back past the start of the line, cut %.1f m shorter"), k + 1, reached - in.from) : + format(STR_C("Vertex %zu: the cant ramp in reaches back into the curve before it, cut %.1f m shorter"), k + 1, reached - in.from)); + in.from = std::min(reached, in.to); + } + if (out.to > R.length + 1e-6) + { + R.warnings.emplace_back(format(STR_C("Vertex %zu: the cant ramp out reaches past the end of the line, cut %.1f m shorter"), k + 1, out.to - R.length)); + out.to = std::max(R.length, out.from); + } + if (in.to > out.from + 1e-6) + { + R.warnings.emplace_back(format(STR_C("Vertex %zu: the cant ramps in and out of the curve overlap by %.1f m"), k + 1, in.to - out.from)); + auto const middle{(in.to + out.from) * 0.5}; + in.to = std::max(in.from, middle); + out.from = std::min(out.to, middle); + } + auto const speed{std::max(1.0, Design.speed)}; + for (auto const *ramp : {&in, &out}) + if (ramp->own && Design.norms.checked) + if (auto const required{ramp_length(speed, ramp->cant, Design.norms)}; ramp->to - ramp->from + 1e-6 < required) + R.warnings.emplace_back(format(STR_C("Vertex %zu: the cant ramp of %.1f m is shorter than the %.0f m a cant of %.0f mm asks for"), k + 1, ramp->to - ramp->from, required, ramp->cant)); + // the curve between its ramps as its elements carry the cant, the transitions at its ends with the full cant of the curve where + // the ramps went off them + push(in.from, 0.0); + push(in.to, turn * cantin); + auto const low{std::max(in.to, start + entry)}; + auto const high{std::min(out.from, end - exit)}; + if (high > low + 1e-6) + { + push(low, turn * cantin); + for (auto i{first}; i <= last; ++i) + { + auto const &e{R.elements[i]}; + for (auto const distance : {0.0, e.length}) + if (auto const chainage{e.chainage + distance}; chainage > low + 1e-6 && chainage < high - 1e-6) + push(chainage, turn * element_cant(e, distance)); + } + push(high, turn * cantout); + } + push(out.from, turn * cantout); + push(out.to, 0.0); + R.ramps.push_back(in); + R.ramps.push_back(out); + reached = out.to; + // the time the cant takes to rise along the ramps of their own + for (auto &curve : R.curves) + if (curve.vertex == static_cast(k)) + { + if (in.own && in.to > in.from) + curve.cant_rate_in = cantin * speed / 3.6 / (in.to - in.from); + if (out.own && out.to > out.from) + curve.cant_rate_out = cantout * speed / 3.6 / (out.to - out.from); + } + } + std::stable_sort(R.cants.begin(), R.cants.end(), [](glm::dvec2 const &A, glm::dvec2 const &B) { return A.x < B.x; }); +} + result fit_between(design const &Design) { result r; @@ -953,6 +1092,7 @@ result fit_between(design const &Design) r.warnings.emplace_back(format(STR_C("The adjoining track at the start is curved (R %.0f m), the curvature changes abruptly"), Design.start_radius)); if (Design.end_radius > 0.0 && Design.end_radius < 10000.0) r.warnings.emplace_back(format(STR_C("The adjoining track at the end is curved (R %.0f m), the curvature changes abruptly"), Design.end_radius)); + make_cants(Design, r); r.valid = r.errors.empty() && r.length > 1e-3; return r; @@ -1828,8 +1968,10 @@ station evaluate(result const &Result, double const Chainage) element_point(e, Result.shape, distance, position, direction); result.position = {position.x, elevation(Result, s), position.y}; result.direction = glm::normalize(glm::dvec3{direction.x, grade(Result, s), direction.y}); - result.cant = element_cant(e, distance); - result.turn = e.turn; + // a cant ramp of its own may lie on the straight before the curve, its cant turned the way the curve turns + auto const cant{signed_cant(Result, e, distance, false)}; + result.cant = std::abs(cant); + result.turn = e.turn != 0 ? e.turn : (cant > 0.0) - (cant < 0.0); return result; } @@ -2435,6 +2577,12 @@ std::vector pieces(result const &Result, design const &Design, std spans.insert(std::next(Span), second); }; double constexpr margin{0.01}; + // the ends of the cant ramps of their own, where the cant starts or stops changing along a straight, a transition or an arc + for (auto const &ramp : Result.ramps) + for (auto const chainage : {ramp.from, ramp.to}) + if (auto inside{std::find_if(spans.begin(), spans.end(), [&](span const &Span) { return chainage_from(Span) < chainage - margin && chainage_to(Span) > chainage + margin; })}; + ramp.own && inside != spans.end()) + split(inside, chainage); for (auto const chainage : Breaks) { auto inside{std::find_if(spans.begin(), spans.end(), [&](span const &Span) { return chainage_from(Span) < chainage - margin && chainage_to(Span) > chainage + margin; })}; @@ -2566,15 +2714,16 @@ std::vector pieces(result const &Result, design const &Design, std auto const y1{y0 + grade(Result, s0) * glm::distance(p0, p1)}; auto const y2{y3 - grade(Result, s3) * glm::distance(p2, p3)}; - auto const roll = [&](double const Cant) { return static_cast(e.turn * glm::degrees(std::asin(std::clamp(Cant / Design.norms.gauge, -1.0, 1.0)))); }; + // the cant signed with the turn, a ramp of its own on a straight too; where it changes all at once, the one of the piece itself + auto const roll = [&](double const Cant) { return static_cast(glm::degrees(std::asin(std::clamp(Cant / Design.norms.gauge, -1.0, 1.0)))); }; segment_data path; path.points[segment_data::point::start] = {p0.x, y0, p0.y}; path.points[segment_data::point::end] = {p3.x, y3, p3.y}; path.points[segment_data::point::control1] = glm::dvec3{p1.x, y1, p1.y} - path.points[segment_data::point::start]; path.points[segment_data::point::control2] = glm::dvec3{p2.x, y2, p2.y} - path.points[segment_data::point::end]; - path.rolls[0] = roll(element_cant(e, piece.from)); - path.rolls[1] = roll(element_cant(e, piece.to)); + path.rolls[0] = roll(signed_cant(Result, e, piece.from, false)); + path.rolls[1] = roll(signed_cant(Result, e, piece.to, true)); path.radius = e.kind == element_kind::straight ? 0.f : static_cast(e.radius); if (e.kind == element_kind::spiral) { diff --git a/editor/editorAlignment.hpp b/editor/editorAlignment.hpp index d815006f..0aa99185 100644 --- a/editor/editorAlignment.hpp +++ b/editor/editorAlignment.hpp @@ -74,6 +74,14 @@ struct vertex // two transition curves meeting at the curvature of the radius, with no arc between them and no cant: the bend of two straights // eased the way the lines are drawn. Their lengths follow from the radius and the deflection, the ones given are left out bool spirals{false}; + // the cant ramp at each end of the curve where it doesn't go along the transition curve (ST-T1-A6 9.6 pkt 5 and 11): how far its + // outer end lies into the curve from the start of the curve, or from its end, below 0 out along the straight, and its length; a + // length below 0 keeps the ramp along the transition, as the norms recommend + double ramp_in_offset{0.0}; + double ramp_in_length{-1.0}; + double ramp_out_offset{0.0}; + double ramp_out_length{-1.0}; + bool own_ramps() const { return ramp_in_length >= 0.0 || ramp_out_length >= 0.0; } }; struct design @@ -187,6 +195,19 @@ struct result std::vector elements; std::vector curves; std::vector profile; + // the cant along the line, mm at the chainages, between them changing evenly; signed with the turn of the curve it belongs to, + // as the rolls of the paths are + std::vector cants; + // the cant ramps at the ends of the curves, where the cant goes from 0 to the one of the curve + struct cant_ramp + { + int vertex{-1}; + double from{0.0}; + double to{0.0}; + double cant{0.0}; // mm, at the end of the ramp in the curve + bool own{false}; // not along the transition curve + }; + std::vector ramps; double length{0.0}; glm::dvec3 start{0.0}; glm::dvec3 end{0.0}; diff --git a/editor/editorAlignmentStore.cpp b/editor/editorAlignmentStore.cpp index 83acbf92..5f6a73cc 100644 --- a/editor/editorAlignmentStore.cpp +++ b/editor/editorAlignmentStore.cpp @@ -48,6 +48,8 @@ bool g_loaded{false}; // vertex [ ]... [spirals 1] // spirals: two transition curves with no arc between them, their lengths following from R // [cants ...]: the cants of the radii of a compound curve set one by one, below 0 the cant in proportion to the curvature +// [ramps ]: the cant ramps of the curve where they don't go along its transitions, +// their outer ends from the start and from the end of the curve, into it above 0; a length below 0 keeps the ramp along the transition // click : a point clicked when the line was laid, its curves fitted to them // element // radii signed with the turn, 0 for no curvature @@ -147,6 +149,8 @@ void mark(scene::layer_handle const Layer) for (auto const &arc : vertex.arcs) text += format(" %.1f", arc.cant); } + if (vertex.own_ramps()) + text += format(" ramps %.3f %.3f %.3f %.3f", vertex.ramp_in_offset, vertex.ramp_in_length, vertex.ramp_out_offset, vertex.ramp_out_length); lines.push_back(text); } for (auto const &click : entry.clicks) @@ -168,6 +172,8 @@ void mark(scene::layer_handle const Layer) alignment::vertex reversed_vertex(alignment::vertex Vertex) { std::swap(Vertex.transition_in, Vertex.transition_out); + std::swap(Vertex.ramp_in_offset, Vertex.ramp_out_offset); + std::swap(Vertex.ramp_in_length, Vertex.ramp_out_length); if (Vertex.compound && false == Vertex.arcs.empty()) { struct part @@ -343,6 +349,8 @@ std::optional read_vertex(std::istringstream &Line, scene::la for (auto &arc : vertex.arcs) Line >> arc.cant; } + if (word == "ramps") + Line >> vertex.ramp_in_offset >> vertex.ramp_in_length >> vertex.ramp_out_offset >> vertex.ramp_out_length; } position = Context.to_world(position); vertex.position = {position.x, position.z}; diff --git a/editor/editorSwitchCurve.cpp b/editor/editorSwitchCurve.cpp index d1393b43..95fb8cc4 100644 --- a/editor/editorSwitchCurve.cpp +++ b/editor/editorSwitchCurve.cpp @@ -30,17 +30,15 @@ constexpr double kAtEnd{0.3}; 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 +// diverging track and the least radius of the main track; the values in its brackets, allowed only by way of exception, are past +// them as well 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}}; +constexpr bent_limit kBent[]{{300.0, 4.000e-3, 1505.522}, {500.0, 4.000e-3, 501.730}, {760.0, 3.333e-3, 497.250}, {1200.0, 2.169e-3, 750.001}, {2500.0, 1.063e-3, 1510.0}}; double heading_at(std::vector const &Track, double const Station) { @@ -147,8 +145,6 @@ switch_report check(std::vector const &Main, std::vector 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)")); @@ -162,14 +158,9 @@ switch_report check(std::vector const &Main, std::vector 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)); + report.warnings.emplace_back(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)); + report.warnings.emplace_back(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)); @@ -177,8 +168,7 @@ switch_report check(std::vector const &Main, std::vector 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)); + 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)"), 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)) diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 676586f6..3e593f66 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -11975,8 +11975,6 @@ 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)" @@ -11987,14 +11985,10 @@ msgstr "Krzywizna toru zasadniczego zmienia znak w obrębie rozjazdu, co jest ni 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)" @@ -12002,8 +11996,6 @@ msgstr "Tor zasadniczy wygięty do R %.0f m, poniżej najmniejszego %.0f m dla r 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)" @@ -12046,3 +12038,51 @@ 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" + +msgid "The cant changes within the switch, %.0f to %.0f mm: it's to stay the same over it (11.4 pkt 7)" +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)" + +msgid "Vertex %zu: the cant ramp in reaches back past the start of the line, cut %.1f m shorter" +msgstr "Wierzchołek %zu: rampa wejściowa sięga przed początek linii, skrócona o %.1f m" + +msgid "Vertex %zu: the cant ramp in reaches back into the curve before it, cut %.1f m shorter" +msgstr "Wierzchołek %zu: rampa wejściowa sięga w łuk przed nim, skrócona o %.1f m" + +msgid "Vertex %zu: the cant ramp out reaches past the end of the line, cut %.1f m shorter" +msgstr "Wierzchołek %zu: rampa wyjściowa sięga za koniec linii, skrócona o %.1f m" + +msgid "Vertex %zu: the cant ramps in and out of the curve overlap by %.1f m" +msgstr "Wierzchołek %zu: rampy wejściowa i wyjściowa łuku zachodzą na siebie o %.1f m" + +msgid "Vertex %zu: the cant ramp of %.1f m is shorter than the %.0f m a cant of %.0f mm asks for" +msgstr "Wierzchołek %zu: rampa przechyłkowa %.1f m jest krótsza niż %.0f m, których wymaga przechyłka %.0f mm" + +msgid "Cant ramps" +msgstr "Rampy przechyłkowe" + +msgid "Ramp in along the transition curve" +msgstr "Rampa wejściowa wzdłuż krzywej przejściowej" + +msgid "Ramp out along the transition curve" +msgstr "Rampa wyjściowa wzdłuż krzywej przejściowej" + +msgid "The norms recommend the cant ramp along the transition curve; off it, it may lie on the straight before the curve or on the arc, a switch on the transition keeping the same cant all along" +msgstr "Normy zalecają rampę przechyłkową wzdłuż krzywej przejściowej; poza nią może leżeć na prostej przed łukiem albo na łuku, a rozjazd na przejściówce ma wtedy na całej długości tę samą przechyłkę" + +msgid "into the curve %.1f m" +msgstr "w głąb łuku %.1f m" + +msgid "How far the outer end of the ramp lies into the curve from its end, below 0 out along the straight before it" +msgstr "Jak daleko w głąb łuku od jego końca leży zewnętrzny koniec rampy; poniżej 0 na prostej przed łukiem" + +msgid "length %.1f m" +msgstr "długość %.1f m" + +msgid "The length of the ramp, the cant rising evenly along it" +msgstr "Długość rampy, przechyłka rośnie wzdłuż niej równomiernie" + +msgid "km %s - %s, %.2f mm/m" +msgstr "km %s - %s, %.2f mm/m" + +msgid "ramp %.0f m, %.0f mm" +msgstr "rampa %.0f m, %.0f mm" diff --git a/tests/unit/test_alignment.cpp b/tests/unit/test_alignment.cpp index 033bbc81..be167a95 100644 --- a/tests/unit/test_alignment.cpp +++ b/tests/unit/test_alignment.cpp @@ -2,7 +2,9 @@ #include "doctest.h" #include "editor/editorAlignment.hpp" +#include #include +#include namespace { @@ -391,3 +393,48 @@ TEST_CASE("alignment: bends clicked close along a curve make one compound curve, } CHECK(design.vertices.size() < 12); } + +TEST_CASE("alignment: a cant ramp moved off the transition curve onto the straight before it") { + auto design{straights({300.0, 300.0}, {30.0 * pi / 180.0}, 80.0)}; + auto &vertex{design.vertices.front()}; + vertex.radius = 800.0; + vertex.transition_in = vertex.transition_out = 60.0; + vertex.cant = 60.0; + auto const along{alignment::compute(design)}; + REQUIRE(along.valid); + REQUIRE(along.ramps.size() == 2); + auto const start{along.ramps.front().from}; + CHECK(along.ramps.front().to - start == doctest::Approx(60.0)); + CHECK(alignment::evaluate(along, start + 30.0).cant == doctest::Approx(30.0)); + + // the whole ramp on the straight, the transition with the full cant all along it, as a switch laid on it wants it + vertex.ramp_in_offset = -40.0; + vertex.ramp_in_length = 40.0; + auto const moved{alignment::compute(design)}; + REQUIRE(moved.valid); + REQUIRE(moved.ramps.size() == 2); + CHECK(moved.ramps.front().own); + CHECK(moved.ramps.front().from == doctest::Approx(start - 40.0)); + CHECK(moved.ramps.front().to == doctest::Approx(start)); + CHECK(alignment::evaluate(moved, start - 50.0).cant == doctest::Approx(0.0)); + CHECK(alignment::evaluate(moved, start - 20.0).cant == doctest::Approx(30.0)); + CHECK(alignment::evaluate(moved, start + 10.0).cant == doctest::Approx(60.0)); + CHECK(alignment::evaluate(moved, start + 50.0).cant == doctest::Approx(60.0)); + // on the straight the cant leans the way the curve turns + CHECK(alignment::evaluate(moved, start - 20.0).turn == alignment::evaluate(moved, start + 100.0).turn); + // 40 m are too short to raise 60 mm at 80 km/h + CHECK(std::any_of(moved.warnings.begin(), moved.warnings.end(), [](std::string const &Warning) { return Warning.find("cant ramp of") != std::string::npos; })); + + // the paths end where the ramp starts and ends, their rolls going along it + auto const paths{alignment::pieces(moved, design, alignment::minimum_pieces(moved, design))}; + auto const meets = [&](double const Chainage) { + auto const point{alignment::evaluate(moved, Chainage).position}; + return std::any_of(paths.begin(), paths.end(), [&](segment_data const &Path) { return glm::distance(Path.points[segment_data::point::start], point) < 0.05; }); + }; + CHECK(meets(start - 40.0)); + CHECK(meets(start)); + for (std::size_t i = 1; i < paths.size(); ++i) + CHECK(paths[i].rolls[0] == doctest::Approx(paths[i - 1].rolls[1]).epsilon(1e-4)); + auto const turned{std::max_element(paths.begin(), paths.end(), [](segment_data const &A, segment_data const &B) { return std::abs(A.rolls[1]) < std::abs(B.rolls[1]); })}; + CHECK(std::abs(turned->rolls[1]) == doctest::Approx(glm::degrees(std::asin(60.0 / design.norms.gauge))).epsilon(1e-3)); +} diff --git a/tests/unit/test_switch_curve.cpp b/tests/unit/test_switch_curve.cpp index a370e5ca..53f3bd8c 100644 --- a/tests/unit/test_switch_curve.cpp +++ b/tests/unit/test_switch_curve.cpp @@ -55,7 +55,6 @@ TEST_CASE("switch on a curve: the curvatures of its tracks on a transition curve 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)); diff --git a/world/Segment.cpp b/world/Segment.cpp index 2abdf075..720e259b 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -578,6 +578,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin, pt, glm::normalize( norm ), glm::vec2 { ( jmm1 * ShapePoints[ j ].texture.x + m1 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv1 } ); + roll_vertex( Output.back(), pos1, parallel1, s - step ); } // jeśli jest wskaźnik do tablicy if (p && *p && !j) // to dla pierwszego punktu @@ -596,6 +597,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin, pt, glm::normalize( norm ), glm::vec2 { ( jmm2 * ShapePoints[ j ].texture.x + m2 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv2 } ); + roll_vertex( Output.back(), pos2, parallel2, s ); } // jeśli jest wskaźnik do tablicy // to dla pierwszego punktu @@ -618,6 +620,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin, pt, glm::normalize( norm ), glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv1 } ); + roll_vertex( Output.back(), pos1, parallel1, s - step ); pt = parallel2 * ( jmm2 * ( ShapePoints[ j ].position.x - fOffsetX.first ) + m2 * ( ShapePoints[ j ].position.x - fOffsetX.second ) ) + pos2; pt.y += ShapePoints[ j ].position.y; @@ -628,6 +631,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin, pt, glm::normalize( norm ), glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv2 } ); + roll_vertex( Output.back(), pos2, parallel2, s ); } } } @@ -638,6 +642,25 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin, return true; }; +void TSegment::roll_vertex( gfx::basic_vertex &Vertex, glm::vec3 const &Position, glm::vec3 const &Parallel, double const Distance ) const { + if( false == m_baked_roll.has_value() ) { + return; + } + auto const delta { GetRoll( std::clamp( Distance, 0.0, fLength ) ) - *m_baked_roll }; + if( std::abs( delta ) < 1e-6f ) { + return; + } + auto const c { std::cos( delta ) }; + auto const s { std::sin( delta ) }; + // across the path and up turned the way the profiles are turned by the roll, along the path as it was + auto const turn = [&]( glm::vec3 const &Vector ) { + auto const across { glm::dot( Vector, Parallel ) }; + auto const along { Vector - Parallel * across - glm::vec3 { 0.f, Vector.y, 0.f } }; + return along + Parallel * ( across * c + Vector.y * s ) + glm::vec3 { 0.f, -across * s + Vector.y * c, 0.f }; }; + Vertex.position = Position + turn( Vertex.position - Position ); + Vertex.normal = turn( Vertex.normal ); +} + bool TSegment::RenderLoftSteps( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, double fTextureLength, int const Steps ) const { if( fTsBuffer.empty() || Steps < 1 || fLength <= 0.0 ) @@ -675,7 +698,8 @@ bool TSegment::RenderLoftSteps( gfx::vertex_array &Output, glm::dvec3 const &Ori point.y += rest * from.position.y + Share * to.position.y; auto normal { ( rest * from.normal.x + Share * to.normal.x ) * Parallel }; normal.y += rest * from.normal.y + Share * to.normal.y; - Output.emplace_back( point, glm::normalize( normal ), glm::vec2 { rest * from.texture.x + Share * to.texture.x, V } ); }; + Output.emplace_back( point, glm::normalize( normal ), glm::vec2 { rest * from.texture.x + Share * to.texture.x, V } ); + roll_vertex( Output.back(), Position, Parallel, Share * fLength ); }; glm::vec3 pos1, pos2, parallel1, parallel2; station( 0, pos1, parallel1 ); @@ -917,6 +941,9 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig Ring.emplace_back( pt, glm::normalize( norm ), glm::vec2{ shape.texture.x, 0.f } ); Wear.push_back( Span.rampwidth != 0.0 ? static_cast( std::clamp( ( Span.rampwidth > 0.0 ? outside - worn : worn - outside ) / std::abs( Span.rampwidth ) + 1.0, 0.0, 1.0 ) ) : 1.f ); } + for( auto &vertex : Ring ) { + roll_vertex( vertex, position, parallel, Distance ); + } }; auto const texturelength { static_cast( Span.texturelength ) }; diff --git a/world/Segment.h b/world/Segment.h index a2e5d9c3..ac30b0c4 100644 --- a/world/Segment.h +++ b/world/Segment.h @@ -102,6 +102,9 @@ class TSegment std::vector m_starts; // distance from Point1 to the start of each part std::optional> m_joints[ 2 ]; double m_dense { 0.0 }; + // a switch makes the profiles of its rails and its bed at the mean of the rolls of its paths: lofted along a path, they're turned + // from it to the roll of the path there, so a switch on a cant ramp follows it; none for the other paths + std::optional m_baked_roll; glm::dvec3 GetFirstDerivative(double const fTime) const; double RombergIntegral(double const fA, double const fB) const; @@ -111,6 +114,8 @@ class TSegment glm::dvec3 RaInterpolate(double const t) const; glm::dvec3 RaInterpolate0(double const t) const; std::size_t piece_at(double const s) const; + // the vertex of a profile lofted at the point and across the path there, turned from the baked roll to the one of the path at the distance + void roll_vertex( gfx::basic_vertex &Vertex, glm::vec3 const &Position, glm::vec3 const &Parallel, double const Distance ) const; public: bool bCurve = false; @@ -200,6 +205,9 @@ public: void set_dense( double const Distance ) { m_dense = Distance; } + void + set_baked_roll( std::optional const Roll ) { + m_baked_roll = Roll; } /* void Render(); diff --git a/world/Track.cpp b/world/Track.cpp index 19a9d140..e57fa715 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -1086,6 +1086,13 @@ void TTrack::init_segments( bool const Initial ) { fTexHeightOffset = texheightoffset; SwitchExtension->Segments[ index ]->InitChain( std::move( chain ) ); } + // the profiles of the switch are made at the mean of the rolls of its paths; lofted, they're turned from it to the roll of + // each path along it, so a switch on a cant ramp follows the ramp to its ends + float mean1, mean2; + profile_rolls( mean1, mean2 ); + for( std::size_t index = 0; index < 2; ++index ) { + SwitchExtension->Segments[ index ]->set_baked_roll( mean1 ); + } } if( Initial ) {