mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-04 18:49:26 +01:00
fix(editor): a curve laid from a free end on a curve, or to one, goes on from its curvature: traced (Tab) from the end of the diverging track of a switch it begins right at P4 at the radius of the switch, not on a straight nor with a transition from a curvature of 0. The design of the line takes the curve of the adjoining track at each end (start_curved/end_curved with the radius, the side and the cant, kept with it in the scenery): the curve of the first vertex turning its way, or of the last one, begins right at the end, its transition going from that curvature and cant to those of its first arc, as between the arcs of a compound curve, none where it goes on at its radius; the radius of its arcs is the one which begins it there, solved for the tangent, the curves next to it making room on the straight. A curve traced next to such an end is fitted by its transitions alone, with none between the curves where the points keep to that as well, or laid from a straight there where it doesn't go on from it or that keeps to the points much closer: eased out of the curve of a plain track by the transition of the norms, the line beginning at its end, or right from P4, as a switch goes on with a straight. A bend of straights turning the way of the curve gets its curve going on from it with the transitions and the cant of the norms; the other way, or a straight, as before: eased out of the curve of a plain track, the jump of the curvature kept at a switch, as at its start, and not warned of. The cant of the adjoining track goes on into the curve with no ramp at that end; the norms ask for the transition between the curves only where the curvature differs by more than 5 % or the cant by 5 mm. The readout by the cursor, the status after laying and the plan tell the curve going on from the track, the plan with a box to go on or not. Dragged back to P4 from a point the curve goes on from its radius too. Selftests laywalk, laybends, curvejoint, endcurves; unit tests of a curve going on from a switch and into a free end, its cant, traced, through a transition into a larger radius, and of a bend of straights after such an end
This commit is contained in:
@@ -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<segment_data> lay_pieces(std::vector<glm::dvec3> const &Points, std::vector<lay_mode> const &Modes, editor_track::snap_target const &End, double &Length, std::string &Error,
|
||||
lay_line_plan *Plan = nullptr) const;
|
||||
bool lay_line(std::vector<glm::dvec3> const &Points, std::vector<lay_mode> 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<glm::dvec3> const &Points, std::vector<lay_mode> 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
|
||||
|
||||
@@ -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<alignment::lay_result> 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<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> const
|
||||
return std::max(alignment::minimum_pieces(Shape, Design), static_cast<std::size_t>(std::ceil(Shape.length / std::max(5.0, tool.piece_length))));
|
||||
};
|
||||
std::vector<segment_data> 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<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> 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<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> 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<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> const
|
||||
return pieces;
|
||||
}
|
||||
|
||||
bool editor_mode::lay_line(std::vector<glm::dvec3> const &Points, std::vector<lay_mode> const &Modes, editor_track::snap_target const &End, lay_line_plan &Plan, std::string &Error) const
|
||||
bool editor_mode::lay_line(std::vector<glm::dvec3> const &Points, std::vector<lay_mode> 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<glm::dvec3> const &Points, std::vector<la
|
||||
design.speed = design_speed(tool.start.track, End.track);
|
||||
design.start = Points.front();
|
||||
auto const joined{End.track != nullptr && glm::length(plan_of(End.direction)) > 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<std::pair<glm::dvec3, glm::dvec2>> easedstart;
|
||||
std::optional<std::pair<glm::dvec3, glm::dvec2>> 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<glm::dvec3> const &Points, std::vector<la
|
||||
traced.push_back(plan_of(Points[k]));
|
||||
}
|
||||
i = last;
|
||||
// the curve right from the free end the line starts at
|
||||
auto const fromend{bends.empty() && Plan.traces.empty() && along.has_value()};
|
||||
// the straight the curve comes from: the one of the free end or of the curve before, else the one through the last two
|
||||
// points clicked before the curve, the last of them then no bend
|
||||
auto entry{anchor};
|
||||
@@ -1179,17 +1235,88 @@ bool editor_mode::lay_line(std::vector<glm::dvec3> const &Points, std::vector<la
|
||||
// the curve may begin before the point the straight was clicked at, and end past the last point where the line goes on
|
||||
base.start_reserve = behind;
|
||||
base.end_reserve = last == inner ? 0.0 : 1e4;
|
||||
auto const fitted{traced_stretch(base, traced, tool.radii, tool.straights_between)};
|
||||
// it goes on from the curve of the free end it starts at, or into the one it finishes at
|
||||
base.start_curved = design.start_curved && fromend;
|
||||
base.end_curved = design.end_curved && reaching;
|
||||
auto fitted{traced_stretch(base, traced, tool.radii, tool.straights_between)};
|
||||
if (false == fitted.has_value())
|
||||
{
|
||||
Error = STR_C("Fitting the curve to the points...");
|
||||
return false;
|
||||
}
|
||||
// next to a free end on a curve the stretch goes on from that curve, or begins from a straight there: eased out of the curve
|
||||
// by the transition lay_pieces lays, or right from the end of the diverging track of a switch, as the switch goes on. The
|
||||
// straight one where the curve next to the free end doesn't go on from it, or where that one doesn't keep to the points and
|
||||
// this one keeps much closer
|
||||
if (base.start_curved || base.end_curved)
|
||||
{
|
||||
auto plain{base};
|
||||
plain.start_curved = plain.end_curved = false;
|
||||
std::vector<glm::dvec2> 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>{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<glm::dvec3> const &Points, std::vector<la
|
||||
return false;
|
||||
design.start_direction = glm::normalize(first);
|
||||
}
|
||||
if (easedstart.has_value())
|
||||
std::tie(design.start, design.start_direction) = *easedstart;
|
||||
if (easedend.has_value())
|
||||
{
|
||||
std::tie(design.end, design.end_direction) = *easedend;
|
||||
end = design.end;
|
||||
}
|
||||
for (auto const &bend : bends)
|
||||
{
|
||||
auto vertex{bend.corner ? alignment::vertex{} : bend.vertex};
|
||||
@@ -1275,7 +1409,7 @@ bool editor_mode::lay_line(std::vector<glm::dvec3> const &Points, std::vector<la
|
||||
if (false == Plan.shape.valid)
|
||||
{
|
||||
Error = Plan.shape.errors.empty() ? std::string{STR_C("The track can't be laid through these points")} : Plan.shape.errors.front();
|
||||
return false;
|
||||
return from_straight();
|
||||
}
|
||||
// the grade breaks at the bends which stayed bends; along a curve, traced or made of bends, it goes on even from end to end
|
||||
for (std::size_t k = 0; k < design.vertices.size() && k < Plan.corners.size(); ++k)
|
||||
@@ -1295,8 +1429,12 @@ bool editor_mode::lay_line(std::vector<glm::dvec3> const &Points, std::vector<la
|
||||
std::optional<alignment::trace_stretch> editor_mode::traced_stretch(alignment::design const &Base, std::vector<glm::dvec2> 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<double> 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<double>(Base.shape), static_cast<double>(Radii), Straights ? 1.0 : 0.0, Base.start_reserve, Base.end_reserve};
|
||||
static_cast<double>(Base.shape), static_cast<double>(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<alignment::trace_stretch> editor_mode::traced_stretch(alignment::d
|
||||
}
|
||||
m_stretch_fitter.ask({key, Base, Points, Radii, Straights});
|
||||
std::vector<double> 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;
|
||||
{
|
||||
|
||||
@@ -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<int>(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<int>(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),
|
||||
|
||||
@@ -129,6 +129,11 @@ editor_mode::selftest_result editor_mode::selftest_dispatch(std::string const &c
|
||||
// layclicks <layout 0..3> <radius> <x> <z>... clicks of the lay tool at the points, a free end within 3 m joined
|
||||
// layspeed <km/h> <own 0|1> the speed set for the laying, and whether it goes also on from a track with its own
|
||||
// laymode <straights|curve>, layradius <R from> <R to>, laytrace <radii 0..3> <straights between 0|1> 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 <x> <z> 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 <from|into> <x> <z> <spacing> <noise> <k0> <k1> <L>... 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 <x> <z> <distance> <degrees> ... <distance> the bends of straights clicked from the free end nearest the point
|
||||
// layp <x> <z>... the points clicked in the mode set, on the ground, a free end within 3 m reached
|
||||
// layreport <label> logs the line the points clicked make, as Enter would lay it: its bends and curves, warnings, traces
|
||||
// layenter, layback, laycancel as Enter, Backspace and Esc while laying
|
||||
|
||||
@@ -46,6 +46,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <array>
|
||||
#include "application/editormodeselftest.h"
|
||||
|
||||
editor_mode::selftest_result editor_mode::selftest_join(std::string const &command, std::istringstream &words)
|
||||
@@ -129,6 +130,87 @@ editor_mode::selftest_result editor_mode::selftest_join(std::string const &comma
|
||||
WriteLog(format("SELFTEST %s end: %.3f m from the target", command.c_str(), geometry::plan_distance(previous, b)));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "endcurves")
|
||||
{
|
||||
// every free end of the rails: where, the way out of it and the curvature of its track as it would go on beyond it
|
||||
for (auto const &end : editor_track::free_ends(nullptr, 1, glm::dvec3{0.0}, 1e7, {}))
|
||||
{
|
||||
auto const bend{editor_track::bend_at(*end.track, end.position)};
|
||||
auto const curvature{bend.has_value() ? bend->curvature : 0.0};
|
||||
WriteLog(format("SELFTEST freeend %s[%d].%s: %.3f %.3f %.3f, out %.6f %.6f, k %.6f (R %.1f), roll %.3f", end.track->name().c_str(), end.point.path, end.point.kind == editor_track::point_kind::end ? "end" : "start", end.position.x, end.position.y,
|
||||
end.position.z, bend.has_value() ? bend->direction.x : 0.0, bend.has_value() ? bend->direction.y : 0.0, curvature, std::abs(curvature) > 1e-9 ? 1.0 / std::abs(curvature) : 0.0, bend.has_value() ? bend->roll : 0.0));
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "curvejoint")
|
||||
{
|
||||
// the paths meeting at the point: the curvature of each, signed the way the first one runs into the point, and the jumps of the
|
||||
// curvature and of the direction from the ones before the point to the ones after it
|
||||
glm::dvec3 point{0.0};
|
||||
words >> point.x >> point.z;
|
||||
// the end of a path nearest the point, within 3 m
|
||||
auto nearest{3.0};
|
||||
auto const given{point};
|
||||
for (auto const *track : simulation::Paths.sequence())
|
||||
if (track != nullptr && false == track->m_editorremoved)
|
||||
for (auto const &path : track->m_paths)
|
||||
for (auto const index : {segment_data::point::start, segment_data::point::end})
|
||||
if (auto const distance{geometry::plan_distance(path.points[index], given)}; distance < nearest)
|
||||
{
|
||||
nearest = distance;
|
||||
point = path.points[index];
|
||||
}
|
||||
struct meeting
|
||||
{
|
||||
TTrack const *track;
|
||||
int path;
|
||||
bool atend;
|
||||
double curvature; // as the path goes away from the point
|
||||
double roll;
|
||||
glm::dvec2 out;
|
||||
};
|
||||
std::vector<meeting> meetings;
|
||||
for (auto const *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved)
|
||||
continue;
|
||||
for (int i = 0; i < static_cast<int>(track->m_paths.size()); ++i)
|
||||
for (auto const atend : {false, true})
|
||||
{
|
||||
auto const &path{track->m_paths[i]};
|
||||
if (geometry::plan_distance(path.points[atend ? segment_data::point::end : segment_data::point::start], point) > 0.05)
|
||||
continue;
|
||||
geometry::bezier const curve{path};
|
||||
auto const first{curve.first(atend ? 1.0 : 0.0)};
|
||||
auto const second{curve.second(atend ? 1.0 : 0.0)};
|
||||
auto const speed{std::hypot(first.x, first.z)};
|
||||
if (speed < 1e-9)
|
||||
continue;
|
||||
auto const curvature{(first.x * second.z - first.z * second.x) / (speed * speed * speed)};
|
||||
glm::dvec2 const along{first.x / speed, first.z / speed};
|
||||
meetings.push_back({track, i, atend, atend ? -curvature : curvature, static_cast<double>(atend ? path.rolls[1] : path.rolls[0]), atend ? -along : along});
|
||||
}
|
||||
}
|
||||
if (meetings.empty())
|
||||
{
|
||||
WriteLog(format("SELFTEST curvejoint %.1f %.1f: no path ends there", point.x, point.z));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
auto const into{-meetings.front().out};
|
||||
std::vector<std::pair<double, glm::dvec2>> before;
|
||||
std::vector<std::pair<double, glm::dvec2>> after;
|
||||
for (auto const &each : meetings)
|
||||
{
|
||||
auto const leaving{glm::dot(each.out, into) > 0.0};
|
||||
auto const curvature{leaving ? each.curvature : -each.curvature};
|
||||
(leaving ? after : before).emplace_back(curvature, leaving ? each.out : -each.out);
|
||||
WriteLog(format("SELFTEST curvejoint %s[%d].%s: %s, k %.6f (R %.1f), roll %.3f", each.track->name().c_str(), each.path, each.atend ? "end" : "start", leaving ? "after" : "before", curvature, std::abs(curvature) > 1e-9 ? 1.0 / std::abs(curvature) : 0.0, each.roll));
|
||||
}
|
||||
for (auto const &[k0, d0] : before)
|
||||
for (auto const &[k1, d1] : after)
|
||||
WriteLog(format("SELFTEST curvejoint jump: k %.6f -> %.6f, by %.6f 1/m (%.1f%%), kink %.4f deg", k0, k1, k1 - k0, std::abs(k0) > 1e-9 ? 100.0 * std::abs(k1 - k0) / std::abs(k0) : 0.0, glm::degrees(std::abs(geometry::signed_angle(d0, d1)))));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "fittime")
|
||||
{
|
||||
// the grade line of a straight laid over rolling ground fitted, as at a click, and as while the track follows the
|
||||
@@ -267,6 +349,123 @@ editor_mode::selftest_result editor_mode::selftest_join(std::string const &comma
|
||||
lay_report(label);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
// a line of known curves going on from the free end nearest the point, within 60 m: segments of the curvature going evenly from the
|
||||
// first to the second (1/m, positive turning the way the track of the end turns, to the left from a straight one) over the length. from: the free end
|
||||
// clicked, then in the mode of a curve the points along the line every Spacing metres and two on the straight after it, 30 and 60 m
|
||||
// on, left for Enter; into: the same line laid towards the free end, the point 60 m on clicked first, the one 30 m on and the ones
|
||||
// along the line in the mode of a curve, the free end last, which lays it. The points shifted across by up to Noise
|
||||
if (command == "laywalk")
|
||||
{
|
||||
std::string way;
|
||||
glm::dvec3 around{0.0};
|
||||
double spacing{10.0};
|
||||
double noise{0.0};
|
||||
words >> way >> around.x >> around.z >> spacing >> noise;
|
||||
std::vector<std::array<double, 3>> segments;
|
||||
for (std::array<double, 3> segment; words >> segment[0] >> segment[1] >> segment[2];)
|
||||
segments.push_back(segment);
|
||||
auto const end{editor_track::find_free_end(nullptr, 1, around, 60.0, {})};
|
||||
auto const bend{end.track != nullptr ? editor_track::bend_at(*end.track, end.position) : std::nullopt};
|
||||
if (false == bend.has_value() || segments.empty() || spacing <= 0.0)
|
||||
{
|
||||
WriteLog("SELFTEST laywalk: no free end near the point, or no line");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
glm::dvec2 position{end.position.x, end.position.z};
|
||||
auto heading{std::atan2(bend->direction.y, bend->direction.x)};
|
||||
auto const sense{bend->curvature < 0.0 ? -1.0 : 1.0};
|
||||
double chainage{0.0};
|
||||
std::vector<glm::dvec2> along;
|
||||
std::vector<glm::dvec2> across;
|
||||
auto next{spacing};
|
||||
for (auto const &[from, to, length] : segments)
|
||||
{
|
||||
auto const steps{std::max(1, static_cast<int>(std::ceil(length / 0.05)))};
|
||||
auto const step{length / steps};
|
||||
auto const start{heading};
|
||||
auto const angle = [&, from = from * sense, to = to * sense, length = length](double const S) { return start + from * S + (to - from) * S * S / (2.0 * length); };
|
||||
for (int i = 0; i < steps; ++i)
|
||||
{
|
||||
auto const middle{angle((i + 0.5) * step)};
|
||||
position += glm::dvec2{std::cos(middle), std::sin(middle)} * step;
|
||||
chainage += step;
|
||||
if (chainage >= next - 1e-6)
|
||||
{
|
||||
auto const now{angle((i + 1) * step)};
|
||||
along.push_back(position);
|
||||
across.push_back({-std::sin(now), std::cos(now)});
|
||||
next += spacing;
|
||||
}
|
||||
}
|
||||
heading = angle(length);
|
||||
}
|
||||
glm::dvec2 const out{std::cos(heading), std::sin(heading)};
|
||||
glm::dvec2 const side{-out.y, out.x};
|
||||
auto const shifted = [&](glm::dvec2 const &Point, glm::dvec2 const &Across, int const Count) {
|
||||
auto const shift{noise * (Count % 3 == 0 ? 1.0 : Count % 3 == 1 ? -0.6 : 0.2)};
|
||||
return glm::dvec3{Point.x + Across.x * shift, 0.0, Point.y + Across.y * shift};
|
||||
};
|
||||
WriteLog(format("SELFTEST laywalk %s from %s[%d]: %.3f %.3f heading %.4f, k %.6f, line %.1f m to %.3f %.3f heading %.4f, %zu points along it", way.c_str(), end.track->name().c_str(), end.point.path, end.position.x, end.position.z, std::atan2(bend->direction.y, bend->direction.x),
|
||||
bend->curvature, chainage, position.x, position.y, heading, along.size()));
|
||||
m_lay.active = true;
|
||||
lay_cancel();
|
||||
if (m_lay.mode != lay_mode::straights)
|
||||
lay_switch_mode();
|
||||
if (way == "into")
|
||||
{
|
||||
lay_point(shifted(position + out * 60.0, side, 0));
|
||||
lay_switch_mode();
|
||||
lay_point(shifted(position + out * 30.0, side, 1));
|
||||
for (auto i{along.size()}; i-- > 0;)
|
||||
if (glm::distance(along[i], glm::dvec2{end.position.x, end.position.z}) > 3.5)
|
||||
lay_point(shifted(along[i], across[i], static_cast<int>(i)));
|
||||
lay_point(end.position);
|
||||
WriteLog(format("SELFTEST laywalk into: %zu points, %s", m_lay.points.size(), m_lay.status.c_str()));
|
||||
}
|
||||
else
|
||||
{
|
||||
lay_point(end.position);
|
||||
lay_switch_mode();
|
||||
for (std::size_t i = 0; i < along.size(); ++i)
|
||||
lay_point(shifted(along[i], across[i], static_cast<int>(i)));
|
||||
lay_point(shifted(position + out * 30.0, side, 1));
|
||||
lay_point(shifted(position + out * 60.0, side, 2));
|
||||
WriteLog(format("SELFTEST laywalk from: %zu points, start %s", m_lay.points.size(), m_lay.start.track != nullptr ? m_lay.start.track->name().c_str() : "-"));
|
||||
}
|
||||
lay_switch_mode();
|
||||
return selftest_result::handled;
|
||||
}
|
||||
// the bends of straights from the free end nearest the point, within 60 m: the free end clicked, then each point the distance on
|
||||
// along the way it goes, which then turns by the angle (degrees, positive the way the track of the end turns, to the left from a
|
||||
// straight one); the last one the end, left for Enter
|
||||
if (command == "laybends")
|
||||
{
|
||||
glm::dvec3 around{0.0};
|
||||
words >> around.x >> around.z;
|
||||
auto const end{editor_track::find_free_end(nullptr, 1, around, 60.0, {})};
|
||||
auto const bend{end.track != nullptr ? editor_track::bend_at(*end.track, end.position) : std::nullopt};
|
||||
if (false == bend.has_value())
|
||||
{
|
||||
WriteLog("SELFTEST laybends: no free end near the point");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
m_lay.active = true;
|
||||
lay_cancel();
|
||||
if (m_lay.mode != lay_mode::straights)
|
||||
lay_switch_mode();
|
||||
lay_point(end.position);
|
||||
glm::dvec2 position{end.position.x, end.position.z};
|
||||
auto heading{std::atan2(bend->direction.y, bend->direction.x)};
|
||||
for (double distance{0.0}; words >> distance;)
|
||||
{
|
||||
position += glm::dvec2{std::cos(heading), std::sin(heading)} * distance;
|
||||
lay_point({position.x, 0.0, position.y});
|
||||
if (double turn{0.0}; words >> turn)
|
||||
heading += glm::radians(turn) * (bend->curvature < 0.0 ? -1.0 : 1.0);
|
||||
}
|
||||
WriteLog(format("SELFTEST laybends from %s[%d] %.3f %.3f: %zu points", end.track->name().c_str(), end.point.path, end.position.x, end.position.z, m_lay.points.size()));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "layenter" || command == "laycancel" || command == "layback")
|
||||
{
|
||||
m_typed.clear();
|
||||
|
||||
@@ -233,6 +233,13 @@ editor_mode::selftest_result editor_mode::selftest_track(std::string const &comm
|
||||
text += format(" [R %.3f L %.3f share %.4f]", arc.radius, arc.transition, arc.share);
|
||||
WriteLog(text);
|
||||
}
|
||||
// the curves the design lays, going on from the curves of the tracks adjoining where they do, and its warnings
|
||||
WriteLog(format("SELFTEST route ends: start R %.1f turn %d curved %d, end R %.1f turn %d curved %d", m_route.design.start_radius, m_route.design.start_turn, m_route.design.start_curved ? 1 : 0, m_route.design.end_radius, m_route.design.end_turn, m_route.design.end_curved ? 1 : 0));
|
||||
for (auto const &curve : m_route.result.curves)
|
||||
WriteLog(format("SELFTEST route curve %d: R %.1f, L %.1f/%.1f, from R %.1f, into R %.1f", curve.vertex, curve.radius, curve.transition_in, curve.transition_out, curve.curvature_in > 0.0 ? 1.0 / curve.curvature_in : 0.0,
|
||||
curve.curvature_out > 0.0 ? 1.0 / curve.curvature_out : 0.0));
|
||||
for (auto const &warning : m_route.result.warnings)
|
||||
WriteLog("SELFTEST route warning: " + warning);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "routevertex" || command == "routearc" || command == "routeshare")
|
||||
|
||||
@@ -1909,6 +1909,11 @@ std::string editor_mode::track_readout() const
|
||||
text += " " + curve_label(curve, laying_norms());
|
||||
if (false == alignment::meets_norms(curve, plan.design))
|
||||
text += "\n" + STR("this curve doesn't meet the norms, it's laid all the same");
|
||||
// the curves next to the free ends going on from the curves of their tracks: the first and the last one
|
||||
if (auto const on{curve_goes_on(plan.shape.curves.front())}; false == on.empty())
|
||||
text += "\n" + on;
|
||||
if (auto const on{curve_goes_on(curve)}; plan.shape.curves.size() > 1 && false == on.empty())
|
||||
text += "\n" + on;
|
||||
}
|
||||
if (lay.mode == lay_mode::curve && false == plan.traces.empty() && false == plan.traces.back().offsets.empty())
|
||||
{
|
||||
|
||||
@@ -102,6 +102,22 @@ inline std::string curve_label(alignment::curve_report const &Curve, alignment::
|
||||
return text + speed_of(curve_sharpest(Curve), Limits);
|
||||
}
|
||||
|
||||
// how a curve laid goes on from the curve of the track it starts at, or into the one of the track it ends at; empty from a straight
|
||||
inline std::string curve_goes_on(alignment::curve_report const &Curve)
|
||||
{
|
||||
std::vector<std::string> lines;
|
||||
if (Curve.curvature_in > 0.0)
|
||||
lines.push_back(Curve.transition_in < 1.0 ? format(STR_C("goes on from R %.0f m of the track it starts at"), 1.0 / Curve.curvature_in) :
|
||||
format(STR_C("goes on from R %.0f m of the track it starts at, a transition of %.0f m between them"), 1.0 / Curve.curvature_in, Curve.transition_in));
|
||||
if (Curve.curvature_out > 0.0)
|
||||
lines.push_back(Curve.transition_out < 1.0 ? format(STR_C("goes into R %.0f m of the track it ends at"), 1.0 / Curve.curvature_out) :
|
||||
format(STR_C("goes into R %.0f m of the track it ends at, a transition of %.0f m between them"), 1.0 / Curve.curvature_out, Curve.transition_out));
|
||||
std::string text;
|
||||
for (auto const &line : lines)
|
||||
text += (text.empty() ? "" : "\n") + line;
|
||||
return text;
|
||||
}
|
||||
|
||||
// the transition curves and the cant the norms put in a layout, a compound curve where it goes on from an arc turning the same way
|
||||
inline std::string eased_of(std::optional<alignment::lay_result> const &Eased)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user