From e74bbf951f87e399b95a824dc70bdefa587c97e0 Mon Sep 17 00:00:00 2001 From: maj00r Date: Sat, 31 Oct 2026 18:23:19 +0100 Subject: [PATCH] fix(track): the cross level of an uneven track goes by the place, not by how it was laid Tracks laid towards each other settled to opposite sides: a step of cant of twice the value at their joint, in the geometry and under the axles. It's now a tilt of the plan, which reads the same from either track. --- tests/unit/test_track_uneven.cpp | 23 +++++++++++++++++++++-- track_uneven_doc.md | 2 +- world/Segment.cpp | 4 ++-- world/TrackUneven.h | 14 ++++++++------ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/tests/unit/test_track_uneven.cpp b/tests/unit/test_track_uneven.cpp index 6333b6ab..195a2d72 100644 --- a/tests/unit/test_track_uneven.cpp +++ b/tests/unit/test_track_uneven.cpp @@ -59,7 +59,7 @@ TEST_CASE("uneven track: measures as the Id-14 limits for its value") { auto const sign{rail == 0 ? 1.0 : -1.0}; auto const stray{track_uneven::field(point + across * (sign * 0.5 * gauge))}; - up[rail].push_back((stray.y - sign * 0.5 * track_uneven::level(point)) * amplitude); + up[rail].push_back((stray.y - sign * 0.5 * track_uneven::level(point, across)) * amplitude); side[rail].push_back(glm::dot(stray, across) * amplitude); } middle.push_back(track_uneven::field(point).y * amplitude); @@ -102,7 +102,26 @@ TEST_CASE("uneven track: a place strays the same every time") CHECK(stray.x == doctest::Approx(0.309777548158).epsilon(1e-9)); CHECK(stray.y == doctest::Approx(-0.295557487607).epsilon(1e-9)); CHECK(stray.z == doctest::Approx(-0.328429734019).epsilon(1e-9)); - CHECK(track_uneven::level(place) == doctest::Approx(0.6 * -0.178598957983).epsilon(1e-9)); + CHECK(track_uneven::level(place, {1.0, 0.0, 0.0}) == doctest::Approx(0.53 * -0.178598957983).epsilon(1e-9)); + CHECK(track_uneven::level(place, {0.0, 0.0, 1.0}) == doctest::Approx(0.53 * -0.868291889902).epsilon(1e-9)); +} + +TEST_CASE("uneven track: tracks laid towards each other meet at one cant") +{ + // the same rail stays lower seen from either of them, and a path taken the other way sees the roll turned over + glm::dvec3 const place{812.25, 0.0, 3071.5}; + glm::dvec3 const across{0.6, 0.0, -0.8}; + CHECK(track_uneven::level(place, -across) == doctest::Approx(-track_uneven::level(place, across))); + CHECK(track_uneven::roll(place, -across, 1.435) == doctest::Approx(-track_uneven::roll(place, across, 1.435))); + // and there's no step along a curve turning through the headings + auto last{track_uneven::level(place, {1.0, 0.0, 0.0})}; + for (int step = 1; step <= 3600; ++step) + { + auto const heading{glm::two_pi() * step / 3600.0}; + auto const value{track_uneven::level(place, {std::cos(heading), 0.0, std::sin(heading)})}; + REQUIRE(std::abs(value - last) < 0.002); + last = value; + } } TEST_CASE("uneven track: the field stays within its bound") diff --git a/track_uneven_doc.md b/track_uneven_doc.md index 8cb443ac..ebced90e 100644 --- a/track_uneven_doc.md +++ b/track_uneven_doc.md @@ -58,7 +58,7 @@ 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. - **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. Na rozjazdach tylko ta wynikająca z fal, bez osobnego siadania toków. +- **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. Za nierównością idą szyny, podsypka, `sleepermodel`, osie, przechył pudła i bujanie kabiny. diff --git a/world/Segment.cpp b/world/Segment.cpp index ad817b5e..84a0c22b 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -380,7 +380,7 @@ void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm:: auto const amount { pOwner->uneven_at( fDistance ) }; rotation.x += static_cast( track_uneven::stray_per_mm * ( amount.up * track_uneven::roll( position, across, gauge ) - + amount.cross * track_uneven::level( position ) / gauge ) ); + + amount.cross * track_uneven::level( position, across ) / gauge ) ); position += stray( position, fDistance ); } @@ -411,7 +411,7 @@ void TSegment::settle_vertex( gfx::basic_vertex &Vertex, glm::dvec3 const &Origi // the rails settling apart turn about the middle of the track, what's outside them follows the rail 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 ) }; - Vertex.position.y -= static_cast( track_uneven::stray_per_mm * pOwner->uneven_at( Distance ).cross * track_uneven::level( Origin + glm::dvec3 { Position } ) * across / gauge ); + Vertex.position.y -= static_cast( track_uneven::stray_per_mm * pOwner->uneven_at( Distance ).cross * track_uneven::level( Origin + glm::dvec3 { Position }, glm::dvec3 { Parallel } ) * across / gauge ); Vertex.position += glm::vec3 { stray( Origin + glm::dvec3 { Vertex.position }, Distance ) }; } diff --git a/world/TrackUneven.h b/world/TrackUneven.h index 7486cea2..5974f42d 100644 --- a/world/TrackUneven.h +++ b/world/TrackUneven.h @@ -22,7 +22,7 @@ namespace track_uneven { inline constexpr float most { 100.f }; // largest value of the attributes, mm inline constexpr double stray_per_mm { 0.00145 }; // field amplitude per mm of "uneven", m -inline constexpr double cross_share { 0.6 }; // share of the field amplitude the rails settle apart by +inline constexpr double cross_share { 0.53 }; // share of the field amplitude the rails settle apart by inline constexpr double fade { 20.0 }; // length over which a track goes from the value at its end to its own, m inline constexpr double dip_share { 0.4 }; // joint dip depth per mm of "uneven", mm inline constexpr double dip_reach { 3.0 }; // reach of the dip on each side of a joint, m @@ -64,8 +64,9 @@ inline constexpr std::array wave_shares { 0.0721, 0.0997, 0.1344, 0.1 inline wave_set<8> const up_waves { make_waves( 0.0, wave_lengths, wave_shares ) }; inline wave_set<8> const x_waves { make_waves( 1.1, wave_lengths, wave_shares ) }; inline wave_set<8> const z_waves { make_waves( 2.3, wave_lengths, wave_shares ) }; -// long waves only: with the short ones the cross level reaching its limit would break the limit of twist -inline wave_set<3> const cross_waves { make_waves<3>( 0.7, { 23.9, 31.7, 41.9 }, { 1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0 } ) }; +// the tilt along each axis of the plan. Long waves only: with the short ones the cross level reaching its limit would break the limit of twist +inline wave_set<3> const tilt_x_waves { make_waves<3>( 0.7, { 23.9, 31.7, 41.9 }, { 1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0 } ) }; +inline wave_set<3> const tilt_z_waves { make_waves<3>( 2.9, { 23.9, 31.7, 41.9 }, { 1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0 } ) }; template double sum( wave_set const &Waves, glm::dvec3 const &Point ) { @@ -83,10 +84,11 @@ inline glm::dvec3 field( glm::dvec3 const &Point ) { return { sum( x_waves, Point ), sum( up_waves, Point ), sum( z_waves, Point ) }; } -// how much lower the right rail lies by settling apart, for an amplitude of 1. Follows the middle of a path, so plain tracks only -inline double level( glm::dvec3 const &Point ) { +// how much lower the rail towards Across lies by settling apart, for an amplitude of 1. A tilt of the plan, so tracks laid towards each +// other meet at one cant. Follows the middle of a path, so plain tracks only +inline double level( glm::dvec3 const &Point, glm::dvec3 const &Across ) { - return cross_share * sum( cross_waves, Point ); + return cross_share * ( Across.x * sum( tilt_x_waves, Point ) + Across.z * sum( tilt_z_waves, Point ) ); } // roll made by the field for an amplitude of 1; positive lowers the rail towards Across, as the roll of a path