mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-02 06:59:13 +01:00
feat(editor): the work of the editor sessions not committed yet, as it stands: a curve traced on from the end of the diverging track of a switch goes on at its radius with no transition in, or from a straight right at the switch (alignment start_held, fit_going_on), the plan taking an end adjoining R 10 km or more for a straight; the switches and slips laid into curves, slid along them and laid alone on one straight (frame_path, switch slide); the route plan reading curves going on from the tracks adjoining, the kept designs taken or forgotten; the orthophoto and terrain model settings, downloads and layers, the tracks onto the terrain; the curvature of a path in the plan (bezier::plan_curvature); the Polish texts of all of it. Not built as committed
This commit is contained in:
@@ -720,6 +720,10 @@ result fit_between(design const &Design)
|
||||
fit.compound = true;
|
||||
fit.curved = true;
|
||||
fit.deflection = deflection;
|
||||
// going on from the diverging track of a switch: at its radius, no transition between them
|
||||
auto const held{from ? Design.start_held : Design.end_held};
|
||||
if (held)
|
||||
(from ? fit.transition_in : fit.transition_out) = 0.0;
|
||||
fit.arcs = {{fit.radius, 0.0, 1.0, -1.0}};
|
||||
if (vertex.compound && false == vertex.arcs.empty())
|
||||
{
|
||||
@@ -727,6 +731,8 @@ result fit_between(design const &Design)
|
||||
for (auto const &arc : vertex.arcs)
|
||||
fit.arcs.push_back({std::max(1.0, arc.radius), std::max(0.0, arc.transition), arc.share, arc.cant});
|
||||
}
|
||||
if (held)
|
||||
(from ? fit.arcs.front() : fit.arcs.back()).radius = from ? Design.start_radius : Design.end_radius;
|
||||
if (from)
|
||||
{
|
||||
fit.ends.from = 1.0 / Design.start_radius;
|
||||
@@ -743,11 +749,24 @@ result fit_between(design const &Design)
|
||||
fit.arcs.back().cant = fit.ends.to_cant;
|
||||
}
|
||||
auto const normalin{perpendicular(directionin) * static_cast<double>(side)};
|
||||
auto const tangents_at = [&](double const Scale, double &Tangentin, double &Tangentout) {
|
||||
// the size of the curve which begins it right at the start, or ends it at the end: the scale of its radii, the arc held at the
|
||||
// radius of a switch kept; of a single arc held so the length of the transition on its other side
|
||||
auto const lengthened{held && fit.arcs.size() == 1};
|
||||
auto &beyond{from ? fit.transition_out : fit.transition_in};
|
||||
auto const sized = [&](double const Value, std::vector<compound_arc> &Arcs) {
|
||||
for (std::size_t i = 0; i < Arcs.size(); ++i)
|
||||
if (false == held || i != (from ? 0 : Arcs.size() - 1))
|
||||
Arcs[i].radius *= Value;
|
||||
};
|
||||
auto const tangents_at = [&](double const Value, double &Tangentin, double &Tangentout) {
|
||||
auto arcs{fit.arcs};
|
||||
for (auto &arc : arcs)
|
||||
arc.radius *= Scale;
|
||||
auto const shape{build_compound(deflection, arcs, fit.transition_in, fit.transition_out, {}, fit.ends)};
|
||||
auto in{fit.transition_in};
|
||||
auto out{fit.transition_out};
|
||||
if (lengthened)
|
||||
(from ? out : in) = Value;
|
||||
else
|
||||
sized(Value, arcs);
|
||||
auto const shape{build_compound(deflection, arcs, in, out, {}, fit.ends)};
|
||||
auto const end{directionin * shape.end.x + normalin * shape.end.y};
|
||||
Tangentin = cross(directionout, end) / cross(directionout, directionin);
|
||||
Tangentout = glm::dot(end - directionin * Tangentin, directionout);
|
||||
@@ -761,14 +780,23 @@ result fit_between(design const &Design)
|
||||
return (from ? in : out) - wanted;
|
||||
};
|
||||
// the radii grow with the tangent: from those given the way the miss says, doubled or halved until it changes its sign, then
|
||||
// the root between, by the regula falsi of Illinois
|
||||
auto low{1.0};
|
||||
// the root between, by the regula falsi of Illinois. The transition out of an arc held moves it in, the tangent growing with it:
|
||||
// from none, doubled up to one turning by nearly all the deflection
|
||||
auto low{lengthened ? 0.0 : 1.0};
|
||||
auto lowmiss{miss(low)};
|
||||
auto high{low};
|
||||
auto highmiss{lowmiss};
|
||||
auto const smallest{5.0 / fit.radius};
|
||||
auto const largest{1e6 / fit.radius};
|
||||
while ((lowmiss < 0.0) == (highmiss < 0.0) && high < largest && low > smallest)
|
||||
auto const longest{1.96 * fit.arcs.front().radius * deflection};
|
||||
while (lengthened && (lowmiss < 0.0) == (highmiss < 0.0) && lowmiss < -1e-5 && high < longest)
|
||||
{
|
||||
low = high;
|
||||
lowmiss = highmiss;
|
||||
high = std::min(longest, std::max(1.0, 2.0 * high));
|
||||
highmiss = miss(high);
|
||||
}
|
||||
while (false == lengthened && (lowmiss < 0.0) == (highmiss < 0.0) && high < largest && low > smallest)
|
||||
{
|
||||
if (lowmiss < 0.0)
|
||||
{
|
||||
@@ -785,14 +813,16 @@ result fit_between(design const &Design)
|
||||
lowmiss = miss(low);
|
||||
}
|
||||
}
|
||||
if ((lowmiss < 0.0) == (highmiss < 0.0))
|
||||
// an arc held which begins right there with no transition out of it
|
||||
auto const met{lengthened && std::abs(lowmiss) <= 1e-5};
|
||||
if (false == met && (lowmiss < 0.0) == (highmiss < 0.0))
|
||||
{
|
||||
r.errors.emplace_back(from ? format(STR_C("Vertex %zu: no curve goes on from the curve of the adjoining track (R %.0f m) to the straight after it"), k + 1, Design.start_radius) :
|
||||
format(STR_C("Vertex %zu: no curve goes on from the straight before it into the curve of the adjoining track (R %.0f m)"), k + 1, Design.end_radius));
|
||||
return r;
|
||||
}
|
||||
auto scale{low};
|
||||
for (int i = 0, kept = 0; i < 60; ++i)
|
||||
for (int i = 0, kept = 0; i < 60 && false == met; ++i)
|
||||
{
|
||||
scale = (low * highmiss - high * lowmiss) / (highmiss - lowmiss);
|
||||
auto const value{miss(scale)};
|
||||
@@ -815,10 +845,12 @@ result fit_between(design const &Design)
|
||||
kept = 1;
|
||||
}
|
||||
}
|
||||
for (auto &arc : fit.arcs)
|
||||
arc.radius *= scale;
|
||||
if (lengthened)
|
||||
beyond = scale;
|
||||
else
|
||||
sized(scale, fit.arcs);
|
||||
fit.radius = fit.arcs.front().radius;
|
||||
tangents_at(1.0, fit.tangent_in, fit.tangent_out);
|
||||
tangents_at(lengthened ? beyond : 1.0, fit.tangent_in, fit.tangent_out);
|
||||
continue;
|
||||
}
|
||||
if (vertex.compound && false == vertex.arcs.empty())
|
||||
@@ -1294,15 +1326,8 @@ result fit_between(design const &Design)
|
||||
auto const &e{r.elements[End ? r.elements.size() - 1 - n : n]};
|
||||
if (e.length < 0.01)
|
||||
continue;
|
||||
auto const arc{e.radius > 0.0 ? 1.0 / e.radius : 0.0};
|
||||
switch (e.kind)
|
||||
{
|
||||
case element_kind::arc: return arc * e.turn;
|
||||
case element_kind::transition_in: return End ? arc * e.turn : 0.0;
|
||||
case element_kind::transition_out: return End ? 0.0 : arc * e.turn;
|
||||
case element_kind::spiral: return (End ? e.curvature_end : e.curvature_start) * e.turn;
|
||||
default: return 0.0;
|
||||
}
|
||||
auto const [start, end]{end_curvatures(e)};
|
||||
return (End ? end : start) * e.turn;
|
||||
}
|
||||
return 0.0;
|
||||
};
|
||||
@@ -1333,6 +1358,19 @@ glm::dvec3 along_end(glm::dvec3 const &Point, glm::dvec2 const &Direction, doubl
|
||||
|
||||
} // namespace
|
||||
|
||||
std::pair<double, double> end_curvatures(element const &Element)
|
||||
{
|
||||
auto const own{Element.radius > 0.0 ? 1.0 / Element.radius : 0.0};
|
||||
switch (Element.kind)
|
||||
{
|
||||
case element_kind::transition_in: return {0.0, own};
|
||||
case element_kind::arc: return {own, own};
|
||||
case element_kind::transition_out: return {own, 0.0};
|
||||
case element_kind::spiral: return {Element.curvature_start, Element.curvature_end};
|
||||
default: return {0.0, 0.0};
|
||||
}
|
||||
}
|
||||
|
||||
result compute(design const &Design)
|
||||
{
|
||||
auto first{fit_between(Design)};
|
||||
@@ -1372,7 +1410,7 @@ double speed_limit(double const Radius, limits const &Limits)
|
||||
return std::sqrt(Radius * (Limits.cant_max + allowance) / factor);
|
||||
}
|
||||
|
||||
result compute_recommended(design &Design, bool const Transitions)
|
||||
result compute_recommended(design &Design)
|
||||
{
|
||||
for (auto &vertex : Design.vertices)
|
||||
{
|
||||
@@ -1381,7 +1419,7 @@ result compute_recommended(design &Design, bool const Transitions)
|
||||
vertex.transition_out = 0.0;
|
||||
}
|
||||
auto r{compute(Design)};
|
||||
if (false == Transitions || false == r.valid)
|
||||
if (false == r.valid)
|
||||
return r;
|
||||
auto const speed{Design.speed};
|
||||
for (int i = 0; i < 3; ++i)
|
||||
@@ -1540,7 +1578,7 @@ design join_design(join_frame const &Frame, join_kind const Kind)
|
||||
std::optional<join_result> fitted_join(join_frame const &Frame, join_kind const Kind)
|
||||
{
|
||||
auto shape{join_design(Frame, Kind)};
|
||||
auto const r{compute_recommended(shape, Frame.request.transitions)};
|
||||
auto const r{compute_recommended(shape)};
|
||||
auto const sharp{std::any_of(r.curves.begin(), r.curves.end(), [](curve_report const &Report) { return Report.radius < 10.0; })};
|
||||
if (false == r.valid || sharp || turning(r) > Frame.heading + kPi + 1e-6 || r.length > 3.0 * Frame.distance + 1.0)
|
||||
return std::nullopt;
|
||||
@@ -2433,6 +2471,8 @@ constexpr double kTraceRobust{0.25};
|
||||
constexpr double kPointsWithin{0.3};
|
||||
// m, how far they may be from it, clicked by hand far out: a row of bends making a curve no closer is cut in parts
|
||||
constexpr double kPointsLoose{1.0};
|
||||
// m, the shortest arc of a compound curve fitted through points: a shorter one is a kink the points made, no arc
|
||||
constexpr double kShortestArc{10.0};
|
||||
|
||||
// the parameters: the logarithms of the radii, then the weights of the shares of all but the first radius, as logarithms too.
|
||||
// Between: the transitions between the arcs those of the norms, else the ones of the base
|
||||
@@ -2567,8 +2607,14 @@ trace_fit fit_trace(design const &Design, std::size_t const Vertex, std::vector<
|
||||
bestcost = cost;
|
||||
bestlog = low + spacing * i;
|
||||
}
|
||||
if (bestcost == std::numeric_limits<double>::max())
|
||||
// two points for each radius; a curve going on from the one of the adjoining track has its start, its curvature there given too, and
|
||||
// where one radius doesn't reach the end from there two are tried: a sharper one after the one it goes on from
|
||||
auto const going{Design.start_curved || Design.end_curved};
|
||||
auto const alone{bestcost < std::numeric_limits<double>::max()};
|
||||
auto const most{std::clamp(Radii > 0 ? Radii : 3, 1, std::max(going && false == alone ? 2 : 1, static_cast<int>(Points.size()) / 2))};
|
||||
if (false == alone && most < 2)
|
||||
return best;
|
||||
if (alone)
|
||||
{
|
||||
constexpr double kGolden{0.6180339887498949};
|
||||
auto a{bestlog - spacing};
|
||||
@@ -2607,7 +2653,6 @@ trace_fit fit_trace(design const &Design, std::size_t const Vertex, std::vector<
|
||||
};
|
||||
std::array<candidate, 4> fits;
|
||||
fits[1] = {{bestlog}, cost_of(one, {bestlog})};
|
||||
auto const most{std::clamp(Radii > 0 ? Radii : 3, 1, std::max(1, static_cast<int>(Points.size()) / 2))};
|
||||
auto const shift{std::log(1.3)};
|
||||
auto const share = [](double const Second, double const First) { return std::log(Second / First); };
|
||||
auto const rms = [&](int const Count) { return std::sqrt(fits[Count].cost / static_cast<double>(Points.size())); };
|
||||
@@ -2624,13 +2669,18 @@ trace_fit fit_trace(design const &Design, std::size_t const Vertex, std::vector<
|
||||
if (wanted(2))
|
||||
{
|
||||
auto const two{residuals(2)};
|
||||
for (auto const fraction : {0.3, 0.5, 0.7})
|
||||
for (auto const sign : {-1.0, 1.0})
|
||||
{
|
||||
std::vector<double> parameters{bestlog + sign * shift, bestlog - sign * shift, share(1.0 - fraction, fraction)};
|
||||
if (auto const cost{least_squares(parameters, two)}; cost < fits[2].cost)
|
||||
fits[2] = {parameters, cost};
|
||||
}
|
||||
// radii far apart too where the curve goes on from another one
|
||||
std::vector<double> shifts{shift};
|
||||
if (going)
|
||||
shifts.insert(shifts.end(), {std::log(3.0), std::log(8.0)});
|
||||
for (auto const apart : shifts)
|
||||
for (auto const fraction : {0.3, 0.5, 0.7})
|
||||
for (auto const sign : {-1.0, 1.0})
|
||||
{
|
||||
std::vector<double> parameters{bestlog + sign * apart, bestlog - sign * apart, share(1.0 - fraction, fraction)};
|
||||
if (auto const cost{least_squares(parameters, two)}; cost < fits[2].cost)
|
||||
fits[2] = {parameters, cost};
|
||||
}
|
||||
take(2);
|
||||
}
|
||||
if (wanted(3))
|
||||
@@ -3361,6 +3411,33 @@ double shortest_between(design const &Design)
|
||||
|
||||
} // namespace
|
||||
|
||||
void settle_laid(std::vector<vertex> &Vertices, result const &Laid)
|
||||
{
|
||||
for (auto const &curve : Laid.curves)
|
||||
{
|
||||
// a curve going on from an adjoining one is begun by that one's radius, not made smaller
|
||||
if (curve.vertex < 0 || curve.vertex >= static_cast<int>(Vertices.size()) || curve.curvature_in > 0.0 || curve.curvature_out > 0.0)
|
||||
continue;
|
||||
auto &vertex{Vertices[curve.vertex]};
|
||||
// two transitions meeting have their lengths from the radius they meet at
|
||||
auto const transitions{vertex.spirals || (curve.transition_in >= vertex.transition_in - 1e-6 && curve.transition_out >= vertex.transition_out - 1e-6)};
|
||||
if (curve.radius >= vertex.radius * (1.0 - 1e-6) && transitions)
|
||||
continue;
|
||||
auto const scale{curve.radius / std::max(1.0, vertex.radius)};
|
||||
vertex.radius = curve.radius;
|
||||
for (auto &arc : vertex.arcs)
|
||||
{
|
||||
arc.radius *= scale;
|
||||
arc.transition *= scale;
|
||||
}
|
||||
if (false == vertex.spirals)
|
||||
{
|
||||
vertex.transition_in = curve.transition_in;
|
||||
vertex.transition_out = curve.transition_out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result ease_corners(design &Design, std::vector<char> &Corners, corner_radii const &Radii, stretch_fit const &Fit)
|
||||
{
|
||||
// a point following a slight bend clicked closer than the curves leave a straight of the shortest length was clicked on a curve
|
||||
@@ -3483,6 +3560,8 @@ result ease_corners(design &Design, std::vector<char> &Corners, corner_radii con
|
||||
break;
|
||||
line = ease_bends(Design, Corners, Radii);
|
||||
}
|
||||
if (line.valid)
|
||||
settle_laid(Design.vertices, line);
|
||||
return line;
|
||||
}
|
||||
|
||||
@@ -3513,6 +3592,7 @@ std::optional<trace_stretch> stretch_of(design const &Design, std::vector<glm::d
|
||||
for (auto &arc : vertex.arcs)
|
||||
arc.radius *= scale;
|
||||
}
|
||||
settle_laid(stretch.vertices, laid);
|
||||
double sum{0.0};
|
||||
for (auto const &projection : project(laid, Points))
|
||||
{
|
||||
@@ -3724,6 +3804,26 @@ void fit_curved(design &Design, std::size_t const Vertex, std::vector<glm::dvec2
|
||||
best = parameters;
|
||||
}
|
||||
}
|
||||
// a curve of another radius than the adjoining one, from too few points to show the transition between them: one as long as the
|
||||
// norms ask for at the speed the sharper of them allows, rather than the curvature jumping at the joint
|
||||
if (best.size() == 2 && Points.size() <= 3)
|
||||
for (int pass = 0; pass < 2; ++pass)
|
||||
{
|
||||
design trial;
|
||||
shaped(best, trial);
|
||||
trial.norms.checked = false;
|
||||
laid_with(compute(trial));
|
||||
auto const allowed{std::max(1.0, std::min(speed, speed_limit(std::min(radius, adjoining), Design.norms)))};
|
||||
auto const needed{std::min(between_curves(allowed, 1.0 / adjoining, adjoiningcant, 1.0 / radius, 0.0, Design.norms), 0.9 * longest)};
|
||||
if (length_of(best[0]) >= needed - 0.5)
|
||||
break;
|
||||
auto const between{parameter_of(needed)};
|
||||
residuals_of const rest{[&](std::vector<double> const &Parameters, std::vector<double> &Residuals) { return residuals({between, Parameters[0]}, Residuals); }};
|
||||
std::vector<double> out{best[1]};
|
||||
if (least_squares(out, rest) == std::numeric_limits<double>::max())
|
||||
break;
|
||||
best = {between, out[0]};
|
||||
}
|
||||
if (best.empty())
|
||||
return;
|
||||
design fitted;
|
||||
@@ -3747,38 +3847,70 @@ void fit_curve(design &Design, std::size_t const Vertex, std::vector<glm::dvec2>
|
||||
auto const speed{std::max(1.0, Design.speed)};
|
||||
// the transitions together turn no more than the curve: longer ones would only be cut to it
|
||||
auto const deflection{vertex_deflection(Design, Vertex)};
|
||||
// the transition into the curve and the one out of it each as long as the points show it, where there are points enough for both: a
|
||||
// curve begun short after a free end and running out long into the straight after it is one curve, not a sharp arc with a flat
|
||||
// one after it
|
||||
auto const apart{Points.size() >= 4};
|
||||
auto const shaped = [&](std::vector<double> const &Parameters, design &Trial) {
|
||||
Trial = Design;
|
||||
auto const radius{std::exp(std::clamp(Parameters[0], std::log(kTraceRadiusMin), std::log(kTraceRadiusMax)))};
|
||||
plain_curve(Trial.vertices[Vertex], radius, std::min({Parameters[1] * Parameters[1], radius * deflection, longest_transition(speed, radius, Trial.norms)}), speed, Trial.norms);
|
||||
auto const longest{longest_transition(speed, radius, Trial.norms)};
|
||||
auto in{std::min(Parameters[1] * Parameters[1], longest)};
|
||||
auto out{std::min(Parameters.back() * Parameters.back(), longest)};
|
||||
if (auto const room{2.0 * radius * deflection}; in + out > room)
|
||||
{
|
||||
auto const scale{room / (in + out)};
|
||||
in *= scale;
|
||||
out *= scale;
|
||||
}
|
||||
auto &curve{Trial.vertices[Vertex]};
|
||||
plain_curve(curve, radius, std::min(in, out), speed, Trial.norms);
|
||||
curve.transition_in = in;
|
||||
curve.transition_out = out;
|
||||
};
|
||||
residuals_of const residuals{[&](std::vector<double> const &Parameters, std::vector<double> &Residuals) {
|
||||
design trial;
|
||||
shaped(Parameters, trial);
|
||||
return stretch_residuals(trial, Points, Residuals);
|
||||
}};
|
||||
// from the transitions it has and from those of the norms; the square root of the length, as a length of nought would hold the
|
||||
// step at nought
|
||||
// from the transitions it has and from those of the norms; the square root of the length, as a length of nought would hold the step
|
||||
// at nought
|
||||
auto const radius{std::max(kTraceRadiusMin, Design.vertices[Vertex].radius)};
|
||||
auto const allowed{std::max(1.0, std::min(speed, speed_limit(radius, Design.norms)))};
|
||||
// started below the longest transition: above it the length is held, the fit no longer moving it
|
||||
auto const started = [&](double const Length) { return std::sqrt(std::max(std::min(Length, 0.9 * std::min(radius * deflection, longest_transition(speed, radius, Design.norms))), 1.0)); };
|
||||
auto const norm{std::max(recommend(allowed, radius, Design.norms).transition, 10.0)};
|
||||
auto const fitted_from = [&](std::vector<std::vector<double>> const &Starts, std::vector<double> &Best) {
|
||||
auto lowest{std::numeric_limits<double>::max()};
|
||||
for (auto parameters : Starts)
|
||||
if (auto const cost{least_squares(parameters, residuals)}; cost < lowest)
|
||||
{
|
||||
lowest = cost;
|
||||
Best = parameters;
|
||||
}
|
||||
return lowest;
|
||||
};
|
||||
std::vector<double> best;
|
||||
auto lowest{std::numeric_limits<double>::max()};
|
||||
for (auto const length : {std::max(Design.vertices[Vertex].transition_in, 10.0), std::max(recommend(allowed, radius, Design.norms).transition, 10.0)})
|
||||
auto const alike{fitted_from({{std::log(radius), started(std::max(Design.vertices[Vertex].transition_in, 10.0))}, {std::log(radius), started(norm)}}, best)};
|
||||
// the two apart, from the ones alike and from one short and the other long: taken where they keep to the points clearly closer, not
|
||||
// only to points clicked a little off
|
||||
constexpr double kApart{0.8};
|
||||
if (apart && false == best.empty())
|
||||
{
|
||||
// started below the longest transition: above it the length is held, the fit no longer moving it
|
||||
std::vector<double> parameters{std::log(radius), std::sqrt(std::min(length, 0.9 * std::min(radius * deflection, longest_transition(speed, radius, Design.norms))))};
|
||||
if (auto const cost{least_squares(parameters, residuals)}; cost < lowest)
|
||||
{
|
||||
lowest = cost;
|
||||
best = parameters;
|
||||
}
|
||||
std::vector<double> two;
|
||||
if (fitted_from({{best[0], best[1], best[1]}, {std::log(radius), started(10.0), started(norm)}, {std::log(radius), started(norm), started(10.0)}}, two) < kApart * alike)
|
||||
best = two;
|
||||
}
|
||||
if (best.empty())
|
||||
return;
|
||||
design fitted;
|
||||
shaped(best, fitted);
|
||||
auto &curve{fitted.vertices[Vertex]};
|
||||
plain_curve(curve, std::max(kTraceRadiusMin, std::round(curve.radius)), std::round(curve.transition_in), speed, fitted.norms);
|
||||
auto const in{std::round(curve.transition_in)};
|
||||
auto const out{std::round(curve.transition_out)};
|
||||
plain_curve(curve, std::max(kTraceRadiusMin, std::round(curve.radius)), std::min(in, out), speed, fitted.norms);
|
||||
curve.transition_in = in;
|
||||
curve.transition_out = out;
|
||||
Design = fitted;
|
||||
}
|
||||
|
||||
@@ -3931,7 +4063,7 @@ std::optional<trace_stretch> fit_arcs(design const &Single, std::vector<glm::dve
|
||||
fit_compound(Shaped, 0, Points);
|
||||
auto const laid{compute(Shaped)};
|
||||
if (std::any_of(laid.elements.begin(), laid.elements.end(), [](element const &Element) {
|
||||
return Element.vertex == 0 && Element.curvature_start == Element.curvature_end && Element.curvature_start > 0.0 && Element.length < 10.0;
|
||||
return Element.vertex == 0 && Element.curvature_start == Element.curvature_end && Element.curvature_start > 0.0 && Element.length < kShortestArc;
|
||||
}))
|
||||
return std::nullopt;
|
||||
auto const stretch{stretch_of(Shaped, Points)};
|
||||
@@ -4108,30 +4240,22 @@ std::optional<trace_stretch> fit_chain(design const &Base, std::vector<glm::dvec
|
||||
origins.push_back(start + across * Parameters[2 * j + 1]);
|
||||
}
|
||||
// where two lines cross: along the first one, along the second
|
||||
auto const crossing = [](glm::dvec2 const &A, glm::dvec2 const &Along, glm::dvec2 const &B, glm::dvec2 const &Other, double &First, double &Second) {
|
||||
auto const determinant{cross(Along, Other)};
|
||||
if (std::abs(determinant) < 1e-9)
|
||||
return false;
|
||||
First = cross(B - A, Other) / determinant;
|
||||
Second = cross(B - A, Along) / determinant;
|
||||
return true;
|
||||
};
|
||||
std::vector<glm::dvec2> vertices;
|
||||
double first;
|
||||
double along;
|
||||
if (false == crossing(start, startdirection, origins.front(), directions.front(), first, along) || first <= 0.0)
|
||||
if (false == geometry::lines_cross(start, startdirection, origins.front(), directions.front(), first, along) || first <= 0.0)
|
||||
return false;
|
||||
vertices.push_back(start + startdirection * first);
|
||||
for (std::size_t j = 0; j + 1 < lines; ++j)
|
||||
{
|
||||
double a;
|
||||
double b;
|
||||
if (false == crossing(origins[j], directions[j], origins[j + 1], directions[j + 1], a, b))
|
||||
if (false == geometry::lines_cross(origins[j], directions[j], origins[j + 1], directions[j + 1], a, b))
|
||||
return false;
|
||||
vertices.push_back(origins[j] + directions[j] * a);
|
||||
}
|
||||
double last;
|
||||
if (false == crossing(end, -enddirection, origins.back(), directions.back(), last, along) || last <= 0.0)
|
||||
if (false == geometry::lines_cross(end, -enddirection, origins.back(), directions.back(), last, along) || last <= 0.0)
|
||||
return false;
|
||||
vertices.push_back(end - enddirection * last);
|
||||
// each vertex on from the one before along the line between them
|
||||
@@ -4210,6 +4334,364 @@ std::optional<trace_stretch> fit_chain(design const &Base, std::vector<glm::dvec
|
||||
|
||||
} // namespace
|
||||
|
||||
trace_stretch fit_going_on(design const &Base, std::vector<glm::dvec2> const &Points)
|
||||
{
|
||||
trace_stretch fit;
|
||||
if (Points.empty() || false == Base.start_curved || Base.start_radius <= 0.0 || glm::length(Base.start_direction) < 1e-9)
|
||||
return fit;
|
||||
auto const speed{std::max(1.0, Base.speed)};
|
||||
auto const start{plan_of(Base.start)};
|
||||
auto const heading{glm::normalize(Base.start_direction)};
|
||||
auto const side{Base.start_turn > 0 ? 1.0 : -1.0};
|
||||
auto const from{1.0 / Base.start_radius};
|
||||
auto const fromcant{std::max(0.0, Base.start_cant)};
|
||||
// going on from the diverging track of a switch: its first arc of the radius of the switch, with no transition before it
|
||||
auto const held{Base.start_held};
|
||||
auto const target{Points.back()};
|
||||
// none where the last point lies on the other side of the heading
|
||||
auto const offset{target - start};
|
||||
auto const lateral{glm::dot(offset, perpendicular(heading) * side)};
|
||||
auto const chord{glm::length(offset)};
|
||||
if (lateral < 1e-3 || chord < 1.0)
|
||||
return fit;
|
||||
// the shape: the transition from the curvature of the adjoining track to the first arc, the arcs with the transitions between them,
|
||||
// the transition from the last one to the straight, which goes on to the last point where the points clicked after the curve show it
|
||||
struct shape
|
||||
{
|
||||
double in{0.0};
|
||||
std::vector<double> curvatures;
|
||||
std::vector<double> arcs;
|
||||
std::vector<double> between;
|
||||
double out{0.0};
|
||||
double straight{0.0};
|
||||
};
|
||||
auto const curvature_of = [](double const Logarithm) { return 1.0 / std::exp(std::clamp(Logarithm, std::log(kTraceRadiusMin), std::log(kTraceRadiusMax))); };
|
||||
auto const segments_of = [&](shape const &Shape) {
|
||||
std::vector<std::array<double, 3>> segments{{from, Shape.curvatures.front(), Shape.in}};
|
||||
for (std::size_t i = 0; i < Shape.curvatures.size(); ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
segments.push_back({Shape.curvatures[i - 1], Shape.curvatures[i], Shape.between[i - 1]});
|
||||
segments.push_back({Shape.curvatures[i], Shape.curvatures[i], Shape.arcs[i]});
|
||||
}
|
||||
segments.push_back({Shape.curvatures.back(), 0.0, Shape.out});
|
||||
segments.push_back({0.0, 0.0, Shape.straight});
|
||||
return segments;
|
||||
};
|
||||
auto const turn_of = [&](shape const &Shape) {
|
||||
double turn{0.0};
|
||||
for (auto const &[first, last, length] : segments_of(Shape))
|
||||
turn += 0.5 * (first + last) * length;
|
||||
return turn;
|
||||
};
|
||||
// where the shape ends, heading which way
|
||||
auto const end_of = [&](shape const &Shape, glm::dvec2 &End, glm::dvec2 &Leaving) {
|
||||
glm::dvec2 origin{start};
|
||||
glm::dvec2 direction{heading};
|
||||
for (auto const &[first, last, length] : segments_of(Shape))
|
||||
{
|
||||
if (length < 1e-9)
|
||||
continue;
|
||||
auto const local{spiral_point(first, last, length, length)};
|
||||
origin += direction * local.x + perpendicular(direction) * side * local.y;
|
||||
direction = turned(direction, side * local.angle);
|
||||
}
|
||||
End = origin;
|
||||
Leaving = direction;
|
||||
};
|
||||
// the line of the shape in the plan, a point every half a metre about
|
||||
auto const line_of = [&](shape const &Shape) {
|
||||
plan_line line;
|
||||
glm::dvec2 origin{start};
|
||||
glm::dvec2 direction{heading};
|
||||
double chainage{0.0};
|
||||
line.points.push_back(origin);
|
||||
line.chainages.push_back(0.0);
|
||||
for (auto const &[first, last, length] : segments_of(Shape))
|
||||
{
|
||||
if (length < 1e-6)
|
||||
continue;
|
||||
glm::dvec2 const across{perpendicular(direction) * side};
|
||||
int const count{std::clamp(static_cast<int>(std::ceil(length / 0.5)), 1, 400)};
|
||||
for (int i = 1; i <= count; ++i)
|
||||
{
|
||||
auto const local{spiral_point(first, last, length, length * i / count)};
|
||||
line.points.push_back(origin + direction * local.x + across * local.y);
|
||||
line.chainages.push_back(chainage + length * i / count);
|
||||
}
|
||||
auto const local{spiral_point(first, last, length, length)};
|
||||
origin += direction * local.x + across * local.y;
|
||||
direction = turned(direction, side * local.angle);
|
||||
chainage += length;
|
||||
}
|
||||
return line;
|
||||
};
|
||||
// the shape of the parameters: the square roots of the lengths of the transitions and of the straight, then for each arc but the
|
||||
// last the logarithm of its radius, the square root of its length and of the transition after it; the last arc, its radius and
|
||||
// length, Last. Held, the transition in is none and the first arc of the radius of the switch; a single arc so is given the square
|
||||
// root of the straight after it in Last in place of its radius
|
||||
auto const single_held = [&](std::vector<double> const &Parameters) { return held && Parameters.size() == 3; };
|
||||
auto const shaped = [&](std::vector<double> const &Parameters, std::array<double, 2> const &Last) {
|
||||
shape result;
|
||||
result.in = held ? 0.0 : Parameters[0] * Parameters[0];
|
||||
result.out = Parameters[1] * Parameters[1];
|
||||
result.straight = Parameters[2] * Parameters[2];
|
||||
for (std::size_t i = 3; i + 2 < Parameters.size(); i += 3)
|
||||
{
|
||||
result.curvatures.push_back(held && i == 3 ? from : curvature_of(Parameters[i]));
|
||||
result.arcs.push_back(Parameters[i + 1] * Parameters[i + 1]);
|
||||
result.between.push_back(Parameters[i + 2] * Parameters[i + 2]);
|
||||
}
|
||||
if (single_held(Parameters))
|
||||
{
|
||||
result.curvatures.push_back(from);
|
||||
result.straight = Last[0] * Last[0];
|
||||
}
|
||||
else
|
||||
result.curvatures.push_back(curvature_of(Last[0]));
|
||||
result.arcs.push_back(Last[1] * Last[1]);
|
||||
return result;
|
||||
};
|
||||
// the radius and the length of the last arc bringing the end to the last point, the rest given: by Newton from the ones found last
|
||||
auto const reach = [&](std::vector<double> const &Parameters, std::array<double, 2> &Solution) {
|
||||
auto const miss = [&](std::array<double, 2> const &At, glm::dvec2 &Miss) {
|
||||
auto const trial{shaped(Parameters, At)};
|
||||
if (turn_of(trial) > kPi - 1e-2)
|
||||
return false;
|
||||
glm::dvec2 end;
|
||||
glm::dvec2 leaving;
|
||||
end_of(trial, end, leaving);
|
||||
Miss = end - target;
|
||||
return true;
|
||||
};
|
||||
constexpr double kStep{1e-6};
|
||||
auto at{Solution};
|
||||
for (int i = 0; i < 40; ++i)
|
||||
{
|
||||
glm::dvec2 here;
|
||||
if (false == miss(at, here))
|
||||
return false;
|
||||
if (glm::length(here) < 1e-5)
|
||||
{
|
||||
Solution = at;
|
||||
return true;
|
||||
}
|
||||
glm::dvec2 radial;
|
||||
glm::dvec2 along;
|
||||
auto byradius{at};
|
||||
byradius[0] += kStep;
|
||||
auto byarc{at};
|
||||
byarc[1] += kStep;
|
||||
if (false == miss(byradius, radial) || false == miss(byarc, along))
|
||||
return false;
|
||||
radial = (radial - here) / kStep;
|
||||
along = (along - here) / kStep;
|
||||
auto const determinant{radial.x * along.y - radial.y * along.x};
|
||||
if (std::abs(determinant) < 1e-12)
|
||||
return false;
|
||||
auto const stepradius{(along.x * here.y - here.x * along.y) / determinant};
|
||||
auto const steparc{(here.x * radial.y - radial.x * here.y) / determinant};
|
||||
// half a step of the logarithm of the radius at most, the curve not swinging round; of the root of the straight after an arc
|
||||
// held two
|
||||
auto const damped{std::min(1.0, (single_held(Parameters) ? 2.0 : 0.5) / std::max(std::abs(stepradius), 1e-12))};
|
||||
at[0] += stepradius * damped;
|
||||
if (single_held(Parameters))
|
||||
at[0] = std::abs(at[0]);
|
||||
at[1] = std::abs(at[1] + steparc * damped);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// the transitions as long as the norms ask for where the points don't show them shorter: a metre short of it weighed as a millimetre
|
||||
// off a point, which only chooses among the shapes too few points leave open
|
||||
constexpr double kShort{0.001};
|
||||
std::array<double, 2> solution{};
|
||||
residuals_of const residuals{[&](std::vector<double> const &Parameters, std::vector<double> &Residuals) {
|
||||
auto at{solution};
|
||||
if (false == reach(Parameters, at))
|
||||
return false;
|
||||
solution = at;
|
||||
auto const trial{shaped(Parameters, at)};
|
||||
auto const line{line_of(trial)};
|
||||
Residuals.clear();
|
||||
for (std::size_t i = 0; i + 1 < Points.size(); ++i)
|
||||
Residuals.push_back(project_on(line, Points[i]).offset);
|
||||
auto const need_between = [&](double const First, double const Second) {
|
||||
auto const sharper{1.0 / std::max({First, Second, 1e-9})};
|
||||
auto const allowed{std::max(1.0, std::min(speed, speed_limit(sharper, Base.norms)))};
|
||||
return std::min(between_curves(allowed, First, 0.0, Second, 0.0, Base.norms), 0.9 * longest_transition(speed, sharper, Base.norms));
|
||||
};
|
||||
auto const last{1.0 / trial.curvatures.back()};
|
||||
auto const allowed{std::max(1.0, std::min(speed, speed_limit(last, Base.norms)))};
|
||||
Residuals.push_back(kShort * std::max(0.0, need_between(from, trial.curvatures.front()) - trial.in));
|
||||
for (std::size_t i = 1; i < trial.curvatures.size(); ++i)
|
||||
Residuals.push_back(kShort * std::max(0.0, need_between(trial.curvatures[i - 1], trial.curvatures[i]) - trial.between[i - 1]));
|
||||
Residuals.push_back(kShort * std::max(0.0, std::min(transition_length(allowed, last, 0.0, Base.norms), 0.9 * longest_transition(speed, last, Base.norms)) - trial.out));
|
||||
// the curve ending at the last point where the points don't show a straight after it
|
||||
Residuals.push_back(kShort * trial.straight);
|
||||
return true;
|
||||
}};
|
||||
std::vector<double> best;
|
||||
std::array<double, 2> bestsolution{};
|
||||
auto lowest{std::numeric_limits<double>::max()};
|
||||
// the square root of the straight to start from: off nought, where the fit couldn't move it
|
||||
constexpr double kSeed{0.5};
|
||||
// an arc of a compound curve shorter than the shortest one, a sharp one squeezed between two long transitions to bring the curve to
|
||||
// the points (R 21 m for a millimetre after R 229 m): no curve to lay, as the fit of the arcs through the points has it. The first arc
|
||||
// going on from a switch at its radius may be as short as it is
|
||||
auto const kinked = [&](shape const &Shape) {
|
||||
if (Shape.curvatures.size() < 2)
|
||||
return false;
|
||||
for (std::size_t i = held ? 1 : 0; i < Shape.arcs.size(); ++i)
|
||||
if (Shape.arcs[i] < kShortestArc)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
// the parameters fitted: all but those of a curve going on from a switch which the shape doesn't take, the transition in and the
|
||||
// radius of the first arc, or the straight after a single arc, which the reach finds then
|
||||
auto const attempt = [&](std::vector<double> Parameters, std::array<double, 2> const &Start) {
|
||||
solution = Start;
|
||||
std::vector<std::size_t> loose;
|
||||
for (std::size_t i = 0; i < Parameters.size(); ++i)
|
||||
if (false == held || (i != 0 && i != (single_held(Parameters) ? 2u : 3u)))
|
||||
loose.push_back(i);
|
||||
auto const whole = [&](std::vector<double> const &Loose) {
|
||||
auto result{Parameters};
|
||||
for (std::size_t i = 0; i < loose.size(); ++i)
|
||||
result[loose[i]] = Loose[i];
|
||||
return result;
|
||||
};
|
||||
std::vector<double> fitted;
|
||||
for (auto const i : loose)
|
||||
fitted.push_back(Parameters[i]);
|
||||
residuals_of const some{[&](std::vector<double> const &Loose, std::vector<double> &Residuals) { return residuals(whole(Loose), Residuals); }};
|
||||
if (auto const cost{least_squares(fitted, some)}; cost < lowest && false == kinked(shaped(whole(fitted), solution)))
|
||||
{
|
||||
lowest = cost;
|
||||
best = whole(fitted);
|
||||
bestsolution = solution;
|
||||
}
|
||||
};
|
||||
// one arc, from two guesses of how far it turns: as the points head at the end, Base ending that way, and as the circle going on
|
||||
// from the start through the last point does; held at the radius of a switch, as far round as that, the straight after it the rest
|
||||
// of the way
|
||||
std::vector<double> turns;
|
||||
if (glm::length(Base.end_direction) > 1e-9)
|
||||
{
|
||||
auto const ending{glm::normalize(Base.end_direction)};
|
||||
auto turn{std::atan2(side * cross(heading, ending), glm::dot(heading, ending))};
|
||||
if (turn < 0.0)
|
||||
turn += 2.0 * kPi;
|
||||
if (turn > 1e-3 && turn < kPi - 2e-2)
|
||||
turns.push_back(turn);
|
||||
}
|
||||
turns.push_back(2.0 * std::asin(std::clamp(lateral / chord, 0.0, 1.0)));
|
||||
for (auto const turn : turns)
|
||||
for (auto const length : {10.0, 25.0})
|
||||
{
|
||||
if (held)
|
||||
{
|
||||
auto const arc{std::max(1.0, Base.start_radius * turn - 0.5 * length)};
|
||||
attempt({0.0, std::sqrt(length), 0.0}, {std::sqrt(std::max(1.0, chord - arc)), std::sqrt(arc)});
|
||||
continue;
|
||||
}
|
||||
auto const radius{std::clamp(chord / std::max(1e-3, 2.0 * std::sin(0.5 * turn)), kTraceRadiusMin, kTraceRadiusMax)};
|
||||
attempt({std::sqrt(length), std::sqrt(length), kSeed}, {std::log(radius), std::sqrt(std::max(1.0, radius * turn - length))});
|
||||
}
|
||||
// Abreast: the shape ends abreast of the last point, not on it
|
||||
auto const settle = [&](shape const &Found, bool const Abreast) -> std::optional<trace_stretch> {
|
||||
glm::dvec2 end;
|
||||
glm::dvec2 leaving;
|
||||
end_of(Found, end, leaving);
|
||||
if (false == Abreast && glm::distance(end, target) > 0.01)
|
||||
return std::nullopt;
|
||||
// laid as the design lays a curve going on from an adjoining one: its vertex where the straights cross, its radii scaled to make
|
||||
// the tangent before it reach the start, which they do
|
||||
design laid{Base};
|
||||
laid.end = {end.x, Base.end.y, end.y};
|
||||
laid.end_direction = leaving;
|
||||
laid.end_curved = false;
|
||||
laid.end_reserve = 0.0;
|
||||
laid.vertices.assign(1, vertex{});
|
||||
glm::dvec2 meeting;
|
||||
if (false == tangent_intersection(laid, meeting))
|
||||
return std::nullopt;
|
||||
auto &curve{laid.vertices.front()};
|
||||
curve.position = meeting;
|
||||
curve.radius = 1.0 / Found.curvatures.front();
|
||||
curve.transition_in = Found.in;
|
||||
curve.transition_out = Found.out;
|
||||
if (Found.curvatures.size() > 1)
|
||||
{
|
||||
// the shares of the arcs by how far they turn
|
||||
curve.compound = true;
|
||||
curve.share = std::max(1e-6, Found.arcs.front() * Found.curvatures.front());
|
||||
for (std::size_t i = 1; i < Found.curvatures.size(); ++i)
|
||||
{
|
||||
compound_arc arc;
|
||||
arc.radius = 1.0 / Found.curvatures[i];
|
||||
arc.transition = Found.between[i - 1];
|
||||
arc.share = std::max(1e-6, Found.arcs[i] * Found.curvatures[i]);
|
||||
curve.arcs.push_back(arc);
|
||||
}
|
||||
}
|
||||
curve.cant = Found.curvatures.size() > 1 ? 0.0 : curved_cant(curve.radius, Found.in, Found.out, fromcant, speed, Base.norms);
|
||||
auto stretch{stretch_of(laid, Points)};
|
||||
if (stretch.has_value())
|
||||
{
|
||||
stretch->leaving = leaving;
|
||||
stretch->ending = end;
|
||||
}
|
||||
return stretch;
|
||||
};
|
||||
std::optional<trace_stretch> one;
|
||||
if (false == best.empty())
|
||||
one = settle(shaped(best, bestsolution), false);
|
||||
// held, the arc of the switch alone, as far round as abreast of the last point seen from its centre and ending there: as the points
|
||||
// clicked along it show it, no transition out of it bringing it to a point on it
|
||||
if (held)
|
||||
{
|
||||
auto const centre{start + perpendicular(heading) * side * Base.start_radius};
|
||||
auto const outward{target - centre};
|
||||
auto sweep{std::fmod(side * (std::atan2(outward.y, outward.x) - std::atan2(start.y - centre.y, start.x - centre.x)), 2.0 * kPi)};
|
||||
if (sweep < 0.0)
|
||||
sweep += 2.0 * kPi;
|
||||
if (glm::length(outward) > 0.5 && sweep > 1e-3 && sweep < kPi - 2e-2)
|
||||
{
|
||||
shape arc;
|
||||
arc.curvatures = {from};
|
||||
arc.arcs = {Base.start_radius * sweep};
|
||||
if (auto const alone{settle(arc, true)}; alone.has_value() && (false == one.has_value() || alone->rms < one->rms))
|
||||
one = alone;
|
||||
}
|
||||
}
|
||||
// two arcs where one doesn't keep to the points and there are enough of them to tell: a flatter one going on from the end, even a
|
||||
// straight about, then a sharper one, as where the line turns hard at the last points. Going on from a switch, the first one of its
|
||||
// radius, then a flatter or a sharper one
|
||||
if (Points.size() >= 3 && (false == one.has_value() || one->worst > kPointsWithin))
|
||||
{
|
||||
auto const single{best.empty() || held ? std::array<double, 2>{std::log(std::clamp(chord, kTraceRadiusMin, kTraceRadiusMax)), std::sqrt(chord)} : bestsolution};
|
||||
auto const radius{std::exp(single[0])};
|
||||
lowest = std::numeric_limits<double>::max();
|
||||
best.clear();
|
||||
std::vector<double> const firsts{held ? std::vector<double>{Base.start_radius} : std::vector<double>{Base.start_radius, 2.0 * Base.start_radius, 3000.0}};
|
||||
std::vector<double> const seconds{held ? std::vector<double>{0.5 * Base.start_radius, 2.0 * Base.start_radius} : std::vector<double>{0.5 * radius}};
|
||||
auto const shares{held ? std::array<double, 2>{0.15, 0.5} : std::array<double, 2>{0.4, 0.7}};
|
||||
for (auto const first : firsts)
|
||||
for (auto const second : seconds)
|
||||
for (auto const share : shares)
|
||||
{
|
||||
auto const along{std::max(1.0, share * chord)};
|
||||
attempt({std::sqrt(10.0), std::sqrt(10.0), kSeed, std::log(first), std::sqrt(along), std::sqrt(10.0)}, {std::log(std::max(kTraceRadiusMin, second)), std::sqrt(std::max(1.0, (1.0 - share) * chord))});
|
||||
}
|
||||
if (false == best.empty())
|
||||
if (auto const two{settle(shaped(best, bestsolution), false)}; two.has_value() && (false == one.has_value() || two->rms < 0.6 * one->rms))
|
||||
one = two;
|
||||
}
|
||||
if (one.has_value())
|
||||
fit = *one;
|
||||
return fit;
|
||||
}
|
||||
|
||||
trace_stretch fit_trace_stretch(design const &Base, std::vector<glm::dvec2> const &Points, int const Radii, bool const Straights)
|
||||
{
|
||||
trace_stretch chosen;
|
||||
@@ -4258,19 +4740,38 @@ trace_stretch fit_trace_stretch(design const &Base, std::vector<glm::dvec2> cons
|
||||
int const side{cross(startdirection, enddirection) > 0.0 ? 1 : -1};
|
||||
auto const fromstart{Base.start_curved && Base.start_radius > 0.0 && Base.start_turn == side};
|
||||
auto const curved{fromstart || (Base.end_curved && Base.end_radius > 0.0 && Base.end_turn == side)};
|
||||
if (curved)
|
||||
// going on from the diverging track of a switch: at its radius, the transition on the other side of the arc making it begin there
|
||||
auto const held{fromstart ? Base.start_held : Base.end_held};
|
||||
if (curved && held)
|
||||
{
|
||||
curve.radius = fromstart ? Base.start_radius : Base.end_radius;
|
||||
one = stretch_of(single, Points);
|
||||
}
|
||||
else if (curved)
|
||||
{
|
||||
curve.radius = fromstart ? Base.start_radius : Base.end_radius;
|
||||
fit_curved(single, 0, Points, fromstart);
|
||||
one = stretch_of(single, Points);
|
||||
// a compound curve where one radius doesn't keep to points enough for two; where it doesn't reach the end at all, as where a
|
||||
// curve sharper than the one it goes on from ends the stretch, of two radii from fewer points too. Its transition from the curve
|
||||
// of the adjoining track as long as the norms ask for at least, the curvature not jumping at the joint
|
||||
auto const loose{one.has_value() && (one->rms > kStretchClose || one->worst > kPointsWithin)};
|
||||
if (one.has_value() && Radii != 1 && (Radii > 1 || loose))
|
||||
auto const missing{false == one.has_value()};
|
||||
if (Radii != 1 && (missing || ((Radii > 1 || loose) && Points.size() >= 4)))
|
||||
if (auto const fit{fit_trace(single, 0, Points, Radii > 1 ? Radii : 2)}; fit.valid && fit.fitted.compound)
|
||||
{
|
||||
auto several{single};
|
||||
several.vertices.front() = fit.fitted;
|
||||
fit_compound(several, 0, Points);
|
||||
if (auto const compound{stretch_of(several, Points)}; compound.has_value() && (Radii > 1 || compound->rms < 0.75 * one->rms))
|
||||
auto &shape{several.vertices.front()};
|
||||
auto const joining{fromstart ? shape.radius : shape.arcs.empty() ? shape.radius : shape.arcs.back().radius};
|
||||
auto const adjoining{fromstart ? Base.start_radius : Base.end_radius};
|
||||
auto const allowed{std::max(1.0, std::min(speed, speed_limit(std::min(joining, adjoining), Base.norms)))};
|
||||
auto const needed{std::min(between_curves(allowed, 1.0 / adjoining, std::max(0.0, fromstart ? Base.start_cant : Base.end_cant), 1.0 / std::max(1.0, joining), 0.0, Base.norms),
|
||||
0.9 * longest_transition(speed, adjoining, Base.norms))};
|
||||
auto &between{fromstart ? shape.transition_in : shape.transition_out};
|
||||
between = std::max(between, std::round(needed));
|
||||
if (auto const compound{stretch_of(several, Points)}; compound.has_value() && (Radii > 1 || false == one.has_value() || compound->rms < 0.75 * one->rms))
|
||||
one = compound;
|
||||
}
|
||||
}
|
||||
@@ -4310,9 +4811,12 @@ trace_stretch fit_trace_stretch(design const &Base, std::vector<glm::dvec2> cons
|
||||
fit_compound(several, 0, Points);
|
||||
compound = stretch_of(several, Points);
|
||||
}
|
||||
if (auto const more{fit_arcs(single, Points, Radii > 1 ? Radii : 8)}; more.has_value() && (false == compound.has_value() || more->worst < compound->worst))
|
||||
// no more radii than half the points: a few points clicked a little off don't show a curve of many radii
|
||||
if (auto const more{fit_arcs(single, Points, Radii > 1 ? Radii : std::clamp(static_cast<int>(Points.size()) / 2, 2, 8))}; more.has_value() && (false == compound.has_value() || more->worst < compound->worst))
|
||||
compound = more;
|
||||
if (compound.has_value() && (Radii > 1 || compound->rms < 0.75 * one->rms || (one->worst > kPointsWithin && compound->worst < one->worst)))
|
||||
// one with an arc as flat as a straight between its curves only where it keeps to all the points much closer: not to come
|
||||
// nearer to a point clicked a little off, sharp curves either side of a straight
|
||||
if (compound.has_value() && (Radii > 1 || compound->rms < 0.75 * one->rms || (one->worst > kPointsWithin && compound->worst < one->worst && (compound->vertices.empty() || false == flattened(compound->vertices.front())))))
|
||||
one = compound;
|
||||
}
|
||||
}
|
||||
@@ -4490,7 +4994,7 @@ void stretch_fitter::work()
|
||||
m_asked.pop_back();
|
||||
auto const asked{*m_fitting};
|
||||
lock.unlock();
|
||||
auto fit{fit_trace_stretch(asked.base, asked.points, asked.radii, asked.straights)};
|
||||
auto fit{asked.going ? fit_going_on(asked.base, asked.points) : fit_trace_stretch(asked.base, asked.points, asked.radii, asked.straights)};
|
||||
lock.lock();
|
||||
constexpr std::size_t kKept{16};
|
||||
m_results.insert(m_results.begin(), {asked.key, std::move(fit)});
|
||||
|
||||
Reference in New Issue
Block a user