17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-02 15:09:13 +01:00

feat(track): track unevenness declared in the track entry

Usage, optional attributes of a track entry, before endtrack:

  uneven <mm>       peak deviation of a rail on a 10 m chord, 0-100; normal and switch
  unevenup <mm>     up and down alone, instead of uneven; normal and switch
  unevenside <mm>   sideways alone, instead of uneven; normal and switch
  unevencross <mm>  cross level alone, instead of uneven; normal only
  jointdip <mm>     depth of the dip at rail joints, every sound distance; normal only,
                    default 0.4 of the up and down component

Values of uneven follow PLK Id-14 appendix 1: 6 mm is the limit at 160 km/h, 10 at 120,
18 at 80, 35 at 40; half of it for a track in a good state. Example:

  uneven 18
  unevencross 25
  jointdip 6
  endtrack

The same fields are in the editor, track panel, section Ride and sound. See
track_uneven_doc.md.

The unevenness is a displacement field over the plan of the scenery, with no random
numbers: one place strays the same every time, and joined tracks, the paths of a switch,
sleepermodel and axles at one place move together. Tracks meet at a joint at the lower of
their values. Joined switches take the lowest of theirs, the tracks joined to them come to
it, and a track with none of a component keeps the switch to its design in it. The clatter
comes from the dipping joints. Curve radius and grade for the physics stay as designed.

Tracks with it get geometry every 2 m on straights: 4x the vertices on Baltyk with all
tracks flagged, 0.2 ms more per frame on td.scn.
This commit is contained in:
maj00r
2026-10-31 15:47:10 +01:00
parent 66a940d4f5
commit e5266e1e6b
15 changed files with 774 additions and 4 deletions

View File

@@ -118,6 +118,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
fStep = fNewStep;
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
if( pOwner->eType == tt_Switch
&& fStep * (3.0 * Global.SplineFidelity) > fLength ) {
@@ -366,6 +370,52 @@ Math3D::vector3 TSegment::GetPoint(double const fDistance) 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 ) / 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 } ) * 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 (bCurve) {
// można by wprowadzić uproszczony wzór dla okręgów płaskich
auto const t = FastGetTFromS(fDistance); // aproksymacja dystansu na krzywej Beziera na parametr (t)
@@ -500,6 +550,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
pt,
glm::normalize( norm ),
glm::vec2 { ( jmm1 * ShapePoints[ j ].texture.x + m1 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv1 } );
settle_vertex( Output.back(), Origin, pos1, parallel1, s - step );
}
// jeśli jest wskaźnik do tablicy
if (p && *p && !j) // to dla pierwszego punktu
@@ -518,6 +569,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
pt,
glm::normalize( norm ),
glm::vec2 { ( jmm2 * ShapePoints[ j ].texture.x + m2 * ShapePoints[ j + iNumShapePoints ].texture.x ) / texturescale, tv2 } );
settle_vertex( Output.back(), Origin, pos2, parallel2, s );
}
// jeśli jest wskaźnik do tablicy
// to dla pierwszego punktu
@@ -540,6 +592,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
pt,
glm::normalize( norm ),
glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv1 } );
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.y += ShapePoints[ j ].position.y;
@@ -550,6 +603,7 @@ bool TSegment::RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin,
pt,
glm::normalize( norm ),
glm::vec2 { ShapePoints[ j ].texture.x / texturescale, tv2 } );
settle_vertex( Output.back(), Origin, pos2, parallel2, s );
}
}
}

View File

@@ -47,6 +47,7 @@ class TSegment
glm::dvec3 Point2;
float fRoll1 { 0.f };
float fRoll2 { 0.f }; // przechyłka na końcach
bool m_uneven { false }; // see TrackUneven.h
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> fTsSlope; // nachylenie dt/du (= dt/ds * fStep) w tych samych punktach
@@ -67,6 +68,12 @@ class TSegment
glm::dvec3 RaInterpolate(double const t) const;
glm::dvec3 RaInterpolate0(double const t) 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:
bool bCurve = false;
@@ -106,6 +113,13 @@ public:
/// <param name="position">Calculated position</param>
/// <param name="rotation">Calculated rotation</param>
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;
inline
glm::dvec3

View File

@@ -47,6 +47,7 @@ TIsolated *TIsolated::pRoot = nullptr;
TTrack::profiles_array TTrack::m_profiles;
TTrack::profiles_map TTrack::m_profilesmap;
int TTrack::s_layoutstamp { 0 };
TSwitchExtension::TSwitchExtension(TTrack *owner, int const what)
{ // na początku wszystko puste
@@ -325,6 +326,7 @@ void TTrack::ConnectPrevPrev(TTrack *pTrack, int typ)
{ //łączenie torów - Point1 własny do Point1 cudzego
if (pTrack)
{ //(pTrack) może być zwrotnicą, a (this) tylko zwykłym odcinkiem
++s_layoutstamp;
trPrev = pTrack;
iPrevDirection = pTrack->eType == tt_Switch ? 0 : typ & 2;
pTrack->trPrev = this;
@@ -335,6 +337,7 @@ void TTrack::ConnectPrevNext(TTrack *pTrack, int typ)
{ //łaczenie torów - Point1 własny do Point2 cudzego
if (pTrack)
{
++s_layoutstamp;
trPrev = pTrack;
iPrevDirection = typ | 1; // 1:zwykły lub pierwszy zwrotnicy, 3:drugi zwrotnicy
pTrack->trNext = this;
@@ -354,6 +357,7 @@ void TTrack::ConnectNextPrev(TTrack *pTrack, int typ)
{ //łaczenie torów - Point2 własny do Point1 cudzego
if (pTrack)
{
++s_layoutstamp;
trNext = pTrack;
iNextDirection = pTrack->eType == tt_Switch ? 0 : typ & 2;
pTrack->trPrev = this;
@@ -373,6 +377,7 @@ void TTrack::ConnectNextNext(TTrack *pTrack, int typ)
{ //łaczenie torów - Point2 własny do Point2 cudzego
if (pTrack)
{
++s_layoutstamp;
trNext = pTrack;
iNextDirection = typ | 1; // 1:zwykły lub pierwszy zwrotnicy, 3:drugi zwrotnicy
pTrack->trNext = this;
@@ -633,6 +638,27 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
m_profile1 = fetch_track_rail_profile( railprofile );
}
}
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" ) {
// memory cell holding friction value modifiers
m_friction.first = parser->getToken<std::string>();
@@ -674,6 +700,10 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
*parser >> token;
str = token;
}
// the segments were made before the attributes were read, and unevenness changes them
if( is_uneven() ) {
init_segments( false );
}
// alternatywny zapis nazwy odcinka izolowanego - po znaku "@" w nazwie toru
if ((i = m_name.find("@")) != std::string::npos && i < m_name.length())
{
@@ -687,6 +717,159 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
// 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() {
location( (
@@ -705,6 +888,14 @@ void TTrack::init_segments( bool const Initial ) {
fTexHeight1 -= fTexHeightOffset;
fTexHeightOffset = 0.f;
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) {
// Ra: łuki segmentowane co 5m albo 314-kątem foremnym
@@ -1351,6 +1542,8 @@ glm::vec3 TTrack::get_nearest_point(const glm::dvec3 &point) const
// wypełnianie tablic VBO
void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) {
++s_layoutstamp;
Geometry1.clear();
Geometry2.clear();
if( SwitchExtension ) {
@@ -2445,6 +2638,21 @@ TTrack::export_as_text_( std::ostream &Output ) const {
&& false == m_profile1.first.empty() ) {
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() ) {
Output << "friction " << m_friction.first << ' ';
}

View File

@@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/Classes.h"
#include "world/Segment.h"
#include "world/TrackUneven.h"
#include "model/material.h"
#include "scene/scenenode.h"
#include "utilities/Names.h"
@@ -238,6 +239,12 @@ public:
TEnvironmentType eEnvironment = e_flat; // dźwięk i oświetlenie
int iAction = 0; // czy modyfikowany eventami (specjalna obsługa przy skanowaniu)
float fOverhead = -1.0; // można normalnie pobierać prąd (0 dla jazdy bezprądowej po danym odcinku, >0-z opuszczonym i ograniczeniem prędkości)
float m_uneven { 0.f }; // mm, see TrackUneven.h; plain tracks and switches
// the components set apart from m_uneven, mm; negative: m_uneven. Cross level on plain tracks only
float m_unevenup { -1.f };
float m_unevenside { -1.f };
float m_unevencross { -1.f };
float m_jointdip { -1.f }; // depth of the dip at rail joints, mm; negative: from m_uneven; plain tracks only
private:
double fVelocity = -1.0; // ograniczenie prędkości // prędkość dla AI (powyżej rośnie prawdopowobieństwo wykolejenia)
public:
@@ -339,6 +346,15 @@ public:
void VelocitySet(float v);
double VelocityGet();
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();
bool DoubleSlip() const;
static void fetch_default_profiles();
@@ -370,6 +386,22 @@ private:
void create_track_rail_profile( gfx::vertex_array &Right, gfx::vertex_array &Left );
void create_track_blade_profile( gfx::vertex_array &Right, gfx::vertex_array &Left );
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_side_profile( gfx::vertex_array &Right, gfx::vertex_array &Left, gfx::vertex_array const &Road, bool const Forcetransition = false );
/// <summary>

125
world/TrackUneven.h Normal file
View File

@@ -0,0 +1,125 @@
/*
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.
// 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.6 }; // 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 };
inline wave_set<8> const up_waves { make_waves( 0.0, wave_lengths, wave_shares ) };
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 ) };
// long waves only: with the short ones the cross level reaching its limit would break the limit of twist
inline wave_set<3> const cross_waves { make_waves<3>( 0.7, { 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, each axis within +-1
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 right rail lies by settling apart, for an amplitude of 1. Follows the middle of a path, so plain tracks only
inline double level( glm::dvec3 const &Point ) {
return cross_share * sum( cross_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

View File

@@ -267,6 +267,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()
{ // ustalenie współrzędnych XYZ
if (pCurrentSegment) // o ile jest tor

View File

@@ -39,6 +39,10 @@ public:
// inline double GetLength(vector3 p1, vector3 cp1, vector3 cp2, vector3 p2)
//{ return pCurrentSegment->ComputeLength(p1,cp1,cp2,p2); };
// 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);
void Reset();
void Render(float fNr);