mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-01 03:29:03 +01:00
feat(editor): the curves of the laying from the photo in the design of the lines: a vertex of two transition curves meeting at its radius, with no arc and no cant, kept in the scenery files; the bends of straights eased with them at a radius from 6000 down to 3000 m, or larger where they'd be too short for the speed, else with an arc of the norms as large as fits, the straights between two bends shared, the bends staying where they were clicked and a curve below the norms kept with its warnings; a stretch traced on the photo fitted with a straight, a curve with its radius and the length of its transitions, a compound one, or two curves with a straight between them, on a thread of its own while the cursor moves; the heights from point to point through all the points clicked. Unit tests of the design of the lines
This commit is contained in:
247
tests/unit/test_alignment.cpp
Normal file
247
tests/unit/test_alignment.cpp
Normal file
@@ -0,0 +1,247 @@
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
#include "editor/editorAlignment.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
|
||||
double constexpr pi{3.14159265358979323846};
|
||||
|
||||
glm::dvec2 heading(double const Angle) { return {std::cos(Angle), std::sin(Angle)}; }
|
||||
|
||||
// straights from the origin along x, bending at the vertices by the deflections given (radians, to the left positive), each leg the
|
||||
// length given; the vertices clicked as bends of straights
|
||||
alignment::design straights(std::vector<double> const &Legs, std::vector<double> const &Deflections, double const Speed) {
|
||||
alignment::design design;
|
||||
design.speed = Speed;
|
||||
glm::dvec2 point{0.0};
|
||||
double direction{0.0};
|
||||
std::vector<glm::dvec2> corners;
|
||||
for (std::size_t i = 0; i < Legs.size(); ++i) {
|
||||
point += heading(direction) * Legs[i];
|
||||
if (i < Deflections.size()) {
|
||||
corners.push_back(point);
|
||||
direction += Deflections[i];
|
||||
}
|
||||
}
|
||||
design.start = {0.0, 0.0, 0.0};
|
||||
design.end = {point.x, 0.0, point.y};
|
||||
design.start_direction = heading(0.0);
|
||||
design.end_direction = heading(direction);
|
||||
for (auto const &corner : corners) {
|
||||
alignment::vertex vertex;
|
||||
vertex.position = corner;
|
||||
design.vertices.push_back(vertex);
|
||||
}
|
||||
if (design.vertices.size() >= 2) {
|
||||
design.vertices.front().offset = Legs.front();
|
||||
design.vertices.back().offset = Legs.back();
|
||||
}
|
||||
return design;
|
||||
}
|
||||
|
||||
// points along the curves of the line every Spacing metres, from 30 m before the first curve to its last one, and two on the straight
|
||||
// after it, shifted across by up to Noise
|
||||
std::vector<glm::dvec2> traced(alignment::result const &Line, double const Spacing, double const Noise) {
|
||||
auto from{Line.length};
|
||||
auto to{0.0};
|
||||
for (auto const &e : Line.elements)
|
||||
if (e.kind != alignment::element_kind::straight) {
|
||||
from = std::min(from, e.chainage);
|
||||
to = std::max(to, e.chainage + e.length);
|
||||
}
|
||||
std::vector<glm::dvec2> points;
|
||||
auto const along = [&](double const Chainage, double const Shift) {
|
||||
auto const station{alignment::evaluate(Line, Chainage)};
|
||||
glm::dvec2 const across{-station.direction.z, station.direction.x};
|
||||
return glm::dvec2{station.position.x, station.position.z} + glm::normalize(across) * Shift;
|
||||
};
|
||||
int count{0};
|
||||
for (auto chainage{std::max(0.0, from - 30.0)}; chainage <= to; chainage += Spacing, ++count)
|
||||
points.push_back(along(chainage, Noise * (count % 3 == 0 ? 1.0 : count % 3 == 1 ? -0.6 : 0.2)));
|
||||
points.push_back(along(to + 30.0, Noise * 0.5));
|
||||
points.push_back(along(to + 60.0, -Noise * 0.5));
|
||||
return points;
|
||||
}
|
||||
|
||||
// the stretch traced as the lay tool asks for it: from the start of the line along its first straight, to the last point along the
|
||||
// straight through the last two
|
||||
alignment::design stretch_base(alignment::design const &Line, std::vector<glm::dvec2> const &Points) {
|
||||
alignment::design base{Line};
|
||||
base.vertices.clear();
|
||||
base.end = {Points.back().x, 0.0, Points.back().y};
|
||||
base.end_direction = glm::normalize(Points.back() - Points[Points.size() - 2]);
|
||||
return base;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("alignment: a slight bend of straights is eased with two transition curves meeting, with no cant") {
|
||||
auto design{straights({500.0, 500.0}, {3.0 * pi / 180.0}, 160.0)};
|
||||
auto const line{alignment::ease_corners(design, {1}, {3000.0, 6000.0})};
|
||||
|
||||
REQUIRE(line.valid);
|
||||
REQUIRE(line.curves.size() == 1);
|
||||
auto const &curve{line.curves.front()};
|
||||
CHECK(curve.spirals);
|
||||
CHECK(curve.cant == doctest::Approx(0.0));
|
||||
CHECK(curve.arc_length < 0.01);
|
||||
CHECK(curve.radius >= 3000.0 - 1e-6);
|
||||
CHECK(curve.radius <= 6000.0 + 1e-6);
|
||||
CHECK(alignment::meets_norms(curve, design));
|
||||
// the bend stays where it was clicked
|
||||
CHECK(line.vertices.front().x == doctest::Approx(500.0));
|
||||
CHECK(line.vertices.front().y == doctest::Approx(0.0));
|
||||
}
|
||||
|
||||
TEST_CASE("alignment: a bend too slight for transitions long enough at the radii given takes a larger radius") {
|
||||
auto design{straights({500.0, 500.0}, {0.2 * pi / 180.0}, 160.0)};
|
||||
auto const line{alignment::ease_corners(design, {1}, {3000.0, 6000.0})};
|
||||
|
||||
REQUIRE(line.valid);
|
||||
REQUIRE(line.curves.size() == 1);
|
||||
auto const &curve{line.curves.front()};
|
||||
CHECK(curve.spirals);
|
||||
CHECK(curve.radius > 6000.0);
|
||||
CHECK(curve.transition_in >= alignment::transition_length(160.0, curve.radius, 0.0, design.norms) - 1e-6);
|
||||
CHECK(alignment::meets_norms(curve, design));
|
||||
}
|
||||
|
||||
TEST_CASE("alignment: a sharp bend between short straights is laid where clicked, an arc with cant below the norms") {
|
||||
auto design{straights({120.0, 120.0}, {60.0 * pi / 180.0}, 160.0)};
|
||||
auto const line{alignment::ease_corners(design, {1}, {3000.0, 6000.0})};
|
||||
|
||||
REQUIRE(line.valid);
|
||||
REQUIRE(line.curves.size() == 1);
|
||||
auto const &curve{line.curves.front()};
|
||||
CHECK_FALSE(curve.spirals);
|
||||
CHECK(curve.cant > 0.0);
|
||||
CHECK(curve.radius < alignment::recommend(160.0, curve.radius, design.norms).radius_min);
|
||||
CHECK_FALSE(alignment::meets_norms(curve, design));
|
||||
CHECK_FALSE(line.warnings.empty());
|
||||
CHECK(curve.tangent_in <= 120.0 + 1e-6);
|
||||
CHECK(line.vertices.front().x == doctest::Approx(120.0));
|
||||
}
|
||||
|
||||
TEST_CASE("alignment: two bends share the straight between them") {
|
||||
auto design{straights({600.0, 250.0, 600.0}, {4.0 * pi / 180.0, -5.0 * pi / 180.0}, 120.0)};
|
||||
auto const line{alignment::ease_corners(design, {1, 1}, {3000.0, 6000.0})};
|
||||
|
||||
REQUIRE(line.valid);
|
||||
REQUIRE(line.curves.size() == 2);
|
||||
CHECK(line.curves[0].tangent_out + line.curves[1].tangent_in <= 250.0 + 1e-3);
|
||||
CHECK(line.vertices[1].x == doctest::Approx(design.vertices[1].position.x));
|
||||
CHECK(line.vertices[1].y == doctest::Approx(design.vertices[1].position.y));
|
||||
}
|
||||
|
||||
TEST_CASE("alignment: two transition curves meet with no arc in a design computed again") {
|
||||
auto design{straights({500.0, 500.0}, {3.0 * pi / 180.0}, 120.0)};
|
||||
design.vertices.front().spirals = true;
|
||||
design.vertices.front().radius = 4000.0;
|
||||
design.vertices.front().cant = 60.0;
|
||||
auto const line{alignment::compute(design)};
|
||||
|
||||
REQUIRE(line.valid);
|
||||
REQUIRE(line.curves.size() == 1);
|
||||
auto const &curve{line.curves.front()};
|
||||
CHECK(curve.spirals);
|
||||
CHECK(curve.cant == doctest::Approx(0.0));
|
||||
CHECK(curve.radius == doctest::Approx(4000.0));
|
||||
CHECK(curve.transition_in == doctest::Approx(alignment::spirals_length(design.shape, 3.0 * pi / 180.0, 4000.0)));
|
||||
// the heading of the line out is that of the straight after the bend
|
||||
auto const station{alignment::evaluate(line, line.length)};
|
||||
CHECK(std::atan2(station.direction.z, station.direction.x) == doctest::Approx(3.0 * pi / 180.0).epsilon(1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("alignment: the radius and the transitions of a curve traced along a known one") {
|
||||
auto truth{straights({500.0, 500.0}, {25.0 * pi / 180.0}, 120.0)};
|
||||
truth.vertices.front().radius = 1000.0;
|
||||
truth.vertices.front().transition_in = truth.vertices.front().transition_out = 120.0;
|
||||
auto const line{alignment::compute(truth)};
|
||||
REQUIRE(line.valid);
|
||||
auto const points{traced(line, 20.0, 0.03)};
|
||||
|
||||
auto const fit{alignment::fit_trace_stretch(stretch_base(truth, points), points, 0, true)};
|
||||
|
||||
REQUIRE(fit.valid);
|
||||
REQUIRE(fit.vertices.size() == 1);
|
||||
CHECK(fit.vertices.front().radius == doctest::Approx(1000.0).epsilon(0.02));
|
||||
CHECK(fit.vertices.front().transition_in == doctest::Approx(120.0).epsilon(0.1));
|
||||
CHECK(fit.rms < 0.04);
|
||||
}
|
||||
|
||||
TEST_CASE("alignment: a plain arc traced keeps short transitions, not the ones of the norms") {
|
||||
auto truth{straights({500.0, 500.0}, {20.0 * pi / 180.0}, 120.0)};
|
||||
truth.vertices.front().radius = 800.0;
|
||||
auto const line{alignment::compute(truth)};
|
||||
REQUIRE(line.valid);
|
||||
auto const points{traced(line, 15.0, 0.0)};
|
||||
|
||||
auto const fit{alignment::fit_trace_stretch(stretch_base(truth, points), points, 0, true)};
|
||||
|
||||
REQUIRE(fit.valid);
|
||||
REQUIRE(fit.vertices.size() == 1);
|
||||
CHECK(fit.vertices.front().radius == doctest::Approx(800.0).epsilon(0.01));
|
||||
CHECK(fit.vertices.front().transition_in < 20.0);
|
||||
CHECK(fit.rms < 0.02);
|
||||
}
|
||||
|
||||
TEST_CASE("alignment: a curve, a straight and a curve the other way traced as two curves") {
|
||||
auto truth{straights({400.0, 700.0, 400.0}, {20.0 * pi / 180.0, -25.0 * pi / 180.0}, 120.0)};
|
||||
truth.vertices[0].radius = 1500.0;
|
||||
truth.vertices[1].radius = 1000.0;
|
||||
for (auto &vertex : truth.vertices)
|
||||
vertex.transition_in = vertex.transition_out = 100.0;
|
||||
auto const line{alignment::compute(truth)};
|
||||
REQUIRE(line.valid);
|
||||
REQUIRE(line.curves.size() == 2);
|
||||
auto const points{traced(line, 20.0, 0.02)};
|
||||
|
||||
auto const fit{alignment::fit_trace_stretch(stretch_base(truth, points), points, 0, true)};
|
||||
|
||||
REQUIRE(fit.valid);
|
||||
REQUIRE(fit.vertices.size() == 2);
|
||||
CHECK(fit.vertices[0].radius == doctest::Approx(line.curves[0].radius).epsilon(0.05));
|
||||
CHECK(fit.vertices[1].radius == doctest::Approx(line.curves[1].radius).epsilon(0.05));
|
||||
CHECK(fit.rms < 0.05);
|
||||
}
|
||||
|
||||
TEST_CASE("alignment: two curves turning the same way with a straight between them traced") {
|
||||
auto truth{straights({400.0, 600.0, 400.0}, {15.0 * pi / 180.0, 15.0 * pi / 180.0}, 120.0)};
|
||||
truth.vertices[0].radius = 1200.0;
|
||||
truth.vertices[1].radius = 900.0;
|
||||
for (auto &vertex : truth.vertices)
|
||||
vertex.transition_in = vertex.transition_out = 90.0;
|
||||
auto const line{alignment::compute(truth)};
|
||||
REQUIRE(line.valid);
|
||||
REQUIRE(line.curves.size() == 2);
|
||||
auto const points{traced(line, 20.0, 0.02)};
|
||||
|
||||
auto const fit{alignment::fit_trace_stretch(stretch_base(truth, points), points, 0, true)};
|
||||
|
||||
REQUIRE(fit.valid);
|
||||
REQUIRE(fit.vertices.size() == 2);
|
||||
CHECK(fit.vertices[0].radius == doctest::Approx(1200.0).epsilon(0.05));
|
||||
CHECK(fit.vertices[1].radius == doctest::Approx(900.0).epsilon(0.05));
|
||||
CHECK(fit.rms < 0.04);
|
||||
}
|
||||
|
||||
TEST_CASE("alignment: a compound curve traced is followed closely") {
|
||||
auto truth{straights({500.0, 500.0}, {35.0 * pi / 180.0}, 100.0)};
|
||||
auto &vertex{truth.vertices.front()};
|
||||
vertex.radius = 1200.0;
|
||||
vertex.compound = true;
|
||||
vertex.share = 0.5;
|
||||
vertex.arcs = {{700.0, 60.0, 0.5}};
|
||||
vertex.transition_in = vertex.transition_out = 80.0;
|
||||
auto const line{alignment::compute(truth)};
|
||||
REQUIRE(line.valid);
|
||||
auto const points{traced(line, 15.0, 0.02)};
|
||||
|
||||
auto const fit{alignment::fit_trace_stretch(stretch_base(truth, points), points, 0, true)};
|
||||
|
||||
REQUIRE(fit.valid);
|
||||
CHECK(fit.rms < 0.05);
|
||||
CHECK(fit.worst < 0.15);
|
||||
}
|
||||
Reference in New Issue
Block a user