diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 0b264b78..5b048396 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1159,7 +1159,35 @@ void editor_mode::render_path_parameters(TTrack &Track) finish_edit(rebuild::none); ImGui::InputFloat(STR_C("Sound distance"), &track->fSoundDistance, 0.0f, 0.0f, "%.1f"); item_tooltip("Distance between the rail joints in metres, sets the rhythm of the wheel clatter; -1: the default"); - finish_edit(rebuild::none); + finish_edit(rebuild::parameters); + if ((track->eType == tt_Normal || track->eType == tt_Switch) && (track->iCategoryFlag & 15) == 1) + { + if (ImGui::InputFloat(STR_C("Unevenness (mm)"), &track->m_uneven, 0.0f, 0.0f, "%.1f")) + track->m_uneven = std::clamp(track->m_uneven, 0.f, track_uneven::most); + item_tooltip("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"); + finish_edit(rebuild::parameters); + struct component + { + char const *label; + float *value; + }; + for (auto const &part : {component{"Up and down (mm)", &track->m_unevenup}, component{"Sideways (mm)", &track->m_unevenside}, component{"Cross level (mm)", &track->m_unevencross}}) + { + if (part.value == &track->m_unevencross && track->eType != tt_Normal) + continue; + if (ImGui::InputFloat(STR_C(part.label), part.value, 0.0f, 0.0f, "%.1f")) + *part.value = *part.value < 0.f ? -1.f : std::min(*part.value, track_uneven::most); + item_tooltip("Set apart from the unevenness, mm. Negative: the unevenness"); + finish_edit(rebuild::parameters); + } + } + if (track->eType == tt_Normal && (track->iCategoryFlag & 15) == 1) + { + if (ImGui::InputFloat(STR_C("Dip at rail joints (mm)"), &track->m_jointdip, 0.0f, 0.0f, "%.1f")) + track->m_jointdip = track->m_jointdip < 0.f ? -1.f : std::min(track->m_jointdip, track_uneven::most); + item_tooltip("Depth of the dip at each rail joint, every sound distance. Works with unevenness at 0. Negative: 0.4 of the unevenness"); + finish_edit(rebuild::parameters); + } ImGui::InputInt(STR_C("Quality"), &track->iQualityFlag, 0); item_tooltip("Quality of the track, 20 is the nominal one; lower values give a rougher ride"); finish_edit(rebuild::none); diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index 418d908f..cbe7c26b 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -152,6 +152,11 @@ editor_track::state editor_track::capture(TTrack const &Track) for (auto const *isolated : Track.Isolated) result.isolated.push_back(isolated->asName); 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.sleeper_frequency = Track.m_sleeper_frequency; result.sleeper_model = Track.m_sleeper_model_name; @@ -250,6 +255,11 @@ void editor_track::apply(TTrack &Track, state const &State) Track.fTexSlope = State.texslope; isolated(Track, State.isolated); 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_frequency = State.sleeper_frequency; Track.m_sleeper_model_name = State.sleeper_model; @@ -2350,6 +2360,16 @@ 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 << ' '; + 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 for (auto const *isolated : Style.Isolated) Text << "isolated " << isolated->asName << ' '; diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index 43d42762..b6137bd8 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -55,6 +55,11 @@ class editor_track float texslope{0.9f}; std::vector isolated; 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}; float sleeper_frequency{0.6f}; std::string sleeper_model; diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 58b30109..a9c61b83 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -861,6 +861,30 @@ msgstr "Odstęp stuków" msgid "Quality" 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" msgstr "Flagi uszkodzeń" diff --git a/tests/unit/test_track_uneven.cpp b/tests/unit/test_track_uneven.cpp new file mode 100644 index 00000000..6333b6ab --- /dev/null +++ b/tests/unit/test_track_uneven.cpp @@ -0,0 +1,152 @@ +#include "stdafx.h" +#include "doctest.h" +#include "world/TrackUneven.h" + +#include +#include + +namespace { + +double deviation(std::vector 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 Values) +{ + for (auto &value : Values) + value = std::abs(value); + std::sort(Values.begin(), Values.end()); + return Values[static_cast(0.999 * Values.size())]; +} + +void add_chord(std::vector &Output, std::vector const &Line, int const Half) +{ + for (int i = Half; i + Half < static_cast(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() * (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 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 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)); +} diff --git a/track_uneven_doc.md b/track_uneven_doc.md new file mode 100644 index 00000000..8cb443ac --- /dev/null +++ b/track_uneven_doc.md @@ -0,0 +1,81 @@ +# 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 ` | `normal`, `switch` | największa odchyłka toku szynowego na cięciwie 10 m; 0-100, domyślnie 0 | +| `unevenup ` | `normal`, `switch` | składowa w pionie, osobno od `uneven` | +| `unevenside ` | `normal`, `switch` | składowa w poziomie, osobno od `uneven` | +| `unevencross `| `normal` | składowa przechyłki, osobno od `uneven`; rozjazd jej nie ma | +| `jointdip ` | `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 | 17 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. 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. 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. + +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. diff --git a/vehicle/DynObj.cpp b/vehicle/DynObj.cpp index 32ba307f..83354288 100644 --- a/vehicle/DynObj.cpp +++ b/vehicle/DynObj.cpp @@ -3627,6 +3627,7 @@ bool TDynamicObject::Update(double dt, double dt1) if( dRailLength > 0.0 ) { for( auto &axle : m_axlesounds ) { axle.distance = axle.offset; + axle.atjoints = false; } } dRailLength = MyTrack->fSoundDistance; @@ -3660,9 +3661,19 @@ bool TDynamicObject::Update(double dt, double dt1) 1 : ctOwner->Vehicle()->DirectionGet() == DirectionGet() ? 1 : -1 ) }; for( auto &axle : m_axlesounds ) { - axle.distance += dDOMoveLen * directioninconsist; - if( axle.distance < 0 - || axle.distance > dRailLength ) { + // on a track with dipping joints the clatter comes from them, elsewhere the axle counts a rail off itself + auto const railphase { Axle0.RailPhase( axle.offset - 0.5 * fAxleDist ) }; + auto crossed { false }; + if( railphase >= 0.0 ) { + crossed = axle.atjoints && std::abs( railphase - axle.distance ) > 0.5 * dRailLength; + axle.distance = railphase; + } + else { + axle.distance += dDOMoveLen * directioninconsist; + crossed = axle.distance < 0 || axle.distance > dRailLength; + } + axle.atjoints = ( railphase >= 0.0 ); + if( crossed ) { axle.distance = clamp_circular( axle.distance, dRailLength ); 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 @@ -8035,6 +8046,12 @@ TDynamicObject::update_shake( double const Timedelta ) { -MoverParameters->AccN * Timedelta * 5.0 * Global.ShakingMultiplierRL, // highlight side sway -MoverParameters->AccVert * Timedelta * Global.ShakingMultiplierUD, -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 ) }; diff --git a/vehicle/DynObj.h b/vehicle/DynObj.h index 36335a32..1bc50b1d 100644 --- a/vehicle/DynObj.h +++ b/vehicle/DynObj.h @@ -402,6 +402,7 @@ private: double distance; // distance to rail joint double offset; // axle offset from centre of the vehicle sound_source clatter; // clatter emitter + bool atjoints { false }; // distance comes from TTrack::rail_phase }; struct powertrain_sounds { diff --git a/world/Segment.cpp b/world/Segment.cpp index 69886f42..ad817b5e 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -118,6 +118,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 fStep = fNewStep; + 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 if( pOwner->eType == tt_Switch && fStep * (3.0 * Global.SplineFidelity) > fLength ) { @@ -366,6 +370,52 @@ Math3D::vector3 TSegment::GetPoint(double const fDistance) 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( track_uneven::stray_per_mm * ( + amount.up * track_uneven::roll( position, across, gauge ) + + amount.cross * track_uneven::level( position ) / 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( track_uneven::stray_per_mm * pOwner->uneven_at( Distance ).cross * track_uneven::level( Origin + glm::dvec3 { Position } ) * 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 (bCurve) { // można by wprowadzić uproszczony wzór dla okręgów płaskich auto const t = FastGetTFromS(fDistance); // aproksymacja dystansu na krzywej Beziera na parametr (t) @@ -500,6 +550,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin, pt, glm::normalize( norm ), glm::vec2 { ( jmm1 * ShapePoints[ j ].texture.x + m1 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv1 } ); + settle_vertex( Output.back(), Origin, pos1, parallel1, s - step ); } // jeśli jest wskaźnik do tablicy if (p && *p && !j) // to dla pierwszego punktu @@ -518,6 +569,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin, pt, glm::normalize( norm ), glm::vec2 { ( jmm2 * ShapePoints[ j ].texture.x + m2 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv2 } ); + settle_vertex( Output.back(), Origin, pos2, parallel2, s ); } // jeśli jest wskaźnik do tablicy // to dla pierwszego punktu @@ -540,6 +592,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin, pt, glm::normalize( norm ), glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv1 } ); + 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.y += ShapePoints[ j ].position.y; @@ -550,6 +603,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin, pt, glm::normalize( norm ), glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv2 } ); + settle_vertex( Output.back(), Origin, pos2, parallel2, s ); } } } diff --git a/world/Segment.h b/world/Segment.h index a3c83ade..492cab94 100644 --- a/world/Segment.h +++ b/world/Segment.h @@ -47,6 +47,7 @@ class TSegment glm::dvec3 Point2; float fRoll1 { 0.f }; float fRoll2 { 0.f }; // przechyłka na końcach + bool m_uneven { false }; // see TrackUneven.h double fLength { -1.0 }; // długość policzona std::vector fTsBuffer; // wartości parametru krzywej t dla równych odcinków s std::vector fTsSlope; // nachylenie dt/du (= dt/ds * fStep) w tych samych punktach @@ -67,6 +68,12 @@ class TSegment glm::dvec3 RaInterpolate(double const t) const; glm::dvec3 RaInterpolate0(double const t) 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: bool bCurve = false; @@ -106,6 +113,13 @@ public: /// Calculated position /// Calculated rotation 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; inline glm::dvec3 diff --git a/world/Track.cpp b/world/Track.cpp index f1a29798..ff5a378a 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -47,6 +47,7 @@ TIsolated *TIsolated::pRoot = nullptr; TTrack::profiles_array TTrack::m_profiles; TTrack::profiles_map TTrack::m_profilesmap; +int TTrack::s_layoutstamp { 0 }; TSwitchExtension::TSwitchExtension(TTrack *owner, int const what) { // na początku wszystko puste @@ -325,6 +326,7 @@ void TTrack::ConnectPrevPrev(TTrack *pTrack, int typ) { //łączenie torów - Point1 własny do Point1 cudzego if (pTrack) { //(pTrack) może być zwrotnicą, a (this) tylko zwykłym odcinkiem + ++s_layoutstamp; trPrev = pTrack; iPrevDirection = pTrack->eType == tt_Switch ? 0 : typ & 2; pTrack->trPrev = this; @@ -335,6 +337,7 @@ void TTrack::ConnectPrevNext(TTrack *pTrack, int typ) { //łaczenie torów - Point1 własny do Point2 cudzego if (pTrack) { + ++s_layoutstamp; trPrev = pTrack; iPrevDirection = typ | 1; // 1:zwykły lub pierwszy zwrotnicy, 3:drugi zwrotnicy pTrack->trNext = this; @@ -354,6 +357,7 @@ void TTrack::ConnectNextPrev(TTrack *pTrack, int typ) { //łaczenie torów - Point2 własny do Point1 cudzego if (pTrack) { + ++s_layoutstamp; trNext = pTrack; iNextDirection = pTrack->eType == tt_Switch ? 0 : typ & 2; pTrack->trPrev = this; @@ -373,6 +377,7 @@ void TTrack::ConnectNextNext(TTrack *pTrack, int typ) { //łaczenie torów - Point2 własny do Point2 cudzego if (pTrack) { + ++s_layoutstamp; trNext = pTrack; iNextDirection = typ | 1; // 1:zwykły lub pierwszy zwrotnicy, 3:drugi zwrotnicy pTrack->trNext = this; @@ -633,6 +638,27 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) m_profile1 = fetch_track_rail_profile( railprofile ); } } + else if( str == "uneven" ) { + // uneven : see TrackUneven.h + auto const uneven { parser->getToken() }; + 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() }; + 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 : depth of the dip at rail joints, with or without uneven + auto const jointdip { parser->getToken() }; + if( eType == tt_Normal && iCategoryFlag == 1 ) { + m_jointdip = std::clamp( jointdip, 0.f, track_uneven::most ); + } + } else if( str == "friction" ) { // memory cell holding friction value modifiers m_friction.first = parser->getToken(); @@ -674,6 +700,10 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) *parser >> token; str = token; } + // the segments were made before the attributes were read, and unevenness changes them + if( is_uneven() ) { + init_segments( false ); + } // alternatywny zapis nazwy odcinka izolowanego - po znaku "@" w nazwie toru if ((i = m_name.find("@")) != std::string::npos && i < m_name.length()) { @@ -687,6 +717,159 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) // 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 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() { location( ( @@ -705,6 +888,14 @@ void TTrack::init_segments( bool const Initial ) { fTexHeight1 -= fTexHeightOffset; fTexHeightOffset = 0.f; 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) { // Ra: łuki segmentowane co 5m albo 314-kątem foremnym @@ -1351,6 +1542,8 @@ glm::vec3 TTrack::get_nearest_point(const glm::dvec3 &point) const // wypełnianie tablic VBO void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) { + ++s_layoutstamp; + Geometry1.clear(); Geometry2.clear(); if( SwitchExtension ) { @@ -2445,6 +2638,21 @@ TTrack::export_as_text_( std::ostream &Output ) const { && false == m_profile1.first.empty() ) { 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() ) { Output << "friction " << m_friction.first << ' '; } diff --git a/world/Track.h b/world/Track.h index 72a6b7ff..ac6d1425 100644 --- a/world/Track.h +++ b/world/Track.h @@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/. #include "utilities/Classes.h" #include "world/Segment.h" +#include "world/TrackUneven.h" #include "model/material.h" #include "scene/scenenode.h" #include "utilities/Names.h" @@ -238,6 +239,12 @@ public: TEnvironmentType eEnvironment = e_flat; // dźwięk i oświetlenie 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 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: double fVelocity = -1.0; // ograniczenie prędkości // prędkość dla AI (powyżej rośnie prawdopowobieństwo wykolejenia) public: @@ -339,6 +346,15 @@ public: void VelocitySet(float v); double VelocityGet(); 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(); bool DoubleSlip() const; static void fetch_default_profiles(); @@ -370,6 +386,22 @@ private: void create_track_rail_profile( gfx::vertex_array &Right, gfx::vertex_array &Left ); void create_track_blade_profile( gfx::vertex_array &Right, gfx::vertex_array &Left ); 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_side_profile( gfx::vertex_array &Right, gfx::vertex_array &Left, gfx::vertex_array const &Road, bool const Forcetransition = false ); /// diff --git a/world/TrackUneven.h b/world/TrackUneven.h new file mode 100644 index 00000000..7486cea2 --- /dev/null +++ b/world/TrackUneven.h @@ -0,0 +1,125 @@ +/* +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 +#include +#include +#include +#include + +// 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. +// 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.6 }; // 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 +using wave_set = std::array; + +// the waves run in all directions of the plan, so no direction of a track is favoured +template +wave_set make_waves( double const Turn, std::array const &Lengths, std::array const &Shares ) { + + auto const golden { glm::pi() * ( 3.0 - std::sqrt( 5.0 ) ) }; + wave_set result {}; + for( std::size_t i = 0; i < Count; ++i ) { + auto const number { glm::two_pi() / 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 wave_lengths { 5.3, 7.1, 9.7, 13.1, 17.9, 23.9, 31.7, 41.9 }; +inline constexpr std::array wave_shares { 0.0721, 0.0997, 0.1344, 0.1703, 0.1549, 0.1386, 0.1226, 0.1074 }; + +inline wave_set<8> const up_waves { make_waves( 0.0, wave_lengths, wave_shares ) }; +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 ) }; +// long waves only: with the short ones the cross level reaching its limit would break the limit of twist +inline wave_set<3> const cross_waves { make_waves<3>( 0.7, { 23.9, 31.7, 41.9 }, { 1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0 } ) }; + +template +double sum( wave_set 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, each axis within +-1 +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 right rail lies by settling apart, for an amplitude of 1. Follows the middle of a path, so plain tracks only +inline double level( glm::dvec3 const &Point ) { + + return cross_share * sum( cross_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() * Fromjoint / dip_reach ) }; + return height * height; +} + +} // namespace track_uneven diff --git a/world/TrkFoll.cpp b/world/TrkFoll.cpp index c437553d..035e0bd9 100644 --- a/world/TrkFoll.cpp +++ b/world/TrkFoll.cpp @@ -267,6 +267,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() { // ustalenie współrzędnych XYZ if (pCurrentSegment) // o ile jest tor diff --git a/world/TrkFoll.h b/world/TrkFoll.h index bba97681..62f386a9 100644 --- a/world/TrkFoll.h +++ b/world/TrkFoll.h @@ -39,6 +39,10 @@ public: // inline double GetLength(vector3 p1, vector3 cp1, vector3 cp2, vector3 p2) //{ return pCurrentSegment->ComputeLength(p1,cp1,cp2,p2); }; // 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); void Reset(); void Render(float fNr);