mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-31 05:20:52 +01:00
feat(editor): each arc of a compound curve has a cant of its own, set in the Plan by each radius ("Cant of R1 (mm)"...), ramped along the transition curves between the arcs; until it's set, the cant of the curve in proportion to the curvature of the arc, as before, "auto" taking it back to that and "Apply recommended for V" all of them. The transitions between the arcs of the norms follow the cants set; warnings where the cant of an arc exceeds the norms, where the unbalanced acceleration does on any arc, and where a transition curve, at the ends or between the arcs, is shorter than the change of the curvature and of the cant along it needs. The labels of the curves show the cants of the arcs where they differ. Stored with the vertex as "cants", reversed with the curve; a unit test
This commit is contained in:
@@ -106,12 +106,14 @@ local_point spiral_point(double const Start, double const End, double const Leng
|
||||
|
||||
struct compound_curve
|
||||
{
|
||||
std::vector<std::array<double, 3>> segments;
|
||||
// the curvature at the start and at the end, the length, the cant at the start and at the end
|
||||
std::vector<std::array<double, 5>> segments;
|
||||
local_point end;
|
||||
double shortened{1.0};
|
||||
};
|
||||
|
||||
compound_curve build_compound(double const Deflection, std::vector<compound_arc> const &Arcs, double const Lengthin, double const Lengthout)
|
||||
// Cants: of the arcs, the cants of the segments stay 0 without them
|
||||
compound_curve build_compound(double const Deflection, std::vector<compound_arc> const &Arcs, double const Lengthin, double const Lengthout, std::vector<double> const &Cants = {})
|
||||
{
|
||||
compound_curve result;
|
||||
if (Arcs.empty())
|
||||
@@ -119,6 +121,7 @@ compound_curve build_compound(double const Deflection, std::vector<compound_arc>
|
||||
std::vector<double> curvatures;
|
||||
for (auto const &arc : Arcs)
|
||||
curvatures.push_back(1.0 / std::max(1.0, arc.radius));
|
||||
auto const cant = [&](std::size_t const Arc) { return Arc < Cants.size() ? Cants[Arc] : 0.0; };
|
||||
auto transitions{curvatures.front() * Lengthin + curvatures.back() * Lengthout};
|
||||
for (std::size_t i = 1; i < Arcs.size(); ++i)
|
||||
transitions += (curvatures[i - 1] + curvatures[i]) * std::max(0.0, Arcs[i].transition);
|
||||
@@ -130,15 +133,15 @@ compound_curve build_compound(double const Deflection, std::vector<compound_arc>
|
||||
double total{0.0};
|
||||
for (auto const &arc : Arcs)
|
||||
total += std::max(0.0, arc.share);
|
||||
std::vector<std::array<double, 3>> segments{{0.0, curvatures.front(), Lengthin * f}};
|
||||
std::vector<std::array<double, 5>> segments{{0.0, curvatures.front(), Lengthin * f, 0.0, cant(0)}};
|
||||
for (std::size_t i = 0; i < Arcs.size(); ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
segments.push_back({curvatures[i - 1], curvatures[i], std::max(0.0, Arcs[i].transition) * f});
|
||||
segments.push_back({curvatures[i - 1], curvatures[i], std::max(0.0, Arcs[i].transition) * f, cant(i - 1), cant(i)});
|
||||
auto const share{total > 0.0 ? std::max(0.0, Arcs[i].share) / total : 1.0 / static_cast<double>(Arcs.size())};
|
||||
segments.push_back({curvatures[i], curvatures[i], share * arcs / curvatures[i]});
|
||||
segments.push_back({curvatures[i], curvatures[i], share * arcs / curvatures[i], cant(i), cant(i)});
|
||||
}
|
||||
segments.push_back({curvatures.back(), 0.0, Lengthout * f});
|
||||
segments.push_back({curvatures.back(), 0.0, Lengthout * f, cant(Arcs.size() - 1), 0.0});
|
||||
double x{0.0};
|
||||
double y{0.0};
|
||||
double heading{0.0};
|
||||
@@ -156,6 +159,18 @@ compound_curve build_compound(double const Deflection, std::vector<compound_arc>
|
||||
return result;
|
||||
}
|
||||
|
||||
// the cants of the arcs, each its own or Cant at the sharpest of them in proportion to the curvature
|
||||
std::vector<double> arc_cants(std::vector<compound_arc> const &Arcs, double const Cant)
|
||||
{
|
||||
auto sharpest{std::numeric_limits<double>::max()};
|
||||
for (auto const &arc : Arcs)
|
||||
sharpest = std::min(sharpest, std::max(1.0, arc.radius));
|
||||
std::vector<double> cants;
|
||||
for (auto const &arc : Arcs)
|
||||
cants.push_back(arc.cant >= 0.0 ? arc.cant : std::max(0.0, Cant) * sharpest / std::max(1.0, arc.radius));
|
||||
return cants;
|
||||
}
|
||||
|
||||
void element_point(element const &Element, transition_shape const Shape, double const Distance, glm::dvec2 &Position, glm::dvec2 &Direction)
|
||||
{
|
||||
switch (Element.kind)
|
||||
@@ -202,11 +217,7 @@ double element_cant(element const &Element, double const Distance)
|
||||
case element_kind::transition_in: return Element.cant * Distance / Element.transition;
|
||||
case element_kind::transition_out: return Element.cant * (1.0 - Distance / Element.transition);
|
||||
case element_kind::arc: return Element.cant;
|
||||
case element_kind::spiral:
|
||||
{
|
||||
auto const curvature{Element.curvature_start + (Element.curvature_end - Element.curvature_start) * Distance / std::max(Element.length, 1e-9)};
|
||||
return Element.curvature_peak > 0.0 ? Element.cant * curvature / Element.curvature_peak : 0.0;
|
||||
}
|
||||
case element_kind::spiral: return Element.cant_start + (Element.cant_end - Element.cant_start) * std::clamp(Distance / std::max(Element.length, 1e-9), 0.0, 1.0);
|
||||
default: return 0.0;
|
||||
}
|
||||
}
|
||||
@@ -261,6 +272,15 @@ double unbalanced_acceleration(double const Speed, double const Radius, double c
|
||||
return Speed * Speed / (12.96 * Radius) - kGravity * Cant / Limits.gauge;
|
||||
}
|
||||
|
||||
std::vector<double> compound_cants(vertex const &Vertex)
|
||||
{
|
||||
if (false == Vertex.compound || Vertex.arcs.empty())
|
||||
return {std::max(0.0, Vertex.cant)};
|
||||
std::vector<compound_arc> arcs{{Vertex.radius, 0.0, Vertex.share, Vertex.first_cant}};
|
||||
arcs.insert(arcs.end(), Vertex.arcs.begin(), Vertex.arcs.end());
|
||||
return arc_cants(arcs, Vertex.cant);
|
||||
}
|
||||
|
||||
double transition_length(double const Speed, double const Radius, double const Cant, limits const &Limits)
|
||||
{
|
||||
double length{0.0};
|
||||
@@ -346,6 +366,10 @@ bool meets_norms(curve_report const &Curve, design const &Design)
|
||||
return false;
|
||||
if (false == Curve.spirals && sharpest < Curve.recommended.radius_min - 1e-6)
|
||||
return false;
|
||||
if (Curve.radii.size() > 1 && Curve.cants.size() == Curve.radii.size())
|
||||
// a compound curve ramps the cant of its first arc in and the one of its last arc out
|
||||
return Curve.transition_in + 1e-6 >= transition_length(Design.speed, Curve.radii.front(), Curve.cants.front(), norms) &&
|
||||
Curve.transition_out + 1e-6 >= transition_length(Design.speed, Curve.radii.back(), Curve.cants.back(), norms);
|
||||
auto const required{transition_length(Design.speed, sharpest, Curve.cant, norms)};
|
||||
return Curve.transition_in + 1e-6 >= required && Curve.transition_out + 1e-6 >= required;
|
||||
}
|
||||
@@ -494,9 +518,9 @@ result fit_between(design const &Design)
|
||||
if (vertex.compound && false == vertex.arcs.empty())
|
||||
{
|
||||
fit.compound = true;
|
||||
fit.arcs = {{fit.radius, 0.0, vertex.share}};
|
||||
fit.arcs = {{fit.radius, 0.0, vertex.share, vertex.first_cant}};
|
||||
for (auto const &arc : vertex.arcs)
|
||||
fit.arcs.push_back({std::max(1.0, arc.radius), std::max(0.0, arc.transition), arc.share});
|
||||
fit.arcs.push_back({std::max(1.0, arc.radius), std::max(0.0, arc.transition), arc.share, arc.cant});
|
||||
fit.deflection = deflection;
|
||||
fit.tangents = [&, k, directionin, directionout, side, scaled](double const Radius, double &Tangentin, double &Tangentout) {
|
||||
auto const &own{fits[k]};
|
||||
@@ -674,7 +698,8 @@ result fit_between(design const &Design)
|
||||
}
|
||||
if (fit.compound)
|
||||
{
|
||||
auto const shape{build_compound(deflection, fit.arcs, fit.transition_in, fit.transition_out)};
|
||||
auto const cants{arc_cants(fit.arcs, fit.cant)};
|
||||
auto const shape{build_compound(deflection, fit.arcs, fit.transition_in, fit.transition_out, cants)};
|
||||
if (shape.shortened < 1.0)
|
||||
r.warnings.emplace_back(format(STR_C("Vertex %zu: transition curves shortened to fit the deflection angle"), k + 1));
|
||||
auto const normalin{perpendicular(directionin) * static_cast<double>(side)};
|
||||
@@ -693,17 +718,17 @@ result fit_between(design const &Design)
|
||||
auto sharpest{fit.radius};
|
||||
for (auto const &arc : fit.arcs)
|
||||
sharpest = std::min(sharpest, arc.radius);
|
||||
auto const peak{1.0 / sharpest};
|
||||
for (auto const &segment : shape.segments)
|
||||
{
|
||||
element piece;
|
||||
piece.kind = element_kind::spiral;
|
||||
piece.vertex = static_cast<int>(k);
|
||||
piece.turn = side;
|
||||
piece.cant = std::max(0.0, vertex.cant);
|
||||
piece.cant = std::max(segment[3], segment[4]);
|
||||
piece.cant_start = segment[3];
|
||||
piece.cant_end = segment[4];
|
||||
piece.curvature_start = segment[0];
|
||||
piece.curvature_end = segment[1];
|
||||
piece.curvature_peak = peak;
|
||||
piece.radius = std::max(segment[0], segment[1]) > 0.0 ? 1.0 / std::max(segment[0], segment[1]) : 0.0;
|
||||
piece.length = segment[2];
|
||||
piece.origin = origin;
|
||||
@@ -728,14 +753,37 @@ result fit_between(design const &Design)
|
||||
report.arc_length = chainage - startchainage;
|
||||
report.tangent_in = glm::distance(position, curvestart);
|
||||
report.tangent_out = glm::distance(position, cursor);
|
||||
report.unbalanced = unbalanced_acceleration(Design.speed, sharpest, std::max(0.0, vertex.cant), Design.norms);
|
||||
report.cant = std::max(0.0, vertex.cant);
|
||||
report.cants = cants;
|
||||
report.unbalanced = -std::numeric_limits<double>::max();
|
||||
for (std::size_t i = 0; i < cants.size(); ++i)
|
||||
{
|
||||
report.unbalanced = std::max(report.unbalanced, unbalanced_acceleration(Design.speed, fit.arcs[i].radius, cants[i], Design.norms));
|
||||
report.cant = std::max(report.cant, cants[i]);
|
||||
}
|
||||
report.recommended = recommend(Design.speed, sharpest, Design.norms);
|
||||
r.curves.push_back(report);
|
||||
if (Design.norms.checked && sharpest < report.recommended.radius_min - 1e-6)
|
||||
auto const &norms{Design.norms};
|
||||
if (false == norms.checked)
|
||||
continue;
|
||||
if (sharpest < report.recommended.radius_min - 1e-6)
|
||||
r.warnings.emplace_back(format(STR_C("Vertex %zu: R %.0f m is below the minimum %.0f m for %.0f km/h"), k + 1, sharpest, report.recommended.radius_min, Design.speed));
|
||||
if (Design.norms.checked && report.unbalanced > Design.norms.unbalanced + 1e-6)
|
||||
r.warnings.emplace_back(format(STR_C("Vertex %zu: unbalanced acceleration %.2f m/s2 exceeds %.2f m/s2"), k + 1, report.unbalanced, Design.norms.unbalanced));
|
||||
if (report.unbalanced > norms.unbalanced + 1e-6)
|
||||
r.warnings.emplace_back(format(STR_C("Vertex %zu: unbalanced acceleration %.2f m/s2 exceeds %.2f m/s2"), k + 1, report.unbalanced, norms.unbalanced));
|
||||
if (report.cant > norms.cant_max + 1e-6)
|
||||
r.warnings.emplace_back(format(STR_C("Vertex %zu: cant %.0f mm exceeds %.0f mm"), k + 1, report.cant, norms.cant_max));
|
||||
// each transition long enough for the change of the curvature and of the cant along it, at the speed the sharpest arc
|
||||
// allows, as the transitions of the norms are set between the arcs
|
||||
auto const allowed{std::max(1.0, std::min(Design.speed, speed_limit(sharpest, norms)))};
|
||||
auto const requiredin{transition_length(allowed, fit.arcs.front().radius, cants.front(), norms)};
|
||||
auto const requiredout{transition_length(allowed, fit.arcs.back().radius, cants.back(), norms)};
|
||||
if (report.transition_in + 1e-6 < requiredin || report.transition_out + 1e-6 < requiredout)
|
||||
r.warnings.emplace_back(format(STR_C("Vertex %zu: transition curves shorter than the required %.0f m"), k + 1, std::max(requiredin, requiredout)));
|
||||
for (std::size_t i = 1; i < fit.arcs.size(); ++i)
|
||||
{
|
||||
auto const required{transition_between(allowed, 1.0 / fit.arcs[i - 1].radius, cants[i - 1], 1.0 / fit.arcs[i].radius, cants[i], norms)};
|
||||
if (fit.arcs[i].transition * shape.shortened + 1e-6 < required)
|
||||
r.warnings.emplace_back(format(STR_C("Vertex %zu: the transition curve between R %.0f and R %.0f m shorter than the required %.0f m"), k + 1, fit.arcs[i - 1].radius, fit.arcs[i].radius, required));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto const radius{fit.radius};
|
||||
@@ -1276,8 +1324,8 @@ void chain_spiral(element_chain &Chain, double const From, double const To, doub
|
||||
spiral.turn = turn;
|
||||
spiral.curvature_start = From * turn;
|
||||
spiral.curvature_end = To * turn;
|
||||
spiral.curvature_peak = std::max(std::abs(From), std::abs(To));
|
||||
spiral.radius = spiral.curvature_peak > 1e-12 ? 1.0 / spiral.curvature_peak : 0.0;
|
||||
auto const peak{std::max(std::abs(From), std::abs(To))};
|
||||
spiral.radius = peak > 1e-12 ? 1.0 / peak : 0.0;
|
||||
spiral.length = Length;
|
||||
spiral.origin = Chain.position;
|
||||
spiral.direction = Chain.direction;
|
||||
@@ -1641,12 +1689,13 @@ void recommend_between(vertex &Vertex, double const Speed, limits const &Limits)
|
||||
for (auto const &arc : Vertex.arcs)
|
||||
sharpest = std::min(sharpest, std::max(1.0, arc.radius));
|
||||
auto const speed{std::max(1.0, std::min(Speed, speed_limit(sharpest, Limits)))};
|
||||
auto const cant_of = [&](double const Radius) { return Vertex.cant * sharpest / Radius; };
|
||||
auto const cants{compound_cants(Vertex)};
|
||||
auto previous{std::max(1.0, Vertex.radius)};
|
||||
for (auto &arc : Vertex.arcs)
|
||||
for (std::size_t i = 0; i < Vertex.arcs.size(); ++i)
|
||||
{
|
||||
auto &arc{Vertex.arcs[i]};
|
||||
auto const radius{std::max(1.0, arc.radius)};
|
||||
arc.transition = transition_between(speed, 1.0 / previous, cant_of(previous), 1.0 / radius, cant_of(radius), Limits);
|
||||
arc.transition = transition_between(speed, 1.0 / previous, cants[i], 1.0 / radius, cants[i + 1], Limits);
|
||||
previous = radius;
|
||||
}
|
||||
}
|
||||
@@ -2019,6 +2068,7 @@ vertex traced_shape(design const &Design, vertex const &Base, std::vector<double
|
||||
compound_arc arc;
|
||||
arc.radius = radius(i);
|
||||
arc.transition = i - 1 < static_cast<int>(Base.arcs.size()) ? Base.arcs[i - 1].transition : 0.0;
|
||||
arc.cant = i - 1 < static_cast<int>(Base.arcs.size()) ? Base.arcs[i - 1].cant : -1.0;
|
||||
arc.share = weights[i] / total;
|
||||
shape.arcs.push_back(arc);
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ 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
|
||||
@@ -67,6 +69,8 @@ struct vertex
|
||||
bool compound{false};
|
||||
double share{1.0};
|
||||
std::vector<compound_arc> 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};
|
||||
@@ -107,6 +111,8 @@ double transition_length(double const Speed, double const Radius, double const C
|
||||
// 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<double> compound_cants(vertex const &Vertex);
|
||||
|
||||
enum class element_kind
|
||||
{
|
||||
@@ -129,7 +135,9 @@ struct element
|
||||
double cant{0.0};
|
||||
double curvature_start{0.0};
|
||||
double curvature_end{0.0};
|
||||
double curvature_peak{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};
|
||||
@@ -140,6 +148,7 @@ struct curve_report
|
||||
int vertex{-1};
|
||||
double radius{0.0};
|
||||
std::vector<double> radii;
|
||||
std::vector<double> cants; // of the radii of a compound curve
|
||||
double transition_in{0.0};
|
||||
double transition_out{0.0};
|
||||
double deflection{0.0};
|
||||
|
||||
@@ -47,6 +47,7 @@ bool g_loaded{false};
|
||||
// pieces <pieces of a transition> <angle of an arc piece>
|
||||
// vertex <x> <z> <offset> <R> <L in> <L out> <cant> <reverse 0/1> <compound 0/1> <share> <arcs> [<R> <L> <share>]... [spirals 1]
|
||||
// spirals: two transition curves with no arc between them, their lengths following from R
|
||||
// [cants <first> <arc>...]: the cants of the radii of a compound curve set one by one, below 0 the cant in proportion to the curvature
|
||||
// element <straight|transition|arc> <vertex> <chainage> <length> <R at the start> <R at the end> <cant> <start x> <start z> <direction x> <direction z>
|
||||
// radii signed with the turn, 0 for no curvature
|
||||
// end
|
||||
@@ -139,6 +140,12 @@ void mark(scene::layer_handle const Layer)
|
||||
text += format(" %.3f %.3f %.9f", arc.radius, arc.transition, arc.share);
|
||||
if (vertex.spirals)
|
||||
text += " spirals 1";
|
||||
if (vertex.first_cant >= 0.0 || std::any_of(vertex.arcs.begin(), vertex.arcs.end(), [](alignment::compound_arc const &Arc) { return Arc.cant >= 0.0; }))
|
||||
{
|
||||
text += format(" cants %.1f", vertex.first_cant);
|
||||
for (auto const &arc : vertex.arcs)
|
||||
text += format(" %.1f", arc.cant);
|
||||
}
|
||||
lines.push_back(text);
|
||||
}
|
||||
for (auto const &e : entry.result.elements)
|
||||
@@ -165,15 +172,17 @@ alignment::vertex reversed_vertex(alignment::vertex Vertex)
|
||||
double radius;
|
||||
double share;
|
||||
double transition;
|
||||
double cant;
|
||||
};
|
||||
std::vector<part> parts{{Vertex.radius, Vertex.share, 0.0}};
|
||||
std::vector<part> parts{{Vertex.radius, Vertex.share, 0.0, Vertex.first_cant}};
|
||||
for (auto const &arc : Vertex.arcs)
|
||||
parts.push_back({arc.radius, arc.share, arc.transition});
|
||||
parts.push_back({arc.radius, arc.share, arc.transition, arc.cant});
|
||||
auto const count{parts.size()};
|
||||
Vertex.radius = parts.back().radius;
|
||||
Vertex.share = parts.back().share;
|
||||
Vertex.first_cant = parts.back().cant;
|
||||
for (std::size_t j = 1; j < count; ++j)
|
||||
Vertex.arcs[j - 1] = {parts[count - 1 - j].radius, parts[count - j].transition, parts[count - 1 - j].share};
|
||||
Vertex.arcs[j - 1] = {parts[count - 1 - j].radius, parts[count - j].transition, parts[count - 1 - j].share, parts[count - 1 - j].cant};
|
||||
}
|
||||
return Vertex;
|
||||
}
|
||||
@@ -312,8 +321,17 @@ std::optional<alignment::vertex> read_vertex(std::istringstream &Line, scene::la
|
||||
return std::nullopt;
|
||||
// written since the bends of the straights are eased with two transition curves, the lines of before end with the arcs
|
||||
for (std::string word; Line >> word;)
|
||||
{
|
||||
if (int value{0}; word == "spirals" && Line >> value)
|
||||
vertex.spirals = (value != 0);
|
||||
// the cants of the arcs of a compound curve, set one by one: of the first arc and of the ones after it, below 0 in proportion
|
||||
if (word == "cants")
|
||||
{
|
||||
Line >> vertex.first_cant;
|
||||
for (auto &arc : vertex.arcs)
|
||||
Line >> arc.cant;
|
||||
}
|
||||
}
|
||||
position = Context.to_world(position);
|
||||
vertex.position = {position.x, position.z};
|
||||
vertex.reverse_turn = (reverse != 0);
|
||||
|
||||
Reference in New Issue
Block a user