17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-01 09:12:08 +01:00
Files
maszyna/world/TrackUneven.h
maj00r 02abc7df0a 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.
2026-10-31 18:33:35 +01:00

183 lines
7.6 KiB
C++

/*
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 <charconv>
#include <cmath>
#include <sstream>
#include <string>
#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 };
};
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 {
double x;
double z;
double phase;
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>
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 ) );
}
// how much of the displacement a point of the profile takes, by its depth under the rail top: all of it down to the foot of the rail,
// none at the foot of the ballast, which stays on the ground as designed
inline double bed_share( double const Depth, double const Rail, double const Height ) {
return Height > 0.0 ? std::clamp( 1.0 - ( Depth - Rail ) / Height, 0.0, 1.0 ) : 1.0;
}
// 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