From 347d9035e0f4c7d1a9caf341148760cde6d820c8 Mon Sep 17 00:00:00 2001 From: maj00r Date: Wed, 14 Oct 2026 09:16:37 +0200 Subject: [PATCH] refactor(track): a lofted rail span is described by one loft_span value instead of twenty arguments, and the knot line helpers (sample, bound, meetings, middle, shifted) live once in world/Knots.h instead of in lambdas of the segment and of lay_rail --- world/Knots.h | 110 +++++++++++++++++++++++++++++ world/Segment.cpp | 113 ++++++++++++++---------------- world/Segment.h | 27 +++++++- world/TrackRails.cpp | 160 +++++++++++++++---------------------------- 4 files changed, 243 insertions(+), 167 deletions(-) create mode 100644 world/Knots.h diff --git a/world/Knots.h b/world/Knots.h new file mode 100644 index 00000000..fa78aad1 --- /dev/null +++ b/world/Knots.h @@ -0,0 +1,110 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include +#include +#include + +namespace knots { + +using line = std::vector; + +inline bool by_position( glm::dvec2 const &A, glm::dvec2 const &B ) { + + return A.x < B.x; +} + +inline void sort( line &Line ) { + + std::sort( Line.begin(), Line.end(), by_position ); +} + +inline double sample( line const &Line, double const Position, double const Default ) { + + if( Line.empty() ) { + return Default; + } + if( Position <= Line.front().x ) { + return Line.front().y; + } + for( std::size_t i = 1; i < Line.size(); ++i ) { + if( Position <= Line[ i ].x ) { + return glm::mix( Line[ i - 1 ].y, Line[ i ].y, ( Position - Line[ i - 1 ].x ) / std::max( Line[ i ].x - Line[ i - 1 ].x, 1e-6 ) ); + } + } + return Line.back().y; +} + +inline line shifted( line Line, double const Value ) { + + for( auto &knot : Line ) { + knot.y += Value; + } + return Line; +} + +inline std::vector stations( line const &A, line const &B ) { + + std::vector result; + for( auto const *each : { &A, &B } ) { + for( auto const &knot : *each ) { + result.push_back( knot.x ); + } + } + std::sort( result.begin(), result.end() ); + return result; +} + +inline std::vector meetings( line const &A, line const &B ) { + + std::vector result; + if( A.empty() || B.empty() ) { + return result; + } + auto const all { stations( A, B ) }; + for( std::size_t i = 1; i < all.size(); ++i ) { + auto const before { sample( A, all[ i - 1 ], 0.0 ) - sample( B, all[ i - 1 ], 0.0 ) }; + auto const after { sample( A, all[ i ], 0.0 ) - sample( B, all[ i ], 0.0 ) }; + if( before * after < 0.0 ) { + result.push_back( all[ i - 1 ] + ( all[ i ] - all[ i - 1 ] ) * before / ( before - after ) ); + } + } + return result; +} + +inline line bound( line const &A, line const &B, bool const Lower ) { + + if( A.empty() || B.empty() ) { + return ( A.empty() ? B : A ); + } + auto all { stations( A, B ) }; + auto const crossed { meetings( A, B ) }; + all.insert( all.end(), crossed.begin(), crossed.end() ); + std::sort( all.begin(), all.end() ); + line result; + for( auto const position : all ) { + auto const a { sample( A, position, 0.0 ) }; + auto const b { sample( B, position, 0.0 ) }; + result.emplace_back( position, Lower ? std::min( a, b ) : std::max( a, b ) ); + } + return result; +} + +inline line middle( line const &A, line const &B ) { + + line result; + for( auto const position : stations( A, B ) ) { + result.emplace_back( position, 0.5 * ( sample( A, position, 0.0 ) + sample( B, position, 0.0 ) ) ); + } + return result; +} + +} // namespace knots diff --git a/world/Segment.cpp b/world/Segment.cpp index 59bbbac8..d52113c7 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -580,47 +580,35 @@ 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, std::vector const &Fitfrom, std::vector const &Fitto ) const { +bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, loft_span const &Span ) const { - if( fTsBuffer.empty() || To - From < 0.001 || ShapePoints.empty() ) + if( fTsBuffer.empty() || Span.to - Span.from < 0.001 || ShapePoints.empty() ) return false; - auto const sample = []( std::vector const &Knots, double const Distance, double const Default ) { - if( Knots.empty() ) - return Default; - if( Distance <= Knots.front().x ) - return Knots.front().y; - for( std::size_t i = 1; i < Knots.size(); ++i ) { - if( Distance <= Knots[ i ].x ) { - return glm::mix( Knots[ i - 1 ].y, Knots[ i ].y, ( Distance - Knots[ i - 1 ].x ) / std::max( Knots[ i ].x - Knots[ i - 1 ].x, 1e-6 ) ); - } - } - return Knots.back().y; }; - - std::vector stations { From, To }; + std::vector stations { Span.from, Span.to }; auto const step { std::max( fStep, 0.2 ) }; - for( auto distance { std::ceil( From / step ) * step }; distance < To; distance += step ) { + for( auto distance { std::ceil( Span.from / step ) * step }; distance < Span.to; distance += step ) { stations.push_back( distance ); } - for( auto const *knots : { &Offsets, &Extents, &Inner } ) { - for( auto const &knot : *knots ) { - if( knot.x > From && knot.x < To ) { + for( auto const *line : { &Span.offsets, &Span.extents, &Span.inner } ) { + for( auto const &knot : *line ) { + if( knot.x > Span.from && knot.x < Span.to ) { stations.push_back( knot.x ); } } } - for( auto const *knots : { &Extents, &Inner } ) { - for( std::size_t i = 1; i < knots->size(); ++i ) { - auto const &a { ( *knots )[ i - 1 ] }; - auto const &b { ( *knots )[ i ] }; + for( auto const *line : { &Span.extents, &Span.inner } ) { + for( std::size_t i = 1; i < line->size(); ++i ) { + auto const &a { ( *line )[ i - 1 ] }; + auto const &b { ( *line )[ i ] }; if( std::abs( b.y - a.y ) < 1e-9 ) { continue; } for( auto const &point : ShapePoints ) { - auto const reach { static_cast( Side * point.position.x - Halfwidth ) }; + auto const reach { static_cast( Span.side * point.position.x - Span.halfwidth ) }; auto const t { ( reach - a.y ) / ( b.y - a.y ) }; auto const distance { glm::mix( a.x, b.x, t ) }; - if( t > 0.0 && t < 1.0 && distance > From && distance < To ) { + if( t > 0.0 && t < 1.0 && distance > Span.from && distance < Span.to ) { stations.push_back( distance ); } } @@ -628,26 +616,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 || false == Fitfrom.empty() || false == Fitto.empty() ) { + if( Span.fromend.bend != 0.0 || Span.toend.bend != 0.0 || false == Span.fromend.fit.empty() || false == Span.toend.fit.empty() ) { auto widest { 0.0 }; for( auto const &point : ShapePoints ) { - widest = std::max( widest, std::abs( static_cast( Side * point.position.x - Halfwidth ) ) ); + widest = std::max( widest, std::abs( static_cast( Span.side * point.position.x - Span.halfwidth ) ) ); } auto offsetmost { 0.0 }; - for( auto const &knot : Offsets ) { + for( auto const &knot : Span.offsets ) { offsetmost = std::max( offsetmost, std::abs( knot.y ) ); } - auto const margin = [&]( double const Bend, std::vector const &Fit ) { + auto const margin = [&]( loft_end const &End ) { auto fitmost { 0.0 }; - for( auto const &knot : Fit ) { + for( auto const &knot : End.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 ) }; + return std::abs( End.bend ) * ( End.fit.empty() ? widest : widest + offsetmost ) + fitmost + 0.001; }; + auto const frommargin { margin( Span.fromend ) }; + auto const tomargin { margin( Span.toend ) }; stations.erase( std::remove_if( stations.begin(), stations.end(), [&]( double const Distance ) { - return ( Distance > From && Distance < To ) - && ( Distance < From + frommargin || Distance > To - tomargin ); } ), stations.end() ); + return ( Distance > Span.from && Distance < Span.to ) + && ( Distance < Span.from + frommargin || Distance > Span.to - tomargin ); } ), stations.end() ); } auto const ring = [&]( double const Distance, double const Middle, std::vector &Ring, std::vector &Wear ) { @@ -660,29 +648,30 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig } auto const position { glm::vec3{ point - Origin } }; auto const parallel { glm::vec3{ across } }; - 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 ) }; + auto const offset { knots::sample( Span.offsets, Distance, 0.0 ) }; + auto const extent { knots::sample( Span.extents, Distance, 1e6 ) }; + auto const innerextent { knots::sample( Span.inner, Distance, -1e6 ) }; + auto const middleextent { knots::sample( Span.extents, Middle, 1e6 ) }; + auto const middleinner { knots::sample( Span.inner, Middle, -1e6 ) }; glm::vec3 outerwall { 0.f }, innerwall { 0.f }; - if( Walls ) { + if( Span.walls ) { auto const delta { 0.001 }; glm::dvec3 ahead, behind, unused; plan_frame( std::min( Distance + delta, static_cast( fLength ) ), ahead, unused ); plan_frame( std::max( Distance - delta, 0.0 ), behind, unused ); auto tangent { glm::dvec3{ ahead.x - behind.x, 0.0, ahead.z - behind.z } }; tangent = ( glm::length2( tangent ) > 0.0 ? glm::normalize( tangent ) : glm::dvec3{ 0.0 } ); - auto const outward { glm::dvec3{ across } * static_cast( Side ) }; - auto const slope = [&]( std::vector const &Knots, double const Default ) { - return ( sample( Knots, Distance + delta, Default ) - sample( Knots, Distance - delta, Default ) + sample( Offsets, Distance + delta, 0.0 ) - sample( Offsets, Distance - delta, 0.0 ) ) / ( 2.0 * delta ); }; - outerwall = glm::vec3{ glm::normalize( outward - tangent * slope( Extents, 1e6 ) ) }; - innerwall = glm::vec3{ glm::normalize( tangent * slope( Inner, -1e6 ) - outward ) }; + auto const outward { glm::dvec3{ across } * static_cast( Span.side ) }; + auto const slope = [&]( knots::line const &Knots, double const Default ) { + return ( knots::sample( Knots, Distance + delta, Default ) - knots::sample( Knots, Distance - delta, Default ) + knots::sample( Span.offsets, Distance + delta, 0.0 ) - knots::sample( Span.offsets, Distance - delta, 0.0 ) ) / ( 2.0 * delta ); }; + outerwall = glm::vec3{ glm::normalize( outward - tangent * slope( Span.extents, 1e6 ) ) }; + innerwall = glm::vec3{ glm::normalize( tangent * slope( Span.inner, -1e6 ) - outward ) }; } - 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 const worn { knots::sample( Span.ramp, Distance, -1e6 ) }; + static loft_end const unshaped; + auto const &shaped { Distance <= Span.from ? Span.fromend : Distance >= Span.to ? Span.toend : unshaped }; + auto const bend { shaped.bend }; + auto const &fit { shaped.fit }; auto along { glm::vec3 { 0.f } }; auto forward { glm::vec3 { 0.f } }; if( bend != 0.0 || false == fit.empty() ) { @@ -699,7 +688,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig Wear.clear(); auto const count { ShapePoints.size() }; auto const reach = [&]( std::size_t const Index ) { - return static_cast( Side * ShapePoints[ Index ].position.x - Halfwidth ); }; + return static_cast( Span.side * ShapePoints[ Index ].position.x - Span.halfwidth ); }; auto const twin = [&]( std::size_t const A, std::size_t const B ) { 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 ) { @@ -716,7 +705,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig return Index < count && ( side > 0 ? reach( Index ) <= middleextent : reach( Index ) >= middleinner ); }; auto first { j }; auto last { j }; - if( Walls ) { + if( Span.walls ) { while( first > 0 && twin( first - 1, j ) ) { --first; } @@ -774,25 +763,25 @@ 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 ) + forward * static_cast( sample( fit, shape.position.y, 0.0 ) ) }; - pt.y += shape.position.y + Lift; + auto pt { parallel * static_cast( Span.side * ( Span.halfwidth + outside + offset ) ) + position + along * static_cast( outside + offset ) + forward * static_cast( knots::sample( fit, shape.position.y, 0.0 ) ) }; + pt.y += shape.position.y + Span.lift; auto norm { shape.normal.x * parallel }; norm.y += shape.normal.y; if( glm::length2( wall ) > 0.f ) { norm = wall; - if( Wallu >= 0.f ) { - shape.texture.x = Wallu; + if( Span.wallu >= 0.f ) { + shape.texture.x = Span.wallu; } } Ring.emplace_back( pt, glm::normalize( norm ), glm::vec2{ shape.texture.x, 0.f } ); - Wear.push_back( Rampwidth != 0.0 ? static_cast( std::clamp( ( Rampwidth > 0.0 ? outside - worn : worn - outside ) / std::abs( Rampwidth ) + 1.0, 0.0, 1.0 ) ) : 1.f ); + Wear.push_back( Span.rampwidth != 0.0 ? static_cast( std::clamp( ( Span.rampwidth > 0.0 ? outside - worn : worn - outside ) / std::abs( Span.rampwidth ) + 1.0, 0.0, 1.0 ) ) : 1.f ); } }; - auto const texturelength { static_cast( fTextureLength ) }; + auto const texturelength { static_cast( Span.texturelength ) }; std::vector ring1, ring2; std::vector wear1, wear2; - float tv1 { clamp_circular( 1.f - static_cast( std::fmod( From, fTextureLength ) / fTextureLength ), 1.0f ) }; + float tv1 { clamp_circular( 1.f - static_cast( std::fmod( Span.from, Span.texturelength ) / Span.texturelength ), 1.0f ) }; for( std::size_t i = 1; i < stations.size(); ++i ) { auto const middle { 0.5 * ( stations[ i - 1 ] + stations[ i ] ) }; ring( stations[ i - 1 ], middle, ring1, wear1 ); @@ -802,9 +791,9 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig for( std::size_t j = 0; j < ring1.size(); ++j ) { Output.emplace_back( ring1[ j ].position, ring1[ j ].normal, glm::vec2{ ring1[ j ].texture.x, tv1 } ); Output.emplace_back( ring2[ j ].position, ring2[ j ].normal, glm::vec2{ ring2[ j ].texture.x, tv2 } ); - if( Userdata != nullptr ) { - Userdata->push_back( { glm::vec4{ wear1[ j ], 0.f, 0.f, 0.f } } ); - Userdata->push_back( { glm::vec4{ wear2[ j ], 0.f, 0.f, 0.f } } ); + if( Span.userdata != nullptr ) { + Span.userdata->push_back( { glm::vec4{ wear1[ j ], 0.f, 0.f, 0.f } } ); + Span.userdata->push_back( { glm::vec4{ wear2[ j ], 0.f, 0.f, 0.f } } ); } } tv1 = tv2; diff --git a/world/Segment.h b/world/Segment.h index 1e9f4270..971794c3 100644 --- a/world/Segment.h +++ b/world/Segment.h @@ -13,6 +13,7 @@ http://mozilla.org/MPL/2.0/. #include "rendering/geometrybank.h" #include #include +#include "world/Knots.h" struct map_colored_paths { std::vector switches; @@ -21,6 +22,30 @@ struct map_colored_paths { std::vector highlighted; }; +struct loft_end { + double bend { 0.0 }; + knots::line fit; +}; + +struct loft_span { + int side { 1 }; + float halfwidth { 0.f }; + double texturelength { 1.0 }; + double from { 0.0 }; + double to { 0.0 }; + knots::line offsets; + knots::line extents; + knots::line inner; + float lift { 0.f }; + gfx::userdata_array *userdata { nullptr }; + knots::line ramp; + double rampwidth { 0.0 }; + bool walls { false }; + float wallu { -1.f }; + loft_end fromend; + loft_end toend; +}; + struct segment_data { // types enum point { @@ -132,7 +157,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 }, std::vector const &Fitfrom = {}, std::vector const &Fitto = {} ) const; + RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Origin, gfx::vertex_array const &ShapePoints, loft_span const &Span ) 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/TrackRails.cpp b/world/TrackRails.cpp index 80dec144..37bdf527 100644 --- a/world/TrackRails.cpp +++ b/world/TrackRails.cpp @@ -1678,10 +1678,31 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i 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 span = [&]( double const Start, double const End, knots::line const &Offsets, knots::line const &Extents, knots::line const &Inner ) { + loft_span result; + result.side = Side; + result.halfwidth = halfwidth; + result.texturelength = texturelength(); + result.from = Start; + result.to = End; + result.offsets = Offsets; + result.extents = Extents; + result.inner = Inner; + auto const bent { bends( Start, End ) }; + result.fromend = { bent.x, fitted( fitfrom, Start ) }; + result.toend = { bent.y, fitted( fitto, End ) }; + return result; }; 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 ), fitted( fitfrom, Start ), fitted( fitto, End ) ) ) { + auto loft { span( Start, End, Offsets, Extents, Inner ) }; + loft.lift = Lift; + loft.userdata = ( Rolled ? nullptr : &userdata ); + loft.ramp = Ramp; + loft.rampwidth = ( Ramp.empty() ? 0.0 : Fade ); + loft.walls = Walls; + loft.wallu = Wallu; + if( Shape.size() > 1 && segment.RenderLoftSpan( vertices, m_origin, Shape, loft ) ) { cut_faces( vertices, Shape.size(), Capstart, Capend, Drawing.bank, sink( true ) ); gfx::vertex_array triangles; gfx::userdata_array triangleuserdata; @@ -1700,43 +1721,6 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i sink( false == Rolled ).emplace_back( GfxRenderer->Insert( triangles, Rolled ? empty_userdata : triangleuserdata, Drawing.bank, GL_TRIANGLES ) ); } }; - auto const level = []( std::vector const &Knots, double const Distance ) { - if( Distance <= Knots.front().x ) { - return Knots.front().y; - } - for( std::size_t i = 1; i < Knots.size(); ++i ) { - if( Distance <= Knots[ i ].x ) { - return glm::mix( Knots[ i - 1 ].y, Knots[ i ].y, ( Distance - Knots[ i - 1 ].x ) / std::max( Knots[ i ].x - Knots[ i - 1 ].x, 1e-6 ) ); - } - } - return Knots.back().y; }; - auto const bound = [&]( std::vector const &A, std::vector const &B, bool const Lower ) { - if( A.empty() || B.empty() ) { - return ( A.empty() ? B : A ); - } - std::vector stations; - for( auto const *knots : { &A, &B } ) { - for( auto const &knot : *knots ) { - stations.push_back( knot.x ); - } - } - std::sort( stations.begin(), stations.end() ); - auto const count { stations.size() }; - for( std::size_t i = 1; i < count; ++i ) { - auto const a { stations[ i - 1 ] }; - auto const b { stations[ i ] }; - auto const before { level( A, a ) - level( B, a ) }; - auto const after { level( A, b ) - level( B, b ) }; - if( before * after < 0.0 ) { - stations.push_back( a + ( b - a ) * before / ( before - after ) ); - } - } - std::sort( stations.begin(), stations.end() ); - std::vector result; - for( auto const station : stations ) { - result.emplace_back( station, Lower ? std::min( level( A, station ), level( B, station ) ) : std::max( level( A, station ), level( B, station ) ) ); - } - return result; }; // the running surface on top of the head is split along the edge of the wheel treads, the part of it out of their reach goes matte; the rest of the profile and the end faces are laid whole std::size_t topfirst { Profile.size() }; std::size_t toplast { 0 }; @@ -1748,36 +1732,16 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i } auto const strip = [&]( std::size_t const First, std::size_t const Last ) { return gfx::vertex_array( Profile.begin() + First, Profile.begin() + Last + 1 ); }; - auto const shifted = []( std::vector Knots, double const Lateral ) { - for( auto &knot : Knots ) { - knot.y += Lateral; - } - return Knots; }; - auto const crossings = [&]( std::vector const &A, std::vector const &B, double const Start, double const End, double const Margin, std::vector &Splits ) { - if( A.empty() || B.empty() ) { - return; - } - std::vector stations; - for( auto const *knots : { &A, &B } ) { - for( auto const &knot : *knots ) { - stations.push_back( knot.x ); - } - } - std::sort( stations.begin(), stations.end() ); - for( std::size_t i = 1; i < stations.size(); ++i ) { - auto const before { level( A, stations[ i - 1 ] ) - level( B, stations[ i - 1 ] ) }; - auto const after { level( A, stations[ i ] ) - level( B, stations[ i ] ) }; - if( before * after < 0.0 ) { - auto const distance { stations[ i - 1 ] + ( stations[ i ] - stations[ i - 1 ] ) * before / ( before - after ) }; - if( distance > Start + Margin && distance < End - Margin ) { - Splits.push_back( distance ); - } + auto const crossings = []( knots::line const &A, knots::line const &B, double const Start, double const End, double const Margin, std::vector &Splits ) { + for( auto const distance : knots::meetings( A, B ) ) { + if( distance > Start + Margin && distance < End - Margin ) { + Splits.push_back( distance ); } } }; 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 ), fitted( fitfrom, Start ), fitted( fitto, End ) ) ) { + if( segment.RenderLoftSpan( vertices, m_origin, Profile, span( Start, End, Offsets, Extents, Inner ) ) ) { cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, sink( true ) ); } piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, Inner ); @@ -1789,7 +1753,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i low = std::min( low, static_cast( Side * point.position.x - halfwidth ) ); high = std::max( high, static_cast( Side * point.position.x - halfwidth ) ); } - auto const inner { shifted( Rolled, -blend ) }; + auto const inner { knots::shifted( Rolled, -blend ) }; std::vector splits { Start, End }; std::vector const lowline { { Start, low }, { End, low } }; std::vector const highline { { Start, high }, { End, high } }; @@ -1799,13 +1763,13 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i } } std::sort( splits.begin(), splits.end() ); - auto const shiny { bound( Extents, inner, true ) }; - auto const blended { bound( Extents, Rolled, true ) }; + auto const shiny { knots::bound( Extents, inner, true ) }; + auto const blended { knots::bound( Extents, Rolled, true ) }; for( std::size_t i = 1; i < splits.size(); ++i ) { auto const middle { 0.5 * ( splits[ i - 1 ] + splits[ i ] ) }; - auto const reach { level( Rolled, middle ) }; - auto const least { std::max( low, Inner.empty() ? low : level( Inner, middle ) ) }; - auto const most { std::min( high, Extents.empty() ? high : level( Extents, middle ) ) }; + auto const reach { knots::sample( Rolled, middle, 0.0 ) }; + auto const least { std::max( low, Inner.empty() ? low : knots::sample( Inner, middle, 0.0 ) ) }; + auto const most { std::min( high, Extents.empty() ? high : knots::sample( Extents, middle, 0.0 ) ) }; auto const shown = [&]( double const Lowest, double const Highest ) { return Highest > Lowest + 1e-6 && Highest > least && Lowest < most; }; if( shown( -1e9, reach - blend ) ) { @@ -1815,11 +1779,11 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i auto const own { current == null_handle ? rail_material( Path ) : current }; if( shown( reach - blend, reach ) ) { current = ( marked ? paint( own, { 1.f, 1.f, 0.f } ) : plain ); - piece( splits[ i - 1 ], splits[ i ], Offsets, blended, false, false, top, false, bound( inner, Inner, false ), marked ? unramped : Rolled ); + piece( splits[ i - 1 ], splits[ i ], Offsets, blended, false, false, top, false, knots::bound( inner, Inner, false ), marked ? unramped : Rolled ); } if( shown( reach, 1e9 ) ) { current = ( marked ? paint( own, { 1.f, 0.f, 1.f } ) : plain ); - piece( splits[ i - 1 ], splits[ i ], Offsets, Extents, false, false, top, false, bound( Rolled, Inner, false ) ); + piece( splits[ i - 1 ], splits[ i ], Offsets, Extents, false, false, top, false, knots::bound( Rolled, Inner, false ) ); } current = plain; } @@ -1827,7 +1791,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 ), fitted( fitfrom, Start ), fitted( fitto, End ) ) ) { + if( segment.RenderLoftSpan( vertices, m_origin, Profile, span( Start, End, Offsets, Extents, Inner ) ) ) { cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, sink( true ) ); } piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, Inner ); @@ -1850,23 +1814,11 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i auto const shared { marked ? tint( Span.material, { 0.f, 0.f, 1.f } ) : Span.material }; auto const ramp = [&]( std::vector const &Knots ) -> std::vector const & { return marked ? unramped : Knots; }; - std::vector centre; - { - std::vector stations; - for( auto const *knots : { &lower, &upper } ) { - for( auto const &knot : *knots ) { - stations.push_back( knot.x ); - } - } - std::sort( stations.begin(), stations.end() ); - for( auto const station : stations ) { - centre.emplace_back( station, 0.5 * ( level( lower, station ) + level( upper, station ) ) ); - } - } - auto const beneath { shifted( lower, -tread_fade ) }; - auto const beyond { shifted( upper, tread_fade ) }; - auto const lowfade { bound( shifted( lower, tread_fade ), centre, true ) }; - auto const highfade { bound( shifted( upper, -tread_fade ), centre, false ) }; + auto const centre { knots::middle( lower, upper ) }; + auto const beneath { knots::shifted( lower, -tread_fade ) }; + auto const beyond { knots::shifted( upper, tread_fade ) }; + auto const lowfade { knots::bound( knots::shifted( lower, tread_fade ), centre, true ) }; + auto const highfade { knots::bound( knots::shifted( upper, -tread_fade ), centre, false ) }; std::vector splits { Start, End }; std::vector const lowline { { Start, low }, { End, low } }; std::vector const highline { { Start, high }, { End, high } }; @@ -1875,47 +1827,47 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i crossings( *knots, *limit, Start, End, 0.001, splits ); } } - crossings( shifted( lower, 2.0 * tread_fade ), upper, Start, End, 0.001, splits ); + crossings( knots::shifted( lower, 2.0 * tread_fade ), upper, Start, End, 0.001, splits ); std::sort( splits.begin(), splits.end() ); for( std::size_t i = 1; i < splits.size(); ++i ) { auto const a { splits[ i - 1 ] }; auto const b { splits[ i ] }; auto const middle { 0.5 * ( a + b ) }; - auto const from { level( lower, middle ) }; - auto const to { level( upper, middle ) }; - auto const inner { level( lowfade, middle ) }; - auto const outer { level( highfade, middle ) }; - auto const least { std::max( low, Inner.empty() ? low : level( Inner, middle ) ) }; - auto const most { std::min( high, Extents.empty() ? high : level( Extents, middle ) ) }; + auto const from { knots::sample( lower, middle, 0.0 ) }; + auto const to { knots::sample( upper, middle, 0.0 ) }; + auto const inner { knots::sample( lowfade, middle, 0.0 ) }; + auto const outer { knots::sample( highfade, middle, 0.0 ) }; + auto const least { std::max( low, Inner.empty() ? low : knots::sample( Inner, middle, 0.0 ) ) }; + auto const most { std::min( high, Extents.empty() ? high : knots::sample( Extents, middle, 0.0 ) ) }; auto const shown = [&]( double const Lowest, double const Highest ) { return Highest > Lowest + 1e-6 && Highest > least && Lowest < most; }; if( shown( -1e9, from - tread_fade ) ) { current = null_handle; - piece( a, b, Offsets, bound( Extents, beneath, true ), false, false, top, true, Inner ); + piece( a, b, Offsets, knots::bound( Extents, beneath, true ), false, false, top, true, Inner ); } if( shown( from - tread_fade, from ) ) { current = owntint; - piece( a, b, Offsets, bound( Extents, lower, true ), false, false, top, false, bound( Inner, beneath, false ), ramp( lower ), false, 0.f, -1.f, tread_fade ); + piece( a, b, Offsets, knots::bound( Extents, lower, true ), false, false, top, false, knots::bound( Inner, beneath, false ), ramp( lower ), false, 0.f, -1.f, tread_fade ); } if( shown( from, inner ) ) { current = sharedtint; - piece( a, b, Offsets, bound( Extents, lowfade, true ), false, false, top, false, bound( Inner, lower, false ), ramp( lower ), false, 0.f, -1.f, -tread_fade ); + piece( a, b, Offsets, knots::bound( Extents, lowfade, true ), false, false, top, false, knots::bound( Inner, lower, false ), ramp( lower ), false, 0.f, -1.f, -tread_fade ); } if( shown( inner, outer ) ) { current = shared; - piece( a, b, Offsets, bound( Extents, highfade, true ), false, false, top, false == marked, bound( Inner, lowfade, false ) ); + piece( a, b, Offsets, knots::bound( Extents, highfade, true ), false, false, top, false == marked, knots::bound( Inner, lowfade, false ) ); } if( shown( outer, to ) ) { current = sharedtint; - piece( a, b, Offsets, bound( Extents, upper, true ), false, false, top, false, bound( Inner, highfade, false ), ramp( upper ), false, 0.f, -1.f, tread_fade ); + piece( a, b, Offsets, knots::bound( Extents, upper, true ), false, false, top, false, knots::bound( Inner, highfade, false ), ramp( upper ), false, 0.f, -1.f, tread_fade ); } if( shown( to, to + tread_fade ) ) { current = owntint; - piece( a, b, Offsets, bound( Extents, beyond, true ), false, false, top, false, bound( Inner, upper, false ), ramp( upper ), false, 0.f, -1.f, -tread_fade ); + piece( a, b, Offsets, knots::bound( Extents, beyond, true ), false, false, top, false, knots::bound( Inner, upper, false ), ramp( upper ), false, 0.f, -1.f, -tread_fade ); } if( shown( to + tread_fade, 1e9 ) ) { current = null_handle; - piece( a, b, Offsets, Extents, false, false, top, true, bound( Inner, beyond, false ) ); + piece( a, b, Offsets, Extents, false, false, top, true, knots::bound( Inner, beyond, false ) ); } } current = null_handle; @@ -2107,7 +2059,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i std::sort( pieceinner.begin(), pieceinner.end(), byposition ); auto const a { std::max( start, From ) }; auto const b { std::min( end, length ) }; - auto const rolled { i < cuts.size() ? bound( rolledfrom, cuts[ i ].endrolled, true ) : rolledfrom }; + auto const rolled { i < cuts.size() ? knots::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 };