diff --git a/application/editormoderoute.cpp b/application/editormoderoute.cpp index 447a88df..da91e933 100644 --- a/application/editormoderoute.cpp +++ b/application/editormoderoute.cpp @@ -79,6 +79,8 @@ void editor_mode::route_bind_ends() design.end_grade = grade_of(chain.end_direction); design.start_radius = chain.start_radius; design.end_radius = chain.end_radius; + design.start_turn = chain.start_turn; + design.end_turn = chain.end_turn; design.start_reserve = editor_track::run_reserve(editor_track::straight_beyond(chain, false, m_straights.tolerance)); design.end_reserve = editor_track::run_reserve(editor_track::straight_beyond(chain, true, m_straights.tolerance)); } @@ -450,6 +452,8 @@ bool editor_mode::digitize_chain(editor_track::chain const &Chain, std::string * design.end_grade = grade_of(Chain.end_direction); design.start_radius = Chain.start_radius; design.end_radius = Chain.end_radius; + design.start_turn = Chain.start_turn; + design.end_turn = Chain.end_turn; auto const preferred{m_route.design.shape}; auto const other{preferred == alignment::transition_shape::clothoid ? alignment::transition_shape::cubic_parabola : alignment::transition_shape::clothoid}; alignment::result result; diff --git a/editor/editorAlignment.cpp b/editor/editorAlignment.cpp index 74bfffff..ac2c1c1e 100644 --- a/editor/editorAlignment.cpp +++ b/editor/editorAlignment.cpp @@ -1088,9 +1088,37 @@ result fit_between(design const &Design) auto const gradeafter{(r.profile[i + 1].elevation - point.elevation) / (r.profile[i + 1].chainage - point.chainage)}; point.tangent = point.radius * std::abs(gradeafter - gradebefore) / 2.0; } - if (Design.start_radius > 0.0 && Design.start_radius < 10000.0) + // a curved track adjoining is a jump of the curvature only where the design meets it at another one: a curve going on from + // the diverging track of a switch at its radius isn't. The curvature of the design there is signed by the side it turns to, a + // piece shorter than 1 cm, as a straight the fit leaves before the curve, passed over + auto const curvature_at = [&](bool const End) { + for (std::size_t n = 0; n < r.elements.size(); ++n) + { + auto const &e{r.elements[End ? r.elements.size() - 1 - n : n]}; + if (e.length < 0.01) + continue; + auto const arc{e.radius > 0.0 ? 1.0 / e.radius : 0.0}; + switch (e.kind) + { + case element_kind::arc: return arc * e.turn; + case element_kind::transition_in: return End ? arc * e.turn : 0.0; + case element_kind::transition_out: return End ? 0.0 : arc * e.turn; + case element_kind::spiral: return (End ? e.curvature_end : e.curvature_start) * e.turn; + default: return 0.0; + } + } + return 0.0; + }; + auto const abrupt = [](double const Radius, int const Turn, double const Own) { + if (Radius <= 0.0 || Radius >= 10000.0) + return false; + // the side not known, as of a design kept in the scenery before it was: taken for the one the design turns to + auto const theirs{(Turn != 0 ? Turn : Own < 0.0 ? -1 : 1) / Radius}; + return std::abs(theirs - Own) > 0.05 * std::max(1.0 / Radius, std::abs(Own)); + }; + if (abrupt(Design.start_radius, Design.start_turn, curvature_at(false))) 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) + if (abrupt(Design.end_radius, Design.end_turn, curvature_at(true))) 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); diff --git a/editor/editorAlignment.hpp b/editor/editorAlignment.hpp index 0aa99185..859e6864 100644 --- a/editor/editorAlignment.hpp +++ b/editor/editorAlignment.hpp @@ -94,6 +94,10 @@ struct design double end_grade{0.0}; double start_radius{0.0}; double end_radius{0.0}; + // the side the adjoining track turns to at the start and at the end, going from the one to the other: 1 left, -1 right, 0 not + // known; a design going on at the curvature of the adjoining track doesn't change it abruptly + int start_turn{0}; + int end_turn{0}; // length of the straight beyond the start and the end, which the curves can take over double start_reserve{0.0}; double end_reserve{0.0}; diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index 15a550df..0e1a44c2 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -97,7 +97,7 @@ double path_radius(segment_data const &Path, double const T) return curvature > 1e-7 ? 1.0 / curvature : 0.0; } -bool adjoining_direction(TTrack const *Neighbour, glm::dvec3 const &Joint, bool const Leaving, glm::dvec3 &Direction, double &Radius) +bool adjoining_direction(TTrack const *Neighbour, glm::dvec3 const &Joint, bool const Leaving, glm::dvec3 &Direction, double &Radius, int &Turn) { if (Neighbour == nullptr) return false; @@ -109,8 +109,18 @@ bool adjoining_direction(TTrack const *Neighbour, glm::dvec3 const &Joint, bool if (glm::distance(point, Joint) > kSamePoint) continue; auto const tangent{glm::normalize(path_tangent(path, atend))}; - Direction = (Leaving != atend) ? tangent : -tangent; + auto const along{Leaving != atend}; + Direction = along ? tangent : -tangent; Radius = path_radius(path, atend ? 1.0 : 0.0); + // the side it turns to as the chain runs, the path gone against its points turning the other way + Turn = 0; + if (Radius > 0.0) + { + bezier const curve{path}; + auto const first{curve.first(atend ? 1.0 : 0.0)}; + auto const second{curve.second(atend ? 1.0 : 0.0)}; + Turn = (first.x * second.z - first.z * second.x > 0.0) == along ? 1 : -1; + } return true; } } @@ -1335,14 +1345,14 @@ bool editor_track::complete_chain(chain &Chain, std::string &Error) auto const firstforward{Chain.forward.front()}; Chain.start = first.points[firstforward ? segment_data::point::start : segment_data::point::end]; Chain.start_direction = firstforward ? path_tangent(first, false) : -path_tangent(first, true); - adjoining_direction(firstforward ? from->trPrev : from->trNext, Chain.start, false, Chain.start_direction, Chain.start_radius); + adjoining_direction(firstforward ? from->trPrev : from->trNext, Chain.start, false, Chain.start_direction, Chain.start_radius, Chain.start_turn); Chain.start_direction = glm::normalize(Chain.start_direction); auto const &last{to->m_paths.front()}; auto const lastforward{Chain.forward.back()}; Chain.end = last.points[lastforward ? segment_data::point::end : segment_data::point::start]; Chain.end_direction = lastforward ? path_tangent(last, true) : -path_tangent(last, false); - adjoining_direction(lastforward ? to->trNext : to->trPrev, Chain.end, true, Chain.end_direction, Chain.end_radius); + adjoining_direction(lastforward ? to->trNext : to->trPrev, Chain.end, true, Chain.end_direction, Chain.end_radius, Chain.end_turn); Chain.end_direction = glm::normalize(Chain.end_direction); Error.clear(); diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index b42a1dd0..439a9cf0 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -272,6 +272,9 @@ class editor_track glm::dvec3 end_direction{0.0}; double start_radius{0.0}; double end_radius{0.0}; + // the side the adjoining track turns to there, going from the start to the end: 1 left, -1 right, 0 straight + int start_turn{0}; + int end_turn{0}; double length{0.0}; double velocity{-1.0}; double radius{0.0}; diff --git a/tests/unit/test_alignment.cpp b/tests/unit/test_alignment.cpp index be167a95..3ebb3d54 100644 --- a/tests/unit/test_alignment.cpp +++ b/tests/unit/test_alignment.cpp @@ -129,6 +129,36 @@ TEST_CASE("alignment: a sharp bend between short straights is laid where clicked CHECK(line.vertices.front().x == doctest::Approx(120.0)); } +TEST_CASE("alignment: a curve going on at the radius of the curved track before it doesn't change the curvature abruptly") { + auto const adjoining = [](alignment::result const &Line) { + return std::any_of(Line.warnings.begin(), Line.warnings.end(), [](std::string const &Warning) { return Warning.find("adjoining track at the start") != std::string::npos; }); + }; + // R 300 with no transition from the very start, as a curve after the diverging track of a switch, R 301 there + auto const tangent{300.0 * std::tan(10.0 * pi / 180.0)}; + auto design{straights({tangent, 200.0}, {20.0 * pi / 180.0}, 40.0)}; + auto &vertex{design.vertices.front()}; + vertex.radius = 300.0; + vertex.transition_in = 0.0; + vertex.transition_out = 0.0; + design.start_radius = 301.0; + design.start_turn = 1; + auto const line{alignment::compute(design)}; + REQUIRE(line.valid); + REQUIRE_FALSE(line.elements.empty()); + CHECK(line.elements.front().kind == alignment::element_kind::arc); + CHECK_FALSE(adjoining(line)); + // the track before turning the other way, or the curve starting with a straight: the curvature jumps + design.start_turn = -1; + CHECK(adjoining(alignment::compute(design))); + design.start_turn = 1; + auto later{straights({tangent + 80.0, 200.0}, {20.0 * pi / 180.0}, 40.0)}; + later.vertices.front() = vertex; + later.vertices.front().position = {tangent + 80.0, 0.0}; + later.start_radius = 301.0; + later.start_turn = 1; + CHECK(adjoining(alignment::compute(later))); +} + TEST_CASE("alignment: two bends share the straight between them") { auto design{straights({600.0, 250.0, 600.0}, {4.0 * pi / 180.0, -5.0 * pi / 180.0}, 120.0)}; std::vector corners{1, 1};