diff --git a/application/editormode.h b/application/editormode.h index 671da0e8..164efc18 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -1513,7 +1513,8 @@ class editor_mode : public application_mode, private editor_track::observer, pri // the track through the points clicked, each the way its mode says; End: the free end it finishes at, the last point then std::vector lay_pieces(std::vector const &Points, std::vector const &Modes, editor_track::snap_target const &End, double &Length, std::string &Error, lay_line_plan *Plan = nullptr) const; - bool lay_line(std::vector const &Points, std::vector const &Modes, editor_track::snap_target const &End, lay_line_plan &Plan, std::string &Error) const; + // Adjoining: the curve next to a free end on a curve goes on from its curvature, where it turns its way; else from a straight there + bool lay_line(std::vector const &Points, std::vector const &Modes, editor_track::snap_target const &End, lay_line_plan &Plan, std::string &Error, bool const Adjoining = true) const; // the curves of a stretch traced, the ones fitted lately kept for the same points // none while the stretch the cursor moves along is still being fitted; Meanwhile: the fit done last of the same points but the one // at the cursor until then diff --git a/application/editormodelay.cpp b/application/editormodelay.cpp index d2a031c9..d0b9261d 100644 --- a/application/editormodelay.cpp +++ b/application/editormodelay.cpp @@ -46,6 +46,29 @@ http: using namespace track_mode; +namespace +{ + +// the curve of the track of a free end the line laid goes on from at its start, or into at its end: its radius, the side it turns to +// as the line runs and its cant leaning into the turn; nothing on a straight +void adjoining_curve(editor_track::snap_target const &End, bool const Atend, double const Gauge, alignment::design &Design) +{ + if (End.track == nullptr || (End.track->eType != tt_Normal && End.track->eType != tt_Switch)) + return; + auto const bend{editor_track::bend_at(*End.track, End.position)}; + if (false == bend.has_value() || std::abs(bend->curvature) < 1e-4) + return; + // the curvature as the track goes on beyond its end: the way the line runs at its start, against it at its end + auto const curvature{Atend ? -bend->curvature : bend->curvature}; + auto const cant{Gauge * std::sin(glm::radians(bend->roll)) * (bend->curvature > 0.0 ? 1.0 : -1.0)}; + (Atend ? Design.end_radius : Design.start_radius) = 1.0 / std::abs(curvature); + (Atend ? Design.end_turn : Design.start_turn) = curvature > 0.0 ? 1 : -1; + (Atend ? Design.end_cant : Design.start_cant) = std::max(0.0, cant); + (Atend ? Design.end_curved : Design.start_curved) = true; +} + +} // namespace + void editor_mode::update_build_tools() { glm::dvec3 const ground{cursor_ground()}; @@ -437,7 +460,8 @@ void editor_mode::point_drag_finish() std::optional editor_mode::eased_layout(TTrack const *From, glm::dvec3 const &Point, glm::dvec2 const &Direction, glm::dvec3 const &Target, double const Radius) const { m_eased_last.reset(); - if (From == nullptr || From->eType != tt_Normal) + // from the diverging track of a switch a curve goes on from its curvature, a straight goes on right from its end, as from the switch + if (From == nullptr || (From->eType != tt_Normal && (From->eType != tt_Switch || m_track_layout == track_layout::straight))) return std::nullopt; auto const end{editor_track::bend_at(*From, Point)}; if (false == end.has_value()) @@ -971,7 +995,8 @@ std::vector editor_mode::lay_pieces(std::vector const return std::max(alignment::minimum_pieces(Shape, Design), static_cast(std::ceil(Shape.length / std::max(5.0, tool.piece_length)))); }; std::vector pieces; - // the curvature of the track laid has to go on from the one of the track it joins, a jump gets a transition curve between them + // the curvature of the track laid has to go on from the one of the track it joins, a jump gets a transition curve between them. Not + // at a switch: its diverging track ends its curve with a jump as it begins it, a straight going on right from there auto const grade{(Points.back().y - Points.front().y) / std::max(1.0, plan_distance(Points.front(), Points.back()))}; auto const eased = [&](editor_track::snap_target const &Joint, double const Chainage, double const Sign) { if (Joint.track == nullptr || Joint.track->eType != tt_Normal) @@ -987,15 +1012,22 @@ std::vector editor_mode::lay_pieces(std::vector const if ((false == in.pieces.empty() || false == out.pieces.empty()) && editor_track::eases_room(in, out, Points.front(), design.start_direction, Points.back(), design.end_direction)) { auto easeddesign{design}; + // an end moved out of the curve of the adjoining track is on a straight if (false == in.pieces.empty()) { easeddesign.start = in.end; easeddesign.start_direction = in.direction; + easeddesign.start_curved = false; + easeddesign.start_radius = 0.0; + easeddesign.start_turn = 0; } if (false == out.pieces.empty()) { easeddesign.end = out.end; easeddesign.end_direction = -out.direction; + easeddesign.end_curved = false; + easeddesign.end_radius = 0.0; + easeddesign.end_turn = 0; } // the bends stay where they are, measured from the ends moved if (easeddesign.vertices.size() >= 2 && shape.vertices.size() == easeddesign.vertices.size()) @@ -1019,6 +1051,12 @@ std::vector editor_mode::lay_pieces(std::vector const { Length = shape.length; pieces = alignment::pieces(shape, design, count(shape, design)); + // a line lay_line begins at the end of the transition out of the curve of the free end keeps the transition before it + auto const after = [&](editor_track::easing const &Easing, glm::dvec3 const &Joint, glm::dvec3 const &Own) { return false == Easing.pieces.empty() && plan_distance(Joint, Own) > 0.01; }; + auto const &before{after(in, Points.front(), design.start) ? in : editor_track::easing{}}; + auto const &beyond{after(out, Points.back(), design.end) ? out : editor_track::easing{}}; + Length = editor_track::eased_length(before, Length, beyond); + pieces = editor_track::eased_pieces(before, pieces, beyond); } // the heights: fitted to the ground, or from point to point through the points clicked TTrack const *style{tool.start.track != nullptr ? tool.start.track : End.track != nullptr ? End.track : tool.copy_selected ? selected_track() : nullptr}; @@ -1033,7 +1071,7 @@ std::vector editor_mode::lay_pieces(std::vector const return pieces; } -bool editor_mode::lay_line(std::vector const &Points, std::vector const &Modes, editor_track::snap_target const &End, lay_line_plan &Plan, std::string &Error) const +bool editor_mode::lay_line(std::vector const &Points, std::vector const &Modes, editor_track::snap_target const &End, lay_line_plan &Plan, std::string &Error, bool const Adjoining) const { auto const &tool{m_lay}; Plan = {}; @@ -1043,6 +1081,22 @@ bool editor_mode::lay_line(std::vector const &Points, std::vector 1e-6}; + // from a free end on a curve, or to one, the curve laid next to it turning its way goes on from its curvature: it begins right at the + // end, not on a straight, its transition going from that curvature. Where no such curve fits, the line is laid from a straight there, + // which lay_pieces eases out of the curve + if (Adjoining) + { + if (tool.start.track != nullptr && glm::length(plan_of(tool.start.direction)) > 1e-6) + adjoining_curve(tool.start, false, design.norms.gauge, design); + if (joined) + adjoining_curve(End, true, design.norms.gauge, design); + } + auto const curved{design.start_curved || design.end_curved}; + auto const from_straight = [&]() { return curved && lay_line(Points, Modes, End, Plan, Error, false); }; + // the end of the transition out of the curve of the free end, the start of the line where a curve traced next to it is laid from a + // straight there; the same at the end + std::optional> easedstart; + std::optional> easedend; // the points between the start and the end: a free end reached is the last point auto const inner{Points.size() - (joined ? 1 : 0)}; auto const mode_of = [&](std::size_t const Index) { return Index < Modes.size() ? Modes[Index] : lay_mode::straights; }; @@ -1117,6 +1171,8 @@ bool editor_mode::lay_line(std::vector const &Points, std::vector const &Points, std::vector kept; + auto const easing = [&](editor_track::snap_target const &Joint, double const Grade) { + return Joint.track != nullptr && Joint.track->eType == tt_Normal ? editor_track::ease_out_of(*Joint.track, Joint.position, Grade, design.speed, design.norms) : editor_track::easing{}; + }; + auto const in{base.start_curved ? easing(tool.start, 0.0) : editor_track::easing{}}; + auto const out{base.end_curved ? easing(End, 0.0) : editor_track::easing{}}; + if (false == in.pieces.empty()) + { + plain.start = in.end; + plain.start_direction = in.direction; + } + if (false == out.pieces.empty()) + { + plain.end = out.end; + plain.end_direction = -out.direction; + } + // the points along the transitions out of the curves left out, the line from the straights after them + for (auto const &point : traced) + if ((in.pieces.empty() || glm::distance(point, plan_of(base.start)) > in.length) && (out.pieces.empty() || glm::distance(point, plan_of(base.end)) > out.length)) + kept.push_back(point); + auto const other{kept.size() >= 2 ? traced_stretch(plain, kept, tool.radii, tool.straights_between) : std::optional{alignment::trace_stretch{}}}; + if (false == other.has_value()) + { + Error = STR_C("Fitting the curve to the points..."); + return false; + } + auto goes_on{false}; + if (fitted->valid) + { + auto laid{base}; + laid.vertices = fitted->vertices; + for (auto const &curve : alignment::compute(laid).curves) + goes_on = goes_on || curve.curvature_in > 0.0 || curve.curvature_out > 0.0; + } + // a curve going on which keeps to the points within 8 cm on average and 30 cm at most, as the fits take it for one keeping to + // them, stays: the straight one fits the points clicked a little off closer with the radii it has more + auto const loose{fitted->valid && (fitted->rms > 0.08 || fitted->worst > 0.3)}; + if (other->valid && (false == fitted->valid || false == goes_on || (loose && other->rms < 0.75 * fitted->rms))) + { + fitted = other; + // the line laid from the end of the transition out of the curve, which lay_pieces lays before it + if (false == in.pieces.empty()) + easedstart = std::make_pair(in.end, in.direction); + if (false == out.pieces.empty()) + easedend = std::make_pair(out.end, -out.direction); + // from a straight there; at a switch with the jump of the curvature it has, which isn't warned of + auto const straight_at = [&](editor_track::snap_target const &Joint, bool &Curved, double &Radius, int &Turn) { + Curved = false; + if (Joint.track != nullptr && Joint.track->eType == tt_Switch) + { + Radius = 0.0; + Turn = 0; + } + }; + if (base.start_curved) + straight_at(tool.start, design.start_curved, design.start_radius, design.start_turn); + if (base.end_curved) + straight_at(End, design.end_curved, design.end_radius, design.end_turn); + } + } auto const &fit{*fitted}; if (false == fit.valid) { Error = STR_C("No curve follows the points clicked along it"); - return false; + return from_straight(); } for (auto const &vertex : fit.vertices) bends.push_back({lifted(vertex.position, entry.y), vertex, false}); @@ -1259,6 +1386,13 @@ bool editor_mode::lay_line(std::vector const &Points, std::vector const &Points, std::vector const &Points, std::vector editor_mode::traced_stretch(alignment::design const &Base, std::vector const &Points, int const Radii, bool const Straights, bool const Meanwhile) const { // the stretches traced before the one the cursor moves along are the same from frame to frame: their fits are kept + // the curves of the adjoining tracks it goes on from or into, signed by their turn, and their cants + auto const curvature = [](bool const Curved, double const Radius, int const Turn) { return Curved && Radius > 0.0 ? Turn / Radius : 0.0; }; std::vector key{Base.start.x, Base.start.z, Base.start_direction.x, Base.start_direction.y, Base.end.x, Base.end.z, Base.end_direction.x, Base.end_direction.y, Base.speed, - static_cast(Base.shape), static_cast(Radii), Straights ? 1.0 : 0.0, Base.start_reserve, Base.end_reserve}; + static_cast(Base.shape), static_cast(Radii), Straights ? 1.0 : 0.0, Base.start_reserve, Base.end_reserve, + curvature(Base.start_curved, Base.start_radius, Base.start_turn), Base.start_curved ? Base.start_cant : 0.0, curvature(Base.end_curved, Base.end_radius, Base.end_turn), + Base.end_curved ? Base.end_cant : 0.0}; for (auto const &point : Points) { key.push_back(point.x); @@ -1330,7 +1468,7 @@ std::optional editor_mode::traced_stretch(alignment::d } m_stretch_fitter.ask({key, Base, Points, Radii, Straights}); std::vector last; - auto const header{std::size_t{14}}; + auto const header{std::size_t{18}}; if (Meanwhile && m_stretch_fitter.latest(last, fit) && last.size() >= header + 2 && key.size() >= header + 2 && std::equal(key.begin(), key.begin() + 4, last.begin()) && std::equal(key.begin() + 8, key.begin() + header, last.begin() + 8)) { @@ -1447,6 +1585,11 @@ void editor_mode::lay_finish(editor_track::snap_target const &End) tool.status += "\n" + error; if (eased.has_value() && eased->compound) tool.status += "\n" + format(STR_C("Compound curve: R %.0f m goes on into R %.0f m through a transition curve of %.0f m"), eased->from_radius, eased->radius, eased->entry); + // the curves next to the free ends going on from the curves of their tracks + if (plan.shape.valid) + for (auto const &curve : plan.shape.curves) + if (auto const on{curve_goes_on(curve)}; false == on.empty()) + tool.status += "\n" + format(STR_C("Curve %d %s"), curve.vertex + 1, on.c_str()); if (eased.has_value() && false == eased->note.empty()) tool.status += "\n" + eased->note; { diff --git a/application/editormoderoute.cpp b/application/editormoderoute.cpp index d4695b6f..ed3331a5 100644 --- a/application/editormoderoute.cpp +++ b/application/editormoderoute.cpp @@ -277,6 +277,11 @@ void editor_mode::route_take_stored(int const Index) design.norms = stored.design.norms; design.transition_pieces = stored.design.transition_pieces; design.arc_piece_angle = stored.design.arc_piece_angle; + // a curve laid going on from the curve of the track adjoining keeps going on from it, its radius the one which begins it there + design.start_curved = stored.design.start_curved; + design.end_curved = stored.design.end_curved; + design.start_cant = stored.design.start_cant; + design.end_cant = stored.design.end_cant; route.designed = static_cast(design.vertices.size()); route.origin = stored.origin; route.vertex = -1; @@ -2105,6 +2110,19 @@ bool editor_mode::render_route_vertex(int const Index) return edited; }; ImU32 const transition{IM_COL32(255, 140, 30, 255)}; + // the first curve, or the last one, may go on from the curve of the track adjoining at that end: it begins right there, its radius + // the one which begins it there, its entry, or exit, the transition between the two curves + auto const count{static_cast(design.vertices.size())}; + for (auto const atend : {false, true}) + { + auto const radius{atend ? design.end_radius : design.start_radius}; + if ((atend ? Index + 1 != count : Index != 0) || radius <= 0.0 || radius >= 10000.0 || (atend && count == 1 && design.start_curved)) + continue; + auto &curved{atend ? design.end_curved : design.start_curved}; + changed |= ImGui::Checkbox((format(atend ? STR_C("Goes into the curve of the track at the end (R %.0f m)") : STR_C("Goes on from the curve of the track at the start (R %.0f m)"), radius) + (atend ? "##curvedend" : "##curvedstart")).c_str(), &curved); + item_tooltip("The curve begins right at the end of the fragment, at the curvature of the track adjoining there, where it turns the same way; its radius is the one " + "which begins it there, the transition at that end goes between the two curves"); + } 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", part::arc); ImGui::SameLine(); changed |= field("##cant", vertex.cant, 1.0f, STR_C("cant %.0f mm"), IM_COL32(240, 170, 60, 255), diff --git a/application/editormodeselftest.cpp b/application/editormodeselftest.cpp index 6ec1a20c..c4bc00d7 100644 --- a/application/editormodeselftest.cpp +++ b/application/editormodeselftest.cpp @@ -129,6 +129,11 @@ editor_mode::selftest_result editor_mode::selftest_dispatch(std::string const &c // layclicks ... clicks of the lay tool at the points, a free end within 3 m joined // layspeed the speed set for the laying, and whether it goes also on from a track with its own // laymode , layradius , laytrace as the panel of the laying sets them +// endcurves logs every free end of the rails: where, the way out and the curvature its track goes on with +// curvejoint logs the paths meeting at the end nearest the point, the curvature of each and its jump from the ones before to the ones after +// laywalk ... a line of known curves on from the free end nearest the point, +// the points along it clicked in the mode of a curve, from the end or towards it (see the command) +// laybends ... the bends of straights clicked from the free end nearest the point // layp ... the points clicked in the mode set, on the ground, a free end within 3 m reached // layreport