17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 03:59:19 +02:00

feat(editor): digitizing reads more of the old tracks: curves of the same hand meeting through their transitions with no straight between are separate vertices, a run turning over 150 degrees is split into vertices at track joints, a curve made of two transitions with no arc is read with the radius at their joint; digitizing tries both transition shapes, then without the minimum arc length when a short curve would lose its transitions; the reason names the track left by how much at any distance

This commit is contained in:
maj00r
2026-10-12 03:45:12 +02:00
parent f5895abfa8
commit 096122bcc3
4 changed files with 74 additions and 6 deletions

View File

@@ -1868,9 +1868,15 @@ bool editor_mode::digitize_chain(editor_track::chain const &Chain, std::string *
double closest{std::numeric_limits<double>::max()};
TTrack *farthest{nullptr};
bool fits{false};
for (auto const shape : {preferred, other})
for (auto const attempt : {0, 1, 2, 3})
{
auto const shape{attempt % 2 == 0 ? preferred : other};
design.shape = shape;
if (attempt == 2)
{
design.norms.arc_min = 0.0;
design.norms.arc_min_factor = 0.0;
}
design.vertices.clear();
for (auto const &entry : editor_track::recognize_line(Chain, m_straights.tolerance, design.norms.gauge, shape))
{
@@ -1905,7 +1911,7 @@ bool editor_mode::digitize_chain(editor_track::chain const &Chain, std::string *
if (false == fits)
{
if (Reason != nullptr)
*Reason = closest < 1000.0 ? format(STR_C("the curves read from the paths leave path %s by %.2f m"), name(farthest).c_str(), closest) : std::string{STR_C("the paths don't make a line of straights, arcs and transition curves")};
*Reason = closest < std::numeric_limits<double>::max() ? format(STR_C("the curves read from the paths leave path %s by %.2f m"), name(farthest).c_str(), closest) : std::string{STR_C("the paths don't make a line of straights, arcs and transition curves")};
return false;
}
auto const &records{alignment_store::records()};

View File

@@ -1130,6 +1130,24 @@ bool tangent_intersection(design const &Design, glm::dvec2 &Point)
return true;
}
bool spiral_tangents(transition_shape const Shape, double const TurnIn, double const TurnOut, double &TangentIn, double &TangentOut)
{
auto const length = [&](double const Turn) { return Shape == transition_shape::clothoid ? 2.0 * Turn : 2.0 * std::tan(Turn); };
if (TurnIn <= 0.0 || TurnOut <= 0.0 || TurnIn >= 0.5 * kPi || TurnOut >= 0.5 * kPi)
return false;
auto const in{transition_point(Shape, length(TurnIn), 1.0, 1.0)};
auto const out{transition_point(Shape, length(TurnOut), 1.0, 1.0)};
auto const deflection{in.angle + out.angle};
if (deflection >= kPi - 1e-3)
return false;
glm::dvec2 const direction{std::cos(deflection), std::sin(deflection)};
glm::dvec2 const normal{-direction.y, direction.x};
auto const end{glm::dvec2{in.x, in.y} + direction * out.x - normal * out.y};
TangentOut = end.y / std::sin(deflection);
TangentIn = end.x - TangentOut * std::cos(deflection);
return TangentIn > 0.0 && TangentOut > 0.0;
}
bool collinear(design const &Design)
{
auto const offset{plan_of(Design.end) - plan_of(Design.start)};

View File

@@ -173,6 +173,7 @@ double speed_limit(double const Radius, limits const &Limits);
result compute_recommended(design &Design, bool const Transitions);
bool tangent_intersection(design const &Design, glm::dvec2 &Point);
bool collinear(design const &Design);
bool spiral_tangents(transition_shape const Shape, double const TurnIn, double const TurnOut, double &TangentIn, double &TangentOut);
struct station
{

View File

@@ -1852,6 +1852,17 @@ std::vector<editor_track::line_vertex> editor_track::recognize_line(chain const
straight[i] = is_straight(*Chain.tracks[i], Tolerance);
turns[i] = signed_angle(tangent_in(i), tangent_out(i));
}
auto const kLongestTurn{0.85 * glm::pi<double>()};
auto const curvature = [&](std::size_t const Index, double const T) {
auto const radius{path_radius(path(Index), T)};
return radius > 0.0 ? 1.0 / radius : 0.0;
};
auto const eases = [&](std::size_t const Index, bool const Atend) {
double peak{0.0};
for (auto const t : {0.0, 0.25, 0.5, 0.75, 1.0})
peak = std::max(peak, curvature(Index, t));
return peak > 0.0 && curvature(Index, Chain.forward[Index] == Atend ? 1.0 : 0.0) < 0.05 * peak;
};
auto const add_curve = [&](std::size_t const First, std::size_t const Last) {
auto const a{point_in(First)};
auto const b{point_out(Last)};
@@ -1881,14 +1892,29 @@ std::vector<editor_track::line_vertex> editor_track::recognize_line(chain const
};
auto const in{shape.transition_in > 0.0 ? pieces(shape.transition_in, true) : 0};
auto const out{shape.transition_out > 0.0 ? pieces(shape.transition_out, false) : 0};
if (in + out <= Last - First)
auto const turn_of = [&](std::size_t const From, std::size_t const To) { return std::abs(std::accumulate(turns.begin() + From, turns.begin() + To + 1, 0.0)); };
auto const length = [&](double const Turn) { return Shape == alignment::transition_shape::clothoid ? 2.0 * shape.radius * Turn : 2.0 * shape.radius * std::tan(Turn); };
if (Last > First && shape.transition_in <= 0.0 && shape.transition_out <= 0.0 && eases(First, false) && eases(Last, true))
{
auto joint{First};
for (auto k = First; k < Last; ++k)
if (curvature(k, Chain.forward[k] ? 1.0 : 0.0) > curvature(joint, Chain.forward[joint] ? 1.0 : 0.0))
joint = k;
double tangentin;
double tangentout;
if (alignment::spiral_tangents(Shape, turn_of(First, joint), turn_of(joint + 1, Last), tangentin, tangentout))
{
shape.radius = 0.5 * (glm::distance(vertex.position, a) / tangentin + glm::distance(b, vertex.position) / tangentout);
shape.transition_in = length(turn_of(First, joint));
shape.transition_out = length(turn_of(joint + 1, Last));
}
}
else if (in + out <= Last - First)
{
auto const turn_of = [&](std::size_t const From, std::size_t const To) { return std::abs(std::accumulate(turns.begin() + From, turns.begin() + To + 1, 0.0)); };
auto const arc{turn_of(First + in, Last - out)};
if (arc > 1e-6 && arc < 2.0 * glm::pi<double>() - 1e-3)
{
shape.radius = glm::distance(point_in(First + in), point_out(Last - out)) / (2.0 * std::sin(arc * 0.5));
auto const length = [&](double const Turn) { return Shape == alignment::transition_shape::clothoid ? 2.0 * shape.radius * Turn : 2.0 * shape.radius * std::tan(Turn); };
if (in > 0)
shape.transition_in = length(turn_of(First, First + in - 1));
if (out > 0)
@@ -1921,11 +1947,28 @@ std::vector<editor_track::line_vertex> editor_track::recognize_line(chain const
// a reverse curve: the next one starts here
if (current != 0 && sign != 0 && current != sign)
break;
if (i > first && (eases(i - 1, true) || eases(i, false)))
break;
if (current != 0)
sign = current;
++i;
}
add_curve(first, i - 1);
auto const total{std::abs(std::accumulate(turns.begin() + first, turns.begin() + i, 0.0))};
auto const parts{std::max(1.0, std::ceil(total / kLongestTurn))};
auto start{first};
double turned{0.0};
double part{1.0};
for (auto k = first; k + 1 < i && part < parts; ++k)
{
turned += std::abs(turns[k]);
if (turned >= total * part / parts)
{
add_curve(start, k);
start = k + 1;
part += 1.0;
}
}
add_curve(start, i - 1);
}
return result;
}