mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 05:09:20 +02:00
feat(editor): vertical profile editor, grade lines kept in the scenery
This commit is contained in:
@@ -752,20 +752,6 @@ result fit_between(design const &Design)
|
||||
auto const gradeafter{(r.profile[i + 1].elevation - point.elevation) / (r.profile[i + 1].chainage - point.chainage)};
|
||||
point.tangent = point.radius * std::abs(gradeafter - gradebefore) / 2.0;
|
||||
}
|
||||
for (std::size_t i = 0; i + 1 < r.profile.size(); ++i)
|
||||
{
|
||||
if (r.profile[i].tangent + r.profile[i + 1].tangent > r.profile[i + 1].chainage - r.profile[i].chainage + 1e-6)
|
||||
r.warnings.emplace_back(format("Vertical curves overlap between chainage %.0f m and %.0f m", r.profile[i].chainage, r.profile[i + 1].chainage));
|
||||
}
|
||||
if (r.profile.size() >= 2)
|
||||
{
|
||||
auto const first{(r.profile[1].elevation - r.profile[0].elevation) / (r.profile[1].chainage - r.profile[0].chainage)};
|
||||
auto const last{(r.profile.back().elevation - r.profile[r.profile.size() - 2].elevation) / (r.profile.back().chainage - r.profile[r.profile.size() - 2].chainage)};
|
||||
if (std::abs(first - Design.start_grade) > 0.0005)
|
||||
r.warnings.emplace_back(format("Grade break at the start: %.1f per mille against %.1f of the adjoining track", first * 1000.0, Design.start_grade * 1000.0));
|
||||
if (std::abs(last - Design.end_grade) > 0.0005)
|
||||
r.warnings.emplace_back(format("Grade break at the end: %.1f per mille against %.1f of the adjoining track", last * 1000.0, Design.end_grade * 1000.0));
|
||||
}
|
||||
if (Design.start_radius > 0.0 && Design.start_radius < 10000.0)
|
||||
r.warnings.emplace_back(format("The adjoining track at the start is curved (R %.0f m), the curvature changes abruptly", Design.start_radius));
|
||||
if (Design.end_radius > 0.0 && Design.end_radius < 10000.0)
|
||||
|
||||
422
editor/editorProfile.cpp
Normal file
422
editor/editorProfile.cpp
Normal file
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "editor/editorProfile.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <optional>
|
||||
|
||||
namespace profile
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename... Args>
|
||||
std::string format(char const *Format, Args... Arguments)
|
||||
{
|
||||
char text[256];
|
||||
std::snprintf(text, sizeof(text), Format, Arguments...);
|
||||
return text;
|
||||
}
|
||||
|
||||
double polyline(line const &Line, double const Chainage)
|
||||
{
|
||||
auto const &points{Line.points};
|
||||
if (points.empty())
|
||||
return 0.0;
|
||||
if (points.size() == 1 || Chainage <= points.front().chainage)
|
||||
return points.front().elevation + (points.size() > 1 ? grade_after(Line, 0) * (Chainage - points.front().chainage) : 0.0);
|
||||
if (Chainage >= points.back().chainage)
|
||||
return points.back().elevation + grade_after(Line, points.size() - 2) * (Chainage - points.back().chainage);
|
||||
auto const next{std::upper_bound(points.begin(), points.end(), Chainage, [](double const Value, pvi const &Point) { return Value < Point.chainage; })};
|
||||
auto const index{static_cast<std::size_t>(std::distance(points.begin(), next)) - 1};
|
||||
return points[index].elevation + grade_after(Line, index) * (Chainage - points[index].chainage);
|
||||
}
|
||||
|
||||
bool overlaps(double const From, double const To, context::zone const &Zone)
|
||||
{
|
||||
return From < Zone.to - 1e-6 && To > Zone.from + 1e-6;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
double grade_after(line const &Line, std::size_t const Index)
|
||||
{
|
||||
auto const &points{Line.points};
|
||||
if (Index + 1 >= points.size())
|
||||
return 0.0;
|
||||
auto const run{points[Index + 1].chainage - points[Index].chainage};
|
||||
return run > 1e-9 ? (points[Index + 1].elevation - points[Index].elevation) / run : 0.0;
|
||||
}
|
||||
|
||||
double required_radius(line const &Line)
|
||||
{
|
||||
return std::max(Line.norms.radius_min, Line.norms.radius_factor * Line.speed * Line.speed);
|
||||
}
|
||||
|
||||
void assign_radii(line &Line)
|
||||
{
|
||||
auto const required{std::ceil(required_radius(Line) / 10.0) * 10.0};
|
||||
for (std::size_t k = 1; k + 1 < Line.points.size(); ++k)
|
||||
{
|
||||
auto &point{Line.points[k]};
|
||||
if (false == point.automatic || point.joint)
|
||||
continue;
|
||||
auto const change{std::abs(grade_after(Line, k) - grade_after(Line, k - 1)) * 1000.0};
|
||||
point.radius = change > Line.norms.curve_threshold + 1e-6 ? required : 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
void complete(line &Line, context const &Context)
|
||||
{
|
||||
auto &points{Line.points};
|
||||
points.erase(std::remove_if(points.begin(), points.end(), [](pvi const &Point) { return Point.joint; }), points.end());
|
||||
if (points.size() < 2)
|
||||
return;
|
||||
bool const start{Line.join_start && Context.start_joined};
|
||||
bool const end{Line.join_end && Context.end_joined};
|
||||
if (start)
|
||||
points.front().elevation = Context.start_elevation;
|
||||
if (end)
|
||||
points.back().elevation = Context.end_elevation;
|
||||
|
||||
// the vertical curve of the joint starts right at the end of the route, off the adjoining track
|
||||
auto const joint = [&](pvi const &End, pvi const &Neighbour, double const Grade, double const Direction) -> std::optional<pvi> {
|
||||
auto const run{(Neighbour.chainage - End.chainage) * Direction};
|
||||
if (run < 1.0)
|
||||
return std::nullopt;
|
||||
auto const offset{std::abs(Neighbour.elevation - (End.elevation + Grade * run * Direction))};
|
||||
if (offset / run * 1000.0 <= Line.norms.curve_threshold + 1e-6)
|
||||
return std::nullopt;
|
||||
auto radius{std::ceil(required_radius(Line) / 10.0) * 10.0};
|
||||
double tangent;
|
||||
if (run * run >= 2.0 * radius * offset)
|
||||
tangent = (run - std::sqrt(run * run - 2.0 * radius * offset)) * 0.5;
|
||||
else
|
||||
{
|
||||
tangent = run * 0.5;
|
||||
radius = run * run / (2.0 * offset);
|
||||
}
|
||||
pvi result;
|
||||
result.chainage = End.chainage + tangent * Direction;
|
||||
result.elevation = End.elevation + Grade * tangent * Direction;
|
||||
result.radius = radius;
|
||||
result.automatic = false;
|
||||
result.joint = true;
|
||||
return result;
|
||||
};
|
||||
if (start)
|
||||
if (auto const point{joint(points.front(), points[1], Context.start_grade, 1.0)})
|
||||
points.insert(points.begin() + 1, *point);
|
||||
if (end)
|
||||
if (auto const point{joint(points.back(), points[points.size() - 2], Context.end_grade, -1.0)})
|
||||
points.insert(points.end() - 1, *point);
|
||||
assign_radii(Line);
|
||||
}
|
||||
|
||||
std::vector<curve> curves(line const &Line)
|
||||
{
|
||||
std::vector<curve> result;
|
||||
auto const &points{Line.points};
|
||||
for (std::size_t k = 1; k + 1 < points.size(); ++k)
|
||||
{
|
||||
auto const radius{points[k].radius};
|
||||
if (radius <= 0.0)
|
||||
continue;
|
||||
curve c;
|
||||
c.point = k;
|
||||
c.change = grade_after(Line, k) - grade_after(Line, k - 1);
|
||||
c.tangent = radius * std::abs(c.change) * 0.5;
|
||||
if (c.tangent < 1e-6)
|
||||
continue;
|
||||
c.sagitta = c.tangent * c.tangent / (2.0 * radius);
|
||||
c.start = points[k].chainage - c.tangent;
|
||||
c.end = points[k].chainage + c.tangent;
|
||||
result.push_back(c);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
double elevation(line const &Line, double const Chainage)
|
||||
{
|
||||
auto height{polyline(Line, Chainage)};
|
||||
for (auto const &c : curves(Line))
|
||||
{
|
||||
auto const distance{std::abs(Chainage - Line.points[c.point].chainage)};
|
||||
if (distance >= c.tangent)
|
||||
continue;
|
||||
height += c.change * (c.tangent - distance) * (c.tangent - distance) / (4.0 * c.tangent);
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
double grade(line const &Line, double const Chainage)
|
||||
{
|
||||
auto const &points{Line.points};
|
||||
if (points.size() < 2)
|
||||
return 0.0;
|
||||
std::size_t index{0};
|
||||
while (index + 2 < points.size() && Chainage >= points[index + 1].chainage)
|
||||
++index;
|
||||
auto slope{grade_after(Line, index)};
|
||||
for (auto const &c : curves(Line))
|
||||
{
|
||||
auto const offset{Chainage - points[c.point].chainage};
|
||||
if (std::abs(offset) >= c.tangent)
|
||||
continue;
|
||||
slope -= c.change * (c.tangent - std::abs(offset)) / (2.0 * c.tangent) * (offset < 0.0 ? -1.0 : 1.0);
|
||||
}
|
||||
return slope;
|
||||
}
|
||||
|
||||
double radius_for_sagitta(line const &Line, std::size_t const Point, double const Sagitta)
|
||||
{
|
||||
if (Point == 0 || Point + 1 >= Line.points.size())
|
||||
return 0.0;
|
||||
auto const change{std::abs(grade_after(Line, Point) - grade_after(Line, Point - 1))};
|
||||
if (change < 1e-9)
|
||||
return Line.points[Point].radius;
|
||||
return std::max(0.0, 8.0 * std::abs(Sagitta) / (change * change));
|
||||
}
|
||||
|
||||
std::vector<issue> check(line const &Line, context const &Context)
|
||||
{
|
||||
std::vector<issue> result;
|
||||
auto const &points{Line.points};
|
||||
auto const &norms{Line.norms};
|
||||
auto const add = [&](bool const Error, int const Point, double const Chainage, std::string Text) {
|
||||
result.push_back({Error, Point, Chainage, std::move(Text)});
|
||||
};
|
||||
if (points.size() < 2)
|
||||
{
|
||||
add(true, -1, 0.0, "The grade line needs at least the two ends");
|
||||
return result;
|
||||
}
|
||||
for (std::size_t k = 0; k + 1 < points.size(); ++k)
|
||||
{
|
||||
if (points[k + 1].chainage - points[k].chainage < 0.5)
|
||||
add(true, static_cast<int>(k + 1), points[k + 1].chainage, format("Points %zu and %zu are closer than 0.5 m", k + 1, k + 2));
|
||||
auto const slope{grade_after(Line, k) * 1000.0};
|
||||
if (std::abs(slope) > norms.grade_max + 1e-6)
|
||||
add(false, static_cast<int>(k), points[k].chainage, format("Grade %.2f per mille between points %zu and %zu exceeds %.2f", slope, k + 1, k + 2, norms.grade_max));
|
||||
}
|
||||
|
||||
auto const list{curves(Line)};
|
||||
auto const required{required_radius(Line)};
|
||||
for (std::size_t k = 1; k + 1 < points.size(); ++k)
|
||||
{
|
||||
auto const change{std::abs(grade_after(Line, k) - grade_after(Line, k - 1)) * 1000.0};
|
||||
auto const radius{points[k].radius};
|
||||
if (radius <= 0.0 && change > norms.curve_threshold + 1e-6)
|
||||
add(false, static_cast<int>(k), points[k].chainage, format("Point %zu: change of grade %.2f per mille needs a vertical curve", k + 1, change));
|
||||
else if (radius > 0.0 && radius < required - 1e-6)
|
||||
add(false, static_cast<int>(k), points[k].chainage, format("Point %zu: R %.0f m is below the required %.0f m for %.0f km/h", k + 1, radius, required, Line.speed));
|
||||
if (radius <= 0.0 && change > 1e-3)
|
||||
for (auto const &zone : Context.switches)
|
||||
if (points[k].chainage > zone.from + 1e-3 && points[k].chainage < zone.to - 1e-3)
|
||||
add(true, static_cast<int>(k), points[k].chainage, format("Point %zu: the grade changes inside switch %s", k + 1, zone.name.c_str()));
|
||||
}
|
||||
for (std::size_t i = 0; i < list.size(); ++i)
|
||||
{
|
||||
auto const &c{list[i]};
|
||||
auto const point{static_cast<int>(c.point)};
|
||||
if (c.start < points.front().chainage - 1e-6 || c.end > points.back().chainage + 1e-6)
|
||||
add(true, point, points[c.point].chainage, format("Point %zu: the vertical curve reaches past the end of the route", c.point + 1));
|
||||
if (i + 1 < list.size() && c.end > list[i + 1].start + 1e-6)
|
||||
add(true, point, c.end, format("Vertical curves of points %zu and %zu overlap by %.2f m", c.point + 1, list[i + 1].point + 1, c.end - list[i + 1].start));
|
||||
for (auto const &zone : Context.switches)
|
||||
if (overlaps(c.start, c.end, zone))
|
||||
add(true, point, points[c.point].chainage, format("Point %zu: the vertical curve lies on switch %s", c.point + 1, zone.name.c_str()));
|
||||
}
|
||||
if (norms.element_min > 0.0)
|
||||
{
|
||||
for (std::size_t k = 1; k + 2 < points.size(); ++k)
|
||||
{
|
||||
auto const tangent = [&](std::size_t const Point) {
|
||||
for (auto const &c : list)
|
||||
if (c.point == Point)
|
||||
return c.tangent;
|
||||
return 0.0;
|
||||
};
|
||||
auto const free{(points[k + 1].chainage - tangent(k + 1)) - (points[k].chainage + tangent(k))};
|
||||
if (free < norms.element_min - 1e-6)
|
||||
add(false, static_cast<int>(k), points[k].chainage, format("Constant grade between points %zu and %zu is %.1f m long, less than %.0f m", k + 1, k + 2, std::max(0.0, free), norms.element_min));
|
||||
}
|
||||
}
|
||||
for (auto const &zone : Context.switches)
|
||||
{
|
||||
auto const slope{std::abs(grade(Line, (zone.from + zone.to) * 0.5)) * 1000.0};
|
||||
if (slope > norms.grade_switch_max + 1e-6)
|
||||
add(false, -1, zone.from, format("Grade %.2f per mille over switch %s exceeds %.2f", slope, zone.name.c_str(), norms.grade_switch_max));
|
||||
}
|
||||
for (auto const &fixed : Context.fixed)
|
||||
{
|
||||
auto const departure{elevation(Line, fixed.first) - fixed.second};
|
||||
if (std::abs(departure) > norms.fixed_tolerance)
|
||||
{
|
||||
add(true, -1, fixed.first, format("At %s the grade line departs %.3f m from a fixed point", format_chainage(fixed.first).c_str(), departure));
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto const ends = [&](bool const Atend) {
|
||||
auto const joined{Atend ? Context.end_joined : Context.start_joined};
|
||||
if (false == joined)
|
||||
return;
|
||||
auto const chainage{Atend ? points.back().chainage : points.front().chainage};
|
||||
auto const departure{elevation(Line, chainage) - (Atend ? Context.end_elevation : Context.start_elevation)};
|
||||
if (std::abs(departure) > norms.fixed_tolerance)
|
||||
add(false, Atend ? static_cast<int>(points.size()) - 1 : 0, chainage, format("The %s of the grade line is %.3f m off the adjoining track", Atend ? "end" : "start", departure));
|
||||
auto const slope{grade(Line, chainage)};
|
||||
auto const change{std::abs(slope - (Atend ? Context.end_grade : Context.start_grade)) * 1000.0};
|
||||
if (change > norms.curve_threshold + 1e-6)
|
||||
add(false, Atend ? static_cast<int>(points.size()) - 1 : 0, chainage, format("Change of grade %.2f per mille against the adjoining track at the %s", change, Atend ? "end" : "start"));
|
||||
};
|
||||
ends(false);
|
||||
ends(true);
|
||||
std::stable_sort(result.begin(), result.end(), [](issue const &A, issue const &B) { return A.error && false == B.error; });
|
||||
return result;
|
||||
}
|
||||
|
||||
line recognize(std::vector<double> const &Chainages, std::vector<double> const &Elevations, std::vector<double> const &Grades, recognition const &Options)
|
||||
{
|
||||
line result;
|
||||
auto const count{std::min({Chainages.size(), Elevations.size(), Grades.size()})};
|
||||
if (count < 2)
|
||||
return result;
|
||||
|
||||
struct part
|
||||
{
|
||||
std::size_t first, last;
|
||||
double slope{0.0}, offset{0.0};
|
||||
};
|
||||
auto const fit = [&](part &Part) {
|
||||
double sx{0.0}, sy{0.0}, sxx{0.0}, sxy{0.0};
|
||||
auto const origin{Chainages[Part.first]};
|
||||
auto const n{static_cast<double>(Part.last - Part.first + 1)};
|
||||
for (auto i = Part.first; i <= Part.last; ++i)
|
||||
{
|
||||
auto const x{Chainages[i] - origin};
|
||||
sx += x;
|
||||
sy += Elevations[i];
|
||||
sxx += x * x;
|
||||
sxy += x * Elevations[i];
|
||||
}
|
||||
auto const denominator{n * sxx - sx * sx};
|
||||
Part.slope = std::abs(denominator) > 1e-12 ? (n * sxy - sx * sy) / denominator : 0.0;
|
||||
Part.offset = (sy - Part.slope * sx) / n - Part.slope * origin;
|
||||
};
|
||||
|
||||
std::vector<part> parts;
|
||||
for (std::size_t i = 0; i < count;)
|
||||
{
|
||||
auto j{i};
|
||||
auto low{Grades[i]}, high{Grades[i]};
|
||||
while (j + 1 < count && std::max(high, Grades[j + 1]) - std::min(low, Grades[j + 1]) <= Options.grade_tolerance)
|
||||
{
|
||||
++j;
|
||||
low = std::min(low, Grades[j]);
|
||||
high = std::max(high, Grades[j]);
|
||||
}
|
||||
if (Chainages[j] - Chainages[i] >= Options.minimum_length)
|
||||
{
|
||||
part p{i, j};
|
||||
fit(p);
|
||||
parts.push_back(p);
|
||||
i = j + 1;
|
||||
}
|
||||
else
|
||||
++i;
|
||||
}
|
||||
for (std::size_t i = 0; i + 1 < parts.size();)
|
||||
{
|
||||
if (std::abs(parts[i].slope - parts[i + 1].slope) < Options.grade_tolerance * 0.5)
|
||||
{
|
||||
parts[i].last = parts[i + 1].last;
|
||||
fit(parts[i]);
|
||||
parts.erase(parts.begin() + i + 1);
|
||||
}
|
||||
else
|
||||
++i;
|
||||
}
|
||||
|
||||
result.points.push_back({Chainages.front(), Elevations.front(), 0.0});
|
||||
for (std::size_t i = 0; i + 1 < parts.size(); ++i)
|
||||
{
|
||||
auto const &a{parts[i]};
|
||||
auto const &b{parts[i + 1]};
|
||||
auto const from{Chainages[a.last]};
|
||||
auto const to{Chainages[b.first]};
|
||||
auto const change{b.slope - a.slope};
|
||||
auto chainage{std::abs(change) > 1e-9 ? (a.offset - b.offset) / change : (from + to) * 0.5};
|
||||
chainage = std::clamp(chainage, from, std::max(from, to));
|
||||
pvi point;
|
||||
point.automatic = false;
|
||||
point.chainage = chainage;
|
||||
point.elevation = a.slope * chainage + a.offset;
|
||||
auto const gap{to - from};
|
||||
if (gap >= Options.sharp_gap && std::abs(change) > 1e-9)
|
||||
point.radius = std::round(gap / std::abs(change));
|
||||
if (point.chainage - result.points.back().chainage >= 0.5)
|
||||
result.points.push_back(point);
|
||||
}
|
||||
if (Chainages[count - 1] - result.points.back().chainage < 0.5 && result.points.size() > 1)
|
||||
result.points.pop_back();
|
||||
result.points.push_back({Chainages[count - 1], Elevations[count - 1], 0.0});
|
||||
|
||||
// the gap between constant grades is shorter than the curve, the sagitta gives the radius
|
||||
for (std::size_t i = 1; i + 1 < result.points.size(); ++i)
|
||||
{
|
||||
auto &point{result.points[i]};
|
||||
if (point.radius <= 0.0)
|
||||
continue;
|
||||
auto const next{std::lower_bound(Chainages.begin(), Chainages.begin() + count, point.chainage)};
|
||||
if (next == Chainages.begin() || next == Chainages.begin() + count)
|
||||
continue;
|
||||
auto const index{static_cast<std::size_t>(std::distance(Chainages.begin(), next))};
|
||||
auto const run{Chainages[index] - Chainages[index - 1]};
|
||||
auto const measured{run > 1e-9 ? Elevations[index - 1] + (Elevations[index] - Elevations[index - 1]) * (point.chainage - Chainages[index - 1]) / run : Elevations[index]};
|
||||
auto const radius{radius_for_sagitta(result, i, measured - point.elevation)};
|
||||
if (radius > 0.0)
|
||||
point.radius = std::round(radius);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<double> breaks(line const &Line)
|
||||
{
|
||||
std::vector<double> result;
|
||||
auto const &points{Line.points};
|
||||
for (std::size_t k = 1; k + 1 < points.size(); ++k)
|
||||
{
|
||||
if (points[k].radius > 0.0)
|
||||
continue;
|
||||
if (std::abs(grade_after(Line, k) - grade_after(Line, k - 1)) > 1e-7)
|
||||
result.push_back(points[k].chainage);
|
||||
}
|
||||
for (auto const &c : curves(Line))
|
||||
{
|
||||
result.push_back(c.start);
|
||||
result.push_back(c.end);
|
||||
}
|
||||
std::sort(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string format_chainage(double const Chainage)
|
||||
{
|
||||
auto const value{std::abs(Chainage)};
|
||||
auto const kilometres{static_cast<long>(value / 1000.0)};
|
||||
return format("%s%ld+%06.2f", Chainage < 0.0 ? "-" : "", kilometres, value - kilometres * 1000.0);
|
||||
}
|
||||
|
||||
}
|
||||
119
editor/editorProfile.hpp
Normal file
119
editor/editorProfile.hpp
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// vertical alignment (grade line) of the rail top along the chainage of a route
|
||||
namespace profile
|
||||
{
|
||||
|
||||
// point of intersection of two grades, with a parabolic vertical curve of radius R (0: none)
|
||||
struct pvi
|
||||
{
|
||||
double chainage{0.0};
|
||||
double elevation{0.0};
|
||||
double radius{0.0};
|
||||
bool automatic{true}; // radius follows the limits
|
||||
bool joint{false}; // placed by complete(), takes the grade over from the adjoining track
|
||||
};
|
||||
|
||||
struct limits
|
||||
{
|
||||
double grade_max{6.0}; // per mille
|
||||
double grade_switch_max{2.5}; // per mille, over switches
|
||||
double radius_factor{0.4}; // R >= k * V^2
|
||||
double radius_min{2000.0};
|
||||
double curve_threshold{2.0}; // per mille, change of grade which needs a vertical curve
|
||||
double element_min{0.0}; // m, constant grade between vertical curves
|
||||
double fixed_tolerance{0.005}; // m, allowed departure from fixed points
|
||||
};
|
||||
|
||||
struct line
|
||||
{
|
||||
std::vector<pvi> points; // ordered by chainage; the first and the last are the ends of the route
|
||||
double speed{100.0};
|
||||
limits norms;
|
||||
// at the joined ends: keep the elevation and take over the grade of the adjoining track
|
||||
bool join_start{true};
|
||||
bool join_end{true};
|
||||
};
|
||||
|
||||
double elevation(line const &Line, double const Chainage);
|
||||
// dy/ds
|
||||
double grade(line const &Line, double const Chainage);
|
||||
// grade of the element between points Index and Index + 1
|
||||
double grade_after(line const &Line, std::size_t const Index);
|
||||
double required_radius(line const &Line);
|
||||
// radii of the automatic points: the required one where the change of grade needs a curve, none elsewhere
|
||||
void assign_radii(line &Line);
|
||||
|
||||
struct curve
|
||||
{
|
||||
std::size_t point{0};
|
||||
double start{0.0};
|
||||
double end{0.0};
|
||||
double tangent{0.0};
|
||||
double sagitta{0.0};
|
||||
double change{0.0}; // grade after - grade before
|
||||
};
|
||||
std::vector<curve> curves(line const &Line);
|
||||
|
||||
// radius which gives specified elevation of the curve at the point of intersection
|
||||
double radius_for_sagitta(line const &Line, std::size_t const Point, double const Sagitta);
|
||||
|
||||
// facts about the route which the checks take into account
|
||||
struct context
|
||||
{
|
||||
double length{0.0};
|
||||
struct zone
|
||||
{
|
||||
double from{0.0};
|
||||
double to{0.0};
|
||||
std::string name;
|
||||
};
|
||||
std::vector<zone> switches;
|
||||
// elevations which the grade line has to keep (chainage, elevation)
|
||||
std::vector<std::pair<double, double>> fixed;
|
||||
// adjoining track at the ends; grades rise along the route
|
||||
double start_elevation{0.0}, end_elevation{0.0};
|
||||
double start_grade{0.0}, end_grade{0.0};
|
||||
bool start_joined{false}, end_joined{false};
|
||||
};
|
||||
|
||||
// locks the joined ends, places the joint points and sets the automatic radii
|
||||
void complete(line &Line, context const &Context);
|
||||
|
||||
struct issue
|
||||
{
|
||||
bool error{false};
|
||||
int point{-1};
|
||||
double chainage{0.0};
|
||||
std::string text;
|
||||
};
|
||||
std::vector<issue> check(line const &Line, context const &Context);
|
||||
|
||||
struct recognition
|
||||
{
|
||||
double grade_tolerance{0.0004};
|
||||
double minimum_length{15.0};
|
||||
double sharp_gap{2.0};
|
||||
};
|
||||
// grade line of existing track, from its elevations and grades sampled along the chainage
|
||||
line recognize(std::vector<double> const &Chainages, std::vector<double> const &Elevations, std::vector<double> const &Grades, recognition const &Options);
|
||||
|
||||
// chainages where the paths have to end so that each of them carries a single grade or a single curve
|
||||
std::vector<double> breaks(line const &Line);
|
||||
|
||||
std::string format_chainage(double const Chainage);
|
||||
|
||||
}
|
||||
@@ -1295,6 +1295,486 @@ TTrack *editor_track::take_straight(straight_run const &Run, TTrack *Edge, glm::
|
||||
return edge;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct bezier
|
||||
{
|
||||
glm::dvec3 p0, p1, p2, p3;
|
||||
explicit bezier(segment_data const &Path)
|
||||
{
|
||||
p0 = Path.points[segment_data::point::start];
|
||||
p3 = Path.points[segment_data::point::end];
|
||||
auto const &control1{Path.points[segment_data::point::control1]};
|
||||
auto const &control2{Path.points[segment_data::point::control2]};
|
||||
if (control1 == glm::dvec3{} && control2 == glm::dvec3{})
|
||||
{
|
||||
p1 = glm::mix(p0, p3, 1.0 / 3.0);
|
||||
p2 = glm::mix(p0, p3, 2.0 / 3.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
p1 = p0 + control1;
|
||||
p2 = p3 + control2;
|
||||
}
|
||||
}
|
||||
glm::dvec3 point(double const T) const
|
||||
{
|
||||
auto const u{1.0 - T};
|
||||
return u * u * u * p0 + 3.0 * u * u * T * p1 + 3.0 * u * T * T * p2 + T * T * T * p3;
|
||||
}
|
||||
glm::dvec3 first(double const T) const
|
||||
{
|
||||
auto const u{1.0 - T};
|
||||
return 3.0 * u * u * (p1 - p0) + 6.0 * u * T * (p2 - p1) + 3.0 * T * T * (p3 - p2);
|
||||
}
|
||||
glm::dvec3 second(double const T) const
|
||||
{
|
||||
return 6.0 * (1.0 - T) * (p2 - 2.0 * p1 + p0) + 6.0 * T * (p3 - 2.0 * p2 + p1);
|
||||
}
|
||||
double plan_length(int const Steps = 32) const
|
||||
{
|
||||
double length{0.0};
|
||||
auto previous{plan_of(point(0.0))};
|
||||
for (int i = 1; i <= Steps; ++i)
|
||||
{
|
||||
auto const next{plan_of(point(static_cast<double>(i) / Steps))};
|
||||
length += glm::distance(previous, next);
|
||||
previous = next;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
// parameter at specified plan distance from the start
|
||||
double parameter(double const Distance) const
|
||||
{
|
||||
int constexpr steps{256};
|
||||
double length{0.0};
|
||||
auto previous{plan_of(point(0.0))};
|
||||
for (int i = 1; i <= steps; ++i)
|
||||
{
|
||||
auto const next{plan_of(point(static_cast<double>(i) / steps))};
|
||||
auto const step{glm::distance(previous, next)};
|
||||
if (length + step >= Distance)
|
||||
return (i - 1 + (step > 1e-12 ? (Distance - length) / step : 0.0)) / steps;
|
||||
length += step;
|
||||
previous = next;
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
};
|
||||
|
||||
using route_node = std::tuple<TTrack *, int, bool>;
|
||||
|
||||
std::vector<route_node> route_next(route_node const &Node)
|
||||
{
|
||||
std::vector<route_node> result;
|
||||
auto *track{std::get<0>(Node)};
|
||||
auto const path{std::get<1>(Node)};
|
||||
auto const forward{std::get<2>(Node)};
|
||||
auto const &exit{track->m_paths[path].points[forward ? segment_data::point::end : segment_data::point::start]};
|
||||
auto *next{neighbour_at(*track, path, forward)};
|
||||
if (next == nullptr || next == track || next->m_editorremoved || false == editor_track::is_supported(*next) || (next->iCategoryFlag & 15) != (track->iCategoryFlag & 15))
|
||||
return result;
|
||||
for (int i = 0; i < static_cast<int>(next->m_paths.size()); ++i)
|
||||
{
|
||||
auto const &candidate{next->m_paths[i]};
|
||||
if (glm::distance(candidate.points[segment_data::point::start], exit) <= kSamePoint)
|
||||
result.emplace_back(next, i, true);
|
||||
else if (glm::distance(candidate.points[segment_data::point::end], exit) <= kSamePoint)
|
||||
result.emplace_back(next, i, false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool editor_track::find_route(TTrack *From, TTrack *To, route &Route, std::string &Error)
|
||||
{
|
||||
Route = {};
|
||||
if (From == nullptr || To == nullptr || false == is_supported(*From) || false == is_supported(*To))
|
||||
{
|
||||
Error = "Select the first and the last path of the route";
|
||||
return false;
|
||||
}
|
||||
if (From == To)
|
||||
{
|
||||
Route.spans.push_back({From, 0, true});
|
||||
Error.clear();
|
||||
return true;
|
||||
}
|
||||
std::vector<route_node> nodes;
|
||||
std::vector<int> parents;
|
||||
std::map<route_node, int> visited;
|
||||
for (int i = 0; i < static_cast<int>(From->m_paths.size()); ++i)
|
||||
for (auto const forward : {true, false})
|
||||
{
|
||||
route_node const node{From, i, forward};
|
||||
visited.emplace(node, static_cast<int>(nodes.size()));
|
||||
nodes.push_back(node);
|
||||
parents.push_back(-1);
|
||||
}
|
||||
int goal{-1};
|
||||
for (std::size_t index = 0; index < nodes.size() && goal < 0 && nodes.size() < 200000; ++index)
|
||||
{
|
||||
for (auto const &next : route_next(nodes[index]))
|
||||
{
|
||||
if (std::get<0>(next) == From || visited.count(next) > 0)
|
||||
continue;
|
||||
visited.emplace(next, static_cast<int>(nodes.size()));
|
||||
nodes.push_back(next);
|
||||
parents.push_back(static_cast<int>(index));
|
||||
if (std::get<0>(next) == To)
|
||||
{
|
||||
goal = static_cast<int>(nodes.size()) - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (goal < 0)
|
||||
{
|
||||
Error = "No route connects the selected paths";
|
||||
return false;
|
||||
}
|
||||
for (int index = goal; index >= 0; index = parents[index])
|
||||
Route.spans.push_back({std::get<0>(nodes[index]), std::get<1>(nodes[index]), std::get<2>(nodes[index])});
|
||||
std::reverse(Route.spans.begin(), Route.spans.end());
|
||||
Error.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
editor_track::route editor_track::run_route(TTrack &Track, double const Maximum)
|
||||
{
|
||||
route result;
|
||||
if (false == is_supported(Track))
|
||||
return result;
|
||||
auto const walk = [&](route_node Node) {
|
||||
std::vector<route_node> nodes;
|
||||
double length{0.0};
|
||||
while (nodes.size() < 5000 && length < Maximum)
|
||||
{
|
||||
auto candidates{route_next(Node)};
|
||||
if (candidates.empty())
|
||||
break;
|
||||
auto chosen{candidates.front()};
|
||||
for (auto const &candidate : candidates)
|
||||
if (std::get<1>(candidate) == 0)
|
||||
chosen = candidate;
|
||||
auto *track{std::get<0>(chosen)};
|
||||
if (track == &Track || std::any_of(nodes.begin(), nodes.end(), [&](route_node const &Other) { return std::get<0>(Other) == track; }))
|
||||
break;
|
||||
nodes.push_back(chosen);
|
||||
length += bezier(track->m_paths[std::get<1>(chosen)]).plan_length(8);
|
||||
Node = chosen;
|
||||
}
|
||||
return nodes;
|
||||
};
|
||||
auto const ahead{walk({&Track, 0, true})};
|
||||
auto const behind{walk({&Track, 0, false})};
|
||||
for (auto node = behind.rbegin(); node != behind.rend(); ++node)
|
||||
if (std::none_of(ahead.begin(), ahead.end(), [&](route_node const &Other) { return std::get<0>(Other) == std::get<0>(*node); }))
|
||||
result.spans.push_back({std::get<0>(*node), std::get<1>(*node), false == std::get<2>(*node)});
|
||||
result.spans.push_back({&Track, 0, true});
|
||||
for (auto const &node : ahead)
|
||||
result.spans.push_back({std::get<0>(node), std::get<1>(node), std::get<2>(node)});
|
||||
return result;
|
||||
}
|
||||
|
||||
editor_track::route editor_track::route_of(chain const &Chain)
|
||||
{
|
||||
route result;
|
||||
for (std::size_t i = 0; i < Chain.tracks.size(); ++i)
|
||||
result.spans.push_back({Chain.tracks[i], 0, Chain.forward[i]});
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<editor_track::route_sample> editor_track::sample_route(route &Route, double const Step)
|
||||
{
|
||||
std::vector<route_sample> samples;
|
||||
double chainage{0.0};
|
||||
for (std::size_t i = 0; i < Route.spans.size(); ++i)
|
||||
{
|
||||
auto &span{Route.spans[i]};
|
||||
bezier const curve{span.track->m_paths[span.path]};
|
||||
auto const count{std::max(2, static_cast<int>(std::ceil(curve.plan_length(16) / std::max(0.1, Step))))};
|
||||
span.from = chainage;
|
||||
auto previous{plan_of(curve.point(span.forward ? 0.0 : 1.0))};
|
||||
for (int j = (samples.empty() ? 0 : 1); j <= count; ++j)
|
||||
{
|
||||
auto const t{span.forward ? static_cast<double>(j) / count : 1.0 - static_cast<double>(j) / count};
|
||||
auto const position{curve.point(t)};
|
||||
chainage += glm::distance(previous, plan_of(position));
|
||||
previous = plan_of(position);
|
||||
auto derivative{curve.first(t)};
|
||||
if (false == span.forward)
|
||||
derivative = -derivative;
|
||||
auto const second{curve.second(t)};
|
||||
auto const planar{std::hypot(derivative.x, derivative.z)};
|
||||
route_sample sample;
|
||||
sample.chainage = chainage;
|
||||
sample.position = position;
|
||||
sample.span = i;
|
||||
if (planar > 1e-9)
|
||||
{
|
||||
sample.direction = glm::dvec2{derivative.x, derivative.z} / planar;
|
||||
sample.grade = derivative.y / planar;
|
||||
sample.curvature = (derivative.x * second.z - derivative.z * second.x) / (planar * planar * planar);
|
||||
}
|
||||
samples.push_back(sample);
|
||||
}
|
||||
span.to = chainage;
|
||||
}
|
||||
Route.length = chainage;
|
||||
return samples;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename Getter>
|
||||
double interpolate(std::vector<editor_track::route_sample> const &Samples, double const Chainage, Getter const &Get)
|
||||
{
|
||||
if (Samples.empty())
|
||||
return 0.0;
|
||||
auto const next{std::lower_bound(Samples.begin(), Samples.end(), Chainage, [](editor_track::route_sample const &Sample, double const Value) { return Sample.chainage < Value; })};
|
||||
if (next == Samples.begin())
|
||||
return Get(Samples.front());
|
||||
if (next == Samples.end())
|
||||
return Get(Samples.back());
|
||||
auto const &before{*std::prev(next)};
|
||||
auto const run{next->chainage - before.chainage};
|
||||
return run > 1e-9 ? Get(before) + (Get(*next) - Get(before)) * (Chainage - before.chainage) / run : Get(before);
|
||||
}
|
||||
|
||||
void set_heights(segment_data &Path, double const Startheight, double const Endheight, double const Startgrade, double const Endgrade)
|
||||
{
|
||||
auto &start{Path.points[segment_data::point::start]};
|
||||
auto &end{Path.points[segment_data::point::end]};
|
||||
auto &control1{Path.points[segment_data::point::control1]};
|
||||
auto &control2{Path.points[segment_data::point::control2]};
|
||||
if (control1 == glm::dvec3{} && control2 == glm::dvec3{})
|
||||
{
|
||||
control1 = (end - start) / 3.0;
|
||||
control2 = (start - end) / 3.0;
|
||||
}
|
||||
start.y = Startheight;
|
||||
end.y = Endheight;
|
||||
control1.y = Startgrade * std::hypot(control1.x, control1.z);
|
||||
control2.y = -Endgrade * std::hypot(control2.x, control2.z);
|
||||
}
|
||||
|
||||
// the path lies in the plane through Origin which rises with Grade along Direction (unit, in plan) and is level across it
|
||||
void plane_heights(segment_data &Path, glm::dvec3 const &Origin, glm::dvec2 const &Direction, double const Grade)
|
||||
{
|
||||
auto &start{Path.points[segment_data::point::start]};
|
||||
auto &end{Path.points[segment_data::point::end]};
|
||||
auto &control1{Path.points[segment_data::point::control1]};
|
||||
auto &control2{Path.points[segment_data::point::control2]};
|
||||
if (control1 == glm::dvec3{} && control2 == glm::dvec3{})
|
||||
{
|
||||
control1 = (end - start) / 3.0;
|
||||
control2 = (start - end) / 3.0;
|
||||
}
|
||||
auto const height = [&](glm::dvec3 const &Point) { return Origin.y + Grade * glm::dot(glm::dvec2{Point.x - Origin.x, Point.z - Origin.z}, Direction); };
|
||||
auto const first{start + control1};
|
||||
auto const second{end + control2};
|
||||
start.y = height(start);
|
||||
end.y = height(end);
|
||||
control1.y = height(first) - start.y;
|
||||
control2.y = height(second) - end.y;
|
||||
}
|
||||
|
||||
glm::dvec2 start_direction(segment_data const &Path)
|
||||
{
|
||||
auto const &start{Path.points[segment_data::point::start]};
|
||||
auto const &control{Path.points[segment_data::point::control1]};
|
||||
glm::dvec2 direction{control.x, control.z};
|
||||
if (glm::length(direction) < 1e-6)
|
||||
direction = {Path.points[segment_data::point::end].x - start.x, Path.points[segment_data::point::end].z - start.z};
|
||||
return glm::length(direction) > 1e-9 ? glm::normalize(direction) : glm::dvec2{1.0, 0.0};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
double editor_track::sampled_elevation(std::vector<route_sample> const &Samples, double const Chainage)
|
||||
{
|
||||
return interpolate(Samples, Chainage, [](route_sample const &Sample) { return Sample.position.y; });
|
||||
}
|
||||
|
||||
double editor_track::sampled_grade(std::vector<route_sample> const &Samples, double const Chainage)
|
||||
{
|
||||
return interpolate(Samples, Chainage, [](route_sample const &Sample) { return Sample.grade; });
|
||||
}
|
||||
|
||||
bool editor_track::adjoining_grade(route const &Route, bool const Atend, double &Grade)
|
||||
{
|
||||
if (Route.spans.empty())
|
||||
return false;
|
||||
auto const &span{Atend ? Route.spans.back() : Route.spans.front()};
|
||||
auto const outward{Atend ? span.forward : false == span.forward};
|
||||
auto const next{route_next({span.track, span.path, outward})};
|
||||
if (next.empty())
|
||||
return false;
|
||||
auto const &[track, path, forward]{next.front()};
|
||||
auto const tangent{path_tangent(track->m_paths[path], false == forward)};
|
||||
auto const plan{std::hypot(tangent.x, tangent.z)};
|
||||
if (plan < 1e-9)
|
||||
return false;
|
||||
auto const along{(forward ? 1.0 : -1.0) * tangent.y / plan};
|
||||
Grade = Atend ? along : -along;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<editor_track::height_gap> editor_track::apply_profile(route &Route, std::function<double(double)> const &Elevation, std::function<double(double)> const &Grade, std::vector<double> const &Breaks, std::vector<std::pair<TTrack *, state>> &States, std::vector<TTrack *> &Created)
|
||||
{
|
||||
double constexpr margin{0.25};
|
||||
auto const remember = [&](TTrack *Track) {
|
||||
if (std::none_of(States.begin(), States.end(), [&](auto const &Entry) { return Entry.first == Track; }) && std::find(Created.begin(), Created.end(), Track) == Created.end())
|
||||
States.emplace_back(Track, capture(*Track));
|
||||
};
|
||||
sample_route(Route, 1.0);
|
||||
std::vector<route_span> spans;
|
||||
for (auto const &span : Route.spans)
|
||||
{
|
||||
if (span.track->eType != tt_Normal)
|
||||
{
|
||||
spans.push_back(span);
|
||||
continue;
|
||||
}
|
||||
std::vector<double> cuts;
|
||||
for (auto const chainage : Breaks)
|
||||
if (chainage > span.from + margin && chainage < span.to - margin)
|
||||
cuts.push_back(chainage);
|
||||
if (cuts.empty())
|
||||
{
|
||||
spans.push_back(span);
|
||||
continue;
|
||||
}
|
||||
remember(span.track);
|
||||
if (span.forward)
|
||||
{
|
||||
auto *piece{span.track};
|
||||
auto origin{span.from};
|
||||
spans.push_back({piece, 0, true});
|
||||
for (auto const cut : cuts)
|
||||
{
|
||||
auto const t{bezier(piece->m_paths.front()).parameter(cut - origin)};
|
||||
auto *created{split_path(*piece, t)};
|
||||
if (created == nullptr)
|
||||
continue;
|
||||
Created.push_back(created);
|
||||
spans.push_back({created, 0, true});
|
||||
piece = created;
|
||||
origin = cut;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<TTrack *> order;
|
||||
for (auto cut = cuts.begin(); cut != cuts.end(); ++cut)
|
||||
{
|
||||
auto const t{bezier(span.track->m_paths.front()).parameter(span.to - *cut)};
|
||||
auto *created{split_path(*span.track, t)};
|
||||
if (created == nullptr)
|
||||
continue;
|
||||
Created.push_back(created);
|
||||
order.push_back(created);
|
||||
}
|
||||
for (auto *piece : order)
|
||||
spans.push_back({piece, 0, false});
|
||||
spans.push_back({span.track, 0, false});
|
||||
}
|
||||
}
|
||||
Route.spans = spans;
|
||||
sample_route(Route, 1.0);
|
||||
|
||||
auto const on_route = [&](TTrack const *Track) {
|
||||
return std::any_of(Route.spans.begin(), Route.spans.end(), [&](route_span const &Span) { return Span.track == Track; });
|
||||
};
|
||||
struct joint
|
||||
{
|
||||
TTrack *track;
|
||||
int path;
|
||||
bool atend;
|
||||
TTrack *neighbour;
|
||||
};
|
||||
std::vector<joint> joints;
|
||||
std::vector<TTrack *> changed;
|
||||
for (auto const &span : Route.spans)
|
||||
{
|
||||
remember(span.track);
|
||||
auto const startchainage{span.forward ? span.from : span.to};
|
||||
auto const endchainage{span.forward ? span.to : span.from};
|
||||
auto const sign{span.forward ? 1.0 : -1.0};
|
||||
auto &path{span.track->m_paths[span.path]};
|
||||
set_heights(path, Elevation(startchainage), Elevation(endchainage), Grade(startchainage) * sign, Grade(endchainage) * sign);
|
||||
add_unique(changed, span.track);
|
||||
if (span.track->eType == tt_Normal)
|
||||
continue;
|
||||
auto const grade{Grade((span.from + span.to) * 0.5) * sign};
|
||||
for (int i = 0; i < static_cast<int>(span.track->m_paths.size()); ++i)
|
||||
{
|
||||
for (auto const atend : {false, true})
|
||||
{
|
||||
auto *neighbour{neighbour_at(*span.track, i, atend)};
|
||||
if (neighbour != nullptr && false == on_route(neighbour) && std::none_of(joints.begin(), joints.end(), [&](joint const &Joint) { return Joint.track == span.track && Joint.neighbour == neighbour; }))
|
||||
joints.push_back({span.track, i, atend, neighbour});
|
||||
}
|
||||
if (i != span.path)
|
||||
plane_heights(span.track->m_paths[i], path.points[segment_data::point::start], start_direction(path), grade);
|
||||
}
|
||||
}
|
||||
commit(changed);
|
||||
sample_route(Route, 1.0);
|
||||
|
||||
std::vector<height_gap> gaps;
|
||||
for (auto const &joint : joints)
|
||||
{
|
||||
auto const &end{joint.track->m_paths[joint.path].points[joint.atend ? segment_data::point::end : segment_data::point::start]};
|
||||
double nearest{std::numeric_limits<double>::max()};
|
||||
double gap{0.0};
|
||||
for (auto const &path : joint.neighbour->m_paths)
|
||||
for (auto const index : {segment_data::point::start, segment_data::point::end})
|
||||
{
|
||||
auto const &point{path.points[index]};
|
||||
auto const distance{std::hypot(point.x - end.x, point.z - end.z)};
|
||||
if (distance < nearest)
|
||||
{
|
||||
nearest = distance;
|
||||
gap = point.y - end.y;
|
||||
}
|
||||
}
|
||||
if (nearest <= kSamePoint && std::abs(gap) > 0.001)
|
||||
gaps.push_back({joint.track, joint.neighbour, gap});
|
||||
}
|
||||
return gaps;
|
||||
}
|
||||
|
||||
void editor_track::keep_heights(chain const &Chain, std::vector<segment_data> &Pieces)
|
||||
{
|
||||
auto existing{route_of(Chain)};
|
||||
auto const samples{sample_route(existing, 1.0)};
|
||||
if (samples.empty() || existing.length <= 1e-6)
|
||||
return;
|
||||
std::vector<double> lengths;
|
||||
double total{0.0};
|
||||
for (auto const &piece : Pieces)
|
||||
{
|
||||
lengths.push_back(bezier(piece).plan_length());
|
||||
total += lengths.back();
|
||||
}
|
||||
if (total <= 1e-6)
|
||||
return;
|
||||
auto const scale{existing.length / total};
|
||||
double chainage{0.0};
|
||||
for (std::size_t i = 0; i < Pieces.size(); ++i)
|
||||
{
|
||||
auto const from{chainage * scale};
|
||||
auto const to{(chainage + lengths[i]) * scale};
|
||||
set_heights(Pieces[i], sampled_elevation(samples, from), sampled_elevation(samples, to), sampled_grade(samples, from) * scale, sampled_grade(samples, to) * scale);
|
||||
chainage += lengths[i];
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<editor_track::switch_template> editor_track::find_switch_templates()
|
||||
{
|
||||
std::vector<switch_template> result;
|
||||
|
||||
@@ -147,6 +147,51 @@ class editor_track
|
||||
static straight_run straight_beyond(chain const &Chain, bool const Atend, straight_tolerance const &Tolerance);
|
||||
static double run_reserve(straight_run const &Run);
|
||||
static TTrack *take_straight(straight_run const &Run, TTrack *Edge, glm::dvec3 const &Joint, glm::dvec3 const &Cut, std::vector<std::pair<TTrack *, state>> &States, std::vector<TTrack *> &Created);
|
||||
// sequence of paths, switches included, which a train can run along
|
||||
struct route_span
|
||||
{
|
||||
TTrack *track{nullptr};
|
||||
int path{0};
|
||||
bool forward{true};
|
||||
double from{0.0}; // chainage, measured in the plan
|
||||
double to{0.0};
|
||||
};
|
||||
struct route
|
||||
{
|
||||
std::vector<route_span> spans;
|
||||
double length{0.0};
|
||||
};
|
||||
static bool find_route(TTrack *From, TTrack *To, route &Route, std::string &Error);
|
||||
// continues both ways from the path, through switches along their path 0
|
||||
static route run_route(TTrack &Track, double const Maximum);
|
||||
static route route_of(chain const &Chain);
|
||||
struct route_sample
|
||||
{
|
||||
double chainage{0.0};
|
||||
glm::dvec3 position{0.0};
|
||||
glm::dvec2 direction{0.0, 1.0};
|
||||
double grade{0.0};
|
||||
double curvature{0.0}; // signed, 1/m, positive turning left
|
||||
std::size_t span{0};
|
||||
};
|
||||
static std::vector<route_sample> sample_route(route &Route, double const Step);
|
||||
static double sampled_elevation(std::vector<route_sample> const &Samples, double const Chainage);
|
||||
static double sampled_grade(std::vector<route_sample> const &Samples, double const Chainage);
|
||||
// grade of the path which adjoins the end of the route, positive rising along the route
|
||||
static bool adjoining_grade(route const &Route, bool const Atend, double &Grade);
|
||||
// path off the route whose end no longer meets the adjoining path in height
|
||||
struct height_gap
|
||||
{
|
||||
TTrack *track{nullptr};
|
||||
TTrack *neighbour{nullptr};
|
||||
double gap{0.0}; // neighbour - track
|
||||
};
|
||||
// sets elevations of the paths of the route; regular paths are split at the chainages in Breaks,
|
||||
// switches are tilted as a whole in the plane of their grade, branches included
|
||||
static std::vector<height_gap> apply_profile(route &Route, std::function<double(double)> const &Elevation, std::function<double(double)> const &Grade, std::vector<double> const &Breaks, std::vector<std::pair<TTrack *, state>> &States, std::vector<TTrack *> &Created);
|
||||
// carries elevations of the chain over to the new pieces, in proportion of the length
|
||||
static void keep_heights(chain const &Chain, std::vector<segment_data> &Pieces);
|
||||
|
||||
struct switch_template
|
||||
{
|
||||
TTrack *source{nullptr};
|
||||
|
||||
Reference in New Issue
Block a user