diff --git a/tests/unit/test_track_uneven.cpp b/tests/unit/test_track_uneven.cpp index 247f9cbf..fd95cec9 100644 --- a/tests/unit/test_track_uneven.cpp +++ b/tests/unit/test_track_uneven.cpp @@ -194,6 +194,39 @@ 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: axles don't stray along the track") +{ + glm::dvec3 const across{0.6, 0.0, -0.8}; + glm::dvec3 const along{0.8, 0.0, 0.6}; + auto const moved{track_uneven::across_only(across * 0.03 + along * 0.05 + glm::dvec3{0.0, 0.02, 0.0}, across)}; + CHECK(glm::dot(moved, along) == doctest::Approx(0.0)); + CHECK(glm::dot(moved, across) == doctest::Approx(0.03)); + CHECK(moved.y == doctest::Approx(0.02)); +} + +TEST_CASE("uneven track: a path of no length gives no vector across") +{ + glm::dvec3 const none{0.0}; + CHECK(track_uneven::across_of(none, none) == none); + CHECK(glm::length(track_uneven::across_of({3.0, 0.0, 4.0}, none)) == doctest::Approx(1.0)); + // it can't pass for a joint: the check of the two vectors being alike fails + CHECK(glm::dot(track_uneven::across_of(none, none), glm::dvec3{1.0, 0.0, 0.0}) < 0.9); + // falls back on the chord + auto const chord{track_uneven::across_of(none, {0.0, 0.0, 10.0})}; + CHECK(chord.x == doctest::Approx(-1.0)); + CHECK(chord.z == doctest::Approx(0.0)); + // straight up has no direction in the plan + CHECK(glm::length(track_uneven::across_of({0.0, 5.0, 0.0}, {0.0, 0.0, 10.0})) == doctest::Approx(1.0)); +} + +TEST_CASE("uneven track: the cant at a joint") +{ + CHECK(track_uneven::joint_roll(0.1f, -0.1f, true, true) == doctest::Approx(0.f)); + CHECK(track_uneven::joint_roll(0.2f, 0.4f, false, false) == doctest::Approx(0.3f)); + CHECK(track_uneven::joint_roll(0.1f, 0.0f, true, false) == doctest::Approx(0.f)); + CHECK(track_uneven::joint_roll(0.0f, 0.1f, false, true) == doctest::Approx(0.f)); +} + 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})}; diff --git a/vehicle/DynObj.cpp b/vehicle/DynObj.cpp index af882828..3f633ccc 100644 --- a/vehicle/DynObj.cpp +++ b/vehicle/DynObj.cpp @@ -3662,11 +3662,13 @@ bool TDynamicObject::Update(double dt, double dt1) 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 - auto const railphase { Axle0.RailPhase( axle.offset - 0.5 * fAxleDist ) }; + // 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 * Axle0.GetTrack()->fSoundDistance; + crossed = axle.atjoints && std::abs( railphase - axle.distance ) > 0.5 * outer.GetTrack()->fSoundDistance; axle.distance = railphase; } else { diff --git a/world/Segment.cpp b/world/Segment.cpp index dd236c61..9c0e1077 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -385,13 +385,12 @@ void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm:: 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 ) ); - position += stray( position, fDistance ); + position += track_uneven::across_only( stray( position, amount, fDistance ), across ); } -glm::dvec3 TSegment::stray( glm::dvec3 const &Point, double const Distance ) const { - auto const amount { pOwner->uneven_at( Distance, m_path ) }; +glm::dvec3 TSegment::stray( glm::dvec3 const &Point, track_uneven::amounts const &Amount, double const Distance ) const { auto const field { track_uneven::field( Point ) }; - glm::dvec3 result { field.x * amount.side, field.y * amount.up, field.z * amount.side }; + glm::dvec3 result { field.x * Amount.side, field.y * Amount.up, field.z * Amount.side }; result *= track_uneven::stray_per_mm; result.y -= pOwner->joint_dip( Distance ); return result; @@ -401,13 +400,25 @@ glm::dvec3 TSegment::uneven_bend( double const Distance ) const { if( false == m_uneven ) { return glm::dvec3 { 0.0 }; } - // over about a bogie; FastGetTFromS extrapolates past the ends of the path + // over about a bogie auto const reach { 1.5 }; auto const strayed = [&]( double const At ) { - return stray( FastGetPoint( FastGetTFromS( At ) ), At ); }; + return stray( point_extended( At ), pOwner->uneven_at( At, m_path ), At ); }; return ( strayed( Distance + reach ) - 2.0 * strayed( Distance ) + strayed( Distance - reach ) ) / ( reach * reach ); } +glm::dvec3 TSegment::point_extended( double const Distance ) const { + auto const on { std::clamp( Distance, 0.0, fLength ) }; + auto const t { FastGetTFromS( on ) }; + auto const point { FastGetPoint( t ) }; + if( on == Distance ) { + return point; + } + auto const direction { FastGetDirection( t, 0.1 / fLength ) }; + auto const size { glm::length( direction ) }; + return size > 0.0 ? point + direction * ( ( Distance - on ) / size ) : point; +} + void TSegment::settle_vertex( gfx::basic_vertex &Vertex, glm::dvec3 const &Origin, glm::vec3 const &Position, glm::vec3 const &Parallel, double const Distance ) const { if( false == m_uneven ) { return; @@ -416,8 +427,12 @@ 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, 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 ) }; + if( share <= 0.0 ) { + return; + } + auto const amount { pOwner->uneven_at( Distance, m_path ) }; + Vertex.position.y -= static_cast( share * track_uneven::stray_per_mm * amount.cross * track_uneven::level( Origin + glm::dvec3 { Position }, glm::dvec3 { Parallel } ) * across / gauge ); + Vertex.position += glm::vec3 { share * stray( Origin + glm::dvec3 { Vertex.position }, amount, Distance ) }; } void TSegment::design_position(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const { @@ -446,15 +461,15 @@ 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 { + if( fTsBuffer.empty() ) { + // never made into a path: tt_Cross and the like + Point = Point1; + Across = glm::dvec3 { 0.0 }; + return; + } 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 ); + Across = track_uneven::across_of( FastGetDirection( t, 0.1 / fLength ), FastGetPoint_1() - FastGetPoint_0() ); } glm::dvec3 TSegment::FastGetPoint(double const t) const diff --git a/world/Segment.h b/world/Segment.h index 77e61f90..67e4ed05 100644 --- a/world/Segment.h +++ b/world/Segment.h @@ -13,6 +13,7 @@ http://mozilla.org/MPL/2.0/. #include "utilities/Classes.h" #include "rendering/geometrybank.h" +#include "world/TrackUneven.h" #include struct map_colored_paths { @@ -77,7 +78,9 @@ class TSegment // moves a lofted vertex with the unevenness of the track void settle_vertex( gfx::basic_vertex &Vertex, glm::dvec3 const &Origin, glm::vec3 const &Position, glm::vec3 const &Parallel, double const Distance ) const; // displacement of a point by the unevenness, at a distance along the path - glm::dvec3 stray( glm::dvec3 const &Point, double const Distance ) const; + glm::dvec3 stray( glm::dvec3 const &Point, track_uneven::amounts const &Amount, double const Distance ) const; + // a point of the path at a distance, past its ends straight on: the polynomial would run off on a short curved path + glm::dvec3 point_extended( double const Distance ) const; void design_position( double const fDistance, glm::dvec3 &position, glm::vec3 &rotation ) const; public: diff --git a/world/Track.cpp b/world/Track.cpp index 31b5f5c5..bdb1cfe9 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -908,6 +908,10 @@ void TTrack::settle_rail_run() const { 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() { @@ -1636,13 +1640,11 @@ void TTrack::share_joints() { 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 ); + 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 ); } } } diff --git a/world/TrackUneven.h b/world/TrackUneven.h index b46ebf7a..dbec7a24 100644 --- a/world/TrackUneven.h +++ b/world/TrackUneven.h @@ -224,6 +224,31 @@ inline double bed_share( double const Depth, double const Rail, double const Hei return Height > 0.0 ? std::clamp( 1.0 - ( Depth - Rail ) / Height, 0.0, 1.0 ) : 1.0; } +// a displacement with the part along the track taken out, Across being a unit vector in the plan. Along the track +// it would only change the distance between coupled vehicles, and with it the forces between them +inline glm::dvec3 across_only( glm::dvec3 const &Offset, glm::dvec3 const &Across ) { + + return Across * glm::dot( Offset, Across ) + glm::dvec3 { 0.0, Offset.y, 0.0 }; +} + +// the unit vector across a direction in the plan, or across the chord if it has none; zero for a path of no length, which has neither +inline glm::dvec3 across_of( glm::dvec3 const &Direction, glm::dvec3 const &Chord ) { + + for( auto const &along : { Direction, Chord } ) { + glm::dvec3 const across { -along.z, 0.0, along.x }; + if( glm::dot( across, across ) > 0.0 ) { + return glm::normalize( across ); + } + } + return glm::dvec3 { 0.0 }; +} + +// the cant where two paths meet, theirs turned to the way of this one: a switch takes that of a plain track, alike ones meet halfway +inline float joint_roll( float const Own, float const Their, bool const Switch, bool const Theirswitch ) { + + return Switch == Theirswitch ? 0.5f * ( Own + Their ) : Switch ? Their : Own; +} + // distance from the joint behind the point inline double rail_phase( double const Along, double const Rail ) {