From 2b8d7d7cf3ab2b2df8fbe3e9c238d6cd96c83869 Mon Sep 17 00:00:00 2001 From: maj00r Date: Sat, 31 Oct 2026 19:05:56 +0100 Subject: [PATCH] refactor(track): what goes with unevenness in one place, readable The rules stay in TrackUneven.h with no dependence on the engine, as they can be tested. Their use on the tracks moves out of Track.cpp to TrackUneven.cpp, split into steps: the group of switches, the values of its paths, the meeting of two ends. One set of helpers for the paths of a track takes the place of the same conditions written out in several places, and the value at the ends of a switch is worked out in one place. The clatter of an axle leaves the long Update of a vehicle. The fields in the editor say themselves when they show. No change in behaviour. --- CMakeLists.txt | 1 + application/editormodetrack.cpp | 13 +- tests/unit/test_track_uneven.cpp | 13 ++ vehicle/DynObj.cpp | 42 ++-- vehicle/DynObj.h | 2 + world/Segment.h | 3 + world/Track.cpp | 272 ------------------------- world/Track.h | 9 + world/TrackUneven.cpp | 336 +++++++++++++++++++++++++++++++ world/TrackUneven.h | 9 + 10 files changed, 405 insertions(+), 295 deletions(-) create mode 100644 world/TrackUneven.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 477f584e..eba44c4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,7 @@ set(SOURCES "model/Texture.cpp" "utilities/Timer.cpp" "world/Track.cpp" +"world/TrackUneven.cpp" "world/Road.cpp" "world/Sweep.cpp" "world/RoadPoint.cpp" diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 4e313bfd..49059c21 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1170,12 +1170,17 @@ void editor_mode::render_path_parameters(TTrack &Track) { char const *label; float *value; + bool shown; }; - for (auto const &part : {component{"Up and down (mm)", &track->m_uneven.up}, component{"Sideways (mm)", &track->m_uneven.side}, component{"Cross level (mm)", &track->m_uneven.cross}, component{"Diverging path (mm)", &track->m_uneven.branch}, component{"Diverging, up and down (mm)", &track->m_uneven.branchup}, component{"Diverging, sideways (mm)", &track->m_uneven.branchside}}) + auto const plain{track->eType == tt_Normal}; + for (auto const &part : {component{"Up and down (mm)", &track->m_uneven.up, true}, + component{"Sideways (mm)", &track->m_uneven.side, true}, + component{"Cross level (mm)", &track->m_uneven.cross, plain}, + component{"Diverging path (mm)", &track->m_uneven.branch, !plain}, + component{"Diverging, up and down (mm)", &track->m_uneven.branchup, !plain}, + component{"Diverging, sideways (mm)", &track->m_uneven.branchside, !plain}}) { - if (part.value == &track->m_uneven.cross && track->eType != tt_Normal) - continue; - if ((part.value == &track->m_uneven.branch || part.value == &track->m_uneven.branchup || part.value == &track->m_uneven.branchside) && track->eType != tt_Switch) + if (false == part.shown) 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); diff --git a/tests/unit/test_track_uneven.cpp b/tests/unit/test_track_uneven.cpp index fd95cec9..65b7da6e 100644 --- a/tests/unit/test_track_uneven.cpp +++ b/tests/unit/test_track_uneven.cpp @@ -194,6 +194,19 @@ TEST_CASE("uneven track: the attribute goes by the entry it's written into") CHECK(track_uneven::write_attribute(track_uneven::read_attribute("0;;;;6", false), false) == "uneven 0;;;;6 "); } +TEST_CASE("uneven track: each component comes to its neighbours along a track") +{ + double const length{500.0}; + auto const atstart{track_uneven::along(track_uneven::amounts{18.0, 16.0, 14.0}, {5.0, 4.0, 3.0}, {7.0, 8.0, 9.0}, 0.0, length)}; + CHECK(atstart.up == doctest::Approx(5.0)); + CHECK(atstart.side == doctest::Approx(4.0)); + CHECK(atstart.cross == doctest::Approx(3.0)); + auto const inside{track_uneven::along(track_uneven::amounts{18.0, 16.0, 14.0}, {5.0, 4.0, 3.0}, {7.0, 8.0, 9.0}, 0.5 * length, length)}; + CHECK(inside.up == doctest::Approx(18.0)); + CHECK(inside.side == doctest::Approx(16.0)); + CHECK(inside.cross == doctest::Approx(14.0)); +} + TEST_CASE("uneven track: axles don't stray along the track") { glm::dvec3 const across{0.6, 0.0, -0.8}; diff --git a/vehicle/DynObj.cpp b/vehicle/DynObj.cpp index 3f633ccc..ac87f395 100644 --- a/vehicle/DynObj.cpp +++ b/vehicle/DynObj.cpp @@ -3661,25 +3661,7 @@ bool TDynamicObject::Update(double dt, double dt1) 1 : ctOwner->Vehicle()->DirectionGet() == DirectionGet() ? 1 : -1 ) }; for( auto &axle : m_axlesounds ) { - // on a track with dipping joints the clatter comes from them, elsewhere the axle counts a rail off itself - // by the outer axle on its side of the vehicle: the two can be on different tracks - auto const &outer { axle.offset >= 0.0 ? Axle0 : Axle1 }; - auto const railphase { outer.RailPhase( axle.offset - std::copysign( 0.5 * fAxleDist, axle.offset ) ) }; - auto crossed { false }; - 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 * outer.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( axle_passed_joint( axle, dDOMoveLen, directioninconsist ) ) { 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 auto const frequency = true == axle.clatter.is_combined() ? MoverParameters->Vel * 0.01 : 1.0; @@ -8089,6 +8071,28 @@ TDynamicObject::update_shake( double const Timedelta ) { } } +bool +TDynamicObject::axle_passed_joint( axle_sounds &Axle, double const Move, int const Direction ) { + + // by the outer axle on its side of the vehicle: the two can be on different tracks + auto const &outer { Axle.offset >= 0.0 ? Axle0 : Axle1 }; + auto const railphase { outer.RailPhase( Axle.offset - std::copysign( 0.5 * fAxleDist, Axle.offset ) ) }; + if( railphase >= 0.0 ) { + // the rails under the axle, which aren't always those under the track the vehicle is on + auto const passed { Axle.atjoints && std::abs( railphase - Axle.distance ) > 0.5 * outer.GetTrack()->fSoundDistance }; + Axle.distance = railphase; + Axle.atjoints = true; + return passed; + } + Axle.atjoints = false; + Axle.distance += Move * Direction; + if( Axle.distance >= 0 && Axle.distance <= dRailLength ) { + return false; + } + Axle.distance = clamp_circular( Axle.distance, dRailLength ); + return true; +} + std::pair TDynamicObject::shake_angles() const { diff --git a/vehicle/DynObj.h b/vehicle/DynObj.h index 1bc50b1d..b903f087 100644 --- a/vehicle/DynObj.h +++ b/vehicle/DynObj.h @@ -404,6 +404,8 @@ private: sound_source clatter; // clatter emitter bool atjoints { false }; // distance comes from TTrack::rail_phase }; + // whether the axle has just passed a rail joint. On a track with dipping joints it goes by them, elsewhere the axle counts a rail off itself + bool axle_passed_joint( axle_sounds &Axle, double const Move, int const Direction ); struct powertrain_sounds { sound_source inverter { sound_placement::engine }; diff --git a/world/Segment.h b/world/Segment.h index 67e4ed05..75e07015 100644 --- a/world/Segment.h +++ b/world/Segment.h @@ -127,6 +127,9 @@ public: set_uneven( bool const Uneven, int const Path = 0 ) { m_uneven = Uneven; m_path = Path; } + glm::dvec3 + end_point( int const End ) const { + return End == 0 ? Point1 : Point2; } // 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 bdb1cfe9..49138b4e 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -49,9 +49,6 @@ 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 @@ -706,214 +703,6 @@ 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( int const Path ) const { - - return track_uneven::declared( m_uneven, eType == tt_Normal, Path ); -} - -bool TTrack::is_uneven() const { - - 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 { - - 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, 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 ) { - 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, glm::dvec3 const &Point ) -> track_uneven::amounts { - if( Other == nullptr ) { - return {}; - } - if( Other->eType == tt_Switch ) { - return Other->switch_uneven_at( Point ); - } - return track_uneven::least( own, Other->uneven_declared() ); }; - return along( own, atend( trPrev, Segment->FastGetPoint_0() ), atend( trNext, Segment->FastGetPoint_1() ), Length() ); -} - -TTrack::switch_stray const &TTrack::switch_uneven() const { - - if( m_switchstray.stamp == s_layoutstamp ) { - return m_switchstray; - } - std::vector group { this }; - 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; - } - if( other->eType == tt_Switch && other->SwitchExtension ) { - 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 - 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.paths = least; - track->m_switchstray.stamp = s_layoutstamp; - } - // 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 { - - if( eType != tt_Normal ) { - return 0.0; - } - return ( m_uneven.dip >= 0.f ? m_uneven.dip : 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; - } - // tracks joined one way only can leave this one out of the run: it stands on its own then, not to walk all of it again on every call - if( m_railrun.stamp != s_layoutstamp ) { - m_railrun = { 0.0, 1.0, s_layoutstamp }; - } -} - void TTrack::update_location() { location( ( @@ -1600,67 +1389,6 @@ 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 ) { - roll = track_uneven::joint_roll( - segment->design_roll( end ), - theirend == end ? -theirs->design_roll( theirend ) : theirs->design_roll( theirend ), - eType == tt_Switch, - neighbour->eType == tt_Switch ); - } - } - } - } - 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(); diff --git a/world/Track.h b/world/Track.h index 7d07f434..f5bb8c1b 100644 --- a/world/Track.h +++ b/world/Track.h @@ -341,6 +341,10 @@ public: void VelocitySet(float v); double VelocityGet(); float Friction() const; + // a switch has two paths, any other track the one + int path_count() const; + TSegment *path_segment( int const Path ) const; + TTrack *path_neighbour( int const Path, int const End ) 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; @@ -402,6 +406,11 @@ private: }; mutable switch_stray m_switchstray; switch_stray const &switch_uneven() const; + void settle_switch_stray() const; + // the lowest value of the paths ending at the point + track_uneven::amounts switch_paths_at( glm::dvec3 const &Point ) const; + // what a plain track comes to at its end, where it meets the other one + track_uneven::amounts uneven_met( TTrack const *Other, glm::dvec3 const &Point ) 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 diff --git a/world/TrackUneven.cpp b/world/TrackUneven.cpp new file mode 100644 index 00000000..152449ad --- /dev/null +++ b/world/TrackUneven.cpp @@ -0,0 +1,336 @@ +/* +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/. +*/ + +#include "stdafx.h" +#include "world/Track.h" + +// The part of TTrack that goes with the unevenness of a track and with what it meets at its ends: joined tracks and switches. +// The rules themselves are in TrackUneven.h, here they get applied to the tracks. + +namespace { + +double const switch_end_reach { 0.3 }; // how near an end of a path counts as being at a point, m +double const joint_reach { 0.2 }; // ends of joined paths further apart than this stay as they are, m +double const joint_alike { 0.9 }; // ends of joined paths less alike in direction than this stay as they are, cosine + +bool is_switch( TTrack const *Track ) { + + return Track != nullptr && Track->eType == tt_Switch && Track->SwitchExtension; +} + +std::array joined_to_switch( TTrack const &Switch ) { + + auto const &extension { *Switch.SwitchExtension }; + return { extension.pPrevs[ 0 ], extension.pPrevs[ 1 ], extension.pNexts[ 0 ], extension.pNexts[ 1 ] }; +} + +// the switch and the switches joined directly to it, and to those +std::vector switch_group( TTrack const &Switch ) { + + std::vector group { &Switch }; + for( std::size_t index = 0; index < group.size(); ++index ) { + for( auto const *other : joined_to_switch( *group[ index ] ) ) { + if( is_switch( other ) && std::find( group.begin(), group.end(), other ) == group.end() ) { + group.push_back( other ); + } + } + } + return group; +} + +// the value along each path: the lowest of that path in the group, 0 in a component that a plain track joined has none of +std::array group_path_values( std::vector const &Group ) { + + std::array values { Group.front()->uneven_declared( 0 ), Group.front()->uneven_declared( 1 ) }; + for( auto const *track : Group ) { + values[ 0 ] = track_uneven::least( values[ 0 ], track->uneven_declared( 0 ) ); + values[ 1 ] = track_uneven::least( values[ 1 ], track->uneven_declared( 1 ) ); + for( auto const *other : joined_to_switch( *track ) ) { + if( other == nullptr || is_switch( other ) ) { + continue; + } + // any other comes to the value of the switch + auto const theirs { other->uneven_declared() }; + for( auto &path : values ) { + if( theirs.up <= 0.0 ) { path.up = 0.0; } + if( theirs.side <= 0.0 ) { path.side = 0.0; } + } + } + } + return values; +} + +// the lowest Value( path, end ) of the ends of the paths at the point; Otherwise when none of them is there +template +track_uneven::amounts least_at_ends( TTrack const &Switch, glm::dvec3 const &Point, track_uneven::amounts const &Otherwise, Value_ const &Value ) { + + auto found { false }; + track_uneven::amounts result { track_uneven::most, track_uneven::most, track_uneven::most }; + for( int path = 0; path < 2; ++path ) { + for( int end = 0; end < 2; ++end ) { + if( glm::length( Switch.path_segment( path )->end_point( end ) - Point ) < switch_end_reach ) { + result = track_uneven::least( result, Value( path, end ) ); + found = true; + } + } + } + return found ? result : Otherwise; +} + +// how the end of a path meets the track joined to it +struct path_joint { + std::optional> place; // none: too far apart, or going different ways + float roll; +}; + +path_joint meet( TTrack const &Track, TSegment const &Segment, int const End, TTrack const &Neighbour ) { + + path_joint result { {}, Segment.design_roll( End ) }; + glm::dvec3 point; + glm::dvec3 across; + Segment.plan_frame( End ? Segment.GetLength() : 0.0, point, across ); + auto closest { joint_reach }; + for( int path = 0; path < Neighbour.path_count(); ++path ) { + auto const *theirs { Neighbour.path_segment( path ) }; + if( theirs == nullptr ) { + continue; + } + for( int end = 0; end < 2; ++end ) { + // laid towards each other, they look across and lean to opposite sides + auto const opposite { end == End }; + glm::dvec3 theirpoint; + glm::dvec3 theiracross; + theirs->plan_frame( end ? theirs->GetLength() : 0.0, theirpoint, theiracross ); + if( opposite ) { + theiracross = -theiracross; + } + auto const gap { glm::length( theirpoint - point ) }; + if( gap >= closest || glm::dot( across, theiracross ) < joint_alike ) { + continue; + } + closest = gap; + result.place.emplace( 0.5 * ( point + theirpoint ), glm::normalize( across + theiracross ) ); + if( Track.iCategoryFlag & 1 ) { + result.roll = track_uneven::joint_roll( + Segment.design_roll( End ), + opposite ? -theirs->design_roll( end ) : theirs->design_roll( end ), + Track.eType == tt_Switch, + Neighbour.eType == tt_Switch ); + } + } + } + return result; +} + +} // namespace + +int TTrack::path_count() const { + + return eType == tt_Switch ? 2 : 1; +} + +TSegment *TTrack::path_segment( int const Path ) const { + + return eType == tt_Switch ? SwitchExtension->Segments[ Path ].get() : Segment.get(); +} + +TTrack *TTrack::path_neighbour( int const Path, int const End ) const { + + if( eType == tt_Switch ) { + return End == 0 ? SwitchExtension->pPrevs[ Path ] : SwitchExtension->pNexts[ Path ]; + } + return End == 0 ? trPrev : trNext; +} + +track_uneven::amounts TTrack::uneven_declared( int const Path ) const { + + return track_uneven::declared( m_uneven, eType == tt_Normal, Path ); +} + +bool TTrack::is_uneven() const { + + 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 { + + 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, int const Path ) const { + + if( eType == tt_Switch ) { + auto const &stray { switch_uneven() }; + return track_uneven::along( stray.paths[ Path ], stray.ends[ Path ][ 0 ], stray.ends[ Path ][ 1 ], Distance, path_segment( Path )->GetLength() ); + } + return track_uneven::along( uneven_declared(), uneven_met( trPrev, Segment->end_point( 0 ) ), uneven_met( trNext, Segment->end_point( 1 ) ), Distance, Length() ); +} + +track_uneven::amounts TTrack::uneven_met( TTrack const *Other, glm::dvec3 const &Point ) const { + + if( Other == nullptr ) { + return {}; + } + return Other->eType == tt_Switch ? Other->switch_uneven_at( Point ) : track_uneven::least( uneven_declared(), Other->uneven_declared() ); +} + +TTrack::switch_stray const &TTrack::switch_uneven() const { + + if( m_switchstray.stamp != s_layoutstamp ) { + settle_switch_stray(); + } + return m_switchstray; +} + +void TTrack::settle_switch_stray() const { + + auto const group { switch_group( *this ) }; + auto const values { group_path_values( group ) }; + for( auto const *track : group ) { + track->m_switchstray.paths = values; + track->m_switchstray.stamp = s_layoutstamp; + } + // at the ends of the paths, with the paths known in all of the group + for( auto const *track : group ) { + for( int path = 0; path < 2; ++path ) { + for( int end = 0; end < 2; ++end ) { + auto const point { track->path_segment( path )->end_point( end ) }; + auto value { track->switch_paths_at( point ) }; + auto const *other { track->path_neighbour( path, end ) }; + if( is_switch( other ) ) { + value = track_uneven::least( value, other->switch_paths_at( point ) ); + } + track->m_switchstray.ends[ path ][ end ] = value; + } + } + } +} + +track_uneven::amounts TTrack::switch_paths_at( glm::dvec3 const &Point ) const { + + auto const &paths { m_switchstray.paths }; + return least_at_ends( *this, Point, track_uneven::least( paths[ 0 ], paths[ 1 ] ), [ & ]( int const Path, int ) { return paths[ Path ]; } ); +} + +track_uneven::amounts TTrack::switch_uneven_at( glm::dvec3 const &Point ) const { + + auto const &stray { switch_uneven() }; + return least_at_ends( *this, Point, track_uneven::least( stray.paths[ 0 ], stray.paths[ 1 ] ), [ & ]( int const Path, int const End ) { return stray.ends[ Path ][ End ]; } ); +} + +double TTrack::joint_depth() const { + + if( eType != tt_Normal ) { + return 0.0; + } + return ( m_uneven.dip >= 0.f ? m_uneven.dip : 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 { Track.Segment->end_point( End ) }; + 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; + } + // tracks joined one way only can leave this one out of the run: it stands on its own then, not to walk all of it again on every call + if( m_railrun.stamp != s_layoutstamp ) { + m_railrun = { 0.0, 1.0, s_layoutstamp }; + } +} + +void TTrack::share_joints() { + + for( int path = 0; path < path_count(); ++path ) { + auto *segment { path_segment( path ) }; + if( segment == nullptr ) { + continue; + } + for( int end = 0; end < 2; ++end ) { + auto const *neighbour { path_neighbour( path, end ) }; + auto const joined { neighbour != nullptr && neighbour != this && ( neighbour->iCategoryFlag & 15 ) == ( iCategoryFlag & 15 ) }; + auto const joint { joined ? meet( *this, *segment, end, *neighbour ) : path_joint { {}, segment->design_roll( end ) } }; + segment->set_joint( end, joint.place, 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; + } + } +} diff --git a/world/TrackUneven.h b/world/TrackUneven.h index dbec7a24..624e6db1 100644 --- a/world/TrackUneven.h +++ b/world/TrackUneven.h @@ -249,6 +249,15 @@ inline float joint_roll( float const Own, float const Their, bool const Switch, return Switch == Theirswitch ? 0.5f * ( Own + Their ) : Switch ? Their : Own; } +// each component the same way +inline amounts along( amounts const &Full, amounts const &Atstart, amounts const &Atend, double const Distance, double const Length ) { + + return { + along( Full.up, Atstart.up, Atend.up, Distance, Length ), + along( Full.side, Atstart.side, Atend.side, Distance, Length ), + along( Full.cross, Atstart.cross, Atend.cross, Distance, Length ) }; +} + // distance from the joint behind the point inline double rail_phase( double const Along, double const Rail ) {