From 9364b03e1ad9d9711942864c4d5ab17a00f487d3 Mon Sep 17 00:00:00 2001 From: maj00r Date: Sat, 31 Oct 2026 20:17:47 +0100 Subject: [PATCH] fix(track): what Sonar reports on the unevenness The blockers are in the EP brake of a vehicle, older than this work: its arrays made with new[] are vectors now, which also removes the deletes. - The caches of the unevenness of a track are no longer mutable: what fills them isn't const any more. - TSegment gets back to RaPositionGet in one piece, with what the path has to do with the others in one struct. - Less nesting in the group of switches and in the fields of the editor, nested conditionals written out, and the attribute is read from a string_view. A test no longer counts in floating point. No change in behaviour. --- application/editormodetrack.cpp | 33 ++++----- tests/unit/test_track_uneven.cpp | 33 ++++++--- vehicle/DynObj.cpp | 73 +++++++++---------- vehicle/DynObj.h | 2 +- world/Segment.cpp | 77 +++++++++----------- world/Segment.h | 29 ++++---- world/Track.h | 21 +++--- world/TrackUneven.cpp | 117 ++++++++++++++++++------------- world/TrackUneven.h | 45 +++++++----- 9 files changed, 223 insertions(+), 207 deletions(-) diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 49059c213..0ae732d86 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1166,26 +1166,23 @@ void editor_mode::render_path_parameters(TTrack &Track) track->m_uneven.value = std::clamp(track->m_uneven.value, 0.f, track_uneven::most); item_tooltip("Peak deviation of a rail on a 10 m chord. Id-14 limits: 6 mm at 160 km/h, 10 at 120, 18 at 80, 35 at 40.\nWith a sound distance set, the rails also dip at the joints. Joined switches use the lowest of their values"); finish_edit(rebuild::parameters); - struct component - { - char const *label; - float *value; - bool shown; - }; - 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 (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); + auto const component = [&](char const *Label, float &Value) { + if (ImGui::InputFloat(STR_C(Label), &Value, 0.0f, 0.0f, "%.1f")) + Value = Value < 0.f ? -1.f : std::min(Value, track_uneven::most); item_tooltip("Set apart from the unevenness, mm. Negative: the unevenness"); finish_edit(rebuild::parameters); + }; + component("Up and down (mm)", track->m_uneven.up); + component("Sideways (mm)", track->m_uneven.side); + if (track->eType == tt_Normal) + { + component("Cross level (mm)", track->m_uneven.cross); + } + else + { + 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); } } if (track->eType == tt_Normal && (track->iCategoryFlag & 15) == 1) diff --git a/tests/unit/test_track_uneven.cpp b/tests/unit/test_track_uneven.cpp index 65b7da6e1..06f4663b0 100644 --- a/tests/unit/test_track_uneven.cpp +++ b/tests/unit/test_track_uneven.cpp @@ -2,6 +2,8 @@ #include "doctest.h" #include "world/TrackUneven.h" +#include +#include #include #include @@ -12,19 +14,20 @@ double deviation(std::vector const &Values) auto mean{0.0}; for (auto const value : Values) mean += value; - mean /= Values.size(); + auto const count{static_cast(Values.size())}; + mean /= count; auto sum{0.0}; for (auto const value : Values) sum += (value - mean) * (value - mean); - return std::sqrt(sum / Values.size()); + return std::sqrt(sum / count); } double peak(std::vector Values) { for (auto &value : Values) value = std::abs(value); - std::sort(Values.begin(), Values.end()); - return Values[static_cast(0.999 * Values.size())]; + std::ranges::sort(Values); + return Values[Values.size() * 999 / 1000]; } void add_chord(std::vector &Output, std::vector const &Line, int const Half) @@ -44,14 +47,22 @@ TEST_CASE("uneven track: measures as the Id-14 limits for its value") int const steps{4000}; int const base{10}; // 5 m int const lines{36}; - double vertical{0.0}, horizontal{0.0}, twist{0.0}, wander{0.0}, level{0.0}, j{0.0}, away{0.0}; + double vertical{0.0}; + double horizontal{0.0}; + double twist{0.0}; + double wander{0.0}; + double level{0.0}; + double j{0.0}; + double away{0.0}; for (int line = 0; line < lines; ++line) { auto const heading{glm::pi() * (line + 0.37) / lines}; glm::dvec3 const start{7919.0 * std::sin(line * 1.7), 0.0, 6577.0 * std::cos(line * 2.3)}; glm::dvec3 const along{std::cos(heading), 0.0, std::sin(heading)}; glm::dvec3 const across{-along.z, 0.0, along.x}; - std::vector up[2], side[2], middle; + std::array, 2> up; + std::array, 2> side; + std::vector middle; for (int i = 0; i < steps; ++i) { auto const point{start + along * (i * step)}; @@ -64,7 +75,11 @@ TEST_CASE("uneven track: measures as the Id-14 limits for its value") } middle.push_back(track_uneven::field(point).y * amplitude); } - std::vector verticals, horizontals, twists, gauges, levels; + std::vector verticals; + std::vector horizontals; + std::vector twists; + std::vector gauges; + std::vector levels; for (int rail = 0; rail < 2; ++rail) { add_chord(verticals, up[rail], base); @@ -269,9 +284,9 @@ TEST_CASE("uneven track: the value along a track meets its neighbours exactly") CHECK(track_uneven::along(18.0, 35.0, 35.0, 0.25, 0.5) == doctest::Approx(35.0).epsilon(0.01)); // no jump anywhere auto last{track_uneven::along(18.0, 35.0, 0.0, 0.0, length)}; - for (auto distance{0.25}; distance <= length; distance += 0.25) + for (int step = 1; step <= 2000; ++step) { - auto const value{track_uneven::along(18.0, 35.0, 0.0, distance, length)}; + auto const value{track_uneven::along(18.0, 35.0, 0.0, step * 0.25, length)}; REQUIRE(std::abs(value - last) < 0.5); last = value; } diff --git a/vehicle/DynObj.cpp b/vehicle/DynObj.cpp index ac87f395c..305e12020 100644 --- a/vehicle/DynObj.cpp +++ b/vehicle/DynObj.cpp @@ -3382,14 +3382,12 @@ bool TDynamicObject::Update(double dt, double dt1) FzadED = 0; //pętla bezpieczeństwa - bez ED auto const FzadPN = Fzad - FrED * MoverParameters->MED_FrED_factor; //np = 0; - // BUG: likely memory leak, allocation per inner loop, deleted only once outside - // TODO: sort this shit out - bool* PrzekrF = new bool[np]; + std::vector PrzekrF( np ); float nPrzekrF = 0; bool test = true; - float* FzED = new float[np]; - float* FzEP = new float[np]; - float* FmaxEP = new float[np]; + std::vector FzED( np ); + std::vector FzEP( np ); + std::vector FmaxEP( np ); // 3. ustaw pojazdom sile hamowania ED // - proporcjonalnie do mozliwosci @@ -3548,11 +3546,6 @@ bool TDynamicObject::Update(double dt, double dt1) } - delete[] PrzekrF; - delete[] FzED; - delete[] FzEP; - delete[] FmaxEP; - MED_oldFED = FzadED; } @@ -3661,33 +3654,32 @@ bool TDynamicObject::Update(double dt, double dt1) 1 : ctOwner->Vehicle()->DirectionGet() == DirectionGet() ? 1 : -1 ) }; for( auto &axle : m_axlesounds ) { - 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; - axle.clatter - .pitch( frequency ) - .gain( volume ) - .play(); - // crude bump simulation, drop down on even axles, move back up on - // the odd ones - // MoverParameters->AccVert += (MoverParameters->Vel*0.1f) * - double const headroom = std::clamp( - MoverParameters->Vmax - ( MoverParameters->Vel + MoverParameters->Vmax * 0.32f ), - 0.0, MoverParameters->Vmax ); - double const jolt = headroom * .05f * ( MyTrack->iDamageFlag * 0.25f ); - if (MyTrack->eType == tt_Normal) - { - MoverParameters->AccVert += std::clamp( jolt, 0.0, 4.0 ); - } - else if (MyTrack->eType == tt_Switch) - { - double const accHorizontal = std::clamp( jolt, 0.0, 1.0 ) * ((axleindex % 2) != 0 ? 1 : -1); - MoverParameters->AccS += accHorizontal; - MoverParameters->AccN += accHorizontal; - MoverParameters->AccVert += std::clamp( jolt, 0.0, 2.0 ); - } - } + if( axle_passed_joint( axle, dDOMoveLen, directioninconsist ) + && 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; + axle.clatter + .pitch( frequency ) + .gain( volume ) + .play(); + // crude bump simulation, drop down on even axles, move back up on + // the odd ones + // MoverParameters->AccVert += (MoverParameters->Vel*0.1f) * + double const headroom = std::clamp( + MoverParameters->Vmax - ( MoverParameters->Vel + MoverParameters->Vmax * 0.32f ), + 0.0, MoverParameters->Vmax ); + double const jolt = headroom * .05f * ( MyTrack->iDamageFlag * 0.25f ); + if (MyTrack->eType == tt_Normal) + { + MoverParameters->AccVert += std::clamp( jolt, 0.0, 4.0 ); + } + else if (MyTrack->eType == tt_Switch) + { + double const accHorizontal = std::clamp( jolt, 0.0, 1.0 ) * ((axleindex % 2) != 0 ? 1 : -1); + MoverParameters->AccS += accHorizontal; + MoverParameters->AccN += accHorizontal; + MoverParameters->AccVert += std::clamp( jolt, 0.0, 2.0 ); + } } ++axleindex; } @@ -8072,12 +8064,11 @@ TDynamicObject::update_shake( double const Timedelta ) { } bool -TDynamicObject::axle_passed_joint( axle_sounds &Axle, double const Move, int const Direction ) { +TDynamicObject::axle_passed_joint( axle_sounds &Axle, double const Move, int const Direction ) const { // 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 ) { + if( auto const railphase { outer.RailPhase( Axle.offset - std::copysign( 0.5 * fAxleDist, Axle.offset ) ) }; 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; diff --git a/vehicle/DynObj.h b/vehicle/DynObj.h index b903f0874..4fee9778e 100644 --- a/vehicle/DynObj.h +++ b/vehicle/DynObj.h @@ -405,7 +405,7 @@ private: 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 ); + bool axle_passed_joint( axle_sounds &Axle, double const Move, int const Direction ) const; struct powertrain_sounds { sound_source inverter { sound_placement::engine }; diff --git a/world/Segment.cpp b/world/Segment.cpp index 9c0e1077e..1aff4ae42 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -69,10 +69,8 @@ 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(); + m_setting.designrolls = { fRoll1, fRoll2 }; + m_setting.joints = {}; bCurve = bIsCurve; if (Global.bRollFix) { // Ra: poprawianie przechyłki @@ -122,7 +120,7 @@ bool TSegment::Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 N fStoop = std::atan2(Point2.y - Point1.y, fLength); // pochylenie toru prostego, żeby nie liczyć wielokrotnie fStep = fNewStep; - if( m_uneven && pOwner->eType != tt_Switch ) { + if( m_setting.uneven && pOwner->eType != tt_Switch ) { // dense enough for the waves; a switch keeps its division, the blades depend on it fStep = std::min( fStep, 2.0 ); } @@ -373,21 +371,6 @@ Math3D::vector3 TSegment::GetPoint(double const fDistance) const }; */ -void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const { - design_position( fDistance, position, rotation ); - if( false == m_uneven ) { - return; - } - // direction and slope stay as designed: the physics takes the curve radius and the grade from them - auto const gauge { std::max( std::abs( pOwner->fTrackWidth ), 0.1f ) }; - glm::dvec3 const across { -std::cos( rotation.z ), 0.0, -std::sin( rotation.z ) }; - auto const amount { pOwner->uneven_at( fDistance, m_path ) }; - 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 += track_uneven::across_only( stray( position, amount, fDistance ), across ); -} - 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 }; @@ -397,30 +380,25 @@ glm::dvec3 TSegment::stray( glm::dvec3 const &Point, track_uneven::amounts const } glm::dvec3 TSegment::uneven_bend( double const Distance ) const { - if( false == m_uneven ) { + if( false == m_setting.uneven ) { return glm::dvec3 { 0.0 }; } // over about a bogie auto const reach { 1.5 }; - auto const strayed = [&]( double const At ) { - return stray( point_extended( At ), pOwner->uneven_at( At, m_path ), At ); }; + auto const strayed = [ this ]( double const At ) { + // past the ends of the path straight on: the polynomial would run off on a short curved one + auto const on { std::clamp( At, 0.0, fLength ) }; + auto const t { FastGetTFromS( on ) }; + auto point { FastGetPoint( t ) }; + if( auto const direction { FastGetDirection( t, 0.1 / fLength ) }; on != At && glm::length( direction ) > 0.0 ) { + point += direction * ( ( At - on ) / glm::length( direction ) ); + } + return stray( point, pOwner->uneven_at( At, m_setting.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 ) { + if( false == m_setting.uneven ) { return; } // the rails settling apart turn about the middle of the track, what's outside them follows the rail; the foot of the ballast stays @@ -430,12 +408,12 @@ void TSegment::settle_vertex( gfx::basic_vertex &Vertex, glm::dvec3 const &Origi if( share <= 0.0 ) { return; } - auto const amount { pOwner->uneven_at( Distance, m_path ) }; + auto const amount { pOwner->uneven_at( Distance, m_setting.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 { +void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const { if (bCurve) { // można by wprowadzić uproszczony wzór dla okręgów płaskich auto const t = FastGetTFromS(fDistance); // aproksymacja dystansu na krzywej Beziera na parametr (t) @@ -458,6 +436,17 @@ void TSegment::design_position(double const fDistance, glm::dvec3 &position, glm rotation.y = static_cast(fStoop); // pochylenie toru prostego rotation.z = static_cast(fDirection); // kierunek toru w planie } + if( false == m_setting.uneven ) { + return; + } + // direction and slope stay as designed: the physics takes the curve radius and the grade from them + auto const gauge { std::max( std::abs( pOwner->fTrackWidth ), 0.1f ) }; + glm::dvec3 const across { -std::cos( rotation.z ), 0.0, -std::sin( rotation.z ) }; + auto const amount { pOwner->uneven_at( fDistance, m_setting.path ) }; + 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 += track_uneven::across_only( stray( position, amount, fDistance ), across ); }; void TSegment::plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3 &Across ) const { @@ -523,9 +512,9 @@ 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( iSkip == 0 && m_setting.joints[ 0 ] ) { + pos1 = glm::vec3{ m_setting.joints[ 0 ]->first - Origin }; + parallel1 = glm::vec3{ m_setting.joints[ 0 ]->second }; } if( iEnd == 0 ) iEnd = iSegCount; @@ -572,9 +561,9 @@ 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 }; + if( i == iSegCount && m_setting.joints[ 1 ] ) { + pos2 = glm::vec3{ m_setting.joints[ 1 ]->first - Origin }; + parallel2 = glm::vec3{ m_setting.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 diff --git a/world/Segment.h b/world/Segment.h index 75e070152..099480e36 100644 --- a/world/Segment.h +++ b/world/Segment.h @@ -9,7 +9,9 @@ http://mozilla.org/MPL/2.0/. #pragma once +#include #include +#include #include "utilities/Classes.h" #include "rendering/geometrybank.h" @@ -50,11 +52,14 @@ 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 - bool m_uneven { false }; // see TrackUneven.h - int m_path { 0 }; // which path of a switch - // where the path meets the next one: the middle of their ends and their common vector across - std::optional> m_joints[ 2 ]; + // how the path sits among the others + struct setting { + std::array designrolls { 0.f, 0.f }; // as entered; fRoll1 and fRoll2 can differ at a joint + bool uneven { false }; // see TrackUneven.h + int path { 0 }; // which path of a switch + // where the path meets the next one: the middle of their ends and their common vector across + std::array>, 2> joints; + } m_setting; double fLength { -1.0 }; // długość policzona std::vector fTsBuffer; // wartości parametru krzywej t dla równych odcinków s std::vector fTsSlope; // nachylenie dt/du (= dt/ds * fStep) w tych samych punktach @@ -79,9 +84,6 @@ class TSegment 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, 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: bool bCurve = false; @@ -125,20 +127,17 @@ public: // call before Init, it sets the density of the geometry void 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; } + m_setting.uneven = Uneven; + m_setting.path = Path; } // 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 ]; } + return m_setting.designrolls[ End ]; } void set_joint( int const End, std::optional> const &Joint, float const Roll ) { - m_joints[ End ] = Joint; + m_setting.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 diff --git a/world/Track.h b/world/Track.h index f5bb8c1b6..72cbd8847 100644 --- a/world/Track.h +++ b/world/Track.h @@ -349,15 +349,15 @@ public: track_uneven::amounts uneven_declared( int const Path = 0 ) const; bool is_uneven() const; // the unevenness at a distance along a path, meeting the tracks joined at its ends - track_uneven::amounts uneven_at( double const Distance, int const Path = 0 ) const; + track_uneven::amounts uneven_at( double const Distance, int const Path = 0 ); // 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 + double joint_dip( double const Distance ); // m // distance from the rail joint behind; negative: the track has no joints that dip - double rail_phase( double const Distance ) const; + double rail_phase( double const Distance ); void ConnectionsLog(); bool DoubleSlip() const; static void fetch_default_profiles(); @@ -395,8 +395,8 @@ private: double way { 1.0 }; // direction of the path along the run int stamp { -1 }; }; - void settle_rail_run() const; - mutable rail_run m_railrun; + void settle_rail_run(); + rail_run m_railrun; // the value along each path of a switch: the lowest of that path in the switches joined, 0 next to a track with none. // And at the ends of the paths, by what meets there: the other path at the common end, the path of a switch joined struct switch_stray { @@ -404,15 +404,16 @@ private: std::array, 2> ends; int stamp { -1 }; }; - mutable switch_stray m_switchstray; - switch_stray const &switch_uneven() const; - void settle_switch_stray() const; + switch_stray m_switchstray; + switch_stray const &switch_uneven(); + void settle_switch_stray(); + void settle_switch_ends(); // 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; + track_uneven::amounts uneven_met( TTrack *Other, glm::dvec3 const &Point ); // the value of a switch where a track joined to it meets it - track_uneven::amounts switch_uneven_at( glm::dvec3 const &Point ) const; + track_uneven::amounts switch_uneven_at( glm::dvec3 const &Point ); static int s_layoutstamp; // bumped when tracks are joined or changed, invalidates the two above void create_road_profile( gfx::vertex_array &Output, bool const Forcetransition = false ); void create_road_side_profile( gfx::vertex_array &Right, gfx::vertex_array &Left, gfx::vertex_array const &Road, bool const Forcetransition = false ); diff --git a/world/TrackUneven.cpp b/world/TrackUneven.cpp index 152449ad2..78505ac3a 100644 --- a/world/TrackUneven.cpp +++ b/world/TrackUneven.cpp @@ -24,42 +24,55 @@ bool is_switch( TTrack const *Track ) { return Track != nullptr && Track->eType == tt_Switch && Track->SwitchExtension; } -std::array joined_to_switch( TTrack const &Switch ) { +glm::dvec3 end_point( TSegment const &Segment, int const End ) { + + return End == 0 ? Segment.FastGetPoint_0() : Segment.FastGetPoint_1(); +} + +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 switch_group( TTrack &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() ) { + std::vector group { &Switch }; + std::vector pending { &Switch }; + while( false == pending.empty() ) { + auto const *track { pending.back() }; + pending.pop_back(); + for( auto *other : joined_to_switch( *track ) ) { + if( is_switch( other ) && std::ranges::find( group, other ) == group.end() ) { group.push_back( other ); + pending.push_back( other ); } } } return group; } +// a plain track with none of a component keeps the switch to its design in it, any other comes to the value of the switch +void keep_to_plain_track( std::array &Values, TTrack const &Plain ) { + + auto const theirs { Plain.uneven_declared() }; + for( auto &path : Values ) { + if( theirs.up <= 0.0 ) { path.up = 0.0; } + if( theirs.side <= 0.0 ) { path.side = 0.0; } + } +} + // 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 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; } + if( other != nullptr && false == is_switch( other ) ) { + keep_to_plain_track( values, *other ); } } } @@ -74,7 +87,7 @@ track_uneven::amounts least_at_ends( TTrack const &Switch, glm::dvec3 const &Poi 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 ) { + if( glm::length( end_point( *Switch.path_segment( path ), end ) - Point ) < switch_end_reach ) { result = track_uneven::least( result, Value( path, end ) ); found = true; } @@ -165,16 +178,16 @@ 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 { +track_uneven::amounts TTrack::uneven_at( double const Distance, int const Path ) { 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() ); + return track_uneven::along( uneven_declared(), uneven_met( trPrev, end_point( *Segment, 0 ) ), uneven_met( trNext, end_point( *Segment, 1 ) ), Distance, Length() ); } -track_uneven::amounts TTrack::uneven_met( TTrack const *Other, glm::dvec3 const &Point ) const { +track_uneven::amounts TTrack::uneven_met( TTrack *Other, glm::dvec3 const &Point ) { if( Other == nullptr ) { return {}; @@ -182,7 +195,7 @@ track_uneven::amounts TTrack::uneven_met( TTrack const *Other, glm::dvec3 const 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 { +TTrack::switch_stray const &TTrack::switch_uneven() { if( m_switchstray.stamp != s_layoutstamp ) { settle_switch_stray(); @@ -190,26 +203,30 @@ TTrack::switch_stray const &TTrack::switch_uneven() const { return m_switchstray; } -void TTrack::settle_switch_stray() const { +void TTrack::settle_switch_stray() { auto const group { switch_group( *this ) }; auto const values { group_path_values( group ) }; - for( auto const *track : group ) { + for( auto *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; + // with the paths known in all of the group + for( auto *track : group ) { + track->settle_switch_ends(); + } +} + +void TTrack::settle_switch_ends() { + + for( int path = 0; path < 2; ++path ) { + for( int end = 0; end < 2; ++end ) { + auto const point { end_point( *path_segment( path ), end ) }; + auto value { switch_paths_at( point ) }; + if( auto const *other { path_neighbour( path, end ) }; is_switch( other ) ) { + value = track_uneven::least( value, other->switch_paths_at( point ) ); } + m_switchstray.ends[ path ][ end ] = value; } } } @@ -217,13 +234,13 @@ void TTrack::settle_switch_stray() const { 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 ]; } ); + return least_at_ends( *this, Point, track_uneven::least( paths[ 0 ], paths[ 1 ] ), [ &paths ]( int const Path, int ) { return paths[ Path ]; } ); } -track_uneven::amounts TTrack::switch_uneven_at( glm::dvec3 const &Point ) const { +track_uneven::amounts TTrack::switch_uneven_at( glm::dvec3 const &Point ) { 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 ]; } ); + return least_at_ends( *this, Point, track_uneven::least( stray.paths[ 0 ], stray.paths[ 1 ] ), [ &stray ]( int const Path, int const End ) { return stray.ends[ Path ][ End ]; } ); } double TTrack::joint_depth() const { @@ -234,7 +251,7 @@ double TTrack::joint_depth() const { return ( m_uneven.dip >= 0.f ? m_uneven.dip : track_uneven::dip_share * uneven_declared().up ); } -double TTrack::joint_dip( double const Distance ) const { +double TTrack::joint_dip( double const Distance ) { auto const phase { rail_phase( Distance ) }; if( phase < 0.0 ) { @@ -245,12 +262,12 @@ double TTrack::joint_dip( double const Distance ) const { return 0.0; } auto const depth { joint_depth() }; - auto const atend = [&]( TTrack const *Other ) { + auto const atend = [ this, depth ]( 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 { +double TTrack::rail_phase( double const Distance ) { if( joint_depth() <= 0.0 || fSoundDistance <= 2.0 * track_uneven::dip_reach ) { return -1.0; @@ -261,22 +278,22 @@ double TTrack::rail_phase( double const Distance ) const { return track_uneven::rail_phase( m_railrun.origin + m_railrun.way * Distance, fSoundDistance ); } -void TTrack::settle_rail_run() const { +void TTrack::settle_rail_run() { // 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 }; + auto const onward = []( TTrack const &Track, int const End, int &Joined ) -> TTrack * { + auto *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 }; + auto const walk = [ this, &onward ]( int &End ) { + auto *track { this }; int joined { 0 }; for( int step = 0; step < 1000000; ++step ) { - auto const *next { onward( *track, End, joined ) }; + auto *next { onward( *track, End, joined ) }; if( next == nullptr || next == this ) { break; } @@ -285,15 +302,14 @@ void TTrack::settle_rail_run() const { } return track; }; auto const place = []( TTrack const &Track, int const End ) { - auto const point { Track.Segment->end_point( End ) }; + auto const point { end_point( *Track.Segment, 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 ) ) { + auto *track { walk( end ) }; + if( auto *other { walk( otherend ) }; place( *other, otherend ) < place( *track, end ) ) { track = other; end = otherend; } @@ -327,7 +343,8 @@ void TTrack::share_joints() { } } if( eType != tt_Switch && Segment && ( iCategoryFlag & 1 ) ) { - float roll1, roll2; + float roll1 { 0.f }; + float roll2 { 0.f }; Segment->GetRolls( roll1, roll2 ); if( roll1 != 0.f || roll2 != 0.f ) { iTrapezoid |= 1; diff --git a/world/TrackUneven.h b/world/TrackUneven.h index 624e6db16..2c32ad660 100644 --- a/world/TrackUneven.h +++ b/world/TrackUneven.h @@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/. #include #include #include +#include #include #include @@ -51,18 +52,20 @@ struct wave { // the numbers of an entry like "uneven 14;35", negative where left out template -std::array read_fields( std::string const &Text ) { +std::array read_fields( std::string_view const Text ) { std::array result; result.fill( -1.f ); - std::size_t start { 0 }; - for( std::size_t index = 0; index < Count && start < Text.size(); ++index ) { - auto const end { std::min( Text.find( ';', start ), Text.size() ) }; - auto value { 0.f }; - if( end > start && std::from_chars( Text.data() + start, Text.data() + end, value ).ec == std::errc {} ) { - result[ index ] = std::clamp( value, 0.f, most ); + auto rest { Text }; + for( auto &field : result ) { + auto const end { std::min( rest.find( ';' ), rest.size() ) }; + if( auto value { 0.f }; end > 0 && std::from_chars( rest.data(), rest.data() + end, value ).ec == std::errc {} ) { + field = std::clamp( value, 0.f, most ); } - start = end + 1; + if( end == rest.size() ) { + break; + } + rest.remove_prefix( end + 1 ); } return result; } @@ -100,7 +103,7 @@ struct attribute { }; // a plain track: value;up;side;cross;dip. A switch: p1-p2;p3-p4;up of p1-p2;side of p1-p2;up of p3-p4;side of p3-p4 -inline attribute read_attribute( std::string const &Text, bool const Switch ) { +inline attribute read_attribute( std::string_view const Text, bool const Switch ) { auto const fields { read_fields<6>( Text ) }; attribute result; @@ -136,15 +139,15 @@ inline std::string write_attribute( attribute const &Values, bool const Switch ) // what a path comes to from the values: what's not given takes the first value, p3-p4 those of p1-p2. Cross level on plain tracks only inline amounts declared( attribute const &Values, bool const Plain, int const Path ) { + auto const given = []( float const Part, double const Otherwise ) { + return Part >= 0.f ? static_cast( Part ) : Otherwise; }; amounts result { - Values.up >= 0.f ? Values.up : Values.value, - Values.side >= 0.f ? Values.side : Values.value, - false == Plain ? 0.f : Values.cross >= 0.f ? Values.cross : Values.value }; + given( Values.up, Values.value ), + given( Values.side, Values.value ), + Plain ? given( Values.cross, Values.value ) : 0.0 }; if( Path == 1 ) { - auto const branch = [ &Values ]( float const Part, double const Straight ) -> double { - return Part >= 0.f ? Part : Values.branch >= 0.f ? Values.branch : Straight; }; - result.up = branch( Values.branchup, result.up ); - result.side = branch( Values.branchside, result.side ); + result.up = given( Values.branchup, given( Values.branch, result.up ) ); + result.side = given( Values.branchside, given( Values.branch, result.side ) ); } return result; } @@ -160,8 +163,9 @@ wave_set make_waves( double const Turn, std::array const & wave_set result {}; for( std::size_t i = 0; i < Count; ++i ) { auto const number { glm::two_pi() / Lengths[ i ] }; - auto const heading { Turn + golden * i }; - result[ i ] = { number * std::cos( heading ), number * std::sin( heading ), golden * ( i + 1 ) * ( 1.0 + 3.0 * Turn ), Shares[ i ] }; + auto const place { static_cast( i ) }; + auto const heading { Turn + golden * place }; + result[ i ] = { number * std::cos( heading ), number * std::sin( heading ), golden * ( place + 1.0 ) * ( 1.0 + 3.0 * Turn ), Shares[ i ] }; } return result; } @@ -246,7 +250,10 @@ inline glm::dvec3 across_of( glm::dvec3 const &Direction, glm::dvec3 const &Chor // 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; + if( Switch == Theirswitch ) { + return 0.5f * ( Own + Their ); + } + return Switch ? Their : Own; } // each component the same way