From 2b554183edf1ad2bc5a037dc7f1c2ba4174ee796 Mon Sep 17 00:00:00 2001 From: maj00r Date: Wed, 14 Oct 2026 00:47:39 +0200 Subject: [PATCH] fix(track): a loft cut along its length keeps its surface where the cut crosses a point of the profile: each span between stations sorts the points as clamped or open by its middle, so the ring at a station where a point lies right on the edge does not drop the next point to its own height and leave a dark groove along the top of the rail; clamped points that are not on the edge are kept between the heights of the edge on both sides of the run, so they never stand above the surface --- world/Segment.cpp | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/world/Segment.cpp b/world/Segment.cpp index f07aa2b7..5dfa3ae7 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -629,7 +629,7 @@ 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() ); - auto const ring = [&]( double const Distance, std::vector &Ring, std::vector &Wear ) { + auto const ring = [&]( double const Distance, double const Middle, std::vector &Ring, std::vector &Wear ) { glm::dvec3 point; glm::dvec3 across; plan_frame( Distance, point, across ); @@ -642,6 +642,8 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig auto const offset { sample( Offsets, Distance, 0.0 ) }; auto const extent { sample( Extents, Distance, 1e6 ) }; auto const innerextent { sample( Inner, Distance, -1e6 ) }; + auto const middleextent { sample( Extents, Middle, 1e6 ) }; + auto const middleinner { sample( Inner, Middle, -1e6 ) }; glm::vec3 outerwall { 0.f }, innerwall { 0.f }; if( Walls ) { auto const delta { 0.001 }; @@ -666,7 +668,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig return ShapePoints[ A ].position.x == ShapePoints[ B ].position.x && ShapePoints[ A ].position.y == ShapePoints[ B ].position.y; }; auto const clamped = [&]( std::size_t const Index ) { auto const outside { reach( Index ) }; - return ( outside > extent ? 1 : outside < innerextent ? -1 : 0 ); }; + return ( outside > middleextent ? 1 : outside < middleinner ? -1 : 0 ); }; for( std::size_t j = 0; j < count; ++j ) { auto shape { ShapePoints[ j ] }; auto outside { reach( j ) }; @@ -675,7 +677,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig if( side != 0 ) { auto const limit { side > 0 ? extent : innerextent }; auto const open = [&]( std::size_t const Index ) { - return Index < count && ( side > 0 ? reach( Index ) <= extent : reach( Index ) >= innerextent ); }; + return Index < count && ( side > 0 ? reach( Index ) <= middleextent : reach( Index ) >= middleinner ); }; auto first { j }; auto last { j }; if( Walls ) { @@ -704,6 +706,32 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig moved = neighbour; break; } + if( moved == count ) { + auto top { -1e6f }; + auto bottom { 1e6f }; + auto ends { 0 }; + for( auto const step : { -1, 1 } ) { + auto index { static_cast( j ) }; + while( index + step >= 0 && index + step < static_cast( count ) && clamped( static_cast( index + step ) ) == side ) { + index += step; + } + auto const neighbour { index + step }; + if( neighbour < 0 || false == open( static_cast( neighbour ) ) ) { + continue; + } + auto const t { static_cast( ( limit - reach( static_cast( neighbour ) ) ) / ( reach( static_cast( index ) ) - reach( static_cast( neighbour ) ) ) ) }; + auto const height { glm::mix( ShapePoints[ neighbour ].position.y, ShapePoints[ index ].position.y, t ) }; + top = std::max( top, height ); + bottom = std::min( bottom, height ); + ++ends; + } + if( ends > 0 ) { + shape.position.y = std::min( shape.position.y, top ); + } + if( ends > 1 ) { + shape.position.y = std::max( shape.position.y, bottom ); + } + } outside = limit; auto const onwall { paired ? ( false == open( partner ) && clamped( partner ) == side ) : moved == count }; if( onwall ) { @@ -728,10 +756,11 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig auto const texturelength { static_cast( fTextureLength ) }; std::vector ring1, ring2; std::vector wear1, wear2; - ring( stations.front(), ring1, wear1 ); float tv1 { clamp_circular( 1.f - static_cast( std::fmod( From, fTextureLength ) / fTextureLength ), 1.0f ) }; for( std::size_t i = 1; i < stations.size(); ++i ) { - ring( stations[ i ], ring2, wear2 ); + auto const middle { 0.5 * ( stations[ i - 1 ] + stations[ i ] ) }; + ring( stations[ i - 1 ], middle, ring1, wear1 ); + ring( stations[ i ], middle, ring2, wear2 ); tv1 = clamp_circular( tv1, 1.0f ); auto const tv2 { tv1 - static_cast( stations[ i ] - stations[ i - 1 ] ) / texturelength }; for( std::size_t j = 0; j < ring1.size(); ++j ) { @@ -742,8 +771,6 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig Userdata->push_back( { glm::vec4{ wear2[ j ], 0.f, 0.f, 0.f } } ); } } - std::swap( ring1, ring2 ); - std::swap( wear1, wear2 ); tv1 = tv2; } return true;