mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-20 17:24:53 +02:00
- SleeperRules: the rules beams are laid by and the faults counted where the layout breaks them - SleeperGeometry: plan helpers, beam boxes with their overlap and support tests and a grid of cells, shared by laying, filling gaps, keeping beams apart and checking instead of three copies of each - SleeperPaths: sampled paths, how near they run and whether one carries another; the tracks near a track and the stretches of a path where a test holds, shared by paths_around/unsettle_around and shared_zones/zones_of - SleeperFeatures: frogs and crossings the timbers turn to; SleeperLines: lines of beams, the rails they carry, where they part - SleeperLayout: clusters and their layout, given the epoch instead of reading it; SleeperModels: the models of the beams - SleeperWorker: the layouts kept per epoch and worked out in the background, behind a small interface - TrackSleepers: the facade, laying the instances, the shared zones and the check - sleepercheck reports a digest of all beams; geomhash skips empty chunks instead of crashing The layout is unchanged: geomhash, sleepercheck with the digest, wings, space, guards, crossings and switches give the same output on baltyk, zz_rozjazdy_id4, zz_rf_slips, zz_rf_wide, zz_slips_s42, zz_demo_przechylka and zz_connections.
710 lines
34 KiB
C++
710 lines
34 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/.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
|
|
#include "world/SleeperLayout.h"
|
|
#include "world/SleeperFeatures.h"
|
|
#include "world/SleeperGeometry.h"
|
|
#include "world/SleeperLines.h"
|
|
#include "world/SleeperPaths.h"
|
|
#include "world/SleeperRules.h"
|
|
#include "world/Track.h"
|
|
|
|
#include <optional>
|
|
|
|
namespace track_sleepers {
|
|
|
|
namespace {
|
|
|
|
// whether the plain sleepers of the two paths would meet anywhere, so they lie on timbers together there
|
|
bool share_anywhere( sampled_path const &Left, sampled_path const &Right ) {
|
|
|
|
auto const margin { Left.half + Right.half + half_width + end_gap };
|
|
if( Left.low.x > Right.high.x + margin || Right.low.x > Left.high.x + margin || Left.low.y > Right.high.y + margin || Right.low.y > Left.high.y + margin ) {
|
|
return false;
|
|
}
|
|
for( auto const &pair : { std::make_pair( &Left, &Right ), std::make_pair( &Right, &Left ) } ) {
|
|
auto const &path { *pair.first };
|
|
for( std::size_t index = 0; index < path.points.size(); ++index ) {
|
|
if( shares_with( path, path.points[ index ], heading( path, path.distances[ index ] ), *pair.second ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// the stretches of the path where another path of the cluster runs so near that their plain sleepers would meet
|
|
std::vector<glm::dvec2> zones_of( sampled_path const &Path, std::vector<sampled_path> const &Paths, std::vector<feature> const &Crossings ) {
|
|
|
|
return stretches_where( Path, [&]( glm::dvec3 const &Point, glm::dvec2 const &Direction ) {
|
|
return std::any_of( Paths.begin(), Paths.end(), [&]( sampled_path const &Other ) {
|
|
if( &Other == &Path ) {
|
|
return false;
|
|
}
|
|
// tracks crossing share the timbers where their sleepers would meet, others only where they're too close to part them
|
|
auto const crossed { std::any_of( Crossings.begin(), Crossings.end(), [&]( feature const &Feature ) {
|
|
return ( Feature.paths[ 0 ] == &Path && Feature.paths[ 1 ] == &Other ) || ( Feature.paths[ 0 ] == &Other && Feature.paths[ 1 ] == &Path ); } ) };
|
|
if( crossed ) {
|
|
return shares_with( Path, Point, Direction, Other );
|
|
}
|
|
double gap;
|
|
glm::dvec2 otherdirection { Direction };
|
|
return beside( Other, Point, shared_spacing( Path, Other ), gap, otherdirection ) && gap < shared_spacing( Path, Other ); } ); } );
|
|
}
|
|
|
|
// the stretches left of the first ones once the second ones are taken out, those shorter than the least length left out too
|
|
std::vector<glm::dvec2> without( std::vector<glm::dvec2> const &Stretches, std::vector<glm::dvec2> const &Taken, double const Least ) {
|
|
|
|
std::vector<glm::dvec2> result { Stretches };
|
|
for( auto const &taken : Taken ) {
|
|
std::vector<glm::dvec2> left;
|
|
for( auto const &stretch : result ) {
|
|
if( stretch.y <= taken.x || stretch.x >= taken.y ) {
|
|
left.push_back( stretch );
|
|
continue;
|
|
}
|
|
if( stretch.x < taken.x ) {
|
|
left.emplace_back( stretch.x, taken.x );
|
|
}
|
|
if( stretch.y > taken.y ) {
|
|
left.emplace_back( taken.y, stretch.y );
|
|
}
|
|
}
|
|
result = std::move( left );
|
|
}
|
|
result.erase( std::remove_if( result.begin(), result.end(), [&]( glm::dvec2 const &Stretch ) { return Stretch.y - Stretch.x < Least; } ), result.end() );
|
|
return result;
|
|
}
|
|
|
|
// the beams of a cluster of tracks, laid together: lines of timbers led along the paths near other paths one after another, each
|
|
// line across all the rails of the cluster it reaches, and the plain sleepers of every path where no line carries it, in step
|
|
// with the last timber carrying it
|
|
class cluster_layout {
|
|
|
|
public:
|
|
cluster_layout( std::vector<TTrack *> const &Tracks, std::uint64_t const Epoch ) :
|
|
tracks( Tracks ), epoch( Epoch ) {}
|
|
|
|
std::shared_ptr<cluster_beams> lay() {
|
|
|
|
auto result { std::make_shared<cluster_beams>() };
|
|
result->epoch = epoch;
|
|
for( auto *track : tracks ) {
|
|
result->beams[ track ];
|
|
for( int path = 0; path < path_count( *track ); ++path ) {
|
|
paths.emplace_back( sample( *track, path ) );
|
|
}
|
|
}
|
|
paths.erase( std::remove_if( paths.begin(), paths.end(), []( sampled_path const &Path ) { return Path.points.size() < 2; } ), paths.end() );
|
|
for( auto *track : tracks ) {
|
|
for( auto &other : paths_around( *track ) ) {
|
|
if( std::find( tracks.begin(), tracks.end(), other.track ) != tracks.end()
|
|
|| std::any_of( context.begin(), context.end(), [&]( sampled_path const &Known ) { return Known.track == other.track && Known.path == other.path; } ) ) {
|
|
continue;
|
|
}
|
|
context.emplace_back( std::move( other ) );
|
|
}
|
|
}
|
|
features = find_features( paths );
|
|
for( auto const &feature : features ) {
|
|
if( false == feature.frog && false == feature.grid ) {
|
|
crossings.push_back( feature );
|
|
}
|
|
}
|
|
zones.resize( paths.size() );
|
|
records.resize( paths.size() );
|
|
done.resize( paths.size() );
|
|
for( std::size_t index = 0; index < paths.size(); ++index ) {
|
|
zones[ index ] = ( tracks.size() > 1 || paths.size() > 1 ? zones_of( paths[ index ], paths, crossings ) : std::vector<glm::dvec2> {} );
|
|
}
|
|
// the lines of timbers, led along the path first by priority with a stretch near another path no line carries yet
|
|
for( std::size_t round = 0; round < 1000; ++round ) {
|
|
auto best { paths.size() };
|
|
glm::dvec2 stretch { 0.0 };
|
|
for( std::size_t index = 0; index < paths.size(); ++index ) {
|
|
auto const left { without( zones[ index ], covered( index ), 0.3 ) };
|
|
if( left.empty() ) {
|
|
continue;
|
|
}
|
|
if( best == paths.size() || before( paths[ index ], paths[ best ] ) ) {
|
|
best = index;
|
|
stretch = *std::max_element( left.begin(), left.end(), []( glm::dvec2 const &Left, glm::dvec2 const &Right ) { return Left.y - Left.x < Right.y - Right.x; } );
|
|
}
|
|
}
|
|
if( best == paths.size() ) {
|
|
break;
|
|
}
|
|
lead( best, stretch );
|
|
done[ best ].push_back( stretch );
|
|
}
|
|
// the plain sleepers of each path, between the timbers carrying it and its ends
|
|
for( std::size_t index = 0; index < paths.size(); ++index ) {
|
|
lay_plain( index );
|
|
}
|
|
keep_apart();
|
|
fill_gaps();
|
|
keep_apart();
|
|
for( auto const &laid : beams ) {
|
|
result->beams[ laid.owner ].push_back( laid.value );
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
struct owned {
|
|
TTrack const *owner;
|
|
beam value;
|
|
};
|
|
|
|
std::vector<TTrack *> const &tracks;
|
|
std::uint64_t const epoch; // of the layout the tracks are laid out for
|
|
std::vector<sampled_path> paths; // of the tracks of the cluster
|
|
std::vector<sampled_path> context; // of the tracks about them, whose rails and sleepers the beams keep clear of
|
|
std::vector<feature> features;
|
|
std::vector<feature> crossings; // the features where tracks cross, without the frogs
|
|
std::vector<std::vector<glm::dvec2>> zones; // of each path, where its plain sleepers would meet those of another one
|
|
// a line of timbers carrying a path: where along the path, and which way the line runs in plan
|
|
struct record {
|
|
double at;
|
|
glm::dvec2 along;
|
|
};
|
|
std::vector<std::vector<record>> records; // the lines of timbers carrying each path
|
|
std::vector<std::vector<glm::dvec2>> done; // the stretches of each path lines were led along
|
|
std::vector<owned> beams;
|
|
|
|
// the stretches of the path the lines laid so far carry it on, and those lines were led along
|
|
std::vector<glm::dvec2> covered( std::size_t const Index ) const {
|
|
|
|
auto result { done[ Index ] };
|
|
auto const at { distances( Index ) };
|
|
if( at.empty() ) {
|
|
return result;
|
|
}
|
|
auto start { at.front() };
|
|
auto last { at.front() };
|
|
for( std::size_t index = 1; index <= at.size(); ++index ) {
|
|
if( index < at.size() && at[ index ] - last <= 1.6 * pitch ) {
|
|
last = at[ index ];
|
|
continue;
|
|
}
|
|
result.emplace_back( start - 0.5 * pitch, last + 0.5 * pitch );
|
|
if( index < at.size() ) {
|
|
start = last = at[ index ];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// the pins of the lines led along the path: the rails of a path crossing this one so wide it's laid on a grid of beams; elsewhere
|
|
// the lines are spread evenly
|
|
std::vector<double> pins( sampled_path const &Path ) const {
|
|
|
|
std::vector<double> result;
|
|
for( auto const &feature : features ) {
|
|
if( false == feature.grid ) {
|
|
continue;
|
|
}
|
|
for( int side = 0; side < 2; ++side ) {
|
|
if( feature.paths[ side ] != &Path ) {
|
|
continue;
|
|
}
|
|
// under the rails of the other path, which runs across the beams
|
|
auto const *other { feature.paths[ 1 - side ] };
|
|
auto const sine { std::max( 0.5, std::sin( 2.0 * std::asin( feature.sine ) ) ) };
|
|
result.push_back( feature.at[ side ] - 0.5 * other->gauge / sine );
|
|
result.push_back( feature.at[ side ] + 0.5 * other->gauge / sine );
|
|
}
|
|
}
|
|
std::sort( result.begin(), result.end() );
|
|
return result;
|
|
}
|
|
|
|
// the line of the timbers carrying the path nearest to the distance on the side, between the distance and the reach from it
|
|
std::optional<record> record_near( std::size_t const Index, double const Distance, double const Side, double const Reach ) const {
|
|
|
|
std::optional<record> result;
|
|
for( auto const &line : records[ Index ] ) {
|
|
auto const along { ( line.at - Distance ) * Side };
|
|
if( along < -0.25 * pitch || along > Reach ) {
|
|
continue;
|
|
}
|
|
if( false == result.has_value() || std::abs( line.at - Distance ) < std::abs( result->at - Distance ) ) {
|
|
result = line;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// where along the path the lines of timbers carry it, in order
|
|
std::vector<double> distances( std::size_t const Index ) const {
|
|
|
|
std::vector<double> result;
|
|
for( auto const &line : records[ Index ] ) {
|
|
result.push_back( line.at );
|
|
}
|
|
std::sort( result.begin(), result.end() );
|
|
result.erase( std::unique( result.begin(), result.end(), []( double const Left, double const Right ) { return Right - Left < 0.05; } ), result.end() );
|
|
return result;
|
|
}
|
|
|
|
// where the path is at the distance and which way it runs in plan; false where it can't tell
|
|
bool frame_at( sampled_path const &Path, double const Distance, glm::dvec3 &Point, glm::vec3 &Angles, glm::dvec2 &Forward ) const {
|
|
|
|
Path.segment->RaPositionGet( Distance, Point, Angles );
|
|
glm::dvec3 behind, ahead;
|
|
glm::vec3 unused;
|
|
Path.segment->RaPositionGet( std::max( 0.0, Distance - 0.1 ), behind, unused );
|
|
Path.segment->RaPositionGet( std::min( Path.length, Distance + 0.1 ), ahead, unused );
|
|
Forward = plan( ahead - behind );
|
|
if( glm::length( Forward ) < 1e-6 ) {
|
|
return false;
|
|
}
|
|
Forward = glm::normalize( Forward );
|
|
return true;
|
|
}
|
|
|
|
// the lines at the distances along the path, turned as the frogs and crossings about them ask, but by no more than the rate
|
|
// from one line to the next, nor from the lines carrying the path at either end, so the ends of long timbers keep their spacing
|
|
void lay_lines( std::size_t const Index, std::vector<double> const &Stations, std::optional<record> const &First, std::optional<record> const &Last, double const Rate, bool const Led ) {
|
|
|
|
auto const &path { paths[ Index ] };
|
|
std::vector<double> distances;
|
|
std::vector<glm::dvec2> forwards;
|
|
std::vector<double> turns; // of the lines off square to the path, in plan
|
|
for( auto const station : Stations ) {
|
|
glm::dvec3 point;
|
|
glm::vec3 angles;
|
|
glm::dvec2 forward;
|
|
if( false == frame_at( path, station, point, angles, forward ) ) {
|
|
continue;
|
|
}
|
|
distances.push_back( station );
|
|
forwards.push_back( forward );
|
|
// the plain sleepers lie square to their track, turned only to meet the timbers next to them
|
|
auto along { Led ? beam_direction( point, forward, features ) : forward };
|
|
if( Led ) {
|
|
// the timbers about a crossing lie square to the mean way of the tracks they carry, the frogs about it don't turn them:
|
|
// square to the main tracks of a scissors crossing, to the bisector of a diamond
|
|
if( auto const mean { whole_at( path, point, forward, along ) }; mean.has_value() ) {
|
|
along = *mean;
|
|
}
|
|
}
|
|
turns.push_back( turn_off( forward, along ) );
|
|
}
|
|
if( distances.empty() ) {
|
|
return;
|
|
}
|
|
auto const limit = [&]( std::size_t const From, std::size_t const To, std::optional<record> const &Seed ) {
|
|
auto const step { To > From ? 1 : -1 };
|
|
std::optional<double> previous;
|
|
auto rate { Rate };
|
|
if( Seed.has_value() ) {
|
|
previous = turn_off( forwards[ From ], Seed->along );
|
|
if( false == Led ) {
|
|
// square after three sleepers
|
|
rate = std::max( 0.004, std::abs( *previous ) / 3.0 );
|
|
}
|
|
}
|
|
for( auto index { From }; ; index += step ) {
|
|
if( previous.has_value() ) {
|
|
turns[ index ] = std::clamp( turns[ index ], *previous - rate, *previous + rate );
|
|
}
|
|
previous = turns[ index ];
|
|
if( index == To ) {
|
|
break;
|
|
}
|
|
} };
|
|
limit( 0, distances.size() - 1, First );
|
|
limit( distances.size() - 1, 0, Last );
|
|
for( std::size_t index = 0; index < distances.size(); ++index ) {
|
|
lay_line( Index, distances[ index ], Led, rotated( forwards[ index ], turns[ index ] ) );
|
|
}
|
|
}
|
|
|
|
// the lines of timbers led along the stretch of the path, spread between the lines carrying it at either end, or the ends of the
|
|
// stretch, and the pins on it
|
|
void lead( std::size_t const Index, glm::dvec2 const &Stretch ) {
|
|
|
|
auto const &path { paths[ Index ] };
|
|
auto const back { record_near( Index, Stretch.x, -1.0, 2.0 * pitch ) };
|
|
auto const fore { record_near( Index, Stretch.y, 1.0, 2.0 * pitch ) };
|
|
anchor const start { back.has_value() ? anchor { back->at, 1.0 } : anchor { Stretch.x <= 0.01 ? 0.0 : Stretch.x, 0.5 } };
|
|
anchor const end { fore.has_value() ? anchor { fore->at, 1.0 } : anchor { Stretch.y >= path.length - 0.01 ? path.length : Stretch.y, 0.5 } };
|
|
if( end.at - start.at <= 0.0 ) {
|
|
return;
|
|
}
|
|
std::vector<anchor> anchors { start };
|
|
for( auto const pin : pins( path ) ) {
|
|
if( pin > start.at + 0.5 * pitch_min && pin < end.at - 0.5 * pitch_min && pin - anchors.back().at >= pitch_min ) {
|
|
anchors.push_back( { pin, 0.0 } );
|
|
}
|
|
}
|
|
anchors.push_back( end );
|
|
std::vector<double> stations;
|
|
for( std::size_t index = 1; index < anchors.size(); ++index ) {
|
|
spread( anchors[ index - 1 ], anchors[ index ], stations );
|
|
}
|
|
lay_lines( Index, stations, back, fore, timber_turn, true );
|
|
}
|
|
|
|
// where the timber led along the path through the point, lying the given way, carries both tracks of a crossing: the way it lies
|
|
// then, square to the middle between the ways of the tracks it carries turned furthest either way
|
|
std::optional<glm::dvec2> whole_at( sampled_path const &Path, glm::dvec3 const &Point, glm::dvec2 const &Forward, glm::dvec2 const &Along ) const {
|
|
|
|
if( crossings.empty() ) {
|
|
return std::nullopt;
|
|
}
|
|
std::vector<crossing> members;
|
|
find_crossings( Path, Point, Along, glm::dvec2 { -Along.y, Along.x }, 0.5 * length_cap + Path.reach, paths, members );
|
|
carry( Path, Forward, Along, members, false, false );
|
|
if( false == whole_with( Path, members ) ) {
|
|
return std::nullopt;
|
|
}
|
|
// the outermost tracks it carries, either way along it: the main tracks of a scissors crossing, the crossing ones of a diamond
|
|
std::pair<double, glm::dvec2> low { 0.0, Forward };
|
|
std::pair<double, glm::dvec2> high { 0.0, Forward };
|
|
for( auto const &crossing : members ) {
|
|
if( false == crossing.carried ) {
|
|
continue;
|
|
}
|
|
if( crossing.across < low.first ) {
|
|
low = { crossing.across, crossing.direction };
|
|
}
|
|
if( crossing.across > high.first ) {
|
|
high = { crossing.across, crossing.direction };
|
|
}
|
|
}
|
|
return rotated( Forward, 0.5 * ( turn_off( Forward, low.second ) + turn_off( Forward, high.second ) ) );
|
|
}
|
|
|
|
bool whole_with( sampled_path const &Path, std::vector<crossing> const &Members ) const {
|
|
|
|
auto const carries = [&]( sampled_path const *Carried ) {
|
|
return Carried == &Path || std::any_of( Members.begin(), Members.end(), [&]( crossing const &Crossing ) { return Crossing.carried && Crossing.path == Carried; } ); };
|
|
return std::any_of( crossings.begin(), crossings.end(), [&]( feature const &Feature ) {
|
|
return carries( Feature.paths[ 0 ] ) && carries( Feature.paths[ 1 ] ); } );
|
|
}
|
|
|
|
// the plain sleepers of the path on the stretches no line of timbers carries it, in step with the timbers at their ends
|
|
void lay_plain( std::size_t const Index ) {
|
|
|
|
auto const &path { paths[ Index ] };
|
|
auto const at { distances( Index ) };
|
|
std::vector<std::pair<anchor, anchor>> stretches;
|
|
if( at.empty() ) {
|
|
stretches.push_back( { { 0.0, 0.5 }, { path.length, 0.5 } } );
|
|
}
|
|
else {
|
|
stretches.push_back( { { 0.0, 0.5 }, { at.front(), 1.0 } } );
|
|
for( std::size_t index = 1; index < at.size(); ++index ) {
|
|
if( at[ index ] - at[ index - 1 ] > 1.6 * pitch ) {
|
|
stretches.push_back( { { at[ index - 1 ], 1.0 }, { at[ index ], 1.0 } } );
|
|
}
|
|
}
|
|
stretches.push_back( { { at.back(), 1.0 }, { path.length, 0.5 } } );
|
|
}
|
|
for( auto const &stretch : stretches ) {
|
|
auto const span { stretch.second.at - stretch.first.at };
|
|
if( span <= 0.0 || span < ( stretch.first.offset + stretch.second.offset - 1.0 ) * pitch + 0.5 * pitch_min ) {
|
|
continue;
|
|
}
|
|
std::vector<double> stations;
|
|
spread( stretch.first, stretch.second, stations );
|
|
// turned back square to the path from the timbers at the ends of the stretch
|
|
auto const first { stretch.first.offset == 1.0 ? record_near( Index, stretch.first.at, 1.0, 0.05 ) : std::optional<record> {} };
|
|
auto const last { stretch.second.offset == 1.0 ? record_near( Index, stretch.second.at, -1.0, 0.05 ) : std::optional<record> {} };
|
|
lay_lines( Index, stations, first, last, sleeper_turn, false );
|
|
}
|
|
}
|
|
|
|
// the beams on the line through the point of the path at the distance: the timber of the line led along it, taking the rails of
|
|
// the paths of the cluster it reaches, or the plain sleeper of the path alone
|
|
void lay_line( std::size_t const Index, double const Distance, bool const Led, glm::dvec2 const &Along ) {
|
|
|
|
auto const &path { paths[ Index ] };
|
|
glm::dvec3 point;
|
|
glm::vec3 angles;
|
|
glm::dvec2 forward;
|
|
if( false == frame_at( path, Distance, point, angles, forward ) ) {
|
|
return;
|
|
}
|
|
auto const along { Along };
|
|
glm::dvec2 const across { -along.y, along.x };
|
|
auto const reach { 0.5 * length_cap + path.reach };
|
|
std::vector<crossing> members, others;
|
|
find_crossings( path, point, along, across, reach, paths, members );
|
|
find_crossings( path, point, along, across, reach, context, others );
|
|
if( Led ) {
|
|
// about a crossing the timbers take all the tracks whose sleepers would meet, elsewhere only those too close to part
|
|
carry( path, forward, along, members, false );
|
|
if( false == whole_with( path, members ) ) {
|
|
for( auto &crossing : members ) {
|
|
crossing.carried = false;
|
|
}
|
|
carry( path, forward, along, members, true );
|
|
}
|
|
}
|
|
std::vector<band> bands { band_of( path, 0.0, std::max( crossing_sine_min, std::abs( glm::dot( forward, along ) ) ), forward ) };
|
|
for( auto const *crossings : { &members, &others } ) {
|
|
for( auto const &crossing : *crossings ) {
|
|
if( crossing.carried ) {
|
|
bands.push_back( band_of( *crossing.path, crossing.across, crossing.sine, crossing.direction ) );
|
|
}
|
|
else if( crossing.sine >= crossing_sine_min && angle_between( crossing.direction, forward ) < grid_angle ) {
|
|
bands.push_back( band_of( *crossing.path, crossing.across, crossing.sine, crossing.direction, false ) );
|
|
}
|
|
}
|
|
}
|
|
// a timber carrying both tracks of a crossing lies whole under all the tracks it carries
|
|
auto const whole { Led && whole_with( path, members ) };
|
|
std::vector<piece> pieces;
|
|
emit_line( bands, pieces, whole );
|
|
cut_long( bands, pieces );
|
|
// the parts of a timber parted between tracks going apart turn square to their own track, the more the further apart the
|
|
// tracks run, till square where their plain sleepers wouldn't touch any more
|
|
std::vector<glm::dvec2> turned( pieces.size(), along );
|
|
for( std::size_t index = 0; index < pieces.size() && pieces.size() > 1; ++index ) {
|
|
auto const *own { pieces[ index ].single };
|
|
if( own == nullptr ) {
|
|
continue;
|
|
}
|
|
band const *nearest { nullptr };
|
|
for( auto const &other : bands ) {
|
|
if( &other != own && other.carried && ( nearest == nullptr || std::abs( other.centre - own->centre ) < std::abs( nearest->centre - own->centre ) ) ) {
|
|
nearest = &other;
|
|
}
|
|
}
|
|
if( nearest == nullptr ) {
|
|
continue;
|
|
}
|
|
auto const spacing { std::abs( nearest->centre - own->centre ) * std::min( own->sine, nearest->sine ) };
|
|
auto const parted { shared_spacing( *own->path, *nearest->path ) };
|
|
auto const apart { own->path->half + nearest->path->half + end_gap };
|
|
auto const share { apart > parted ? std::clamp( ( spacing - parted ) / ( apart - parted ), 0.0, 1.0 ) : 1.0 };
|
|
turned[ index ] = rotated( along, share * turn_off( along, own->direction ) );
|
|
}
|
|
auto const turned_for = [&]( sampled_path const *Path ) {
|
|
for( std::size_t index = 0; index < pieces.size(); ++index ) {
|
|
if( pieces[ index ].single != nullptr && pieces[ index ].single->path == Path ) {
|
|
return turned[ index ];
|
|
}
|
|
}
|
|
return along; };
|
|
if( Led ) {
|
|
records[ Index ].push_back( { Distance, turned_for( &path ) } );
|
|
for( auto const &crossing : members ) {
|
|
if( crossing.carried ) {
|
|
records[ crossing.path - paths.data() ].push_back( { crossing.at, turned_for( crossing.path ) } );
|
|
}
|
|
}
|
|
}
|
|
// the beam lies tilted with the roll of the path, as the ballast is
|
|
glm::dvec3 const flatacross { across.x, 0.0, across.y };
|
|
glm::dvec3 const up { 0.0, 1.0, 0.0 };
|
|
auto const roll { static_cast<double>( angles.x ) };
|
|
auto const rolledacross { flatacross * std::cos( roll ) - up * std::sin( roll ) };
|
|
auto const rolledup { flatacross * std::sin( roll ) + up * std::cos( roll ) };
|
|
auto const top { point - rolledup * static_cast<double>( path.track->rail_height() ) };
|
|
// a sleeper of a plain track running alone and square to it, the rest are the timbers of the switches and crossings
|
|
auto const carried { std::count_if( bands.begin(), bands.end(), []( band const &Band ) { return Band.carried; } ) };
|
|
auto const plain { path.track->eType == tt_Normal && carried == 1 && pieces.size() == 1 && false == pieces.front().clipped && std::abs( glm::dot( forward, along ) ) > 0.9998 };
|
|
for( std::size_t index = 0; index < pieces.size(); ++index ) {
|
|
auto const &piece { pieces[ index ] };
|
|
auto const &way { turned[ index ] };
|
|
if( way == along ) {
|
|
beams.push_back( { path.track, { top + rolledacross * piece.from, top + rolledacross * piece.to, glm::dvec3 { along.x, 0.0, along.y }, plain, piece.corefrom - piece.from, piece.coreto - piece.from } } );
|
|
continue;
|
|
}
|
|
// turned about where the middle of its track crosses the line
|
|
auto const centre { top + rolledacross * piece.single->centre };
|
|
glm::dvec3 const turnedacross { glm::dvec3 { -way.y, 0.0, way.x } * std::cos( roll ) - up * std::sin( roll ) };
|
|
beams.push_back( { path.track, { centre + turnedacross * ( piece.from - piece.single->centre ), centre + turnedacross * ( piece.to - piece.single->centre ), glm::dvec3 { way.x, 0.0, way.y }, false, piece.corefrom - piece.from, piece.coreto - piece.from } } );
|
|
}
|
|
}
|
|
|
|
// a sleeper of the path put in where the rails of it go further without a beam under them than the pitch lets them, turned
|
|
// as the beams about it are: where beams were left out or lines of timbers led along different paths meet
|
|
void fill_gaps() {
|
|
|
|
auto const count { beams.size() };
|
|
// the beams by the cells of a grid they reach into
|
|
plan_grid<std::size_t> grid { 2.0 };
|
|
for( std::size_t laid = 0; laid < count; ++laid ) {
|
|
auto const &beam { beams[ laid ].value };
|
|
grid.insert( plan( 0.5 * ( beam.from + beam.to ) ), 0.5 * glm::length( plan( beam.to - beam.from ) ) + half_width, laid );
|
|
}
|
|
for( std::size_t index = 0; index < paths.size(); ++index ) {
|
|
auto const &path { paths[ index ] };
|
|
// where along the path beams lie under each of its rails, and which way they run
|
|
auto const steps { std::max( 2, static_cast<int>( std::ceil( path.length / 0.05 ) ) ) };
|
|
std::vector<double> gaps;
|
|
std::vector<glm::dvec2> turns;
|
|
for( auto const side : { -1.0, 1.0 } ) {
|
|
std::optional<std::pair<double, glm::dvec2>> last;
|
|
for( int step = 0; step <= steps; ++step ) {
|
|
auto const distance { path.length * step / steps };
|
|
glm::dvec3 point, across;
|
|
path.segment->plan_frame( distance, point, across );
|
|
auto const rail { plan( point + across * ( side * 0.5 * path.gauge ) ) };
|
|
std::optional<glm::dvec2> found;
|
|
auto const *cell { grid.at( rail ) };
|
|
if( cell == nullptr ) {
|
|
continue;
|
|
}
|
|
for( auto const laid : *cell ) {
|
|
auto const box { box_of( beams[ laid ].value ) };
|
|
if( 2.0 * box.half >= 0.1 && holds( box, rail ) ) {
|
|
found = glm::dvec2 { -box.axis.y, box.axis.x };
|
|
break;
|
|
}
|
|
}
|
|
if( false == found.has_value() ) {
|
|
continue;
|
|
}
|
|
if( last.has_value() && distance - last->first > 0.06 && distance - last->first + 2.0 * half_width > 1.25 * pitch_max ) {
|
|
// the middle of the stretch without one, and the way the beams at its ends run
|
|
gaps.push_back( 0.5 * ( distance + last->first ) );
|
|
auto along { last->second + ( glm::dot( last->second, *found ) < 0.0 ? -*found : *found ) };
|
|
turns.push_back( glm::length( along ) > 1e-9 ? glm::normalize( along ) : heading( path, gaps.back() ) );
|
|
}
|
|
last = std::make_pair( distance, *found );
|
|
}
|
|
}
|
|
// a sleeper in each, those of both rails about one place put in once
|
|
for( std::size_t gap = 0; gap < gaps.size(); ++gap ) {
|
|
if( std::any_of( gaps.begin(), gaps.begin() + gap, [&]( double const Other ) { return std::abs( Other - gaps[ gap ] ) < 0.3; } ) ) {
|
|
continue;
|
|
}
|
|
lay_line( index, gaps[ gap ], false, turns[ gap ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
// a beam over one laid before it is left out: where tracks cross too wide for one timber to take both, or the lines of two
|
|
// stretches meet at an angle
|
|
void keep_apart() {
|
|
|
|
plan_grid<beam_box> grid { 3.0 };
|
|
std::vector<owned> kept;
|
|
auto const clear_of_laid = [&]( beam_box const &Box ) {
|
|
return grid.all_about( Box.centre, Box.half + half_width, [&]( beam_box const &Other ) { return apart( Box, Other ); } ); };
|
|
for( auto laid : beams ) {
|
|
auto const length { glm::length( laid.value.to - laid.value.from ) };
|
|
if( length < 0.1 ) {
|
|
continue;
|
|
}
|
|
if( false == clear_of_laid( box_of( laid.value ) ) ) {
|
|
// shortened from either end or both, down to the least overhang past the rails it carries, till it's clear
|
|
auto const direction { ( laid.value.to - laid.value.from ) / length };
|
|
auto const most { std::max( 0.0, laid.value.corefrom - overhang_min ) }; // off the from end
|
|
auto const mostto { std::max( 0.0, length - laid.value.coreto - overhang_min ) };
|
|
auto found { false };
|
|
for( int step = 1; step <= 20 && false == found; ++step ) {
|
|
auto const share { step / 20.0 };
|
|
for( auto const ends : { glm::dvec2 { 0.0, mostto * share }, glm::dvec2 { most * share, 0.0 }, glm::dvec2 { most * share, mostto * share } } ) {
|
|
auto shortened { laid.value };
|
|
shortened.from = laid.value.from + direction * ends.x;
|
|
shortened.to = laid.value.to - direction * ends.y;
|
|
shortened.corefrom -= ends.x;
|
|
shortened.coreto -= ends.x;
|
|
if( clear_of_laid( box_of( shortened ) ) ) {
|
|
shortened.plain = false;
|
|
laid.value = shortened;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if( false == found ) {
|
|
++faults().dropped;
|
|
continue;
|
|
}
|
|
++faults().clipped;
|
|
}
|
|
auto const box { box_of( laid.value ) };
|
|
grid.insert( box.centre, box.half + half_width, box );
|
|
kept.push_back( laid );
|
|
}
|
|
beams = std::move( kept );
|
|
}
|
|
};
|
|
|
|
|
|
} // namespace
|
|
|
|
// the tracks whose timbers are laid together with those of the track: all those reached from it through paths whose plain
|
|
// sleepers would meet, in the order of their names
|
|
std::vector<TTrack *> cluster_of( TTrack &Track ) {
|
|
|
|
std::vector<TTrack *> result { &Track };
|
|
for( std::size_t index = 0; index < result.size() && result.size() < cluster_cap; ++index ) {
|
|
auto const around { paths_around( *result[ index ] ) };
|
|
for( auto const &path : around ) {
|
|
if( path.track != result[ index ] ) {
|
|
continue;
|
|
}
|
|
for( auto const &other : around ) {
|
|
if( other.track == result[ index ] || std::find( result.begin(), result.end(), other.track ) != result.end() ) {
|
|
continue;
|
|
}
|
|
if( share_anywhere( path, other ) ) {
|
|
result.push_back( other.track );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
std::sort( result.begin(), result.end(), []( TTrack const *Left, TTrack const *Right ) {
|
|
return ( Left->name() != Right->name() ? Left->name() < Right->name() : Left < Right ); } );
|
|
return result;
|
|
}
|
|
|
|
std::shared_ptr<cluster_beams> lay_out( std::vector<TTrack *> const &Tracks, std::uint64_t const Epoch ) {
|
|
|
|
return cluster_layout( Tracks, Epoch ).lay();
|
|
}
|
|
|
|
// how far the paths of the switch stand off one plane where they share the timbers of its panel, and where along the diverging
|
|
// path; the switch itself is out of shape for its timbers where it's more than the tolerance
|
|
std::pair<double, double> panel_off_plane( TTrack &Track ) {
|
|
|
|
std::pair<double, double> result { 0.0, 0.0 };
|
|
if( Track.eType != tt_Switch ) {
|
|
return result;
|
|
}
|
|
auto const main { sample( Track, 0 ) };
|
|
auto const diverging { sample( Track, 1 ) };
|
|
if( main.points.size() < 2 || diverging.points.size() < 2 ) {
|
|
return result;
|
|
}
|
|
auto alongside { false };
|
|
for( std::size_t index = 0; index < diverging.points.size(); ++index ) {
|
|
double gap;
|
|
glm::dvec2 direction;
|
|
if( false == beside( main, diverging.points[ index ], shared_spacing( diverging, main ), gap, direction ) ) {
|
|
if( alongside ) {
|
|
// apart far enough to lay their own
|
|
break;
|
|
}
|
|
// at the start, which both paths share
|
|
continue;
|
|
}
|
|
alongside = true;
|
|
auto const off { off_plane( diverging, diverging.points[ index ], main ) };
|
|
if( off > result.first ) {
|
|
result = { off, diverging.distances[ index ] };
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
} // namespace track_sleepers
|