From f7821eea9936846cf3acb229f141bdcdb0fd5c88 Mon Sep 17 00:00:00 2001 From: maj00r Date: Fri, 23 Oct 2026 19:24:47 +0200 Subject: [PATCH] fix(sleepers): the wing rails on the timbers as they're bent out: the rails of a track taken in their parts between the cuts at the crossings, as they're laid, a part going on over the gap at an obtuse crossing where its check rail lies; three rails or more close beside held by the fastening of two rails covering the most of them first, then those before and after it, one holding more rails than its seats not narrowed; a rail lying on the brace of a chair beside takes the brace away; a plate cut short at the fastening beside from where its narrowing left it --- world/SleeperFastenings.cpp | 183 ++++++++++++++++++++++-------------- world/SleeperFastenings.h | 1 + world/SleeperRules.h | 2 + world/Track.cpp | 5 + world/Track.h | 1 + 5 files changed, 120 insertions(+), 72 deletions(-) diff --git a/world/SleeperFastenings.cpp b/world/SleeperFastenings.cpp index b18ac69b..b31318ad 100644 --- a/world/SleeperFastenings.cpp +++ b/world/SleeperFastenings.cpp @@ -18,6 +18,7 @@ http://mozilla.org/MPL/2.0/. #include "simulation/simulation.h" #include +#include #include #include @@ -57,50 +58,67 @@ fastened_rails::fastened_rails( TTrack &Track ) { auto const bladestretch = [&]( int const Path ) { auto const &shape { track->blade( Path ) }; return glm::dvec2 { shape.valid ? shape.tip : 0.0, track_rails::blade_length( *track, Path ) }; }; + // 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( Side == bladeside( Path ) ) { + Line.blade = { 0.0, bladestretch( Path ).y }; + } + if( Side == bladeside( 1 - Path ) ) { + auto const stretch { bladestretch( 1 - Path ) }; + Line.stock = { std::max( 0.0, stretch.x - chair_lead ), stretch.y }; + } + } + 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 ) ); }; 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() }; - auto const count { std::max( 1, static_cast( std::ceil( length / fastening_step ) ) ) }; for( auto const side : { -1, 1 } ) { - 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 { length * 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 }; - 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 ); - } - if( switched ) { - if( side == bladeside( path ) ) { - line.blade = { 0.0, bladestretch( path ).y }; + // 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; } - if( side == bladeside( 1 - path ) ) { - auto const stretch { bladestretch( 1 - path ) }; - line.stock = { std::max( 0.0, stretch.x - chair_lead ), stretch.y }; + auto const offsets { knots::sum( before != nullptr ? before->startoffsets : knots::line {}, after != nullptr ? after->endoffsets : knots::line {} ) }; + 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 ); } + add( std::move( line ), 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 ) ); } } } @@ -189,47 +207,54 @@ std::vector fastened_rails::on( beam const &Beam, beam_model const &M return Beam.from + span * ( At / length ); }; auto const has = [&]( int const Part ) { return Model.parts[ Part ] != nullptr; }; - // the rails from the first to the last under fastenings of two rails in turn, each with its seats under two rails side by side - // and over as many of the rails after them as it reaches - auto const crowd = [&]( std::size_t First, std::size_t const Last ) { + // the rails from the first to the last under fastenings of two rails, each with its seats under two rails side by side and over as + // many of the rails around as it reaches: the one holding the most first, the rails before and after it in turn + std::function crowd = [&]( std::size_t const First, std::size_t const Last ) { auto const &reach { Model.spans[ part_check ] }; - while( First <= Last ) { - std::size_t covered { 0 }; - auto origin { 0.0 }; - auto turn { 1.0 }; - for( auto seat { First }; seat < Last; ++seat ) { - for( auto const direction : { 1.0, -1.0 } ) { - auto const at { 0.5 * ( rails[ seat ].at + rails[ seat + 1 ].at - direction * check_seats ) }; - auto const from { at + std::min( direction * reach.x, direction * reach.y ) }; - auto const to { at + std::max( direction * reach.x, direction * reach.y ) }; - if( rails[ First ].at < from ) { - continue; - } - std::size_t count { 0 }; - while( First + count <= Last && rails[ First + count ].at <= to ) { - ++count; - } - if( count > covered ) { - covered = count; - origin = at; - turn = direction; - } + std::size_t covered { 0 }; + std::size_t from { First }; + auto origin { 0.0 }; + auto turn { 1.0 }; + for( auto seat { First }; seat < Last; ++seat ) { + for( auto const direction : { 1.0, -1.0 } ) { + auto const at { 0.5 * ( rails[ seat ].at + rails[ seat + 1 ].at - direction * check_seats ) }; + auto const low { at + std::min( direction * reach.x, direction * reach.y ) }; + auto const high { at + std::max( direction * reach.x, direction * reach.y ) }; + auto start { seat }; + while( start > First && rails[ start - 1 ].at >= low ) { + --start; + } + auto end { seat + 1 }; + while( end < Last && rails[ end + 1 ].at <= high ) { + ++end; + } + if( end - start + 1 > covered ) { + covered = end - start + 1; + from = start; + origin = at; + turn = direction; } } - if( covered < 2 ) { - result.push_back( { part_plate, point( rails[ First ].at ), rails[ First ].inward } ); - ++First; - continue; + } + if( covered < 2 ) { + for( auto rail { First }; rail <= Last; ++rail ) { + result.push_back( { part_plate, point( rails[ rail ].at ), rails[ rail ].inward } ); } - result.push_back( { part_check, point( origin ), axis * turn, 1.f, static_cast( 0.5 * check_seats ), 0.f, true } ); - First += covered; + return; + } + result.push_back( { part_check, point( origin ), axis * turn, 1.f, static_cast( 0.5 * check_seats ), 0.f, true, covered > 2 } ); + if( from > First ) { + crowd( First, from - 1 ); + } + if( from + covered <= Last ) { + crowd( from + covered, Last ); } }; for( std::size_t index = 0; index < rails.size(); ++index ) { auto const &rail { rails[ index ] }; // three rails or more close beside one another, as at the crossings of the slips, none a stock rail or with a check rail auto last { index }; - while( last + 1 < rails.size() && rails[ last + 1 ].at - rails[ last ].at < fastening_pair && rails[ last ].kind == role::plain && rails[ last + 1 ].kind == role::plain ) { + while( last + 1 < rails.size() && rails[ last + 1 ].at - rails[ last ].at < fastening_crowd && rails[ last ].kind == role::plain && rails[ last + 1 ].kind == role::plain ) { ++last; } if( last >= index + 2 && has( part_check ) ) { @@ -338,7 +363,7 @@ void fastened_rails::fit( std::vector &Fastenings, beam const &Beam, switch( Fastening.part ) { case part_plate: { return plate_least; } case part_slide: { return slide_least; } - case part_check: { return check_least; } + case part_check: { return Fastening.whole ? 1.0 : check_least; } default: { return 1.0; } } }; // those of one rail together, the rails in the order along the timber @@ -451,8 +476,8 @@ void fastened_rails::fit( std::vector &Fastenings, beam const &Beam, kept = index; continue; } - auto const firstreach { extent( first ) }; - auto const secondreach { extent( second ) }; + auto firstreach { extent( first ) }; + auto secondreach { extent( second ) }; auto const drop = [&]( std::pair const &Rail ) { for( auto member { Rail.first }; member < Rail.second; ++member ) { dropped[ order[ member ] ] = true; @@ -468,8 +493,13 @@ void fastened_rails::fit( std::vector &Fastenings, beam const &Beam, auto const turn { glm::dot( fastening.across, axis ) }; auto const toward { turn * Toward > 0.0 ? span.y : span.x }; auto const away { turn * Toward > 0.0 ? span.x : span.y }; - auto const stays { fastening.placed( away, 1.0 ) }; - auto const scale { ( Toward * turn * Room - stays ) / ( toward - away ) }; + // the other end where the narrowing left it, or as the plate was if that leaves too little of it + auto stays { fastening.placed( away, fastening.scale ) }; + auto scale { ( Toward * turn * Room - stays ) / ( toward - away ) }; + if( scale < clip_least ) { + stays = fastening.placed( away, 1.0 ); + scale = ( Toward * turn * Room - stays ) / ( toward - away ); + } if( scale < clip_least ) { return false; } @@ -489,15 +519,24 @@ void fastened_rails::fit( std::vector &Fastenings, beam const &Beam, } return Rail; }; // the brace of a chair where the rail beside comes too close for it, its slide plate kept - auto const brace = [&]( std::pair const &Rail, int const Side ) { + auto const brace = [&]( std::pair const &Rail, int const Side, double const Reaching = fastening_gap ) { for( auto member { Rail.first }; member < Rail.second; ++member ) { auto const &fastening { Fastenings[ order[ member ] ] }; - if( fastening.part == part_chair && reach( fastening, fastening.scale )[ Side ] > fastening_gap ) { + if( fastening.part == part_chair && false == dropped[ order[ member ] ] && reach( fastening, fastening.scale )[ Side ] > Reaching ) { dropped[ order[ member ] ] = true; return true; } } return false; }; + // a rail lying on the brace of a chair beside takes the brace away + if( brace( first, 1, apart ) || brace( second, 0, apart ) ) { + if( false == overlaps() ) { + kept = index; + continue; + } + firstreach = extent( first ); + secondreach = extent( second ); + } // of two rails with check rails, the one with its check rail toward the other one, lying on the fastening of the other // one then, goes in a plate cut short at that fastening, or without its own, the other one keeping its check rail seated if( checks( first ) && checks( second ) && faces( first, 1.0 ) != faces( second, -1.0 ) ) { diff --git a/world/SleeperFastenings.h b/world/SleeperFastenings.h index 98a52fc4..9ad6ea24 100644 --- a/world/SleeperFastenings.h +++ b/world/SleeperFastenings.h @@ -31,6 +31,7 @@ struct fastening { float pivot { 0.f }; // the x of the part kept in place as it's narrowed float shift { 0.f }; // along its x, how far the pivot is moved bool both { false }; // the fastening of two rails close beside each other, neither with a check rail of its own seated + bool whole { false }; // holding more rails than its seats, out to its ends: not narrowed // where an x of the part lands along its across, at the scale double placed( double const X, double const Scale ) const { return pivot + shift + Scale * ( X - pivot ); } diff --git a/world/SleeperRules.h b/world/SleeperRules.h index b1e3062b..6b823438 100644 --- a/world/SleeperRules.h +++ b/world/SleeperRules.h @@ -57,6 +57,8 @@ double constexpr fastening_merge { 0.03 }; // one, as the legs of the nose where they're joined, in one plate between them double constexpr fastening_pair { 0.2 }; double constexpr fastening_nose { 0.05 }; +// three rails or more with less than this between each and the next, as the legs of a nose with the wing rails beside, are held together +double constexpr fastening_crowd { 0.3 }; // how far apart the rails lie in the fastening of both, the check part of the model double constexpr check_seats { 0.113 }; // the room left between the fastenings of rails close by, and how narrow their plates get to keep it diff --git a/world/Track.cpp b/world/Track.cpp index e0d0ac12..9ca80383 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -1595,6 +1595,11 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { auto const layout { plan_rails( Bank != 0 ) }; if( Bank != 0 ) { m_crossings = layout.crossed; + for( int path = 0; path < 2; ++path ) { + for( int side = 0; side < 2; ++side ) { + m_railplans[ path ][ side ] = layout.plans[ path ][ side ]; + } + } } 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/Track.h b/world/Track.h index 5c491758..9d18f06c 100644 --- a/world/Track.h +++ b/world/Track.h @@ -217,6 +217,7 @@ public: std::vector m_paths; // source data for owned paths bool m_editorremoved { false }; std::vector m_crossings; // tracks whose rails cross the rails of this one, found when its geometry was made + track_rails::rail_plan m_railplans[ 2 ][ 2 ]; // the cuts of its rails, by path and right side, as its geometry was made scene::basic_node *m_road { nullptr }; // road or road junction which generated this path. such path is one-way, and isn't saved nor edited on its own std::array m_routeweights { 0.f, 0.f, 0.f }; // odds of each route through a crossroads being picked by a driver, by the number of the route. all zero: even odds int iterate_stamp = 0;