17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-02 12:49:13 +01:00

Merge remote-tracking branch 'origin/feat/track-uneven' into feat/renderers-cleanup

This commit is contained in:
2026-10-31 18:53:48 +01:00
15 changed files with 807 additions and 6 deletions

View File

@@ -703,8 +703,36 @@ std::vector<track_property> make_properties()
// ride and sound // ride and sound
add_float(list, "friction", section::ride, group::ride_sound, "Friction", "Friction coefficient of the rails, 0.15 for the dry rails; lower values make the wheels slip", &TTrack::fFriction, 3, 0.f, false); add_float(list, "friction", section::ride, group::ride_sound, "Friction", "Friction coefficient of the rails, 0.15 for the dry rails; lower values make the wheels slip", &TTrack::fFriction, 3, 0.f, false);
list.back().applies = any; list.back().applies = any;
add_float(list, "sounddistance", section::ride, group::ride_sound, "Sound distance (m)", "Distance between the rail joints, sets the rhythm of the wheel clatter; -1: the default", &TTrack::fSoundDistance, 1, -1.f, false); // the dips at the rail joints of an uneven track go with it
add_float(list, "sounddistance", section::ride, group::ride_sound, "Sound distance (m)", "Distance between the rail joints, sets the rhythm of the wheel clatter; -1: the default", &TTrack::fSoundDistance, 1, -1.f, true);
list.back().applies = any; list.back().applies = any;
{
// the unevenness of the rails (world/TrackUneven.h), mm up to track_uneven::most; with Unset a negative value is -1, the
// component taken from the unevenness
auto const uneven = [&](char const *Key, char const *Label, char const *Tooltip, float TTrack::*Member, bool const Unset, bool const Switches) {
auto &property{add(list, Key, field::number, section::ride, group::ride_sound, Label, Tooltip)};
property.rebuild = true;
property.applies = [Switches](TTrack const &Track) {
return Track.m_visible && (Track.iCategoryFlag & 15) == kRailCategory && (Track.eType == tt_Normal || (Switches && Track.eType == tt_Switch));
};
property.get = [Member](TTrack const &Track) { return decimal(Track.*Member, 1); };
property.set = [Member, Unset](TTrack &Track, std::string const &Text) {
double value;
if (false == parse_number(Text, value))
return false;
auto const clamped{(Unset && value < 0.0) ? -1.f : std::clamp(static_cast<float>(value), 0.f, track_uneven::most)};
if (decimal(clamped, 1) == decimal(Track.*Member, 1))
return false;
Track.*Member = clamped;
return true;
};
};
uneven("uneven", "Unevenness (mm)", "Peak deviation of a rail on a 10 m chord. Id-14 limits: 6 mm at 160 km/h, 10 at 120, 18 at 80, 35 at 40.\nWith a sound distance set, the rails also dip at the joints. Joined switches use the lowest of their values", &TTrack::m_uneven, false, true);
uneven("unevenup", "Up and down (mm)", "Set apart from the unevenness, mm. Negative: the unevenness", &TTrack::m_unevenup, true, true);
uneven("unevenside", "Sideways (mm)", "Set apart from the unevenness, mm. Negative: the unevenness", &TTrack::m_unevenside, true, true);
uneven("unevencross", "Cross level (mm)", "Set apart from the unevenness, mm. Negative: the unevenness", &TTrack::m_unevencross, true, false);
uneven("jointdip", "Dip at rail joints (mm)", "Depth of the dip at each rail joint, every sound distance. Works with unevenness at 0. Negative: 0.4 of the unevenness", &TTrack::m_jointdip, true, false);
}
{ {
auto &property{add(list, "quality", field::number, section::ride, group::ride_sound, "Quality", "Quality of the track, 20 is the nominal one; lower values give a rougher ride")}; auto &property{add(list, "quality", field::number, section::ride, group::ride_sound, "Quality", "Quality of the track, 20 is the nominal one; lower values give a rougher ride")};
property.applies = any; property.applies = any;

View File

@@ -248,6 +248,11 @@ editor_track::state editor_track::capture(TTrack const &Track)
for (auto const *isolated : Track.Isolated) for (auto const *isolated : Track.Isolated)
result.isolated.push_back(isolated->asName); result.isolated.push_back(isolated->asName);
result.overhead = Track.fOverhead; result.overhead = Track.fOverhead;
result.uneven = Track.m_uneven;
result.unevenup = Track.m_unevenup;
result.unevenside = Track.m_unevenside;
result.unevencross = Track.m_unevencross;
result.jointdip = Track.m_jointdip;
result.sleepers = Track.m_sleeper_enabled; result.sleepers = Track.m_sleeper_enabled;
result.sleeper_frequency = Track.m_sleeper_frequency; result.sleeper_frequency = Track.m_sleeper_frequency;
result.sleeper_model = Track.m_sleeper_model_name; result.sleeper_model = Track.m_sleeper_model_name;
@@ -355,6 +360,11 @@ void editor_track::apply(TTrack &Track, state const &State)
Track.fTexSlope = State.texslope; Track.fTexSlope = State.texslope;
isolated(Track, State.isolated); isolated(Track, State.isolated);
overhead(Track, State.overhead); overhead(Track, State.overhead);
Track.m_uneven = State.uneven;
Track.m_unevenup = State.unevenup;
Track.m_unevenside = State.unevenside;
Track.m_unevencross = State.unevencross;
Track.m_jointdip = State.jointdip;
Track.m_sleeper_enabled = State.sleepers; Track.m_sleeper_enabled = State.sleepers;
Track.m_sleeper_frequency = State.sleeper_frequency; Track.m_sleeper_frequency = State.sleeper_frequency;
Track.m_sleeper_model_name = State.sleeper_model; Track.m_sleeper_model_name = State.sleeper_model;
@@ -3225,6 +3235,16 @@ void write_ending(std::ostream &Text, TTrack const &Style)
{ {
if ((Style.iCategoryFlag & 15) == 1 && false == Style.m_profile1.first.empty()) if ((Style.iCategoryFlag & 15) == 1 && false == Style.m_profile1.first.empty())
Text << "railprofile " << Style.m_profile1.first << ' '; Text << "railprofile " << Style.m_profile1.first << ' ';
if (Style.m_uneven > 0.f)
Text << "uneven " << Style.m_uneven << ' ';
if (Style.m_unevenup >= 0.f)
Text << "unevenup " << Style.m_unevenup << ' ';
if (Style.m_unevenside >= 0.f)
Text << "unevenside " << Style.m_unevenside << ' ';
if (Style.m_unevencross >= 0.f)
Text << "unevencross " << Style.m_unevencross << ' ';
if (Style.m_jointdip >= 0.f)
Text << "jointdip " << Style.m_jointdip << ' ';
// a piece of the track made from another one stays in its track circuit and under the same overhead line // a piece of the track made from another one stays in its track circuit and under the same overhead line
for (auto const *isolated : Style.Isolated) for (auto const *isolated : Style.Isolated)
Text << "isolated " << isolated->asName << ' '; Text << "isolated " << isolated->asName << ' ';

View File

@@ -63,6 +63,11 @@ class editor_track
float texslope{0.9f}; float texslope{0.9f};
std::vector<std::string> isolated; std::vector<std::string> isolated;
float overhead{-1.f}; float overhead{-1.f};
float uneven{0.f};
float unevenup{-1.f};
float unevenside{-1.f};
float unevencross{-1.f};
float jointdip{-1.f};
bool sleepers{false}; bool sleepers{false};
float sleeper_frequency{0.6f}; float sleeper_frequency{0.6f};
std::string sleeper_model; std::string sleeper_model;

View File

@@ -547,6 +547,30 @@ msgstr "Odstęp stuków"
msgid "Quality" msgid "Quality"
msgstr "Jakość" msgstr "Jakość"
msgid "Unevenness (mm)"
msgstr "Nierówności toru (mm)"
msgid "Peak deviation of a rail on a 10 m chord. Id-14 limits: 6 mm at 160 km/h, 10 at 120, 18 at 80, 35 at 40.\nWith a sound distance set, the rails also dip at the joints. Joined switches use the lowest of their values"
msgstr "Największa odchyłka toku szynowego na cięciwie 10 m. Limity Id-14: 6 mm przy 160 km/h, 10 przy 120, 18 przy 80, 35 przy 40.\nPrzy podanym odstępie styków szyny zapadają się też na stykach. Połączone rozjazdy mają najmniejszą ze swoich wartości"
msgid "Up and down (mm)"
msgstr "W pionie (mm)"
msgid "Sideways (mm)"
msgstr "W poziomie (mm)"
msgid "Cross level (mm)"
msgstr "Przechyłka (mm)"
msgid "Set apart from the unevenness, mm. Negative: the unevenness"
msgstr "Osobno od nierówności, mm. Ujemna: wartość nierówności"
msgid "Dip at rail joints (mm)"
msgstr "Zapadnięcie na stykach szyn (mm)"
msgid "Depth of the dip at each rail joint, every sound distance. Works with unevenness at 0. Negative: 0.4 of the unevenness"
msgstr "Głębokość zapadnięcia na każdym styku szyn, co odstęp styków. Działa też przy nierównościach 0. Ujemna: 0,4 nierówności"
msgid "Damage flags" msgid "Damage flags"
msgstr "Flagi uszkodzeń" msgstr "Flagi uszkodzeń"

View File

@@ -0,0 +1,175 @@
#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, across)) * 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 < 40.0); // the long waves go past the 15 mm off the survey marks of appendix 1a
}
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.311350299745).epsilon(1e-9));
CHECK(stray.z == doctest::Approx(-0.328429734019).epsilon(1e-9));
CHECK(track_uneven::level(place, {1.0, 0.0, 0.0}) == doctest::Approx(0.53 * -0.178598957983).epsilon(1e-9));
CHECK(track_uneven::level(place, {0.0, 0.0, 1.0}) == doctest::Approx(0.53 * -0.868291889902).epsilon(1e-9));
}
TEST_CASE("uneven track: tracks laid towards each other meet at one cant")
{
// the same rail stays lower seen from either of them, and a path taken the other way sees the roll turned over
glm::dvec3 const place{812.25, 0.0, 3071.5};
glm::dvec3 const across{0.6, 0.0, -0.8};
CHECK(track_uneven::level(place, -across) == doctest::Approx(-track_uneven::level(place, across)));
CHECK(track_uneven::roll(place, -across, 1.435) == doctest::Approx(-track_uneven::roll(place, across, 1.435)));
// and there's no step along a curve turning through the headings
auto last{track_uneven::level(place, {1.0, 0.0, 0.0})};
for (int step = 1; step <= 3600; ++step)
{
auto const heading{glm::two_pi<double>() * step / 3600.0};
auto const value{track_uneven::level(place, {std::cos(heading), 0.0, std::sin(heading)})};
REQUIRE(std::abs(value - last) < 0.002);
last = value;
}
}
TEST_CASE("uneven track: the field stays within its bound")
{
auto most{0.0};
auto highest{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.z)});
highest = std::max(highest, std::abs(stray.y));
}
CHECK(most <= 1.0);
CHECK(most > 0.5);
CHECK(highest <= 1.69);
CHECK(highest > 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));
}

83
track_uneven_doc.md Normal file
View File

@@ -0,0 +1,83 @@
# Nierówności toru
Opcjonalne atrybuty wpisu toru, przed `endtrack`:
```
node -1 0 tor1 track normal 100.0 1.435 0.15 25.0 20 0 flat vis
rail_screw_used1 6 1435mm/tpbps-new2 0.2 0.5 1.1
0.0 0.2 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.2 100.0 0.0
0.0
uneven 18
unevencross 25
jointdip 6
endtrack
```
| atrybut | dotyczy | opis |
|-------------------|-----------------------|---------------------------------------------------------------------------------------|
| `uneven <mm>` | `normal`, `switch` | największa odchyłka toku szynowego na cięciwie 10 m; 0-100, domyślnie 0 |
| `unevenup <mm>` | `normal`, `switch` | składowa w pionie, osobno od `uneven` |
| `unevenside <mm>` | `normal`, `switch` | składowa w poziomie, osobno od `uneven` |
| `unevencross <mm>`| `normal` | składowa przechyłki, osobno od `uneven`; rozjazd jej nie ma |
| `jointdip <mm>` | `normal` | głębokość zapadnięcia na styku szyn; 0-100, domyślnie 0,4 składowej w pionie |
Składowa niepodana przyjmuje wartość `uneven`. Na przykład sam bok to `unevenside 30`, a same zapadnięcia na stykach to `jointdip 6`.
Wszystkie są też w edytorze, w panelu toru, w sekcji „Ride and sound”. Na drogach, rzekach i obrotnicach są ignorowane.
## Jaką wartość wybrać
`uneven` odpowiada kolumnie „nierówności pionowe” z instrukcji PLK Id-14, załącznik 1 (klasa C):
| prędkość [km/h] | 20 | 40 | 60 | 80 | 100 | 120 | 160 |
|-----------------|----|----|----|----|-----|-----|-----|
| `uneven` [mm] | 50 | 35 | 25 | 18 | 14 | 10 | 6 |
Tor w dobrym stanie to według instrukcji połowa tej wartości (klasa A), w dostatecznym 3/4 (klasa B).
Tor z `uneven 18` mierzy się tak (`tests/unit/test_track_uneven.cpp`), w nawiasie limit Id-14 dla 80 km/h:
| parametr | wynik | Id-14 |
|---------------------------|---------|---------|
| nierówności pionowe | 18 mm | 18 mm |
| nierówności poziome | 19 mm | 17 mm |
| wichrowatość na bazie 5 m | 15 mm | 16 mm |
| szerokość toru | ±10 mm | +10/-8 mm |
| przechyłka | 20 mm | 20 mm |
| wskaźnik J | 6,1 | 5,3 |
## Jak to działa
Nierówność to pole przesunięć zależne od położenia na mapie, złożone z fal o długości 5-42 m. W pionie krótkich fal jest mniej, żeby częściej dochodziły blisko wartości szczytowej, a do tego są fale 47-83 m: na cięciwie 10 m prawie się nie liczą, pudło unosi się na nich i opada, a pantograf za nimi nadąża. Nie ma w nim losowania: to samo miejsce wygina się tak samo przy każdym uruchomieniu. Przesunięty tor dostaje fale z nowego miejsca.
Każda składowa łączy się osobno:
- **Tor z torem:** na styku obowiązuje mniejsza z dwóch wartości, a każdy tor dochodzi do niej płynnie na 20 m. Wolny koniec toru leży w projekcie.
- **Rozjazd:** wszystkie jego drogi mają jedną wartość. Rozjazdy złączone bezpośrednio przyjmują najmniejszą ze swoich. Dołączony tor dochodzi do wartości rozjazdu, także większej od własnej. Tor bez danej składowej trzyma w niej cały rozjazd w projekcie.
- **Styki:** zapadnięcia są co `fSoundDistance`, na długości 6 m, i biegną dalej przez kolejne tory o tym samym odstępie. Stukot osi wypada w miejscu zapadnięcia. Przy odstępie 6 m lub mniejszym oraz na rozjazdach zapadnięć nie ma.
- **Przechyłka:** dolicza się do projektowej. Jest pochyleniem w planie mapy, więc tory wpisane ku sobie spotykają się na tej samej przechyłce. Na rozjazdach tylko ta wynikająca z fal, bez osobnego siadania toków.
Za nierównością idą szyny, podsypka, `sleepermodel`, osie, przechył pudła i bujanie kabiny.
Nie idą za nią modele z includów (np. napędy i podkłady rozjazdów), teren ani sieć trakcyjna, więc przy dużych wartościach szyna rozjeżdża się z takim modelem o kilka centymetrów.
Pantograf podnosi się z szybkością ciśnienia, ok. 0,14 m/s, a szczelina do drutu większa niż 1 cm odcina napięcie i wybija wyłącznik szybki. Nierówność dokłada się do tego przy dużej prędkości. Na torach Bałtyku symulacja daje szczelinę 0,6 cm, gdy składowa w pionie razy prędkość w km/h wynosi 925, czyli np. 8 mm przy 120 km/h, 13 mm przy 70 km/h i 23 mm przy 40 km/h. Wartości z tabeli Id-14 są od tego większe. Sam drut też to potrafi: na Bałtyku przy `tor0290` wznosi się o ok. 1%, więc pantograf nadąża tam do ok. 46 km/h, a powyżej wyłącznik wybija także na prostym torze.
Fizyka bierze promień łuku i pochylenie z projektu, więc nierówności nie zmieniają tych wartości, a do wykolejeń dokładają tylko przechyłkę, ok. 0,06 m/s² przy `uneven 18` wobec progu ok. 3,2 m/s².
## Wydajność
Tor z nierównością ma geometrię co 2 m zamiast co 10 m na prostych. Rozjazdy zachowują swój podział.
Zmierzone (kamera w ruchu lub kabina, 1920×1080):
| scena | bez flag | z flagami 18/35 |
|---------------------------------------|--------------------|--------------------|
| Bałtyk, 56 km torów: wierzchołki | 0,56 mln (ok. 27 MB) | 2,17 mln (ok. 104 MB) |
| Bałtyk: najdłuższa klatka w przelocie | 31,5 ms | 64 ms |
| `td.scn`, jazda 120 km/h: klatka | 2,73 ms | 2,93 ms |
Koszt rośnie z liczbą oflagowanych torów, a najdłuższe klatki pojawiają się przy budowaniu geometrii kolejnych sekcji. Sama jazda po takim torze praktycznie nie kosztuje: ok. 0,3 µs na oś w kroku fizyki.

View File

@@ -3642,6 +3642,7 @@ bool TDynamicObject::Update(double dt, double dt1)
if( dRailLength > 0.0 ) { if( dRailLength > 0.0 ) {
for( auto &axle : m_axlesounds ) { for( auto &axle : m_axlesounds ) {
axle.distance = axle.offset; axle.distance = axle.offset;
axle.atjoints = false;
} }
} }
dRailLength = MyTrack->fSoundDistance; dRailLength = MyTrack->fSoundDistance;
@@ -3675,10 +3676,23 @@ bool TDynamicObject::Update(double dt, double dt1)
1 : 1 :
ctOwner->Vehicle()->DirectionGet() == DirectionGet() ? 1 : -1 ) }; ctOwner->Vehicle()->DirectionGet() == DirectionGet() ? 1 : -1 ) };
for( auto &axle : m_axlesounds ) { for( auto &axle : m_axlesounds ) {
axle.distance += dDOMoveLen * directioninconsist; // on a track with dipping joints the clatter comes from them, elsewhere the axle counts a rail off itself
if( axle.distance < 0 auto const railphase { Axle0.RailPhase( axle.offset - 0.5 * fAxleDist ) };
|| axle.distance > dRailLength ) { auto crossed { false };
axle.distance = clamp_circular( axle.distance, dRailLength ); if( railphase >= 0.0 ) {
// the rails under the axle, which aren't always those under the track the vehicle is on
crossed = axle.atjoints && std::abs( railphase - axle.distance ) > 0.5 * Axle0.GetTrack()->fSoundDistance;
axle.distance = railphase;
}
else {
axle.distance += dDOMoveLen * directioninconsist;
crossed = axle.distance < 0 || axle.distance > dRailLength;
if( crossed ) {
axle.distance = clamp_circular( axle.distance, dRailLength );
}
}
axle.atjoints = ( railphase >= 0.0 );
if( crossed ) {
if( MoverParameters->Vel > 0.1 ) { if( MoverParameters->Vel > 0.1 ) {
// NOTE: for combined clatter sound we supply 1/100th of actual value, as the sound module converts does the opposite, converting received (typically) 0-1 values to 0-100 range // NOTE: for combined clatter sound we supply 1/100th of actual value, as the sound module converts does the opposite, converting received (typically) 0-1 values to 0-100 range
auto const frequency = true == axle.clatter.is_combined() ? MoverParameters->Vel * 0.01 : 1.0; auto const frequency = true == axle.clatter.is_combined() ? MoverParameters->Vel * 0.01 : 1.0;
@@ -8050,6 +8064,12 @@ TDynamicObject::update_shake( double const Timedelta ) {
-MoverParameters->AccN * Timedelta * 5.0 * Global.ShakingMultiplierRL, // highlight side sway -MoverParameters->AccN * Timedelta * 5.0 * Global.ShakingMultiplierRL, // highlight side sway
-MoverParameters->AccVert * Timedelta * Global.ShakingMultiplierUD, -MoverParameters->AccVert * Timedelta * Global.ShakingMultiplierUD,
-MoverParameters->AccSVBased * Timedelta * 1.5 * Global.ShakingMultiplierBF); // accent acceleration/deceleration -MoverParameters->AccSVBased * Timedelta * 1.5 * Global.ShakingMultiplierBF); // accent acceleration/deceleration
// unevenness of the track under the outer axles
auto const ride { 0.5 * ( Axle0.UnevenBend() + Axle1.UnevenBend() ) * ( MoverParameters->V * MoverParameters->V ) };
shakevector += glm::dvec3(
-glm::dot( ride, vLeft ) * Timedelta * 5.0 * Global.ShakingMultiplierRL,
-ride.y * Timedelta * Global.ShakingMultiplierUD,
0.0 );
} }
auto shake { 1.25 * ShakeSpring.ComputateForces( shakevector, ShakeState.offset ) }; auto shake { 1.25 * ShakeSpring.ComputateForces( shakevector, ShakeState.offset ) };

View File

@@ -401,6 +401,7 @@ private:
double distance; // distance to rail joint double distance; // distance to rail joint
double offset; // axle offset from centre of the vehicle double offset; // axle offset from centre of the vehicle
sound_source clatter; // clatter emitter sound_source clatter; // clatter emitter
bool atjoints { false }; // distance comes from TTrack::rail_phase
}; };
struct powertrain_sounds { struct powertrain_sounds {

View File

@@ -135,6 +135,10 @@ bool TSegment::Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 N
fStoop = std::atan2(Point2.y - Point1.y, fLength); // pochylenie toru prostego, żeby nie liczyć wielokrotnie fStoop = std::atan2(Point2.y - Point1.y, fLength); // pochylenie toru prostego, żeby nie liczyć wielokrotnie
fStep = ( fNewStep > 0.0 && std::isfinite( fNewStep ) ) ? fNewStep : fLength; fStep = ( fNewStep > 0.0 && std::isfinite( fNewStep ) ) ? fNewStep : fLength;
if( m_uneven && pOwner->eType != tt_Switch ) {
// dense enough for the waves; a switch keeps its division, the blades depend on it
fStep = std::min( fStep, 2.0 );
}
// NOTE: optionally replace this part with the commented version, after solving geometry issues with double switches // NOTE: optionally replace this part with the commented version, after solving geometry issues with double switches
if( pOwner->eType == tt_Switch if( pOwner->eType == tt_Switch
&& fStep * (3.0 * Global.SplineFidelity) > fLength ) { && fStep * (3.0 * Global.SplineFidelity) > fLength ) {
@@ -426,7 +430,54 @@ Math3D::vector3 TSegment::GetPoint(double const fDistance) const
*/ */
void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const { void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const {
design_position( fDistance, position, rotation );
if( false == m_uneven ) {
return;
}
// direction and slope stay as designed: the physics takes the curve radius and the grade from them
auto const gauge { std::max( std::abs( pOwner->fTrackWidth ), 0.1f ) };
glm::dvec3 const across { -std::cos( rotation.z ), 0.0, -std::sin( rotation.z ) };
auto const amount { pOwner->uneven_at( fDistance ) };
rotation.x += static_cast<float>( track_uneven::stray_per_mm * (
amount.up * track_uneven::roll( position, across, gauge )
+ amount.cross * track_uneven::level( position, across ) / gauge ) );
position += stray( position, fDistance );
}
glm::dvec3 TSegment::stray( glm::dvec3 const &Point, double const Distance ) const {
auto const amount { pOwner->uneven_at( Distance ) };
auto const field { track_uneven::field( Point ) };
glm::dvec3 result { field.x * amount.side, field.y * amount.up, field.z * amount.side };
result *= track_uneven::stray_per_mm;
result.y -= pOwner->joint_dip( Distance );
return result;
}
glm::dvec3 TSegment::uneven_bend( double const Distance ) const {
if( false == m_uneven ) {
return glm::dvec3 { 0.0 };
}
// over about a bogie; FastGetTFromS extrapolates past the ends of the path
auto const reach { 1.5 };
auto const strayed = [&]( double const At ) {
return stray( FastGetPoint( FastGetTFromS( At ) ), At ); };
return ( strayed( Distance + reach ) - 2.0 * strayed( Distance ) + strayed( Distance - reach ) ) / ( reach * reach );
}
void TSegment::settle_vertex( gfx::basic_vertex &Vertex, glm::dvec3 const &Origin, glm::vec3 const &Position, glm::vec3 const &Parallel, double const Distance ) const {
if( false == m_uneven ) {
return;
}
// the rails settling apart turn about the middle of the track, what's outside them follows the rail
auto const gauge { std::max( std::abs( pOwner->fTrackWidth ), 0.1f ) };
auto const across { std::clamp( glm::dot( Vertex.position - Position, Parallel ), -0.5f * gauge, 0.5f * gauge ) };
Vertex.position.y -= static_cast<float>( track_uneven::stray_per_mm * pOwner->uneven_at( Distance ).cross * track_uneven::level( Origin + glm::dvec3 { Position }, glm::dvec3 { Parallel } ) * across / gauge );
Vertex.position += glm::vec3 { stray( Origin + glm::dvec3 { Vertex.position }, Distance ) };
}
void TSegment::design_position(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const {
if( false == m_pieces.empty() ) { if( false == m_pieces.empty() ) {
// the pieces keep to their design, the unevenness is the one of the whole path
auto const piece { piece_at( fDistance ) }; auto const piece { piece_at( fDistance ) };
m_pieces[ piece ]->RaPositionGet( fDistance - m_starts[ piece ], position, rotation ); m_pieces[ piece ]->RaPositionGet( fDistance - m_starts[ piece ], position, rotation );
} }
@@ -579,6 +630,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
glm::normalize( norm ), glm::normalize( norm ),
glm::vec2 { ( jmm1 * ShapePoints[ j ].texture.x + m1 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv1 } ); glm::vec2 { ( jmm1 * ShapePoints[ j ].texture.x + m1 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv1 } );
roll_vertex( Output.back(), pos1, parallel1, s - step ); roll_vertex( Output.back(), pos1, parallel1, s - step );
settle_vertex( Output.back(), Origin, pos1, parallel1, s - step );
} }
// jeśli jest wskaźnik do tablicy // jeśli jest wskaźnik do tablicy
if (p && *p && !j) // to dla pierwszego punktu if (p && *p && !j) // to dla pierwszego punktu
@@ -598,6 +650,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
glm::normalize( norm ), glm::normalize( norm ),
glm::vec2 { ( jmm2 * ShapePoints[ j ].texture.x + m2 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv2 } ); glm::vec2 { ( jmm2 * ShapePoints[ j ].texture.x + m2 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv2 } );
roll_vertex( Output.back(), pos2, parallel2, s ); roll_vertex( Output.back(), pos2, parallel2, s );
settle_vertex( Output.back(), Origin, pos2, parallel2, s );
} }
// jeśli jest wskaźnik do tablicy // jeśli jest wskaźnik do tablicy
// to dla pierwszego punktu // to dla pierwszego punktu
@@ -621,6 +674,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
glm::normalize( norm ), glm::normalize( norm ),
glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv1 } ); glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv1 } );
roll_vertex( Output.back(), pos1, parallel1, s - step ); roll_vertex( Output.back(), pos1, parallel1, s - step );
settle_vertex( Output.back(), Origin, pos1, parallel1, s - step );
pt = parallel2 * ( jmm2 * ( ShapePoints[ j ].position.x - fOffsetX.first ) + m2 * ( ShapePoints[ j ].position.x - fOffsetX.second ) ) + pos2; pt = parallel2 * ( jmm2 * ( ShapePoints[ j ].position.x - fOffsetX.first ) + m2 * ( ShapePoints[ j ].position.x - fOffsetX.second ) ) + pos2;
pt.y += ShapePoints[ j ].position.y; pt.y += ShapePoints[ j ].position.y;
@@ -632,6 +686,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
glm::normalize( norm ), glm::normalize( norm ),
glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv2 } ); glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv2 } );
roll_vertex( Output.back(), pos2, parallel2, s ); roll_vertex( Output.back(), pos2, parallel2, s );
settle_vertex( Output.back(), Origin, pos2, parallel2, s );
} }
} }
} }
@@ -699,7 +754,8 @@ bool TSegment::RenderLoftSteps( gfx::vertex_array &Output, glm::dvec3 const &Ori
auto normal { ( rest * from.normal.x + Share * to.normal.x ) * Parallel }; auto normal { ( rest * from.normal.x + Share * to.normal.x ) * Parallel };
normal.y += rest * from.normal.y + Share * to.normal.y; normal.y += rest * from.normal.y + Share * to.normal.y;
Output.emplace_back( point, glm::normalize( normal ), glm::vec2 { rest * from.texture.x + Share * to.texture.x, V } ); Output.emplace_back( point, glm::normalize( normal ), glm::vec2 { rest * from.texture.x + Share * to.texture.x, V } );
roll_vertex( Output.back(), Position, Parallel, Share * fLength ); }; roll_vertex( Output.back(), Position, Parallel, Share * fLength );
settle_vertex( Output.back(), Origin, Position, Parallel, Share * fLength ); };
glm::vec3 pos1, pos2, parallel1, parallel2; glm::vec3 pos1, pos2, parallel1, parallel2;
station( 0, pos1, parallel1 ); station( 0, pos1, parallel1 );
@@ -1044,6 +1100,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
} }
for( auto &vertex : Ring ) { for( auto &vertex : Ring ) {
roll_vertex( vertex, position, parallel, Distance ); roll_vertex( vertex, position, parallel, Distance );
settle_vertex( vertex, Origin, position, parallel, Distance );
} }
}; };

View File

@@ -101,6 +101,7 @@ class TSegment
glm::dvec3 Point2; glm::dvec3 Point2;
float fRoll1 { 0.f }; float fRoll1 { 0.f };
float fRoll2 { 0.f }; // przechyłka na końcach float fRoll2 { 0.f }; // przechyłka na końcach
bool m_uneven { false }; // see TrackUneven.h
double fLength { -1.0 }; // długość policzona double fLength { -1.0 }; // długość policzona
std::vector<double> fTsBuffer; // wartości parametru krzywej t dla równych odcinków s std::vector<double> fTsBuffer; // wartości parametru krzywej t dla równych odcinków s
std::vector<double> fTsSlope; // nachylenie dt/du (= dt/ds * fStep) w tych samych punktach std::vector<double> fTsSlope; // nachylenie dt/du (= dt/ds * fStep) w tych samych punktach
@@ -131,6 +132,12 @@ class TSegment
// the vertex of a profile lofted at the point and across the path there, turned from the baked roll to the one of the path at the distance // the vertex of a profile lofted at the point and across the path there, turned from the baked roll to the one of the path at the distance
void roll_vertex( gfx::basic_vertex &Vertex, glm::vec3 const &Position, glm::vec3 const &Parallel, double const Distance ) const; void roll_vertex( gfx::basic_vertex &Vertex, glm::vec3 const &Position, glm::vec3 const &Parallel, double const Distance ) const;
// moves a lofted vertex with the unevenness of the track
void settle_vertex( gfx::basic_vertex &Vertex, glm::dvec3 const &Origin, glm::vec3 const &Position, glm::vec3 const &Parallel, double const Distance ) const;
// displacement of a point by the unevenness, at a distance along the path
glm::dvec3 stray( glm::dvec3 const &Point, double const Distance ) const;
void design_position( double const fDistance, glm::dvec3 &position, glm::vec3 &rotation ) const;
public: public:
bool bCurve = false; bool bCurve = false;
@@ -179,6 +186,13 @@ public:
/// <param name="position">Calculated position</param> /// <param name="position">Calculated position</param>
/// <param name="rotation">Calculated rotation</param> /// <param name="rotation">Calculated rotation</param>
void RaPositionGet(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const; void RaPositionGet(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const;
// call before Init, it sets the density of the geometry
void
set_uneven( bool const Uneven ) {
m_uneven = Uneven; }
// second derivative of the displacement along the path; times speed squared: acceleration of an axle
glm::dvec3
uneven_bend( double const Distance ) const;
glm::dvec3 FastGetPoint(double const t) const; glm::dvec3 FastGetPoint(double const t) const;
inline inline
glm::dvec3 glm::dvec3

View File

@@ -85,6 +85,7 @@ void init_switch_piece( TSegment &Segment, segment_data const &Piece, bool const
TTrack::profiles_array TTrack::m_profiles; TTrack::profiles_array TTrack::m_profiles;
TTrack::profiles_map TTrack::m_profilesmap; TTrack::profiles_map TTrack::m_profilesmap;
int TTrack::s_layoutstamp { 0 };
TSwitchExtension::TSwitchExtension(TTrack *owner, int const what) TSwitchExtension::TSwitchExtension(TTrack *owner, int const what)
{ // na początku wszystko puste { // na początku wszystko puste
@@ -367,6 +368,7 @@ void TTrack::ConnectPrevPrev(TTrack *pTrack, int typ)
{ //łączenie torów - Point1 własny do Point1 cudzego { //łączenie torów - Point1 własny do Point1 cudzego
if (pTrack) if (pTrack)
{ //(pTrack) może być zwrotnicą, a (this) tylko zwykłym odcinkiem { //(pTrack) może być zwrotnicą, a (this) tylko zwykłym odcinkiem
++s_layoutstamp;
trPrev = pTrack; trPrev = pTrack;
iPrevDirection = pTrack->eType == tt_Switch ? 0 : typ & 2; iPrevDirection = pTrack->eType == tt_Switch ? 0 : typ & 2;
pTrack->trPrev = this; pTrack->trPrev = this;
@@ -377,6 +379,7 @@ void TTrack::ConnectPrevNext(TTrack *pTrack, int typ)
{ //łaczenie torów - Point1 własny do Point2 cudzego { //łaczenie torów - Point1 własny do Point2 cudzego
if (pTrack) if (pTrack)
{ {
++s_layoutstamp;
trPrev = pTrack; trPrev = pTrack;
iPrevDirection = typ | 1; // 1:zwykły lub pierwszy zwrotnicy, 3:drugi zwrotnicy iPrevDirection = typ | 1; // 1:zwykły lub pierwszy zwrotnicy, 3:drugi zwrotnicy
pTrack->trNext = this; pTrack->trNext = this;
@@ -396,6 +399,7 @@ void TTrack::ConnectNextPrev(TTrack *pTrack, int typ)
{ //łaczenie torów - Point2 własny do Point1 cudzego { //łaczenie torów - Point2 własny do Point1 cudzego
if (pTrack) if (pTrack)
{ {
++s_layoutstamp;
trNext = pTrack; trNext = pTrack;
iNextDirection = pTrack->eType == tt_Switch ? 0 : typ & 2; iNextDirection = pTrack->eType == tt_Switch ? 0 : typ & 2;
pTrack->trPrev = this; pTrack->trPrev = this;
@@ -415,6 +419,7 @@ void TTrack::ConnectNextNext(TTrack *pTrack, int typ)
{ //łaczenie torów - Point2 własny do Point2 cudzego { //łaczenie torów - Point2 własny do Point2 cudzego
if (pTrack) if (pTrack)
{ {
++s_layoutstamp;
trNext = pTrack; trNext = pTrack;
iNextDirection = typ | 1; // 1:zwykły lub pierwszy zwrotnicy, 3:drugi zwrotnicy iNextDirection = typ | 1; // 1:zwykły lub pierwszy zwrotnicy, 3:drugi zwrotnicy
pTrack->trNext = this; pTrack->trNext = this;
@@ -731,6 +736,27 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
ErrorLog( "Bad track: " + str + " at end " + std::to_string( end ) + " of track \"" + m_name + "\" which has no such end" ); ErrorLog( "Bad track: " + str + " at end " + std::to_string( end ) + " of track \"" + m_name + "\" which has no such end" );
} }
} }
else if( str == "uneven" ) {
// uneven <mm>: see TrackUneven.h
auto const uneven { parser->getToken<float>() };
if( ( eType == tt_Normal || eType == tt_Switch ) && iCategoryFlag == 1 ) {
m_uneven = std::clamp( uneven, 0.f, track_uneven::most );
}
}
else if( str == "unevenup" || str == "unevenside" || str == "unevencross" ) {
// the components of uneven set apart, mm
auto const value { parser->getToken<float>() };
if( ( eType == tt_Normal || eType == tt_Switch ) && iCategoryFlag == 1 ) {
( str == "unevenup" ? m_unevenup : str == "unevenside" ? m_unevenside : m_unevencross ) = std::clamp( value, 0.f, track_uneven::most );
}
}
else if( str == "jointdip" ) {
// jointdip <mm>: depth of the dip at rail joints, with or without uneven
auto const jointdip { parser->getToken<float>() };
if( eType == tt_Normal && iCategoryFlag == 1 ) {
m_jointdip = std::clamp( jointdip, 0.f, track_uneven::most );
}
}
else if( str == "friction" ) { else if( str == "friction" ) {
// memory cell holding friction value modifiers // memory cell holding friction value modifiers
m_friction.first = parser->getToken<std::string>(); m_friction.first = parser->getToken<std::string>();
@@ -807,6 +833,159 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
// to a stale origin and the renderer would draw sleepers in the wrong place). // to a stale origin and the renderer would draw sleepers in the wrong place).
} }
track_uneven::amounts TTrack::uneven_declared() const {
return {
m_unevenup >= 0.f ? m_unevenup : m_uneven,
m_unevenside >= 0.f ? m_unevenside : m_uneven,
eType != tt_Normal ? 0.f : m_unevencross >= 0.f ? m_unevencross : m_uneven };
}
bool TTrack::is_uneven() const {
auto const declared { uneven_declared() };
return declared.up > 0.0 || declared.side > 0.0 || declared.cross > 0.0 || joint_depth() > 0.0;
}
track_uneven::amounts TTrack::uneven_at( double const Distance ) const {
if( eType == tt_Switch ) {
return switch_uneven();
}
auto const own { uneven_declared() };
auto const atend = [&]( TTrack const *Other ) -> track_uneven::amounts {
if( Other == nullptr ) {
return {};
}
if( Other->eType == tt_Switch ) {
return Other->switch_uneven();
}
auto const theirs { Other->uneven_declared() };
return { std::min( own.up, theirs.up ), std::min( own.side, theirs.side ), std::min( own.cross, theirs.cross ) }; };
auto const start { atend( trPrev ) };
auto const end { atend( trNext ) };
return {
track_uneven::along( own.up, start.up, end.up, Distance, Length() ),
track_uneven::along( own.side, start.side, end.side, Distance, Length() ),
track_uneven::along( own.cross, start.cross, end.cross, Distance, Length() ) };
}
track_uneven::amounts TTrack::switch_uneven() const {
if( m_switchstray.stamp == s_layoutstamp ) {
return m_switchstray.value;
}
std::vector<TTrack const *> group { this };
auto least { uneven_declared() };
for( std::size_t index = 0; index < group.size(); ++index ) {
auto const &extension { *group[ index ]->SwitchExtension };
for( auto const *other : { extension.pPrevs[ 0 ], extension.pPrevs[ 1 ], extension.pNexts[ 0 ], extension.pNexts[ 1 ] } ) {
if( other == nullptr ) {
continue;
}
auto const theirs { other->uneven_declared() };
if( other->eType == tt_Switch && other->SwitchExtension ) {
least.up = std::min( least.up, theirs.up );
least.side = std::min( least.side, theirs.side );
if( std::find( group.begin(), group.end(), other ) == group.end() ) {
group.push_back( other );
}
}
else {
// a track with none of a component keeps to its design in it, any other comes to the value of the switch
if( theirs.up <= 0.0 ) { least.up = 0.0; }
if( theirs.side <= 0.0 ) { least.side = 0.0; }
}
}
}
for( auto const *track : group ) {
track->m_switchstray = { least, s_layoutstamp };
}
return least;
}
double TTrack::joint_depth() const {
if( eType != tt_Normal ) {
return 0.0;
}
return ( m_jointdip >= 0.f ? m_jointdip : track_uneven::dip_share * uneven_declared().up );
}
double TTrack::joint_dip( double const Distance ) const {
auto const phase { rail_phase( Distance ) };
if( phase < 0.0 ) {
return 0.0;
}
auto const fromjoint { std::min( phase, fSoundDistance - phase ) };
if( fromjoint >= track_uneven::dip_reach ) {
return 0.0;
}
auto const depth { joint_depth() };
auto const atend = [&]( TTrack const *Other ) {
return ( Other == nullptr || Other->fSoundDistance != fSoundDistance ? 0.0 : std::min( depth, Other->joint_depth() ) ); };
return 0.001 * track_uneven::dip( fromjoint ) * track_uneven::along( depth, atend( trPrev ), atend( trNext ), Distance, Length() );
}
double TTrack::rail_phase( double const Distance ) const {
if( joint_depth() <= 0.0 || fSoundDistance <= 2.0 * track_uneven::dip_reach ) {
return -1.0;
}
if( m_railrun.stamp != s_layoutstamp ) {
settle_rail_run();
}
return track_uneven::rail_phase( m_railrun.origin + m_railrun.way * Distance, fSoundDistance );
}
void TTrack::settle_rail_run() const {
// the next track of the run past an end of a track, and which of its ends is joined there
auto const onward = []( TTrack const &Track, int const End, int &Joined ) -> TTrack const * {
auto const *other { End == 0 ? Track.trPrev : Track.trNext };
if( other == nullptr || other == &Track || other->joint_depth() <= 0.0 || other->fSoundDistance != Track.fSoundDistance ) {
return nullptr;
}
Joined = ( End == 0 ? Track.iPrevDirection : Track.iNextDirection ) & 1;
return other; };
// walks from this track to an end of the run
auto const walk = [&]( int &End ) {
auto const *track { this };
int joined { 0 };
for( int step = 0; step < 1000000; ++step ) {
auto const *next { onward( *track, End, joined ) };
if( next == nullptr || next == this ) {
break;
}
track = next;
End = 1 - joined;
}
return track; };
auto const place = []( TTrack const &Track, int const End ) {
auto const point { End == 0 ? Track.Segment->FastGetPoint_0() : Track.Segment->FastGetPoint_1() };
return std::make_pair( point.x, point.z ); };
// start at the end with the lesser coordinates, to get the same joints from any track of the run
int end { 0 };
int otherend { 1 };
auto const *track { walk( end ) };
auto const *other { walk( otherend ) };
if( place( *other, otherend ) < place( *track, end ) ) {
track = other;
end = otherend;
}
auto along { 0.0 };
while( track != nullptr && track->m_railrun.stamp != s_layoutstamp ) {
auto const length { track->Length() };
track->m_railrun = { end == 0 ? along : along + length, end == 0 ? 1.0 : -1.0, s_layoutstamp };
along += length;
int joined { 0 };
track = onward( *track, 1 - end, joined );
end = joined;
}
}
void TTrack::update_location() { void TTrack::update_location() {
location( ( location( (
@@ -825,6 +1004,14 @@ void TTrack::init_segments( bool const Initial ) {
fTexHeight1 -= fTexHeightOffset; fTexHeight1 -= fTexHeightOffset;
fTexHeightOffset = 0.f; fTexHeightOffset = 0.f;
iTrapezoid = 0; iTrapezoid = 0;
++s_layoutstamp;
if( eType == tt_Switch && SwitchExtension ) {
SwitchExtension->Segments[ 0 ]->set_uneven( is_uneven() );
SwitchExtension->Segments[ 1 ]->set_uneven( is_uneven() );
}
else if( Segment ) {
Segment->set_uneven( is_uneven() );
}
switch (eType) { switch (eType) {
// Ra: łuki segmentowane co 5m albo 314-kątem foremnym // Ra: łuki segmentowane co 5m albo 314-kątem foremnym
@@ -1510,6 +1697,8 @@ glm::vec3 TTrack::get_nearest_point(const glm::dvec3 &point) const
// wypełnianie tablic VBO // wypełnianie tablic VBO
void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) { void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) {
++s_layoutstamp;
track_rails::forget(); track_rails::forget();
auto const erase = [ this ]( gfx::geometry_handle const &Chunk ) { auto const erase = [ this ]( gfx::geometry_handle const &Chunk ) {
if( SwitchExtension if( SwitchExtension
@@ -2841,6 +3030,21 @@ TTrack::export_as_text_( std::ostream &Output ) const {
&& false == m_profile1.first.empty() ) { && false == m_profile1.first.empty() ) {
Output << "railprofile " << m_profile1.first << ' '; Output << "railprofile " << m_profile1.first << ' ';
} }
if( m_uneven > 0.f ) {
Output << "uneven " << m_uneven << ' ';
}
if( m_unevenup >= 0.f ) {
Output << "unevenup " << m_unevenup << ' ';
}
if( m_unevenside >= 0.f ) {
Output << "unevenside " << m_unevenside << ' ';
}
if( m_unevencross >= 0.f ) {
Output << "unevencross " << m_unevencross << ' ';
}
if( m_jointdip >= 0.f ) {
Output << "jointdip " << m_jointdip << ' ';
}
if( false == m_friction.first.empty() ) { if( false == m_friction.first.empty() ) {
Output << "friction " << m_friction.first << ' '; Output << "friction " << m_friction.first << ' ';
} }

View File

@@ -22,6 +22,7 @@ http://mozilla.org/MPL/2.0/.
#include "world/RailLayout.h" #include "world/RailLayout.h"
#include "world/TrackSleepers.h" #include "world/TrackSleepers.h"
#include "world/TrackBlend.h" #include "world/TrackBlend.h"
#include "world/TrackUneven.h"
#include "model/material.h" #include "model/material.h"
#include "scene/scenenode.h" #include "scene/scenenode.h"
#include "utilities/Names.h" #include "utilities/Names.h"
@@ -286,6 +287,12 @@ public:
TEnvironmentType eEnvironment = e_flat; // dźwięk i oświetlenie TEnvironmentType eEnvironment = e_flat; // dźwięk i oświetlenie
int iAction = 0; // czy modyfikowany eventami (specjalna obsługa przy skanowaniu) int iAction = 0; // czy modyfikowany eventami (specjalna obsługa przy skanowaniu)
float fOverhead = -1.0; // można normalnie pobierać prąd (0 dla jazdy bezprądowej po danym odcinku, >0-z opuszczonym i ograniczeniem prędkości) float fOverhead = -1.0; // można normalnie pobierać prąd (0 dla jazdy bezprądowej po danym odcinku, >0-z opuszczonym i ograniczeniem prędkości)
float m_uneven { 0.f }; // mm, see TrackUneven.h; plain tracks and switches
// the components set apart from m_uneven, mm; negative: m_uneven. Cross level on plain tracks only
float m_unevenup { -1.f };
float m_unevenside { -1.f };
float m_unevencross { -1.f };
float m_jointdip { -1.f }; // depth of the dip at rail joints, mm; negative: from m_uneven; plain tracks only
private: private:
double fVelocity = -1.0; // ograniczenie prędkości // prędkość dla AI (powyżej rośnie prawdopowobieństwo wykolejenia) double fVelocity = -1.0; // ograniczenie prędkości // prędkość dla AI (powyżej rośnie prawdopowobieństwo wykolejenia)
public: public:
@@ -406,6 +413,15 @@ public:
void VelocitySet(float v); void VelocitySet(float v);
double VelocityGet(); double VelocityGet();
float Friction() const; float Friction() const;
// the unevenness the track declares
track_uneven::amounts uneven_declared() const;
bool is_uneven() const;
// the unevenness at a distance along the track, meeting the tracks joined at its ends
track_uneven::amounts uneven_at( double const Distance ) const;
double joint_depth() const; // mm
double joint_dip( double const Distance ) const; // m
// distance from the rail joint behind; negative: the track has no joints that dip
double rail_phase( double const Distance ) const;
void ConnectionsLog(); void ConnectionsLog();
bool DoubleSlip() const; bool DoubleSlip() const;
static void fetch_default_profiles(); static void fetch_default_profiles();
@@ -439,6 +455,22 @@ private:
void profile_rolls( float &Roll1, float &Roll2 ) const; void profile_rolls( float &Roll1, float &Roll2 ) const;
void share_joints(); void share_joints();
void create_track_bed_profile( gfx::vertex_array &Output, TTrack const *Previous, TTrack const *Next ); void create_track_bed_profile( gfx::vertex_array &Output, TTrack const *Previous, TTrack const *Next );
// rail joints run on across joined tracks, however the line is cut into them, so the clatter keeps its rhythm
struct rail_run {
double origin { 0.0 }; // position along the run of Point1
double way { 1.0 }; // direction of the path along the run
int stamp { -1 };
};
void settle_rail_run() const;
mutable rail_run m_railrun;
// one value along all paths of a switch: the lowest of the switches joined, 0 next to a track with none
track_uneven::amounts switch_uneven() const;
struct switch_stray {
track_uneven::amounts value;
int stamp { -1 };
};
mutable switch_stray m_switchstray;
static int s_layoutstamp; // bumped when tracks are joined or changed, invalidates the two above
void create_road_profile( gfx::vertex_array &Output, bool const Forcetransition = false ); void create_road_profile( gfx::vertex_array &Output, bool const Forcetransition = false );
void create_road_side_profile( gfx::vertex_array &Right, gfx::vertex_array &Left, gfx::vertex_array const &Road, bool const Forcetransition = false ); void create_road_side_profile( gfx::vertex_array &Right, gfx::vertex_array &Left, gfx::vertex_array const &Road, bool const Forcetransition = false );
/// <summary> /// <summary>

129
world/TrackUneven.h Normal file
View File

@@ -0,0 +1,129 @@
/*
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 <array>
#include <cmath>
#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>
// Track unevenness: a displacement field over the plan of the scenery, so everything at one place moves together: joined tracks, the
// paths of a switch, sleepers, axles. Waves 5-42 m long, shares after the German track spectrum with the long waves cut down; up and down to 83 m.
// The "uneven" value is the peak deviation of a rail on a 10 m chord, as in PLK Id-14 appendix 1; see test_track_uneven.cpp.
namespace track_uneven {
inline constexpr float most { 100.f }; // largest value of the attributes, mm
inline constexpr double stray_per_mm { 0.00145 }; // field amplitude per mm of "uneven", m
inline constexpr double cross_share { 0.53 }; // share of the field amplitude the rails settle apart by
inline constexpr double fade { 20.0 }; // length over which a track goes from the value at its end to its own, m
inline constexpr double dip_share { 0.4 }; // joint dip depth per mm of "uneven", mm
inline constexpr double dip_reach { 3.0 }; // reach of the dip on each side of a joint, m
// the value of each component of the unevenness, mm
struct amounts {
double up { 0.0 };
double side { 0.0 };
double cross { 0.0 };
};
struct wave {
double x;
double z;
double phase;
double share;
};
template <std::size_t Count>
using wave_set = std::array<wave, Count>;
// the waves run in all directions of the plan, so no direction of a track is favoured
template <std::size_t Count>
wave_set<Count> make_waves( double const Turn, std::array<double, Count> const &Lengths, std::array<double, Count> const &Shares ) {
auto const golden { glm::pi<double>() * ( 3.0 - std::sqrt( 5.0 ) ) };
wave_set<Count> result {};
for( std::size_t i = 0; i < Count; ++i ) {
auto const number { glm::two_pi<double>() / Lengths[ i ] };
auto const heading { Turn + golden * i };
result[ i ] = { number * std::cos( heading ), number * std::sin( heading ), golden * ( i + 1 ) * ( 1.0 + 3.0 * Turn ), Shares[ i ] };
}
return result;
}
inline constexpr std::array<double, 8> wave_lengths { 5.3, 7.1, 9.7, 13.1, 17.9, 23.9, 31.7, 41.9 };
inline constexpr std::array<double, 8> wave_shares { 0.0721, 0.0997, 0.1344, 0.1703, 0.1549, 0.1386, 0.1226, 0.1074 };
// fewer short ones than sideways: the rails come near the limit in the vertical more often, with the peak still at it.
// The long ones hardly count on the 10 m chord: the body rises and falls on them, slowly enough for a pantograph to keep up
inline wave_set<8> const up_waves { make_waves<8>( 0.0, { 8.3, 11.9, 16.7, 23.9, 37.1, 47.3, 61.9, 83.3 }, { 0.14, 0.19, 0.22, 0.22, 0.23, 0.23, 0.23, 0.23 } ) };
inline wave_set<8> const x_waves { make_waves( 1.1, wave_lengths, wave_shares ) };
inline wave_set<8> const z_waves { make_waves( 2.3, wave_lengths, wave_shares ) };
// the tilt along each axis of the plan. Long waves only: with the short ones the cross level reaching its limit would break the limit of twist
inline wave_set<3> const tilt_x_waves { make_waves<3>( 0.7, { 23.9, 31.7, 41.9 }, { 1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0 } ) };
inline wave_set<3> const tilt_z_waves { make_waves<3>( 2.9, { 23.9, 31.7, 41.9 }, { 1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0 } ) };
template <std::size_t Count>
double sum( wave_set<Count> const &Waves, glm::dvec3 const &Point ) {
auto total { 0.0 };
for( auto const &wave : Waves ) {
total += wave.share * std::sin( wave.x * Point.x + wave.z * Point.z + wave.phase );
}
return total;
}
// displacement at the point for an amplitude of 1, sideways within +-1, up and down within +-1.69
inline glm::dvec3 field( glm::dvec3 const &Point ) {
return { sum( x_waves, Point ), sum( up_waves, Point ), sum( z_waves, Point ) };
}
// how much lower the rail towards Across lies by settling apart, for an amplitude of 1. A tilt of the plan, so tracks laid towards each
// other meet at one cant. Follows the middle of a path, so plain tracks only
inline double level( glm::dvec3 const &Point, glm::dvec3 const &Across ) {
return cross_share * ( Across.x * sum( tilt_x_waves, Point ) + Across.z * sum( tilt_z_waves, Point ) );
}
// roll made by the field for an amplitude of 1; positive lowers the rail towards Across, as the roll of a path
inline double roll( glm::dvec3 const &Point, glm::dvec3 const &Across, double const Gauge ) {
return ( sum( up_waves, Point - Across * ( 0.5 * Gauge ) ) - sum( up_waves, Point + Across * ( 0.5 * Gauge ) ) ) / Gauge;
}
// value at a distance along a track: Full inside, exactly Atstart and Atend at its ends where it meets the neighbours
inline double along( double const Full, double const Atstart, double const Atend, double const Distance, double const Length ) {
auto const smooth = []( double const Share ) {
auto const share { std::clamp( Share, 0.0, 1.0 ) };
return share * share * ( 3.0 - 2.0 * share ); };
auto const ends { Length > 0.0 ? Atstart + ( Atend - Atstart ) * smooth( Distance / Length ) : Atstart };
return ends + ( Full - ends ) * std::min( smooth( Distance / fade ), smooth( ( Length - Distance ) / fade ) );
}
// distance from the joint behind the point
inline double rail_phase( double const Along, double const Rail ) {
auto const phase { std::fmod( Along, Rail ) };
return phase < 0.0 ? phase + Rail : phase;
}
// share of the dip depth at a distance from the joint
inline double dip( double const Fromjoint ) {
if( Fromjoint >= dip_reach ) {
return 0.0;
}
auto const height { std::cos( glm::half_pi<double>() * Fromjoint / dip_reach ) };
return height * height;
}
} // namespace track_uneven

View File

@@ -386,6 +386,11 @@ bool TTrackFollower::Move(double fDistance, bool bPrimary)
} }
}; };
double TTrackFollower::RailPhase( double const Offset ) const {
return pCurrentTrack != nullptr ? pCurrentTrack->rail_phase( fCurrentDistance + fDirection * Offset ) : -1.0;
}
bool TTrackFollower::ComputatePosition() bool TTrackFollower::ComputatePosition()
{ // ustalenie współrzędnych XYZ { // ustalenie współrzędnych XYZ
if (pCurrentSegment) // o ile jest tor if (pCurrentSegment) // o ile jest tor

View File

@@ -39,6 +39,10 @@ public:
// inline double GetLength(vector3 p1, vector3 cp1, vector3 cp2, vector3 p2) // inline double GetLength(vector3 p1, vector3 cp1, vector3 cp2, vector3 p2)
//{ return pCurrentSegment->ComputeLength(p1,cp1,cp2,p2); }; //{ return pCurrentSegment->ComputeLength(p1,cp1,cp2,p2); };
// inline double GetRadius(double L, double d); //McZapkie-150503 // inline double GetRadius(double L, double d); //McZapkie-150503
inline glm::dvec3 UnevenBend() const {
return pCurrentSegment ? pCurrentSegment->uneven_bend( fCurrentDistance ) : glm::dvec3 { 0.0 }; };
// TTrack::rail_phase of the point Offset ahead of the axle along the vehicle
double RailPhase( double const Offset ) const;
bool Init(TTrack *pTrack, TDynamicObject *NewOwner, double fDir); bool Init(TTrack *pTrack, TDynamicObject *NewOwner, double fDir);
void Reset(); void Reset();
void Render(float fNr); void Render(float fNr);