From a84e30670f62de003278bcb23aa8014908dda4b1 Mon Sep 17 00:00:00 2001 From: maj00r Date: Wed, 14 Oct 2026 01:17:28 +0200 Subject: [PATCH] fix(track): the legs of an acute nose are cut along the line halfway between the gauge lines of the two rails, followed every 10 cm from the tip to where the full profiles part, instead of a straight bisector, so the nose of a switch frog with a curved diverging path has no seam opening along its middle --- world/TrackRails.cpp | 105 ++++++++++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/world/TrackRails.cpp b/world/TrackRails.cpp index da2486fc..59e27db7 100644 --- a/world/TrackRails.cpp +++ b/world/TrackRails.cpp @@ -954,15 +954,11 @@ std::vector wing_extension( TTrack &Track, TTrack::check_rail_set co } // acute frog: the rail coming from the throat bends out as a wing rail, the rail going on behind the gap is a leg of the nose -void acute( TTrack::rail_plan &Plan, double const At, double const Toward, double const Slope, std::vector const &Extension, TTrack::rail_size const &Size ) { +void acute( TTrack::rail_plan &Plan, double const At, double const Toward, double const Slope, std::vector const &Extension, std::vector const &Legs ) { - auto const half { Slope / ( 1.0 + std::sqrt( 1.0 + Slope * Slope ) ) }; - auto const tip { 0.5 * nose_tip }; - auto const apart { Size.high + 0.01 }; auto const throat { At + Toward * flangeway / Slope }; auto const flare { At - Toward * wing_reach( Slope ) }; - auto const nose { At - Toward * tip / half }; - auto const blunt { At - Toward * apart / half }; + auto const nose { Toward < 0.0 ? Legs.front().x : Legs.back().x }; std::vector wing { { throat, 0.0 }, { flare, std::abs( flare - throat ) * Slope } }; wing.insert( wing.end(), Extension.begin(), Extension.end() ); auto const wingend { Extension.empty() ? flare : Extension.back().x }; @@ -978,11 +974,11 @@ void acute( TTrack::rail_plan &Plan, double const At, double const Toward, doubl cut.endoffsets = wing; cut.endrolled = rolled; cut.start = nose; - cut.startextents = { { nose, tip }, { blunt, apart } }; + cut.startextents = Legs; } else { cut.end = nose; - cut.endextents = { { blunt, apart }, { nose, tip } }; + cut.endextents = Legs; cut.start = wingend; cut.startoffsets = wing; cut.startrolled = rolled; @@ -1090,44 +1086,53 @@ struct frame_corner { double facing { 0.0 }; }; +double lateral( gauge_line const &Line, int const Side, glm::dvec3 const &Point, double &Distance ) { + + glm::dvec3 point, tangent; + for( int step = 0; step < 8; ++step ) { + Line.at( Distance, point, tangent ); + Distance += glm::dot( plan( Point - point ), glm::normalize( plan( tangent ) ) ); + } + Line.at( Distance, point, tangent ); + return -glm::dot( plan( Point - point ), plan( Line.normal( Distance, Side ) ) ); +} + +double nearest_distance( gauge_line const &Line, glm::dvec3 const &Point ) { + + auto result { Line.distances.front() }; + auto nearest { std::numeric_limits::max() }; + for( std::size_t i = 0; i < Line.points.size(); ++i ) { + auto const gap { glm::length( plan( Line.points[ i ] - Point ) ) }; + if( gap < nearest ) { + nearest = gap; + result = Line.distances[ i ]; + } + } + return result; +} + std::vector tread_edge( TTrack const &Track, int const Path, int const Side, TTrack const &Other, int const Otherpath, int const Otherside, double const From, double const To, bool &Below, std::vector &Gauge ) { auto const &ours { rail_line( Track, Path, Side > 0, false ) }; auto const &theirs { rail_line( Other, Otherpath, Otherside > 0, false ) }; - auto const project = [&]( gauge_line const &Line, int const Lineside, glm::dvec3 const &Point, double &Distance ) { - glm::dvec3 point, tangent; - for( int step = 0; step < 8; ++step ) { - Line.at( Distance, point, tangent ); - Distance += glm::dot( plan( Point - point ), glm::normalize( plan( tangent ) ) ); - } - Line.at( Distance, point, tangent ); - return -glm::dot( plan( Point - point ), plan( Line.normal( Distance, Lineside ) ) ); }; glm::dvec3 point, tangent; ours.at( From, point, tangent ); - auto otherat { theirs.distances.front() }; - auto nearest { std::numeric_limits::max() }; - for( std::size_t i = 0; i < theirs.points.size(); ++i ) { - auto const gap { glm::length( plan( theirs.points[ i ] - point ) ) }; - if( gap < nearest ) { - nearest = gap; - otherat = theirs.distances[ i ]; - } - } + auto otherat { nearest_distance( theirs, point ) }; std::vector knots; auto below { 0 }; auto const count { std::max( 2, static_cast( std::ceil( ( To - From ) / 0.1 ) ) + 1 ) }; for( int i = 0; i < count; ++i ) { auto distance { glm::mix( From, To, static_cast( i ) / ( count - 1 ) ) }; ours.at( distance, point, tangent ); - project( theirs, Otherside, point, otherat ); + lateral( theirs, Otherside, point, otherat ); glm::dvec3 line; theirs.at( otherat, line, tangent ); auto const normal { theirs.normal( otherat, Otherside ) }; auto at { distance }; - auto const gauge { project( ours, Side, line, at ) }; + auto const gauge { lateral( ours, Side, line, at ) }; Gauge.emplace_back( at, gauge ); at = distance; - auto const edge { project( ours, Side, line - normal * rolled_band, at ) }; + auto const edge { lateral( ours, Side, line - normal * rolled_band, at ) }; below += ( gauge < edge ? 1 : -1 ); knots.emplace_back( at, edge ); } @@ -1138,6 +1143,44 @@ std::vector tread_edge( TTrack const &Track, int const Path, int con return knots; } +std::vector nose_extents( TTrack const &Track, int const Path, int const Side, TTrack const &Other, int const Otherpath, int const Otherside, double const At, double const Toward, double const Slope, TTrack::rail_size const &Size ) { + + auto const half { Slope / ( 1.0 + std::sqrt( 1.0 + Slope * Slope ) ) }; + auto const tip { 0.5 * nose_tip }; + auto const apart { Size.high + 0.01 }; + auto const nose { At - Toward * tip / half }; + auto const blunt { At - Toward * apart / half }; + std::vector straight { { std::min( nose, blunt ), Toward < 0.0 ? tip : apart }, { std::max( nose, blunt ), Toward < 0.0 ? apart : tip } }; + auto const &ours { rail_line( Track, Path, Side > 0, false ) }; + auto const &theirs { rail_line( Other, Otherpath, Otherside > 0, false ) }; + glm::dvec3 point, tangent; + ours.at( nose, point, tangent ); + auto otherat { nearest_distance( theirs, point ) }; + auto extent { tip }; + auto const step { 0.1 }; + auto const limit { static_cast( std::ceil( 3.0 * std::abs( blunt - nose ) / step ) ) }; + std::vector knots; + for( int i = 0; i <= limit; ++i ) { + auto const distance { nose - Toward * step * i }; + ours.at( distance, point, tangent ); + auto const normal { ours.normal( distance, Side ) }; + for( int iteration = 0; iteration < 6; ++iteration ) { + extent += 0.5 * ( lateral( theirs, Otherside, point - normal * extent, otherat ) - extent ); + } + if( extent >= apart && false == knots.empty() ) { + auto const &last { knots.back() }; + knots.emplace_back( glm::mix( last.x, distance, ( apart - last.y ) / ( extent - last.y ) ), apart ); + break; + } + knots.emplace_back( distance, std::max( extent, 0.0 ) ); + } + if( knots.size() < 2 || knots.back().y < apart ) { + return straight; + } + std::sort( knots.begin(), knots.end(), []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; } ); + return knots; +} + } // namespace material_handle TTrack::rail_material( int const Path ) const { @@ -1293,7 +1336,8 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { auto const slope { std::tan( crossing.angle ) }; wingends[ crossing.path ][ crossing.side > 0 ].emplace_back( crossing.at - toward * wing_reach( slope ), -toward ); acute( plan, crossing.at, toward, slope, - wing_extension( *this, checks, crossing.path, crossing.side, crossing.at, toward, slope, *crossing.track, crossing.otherpath, crossing.otherside, crossing.otherat, crossing.aligned ? -toward : toward ), size ); + wing_extension( *this, checks, crossing.path, crossing.side, crossing.at, toward, slope, *crossing.track, crossing.otherpath, crossing.otherside, crossing.otherat, crossing.aligned ? -toward : toward ), + nose_extents( *this, crossing.path, crossing.side, *crossing.track, crossing.otherpath, crossing.otherside, crossing.at, toward, slope, size ) ); auto const nose { toward < 0.0 ? plan.cuts.back().start : plan.cuts.back().end }; auto const shared { crossing.at - toward * ( rolled_band + size.head + tread_fade ) / slope }; tread( crossing.path, crossing.side, { std::min( nose, shared ), std::max( nose, shared ) }, *crossing.track, crossing.otherpath, toward > 0.0 ? 1 : -1, crossing.otherside ); @@ -1419,7 +1463,8 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { } wingends[ path ][ frog.side[ path ] > 0 ].emplace_back( frog.at[ path ] + wing_reach( slope ), 1.0 ); acute( plans[ path ][ frog.side[ path ] > 0 ], frog.at[ path ], -1.0, slope, - wing_extension( *this, checks, path, frog.side[ path ], frog.at[ path ], -1.0, slope, *this, 1 - path, frog.side[ 1 - path ], frog.at[ 1 - path ], 1.0 ), size ); + wing_extension( *this, checks, path, frog.side[ path ], frog.at[ path ], -1.0, slope, *this, 1 - path, frog.side[ 1 - path ], frog.at[ 1 - path ], 1.0 ), + nose_extents( *this, path, frog.side[ path ], *this, 1 - path, frog.side[ 1 - path ], frog.at[ path ], -1.0, slope, size ) ); auto const nose { plans[ path ][ frog.side[ path ] > 0 ].cuts.back().start }; auto const shared { frog.at[ path ] + ( rolled_band + size.head + tread_fade ) / slope }; tread( path, frog.side[ path ], { std::min( nose, shared ), std::max( nose, shared ) }, *this, 1 - path, -1, frog.side[ 1 - path ] );