From 474e1f3104be8b2cafeb68bb4d92967e71e54987 Mon Sep 17 00:00:00 2001 From: maj00r Date: Wed, 14 Oct 2026 09:52:19 +0200 Subject: [PATCH] refactor(track): the crossings of the rails are found in steps, the tracks nearby, the meeting of two rails with its refined place and the crossing made of it, and the crossings of the neighbours mapped on this track, and the check rails are planned, merged, kept clear of blocks and cut in separate functions instead of two long functions; path_count and path_neighbour replace the repeated choice of the paths and the ends of a switch or a plain track --- world/RailCrossings.cpp | 656 +++++++++++++++++++++++----------------- world/Track.cpp | 6 +- world/TrackRails.h | 2 + 3 files changed, 380 insertions(+), 284 deletions(-) diff --git a/world/RailCrossings.cpp b/world/RailCrossings.cpp index 4fce7bde..5f749fd1 100644 --- a/world/RailCrossings.cpp +++ b/world/RailCrossings.cpp @@ -161,173 +161,219 @@ void rail_offset( TTrack const &Track, int const Path, int const Side, glm::dvec Lateral = lateral( line, Side, Point, Distance ); } +int path_count( TTrack const &Track ) { + + return ( Track.eType == tt_Switch ? 2 : 1 ); +} + +TTrack *path_neighbour( TTrack const &Track, int const Path, int const End ) { + + if( Track.eType == tt_Switch ) { + return ( End ? Track.SwitchExtension->pNexts[ Path ] : Track.SwitchExtension->pPrevs[ Path ] ); + } + return ( End ? Track.trNext : Track.trPrev ); +} + } // namespace track_rails -std::vector TTrack::rail_crossings( bool const Joined ) { +namespace { - std::vector result; - if( false == cut_rails( *this ) || simulation::Region == nullptr ) { - return result; - } - auto &memo { memo_of( *this ) }; - if( Joined && memo.joined.has_value() ) { - return *memo.joined; - } - auto const extension { 10.0 }; - auto const paths { eType == tt_Switch ? 2 : 1 }; - if( memo.own.has_value() ) { - result = *memo.own; - } - else { - auto const sections { simulation::Region->sections( location(), static_cast( radius() + 50.0 ) ) }; - std::vector others; - for( auto *section : sections ) { - for( auto const &cell : section->m_cells ) { - if( glm::length( cell.area().center - location() ) > cell.area().radius + radius() + 1.0 ) { +struct rail_run { + TTrack *track { nullptr }; + int path { 0 }; + int side { 0 }; + gauge_line const *line { nullptr }; + double from { 0.0 }; + double length { 0.0 }; +}; + +std::vector nearby_rails( TTrack &Track ) { + + std::vector others; + auto const sections { simulation::Region->sections( Track.location(), static_cast( Track.radius() + 50.0 ) ) }; + for( auto *section : sections ) { + for( auto const &cell : section->m_cells ) { + if( glm::length( cell.area().center - Track.location() ) > cell.area().radius + Track.radius() + 1.0 ) { + continue; + } + for( auto *other : cell.m_directories.paths ) { + if( other == nullptr || other == &Track || false == cut_rails( *other ) ) { continue; } - for( auto *other : cell.m_directories.paths ) { - if( other == nullptr || other == this || false == cut_rails( *other ) ) { - continue; - } - if( glm::length( other->location() - location() ) > other->radius() + radius() + 1.0 ) { - continue; - } - if( std::find( others.begin(), others.end(), other ) == others.end() ) { - others.push_back( other ); + if( glm::length( other->location() - Track.location() ) > other->radius() + Track.radius() + 1.0 ) { + continue; + } + if( std::find( others.begin(), others.end(), other ) == others.end() ) { + others.push_back( other ); + } + } + } + } + return others; +} + +double line_bend( gauge_line const &Line, double const At ) { + + glm::dvec3 point, before, after; + Line.at( At - 1.0, point, before ); + Line.at( At + 1.0, point, after ); + return std::abs( cross( plan( before ), plan( after ) ) ); +} + +double heading( glm::dvec2 const &Tangent ) { + + auto const angle { std::atan2( Tangent.y, Tangent.x ) }; + return ( angle < 0.0 ? angle + M_PI : angle ); +} + +bool overlap( gauge_line const &A, gauge_line const &B ) { + + return A.low.x <= B.high.x && B.low.x <= A.high.x && A.low.y <= B.high.y && B.low.y <= A.high.y; +} + +bool same_place( rail_crossing const &Crossing, int const Path, int const Side, double const At ) { + + return Crossing.path == Path && Crossing.side == Side && std::abs( Crossing.at - At ) < 0.05; +} + +void refine_meeting( gauge_line const &Ours, gauge_line const &Theirs, double &S, double &T ) { + + glm::dvec3 p, tp, q, tq; + for( int step = 0; step < 6; ++step ) { + Ours.at( S, p, tp ); + Theirs.at( T, q, tq ); + auto const d { cross( plan( tp ), plan( tq ) ) }; + if( std::abs( d ) < 1e-9 ) { + break; + } + auto const offset { plan( q ) - plan( p ) }; + S += cross( offset, plan( tq ) ) / d; + T += cross( offset, plan( tp ) ) / d; + } +} + +rail_crossing make_crossing( rail_run const &Ours, rail_run const &Theirs, double const S, double const T, glm::dvec2 const &Tangent, glm::dvec2 const &Othertangent, double const Angle ) { + + auto const normal { glm::normalize( plan( Ours.line->normal( S, Ours.side ) ) ) }; + auto const othernormal { glm::normalize( plan( Theirs.line->normal( T, Theirs.side ) ) ) }; + rail_crossing crossing; + crossing.track = Theirs.track; + crossing.path = Ours.path; + crossing.side = Ours.side; + crossing.at = S; + crossing.angle = Angle; + crossing.into = glm::dot( Tangent, othernormal ); + crossing.facing = glm::dot( normal, othernormal ); + auto const ourbend { line_bend( *Ours.line, S ) }; + auto const theirbend { line_bend( *Theirs.line, T ) }; + crossing.nose = ( std::abs( ourbend - theirbend ) > 1e-4 ? ourbend < theirbend : heading( Tangent ) < heading( Othertangent ) ); + crossing.otherpath = Theirs.path; + crossing.otherside = Theirs.side; + crossing.otherat = T; + crossing.aligned = glm::dot( Tangent, Othertangent ) > 0.0; + return crossing; +} + +void meet_rails( rail_run const &Ours, rail_run const &Theirs, std::vector &Result ) { + + auto const &line { *Ours.line }; + auto const &theirs { *Theirs.line }; + if( false == overlap( line, theirs ) ) { + return; + } + for( std::size_t i = 1; i < line.points.size(); ++i ) { + auto const a0 { plan( line.points[ i - 1 ] ) }; + auto const a { plan( line.points[ i ] ) - a0 }; + auto const alow { glm::min( a0, a0 + a ) - 0.02 * glm::length( a ) - 1e-6 }; + auto const ahigh { glm::max( a0, a0 + a ) + 0.02 * glm::length( a ) + 1e-6 }; + if( alow.x > theirs.high.x + 1.0 || theirs.low.x - 1.0 > ahigh.x || alow.y > theirs.high.y + 1.0 || theirs.low.y - 1.0 > ahigh.y ) { + continue; + } + for( std::size_t j = 1; j < theirs.points.size(); ++j ) { + auto const b0 { plan( theirs.points[ j - 1 ] ) }; + auto const b { plan( theirs.points[ j ] ) - b0 }; + auto const reach { 0.02 * glm::length( b ) }; + if( alow.x > std::max( b0.x, b0.x + b.x ) + reach || std::min( b0.x, b0.x + b.x ) - reach > ahigh.x + || alow.y > std::max( b0.y, b0.y + b.y ) + reach || std::min( b0.y, b0.y + b.y ) - reach > ahigh.y ) { + continue; + } + double u, v; + if( false == meet( a0, a, b0, b, 1e-12, u, v ) ) { + continue; + } + if( u < -0.01 || u > 1.01 || v < -0.01 || v > 1.01 ) { + continue; + } + auto s { glm::mix( line.distances[ i - 1 ], line.distances[ i ], u ) }; + auto t { glm::mix( theirs.distances[ j - 1 ], theirs.distances[ j ], v ) }; + refine_meeting( line, theirs, s, t ); + glm::dvec3 p, tp, q, tq; + line.at( s, p, tp ); + theirs.at( t, q, tq ); + if( glm::length( plan( p ) - plan( q ) ) > 0.01 + || s < 0.0 || s > Ours.length + || t < Theirs.from || t > Theirs.length ) { + continue; + } + if( std::abs( p.y - q.y ) > 0.5 ) { + continue; + } + if( ( s < 0.5 || s > Ours.length - 0.5 ) && ( t < 0.5 || t > Theirs.length - 0.5 ) && joined( *Ours.track, *Theirs.track ) ) { + continue; + } + auto const tangent { glm::normalize( plan( tp ) ) }; + auto const othertangent { glm::normalize( plan( tq ) ) }; + auto const angle { std::acos( std::min( 1.0, std::abs( glm::dot( tangent, othertangent ) ) ) ) }; + if( angle < 0.03 ) { + continue; + } + if( std::any_of( Result.begin(), Result.end(), [&]( rail_crossing const &Crossing ) { return same_place( Crossing, Ours.path, Ours.side, s ); } ) ) { + continue; + } + Result.push_back( make_crossing( Ours, Theirs, s, t, tangent, othertangent, angle ) ); + } + } +} + +std::vector own_crossings( TTrack &Track ) { + + std::vector result; + for( auto *other : nearby_rails( Track ) ) { + for( int otherpath = 0; otherpath < path_count( *other ); ++otherpath ) { + rail_run theirs { other, otherpath }; + theirs.from = ( other->eType == tt_Switch ? blade_length( *other, otherpath ) : 0.0 ); + theirs.length = path_segment( *other, otherpath ).GetLength(); + for( int otherside = 0; otherside < 2; ++otherside ) { + theirs.side = ( otherside ? 1 : -1 ); + theirs.line = &rail_line( *other, otherpath, otherside, true ); + for( int path = 0; path < path_count( Track ); ++path ) { + auto const length { path_segment( Track, path ).GetLength() }; + for( int side = 0; side < 2; ++side ) { + meet_rails( { &Track, path, side ? 1 : -1, &rail_line( Track, path, side, false ), 0.0, length }, theirs, result ); } } } } - auto const bend = [&]( gauge_line const &Line, double const At ) { - glm::dvec3 point, before, after; - Line.at( At - 1.0, point, before ); - Line.at( At + 1.0, point, after ); - return std::abs( cross( plan( before ), plan( after ) ) ); }; - auto const heading = []( glm::dvec2 const &Tangent ) { - auto const angle { std::atan2( Tangent.y, Tangent.x ) }; - return ( angle < 0.0 ? angle + M_PI : angle ); }; - auto const overlap = []( gauge_line const &A, gauge_line const &B ) { - return A.low.x <= B.high.x && B.low.x <= A.high.x && A.low.y <= B.high.y && B.low.y <= A.high.y; }; - for( auto *other : others ) { - auto const otherpaths { other->eType == tt_Switch ? 2 : 1 }; - for( int otherpath = 0; otherpath < otherpaths; ++otherpath ) { - auto const &othersegment { path_segment( *other, otherpath ) }; - auto const otherfrom { other->eType == tt_Switch ? blade_length( *other, otherpath ) : 0.0 }; - auto const otherlength { othersegment.GetLength() }; - for( int otherside = 0; otherside < 2; ++otherside ) { - auto const &theirs { rail_line( *other, otherpath, otherside, true ) }; - for( int path = 0; path < paths; ++path ) { - auto const length { path_segment( *this, path ).GetLength() }; - for( int side = 0; side < 2; ++side ) { - auto const &line { rail_line( *this, path, side, false ) }; - if( false == overlap( line, theirs ) ) { - continue; - } - for( std::size_t i = 1; i < line.points.size(); ++i ) { - auto const a0 { plan( line.points[ i - 1 ] ) }; - auto const a { plan( line.points[ i ] ) - a0 }; - auto const alow { glm::min( a0, a0 + a ) - 0.02 * glm::length( a ) - 1e-6 }; - auto const ahigh { glm::max( a0, a0 + a ) + 0.02 * glm::length( a ) + 1e-6 }; - if( alow.x > theirs.high.x + 1.0 || theirs.low.x - 1.0 > ahigh.x || alow.y > theirs.high.y + 1.0 || theirs.low.y - 1.0 > ahigh.y ) { - continue; - } - for( std::size_t j = 1; j < theirs.points.size(); ++j ) { - auto const b0 { plan( theirs.points[ j - 1 ] ) }; - auto const b { plan( theirs.points[ j ] ) - b0 }; - auto const reach { 0.02 * glm::length( b ) }; - if( alow.x > std::max( b0.x, b0.x + b.x ) + reach || std::min( b0.x, b0.x + b.x ) - reach > ahigh.x - || alow.y > std::max( b0.y, b0.y + b.y ) + reach || std::min( b0.y, b0.y + b.y ) - reach > ahigh.y ) { - continue; - } - double u, v; - if( false == meet( a0, a, b0, b, 1e-12, u, v ) ) { - continue; - } - if( u < -0.01 || u > 1.01 || v < -0.01 || v > 1.01 ) { - continue; - } - auto s { glm::mix( line.distances[ i - 1 ], line.distances[ i ], u ) }; - auto t { glm::mix( theirs.distances[ j - 1 ], theirs.distances[ j ], v ) }; - glm::dvec3 p, tp, q, tq; - for( int step = 0; step < 6; ++step ) { - line.at( s, p, tp ); - theirs.at( t, q, tq ); - auto const d { cross( plan( tp ), plan( tq ) ) }; - if( std::abs( d ) < 1e-9 ) { - break; - } - auto const offset { plan( q ) - plan( p ) }; - s += cross( offset, plan( tq ) ) / d; - t += cross( offset, plan( tp ) ) / d; - } - line.at( s, p, tp ); - theirs.at( t, q, tq ); - if( glm::length( plan( p ) - plan( q ) ) > 0.01 - || s < 0.0 || s > length - || t < otherfrom || t > otherlength ) { - continue; - } - if( std::abs( p.y - q.y ) > 0.5 ) { - continue; - } - if( ( s < 0.5 || s > length - 0.5 ) && ( t < 0.5 || t > otherlength - 0.5 ) && joined( *this, *other ) ) { - continue; - } - auto const tangent { glm::normalize( plan( tp ) ) }; - auto const othertangent { glm::normalize( plan( tq ) ) }; - auto const angle { std::acos( std::min( 1.0, std::abs( glm::dot( tangent, othertangent ) ) ) ) }; - if( angle < 0.03 ) { - continue; - } - auto const sidevalue { side ? 1 : -1 }; - if( std::any_of( result.begin(), result.end(), [&]( rail_crossing const &Crossing ) { - return Crossing.path == path && Crossing.side == sidevalue && std::abs( Crossing.at - s ) < 0.05; } ) ) { - continue; - } - auto const normal { glm::normalize( plan( line.normal( s, sidevalue ) ) ) }; - auto const othernormal { glm::normalize( plan( theirs.normal( t, otherside ? 1 : -1 ) ) ) }; - rail_crossing crossing; - crossing.track = other; - crossing.path = path; - crossing.side = sidevalue; - crossing.at = s; - crossing.angle = angle; - crossing.into = glm::dot( tangent, othernormal ); - crossing.facing = glm::dot( normal, othernormal ); - auto const ourbend { bend( line, s ) }; - auto const theirbend { bend( theirs, t ) }; - crossing.nose = ( std::abs( ourbend - theirbend ) > 1e-4 ? ourbend < theirbend : heading( tangent ) < heading( othertangent ) ); - crossing.otherpath = otherpath; - crossing.otherside = ( otherside ? 1 : -1 ); - crossing.otherat = t; - crossing.aligned = glm::dot( tangent, othertangent ) > 0.0; - result.push_back( crossing ); - } - } - } - } - } - } - } - memo.own = result; } - if( false == Joined ) { - return result; - } - // crossings of the tracks going on from the ends, near enough for their check rails and wings to reach this one - auto const owncount { result.size() }; - for( int path = 0; path < paths; ++path ) { - auto const &segment { path_segment( *this, path ) }; + return result; +} + +// crossings of the tracks going on from the ends, near enough for their check rails and wings to reach this one +void add_neighbour_crossings( TTrack &Track, std::vector &Result ) { + + auto const extension { 10.0 }; + auto const owncount { Result.size() }; + for( int path = 0; path < path_count( Track ); ++path ) { + auto const &segment { path_segment( Track, path ) }; auto const length { segment.GetLength() }; for( int end = 0; end < 2; ++end ) { - auto *neighbour { eType == tt_Switch ? ( end ? SwitchExtension->pNexts[ path ] : SwitchExtension->pPrevs[ path ] ) : ( end ? trNext : trPrev ) }; - if( neighbour == nullptr || neighbour == this || false == cut_rails( *neighbour ) ) { + auto *neighbour { path_neighbour( Track, path, end ) }; + if( neighbour == nullptr || neighbour == &Track || false == cut_rails( *neighbour ) ) { continue; } auto const joint { end ? segment.FastGetPoint_1() : segment.FastGetPoint_0() }; auto const theirs { neighbour->rail_crossings( false ) }; - for( int neighbourpath = 0; neighbourpath < ( neighbour->eType == tt_Switch ? 2 : 1 ); ++neighbourpath ) { + for( int neighbourpath = 0; neighbourpath < path_count( *neighbour ); ++neighbourpath ) { auto const &neighboursegment { path_segment( *neighbour, neighbourpath ) }; auto const neighbourlength { neighboursegment.GetLength() }; auto const tostart { glm::length( neighboursegment.FastGetPoint_0() - joint ) }; @@ -338,7 +384,7 @@ std::vector TTrack::rail_crossings( bool const Joine auto const atstart { tostart <= toend }; auto const same { atstart == ( end == 1 ) }; for( auto const &crossing : theirs ) { - if( crossing.path != neighbourpath || crossing.track == this ) { + if( crossing.path != neighbourpath || crossing.track == &Track ) { continue; } auto const along { atstart ? crossing.at : neighbourlength - crossing.at }; @@ -354,46 +400,42 @@ std::vector TTrack::rail_crossings( bool const Joine mapped.into = -crossing.into; mapped.aligned = !crossing.aligned; } - if( std::none_of( result.begin(), result.begin() + owncount, [&]( rail_crossing const &Crossing ) { - return Crossing.path == mapped.path && Crossing.side == mapped.side && std::abs( Crossing.at - mapped.at ) < 0.05; } ) ) { - result.push_back( mapped ); + if( std::none_of( Result.begin(), Result.begin() + owncount, [&]( rail_crossing const &Crossing ) { return same_place( Crossing, mapped.path, mapped.side, mapped.at ); } ) ) { + Result.push_back( mapped ); } } } } } - memo.joined = result; - return result; } -track_rails::check_rail_set TTrack::check_rails( std::vector const &Crossings ) { +struct planned_check { + double from { 0.0 }; + double to { 0.0 }; + bool flarefrom { true }; + bool flareto { true }; + double shortest { 1.5 }; + double flare { 0.02 }; + double frombend { 0.0 }; + double tobend { 0.0 }; +}; +using planned_check_set = std::array, 2>, 2>; // path, right side - struct planned { - double from { 0.0 }; - double to { 0.0 }; - bool flarefrom { true }; - bool flareto { true }; - double shortest { 1.5 }; - double flare { 0.02 }; - double frombend { 0.0 }; - double tobend { 0.0 }; - }; - std::vector plans[ 2 ][ 2 ]; - check_rail_set result; - auto const size { rail_dimensions() }; - if( size.grooved ) { - return result; - } - auto const acute = [&]( std::vector &Other, double const At, double const Toward, double const Slope, double const Flare ) { - auto const half { 0.5 * check_length( Slope ) }; - Other.push_back( { At + Toward * 0.25 - half, At + Toward * 0.25 + half, true, true, 1.5, Flare } ); - }; +void add_acute_check( std::vector &Checks, double const At, double const Toward, double const Slope, double const Flare ) { + + auto const half { 0.5 * check_length( Slope ) }; + Checks.push_back( { At + Toward * 0.25 - half, At + Toward * 0.25 + half, true, true, 1.5, Flare } ); +} + +planned_check_set planned_checks( TTrack const &Track, std::vector const &Crossings ) { + + planned_check_set plans; for( auto const &crossing : Crossings ) { if( crossing.angle > middle_angle || crossing.track->rail_dimensions().grooved ) { continue; } if( crossing.facing < 0.0 ) { - acute( plans[ crossing.path ][ crossing.side < 0 ], crossing.at, crossing.into > 0.0 ? 1.0 : -1.0, std::tan( crossing.angle ), 0.02 ); + add_acute_check( plans[ crossing.path ][ crossing.side < 0 ], crossing.at, crossing.into > 0.0 ? 1.0 : -1.0, std::tan( crossing.angle ), 0.02 ); } else if( crossing.into > 0.0 ) { plans[ crossing.path ][ crossing.side > 0 ].push_back( { crossing.at, crossing.at + 1.2, false, true, 0.5, 0.02, -knuckle_bend( crossing.into ), 0.0 } ); @@ -402,120 +444,172 @@ track_rails::check_rail_set TTrack::check_rails( std::vector cons plans[ crossing.path ][ crossing.side > 0 ].push_back( { crossing.at - 1.2, crossing.at, true, false, 0.5, 0.02, 0.0, knuckle_bend( crossing.into ) } ); } } - if( eType == tt_Switch ) { - auto const frog { this->frog() }; + if( Track.eType == tt_Switch ) { + auto const frog { Track.frog() }; if( frog.valid ) { for( int path = 0; path < 2; ++path ) { - acute( plans[ path ][ frog.side[ path ] < 0 ], frog.at[ path ], -1.0, std::tan( frog.angle ), 0.04 ); + add_acute_check( plans[ path ][ frog.side[ path ] < 0 ], frog.at[ path ], -1.0, std::tan( frog.angle ), 0.04 ); } } } - auto const halfwidth { 0.5 * std::abs( fTrackWidth ) }; + return plans; +} + +void merge_checks( std::vector &Checks ) { + + std::sort( Checks.begin(), Checks.end(), []( planned_check const &A, planned_check const &B ) { return A.from < B.from; } ); + for( std::size_t i = 1; i < Checks.size(); ++i ) { + if( Checks[ i ].from < Checks[ i - 1 ].to + 2.0 ) { + if( Checks[ i ].to > Checks[ i - 1 ].to ) { + Checks[ i - 1 ].to = Checks[ i ].to; + Checks[ i - 1 ].flareto = Checks[ i ].flareto; + Checks[ i - 1 ].tobend = Checks[ i ].tobend; + } + Checks[ i - 1 ].shortest = std::min( Checks[ i - 1 ].shortest, Checks[ i ].shortest ); + Checks[ i - 1 ].flare = std::max( Checks[ i - 1 ].flare, Checks[ i ].flare ); + Checks.erase( Checks.begin() + i-- ); + } + } +} + +bool blocked_by( std::vector const &Others, glm::dvec2 const &Gauge, glm::dvec2 const &Inward, double const Clearance ) { + + auto blocked { false }; + for( auto const &other : Others ) { + for( std::size_t i = 1; i < other.points.size() && false == blocked; ++i ) { + auto const a { plan( other.points[ i - 1 ] ) }; + auto const edge { plan( other.points[ i ] ) - a }; + double t, u; + if( false == meet( Gauge, Inward, a, edge, 1e-9, t, u ) ) { + continue; + } + blocked = ( u > -1e-6 && u < 1.0 + 1e-6 && t > -0.05 && t < Clearance ); + } + } + return blocked; +} + +// check rails keep clear of the blades and the rails of the other path of a switch running inside the rail, here and in a switch joined at an end, and of the blade tips +knots::line check_blocks( TTrack const &Track, int const Path, int const Side, std::vector const &Checks, double const Clearance ) { + + auto const halfwidth { 0.5 * std::abs( Track.fTrackWidth ) }; + auto const &segment { path_segment( Track, Path ) }; + auto const length { segment.GetLength() }; + knots::line blocks; + auto const start { plan( segment.FastGetPoint_0() ) }; + auto const end { plan( segment.FastGetPoint_1() ) }; + std::vector others; + auto const add_path = [&]( TSegment const &Other ) { + others.push_back( make_gauge_line( Other, -halfwidth, 0.0 ) ); + others.push_back( make_gauge_line( Other, halfwidth, 0.0 ) ); }; + if( Track.eType == tt_Switch ) { + blocks.emplace_back( -1e9, 0.3 ); + add_path( path_segment( Track, 1 - Path ) ); + } + for( auto const *neighbour : { Track.trPrev, Track.trNext } ) { + if( neighbour == nullptr || neighbour == &Track || neighbour->eType != tt_Switch || neighbour->SwitchExtension == nullptr ) { + continue; + } + auto const tip { plan( neighbour->SwitchExtension->Segments[ 0 ]->FastGetPoint_0() ) }; + if( glm::distance( start, tip ) < 0.1 ) { + blocks.emplace_back( -1e9, 0.3 ); + continue; + } + if( glm::distance( end, tip ) < 0.1 ) { + blocks.emplace_back( length - 0.3, 1e9 ); + continue; + } + for( int otherpath = 0; otherpath < 2; ++otherpath ) { + auto const &other { *neighbour->SwitchExtension->Segments[ otherpath ] }; + auto const continues = [&]( glm::dvec2 const &Point ) { + return glm::distance( Point, plan( other.FastGetPoint_0() ) ) < 0.1 || glm::distance( Point, plan( other.FastGetPoint_1() ) ) < 0.1; }; + if( false == continues( start ) && false == continues( end ) ) { + add_path( other ); + } + } + } + if( false == others.empty() ) { + auto const ours { make_gauge_line( segment, Side * halfwidth, 0.0 ) }; + for( auto const &check : Checks ) { + for( auto s { check.from }; s <= check.to; s += 0.1 ) { + glm::dvec3 point, tangent; + ours.at( s, point, tangent ); + if( blocked_by( others, plan( point ), plan( ours.normal( s, Side ) ), Clearance ) ) { + blocks.emplace_back( s - 0.3, s + 0.3 ); + } + } + } + } + knots::sort( blocks ); + return blocks; +} + +void cut_check( planned_check const &Check, knots::line const &Blocks, std::vector &Result ) { + + auto const add = [&]( double const From, double const To ) { + Result.push_back( { + From, To, + ( From > Check.from || Check.flarefrom ) ? Check.flare : 0.0, + ( To < Check.to || Check.flareto ) ? Check.flare : 0.0, + From > Check.from ? 0.0 : Check.frombend, + To < Check.to ? 0.0 : Check.tobend } ); }; + auto from { Check.from }; + for( auto const &block : Blocks ) { + if( block.y <= from || block.x >= Check.to ) { + continue; + } + if( block.x - from >= Check.shortest ) { + add( from, block.x ); + } + from = std::max( from, block.y ); + } + if( Check.to - from >= Check.shortest ) { + add( from, Check.to ); + } +} + +} // namespace + +std::vector TTrack::rail_crossings( bool const Joined ) { + + if( false == cut_rails( *this ) || simulation::Region == nullptr ) { + return {}; + } + auto &memo { memo_of( *this ) }; + if( Joined && memo.joined.has_value() ) { + return *memo.joined; + } + if( false == memo.own.has_value() ) { + memo.own = own_crossings( *this ); + } + auto result { *memo.own }; + if( false == Joined ) { + return result; + } + add_neighbour_crossings( *this, result ); + memo.joined = result; + return result; +} + +track_rails::check_rail_set TTrack::check_rails( std::vector const &Crossings ) { + + check_rail_set result; + auto const size { rail_dimensions() }; + if( size.grooved ) { + return result; + } + auto plans { planned_checks( *this, Crossings ) }; auto const clearance { 0.046 + 0.04 * size.head / 0.0735 + size.head }; - for( int path = 0; path < ( eType == tt_Switch ? 2 : 1 ); ++path ) { - auto const &segment { path_segment( *this, path ) }; - auto const length { segment.GetLength() }; + for( int path = 0; path < path_count( *this ); ++path ) { for( int right = 0; right < 2; ++right ) { - auto const side { right ? 1 : -1 }; auto &checks { plans[ path ][ right ] }; if( checks.empty() ) { continue; } - std::sort( checks.begin(), checks.end(), []( planned const &A, planned const &B ) { return A.from < B.from; } ); - for( std::size_t i = 1; i < checks.size(); ++i ) { - if( checks[ i ].from < checks[ i - 1 ].to + 2.0 ) { - if( checks[ i ].to > checks[ i - 1 ].to ) { - checks[ i - 1 ].to = checks[ i ].to; - checks[ i - 1 ].flareto = checks[ i ].flareto; - checks[ i - 1 ].tobend = checks[ i ].tobend; - } - checks[ i - 1 ].shortest = std::min( checks[ i - 1 ].shortest, checks[ i ].shortest ); - checks[ i - 1 ].flare = std::max( checks[ i - 1 ].flare, checks[ i ].flare ); - checks.erase( checks.begin() + i-- ); - } - } - // check rails keep clear of the blades and the rails of the other path of a switch running inside the rail, here and in a switch joined at an end, and of the blade tips - std::vector blocks; - auto const start { plan( segment.FastGetPoint_0() ) }; - auto const end { plan( segment.FastGetPoint_1() ) }; - std::vector others; - auto const add_path = [&]( TSegment const &Other ) { - others.push_back( make_gauge_line( Other, -halfwidth, 0.0 ) ); - others.push_back( make_gauge_line( Other, halfwidth, 0.0 ) ); }; - if( eType == tt_Switch ) { - blocks.emplace_back( -1e9, 0.3 ); - add_path( path_segment( *this, 1 - path ) ); - } - for( auto const *neighbour : { trPrev, trNext } ) { - if( neighbour == nullptr || neighbour == this || neighbour->eType != tt_Switch || neighbour->SwitchExtension == nullptr ) { - continue; - } - auto const tip { plan( neighbour->SwitchExtension->Segments[ 0 ]->FastGetPoint_0() ) }; - if( glm::distance( start, tip ) < 0.1 ) { - blocks.emplace_back( -1e9, 0.3 ); - continue; - } - if( glm::distance( end, tip ) < 0.1 ) { - blocks.emplace_back( length - 0.3, 1e9 ); - continue; - } - for( int otherpath = 0; otherpath < 2; ++otherpath ) { - auto const &other { *neighbour->SwitchExtension->Segments[ otherpath ] }; - auto const continues = [&]( glm::dvec2 const &Point ) { - return glm::distance( Point, plan( other.FastGetPoint_0() ) ) < 0.1 || glm::distance( Point, plan( other.FastGetPoint_1() ) ) < 0.1; }; - if( false == continues( start ) && false == continues( end ) ) { - add_path( other ); - } - } - } - if( false == others.empty() ) { - auto const ours { make_gauge_line( segment, side * halfwidth, 0.0 ) }; - for( auto const &check : checks ) { - for( auto s { check.from }; s <= check.to; s += 0.1 ) { - glm::dvec3 point, tangent; - ours.at( s, point, tangent ); - auto const gauge { plan( point ) }; - auto const inward { plan( ours.normal( s, side ) ) }; - auto blocked { false }; - for( auto const &other : others ) { - for( std::size_t i = 1; i < other.points.size() && false == blocked; ++i ) { - auto const a { plan( other.points[ i - 1 ] ) }; - auto const edge { plan( other.points[ i ] ) - a }; - double t, u; - if( false == meet( gauge, inward, a, edge, 1e-9, t, u ) ) { - continue; - } - blocked = ( u > -1e-6 && u < 1.0 + 1e-6 && t > -0.05 && t < clearance ); - } - } - if( blocked ) { - blocks.emplace_back( s - 0.3, s + 0.3 ); - } - } - } - } - std::sort( blocks.begin(), blocks.end(), []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; } ); + merge_checks( checks ); + auto const blocks { check_blocks( *this, path, right ? 1 : -1, checks, clearance ) }; for( auto const &check : checks ) { - auto const add = [&]( double const From, double const To ) { - result[ path ][ right ].push_back( { - From, To, - ( From > check.from || check.flarefrom ) ? check.flare : 0.0, - ( To < check.to || check.flareto ) ? check.flare : 0.0, - From > check.from ? 0.0 : check.frombend, - To < check.to ? 0.0 : check.tobend } ); }; - auto from { check.from }; - for( auto const &block : blocks ) { - if( block.y <= from || block.x >= check.to ) { - continue; - } - if( block.x - from >= check.shortest ) { - add( from, block.x ); - } - from = std::max( from, block.y ); - } - if( check.to - from >= check.shortest ) { - add( from, check.to ); - } + cut_check( check, blocks, result[ path ][ right ] ); } } } diff --git a/world/Track.cpp b/world/Track.cpp index 20ee978f..a5bec236 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -1381,20 +1381,20 @@ void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) { void TTrack::share_joints() { - for( int path = 0; path < ( eType == tt_Switch ? 2 : 1 ); ++path ) { + for( int path = 0; path < track_rails::path_count( *this ); ++path ) { auto *segment { eType == tt_Switch ? SwitchExtension->Segments[ path ].get() : Segment.get() }; if( segment == nullptr ) { continue; } for( int end = 0; end < 2; ++end ) { std::optional> joint; - auto *neighbour { eType == tt_Switch ? ( end ? SwitchExtension->pNexts[ path ] : SwitchExtension->pPrevs[ path ] ) : ( end ? trNext : trPrev ) }; + auto *neighbour { track_rails::path_neighbour( *this, path, end ) }; if( neighbour != nullptr && neighbour != this && ( neighbour->iCategoryFlag & 15 ) == ( iCategoryFlag & 15 ) ) { glm::dvec3 point; glm::dvec3 across; segment->plan_frame( end ? segment->GetLength() : 0.0, point, across ); auto closest { 0.2 }; - for( int theirpath = 0; theirpath < ( neighbour->eType == tt_Switch ? 2 : 1 ); ++theirpath ) { + for( int theirpath = 0; theirpath < track_rails::path_count( *neighbour ); ++theirpath ) { auto const *theirs { neighbour->eType == tt_Switch ? neighbour->SwitchExtension->Segments[ theirpath ].get() : neighbour->Segment.get() }; if( theirs == nullptr ) { continue; diff --git a/world/TrackRails.h b/world/TrackRails.h index 1ae62522..b7404661 100644 --- a/world/TrackRails.h +++ b/world/TrackRails.h @@ -34,6 +34,8 @@ gfx::userdata_array worn_userdata( std::size_t const Count ); void cut_faces( gfx::vertex_array const &Strip, std::size_t const Ring, bool const Start, bool const End, gfx::geometrybank_handle const &Bank, TTrack::geometryhandle_sequence &Geometry ); TSegment const &path_segment( TTrack const &Track, int const Path ); +int path_count( TTrack const &Track ); +TTrack *path_neighbour( TTrack const &Track, int const Path, int const End ); double knuckle_bend( double const Into ); bool replaced_check( std::vector const &Checks, double const Wingend, double const Direction, double &Far ); gauge_line const &rail_line( TTrack const &Track, int const Path, int const Side, bool const Blades );