From 88609a1657e7c41286e24b37733789a28d9a21d6 Mon Sep 17 00:00:00 2001 From: maj00r Date: Sat, 31 Oct 2026 18:31:28 +0100 Subject: [PATCH] fix(track): the ends of joined paths meet, in place and in cant Ends less than 20 cm apart meet in the middle with a common vector across, which closes the cracks from coordinates that don't match in the data, on tracks with no unevenness as well. At such a joint a switch takes the cant of the plain track, and alike ones meet halfway: the slips on Baltyk had steps of 0.2 degrees. The physics goes by the same cant. --- track_uneven_doc.md | 2 ++ world/Segment.cpp | 24 ++++++++++++++++ world/Segment.h | 15 ++++++++++ world/Track.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++++ world/Track.h | 2 ++ 5 files changed, 112 insertions(+) diff --git a/track_uneven_doc.md b/track_uneven_doc.md index 4b71140b..a9b888ae 100644 --- a/track_uneven_doc.md +++ b/track_uneven_doc.md @@ -60,6 +60,8 @@ Każda składowa łączy się osobno: - **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. +Końce złączonych dróg spotykają się w środku między sobą, z jednym kierunkiem poprzecznym, jeśli są bliżej niż 20 cm, co zamyka pęknięcia z niezgodnych współrzędnych w danych, także na torach bez nierówności. Przechyłkę na takim styku rozjazd bierze od zwykłego toru, a dwa tory albo dwa rozjazdy spotykają się w połowie. To dotyczy też fizyki. + Za nierównością idą szyny, wierzch podsypki, `sleepermodel`, osie, przechył pudła i bujanie kabiny. Skarpa podsypki przejmuje różnicę, a jej spód zostaje w projekcie, na terenie. 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. diff --git a/world/Segment.cpp b/world/Segment.cpp index 3668ed13..16f878c9 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -69,6 +69,10 @@ bool TSegment::Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 N // poprawienie przechyłki fRoll1 = static_cast(glm::radians(fNewRoll1)); // Ra: przeliczone jest bardziej przydatne do obliczeń fRoll2 = static_cast(glm::radians(fNewRoll2)); + m_designrolls[ 0 ] = fRoll1; + m_designrolls[ 1 ] = fRoll2; + m_joints[ 0 ].reset(); + m_joints[ 1 ].reset(); bCurve = bIsCurve; if (Global.bRollFix) { // Ra: poprawianie przechyłki @@ -441,6 +445,18 @@ void TSegment::design_position(double const fDistance, glm::dvec3 &position, glm } }; +void TSegment::plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3 &Across ) const { + auto const t { FastGetTFromS( std::clamp( Distance, 0.0, fLength ) ) }; + Point = FastGetPoint( t ); + auto const dir { FastGetDirection( t, 0.1 / fLength ) }; + Across = glm::dvec3{ -dir.z, 0.0, dir.x }; + if( glm::length2( Across ) == 0.0 ) { + auto const chord { FastGetPoint_1() - FastGetPoint_0() }; + Across = glm::dvec3{ -chord.z, 0.0, chord.x }; + } + Across = glm::normalize( Across ); +} + glm::dvec3 TSegment::FastGetPoint(double const t) const { // return (bCurve?std::lerp(t,Point1,CPointOut,CPointIn,Point2):((1.0-t)*Point1+(t)*Point2)); @@ -492,6 +508,10 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin, parallel1 = glm::vec3{ glm::dvec3{ FastGetPoint_1() - FastGetPoint_0() } }; } parallel1 = glm::normalize( parallel1 ); + if( iSkip == 0 && m_joints[ 0 ] ) { + pos1 = glm::vec3{ m_joints[ 0 ]->first - Origin }; + parallel1 = glm::vec3{ m_joints[ 0 ]->second }; + } if( iEnd == 0 ) iEnd = iSegCount; fEnd = static_cast(fLength * double( iEnd ) / double( iSegCount )); @@ -537,6 +557,10 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin, parallel2 = glm::vec3{ glm::dvec3{ FastGetPoint_1() - FastGetPoint_0() } }; } parallel2 = glm::normalize( parallel2 ); + if( i == iSegCount && m_joints[ 1 ] ) { + pos2 = glm::vec3{ m_joints[ 1 ]->first - Origin }; + parallel2 = glm::vec3{ m_joints[ 1 ]->second }; + } // TODO: refactor the loop, there's no need to calculate starting points for each segment when we can copy the end points of the previous one if( Transition ) { diff --git a/world/Segment.h b/world/Segment.h index 492cab94..7af8a894 100644 --- a/world/Segment.h +++ b/world/Segment.h @@ -9,6 +9,8 @@ http://mozilla.org/MPL/2.0/. #pragma once +#include + #include "utilities/Classes.h" #include "rendering/geometrybank.h" #include @@ -47,6 +49,9 @@ class TSegment glm::dvec3 Point2; 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 + // 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 @@ -117,6 +122,16 @@ public: void set_uneven( bool const Uneven ) { m_uneven = Uneven; } + // 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; + float + design_roll( int const End ) const { + return m_designrolls[ End ]; } + void + set_joint( int const End, std::optional> const &Joint, float const Roll ) { + m_joints[ End ] = Joint; + ( End == 0 ? fRoll1 : fRoll2 ) = Roll; } // second derivative of the displacement along the path; times speed squared: acceleration of an axle glm::dvec3 uneven_bend( double const Distance ) const; diff --git a/world/Track.cpp b/world/Track.cpp index b5589eed..6ac1ce7c 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -1561,8 +1561,72 @@ void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) { create_geometry( Bank ); } +void TTrack::share_joints() { + + int const paths { eType == tt_Switch ? 2 : 1 }; + auto const segment_of = []( TTrack const &Track, int const Path ) -> TSegment * { + return ( Track.eType == tt_Switch ? Track.SwitchExtension->Segments[ Path ].get() : Track.Segment.get() ); }; + for( int path = 0; path < paths; ++path ) { + auto *segment { segment_of( *this, path ) }; + if( segment == nullptr ) { + continue; + } + for( int end = 0; end < 2; ++end ) { + std::optional> joint; + auto roll { segment->design_roll( end ) }; + auto const *neighbour { eType == tt_Switch ? + ( end ? SwitchExtension->pNexts[ path ] : SwitchExtension->pPrevs[ path ] ) : + ( end ? trNext : trPrev ) }; + if( neighbour != nullptr && neighbour != this && ( neighbour->iCategoryFlag & 15 ) == ( iCategoryFlag & 15 ) ) { + glm::dvec3 point; + glm::dvec3 across; + segment->plan_frame( end ? segment->GetLength() : 0.0, point, across ); + auto closest { 0.2 }; + for( int theirpath = 0; theirpath < ( neighbour->eType == tt_Switch ? 2 : 1 ); ++theirpath ) { + auto const *theirs { segment_of( *neighbour, theirpath ) }; + if( theirs == nullptr ) { + continue; + } + for( int theirend = 0; theirend < 2; ++theirend ) { + glm::dvec3 theirpoint; + glm::dvec3 theiracross; + theirs->plan_frame( theirend ? theirs->GetLength() : 0.0, theirpoint, theiracross ); + if( theirend == end ) { + theiracross = -theiracross; + } + auto const gap { glm::length( theirpoint - point ) }; + if( gap >= closest || glm::dot( across, theiracross ) < 0.9 ) { + continue; + } + closest = gap; + joint.emplace( 0.5 * ( point + theirpoint ), glm::normalize( across + theiracross ) ); + if( iCategoryFlag & 1 ) { + // the cant of a plain track is what was designed, a switch takes it; alike ones meet halfway + auto const own { segment->design_roll( end ) }; + auto const their { theirend == end ? -theirs->design_roll( theirend ) : theirs->design_roll( theirend ) }; + roll = ( + ( eType == tt_Switch ) == ( neighbour->eType == tt_Switch ) ? 0.5f * ( own + their ) : + eType == tt_Switch ? their : + own ); + } + } + } + } + segment->set_joint( end, joint, roll ); + } + } + if( eType != tt_Switch && Segment && ( iCategoryFlag & 1 ) ) { + float roll1, roll2; + Segment->GetRolls( roll1, roll2 ); + if( roll1 != 0.f || roll2 != 0.f ) { + iTrapezoid |= 1; + } + } +} + void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { gfx::userdata_array empty_userdata; + share_joints(); // bake per-instance sleeper transforms now that the owning cell has assigned m_origin. // safe to call here even if the track has no sleepermodel (early-outs internally). if( m_sleeper_enabled && m_sleeper_local_transforms.empty() ) { @@ -3845,6 +3909,11 @@ path_table::InitTracks() { isolated = isolated->Next(); } + // the physics goes by the cant at the joints too, and comes before any geometry is made + for( auto *track : m_items ) { + track->share_joints(); + } + TTrack::fetch_default_profiles(); } diff --git a/world/Track.h b/world/Track.h index 92516ee6..f7b581b8 100644 --- a/world/Track.h +++ b/world/Track.h @@ -353,6 +353,8 @@ public: track_uneven::amounts uneven_at( double const Distance ) 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 + void share_joints(); 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