diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 64970669..2db70fea 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1868,9 +1868,15 @@ bool editor_mode::digitize_chain(editor_track::chain const &Chain, std::string * double closest{std::numeric_limits::max()}; TTrack *farthest{nullptr}; bool fits{false}; - for (auto const shape : {preferred, other}) + for (auto const attempt : {0, 1, 2, 3}) { + auto const shape{attempt % 2 == 0 ? preferred : other}; design.shape = shape; + if (attempt == 2) + { + design.norms.arc_min = 0.0; + design.norms.arc_min_factor = 0.0; + } design.vertices.clear(); for (auto const &entry : editor_track::recognize_line(Chain, m_straights.tolerance, design.norms.gauge, shape)) { @@ -1905,7 +1911,7 @@ bool editor_mode::digitize_chain(editor_track::chain const &Chain, std::string * if (false == fits) { if (Reason != nullptr) - *Reason = closest < 1000.0 ? format(STR_C("the curves read from the paths leave path %s by %.2f m"), name(farthest).c_str(), closest) : std::string{STR_C("the paths don't make a line of straights, arcs and transition curves")}; + *Reason = closest < std::numeric_limits::max() ? format(STR_C("the curves read from the paths leave path %s by %.2f m"), name(farthest).c_str(), closest) : std::string{STR_C("the paths don't make a line of straights, arcs and transition curves")}; return false; } auto const &records{alignment_store::records()}; diff --git a/editor/editorAlignment.cpp b/editor/editorAlignment.cpp index 59ba13c5..71a8ebfd 100644 --- a/editor/editorAlignment.cpp +++ b/editor/editorAlignment.cpp @@ -1130,6 +1130,24 @@ bool tangent_intersection(design const &Design, glm::dvec2 &Point) return true; } +bool spiral_tangents(transition_shape const Shape, double const TurnIn, double const TurnOut, double &TangentIn, double &TangentOut) +{ + auto const length = [&](double const Turn) { return Shape == transition_shape::clothoid ? 2.0 * Turn : 2.0 * std::tan(Turn); }; + if (TurnIn <= 0.0 || TurnOut <= 0.0 || TurnIn >= 0.5 * kPi || TurnOut >= 0.5 * kPi) + return false; + auto const in{transition_point(Shape, length(TurnIn), 1.0, 1.0)}; + auto const out{transition_point(Shape, length(TurnOut), 1.0, 1.0)}; + auto const deflection{in.angle + out.angle}; + if (deflection >= kPi - 1e-3) + return false; + glm::dvec2 const direction{std::cos(deflection), std::sin(deflection)}; + glm::dvec2 const normal{-direction.y, direction.x}; + auto const end{glm::dvec2{in.x, in.y} + direction * out.x - normal * out.y}; + TangentOut = end.y / std::sin(deflection); + TangentIn = end.x - TangentOut * std::cos(deflection); + return TangentIn > 0.0 && TangentOut > 0.0; +} + bool collinear(design const &Design) { auto const offset{plan_of(Design.end) - plan_of(Design.start)}; diff --git a/editor/editorAlignment.hpp b/editor/editorAlignment.hpp index 474465c6..7067055b 100644 --- a/editor/editorAlignment.hpp +++ b/editor/editorAlignment.hpp @@ -173,6 +173,7 @@ double speed_limit(double const Radius, limits const &Limits); result compute_recommended(design &Design, bool const Transitions); bool tangent_intersection(design const &Design, glm::dvec2 &Point); bool collinear(design const &Design); +bool spiral_tangents(transition_shape const Shape, double const TurnIn, double const TurnOut, double &TangentIn, double &TangentOut); struct station { diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index 859e76cb..060781c4 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -1852,6 +1852,17 @@ std::vector editor_track::recognize_line(chain const straight[i] = is_straight(*Chain.tracks[i], Tolerance); turns[i] = signed_angle(tangent_in(i), tangent_out(i)); } + auto const kLongestTurn{0.85 * glm::pi()}; + auto const curvature = [&](std::size_t const Index, double const T) { + auto const radius{path_radius(path(Index), T)}; + return radius > 0.0 ? 1.0 / radius : 0.0; + }; + auto const eases = [&](std::size_t const Index, bool const Atend) { + double peak{0.0}; + for (auto const t : {0.0, 0.25, 0.5, 0.75, 1.0}) + peak = std::max(peak, curvature(Index, t)); + return peak > 0.0 && curvature(Index, Chain.forward[Index] == Atend ? 1.0 : 0.0) < 0.05 * peak; + }; auto const add_curve = [&](std::size_t const First, std::size_t const Last) { auto const a{point_in(First)}; auto const b{point_out(Last)}; @@ -1881,14 +1892,29 @@ std::vector editor_track::recognize_line(chain const }; auto const in{shape.transition_in > 0.0 ? pieces(shape.transition_in, true) : 0}; auto const out{shape.transition_out > 0.0 ? pieces(shape.transition_out, false) : 0}; - if (in + out <= Last - First) + auto const turn_of = [&](std::size_t const From, std::size_t const To) { return std::abs(std::accumulate(turns.begin() + From, turns.begin() + To + 1, 0.0)); }; + auto const length = [&](double const Turn) { return Shape == alignment::transition_shape::clothoid ? 2.0 * shape.radius * Turn : 2.0 * shape.radius * std::tan(Turn); }; + if (Last > First && shape.transition_in <= 0.0 && shape.transition_out <= 0.0 && eases(First, false) && eases(Last, true)) + { + auto joint{First}; + for (auto k = First; k < Last; ++k) + if (curvature(k, Chain.forward[k] ? 1.0 : 0.0) > curvature(joint, Chain.forward[joint] ? 1.0 : 0.0)) + joint = k; + double tangentin; + double tangentout; + if (alignment::spiral_tangents(Shape, turn_of(First, joint), turn_of(joint + 1, Last), tangentin, tangentout)) + { + shape.radius = 0.5 * (glm::distance(vertex.position, a) / tangentin + glm::distance(b, vertex.position) / tangentout); + shape.transition_in = length(turn_of(First, joint)); + shape.transition_out = length(turn_of(joint + 1, Last)); + } + } + else if (in + out <= Last - First) { - auto const turn_of = [&](std::size_t const From, std::size_t const To) { return std::abs(std::accumulate(turns.begin() + From, turns.begin() + To + 1, 0.0)); }; auto const arc{turn_of(First + in, Last - out)}; if (arc > 1e-6 && arc < 2.0 * glm::pi() - 1e-3) { shape.radius = glm::distance(point_in(First + in), point_out(Last - out)) / (2.0 * std::sin(arc * 0.5)); - auto const length = [&](double const Turn) { return Shape == alignment::transition_shape::clothoid ? 2.0 * shape.radius * Turn : 2.0 * shape.radius * std::tan(Turn); }; if (in > 0) shape.transition_in = length(turn_of(First, First + in - 1)); if (out > 0) @@ -1921,11 +1947,28 @@ std::vector editor_track::recognize_line(chain const // a reverse curve: the next one starts here if (current != 0 && sign != 0 && current != sign) break; + if (i > first && (eases(i - 1, true) || eases(i, false))) + break; if (current != 0) sign = current; ++i; } - add_curve(first, i - 1); + auto const total{std::abs(std::accumulate(turns.begin() + first, turns.begin() + i, 0.0))}; + auto const parts{std::max(1.0, std::ceil(total / kLongestTurn))}; + auto start{first}; + double turned{0.0}; + double part{1.0}; + for (auto k = first; k + 1 < i && part < parts; ++k) + { + turned += std::abs(turns[k]); + if (turned >= total * part / parts) + { + add_curve(start, k); + start = k + 1; + part += 1.0; + } + } + add_curve(start, i - 1); } return result; }