/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http: */ #pragma once #include #include #include #include #include #include #include #include #include #include "world/Segment.h" namespace alignment { enum class transition_shape { cubic_parabola, clothoid }; struct limits { double gauge{1500.0}; double unbalanced{0.6}; double cant_max{150.0}; double cant_rate{35.0}; double ramp_factor{10.0}; double jerk{0.3}; double vertical_factor{0.4}; double vertical_min{2000.0}; double tangent_min_time{0.0}; double arc_min{20.0}; double arc_min_factor{0.4}; bool checked{true}; bool operator==(limits const &) const = default; }; 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 { glm::dvec2 position{0.0}; double offset{100.0}; double radius{1000.0}; double transition_in{0.0}; double transition_out{0.0}; double cant{0.0}; bool reverse_turn{false}; 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}; // 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 { glm::dvec3 start{0.0}; glm::dvec3 end{0.0}; glm::dvec2 start_direction{0.0, 1.0}; glm::dvec2 end_direction{0.0, 1.0}; double start_grade{0.0}; 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}; // the curve of the first vertex goes on from the curve of the adjoining track at the start, where it turns its way, as a track laid on // from a curved free end: it begins right at the start, with no straight before it, its transition in going from the curvature and // the cant of the adjoining track (start_radius, start_turn, start_cant) to those of its first arc. The radii of its arcs are the // ones which begin it there, the transitions and the shares as given. The same for the curve of the last vertex at the end; a curve // of the only vertex goes on from the start alone bool start_curved{false}; bool end_curved{false}; // the curve going on from the adjoining track at the start, or into the one at the end, goes on from the diverging track of a switch: // at its radius, with no transition between them. The arcs after the first one, or the transition out of a single one, are what // makes it begin right there bool start_held{false}; bool end_held{false}; // mm, the cant of the adjoining track at the start and at the end, leaning into its turn double start_cant{0.0}; double end_cant{0.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}; std::vector vertices; transition_shape shape{transition_shape::cubic_parabola}; double speed{100.0}; int transition_pieces{1}; double arc_piece_angle{10.0}; limits norms; }; struct recommendation { double cant_equilibrium{0.0}; double cant_min{0.0}; double cant{0.0}; double radius_min{0.0}; double transition{0.0}; }; recommendation recommend(double const Speed, double const Radius, limits const &Limits); double transition_length(double const Speed, double const Radius, double const Cant, limits const &Limits); // the transition curve from the curvature and the cant of one arc to those of the next one, 0 for a straight; the curvature positive // 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 { straight, transition_in, arc, transition_out, spiral }; struct element { element_kind kind{element_kind::straight}; int vertex{-1}; double chainage{0.0}; double length{0.0}; double radius{0.0}; double transition{0.0}; int turn{0}; double cant{0.0}; double curvature_start{0.0}; double curvature_end{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}; }; // the curvature of the element at its start and at its end, not signed by the side it turns to: an arc of a compound curve its own std::pair end_curvatures(element const &Element); 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}; double arc_length{0.0}; double tangent_in{0.0}; double tangent_out{0.0}; double unbalanced{0.0}; double cant_rate_in{0.0}; double cant_rate_out{0.0}; double jerk_in{0.0}; double jerk_out{0.0}; recommendation recommended; bool spirals{false}; // two transition curves with no arc between them double cant{0.0}; // the curvature and the cant of the curve of the adjoining track it goes on from at its start, and into at its end; 0 from a straight double curvature_in{0.0}; double cant_in{0.0}; double curvature_out{0.0}; double cant_out{0.0}; }; struct profile_point { double chainage{0.0}; double elevation{0.0}; double radius{0.0}; double tangent{0.0}; }; struct result { bool valid{false}; transition_shape shape{transition_shape::cubic_parabola}; double gauge{1500.0}; std::vector errors; std::vector warnings; std::vector radius_warnings; std::vector vertices; std::vector vertex_chainages; std::vector vertex_elevations; 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}; double start_extension{0.0}; double end_extension{0.0}; }; result compute(design const &Design); double speed_limit(double const Radius, limits const &Limits); result compute_recommended(design &Design); 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 { glm::dvec3 position{0.0}; glm::dvec3 direction{0.0}; double cant{0.0}; int turn{0}; }; station evaluate(result const &Result, double const Chainage); double elevation(result const &Result, double const Chainage); double grade(result const &Result, double const Chainage); // a point of the plan projected on the alignment, its offset positive to the left; past the ends it's measured from the straights carried on struct plan_projection { double chainage{0.0}; double offset{0.0}; }; std::vector project(result const &Result, std::vector const &Points); struct trace_fit { bool valid{false}; vertex fitted; double rms{0.0}; double worst{0.0}; }; // the radii of the curve of the vertex and their shares which follow the points traced along the track best; the tangents, the // transitions and the cant stay. Radii 0 takes the fewest radii, up to three, which follow the points about as well as more would trace_fit fit_trace(design const &Design, std::size_t const Vertex, std::vector const &Points, int const Radii); // the radii of the curve of the vertex and their shares fitted again to the points, as many radii as it has; its transitions, the // ones between the arcs too, and its cants kept: a curve whose transitions were lengthened keeps to the points it was laid through trace_fit refit_radii(design const &Design, std::size_t const Vertex, std::vector const &Points); std::size_t minimum_pieces(result const &Result, design const &Design); // Breaks: ascending chainages at which pieces have to end. Counts: least number of the pieces between the consecutive breaks, // one more than the breaks; Intervals receives the number of the interval of each piece std::vector pieces(result const &Result, design const &Design, std::size_t const Count, std::vector const &Breaks = {}, std::vector const &Counts = {}, std::vector *Intervals = nullptr); // the points of the plan set square onto the line, the chainages of them in their order between From and To; empty where they // don't follow one another along it std::vector breaks_at(result const &Result, std::vector const &Points, double const From, double const To); // the pieces of the line ending at the joints of the old paths set square onto it, so that each old path keeps its stretch of the // ground: Intervals receives the old path of each piece, or stays empty where the joints don't follow one another along the line // and the pieces are cut as by pieces() alone std::vector pieces_at(result const &Result, design const &Design, std::size_t const Count, std::vector const &Joints, std::vector &Intervals); struct join_end { glm::dvec3 position{0.0}; glm::dvec2 direction{0.0, 1.0}; double roll{0.0}; bool operator==(join_end const &) const = default; }; enum class join_kind { none, straight, arc, curve, reverse_curve, bezier }; struct join_request { join_end start; join_end end; double radius{0.0}; int variant{0}; double speed{100.0}; double kink{0.1}; transition_shape shape{transition_shape::cubic_parabola}; limits norms; }; struct join_result { join_kind kind{join_kind::none}; std::vector pieces; double length{0.0}; double radius{0.0}; 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; }; join_result join(join_request const &Request); enum class lay_kind { arc_straight, straight_arc, arc, straight }; // a track laid on from an end the way the layout lays it, with the transition curves of the norms: from the curvature the end has // into the arc, between the arcs of a compound curve where both turn the same way, and out of the arc to the straight struct lay_request { join_end start; // the roll in degrees, as the track goes on double curvature{0.0}; // of the end as the track goes on, positive turning from x towards z glm::dvec3 target{0.0}; lay_kind kind{lay_kind::arc_straight}; double radius{500.0}; // of the arc, but the arc through the target double speed{100.0}; double piece_length{100.0}; limits norms; bool operator==(lay_request const &) const = default; }; // no pieces where the norms find no room for the transitions, the note says why struct lay_result { std::vector pieces; std::string note; double length{0.0}; double radius{0.0}; double cant{0.0}; double entry{0.0}; // the transition out of the curvature of the end double exit{0.0}; double from_radius{0.0}; // of the end, 0 for a straight bool compound{false}; }; std::optional lay_on(lay_request const &Request); // the transitions between the arcs of a compound curve which the norms ask for at the speed and at the cants of the arcs void recommend_between(vertex &Vertex, double const Speed, limits const &Limits); // all the transitions of the vertex as long as the norms ask for at its radii and its cants, the cants kept: a cant lowered // lengthens them, the acceleration across left changing more along them void norm_transitions(vertex &Vertex, double const Speed, limits const &Limits); // the length of each of the two transition curves of a vertex with spirals, meeting at the curvature of the radius double spirals_length(transition_shape const Shape, double const Deflection, double const Radius); // the curve keeps to the norms at the speed of the design: the radius, the acceleration across, the transitions long enough bool meets_norms(curve_report const &Curve, design const &Design); // the radii the bends of the straights are eased with: the two transition curves meet at a radius from High down to Low, or above // High where they'd be too short for the speed; where they don't fit, an arc with the transitions and the cant of the norms struct corner_radii { double low{3000.0}; double high{6000.0}; }; // a stretch of the line traced on the photo: the points clicked along the middle of the track, from the straight Base starts on to // the one it ends on, both fixed by the points clicked on them struct trace_stretch { bool valid{false}; // the curves of the stretch with their places: one, of up to three radii, or two with a straight between them where the points // follow those much better; none where the stretch is straight std::vector vertices; std::vector offsets; // of the points from the line laid, positive to the left double rms{0.0}; double worst{0.0}; glm::dvec2 leaving{0.0}; // the heading at the end where the fit chose it, fit_going_on glm::dvec2 ending{0.0}; // where it ends there: the last point, or abreast of it on the arc of a switch going on }; trace_stretch fit_trace_stretch(design const &Base, std::vector const &Points, int const Radii, bool const Straights); // a curve going on from the curve of the free end Base starts at (start_curved), along the points to the last of them, where it ends on a // straight heading as it comes out: from the curvature of that end through a transition to an arc and through another to the straight. // The transitions as long as the norms ask for where the points don't show them shorter. Not valid where no such curve follows them trace_stretch fit_going_on(design const &Base, std::vector const &Points); // the fit of a stretch traced, with up to three radii and no straight in it; none while it's being fitted elsewhere using stretch_fit = std::function(design const &, std::vector const &)>; // the vertices marked 1 are given the curves of the bends of straights clicked on the photo, as large as fit between the neighbouring // ones, these sharing the straights between them; the vertices stay where they are, a curve below the norms is left with its // warnings. A row of bends turning the same way, clicked so close that the straights between their curves would be shorter than // the shortest element, is taken for points along a curve: one curve, compound where it has to, is fitted through them, or one for // each part of the row where the whole doesn't keep to them; such a vertex is marked 0 then. Fit fits it, by fit_trace_stretch // where none is given result ease_corners(design &Design, std::vector &Corners, corner_radii const &Radii, stretch_fit const &Fit = {}); // the curves laid smaller than their vertices ask for, to fit between their straights or into their deflection, given the radii and the // transitions they were laid with: the design tells what's laid, and the plan lays the same curve again void settle_laid(std::vector &Vertices, result const &Laid); // the stretches traced fitted on a thread of their own while the cursor moves: fitting one takes tens of milliseconds. The one asked // for last is fitted first, the oldest of too many waiting left out, the fits of the last ones kept class stretch_fitter { public: struct request { std::vector key; // tells the requests apart design base; std::vector points; int radii{0}; bool straights{true}; bool going{false}; // fit_going_on }; ~stretch_fitter(); void ask(request Request); // the fit of the request of the key, once it's done bool done(std::vector const &Key, trace_stretch &Fit) const; // the fit done last, of whatever request bool latest(std::vector &Key, trace_stretch &Fit) const; private: void work(); mutable std::mutex m_mutex; std::condition_variable m_wake; std::vector m_asked; std::optional m_fitting; std::vector, trace_stretch>> m_results; // the last done first bool m_stop{false}; std::thread m_thread; }; }