17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-05 19:29:11 +01:00

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.
This commit is contained in:
maj00r
2026-10-31 18:58:20 +01:00
parent 798e8d6a84
commit ec2d226688
7 changed files with 110 additions and 92 deletions

View File

@@ -1162,8 +1162,8 @@ void editor_mode::render_path_parameters(TTrack &Track)
finish_edit(rebuild::parameters); finish_edit(rebuild::parameters);
if ((track->eType == tt_Normal || track->eType == tt_Switch) && (track->iCategoryFlag & 15) == 1) 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")) if (ImGui::InputFloat(STR_C("Unevenness (mm)"), &track->m_uneven.value, 0.0f, 0.0f, "%.1f"))
track->m_uneven = std::clamp(track->m_uneven, 0.f, track_uneven::most); 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"); 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); finish_edit(rebuild::parameters);
struct component struct component
@@ -1171,11 +1171,11 @@ void editor_mode::render_path_parameters(TTrack &Track)
char const *label; char const *label;
float *value; 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; 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; continue;
if (ImGui::InputFloat(STR_C(part.label), part.value, 0.0f, 0.0f, "%.1f")) 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); *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 (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")) if (ImGui::InputFloat(STR_C("Dip at rail joints (mm)"), &track->m_uneven.dip, 0.0f, 0.0f, "%.1f"))
track->m_jointdip = track->m_jointdip < 0.f ? -1.f : std::min(track->m_jointdip, track_uneven::most); 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"); 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); finish_edit(rebuild::parameters);
} }

View File

@@ -153,13 +153,6 @@ editor_track::state editor_track::capture(TTrack const &Track)
result.isolated.push_back(isolated->asName); result.isolated.push_back(isolated->asName);
result.overhead = Track.fOverhead; result.overhead = Track.fOverhead;
result.uneven = Track.m_uneven; 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.sleepers = Track.m_sleeper_enabled;
result.sleeper_frequency = Track.m_sleeper_frequency; result.sleeper_frequency = Track.m_sleeper_frequency;
result.sleeper_model = Track.m_sleeper_model_name; 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); isolated(Track, State.isolated);
overhead(Track, State.overhead); overhead(Track, State.overhead);
Track.m_uneven = State.uneven; 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_enabled = State.sleepers;
Track.m_sleeper_frequency = State.sleeper_frequency; Track.m_sleeper_frequency = State.sleeper_frequency;
Track.m_sleeper_model_name = State.sleeper_model; 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 << ' '; 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()) if ((Style.iCategoryFlag & 15) == 1 && false == Style.m_profile1.first.empty())
Text << "railprofile " << Style.m_profile1.first << ' '; 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 // 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) for (auto const *isolated : Style.Isolated)
Text << "isolated " << isolated->asName << ' '; Text << "isolated " << isolated->asName << ' ';
@@ -2418,7 +2404,7 @@ TTrack *editor_track::create_switch(switch_template const &Template, std::vector
write_points(text, Paths[1]); write_points(text, Paths[1]);
if (auto const trackbed{ballast_of(Style)}; trackbed != null_handle) if (auto const trackbed{ballast_of(Style)}; trackbed != null_handle)
text << "trackbed " << material_name(trackbed) << ' '; text << "trackbed " << material_name(trackbed) << ' ';
write_ending(text, Style); write_ending(text, Style, true);
track = load_path(text.str(), Style, Name); track = load_path(text.str(), Style, Name);
} }
track->m_paths.assign(Paths.begin(), Paths.begin() + 2); 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); write_points(text, Path);
if (auto const speed{velocity(Style)}; speed > 0.0) if (auto const speed{velocity(Style)}; speed > 0.0)
text << "velocity " << speed << ' '; text << "velocity " << speed << ' ';
write_ending(text, Style); write_ending(text, Style, false);
auto *track{load_path(text.str(), Style)}; auto *track{load_path(text.str(), Style)};
track->m_paths = {Path}; track->m_paths = {Path};
return track; return track;

View File

@@ -17,6 +17,7 @@ http:
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include "world/Segment.h" #include "world/Segment.h"
#include "world/TrackUneven.h"
#include "utilities/Classes.h" #include "utilities/Classes.h"
class TTrack; class TTrack;
@@ -55,14 +56,7 @@ class editor_track
float texslope{0.9f}; float texslope{0.9f};
std::vector<std::string> isolated; std::vector<std::string> isolated;
float overhead{-1.f}; float overhead{-1.f};
float uneven{0.f}; track_uneven::attribute uneven;
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};
bool sleepers{false}; bool sleepers{false};
float sleeper_frequency{0.6f}; float sleeper_frequency{0.6f};
std::string sleeper_model; std::string sleeper_model;

View File

@@ -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"); 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") 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})}; auto const least{track_uneven::least({14.0, 9.0, 0.0}, {7.0, 35.0, 3.0})};

View File

@@ -642,10 +642,10 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
} }
} }
else if( str == "uneven" ) { else if( str == "uneven" ) {
// uneven <mm>[;<mm>...]: see read_uneven and TrackUneven.h. The semicolons stay in the token // uneven <mm>[;<mm>...]: see TrackUneven.h. The semicolons stay in the token
auto const values { parser->getToken<std::string>( false, "\n\r\t " ) }; auto const values { parser->getToken<std::string>( false, "\n\r\t " ) };
if( ( eType == tt_Normal || eType == tt_Switch ) && iCategoryFlag == 1 ) { 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" ) { 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). // 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<float, 6> { m_uneven, m_unevenbranch, m_unevenup, m_unevenside, m_unevenbranchup, m_unevenbranchside } :
std::array<float, 6> { 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 TTrack::uneven_declared( int const Path ) const {
track_uneven::amounts result { return track_uneven::declared( m_uneven, eType == tt_Normal, Path );
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;
} }
bool TTrack::is_uneven() const { bool TTrack::is_uneven() const {
@@ -873,7 +833,7 @@ double TTrack::joint_depth() const {
if( eType != tt_Normal ) { if( eType != tt_Normal ) {
return 0.0; 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 { 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() ) { && false == m_profile1.first.empty() ) {
Output << "railprofile " << m_profile1.first << ' '; Output << "railprofile " << m_profile1.first << ' ';
} }
Output << uneven_text(); Output << track_uneven::write_attribute( m_uneven, eType == tt_Switch );
if( false == m_friction.first.empty() ) { if( false == m_friction.first.empty() ) {
Output << "friction " << m_friction.first << ' '; Output << "friction " << m_friction.first << ' ';
} }

View File

@@ -239,16 +239,7 @@ public:
TEnvironmentType eEnvironment = e_flat; // dźwięk i oświetlenie TEnvironmentType eEnvironment = e_flat; // dźwięk i oświetlenie
int iAction = 0; // czy modyfikowany eventami (specjalna obsługa przy skanowaniu) 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 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 track_uneven::attribute m_uneven; // 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
private: private:
double fVelocity = -1.0; // ograniczenie prędkości // prędkość dla AI (powyżej rośnie prawdopowobieństwo wykolejenia) double fVelocity = -1.0; // ograniczenie prędkości // prędkość dla AI (powyżej rośnie prawdopowobieństwo wykolejenia)
public: public:
@@ -350,10 +341,6 @@ public:
void VelocitySet(float v); void VelocitySet(float v);
double VelocityGet(); double VelocityGet();
float Friction() const; 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 // the unevenness the track declares, on a path of a switch
track_uneven::amounts uneven_declared( int const Path = 0 ) const; track_uneven::amounts uneven_declared( int const Path = 0 ) const;
bool is_uneven() const; bool is_uneven() const;

View File

@@ -87,6 +87,68 @@ std::string write_fields( std::array<float, Count> const &Fields ) {
return text.str(); 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<float, 6> { Values.value, Values.branch, Values.up, Values.side, Values.branchup, Values.branchside } :
std::array<float, 6> { 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 <std::size_t Count> template <std::size_t Count>
using wave_set = std::array<wave, Count>; using wave_set = std::array<wave, Count>;