17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-01 05:42:44 +01:00
Files
maszyna/tests/unit/test_track_uneven.cpp
maj00r e5266e1e6b feat(track): track unevenness declared in the track entry
Usage, optional attributes of a track entry, before endtrack:

  uneven <mm>       peak deviation of a rail on a 10 m chord, 0-100; normal and switch
  unevenup <mm>     up and down alone, instead of uneven; normal and switch
  unevenside <mm>   sideways alone, instead of uneven; normal and switch
  unevencross <mm>  cross level alone, instead of uneven; normal only
  jointdip <mm>     depth of the dip at rail joints, every sound distance; normal only,
                    default 0.4 of the up and down component

Values of uneven follow PLK Id-14 appendix 1: 6 mm is the limit at 160 km/h, 10 at 120,
18 at 80, 35 at 40; half of it for a track in a good state. Example:

  uneven 18
  unevencross 25
  jointdip 6
  endtrack

The same fields are in the editor, track panel, section Ride and sound. See
track_uneven_doc.md.

The unevenness is a displacement field over the plan of the scenery, with no random
numbers: one place strays the same every time, and joined tracks, the paths of a switch,
sleepermodel and axles at one place move together. Tracks meet at a joint at the lower of
their values. Joined switches take the lowest of theirs, the tracks joined to them come to
it, and a track with none of a component keeps the switch to its design in it. The clatter
comes from the dipping joints. Curve radius and grade for the physics stay as designed.

Tracks with it get geometry every 2 m on straights: 4x the vertices on Baltyk with all
tracks flagged, 0.2 ms more per frame on td.scn.
2026-10-31 15:47:10 +01:00

153 lines
5.9 KiB
C++

#include "stdafx.h"
#include "doctest.h"
#include "world/TrackUneven.h"
#include <cmath>
#include <vector>
namespace {
double deviation(std::vector<double> const &Values)
{
auto mean{0.0};
for (auto const value : Values)
mean += value;
mean /= Values.size();
auto sum{0.0};
for (auto const value : Values)
sum += (value - mean) * (value - mean);
return std::sqrt(sum / Values.size());
}
double peak(std::vector<double> Values)
{
for (auto &value : Values)
value = std::abs(value);
std::sort(Values.begin(), Values.end());
return Values[static_cast<std::size_t>(0.999 * Values.size())];
}
void add_chord(std::vector<double> &Output, std::vector<double> const &Line, int const Half)
{
for (int i = Half; i + Half < static_cast<int>(Line.size()); ++i)
Output.push_back(Line[i] - 0.5 * (Line[i - Half] + Line[i + Half]));
}
} // namespace
TEST_CASE("uneven track: measures as the Id-14 limits for its value")
{
// appendix 1, class C at 80 km/h: 18 mm vertically, 17 mm sideways, 16 mm of twist on 5 m, gauge +10/-8 mm, 20 mm of cross level, J = 5.3
auto const amplitude{track_uneven::stray_per_mm * 18.0 * 1000.0}; // mm
double const gauge{1.435};
double const step{0.5};
int const steps{4000};
int const base{10}; // 5 m
int const lines{36};
double vertical{0.0}, horizontal{0.0}, twist{0.0}, wander{0.0}, level{0.0}, j{0.0}, away{0.0};
for (int line = 0; line < lines; ++line)
{
auto const heading{glm::pi<double>() * (line + 0.37) / lines};
glm::dvec3 const start{7919.0 * std::sin(line * 1.7), 0.0, 6577.0 * std::cos(line * 2.3)};
glm::dvec3 const along{std::cos(heading), 0.0, std::sin(heading)};
glm::dvec3 const across{-along.z, 0.0, along.x};
std::vector<double> up[2], side[2], middle;
for (int i = 0; i < steps; ++i)
{
auto const point{start + along * (i * step)};
for (int rail = 0; rail < 2; ++rail)
{
auto const sign{rail == 0 ? 1.0 : -1.0};
auto const stray{track_uneven::field(point + across * (sign * 0.5 * gauge))};
up[rail].push_back((stray.y - sign * 0.5 * track_uneven::level(point)) * amplitude);
side[rail].push_back(glm::dot(stray, across) * amplitude);
}
middle.push_back(track_uneven::field(point).y * amplitude);
}
std::vector<double> verticals, horizontals, twists, gauges, levels;
for (int rail = 0; rail < 2; ++rail)
{
add_chord(verticals, up[rail], base);
add_chord(horizontals, side[rail], base);
}
for (int i = 0; i + base < steps; ++i)
twists.push_back((up[0][i + base] - up[1][i + base]) - (up[0][i] - up[1][i]));
for (int i = 0; i < steps; ++i)
{
gauges.push_back(side[0][i] - side[1][i]);
levels.push_back(up[0][i] - up[1][i]);
}
vertical += peak(verticals) / lines;
horizontal += peak(horizontals) / lines;
twist += peak(twists) / lines;
wander += peak(gauges) / lines;
level += peak(levels) / lines;
away += peak(middle) / lines;
j += (deviation(verticals) + deviation(horizontals) + deviation(twists) + 0.5 * deviation(gauges)) / 3.5 / lines;
}
CHECK(vertical == doctest::Approx(18.0).epsilon(0.1));
CHECK(horizontal == doctest::Approx(17.0).epsilon(0.2));
CHECK(twist == doctest::Approx(16.0).epsilon(0.2));
CHECK(wander == doctest::Approx(10.0).epsilon(0.2));
CHECK(level == doctest::Approx(20.0).epsilon(0.1));
CHECK(j == doctest::Approx(5.3).epsilon(0.2));
CHECK(away < 25.0); // appendix 1a: 15 mm off the survey marks
}
TEST_CASE("uneven track: a place strays the same every time")
{
// no random numbers: changing these changes how every uneven track in every scenery looks
glm::dvec3 const place{1234.5, 0.0, -678.9};
auto const stray{track_uneven::field(place)};
CHECK(stray.x == doctest::Approx(0.309777548158).epsilon(1e-9));
CHECK(stray.y == doctest::Approx(-0.295557487607).epsilon(1e-9));
CHECK(stray.z == doctest::Approx(-0.328429734019).epsilon(1e-9));
CHECK(track_uneven::level(place) == doctest::Approx(0.6 * -0.178598957983).epsilon(1e-9));
}
TEST_CASE("uneven track: the field stays within its bound")
{
auto most{0.0};
for (int i = 0; i < 20000; ++i)
{
auto const stray{track_uneven::field({i * 3.7 - 30000.0, 0.0, i * -2.9 + 41000.0})};
most = std::max({most, std::abs(stray.x), std::abs(stray.y), std::abs(stray.z)});
}
CHECK(most <= 1.0);
CHECK(most > 0.5);
}
TEST_CASE("uneven track: the value along a track meets its neighbours exactly")
{
double const length{500.0};
CHECK(track_uneven::along(20.0, 5.0, 7.0, 0.0, length) == doctest::Approx(5.0));
CHECK(track_uneven::along(20.0, 5.0, 7.0, length, length) == doctest::Approx(7.0));
CHECK(track_uneven::along(20.0, 5.0, 7.0, 0.5 * length, length) == doctest::Approx(20.0));
// equal values stay constant
CHECK(track_uneven::along(20.0, 20.0, 20.0, 3.0, length) == doctest::Approx(20.0));
// a short track between ends of no value hardly strays
CHECK(track_uneven::along(20.0, 0.0, 0.0, 2.5, 5.0) < 1.0);
// a switch of more than the track: the track starts at the value of the switch
CHECK(track_uneven::along(18.0, 35.0, 18.0, 0.0, length) == doctest::Approx(35.0));
CHECK(track_uneven::along(18.0, 35.0, 18.0, track_uneven::fade, length) == doctest::Approx(18.0));
// the short piece inside a slip goes with its switches
CHECK(track_uneven::along(18.0, 35.0, 35.0, 0.25, 0.5) == doctest::Approx(35.0).epsilon(0.01));
// no jump anywhere
auto last{track_uneven::along(18.0, 35.0, 0.0, 0.0, length)};
for (auto distance{0.25}; distance <= length; distance += 0.25)
{
auto const value{track_uneven::along(18.0, 35.0, 0.0, distance, length)};
REQUIRE(std::abs(value - last) < 0.5);
last = value;
}
}
TEST_CASE("uneven track: the rails dip around a joint")
{
CHECK(track_uneven::dip(0.0) == doctest::Approx(1.0));
CHECK(track_uneven::dip(1.0) == doctest::Approx(0.75).epsilon(0.01)); // the worst case for rings 2 m apart
CHECK(track_uneven::dip(track_uneven::dip_reach) == doctest::Approx(0.0));
CHECK(track_uneven::rail_phase(26.0, 25.0) == doctest::Approx(1.0));
CHECK(track_uneven::rail_phase(-1.0, 25.0) == doctest::Approx(24.0));
}