mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-20 15:04:54 +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.
277 lines
12 KiB
C++
277 lines
12 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/SleeperLines.h"
|
|
#include "world/SleeperGeometry.h"
|
|
#include "world/SleeperRules.h"
|
|
|
|
namespace track_sleepers {
|
|
|
|
// the beams spread evenly between two anchors, the pin at the first one included and the one at the last left to the next stretch
|
|
void spread( anchor const &From, anchor const &To, std::vector<double> &Stations ) {
|
|
|
|
auto const span { To.at - From.at };
|
|
if( span <= 0.0 ) {
|
|
return;
|
|
}
|
|
auto count { static_cast<int>( std::round( span / pitch + 1.0 - From.offset - To.offset ) ) };
|
|
if( From.offset == 0.5 && To.offset == 0.5 ) {
|
|
// a track between two others gets a beam however short it is
|
|
count = std::max( 1, count );
|
|
}
|
|
// the rails are carried at most so far apart, the beams pulled closer where it takes one more
|
|
while( span / ( count - 1 + From.offset + To.offset ) > pitch_max ) {
|
|
++count;
|
|
}
|
|
auto const intervals { count - 1 + From.offset + To.offset };
|
|
if( count < 1 || intervals <= 0.0 ) {
|
|
if( From.offset == 0.0 ) {
|
|
Stations.push_back( From.at );
|
|
}
|
|
return;
|
|
}
|
|
auto const step { span / intervals };
|
|
auto const last { To.offset == 0.0 ? count - 1 : count };
|
|
for( int index = 0; index < last; ++index ) {
|
|
Stations.push_back( From.at + ( From.offset + index ) * step );
|
|
}
|
|
}
|
|
|
|
void find_crossings( sampled_path const &Path, glm::dvec3 const &Point, glm::dvec2 const &Along, glm::dvec2 const &Across, double const Reach, std::vector<sampled_path> const &Around, std::vector<crossing> &Crossings ) {
|
|
|
|
for( auto const &other : Around ) {
|
|
if( &other == &Path || false == near_bounds( other, Point, Reach ) ) {
|
|
continue;
|
|
}
|
|
for( std::size_t index = 0; index + 1 < other.points.size(); ++index ) {
|
|
auto const &from { other.points[ index ] };
|
|
auto const &to { other.points[ index + 1 ] };
|
|
auto const fromside { glm::dot( plan( from - Point ), Along ) };
|
|
auto const toside { glm::dot( plan( to - Point ), Along ) };
|
|
if( ( fromside < 0.0 ) == ( toside < 0.0 ) ) {
|
|
continue;
|
|
}
|
|
auto const share { fromside / ( fromside - toside ) };
|
|
auto const point { from + ( to - from ) * share };
|
|
auto const across { glm::dot( plan( point - Point ), Across ) };
|
|
if( std::abs( across ) > Reach || std::abs( point.y - Point.y ) > level_tolerance ) {
|
|
continue;
|
|
}
|
|
auto const direction { plan( to - from ) };
|
|
if( glm::dot( direction, direction ) < 1e-12 ) {
|
|
continue;
|
|
}
|
|
auto const unit { glm::normalize( direction ) };
|
|
auto const at { glm::mix( other.distances[ index ], other.distances[ index + 1 ], share ) };
|
|
Crossings.push_back( { &other, point, unit, at, across, std::abs( glm::dot( unit, Along ) ), false } );
|
|
}
|
|
}
|
|
}
|
|
|
|
// the paths whose rails the line of the beam through the point of the path takes, marked among the paths of its cluster crossing
|
|
// the line: those whose plain sleepers would meet the sleepers of the path or of any the line takes already, at a narrow enough
|
|
// angle to the line, and in one plane with the path unless they run too close to be parted
|
|
// the timber takes the rails of a path whose plain sleepers would meet those of a path it takes already; where Tight, only of one so
|
|
// close that their sleepers couldn't be laid apart even cut short
|
|
void carry( sampled_path const &Path, glm::dvec2 const &Forward, glm::dvec2 const &Along, std::vector<crossing> &Crossings, bool const Tight, bool const Count ) {
|
|
|
|
struct carried_path {
|
|
double across;
|
|
double half;
|
|
double rails;
|
|
double sine;
|
|
};
|
|
std::vector<carried_path> carried { { 0.0, Path.half, Path.rails, std::max( crossing_sine_min, std::abs( glm::dot( Forward, Along ) ) ) } };
|
|
for( auto added { true }; added; ) {
|
|
added = false;
|
|
for( auto &crossing : Crossings ) {
|
|
if( crossing.carried || crossing.sine < crossing_sine_min || angle_between( crossing.direction, Forward ) >= grid_angle ) {
|
|
continue;
|
|
}
|
|
auto const close { std::any_of( carried.begin(), carried.end(), [&]( carried_path const &Carried ) {
|
|
auto const reach { Tight ? crossing.path->rails + Carried.rails + 2.0 * overhang_min + end_gap : crossing.path->half + Carried.half + end_gap };
|
|
return std::abs( crossing.across - Carried.across ) * std::min( crossing.sine, Carried.sine ) < reach; } ) };
|
|
if( false == close ) {
|
|
continue;
|
|
}
|
|
if( off_plane( *crossing.path, crossing.point, Path ) > coplanar_tolerance ) {
|
|
if( std::abs( crossing.across ) * crossing.sine >= shared_spacing( *crossing.path, Path ) ) {
|
|
continue;
|
|
}
|
|
// too close to the path to be parted, out of the plane of its rails
|
|
if( Count ) {
|
|
++faults().offplane;
|
|
}
|
|
}
|
|
crossing.carried = true;
|
|
added = true;
|
|
carried.push_back( { crossing.across, crossing.path->half, crossing.path->rails, crossing.sine } );
|
|
}
|
|
}
|
|
// the line doesn't carry the rails of a track past those of one it doesn't, that one lays its own beams there
|
|
auto const blocks = [&]( crossing const &Other ) {
|
|
return false == Other.carried && Other.sine >= crossing_sine_min && angle_between( Other.direction, Forward ) < grid_angle; };
|
|
for( auto demoted { true }; demoted; ) {
|
|
demoted = false;
|
|
for( auto &crossing : Crossings ) {
|
|
if( false == crossing.carried ) {
|
|
continue;
|
|
}
|
|
if( std::any_of( Crossings.begin(), Crossings.end(), [&]( struct crossing const &Other ) {
|
|
return blocks( Other ) && Other.across * crossing.across > 0.0 && std::abs( Other.across ) < std::abs( crossing.across ); } ) ) {
|
|
crossing.carried = false;
|
|
demoted = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
band band_of( sampled_path const &Path, double const Across, double const Sine, glm::dvec2 const &Direction, bool const Carried ) {
|
|
|
|
return { Across - Path.rails / Sine, Across + Path.rails / Sine, overhang / Sine, overhang_min / Sine, Carried, &Path, Across, Direction, Sine };
|
|
}
|
|
|
|
// the beams on the line through the point: one timber under all the rails it carries, the ends keeping clear of the rails of the
|
|
// tracks the line doesn't carry and of the sleepers under them. the timber is parted between the rails of two tracks where each
|
|
// keeps at least its least overhang past its rails, unless it's whole: about a crossing of the tracks, as in a scissors crossing
|
|
void emit_line( std::vector<band> &Bands, std::vector<piece> &Pieces, bool const Whole ) {
|
|
|
|
std::sort( Bands.begin(), Bands.end(), []( band const &Left, band const &Right ) { return Left.from < Right.from; } );
|
|
auto open { false };
|
|
piece current;
|
|
auto end { 0.0 }, endoverhang { 0.0 }, endoverhangmin { 0.0 };
|
|
// the far side of the last rails not carried, and of the sleepers under them
|
|
auto fenced { false };
|
|
auto fencerails { 0.0 }, fencereach { 0.0 };
|
|
for( auto const &next : Bands ) {
|
|
if( false == next.carried ) {
|
|
if( open ) {
|
|
auto const kept { std::max( 0.0, std::clamp( next.from - next.overhang - end - end_gap, std::min( endoverhangmin, next.from - end - end_gap ), endoverhang ) ) };
|
|
current.clipped = current.clipped || kept < endoverhang;
|
|
if( kept < endoverhangmin ) {
|
|
++faults().clipped;
|
|
}
|
|
current.to = end + kept;
|
|
current.coreto = end;
|
|
Pieces.push_back( current );
|
|
open = false;
|
|
}
|
|
if( false == fenced || next.to > fencerails ) {
|
|
fencerails = next.to;
|
|
fencereach = next.to + next.overhang;
|
|
}
|
|
fenced = true;
|
|
continue;
|
|
}
|
|
if( false == open ) {
|
|
current = piece();
|
|
auto startoverhang { next.overhang };
|
|
if( fenced ) {
|
|
startoverhang = std::max( 0.0, std::clamp( next.from - fencereach - end_gap, std::min( next.overhangmin, next.from - fencerails - end_gap ), next.overhang ) );
|
|
current.clipped = startoverhang < next.overhang;
|
|
if( startoverhang < next.overhangmin ) {
|
|
++faults().clipped;
|
|
}
|
|
}
|
|
current.from = next.from - startoverhang;
|
|
current.corefrom = next.from;
|
|
current.single = &next;
|
|
end = next.to;
|
|
endoverhang = next.overhang;
|
|
endoverhangmin = next.overhangmin;
|
|
open = true;
|
|
continue;
|
|
}
|
|
if( false == Whole && next.from - end >= endoverhangmin + next.overhangmin + end_gap ) {
|
|
// the room between them shared by their overhangs
|
|
auto const room { next.from - end - end_gap };
|
|
auto const ours { std::min( endoverhang, room * endoverhang / ( endoverhang + next.overhang ) ) };
|
|
auto const theirs { std::min( next.overhang, room - ours ) };
|
|
current.to = end + ours;
|
|
current.coreto = end;
|
|
current.clipped = current.clipped || ours < endoverhang;
|
|
Pieces.push_back( current );
|
|
current = piece();
|
|
current.from = next.from - theirs;
|
|
current.corefrom = next.from;
|
|
current.clipped = theirs < next.overhang;
|
|
current.single = &next;
|
|
end = next.to;
|
|
endoverhang = next.overhang;
|
|
endoverhangmin = next.overhangmin;
|
|
continue;
|
|
}
|
|
// one timber under the rails of all the tracks it carries
|
|
current.single = nullptr;
|
|
if( next.to > end ) {
|
|
end = next.to;
|
|
endoverhang = next.overhang;
|
|
endoverhangmin = next.overhangmin;
|
|
}
|
|
}
|
|
if( open ) {
|
|
current.to = end + endoverhang;
|
|
current.coreto = end;
|
|
Pieces.push_back( current );
|
|
}
|
|
}
|
|
|
|
// a timber longer than the cap cut in two where the rails of the tracks it carries are furthest apart, the ends sharing the
|
|
// room between them, till every piece is short enough or there's nowhere to cut
|
|
void cut_long( std::vector<band> const &Bands, std::vector<piece> &Pieces ) {
|
|
|
|
for( std::size_t index = 0; index < Pieces.size(); ) {
|
|
auto &whole { Pieces[ index ] };
|
|
if( whole.to - whole.from <= length_cap ) {
|
|
++index;
|
|
continue;
|
|
}
|
|
// the widest gap between the rails under the timber
|
|
band const *left { nullptr };
|
|
band const *right { nullptr };
|
|
band const *last { nullptr };
|
|
auto widest { 0.0 };
|
|
auto end { std::numeric_limits<double>::lowest() };
|
|
for( auto const &band : Bands ) {
|
|
if( false == band.carried || band.from < whole.from || band.to > whole.to ) {
|
|
continue;
|
|
}
|
|
if( last != nullptr && band.from - end > widest ) {
|
|
widest = band.from - end;
|
|
left = last;
|
|
right = &band;
|
|
}
|
|
if( band.to > end ) {
|
|
end = band.to;
|
|
last = &band;
|
|
}
|
|
}
|
|
if( left == nullptr ) {
|
|
++index;
|
|
continue;
|
|
}
|
|
auto const room { std::max( 0.0, widest - end_gap ) };
|
|
auto const ours { std::min( left->overhang, room * left->overhang / ( left->overhang + right->overhang ) ) };
|
|
auto const theirs { std::min( right->overhang, room - ours ) };
|
|
if( ours < left->overhangmin || theirs < right->overhangmin ) {
|
|
++faults().clipped;
|
|
}
|
|
piece second { right->from - theirs, whole.to, true, right->from, whole.coreto, nullptr };
|
|
whole.to = left->to + ours;
|
|
whole.coreto = left->to;
|
|
whole.clipped = true;
|
|
Pieces.insert( Pieces.begin() + index + 1, second );
|
|
}
|
|
}
|
|
|
|
|
|
} // namespace track_sleepers
|