From f155956669b603c491e375534c51356c7f646763 Mon Sep 17 00:00:00 2001 From: maj00r Date: Sat, 24 Oct 2026 00:23:50 +0200 Subject: [PATCH] perf(sleepers): the rails of each track for the fastenings worked out once and kept until its geometry is made again, it goes far or the crossings change, not again for every track laid around it; the fastenings laid only out to 30 m past where they're drawn, the tracks further out keeping their beams to lay them on as they come near; out past 50 m the tracks laid while the laying in a frame took under 2 ms, the rest in the frames after --- rendering/opengl46/opengl46renderer.cpp | 27 +++- rendering/opengl46/opengl46renderer.h | 2 + world/SleeperFastenings.cpp | 185 ++++++++++++++---------- world/SleeperFastenings.h | 13 +- world/SleeperRules.h | 6 + world/Track.cpp | 1 + world/TrackSleepers.cpp | 95 ++++++++---- world/TrackSleepers.h | 10 +- 8 files changed, 225 insertions(+), 114 deletions(-) diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index 992b43c7..8bd4c0fb 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -6203,6 +6203,12 @@ void opengl46_renderer::Render_Timbers( scene::basic_cell::path_sequence::const_ m_timbersweepframe = m_framestamp; track_sleepers::release_far( camera, range + 600.0 ); } + if( m_timberlayframe != m_framestamp ) { + m_timberlayframe = m_framestamp; + m_timberlaying = 0.0; + } + // the fastenings laid out to a little past where they're drawn + auto const fasteningrange = std::min( static_cast( Global.SleeperDistance ), track_sleepers::fastening_range ) + track_sleepers::fastening_lead; glm::mat4 const originmodelview = OpenGLMatrices.data( GL_MODELVIEW ); for( auto &bucket : m_timberbuckets ) { bucket.instances.clear(); @@ -6243,15 +6249,28 @@ void opengl46_renderer::Render_Timbers( scene::basic_cell::path_sequence::const_ for( auto first { First }; first != Last; ++first ) { auto *track = *first; if( false == track->m_visible || false == track->m_sleepers3d.active ) { continue; } - if( glm::length( track->location() - camera ) > range + track->radius() ) { + auto const trackdistance = glm::length( track->location() - camera ); + if( trackdistance > range + track->radius() ) { // worked out ahead, in the background, before it comes in range - if( false == shadows && track->m_sleepers3d.dirty && glm::length( track->location() - camera ) < range + track->radius() + 300.0 ) { + if( false == shadows && track->m_sleepers3d.dirty && trackdistance < range + track->radius() + 300.0 ) { track_sleepers::prefetch( *track ); } continue; } - if( track->m_sleepers3d.dirty ) { - track_sleepers::lay( *track ); + auto const fastened = ( trackdistance < fasteningrange + track->radius() ); + if( track->m_sleepers3d.dirty || ( fastened && false == track->m_sleepers3d.fastened ) ) { + // the nearest at once, the others while the frame has the time, the cells nearest coming first; till then a track draws + // what it had + if( trackdistance < track_sleepers::lay_near + track->radius() || m_timberlaying < track_sleepers::lay_budget ) { + auto const start = std::chrono::steady_clock::now(); + if( track->m_sleepers3d.dirty ) { + track_sleepers::lay( *track, fastened ); + } + else { + track_sleepers::fasten( *track ); + } + m_timberlaying += std::chrono::duration( std::chrono::steady_clock::now() - start ).count(); + } } auto const &laid = track->m_sleepers3d; if( laid.timbers == nullptr ) { continue; } diff --git a/rendering/opengl46/opengl46renderer.h b/rendering/opengl46/opengl46renderer.h index 20f90777..0c269640 100644 --- a/rendering/opengl46/opengl46renderer.h +++ b/rendering/opengl46/opengl46renderer.h @@ -610,6 +610,8 @@ class opengl46_renderer : public gfx_renderer { unsigned int m_framestamp; // id of currently rendered gfx frame unsigned int m_timbersweepframe { 0 }; // the frame the beams of the tracks far away were last let go in + unsigned int m_timberlayframe { 0 }; // the frame the time spent laying the beams is counted for + double m_timberlaying { 0.0 }; // in ms float m_framerate; double m_updateaccumulator{0.0}; std::string m_debugtimestext; diff --git a/world/SleeperFastenings.cpp b/world/SleeperFastenings.cpp index dfae41f8..e047230d 100644 --- a/world/SleeperFastenings.cpp +++ b/world/SleeperFastenings.cpp @@ -52,90 +52,119 @@ fastened_rails::fastened_rails( TTrack &Track ) { if( bedded( *Other ) && std::find( tracks.begin(), tracks.end(), Other ) == tracks.end() ) { tracks.push_back( Other ); } } ); + auto &kept { fastened_rails::kept() }; for( auto *track : tracks ) { - auto const size { track->rail_dimensions() }; - auto const middle { 0.5 * std::abs( track->fTrackWidth ) + size.web }; - auto const cut { track_rails::cut_rails( *track ) }; - auto const switched { track->eType == tt_Switch && track->SwitchExtension != nullptr }; - auto const rise { wing_rise( *track ) }; - // what the rail is along the path: a blade, the stock rail of a blade, checked - auto const add = [&]( rail &&Line, int const Path, int const Side ) { - if( switched ) { - if( auto const heel { blade_heel( *track, Path, Side ) }; heel > 0.0 ) { - Line.blade = { 0.0, heel }; - } - Line.stock = stock_stretch( *track, Path, Side ); + auto &rails { kept[ track ] }; + if( rails == nullptr ) { + rails = std::make_shared const>( rails_of( *track ) ); + } + m_kept.push_back( rails ); + m_rails.insert( m_rails.end(), rails->begin(), rails->end() ); + } +} + +fastened_rails::kept_rails &fastened_rails::kept() { + + static kept_rails tracks; + static unsigned int stamp { 0 }; + if( stamp != track_rails::memo_stamp() ) { + tracks.clear(); + stamp = track_rails::memo_stamp(); + } + return tracks; +} + +void forget_rails( TTrack const &Track ) { + + fastened_rails::kept().erase( &Track ); +} + +std::vector fastened_rails::rails_of( TTrack &Track ) { + + std::vector result; + auto const size { Track.rail_dimensions() }; + auto const middle { 0.5 * std::abs( Track.fTrackWidth ) + size.web }; + auto const cut { track_rails::cut_rails( Track ) }; + auto const switched { Track.eType == tt_Switch && Track.SwitchExtension != nullptr }; + auto const rise { wing_rise( Track ) }; + // what the rail is along the path: a blade, the stock rail of a blade, checked + auto const add = [&]( rail &&Line, int const Path, int const Side ) { + if( switched ) { + if( auto const heel { blade_heel( Track, Path, Side ) }; heel > 0.0 ) { + Line.blade = { 0.0, heel }; } - if( cut ) { - for( auto const &check : track_rails::rail_checks( *track )[ Path ][ Side > 0 ? 1 : 0 ] ) { - Line.checks.emplace_back( check.from, check.to ); - } - Line.guards = track->m_railguards[ Path ][ Side > 0 ? 1 : 0 ]; + Line.stock = stock_stretch( Track, Path, Side ); + } + if( cut ) { + for( auto const &check : track_rails::rail_checks( Track )[ Path ][ Side > 0 ? 1 : 0 ] ) { + Line.checks.emplace_back( check.from, check.to ); } - m_rails.emplace_back( std::move( Line ) ); }; - // the rail of the side of the path from and to along it, by points the given way out from the middle of the path - auto const sampled = [&]( int const Path, int const Side, double const From, double const To, double const Middle, knots::line const &Offsets ) { - auto const *segment { path_segment( *track, Path ) }; - auto const count { std::max( 1, static_cast( std::ceil( ( To - From ) / fastening_step ) ) ) }; - rail line; - line.track = track; - line.path = Path; - line.side = Side; - line.foot = size.web - size.low; - line.low = glm::dvec2 { std::numeric_limits::max() }; - line.high = glm::dvec2 { std::numeric_limits::lowest() }; - for( int step = 0; step <= count; ++step ) { - auto const distance { From + ( To - From ) * step / count }; - glm::dvec3 point, across; - segment->plan_frame( distance, point, across ); - auto const flat { plan( across ) }; - auto const inward { glm::length( flat ) > 1e-9 ? glm::normalize( flat ) * static_cast( -Side ) : glm::dvec2 { 1.0, 0.0 } }; - auto const position { plan( point ) - inward * ( Middle + knots::sample( Offsets, distance, 0.0 ) ) }; - line.points.push_back( position ); - line.heights.push_back( point.y ); - line.inward.push_back( inward ); - line.distances.push_back( distance ); - line.low = glm::min( line.low, position ); - line.high = glm::max( line.high, position ); - } - return line; }; - for( int path = 0; path < path_count( *track ); ++path ) { - auto const *segment { path_segment( *track, path ) }; - if( segment == nullptr ) { - continue; - } - auto const length { segment->GetLength() }; - for( auto const side : { -1, 1 } ) { - // the rail in its parts between the cuts at the crossings, as they're laid: a wing rail bent out from the path; on over the - // gap at an obtuse crossing, where its check rail lies - auto cuts { cut ? track->m_railplans[ path ][ side > 0 ].cuts : std::vector {} }; - std::stable_sort( cuts.begin(), cuts.end(), []( track_rails::rail_cut const &A, track_rails::rail_cut const &B ) { return A.at < B.at; } ); - for( std::size_t part = 0; part <= cuts.size(); ++part ) { - auto const *before { part > 0 ? &cuts[ part - 1 ] : nullptr }; - auto const *after { part < cuts.size() ? &cuts[ part ] : nullptr }; - auto const from { std::max( 0.0, before != nullptr ? before->start : 0.0 ) }; - auto const to { std::min( length, after != nullptr ? std::max( after->end, after->start ) : length ) }; - if( to - from < fastening_merge ) { - continue; - } - auto line { sampled( path, side, from, to, middle, track_rails::part_offsets( before, after ) ) }; - line.rises = track_rails::part_rises( rise > 0.0 ? track_rails::wing_shares( before, after, stock_stretch( *track, path, side ) ) : knots::line {}, rise, blade_heel( *track, path, side ) ); - add( std::move( line ), path, side ); + Line.guards = Track.m_railguards[ Path ][ Side > 0 ? 1 : 0 ]; + } + result.emplace_back( std::move( Line ) ); }; + // the rail of the side of the path from and to along it, by points the given way out from the middle of the path + auto const sampled = [&]( int const Path, int const Side, double const From, double const To, double const Middle, knots::line const &Offsets ) { + auto const *segment { path_segment( Track, Path ) }; + auto const count { std::max( 1, static_cast( std::ceil( ( To - From ) / fastening_step ) ) ) }; + rail line; + line.track = &Track; + line.path = Path; + line.side = Side; + line.foot = size.web - size.low; + line.low = glm::dvec2 { std::numeric_limits::max() }; + line.high = glm::dvec2 { std::numeric_limits::lowest() }; + for( int step = 0; step <= count; ++step ) { + auto const distance { From + ( To - From ) * step / count }; + glm::dvec3 point, across; + segment->plan_frame( distance, point, across ); + auto const flat { plan( across ) }; + auto const inward { glm::length( flat ) > 1e-9 ? glm::normalize( flat ) * static_cast( -Side ) : glm::dvec2 { 1.0, 0.0 } }; + auto const position { plan( point ) - inward * ( Middle + knots::sample( Offsets, distance, 0.0 ) ) }; + line.points.push_back( position ); + line.heights.push_back( point.y ); + line.inward.push_back( inward ); + line.distances.push_back( distance ); + line.low = glm::min( line.low, position ); + line.high = glm::max( line.high, position ); + } + return line; }; + for( int path = 0; path < path_count( Track ); ++path ) { + auto const *segment { path_segment( Track, path ) }; + if( segment == nullptr ) { + continue; + } + auto const length { segment->GetLength() }; + for( auto const side : { -1, 1 } ) { + // the rail in its parts between the cuts at the crossings, as they're laid: a wing rail bent out from the path; on over the + // gap at an obtuse crossing, where its check rail lies + auto cuts { cut ? Track.m_railplans[ path ][ side > 0 ].cuts : std::vector {} }; + std::stable_sort( cuts.begin(), cuts.end(), []( track_rails::rail_cut const &A, track_rails::rail_cut const &B ) { return A.at < B.at; } ); + for( std::size_t part = 0; part <= cuts.size(); ++part ) { + auto const *before { part > 0 ? &cuts[ part - 1 ] : nullptr }; + auto const *after { part < cuts.size() ? &cuts[ part ] : nullptr }; + auto const from { std::max( 0.0, before != nullptr ? before->start : 0.0 ) }; + auto const to { std::min( length, after != nullptr ? std::max( after->end, after->start ) : length ) }; + if( to - from < fastening_merge ) { + continue; } - // the guard rails of the crossings at wide angles inside the rail, the flangeway from it - for( auto const &guard : cut ? track->m_railguards[ path ][ side > 0 ] : std::vector {} ) { - auto const from { std::max( 0.0, guard.x ) }; - auto const to { std::min( length, guard.y ) }; - if( to - from < fastening_merge ) { - continue; - } - auto line { sampled( path, side, from, to, middle - ( track_rails::flangeway + size.head ), {} ) }; - line.guard = true; - m_rails.emplace_back( std::move( line ) ); + auto line { sampled( path, side, from, to, middle, track_rails::part_offsets( before, after ) ) }; + line.rises = track_rails::part_rises( rise > 0.0 ? track_rails::wing_shares( before, after, stock_stretch( Track, path, side ) ) : knots::line {}, rise, blade_heel( Track, path, side ) ); + add( std::move( line ), path, side ); + } + // the guard rails of the crossings at wide angles inside the rail, the flangeway from it + for( auto const &guard : cut ? Track.m_railguards[ path ][ side > 0 ] : std::vector {} ) { + auto const from { std::max( 0.0, guard.x ) }; + auto const to { std::min( length, guard.y ) }; + if( to - from < fastening_merge ) { + continue; } + auto line { sampled( path, side, from, to, middle - ( track_rails::flangeway + size.head ), {} ) }; + line.guard = true; + result.emplace_back( std::move( line ) ); } } } + return result; } std::vector fastened_rails::rails_on( beam const &Beam ) const { @@ -150,7 +179,7 @@ std::vector fastened_rails::rails_on( beam const &Beam ) co auto const low { glm::min( start, plan( Beam.to ) ) }; auto const high { glm::max( start, plan( Beam.to ) ) }; std::vector hits; - for( auto const &line : m_rails ) { + for( rail const &line : m_rails ) { if( line.high.x < low.x || line.low.x > high.x || line.high.y < low.y || line.low.y > high.y ) { continue; } @@ -387,7 +416,7 @@ void fastened_rails::along( std::vector &Fastenings, beam const &Beam double to; }; std::vector stretches; - for( auto const &line : m_rails ) { + for( rail const &line : m_rails ) { // a guard rail along the timber lies in the fastening of its running rail if( line.guard || line.high.x < low.x || line.low.x > high.x || line.high.y < low.y || line.low.y > high.y ) { continue; diff --git a/world/SleeperFastenings.h b/world/SleeperFastenings.h index dc8d88da..41c1ee80 100644 --- a/world/SleeperFastenings.h +++ b/world/SleeperFastenings.h @@ -10,7 +10,10 @@ http://mozilla.org/MPL/2.0/. #pragma once #include +#include +#include #include +#include #include #include @@ -92,6 +95,13 @@ private: role at( double const Distance ) const; bool guarded( double const Distance ) const; }; + // the rails of each track, worked out as the first track around it is laid and kept until it's laid anew or goes far, or + // the crossings change + using kept_rails = std::unordered_map const>>; + static kept_rails &kept(); + friend void forget_rails( TTrack const &Track ); + // the rails of the track, in their parts between the cuts, and its guard rails + static std::vector rails_of( TTrack &Track ); // the rails lying on the timber, in the order along it, one where several lie on one another, but the blades std::vector rails_on( beam const &Beam ) const; @@ -106,7 +116,8 @@ private: // across leave, on the timber with the rail over its middle or over its side toward the next one void along( std::vector &Fastenings, beam const &Beam, beam_model const &Model ) const; - std::vector m_rails; + std::vector const>> m_kept; + std::vector> m_rails; }; // the fastenings of a track with them in the model of its timbers, as laid on the timbers laid with it diff --git a/world/SleeperRules.h b/world/SleeperRules.h index 30267cfa..34e85b47 100644 --- a/world/SleeperRules.h +++ b/world/SleeperRules.h @@ -87,6 +87,12 @@ double constexpr pad_narrowest { 0.02 }; double constexpr fastening_overlap { 0.01 }; // how far from the camera the fastenings are drawn at most double constexpr fastening_range { 120.0 }; +// how much further out the fastenings are laid, there as the camera comes near +double constexpr fastening_lead { 30.0 }; +// how near the camera the beams of a track are laid at once as they're needed; further out while the laying in a frame took less +// than the budget, in ms, the rest left for the frames after +double constexpr lay_near { 50.0 }; +double constexpr lay_budget { 2.0 }; // how many tracks a cluster takes at most std::size_t constexpr cluster_cap { 256 }; diff --git a/world/Track.cpp b/world/Track.cpp index 82e8a954..00c8a3e9 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -1604,6 +1604,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { } } } + track_sleepers::forget_rails( *this ); } auto const lay_rail = [&]( int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, double const From, bool const Openstart, bool const Openend, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts ) { return this->lay_rail( layout, drawing, Path, Profile, Side, Texturelength, From, Openstart, Openend, Geometry, Cuts ); }; diff --git a/world/TrackSleepers.cpp b/world/TrackSleepers.cpp index 4f3502d2..f819d8c1 100644 --- a/world/TrackSleepers.cpp +++ b/world/TrackSleepers.cpp @@ -123,29 +123,22 @@ std::pair shared_zone( TTrack const &Track, int const Path ) { return { zones.front().first, zones.back().second }; } -void lay( TTrack &Track ) { +namespace { + +// the beams as instances of the parts of the model of the track: those of the fastenings with them, or left for later and the beams +// kept for them meanwhile +void instance( TTrack &Track, std::vector &&Beams, bool const Fastened ) { auto &laid { Track.m_sleepers3d }; - std::vector placed; - if( laid.active && in_background() && timber_model( Track ) != nullptr ) { - // worked out in the background; till then the track keeps what it has, and tries again - auto beams { laid_out( Track ) }; - if( false == beams.has_value() ) { - ask_layout( Track ); - return; - } - placed = std::move( *beams ); - } - laid.dirty = false; for( auto &instances : laid.instances ) { instances.clear(); } laid.whole.clear(); laid.plain.clear(); laid.chunks.clear(); - laid.timbers = timber_model( Track ); - laid.plains = plain_model( Track ); - if( false == laid.active || laid.timbers == nullptr ) { + laid.fastened = true; + std::vector {}.swap( laid.beams ); + if( laid.timbers == nullptr ) { return; } // the sleepers of the model of the track itself (sleepermodel) at the height they're drawn at without the 3D sleepers: the @@ -153,16 +146,6 @@ void lay( TTrack &Track ) { auto const ownplains { laid.plains != nullptr && Track.m_sleeper_enabled && laid.plains != plain_model() }; auto const plainlift { ownplains ? Track.rail_height() + Track.m_sleeper_offset.z : 0.f }; auto const &parts { *laid.timbers }; - if( auto const panel { panel_off_plane( Track ) }; panel.first > coplanar_tolerance ) { - WriteLog( - "sleepers3d: switch \"" + Track.name() + "\" has its paths " + to_string( panel.first * 1000.0, 1 ) + " mm off one plane " - + to_string( panel.second, 2 ) + " m along, where they share the timbers of its panel" ); - } - laid.laying = true; - auto const beams { in_background() ? std::move( placed ) : place( Track ) }; - laid.laying = false; - // the layout of the cluster isn't needed by this track any more, the instances below are what's drawn - forget_layout( Track ); // the rails around, where they lie on the timbers, for the fastenings of the model; found at the first timber, none for plain sleepers std::optional rails; @@ -199,7 +182,7 @@ void lay( TTrack &Track ) { runbeams.clear(); }; - for( auto const &beam : beams ) { + for( auto const &beam : Beams ) { if( runbeams.empty() ) { for( int part = 0; part < part_count; ++part ) { run.first[ part ] = static_cast( laid.instances[ part ].size() ); @@ -265,7 +248,7 @@ void lay( TTrack &Track ) { put( part_end, scale, parts.spans[ part_end ].x, -half ); put( part_begin, scale, parts.spans[ part_begin ].y, half ); } - if( parts.fastened() && false == ( beam.plain && laid.plains != nullptr ) ) { + if( Fastened && parts.fastened() && false == ( beam.plain && laid.plains != nullptr ) ) { if( false == rails.has_value() ) { rails.emplace( Track ); } @@ -296,6 +279,55 @@ void lay( TTrack &Track ) { laid.whole.shrink_to_fit(); laid.plain.shrink_to_fit(); laid.chunks.shrink_to_fit(); + if( false == Fastened && parts.fastened() ) { + laid.fastened = false; + laid.beams = std::move( Beams ); + } +} + +} // namespace + +void lay( TTrack &Track, bool const Fastened ) { + + auto &laid { Track.m_sleepers3d }; + std::vector placed; + if( laid.active && in_background() && timber_model( Track ) != nullptr ) { + // worked out in the background; till then the track keeps what it has, and tries again + auto beams { laid_out( Track ) }; + if( false == beams.has_value() ) { + ask_layout( Track ); + return; + } + placed = std::move( *beams ); + } + laid.dirty = false; + laid.timbers = timber_model( Track ); + laid.plains = plain_model( Track ); + if( false == laid.active || laid.timbers == nullptr ) { + instance( Track, {}, true ); + return; + } + if( auto const panel { panel_off_plane( Track ) }; panel.first > coplanar_tolerance ) { + WriteLog( + "sleepers3d: switch \"" + Track.name() + "\" has its paths " + to_string( panel.first * 1000.0, 1 ) + " mm off one plane " + + to_string( panel.second, 2 ) + " m along, where they share the timbers of its panel" ); + } + laid.laying = true; + auto beams { in_background() ? std::move( placed ) : place( Track ) }; + laid.laying = false; + // the layout of the cluster isn't needed by this track any more, the instances below are what's drawn + forget_layout( Track ); + instance( Track, std::move( beams ), Fastened ); +} + +void fasten( TTrack &Track ) { + + auto &laid { Track.m_sleepers3d }; + if( laid.fastened || laid.dirty ) { + return; + } + auto beams { std::move( laid.beams ) }; + instance( Track, std::move( beams ), true ); } void release_far( glm::dvec3 const &Camera, double const Range ) { @@ -309,15 +341,19 @@ void release_far( glm::dvec3 const &Camera, double const Range ) { next = 0; } auto *track { tracks[ next++ ] }; - if( track == nullptr || false == track->m_sleepers3d.active || track->m_sleepers3d.laying ) { + if( track == nullptr || track->m_sleepers3d.laying ) { continue; } if( glm::length( track->location() - Camera ) - track->radius() <= Range ) { continue; } + forget_rails( *track ); + if( false == track->m_sleepers3d.active ) { + continue; + } auto &laid { track->m_sleepers3d }; forget_layout( *track ); - auto const holds { laid.chunks.capacity() > 0 || laid.whole.capacity() > 0 || laid.plain.capacity() > 0 + auto const holds { laid.chunks.capacity() > 0 || laid.whole.capacity() > 0 || laid.plain.capacity() > 0 || laid.beams.capacity() > 0 || std::any_of( laid.instances.begin(), laid.instances.end(), []( std::vector const &Instances ) { return Instances.capacity() > 0; } ) }; if( false == holds ) { continue; @@ -328,6 +364,7 @@ void release_far( glm::dvec3 const &Camera, double const Range ) { std::vector {}.swap( laid.whole ); std::vector {}.swap( laid.plain ); std::vector {}.swap( laid.chunks ); + std::vector {}.swap( laid.beams ); laid.dirty = true; } } diff --git a/world/TrackSleepers.h b/world/TrackSleepers.h index 1e3c831b..ba9772e2 100644 --- a/world/TrackSleepers.h +++ b/world/TrackSleepers.h @@ -103,6 +103,8 @@ struct laid { std::vector whole; // transforms of the timbers of a model without parts, drawn whole std::vector plain; // transforms of the plain sleepers, whole models, relative to the origin of the track std::vector chunks; + bool fastened { false }; // the fastenings are laid, else they're laid as the track comes near where they're drawn + std::vector beams; // kept for the fastenings till they're laid }; // how far the ballast of a track with beams is lowered, so they stick out of it @@ -147,10 +149,14 @@ struct pack_model { // the models of sleepers and timbers in the scenery data, found once in models/ by the names of their files telling sleepers // or timbers: podklad, podrozjazd, sleeper, timber. the timber ones first, then by the name std::vector const &pack_models(); -// the beams of the track placed anew, as instances of the parts of the model -void lay( TTrack &Track ); +// the beams of the track placed anew, as instances of the parts of the model; with the fastenings, or those left for later +void lay( TTrack &Track, bool const Fastened = true ); +// the fastenings left out laid on the beams of the track +void fasten( TTrack &Track ); // the beams of the tracks around the track to be placed again, as they depend on where it runs void unsettle_around( TTrack &Track ); +// the rails of the track kept for the fastenings of the tracks around let go, as they're laid anew or the track goes far +void forget_rails( TTrack const &Track ); // the beams worked out in a thread of their own as the tracks come near, while the simulation runs; off in the editor, where the // tracks change and the beams are worked out as they're needed. turning it off waits for the beams being worked out void lay_in_background( bool const Background );