From a87148a96cf128ef8221579a0297d669e3285e55 Mon Sep 17 00:00:00 2001 From: maj00r Date: Wed, 14 Oct 2026 01:49:24 +0200 Subject: [PATCH] fix(track): at an obtuse crossing the rail ending at the meeting point and the rail going on from it are joined by a mitre on the bisector of the bend, like one bent knuckle rail, instead of two square ends overlapping in a wedge, and the check rails of the two tracks meet there the same way instead of overlapping each other by 3 cm; a loft span can turn its first and last cross-section about the gauge line for such joints --- world/Segment.cpp | 24 ++++++++++++++++++++++-- world/Segment.h | 2 +- world/Track.h | 4 ++++ world/TrackRails.cpp | 39 +++++++++++++++++++++++++++++++++------ 4 files changed, 60 insertions(+), 9 deletions(-) diff --git a/world/Segment.cpp b/world/Segment.cpp index 5dfa3ae7..bcef534f 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -580,7 +580,7 @@ void TSegment::plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3 Across = glm::normalize( Across ); } -bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, int const Side, float const Halfwidth, double const fTextureLength, double const From, double const To, std::vector const &Offsets, std::vector const &Extents, float const Lift, std::vector const &Inner, gfx::userdata_array *Userdata, std::vector const &Ramp, double const Rampwidth, bool const Walls, float const Wallu ) const { +bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, int const Side, float const Halfwidth, double const fTextureLength, double const From, double const To, std::vector const &Offsets, std::vector const &Extents, float const Lift, std::vector const &Inner, gfx::userdata_array *Userdata, std::vector const &Ramp, double const Rampwidth, bool const Walls, float const Wallu, glm::dvec2 const &Bends ) const { if( fTsBuffer.empty() || To - From < 0.001 || ShapePoints.empty() ) return false; @@ -628,6 +628,15 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig } std::sort( stations.begin(), stations.end() ); stations.erase( std::unique( stations.begin(), stations.end(), []( double const A, double const B ) { return B - A < 0.001; } ), stations.end() ); + if( Bends.x != 0.0 || Bends.y != 0.0 ) { + auto widest { 0.0 }; + for( auto const &point : ShapePoints ) { + widest = std::max( widest, std::abs( static_cast( Side * point.position.x - Halfwidth ) ) ); + } + stations.erase( std::remove_if( stations.begin(), stations.end(), [&]( double const Distance ) { + return ( Distance > From && Distance < From + std::abs( Bends.x ) * widest + 0.001 ) + || ( Distance < To && Distance > To - std::abs( Bends.y ) * widest - 0.001 ); } ), stations.end() ); + } auto const ring = [&]( double const Distance, double const Middle, std::vector &Ring, std::vector &Wear ) { glm::dvec3 point; @@ -659,6 +668,17 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig innerwall = glm::vec3{ glm::normalize( tangent * slope( Inner, -1e6 ) - outward ) }; } auto const worn { sample( Ramp, Distance, -1e6 ) }; + auto const bend { Distance <= From ? Bends.x : Distance >= To ? Bends.y : 0.0 }; + auto along { glm::vec3 { 0.f } }; + if( bend != 0.0 ) { + glm::dvec3 ahead, behind, unused; + plan_frame( std::min( Distance + 0.001, static_cast( fLength ) ), ahead, unused ); + plan_frame( std::max( Distance - 0.001, 0.0 ), behind, unused ); + auto const tangent { glm::dvec3 { ahead.x - behind.x, 0.0, ahead.z - behind.z } }; + if( glm::length2( tangent ) > 0.0 ) { + along = glm::vec3 { glm::normalize( tangent ) * bend }; + } + } Ring.clear(); Wear.clear(); auto const count { ShapePoints.size() }; @@ -738,7 +758,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig wall = ( side > 0 ? outerwall : innerwall ); } } - auto pt { parallel * static_cast( Side * ( Halfwidth + outside + offset ) ) + position }; + auto pt { parallel * static_cast( Side * ( Halfwidth + outside + offset ) ) + position + along * static_cast( outside + offset ) }; pt.y += shape.position.y + Lift; auto norm { shape.normal.x * parallel }; norm.y += shape.normal.y; diff --git a/world/Segment.h b/world/Segment.h index 22c7321d..1ab6f317 100644 --- a/world/Segment.h +++ b/world/Segment.h @@ -132,7 +132,7 @@ public: RenderLoft( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, bool const Transition, double fTextureLength, double Texturescale = 1.0, int iSkip = 0, int iEnd = 0, std::pair fOffsetX = {0.f, 0.f}, glm::vec3 **p = nullptr, bool bRender = true ); // the profile lofted from distance From to To, moved outwards by Offsets and cut on its outer side to Extents beyond the gauge line, both as (distance, value) knots bool - RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, int const Side, float const Halfwidth, double const fTextureLength, double const From, double const To, std::vector const &Offsets, std::vector const &Extents = {}, float const Lift = 0.f, std::vector const &Inner = {}, gfx::userdata_array *Userdata = nullptr, std::vector const &Ramp = {}, double const Rampwidth = 0.0, bool const Walls = false, float const Wallu = -1.f ) const; + RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, int const Side, float const Halfwidth, double const fTextureLength, double const From, double const To, std::vector const &Offsets, std::vector const &Extents = {}, float const Lift = 0.f, std::vector const &Inner = {}, gfx::userdata_array *Userdata = nullptr, std::vector const &Ramp = {}, double const Rampwidth = 0.0, bool const Walls = false, float const Wallu = -1.f, glm::dvec2 const &Bends = glm::dvec2 { 0.0 } ) const; // point of the path and the unit vector across it towards the right rail profile, at a distance from Point1 void plan_frame( double const Distance, glm::dvec3 &Point, glm::dvec3 &Across ) const; diff --git a/world/Track.h b/world/Track.h index 017b72f9..28c36ec6 100644 --- a/world/Track.h +++ b/world/Track.h @@ -348,6 +348,8 @@ public: double to { 0.0 }; double flarefrom { 0.0 }; double flareto { 0.0 }; + double frombend { 0.0 }; + double tobend { 0.0 }; }; using check_rail_set = std::array, 2>, 2>; // path, right side // the check rails beside the rails of each path, kept clear of the blades and the rails of other paths running inside them @@ -361,6 +363,8 @@ public: std::vector endrolled, startrolled; // how far out from the gauge line the wheels roll on the head, of the part coming from below and of the part going on glm::dvec2 endmiter { 0.0 }; glm::dvec2 startmiter { 0.0 }; + double endbend { 0.0 }; + double startbend { 0.0 }; std::vector endinner, startinner; bool endcap { true }; bool startcap { true }; diff --git a/world/TrackRails.cpp b/world/TrackRails.cpp index 59e27db7..0cdc0be7 100644 --- a/world/TrackRails.cpp +++ b/world/TrackRails.cpp @@ -122,6 +122,11 @@ TSegment const &path_segment( TTrack const &Track, int const Path ) { return ( Track.eType == tt_Switch ? *Track.SwitchExtension->Segments[ Path ] : *Track.Segment ); } +double knuckle_bend( double const Into ) { + + return std::abs( Into ) / ( 1.0 + std::sqrt( std::max( 0.0, 1.0 - Into * Into ) ) ); +} + // a check rail starting right beside the end of a wing rail running along the same rail, which the wing rail goes on in place of; the far end of it bool replaced_check( std::vector const &Checks, double const Wingend, double const Direction, double &Far ) { @@ -712,6 +717,8 @@ TTrack::check_rail_set TTrack::check_rails( std::vector const &Cr 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; @@ -731,10 +738,10 @@ TTrack::check_rail_set TTrack::check_rails( std::vector const &Cr acute( 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 - 0.03, crossing.at + 1.2, false, true, 0.5, 0.02 } ); + 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 } ); } else { - plans[ crossing.path ][ crossing.side > 0 ].push_back( { crossing.at - 1.2, crossing.at + 0.03, true, false, 0.5, 0.02 } ); + 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 ) { @@ -762,6 +769,7 @@ TTrack::check_rail_set TTrack::check_rails( std::vector const &Cr 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 ); @@ -834,7 +842,9 @@ TTrack::check_rail_set TTrack::check_rails( std::vector const &Cr 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 } ); }; + ( 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 ) { @@ -992,10 +1002,12 @@ void obtuse( TTrack::rail_plan &Plan, double const At, double const Into, TTrack auto const gap { flangeway / std::abs( Into ) }; auto const full { Size.high + 0.0135 }; auto const taper { ( flangeway + full ) / std::abs( Into ) }; + auto const bend { knuckle_bend( Into ) }; TTrack::rail_cut cut; cut.at = At; if( Into > 0.0 ) { cut.end = At; + cut.endbend = -bend; cut.start = At + gap; cut.startextents = { { At + gap, 0.006 }, { At + taper, full } }; } @@ -1003,6 +1015,7 @@ void obtuse( TTrack::rail_plan &Plan, double const At, double const Into, TTrack cut.end = At - gap; cut.endextents = { { At - taper, full }, { At - gap, 0.006 } }; cut.start = At; + cut.startbend = bend; } Plan.cuts.push_back( cut ); } @@ -1588,10 +1601,16 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i return nullptr; }; // parts no wheel rolls on go in the matte material of the cut faces + glm::dvec2 bendfrom { 0.0 }; + glm::dvec2 bendto { 0.0 }; + auto const bends = [&]( double const Start, double const End ) { + return glm::dvec2 { + std::abs( Start - bendfrom.x ) < 1e-6 ? bendfrom.y : 0.0, + std::abs( End - bendto.x ) < 1e-6 ? bendto.y : 0.0 }; }; auto const piece = [&]( double const Start, double const End, std::vector const &Offsets, std::vector const &Extents, bool const Capstart, bool const Capend, gfx::vertex_array const &Shape, bool const Rolled, std::vector const &Inner, std::vector const &Ramp = {}, bool const Walls = false, float const Lift = 0.f, float const Wallu = -1.f, double const Fade = blend ) { gfx::vertex_array vertices; gfx::userdata_array userdata; - if( Shape.size() > 1 && segment.RenderLoftSpan( vertices, m_origin, Shape, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, Lift, Inner, Rolled ? nullptr : &userdata, Ramp, Ramp.empty() ? 0.0 : Fade, Walls, Wallu ) ) { + if( Shape.size() > 1 && segment.RenderLoftSpan( vertices, m_origin, Shape, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, Lift, Inner, Rolled ? nullptr : &userdata, Ramp, Ramp.empty() ? 0.0 : Fade, Walls, Wallu, bends( Start, End ) ) ) { cut_faces( vertices, Shape.size(), Capstart, Capend, Drawing.bank, sink( true ) ); gfx::vertex_array triangles; gfx::userdata_array triangleuserdata; @@ -1687,7 +1706,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i }; auto const running = [&]( double const Start, double const End, std::vector const &Offsets, std::vector const &Extents, std::vector const &Inner, std::vector const &Rolled, bool const Capstart, bool const Capend ) { gfx::vertex_array vertices; - if( segment.RenderLoftSpan( vertices, m_origin, Profile, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, 0.f, Inner ) ) { + if( segment.RenderLoftSpan( vertices, m_origin, Profile, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, 0.f, Inner, nullptr, {}, 0.0, false, -1.f, bends( Start, End ) ) ) { cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, sink( true ) ); } piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, Inner ); @@ -1737,7 +1756,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i auto const treaded = [&]( double const Start, double const End, std::vector const &Offsets, std::vector const &Extents, std::vector const &Inner, bool const Capstart, bool const Capend, rail_tread const &Span ) { current = null_handle; gfx::vertex_array vertices; - if( segment.RenderLoftSpan( vertices, m_origin, Profile, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, 0.f, Inner ) ) { + if( segment.RenderLoftSpan( vertices, m_origin, Profile, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, 0.f, Inner, nullptr, {}, 0.0, false, -1.f, bends( Start, End ) ) ) { cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, sink( true ) ); } piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, Inner ); @@ -1929,7 +1948,10 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i else { offsets.push_back( { to, 0.0 } ); } + bendfrom = { a, a == from ? check.frombend : 0.0 }; + bendto = { b, b == to ? check.tobend : 0.0 }; piece( a, b, offsets, {}, a > 0.0 || from >= 0.0, to <= length, shape, false, {} ); + bendfrom = bendto = glm::dvec2 { 0.0 }; } for( auto const &fill : Layout.fills[ Path ][ Side > 0 ] ) { auto const a { std::max( fill.from, From ) }; @@ -1980,6 +2002,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i std::sort( cuts.begin(), cuts.end(), []( rail_cut const &A, rail_cut const &B ) { return A.at < B.at; } ); auto start { -1e9 }; auto startcap { true }; + auto startbend { 0.0 }; std::vector rolledfrom; std::vector offsets, extents, inner; for( std::size_t i = 0; i <= cuts.size(); ++i ) { @@ -2000,6 +2023,8 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i auto const rolled { i < cuts.size() ? bound( rolledfrom, cuts[ i ].endrolled, true ) : rolledfrom }; auto const capstart { start > From ? startcap : Openstart }; auto const capend { end < length ? cuts[ i ].endcap : Openend }; + bendfrom = { a, start > From ? startbend : 0.0 }; + bendto = { b, end < length ? cuts[ i ].endbend : 0.0 }; if( b - a > 0.01 ) { std::vector active; for( auto const &span : Spans ) { @@ -2037,10 +2062,12 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i } current = null_handle; } + bendfrom = bendto = glm::dvec2 { 0.0 }; if( i < cuts.size() ) { rolledfrom = cuts[ i ].startrolled; start = cuts[ i ].start; startcap = cuts[ i ].startcap; + startbend = cuts[ i ].startbend; offsets = cuts[ i ].startoffsets; extents = cuts[ i ].startextents; inner = cuts[ i ].startinner;