From ec2d22668876c358723bc0925436864eac10aa5e Mon Sep 17 00:00:00 2001 From: maj00r Date: Sat, 31 Oct 2026 18:58:20 +0100 Subject: [PATCH] fix(editor): the uneven attribute is written in the layout of the entry it goes into Its numbers go by position, and the positions differ between a plain track and a switch. A switch made in the style of a plain track wrote them in the layout of the plain one, so they were read back as other values: the up and down part as the value of the path p3-p4, and so on. The values are now one struct with pure functions to read, write and resolve them, which replaces eight fields and which the unit tests can reach. --- application/editormodetrack.cpp | 14 ++++---- editor/editorTrack.cpp | 22 +++--------- editor/editorTrack.hpp | 10 ++---- tests/unit/test_track_uneven.cpp | 29 +++++++++++++++ world/Track.cpp | 50 +++----------------------- world/Track.h | 15 +------- world/TrackUneven.h | 62 ++++++++++++++++++++++++++++++++ 7 files changed, 110 insertions(+), 92 deletions(-) diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 717a3657..4e313bfd 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1162,8 +1162,8 @@ void editor_mode::render_path_parameters(TTrack &Track) finish_edit(rebuild::parameters); if ((track->eType == tt_Normal || track->eType == tt_Switch) && (track->iCategoryFlag & 15) == 1) { - if (ImGui::InputFloat(STR_C("Unevenness (mm)"), &track->m_uneven, 0.0f, 0.0f, "%.1f")) - track->m_uneven = std::clamp(track->m_uneven, 0.f, track_uneven::most); + if (ImGui::InputFloat(STR_C("Unevenness (mm)"), &track->m_uneven.value, 0.0f, 0.0f, "%.1f")) + 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 @@ -1171,11 +1171,11 @@ void editor_mode::render_path_parameters(TTrack &Track) char const *label; float *value; }; - for (auto const &part : {component{"Up and down (mm)", &track->m_unevenup}, component{"Sideways (mm)", &track->m_unevenside}, component{"Cross level (mm)", &track->m_unevencross}, component{"Diverging path (mm)", &track->m_unevenbranch}, component{"Diverging, up and down (mm)", &track->m_unevenbranchup}, component{"Diverging, sideways (mm)", &track->m_unevenbranchside}}) + 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}}) { - if (part.value == &track->m_unevencross && track->eType != tt_Normal) + if (part.value == &track->m_uneven.cross && track->eType != tt_Normal) continue; - if ((part.value == &track->m_unevenbranch || part.value == &track->m_unevenbranchup || part.value == &track->m_unevenbranchside) && track->eType != tt_Switch) + if ((part.value == &track->m_uneven.branch || part.value == &track->m_uneven.branchup || part.value == &track->m_uneven.branchside) && track->eType != tt_Switch) 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); @@ -1185,8 +1185,8 @@ void editor_mode::render_path_parameters(TTrack &Track) } if (track->eType == tt_Normal && (track->iCategoryFlag & 15) == 1) { - if (ImGui::InputFloat(STR_C("Dip at rail joints (mm)"), &track->m_jointdip, 0.0f, 0.0f, "%.1f")) - track->m_jointdip = track->m_jointdip < 0.f ? -1.f : std::min(track->m_jointdip, track_uneven::most); + if (ImGui::InputFloat(STR_C("Dip at rail joints (mm)"), &track->m_uneven.dip, 0.0f, 0.0f, "%.1f")) + track->m_uneven.dip = track->m_uneven.dip < 0.f ? -1.f : std::min(track->m_uneven.dip, track_uneven::most); item_tooltip("Depth of the dip at each rail joint, every sound distance. Works with unevenness at 0. Negative: 0.4 of the unevenness"); finish_edit(rebuild::parameters); } diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index da426007..b7050174 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -153,13 +153,6 @@ editor_track::state editor_track::capture(TTrack const &Track) result.isolated.push_back(isolated->asName); result.overhead = Track.fOverhead; result.uneven = Track.m_uneven; - result.unevenup = Track.m_unevenup; - result.unevenside = Track.m_unevenside; - result.unevencross = Track.m_unevencross; - result.unevenbranch = Track.m_unevenbranch; - result.unevenbranchup = Track.m_unevenbranchup; - result.unevenbranchside = Track.m_unevenbranchside; - result.jointdip = Track.m_jointdip; result.sleepers = Track.m_sleeper_enabled; result.sleeper_frequency = Track.m_sleeper_frequency; result.sleeper_model = Track.m_sleeper_model_name; @@ -259,13 +252,6 @@ void editor_track::apply(TTrack &Track, state const &State) isolated(Track, State.isolated); overhead(Track, State.overhead); Track.m_uneven = State.uneven; - Track.m_unevenup = State.unevenup; - Track.m_unevenside = State.unevenside; - Track.m_unevencross = State.unevencross; - Track.m_unevenbranch = State.unevenbranch; - Track.m_unevenbranchup = State.unevenbranchup; - Track.m_unevenbranchside = State.unevenbranchside; - Track.m_jointdip = State.jointdip; Track.m_sleeper_enabled = State.sleepers; Track.m_sleeper_frequency = State.sleeper_frequency; Track.m_sleeper_model_name = State.sleeper_model; @@ -2362,11 +2348,11 @@ void write_points(std::ostream &Text, segment_data const &Path) Text << start.x << ' ' << start.y << ' ' << start.z << ' ' << Path.rolls[0] << ' ' << control1.x << ' ' << control1.y << ' ' << control1.z << ' ' << control2.x << ' ' << control2.y << ' ' << control2.z << ' ' << end.x << ' ' << end.y << ' ' << end.z << ' ' << Path.rolls[1] << ' ' << Path.radius << ' '; } -void write_ending(std::ostream &Text, TTrack const &Style) +void write_ending(std::ostream &Text, TTrack const &Style, bool const Switch) { if ((Style.iCategoryFlag & 15) == 1 && false == Style.m_profile1.first.empty()) Text << "railprofile " << Style.m_profile1.first << ' '; - Text << Style.uneven_text(); + Text << track_uneven::write_attribute(Style.m_uneven, Switch); // a piece of the track made from another one stays in its track circuit and under the same overhead line for (auto const *isolated : Style.Isolated) Text << "isolated " << isolated->asName << ' '; @@ -2418,7 +2404,7 @@ TTrack *editor_track::create_switch(switch_template const &Template, std::vector write_points(text, Paths[1]); if (auto const trackbed{ballast_of(Style)}; trackbed != null_handle) text << "trackbed " << material_name(trackbed) << ' '; - write_ending(text, Style); + write_ending(text, Style, true); track = load_path(text.str(), Style, Name); } track->m_paths.assign(Paths.begin(), Paths.begin() + 2); @@ -2679,7 +2665,7 @@ TTrack *editor_track::create_path(TTrack const &Style, segment_data const &Path) write_points(text, Path); if (auto const speed{velocity(Style)}; speed > 0.0) text << "velocity " << speed << ' '; - write_ending(text, Style); + write_ending(text, Style, false); auto *track{load_path(text.str(), Style)}; track->m_paths = {Path}; return track; diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index 61858336..14f9e9a4 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -17,6 +17,7 @@ http: #include #include "world/Segment.h" +#include "world/TrackUneven.h" #include "utilities/Classes.h" class TTrack; @@ -55,14 +56,7 @@ class editor_track float texslope{0.9f}; std::vector isolated; float overhead{-1.f}; - float uneven{0.f}; - float unevenup{-1.f}; - float unevenside{-1.f}; - float unevencross{-1.f}; - float unevenbranch{-1.f}; - float unevenbranchup{-1.f}; - float unevenbranchside{-1.f}; - float jointdip{-1.f}; + track_uneven::attribute uneven; bool sleepers{false}; float sleeper_frequency{0.6f}; std::string sleeper_model; diff --git a/tests/unit/test_track_uneven.cpp b/tests/unit/test_track_uneven.cpp index 22970fa6..247f9cbf 100644 --- a/tests/unit/test_track_uneven.cpp +++ b/tests/unit/test_track_uneven.cpp @@ -165,6 +165,35 @@ TEST_CASE("uneven track: all the values in one attribute") CHECK(track_uneven::write_fields<6>({18.f, -1.f, -1.f, -1.f, -1.f, -1.f}) == "18"); } +TEST_CASE("uneven track: the attribute goes by the entry it's written into") +{ + auto const plain{track_uneven::read_attribute("18;22;19;25;6", false)}; + CHECK(track_uneven::write_attribute(plain, false) == "uneven 18;22;19;25;6 "); + // a switch made in the style of a plain track: no path p3-p4 of its own, the components in their places + CHECK(track_uneven::write_attribute(plain, true) == "uneven 18;;22;19 "); + auto const made{track_uneven::read_attribute("18;;22;19", true)}; + CHECK(track_uneven::declared(made, false, 0).up == doctest::Approx(22.0)); + CHECK(track_uneven::declared(made, false, 0).side == doctest::Approx(19.0)); + CHECK(track_uneven::declared(made, false, 1).up == doctest::Approx(22.0)); + CHECK(track_uneven::declared(made, false, 0).cross == doctest::Approx(0.0)); + // a switch with its own values, and a plain track in its style + auto const points{track_uneven::read_attribute("14;35;16;17", true)}; + CHECK(track_uneven::write_attribute(points, true) == "uneven 14;35;16;17 "); + CHECK(track_uneven::declared(points, false, 1).up == doctest::Approx(35.0)); + CHECK(track_uneven::declared(points, false, 1).side == doctest::Approx(35.0)); + CHECK(track_uneven::write_attribute(points, false) == "uneven 14;16;17 "); + // a plain track: the value stands in for what's left out, only there is the cross level + auto const track{track_uneven::read_attribute("18;;30", false)}; + CHECK(track_uneven::declared(track, true, 0).up == doctest::Approx(18.0)); + CHECK(track_uneven::declared(track, true, 0).side == doctest::Approx(30.0)); + CHECK(track_uneven::declared(track, true, 0).cross == doctest::Approx(18.0)); + // nothing given, nothing written + CHECK(track_uneven::write_attribute({}, false).empty()); + CHECK(track_uneven::write_attribute({}, true).empty()); + // a value of 0 stays when there's more after it + CHECK(track_uneven::write_attribute(track_uneven::read_attribute("0;;;;6", false), false) == "uneven 0;;;;6 "); +} + 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/world/Track.cpp b/world/Track.cpp index 8359f78a..31b5f5c5 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -642,10 +642,10 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) } } else if( str == "uneven" ) { - // uneven [;...]: see read_uneven and TrackUneven.h. The semicolons stay in the token + // uneven [;...]: see TrackUneven.h. The semicolons stay in the token auto const values { parser->getToken( false, "\n\r\t " ) }; if( ( eType == tt_Normal || eType == tt_Switch ) && iCategoryFlag == 1 ) { - read_uneven( values ); + m_uneven = track_uneven::read_attribute( values, eType == tt_Switch ); } } else if( str == "friction" ) { @@ -706,49 +706,9 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) // to a stale origin and the renderer would draw sleepers in the wrong place). } -void TTrack::read_uneven( std::string const &Text ) { - - auto const fields { track_uneven::read_fields<6>( Text ) }; - m_uneven = std::max( fields[ 0 ], 0.f ); - if( eType == tt_Switch ) { - m_unevenbranch = fields[ 1 ]; - m_unevenup = fields[ 2 ]; - m_unevenside = fields[ 3 ]; - m_unevenbranchup = fields[ 4 ]; - m_unevenbranchside = fields[ 5 ]; - } - else { - m_unevenup = fields[ 1 ]; - m_unevenside = fields[ 2 ]; - m_unevencross = fields[ 3 ]; - m_jointdip = fields[ 4 ]; - } -} - -std::string TTrack::uneven_text() const { - - auto const fields { eType == tt_Switch ? - std::array { m_uneven, m_unevenbranch, m_unevenup, m_unevenside, m_unevenbranchup, m_unevenbranchside } : - std::array { m_uneven, m_unevenup, m_unevenside, m_unevencross, m_jointdip, -1.f } }; - if( m_uneven <= 0.f && std::all_of( fields.begin() + 1, fields.end(), []( float const Field ) { return Field < 0.f; } ) ) { - return {}; - } - return "uneven " + track_uneven::write_fields( fields ) + ' '; -} - track_uneven::amounts TTrack::uneven_declared( int const Path ) const { - track_uneven::amounts result { - m_unevenup >= 0.f ? m_unevenup : m_uneven, - m_unevenside >= 0.f ? m_unevenside : m_uneven, - eType != tt_Normal ? 0.f : m_unevencross >= 0.f ? m_unevencross : m_uneven }; - if( Path == 1 ) { - auto const branch = [ this ]( float const Part, double const Straight ) -> double { - return Part >= 0.f ? Part : m_unevenbranch >= 0.f ? m_unevenbranch : Straight; }; - result.up = branch( m_unevenbranchup, result.up ); - result.side = branch( m_unevenbranchside, result.side ); - } - return result; + return track_uneven::declared( m_uneven, eType == tt_Normal, Path ); } bool TTrack::is_uneven() const { @@ -873,7 +833,7 @@ double TTrack::joint_depth() const { if( eType != tt_Normal ) { return 0.0; } - return ( m_jointdip >= 0.f ? m_jointdip : track_uneven::dip_share * uneven_declared().up ); + return ( m_uneven.dip >= 0.f ? m_uneven.dip : track_uneven::dip_share * uneven_declared().up ); } double TTrack::joint_dip( double const Distance ) const { @@ -2782,7 +2742,7 @@ TTrack::export_as_text_( std::ostream &Output ) const { && false == m_profile1.first.empty() ) { Output << "railprofile " << m_profile1.first << ' '; } - Output << uneven_text(); + Output << track_uneven::write_attribute( m_uneven, eType == tt_Switch ); if( false == m_friction.first.empty() ) { Output << "friction " << m_friction.first << ' '; } diff --git a/world/Track.h b/world/Track.h index 1d366ebf..7d07f434 100644 --- a/world/Track.h +++ b/world/Track.h @@ -239,16 +239,7 @@ public: TEnvironmentType eEnvironment = e_flat; // dźwięk i oświetlenie int iAction = 0; // czy modyfikowany eventami (specjalna obsługa przy skanowaniu) float fOverhead = -1.0; // można normalnie pobierać prąd (0 dla jazdy bezprądowej po danym odcinku, >0-z opuszczonym i ograniczeniem prędkości) - float m_uneven { 0.f }; // mm, see TrackUneven.h; plain tracks and switches - // the components set apart from m_uneven, mm; negative: m_uneven. Cross level on plain tracks only - float m_unevenup { -1.f }; - float m_unevenside { -1.f }; - float m_unevencross { -1.f }; - // the diverging path of a switch, mm; negative: as the straight one - float m_unevenbranch { -1.f }; - float m_unevenbranchup { -1.f }; - float m_unevenbranchside { -1.f }; - float m_jointdip { -1.f }; // depth of the dip at rail joints, mm; negative: from m_uneven; plain tracks only + track_uneven::attribute m_uneven; // plain tracks and switches private: double fVelocity = -1.0; // ograniczenie prędkości // prędkość dla AI (powyżej rośnie prawdopowobieństwo wykolejenia) public: @@ -350,10 +341,6 @@ public: void VelocitySet(float v); double VelocityGet(); float Friction() const; - // the attribute of the entry. A plain track: the value;up and down;sideways;cross level;dip at the joints. - // A switch: path p1-p2;path p3-p4;up and down of the first;sideways of the first;up and down of the second;sideways of the second - void read_uneven( std::string const &Text ); - std::string uneven_text() 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; diff --git a/world/TrackUneven.h b/world/TrackUneven.h index f4a50eb7..b46ebf7a 100644 --- a/world/TrackUneven.h +++ b/world/TrackUneven.h @@ -87,6 +87,68 @@ std::string write_fields( std::array const &Fields ) { return text.str(); } +// the values of the attribute, mm, negative where not given. Cross level and the dip at joints for plain tracks, the path p3-p4 for switches +struct attribute { + float value { 0.f }; + float up { -1.f }; + float side { -1.f }; + float cross { -1.f }; + float dip { -1.f }; + float branch { -1.f }; + float branchup { -1.f }; + float branchside { -1.f }; +}; + +// 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 ) { + + auto const fields { read_fields<6>( Text ) }; + attribute result; + result.value = std::max( fields[ 0 ], 0.f ); + if( Switch ) { + result.branch = fields[ 1 ]; + result.up = fields[ 2 ]; + result.side = fields[ 3 ]; + result.branchup = fields[ 4 ]; + result.branchside = fields[ 5 ]; + } + else { + result.up = fields[ 1 ]; + result.side = fields[ 2 ]; + result.cross = fields[ 3 ]; + result.dip = fields[ 4 ]; + } + return result; +} + +// in the layout of the entry it goes into, which can be of another type than the track the values come from +inline std::string write_attribute( attribute const &Values, bool const Switch ) { + + auto const fields { Switch ? + std::array { Values.value, Values.branch, Values.up, Values.side, Values.branchup, Values.branchside } : + std::array { Values.value, Values.up, Values.side, Values.cross, Values.dip, -1.f } }; + if( Values.value <= 0.f && std::all_of( fields.begin() + 1, fields.end(), []( float const Field ) { return Field < 0.f; } ) ) { + return {}; + } + return "uneven " + write_fields( fields ) + ' '; +} + +// 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 ) { + + 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 }; + 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 ); + } + return result; +} + template using wave_set = std::array;