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;