mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-26 20:34:54 +01:00
feat(editor): laid to the norms, a track laid on from an end gets its transition curves, into the arc, between the arcs of a compound curve and out to the straight
A track laid on from a free end in the PKP PLK laying, by a drag out of the end, by clicks from it or laid back onto it, starts from the curvature and the cant the end has. An arc turning the same way as the end is a compound curve: a transition of the norms ramps the curvature and the cant from the one arc into the other. From a straight the arc gets its entry transition, the arc then straight its exit one. Where the way is too short the transitions are cut and the cant lowered to what they ramp, and the readout says so; with no room at all the track is laid as before and the note says why. The curves traced on the orthophoto and the recommended values of a compound curve get the transitions between their arcs too. The free laying stays as it was.
This commit is contained in:
@@ -1482,6 +1482,10 @@ class editor_mode : public application_mode, private editor_track::observer, pri
|
||||
TTrack const *m_join_track{nullptr};
|
||||
editor_track::point_ref m_join_point;
|
||||
mutable alignment::join_result m_join_last;
|
||||
// the layout laid on from the end of From with the transition curves of the norms; none in the free laying, or where it doesn't fit
|
||||
std::optional<alignment::lay_result> eased_layout(TTrack const *From, glm::dvec3 const &Point, glm::dvec2 const &Direction, glm::dvec3 const &Target, double const Radius) const;
|
||||
mutable std::optional<alignment::lay_result> m_eased_last;
|
||||
mutable std::optional<std::pair<alignment::lay_request, std::optional<alignment::lay_result>>> m_eased_cache;
|
||||
bool track_wheel(double const Offset);
|
||||
// a touchpad sends fractions of a notch: a choice moves a step for each whole notch gathered, and only one in a gesture when
|
||||
// Gesture; a pause, another key or the other way starts over
|
||||
|
||||
@@ -408,8 +408,40 @@ void editor_mode::point_drag_finish()
|
||||
m_track_joints.clear();
|
||||
}
|
||||
|
||||
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 (false == m_lay.transitions || From == nullptr || From->eType != tt_Normal)
|
||||
return std::nullopt;
|
||||
auto const end{editor_track::bend_at(*From, Point)};
|
||||
if (false == end.has_value())
|
||||
return std::nullopt;
|
||||
alignment::lay_request request;
|
||||
request.start.position = Point;
|
||||
request.start.direction = Direction;
|
||||
request.start.roll = end->roll;
|
||||
request.curvature = end->curvature;
|
||||
request.target = Target;
|
||||
switch (m_track_layout)
|
||||
{
|
||||
case track_layout::straight: request.kind = alignment::lay_kind::straight; break;
|
||||
case track_layout::arc: request.kind = alignment::lay_kind::arc; break;
|
||||
case track_layout::straight_arc: request.kind = alignment::lay_kind::straight_arc; break;
|
||||
default: request.kind = alignment::lay_kind::arc_straight; break;
|
||||
}
|
||||
request.radius = Radius;
|
||||
request.speed = design_speed(From, nullptr);
|
||||
request.piece_length = m_lay.piece_length;
|
||||
request.norms = laying_norms();
|
||||
if (false == m_eased_cache.has_value() || m_eased_cache->first != request)
|
||||
m_eased_cache.emplace(request, alignment::lay_on(request));
|
||||
m_eased_last = m_eased_cache->second;
|
||||
return m_eased_last;
|
||||
}
|
||||
|
||||
std::vector<segment_data> editor_mode::extend_pieces() const
|
||||
{
|
||||
m_eased_last.reset();
|
||||
std::vector<segment_data> result;
|
||||
auto const &tool{m_extend};
|
||||
editor_track::snap_target target;
|
||||
@@ -452,7 +484,8 @@ std::vector<segment_data> editor_mode::extend_pieces() const
|
||||
auto radius{m_lay.radius};
|
||||
if (auto const values{typed_values()}; false == values.empty() && (m_track_layout == track_layout::arc_straight || m_track_layout == track_layout::straight_arc) && std::abs(values[0]) >= 1.0)
|
||||
radius = std::abs(values[0]);
|
||||
auto pieces{editor_track::layout_pieces(m_track_layout, tool.point, tool.direction, tool.grade, tool.mouse, radius)};
|
||||
auto const eased{eased_layout(tool.track, tool.point, tool.direction, tool.mouse, radius)};
|
||||
auto pieces{eased.has_value() && false == eased->pieces.empty() ? eased->pieces : editor_track::layout_pieces(m_track_layout, tool.point, tool.direction, tool.grade, tool.mouse, radius)};
|
||||
follow_ground(pieces, tool.point.y, tool.grade, tool.mouse.y, std::numeric_limits<double>::quiet_NaN(), tool.track);
|
||||
return pieces;
|
||||
}
|
||||
@@ -807,6 +840,7 @@ std::vector<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> const
|
||||
{
|
||||
auto const &tool{m_lay};
|
||||
Length = 0.0;
|
||||
m_eased_last.reset();
|
||||
if (Points.size() < 2)
|
||||
return {};
|
||||
if (Points.size() == 2)
|
||||
@@ -824,8 +858,10 @@ std::vector<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> const
|
||||
if (tool.start.track != nullptr)
|
||||
{
|
||||
auto const grade{-grade_of(tool.start.direction)};
|
||||
pieces = editor_track::layout_pieces(m_track_layout, Points[0], glm::normalize(leaving), grade, Points[1], tool.radius);
|
||||
Error = narrowed(pieces, m_track_layout, tool.radius);
|
||||
auto const eased{eased_layout(tool.start.track, Points[0], glm::normalize(leaving), Points[1], tool.radius)};
|
||||
auto const laid{eased.has_value() && false == eased->pieces.empty()};
|
||||
pieces = laid ? eased->pieces : editor_track::layout_pieces(m_track_layout, Points[0], glm::normalize(leaving), grade, Points[1], tool.radius);
|
||||
Error = laid ? narrowed(m_track_layout, tool.radius, eased->radius) : narrowed(pieces, m_track_layout, tool.radius);
|
||||
follow_ground(pieces, Points[0].y, grade, Points[1].y, std::numeric_limits<double>::quiet_NaN(), tool.start.track);
|
||||
}
|
||||
else if (End.track == nullptr)
|
||||
@@ -836,10 +872,12 @@ std::vector<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> const
|
||||
}
|
||||
else if (glm::length(plan_of(End.direction)) > 1e-6)
|
||||
{
|
||||
auto const back{editor_track::layout_pieces(m_track_layout, End.position, glm::normalize(-plan_of(End.direction)), -grade_of(End.direction), Points[0], tool.radius)};
|
||||
auto const eased{eased_layout(End.track, End.position, glm::normalize(-plan_of(End.direction)), Points[0], tool.radius)};
|
||||
auto const laid{eased.has_value() && false == eased->pieces.empty()};
|
||||
auto const back{laid ? eased->pieces : editor_track::layout_pieces(m_track_layout, End.position, glm::normalize(-plan_of(End.direction)), -grade_of(End.direction), Points[0], tool.radius)};
|
||||
for (auto piece{back.rbegin()}; piece != back.rend(); ++piece)
|
||||
pieces.push_back(reversed(*piece));
|
||||
Error = narrowed(pieces, m_track_layout, tool.radius);
|
||||
Error = laid ? narrowed(m_track_layout, tool.radius, eased->radius) : narrowed(pieces, m_track_layout, tool.radius);
|
||||
follow_ground(pieces, Points[0].y, std::numeric_limits<double>::quiet_NaN(), End.position.y, grade_of(End.direction), End.track);
|
||||
}
|
||||
for (auto const &piece : pieces)
|
||||
@@ -1020,6 +1058,7 @@ void editor_mode::lay_finish(editor_track::snap_target const &End)
|
||||
double length;
|
||||
std::string error;
|
||||
auto const pieces{lay_pieces(tool.points, End, length, error)};
|
||||
auto const eased{m_eased_last};
|
||||
tool.status_time = ImGui::GetTime();
|
||||
if (pieces.empty())
|
||||
{
|
||||
@@ -1043,6 +1082,10 @@ void editor_mode::lay_finish(editor_track::snap_target const &End)
|
||||
tool.status = format(STR_P("Laid %zu track, %.1f m, Ctrl+Z takes it back", "Laid %zu tracks, %.1f m, Ctrl+Z takes them back", static_cast<long long>(created.size())), created.size(), length);
|
||||
if (false == error.empty())
|
||||
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);
|
||||
if (eased.has_value() && false == eased->note.empty())
|
||||
tool.status += "\n" + eased->note;
|
||||
{
|
||||
std::vector<std::pair<TTrack *, bool>> run;
|
||||
if (tool.start.track != nullptr && tool.start.track->eType == tt_Normal)
|
||||
|
||||
@@ -52,10 +52,15 @@ bool editor_mode::route_active() const
|
||||
|
||||
void editor_mode::route_recommend(alignment::vertex &Vertex) const
|
||||
{
|
||||
auto const recommended{alignment::recommend(m_route.design.speed, Vertex.radius, laying_norms())};
|
||||
auto sharpest{Vertex.radius};
|
||||
if (Vertex.compound)
|
||||
for (auto const &arc : Vertex.arcs)
|
||||
sharpest = std::min(sharpest, arc.radius);
|
||||
auto const recommended{alignment::recommend(m_route.design.speed, sharpest, laying_norms())};
|
||||
Vertex.cant = recommended.cant;
|
||||
Vertex.transition_in = recommended.transition;
|
||||
Vertex.transition_out = recommended.transition;
|
||||
alignment::recommend_between(Vertex, m_route.design.speed, laying_norms());
|
||||
}
|
||||
|
||||
void editor_mode::route_bind_ends()
|
||||
|
||||
@@ -1782,6 +1782,8 @@ std::string editor_mode::track_readout() const
|
||||
if (piece.radius != 0.0f && (radius == 0.0 || std::abs(piece.radius) < radius))
|
||||
radius = std::abs(piece.radius);
|
||||
}
|
||||
if (m_eased_last.has_value() && false == m_eased_last->pieces.empty())
|
||||
radius = m_eased_last->radius;
|
||||
editor_track::snap_target target;
|
||||
auto const snapped{extend_snap(target) && target.track != nullptr};
|
||||
editor_track::branch_link link;
|
||||
@@ -1791,6 +1793,8 @@ std::string editor_mode::track_readout() const
|
||||
if (snapped && pieces.empty())
|
||||
return format(STR_C("no track fits to the end of %s"), name(target.track).c_str());
|
||||
auto text{radius > 0.0 ? format("L %.2f m R %.0f m", length, radius) + speed_of(radius, laying_norms()) : format("L %.2f m %s", length, STR_C("straight"))};
|
||||
if (auto const eased{eased_of(m_eased_last)}; false == eased.empty())
|
||||
text += "\n" + eased;
|
||||
if (auto const vertical{vertical_of(pieces)}; false == vertical.empty())
|
||||
text += "\n" + vertical;
|
||||
if (snapped)
|
||||
@@ -1817,10 +1821,14 @@ std::string editor_mode::track_readout() const
|
||||
else if (radius == 0.0 || r < radius)
|
||||
radius = r;
|
||||
}
|
||||
if (m_eased_last.has_value() && false == m_eased_last->pieces.empty())
|
||||
radius = m_eased_last->radius;
|
||||
if (radius > 0.0)
|
||||
text = (straight ? format("L %.1f m %s + R %.0f m", lay.preview_length, STR_C("straight"), radius) : format("L %.1f m R %.0f m", lay.preview_length, radius)) + speed_of(radius, laying_norms());
|
||||
else
|
||||
text = format("L %.1f m %s", lay.preview_length, STR_C("straight"));
|
||||
if (auto const eased{eased_of(m_eased_last)}; false == eased.empty())
|
||||
text += "\n" + eased;
|
||||
if (auto const vertical{vertical_of(lay.preview)}; false == vertical.empty())
|
||||
text += "\n" + vertical;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,21 @@ inline std::string speed_of(double const Radius, alignment::limits const &Limits
|
||||
return speed > 0.0 ? format(STR_C(" (up to %.0f km/h)"), speed) : std::string{};
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (false == Eased.has_value())
|
||||
return {};
|
||||
if (Eased->pieces.empty() || (Eased->entry <= 0.0 && Eased->exit <= 0.0))
|
||||
return Eased->note;
|
||||
auto text{Eased->compound ? format(STR_C("compound curve on from R %.0f m, a transition of %.0f m between the arcs"), Eased->from_radius, Eased->entry) : format(STR_C("transition curves %.0f / %.0f m"), Eased->entry, Eased->exit)};
|
||||
if (Eased->cant > 0.0)
|
||||
text += format(STR_C(" cant %.0f mm"), Eased->cant);
|
||||
if (false == Eased->note.empty())
|
||||
text += "\n" + Eased->note;
|
||||
return text;
|
||||
}
|
||||
|
||||
// the rise of the track from its start to its end, and the grades along it
|
||||
inline std::string vertical_of(std::vector<segment_data> const &Pieces)
|
||||
{
|
||||
@@ -106,17 +121,20 @@ inline std::string lines_of(std::vector<std::string> const &Texts)
|
||||
return text;
|
||||
}
|
||||
|
||||
inline std::string narrowed(editor_track::layout const Layout, double const Radius, double const Arc)
|
||||
{
|
||||
if ((Layout != editor_track::layout::arc_straight && Layout != editor_track::layout::straight_arc) || Arc == 0.0 || std::abs(Arc - Radius) < Radius * 0.01)
|
||||
return {};
|
||||
return format(STR_C("R %.0f m doesn't reach the cursor, the arc goes through it with R %.0f m"), Radius, Arc);
|
||||
}
|
||||
|
||||
inline std::string narrowed(std::vector<segment_data> const &Pieces, editor_track::layout const Layout, double const Radius)
|
||||
{
|
||||
if (Layout != editor_track::layout::arc_straight && Layout != editor_track::layout::straight_arc)
|
||||
return {};
|
||||
double sharpest{0.0};
|
||||
for (auto const &piece : Pieces)
|
||||
if (auto const radius{piece_plan_radius(piece)}; radius >= 0.5 && (sharpest == 0.0 || radius < sharpest))
|
||||
sharpest = radius;
|
||||
if (sharpest == 0.0 || sharpest >= Radius * 0.99)
|
||||
return {};
|
||||
return format(STR_C("R %.0f m doesn't reach the cursor, the arc goes through it with R %.0f m"), Radius, sharpest);
|
||||
return narrowed(Layout, Radius, sharpest > Radius ? 0.0 : sharpest);
|
||||
}
|
||||
|
||||
inline std::string describe(editor_track::straight const &Line)
|
||||
|
||||
@@ -265,6 +265,20 @@ double transition_length(double const Speed, double const Radius, double const C
|
||||
return length > 0.0 ? ceil_to(length, 5.0) : 0.0;
|
||||
}
|
||||
|
||||
double transition_between(double const Speed, double const Curvature0, double const Cant0, double const Curvature1, double const Cant1, limits const &Limits)
|
||||
{
|
||||
auto const cant{std::abs(Cant1 - Cant0)};
|
||||
double length{0.0};
|
||||
if (cant > 0.0)
|
||||
{
|
||||
length = std::max(length, Speed * cant / (3.6 * Limits.cant_rate));
|
||||
length = std::max(length, cant / 1000.0 * Limits.ramp_factor * Speed);
|
||||
}
|
||||
auto const unbalanced = [&](double const Curvature, double const Cant) { return Speed * Speed / 12.96 * Curvature - kGravity * Cant / Limits.gauge; };
|
||||
length = std::max(length, std::abs(unbalanced(Curvature1, Cant1) - unbalanced(Curvature0, Cant0)) * Speed / (3.6 * Limits.jerk));
|
||||
return length > 0.0 ? ceil_to(length, 5.0) : 0.0;
|
||||
}
|
||||
|
||||
recommendation recommend(double const Speed, double const Radius, limits const &Limits)
|
||||
{
|
||||
recommendation result;
|
||||
@@ -1124,6 +1138,450 @@ join_result join(join_request const &Request)
|
||||
return chosen;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// elements laid one after another from a point in a direction, put together the way the alignment computes them; the cant along
|
||||
// them is kept apart, a transition between two canted arcs ramps it from the one to the other
|
||||
struct element_chain
|
||||
{
|
||||
result shape;
|
||||
glm::dvec2 position{0.0};
|
||||
glm::dvec2 direction{0.0, 1.0};
|
||||
std::vector<std::array<double, 2>> cants; // chainage, cant
|
||||
};
|
||||
|
||||
void chain_push(element_chain &Chain, element Element, double const Cant0, double const Cant1)
|
||||
{
|
||||
Element.chainage = Chain.shape.length;
|
||||
Chain.shape.length += Element.length;
|
||||
Chain.shape.elements.push_back(Element);
|
||||
Chain.cants.push_back({Element.chainage, Cant0});
|
||||
Chain.cants.push_back({Chain.shape.length, Cant1});
|
||||
}
|
||||
|
||||
void chain_straight(element_chain &Chain, double const Length)
|
||||
{
|
||||
if (Length < 1e-6)
|
||||
return;
|
||||
element straight;
|
||||
straight.kind = element_kind::straight;
|
||||
straight.origin = Chain.position;
|
||||
straight.direction = Chain.direction;
|
||||
straight.length = Length;
|
||||
Chain.position += Chain.direction * Length;
|
||||
chain_push(Chain, straight, 0.0, 0.0);
|
||||
}
|
||||
|
||||
void chain_arc(element_chain &Chain, double const Curvature, double const Length, double const Cant)
|
||||
{
|
||||
if (Length < 1e-6)
|
||||
return;
|
||||
if (std::abs(Curvature) < 1e-9)
|
||||
{
|
||||
chain_straight(Chain, Length);
|
||||
return;
|
||||
}
|
||||
element arc;
|
||||
arc.kind = element_kind::arc;
|
||||
arc.turn = Curvature > 0.0 ? 1 : -1;
|
||||
arc.radius = 1.0 / std::abs(Curvature);
|
||||
arc.length = Length;
|
||||
arc.origin = Chain.position + perpendicular(Chain.direction) / Curvature;
|
||||
arc.normal = Chain.position - arc.origin;
|
||||
arc.direction = Chain.direction;
|
||||
auto const angle{arc.turn * Length / arc.radius};
|
||||
Chain.position = arc.origin + turned(arc.normal, angle);
|
||||
Chain.direction = turned(Chain.direction, angle);
|
||||
chain_push(Chain, arc, Cant, Cant);
|
||||
}
|
||||
|
||||
// a curvature going on through 0 turns the curve the other way: it's kept signed, the element turning to the left
|
||||
void chain_spiral(element_chain &Chain, double const From, double const To, double const Length, double const Cant0, double const Cant1)
|
||||
{
|
||||
if (Length < 1e-6)
|
||||
return;
|
||||
int const turn{(From * To < 0.0 || From + To >= 0.0) ? 1 : -1};
|
||||
element spiral;
|
||||
spiral.kind = element_kind::spiral;
|
||||
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;
|
||||
spiral.length = Length;
|
||||
spiral.origin = Chain.position;
|
||||
spiral.direction = Chain.direction;
|
||||
spiral.normal = perpendicular(Chain.direction) * static_cast<double>(turn);
|
||||
auto const local{spiral_point(spiral.curvature_start, spiral.curvature_end, Length, Length)};
|
||||
Chain.position = spiral.origin + spiral.direction * local.x + spiral.normal * local.y;
|
||||
Chain.direction = glm::normalize(spiral.direction * std::cos(local.angle) + spiral.normal * std::sin(local.angle));
|
||||
chain_push(Chain, spiral, Cant0, Cant1);
|
||||
}
|
||||
|
||||
lay_result chain_pieces(element_chain &Chain, lay_request const &Request)
|
||||
{
|
||||
auto &shape{Chain.shape};
|
||||
shape.gauge = Request.norms.gauge;
|
||||
shape.start = Request.start.position;
|
||||
shape.end = {Chain.position.x, Request.target.y, Chain.position.y};
|
||||
shape.profile = {{0.0, Request.start.position.y, 0.0, 0.0}, {shape.length, Request.target.y, 0.0, 0.0}};
|
||||
shape.valid = shape.length > 1e-3;
|
||||
design style;
|
||||
style.norms = Request.norms;
|
||||
lay_result result;
|
||||
result.length = shape.length;
|
||||
auto const count{std::max(minimum_pieces(shape, style), static_cast<std::size_t>(std::ceil(shape.length / std::max(5.0, Request.piece_length) - 1e-9)))};
|
||||
result.pieces = pieces(shape, style, count);
|
||||
auto const cant_at = [&](double const Chainage) {
|
||||
auto const &cants{Chain.cants};
|
||||
for (std::size_t i = 0; i + 1 < cants.size(); i += 2)
|
||||
if (Chainage <= cants[i + 1][0] + 1e-6)
|
||||
{
|
||||
auto const span{cants[i + 1][0] - cants[i][0]};
|
||||
auto const t{span > 1e-9 ? std::clamp((Chainage - cants[i][0]) / span, 0.0, 1.0) : 1.0};
|
||||
return cants[i][1] + (cants[i + 1][1] - cants[i][1]) * t;
|
||||
}
|
||||
return cants.empty() ? 0.0 : cants.back()[1];
|
||||
};
|
||||
auto const roll = [&](double const Cant) { return static_cast<float>(glm::degrees(std::asin(std::clamp(Cant / Request.norms.gauge, -1.0, 1.0)))); };
|
||||
double chainage{0.0};
|
||||
for (auto &piece : result.pieces)
|
||||
{
|
||||
piece.rolls[0] = roll(cant_at(chainage));
|
||||
chainage += geometry::bezier{piece}.plan_length();
|
||||
piece.rolls[1] = roll(cant_at(chainage));
|
||||
}
|
||||
if (false == result.pieces.empty())
|
||||
result.pieces.front().rolls[0] = static_cast<float>(Request.start.roll);
|
||||
return result;
|
||||
}
|
||||
|
||||
// a transition of the length given from the curvature of the start, as the position and the direction it ends with
|
||||
struct spiral_end
|
||||
{
|
||||
double x{0.0};
|
||||
double y{0.0};
|
||||
double angle{0.0};
|
||||
int turn{1};
|
||||
|
||||
spiral_end(double const From, double const To, double const Length)
|
||||
{
|
||||
turn = (From * To < 0.0 || From + To >= 0.0) ? 1 : -1;
|
||||
auto const local{spiral_point(From * turn, To * turn, Length, Length)};
|
||||
x = local.x;
|
||||
y = local.y;
|
||||
angle = local.angle;
|
||||
}
|
||||
void apply(glm::dvec2 &Position, glm::dvec2 &Direction) const
|
||||
{
|
||||
auto const normal{perpendicular(Direction) * static_cast<double>(turn)};
|
||||
Position += Direction * x + normal * y;
|
||||
Direction = glm::normalize(Direction * std::cos(angle) + normal * std::sin(angle));
|
||||
}
|
||||
};
|
||||
|
||||
void arc_end(glm::dvec2 &Position, glm::dvec2 &Direction, double const Curvature, double const Length)
|
||||
{
|
||||
auto const centre{Position + perpendicular(Direction) / Curvature};
|
||||
auto const angle{Curvature * Length};
|
||||
Position = centre + turned(Position - centre, angle);
|
||||
Direction = turned(Direction, angle);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<lay_result> lay_on(lay_request const &Request)
|
||||
{
|
||||
auto const &norms{Request.norms};
|
||||
if (glm::length(Request.start.direction) < 1e-9)
|
||||
return std::nullopt;
|
||||
glm::dvec2 const start{Request.start.position.x, Request.start.position.z};
|
||||
auto const direction{glm::normalize(Request.start.direction)};
|
||||
glm::dvec2 const target{Request.target.x, Request.target.z};
|
||||
auto const offset{target - start};
|
||||
auto const chord{glm::length(offset)};
|
||||
auto const lateral{cross(direction, offset)};
|
||||
if (chord < 0.5 || glm::dot(offset, direction) <= 0.0)
|
||||
return std::nullopt;
|
||||
auto const k0{Request.curvature};
|
||||
auto const c0{norms.gauge * std::sin(glm::radians(Request.start.roll))};
|
||||
if (Request.kind == lay_kind::straight && std::abs(k0) < 1e-9 && std::abs(c0) < 5.0)
|
||||
return std::nullopt;
|
||||
if (Request.kind == lay_kind::arc && std::abs(lateral) < 0.005 * chord + 0.05 && std::abs(k0) < 1e-9 && std::abs(c0) < 5.0)
|
||||
return std::nullopt;
|
||||
int const side{lateral > 0.0 ? 1 : -1};
|
||||
// the speed the arc allows and its cant; a transition is ramped for the lower speed of its two ends
|
||||
auto const allowed = [&](double const Curvature) { return std::abs(Curvature) < 1e-9 ? Request.speed : std::max(1.0, std::min(Request.speed, speed_limit(1.0 / std::abs(Curvature), norms))); };
|
||||
auto const cant_of = [&](double const Curvature) { return std::abs(Curvature) < 1e-9 ? 0.0 : std::copysign(recommend(allowed(Curvature), 1.0 / std::abs(Curvature), norms).cant, Curvature); };
|
||||
auto const slower = [&](double const From, double const To) { return std::min(allowed(From), allowed(To)); };
|
||||
auto const between = [&](double const From, double const Fromcant, double const To, double const Tocant) {
|
||||
if (std::abs(To - From) <= 0.05 * std::max(std::abs(From), std::abs(To)) && std::abs(Tocant - Fromcant) < 5.0)
|
||||
return 0.0;
|
||||
return transition_between(slower(From, To), From, Fromcant, To, Tocant, norms);
|
||||
};
|
||||
// the change of the cant a transition of the length ramps at the speed
|
||||
auto const ramped = [&](double const Length, double const Speed) { return std::min(Length * 1000.0 / (norms.ramp_factor * Speed), norms.cant_rate * 3.6 * Length / Speed); };
|
||||
|
||||
// the transitions of the norms cut to the share, the cant of the arc lowered to what they can ramp; none where the cant of the
|
||||
// end can't be ramped to one leaning into the arc
|
||||
struct layout_plan
|
||||
{
|
||||
double out{0.0}; // out of the curvature of the end to the straight, before the straight of the layout
|
||||
double entry{0.0};
|
||||
double exit{0.0};
|
||||
double cant{0.0};
|
||||
double from{0.0}; // the curvature and the cant the entry starts from
|
||||
double fromcant{0.0};
|
||||
bool lowered{false};
|
||||
};
|
||||
auto const planned = [&](double const Curvature, bool const Lead, bool const Exit, double const Share) -> std::optional<layout_plan> {
|
||||
layout_plan result;
|
||||
auto const wanted{cant_of(Curvature)};
|
||||
if (Lead)
|
||||
{
|
||||
result.out = between(k0, c0, 0.0, 0.0) * Share;
|
||||
if (std::abs(c0) > ramped(result.out, slower(k0, 0.0)) + 1e-6)
|
||||
return std::nullopt;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.from = k0;
|
||||
result.fromcant = c0;
|
||||
}
|
||||
result.entry = between(result.from, result.fromcant, Curvature, wanted) * Share;
|
||||
result.exit = Exit ? between(Curvature, wanted, 0.0, 0.0) * Share : 0.0;
|
||||
auto cant{wanted};
|
||||
if (result.entry > 0.0)
|
||||
{
|
||||
auto const reach{ramped(result.entry, slower(result.from, Curvature))};
|
||||
cant = std::clamp(cant, result.fromcant - reach, result.fromcant + reach);
|
||||
}
|
||||
else
|
||||
cant = result.fromcant;
|
||||
if (Exit)
|
||||
{
|
||||
auto const reach{result.exit > 0.0 ? ramped(result.exit, allowed(Curvature)) : 0.0};
|
||||
cant = std::clamp(cant, -reach, reach);
|
||||
}
|
||||
if (cant * Curvature < -1e-9 || (result.entry > 0.0 && std::abs(cant - result.fromcant) > ramped(result.entry, slower(result.from, Curvature)) + 1e-6) || (result.entry <= 0.0 && std::abs(cant - result.fromcant) > 1e-6))
|
||||
return std::nullopt;
|
||||
result.cant = cant;
|
||||
result.lowered = std::abs(cant) < std::abs(wanted) - 0.5;
|
||||
return result;
|
||||
};
|
||||
auto const begin = [&]() {
|
||||
element_chain chain;
|
||||
chain.position = start;
|
||||
chain.direction = direction;
|
||||
return chain;
|
||||
};
|
||||
auto const made = [&](element_chain &Chain, double const Curvature, layout_plan const &Plan, double const Share) {
|
||||
auto result{chain_pieces(Chain, Request)};
|
||||
result.radius = std::abs(Curvature) > 1e-9 ? 1.0 / std::abs(Curvature) : 0.0;
|
||||
result.cant = std::abs(Plan.cant);
|
||||
result.entry = Plan.out > 0.0 ? Plan.out : Plan.entry;
|
||||
result.exit = Plan.out > 0.0 ? Plan.entry : Plan.exit;
|
||||
result.from_radius = std::abs(k0) > 1e-9 ? 1.0 / std::abs(k0) : 0.0;
|
||||
result.compound = k0 * Curvature > 0.0 && Plan.out <= 0.0 && Plan.entry > 0.0;
|
||||
if (Share < 1.0)
|
||||
result.note = format(STR_C("The transition curves are cut to %.0f%% of what the norms ask for, there's no more room"), Share * 100.0);
|
||||
if (Plan.lowered)
|
||||
result.note += (result.note.empty() ? "" : "\n") + format(STR_C("The cant is lowered to %.0f mm, what the transition curves ramp"), std::abs(Plan.cant));
|
||||
return result;
|
||||
};
|
||||
|
||||
auto const arc_through = [&](double const Share) -> std::optional<lay_result> {
|
||||
auto const reference{2.0 * lateral / (chord * chord)};
|
||||
auto curvature{reference};
|
||||
std::optional<layout_plan> plan;
|
||||
// the transition and the cant follow the curvature found, which moves with them
|
||||
for (int pass = 0; pass < 4; ++pass)
|
||||
{
|
||||
plan = planned(curvature, false, false, Share);
|
||||
if (false == plan.has_value())
|
||||
return std::nullopt;
|
||||
// over the curvatures turning to the target, from flat to sharp, it goes from inside of the circle of the arc to outside
|
||||
auto const outside = [&](double const Curvature) {
|
||||
auto position{start};
|
||||
auto heading{direction};
|
||||
spiral_end{k0, Curvature, plan->entry}.apply(position, heading);
|
||||
return glm::distance(target, position + perpendicular(heading) / Curvature) - 1.0 / std::abs(Curvature);
|
||||
};
|
||||
auto low{std::log(1e-6)};
|
||||
auto high{std::log(0.1)};
|
||||
if (outside(side * std::exp(low)) >= 0.0 || outside(side * std::exp(high)) <= 0.0)
|
||||
return std::nullopt;
|
||||
for (int i = 0; i < 60; ++i)
|
||||
{
|
||||
auto const middle{(low + high) * 0.5};
|
||||
(outside(side * std::exp(middle)) < 0.0 ? low : high) = middle;
|
||||
}
|
||||
curvature = side * std::exp((low + high) * 0.5);
|
||||
}
|
||||
// a transition too long for the way to the target squeezes the arc after it, a shorter one is tried then
|
||||
if (std::abs(curvature) > 1.3 * std::abs(reference) + 1e-5)
|
||||
return std::nullopt;
|
||||
auto chain{begin()};
|
||||
chain_spiral(chain, k0, curvature, plan->entry, c0, plan->cant);
|
||||
auto const centre{chain.position + perpendicular(chain.direction) / curvature};
|
||||
auto const angle{geometry::signed_angle(chain.position - centre, target - centre) * side};
|
||||
if (angle <= 1e-4 || angle >= kPi)
|
||||
return std::nullopt;
|
||||
chain_arc(chain, curvature, angle / std::abs(curvature), plan->cant);
|
||||
return made(chain, curvature, *plan, Share);
|
||||
};
|
||||
|
||||
auto const arc_straight = [&](double const Share) -> std::optional<lay_result> {
|
||||
auto const curvature{side / std::max(1.0, Request.radius)};
|
||||
auto const plan{planned(curvature, false, true, Share)};
|
||||
if (false == plan.has_value())
|
||||
return std::nullopt;
|
||||
auto position{start};
|
||||
auto heading{direction};
|
||||
spiral_end{k0, curvature, plan->entry}.apply(position, heading);
|
||||
spiral_end const out{curvature, 0.0, plan->exit};
|
||||
// the arc grows until the straight after the transition out of it runs through the target
|
||||
auto const miss = [&](double const Arc) {
|
||||
auto at{position};
|
||||
auto towards{heading};
|
||||
arc_end(at, towards, curvature, Arc);
|
||||
out.apply(at, towards);
|
||||
return std::array<double, 2>{cross(towards, target - at) * side, glm::dot(towards, target - at)};
|
||||
};
|
||||
if (miss(0.0)[0] <= 0.0)
|
||||
return std::nullopt;
|
||||
auto const radius{1.0 / std::abs(curvature)};
|
||||
auto const step{std::max(0.5, radius * glm::radians(2.0))};
|
||||
double low{0.0};
|
||||
double high{-1.0};
|
||||
for (auto arc{step}; arc < 2.0 * kPi * radius; arc += step)
|
||||
{
|
||||
if (miss(arc)[0] <= 0.0)
|
||||
{
|
||||
high = arc;
|
||||
break;
|
||||
}
|
||||
low = arc;
|
||||
}
|
||||
if (high < 0.0)
|
||||
return std::nullopt;
|
||||
for (int i = 0; i < 60; ++i)
|
||||
{
|
||||
auto const middle{(low + high) * 0.5};
|
||||
(miss(middle)[0] > 0.0 ? low : high) = middle;
|
||||
}
|
||||
auto const arc{(low + high) * 0.5};
|
||||
auto const straight{miss(arc)[1]};
|
||||
if (straight < 0.0)
|
||||
return std::nullopt;
|
||||
auto chain{begin()};
|
||||
chain_spiral(chain, k0, curvature, plan->entry, c0, plan->cant);
|
||||
chain_arc(chain, curvature, arc, plan->cant);
|
||||
chain_spiral(chain, curvature, 0.0, plan->exit, plan->cant, 0.0);
|
||||
chain_straight(chain, straight);
|
||||
return made(chain, curvature, *plan, Share);
|
||||
};
|
||||
|
||||
auto const straight_arc = [&](double const Share) -> std::optional<lay_result> {
|
||||
auto const curvature{side / std::max(1.0, Request.radius)};
|
||||
auto const plan{planned(curvature, true, false, Share)};
|
||||
if (false == plan.has_value())
|
||||
return std::nullopt;
|
||||
auto lead{begin()};
|
||||
chain_spiral(lead, k0, 0.0, plan->out, c0, 0.0);
|
||||
spiral_end const into{0.0, curvature, plan->entry};
|
||||
// the end of the arc with no straight before it; the straight moves it along the lead, the arc sideways
|
||||
auto const end = [&](double const Arc) {
|
||||
auto at{lead.position};
|
||||
auto towards{lead.direction};
|
||||
into.apply(at, towards);
|
||||
arc_end(at, towards, curvature, Arc);
|
||||
return at;
|
||||
};
|
||||
auto const miss = [&](double const Arc) { return cross(lead.direction, target - end(Arc)) * side; };
|
||||
auto const most{kPi / std::abs(curvature)};
|
||||
if (miss(0.0) <= 0.0 || miss(most) > 0.0)
|
||||
return std::nullopt;
|
||||
double low{0.0};
|
||||
double high{most};
|
||||
for (int i = 0; i < 60; ++i)
|
||||
{
|
||||
auto const middle{(low + high) * 0.5};
|
||||
(miss(middle) > 0.0 ? low : high) = middle;
|
||||
}
|
||||
auto const arc{(low + high) * 0.5};
|
||||
auto const straight{glm::dot(lead.direction, target - end(arc))};
|
||||
if (straight < 0.5)
|
||||
return std::nullopt;
|
||||
auto chain{lead};
|
||||
chain_straight(chain, straight);
|
||||
chain_spiral(chain, 0.0, curvature, plan->entry, 0.0, plan->cant);
|
||||
chain_arc(chain, curvature, arc, plan->cant);
|
||||
return made(chain, curvature, *plan, Share);
|
||||
};
|
||||
|
||||
auto const straight = [&](double const Share) -> std::optional<lay_result> {
|
||||
auto const plan{planned(0.0, true, false, Share)};
|
||||
if (false == plan.has_value())
|
||||
return std::nullopt;
|
||||
auto chain{begin()};
|
||||
chain_spiral(chain, k0, 0.0, plan->out, c0, 0.0);
|
||||
auto const along{glm::dot(chain.direction, target - chain.position)};
|
||||
if (along < 0.5)
|
||||
return std::nullopt;
|
||||
chain_straight(chain, along);
|
||||
return made(chain, 0.0, *plan, Share);
|
||||
};
|
||||
|
||||
auto const first = [](auto const &Layout) -> std::optional<lay_result> {
|
||||
for (auto const share : {1.0, 0.75, 0.5, 0.35, 0.25})
|
||||
if (auto laid{Layout(share)}; laid.has_value())
|
||||
return laid;
|
||||
return std::nullopt;
|
||||
};
|
||||
std::optional<lay_result> laid;
|
||||
switch (Request.kind)
|
||||
{
|
||||
case lay_kind::straight: laid = first(straight); break;
|
||||
case lay_kind::arc: laid = first(arc_through); break;
|
||||
case lay_kind::straight_arc:
|
||||
laid = first(straight_arc);
|
||||
if (false == laid.has_value())
|
||||
laid = first(arc_through);
|
||||
break;
|
||||
default:
|
||||
laid = first(arc_straight);
|
||||
if (false == laid.has_value())
|
||||
laid = first(arc_through);
|
||||
break;
|
||||
}
|
||||
if (laid.has_value())
|
||||
return laid;
|
||||
lay_result refused;
|
||||
refused.note = std::abs(c0) >= 5.0 ? format(STR_C("No room to ramp the cant of %.0f mm the track ends with, the track is laid without transition curves"), std::abs(c0))
|
||||
: std::string{STR_C("No room for the transition curves of the norms, the track is laid without them")};
|
||||
return refused;
|
||||
}
|
||||
|
||||
void recommend_between(vertex &Vertex, double const Speed, limits const &Limits)
|
||||
{
|
||||
if (false == Vertex.compound || Vertex.arcs.empty())
|
||||
return;
|
||||
auto sharpest{std::max(1.0, Vertex.radius)};
|
||||
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 previous{std::max(1.0, Vertex.radius)};
|
||||
for (auto &arc : Vertex.arcs)
|
||||
{
|
||||
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);
|
||||
previous = radius;
|
||||
}
|
||||
}
|
||||
|
||||
bool tangent_intersection(design const &Design, glm::dvec2 &Point)
|
||||
{
|
||||
auto const start{plan_of(Design.start)};
|
||||
@@ -1460,7 +1918,7 @@ constexpr double kTraceRadiusMax{50000.0};
|
||||
constexpr double kTraceRobust{0.25};
|
||||
|
||||
// the parameters: the logarithms of the radii, then the weights of the shares of all but the first radius, as logarithms too
|
||||
vertex traced_shape(vertex const &Base, std::vector<double> const &Parameters, int const Radii)
|
||||
vertex traced_shape(design const &Design, vertex const &Base, std::vector<double> const &Parameters, int const Radii)
|
||||
{
|
||||
auto shape{Base};
|
||||
auto const radius = [&](int const Index) { return std::exp(std::clamp(Parameters[Index], std::log(kTraceRadiusMin), std::log(kTraceRadiusMax))); };
|
||||
@@ -1488,6 +1946,8 @@ vertex traced_shape(vertex const &Base, std::vector<double> const &Parameters, i
|
||||
arc.share = weights[i] / total;
|
||||
shape.arcs.push_back(arc);
|
||||
}
|
||||
if (Design.norms.checked)
|
||||
recommend_between(shape, Design.speed, Design.norms);
|
||||
return shape;
|
||||
}
|
||||
|
||||
@@ -1514,7 +1974,7 @@ trace_fit fit_trace(design const &Design, std::size_t const Vertex, std::vector<
|
||||
return [&, Count](std::vector<double> const &Parameters, std::vector<double> &Residuals) {
|
||||
auto trial{Design};
|
||||
trial.norms.checked = false;
|
||||
trial.vertices[Vertex] = traced_shape(base, Parameters, Count);
|
||||
trial.vertices[Vertex] = traced_shape(Design, base, Parameters, Count);
|
||||
auto const result{compute(trial)};
|
||||
if (false == result.valid)
|
||||
return false;
|
||||
@@ -1645,10 +2105,12 @@ trace_fit fit_trace(design const &Design, std::size_t const Vertex, std::vector<
|
||||
take(3);
|
||||
}
|
||||
|
||||
auto shape{traced_shape(base, fits[chosen].parameters, chosen)};
|
||||
auto shape{traced_shape(Design, base, fits[chosen].parameters, chosen)};
|
||||
shape.radius = std::max(kTraceRadiusMin, std::round(shape.radius));
|
||||
for (auto &arc : shape.arcs)
|
||||
arc.radius = std::max(kTraceRadiusMin, std::round(arc.radius));
|
||||
if (Design.norms.checked)
|
||||
recommend_between(shape, Design.speed, Design.norms);
|
||||
auto trial{Design};
|
||||
trial.vertices[Vertex] = shape;
|
||||
auto const result{compute(trial)};
|
||||
|
||||
@@ -9,6 +9,7 @@ http:
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
@@ -38,6 +39,8 @@ struct limits
|
||||
double arc_min{20.0};
|
||||
double arc_min_factor{0.4};
|
||||
bool checked{true};
|
||||
|
||||
bool operator==(limits const &) const = default;
|
||||
};
|
||||
|
||||
struct compound_arc
|
||||
@@ -92,6 +95,9 @@ struct recommendation
|
||||
};
|
||||
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);
|
||||
|
||||
enum class element_kind
|
||||
@@ -217,6 +223,8 @@ 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
|
||||
@@ -259,4 +267,47 @@ struct join_result
|
||||
|
||||
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<segment_data> 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_result> lay_on(lay_request const &Request);
|
||||
|
||||
// the transitions between the arcs of a compound curve which the norms ask for at the speed, the cant of the vertex on its sharpest arc
|
||||
void recommend_between(vertex &Vertex, double const Speed, limits const &Limits);
|
||||
|
||||
}
|
||||
|
||||
@@ -178,6 +178,14 @@ class editor_track
|
||||
glm::dvec2 direction{0.0};
|
||||
std::vector<segment_data> pieces;
|
||||
};
|
||||
// the curvature and the roll a track ends with at Joint, as it goes on beyond the joint
|
||||
struct bend
|
||||
{
|
||||
double curvature{0.0}; // positive turning from x towards z
|
||||
double roll{0.0};
|
||||
glm::dvec2 direction{0.0};
|
||||
};
|
||||
static std::optional<bend> bend_at(TTrack const &Neighbour, glm::dvec3 const &Joint);
|
||||
static easing ease_out_of(TTrack const &Neighbour, glm::dvec3 const &Joint, double const Grade, double const Speed, alignment::limits const &Limits);
|
||||
static bool eases_room(easing const &In, easing const &Out, glm::dvec3 const &Start, glm::dvec2 const &Startdirection, glm::dvec3 const &End, glm::dvec2 const &Enddirection);
|
||||
static std::vector<segment_data> eased_pieces(easing const &In, std::vector<segment_data> const &Middle, easing const &Out);
|
||||
|
||||
@@ -640,33 +640,48 @@ std::vector<segment_data> editor_track::layout_pieces(layout const Kind, glm::dv
|
||||
return straight_then_arc(Point, Direction, Grade, offset, radius, side);
|
||||
}
|
||||
|
||||
editor_track::easing editor_track::ease_out_of(TTrack const &Neighbour, glm::dvec3 const &Joint, double const Grade, double const Speed, alignment::limits const &Limits)
|
||||
std::optional<editor_track::bend> editor_track::bend_at(TTrack const &Neighbour, glm::dvec3 const &Joint)
|
||||
{
|
||||
easing result;
|
||||
for (auto const &path : Neighbour.m_paths)
|
||||
for (auto const atend : {false, true})
|
||||
{
|
||||
if (glm::distance(path.points[atend ? segment_data::point::end : segment_data::point::start], Joint) > 0.05)
|
||||
continue;
|
||||
bend result;
|
||||
auto const sign{atend ? 1.0 : -1.0};
|
||||
result.roll = atend ? static_cast<double>(path.rolls[1]) : -static_cast<double>(path.rolls[0]);
|
||||
if (false == has_controls(path))
|
||||
{
|
||||
auto const chord{plan_of(path.points[segment_data::point::end] - path.points[segment_data::point::start])};
|
||||
if (glm::length(chord) < 1e-9)
|
||||
return std::nullopt;
|
||||
result.direction = glm::normalize(chord) * sign;
|
||||
return result;
|
||||
}
|
||||
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)
|
||||
return result;
|
||||
auto const sign{atend ? 1.0 : -1.0};
|
||||
auto const curvature{sign * (first.x * second.z - first.z * second.x) / (speed * speed * speed)};
|
||||
glm::dvec2 const direction{sign * first.x / speed, sign * first.z / speed};
|
||||
auto const roll{atend ? static_cast<double>(path.rolls[1]) : -static_cast<double>(path.rolls[0])};
|
||||
result.curvature = curvature;
|
||||
if (std::abs(curvature) < 1e-4)
|
||||
return result;
|
||||
result.length = std::max(20.0, alignment::recommend(Speed, 1.0 / std::abs(curvature), Limits).transition);
|
||||
lay_transition(result, Joint, direction, Grade, roll);
|
||||
return std::nullopt;
|
||||
result.curvature = sign * (first.x * second.z - first.z * second.x) / (speed * speed * speed);
|
||||
result.direction = {sign * first.x / speed, sign * first.z / speed};
|
||||
return result;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
editor_track::easing editor_track::ease_out_of(TTrack const &Neighbour, glm::dvec3 const &Joint, double const Grade, double const Speed, alignment::limits const &Limits)
|
||||
{
|
||||
easing result;
|
||||
auto const end{bend_at(Neighbour, Joint)};
|
||||
if (false == end.has_value())
|
||||
return result;
|
||||
result.curvature = end->curvature;
|
||||
if (std::abs(end->curvature) < 1e-4)
|
||||
return result;
|
||||
result.length = std::max(20.0, alignment::recommend(Speed, 1.0 / std::abs(end->curvature), Limits).transition);
|
||||
lay_transition(result, Joint, end->direction, Grade, end->roll);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -10222,3 +10222,24 @@ msgstr "LPM: punkt na środku toru na zdjęciu. Enter: zastosuj, Esc: porzuć"
|
||||
|
||||
msgid "Click: one more point, the curve follows. Ctrl+click: take a point out"
|
||||
msgstr "LPM: kolejny punkt, łuk idzie za nimi. Ctrl+LPM: usuń punkt"
|
||||
|
||||
msgid "compound curve on from R %.0f m, a transition of %.0f m between the arcs"
|
||||
msgstr "łuk koszowy dalej od R %.0f m, krzywa przejściowa %.0f m między łukami"
|
||||
|
||||
msgid "Compound curve: R %.0f m goes on into R %.0f m through a transition curve of %.0f m"
|
||||
msgstr "Łuk koszowy: R %.0f m przechodzi w R %.0f m przez krzywą przejściową %.0f m"
|
||||
|
||||
msgid "transition curves %.0f / %.0f m"
|
||||
msgstr "krzywe przejściowe %.0f / %.0f m"
|
||||
|
||||
msgid "No room for the transition curves of the norms, the track is laid without them"
|
||||
msgstr "Brak miejsca na krzywe przejściowe wg norm, tor ułożony bez nich"
|
||||
|
||||
msgid "No room to ramp the cant of %.0f mm the track ends with, the track is laid without transition curves"
|
||||
msgstr "Brak miejsca, żeby zejść z przechyłką %.0f mm z końca toru, tor ułożony bez krzywych przejściowych"
|
||||
|
||||
msgid "The transition curves are cut to %.0f%% of what the norms ask for, there's no more room"
|
||||
msgstr "Krzywe przejściowe skrócone do %.0f%% długości wg norm, nie ma na więcej miejsca"
|
||||
|
||||
msgid "The cant is lowered to %.0f mm, what the transition curves ramp"
|
||||
msgstr "Przechyłka obniżona do %.0f mm, tyle zdążą podnieść krzywe przejściowe"
|
||||
|
||||
Reference in New Issue
Block a user