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 );