mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-02 12:49:13 +01:00
Merge remote-tracking branch 'origin/feat/track-uneven' into feat/renderers-cleanup
This commit is contained in:
@@ -703,8 +703,36 @@ std::vector<track_property> make_properties()
|
|||||||
// ride and sound
|
// ride and sound
|
||||||
add_float(list, "friction", section::ride, group::ride_sound, "Friction", "Friction coefficient of the rails, 0.15 for the dry rails; lower values make the wheels slip", &TTrack::fFriction, 3, 0.f, false);
|
add_float(list, "friction", section::ride, group::ride_sound, "Friction", "Friction coefficient of the rails, 0.15 for the dry rails; lower values make the wheels slip", &TTrack::fFriction, 3, 0.f, false);
|
||||||
list.back().applies = any;
|
list.back().applies = any;
|
||||||
add_float(list, "sounddistance", section::ride, group::ride_sound, "Sound distance (m)", "Distance between the rail joints, sets the rhythm of the wheel clatter; -1: the default", &TTrack::fSoundDistance, 1, -1.f, false);
|
// the dips at the rail joints of an uneven track go with it
|
||||||
|
add_float(list, "sounddistance", section::ride, group::ride_sound, "Sound distance (m)", "Distance between the rail joints, sets the rhythm of the wheel clatter; -1: the default", &TTrack::fSoundDistance, 1, -1.f, true);
|
||||||
list.back().applies = any;
|
list.back().applies = any;
|
||||||
|
{
|
||||||
|
// the unevenness of the rails (world/TrackUneven.h), mm up to track_uneven::most; with Unset a negative value is -1, the
|
||||||
|
// component taken from the unevenness
|
||||||
|
auto const uneven = [&](char const *Key, char const *Label, char const *Tooltip, float TTrack::*Member, bool const Unset, bool const Switches) {
|
||||||
|
auto &property{add(list, Key, field::number, section::ride, group::ride_sound, Label, Tooltip)};
|
||||||
|
property.rebuild = true;
|
||||||
|
property.applies = [Switches](TTrack const &Track) {
|
||||||
|
return Track.m_visible && (Track.iCategoryFlag & 15) == kRailCategory && (Track.eType == tt_Normal || (Switches && Track.eType == tt_Switch));
|
||||||
|
};
|
||||||
|
property.get = [Member](TTrack const &Track) { return decimal(Track.*Member, 1); };
|
||||||
|
property.set = [Member, Unset](TTrack &Track, std::string const &Text) {
|
||||||
|
double value;
|
||||||
|
if (false == parse_number(Text, value))
|
||||||
|
return false;
|
||||||
|
auto const clamped{(Unset && value < 0.0) ? -1.f : std::clamp(static_cast<float>(value), 0.f, track_uneven::most)};
|
||||||
|
if (decimal(clamped, 1) == decimal(Track.*Member, 1))
|
||||||
|
return false;
|
||||||
|
Track.*Member = clamped;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
uneven("uneven", "Unevenness (mm)", "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", &TTrack::m_uneven, false, true);
|
||||||
|
uneven("unevenup", "Up and down (mm)", "Set apart from the unevenness, mm. Negative: the unevenness", &TTrack::m_unevenup, true, true);
|
||||||
|
uneven("unevenside", "Sideways (mm)", "Set apart from the unevenness, mm. Negative: the unevenness", &TTrack::m_unevenside, true, true);
|
||||||
|
uneven("unevencross", "Cross level (mm)", "Set apart from the unevenness, mm. Negative: the unevenness", &TTrack::m_unevencross, true, false);
|
||||||
|
uneven("jointdip", "Dip at rail joints (mm)", "Depth of the dip at each rail joint, every sound distance. Works with unevenness at 0. Negative: 0.4 of the unevenness", &TTrack::m_jointdip, true, false);
|
||||||
|
}
|
||||||
{
|
{
|
||||||
auto &property{add(list, "quality", field::number, section::ride, group::ride_sound, "Quality", "Quality of the track, 20 is the nominal one; lower values give a rougher ride")};
|
auto &property{add(list, "quality", field::number, section::ride, group::ride_sound, "Quality", "Quality of the track, 20 is the nominal one; lower values give a rougher ride")};
|
||||||
property.applies = any;
|
property.applies = any;
|
||||||
|
|||||||
@@ -248,6 +248,11 @@ editor_track::state editor_track::capture(TTrack const &Track)
|
|||||||
for (auto const *isolated : Track.Isolated)
|
for (auto const *isolated : Track.Isolated)
|
||||||
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.unevenup = Track.m_unevenup;
|
||||||
|
result.unevenside = Track.m_unevenside;
|
||||||
|
result.unevencross = Track.m_unevencross;
|
||||||
|
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;
|
||||||
@@ -355,6 +360,11 @@ void editor_track::apply(TTrack &Track, state const &State)
|
|||||||
Track.fTexSlope = State.texslope;
|
Track.fTexSlope = State.texslope;
|
||||||
isolated(Track, State.isolated);
|
isolated(Track, State.isolated);
|
||||||
overhead(Track, State.overhead);
|
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_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;
|
||||||
@@ -3225,6 +3235,16 @@ 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 << "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 << ' ';
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ 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};
|
||||||
|
float unevenup{-1.f};
|
||||||
|
float unevenside{-1.f};
|
||||||
|
float unevencross{-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;
|
||||||
|
|||||||
@@ -547,6 +547,30 @@ msgstr "Odstęp stuków"
|
|||||||
msgid "Quality"
|
msgid "Quality"
|
||||||
msgstr "Jakość"
|
msgstr "Jakość"
|
||||||
|
|
||||||
|
msgid "Unevenness (mm)"
|
||||||
|
msgstr "Nierówności toru (mm)"
|
||||||
|
|
||||||
|
msgid "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"
|
||||||
|
msgstr "Największa odchyłka toku szynowego na cięciwie 10 m. Limity Id-14: 6 mm przy 160 km/h, 10 przy 120, 18 przy 80, 35 przy 40.\nPrzy podanym odstępie styków szyny zapadają się też na stykach. Połączone rozjazdy mają najmniejszą ze swoich wartości"
|
||||||
|
|
||||||
|
msgid "Up and down (mm)"
|
||||||
|
msgstr "W pionie (mm)"
|
||||||
|
|
||||||
|
msgid "Sideways (mm)"
|
||||||
|
msgstr "W poziomie (mm)"
|
||||||
|
|
||||||
|
msgid "Cross level (mm)"
|
||||||
|
msgstr "Przechyłka (mm)"
|
||||||
|
|
||||||
|
msgid "Set apart from the unevenness, mm. Negative: the unevenness"
|
||||||
|
msgstr "Osobno od nierówności, mm. Ujemna: wartość nierówności"
|
||||||
|
|
||||||
|
msgid "Dip at rail joints (mm)"
|
||||||
|
msgstr "Zapadnięcie na stykach szyn (mm)"
|
||||||
|
|
||||||
|
msgid "Depth of the dip at each rail joint, every sound distance. Works with unevenness at 0. Negative: 0.4 of the unevenness"
|
||||||
|
msgstr "Głębokość zapadnięcia na każdym styku szyn, co odstęp styków. Działa też przy nierównościach 0. Ujemna: 0,4 nierówności"
|
||||||
|
|
||||||
msgid "Damage flags"
|
msgid "Damage flags"
|
||||||
msgstr "Flagi uszkodzeń"
|
msgstr "Flagi uszkodzeń"
|
||||||
|
|
||||||
|
|||||||
175
tests/unit/test_track_uneven.cpp
Normal file
175
tests/unit/test_track_uneven.cpp
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
#include "doctest.h"
|
||||||
|
#include "world/TrackUneven.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
double deviation(std::vector<double> const &Values)
|
||||||
|
{
|
||||||
|
auto mean{0.0};
|
||||||
|
for (auto const value : Values)
|
||||||
|
mean += value;
|
||||||
|
mean /= Values.size();
|
||||||
|
auto sum{0.0};
|
||||||
|
for (auto const value : Values)
|
||||||
|
sum += (value - mean) * (value - mean);
|
||||||
|
return std::sqrt(sum / Values.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
double peak(std::vector<double> Values)
|
||||||
|
{
|
||||||
|
for (auto &value : Values)
|
||||||
|
value = std::abs(value);
|
||||||
|
std::sort(Values.begin(), Values.end());
|
||||||
|
return Values[static_cast<std::size_t>(0.999 * Values.size())];
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_chord(std::vector<double> &Output, std::vector<double> const &Line, int const Half)
|
||||||
|
{
|
||||||
|
for (int i = Half; i + Half < static_cast<int>(Line.size()); ++i)
|
||||||
|
Output.push_back(Line[i] - 0.5 * (Line[i - Half] + Line[i + Half]));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("uneven track: measures as the Id-14 limits for its value")
|
||||||
|
{
|
||||||
|
// appendix 1, class C at 80 km/h: 18 mm vertically, 17 mm sideways, 16 mm of twist on 5 m, gauge +10/-8 mm, 20 mm of cross level, J = 5.3
|
||||||
|
auto const amplitude{track_uneven::stray_per_mm * 18.0 * 1000.0}; // mm
|
||||||
|
double const gauge{1.435};
|
||||||
|
double const step{0.5};
|
||||||
|
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};
|
||||||
|
for (int line = 0; line < lines; ++line)
|
||||||
|
{
|
||||||
|
auto const heading{glm::pi<double>() * (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<double> up[2], side[2], middle;
|
||||||
|
for (int i = 0; i < steps; ++i)
|
||||||
|
{
|
||||||
|
auto const point{start + along * (i * step)};
|
||||||
|
for (int rail = 0; rail < 2; ++rail)
|
||||||
|
{
|
||||||
|
auto const sign{rail == 0 ? 1.0 : -1.0};
|
||||||
|
auto const stray{track_uneven::field(point + across * (sign * 0.5 * gauge))};
|
||||||
|
up[rail].push_back((stray.y - sign * 0.5 * track_uneven::level(point, across)) * amplitude);
|
||||||
|
side[rail].push_back(glm::dot(stray, across) * amplitude);
|
||||||
|
}
|
||||||
|
middle.push_back(track_uneven::field(point).y * amplitude);
|
||||||
|
}
|
||||||
|
std::vector<double> verticals, horizontals, twists, gauges, levels;
|
||||||
|
for (int rail = 0; rail < 2; ++rail)
|
||||||
|
{
|
||||||
|
add_chord(verticals, up[rail], base);
|
||||||
|
add_chord(horizontals, side[rail], base);
|
||||||
|
}
|
||||||
|
for (int i = 0; i + base < steps; ++i)
|
||||||
|
twists.push_back((up[0][i + base] - up[1][i + base]) - (up[0][i] - up[1][i]));
|
||||||
|
for (int i = 0; i < steps; ++i)
|
||||||
|
{
|
||||||
|
gauges.push_back(side[0][i] - side[1][i]);
|
||||||
|
levels.push_back(up[0][i] - up[1][i]);
|
||||||
|
}
|
||||||
|
vertical += peak(verticals) / lines;
|
||||||
|
horizontal += peak(horizontals) / lines;
|
||||||
|
twist += peak(twists) / lines;
|
||||||
|
wander += peak(gauges) / lines;
|
||||||
|
level += peak(levels) / lines;
|
||||||
|
away += peak(middle) / lines;
|
||||||
|
j += (deviation(verticals) + deviation(horizontals) + deviation(twists) + 0.5 * deviation(gauges)) / 3.5 / lines;
|
||||||
|
}
|
||||||
|
CHECK(vertical == doctest::Approx(18.0).epsilon(0.1));
|
||||||
|
CHECK(horizontal == doctest::Approx(17.0).epsilon(0.2));
|
||||||
|
CHECK(twist == doctest::Approx(16.0).epsilon(0.2));
|
||||||
|
CHECK(wander == doctest::Approx(10.0).epsilon(0.2));
|
||||||
|
CHECK(level == doctest::Approx(20.0).epsilon(0.1));
|
||||||
|
CHECK(j == doctest::Approx(5.3).epsilon(0.2));
|
||||||
|
CHECK(away < 40.0); // the long waves go past the 15 mm off the survey marks of appendix 1a
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("uneven track: a place strays the same every time")
|
||||||
|
{
|
||||||
|
// no random numbers: changing these changes how every uneven track in every scenery looks
|
||||||
|
glm::dvec3 const place{1234.5, 0.0, -678.9};
|
||||||
|
auto const stray{track_uneven::field(place)};
|
||||||
|
CHECK(stray.x == doctest::Approx(0.309777548158).epsilon(1e-9));
|
||||||
|
CHECK(stray.y == doctest::Approx(0.311350299745).epsilon(1e-9));
|
||||||
|
CHECK(stray.z == doctest::Approx(-0.328429734019).epsilon(1e-9));
|
||||||
|
CHECK(track_uneven::level(place, {1.0, 0.0, 0.0}) == doctest::Approx(0.53 * -0.178598957983).epsilon(1e-9));
|
||||||
|
CHECK(track_uneven::level(place, {0.0, 0.0, 1.0}) == doctest::Approx(0.53 * -0.868291889902).epsilon(1e-9));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("uneven track: tracks laid towards each other meet at one cant")
|
||||||
|
{
|
||||||
|
// the same rail stays lower seen from either of them, and a path taken the other way sees the roll turned over
|
||||||
|
glm::dvec3 const place{812.25, 0.0, 3071.5};
|
||||||
|
glm::dvec3 const across{0.6, 0.0, -0.8};
|
||||||
|
CHECK(track_uneven::level(place, -across) == doctest::Approx(-track_uneven::level(place, across)));
|
||||||
|
CHECK(track_uneven::roll(place, -across, 1.435) == doctest::Approx(-track_uneven::roll(place, across, 1.435)));
|
||||||
|
// and there's no step along a curve turning through the headings
|
||||||
|
auto last{track_uneven::level(place, {1.0, 0.0, 0.0})};
|
||||||
|
for (int step = 1; step <= 3600; ++step)
|
||||||
|
{
|
||||||
|
auto const heading{glm::two_pi<double>() * step / 3600.0};
|
||||||
|
auto const value{track_uneven::level(place, {std::cos(heading), 0.0, std::sin(heading)})};
|
||||||
|
REQUIRE(std::abs(value - last) < 0.002);
|
||||||
|
last = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("uneven track: the field stays within its bound")
|
||||||
|
{
|
||||||
|
auto most{0.0};
|
||||||
|
auto highest{0.0};
|
||||||
|
for (int i = 0; i < 20000; ++i)
|
||||||
|
{
|
||||||
|
auto const stray{track_uneven::field({i * 3.7 - 30000.0, 0.0, i * -2.9 + 41000.0})};
|
||||||
|
most = std::max({most, std::abs(stray.x), std::abs(stray.z)});
|
||||||
|
highest = std::max(highest, std::abs(stray.y));
|
||||||
|
}
|
||||||
|
CHECK(most <= 1.0);
|
||||||
|
CHECK(most > 0.5);
|
||||||
|
CHECK(highest <= 1.69);
|
||||||
|
CHECK(highest > 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("uneven track: the value along a track meets its neighbours exactly")
|
||||||
|
{
|
||||||
|
double const length{500.0};
|
||||||
|
CHECK(track_uneven::along(20.0, 5.0, 7.0, 0.0, length) == doctest::Approx(5.0));
|
||||||
|
CHECK(track_uneven::along(20.0, 5.0, 7.0, length, length) == doctest::Approx(7.0));
|
||||||
|
CHECK(track_uneven::along(20.0, 5.0, 7.0, 0.5 * length, length) == doctest::Approx(20.0));
|
||||||
|
// equal values stay constant
|
||||||
|
CHECK(track_uneven::along(20.0, 20.0, 20.0, 3.0, length) == doctest::Approx(20.0));
|
||||||
|
// a short track between ends of no value hardly strays
|
||||||
|
CHECK(track_uneven::along(20.0, 0.0, 0.0, 2.5, 5.0) < 1.0);
|
||||||
|
// a switch of more than the track: the track starts at the value of the switch
|
||||||
|
CHECK(track_uneven::along(18.0, 35.0, 18.0, 0.0, length) == doctest::Approx(35.0));
|
||||||
|
CHECK(track_uneven::along(18.0, 35.0, 18.0, track_uneven::fade, length) == doctest::Approx(18.0));
|
||||||
|
// the short piece inside a slip goes with its switches
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
auto const value{track_uneven::along(18.0, 35.0, 0.0, distance, length)};
|
||||||
|
REQUIRE(std::abs(value - last) < 0.5);
|
||||||
|
last = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("uneven track: the rails dip around a joint")
|
||||||
|
{
|
||||||
|
CHECK(track_uneven::dip(0.0) == doctest::Approx(1.0));
|
||||||
|
CHECK(track_uneven::dip(1.0) == doctest::Approx(0.75).epsilon(0.01)); // the worst case for rings 2 m apart
|
||||||
|
CHECK(track_uneven::dip(track_uneven::dip_reach) == doctest::Approx(0.0));
|
||||||
|
CHECK(track_uneven::rail_phase(26.0, 25.0) == doctest::Approx(1.0));
|
||||||
|
CHECK(track_uneven::rail_phase(-1.0, 25.0) == doctest::Approx(24.0));
|
||||||
|
}
|
||||||
83
track_uneven_doc.md
Normal file
83
track_uneven_doc.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# Nierówności toru
|
||||||
|
|
||||||
|
Opcjonalne atrybuty wpisu toru, przed `endtrack`:
|
||||||
|
|
||||||
|
```
|
||||||
|
node -1 0 tor1 track normal 100.0 1.435 0.15 25.0 20 0 flat vis
|
||||||
|
rail_screw_used1 6 1435mm/tpbps-new2 0.2 0.5 1.1
|
||||||
|
0.0 0.2 0.0 0.0
|
||||||
|
0.0 0.0 0.0
|
||||||
|
0.0 0.0 0.0
|
||||||
|
0.0 0.2 100.0 0.0
|
||||||
|
0.0
|
||||||
|
uneven 18
|
||||||
|
unevencross 25
|
||||||
|
jointdip 6
|
||||||
|
endtrack
|
||||||
|
```
|
||||||
|
|
||||||
|
| atrybut | dotyczy | opis |
|
||||||
|
|-------------------|-----------------------|---------------------------------------------------------------------------------------|
|
||||||
|
| `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`.
|
||||||
|
|
||||||
|
Wszystkie są też w edytorze, w panelu toru, w sekcji „Ride and sound”. Na drogach, rzekach i obrotnicach są ignorowane.
|
||||||
|
|
||||||
|
## Jaką wartość wybrać
|
||||||
|
|
||||||
|
`uneven` odpowiada kolumnie „nierówności pionowe” z instrukcji PLK Id-14, załącznik 1 (klasa C):
|
||||||
|
|
||||||
|
| prędkość [km/h] | 20 | 40 | 60 | 80 | 100 | 120 | 160 |
|
||||||
|
|-----------------|----|----|----|----|-----|-----|-----|
|
||||||
|
| `uneven` [mm] | 50 | 35 | 25 | 18 | 14 | 10 | 6 |
|
||||||
|
|
||||||
|
Tor w dobrym stanie to według instrukcji połowa tej wartości (klasa A), w dostatecznym 3/4 (klasa B).
|
||||||
|
|
||||||
|
Tor z `uneven 18` mierzy się tak (`tests/unit/test_track_uneven.cpp`), w nawiasie limit Id-14 dla 80 km/h:
|
||||||
|
|
||||||
|
| parametr | wynik | Id-14 |
|
||||||
|
|---------------------------|---------|---------|
|
||||||
|
| nierówności pionowe | 18 mm | 18 mm |
|
||||||
|
| nierówności poziome | 19 mm | 17 mm |
|
||||||
|
| wichrowatość na bazie 5 m | 15 mm | 16 mm |
|
||||||
|
| szerokość toru | ±10 mm | +10/-8 mm |
|
||||||
|
| przechyłka | 20 mm | 20 mm |
|
||||||
|
| wskaźnik J | 6,1 | 5,3 |
|
||||||
|
|
||||||
|
## Jak to działa
|
||||||
|
|
||||||
|
Nierówność to pole przesunięć zależne od położenia na mapie, złożone z fal o długości 5-42 m. W pionie krótkich fal jest mniej, żeby częściej dochodziły blisko wartości szczytowej, a do tego są fale 47-83 m: na cięciwie 10 m prawie się nie liczą, pudło unosi się na nich i opada, a pantograf za nimi nadąża. Nie ma w nim losowania: to samo miejsce wygina się tak samo przy każdym uruchomieniu. Przesunięty tor dostaje fale z nowego miejsca.
|
||||||
|
|
||||||
|
Każda składowa łączy się osobno:
|
||||||
|
|
||||||
|
- **Tor z torem:** na styku obowiązuje mniejsza z dwóch wartości, a każdy tor dochodzi do niej płynnie na 20 m. Wolny koniec toru leży w projekcie.
|
||||||
|
- **Rozjazd:** wszystkie jego drogi mają jedną wartość. Rozjazdy złączone bezpośrednio przyjmują najmniejszą ze swoich. Dołączony tor dochodzi do wartości rozjazdu, także większej od własnej. Tor bez danej składowej trzyma w niej cały rozjazd w projekcie.
|
||||||
|
- **Styki:** zapadnięcia są co `fSoundDistance`, na długości 6 m, i biegną dalej przez kolejne tory o tym samym odstępie. Stukot osi wypada w miejscu zapadnięcia. Przy odstępie 6 m lub mniejszym oraz na rozjazdach zapadnięć nie ma.
|
||||||
|
- **Przechyłka:** dolicza się do projektowej. Jest pochyleniem w planie mapy, więc tory wpisane ku sobie spotykają się na tej samej przechyłce. Na rozjazdach tylko ta wynikająca z fal, bez osobnego siadania toków.
|
||||||
|
|
||||||
|
Za nierównością idą szyny, podsypka, `sleepermodel`, osie, przechył pudła i bujanie kabiny.
|
||||||
|
|
||||||
|
Nie idą za nią modele z includów (np. napędy i podkłady rozjazdów), teren ani sieć trakcyjna, więc przy dużych wartościach szyna rozjeżdża się z takim modelem o kilka centymetrów.
|
||||||
|
|
||||||
|
Pantograf podnosi się z szybkością ciśnienia, ok. 0,14 m/s, a szczelina do drutu większa niż 1 cm odcina napięcie i wybija wyłącznik szybki. Nierówność dokłada się do tego przy dużej prędkości. Na torach Bałtyku symulacja daje szczelinę 0,6 cm, gdy składowa w pionie razy prędkość w km/h wynosi 925, czyli np. 8 mm przy 120 km/h, 13 mm przy 70 km/h i 23 mm przy 40 km/h. Wartości z tabeli Id-14 są od tego większe. Sam drut też to potrafi: na Bałtyku przy `tor0290` wznosi się o ok. 1%, więc pantograf nadąża tam do ok. 46 km/h, a powyżej wyłącznik wybija także na prostym torze.
|
||||||
|
|
||||||
|
Fizyka bierze promień łuku i pochylenie z projektu, więc nierówności nie zmieniają tych wartości, a do wykolejeń dokładają tylko przechyłkę, ok. 0,06 m/s² przy `uneven 18` wobec progu ok. 3,2 m/s².
|
||||||
|
|
||||||
|
## Wydajność
|
||||||
|
|
||||||
|
Tor z nierównością ma geometrię co 2 m zamiast co 10 m na prostych. Rozjazdy zachowują swój podział.
|
||||||
|
|
||||||
|
Zmierzone (kamera w ruchu lub kabina, 1920×1080):
|
||||||
|
|
||||||
|
| scena | bez flag | z flagami 18/35 |
|
||||||
|
|---------------------------------------|--------------------|--------------------|
|
||||||
|
| Bałtyk, 56 km torów: wierzchołki | 0,56 mln (ok. 27 MB) | 2,17 mln (ok. 104 MB) |
|
||||||
|
| Bałtyk: najdłuższa klatka w przelocie | 31,5 ms | 64 ms |
|
||||||
|
| `td.scn`, jazda 120 km/h: klatka | 2,73 ms | 2,93 ms |
|
||||||
|
|
||||||
|
Koszt rośnie z liczbą oflagowanych torów, a najdłuższe klatki pojawiają się przy budowaniu geometrii kolejnych sekcji. Sama jazda po takim torze praktycznie nie kosztuje: ok. 0,3 µs na oś w kroku fizyki.
|
||||||
@@ -3642,6 +3642,7 @@ bool TDynamicObject::Update(double dt, double dt1)
|
|||||||
if( dRailLength > 0.0 ) {
|
if( dRailLength > 0.0 ) {
|
||||||
for( auto &axle : m_axlesounds ) {
|
for( auto &axle : m_axlesounds ) {
|
||||||
axle.distance = axle.offset;
|
axle.distance = axle.offset;
|
||||||
|
axle.atjoints = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dRailLength = MyTrack->fSoundDistance;
|
dRailLength = MyTrack->fSoundDistance;
|
||||||
@@ -3675,10 +3676,23 @@ bool TDynamicObject::Update(double dt, double dt1)
|
|||||||
1 :
|
1 :
|
||||||
ctOwner->Vehicle()->DirectionGet() == DirectionGet() ? 1 : -1 ) };
|
ctOwner->Vehicle()->DirectionGet() == DirectionGet() ? 1 : -1 ) };
|
||||||
for( auto &axle : m_axlesounds ) {
|
for( auto &axle : m_axlesounds ) {
|
||||||
axle.distance += dDOMoveLen * directioninconsist;
|
// on a track with dipping joints the clatter comes from them, elsewhere the axle counts a rail off itself
|
||||||
if( axle.distance < 0
|
auto const railphase { Axle0.RailPhase( axle.offset - 0.5 * fAxleDist ) };
|
||||||
|| axle.distance > dRailLength ) {
|
auto crossed { false };
|
||||||
axle.distance = clamp_circular( axle.distance, dRailLength );
|
if( railphase >= 0.0 ) {
|
||||||
|
// the rails under the axle, which aren't always those under the track the vehicle is on
|
||||||
|
crossed = axle.atjoints && std::abs( railphase - axle.distance ) > 0.5 * Axle0.GetTrack()->fSoundDistance;
|
||||||
|
axle.distance = railphase;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
axle.distance += dDOMoveLen * directioninconsist;
|
||||||
|
crossed = axle.distance < 0 || axle.distance > dRailLength;
|
||||||
|
if( crossed ) {
|
||||||
|
axle.distance = clamp_circular( axle.distance, dRailLength );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
axle.atjoints = ( railphase >= 0.0 );
|
||||||
|
if( crossed ) {
|
||||||
if( MoverParameters->Vel > 0.1 ) {
|
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
|
// 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;
|
auto const frequency = true == axle.clatter.is_combined() ? MoverParameters->Vel * 0.01 : 1.0;
|
||||||
@@ -8050,6 +8064,12 @@ TDynamicObject::update_shake( double const Timedelta ) {
|
|||||||
-MoverParameters->AccN * Timedelta * 5.0 * Global.ShakingMultiplierRL, // highlight side sway
|
-MoverParameters->AccN * Timedelta * 5.0 * Global.ShakingMultiplierRL, // highlight side sway
|
||||||
-MoverParameters->AccVert * Timedelta * Global.ShakingMultiplierUD,
|
-MoverParameters->AccVert * Timedelta * Global.ShakingMultiplierUD,
|
||||||
-MoverParameters->AccSVBased * Timedelta * 1.5 * Global.ShakingMultiplierBF); // accent acceleration/deceleration
|
-MoverParameters->AccSVBased * Timedelta * 1.5 * Global.ShakingMultiplierBF); // accent acceleration/deceleration
|
||||||
|
// unevenness of the track under the outer axles
|
||||||
|
auto const ride { 0.5 * ( Axle0.UnevenBend() + Axle1.UnevenBend() ) * ( MoverParameters->V * MoverParameters->V ) };
|
||||||
|
shakevector += glm::dvec3(
|
||||||
|
-glm::dot( ride, vLeft ) * Timedelta * 5.0 * Global.ShakingMultiplierRL,
|
||||||
|
-ride.y * Timedelta * Global.ShakingMultiplierUD,
|
||||||
|
0.0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
auto shake { 1.25 * ShakeSpring.ComputateForces( shakevector, ShakeState.offset ) };
|
auto shake { 1.25 * ShakeSpring.ComputateForces( shakevector, ShakeState.offset ) };
|
||||||
|
|||||||
@@ -401,6 +401,7 @@ private:
|
|||||||
double distance; // distance to rail joint
|
double distance; // distance to rail joint
|
||||||
double offset; // axle offset from centre of the vehicle
|
double offset; // axle offset from centre of the vehicle
|
||||||
sound_source clatter; // clatter emitter
|
sound_source clatter; // clatter emitter
|
||||||
|
bool atjoints { false }; // distance comes from TTrack::rail_phase
|
||||||
};
|
};
|
||||||
|
|
||||||
struct powertrain_sounds {
|
struct powertrain_sounds {
|
||||||
|
|||||||
@@ -135,6 +135,10 @@ 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
|
fStoop = std::atan2(Point2.y - Point1.y, fLength); // pochylenie toru prostego, żeby nie liczyć wielokrotnie
|
||||||
|
|
||||||
fStep = ( fNewStep > 0.0 && std::isfinite( fNewStep ) ) ? fNewStep : fLength;
|
fStep = ( fNewStep > 0.0 && std::isfinite( fNewStep ) ) ? fNewStep : fLength;
|
||||||
|
if( m_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 );
|
||||||
|
}
|
||||||
// NOTE: optionally replace this part with the commented version, after solving geometry issues with double switches
|
// NOTE: optionally replace this part with the commented version, after solving geometry issues with double switches
|
||||||
if( pOwner->eType == tt_Switch
|
if( pOwner->eType == tt_Switch
|
||||||
&& fStep * (3.0 * Global.SplineFidelity) > fLength ) {
|
&& fStep * (3.0 * Global.SplineFidelity) > fLength ) {
|
||||||
@@ -426,7 +430,54 @@ Math3D::vector3 TSegment::GetPoint(double const fDistance) const
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
void TSegment::RaPositionGet(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) 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 ) };
|
||||||
|
rotation.x += static_cast<float>( track_uneven::stray_per_mm * (
|
||||||
|
amount.up * track_uneven::roll( position, across, gauge )
|
||||||
|
+ amount.cross * track_uneven::level( position, across ) / gauge ) );
|
||||||
|
position += stray( position, fDistance );
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::dvec3 TSegment::stray( glm::dvec3 const &Point, double const Distance ) const {
|
||||||
|
auto const amount { pOwner->uneven_at( Distance ) };
|
||||||
|
auto const field { track_uneven::field( Point ) };
|
||||||
|
glm::dvec3 result { field.x * amount.side, field.y * amount.up, field.z * amount.side };
|
||||||
|
result *= track_uneven::stray_per_mm;
|
||||||
|
result.y -= pOwner->joint_dip( Distance );
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::dvec3 TSegment::uneven_bend( double const Distance ) const {
|
||||||
|
if( false == m_uneven ) {
|
||||||
|
return glm::dvec3 { 0.0 };
|
||||||
|
}
|
||||||
|
// over about a bogie; FastGetTFromS extrapolates past the ends of the path
|
||||||
|
auto const reach { 1.5 };
|
||||||
|
auto const strayed = [&]( double const At ) {
|
||||||
|
return stray( FastGetPoint( FastGetTFromS( At ) ), At ); };
|
||||||
|
return ( strayed( Distance + reach ) - 2.0 * strayed( Distance ) + strayed( Distance - reach ) ) / ( reach * reach );
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// the rails settling apart turn about the middle of the track, what's outside them follows the rail
|
||||||
|
auto const gauge { std::max( std::abs( pOwner->fTrackWidth ), 0.1f ) };
|
||||||
|
auto const across { std::clamp( glm::dot( Vertex.position - Position, Parallel ), -0.5f * gauge, 0.5f * gauge ) };
|
||||||
|
Vertex.position.y -= static_cast<float>( track_uneven::stray_per_mm * pOwner->uneven_at( Distance ).cross * track_uneven::level( Origin + glm::dvec3 { Position }, glm::dvec3 { Parallel } ) * across / gauge );
|
||||||
|
Vertex.position += glm::vec3 { stray( Origin + glm::dvec3 { Vertex.position }, Distance ) };
|
||||||
|
}
|
||||||
|
|
||||||
|
void TSegment::design_position(double const fDistance, glm::dvec3 &position, glm::vec3 &rotation) const {
|
||||||
if( false == m_pieces.empty() ) {
|
if( false == m_pieces.empty() ) {
|
||||||
|
// the pieces keep to their design, the unevenness is the one of the whole path
|
||||||
auto const piece { piece_at( fDistance ) };
|
auto const piece { piece_at( fDistance ) };
|
||||||
m_pieces[ piece ]->RaPositionGet( fDistance - m_starts[ piece ], position, rotation );
|
m_pieces[ piece ]->RaPositionGet( fDistance - m_starts[ piece ], position, rotation );
|
||||||
}
|
}
|
||||||
@@ -579,6 +630,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
|
|||||||
glm::normalize( norm ),
|
glm::normalize( norm ),
|
||||||
glm::vec2 { ( jmm1 * ShapePoints[ j ].texture.x + m1 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv1 } );
|
glm::vec2 { ( jmm1 * ShapePoints[ j ].texture.x + m1 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv1 } );
|
||||||
roll_vertex( Output.back(), pos1, parallel1, s - step );
|
roll_vertex( Output.back(), pos1, parallel1, s - step );
|
||||||
|
settle_vertex( Output.back(), Origin, pos1, parallel1, s - step );
|
||||||
}
|
}
|
||||||
// jeśli jest wskaźnik do tablicy
|
// jeśli jest wskaźnik do tablicy
|
||||||
if (p && *p && !j) // to dla pierwszego punktu
|
if (p && *p && !j) // to dla pierwszego punktu
|
||||||
@@ -598,6 +650,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
|
|||||||
glm::normalize( norm ),
|
glm::normalize( norm ),
|
||||||
glm::vec2 { ( jmm2 * ShapePoints[ j ].texture.x + m2 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv2 } );
|
glm::vec2 { ( jmm2 * ShapePoints[ j ].texture.x + m2 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv2 } );
|
||||||
roll_vertex( Output.back(), pos2, parallel2, s );
|
roll_vertex( Output.back(), pos2, parallel2, s );
|
||||||
|
settle_vertex( Output.back(), Origin, pos2, parallel2, s );
|
||||||
}
|
}
|
||||||
// jeśli jest wskaźnik do tablicy
|
// jeśli jest wskaźnik do tablicy
|
||||||
// to dla pierwszego punktu
|
// to dla pierwszego punktu
|
||||||
@@ -621,6 +674,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
|
|||||||
glm::normalize( norm ),
|
glm::normalize( norm ),
|
||||||
glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv1 } );
|
glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv1 } );
|
||||||
roll_vertex( Output.back(), pos1, parallel1, s - step );
|
roll_vertex( Output.back(), pos1, parallel1, s - step );
|
||||||
|
settle_vertex( Output.back(), Origin, pos1, parallel1, s - step );
|
||||||
|
|
||||||
pt = parallel2 * ( jmm2 * ( ShapePoints[ j ].position.x - fOffsetX.first ) + m2 * ( ShapePoints[ j ].position.x - fOffsetX.second ) ) + pos2;
|
pt = parallel2 * ( jmm2 * ( ShapePoints[ j ].position.x - fOffsetX.first ) + m2 * ( ShapePoints[ j ].position.x - fOffsetX.second ) ) + pos2;
|
||||||
pt.y += ShapePoints[ j ].position.y;
|
pt.y += ShapePoints[ j ].position.y;
|
||||||
@@ -632,6 +686,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
|
|||||||
glm::normalize( norm ),
|
glm::normalize( norm ),
|
||||||
glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv2 } );
|
glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv2 } );
|
||||||
roll_vertex( Output.back(), pos2, parallel2, s );
|
roll_vertex( Output.back(), pos2, parallel2, s );
|
||||||
|
settle_vertex( Output.back(), Origin, pos2, parallel2, s );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -699,7 +754,8 @@ bool TSegment::RenderLoftSteps( gfx::vertex_array &Output, glm::dvec3 const &Ori
|
|||||||
auto normal { ( rest * from.normal.x + Share * to.normal.x ) * Parallel };
|
auto normal { ( rest * from.normal.x + Share * to.normal.x ) * Parallel };
|
||||||
normal.y += rest * from.normal.y + Share * to.normal.y;
|
normal.y += rest * from.normal.y + Share * to.normal.y;
|
||||||
Output.emplace_back( point, glm::normalize( normal ), glm::vec2 { rest * from.texture.x + Share * to.texture.x, V } );
|
Output.emplace_back( point, glm::normalize( normal ), glm::vec2 { rest * from.texture.x + Share * to.texture.x, V } );
|
||||||
roll_vertex( Output.back(), Position, Parallel, Share * fLength ); };
|
roll_vertex( Output.back(), Position, Parallel, Share * fLength );
|
||||||
|
settle_vertex( Output.back(), Origin, Position, Parallel, Share * fLength ); };
|
||||||
|
|
||||||
glm::vec3 pos1, pos2, parallel1, parallel2;
|
glm::vec3 pos1, pos2, parallel1, parallel2;
|
||||||
station( 0, pos1, parallel1 );
|
station( 0, pos1, parallel1 );
|
||||||
@@ -1044,6 +1100,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig
|
|||||||
}
|
}
|
||||||
for( auto &vertex : Ring ) {
|
for( auto &vertex : Ring ) {
|
||||||
roll_vertex( vertex, position, parallel, Distance );
|
roll_vertex( vertex, position, parallel, Distance );
|
||||||
|
settle_vertex( vertex, Origin, position, parallel, Distance );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ class TSegment
|
|||||||
glm::dvec3 Point2;
|
glm::dvec3 Point2;
|
||||||
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
|
||||||
|
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
|
||||||
@@ -131,6 +132,12 @@ class TSegment
|
|||||||
// the vertex of a profile lofted at the point and across the path there, turned from the baked roll to the one of the path at the distance
|
// the vertex of a profile lofted at the point and across the path there, turned from the baked roll to the one of the path at the distance
|
||||||
void roll_vertex( gfx::basic_vertex &Vertex, glm::vec3 const &Position, glm::vec3 const &Parallel, double const Distance ) const;
|
void roll_vertex( gfx::basic_vertex &Vertex, glm::vec3 const &Position, glm::vec3 const &Parallel, double const Distance ) const;
|
||||||
|
|
||||||
|
// moves a lofted vertex with the unevenness of the track
|
||||||
|
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, double const Distance ) const;
|
||||||
|
void design_position( double const fDistance, glm::dvec3 &position, glm::vec3 &rotation ) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool bCurve = false;
|
bool bCurve = false;
|
||||||
|
|
||||||
@@ -179,6 +186,13 @@ public:
|
|||||||
/// <param name="position">Calculated position</param>
|
/// <param name="position">Calculated position</param>
|
||||||
/// <param name="rotation">Calculated rotation</param>
|
/// <param name="rotation">Calculated rotation</param>
|
||||||
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
|
||||||
|
void
|
||||||
|
set_uneven( bool const Uneven ) {
|
||||||
|
m_uneven = Uneven; }
|
||||||
|
// second derivative of the displacement along the path; times speed squared: acceleration of an axle
|
||||||
|
glm::dvec3
|
||||||
|
uneven_bend( double const Distance ) const;
|
||||||
glm::dvec3 FastGetPoint(double const t) const;
|
glm::dvec3 FastGetPoint(double const t) const;
|
||||||
inline
|
inline
|
||||||
glm::dvec3
|
glm::dvec3
|
||||||
|
|||||||
204
world/Track.cpp
204
world/Track.cpp
@@ -85,6 +85,7 @@ void init_switch_piece( TSegment &Segment, segment_data const &Piece, bool const
|
|||||||
|
|
||||||
TTrack::profiles_array TTrack::m_profiles;
|
TTrack::profiles_array TTrack::m_profiles;
|
||||||
TTrack::profiles_map TTrack::m_profilesmap;
|
TTrack::profiles_map TTrack::m_profilesmap;
|
||||||
|
int TTrack::s_layoutstamp { 0 };
|
||||||
|
|
||||||
TSwitchExtension::TSwitchExtension(TTrack *owner, int const what)
|
TSwitchExtension::TSwitchExtension(TTrack *owner, int const what)
|
||||||
{ // na początku wszystko puste
|
{ // na początku wszystko puste
|
||||||
@@ -367,6 +368,7 @@ void TTrack::ConnectPrevPrev(TTrack *pTrack, int typ)
|
|||||||
{ //łączenie torów - Point1 własny do Point1 cudzego
|
{ //łączenie torów - Point1 własny do Point1 cudzego
|
||||||
if (pTrack)
|
if (pTrack)
|
||||||
{ //(pTrack) może być zwrotnicą, a (this) tylko zwykłym odcinkiem
|
{ //(pTrack) może być zwrotnicą, a (this) tylko zwykłym odcinkiem
|
||||||
|
++s_layoutstamp;
|
||||||
trPrev = pTrack;
|
trPrev = pTrack;
|
||||||
iPrevDirection = pTrack->eType == tt_Switch ? 0 : typ & 2;
|
iPrevDirection = pTrack->eType == tt_Switch ? 0 : typ & 2;
|
||||||
pTrack->trPrev = this;
|
pTrack->trPrev = this;
|
||||||
@@ -377,6 +379,7 @@ void TTrack::ConnectPrevNext(TTrack *pTrack, int typ)
|
|||||||
{ //łaczenie torów - Point1 własny do Point2 cudzego
|
{ //łaczenie torów - Point1 własny do Point2 cudzego
|
||||||
if (pTrack)
|
if (pTrack)
|
||||||
{
|
{
|
||||||
|
++s_layoutstamp;
|
||||||
trPrev = pTrack;
|
trPrev = pTrack;
|
||||||
iPrevDirection = typ | 1; // 1:zwykły lub pierwszy zwrotnicy, 3:drugi zwrotnicy
|
iPrevDirection = typ | 1; // 1:zwykły lub pierwszy zwrotnicy, 3:drugi zwrotnicy
|
||||||
pTrack->trNext = this;
|
pTrack->trNext = this;
|
||||||
@@ -396,6 +399,7 @@ void TTrack::ConnectNextPrev(TTrack *pTrack, int typ)
|
|||||||
{ //łaczenie torów - Point2 własny do Point1 cudzego
|
{ //łaczenie torów - Point2 własny do Point1 cudzego
|
||||||
if (pTrack)
|
if (pTrack)
|
||||||
{
|
{
|
||||||
|
++s_layoutstamp;
|
||||||
trNext = pTrack;
|
trNext = pTrack;
|
||||||
iNextDirection = pTrack->eType == tt_Switch ? 0 : typ & 2;
|
iNextDirection = pTrack->eType == tt_Switch ? 0 : typ & 2;
|
||||||
pTrack->trPrev = this;
|
pTrack->trPrev = this;
|
||||||
@@ -415,6 +419,7 @@ void TTrack::ConnectNextNext(TTrack *pTrack, int typ)
|
|||||||
{ //łaczenie torów - Point2 własny do Point2 cudzego
|
{ //łaczenie torów - Point2 własny do Point2 cudzego
|
||||||
if (pTrack)
|
if (pTrack)
|
||||||
{
|
{
|
||||||
|
++s_layoutstamp;
|
||||||
trNext = pTrack;
|
trNext = pTrack;
|
||||||
iNextDirection = typ | 1; // 1:zwykły lub pierwszy zwrotnicy, 3:drugi zwrotnicy
|
iNextDirection = typ | 1; // 1:zwykły lub pierwszy zwrotnicy, 3:drugi zwrotnicy
|
||||||
pTrack->trNext = this;
|
pTrack->trNext = this;
|
||||||
@@ -731,6 +736,27 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
|
|||||||
ErrorLog( "Bad track: " + str + " at end " + std::to_string( end ) + " of track \"" + m_name + "\" which has no such end" );
|
ErrorLog( "Bad track: " + str + " at end " + std::to_string( end ) + " of track \"" + m_name + "\" which has no such end" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if( str == "uneven" ) {
|
||||||
|
// uneven <mm>: see TrackUneven.h
|
||||||
|
auto const uneven { parser->getToken<float>() };
|
||||||
|
if( ( eType == tt_Normal || eType == tt_Switch ) && iCategoryFlag == 1 ) {
|
||||||
|
m_uneven = std::clamp( uneven, 0.f, track_uneven::most );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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" ) {
|
||||||
// memory cell holding friction value modifiers
|
// memory cell holding friction value modifiers
|
||||||
m_friction.first = parser->getToken<std::string>();
|
m_friction.first = parser->getToken<std::string>();
|
||||||
@@ -807,6 +833,159 @@ 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 {
|
||||||
|
|
||||||
|
return {
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TTrack::is_uneven() const {
|
||||||
|
|
||||||
|
auto const declared { uneven_declared() };
|
||||||
|
return declared.up > 0.0 || declared.side > 0.0 || declared.cross > 0.0 || joint_depth() > 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
track_uneven::amounts TTrack::uneven_at( double const Distance ) const {
|
||||||
|
|
||||||
|
if( eType == tt_Switch ) {
|
||||||
|
return switch_uneven();
|
||||||
|
}
|
||||||
|
auto const own { uneven_declared() };
|
||||||
|
auto const atend = [&]( TTrack const *Other ) -> track_uneven::amounts {
|
||||||
|
if( Other == nullptr ) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if( Other->eType == tt_Switch ) {
|
||||||
|
return Other->switch_uneven();
|
||||||
|
}
|
||||||
|
auto const theirs { Other->uneven_declared() };
|
||||||
|
return { std::min( own.up, theirs.up ), std::min( own.side, theirs.side ), std::min( own.cross, theirs.cross ) }; };
|
||||||
|
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 {
|
||||||
|
|
||||||
|
if( m_switchstray.stamp == s_layoutstamp ) {
|
||||||
|
return m_switchstray.value;
|
||||||
|
}
|
||||||
|
std::vector<TTrack const *> group { this };
|
||||||
|
auto least { uneven_declared() };
|
||||||
|
for( std::size_t index = 0; index < group.size(); ++index ) {
|
||||||
|
auto const &extension { *group[ index ]->SwitchExtension };
|
||||||
|
for( auto const *other : { extension.pPrevs[ 0 ], extension.pPrevs[ 1 ], extension.pNexts[ 0 ], extension.pNexts[ 1 ] } ) {
|
||||||
|
if( other == nullptr ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto const theirs { other->uneven_declared() };
|
||||||
|
if( other->eType == tt_Switch && other->SwitchExtension ) {
|
||||||
|
least.up = std::min( least.up, theirs.up );
|
||||||
|
least.side = std::min( least.side, theirs.side );
|
||||||
|
if( std::find( group.begin(), group.end(), other ) == group.end() ) {
|
||||||
|
group.push_back( other );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 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; }
|
||||||
|
if( theirs.side <= 0.0 ) { least.side = 0.0; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for( auto const *track : group ) {
|
||||||
|
track->m_switchstray = { least, s_layoutstamp };
|
||||||
|
}
|
||||||
|
return least;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
double TTrack::joint_dip( double const Distance ) const {
|
||||||
|
|
||||||
|
auto const phase { rail_phase( Distance ) };
|
||||||
|
if( phase < 0.0 ) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
auto const fromjoint { std::min( phase, fSoundDistance - phase ) };
|
||||||
|
if( fromjoint >= track_uneven::dip_reach ) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
auto const depth { joint_depth() };
|
||||||
|
auto const atend = [&]( 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 {
|
||||||
|
|
||||||
|
if( joint_depth() <= 0.0 || fSoundDistance <= 2.0 * track_uneven::dip_reach ) {
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
|
if( m_railrun.stamp != s_layoutstamp ) {
|
||||||
|
settle_rail_run();
|
||||||
|
}
|
||||||
|
return track_uneven::rail_phase( m_railrun.origin + m_railrun.way * Distance, fSoundDistance );
|
||||||
|
}
|
||||||
|
|
||||||
|
void TTrack::settle_rail_run() const {
|
||||||
|
|
||||||
|
// 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 };
|
||||||
|
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 };
|
||||||
|
int joined { 0 };
|
||||||
|
for( int step = 0; step < 1000000; ++step ) {
|
||||||
|
auto const *next { onward( *track, End, joined ) };
|
||||||
|
if( next == nullptr || next == this ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
track = next;
|
||||||
|
End = 1 - joined;
|
||||||
|
}
|
||||||
|
return track; };
|
||||||
|
auto const place = []( TTrack const &Track, int const End ) {
|
||||||
|
auto const point { End == 0 ? Track.Segment->FastGetPoint_0() : Track.Segment->FastGetPoint_1() };
|
||||||
|
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 ) ) {
|
||||||
|
track = other;
|
||||||
|
end = otherend;
|
||||||
|
}
|
||||||
|
auto along { 0.0 };
|
||||||
|
while( track != nullptr && track->m_railrun.stamp != s_layoutstamp ) {
|
||||||
|
auto const length { track->Length() };
|
||||||
|
track->m_railrun = { end == 0 ? along : along + length, end == 0 ? 1.0 : -1.0, s_layoutstamp };
|
||||||
|
along += length;
|
||||||
|
int joined { 0 };
|
||||||
|
track = onward( *track, 1 - end, joined );
|
||||||
|
end = joined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TTrack::update_location() {
|
void TTrack::update_location() {
|
||||||
|
|
||||||
location( (
|
location( (
|
||||||
@@ -825,6 +1004,14 @@ void TTrack::init_segments( bool const Initial ) {
|
|||||||
fTexHeight1 -= fTexHeightOffset;
|
fTexHeight1 -= fTexHeightOffset;
|
||||||
fTexHeightOffset = 0.f;
|
fTexHeightOffset = 0.f;
|
||||||
iTrapezoid = 0;
|
iTrapezoid = 0;
|
||||||
|
++s_layoutstamp;
|
||||||
|
if( eType == tt_Switch && SwitchExtension ) {
|
||||||
|
SwitchExtension->Segments[ 0 ]->set_uneven( is_uneven() );
|
||||||
|
SwitchExtension->Segments[ 1 ]->set_uneven( is_uneven() );
|
||||||
|
}
|
||||||
|
else if( Segment ) {
|
||||||
|
Segment->set_uneven( is_uneven() );
|
||||||
|
}
|
||||||
|
|
||||||
switch (eType) {
|
switch (eType) {
|
||||||
// Ra: łuki segmentowane co 5m albo 314-kątem foremnym
|
// Ra: łuki segmentowane co 5m albo 314-kątem foremnym
|
||||||
@@ -1510,6 +1697,8 @@ glm::vec3 TTrack::get_nearest_point(const glm::dvec3 &point) const
|
|||||||
// wypełnianie tablic VBO
|
// wypełnianie tablic VBO
|
||||||
void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) {
|
void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) {
|
||||||
|
|
||||||
|
++s_layoutstamp;
|
||||||
|
|
||||||
track_rails::forget();
|
track_rails::forget();
|
||||||
auto const erase = [ this ]( gfx::geometry_handle const &Chunk ) {
|
auto const erase = [ this ]( gfx::geometry_handle const &Chunk ) {
|
||||||
if( SwitchExtension
|
if( SwitchExtension
|
||||||
@@ -2841,6 +3030,21 @@ 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 " << 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 << ' ';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "world/RailLayout.h"
|
#include "world/RailLayout.h"
|
||||||
#include "world/TrackSleepers.h"
|
#include "world/TrackSleepers.h"
|
||||||
#include "world/TrackBlend.h"
|
#include "world/TrackBlend.h"
|
||||||
|
#include "world/TrackUneven.h"
|
||||||
#include "model/material.h"
|
#include "model/material.h"
|
||||||
#include "scene/scenenode.h"
|
#include "scene/scenenode.h"
|
||||||
#include "utilities/Names.h"
|
#include "utilities/Names.h"
|
||||||
@@ -286,6 +287,12 @@ 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
|
||||||
|
// 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 };
|
||||||
|
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:
|
||||||
@@ -406,6 +413,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
|
||||||
|
track_uneven::amounts uneven_declared() const;
|
||||||
|
bool is_uneven() const;
|
||||||
|
// the unevenness at a distance along the track, meeting the tracks joined at its ends
|
||||||
|
track_uneven::amounts uneven_at( double const Distance ) const;
|
||||||
|
double joint_depth() const; // mm
|
||||||
|
double joint_dip( double const Distance ) const; // m
|
||||||
|
// distance from the rail joint behind; negative: the track has no joints that dip
|
||||||
|
double rail_phase( double const Distance ) const;
|
||||||
void ConnectionsLog();
|
void ConnectionsLog();
|
||||||
bool DoubleSlip() const;
|
bool DoubleSlip() const;
|
||||||
static void fetch_default_profiles();
|
static void fetch_default_profiles();
|
||||||
@@ -439,6 +455,22 @@ private:
|
|||||||
void profile_rolls( float &Roll1, float &Roll2 ) const;
|
void profile_rolls( float &Roll1, float &Roll2 ) const;
|
||||||
void share_joints();
|
void share_joints();
|
||||||
void create_track_bed_profile( gfx::vertex_array &Output, TTrack const *Previous, TTrack const *Next );
|
void create_track_bed_profile( gfx::vertex_array &Output, TTrack const *Previous, TTrack const *Next );
|
||||||
|
// rail joints run on across joined tracks, however the line is cut into them, so the clatter keeps its rhythm
|
||||||
|
struct rail_run {
|
||||||
|
double origin { 0.0 }; // position along the run of Point1
|
||||||
|
double way { 1.0 }; // direction of the path along the run
|
||||||
|
int stamp { -1 };
|
||||||
|
};
|
||||||
|
void settle_rail_run() const;
|
||||||
|
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
|
||||||
|
track_uneven::amounts switch_uneven() const;
|
||||||
|
struct switch_stray {
|
||||||
|
track_uneven::amounts value;
|
||||||
|
int stamp { -1 };
|
||||||
|
};
|
||||||
|
mutable switch_stray m_switchstray;
|
||||||
|
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 );
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
129
world/TrackUneven.h
Normal file
129
world/TrackUneven.h
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
This Source Code Form is subject to the
|
||||||
|
terms of the Mozilla Public License, v.
|
||||||
|
2.0. If a copy of the MPL was not
|
||||||
|
distributed with this file, You can
|
||||||
|
obtain one at
|
||||||
|
http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/constants.hpp>
|
||||||
|
|
||||||
|
// Track unevenness: a displacement field over the plan of the scenery, so everything at one place moves together: joined tracks, the
|
||||||
|
// paths of a switch, sleepers, axles. Waves 5-42 m long, shares after the German track spectrum with the long waves cut down; up and down to 83 m.
|
||||||
|
// The "uneven" value is the peak deviation of a rail on a 10 m chord, as in PLK Id-14 appendix 1; see test_track_uneven.cpp.
|
||||||
|
namespace track_uneven {
|
||||||
|
|
||||||
|
inline constexpr float most { 100.f }; // largest value of the attributes, mm
|
||||||
|
inline constexpr double stray_per_mm { 0.00145 }; // field amplitude per mm of "uneven", m
|
||||||
|
inline constexpr double cross_share { 0.53 }; // share of the field amplitude the rails settle apart by
|
||||||
|
inline constexpr double fade { 20.0 }; // length over which a track goes from the value at its end to its own, m
|
||||||
|
inline constexpr double dip_share { 0.4 }; // joint dip depth per mm of "uneven", mm
|
||||||
|
inline constexpr double dip_reach { 3.0 }; // reach of the dip on each side of a joint, m
|
||||||
|
|
||||||
|
// the value of each component of the unevenness, mm
|
||||||
|
struct amounts {
|
||||||
|
double up { 0.0 };
|
||||||
|
double side { 0.0 };
|
||||||
|
double cross { 0.0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wave {
|
||||||
|
double x;
|
||||||
|
double z;
|
||||||
|
double phase;
|
||||||
|
double share;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <std::size_t Count>
|
||||||
|
using wave_set = std::array<wave, Count>;
|
||||||
|
|
||||||
|
// the waves run in all directions of the plan, so no direction of a track is favoured
|
||||||
|
template <std::size_t Count>
|
||||||
|
wave_set<Count> make_waves( double const Turn, std::array<double, Count> const &Lengths, std::array<double, Count> const &Shares ) {
|
||||||
|
|
||||||
|
auto const golden { glm::pi<double>() * ( 3.0 - std::sqrt( 5.0 ) ) };
|
||||||
|
wave_set<Count> result {};
|
||||||
|
for( std::size_t i = 0; i < Count; ++i ) {
|
||||||
|
auto const number { glm::two_pi<double>() / 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 ] };
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr std::array<double, 8> wave_lengths { 5.3, 7.1, 9.7, 13.1, 17.9, 23.9, 31.7, 41.9 };
|
||||||
|
inline constexpr std::array<double, 8> wave_shares { 0.0721, 0.0997, 0.1344, 0.1703, 0.1549, 0.1386, 0.1226, 0.1074 };
|
||||||
|
|
||||||
|
// fewer short ones than sideways: the rails come near the limit in the vertical more often, with the peak still at it.
|
||||||
|
// The long ones hardly count on the 10 m chord: the body rises and falls on them, slowly enough for a pantograph to keep up
|
||||||
|
inline wave_set<8> const up_waves { make_waves<8>( 0.0, { 8.3, 11.9, 16.7, 23.9, 37.1, 47.3, 61.9, 83.3 }, { 0.14, 0.19, 0.22, 0.22, 0.23, 0.23, 0.23, 0.23 } ) };
|
||||||
|
inline wave_set<8> const x_waves { make_waves( 1.1, wave_lengths, wave_shares ) };
|
||||||
|
inline wave_set<8> const z_waves { make_waves( 2.3, wave_lengths, wave_shares ) };
|
||||||
|
// the tilt along each axis of the plan. Long waves only: with the short ones the cross level reaching its limit would break the limit of twist
|
||||||
|
inline wave_set<3> const tilt_x_waves { make_waves<3>( 0.7, { 23.9, 31.7, 41.9 }, { 1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0 } ) };
|
||||||
|
inline wave_set<3> const tilt_z_waves { make_waves<3>( 2.9, { 23.9, 31.7, 41.9 }, { 1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0 } ) };
|
||||||
|
|
||||||
|
template <std::size_t Count>
|
||||||
|
double sum( wave_set<Count> const &Waves, glm::dvec3 const &Point ) {
|
||||||
|
|
||||||
|
auto total { 0.0 };
|
||||||
|
for( auto const &wave : Waves ) {
|
||||||
|
total += wave.share * std::sin( wave.x * Point.x + wave.z * Point.z + wave.phase );
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
// displacement at the point for an amplitude of 1, sideways within +-1, up and down within +-1.69
|
||||||
|
inline glm::dvec3 field( glm::dvec3 const &Point ) {
|
||||||
|
|
||||||
|
return { sum( x_waves, Point ), sum( up_waves, Point ), sum( z_waves, Point ) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// how much lower the rail towards Across lies by settling apart, for an amplitude of 1. A tilt of the plan, so tracks laid towards each
|
||||||
|
// other meet at one cant. Follows the middle of a path, so plain tracks only
|
||||||
|
inline double level( glm::dvec3 const &Point, glm::dvec3 const &Across ) {
|
||||||
|
|
||||||
|
return cross_share * ( Across.x * sum( tilt_x_waves, Point ) + Across.z * sum( tilt_z_waves, Point ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// roll made by the field for an amplitude of 1; positive lowers the rail towards Across, as the roll of a path
|
||||||
|
inline double roll( glm::dvec3 const &Point, glm::dvec3 const &Across, double const Gauge ) {
|
||||||
|
|
||||||
|
return ( sum( up_waves, Point - Across * ( 0.5 * Gauge ) ) - sum( up_waves, Point + Across * ( 0.5 * Gauge ) ) ) / Gauge;
|
||||||
|
}
|
||||||
|
|
||||||
|
// value at a distance along a track: Full inside, exactly Atstart and Atend at its ends where it meets the neighbours
|
||||||
|
inline double along( double const Full, double const Atstart, double const Atend, double const Distance, double const Length ) {
|
||||||
|
|
||||||
|
auto const smooth = []( double const Share ) {
|
||||||
|
auto const share { std::clamp( Share, 0.0, 1.0 ) };
|
||||||
|
return share * share * ( 3.0 - 2.0 * share ); };
|
||||||
|
auto const ends { Length > 0.0 ? Atstart + ( Atend - Atstart ) * smooth( Distance / Length ) : Atstart };
|
||||||
|
return ends + ( Full - ends ) * std::min( smooth( Distance / fade ), smooth( ( Length - Distance ) / fade ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// distance from the joint behind the point
|
||||||
|
inline double rail_phase( double const Along, double const Rail ) {
|
||||||
|
|
||||||
|
auto const phase { std::fmod( Along, Rail ) };
|
||||||
|
return phase < 0.0 ? phase + Rail : phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
// share of the dip depth at a distance from the joint
|
||||||
|
inline double dip( double const Fromjoint ) {
|
||||||
|
|
||||||
|
if( Fromjoint >= dip_reach ) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
auto const height { std::cos( glm::half_pi<double>() * Fromjoint / dip_reach ) };
|
||||||
|
return height * height;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace track_uneven
|
||||||
@@ -386,6 +386,11 @@ bool TTrackFollower::Move(double fDistance, bool bPrimary)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
double TTrackFollower::RailPhase( double const Offset ) const {
|
||||||
|
|
||||||
|
return pCurrentTrack != nullptr ? pCurrentTrack->rail_phase( fCurrentDistance + fDirection * Offset ) : -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
bool TTrackFollower::ComputatePosition()
|
bool TTrackFollower::ComputatePosition()
|
||||||
{ // ustalenie współrzędnych XYZ
|
{ // ustalenie współrzędnych XYZ
|
||||||
if (pCurrentSegment) // o ile jest tor
|
if (pCurrentSegment) // o ile jest tor
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ public:
|
|||||||
// inline double GetLength(vector3 p1, vector3 cp1, vector3 cp2, vector3 p2)
|
// inline double GetLength(vector3 p1, vector3 cp1, vector3 cp2, vector3 p2)
|
||||||
//{ return pCurrentSegment->ComputeLength(p1,cp1,cp2,p2); };
|
//{ return pCurrentSegment->ComputeLength(p1,cp1,cp2,p2); };
|
||||||
// inline double GetRadius(double L, double d); //McZapkie-150503
|
// inline double GetRadius(double L, double d); //McZapkie-150503
|
||||||
|
inline glm::dvec3 UnevenBend() const {
|
||||||
|
return pCurrentSegment ? pCurrentSegment->uneven_bend( fCurrentDistance ) : glm::dvec3 { 0.0 }; };
|
||||||
|
// TTrack::rail_phase of the point Offset ahead of the axle along the vehicle
|
||||||
|
double RailPhase( double const Offset ) const;
|
||||||
bool Init(TTrack *pTrack, TDynamicObject *NewOwner, double fDir);
|
bool Init(TTrack *pTrack, TDynamicObject *NewOwner, double fDir);
|
||||||
void Reset();
|
void Reset();
|
||||||
void Render(float fNr);
|
void Render(float fNr);
|
||||||
|
|||||||
Reference in New Issue
Block a user