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 ) {