mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 02:49:19 +02:00
Merge branch 'feat/modern-editor' of https://github.com/MaSzyna-EU07/maszyna into feat/modern-editor
This commit is contained in:
@@ -9,11 +9,12 @@ http:
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "editor/editorAlignment.hpp"
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
|
||||
@@ -26,43 +27,20 @@ namespace
|
||||
double const kGravity{9.81};
|
||||
double const kPi{3.14159265358979323846};
|
||||
|
||||
double cross(glm::dvec2 const &A, glm::dvec2 const &B)
|
||||
{
|
||||
return A.x * B.y - A.y * B.x;
|
||||
}
|
||||
using geometry::cross;
|
||||
using geometry::plan_of;
|
||||
using geometry::turned;
|
||||
|
||||
glm::dvec2 perpendicular(glm::dvec2 const &Vector)
|
||||
{
|
||||
return {-Vector.y, Vector.x};
|
||||
}
|
||||
|
||||
glm::dvec2 rotate(glm::dvec2 const &Vector, double const Angle)
|
||||
{
|
||||
auto const c{std::cos(Angle)};
|
||||
auto const s{std::sin(Angle)};
|
||||
return {Vector.x * c - Vector.y * s, Vector.x * s + Vector.y * c};
|
||||
}
|
||||
|
||||
glm::dvec2 plan(glm::dvec3 const &Point)
|
||||
{
|
||||
return {Point.x, Point.z};
|
||||
}
|
||||
|
||||
double ceil_to(double const Value, double const Step)
|
||||
{
|
||||
return std::ceil(Value / Step - 1e-9) * Step;
|
||||
}
|
||||
|
||||
std::string format(char const *Format, ...)
|
||||
{
|
||||
char buffer[512];
|
||||
va_list arguments;
|
||||
va_start(arguments, Format);
|
||||
std::vsnprintf(buffer, sizeof(buffer), Format, arguments);
|
||||
va_end(arguments);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
struct local_point
|
||||
{
|
||||
double x{0.0};
|
||||
@@ -157,8 +135,8 @@ void element_point(element const &Element, transition_shape const Shape, double
|
||||
case element_kind::arc:
|
||||
{
|
||||
auto const angle{Element.turn * Distance / Element.radius};
|
||||
Position = Element.origin + rotate(Element.normal, angle);
|
||||
Direction = rotate(Element.direction, angle);
|
||||
Position = Element.origin + turned(Element.normal, angle);
|
||||
Direction = turned(Element.direction, angle);
|
||||
break;
|
||||
}
|
||||
case element_kind::transition_in:
|
||||
@@ -267,7 +245,6 @@ recommendation recommend(double const Speed, double const Radius, limits const &
|
||||
auto const factor{Limits.gauge / (kGravity * 12.96)};
|
||||
auto const allowance{Limits.gauge * Limits.unbalanced / kGravity};
|
||||
result.radius_min = factor * Speed * Speed / (Limits.cant_max + allowance);
|
||||
result.vertical_radius = std::max(Limits.vertical_min, Limits.vertical_factor * Speed * Speed);
|
||||
if (Radius <= 0.0)
|
||||
return result;
|
||||
result.cant_equilibrium = factor * Speed * Speed / Radius;
|
||||
@@ -277,14 +254,21 @@ recommendation recommend(double const Speed, double const Radius, limits const &
|
||||
return result;
|
||||
}
|
||||
|
||||
result compute(design const &Design)
|
||||
namespace
|
||||
{
|
||||
|
||||
result fit_between(design const &Design)
|
||||
{
|
||||
result r;
|
||||
r.shape = Design.shape;
|
||||
r.gauge = Design.norms.gauge;
|
||||
r.start = Design.start;
|
||||
r.end = Design.end;
|
||||
auto const startreserve{std::max(0.0, Design.start_reserve)};
|
||||
auto const endreserve{std::max(0.0, Design.end_reserve)};
|
||||
|
||||
auto const start{plan(Design.start)};
|
||||
auto const end{plan(Design.end)};
|
||||
auto const start{plan_of(Design.start)};
|
||||
auto const end{plan_of(Design.end)};
|
||||
auto const startdirection{glm::normalize(Design.start_direction)};
|
||||
auto const enddirection{glm::normalize(Design.end_direction)};
|
||||
auto const count{Design.vertices.size()};
|
||||
@@ -351,6 +335,16 @@ result compute(design const &Design)
|
||||
straight.length = Length;
|
||||
push(straight);
|
||||
};
|
||||
auto const starts_early = [&](double const Straight) {
|
||||
if (Straight < 0.0)
|
||||
r.start_extension = -Straight;
|
||||
if (Straight >= -1e-4 || -Straight <= startreserve + 1e-4)
|
||||
return;
|
||||
if (startreserve > 0.0)
|
||||
r.errors.emplace_back(format("Vertex 1: the curve starts %.2f m before the adjoining straight", -Straight - startreserve));
|
||||
else
|
||||
r.errors.emplace_back(format("Vertex 1: the curve starts %.2f m before the fixed start", -Straight));
|
||||
};
|
||||
|
||||
struct fitted
|
||||
{
|
||||
@@ -441,9 +435,9 @@ result compute(design const &Design)
|
||||
std::vector<double> scales(count, 1.0);
|
||||
for (std::size_t leg = 0; leg + 1 < polygon.size(); ++leg)
|
||||
{
|
||||
auto const available{glm::distance(polygon[leg], polygon[leg + 1])};
|
||||
auto const available{glm::distance(polygon[leg], polygon[leg + 1]) + (leg == 0 ? startreserve : 0.0) + (leg == count ? endreserve : 0.0)};
|
||||
auto const required{(leg > 0 ? fits[leg - 1].tangent_out : 0.0) + (leg < count ? fits[leg].tangent_in : 0.0)};
|
||||
if (required <= available)
|
||||
if (required <= available + 1e-6)
|
||||
continue;
|
||||
auto const scale{available / required * (1.0 - 1e-9)};
|
||||
if (leg > 0)
|
||||
@@ -534,13 +528,10 @@ result compute(design const &Design)
|
||||
auto const along{cross(directionout, endoffset) / cross(directionout, directionin)};
|
||||
auto const curvestart{position - directionin * along};
|
||||
auto const straight{glm::dot(curvestart - cursor, directionin)};
|
||||
if (straight < -1e-4)
|
||||
{
|
||||
if (k == 0)
|
||||
r.errors.emplace_back(format("Vertex 1: the curve starts %.2f m before the fixed start", -straight));
|
||||
else
|
||||
r.errors.emplace_back(format("Curves of vertices %zu and %zu overlap by %.2f m", k, k + 1, -straight));
|
||||
}
|
||||
if (k == 0)
|
||||
starts_early(straight);
|
||||
else if (straight < -1e-4)
|
||||
r.errors.emplace_back(format("Curves of vertices %zu and %zu overlap by %.2f m", k, k + 1, -straight));
|
||||
push_straight(cursor, directionin, straight);
|
||||
auto const startchainage{chainage};
|
||||
glm::dvec2 origin{curvestart};
|
||||
@@ -564,7 +555,7 @@ result compute(design const &Design)
|
||||
push(piece);
|
||||
auto const local{spiral_point(segment[0], segment[1], segment[2], segment[2])};
|
||||
origin += heading * local.x + piece.normal * local.y;
|
||||
heading = rotate(heading, side * local.angle);
|
||||
heading = turned(heading, side * local.angle);
|
||||
}
|
||||
vertexchainage[k] = (startchainage + chainage) * 0.5;
|
||||
cursor = origin;
|
||||
@@ -615,13 +606,10 @@ result compute(design const &Design)
|
||||
}
|
||||
|
||||
auto const straight{glm::dot(curvestart - cursor, directionin)};
|
||||
if (straight < -1e-4)
|
||||
{
|
||||
if (k == 0)
|
||||
r.errors.emplace_back(format("Vertex 1: the curve starts %.2f m before the fixed start", -straight));
|
||||
else
|
||||
r.errors.emplace_back(format("Curves of vertices %zu and %zu overlap by %.2f m", k, k + 1, -straight));
|
||||
}
|
||||
if (k == 0)
|
||||
starts_early(straight);
|
||||
else if (straight < -1e-4)
|
||||
r.errors.emplace_back(format("Curves of vertices %zu and %zu overlap by %.2f m", k, k + 1, -straight));
|
||||
push_straight(cursor, directionin, straight);
|
||||
|
||||
element curve;
|
||||
@@ -644,7 +632,7 @@ result compute(design const &Design)
|
||||
curve.length = radius * std::max(0.0, arcangle);
|
||||
curve.origin = centre;
|
||||
curve.normal = arcstart - centre;
|
||||
curve.direction = rotate(directionin, side * endin.angle);
|
||||
curve.direction = turned(directionin, side * endin.angle);
|
||||
vertexchainage[k] = chainage + curve.length * 0.5;
|
||||
if (curve.length > 1e-6)
|
||||
push(curve);
|
||||
@@ -698,8 +686,15 @@ result compute(design const &Design)
|
||||
}
|
||||
auto const lastdirection{glm::normalize(polygon.back() - polygon[polygon.size() - 2])};
|
||||
auto const tail{glm::dot(end - cursor, lastdirection)};
|
||||
if (tail < -1e-4)
|
||||
r.errors.emplace_back(format("Vertex %zu: the curve ends %.2f m past the fixed end", count, -tail));
|
||||
if (tail < 0.0)
|
||||
r.end_extension = -tail;
|
||||
if (tail < -1e-4 && -tail > endreserve + 1e-4)
|
||||
{
|
||||
if (endreserve > 0.0)
|
||||
r.errors.emplace_back(format("Vertex %zu: the curve ends %.2f m past the adjoining straight", count, -tail - endreserve));
|
||||
else
|
||||
r.errors.emplace_back(format("Vertex %zu: the curve ends %.2f m past the fixed end", count, -tail));
|
||||
}
|
||||
push_straight(cursor, lastdirection, tail);
|
||||
r.length = chainage;
|
||||
|
||||
@@ -716,12 +711,11 @@ result compute(design const &Design)
|
||||
|
||||
r.vertex_chainages = vertexchainage;
|
||||
r.profile.push_back({0.0, Design.start.y, 0.0, 0.0});
|
||||
auto const radius{std::max(Design.norms.vertical_min, Design.norms.vertical_factor * Design.speed * Design.speed)};
|
||||
for (std::size_t k = 0; k < count; ++k)
|
||||
{
|
||||
auto const &vertex{Design.vertices[k]};
|
||||
auto const elevation{vertex.auto_elevation ? Design.start.y + (Design.end.y - Design.start.y) * vertexchainage[k] / std::max(r.length, 1e-6) : vertex.elevation};
|
||||
auto const elevation{Design.start.y + (Design.end.y - Design.start.y) * vertexchainage[k] / std::max(r.length, 1e-6)};
|
||||
r.vertex_elevations.push_back(elevation);
|
||||
auto const radius{vertex.vertical_radius > 0.0 ? vertex.vertical_radius : std::max(Design.norms.vertical_min, Design.norms.vertical_factor * Design.speed * Design.speed)};
|
||||
if (vertexchainage[k] <= r.profile.back().chainage + 1e-3)
|
||||
continue;
|
||||
r.profile.push_back({vertexchainage[k], elevation, radius, 0.0});
|
||||
@@ -734,20 +728,6 @@ result compute(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)
|
||||
@@ -757,10 +737,43 @@ result compute(design const &Design)
|
||||
return r;
|
||||
}
|
||||
|
||||
glm::dvec3 along_end(glm::dvec3 const &Point, glm::dvec2 const &Direction, double const Grade, double const Distance)
|
||||
{
|
||||
auto const direction{glm::normalize(Direction)};
|
||||
return {Point.x + direction.x * Distance, Point.y + Grade * Distance, Point.z + direction.y * Distance};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
result compute(design const &Design)
|
||||
{
|
||||
auto first{fit_between(Design)};
|
||||
auto const startextension{first.start_extension > 1e-4 ? first.start_extension : 0.0};
|
||||
auto const endextension{first.end_extension > 1e-4 ? first.end_extension : 0.0};
|
||||
if (false == first.errors.empty() || (startextension == 0.0 && endextension == 0.0))
|
||||
{
|
||||
first.start_extension = first.end_extension = 0.0;
|
||||
return first;
|
||||
}
|
||||
auto extended{Design};
|
||||
extended.start = along_end(Design.start, Design.start_direction, Design.start_grade, -startextension);
|
||||
extended.end = along_end(Design.end, Design.end_direction, Design.end_grade, endextension);
|
||||
extended.start_reserve = extended.end_reserve = 0.0;
|
||||
if (extended.vertices.size() >= 2)
|
||||
{
|
||||
extended.vertices.front().offset += startextension;
|
||||
extended.vertices.back().offset += endextension;
|
||||
}
|
||||
auto r{fit_between(extended)};
|
||||
r.start_extension = startextension;
|
||||
r.end_extension = endextension;
|
||||
return r;
|
||||
}
|
||||
|
||||
bool tangent_intersection(design const &Design, glm::dvec2 &Point)
|
||||
{
|
||||
auto const start{plan(Design.start)};
|
||||
auto const offset{plan(Design.end) - start};
|
||||
auto const start{plan_of(Design.start)};
|
||||
auto const offset{plan_of(Design.end) - start};
|
||||
auto const startdirection{glm::normalize(Design.start_direction)};
|
||||
auto const enddirection{glm::normalize(Design.end_direction)};
|
||||
auto const determinant{cross(startdirection, enddirection)};
|
||||
@@ -776,7 +789,7 @@ bool tangent_intersection(design const &Design, glm::dvec2 &Point)
|
||||
|
||||
bool collinear(design const &Design)
|
||||
{
|
||||
auto const offset{plan(Design.end) - plan(Design.start)};
|
||||
auto const offset{plan_of(Design.end) - plan_of(Design.start)};
|
||||
auto const startdirection{glm::normalize(Design.start_direction)};
|
||||
auto const enddirection{glm::normalize(Design.end_direction)};
|
||||
return std::abs(cross(startdirection, offset)) <= 0.01 && glm::dot(startdirection, enddirection) >= std::cos(1e-4) && glm::dot(startdirection, offset) > 0.0;
|
||||
|
||||
@@ -45,9 +45,6 @@ struct vertex
|
||||
double transition_in{0.0};
|
||||
double transition_out{0.0};
|
||||
double cant{0.0};
|
||||
bool auto_elevation{true};
|
||||
double elevation{0.0};
|
||||
double vertical_radius{0.0};
|
||||
bool reverse_turn{false};
|
||||
bool compound{false};
|
||||
double radius2{600.0};
|
||||
@@ -65,6 +62,9 @@ struct design
|
||||
double end_grade{0.0};
|
||||
double start_radius{0.0};
|
||||
double end_radius{0.0};
|
||||
// length of the straight beyond the start and the end, which the curves can take over
|
||||
double start_reserve{0.0};
|
||||
double end_reserve{0.0};
|
||||
std::vector<vertex> vertices;
|
||||
transition_shape shape{transition_shape::cubic_parabola};
|
||||
double speed{100.0};
|
||||
@@ -80,7 +80,6 @@ struct recommendation
|
||||
double cant{0.0};
|
||||
double radius_min{0.0};
|
||||
double transition{0.0};
|
||||
double vertical_radius{0.0};
|
||||
};
|
||||
recommendation recommend(double const Speed, double const Radius, limits const &Limits);
|
||||
double transition_length(double const Speed, double const Radius, double const Cant, limits const &Limits);
|
||||
@@ -152,6 +151,10 @@ struct result
|
||||
std::vector<curve_report> curves;
|
||||
std::vector<profile_point> profile;
|
||||
double length{0.0};
|
||||
glm::dvec3 start{0.0};
|
||||
glm::dvec3 end{0.0};
|
||||
double start_extension{0.0};
|
||||
double end_extension{0.0};
|
||||
};
|
||||
|
||||
result compute(design const &Design);
|
||||
|
||||
26
editor/editorFormat.hpp
Normal file
26
editor/editorFormat.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
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 <cstdio>
|
||||
#include <string>
|
||||
|
||||
// printf-style formatting into a string, for the messages and the data lines of the editor
|
||||
template <typename... Args>
|
||||
std::string format(char const *Format, Args... Arguments)
|
||||
{
|
||||
auto const length{std::snprintf(nullptr, 0, Format, Arguments...)};
|
||||
if (length <= 0)
|
||||
return {};
|
||||
std::string text(static_cast<std::size_t>(length) + 1, '\0');
|
||||
std::snprintf(text.data(), text.size(), Format, Arguments...);
|
||||
text.resize(static_cast<std::size_t>(length));
|
||||
return text;
|
||||
}
|
||||
320
editor/editorGauge.cpp
Normal file
320
editor/editorGauge.cpp
Normal file
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
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/editorGauge.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
|
||||
namespace gauge
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// the widening runs up ahead of the change: on the inner side from 20 m before it, on the outer side from 26 m
|
||||
// to 6 m before it (figures 3 to 5 of the standard)
|
||||
double constexpr inner_lead{20.0};
|
||||
double constexpr outer_lead{26.0};
|
||||
double constexpr outer_full{6.0};
|
||||
|
||||
struct side_widening
|
||||
{
|
||||
double inner{0.0};
|
||||
double outer{0.0};
|
||||
};
|
||||
|
||||
// above the lower part the gauge includes the curves of R >= 250 m already
|
||||
side_widening upper_widening(double const Radius)
|
||||
{
|
||||
if (Radius >= 250.0)
|
||||
return {};
|
||||
return {std::max(0.0, 50.0 / Radius - 0.185), std::max(0.0, 60.0 / Radius - 0.225)};
|
||||
}
|
||||
|
||||
side_widening lower_widening(double const Radius)
|
||||
{
|
||||
if (Radius >= 250.0)
|
||||
return {3.75 / Radius, 3.75 / Radius};
|
||||
return upper_widening(Radius);
|
||||
}
|
||||
|
||||
// values at the points of a route, read as linear between them and continued past the ends
|
||||
class field
|
||||
{
|
||||
public:
|
||||
field(std::vector<double> const &Chainage, std::vector<double> Values) : m_chainage{Chainage}, m_values{std::move(Values)}, m_integral(m_values.size(), 0.0)
|
||||
{
|
||||
for (std::size_t i = 1; i < m_values.size(); ++i)
|
||||
m_integral[i] = m_integral[i - 1] + 0.5 * (m_values[i - 1] + m_values[i]) * (m_chainage[i] - m_chainage[i - 1]);
|
||||
}
|
||||
|
||||
double mean(double const From, double const To) const
|
||||
{
|
||||
return (integral(To) - integral(From)) / (To - From);
|
||||
}
|
||||
|
||||
// larger of the means over the window placed ahead of the point in both directions
|
||||
double ahead(double const Chainage, double const Near, double const Far) const
|
||||
{
|
||||
return std::max(mean(Chainage + Near, Chainage + Far), mean(Chainage - Far, Chainage - Near));
|
||||
}
|
||||
|
||||
private:
|
||||
double integral(double const Chainage) const
|
||||
{
|
||||
if (Chainage <= m_chainage.front())
|
||||
return m_values.front() * (Chainage - m_chainage.front());
|
||||
if (Chainage >= m_chainage.back())
|
||||
return m_integral.back() + m_values.back() * (Chainage - m_chainage.back());
|
||||
auto const next{static_cast<std::size_t>(std::upper_bound(m_chainage.begin(), m_chainage.end(), Chainage) - m_chainage.begin())};
|
||||
auto const i{next - 1};
|
||||
auto const span{m_chainage[next] - m_chainage[i]};
|
||||
auto const offset{Chainage - m_chainage[i]};
|
||||
auto const value{span > 0.0 ? m_values[i] + (m_values[next] - m_values[i]) * offset / span : m_values[i]};
|
||||
return m_integral[i] + 0.5 * (m_values[i] + value) * offset;
|
||||
}
|
||||
|
||||
std::vector<double> const &m_chainage;
|
||||
std::vector<double> m_values;
|
||||
std::vector<double> m_integral;
|
||||
};
|
||||
|
||||
// half of the distance between the heads of the rails
|
||||
double constexpr rail_spread{0.75};
|
||||
|
||||
// the point turned by the angle of the sine about the head of the lower rail
|
||||
place turn(section const &Section, place const &Point, double const Sine)
|
||||
{
|
||||
auto const pivot{Section.tilt > 0.0 ? -rail_spread : rail_spread};
|
||||
auto const cosine{std::sqrt(std::max(0.0, 1.0 - Sine * Sine))};
|
||||
auto const lateral{Point.lateral - pivot};
|
||||
return {pivot + lateral * cosine - Point.height * Sine, lateral * Sine + Point.height * cosine};
|
||||
}
|
||||
|
||||
// lower part common to all gauges, from the axis between the rails: the wheel zone at the running edge (717.5 mm
|
||||
// from the axis) with the flangeway of the dimension "b" 58 mm wide and 38 mm deep, the dimension "a" of 150 mm
|
||||
// at the rail top outside of it, then the limit installation gauge up to 380 mm
|
||||
std::vector<profile::point> const common_lower{{0.6595, 0.055}, {0.6595, -0.038}, {0.7175, -0.038}, {0.7175, 0.0}, {0.8675, 0.0},
|
||||
{0.8675, 0.055}, {1.211, 0.055}, {1.585, 0.380}, {1.675, 0.380}};
|
||||
|
||||
// the wheel zone follows the rails, without the widening in the curves
|
||||
bool wheel_zone(profile::point const &Point)
|
||||
{
|
||||
return Point.height <= 0.055 + 1e-9 && Point.half_width < 1.0;
|
||||
}
|
||||
|
||||
// names of the kinds in the file
|
||||
char const *const kind_names[]{"unified", "installation", "road"};
|
||||
|
||||
// outline from the points listed top down, in millimetres, as in the tables of the standard, over the lower part
|
||||
profile installation(std::string const &Name, std::vector<std::pair<int, int>> const &Points)
|
||||
{
|
||||
profile result{Name, kind::installation, common_lower};
|
||||
for (auto point = Points.rbegin(); point != Points.rend(); ++point)
|
||||
result.outline.push_back({point->second * 0.001, point->first * 0.001});
|
||||
return result;
|
||||
}
|
||||
|
||||
// point of the outline on the side (+1 left, -1 right) at the section
|
||||
place outline_point(profile const &Profile, section const &Section, std::size_t const Index, int const Side)
|
||||
{
|
||||
auto const &point{Profile.outline[Index]};
|
||||
// the step at the top of the lower part belongs to it from below
|
||||
bool const lower{point.height < lower_part - 1e-9 || (point.height < lower_part + 1e-9 && Index > 0 && Profile.outline[Index - 1].height < lower_part - 1e-9)};
|
||||
auto const i{Side > 0 ? 1 : 0};
|
||||
auto const widening{wheel_zone(point) ? 0.0 : lower ? Section.lower[i] : Section.upper[i] + Section.cant[i] * point.height / 1.5};
|
||||
auto const width{Section.base + point.half_width + widening};
|
||||
place const result{Side * width, point.height};
|
||||
return lower && Section.tilt != 0.0 ? turn(Section, result, Section.tilt) : result;
|
||||
}
|
||||
|
||||
// sign of the curvature at the points, from the nearest curve along the line where it's straight
|
||||
std::vector<double> curve_sides(std::vector<double> const &Curvature)
|
||||
{
|
||||
auto const side{[](double const Curvature) { return std::abs(Curvature) > 1e-6 ? (Curvature > 0.0 ? 1.0 : -1.0) : 0.0; }};
|
||||
std::vector<double> sides(Curvature.size(), 0.0);
|
||||
double last{0.0};
|
||||
for (std::size_t i = 0; i < Curvature.size(); ++i)
|
||||
sides[i] = last = side(Curvature[i]) != 0.0 ? side(Curvature[i]) : last;
|
||||
last = 0.0;
|
||||
for (std::size_t i = Curvature.size(); i-- > 0;)
|
||||
{
|
||||
if (side(Curvature[i]) != 0.0)
|
||||
last = side(Curvature[i]);
|
||||
else if (sides[i] == 0.0)
|
||||
sides[i] = last;
|
||||
}
|
||||
return sides;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
profile const *choice::of(int const Category) const
|
||||
{
|
||||
if ((Category & 1) != 0)
|
||||
return railway;
|
||||
if ((Category & 2) != 0)
|
||||
return road;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<profile> default_profiles()
|
||||
{
|
||||
// limit installation gauge up to 1170 mm, then the outline of the unified gauge
|
||||
auto lower{common_lower};
|
||||
lower.insert(lower.end(), {{1.675, 1.170}, {2.000, 1.170}});
|
||||
std::vector<profile::point> const gpl1{{2.000, 3.050}, {1.900, 3.850}, {1.800, 4.250}, {1.600, 4.500}, {1.450, 4.632}};
|
||||
std::vector<profile::point> const gpl2{{2.000, 3.625}, {1.920, 4.900}};
|
||||
std::vector<profile::point> const pantograph{{1.450, 6.600}, {1.150, 6.900}, {0.0, 6.900}};
|
||||
auto const make{[&](std::string const &Name, std::vector<std::vector<profile::point>> const &Parts) {
|
||||
profile result{Name, kind::unified, lower};
|
||||
for (auto const &part : Parts)
|
||||
result.outline.insert(result.outline.end(), part.begin(), part.end());
|
||||
return result;
|
||||
}};
|
||||
// tables 3, 5, 7, 9 of the type cards and the drawing of GC, above 380 mm
|
||||
return {make("GPL-1", {gpl1, {{1.260, 4.800}, {1.180, 4.850}, {0.0, 4.850}}}),
|
||||
make("GPL-1 + pantograf", {gpl1, pantograph}),
|
||||
make("GPL-2", {gpl2, {{0.0, 4.900}}}),
|
||||
make("GPL-2 + pantograf", {gpl2, {{1.450, 4.900}}, pantograph}),
|
||||
installation("G1 graniczna (GSZ)", {{4375, 0}, {4375, 666}, {4010, 1255}, {3700, 1550}, {3250, 1755}, {1170, 1695}, {1170, 1675}}),
|
||||
installation("G1 nominalna (NSZ)", {{4385, 0}, {4385, 785}, {4010, 1365}, {3700, 1655}, {3250, 1860}, {1170, 1860}, {1170, 1675}}),
|
||||
installation("G2 graniczna (GSZ)", {{4765, 0}, {4765, 940}, {3835, 1600}, {3530, 1765}, {1170, 1695}, {1170, 1675}}),
|
||||
installation("G2 nominalna (NSZ)", {{4770, 0}, {4770, 1060}, {3835, 1710}, {3530, 1870}, {1170, 1870}, {1170, 1675}}),
|
||||
installation("GA graniczna (GSZ)", {{4415, 0}, {4415, 687}, {4080, 1225}, {3808, 1490}, {3250, 1755}, {1170, 1700}, {1170, 1675}}),
|
||||
installation("GA nominalna (NSZ)", {{4425, 0}, {4425, 805}, {4080, 1340}, {3880, 1600}, {3250, 1860}, {1170, 1860}, {1170, 1675}}),
|
||||
installation("GB graniczna (GSZ)", {{4415, 0}, {4415, 687}, {4080, 1495}, {3250, 1755}, {1170, 1700}, {1170, 1675}}),
|
||||
installation("GB nominalna (NSZ)", {{4425, 0}, {4425, 805}, {4110, 1610}, {3250, 1860}, {1170, 1860}, {1170, 1675}}),
|
||||
installation("GC graniczna (GSZ)", {{4800, 0}, {4800, 1693}, {3550, 1765}, {1170, 1765}, {1170, 1675}}),
|
||||
installation("GC nominalna (NSZ)", {{4820, 0}, {4820, 1815}, {3550, 1870}, {1170, 1870}, {1170, 1675}}),
|
||||
{"Droga 4,70 m", kind::road, {{0.5, 0.0}, {0.5, 4.70}}},
|
||||
{"Droga 4,50 m", kind::road, {{0.5, 0.0}, {0.5, 4.50}}}};
|
||||
}
|
||||
|
||||
// one outline per line: the name in quotes, the kind, then pairs of half-width and height
|
||||
std::vector<profile> load_profiles(std::string const &File)
|
||||
{
|
||||
std::ifstream input{File};
|
||||
std::vector<profile> profiles;
|
||||
std::string line;
|
||||
while (std::getline(input, line))
|
||||
{
|
||||
std::istringstream stream{line};
|
||||
profile profile;
|
||||
std::string kind;
|
||||
if (false == static_cast<bool>(stream >> std::quoted(profile.name) >> kind))
|
||||
continue;
|
||||
auto const known{std::find(std::begin(kind_names), std::end(kind_names), kind)};
|
||||
if (known == std::end(kind_names))
|
||||
continue;
|
||||
profile.kind = static_cast<gauge::kind>(known - std::begin(kind_names));
|
||||
profile::point point;
|
||||
while (stream >> point.half_width >> point.height)
|
||||
profile.outline.push_back(point);
|
||||
if (profile.outline.size() >= 2)
|
||||
profiles.push_back(std::move(profile));
|
||||
}
|
||||
return profiles.empty() ? default_profiles() : profiles;
|
||||
}
|
||||
|
||||
void save_profiles(std::string const &File, std::vector<profile> const &Profiles)
|
||||
{
|
||||
std::ofstream output{File};
|
||||
for (auto const &profile : Profiles)
|
||||
{
|
||||
output << std::quoted(profile.name) << ' ' << kind_names[static_cast<int>(profile.kind)];
|
||||
for (auto const &point : profile.outline)
|
||||
output << ' ' << point.half_width << ' ' << point.height;
|
||||
output << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<section> sections(kind const Kind, std::vector<double> const &Chainage, std::vector<double> const &Curvature, std::vector<double> const &Cant)
|
||||
{
|
||||
auto const count{Chainage.size()};
|
||||
std::vector<section> result(count);
|
||||
if (count == 0 || Kind == kind::road)
|
||||
return result;
|
||||
// per side, the parts when it's the inner side of the curve and when it's the outer one
|
||||
for (int side = 0; side < 2; ++side)
|
||||
{
|
||||
std::vector<double> upperinner(count), upperouter(count), lowerinner(count), lowerouter(count), cantinner(count), cantouter(count);
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
if (std::abs(Curvature[i]) < 1e-6)
|
||||
continue;
|
||||
auto const radius{1.0 / std::abs(Curvature[i])};
|
||||
bool const inner{(Curvature[i] > 0.0) == (side == 1)};
|
||||
auto const upper{Kind == kind::unified ? upper_widening(radius) : lower_widening(radius)};
|
||||
auto const lower{lower_widening(radius)};
|
||||
(inner ? upperinner : upperouter)[i] = inner ? upper.inner : upper.outer;
|
||||
(inner ? lowerinner : lowerouter)[i] = inner ? lower.inner : lower.outer;
|
||||
(inner ? cantinner : cantouter)[i] = Cant[i];
|
||||
}
|
||||
field const fields[]{{Chainage, std::move(upperinner)}, {Chainage, std::move(upperouter)}, {Chainage, std::move(lowerinner)},
|
||||
{Chainage, std::move(lowerouter)}, {Chainage, std::move(cantinner)}, {Chainage, std::move(cantouter)}};
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
auto const inner{[&](field const &Field) { return Field.ahead(Chainage[i], 0.0, inner_lead); }};
|
||||
auto const outer{[&](field const &Field) { return Field.ahead(Chainage[i], outer_full, outer_lead); }};
|
||||
result[i].upper[side] = inner(fields[0]) + outer(fields[1]);
|
||||
result[i].lower[side] = inner(fields[2]) + outer(fields[3]);
|
||||
result[i].cant[side] = inner(fields[4]) - outer(fields[5]);
|
||||
}
|
||||
}
|
||||
auto const sides{curve_sides(Curvature)};
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
result[i].tilt = -sides[i] * std::min(1.0, Cant[i] / 1.5);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ring(profile const &Profile, section const &Section, std::vector<place> &Points)
|
||||
{
|
||||
auto const count{Profile.outline.size()};
|
||||
Points.clear();
|
||||
for (std::size_t j = 0; j < count; ++j)
|
||||
Points.push_back(outline_point(Profile, Section, j, -1));
|
||||
for (std::size_t j = count; j-- > 0;)
|
||||
Points.push_back(outline_point(Profile, Section, j, 1));
|
||||
}
|
||||
|
||||
double intrusion(std::vector<place> const &Ring, place const &Point)
|
||||
{
|
||||
if (Ring.size() < 3)
|
||||
return 0.0;
|
||||
bool inside{false};
|
||||
auto nearest{std::numeric_limits<double>::max()};
|
||||
auto const *previous{&Ring.back()};
|
||||
for (auto const ¤t : Ring)
|
||||
{
|
||||
auto const lateral{current.lateral - previous->lateral};
|
||||
auto const height{current.height - previous->height};
|
||||
if ((current.height > Point.height) != (previous->height > Point.height) && Point.lateral < previous->lateral + lateral * (Point.height - previous->height) / height)
|
||||
inside = false == inside;
|
||||
auto const length{lateral * lateral + height * height};
|
||||
auto const t{length > 0.0 ? std::clamp(((Point.lateral - previous->lateral) * lateral + (Point.height - previous->height) * height) / length, 0.0, 1.0) : 0.0};
|
||||
nearest = std::min(nearest, std::hypot(Point.lateral - previous->lateral - lateral * t, Point.height - previous->height - height * t));
|
||||
previous = ¤t;
|
||||
}
|
||||
return inside ? nearest : -nearest;
|
||||
}
|
||||
|
||||
double widest(profile const &Profile)
|
||||
{
|
||||
double result{0.0};
|
||||
for (auto const &point : Profile.outline)
|
||||
result = std::max(result, point.half_width);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace gauge
|
||||
95
editor/editorGauge.hpp
Normal file
95
editor/editorGauge.hpp
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
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 <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// structure gauge (skrajnia budowli) of the PKP PLK technical standards, volume II: the space along the track
|
||||
// which has to stay clear of the objects
|
||||
namespace gauge
|
||||
{
|
||||
|
||||
// how the outline widens along the track
|
||||
enum class kind
|
||||
{
|
||||
unified, // skrajnia budowli ujednolicona GPL: the curves of R >= 250 m are included above the lower part
|
||||
installation, // graniczna or nominalna skrajnia zabudowy: widened by 3750/R in all curves
|
||||
road // skrajnia drogi: measured from the edge of the carriageway, no widening
|
||||
};
|
||||
|
||||
// right half of the outline on a straight track, in metres: half-width from the axis (for the roads from the edge
|
||||
// of the carriageway) and height above the rail top or the road surface, listed from the bottom up; the left half
|
||||
// is the mirror
|
||||
struct profile
|
||||
{
|
||||
std::string name;
|
||||
gauge::kind kind{kind::unified};
|
||||
struct point
|
||||
{
|
||||
double half_width{0.0};
|
||||
double height{0.0};
|
||||
};
|
||||
std::vector<point> outline;
|
||||
};
|
||||
|
||||
// the outlines checked along the paths: the railway one on the tracks, the road one on the roads
|
||||
struct choice
|
||||
{
|
||||
profile const *railway{nullptr};
|
||||
profile const *road{nullptr};
|
||||
// by the category flags of the path, 1 a track and 2 a road; null when there's no outline for it
|
||||
profile const *of(int Category) const;
|
||||
};
|
||||
|
||||
// GPL-1 and GPL-2 over the limit installation gauge below 1170 mm, with and without the pantograph gauge, the limit
|
||||
// and nominal installation gauges G1, G2, GA, GB, GC of the PKP PLK technical standards, and the road gauges
|
||||
std::vector<profile> default_profiles();
|
||||
// outlines kept in the file, the defaults when there's none
|
||||
std::vector<profile> load_profiles(std::string const &File);
|
||||
void save_profiles(std::string const &File, std::vector<profile> const &Profiles);
|
||||
|
||||
// the points of the paths in the scenery lie at the rail foot, the gauge is measured from the rail head
|
||||
double constexpr rail_height{0.18};
|
||||
|
||||
// below this height the outline is the limit installation gauge, which leaves the room for the platforms
|
||||
double constexpr lower_part{1.17};
|
||||
|
||||
// widening of the sides of the gauge at a place of the track, metres; index 0 is the right side, 1 the left one
|
||||
struct section
|
||||
{
|
||||
double base{0.0}; // added to the half-width of the outline: half of the carriageway of a road
|
||||
std::array<double, 2> upper{}; // from the radius, above the lower part
|
||||
std::array<double, 2> lower{}; // from the radius, in the lower part
|
||||
std::array<double, 2> cant{}; // cant in metres, positive on the inner side of the curve and negative on the outer one
|
||||
double tilt{0.0}; // sine of the slope of the plane of the rail heads, positive with the left rail raised
|
||||
};
|
||||
// point of the cross-section, metres: lateral positive to the left, height above the rail top
|
||||
struct place
|
||||
{
|
||||
double lateral{0.0};
|
||||
double height{0.0};
|
||||
};
|
||||
// sections at the points of a route, from their chainage, curvature (1/m, positive turning left) and cant (m).
|
||||
// The widening begins and ends ahead of the changes of the curvature and cant, as the vehicles enter a curve;
|
||||
// the plane of the rail heads tilts with the cant, the outer rail raised
|
||||
std::vector<section> sections(kind Kind, std::vector<double> const &Chainage, std::vector<double> const &Curvature, std::vector<double> const &Cant);
|
||||
|
||||
// the outline across the track at the section, from the bottom right up and down to the bottom left, closed across
|
||||
// the bottom. With the widening: the cant shifts the part above the lower one, the lower part turns with the plane
|
||||
// of the rail heads instead
|
||||
void ring(profile const &Profile, section const &Section, std::vector<place> &Points);
|
||||
// distance of the point from the ring in any direction, metres, positive inside and negative outside
|
||||
double intrusion(std::vector<place> const &Ring, place const &Point);
|
||||
// largest half-width of the outline, metres
|
||||
double widest(profile const &Profile);
|
||||
|
||||
} // namespace gauge
|
||||
131
editor/editorGeometry.hpp
Normal file
131
editor/editorGeometry.hpp
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
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 <algorithm>
|
||||
#include <cmath>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "world/Segment.h"
|
||||
|
||||
// plan (x, z) geometry and cubic Bezier curves of the paths, shared by the track tools
|
||||
namespace geometry
|
||||
{
|
||||
|
||||
// points of the paths closer than this are taken for the same point
|
||||
double constexpr same_point{0.05};
|
||||
|
||||
inline glm::dvec2 plan_of(glm::dvec3 const &Point)
|
||||
{
|
||||
return {Point.x, Point.z};
|
||||
}
|
||||
|
||||
inline double plan_distance(glm::dvec3 const &A, glm::dvec3 const &B)
|
||||
{
|
||||
return std::hypot(A.x - B.x, A.z - B.z);
|
||||
}
|
||||
|
||||
// z component of the 3d cross product; for a unit Direction it's the offset of the Point to its left
|
||||
inline double cross(glm::dvec2 const &Direction, glm::dvec2 const &Point)
|
||||
{
|
||||
return Direction.x * Point.y - Direction.y * Point.x;
|
||||
}
|
||||
|
||||
inline glm::dvec2 turned(glm::dvec2 const &Vector, double const Angle)
|
||||
{
|
||||
auto const c{std::cos(Angle)};
|
||||
auto const s{std::sin(Angle)};
|
||||
return {Vector.x * c - Vector.y * s, Vector.x * s + Vector.y * c};
|
||||
}
|
||||
|
||||
inline double signed_angle(glm::dvec2 const &From, glm::dvec2 const &To)
|
||||
{
|
||||
return std::atan2(cross(From, To), glm::dot(From, To));
|
||||
}
|
||||
|
||||
// slope of the direction, rise over the run in the plan
|
||||
inline double grade_of(glm::dvec3 const &Direction)
|
||||
{
|
||||
return Direction.y / std::max(1e-9, std::hypot(Direction.x, Direction.z));
|
||||
}
|
||||
|
||||
// path as a cubic Bezier curve; a path without control points is a straight with evenly spread parameter
|
||||
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(p0)};
|
||||
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, int const Steps = 256) const
|
||||
{
|
||||
double length{0.0};
|
||||
auto previous{plan_of(p0)};
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// distance between the ends of the path, in the plan
|
||||
inline double chord_length(segment_data const &Path)
|
||||
{
|
||||
return glm::length(plan_of(Path.points[segment_data::point::end] - Path.points[segment_data::point::start]));
|
||||
}
|
||||
|
||||
} // namespace geometry
|
||||
284
editor/editorInfra.cpp
Normal file
284
editor/editorInfra.cpp
Normal file
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
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/editorInfra.hpp"
|
||||
|
||||
#include "editor/editorGeometry.hpp"
|
||||
#include "world/Track.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
|
||||
namespace infra
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using geometry::bezier;
|
||||
using geometry::plan_distance;
|
||||
using geometry::plan_of;
|
||||
|
||||
int const kSteps{128};
|
||||
|
||||
// arc length in the plan at the evenly spread parameters
|
||||
std::array<double, kSteps + 1> lengths(bezier const &Curve)
|
||||
{
|
||||
std::array<double, kSteps + 1> result{};
|
||||
auto previous{Curve.p0};
|
||||
for (int i = 1; i <= kSteps; ++i)
|
||||
{
|
||||
auto const next{Curve.point(static_cast<double>(i) / kSteps)};
|
||||
result[i] = result[i - 1] + plan_distance(previous, next);
|
||||
previous = next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
double parameter(std::array<double, kSteps + 1> const &Lengths, double const S)
|
||||
{
|
||||
if (S <= 0.0)
|
||||
return 0.0;
|
||||
if (S >= Lengths.back())
|
||||
return 1.0;
|
||||
auto const next{std::upper_bound(Lengths.begin(), Lengths.end(), S)};
|
||||
auto const index{static_cast<int>(std::distance(Lengths.begin(), next)) - 1};
|
||||
auto const run{Lengths[index + 1] - Lengths[index]};
|
||||
auto const fraction{run > 1e-12 ? (S - Lengths[index]) / run : 0.0};
|
||||
return (index + fraction) / kSteps;
|
||||
}
|
||||
|
||||
double length_at(std::array<double, kSteps + 1> const &Lengths, double const T)
|
||||
{
|
||||
auto const position{std::clamp(T, 0.0, 1.0) * kSteps};
|
||||
auto const index{std::min(static_cast<int>(position), kSteps - 1)};
|
||||
return Lengths[index] + (Lengths[index + 1] - Lengths[index]) * (position - index);
|
||||
}
|
||||
|
||||
glm::dvec2 plan_direction(bezier const &Curve, double const T)
|
||||
{
|
||||
auto tangent{Curve.first(T)};
|
||||
glm::dvec2 result{tangent.x, tangent.z};
|
||||
if (glm::length(result) < 1e-9)
|
||||
result = {Curve.p3.x - Curve.p0.x, Curve.p3.z - Curve.p0.z};
|
||||
return glm::length(result) > 1e-9 ? glm::normalize(result) : glm::dvec2{0.0, 1.0};
|
||||
}
|
||||
|
||||
std::string lower(std::string Text)
|
||||
{
|
||||
std::transform(Text.begin(), Text.end(), Text.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return Text;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
char const *name(kind const Kind)
|
||||
{
|
||||
switch (Kind)
|
||||
{
|
||||
case kind::model: return "model";
|
||||
case kind::include: return "include";
|
||||
case kind::traction: return "traction";
|
||||
case kind::memcell: return "memcell";
|
||||
case kind::launcher: return "eventlauncher";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
char const *name(category const Category)
|
||||
{
|
||||
switch (Category)
|
||||
{
|
||||
case category::signal: return "Signals";
|
||||
case category::sign: return "Signs";
|
||||
case category::pole: return "Poles and gantries";
|
||||
case category::catenary: return "Catenary";
|
||||
case category::logic: return "Logic";
|
||||
case category::other: return "Other";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<category> const &categories()
|
||||
{
|
||||
static std::vector<category> const result{category::signal, category::sign, category::pole, category::catenary, category::logic, category::other};
|
||||
return result;
|
||||
}
|
||||
|
||||
double plan_length(segment_data const &Path)
|
||||
{
|
||||
return lengths(bezier{Path}).back();
|
||||
}
|
||||
|
||||
std::vector<glm::dvec3> outline(segment_data const &Path, double const Step)
|
||||
{
|
||||
bezier const shape{Path};
|
||||
// the speed along the curve doesn't exceed three times the longest leg of the control polygon
|
||||
auto const leg{std::max({plan_distance(shape.p0, shape.p1), plan_distance(shape.p1, shape.p2), plan_distance(shape.p2, shape.p3)})};
|
||||
auto const count{std::clamp(static_cast<int>(std::ceil(3.0 * leg / std::max(Step, 0.1))), 1, 4096)};
|
||||
std::vector<glm::dvec3> result;
|
||||
result.reserve(count + 1);
|
||||
for (int i = 0; i <= count; ++i)
|
||||
result.push_back(shape.point(static_cast<double>(i) / count));
|
||||
return result;
|
||||
}
|
||||
|
||||
frame frame_at(segment_data const &Path, double const S)
|
||||
{
|
||||
bezier const shape{Path};
|
||||
auto const t{parameter(lengths(shape), S)};
|
||||
return {shape.point(t), plan_direction(shape, t)};
|
||||
}
|
||||
|
||||
frame frame_at(station const &Station)
|
||||
{
|
||||
if (Station.track == nullptr || Station.path < 0 || Station.path >= static_cast<int>(Station.track->m_paths.size()))
|
||||
return {};
|
||||
return frame_at(Station.track->m_paths[Station.path], Station.s);
|
||||
}
|
||||
|
||||
double project(segment_data const &Path, glm::dvec3 const &Point, double &S, bool &Interior)
|
||||
{
|
||||
bezier const shape{Path};
|
||||
auto const table{lengths(shape)};
|
||||
auto const distance = [&](double const T) { return plan_distance(shape.point(T), Point); };
|
||||
auto best{0};
|
||||
auto bestdistance{distance(0.0)};
|
||||
for (int i = 1; i <= kSteps; ++i)
|
||||
{
|
||||
auto const d{distance(static_cast<double>(i) / kSteps)};
|
||||
if (d < bestdistance)
|
||||
{
|
||||
bestdistance = d;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
// golden section search around the nearest sample
|
||||
auto low{std::max(0, best - 1) / static_cast<double>(kSteps)};
|
||||
auto high{std::min(kSteps, best + 1) / static_cast<double>(kSteps)};
|
||||
double const ratio{0.6180339887498949};
|
||||
auto a{high - ratio * (high - low)};
|
||||
auto b{low + ratio * (high - low)};
|
||||
auto da{distance(a)}, db{distance(b)};
|
||||
for (int i = 0; i < 40; ++i)
|
||||
{
|
||||
if (da < db)
|
||||
{
|
||||
high = b;
|
||||
b = a;
|
||||
db = da;
|
||||
a = high - ratio * (high - low);
|
||||
da = distance(a);
|
||||
}
|
||||
else
|
||||
{
|
||||
low = a;
|
||||
a = b;
|
||||
da = db;
|
||||
b = low + ratio * (high - low);
|
||||
db = distance(b);
|
||||
}
|
||||
}
|
||||
auto t{(low + high) * 0.5};
|
||||
if (distance(0.0) <= distance(t))
|
||||
t = 0.0;
|
||||
if (distance(1.0) <= distance(t))
|
||||
t = 1.0;
|
||||
S = length_at(table, t);
|
||||
Interior = (t > 1e-6 && t < 1.0 - 1e-6);
|
||||
return distance(t);
|
||||
}
|
||||
|
||||
double project(TTrack &Track, glm::dvec3 const &Point, station &Station, bool &Interior)
|
||||
{
|
||||
auto best{-1.0};
|
||||
for (int i = 0; i < static_cast<int>(Track.m_paths.size()); ++i)
|
||||
{
|
||||
double s;
|
||||
bool interior;
|
||||
auto const d{project(Track.m_paths[i], Point, s, interior)};
|
||||
// an interior point takes precedence over an end shared with the other path
|
||||
if (best < 0.0 || d < best - 1e-6 || (std::abs(d - best) <= 1e-6 && interior && false == Interior))
|
||||
{
|
||||
best = d;
|
||||
Station = {&Track, i, s};
|
||||
Interior = interior;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
double heading(glm::dvec2 const &Forward)
|
||||
{
|
||||
return glm::degrees(std::atan2(Forward.x, Forward.y));
|
||||
}
|
||||
|
||||
anchor make_anchor(TTrack *Track, int const Path, glm::dvec3 const &Point)
|
||||
{
|
||||
double s;
|
||||
bool interior;
|
||||
project(Track->m_paths[Path], Point, s, interior);
|
||||
return make_anchor({Track, Path, s}, Point);
|
||||
}
|
||||
|
||||
anchor make_anchor(station const &At, glm::dvec3 const &Point)
|
||||
{
|
||||
anchor result;
|
||||
result.at = At;
|
||||
auto const axis{frame_at(At)};
|
||||
glm::dvec2 const side{axis.forward.y, -axis.forward.x};
|
||||
result.offset = glm::dot(plan_of(Point - axis.point), side);
|
||||
result.height = Point.y - axis.point.y;
|
||||
result.foot = axis.point;
|
||||
if (At.track != nullptr && At.path >= 0 && At.path < static_cast<int>(At.track->m_paths.size()))
|
||||
{
|
||||
auto const &path{At.track->m_paths[At.path]};
|
||||
result.start = path.points[segment_data::point::start];
|
||||
result.end = path.points[segment_data::point::end];
|
||||
result.length = plan_length(path);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::dvec3 place(anchor &Anchor)
|
||||
{
|
||||
auto const axis{frame_at(Anchor.at)};
|
||||
Anchor.foot = axis.point;
|
||||
glm::dvec2 const side{axis.forward.y, -axis.forward.x};
|
||||
return {axis.point.x + side.x * Anchor.offset, axis.point.y + Anchor.height, axis.point.z + side.y * Anchor.offset};
|
||||
}
|
||||
|
||||
std::vector<rule> default_rules()
|
||||
{
|
||||
return {
|
||||
{category::signal, "semafor sem_ sem- tarcza tarcz_ tz_ tm_ tzw powtarz sygnaliz sygnal signal"},
|
||||
{category::sign, "wskaz wsk_ wsk- w4 w8 w11 w24 w27 kilometr hektometr slupek_km znak tablic sign"},
|
||||
{category::catenary, "trakcj siec_ siec- catenary przewod lina_nosna"},
|
||||
{category::pole, "slup maszt wysieg bramk konstrukcj kotw odciag pole gantry"},
|
||||
};
|
||||
}
|
||||
|
||||
category classify(std::string const &Name, std::vector<rule> const &Rules)
|
||||
{
|
||||
auto const text{lower(Name)};
|
||||
for (auto const &rule : Rules)
|
||||
{
|
||||
std::istringstream words{lower(rule.keywords)};
|
||||
std::string word;
|
||||
while (words >> word)
|
||||
if (text.find(word) != std::string::npos)
|
||||
return rule.group;
|
||||
}
|
||||
return category::other;
|
||||
}
|
||||
|
||||
} // namespace infra
|
||||
130
editor/editorInfra.hpp
Normal file
130
editor/editorInfra.hpp
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
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 <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "world/Segment.h"
|
||||
#include "scene/scenenode.h"
|
||||
|
||||
class TTrack;
|
||||
|
||||
// objects which belong to the railway infrastructure (signals, signs, catenary, poles, the logic placed along the
|
||||
// line) bound to the paths they stand by, so they follow the changes of the track geometry
|
||||
namespace infra
|
||||
{
|
||||
|
||||
enum class kind
|
||||
{
|
||||
model,
|
||||
include,
|
||||
traction,
|
||||
memcell,
|
||||
launcher
|
||||
};
|
||||
enum class category
|
||||
{
|
||||
signal,
|
||||
sign,
|
||||
pole,
|
||||
catenary,
|
||||
logic,
|
||||
other
|
||||
};
|
||||
char const *name(kind Kind);
|
||||
char const *name(category Category);
|
||||
std::vector<category> const &categories();
|
||||
|
||||
// point of a path, measured in the plan from the start of the path
|
||||
struct station
|
||||
{
|
||||
TTrack *track{nullptr};
|
||||
int path{0};
|
||||
double s{0.0};
|
||||
};
|
||||
struct frame
|
||||
{
|
||||
glm::dvec3 point{0.0};
|
||||
glm::dvec2 forward{0.0, 1.0}; // in the plan, x and z
|
||||
};
|
||||
|
||||
double plan_length(segment_data const &Path);
|
||||
// points of the path, no farther apart in the plan than about specified step; the ends included
|
||||
std::vector<glm::dvec3> outline(segment_data const &Path, double Step);
|
||||
frame frame_at(segment_data const &Path, double S);
|
||||
frame frame_at(station const &Station);
|
||||
// nearest point of specified path in the plan. returns: distance in the plan; Interior tells the nearest point
|
||||
// isn't one of the ends of the path
|
||||
double project(segment_data const &Path, glm::dvec3 const &Point, double &S, bool &Interior);
|
||||
// nearest point among the paths of the track
|
||||
double project(TTrack &Track, glm::dvec3 const &Point, station &Station, bool &Interior);
|
||||
// angle around the vertical axis which turns a model to face specified direction, in degrees
|
||||
double heading(glm::dvec2 const &Forward);
|
||||
|
||||
// location of a point relative to the rail axis: the station, a sideways offset and a height above the rail top
|
||||
struct anchor
|
||||
{
|
||||
station at;
|
||||
double offset{0.0}; // positive to the right of the direction of the path
|
||||
double height{0.0};
|
||||
glm::dvec3 foot{0.0}; // point of the axis the location was measured from
|
||||
// ends and length of the path when the station was measured, to tell whether the path changed since
|
||||
glm::dvec3 start{0.0}, end{0.0};
|
||||
double length{0.0};
|
||||
};
|
||||
anchor make_anchor(station const &At, glm::dvec3 const &Point);
|
||||
// anchor at the place of the path nearest to the point
|
||||
anchor make_anchor(TTrack *Track, int const Path, glm::dvec3 const &Point);
|
||||
// location for the current geometry of the path
|
||||
glm::dvec3 place(anchor &Anchor);
|
||||
|
||||
// rules telling infrastructure apart by the names of the files and nodes
|
||||
struct rule
|
||||
{
|
||||
category group{category::other};
|
||||
std::string keywords; // separated by spaces, matched as parts of the lower case name
|
||||
};
|
||||
std::vector<rule> default_rules();
|
||||
// first rule in the list which matches. returns: category::other if none does
|
||||
category classify(std::string const &Name, std::vector<rule> const &Rules);
|
||||
|
||||
struct binding
|
||||
{
|
||||
kind type{kind::model};
|
||||
category group{category::other};
|
||||
scene::basic_node *node{nullptr}; // the object, unless it's an include
|
||||
scene::instance_handle include{0};
|
||||
std::vector<anchor> anchors; // one per point which defines the object: four for a traction piece
|
||||
double yaw{0.0}; // rotation of the object relative to the direction of the path
|
||||
bool turns{false}; // the object has a rotation which follows the path
|
||||
std::string label;
|
||||
bool lost{false}; // none of the paths around could take the object over
|
||||
|
||||
bool same(binding const &Other) const
|
||||
{
|
||||
return type == Other.type && node == Other.node && include == Other.include;
|
||||
}
|
||||
};
|
||||
|
||||
// placement of a bound object, to take a change back
|
||||
struct object_state
|
||||
{
|
||||
binding saved;
|
||||
glm::dvec3 location{0.0};
|
||||
glm::vec3 angles{0.f};
|
||||
std::array<glm::dvec3, 4> points{};
|
||||
std::string directive;
|
||||
bool moved{false}; // the object followed a change since the state was taken
|
||||
};
|
||||
|
||||
} // namespace infra
|
||||
415
editor/editorProfile.cpp
Normal file
415
editor/editorProfile.cpp
Normal file
@@ -0,0 +1,415 @@
|
||||
/*
|
||||
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 "editor/editorFormat.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <optional>
|
||||
|
||||
namespace profile
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -9,6 +9,8 @@ http:
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "editor/editorTrack.hpp"
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
|
||||
#include "world/Track.h"
|
||||
#include "vehicle/DynObj.h"
|
||||
@@ -27,12 +29,20 @@ http:
|
||||
#include <numeric>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
double const kSamePoint{0.05};
|
||||
using geometry::bezier;
|
||||
using geometry::chord_length;
|
||||
using geometry::cross;
|
||||
using geometry::plan_of;
|
||||
using geometry::signed_angle;
|
||||
using geometry::turned;
|
||||
|
||||
double const kSamePoint{geometry::same_point};
|
||||
|
||||
segment_data::point point_index(editor_track::point_kind const Kind)
|
||||
{
|
||||
@@ -45,11 +55,6 @@ segment_data::point point_index(editor_track::point_kind const Kind)
|
||||
}
|
||||
}
|
||||
|
||||
bool is_end(editor_track::point_kind const Kind)
|
||||
{
|
||||
return Kind == editor_track::point_kind::start || Kind == editor_track::point_kind::end;
|
||||
}
|
||||
|
||||
bool has_switch_paths(TTrack const &Track)
|
||||
{
|
||||
return Track.SwitchExtension != nullptr && (Track.eType == tt_Switch || Track.eType == tt_Cross);
|
||||
@@ -74,17 +79,11 @@ glm::dvec3 path_tangent(segment_data const &Path, bool const Atend)
|
||||
|
||||
double path_radius(segment_data const &Path, double const T)
|
||||
{
|
||||
auto const &p0{Path.points[segment_data::point::start]};
|
||||
auto const &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{})
|
||||
if (Path.points[segment_data::point::control1] == glm::dvec3{} && Path.points[segment_data::point::control2] == glm::dvec3{})
|
||||
return 0.0;
|
||||
auto const p1{p0 + control1};
|
||||
auto const p2{p3 + control2};
|
||||
auto const u{1.0 - T};
|
||||
auto const first{3.0 * u * u * (p1 - p0) + 6.0 * u * T * (p2 - p1) + 3.0 * T * T * (p3 - p2)};
|
||||
auto const second{6.0 * u * (p2 - 2.0 * p1 + p0) + 6.0 * T * (p3 - 2.0 * p2 + p1)};
|
||||
bezier const curve{Path};
|
||||
auto const first{curve.first(T)};
|
||||
auto const second{curve.second(T)};
|
||||
auto const speed{std::hypot(first.x, first.z)};
|
||||
auto const curvature{std::abs(first.x * second.z - first.z * second.x) / (speed * speed * speed)};
|
||||
return curvature > 1e-7 ? 1.0 / curvature : 0.0;
|
||||
@@ -123,8 +122,12 @@ bool adjoining_direction(TTrack const *Neighbour, glm::dvec3 const &Joint, bool
|
||||
|
||||
}
|
||||
|
||||
editor_track::observer *editor_track::s_observer{nullptr};
|
||||
|
||||
editor_track::state editor_track::capture(TTrack const &Track)
|
||||
{
|
||||
if (s_observer != nullptr)
|
||||
s_observer->track_captured(Track);
|
||||
state result;
|
||||
result.paths = Track.m_paths;
|
||||
result.velocity = Track.fVelocity;
|
||||
@@ -237,6 +240,8 @@ void editor_track::commit(std::vector<TTrack *> const &Tracks)
|
||||
for (auto *track : affected)
|
||||
for (auto *vehicle : track->Dynamics)
|
||||
vehicle->Move(0.000001);
|
||||
if (s_observer != nullptr)
|
||||
s_observer->tracks_committed(Tracks);
|
||||
}
|
||||
|
||||
void editor_track::commit_parameters(TTrack &Track)
|
||||
@@ -384,7 +389,7 @@ std::vector<std::pair<TTrack *, editor_track::point_ref>> editor_track::connecte
|
||||
|
||||
bool editor_track::is_connected(TTrack const &Track, point_ref const &Point)
|
||||
{
|
||||
if (false == is_end(Point.kind))
|
||||
if (false == editor_track::is_end(Point.kind))
|
||||
return false;
|
||||
return false == connected_points(Track, point_position(Track, Point)).empty();
|
||||
}
|
||||
@@ -447,7 +452,7 @@ editor_track::snap_target editor_track::find_snap_target(TTrack const &Track, gl
|
||||
|
||||
void editor_track::snap_point(TTrack &Track, point_ref const &Point, snap_target const &Target, bool const Aligntangent)
|
||||
{
|
||||
if (Target.track == nullptr || false == is_end(Point.kind))
|
||||
if (Target.track == nullptr || false == editor_track::is_end(Point.kind))
|
||||
return;
|
||||
|
||||
move_point(Track, Point, Target.position);
|
||||
@@ -777,23 +782,6 @@ bool editor_track::find_chain(TTrack *From, TTrack *To, chain &Chain, std::strin
|
||||
namespace
|
||||
{
|
||||
|
||||
glm::dvec2 plan_of(glm::dvec3 const &Point)
|
||||
{
|
||||
return {Point.x, Point.z};
|
||||
}
|
||||
|
||||
glm::dvec2 turned(glm::dvec2 const &Vector, double const Angle)
|
||||
{
|
||||
auto const c{std::cos(Angle)};
|
||||
auto const s{std::sin(Angle)};
|
||||
return {Vector.x * c - Vector.y * s, Vector.x * s + Vector.y * c};
|
||||
}
|
||||
|
||||
double lateral_of(glm::dvec2 const &Direction, glm::dvec2 const &Offset)
|
||||
{
|
||||
return Direction.x * Offset.y - Direction.y * Offset.x;
|
||||
}
|
||||
|
||||
bool plan_straight(segment_data const &Path, editor_track::straight_tolerance const &Tolerance)
|
||||
{
|
||||
auto const chord{plan_of(Path.points[segment_data::point::end] - Path.points[segment_data::point::start])};
|
||||
@@ -808,7 +796,7 @@ bool plan_straight(segment_data const &Path, editor_track::straight_tolerance co
|
||||
return true;
|
||||
auto const direction{chord / length};
|
||||
for (auto const index : {segment_data::point::control1, segment_data::point::control2})
|
||||
if (std::abs(lateral_of(direction, plan_of(Path.points[index]))) > Tolerance.offset)
|
||||
if (std::abs(cross(direction, plan_of(Path.points[index]))) > Tolerance.offset)
|
||||
return false;
|
||||
auto const tangentstart{plan_of(path_tangent(Path, false))};
|
||||
auto const tangentend{plan_of(path_tangent(Path, true))};
|
||||
@@ -816,8 +804,8 @@ bool plan_straight(segment_data const &Path, editor_track::straight_tolerance co
|
||||
return false;
|
||||
auto const a{glm::normalize(tangentstart)};
|
||||
auto const b{glm::normalize(tangentend)};
|
||||
auto const turn{std::abs(std::atan2(lateral_of(a, b), glm::dot(a, b)))};
|
||||
auto const deviation{std::max(std::abs(lateral_of(direction, a)), std::abs(lateral_of(direction, b)))};
|
||||
auto const turn{std::abs(std::atan2(cross(a, b), glm::dot(a, b)))};
|
||||
auto const deviation{std::max(std::abs(cross(direction, a)), std::abs(cross(direction, b)))};
|
||||
return turn * Tolerance.radius <= length && deviation * Tolerance.radius <= length;
|
||||
}
|
||||
|
||||
@@ -841,11 +829,6 @@ glm::dvec2 direction_at(TTrack const &Track, glm::dvec3 const &Joint, bool const
|
||||
return {0.0, 1.0};
|
||||
}
|
||||
|
||||
double signed_angle(glm::dvec2 const &From, glm::dvec2 const &To)
|
||||
{
|
||||
return std::atan2(lateral_of(From, To), glm::dot(From, To));
|
||||
}
|
||||
|
||||
TTrack *neighbour_at(TTrack const &Track, int const Path, bool const Atend)
|
||||
{
|
||||
if (has_switch_paths(Track))
|
||||
@@ -928,7 +911,7 @@ editor_track::straight editor_track::find_straight(TTrack &Track, straight_toler
|
||||
auto const &first{Track.m_paths[firstpath]};
|
||||
auto const origin{first.points[segment_data::point::start]};
|
||||
auto const direction{glm::normalize(plan_of(first.points[segment_data::point::end] - origin))};
|
||||
auto const lateral = [&](glm::dvec3 const &Point) { return std::abs(lateral_of(direction, plan_of(Point - origin))); };
|
||||
auto const lateral = [&](glm::dvec3 const &Point) { return std::abs(cross(direction, plan_of(Point - origin))); };
|
||||
auto const accepts = [&](TTrack const *Other, glm::dvec3 const &Joint) {
|
||||
if (Other == nullptr || false == is_supported(*Other) || Other->m_editorremoved)
|
||||
return -1;
|
||||
@@ -942,7 +925,7 @@ editor_track::straight editor_track::find_straight(TTrack &Track, straight_toler
|
||||
if (glm::distance(start, Joint) > kSamePoint && glm::distance(end, Joint) > kSamePoint)
|
||||
continue;
|
||||
auto const otherdirection{glm::normalize(plan_of(end - start))};
|
||||
if (std::abs(lateral_of(direction, otherdirection)) > Tolerance.angle)
|
||||
if (std::abs(cross(direction, otherdirection)) > Tolerance.angle)
|
||||
continue;
|
||||
if (lateral(start) > Tolerance.offset || lateral(end) > Tolerance.offset)
|
||||
continue;
|
||||
@@ -1182,6 +1165,538 @@ bool editor_track::find_curve(TTrack &Track, straight_tolerance const &Tolerance
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// shorter remainders of a straight path are absorbed by moving the joint instead of splitting the path
|
||||
double split_margin(double const Length)
|
||||
{
|
||||
return std::max(0.5, 0.002 * Length);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
editor_track::straight_run editor_track::straight_beyond(chain const &Chain, bool const Atend, straight_tolerance const &Tolerance)
|
||||
{
|
||||
straight_run run;
|
||||
if (Chain.tracks.empty())
|
||||
return run;
|
||||
auto *edge{Atend ? Chain.tracks.back() : Chain.tracks.front()};
|
||||
bool const forward{Atend ? Chain.forward.back() : Chain.forward.front()};
|
||||
TTrack *current{forward == Atend ? edge->trNext : edge->trPrev};
|
||||
auto joint{Atend ? Chain.end : Chain.start};
|
||||
auto const origin{plan_of(joint)};
|
||||
auto direction{glm::normalize(plan_of(Atend ? Chain.end_direction : -Chain.start_direction))};
|
||||
while (current != nullptr && run.tracks.size() < 1000)
|
||||
{
|
||||
std::string reason;
|
||||
if (current->eType != tt_Normal || current->m_editorremoved || false == can_edit_geometry(*current, reason))
|
||||
break;
|
||||
if (std::find(Chain.tracks.begin(), Chain.tracks.end(), current) != Chain.tracks.end() || std::find(run.tracks.begin(), run.tracks.end(), current) != run.tracks.end())
|
||||
break;
|
||||
auto const &path{current->m_paths.front()};
|
||||
if (false == plan_straight(path, Tolerance))
|
||||
break;
|
||||
bool outward;
|
||||
if (glm::distance(path.points[segment_data::point::start], joint) <= kSamePoint)
|
||||
outward = true;
|
||||
else if (glm::distance(path.points[segment_data::point::end], joint) <= kSamePoint)
|
||||
outward = false;
|
||||
else
|
||||
break;
|
||||
auto const &opposite{path.points[outward ? segment_data::point::end : segment_data::point::start]};
|
||||
if (run.tracks.empty())
|
||||
{
|
||||
auto const own{glm::normalize(plan_of(opposite - joint))};
|
||||
if (std::abs(cross(direction, own)) > 0.01 || glm::dot(direction, own) <= 0.0)
|
||||
break;
|
||||
direction = own;
|
||||
}
|
||||
auto const offset{plan_of(opposite) - origin};
|
||||
auto const along{glm::dot(offset, direction)};
|
||||
if (along <= run.length + 1e-3 || std::abs(cross(direction, offset)) > Tolerance.offset)
|
||||
break;
|
||||
run.tracks.push_back(current);
|
||||
run.outward.push_back(outward);
|
||||
run.length = along;
|
||||
joint = opposite;
|
||||
current = outward ? current->trNext : current->trPrev;
|
||||
}
|
||||
return run;
|
||||
}
|
||||
|
||||
double editor_track::run_reserve(straight_run const &Run)
|
||||
{
|
||||
if (Run.tracks.empty())
|
||||
return 0.0;
|
||||
return std::max(0.0, Run.length - split_margin(chord_length(Run.tracks.back()->m_paths.front())) - 0.01);
|
||||
}
|
||||
|
||||
TTrack *editor_track::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)
|
||||
{
|
||||
auto const length{glm::length(plan_of(Cut - Joint))};
|
||||
auto *edge{Edge};
|
||||
double covered{0.0};
|
||||
for (std::size_t i = 0; i < Run.tracks.size(); ++i)
|
||||
{
|
||||
auto *track{Run.tracks[i]};
|
||||
auto const outward{Run.outward[i]};
|
||||
auto const &path{track->m_paths.front()};
|
||||
auto const pathlength{chord_length(path)};
|
||||
auto const margin{split_margin(pathlength)};
|
||||
auto const remaining{length - covered};
|
||||
if (remaining <= margin)
|
||||
{
|
||||
if (std::abs(remaining) > 1e-4)
|
||||
{
|
||||
States.emplace_back(track, capture(*track));
|
||||
move_point(*track, {0, outward ? point_kind::start : point_kind::end}, Cut);
|
||||
commit({track});
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (remaining < pathlength - margin)
|
||||
{
|
||||
auto const &nearpoint{path.points[outward ? segment_data::point::start : segment_data::point::end]};
|
||||
auto const &farpoint{path.points[outward ? segment_data::point::end : segment_data::point::start]};
|
||||
auto const t{nearest_parameter(*track, nearpoint + (farpoint - nearpoint) * (remaining / pathlength))};
|
||||
auto const before{capture(*track)};
|
||||
auto *created{split_path(*track, t)};
|
||||
if (created == nullptr)
|
||||
break;
|
||||
States.emplace_back(track, before);
|
||||
Created.push_back(created);
|
||||
edge = outward ? track : created;
|
||||
break;
|
||||
}
|
||||
edge = track;
|
||||
covered += pathlength;
|
||||
}
|
||||
return edge;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
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]};
|
||||
auto const &path{span.track->m_paths[span.path]};
|
||||
bezier const curve{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;
|
||||
sample.cant = 1.5 * std::sin(std::abs(glm::radians(path.rolls[0] + (path.rolls[1] - path.rolls[0]) * t)));
|
||||
if (planar > 1e-9)
|
||||
{
|
||||
sample.direction = plan_of(derivative) / 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>
|
||||
auto interpolate(std::vector<editor_track::route_sample> const &Samples, double const Chainage, Getter const &Get) -> decltype(Get(Samples.front()))
|
||||
{
|
||||
if (Samples.empty())
|
||||
return {};
|
||||
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(plan_of(Point - Origin), 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; });
|
||||
}
|
||||
|
||||
glm::dvec3 editor_track::sampled_position(std::vector<route_sample> const &Samples, double const Chainage)
|
||||
{
|
||||
return interpolate(Samples, Chainage, [](route_sample const &Sample) { return Sample.position; });
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -1213,7 +1728,7 @@ std::vector<editor_track::switch_template> editor_track::find_switch_templates()
|
||||
local.points[segment_data::point::control1] = tolocal(path.points[segment_data::point::control1]);
|
||||
local.points[segment_data::point::control2] = tolocal(path.points[segment_data::point::control2]);
|
||||
}
|
||||
if (glm::length(glm::dvec2{entry.local[1].points[segment_data::point::start].x, entry.local[1].points[segment_data::point::start].z}) > kSamePoint)
|
||||
if (glm::length(plan_of(entry.local[1].points[segment_data::point::start])) > kSamePoint)
|
||||
continue;
|
||||
if (entry.local[1].points[segment_data::point::end].x < 0.0)
|
||||
{
|
||||
@@ -1229,8 +1744,7 @@ std::vector<editor_track::switch_template> editor_track::find_switch_templates()
|
||||
auto const angle{std::atan2(std::abs(tangent.x), tangent.z)};
|
||||
entry.ratio = angle > 1e-6 ? 1.0 / std::tan(angle) : 0.0;
|
||||
entry.radius = path_radius(entry.local[1], 0.25);
|
||||
char key[256];
|
||||
std::snprintf(key, sizeof(key), "%.2f|%.2f|%.2f|%s|%s", entry.length, entry.local[1].points[segment_data::point::end].x, entry.local[1].points[segment_data::point::end].z, material_name(track->m_material1).c_str(), material_name(track->m_material2).c_str());
|
||||
auto const key{format("%.2f|%.2f|%.2f|%s|%s", entry.length, entry.local[1].points[segment_data::point::end].x, entry.local[1].points[segment_data::point::end].z, material_name(track->m_material1).c_str(), material_name(track->m_material2).c_str())};
|
||||
auto const found{lookup.find(key)};
|
||||
if (found != lookup.end())
|
||||
{
|
||||
@@ -1238,9 +1752,7 @@ std::vector<editor_track::switch_template> editor_track::find_switch_templates()
|
||||
continue;
|
||||
}
|
||||
entry.count = 1;
|
||||
char label[256];
|
||||
std::snprintf(label, sizeof(label), "L %.2f m 1:%.1f R %.0f m %s", entry.length, entry.ratio, entry.radius, material_name(track->m_material1).c_str());
|
||||
entry.label = label;
|
||||
entry.label = format("L %.2f m 1:%.1f R %.0f m %s", entry.length, entry.ratio, entry.radius, material_name(track->m_material1).c_str());
|
||||
lookup.emplace(key, result.size());
|
||||
result.push_back(entry);
|
||||
}
|
||||
@@ -1324,6 +1836,44 @@ std::vector<editor_track::switch_template> editor_track::standard_switch_templat
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// definition of a path in the scenery text, up to its points: type, length, parameters, environment and textures
|
||||
void write_header(std::ostream &Text, char const *Type, double const Length, double const Sound, TTrack const &Style, material_handle const Second)
|
||||
{
|
||||
char const *environments[] = {"flat", "mountains", "canyon", "tunnel", "bridge", "bank"};
|
||||
auto const environment{Style.eEnvironment >= e_flat && Style.eEnvironment <= e_bank ? environments[Style.eEnvironment] : "flat"};
|
||||
Text << Type << ' ' << Length << ' ' << Style.fTrackWidth << ' ' << Style.fFriction << ' ' << Sound << ' ' << Style.iQualityFlag << ' ' << 0 << ' ' << environment << ' ';
|
||||
if (Style.m_visible)
|
||||
Text << "vis " << editor_track::material_name(Style.m_material1) << ' ' << Style.fTexLength << ' ' << editor_track::material_name(Second) << ' ' << editor_track::texture_height(Style) << ' ' << Style.fTexWidth << ' ' << Style.fTexSlope << ' ';
|
||||
else
|
||||
Text << "unvis ";
|
||||
}
|
||||
|
||||
void write_points(std::ostream &Text, segment_data const &Path)
|
||||
{
|
||||
auto const &start{Path.points[segment_data::point::start]};
|
||||
auto const &control1{Path.points[segment_data::point::control1]};
|
||||
auto const &control2{Path.points[segment_data::point::control2]};
|
||||
auto const &end{Path.points[segment_data::point::end]};
|
||||
Text << start.x << ' ' << start.y << ' ' << start.z << ' ' << Path.rolls[0] << ' ' << control1.x << ' ' << control1.y << ' ' << control1.z << ' ' << control2.x << ' ' << control2.y << ' ' << control2.z << ' ' << end.x << ' ' << end.y << ' ' << end.z << ' ' << Path.rolls[1] << ' ' << Path.radius << ' ';
|
||||
}
|
||||
|
||||
void write_ending(std::ostream &Text, TTrack const &Style)
|
||||
{
|
||||
if ((Style.iCategoryFlag & 15) == 1 && false == Style.m_profile1.first.empty())
|
||||
Text << "railprofile " << Style.m_profile1.first << ' ';
|
||||
Text << "endtrack\n";
|
||||
}
|
||||
|
||||
material_handle ballast_of(TTrack const &Style)
|
||||
{
|
||||
return Style.eType == tt_Switch ? (Style.SwitchExtension ? Style.SwitchExtension->m_material3 : null_handle) : Style.m_material2;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TTrack *editor_track::create_switch(switch_template const &Template, std::vector<segment_data> const &Paths, TTrack const &Style, std::string const &Name)
|
||||
{
|
||||
if (Paths.size() < 2)
|
||||
@@ -1335,31 +1885,14 @@ TTrack *editor_track::create_switch(switch_template const &Template, std::vector
|
||||
}
|
||||
else
|
||||
{
|
||||
char const *environments[] = {"flat", "mountains", "canyon", "tunnel", "bridge", "bank"};
|
||||
auto const environment{Style.eEnvironment >= e_flat && Style.eEnvironment <= e_bank ? environments[Style.eEnvironment] : "flat"};
|
||||
std::ostringstream text;
|
||||
text.precision(std::numeric_limits<double>::digits10);
|
||||
text << "switch " << Template.length << ' ' << Style.fTrackWidth << ' ' << Style.fFriction << ' ' << 10.0 << ' ' << Style.iQualityFlag << ' ' << 0 << ' ' << environment << ' ';
|
||||
auto const rails{Style.m_material1};
|
||||
auto const trackbed{Style.eType == tt_Switch ? (Style.SwitchExtension ? Style.SwitchExtension->m_material3 : null_handle) : Style.m_material2};
|
||||
if (Style.m_visible)
|
||||
text << "vis " << material_name(rails) << ' ' << Style.fTexLength << ' ' << material_name(rails) << ' ' << texture_height(Style) << ' ' << Style.fTexWidth << ' ' << Style.fTexSlope << ' ';
|
||||
else
|
||||
text << "unvis ";
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
auto const &path{Paths[i]};
|
||||
auto const &start{path.points[segment_data::point::start]};
|
||||
auto const &control1{path.points[segment_data::point::control1]};
|
||||
auto const &control2{path.points[segment_data::point::control2]};
|
||||
auto const &end{path.points[segment_data::point::end]};
|
||||
text << start.x << ' ' << start.y << ' ' << start.z << ' ' << path.rolls[0] << ' ' << control1.x << ' ' << control1.y << ' ' << control1.z << ' ' << control2.x << ' ' << control2.y << ' ' << control2.z << ' ' << end.x << ' ' << end.y << ' ' << end.z << ' ' << path.rolls[1] << ' ' << path.radius << ' ';
|
||||
}
|
||||
if (trackbed != null_handle)
|
||||
write_header(text, "switch", Template.length, 10.0, Style, Style.m_material1);
|
||||
write_points(text, Paths[0]);
|
||||
write_points(text, Paths[1]);
|
||||
if (auto const trackbed{ballast_of(Style)}; trackbed != null_handle)
|
||||
text << "trackbed " << material_name(trackbed) << ' ';
|
||||
if ((Style.iCategoryFlag & 15) == 1 && false == Style.m_profile1.first.empty())
|
||||
text << "railprofile " << Style.m_profile1.first << ' ';
|
||||
text << "endtrack\n";
|
||||
write_ending(text, Style);
|
||||
track = load_path(text.str(), Style, Name);
|
||||
}
|
||||
track->m_paths.assign(Paths.begin(), Paths.begin() + 2);
|
||||
@@ -1443,7 +1976,7 @@ void editor_track::move_straight(straight const &Line, glm::dvec3 const &Start,
|
||||
if (newlength < 1e-3)
|
||||
return;
|
||||
auto const newdirection{newplan / newlength};
|
||||
auto const angle{std::atan2(lateral_of(direction, newdirection), glm::dot(direction, newdirection))};
|
||||
auto const angle{std::atan2(cross(direction, newdirection), glm::dot(direction, newdirection))};
|
||||
auto const along = [&](glm::dvec3 const &Point) { return glm::dot(plan_of(Point - Line.start), direction); };
|
||||
|
||||
struct interval
|
||||
@@ -1483,7 +2016,7 @@ void editor_track::move_straight(straight const &Line, glm::dvec3 const &Start,
|
||||
};
|
||||
auto const transform = [&](glm::dvec3 const &Point) {
|
||||
auto const a{along(Point)};
|
||||
auto const offset{lateral_of(direction, plan_of(Point - Line.start))};
|
||||
auto const offset{cross(direction, plan_of(Point - Line.start))};
|
||||
auto const mapped{remap(a)};
|
||||
auto const oldheight{Line.start.y + (Line.end.y - Line.start.y) * a / Line.length};
|
||||
auto const newheight{Start.y + (End.y - Start.y) * mapped / newlength};
|
||||
@@ -1613,47 +2146,35 @@ TTrack *editor_track::clone(TTrack const &Template)
|
||||
|
||||
TTrack *editor_track::create_path(TTrack const &Style, segment_data const &Path)
|
||||
{
|
||||
char const *environments[] = {"flat", "mountains", "canyon", "tunnel", "bridge", "bank"};
|
||||
auto const environment{Style.eEnvironment >= e_flat && Style.eEnvironment <= e_bank ? environments[Style.eEnvironment] : "flat"};
|
||||
auto const category{Style.iCategoryFlag & 15};
|
||||
std::ostringstream text;
|
||||
text.precision(std::numeric_limits<double>::digits10);
|
||||
text << (category == 2 ? "road " : category == 4 ? "river " : "normal ") << glm::distance(Path.points[segment_data::point::start], Path.points[segment_data::point::end]) << ' ' << Style.fTrackWidth << ' ' << Style.fFriction << ' ' << Style.fSoundDistance << ' ' << Style.iQualityFlag << ' ' << 0 << ' ' << environment << ' ';
|
||||
auto const ballast{Style.eType == tt_Switch ? (Style.SwitchExtension ? Style.SwitchExtension->m_material3 : null_handle) : Style.m_material2};
|
||||
if (Style.m_visible)
|
||||
text << "vis " << material_name(Style.m_material1) << ' ' << Style.fTexLength << ' ' << material_name(ballast) << ' ' << texture_height(Style) << ' ' << Style.fTexWidth << ' ' << Style.fTexSlope << ' ';
|
||||
else
|
||||
text << "unvis ";
|
||||
auto const &start{Path.points[segment_data::point::start]};
|
||||
auto const &control1{Path.points[segment_data::point::control1]};
|
||||
auto const &control2{Path.points[segment_data::point::control2]};
|
||||
auto const &end{Path.points[segment_data::point::end]};
|
||||
text << start.x << ' ' << start.y << ' ' << start.z << ' ' << Path.rolls[0] << ' ' << control1.x << ' ' << control1.y << ' ' << control1.z << ' ' << control2.x << ' ' << control2.y << ' ' << control2.z << ' ' << end.x << ' ' << end.y << ' ' << end.z << ' ' << Path.rolls[1] << ' ' << Path.radius << ' ';
|
||||
auto const speed{velocity(Style)};
|
||||
if (speed > 0.0)
|
||||
write_header(text, category == 2 ? "road" : category == 4 ? "river" : "normal", glm::distance(Path.points[segment_data::point::start], Path.points[segment_data::point::end]), Style.fSoundDistance, Style, ballast_of(Style));
|
||||
write_points(text, Path);
|
||||
if (auto const speed{velocity(Style)}; speed > 0.0)
|
||||
text << "velocity " << speed << ' ';
|
||||
if (category == 1 && false == Style.m_profile1.first.empty())
|
||||
text << "railprofile " << Style.m_profile1.first << ' ';
|
||||
text << "endtrack\n";
|
||||
write_ending(text, Style);
|
||||
auto *track{load_path(text.str(), Style)};
|
||||
track->m_paths = {Path};
|
||||
return track;
|
||||
}
|
||||
|
||||
glm::dvec3 editor_track::point_at(TTrack const &Track, double const T)
|
||||
{
|
||||
auto const &path{Track.m_paths.front()};
|
||||
auto const p0{path.points[segment_data::point::start]};
|
||||
auto const p3{path.points[segment_data::point::end]};
|
||||
auto const p1{p0 + path.points[segment_data::point::control1]};
|
||||
auto const p2{p3 + path.points[segment_data::point::control2]};
|
||||
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;
|
||||
}
|
||||
|
||||
double editor_track::nearest_parameter(TTrack const &Track, glm::dvec3 const &Point)
|
||||
{
|
||||
if (Track.m_paths.empty())
|
||||
return 0.5;
|
||||
auto const &path{Track.m_paths.front()};
|
||||
auto const p0{path.points[segment_data::point::start]};
|
||||
auto const p3{path.points[segment_data::point::end]};
|
||||
auto const p1{p0 + path.points[segment_data::point::control1]};
|
||||
auto const p2{p3 + path.points[segment_data::point::control2]};
|
||||
auto const at = [&](double const T) {
|
||||
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;
|
||||
};
|
||||
auto const distance = [&](double const T) { auto const q{at(T)}; return glm::distance(glm::dvec2{q.x, q.z}, glm::dvec2{Point.x, Point.z}); };
|
||||
auto const distance = [&](double const T) { return geometry::plan_distance(point_at(Track, T), Point); };
|
||||
double best{0.5};
|
||||
for (int i = 0; i <= 200; ++i)
|
||||
if (distance(i / 200.0) < distance(best))
|
||||
@@ -1707,6 +2228,8 @@ TTrack *editor_track::split_path(TTrack &Track, double const T)
|
||||
second.rolls[0] = roll;
|
||||
auto *created{create_path(Track, second)};
|
||||
Track.m_paths.front() = first;
|
||||
if (s_observer != nullptr)
|
||||
s_observer->track_split(Track, *created, first);
|
||||
commit({&Track, created});
|
||||
return created;
|
||||
}
|
||||
@@ -1743,12 +2266,8 @@ void editor_track::retire(TTrack &Track)
|
||||
update_transition(*neighbour);
|
||||
rebuild_geometry(*neighbour);
|
||||
}
|
||||
}
|
||||
|
||||
void editor_track::revive(TTrack &Track)
|
||||
{
|
||||
Track.m_editorremoved = false;
|
||||
commit({&Track});
|
||||
if (s_observer != nullptr)
|
||||
s_observer->track_retired(Track);
|
||||
}
|
||||
|
||||
void editor_track::rebuild_geometry(TTrack &Track)
|
||||
|
||||
@@ -25,6 +25,7 @@ class editor_track
|
||||
{
|
||||
public:
|
||||
enum class point_kind { start, control1, control2, end };
|
||||
static bool is_end(point_kind const Kind) { return Kind == point_kind::start || Kind == point_kind::end; }
|
||||
struct point_ref
|
||||
{
|
||||
int path{-1};
|
||||
@@ -108,6 +109,8 @@ class editor_track
|
||||
double length{0.0};
|
||||
double grade{0.0};
|
||||
double azimuth{0.0};
|
||||
// distance of the point from the start, measured along the line in the plan
|
||||
double along(glm::dvec3 const &Point) const { return (Point.x - start.x) * direction.x + (Point.z - start.z) * direction.y; }
|
||||
};
|
||||
struct straight_tolerance
|
||||
{
|
||||
@@ -137,6 +140,63 @@ class editor_track
|
||||
double split{0.5};
|
||||
};
|
||||
static bool find_curve(TTrack &Track, straight_tolerance const &Tolerance, double const Gauge, curve &Curve);
|
||||
// regular straight paths continuing a chain end in line, which the chain can take over
|
||||
struct straight_run
|
||||
{
|
||||
std::vector<TTrack *> tracks;
|
||||
std::vector<bool> outward;
|
||||
double length{0.0};
|
||||
};
|
||||
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
|
||||
double cant{0.0}; // m, raise of the outer rail
|
||||
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);
|
||||
static glm::dvec3 sampled_position(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};
|
||||
@@ -156,12 +216,13 @@ class editor_track
|
||||
static TTrack *create_path(TTrack const &Style, segment_data const &Path);
|
||||
static TTrack *split_path(TTrack &Track, double const T);
|
||||
static double nearest_parameter(TTrack const &Track, glm::dvec3 const &Point);
|
||||
// point of the first path at the parameter of nearest_parameter()
|
||||
static glm::dvec3 point_at(TTrack const &Track, double const T);
|
||||
static std::vector<segment_data> place_switch(switch_template const &Template, glm::dvec3 const &Origin, glm::dvec2 const &Direction, int const Side, double const Grade);
|
||||
static TTrack *create_switch(switch_template const &Template, std::vector<segment_data> const &Paths, TTrack const &Style, std::string const &Name = {});
|
||||
static void move_straights(std::vector<straight> const &Lines, std::vector<std::pair<glm::dvec3, glm::dvec3>> const &Ends);
|
||||
static std::vector<TTrack *> relay(chain const &Chain, std::vector<segment_data> const &Pieces);
|
||||
static void retire(TTrack &Track);
|
||||
static void revive(TTrack &Track);
|
||||
|
||||
static std::string material_name(material_handle const Material);
|
||||
static material_handle fetch_material(std::string const &Name);
|
||||
@@ -172,7 +233,21 @@ class editor_track
|
||||
static void velocity(TTrack &Track, double const Velocity);
|
||||
static void damage(TTrack &Track, int const Damage);
|
||||
|
||||
// told about the changes of the paths, to let the objects which belong to them follow
|
||||
class observer
|
||||
{
|
||||
public:
|
||||
virtual ~observer() = default;
|
||||
virtual void track_captured(TTrack const &Track) = 0;
|
||||
virtual void tracks_committed(std::vector<TTrack *> const &Tracks) = 0;
|
||||
virtual void track_retired(TTrack &Track) = 0;
|
||||
// called before the commit; Original keeps the beginning of the path, given in First
|
||||
virtual void track_split(TTrack &Original, TTrack &Created, segment_data const &First) = 0;
|
||||
};
|
||||
static void observe(observer *Observer) { s_observer = Observer; }
|
||||
|
||||
private:
|
||||
static observer *s_observer;
|
||||
static void disconnect(TTrack &Track);
|
||||
static void join(TTrack &Track);
|
||||
static void connect(TTrack &Track, bool const Prevside, TTrack *Other, int const Endpointid);
|
||||
|
||||
Reference in New Issue
Block a user