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

feat(track): all of the unevenness in one attribute, with values for each path of a switch

Usage, before endtrack, in mm, 0-100, separated by semicolons with no
spaces:

  a plain track:  uneven value;up;side;cross;dip
  a switch:       uneven p1-p2;p3-p4;up p1-p2;side p1-p2;up p3-p4;side p3-p4

  uneven 18           the value alone
  uneven 18;;;25;6    with its own cross level and dip at the joints
  uneven 0;;30        sideways alone
  uneven 14;35        a switch, 14 on the straight path, 35 on the diverging

What's left out takes the first number, the dip goes by the up and down
one, and a path p3-p4 with no numbers of its own takes those of p1-p2.
This replaces unevenup, unevenside, unevencross and jointdip. The fields
in the editor stay, track panel, section Ride and sound.

At the end they share, the paths of a switch keep to the lower value, and
each comes to its own value along the way. Joined switches take the lowest
of the same path, a path joined to another path the lower of the two, and
a plain track comes to the value of the path it meets. See
track_uneven_doc.md.
This commit is contained in:
maj00r
2026-10-31 18:33:35 +01:00
parent 88609a1657
commit 02abc7df0a
11 changed files with 266 additions and 96 deletions

View File

@@ -1171,10 +1171,12 @@ 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}}) 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}})
{ {
if (part.value == &track->m_unevencross && track->eType != tt_Normal) if (part.value == &track->m_unevencross && 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)
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);
item_tooltip("Set apart from the unevenness, mm. Negative: the unevenness"); item_tooltip("Set apart from the unevenness, mm. Negative: the unevenness");

View File

@@ -156,6 +156,9 @@ editor_track::state editor_track::capture(TTrack const &Track)
result.unevenup = Track.m_unevenup; result.unevenup = Track.m_unevenup;
result.unevenside = Track.m_unevenside; result.unevenside = Track.m_unevenside;
result.unevencross = Track.m_unevencross; 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.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;
@@ -259,6 +262,9 @@ void editor_track::apply(TTrack &Track, state const &State)
Track.m_unevenup = State.unevenup; Track.m_unevenup = State.unevenup;
Track.m_unevenside = State.unevenside; Track.m_unevenside = State.unevenside;
Track.m_unevencross = State.unevencross; 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_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;
@@ -2360,16 +2366,7 @@ void write_ending(std::ostream &Text, TTrack const &Style)
{ {
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 << ' ';
if (Style.m_uneven > 0.f) Text << Style.uneven_text();
Text << "uneven " << Style.m_uneven << ' ';
if (Style.m_unevenup >= 0.f)
Text << "unevenup " << Style.m_unevenup << ' ';
if (Style.m_unevenside >= 0.f)
Text << "unevenside " << Style.m_unevenside << ' ';
if (Style.m_unevencross >= 0.f)
Text << "unevencross " << Style.m_unevencross << ' ';
if (Style.m_jointdip >= 0.f)
Text << "jointdip " << Style.m_jointdip << ' ';
// 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 << ' ';

View File

@@ -59,6 +59,9 @@ class editor_track
float unevenup{-1.f}; float unevenup{-1.f};
float unevenside{-1.f}; float unevenside{-1.f};
float unevencross{-1.f}; float unevencross{-1.f};
float unevenbranch{-1.f};
float unevenbranchup{-1.f};
float unevenbranchside{-1.f};
float jointdip{-1.f}; float jointdip{-1.f};
bool sleepers{false}; bool sleepers{false};
float sleeper_frequency{0.6f}; float sleeper_frequency{0.6f};

View File

@@ -876,6 +876,15 @@ msgstr "W poziomie (mm)"
msgid "Cross level (mm)" msgid "Cross level (mm)"
msgstr "Przechyłka (mm)" msgstr "Przechyłka (mm)"
msgid "Diverging path (mm)"
msgstr "Tor na bok (mm)"
msgid "Diverging, up and down (mm)"
msgstr "Na bok, w pionie (mm)"
msgid "Diverging, sideways (mm)"
msgstr "Na bok, w poziomie (mm)"
msgid "Set apart from the unevenness, mm. Negative: the unevenness" msgid "Set apart from the unevenness, mm. Negative: the unevenness"
msgstr "Osobno od nierówności, mm. Ujemna: wartość nierówności" msgstr "Osobno od nierówności, mm. Ujemna: wartość nierówności"

View File

@@ -140,6 +140,43 @@ TEST_CASE("uneven track: the field stays within its bound")
CHECK(highest > 0.5); CHECK(highest > 0.5);
} }
TEST_CASE("uneven track: all the values in one attribute")
{
auto const one{track_uneven::read_fields<6>("18")};
CHECK(one[0] == doctest::Approx(18.f));
CHECK(one[1] < 0.f);
auto const both{track_uneven::read_fields<6>("14;35")};
CHECK(both[0] == doctest::Approx(14.f));
CHECK(both[1] == doctest::Approx(35.f));
CHECK(both[2] < 0.f);
auto const gaps{track_uneven::read_fields<6>("18.5;;;25;6")};
CHECK(gaps[0] == doctest::Approx(18.5f));
CHECK(gaps[1] < 0.f);
CHECK(gaps[2] < 0.f);
CHECK(gaps[3] == doctest::Approx(25.f));
CHECK(gaps[4] == doctest::Approx(6.f));
auto const bounds{track_uneven::read_fields<6>("500;-3;x")};
CHECK(bounds[0] == doctest::Approx(track_uneven::most));
CHECK(bounds[1] == doctest::Approx(0.f));
CHECK(bounds[2] < 0.f);
CHECK(track_uneven::write_fields<6>({14.f, 35.f, -1.f, -1.f, -1.f, -1.f}) == "14;35");
CHECK(track_uneven::write_fields<6>({18.5f, -1.f, -1.f, 25.f, 6.f, -1.f}) == "18.5;;;25;6");
CHECK(track_uneven::write_fields<6>({0.f, -1.f, 30.f, -1.f, -1.f, -1.f}) == "0;;30");
CHECK(track_uneven::write_fields<6>({18.f, -1.f, -1.f, -1.f, -1.f, -1.f}) == "18");
}
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})};
CHECK(least.up == doctest::Approx(7.0));
CHECK(least.side == doctest::Approx(9.0));
CHECK(least.cross == doctest::Approx(0.0));
// the diverging path of a switch: from the common end at the lowest to its own value at the far end
double const length{30.0};
CHECK(track_uneven::along(35.0, 7.0, 35.0, 0.0, length) == doctest::Approx(7.0));
CHECK(track_uneven::along(35.0, 7.0, 35.0, length, length) == doctest::Approx(35.0));
}
TEST_CASE("uneven track: the value along a track meets its neighbours exactly") TEST_CASE("uneven track: the value along a track meets its neighbours exactly")
{ {
double const length{500.0}; double const length{500.0};

View File

@@ -1,6 +1,6 @@
# Nierówności toru # Nierówności toru
Opcjonalne atrybuty wpisu toru, przed `endtrack`: Opcjonalny atrybut wpisu toru, przed `endtrack`:
``` ```
node -1 0 tor1 track normal 100.0 1.435 0.15 25.0 20 0 flat vis node -1 0 tor1 track normal 100.0 1.435 0.15 25.0 20 0 flat vis
@@ -10,21 +10,22 @@ rail_screw_used1 6 1435mm/tpbps-new2 0.2 0.5 1.1
0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.2 100.0 0.0 0.0 0.2 100.0 0.0
0.0 0.0
uneven 18 uneven 18;;;25;6
unevencross 25
jointdip 6
endtrack endtrack
``` ```
| atrybut | dotyczy | opis | Liczby w mm, 0-100, rozdzielone średnikiem, bez spacji:
|-------------------|-----------------------|---------------------------------------------------------------------------------------|
| `uneven <mm>` | `normal`, `switch` | największa odchyłka toku szynowego na cięciwie 10 m; 0-100, domyślnie 0 |
| `unevenup <mm>` | `normal`, `switch` | składowa w pionie, osobno od `uneven` |
| `unevenside <mm>` | `normal`, `switch` | składowa w poziomie, osobno od `uneven` |
| `unevencross <mm>`| `normal` | składowa przechyłki, osobno od `uneven`; rozjazd jej nie ma |
| `jointdip <mm>` | `normal` | głębokość zapadnięcia na styku szyn; 0-100, domyślnie 0,4 składowej w pionie |
Składowa niepodana przyjmuje wartość `uneven`. Na przykład sam bok to `unevenside 30`, a same zapadnięcia na stykach to `jointdip 6`. | pozycja | tor `normal` | rozjazd `switch` |
|---------|--------------------------------------------------|-----------------------------|
| 1 | największa odchyłka toku na cięciwie 10 m | to samo, droga p1-p2 |
| 2 | w pionie | droga p3-p4 |
| 3 | w poziomie | droga p1-p2, w pionie |
| 4 | przechyłka | droga p1-p2, w poziomie |
| 5 | zapadnięcie na styku, domyślnie 0,4 pionu | droga p3-p4, w pionie |
| 6 | | droga p3-p4, w poziomie |
Pozycję pominiętą zastępuje pierwsza liczba, a zapadnięcie liczy się z pionu. Końcowe można opuścić. Sam bok to `uneven 0;;30`, a same zapadnięcia na stykach to `uneven 0;;;;6`. Rozjazd bez składowych na drodze p3-p4 przyjmuje wartości z drogi p1-p2. Rozjazd na 100 km/h na wprost i 40 km/h na bok to na przykład `uneven 14;35`.
Wszystkie są też w edytorze, w panelu toru, w sekcji „Ride and sound”. Na drogach, rzekach i obrotnicach są ignorowane. Wszystkie są też w edytorze, w panelu toru, w sekcji „Ride and sound”. Na drogach, rzekach i obrotnicach są ignorowane.
@@ -56,7 +57,7 @@ Nierówność to pole przesunięć zależne od położenia na mapie, złożone z
Każda składowa łączy się osobno: 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. - **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. - **Rozjazd:** każda droga ma swoją wartość, a na wspólnym końcu obu obowiązuje mniejsza z nich. Rozjazdy złączone bezpośrednio przyjmują najmniejszą z tej samej drogi, a na styku dwóch dróg mniejszą z obu. Dołączony tor dochodzi do wartości rozjazdu na końcu, do którego dochodzi, 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. - **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. 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. - **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.

View File

@@ -381,7 +381,7 @@ void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm::
// direction and slope stay as designed: the physics takes the curve radius and the grade from them // 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 ) }; 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 ) }; glm::dvec3 const across { -std::cos( rotation.z ), 0.0, -std::sin( rotation.z ) };
auto const amount { pOwner->uneven_at( fDistance ) }; auto const amount { pOwner->uneven_at( fDistance, m_path ) };
rotation.x += static_cast<float>( track_uneven::stray_per_mm * ( rotation.x += static_cast<float>( track_uneven::stray_per_mm * (
amount.up * track_uneven::roll( position, across, gauge ) amount.up * track_uneven::roll( position, across, gauge )
+ amount.cross * track_uneven::level( position, across ) / gauge ) ); + amount.cross * track_uneven::level( position, across ) / gauge ) );
@@ -389,7 +389,7 @@ void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm::
} }
glm::dvec3 TSegment::stray( glm::dvec3 const &Point, double const Distance ) const { glm::dvec3 TSegment::stray( glm::dvec3 const &Point, double const Distance ) const {
auto const amount { pOwner->uneven_at( Distance ) }; auto const amount { pOwner->uneven_at( Distance, m_path ) };
auto const field { track_uneven::field( Point ) }; 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 *= track_uneven::stray_per_mm;
@@ -416,7 +416,7 @@ 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 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 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 ) }; auto const share { pOwner->uneven_bed_share( Position.y - Vertex.position.y ) };
Vertex.position.y -= static_cast<float>( share * track_uneven::stray_per_mm * pOwner->uneven_at( Distance ).cross * track_uneven::level( Origin + glm::dvec3 { Position }, glm::dvec3 { Parallel } ) * across / gauge ); Vertex.position.y -= static_cast<float>( 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 ) }; Vertex.position += glm::vec3 { share * stray( Origin + glm::dvec3 { Vertex.position }, Distance ) };
} }

View File

@@ -50,9 +50,10 @@ class TSegment
float fRoll1 { 0.f }; float fRoll1 { 0.f };
float fRoll2 { 0.f }; // przechyłka na końcach 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 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 // where the path meets the next one: the middle of their ends and their common vector across
std::optional<std::pair<glm::dvec3, glm::dvec3>> m_joints[ 2 ]; std::optional<std::pair<glm::dvec3, glm::dvec3>> m_joints[ 2 ];
bool m_uneven { false }; // see TrackUneven.h
double fLength { -1.0 }; // długość policzona double fLength { -1.0 }; // długość policzona
std::vector<double> fTsBuffer; // wartości parametru krzywej t dla równych odcinków s std::vector<double> fTsBuffer; // wartości parametru krzywej t dla równych odcinków s
std::vector<double> fTsSlope; // nachylenie dt/du (= dt/ds * fStep) w tych samych punktach std::vector<double> fTsSlope; // nachylenie dt/du (= dt/ds * fStep) w tych samych punktach
@@ -120,8 +121,9 @@ public:
void RaPositionGet(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const; void RaPositionGet(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const;
// call before Init, it sets the density of the geometry // call before Init, it sets the density of the geometry
void void
set_uneven( bool const Uneven ) { set_uneven( bool const Uneven, int const Path = 0 ) {
m_uneven = Uneven; } m_uneven = Uneven;
m_path = Path; }
// a point of the path and the unit vector across it in the plan // a point of the path and the unit vector across it in the plan
void void
plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3 &Across ) const; plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3 &Across ) const;

View File

@@ -49,6 +49,9 @@ TTrack::profiles_array TTrack::m_profiles;
TTrack::profiles_map TTrack::m_profilesmap; TTrack::profiles_map TTrack::m_profilesmap;
int TTrack::s_layoutstamp { 0 }; int TTrack::s_layoutstamp { 0 };
// how near an end of a path counts as being at a point, m
static double const switch_end_reach { 0.3 };
TSwitchExtension::TSwitchExtension(TTrack *owner, int const what) TSwitchExtension::TSwitchExtension(TTrack *owner, int const what)
{ // na początku wszystko puste { // na początku wszystko puste
pNexts[0] = nullptr; // wskaźniki do kolejnych odcinków ruchu pNexts[0] = nullptr; // wskaźniki do kolejnych odcinków ruchu
@@ -639,24 +642,10 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
} }
} }
else if( str == "uneven" ) { else if( str == "uneven" ) {
// uneven <mm>: see TrackUneven.h // uneven <mm>[;<mm>...]: see read_uneven and TrackUneven.h. The semicolons stay in the token
auto const uneven { parser->getToken<float>() }; 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 ) {
m_uneven = std::clamp( uneven, 0.f, track_uneven::most ); read_uneven( values );
}
}
else if( str == "unevenup" || str == "unevenside" || str == "unevencross" ) {
// the components of uneven set apart, mm
auto const value { parser->getToken<float>() };
if( ( eType == tt_Normal || eType == tt_Switch ) && iCategoryFlag == 1 ) {
( str == "unevenup" ? m_unevenup : str == "unevenside" ? m_unevenside : m_unevencross ) = std::clamp( value, 0.f, track_uneven::most );
}
}
else if( str == "jointdip" ) {
// jointdip <mm>: depth of the dip at rail joints, with or without uneven
auto const jointdip { parser->getToken<float>() };
if( eType == tt_Normal && iCategoryFlag == 1 ) {
m_jointdip = std::clamp( jointdip, 0.f, track_uneven::most );
} }
} }
else if( str == "friction" ) { else if( str == "friction" ) {
@@ -717,18 +706,56 @@ 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).
} }
track_uneven::amounts TTrack::uneven_declared() const { void TTrack::read_uneven( std::string const &Text ) {
return { 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 result {
m_unevenup >= 0.f ? m_unevenup : m_uneven, m_unevenup >= 0.f ? m_unevenup : m_uneven,
m_unevenside >= 0.f ? m_unevenside : m_uneven, m_unevenside >= 0.f ? m_unevenside : m_uneven,
eType != tt_Normal ? 0.f : m_unevencross >= 0.f ? m_unevencross : 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 {
auto const declared { uneven_declared() }; auto const any = []( track_uneven::amounts const &Amount ) {
return declared.up > 0.0 || declared.side > 0.0 || declared.cross > 0.0 || joint_depth() > 0.0; return Amount.up > 0.0 || Amount.side > 0.0 || Amount.cross > 0.0; };
return any( uneven_declared( 0 ) ) || any( uneven_declared( 1 ) ) || joint_depth() > 0.0;
} }
double TTrack::uneven_bed_share( double const Depth ) const { double TTrack::uneven_bed_share( double const Depth ) const {
@@ -736,61 +763,109 @@ 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 ); 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 ) const { track_uneven::amounts TTrack::uneven_at( double const Distance, int const Path ) const {
auto const along = [ Distance ]( track_uneven::amounts const &Own, track_uneven::amounts const &Start, track_uneven::amounts const &End, double const Length ) -> track_uneven::amounts {
return {
track_uneven::along( Own.up, Start.up, End.up, Distance, Length ),
track_uneven::along( Own.side, Start.side, End.side, Distance, Length ),
track_uneven::along( Own.cross, Start.cross, End.cross, Distance, Length ) }; };
if( eType == tt_Switch ) { if( eType == tt_Switch ) {
return switch_uneven(); auto const &stray { switch_uneven() };
return along( stray.paths[ Path ], stray.ends[ Path ][ 0 ], stray.ends[ Path ][ 1 ], SwitchExtension->Segments[ Path ]->GetLength() );
} }
auto const own { uneven_declared() }; auto const own { uneven_declared() };
auto const atend = [&]( TTrack const *Other ) -> track_uneven::amounts { auto const atend = [&]( TTrack const *Other, glm::dvec3 const &Point ) -> track_uneven::amounts {
if( Other == nullptr ) { if( Other == nullptr ) {
return {}; return {};
} }
if( Other->eType == tt_Switch ) { if( Other->eType == tt_Switch ) {
return Other->switch_uneven(); return Other->switch_uneven_at( Point );
} }
auto const theirs { Other->uneven_declared() }; return track_uneven::least( own, Other->uneven_declared() ); };
return { std::min( own.up, theirs.up ), std::min( own.side, theirs.side ), std::min( own.cross, theirs.cross ) }; }; return along( own, atend( trPrev, Segment->FastGetPoint_0() ), atend( trNext, Segment->FastGetPoint_1() ), Length() );
auto const start { atend( trPrev ) };
auto const end { atend( trNext ) };
return {
track_uneven::along( own.up, start.up, end.up, Distance, Length() ),
track_uneven::along( own.side, start.side, end.side, Distance, Length() ),
track_uneven::along( own.cross, start.cross, end.cross, Distance, Length() ) };
} }
track_uneven::amounts TTrack::switch_uneven() const { TTrack::switch_stray const &TTrack::switch_uneven() const {
if( m_switchstray.stamp == s_layoutstamp ) { if( m_switchstray.stamp == s_layoutstamp ) {
return m_switchstray.value; return m_switchstray;
} }
std::vector<TTrack const *> group { this }; std::vector<TTrack const *> group { this };
auto least { uneven_declared() }; std::array<track_uneven::amounts, 2> least { uneven_declared( 0 ), uneven_declared( 1 ) };
for( std::size_t index = 0; index < group.size(); ++index ) { for( std::size_t index = 0; index < group.size(); ++index ) {
auto const &extension { *group[ index ]->SwitchExtension }; auto const &extension { *group[ index ]->SwitchExtension };
for( auto const *other : { extension.pPrevs[ 0 ], extension.pPrevs[ 1 ], extension.pNexts[ 0 ], extension.pNexts[ 1 ] } ) { for( auto const *other : { extension.pPrevs[ 0 ], extension.pPrevs[ 1 ], extension.pNexts[ 0 ], extension.pNexts[ 1 ] } ) {
if( other == nullptr ) { if( other == nullptr ) {
continue; continue;
} }
auto const theirs { other->uneven_declared() };
if( other->eType == tt_Switch && other->SwitchExtension ) { if( other->eType == tt_Switch && other->SwitchExtension ) {
least.up = std::min( least.up, theirs.up ); least[ 0 ] = track_uneven::least( least[ 0 ], other->uneven_declared( 0 ) );
least.side = std::min( least.side, theirs.side ); least[ 1 ] = track_uneven::least( least[ 1 ], other->uneven_declared( 1 ) );
if( std::find( group.begin(), group.end(), other ) == group.end() ) { if( std::find( group.begin(), group.end(), other ) == group.end() ) {
group.push_back( other ); group.push_back( other );
} }
} }
else { else {
// a track with none of a component keeps to its design in it, any other comes to the value of the switch // a track with none of a component keeps to its design in it, any other comes to the value of the switch
if( theirs.up <= 0.0 ) { least.up = 0.0; } auto const theirs { other->uneven_declared() };
if( theirs.side <= 0.0 ) { least.side = 0.0; } for( auto &path : least ) {
if( theirs.up <= 0.0 ) { path.up = 0.0; }
if( theirs.side <= 0.0 ) { path.side = 0.0; }
}
} }
} }
} }
for( auto const *track : group ) { for( auto const *track : group ) {
track->m_switchstray = { least, s_layoutstamp }; track->m_switchstray.paths = least;
track->m_switchstray.stamp = s_layoutstamp;
} }
return least; // the lowest of the paths of a switch ending at a point
auto const paths_at = []( TTrack const &Track, glm::dvec3 const &Point ) {
auto result { track_uneven::least( Track.m_switchstray.paths[ 0 ], Track.m_switchstray.paths[ 1 ] ) };
auto found { false };
track_uneven::amounts ending { track_uneven::most, track_uneven::most, track_uneven::most };
for( int path = 0; path < 2; ++path ) {
auto const &segment { *Track.SwitchExtension->Segments[ path ] };
if( glm::length( segment.FastGetPoint_0() - Point ) < switch_end_reach || glm::length( segment.FastGetPoint_1() - Point ) < switch_end_reach ) {
ending = track_uneven::least( ending, Track.m_switchstray.paths[ path ] );
found = true;
}
}
return found ? ending : result; };
for( auto const *track : group ) {
auto const &extension { *track->SwitchExtension };
for( int path = 0; path < 2; ++path ) {
auto const &segment { *extension.Segments[ path ] };
for( int end = 0; end < 2; ++end ) {
auto const point { end == 0 ? segment.FastGetPoint_0() : segment.FastGetPoint_1() };
auto value { paths_at( *track, point ) };
auto const *other { end == 0 ? extension.pPrevs[ path ] : extension.pNexts[ path ] };
if( other != nullptr && other->eType == tt_Switch && other->SwitchExtension ) {
value = track_uneven::least( value, paths_at( *other, point ) );
}
track->m_switchstray.ends[ path ][ end ] = value;
}
}
}
return m_switchstray;
}
track_uneven::amounts TTrack::switch_uneven_at( glm::dvec3 const &Point ) const {
auto const &stray { switch_uneven() };
auto found { false };
track_uneven::amounts result { track_uneven::most, track_uneven::most, track_uneven::most };
for( int path = 0; path < 2; ++path ) {
auto const &segment { *SwitchExtension->Segments[ path ] };
for( int end = 0; end < 2; ++end ) {
if( glm::length( ( end == 0 ? segment.FastGetPoint_0() : segment.FastGetPoint_1() ) - Point ) < switch_end_reach ) {
result = track_uneven::least( result, stray.ends[ path ][ end ] );
found = true;
}
}
}
return found ? result : track_uneven::least( stray.paths[ 0 ], stray.paths[ 1 ] );
} }
double TTrack::joint_depth() const { double TTrack::joint_depth() const {
@@ -896,7 +971,7 @@ void TTrack::init_segments( bool const Initial ) {
++s_layoutstamp; ++s_layoutstamp;
if( eType == tt_Switch && SwitchExtension ) { if( eType == tt_Switch && SwitchExtension ) {
SwitchExtension->Segments[ 0 ]->set_uneven( is_uneven() ); SwitchExtension->Segments[ 0 ]->set_uneven( is_uneven() );
SwitchExtension->Segments[ 1 ]->set_uneven( is_uneven() ); SwitchExtension->Segments[ 1 ]->set_uneven( is_uneven(), 1 );
} }
else if( Segment ) { else if( Segment ) {
Segment->set_uneven( is_uneven() ); Segment->set_uneven( is_uneven() );
@@ -2707,21 +2782,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 << ' ';
} }
if( m_uneven > 0.f ) { Output << uneven_text();
Output << "uneven " << m_uneven << ' ';
}
if( m_unevenup >= 0.f ) {
Output << "unevenup " << m_unevenup << ' ';
}
if( m_unevenside >= 0.f ) {
Output << "unevenside " << m_unevenside << ' ';
}
if( m_unevencross >= 0.f ) {
Output << "unevencross " << m_unevencross << ' ';
}
if( m_jointdip >= 0.f ) {
Output << "jointdip " << m_jointdip << ' ';
}
if( false == m_friction.first.empty() ) { if( false == m_friction.first.empty() ) {
Output << "friction " << m_friction.first << ' '; Output << "friction " << m_friction.first << ' ';
} }

View File

@@ -244,6 +244,10 @@ public:
float m_unevenup { -1.f }; float m_unevenup { -1.f };
float m_unevenside { -1.f }; float m_unevenside { -1.f };
float m_unevencross { -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 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)
@@ -346,11 +350,15 @@ public:
void VelocitySet(float v); void VelocitySet(float v);
double VelocityGet(); double VelocityGet();
float Friction() const; float Friction() const;
// the unevenness the track declares // the attribute of the entry. A plain track: the value;up and down;sideways;cross level;dip at the joints.
track_uneven::amounts uneven_declared() const; // 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; bool is_uneven() const;
// the unevenness at a distance along the track, meeting the tracks joined at its ends // the unevenness at a distance along a path, meeting the tracks joined at its ends
track_uneven::amounts uneven_at( double const Distance ) const; track_uneven::amounts uneven_at( double const Distance, int const Path = 0 ) const;
// how much of the displacement a point of the profile at the depth takes // how much of the displacement a point of the profile at the depth takes
double uneven_bed_share( double const Depth ) const; double uneven_bed_share( double const Depth ) const;
// closes the gaps and steps of cant between the ends of joined paths // closes the gaps and steps of cant between the ends of joined paths
@@ -398,13 +406,17 @@ private:
}; };
void settle_rail_run() const; void settle_rail_run() const;
mutable rail_run m_railrun; mutable rail_run m_railrun;
// one value along all paths of a switch: the lowest of the switches joined, 0 next to a track with none // the value along each path of a switch: the lowest of that path in the switches joined, 0 next to a track with none.
track_uneven::amounts switch_uneven() const; // 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 { struct switch_stray {
track_uneven::amounts value; std::array<track_uneven::amounts, 2> paths;
std::array<std::array<track_uneven::amounts, 2>, 2> ends;
int stamp { -1 }; int stamp { -1 };
}; };
mutable switch_stray m_switchstray; mutable switch_stray m_switchstray;
switch_stray const &switch_uneven() const;
// the value of a switch where a track joined to it meets it
track_uneven::amounts switch_uneven_at( glm::dvec3 const &Point ) const;
static int s_layoutstamp; // bumped when tracks are joined or changed, invalidates the two above 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_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 ); void create_road_side_profile( gfx::vertex_array &Right, gfx::vertex_array &Left, gfx::vertex_array const &Road, bool const Forcetransition = false );

View File

@@ -11,7 +11,10 @@ http://mozilla.org/MPL/2.0/.
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <charconv>
#include <cmath> #include <cmath>
#include <sstream>
#include <string>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/constants.hpp> #include <glm/gtc/constants.hpp>
@@ -34,6 +37,11 @@ struct amounts {
double cross { 0.0 }; double cross { 0.0 };
}; };
inline amounts least( amounts const &Left, amounts const &Right ) {
return { std::min( Left.up, Right.up ), std::min( Left.side, Right.side ), std::min( Left.cross, Right.cross ) };
}
struct wave { struct wave {
double x; double x;
double z; double z;
@@ -41,6 +49,44 @@ struct wave {
double share; double share;
}; };
// the numbers of an entry like "uneven 14;35", negative where left out
template <std::size_t Count>
std::array<float, Count> read_fields( std::string const &Text ) {
std::array<float, Count> 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 );
}
start = end + 1;
}
return result;
}
// and back, with the ones left out at the end dropped
template <std::size_t Count>
std::string write_fields( std::array<float, Count> const &Fields ) {
auto count { Count };
while( count > 1 && Fields[ count - 1 ] < 0.f ) {
--count;
}
std::ostringstream text;
for( std::size_t index = 0; index < count; ++index ) {
if( index > 0 ) {
text << ';';
}
if( Fields[ index ] >= 0.f ) {
text << Fields[ index ];
}
}
return text.str();
}
template <std::size_t Count> template <std::size_t Count>
using wave_set = std::array<wave, Count>; using wave_set = std::array<wave, Count>;