diff --git a/application/editormoderoute.cpp b/application/editormoderoute.cpp index 1f093e50..b54b364d 100644 --- a/application/editormoderoute.cpp +++ b/application/editormoderoute.cpp @@ -58,6 +58,10 @@ void editor_mode::route_recommend(alignment::vertex &Vertex) const sharpest = std::min(sharpest, arc.radius); auto const recommended{alignment::recommend(m_route.design.speed, sharpest, laying_norms())}; Vertex.cant = recommended.cant; + // the cants of the arcs in proportion to their curvature, as the norms recommend them + Vertex.first_cant = -1.0; + for (auto &arc : Vertex.arcs) + arc.cant = -1.0; Vertex.transition_in = recommended.transition; Vertex.transition_out = recommended.transition; alignment::recommend_between(Vertex, m_route.design.speed, laying_norms()); @@ -1740,7 +1744,9 @@ bool editor_mode::render_route_vertex(int const Index) ImU32 const transition{IM_COL32(255, 140, 30, 255)}; changed |= field("##radius", vertex.radius, 5.0f, vertex.compound ? STR_C("R1 %.0f m") : STR_C("R %.0f m"), overlay_color::grip, "Radius of the arc: drag sideways, or a double click to type it"); ImGui::SameLine(); - changed |= field("##cant", vertex.cant, 1.0f, STR_C("cant %.0f mm"), IM_COL32(240, 170, 60, 255), "Height of the outer rail above the inner one"); + changed |= field("##cant", vertex.cant, 1.0f, STR_C("cant %.0f mm"), IM_COL32(240, 170, 60, 255), + vertex.compound ? "Height of the outer rail above the inner one on the sharpest arc; the arcs with no cant of their own get it in proportion to their curvature" + : "Height of the outer rail above the inner one"); changed |= field("##entry", vertex.transition_in, 1.0f, STR_C("entry %.0f m"), transition, "Length of the transition curve leading into the arc"); ImGui::SameLine(); changed |= field("##exit", vertex.transition_out, 1.0f, STR_C("exit %.0f m"), transition, "Length of the transition curve leading out of the arc"); @@ -1766,9 +1772,29 @@ bool editor_mode::render_route_vertex(int const Index) for (auto const &arc : vertex.arcs) total += std::max(0.0, arc.share); auto const percent = [&](double const Share) { return total > 0.0 ? 100.0 * std::max(0.0, Share) / total : 0.0; }; + auto const cants{alignment::compound_cants(vertex)}; + // the cant of one arc: the one in proportion to the curvature until it's given its own + auto const arc_cant = [&](int const Arc, double &Cant) { + auto value{static_cast(Arc) < cants.size() ? cants[Arc] : std::max(0.0, Cant)}; + auto edited{ImGui::DragScalar(format(STR_C("Cant of R%d (mm)"), Arc + 1).c_str(), ImGuiDataType_Double, &value, 1.0f, nullptr, nullptr, "%.0f")}; + if (edited) + Cant = std::max(0.0, value); + item_tooltip("The cant of this arc, ramped along the transition curves to the arcs either side of it; until it's set here, the cant of the curve in proportion to the curvature of the arc"); + ImGui::SameLine(); + if (Cant < 0.0) + ImGui::TextDisabled("%s", STR_C("auto")); + else if (ImGui::SmallButton(STR_C("auto"))) + { + Cant = -1.0; + edited = true; + } + item_tooltip("The cant of the arc in proportion to its curvature again"); + return edited; + }; changed |= ImGui::DragScalar(format(STR_C("Share of R%d"), 1).c_str(), ImGuiDataType_Double, &vertex.share, 0.01f, nullptr, nullptr, "%.2f"); ImGui::SameLine(); ImGui::TextDisabled("%.0f%%", percent(vertex.share)); + changed |= arc_cant(0, vertex.first_cant); int removed{-1}; for (int k = 0; k < static_cast(vertex.arcs.size()); ++k) { @@ -1784,6 +1810,7 @@ bool editor_mode::render_route_vertex(int const Index) changed |= ImGui::DragScalar(format(STR_C("Share of R%d"), k + 2).c_str(), ImGuiDataType_Double, &arc.share, 0.01f, nullptr, nullptr, "%.2f"); ImGui::SameLine(); ImGui::TextDisabled("%.0f%%", percent(arc.share)); + changed |= arc_cant(k + 1, arc.cant); arc.radius = std::max(1.0, arc.radius); arc.transition = std::max(0.0, arc.transition); arc.share = std::max(0.0, arc.share); diff --git a/application/editormodetrackshared.h b/application/editormodetrackshared.h index fd97d2fe..96002301 100644 --- a/application/editormodetrackshared.h +++ b/application/editormodetrackshared.h @@ -92,7 +92,12 @@ inline std::string curve_label(alignment::curve_report const &Curve, alignment:: text += format(i == 0 ? "%.0f" : "/%.0f", Curve.radii[i]); if (Curve.transition_in > 0.0 || Curve.transition_out > 0.0) text += std::abs(Curve.transition_in - Curve.transition_out) < 0.5 ? format(" L %.0f", Curve.transition_in) : format(" L %.0f/%.0f", Curve.transition_in, Curve.transition_out); - if (Curve.cant > 0.0) + // a compound curve shows the cant of each arc where they aren't all the same + auto const varied{std::any_of(Curve.cants.begin(), Curve.cants.end(), [&](double const Cant) { return std::abs(Cant - Curve.cants.front()) >= 0.5; })}; + if (varied) + for (std::size_t i = 0; i < Curve.cants.size(); ++i) + text += format(i == 0 ? " h %.0f" : "/%.0f", Curve.cants[i]); + else if (Curve.cant > 0.0) text += format(" h %.0f", Curve.cant); return text + speed_of(curve_sharpest(Curve), Limits); } @@ -301,6 +306,7 @@ inline void compound_from(editor_track::curve const &Curve, alignment::vertex &V Vertex.compound = true; Vertex.radius = std::round(Curve.arcs.front().radius); Vertex.share = Curve.arcs.front().turn; + Vertex.first_cant = -1.0; Vertex.arcs.clear(); for (std::size_t i = 1; i < Curve.arcs.size(); ++i) Vertex.arcs.push_back({std::round(Curve.arcs[i].radius), std::round(Curve.arcs[i].transition), Curve.arcs[i].turn}); diff --git a/editor/editorAlignment.cpp b/editor/editorAlignment.cpp index 77d91caa..64823f7c 100644 --- a/editor/editorAlignment.cpp +++ b/editor/editorAlignment.cpp @@ -106,12 +106,14 @@ local_point spiral_point(double const Start, double const End, double const Leng struct compound_curve { - std::vector> segments; + // the curvature at the start and at the end, the length, the cant at the start and at the end + std::vector> segments; local_point end; double shortened{1.0}; }; -compound_curve build_compound(double const Deflection, std::vector const &Arcs, double const Lengthin, double const Lengthout) +// Cants: of the arcs, the cants of the segments stay 0 without them +compound_curve build_compound(double const Deflection, std::vector const &Arcs, double const Lengthin, double const Lengthout, std::vector const &Cants = {}) { compound_curve result; if (Arcs.empty()) @@ -119,6 +121,7 @@ compound_curve build_compound(double const Deflection, std::vector std::vector curvatures; for (auto const &arc : Arcs) curvatures.push_back(1.0 / std::max(1.0, arc.radius)); + auto const cant = [&](std::size_t const Arc) { return Arc < Cants.size() ? Cants[Arc] : 0.0; }; auto transitions{curvatures.front() * Lengthin + curvatures.back() * Lengthout}; for (std::size_t i = 1; i < Arcs.size(); ++i) transitions += (curvatures[i - 1] + curvatures[i]) * std::max(0.0, Arcs[i].transition); @@ -130,15 +133,15 @@ compound_curve build_compound(double const Deflection, std::vector double total{0.0}; for (auto const &arc : Arcs) total += std::max(0.0, arc.share); - std::vector> segments{{0.0, curvatures.front(), Lengthin * f}}; + std::vector> segments{{0.0, curvatures.front(), Lengthin * f, 0.0, cant(0)}}; for (std::size_t i = 0; i < Arcs.size(); ++i) { if (i > 0) - segments.push_back({curvatures[i - 1], curvatures[i], std::max(0.0, Arcs[i].transition) * f}); + segments.push_back({curvatures[i - 1], curvatures[i], std::max(0.0, Arcs[i].transition) * f, cant(i - 1), cant(i)}); auto const share{total > 0.0 ? std::max(0.0, Arcs[i].share) / total : 1.0 / static_cast(Arcs.size())}; - segments.push_back({curvatures[i], curvatures[i], share * arcs / curvatures[i]}); + segments.push_back({curvatures[i], curvatures[i], share * arcs / curvatures[i], cant(i), cant(i)}); } - segments.push_back({curvatures.back(), 0.0, Lengthout * f}); + segments.push_back({curvatures.back(), 0.0, Lengthout * f, cant(Arcs.size() - 1), 0.0}); double x{0.0}; double y{0.0}; double heading{0.0}; @@ -156,6 +159,18 @@ compound_curve build_compound(double const Deflection, std::vector return result; } +// the cants of the arcs, each its own or Cant at the sharpest of them in proportion to the curvature +std::vector arc_cants(std::vector const &Arcs, double const Cant) +{ + auto sharpest{std::numeric_limits::max()}; + for (auto const &arc : Arcs) + sharpest = std::min(sharpest, std::max(1.0, arc.radius)); + std::vector cants; + for (auto const &arc : Arcs) + cants.push_back(arc.cant >= 0.0 ? arc.cant : std::max(0.0, Cant) * sharpest / std::max(1.0, arc.radius)); + return cants; +} + void element_point(element const &Element, transition_shape const Shape, double const Distance, glm::dvec2 &Position, glm::dvec2 &Direction) { switch (Element.kind) @@ -202,11 +217,7 @@ double element_cant(element const &Element, double const Distance) case element_kind::transition_in: return Element.cant * Distance / Element.transition; case element_kind::transition_out: return Element.cant * (1.0 - Distance / Element.transition); case element_kind::arc: return Element.cant; - case element_kind::spiral: - { - auto const curvature{Element.curvature_start + (Element.curvature_end - Element.curvature_start) * Distance / std::max(Element.length, 1e-9)}; - return Element.curvature_peak > 0.0 ? Element.cant * curvature / Element.curvature_peak : 0.0; - } + case element_kind::spiral: return Element.cant_start + (Element.cant_end - Element.cant_start) * std::clamp(Distance / std::max(Element.length, 1e-9), 0.0, 1.0); default: return 0.0; } } @@ -261,6 +272,15 @@ double unbalanced_acceleration(double const Speed, double const Radius, double c return Speed * Speed / (12.96 * Radius) - kGravity * Cant / Limits.gauge; } +std::vector compound_cants(vertex const &Vertex) +{ + if (false == Vertex.compound || Vertex.arcs.empty()) + return {std::max(0.0, Vertex.cant)}; + std::vector arcs{{Vertex.radius, 0.0, Vertex.share, Vertex.first_cant}}; + arcs.insert(arcs.end(), Vertex.arcs.begin(), Vertex.arcs.end()); + return arc_cants(arcs, Vertex.cant); +} + double transition_length(double const Speed, double const Radius, double const Cant, limits const &Limits) { double length{0.0}; @@ -346,6 +366,10 @@ bool meets_norms(curve_report const &Curve, design const &Design) return false; if (false == Curve.spirals && sharpest < Curve.recommended.radius_min - 1e-6) return false; + if (Curve.radii.size() > 1 && Curve.cants.size() == Curve.radii.size()) + // a compound curve ramps the cant of its first arc in and the one of its last arc out + return Curve.transition_in + 1e-6 >= transition_length(Design.speed, Curve.radii.front(), Curve.cants.front(), norms) && + Curve.transition_out + 1e-6 >= transition_length(Design.speed, Curve.radii.back(), Curve.cants.back(), norms); auto const required{transition_length(Design.speed, sharpest, Curve.cant, norms)}; return Curve.transition_in + 1e-6 >= required && Curve.transition_out + 1e-6 >= required; } @@ -494,9 +518,9 @@ result fit_between(design const &Design) if (vertex.compound && false == vertex.arcs.empty()) { fit.compound = true; - fit.arcs = {{fit.radius, 0.0, vertex.share}}; + fit.arcs = {{fit.radius, 0.0, vertex.share, vertex.first_cant}}; for (auto const &arc : vertex.arcs) - fit.arcs.push_back({std::max(1.0, arc.radius), std::max(0.0, arc.transition), arc.share}); + fit.arcs.push_back({std::max(1.0, arc.radius), std::max(0.0, arc.transition), arc.share, arc.cant}); fit.deflection = deflection; fit.tangents = [&, k, directionin, directionout, side, scaled](double const Radius, double &Tangentin, double &Tangentout) { auto const &own{fits[k]}; @@ -674,7 +698,8 @@ result fit_between(design const &Design) } if (fit.compound) { - auto const shape{build_compound(deflection, fit.arcs, fit.transition_in, fit.transition_out)}; + auto const cants{arc_cants(fit.arcs, fit.cant)}; + auto const shape{build_compound(deflection, fit.arcs, fit.transition_in, fit.transition_out, cants)}; if (shape.shortened < 1.0) r.warnings.emplace_back(format(STR_C("Vertex %zu: transition curves shortened to fit the deflection angle"), k + 1)); auto const normalin{perpendicular(directionin) * static_cast(side)}; @@ -693,17 +718,17 @@ result fit_between(design const &Design) auto sharpest{fit.radius}; for (auto const &arc : fit.arcs) sharpest = std::min(sharpest, arc.radius); - auto const peak{1.0 / sharpest}; for (auto const &segment : shape.segments) { element piece; piece.kind = element_kind::spiral; piece.vertex = static_cast(k); piece.turn = side; - piece.cant = std::max(0.0, vertex.cant); + piece.cant = std::max(segment[3], segment[4]); + piece.cant_start = segment[3]; + piece.cant_end = segment[4]; piece.curvature_start = segment[0]; piece.curvature_end = segment[1]; - piece.curvature_peak = peak; piece.radius = std::max(segment[0], segment[1]) > 0.0 ? 1.0 / std::max(segment[0], segment[1]) : 0.0; piece.length = segment[2]; piece.origin = origin; @@ -728,14 +753,37 @@ result fit_between(design const &Design) report.arc_length = chainage - startchainage; report.tangent_in = glm::distance(position, curvestart); report.tangent_out = glm::distance(position, cursor); - report.unbalanced = unbalanced_acceleration(Design.speed, sharpest, std::max(0.0, vertex.cant), Design.norms); - report.cant = std::max(0.0, vertex.cant); + report.cants = cants; + report.unbalanced = -std::numeric_limits::max(); + for (std::size_t i = 0; i < cants.size(); ++i) + { + report.unbalanced = std::max(report.unbalanced, unbalanced_acceleration(Design.speed, fit.arcs[i].radius, cants[i], Design.norms)); + report.cant = std::max(report.cant, cants[i]); + } report.recommended = recommend(Design.speed, sharpest, Design.norms); r.curves.push_back(report); - if (Design.norms.checked && sharpest < report.recommended.radius_min - 1e-6) + auto const &norms{Design.norms}; + if (false == norms.checked) + continue; + if (sharpest < report.recommended.radius_min - 1e-6) r.warnings.emplace_back(format(STR_C("Vertex %zu: R %.0f m is below the minimum %.0f m for %.0f km/h"), k + 1, sharpest, report.recommended.radius_min, Design.speed)); - if (Design.norms.checked && report.unbalanced > Design.norms.unbalanced + 1e-6) - r.warnings.emplace_back(format(STR_C("Vertex %zu: unbalanced acceleration %.2f m/s2 exceeds %.2f m/s2"), k + 1, report.unbalanced, Design.norms.unbalanced)); + if (report.unbalanced > norms.unbalanced + 1e-6) + r.warnings.emplace_back(format(STR_C("Vertex %zu: unbalanced acceleration %.2f m/s2 exceeds %.2f m/s2"), k + 1, report.unbalanced, norms.unbalanced)); + if (report.cant > norms.cant_max + 1e-6) + r.warnings.emplace_back(format(STR_C("Vertex %zu: cant %.0f mm exceeds %.0f mm"), k + 1, report.cant, norms.cant_max)); + // each transition long enough for the change of the curvature and of the cant along it, at the speed the sharpest arc + // allows, as the transitions of the norms are set between the arcs + auto const allowed{std::max(1.0, std::min(Design.speed, speed_limit(sharpest, norms)))}; + auto const requiredin{transition_length(allowed, fit.arcs.front().radius, cants.front(), norms)}; + auto const requiredout{transition_length(allowed, fit.arcs.back().radius, cants.back(), norms)}; + if (report.transition_in + 1e-6 < requiredin || report.transition_out + 1e-6 < requiredout) + r.warnings.emplace_back(format(STR_C("Vertex %zu: transition curves shorter than the required %.0f m"), k + 1, std::max(requiredin, requiredout))); + for (std::size_t i = 1; i < fit.arcs.size(); ++i) + { + auto const required{transition_between(allowed, 1.0 / fit.arcs[i - 1].radius, cants[i - 1], 1.0 / fit.arcs[i].radius, cants[i], norms)}; + if (fit.arcs[i].transition * shape.shortened + 1e-6 < required) + r.warnings.emplace_back(format(STR_C("Vertex %zu: the transition curve between R %.0f and R %.0f m shorter than the required %.0f m"), k + 1, fit.arcs[i - 1].radius, fit.arcs[i].radius, required)); + } continue; } auto const radius{fit.radius}; @@ -1276,8 +1324,8 @@ void chain_spiral(element_chain &Chain, double const From, double const To, doub spiral.turn = turn; spiral.curvature_start = From * turn; spiral.curvature_end = To * turn; - spiral.curvature_peak = std::max(std::abs(From), std::abs(To)); - spiral.radius = spiral.curvature_peak > 1e-12 ? 1.0 / spiral.curvature_peak : 0.0; + auto const peak{std::max(std::abs(From), std::abs(To))}; + spiral.radius = peak > 1e-12 ? 1.0 / peak : 0.0; spiral.length = Length; spiral.origin = Chain.position; spiral.direction = Chain.direction; @@ -1641,12 +1689,13 @@ void recommend_between(vertex &Vertex, double const Speed, limits const &Limits) for (auto const &arc : Vertex.arcs) sharpest = std::min(sharpest, std::max(1.0, arc.radius)); auto const speed{std::max(1.0, std::min(Speed, speed_limit(sharpest, Limits)))}; - auto const cant_of = [&](double const Radius) { return Vertex.cant * sharpest / Radius; }; + auto const cants{compound_cants(Vertex)}; auto previous{std::max(1.0, Vertex.radius)}; - for (auto &arc : Vertex.arcs) + for (std::size_t i = 0; i < Vertex.arcs.size(); ++i) { + auto &arc{Vertex.arcs[i]}; auto const radius{std::max(1.0, arc.radius)}; - arc.transition = transition_between(speed, 1.0 / previous, cant_of(previous), 1.0 / radius, cant_of(radius), Limits); + arc.transition = transition_between(speed, 1.0 / previous, cants[i], 1.0 / radius, cants[i + 1], Limits); previous = radius; } } @@ -2019,6 +2068,7 @@ vertex traced_shape(design const &Design, vertex const &Base, std::vector(Base.arcs.size()) ? Base.arcs[i - 1].transition : 0.0; + arc.cant = i - 1 < static_cast(Base.arcs.size()) ? Base.arcs[i - 1].cant : -1.0; arc.share = weights[i] / total; shape.arcs.push_back(arc); } diff --git a/editor/editorAlignment.hpp b/editor/editorAlignment.hpp index 0dc9ef6d..793c3084 100644 --- a/editor/editorAlignment.hpp +++ b/editor/editorAlignment.hpp @@ -53,6 +53,8 @@ struct compound_arc double radius{600.0}; double transition{0.0}; double share{1.0}; + // the cant of the arc; below 0 the cant of the vertex, which is the one of the sharpest arc, in proportion to the curvature + double cant{-1.0}; }; struct vertex @@ -67,6 +69,8 @@ struct vertex bool compound{false}; double share{1.0}; std::vector arcs; + // the cant of the first arc of a compound curve, as the ones of the arcs after it + double first_cant{-1.0}; // 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}; @@ -107,6 +111,8 @@ double transition_length(double const Speed, double const Radius, double const C // turning one way, the cant with the same sign as the curvature it leans into double transition_between(double const Speed, double const Curvature0, double const Cant0, double const Curvature1, double const Cant1, limits const &Limits); double unbalanced_acceleration(double const Speed, double const Radius, double const Cant, limits const &Limits); +// the cants of the arcs of a compound curve, the first one's first: each its own, or the one in proportion to its curvature +std::vector compound_cants(vertex const &Vertex); enum class element_kind { @@ -129,7 +135,9 @@ struct element double cant{0.0}; double curvature_start{0.0}; double curvature_end{0.0}; - double curvature_peak{0.0}; + // the cant of a spiral at its start and at its end, going from the one to the other along it + double cant_start{0.0}; + double cant_end{0.0}; glm::dvec2 origin{0.0}; glm::dvec2 direction{0.0, 1.0}; glm::dvec2 normal{0.0}; @@ -140,6 +148,7 @@ struct curve_report int vertex{-1}; double radius{0.0}; std::vector radii; + std::vector cants; // of the radii of a compound curve double transition_in{0.0}; double transition_out{0.0}; double deflection{0.0}; diff --git a/editor/editorAlignmentStore.cpp b/editor/editorAlignmentStore.cpp index 0b9a6d8d..74ae3d4b 100644 --- a/editor/editorAlignmentStore.cpp +++ b/editor/editorAlignmentStore.cpp @@ -47,6 +47,7 @@ bool g_loaded{false}; // pieces // 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 // element // radii signed with the turn, 0 for no curvature // end @@ -139,6 +140,12 @@ void mark(scene::layer_handle const Layer) text += format(" %.3f %.3f %.9f", arc.radius, arc.transition, arc.share); if (vertex.spirals) text += " spirals 1"; + if (vertex.first_cant >= 0.0 || std::any_of(vertex.arcs.begin(), vertex.arcs.end(), [](alignment::compound_arc const &Arc) { return Arc.cant >= 0.0; })) + { + text += format(" cants %.1f", vertex.first_cant); + for (auto const &arc : vertex.arcs) + text += format(" %.1f", arc.cant); + } lines.push_back(text); } for (auto const &e : entry.result.elements) @@ -165,15 +172,17 @@ alignment::vertex reversed_vertex(alignment::vertex Vertex) double radius; double share; double transition; + double cant; }; - std::vector parts{{Vertex.radius, Vertex.share, 0.0}}; + std::vector parts{{Vertex.radius, Vertex.share, 0.0, Vertex.first_cant}}; for (auto const &arc : Vertex.arcs) - parts.push_back({arc.radius, arc.share, arc.transition}); + parts.push_back({arc.radius, arc.share, arc.transition, arc.cant}); auto const count{parts.size()}; Vertex.radius = parts.back().radius; Vertex.share = parts.back().share; + Vertex.first_cant = parts.back().cant; for (std::size_t j = 1; j < count; ++j) - Vertex.arcs[j - 1] = {parts[count - 1 - j].radius, parts[count - j].transition, parts[count - 1 - j].share}; + Vertex.arcs[j - 1] = {parts[count - 1 - j].radius, parts[count - j].transition, parts[count - 1 - j].share, parts[count - 1 - j].cant}; } return Vertex; } @@ -312,8 +321,17 @@ std::optional read_vertex(std::istringstream &Line, scene::la return std::nullopt; // written since the bends of the straights are eased with two transition curves, the lines of before end with the arcs for (std::string word; Line >> word;) + { if (int value{0}; word == "spirals" && Line >> value) vertex.spirals = (value != 0); + // the cants of the arcs of a compound curve, set one by one: of the first arc and of the ones after it, below 0 in proportion + if (word == "cants") + { + Line >> vertex.first_cant; + for (auto &arc : vertex.arcs) + Line >> arc.cant; + } + } position = Context.to_world(position); vertex.position = {position.x, position.z}; vertex.reverse_turn = (reverse != 0); diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 48ca9766..a6c0e1c4 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -11905,3 +11905,18 @@ msgstr "Załamanie wyokrąglone tak, jak projektuje się linie: dwie krzywe prze msgid "Each bend of the straights clicked at the height of the ground, an even grade between them: the grade changes only at the bends. A curve traced, or bends clicked close along a curve, goes at an even grade from one end of it to the other" msgstr "Każde kliknięte załamanie prostych na wysokości terenu, między nimi równe pochylenie: pochylenie zmienia się tylko na załamaniach. Łuk odrysowany albo załamania kliknięte gęsto wzdłuż łuku idą jednym pochyleniem od jego początku do końca" + +msgid "Vertex %zu: the transition curve between R %.0f and R %.0f m shorter than the required %.0f m" +msgstr "Wierzchołek %zu: krzywa przejściowa między R %.0f a R %.0f m krótsza niż wymagane %.0f m" + +msgid "Cant of R%d (mm)" +msgstr "Przechyłka R%d (mm)" + +msgid "The cant of this arc, ramped along the transition curves to the arcs either side of it; until it's set here, the cant of the curve in proportion to the curvature of the arc" +msgstr "Przechyłka tego łuku, zmieniana na krzywych przejściowych do łuków po obu jego stronach; dopóki nie jest tu ustawiona, przechyłka łuku koszowego proporcjonalna do krzywizny tego łuku" + +msgid "The cant of the arc in proportion to its curvature again" +msgstr "Przechyłka łuku znów proporcjonalna do jego krzywizny" + +msgid "Height of the outer rail above the inner one on the sharpest arc; the arcs with no cant of their own get it in proportion to their curvature" +msgstr "Wysokość szyny zewnętrznej ponad wewnętrzną na najostrzejszym łuku; łuki bez własnej przechyłki dostają ją proporcjonalnie do swojej krzywizny" diff --git a/tests/unit/test_alignment.cpp b/tests/unit/test_alignment.cpp index 64a8977f..9f4c5990 100644 --- a/tests/unit/test_alignment.cpp +++ b/tests/unit/test_alignment.cpp @@ -257,6 +257,52 @@ TEST_CASE("alignment: a compound curve traced is followed closely") { CHECK(fit.worst < 0.2); } +TEST_CASE("alignment: each arc of a compound curve has a cant of its own, ramped along the transitions between them") { + auto design{straights({500.0, 500.0}, {35.0 * pi / 180.0}, 100.0)}; + auto &vertex{design.vertices.front()}; + vertex.radius = 1200.0; + vertex.compound = true; + vertex.share = 0.5; + vertex.arcs = {{700.0, 60.0, 0.5}}; + vertex.transition_in = vertex.transition_out = 80.0; + vertex.cant = 120.0; + + // until they're set, the cants of the arcs go with their curvature, the cant of the curve on the sharpest one + auto const proportional{alignment::compound_cants(vertex)}; + REQUIRE(proportional.size() == 2); + CHECK(proportional[0] == doctest::Approx(70.0)); + CHECK(proportional[1] == doctest::Approx(120.0)); + + vertex.first_cant = 40.0; + vertex.arcs.front().cant = 110.0; + auto const line{alignment::compute(design)}; + REQUIRE(line.valid); + REQUIRE(line.curves.size() == 1); + auto const &curve{line.curves.front()}; + REQUIRE(curve.cants.size() == 2); + CHECK(curve.cants[0] == doctest::Approx(40.0)); + CHECK(curve.cants[1] == doctest::Approx(110.0)); + CHECK(curve.cant == doctest::Approx(110.0)); + + std::vector pieces; + for (auto const &e : line.elements) + if (e.vertex == 0) + pieces.push_back(e); + // in, R1, between, R2, out + REQUIRE(pieces.size() == 5); + auto const cant = [&](alignment::element const &Element, double const Along) { return alignment::evaluate(line, Element.chainage + Element.length * Along).cant; }; + CHECK(cant(pieces[0], 0.0) == doctest::Approx(0.0).epsilon(1e-6)); + CHECK(cant(pieces[0], 0.5) == doctest::Approx(20.0)); + CHECK(cant(pieces[1], 0.5) == doctest::Approx(40.0)); + CHECK(cant(pieces[2], 0.5) == doctest::Approx(75.0)); + CHECK(cant(pieces[3], 0.5) == doctest::Approx(110.0)); + CHECK(cant(pieces[4], 0.5) == doctest::Approx(55.0)); + + // the transition between the arcs of the norms for the change of the cant set + alignment::recommend_between(vertex, design.speed, design.norms); + CHECK(vertex.arcs.front().transition == doctest::Approx(alignment::transition_between(design.speed, 1.0 / 1200.0, 40.0, 1.0 / 700.0, 110.0, design.norms))); +} + TEST_CASE("alignment: bends clicked close along a curve make one compound curve, not curves with short straights between them") { // the points clicked in the scenery abc, at 60 km/h: from a straight along z into a curve to the left alignment::design design;