diff --git a/world/Segment.cpp b/world/Segment.cpp index 27f233e1..d7da147c 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 ) 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 ) const { if( fTsBuffer.empty() || To - From < 0.001 || ShapePoints.empty() ) return false; @@ -662,41 +662,53 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig auto const count { ShapePoints.size() }; auto const reach = [&]( std::size_t const Index ) { return static_cast( Side * ShapePoints[ Index ].position.x - 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 ) { + auto const outside { reach( Index ) }; + return ( outside > extent ? 1 : outside < innerextent ? -1 : 0 ); }; for( std::size_t j = 0; j < count; ++j ) { auto shape { ShapePoints[ j ] }; auto outside { reach( j ) }; auto wall { glm::vec3{ 0.f } }; - if( outside > extent ) { - wall = outerwall; - for( auto const neighbour : { j - 1, j + 1 } ) { - if( neighbour >= count || reach( neighbour ) > extent ) { - continue; + auto const side { clamped( j ) }; + 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 ); }; + auto first { j }; + auto last { j }; + if( Walls ) { + while( first > 0 && twin( first - 1, j ) ) { + --first; + } + while( last + 1 < count && twin( last + 1, j ) ) { + ++last; } - auto const &inner { ShapePoints[ neighbour ] }; - auto const t { static_cast( ( extent - reach( neighbour ) ) / std::max( outside - reach( neighbour ), 1e-6 ) ) }; - shape.position.y = glm::mix( inner.position.y, shape.position.y, t ); - shape.texture.x = glm::mix( inner.texture.x, shape.texture.x, t ); - shape.normal = inner.normal; - wall = glm::vec3{ 0.f }; - break; } - outside = extent; - } - else if( outside < innerextent ) { - wall = innerwall; - for( auto const neighbour : { j - 1, j + 1 } ) { - if( neighbour >= count || reach( neighbour ) < innerextent ) { + auto const paired { last > first && first > 0 && last + 1 < count }; + auto const before { paired ? first - 1 : j - 1 }; + auto const after { paired ? last + 1 : j + 1 }; + auto const partner { j == last ? after : before }; + auto moved { count }; + for( auto const neighbour : { paired ? partner : before, paired ? ( partner == before ? after : before ) : after } ) { + if( false == open( neighbour ) ) { continue; } auto const &other { ShapePoints[ neighbour ] }; - auto const t { static_cast( ( innerextent - reach( neighbour ) ) / std::min( outside - reach( neighbour ), -1e-6 ) ) }; + auto const t { static_cast( ( limit - reach( neighbour ) ) / ( side > 0 ? std::max( outside - reach( neighbour ), 1e-6 ) : std::min( outside - reach( neighbour ), -1e-6 ) ) ) }; shape.position.y = glm::mix( other.position.y, shape.position.y, t ); shape.texture.x = glm::mix( other.texture.x, shape.texture.x, t ); - shape.normal = other.normal; - wall = glm::vec3{ 0.f }; + auto const blend { glm::mix( other.normal, shape.normal, t ) }; + shape.normal = ( glm::length2( blend ) > 1e-6f ? blend : other.normal ); + moved = neighbour; break; } - outside = innerextent; + outside = limit; + auto const onwall { paired ? ( false == open( partner ) && clamped( partner ) == side ) : moved == count }; + if( onwall ) { + wall = ( side > 0 ? outerwall : innerwall ); + } } auto pt { parallel * static_cast( Side * ( Halfwidth + outside + offset ) ) + position }; pt.y += shape.position.y + Lift; @@ -704,6 +716,9 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig norm.y += shape.normal.y; if( glm::length2( wall ) > 0.f ) { norm = wall; + if( Wallu >= 0.f ) { + shape.texture.x = 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( ( outside - worn + Rampwidth ) / Rampwidth, 0.0, 1.0 ) ) : 1.f ); diff --git a/world/Segment.h b/world/Segment.h index f0d85ef3..22c7321d 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 ) 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 ) 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 5570cf39..bdcf8d17 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -2626,6 +2626,7 @@ TTrack::rail_dimensions() const { result.high = std::max( result.high, static_cast( point.x ) ); } result.height = top - bottom; + result.crest = top; result.head = 0.0; for( std::size_t i = 0; i < count; ++i ) { auto const &point { profile[ i ].position }; @@ -2633,6 +2634,7 @@ TTrack::rail_dimensions() const { result.head = std::max( result.head, static_cast( point.x ) ); } } + result.tread = result.head; std::vector heads, lips; for( std::size_t i = 0; i < count; ++i ) { auto const &point { profile[ i ].position }; @@ -2663,6 +2665,18 @@ TTrack::rail_dimensions() const { result.lip = std::max( result.lip, static_cast( profile[ i ].position.x ) ); result.liptop = std::max( result.liptop, static_cast( profile[ i ].position.y ) ); } + result.tread = -1e9; + for( auto const i : heads ) { + if( profile[ i ].position.y > top - 0.0005 ) { + result.tread = std::max( result.tread, static_cast( profile[ i ].position.x ) ); + } + } + result.ledge = 1e9; + for( auto const i : lips ) { + if( profile[ i ].position.y > result.liptop - 0.0005 ) { + result.ledge = std::min( result.ledge, static_cast( profile[ i ].position.x ) ); + } + } result.rim = 1e9; result.crown = -1e9; auto const reach = [&]( double const X ) { diff --git a/world/Track.h b/world/Track.h index ca9406d9..98bda210 100644 --- a/world/Track.h +++ b/world/Track.h @@ -330,6 +330,9 @@ public: double rim { 0.0 }; double crown { 0.0 }; double liptop { 0.0 }; + double crest { 0.0 }; + double tread { 0.0735 }; + double ledge { 0.0 }; }; rail_size rail_dimensions() const; // a check rail beside a rail, how far its ends turn away from the running rail @@ -382,11 +385,13 @@ public: std::vector inner, outer; bool capfrom { false }; bool capto { false }; + double lift { 0.0 }; }; struct rail_layout { rail_plan plans[ 2 ][ 2 ]; // path, right side std::vector bands[ 2 ][ 2 ]; std::vector fills[ 2 ][ 2 ]; + std::vector blocks[ 2 ][ 2 ]; std::vector guards[ 2 ][ 2 ]; std::vector wingends[ 2 ][ 2 ]; // ends of the wing rails of other rails running along each rail, and which way they run there check_rail_set checks; @@ -493,6 +498,7 @@ private: float halfwidth { 0.f }; gfx::vertex_array checkshapes[ 2 ]; gfx::vertex_array plates[ 2 ]; + float platetexture { 0.f }; }; rail_drawing make_rail_drawing( gfx::geometrybank_handle const &Bank ); // a rail along a path from a distance on, in parts where other rails cross it, and the check rails beside it. false: no crossing, the rail is laid whole as before diff --git a/world/TrackRails.cpp b/world/TrackRails.cpp index a32e9c6c..3035d627 100644 --- a/world/TrackRails.cpp +++ b/world/TrackRails.cpp @@ -158,6 +158,7 @@ void forget() { ++stamp; } + double wing_reach( double const Slope ) { return 0.15 / Slope + 0.3; @@ -925,8 +926,8 @@ TTrack::rail_band groove_band( double const At, double const Into, double const auto const seam { 0.001 }; TTrack::rail_band band { At, Into, Facing }; band.whole = ( false == Through ); - band.from = ( Through ? 0.0 : seam - std::max( Other.head, Other.crown ) ); - band.to = ( Through ? ( Other.grooved ? -Other.lip : flangeway ) : -Other.rim - seam ); + band.from = ( Through ? 0.0 : seam - Other.tread ); + band.to = ( Through ? ( Other.grooved ? -Other.lip : flangeway ) : -Other.ledge - seam ); return band; } @@ -935,23 +936,30 @@ void vee_fill( std::vector &Fills, double const At, double co auto const seam { 0.001 }; auto const sign { Outwards ? 1.0 : -1.0 }; auto const edge { Outwards ? std::max( Own.head, Own.crown ) : Own.rim }; + auto const flat { Outwards ? Own.tread : Own.ledge }; auto const inside { Outwards == ( Facing > 0.0 ) }; auto const closest { inside ? -Other.rim : -std::max( Other.head, Other.crown ) }; + auto const rounded { inside ? Other.ledge - Other.rim : std::max( Other.head, Other.crown ) - Other.tread }; auto const own { Outwards ? Own.high - edge : edge - Own.low }; auto const other { inside ? Other.rim - Other.low : Other.high - std::max( Other.head, Other.crown ) }; - auto const gap { own + other / std::max( std::abs( Facing ), 0.1 ) }; + auto const slant { std::max( std::abs( Facing ), 0.1 ) }; + auto const gap { own + other / slant }; auto const meets = [&]( double const Width ) { return At + ( closest + Facing * ( edge + sign * Width ) ) / Into; }; auto const apex { meets( 0.0 ) }; auto const end { meets( gap ) }; + auto const reach { seam + rounded / slant }; TTrack::rail_fill fill; fill.from = std::min( apex, end ); fill.to = std::max( apex, end ); fill.capfrom = ( end < apex ); fill.capto = ( end > apex ); - std::vector line { { apex, edge + sign * seam }, { end, edge + sign * ( gap + seam ) } }; + if( Own.grooved ) { + fill.lift = std::min( Outwards ? Own.crest : Own.liptop, inside ? Other.liptop : Other.crest ) - Own.liptop; + } + std::vector line { { apex, edge + sign * reach }, { end, edge + sign * ( gap + reach ) } }; std::sort( line.begin(), line.end(), []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; } ); - std::vector fixed { { fill.from, edge - sign * seam }, { fill.to, edge - sign * seam } }; + std::vector fixed { { fill.from, flat - sign * seam }, { fill.to, flat - sign * seam } }; fill.outer = ( Outwards ? line : fixed ); fill.inner = ( Outwards ? fixed : line ); Fills.push_back( fill ); @@ -996,9 +1004,15 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { if( size.grooved || theirs.grooved ) { auto const through { size.grooved != theirs.grooved ? size.grooved : goes_through( crossing.owner ? *crossing.owner : *this, *crossing.track ) }; layout.bands[ crossing.path ][ crossing.side > 0 ].push_back( groove_band( crossing.at, crossing.into, crossing.facing, through, theirs ) ); - if( through && size.grooved && theirs.grooved && crossing.angle <= middle_angle ) { - vee_fill( layout.fills[ crossing.path ][ crossing.side > 0 ], crossing.at, crossing.into, crossing.facing, size, theirs, true ); - vee_fill( layout.fills[ crossing.path ][ crossing.side > 0 ], crossing.at, crossing.into, crossing.facing, size, theirs, false ); + if( size.grooved && theirs.grooved && crossing.angle <= middle_angle ) { + std::vector vee; + vee_fill( vee, crossing.at, crossing.into, crossing.facing, size, theirs, true ); + vee_fill( vee, crossing.at, crossing.into, crossing.facing, size, theirs, false ); + layout.blocks[ crossing.path ][ crossing.side > 0 ].emplace_back( std::min( vee[ 0 ].from, vee[ 1 ].from ), std::max( vee[ 0 ].to, vee[ 1 ].to ) ); + if( through ) { + auto &fills { layout.fills[ crossing.path ][ crossing.side > 0 ] }; + fills.insert( fills.end(), vee.begin(), vee.end() ); + } } continue; } @@ -1138,9 +1152,12 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { auto &fills { layout.fills[ path ][ frog.side[ path ] > 0 ] }; if( size.grooved ) { layout.bands[ path ][ frog.side[ path ] > 0 ].push_back( groove_band( frog.at[ path ], into, facing, path == 0, size ) ); + std::vector vee; + vee_fill( vee, frog.at[ path ], into, facing, size, size, true ); + vee_fill( vee, frog.at[ path ], into, facing, size, size, false ); + layout.blocks[ path ][ frog.side[ path ] > 0 ].emplace_back( std::min( vee[ 0 ].from, vee[ 1 ].from ), std::max( vee[ 0 ].to, vee[ 1 ].to ) ); if( path == 0 ) { - vee_fill( fills, frog.at[ path ], into, facing, size, size, true ); - vee_fill( fills, frog.at[ path ], into, facing, size, size, false ); + fills.insert( fills.end(), vee.begin(), vee.end() ); } continue; } @@ -1191,6 +1208,17 @@ TTrack::rail_drawing TTrack::make_rail_drawing( gfx::geometrybank_handle const & texture = outline[ i ].texture.s; } } + if( drawing.size.grooved ) { + auto sum { 0.f }; + auto points { 0 }; + for( std::size_t i = 0; i < outline.size() / 2; ++i ) { + if( outline[ i ].position.y > highest - 0.0005f && outline[ i ].position.x > -0.02f ) { + sum += outline[ i ].texture.s; + ++points; + } + } + texture = sum / std::max( points, 1 ); + } auto const top { static_cast( ( drawing.size.grooved ? drawing.size.liptop : highest ) - 0.0005 ) }; auto const bottom { static_cast( -drawing.size.height ) }; gfx::vertex_array plate { @@ -1200,6 +1228,7 @@ TTrack::rail_drawing TTrack::make_rail_drawing( gfx::geometrybank_handle const & {{ -1.f, top, 0.f }, { 0.f, 1.f, 0.f }, { texture, 0.f }}, {{ -1.f, top, 0.f }, { -1.f, 0.f, 0.f }, { texture, 0.f }}, {{ -1.f, bottom, 0.f }, { -1.f, 0.f, 0.f }, { texture, 0.f }} }; + drawing.platetexture = texture; create_track_rail_profile( drawing.plates[ 0 ], drawing.plates[ 1 ], &plate ); for( auto &shape : drawing.plates ) { shape.resize( plate.size() ); @@ -1219,10 +1248,10 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i auto const &plan { Layout.plans[ Path ][ Side > 0 ] }; auto const length { segment.GetLength() }; // parts no wheel rolls on go in the matte material of the cut faces - 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 ) { + 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 ) { 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, 0.f, Inner, Rolled ? nullptr : &userdata, Ramp, Ramp.empty() ? 0.0 : blend, Walls ) ) { + 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 : blend, Walls, Wallu ) ) { cut_faces( vertices, Shape.size(), Capstart, Capend, Drawing.bank, Cuts ); gfx::vertex_array triangles; gfx::userdata_array triangleuserdata; @@ -1441,7 +1470,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i auto const a { std::max( fill.from, From ) }; auto const b { std::min( fill.to, length ) }; if( b - a > 0.01 ) { - piece( a, b, {}, fill.outer, a == fill.from && fill.capfrom, b == fill.to && fill.capto, Drawing.plates[ Side > 0 ? 0 : 1 ], true, fill.inner, {}, true ); + piece( a, b, {}, fill.outer, a == fill.from && fill.capfrom, b == fill.to && fill.capto, Drawing.plates[ Side > 0 ? 0 : 1 ], true, fill.inner, {}, true, static_cast( fill.lift ) ); } } auto const &bands { Layout.bands[ Path ][ Side > 0 ] }; @@ -1480,7 +1509,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i ( outwards ? cut.endinner : cut.endextents ) = endknots; ( outwards ? cut.startextents : cut.startinner ) = startknots; return cut; }; - auto const lay = [&]( gfx::vertex_array const &Shape, std::vector cuts, bool const Wheels ) { + auto const lay = [&]( gfx::vertex_array const &Shape, std::vector cuts, bool const Wheels, float const Wallu = -1.f ) { 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 }; @@ -1506,7 +1535,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i auto const capend { end < length ? cuts[ i ].endcap : Openend }; if( b - a > 0.01 ) { if( false == Wheels || rolled.empty() || topfirst >= toplast ) { - piece( a, b, pieceoffsets, pieceextents, capstart, capend, Shape, true, pieceinner, {}, false == Wheels ); + piece( a, b, pieceoffsets, pieceextents, capstart, capend, Shape, true, pieceinner, {}, false == Wheels, 0.f, Wallu ); } else { running( a, b, pieceoffsets, pieceextents, pieceinner, rolled, capstart, capend ); @@ -1543,7 +1572,15 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i point.position = glm::mix( Profile[ A ].position, Profile[ B ].position, t ); point.texture = glm::mix( Profile[ A ].texture, Profile[ B ].texture, t ); return point; }; - std::vector tops; + auto const flattened = [&]( std::size_t const Index ) { + auto point { Profile[ Index ] }; + auto const &file { outline[ Side > 0 ? Index : count - 1 - Index ].position }; + if( file.y > size.crest - 0.0005 || ( file.x < -0.02 && file.y > size.liptop - 0.0005 ) ) { + point.normal = { 0.f, 1.f, 0.f }; + point.texture.x = Drawing.platetexture; + } + return point; }; + std::vector tops, flats; gfx::vertex_array body; std::vector floors; auto floortexture { 0.f }; @@ -1562,33 +1599,47 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i while( i + 1 < count && above( i + 1 ) ) { ++i; } - gfx::vertex_array top; + gfx::vertex_array top, flat; if( first > 0 ) { if( height( first - 1 ) > -1e-4 ) { top.push_back( Profile[ first - 1 ] ); + flat.push_back( flattened( first - 1 ) ); } else { top.push_back( floorpoint( first - 1, first ) ); + flat.push_back( top.back() ); body.push_back( top.back() ); floors.push_back( body.size() ); body.push_back( top.back() ); } } - top.insert( top.end(), Profile.begin() + first, Profile.begin() + i + 1 ); + for( auto k { first }; k <= i; ++k ) { + top.push_back( Profile[ k ] ); + flat.push_back( flattened( k ) ); + } if( i + 1 < count ) { if( height( i + 1 ) > -1e-4 ) { top.push_back( Profile[ i + 1 ] ); + flat.push_back( flattened( i + 1 ) ); } else { top.push_back( floorpoint( i, i + 1 ) ); + flat.push_back( top.back() ); floors.push_back( body.size() ); body.push_back( top.back() ); body.push_back( top.back() ); } } - top.insert( top.begin(), top.front() ); - top.push_back( top.back() ); + for( auto *strip : { &top, &flat } ) { + gfx::vertex_array doubled { strip->front(), strip->front() }; + for( std::size_t k = 1; k + 1 < strip->size(); ++k ) { + doubled.insert( doubled.end(), 2, ( *strip )[ k ] ); + } + doubled.insert( doubled.end(), 2, strip->back() ); + *strip = doubled; + } tops.push_back( top ); + flats.push_back( flat ); } if( floors.size() > 1 ) { auto const across { body[ floors.back() ].position - body[ floors.front() ].position }; @@ -1611,13 +1662,77 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i } } lay( body, wholecuts, false ); - for( auto const &top : tops ) { - auto const reach { reaches( top ) }; + auto const wallu { floortextures > 0 ? floortexture / floortextures : -1.f }; + for( std::size_t k = 0; k < tops.size(); ++k ) { + auto const reach { reaches( tops[ k ] ) }; + auto const head { reach.x + reach.y > 0.0 }; + auto edge { head ? 1e9 : -1e9 }; + for( auto const &point : tops[ k ] ) { + if( point.position.y > ( head ? size.crest : size.liptop ) - 0.0005 ) { + auto const across { static_cast( Side * point.position.x - halfwidth ) }; + edge = ( head ? std::min( edge, across ) : std::max( edge, across ) ); + } + } std::vector cuts { plan.cuts }; for( auto const &band : bands ) { - cuts.push_back( band_cut( band, reach ) ); + auto cut { band_cut( band, reach ) }; + if( false == band.whole && std::abs( band.facing ) >= 1e-3 && std::abs( edge ) < 1.0 ) { + auto const blunt { edge + ( head ? nose_tip : -nose_tip ) }; + if( head == ( band.facing / band.into > 0.0 ) ) { + cut.start = std::max( cut.start, band.at + ( ( band.into > 0.0 ? band.to : band.from ) + band.facing * blunt ) / band.into ); + cut.startcap = true; + } + else { + cut.end = std::min( cut.end, band.at + ( ( band.into > 0.0 ? band.from : band.to ) + band.facing * blunt ) / band.into ); + cut.endcap = true; + } + } + cuts.push_back( cut ); } - lay( top, cuts, false ); + std::vector spans { Layout.blocks[ Path ][ Side > 0 ] }; + for( auto const &cut : cuts ) { + for( auto &span : spans ) { + if( cut.at > span.x && cut.at < span.y ) { + span = { std::min( span.x, cut.end ), std::max( span.y, cut.start ) }; + } + } + } + std::sort( spans.begin(), spans.end(), byposition ); + std::vector merged; + for( auto const &span : spans ) { + if( false == merged.empty() && span.x <= merged.back().y ) { + merged.back().y = std::max( merged.back().y, span.y ); + } + else { + merged.push_back( span ); + } + } + if( merged.empty() ) { + lay( tops[ k ], cuts, false, wallu ); + continue; + } + auto const seam = []( double const At, double const End, double const Start ) { + rail_cut cut; + cut.at = At; + cut.end = End; + cut.start = Start; + cut.endcap = false; + cut.startcap = false; + return cut; }; + std::vector outside, inside; + for( auto const &cut : cuts ) { + auto const within { std::any_of( merged.begin(), merged.end(), [&]( glm::dvec2 const &Span ) { return cut.at > Span.x && cut.at < Span.y; } ) }; + ( within ? inside : outside ).push_back( cut ); + } + inside.push_back( seam( merged.front().x - 0.001, -1e9, merged.front().x ) ); + for( std::size_t m = 0; m < merged.size(); ++m ) { + outside.push_back( seam( 0.5 * ( merged[ m ].x + merged[ m ].y ), merged[ m ].x, merged[ m ].y ) ); + inside.push_back( m + 1 < merged.size() ? + seam( 0.5 * ( merged[ m ].y + merged[ m + 1 ].x ), merged[ m ].y, merged[ m + 1 ].x ) : + seam( merged[ m ].y + 0.001, merged[ m ].y, 1e9 ) ); + } + lay( tops[ k ], outside, false, wallu ); + lay( flats[ k ], inside, false, wallu ); } return true; }