From 438cb4ea3a847330403bcb280db138873fa4bf55 Mon Sep 17 00:00:00 2001 From: maj00r Date: Mon, 19 Oct 2026 18:40:43 +0200 Subject: [PATCH] feat(editor): a join of ends side by side shifts the track with reverse curves over the whole gap The reverse curves of a join start and end at the joined ends, with the largest radius that fits, so a shift of the track spacing (4 -> 4.75 m over 400 m) is one S instead of two curves with straights around them. A straight or a single arc with a kink at its ends, though still under the joints check, comes after the smooth curves among the variants; a practically exact one stays first. An element under 1 mm, like the straight left between the reverse curves by the radius fit, no longer drops out of the pieces and leaves a gap: the neighbouring piece takes it over. --- editor/editorAlignment.cpp | 38 ++++++++++++++++++++++++++++++++++---- editor/editorAlignment.hpp | 1 + 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/editor/editorAlignment.cpp b/editor/editorAlignment.cpp index 1bfa3f8e..3ad50910 100644 --- a/editor/editorAlignment.cpp +++ b/editor/editorAlignment.cpp @@ -969,10 +969,12 @@ std::optional straight_join(join_frame const &Frame) { auto const &a{Frame.request.start}; auto const &b{Frame.request.end}; - if (std::abs(geometry::signed_angle(Frame.start_direction, Frame.chord)) > Frame.tolerance || std::abs(geometry::signed_angle(Frame.chord, Frame.end_direction)) > Frame.tolerance) + auto const kink{std::max(std::abs(geometry::signed_angle(Frame.start_direction, Frame.chord)), std::abs(geometry::signed_angle(Frame.chord, Frame.end_direction)))}; + if (kink > Frame.tolerance) return std::nullopt; join_result straight; straight.kind = join_kind::straight; + straight.kink = kink; segment_data path; path.points[segment_data::point::start] = a.position; path.points[segment_data::point::end] = b.position; @@ -992,10 +994,12 @@ std::optional arc_join(join_frame const &Frame) auto const angle{2.0 * std::atan2(std::abs(lateral), glm::dot(Frame.start_direction, Frame.chord))}; int const side{lateral > 0.0 ? 1 : -1}; auto const radius{Frame.distance * Frame.distance / (2.0 * std::abs(lateral))}; - if (angle >= kPi - 1e-3 || std::abs(geometry::signed_angle(turned(Frame.start_direction, side * angle), Frame.end_direction)) > Frame.tolerance) + auto const kink{std::abs(geometry::signed_angle(turned(Frame.start_direction, side * angle), Frame.end_direction))}; + if (angle >= kPi - 1e-3 || kink > Frame.tolerance) return std::nullopt; join_result arc; arc.kind = join_kind::arc; + arc.kink = kink; arc.pieces = geometry::arc_pieces(a.position, Frame.start_direction, Frame.rise / (radius * angle), radius, angle, side); arc.pieces.back().points[segment_data::point::end] = b.position; spread_rolls(arc.pieces, a.roll, b.roll); @@ -1024,7 +1028,7 @@ design join_design(join_frame const &Frame, join_kind const Kind) shape.vertices = {corner}; else { - corner.offset = Frame.distance / 3.0; + corner.offset = Frame.distance / 4.0; shape.vertices = {corner, corner}; } return shape; @@ -1103,9 +1107,11 @@ join_result join(join_request const &Request) auto const enddirection{glm::normalize(b.direction)}; join_frame const frame{Request, startdirection, enddirection, chord, distance, glm::radians(Request.kink), std::abs(geometry::signed_angle(startdirection, enddirection)), b.position.y - a.position.y}; std::vector candidates; + std::vector kinked; for (auto const &candidate : {straight_join(frame), arc_join(frame), curve_join(frame)}) if (candidate.has_value()) - candidates.push_back(*candidate); + (candidate->kink > 0.1 * frame.tolerance ? kinked : candidates).push_back(*candidate); + candidates.insert(candidates.end(), kinked.begin(), kinked.end()); if (candidates.empty()) candidates.push_back(bezier_join(frame)); auto const count{static_cast(candidates.size())}; @@ -1253,12 +1259,36 @@ std::vector pieces(result const &Result, design const &Design, std auto const chainage_from = [&](span const &Span) { return Span.generic ? Span.start : Result.elements[Span.element].chainage + Span.from; }; auto const chainage_to = [&](span const &Span) { return Span.generic ? Span.end : Result.elements[Span.element].chainage + Span.to; }; std::vector spans; + std::optional lead; for (std::size_t i = 0; i < Result.elements.size(); ++i) { auto const &e{Result.elements[i]}; auto const count{element_pieces(e, Design.shape, Design.transition_pieces, Design.arc_piece_angle)}; + if (count == 0) + { + if (e.length <= 0.0) + continue; + if (spans.empty()) + { + lead = lead.value_or(e.chainage); + continue; + } + auto &previous{spans.back()}; + previous.start = chainage_from(previous); + previous.end = e.chainage + e.length; + previous.generic = true; + continue; + } for (std::size_t j = 0; j < count; ++j) spans.push_back({i, e.length * j / count, e.length * (j + 1) / count}); + if (lead.has_value()) + { + auto &first{spans[spans.size() - count]}; + first.end = chainage_to(first); + first.start = *lead; + first.generic = true; + lead.reset(); + } } double const shortest{1.0}; for (std::size_t i = 0; i < spans.size() && spans.size() > 1;) diff --git a/editor/editorAlignment.hpp b/editor/editorAlignment.hpp index 171c8ac3..48cefcc5 100644 --- a/editor/editorAlignment.hpp +++ b/editor/editorAlignment.hpp @@ -232,6 +232,7 @@ struct join_result double cant{0.0}; double transition{0.0}; double speed{0.0}; + double kink{0.0}; int variants{0}; std::vector warnings; std::vector radius_warnings;