mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-27 11:47:29 +01:00
248 lines
9.8 KiB
C++
248 lines
9.8 KiB
C++
#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);
|
|
}
|