From 02abc7df0ab4baa288d977e3d2650ec1127fe999 Mon Sep 17 00:00:00 2001 From: maj00r Date: Sat, 31 Oct 2026 18:33:35 +0100 Subject: [PATCH] feat(track): all of the unevenness in one attribute, with values for each path of a switch Usage, before endtrack, in mm, 0-100, separated by semicolons with no spaces: a plain track: uneven value;up;side;cross;dip a switch: uneven p1-p2;p3-p4;up p1-p2;side p1-p2;up p3-p4;side p3-p4 uneven 18 the value alone uneven 18;;;25;6 with its own cross level and dip at the joints uneven 0;;30 sideways alone uneven 14;35 a switch, 14 on the straight path, 35 on the diverging What's left out takes the first number, the dip goes by the up and down one, and a path p3-p4 with no numbers of its own takes those of p1-p2. This replaces unevenup, unevenside, unevencross and jointdip. The fields in the editor stay, track panel, section Ride and sound. At the end they share, the paths of a switch keep to the lower value, and each comes to its own value along the way. Joined switches take the lowest of the same path, a path joined to another path the lower of the two, and a plain track comes to the value of the path it meets. See track_uneven_doc.md. --- application/editormodetrack.cpp | 4 +- editor/editorTrack.cpp | 17 ++- editor/editorTrack.hpp | 3 + lang/editor_pl.po | 9 ++ tests/unit/test_track_uneven.cpp | 37 +++++++ track_uneven_doc.md | 27 ++--- world/Segment.cpp | 6 +- world/Segment.h | 8 +- world/Track.cpp | 179 +++++++++++++++++++++---------- world/Track.h | 26 +++-- world/TrackUneven.h | 46 ++++++++ 11 files changed, 266 insertions(+), 96 deletions(-) diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 5b048396..717a3657 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1171,10 +1171,12 @@ void editor_mode::render_path_parameters(TTrack &Track) 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}}) + 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}, component{"Diverging path (mm)", &track->m_unevenbranch}, component{"Diverging, up and down (mm)", &track->m_unevenbranchup}, component{"Diverging, sideways (mm)", &track->m_unevenbranchside}}) { if (part.value == &track->m_unevencross && track->eType != tt_Normal) continue; + if ((part.value == &track->m_unevenbranch || part.value == &track->m_unevenbranchup || part.value == &track->m_unevenbranchside) && track->eType != tt_Switch) + 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"); diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index cbe7c26b..da426007 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -156,6 +156,9 @@ editor_track::state editor_track::capture(TTrack const &Track) result.unevenup = Track.m_unevenup; result.unevenside = Track.m_unevenside; result.unevencross = Track.m_unevencross; + result.unevenbranch = Track.m_unevenbranch; + result.unevenbranchup = Track.m_unevenbranchup; + result.unevenbranchside = Track.m_unevenbranchside; result.jointdip = Track.m_jointdip; result.sleepers = Track.m_sleeper_enabled; result.sleeper_frequency = Track.m_sleeper_frequency; @@ -259,6 +262,9 @@ void editor_track::apply(TTrack &Track, state const &State) Track.m_unevenup = State.unevenup; Track.m_unevenside = State.unevenside; Track.m_unevencross = State.unevencross; + Track.m_unevenbranch = State.unevenbranch; + Track.m_unevenbranchup = State.unevenbranchup; + Track.m_unevenbranchside = State.unevenbranchside; Track.m_jointdip = State.jointdip; Track.m_sleeper_enabled = State.sleepers; Track.m_sleeper_frequency = State.sleeper_frequency; @@ -2360,16 +2366,7 @@ 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 << ' '; + Text << Style.uneven_text(); // 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 b6137bd8..61858336 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -59,6 +59,9 @@ class editor_track float unevenup{-1.f}; float unevenside{-1.f}; float unevencross{-1.f}; + float unevenbranch{-1.f}; + float unevenbranchup{-1.f}; + float unevenbranchside{-1.f}; float jointdip{-1.f}; bool sleepers{false}; float sleeper_frequency{0.6f}; diff --git a/lang/editor_pl.po b/lang/editor_pl.po index a9c61b83..dbf7c124 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -876,6 +876,15 @@ msgstr "W poziomie (mm)" msgid "Cross level (mm)" msgstr "Przechyłka (mm)" +msgid "Diverging path (mm)" +msgstr "Tor na bok (mm)" + +msgid "Diverging, up and down (mm)" +msgstr "Na bok, w pionie (mm)" + +msgid "Diverging, sideways (mm)" +msgstr "Na bok, w poziomie (mm)" + msgid "Set apart from the unevenness, mm. Negative: the unevenness" msgstr "Osobno od nierówności, mm. Ujemna: wartość nierówności" diff --git a/tests/unit/test_track_uneven.cpp b/tests/unit/test_track_uneven.cpp index 46bd1e5f..22970fa6 100644 --- a/tests/unit/test_track_uneven.cpp +++ b/tests/unit/test_track_uneven.cpp @@ -140,6 +140,43 @@ TEST_CASE("uneven track: the field stays within its bound") CHECK(highest > 0.5); } +TEST_CASE("uneven track: all the values in one attribute") +{ + auto const one{track_uneven::read_fields<6>("18")}; + CHECK(one[0] == doctest::Approx(18.f)); + CHECK(one[1] < 0.f); + auto const both{track_uneven::read_fields<6>("14;35")}; + CHECK(both[0] == doctest::Approx(14.f)); + CHECK(both[1] == doctest::Approx(35.f)); + CHECK(both[2] < 0.f); + auto const gaps{track_uneven::read_fields<6>("18.5;;;25;6")}; + CHECK(gaps[0] == doctest::Approx(18.5f)); + CHECK(gaps[1] < 0.f); + CHECK(gaps[2] < 0.f); + CHECK(gaps[3] == doctest::Approx(25.f)); + CHECK(gaps[4] == doctest::Approx(6.f)); + auto const bounds{track_uneven::read_fields<6>("500;-3;x")}; + CHECK(bounds[0] == doctest::Approx(track_uneven::most)); + CHECK(bounds[1] == doctest::Approx(0.f)); + CHECK(bounds[2] < 0.f); + CHECK(track_uneven::write_fields<6>({14.f, 35.f, -1.f, -1.f, -1.f, -1.f}) == "14;35"); + CHECK(track_uneven::write_fields<6>({18.5f, -1.f, -1.f, 25.f, 6.f, -1.f}) == "18.5;;;25;6"); + CHECK(track_uneven::write_fields<6>({0.f, -1.f, 30.f, -1.f, -1.f, -1.f}) == "0;;30"); + CHECK(track_uneven::write_fields<6>({18.f, -1.f, -1.f, -1.f, -1.f, -1.f}) == "18"); +} + +TEST_CASE("uneven track: amounts take the lowest of each component") +{ + auto const least{track_uneven::least({14.0, 9.0, 0.0}, {7.0, 35.0, 3.0})}; + CHECK(least.up == doctest::Approx(7.0)); + CHECK(least.side == doctest::Approx(9.0)); + CHECK(least.cross == doctest::Approx(0.0)); + // the diverging path of a switch: from the common end at the lowest to its own value at the far end + double const length{30.0}; + CHECK(track_uneven::along(35.0, 7.0, 35.0, 0.0, length) == doctest::Approx(7.0)); + CHECK(track_uneven::along(35.0, 7.0, 35.0, length, length) == doctest::Approx(35.0)); +} + TEST_CASE("uneven track: the value along a track meets its neighbours exactly") { double const length{500.0}; diff --git a/track_uneven_doc.md b/track_uneven_doc.md index a9b888ae..cbbb70c0 100644 --- a/track_uneven_doc.md +++ b/track_uneven_doc.md @@ -1,6 +1,6 @@ # Nierówności toru -Opcjonalne atrybuty wpisu toru, przed `endtrack`: +Opcjonalny atrybut wpisu toru, przed `endtrack`: ``` node -1 0 tor1 track normal 100.0 1.435 0.15 25.0 20 0 flat vis @@ -10,21 +10,22 @@ rail_screw_used1 6 1435mm/tpbps-new2 0.2 0.5 1.1 0.0 0.0 0.0 0.0 0.2 100.0 0.0 0.0 -uneven 18 -unevencross 25 -jointdip 6 +uneven 18;;;25;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 | +Liczby w mm, 0-100, rozdzielone średnikiem, bez spacji: -Składowa niepodana przyjmuje wartość `uneven`. Na przykład sam bok to `unevenside 30`, a same zapadnięcia na stykach to `jointdip 6`. +| pozycja | tor `normal` | rozjazd `switch` | +|---------|--------------------------------------------------|-----------------------------| +| 1 | największa odchyłka toku na cięciwie 10 m | to samo, droga p1-p2 | +| 2 | w pionie | droga p3-p4 | +| 3 | w poziomie | droga p1-p2, w pionie | +| 4 | przechyłka | droga p1-p2, w poziomie | +| 5 | zapadnięcie na styku, domyślnie 0,4 pionu | droga p3-p4, w pionie | +| 6 | | droga p3-p4, w poziomie | + +Pozycję pominiętą zastępuje pierwsza liczba, a zapadnięcie liczy się z pionu. Końcowe można opuścić. Sam bok to `uneven 0;;30`, a same zapadnięcia na stykach to `uneven 0;;;;6`. Rozjazd bez składowych na drodze p3-p4 przyjmuje wartości z drogi p1-p2. Rozjazd na 100 km/h na wprost i 40 km/h na bok to na przykład `uneven 14;35`. Wszystkie są też w edytorze, w panelu toru, w sekcji „Ride and sound”. Na drogach, rzekach i obrotnicach są ignorowane. @@ -56,7 +57,7 @@ Nierówność to pole przesunięć zależne od położenia na mapie, złożone z 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. +- **Rozjazd:** każda droga ma swoją wartość, a na wspólnym końcu obu obowiązuje mniejsza z nich. Rozjazdy złączone bezpośrednio przyjmują najmniejszą z tej samej drogi, a na styku dwóch dróg mniejszą z obu. Dołączony tor dochodzi do wartości rozjazdu na końcu, do którego dochodzi, 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. diff --git a/world/Segment.cpp b/world/Segment.cpp index 16f878c9..dd236c61 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -381,7 +381,7 @@ void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm:: // 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 ) }; + auto const amount { pOwner->uneven_at( fDistance, m_path ) }; rotation.x += static_cast( track_uneven::stray_per_mm * ( amount.up * track_uneven::roll( position, across, gauge ) + amount.cross * track_uneven::level( position, across ) / gauge ) ); @@ -389,7 +389,7 @@ void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm:: } glm::dvec3 TSegment::stray( glm::dvec3 const &Point, double const Distance ) const { - auto const amount { pOwner->uneven_at( Distance ) }; + auto const amount { pOwner->uneven_at( Distance, m_path ) }; 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; @@ -416,7 +416,7 @@ void TSegment::settle_vertex( gfx::basic_vertex &Vertex, glm::dvec3 const &Origi 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 ) }; auto const share { pOwner->uneven_bed_share( Position.y - Vertex.position.y ) }; - Vertex.position.y -= static_cast( share * track_uneven::stray_per_mm * pOwner->uneven_at( Distance ).cross * track_uneven::level( Origin + glm::dvec3 { Position }, glm::dvec3 { Parallel } ) * across / gauge ); + Vertex.position.y -= static_cast( share * track_uneven::stray_per_mm * pOwner->uneven_at( Distance, m_path ).cross * track_uneven::level( Origin + glm::dvec3 { Position }, glm::dvec3 { Parallel } ) * across / gauge ); Vertex.position += glm::vec3 { share * stray( Origin + glm::dvec3 { Vertex.position }, Distance ) }; } diff --git a/world/Segment.h b/world/Segment.h index 7af8a894..77e61f90 100644 --- a/world/Segment.h +++ b/world/Segment.h @@ -50,9 +50,10 @@ class TSegment float fRoll1 { 0.f }; float fRoll2 { 0.f }; // przechyłka na końcach float m_designrolls[ 2 ] { 0.f, 0.f }; // as entered; the two above can differ at a joint + bool m_uneven { false }; // see TrackUneven.h + int m_path { 0 }; // which path of a switch // where the path meets the next one: the middle of their ends and their common vector across std::optional> m_joints[ 2 ]; - 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 @@ -120,8 +121,9 @@ public: 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; } + set_uneven( bool const Uneven, int const Path = 0 ) { + m_uneven = Uneven; + m_path = Path; } // a point of the path and the unit vector across it in the plan void plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3 &Across ) const; diff --git a/world/Track.cpp b/world/Track.cpp index 6ac1ce7c..8359f78a 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -49,6 +49,9 @@ TTrack::profiles_array TTrack::m_profiles; TTrack::profiles_map TTrack::m_profilesmap; int TTrack::s_layoutstamp { 0 }; +// how near an end of a path counts as being at a point, m +static double const switch_end_reach { 0.3 }; + TSwitchExtension::TSwitchExtension(TTrack *owner, int const what) { // na początku wszystko puste pNexts[0] = nullptr; // wskaźniki do kolejnych odcinków ruchu @@ -639,24 +642,10 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) } } else if( str == "uneven" ) { - // uneven : see TrackUneven.h - auto const uneven { parser->getToken() }; + // uneven [;...]: see read_uneven and TrackUneven.h. The semicolons stay in the token + auto const values { parser->getToken( false, "\n\r\t " ) }; 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 ); + read_uneven( values ); } } else if( str == "friction" ) { @@ -717,18 +706,56 @@ 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 { +void TTrack::read_uneven( std::string const &Text ) { - return { + auto const fields { track_uneven::read_fields<6>( Text ) }; + m_uneven = std::max( fields[ 0 ], 0.f ); + if( eType == tt_Switch ) { + m_unevenbranch = fields[ 1 ]; + m_unevenup = fields[ 2 ]; + m_unevenside = fields[ 3 ]; + m_unevenbranchup = fields[ 4 ]; + m_unevenbranchside = fields[ 5 ]; + } + else { + m_unevenup = fields[ 1 ]; + m_unevenside = fields[ 2 ]; + m_unevencross = fields[ 3 ]; + m_jointdip = fields[ 4 ]; + } +} + +std::string TTrack::uneven_text() const { + + auto const fields { eType == tt_Switch ? + std::array { m_uneven, m_unevenbranch, m_unevenup, m_unevenside, m_unevenbranchup, m_unevenbranchside } : + std::array { m_uneven, m_unevenup, m_unevenside, m_unevencross, m_jointdip, -1.f } }; + if( m_uneven <= 0.f && std::all_of( fields.begin() + 1, fields.end(), []( float const Field ) { return Field < 0.f; } ) ) { + return {}; + } + return "uneven " + track_uneven::write_fields( fields ) + ' '; +} + +track_uneven::amounts TTrack::uneven_declared( int const Path ) const { + + track_uneven::amounts result { 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 }; + if( Path == 1 ) { + auto const branch = [ this ]( float const Part, double const Straight ) -> double { + return Part >= 0.f ? Part : m_unevenbranch >= 0.f ? m_unevenbranch : Straight; }; + result.up = branch( m_unevenbranchup, result.up ); + result.side = branch( m_unevenbranchside, result.side ); + } + return result; } 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; + auto const any = []( track_uneven::amounts const &Amount ) { + return Amount.up > 0.0 || Amount.side > 0.0 || Amount.cross > 0.0; }; + return any( uneven_declared( 0 ) ) || any( uneven_declared( 1 ) ) || joint_depth() > 0.0; } double TTrack::uneven_bed_share( double const Depth ) const { @@ -736,61 +763,109 @@ double TTrack::uneven_bed_share( double const Depth ) const { return track_uneven::bed_share( Depth, std::abs( track_rail_profile( m_profile1.second ).front().position.y ), fTexHeight1 ); } -track_uneven::amounts TTrack::uneven_at( double const Distance ) const { +track_uneven::amounts TTrack::uneven_at( double const Distance, int const Path ) const { + auto const along = [ Distance ]( track_uneven::amounts const &Own, track_uneven::amounts const &Start, track_uneven::amounts const &End, double const Length ) -> track_uneven::amounts { + 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 ) }; }; if( eType == tt_Switch ) { - return switch_uneven(); + auto const &stray { switch_uneven() }; + return along( stray.paths[ Path ], stray.ends[ Path ][ 0 ], stray.ends[ Path ][ 1 ], SwitchExtension->Segments[ Path ]->GetLength() ); } auto const own { uneven_declared() }; - auto const atend = [&]( TTrack const *Other ) -> track_uneven::amounts { + auto const atend = [&]( TTrack const *Other, glm::dvec3 const &Point ) -> track_uneven::amounts { if( Other == nullptr ) { return {}; } if( Other->eType == tt_Switch ) { - return Other->switch_uneven(); + return Other->switch_uneven_at( Point ); } - 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() ) }; + return track_uneven::least( own, Other->uneven_declared() ); }; + return along( own, atend( trPrev, Segment->FastGetPoint_0() ), atend( trNext, Segment->FastGetPoint_1() ), Length() ); } -track_uneven::amounts TTrack::switch_uneven() const { +TTrack::switch_stray const &TTrack::switch_uneven() const { if( m_switchstray.stamp == s_layoutstamp ) { - return m_switchstray.value; + return m_switchstray; } std::vector group { this }; - auto least { uneven_declared() }; + std::array least { uneven_declared( 0 ), uneven_declared( 1 ) }; 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 ); + least[ 0 ] = track_uneven::least( least[ 0 ], other->uneven_declared( 0 ) ); + least[ 1 ] = track_uneven::least( least[ 1 ], other->uneven_declared( 1 ) ); 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; } + auto const theirs { other->uneven_declared() }; + for( auto &path : least ) { + if( theirs.up <= 0.0 ) { path.up = 0.0; } + if( theirs.side <= 0.0 ) { path.side = 0.0; } + } } } } for( auto const *track : group ) { - track->m_switchstray = { least, s_layoutstamp }; + track->m_switchstray.paths = least; + track->m_switchstray.stamp = s_layoutstamp; } - return least; + // the lowest of the paths of a switch ending at a point + auto const paths_at = []( TTrack const &Track, glm::dvec3 const &Point ) { + auto result { track_uneven::least( Track.m_switchstray.paths[ 0 ], Track.m_switchstray.paths[ 1 ] ) }; + auto found { false }; + track_uneven::amounts ending { track_uneven::most, track_uneven::most, track_uneven::most }; + for( int path = 0; path < 2; ++path ) { + auto const &segment { *Track.SwitchExtension->Segments[ path ] }; + if( glm::length( segment.FastGetPoint_0() - Point ) < switch_end_reach || glm::length( segment.FastGetPoint_1() - Point ) < switch_end_reach ) { + ending = track_uneven::least( ending, Track.m_switchstray.paths[ path ] ); + found = true; + } + } + return found ? ending : result; }; + for( auto const *track : group ) { + auto const &extension { *track->SwitchExtension }; + for( int path = 0; path < 2; ++path ) { + auto const &segment { *extension.Segments[ path ] }; + for( int end = 0; end < 2; ++end ) { + auto const point { end == 0 ? segment.FastGetPoint_0() : segment.FastGetPoint_1() }; + auto value { paths_at( *track, point ) }; + auto const *other { end == 0 ? extension.pPrevs[ path ] : extension.pNexts[ path ] }; + if( other != nullptr && other->eType == tt_Switch && other->SwitchExtension ) { + value = track_uneven::least( value, paths_at( *other, point ) ); + } + track->m_switchstray.ends[ path ][ end ] = value; + } + } + } + return m_switchstray; +} + +track_uneven::amounts TTrack::switch_uneven_at( glm::dvec3 const &Point ) const { + + auto const &stray { switch_uneven() }; + auto found { false }; + track_uneven::amounts result { track_uneven::most, track_uneven::most, track_uneven::most }; + for( int path = 0; path < 2; ++path ) { + auto const &segment { *SwitchExtension->Segments[ path ] }; + for( int end = 0; end < 2; ++end ) { + if( glm::length( ( end == 0 ? segment.FastGetPoint_0() : segment.FastGetPoint_1() ) - Point ) < switch_end_reach ) { + result = track_uneven::least( result, stray.ends[ path ][ end ] ); + found = true; + } + } + } + return found ? result : track_uneven::least( stray.paths[ 0 ], stray.paths[ 1 ] ); } double TTrack::joint_depth() const { @@ -896,7 +971,7 @@ void TTrack::init_segments( bool const Initial ) { ++s_layoutstamp; if( eType == tt_Switch && SwitchExtension ) { SwitchExtension->Segments[ 0 ]->set_uneven( is_uneven() ); - SwitchExtension->Segments[ 1 ]->set_uneven( is_uneven() ); + SwitchExtension->Segments[ 1 ]->set_uneven( is_uneven(), 1 ); } else if( Segment ) { Segment->set_uneven( is_uneven() ); @@ -2707,21 +2782,7 @@ 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 << ' '; - } + Output << uneven_text(); if( false == m_friction.first.empty() ) { Output << "friction " << m_friction.first << ' '; } diff --git a/world/Track.h b/world/Track.h index f7b581b8..1d366ebf 100644 --- a/world/Track.h +++ b/world/Track.h @@ -244,6 +244,10 @@ public: float m_unevenup { -1.f }; float m_unevenside { -1.f }; float m_unevencross { -1.f }; + // the diverging path of a switch, mm; negative: as the straight one + float m_unevenbranch { -1.f }; + float m_unevenbranchup { -1.f }; + float m_unevenbranchside { -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) @@ -346,11 +350,15 @@ public: void VelocitySet(float v); double VelocityGet(); float Friction() const; - // the unevenness the track declares - track_uneven::amounts uneven_declared() const; + // the attribute of the entry. A plain track: the value;up and down;sideways;cross level;dip at the joints. + // A switch: path p1-p2;path p3-p4;up and down of the first;sideways of the first;up and down of the second;sideways of the second + void read_uneven( std::string const &Text ); + std::string uneven_text() const; + // the unevenness the track declares, on a path of a switch + track_uneven::amounts uneven_declared( int const Path = 0 ) 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; + // the unevenness at a distance along a path, meeting the tracks joined at its ends + track_uneven::amounts uneven_at( double const Distance, int const Path = 0 ) const; // how much of the displacement a point of the profile at the depth takes double uneven_bed_share( double const Depth ) const; // closes the gaps and steps of cant between the ends of joined paths @@ -398,13 +406,17 @@ private: }; 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; + // the value along each path of a switch: the lowest of that path in the switches joined, 0 next to a track with none. + // And at the ends of the paths, by what meets there: the other path at the common end, the path of a switch joined struct switch_stray { - track_uneven::amounts value; + std::array paths; + std::array, 2> ends; int stamp { -1 }; }; mutable switch_stray m_switchstray; + switch_stray const &switch_uneven() const; + // the value of a switch where a track joined to it meets it + track_uneven::amounts switch_uneven_at( glm::dvec3 const &Point ) const; 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 index 182f3799..f4a50eb7 100644 --- a/world/TrackUneven.h +++ b/world/TrackUneven.h @@ -11,7 +11,10 @@ http://mozilla.org/MPL/2.0/. #include #include +#include #include +#include +#include #include #include @@ -34,6 +37,11 @@ struct amounts { double cross { 0.0 }; }; +inline amounts least( amounts const &Left, amounts const &Right ) { + + return { std::min( Left.up, Right.up ), std::min( Left.side, Right.side ), std::min( Left.cross, Right.cross ) }; +} + struct wave { double x; double z; @@ -41,6 +49,44 @@ struct wave { double share; }; +// the numbers of an entry like "uneven 14;35", negative where left out +template +std::array read_fields( std::string const &Text ) { + + std::array result; + result.fill( -1.f ); + std::size_t start { 0 }; + for( std::size_t index = 0; index < Count && start < Text.size(); ++index ) { + auto const end { std::min( Text.find( ';', start ), Text.size() ) }; + auto value { 0.f }; + if( end > start && std::from_chars( Text.data() + start, Text.data() + end, value ).ec == std::errc {} ) { + result[ index ] = std::clamp( value, 0.f, most ); + } + start = end + 1; + } + return result; +} + +// and back, with the ones left out at the end dropped +template +std::string write_fields( std::array const &Fields ) { + + auto count { Count }; + while( count > 1 && Fields[ count - 1 ] < 0.f ) { + --count; + } + std::ostringstream text; + for( std::size_t index = 0; index < count; ++index ) { + if( index > 0 ) { + text << ';'; + } + if( Fields[ index ] >= 0.f ) { + text << Fields[ index ]; + } + } + return text.str(); +} + template using wave_set = std::array;