From 23975654a4594c3817ba5319ade5dc8857057cee Mon Sep 17 00:00:00 2001 From: maj00r Date: Wed, 14 Oct 2026 03:09:08 +0200 Subject: [PATCH] feat(track): at a crossing at a wide angle the rails and guard rails ending against the side of another rail or guard rail are glued to it, their ends fitted to its profile height by height instead of square ends with caps and a gap of 5 mm, so the corners of the crossing are closed; the ends at the flangeways stay open for the flanges; a loft span can shift its first and last cross-section along the track by a fit given per height of the profile, and the rail profile gives the outlines of its field and gauge sides --- world/Segment.cpp | 30 ++++++++++++++++++------ world/Segment.h | 2 +- world/Track.cpp | 30 ++++++++++++++++++++++++ world/Track.h | 2 ++ world/TrackRails.cpp | 56 ++++++++++++++++++++++++++++++++++++++------ 5 files changed, 105 insertions(+), 15 deletions(-) diff --git a/world/Segment.cpp b/world/Segment.cpp index bcef534f..59bbbac8 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, glm::dvec2 const &Bends ) 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, std::vector const &Fitfrom, std::vector const &Fitto ) const { if( fTsBuffer.empty() || To - From < 0.001 || ShapePoints.empty() ) return false; @@ -628,14 +628,26 @@ 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 ) { + if( Bends.x != 0.0 || Bends.y != 0.0 || false == Fitfrom.empty() || false == Fitto.empty() ) { auto widest { 0.0 }; for( auto const &point : ShapePoints ) { widest = std::max( widest, std::abs( static_cast( Side * point.position.x - Halfwidth ) ) ); } + auto offsetmost { 0.0 }; + for( auto const &knot : Offsets ) { + offsetmost = std::max( offsetmost, std::abs( knot.y ) ); + } + auto const margin = [&]( double const Bend, std::vector const &Fit ) { + auto fitmost { 0.0 }; + for( auto const &knot : Fit ) { + fitmost = std::max( fitmost, std::abs( knot.y ) ); + } + return std::abs( Bend ) * ( Fit.empty() ? widest : widest + offsetmost ) + fitmost + 0.001; }; + auto const frommargin { margin( Bends.x, Fitfrom ) }; + auto const tomargin { margin( Bends.y, Fitto ) }; 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() ); + return ( Distance > From && Distance < To ) + && ( Distance < From + frommargin || Distance > To - tomargin ); } ), stations.end() ); } auto const ring = [&]( double const Distance, double const Middle, std::vector &Ring, std::vector &Wear ) { @@ -669,14 +681,18 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig } auto const worn { sample( Ramp, Distance, -1e6 ) }; auto const bend { Distance <= From ? Bends.x : Distance >= To ? Bends.y : 0.0 }; + static std::vector const unfitted; + auto const &fit { Distance <= From ? Fitfrom : Distance >= To ? Fitto : unfitted }; auto along { glm::vec3 { 0.f } }; - if( bend != 0.0 ) { + auto forward { glm::vec3 { 0.f } }; + if( bend != 0.0 || false == fit.empty() ) { 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 }; + forward = glm::vec3 { glm::normalize( tangent ) }; + along = forward * static_cast( bend ); } } Ring.clear(); @@ -758,7 +774,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 + along * static_cast( outside + offset ) }; + auto pt { parallel * static_cast( Side * ( Halfwidth + outside + offset ) ) + position + along * static_cast( outside + offset ) + forward * static_cast( sample( fit, shape.position.y, 0.0 ) ) }; 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 1ab6f317..1e9f4270 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, glm::dvec2 const &Bends = glm::dvec2 { 0.0 } ) 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 }, std::vector const &Fitfrom = {}, std::vector const &Fitto = {} ) 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.cpp b/world/Track.cpp index 40c6ec5b..f701c5dd 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -2635,6 +2635,36 @@ TTrack::rail_dimensions() const { } result.height = top - bottom; result.crest = top; + std::size_t firsttop { count }; + std::size_t lasttop { 0 }; + for( std::size_t i = 0; i < count; ++i ) { + if( profile[ i ].position.y > top - 1e-4 ) { + firsttop = std::min( firsttop, i ); + lasttop = std::max( lasttop, i ); + } + } + std::vector sides[ 2 ]; + for( std::size_t i = 0; i < count; ++i ) { + auto const &point { profile[ i ].position }; + if( i <= firsttop ) { + sides[ 0 ].emplace_back( point.y, point.x ); + } + if( i >= lasttop ) { + sides[ 1 ].emplace_back( point.y, point.x ); + } + } + for( auto &side : sides ) { + std::sort( side.begin(), side.end(), []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; } ); + } + auto const mean = []( std::vector const &Side ) { + auto sum { 0.0 }; + for( auto const &knot : Side ) { + sum += knot.y; + } + return sum / std::max( Side.size(), 1 ); }; + auto const fieldfirst { mean( sides[ 0 ] ) > mean( sides[ 1 ] ) }; + result.field = sides[ fieldfirst ? 0 : 1 ]; + result.gauge = sides[ fieldfirst ? 1 : 0 ]; auto const middle { top - 0.5 * result.height }; auto webfrom { 1e9 }; auto webto { -1e9 }; diff --git a/world/Track.h b/world/Track.h index 9f15fab4..c415bf00 100644 --- a/world/Track.h +++ b/world/Track.h @@ -342,6 +342,7 @@ public: double ledge { 0.0 }; double web { 0.0365 }; double neck { -0.0467 }; + std::vector field, gauge; }; rail_size rail_dimensions() const; // a check rail beside a rail, how far its ends turn away from the running rail @@ -367,6 +368,7 @@ public: glm::dvec2 startmiter { 0.0 }; double endbend { 0.0 }; double startbend { 0.0 }; + std::vector endfit, startfit; std::vector endinner, startinner; bool endcap { true }; bool startcap { true }; diff --git a/world/TrackRails.cpp b/world/TrackRails.cpp index 7dd82d02..80dec144 100644 --- a/world/TrackRails.cpp +++ b/world/TrackRails.cpp @@ -1021,7 +1021,7 @@ void obtuse( TTrack::rail_plan &Plan, double const At, double const Into, TTrack } // crossing at a wide angle: running rails are broken for the flanges of the other track, the ones of the track not going through also where the head of the other rail lies; guard rails give way to the running rails and flangeways of the other track, the ones of the track not going through also to its guard rails -void wide( TTrack::rail_plan &Plan, double const At, double const Into, double const Facing, double const From, double const To, double const Inner, double const Outer ) { +void wide( TTrack::rail_plan &Plan, double const At, double const Into, double const Facing, double const From, double const To, double const Inner, double const Outer, std::vector const &Fromline = {}, std::vector const &Toline = {} ) { auto const low { ( From + std::min( Inner * Facing, Outer * Facing ) - 0.005 ) / Into }; auto const high { ( To + std::max( Inner * Facing, Outer * Facing ) + 0.005 ) / Into }; @@ -1031,9 +1031,31 @@ void wide( TTrack::rail_plan &Plan, double const At, double const Into, double c cut.start = At + std::max( low, high ); cut.endmiter = { At + ( Into > 0.0 ? From - 0.005 : To + 0.005 ) / Into, Facing / Into }; cut.startmiter = { At + ( Into > 0.0 ? To + 0.005 : From - 0.005 ) / Into, Facing / Into }; + auto const glue = [&]( std::vector const &Line, double const Edge, double &Position, glm::dvec2 &Miter, double &Bend, std::vector &Fit, bool &Cap ) { + if( Line.empty() ) { + return; + } + Position = At + Edge / Into; + Miter = {}; + Bend = Facing / Into; + for( auto const &knot : Line ) { + Fit.emplace_back( knot.x, ( knot.y - Edge ) / Into ); + } + Cap = false; }; + glue( Into > 0.0 ? Fromline : Toline, Into > 0.0 ? From : To, cut.end, cut.endmiter, cut.endbend, cut.endfit, cut.endcap ); + glue( Into > 0.0 ? Toline : Fromline, Into > 0.0 ? To : From, cut.start, cut.startmiter, cut.startbend, cut.startfit, cut.startcap ); Plan.cuts.push_back( cut ); } +std::vector glue_line( std::vector const &Outline, double const Base ) { + + std::vector line; + for( auto const &knot : Outline ) { + line.emplace_back( knot.x, Base - knot.y ); + } + return line; +} + TTrack::rail_fill guard_fill( TTrack::guard_rail const &Guard, TTrack::rail_size const &Size ) { auto const offset { Size.web - ( flangeway + Size.head ) }; @@ -1338,7 +1360,7 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { if( crossing.angle > wide_angle ) { auto const other { crossing.track->rail_dimensions() }; auto const through { goes_through( *this, *crossing.track ) }; - wide( plan, crossing.at, crossing.into, crossing.facing, through ? 0.0 : -other.head, flangeway, 0.0, railhead ); + wide( plan, crossing.at, crossing.into, crossing.facing, through ? 0.0 : -other.head, flangeway, 0.0, railhead, through ? std::vector{} : glue_line( other.field, 0.0 ) ); if( through ) { tread( crossing.path, crossing.side, across( crossing, other ), *crossing.track, crossing.otherpath ); } @@ -1346,7 +1368,7 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { auto &spans { guards[ crossing.path ][ crossing.side > 0 ] }; TTrack::guard_rail guard { crossing.at - reach, crossing.at + reach }; guard.through = through; - wide( guard.plan, crossing.at, crossing.into, crossing.facing, -other.head, through ? flangeway : flangeway + other.head, guardoffset, guardoffset + railhead ); + wide( guard.plan, crossing.at, crossing.into, crossing.facing, -other.head, through ? flangeway : flangeway + other.head, guardoffset, guardoffset + railhead, glue_line( other.field, 0.0 ), through ? std::vector{} : glue_line( other.gauge, flangeway + other.head ) ); auto merged { false }; for( auto &span : spans ) { if( guard.from < span.to + 2.0 && guard.to > span.from - 2.0 ) { @@ -1652,10 +1674,14 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i 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 }; }; + std::pair> fitfrom, fitto; + std::vector const unfitted; + auto const fitted = [&]( std::pair> const &Fit, double const Distance ) -> std::vector const & { + return ( std::abs( Distance - Fit.first ) < 1e-6 ? Fit.second : unfitted ); }; 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, bends( Start, End ) ) ) { + 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 ), fitted( fitfrom, Start ), fitted( fitto, End ) ) ) { cut_faces( vertices, Shape.size(), Capstart, Capend, Drawing.bank, sink( true ) ); gfx::vertex_array triangles; gfx::userdata_array triangleuserdata; @@ -1751,7 +1777,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, nullptr, {}, 0.0, false, -1.f, bends( Start, End ) ) ) { + 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 ), fitted( fitfrom, Start ), fitted( fitto, End ) ) ) { cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, sink( true ) ); } piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, Inner ); @@ -1801,7 +1827,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, nullptr, {}, 0.0, false, -1.f, bends( Start, End ) ) ) { + 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 ), fitted( fitfrom, Start ), fitted( fitto, End ) ) ) { cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, sink( true ) ); } piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, Inner ); @@ -1946,9 +1972,11 @@ 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 { guard.from }; auto startmiter { guard.frommiter }; + rail_cut startcut; for( std::size_t i = 0; i <= cuts.size(); ++i ) { auto const end { i < cuts.size() ? cuts[ i ].end : guard.to }; auto const endmiter { i < cuts.size() ? cuts[ i ].endmiter : guard.tomiter }; + auto const endcut { i < cuts.size() ? cuts[ i ] : rail_cut {} }; auto const a { std::max( { mitered( startmiter, start, false ), From, 0.0 } ) }; auto const b { std::min( { mitered( endmiter, end, true ), length, mitered( guard.tomiter, guard.to, true ) } ) }; if( b - a > 0.01 ) { @@ -1959,11 +1987,20 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i ( ending == ( miter.y < 0.0 ) ? uppers : lowers ).emplace_back( 1.0 / miter.y, -miter.x / miter.y - guardoffset ); } } - piece( a, b, offsets, envelope( uppers, true, a, b ), true, true, Profile, false, envelope( lowers, false, a, b ) ); + auto const glued { a == start && false == startcut.startfit.empty() }; + auto const gluedend { b == end && false == endcut.endfit.empty() }; + bendfrom = { a, glued ? startcut.startbend : 0.0 }; + bendto = { b, gluedend ? endcut.endbend : 0.0 }; + fitfrom = { a, glued ? startcut.startfit : unfitted }; + fitto = { b, gluedend ? endcut.endfit : unfitted }; + piece( a, b, offsets, envelope( uppers, true, a, b ), false == glued, false == gluedend, Profile, false, envelope( lowers, false, a, b ) ); + bendfrom = bendto = glm::dvec2 { 0.0 }; + fitfrom = fitto = {}; } if( i < cuts.size() && cuts[ i ].start > start ) { start = cuts[ i ].start; startmiter = cuts[ i ].startmiter; + startcut = cuts[ i ]; } } } @@ -2052,6 +2089,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i auto start { -1e9 }; auto startcap { true }; auto startbend { 0.0 }; + std::vector startfit; std::vector rolledfrom; std::vector offsets, extents, inner; for( std::size_t i = 0; i <= cuts.size(); ++i ) { @@ -2074,6 +2112,8 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i 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 }; + fitfrom = { a, start > From ? startfit : unfitted }; + fitto = { b, end < length ? cuts[ i ].endfit : unfitted }; if( b - a > 0.01 ) { std::vector active; for( auto const &span : Spans ) { @@ -2112,11 +2152,13 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i current = null_handle; } bendfrom = bendto = glm::dvec2 { 0.0 }; + fitfrom = fitto = {}; if( i < cuts.size() ) { rolledfrom = cuts[ i ].startrolled; start = cuts[ i ].start; startcap = cuts[ i ].startcap; startbend = cuts[ i ].startbend; + startfit = cuts[ i ].startfit; offsets = cuts[ i ].startoffsets; extents = cuts[ i ].startextents; inner = cuts[ i ].startinner;