diff --git a/world/Segment.cpp b/world/Segment.cpp index 4f755209..27f233e1 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 ) 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 ) const { if( fTsBuffer.empty() || To - From < 0.001 || ShapePoints.empty() ) return false; @@ -642,6 +642,20 @@ 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 ) }; + glm::vec3 outerwall { 0.f }, innerwall { 0.f }; + if( 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 worn { sample( Ramp, Distance, -1e6 ) }; Ring.clear(); Wear.clear(); @@ -651,7 +665,9 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig 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; @@ -661,11 +677,13 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig 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 ) { continue; @@ -675,6 +693,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig 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 }; break; } outside = innerextent; @@ -683,6 +702,9 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig pt.y += shape.position.y + Lift; auto norm { shape.normal.x * parallel }; norm.y += shape.normal.y; + if( glm::length2( wall ) > 0.f ) { + norm = wall; + } 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 68b0a93f..f0d85ef3 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 ) 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 ) 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 a8029597..5570cf39 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -2633,6 +2633,53 @@ TTrack::rail_dimensions() const { result.head = std::max( result.head, static_cast( point.x ) ); } } + std::vector heads, lips; + for( std::size_t i = 0; i < count; ++i ) { + auto const &point { profile[ i ].position }; + if( point.y > top - 0.015 ) { + ( point.x < -0.02 ? lips : heads ).push_back( i ); + } + } + if( heads.empty() || lips.empty() ) { + return result; + } + auto const first { heads.back() < lips.front() ? heads.back() : lips.back() }; + auto const last { heads.back() < lips.front() ? lips.front() : heads.front() }; + if( first >= last || ( heads.back() > lips.front() && lips.back() > heads.front() ) ) { + return result; + } + auto groove { 1e9 }; + for( auto i { first + 1 }; i < last; ++i ) { + groove = std::min( groove, static_cast( profile[ i ].position.y ) ); + } + if( groove > top ) { + return result; + } + result.grooved = true; + result.groove = groove; + result.lip = -1e9; + result.liptop = -1e9; + for( auto const i : lips ) { + 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.rim = 1e9; + result.crown = -1e9; + auto const reach = [&]( double const X ) { + result.rim = std::min( result.rim, X ); + result.crown = std::max( result.crown, X ); }; + for( std::size_t i = 0; i < count; ++i ) { + auto const &point { profile[ i ].position }; + if( point.y > groove + 1e-4 ) { + reach( point.x ); + } + if( i + 1 < count ) { + auto const &next { profile[ i + 1 ].position }; + if( ( point.y - groove ) * ( next.y - groove ) < 0.0 ) { + reach( point.x + ( next.x - point.x ) * ( groove - point.y ) / ( next.y - point.y ) ); + } + } + } return result; } diff --git a/world/Track.h b/world/Track.h index 28830cea..ca9406d9 100644 --- a/world/Track.h +++ b/world/Track.h @@ -315,6 +315,7 @@ public: int otherside { 0 }; // rail profile of the other rail, 1 right, -1 left double otherat { 0.0 }; // distance of the crossing along the other path bool aligned { true }; // both paths run the same way at the crossing + TTrack *owner { nullptr }; }; std::vector rail_crossings( bool const Joined = true ); // the size of the rail profile, out from the gauge face: positive outwards, the side of the head away from the track @@ -323,6 +324,12 @@ public: double height { 0.172 }; double low { -0.0385 }; // the reach of the profile inwards double high { 0.1115 }; // the reach of the profile outwards + bool grooved { false }; + double groove { 0.0 }; + double lip { 0.0 }; + double rim { 0.0 }; + double crown { 0.0 }; + double liptop { 0.0 }; }; rail_size rail_dimensions() const; // a check rail beside a rail, how far its ends turn away from the running rail @@ -344,6 +351,9 @@ public: std::vector endrolled, startrolled; // how far out from the gauge line the wheels roll on the head, of the part coming from below and of the part going on glm::dvec2 endmiter { 0.0 }; glm::dvec2 startmiter { 0.0 }; + std::vector endinner, startinner; + bool endcap { true }; + bool startcap { true }; }; struct rail_plan { std::vector cuts; @@ -358,8 +368,25 @@ public: glm::dvec2 frommiter { 0.0 }; glm::dvec2 tomiter { 0.0 }; }; + struct rail_band { + double at { 0.0 }; + double into { 0.0 }; + double facing { 0.0 }; + double from { 0.0 }; + double to { 0.0 }; + bool whole { true }; + }; + struct rail_fill { + double from { 0.0 }; + double to { 0.0 }; + std::vector inner, outer; + bool capfrom { false }; + bool capto { false }; + }; struct rail_layout { rail_plan plans[ 2 ][ 2 ]; // path, right side + std::vector bands[ 2 ][ 2 ]; + std::vector fills[ 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; @@ -465,6 +492,7 @@ private: rail_size size; float halfwidth { 0.f }; gfx::vertex_array checkshapes[ 2 ]; + gfx::vertex_array plates[ 2 ]; }; 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 082ddceb..a32e9c6c 100644 --- a/world/TrackRails.cpp +++ b/world/TrackRails.cpp @@ -93,13 +93,6 @@ gauge_line make_gauge_line( TSegment const &Segment, double const Offset, double return line; } -// rails of the profiles the frogs, check rails and guard rails are made for; others are laid whole -bool cut_profile( std::string Profile ) { - - std::transform( Profile.begin(), Profile.end(), Profile.begin(), []( unsigned char const Char ) { return static_cast( std::tolower( Char ) ); } ); - return Profile.empty() || Profile == "default" || Profile == "s60" || Profile == "uic60" || Profile == "s39"; -} - bool joined( TTrack const &Track, TTrack const &Other ) { auto const next = []( TTrack const &From, TTrack const &To ) { @@ -181,7 +174,6 @@ bool cut_rails( TTrack const &Track ) { return ( Track.iCategoryFlag & 15 ) == 1 && Track.m_material1 != null_handle && false == Track.m_editorremoved - && cut_profile( Track.m_profile1.first ) && ( Track.eType == tt_Normal ? Track.Segment != nullptr : Track.eType == tt_Switch && Track.SwitchExtension && Track.SwitchExtension->m_frogs && Track.SwitchExtension->Segments[ 0 ] && Track.SwitchExtension->Segments[ 1 ] ); } @@ -576,6 +568,7 @@ std::vector TTrack::rail_crossings( bool const Joined ) { auto mapped { crossing }; mapped.path = path; mapped.at = ( end ? length + along : -along ); + mapped.owner = neighbour; if( false == same ) { mapped.side = -crossing.side; mapped.into = -crossing.into; @@ -606,12 +599,15 @@ TTrack::check_rail_set TTrack::check_rails( std::vector const &Cr std::vector plans[ 2 ][ 2 ]; check_rail_set result; auto const size { rail_dimensions() }; + if( size.grooved ) { + return result; + } auto const acute = [&]( std::vector &Other, double const At, double const Toward, double const Slope, double const Flare ) { auto const half { 0.5 * check_length( Slope ) }; Other.push_back( { At + Toward * 0.25 - half, At + Toward * 0.25 + half, true, true, 1.5, Flare } ); }; for( auto const &crossing : Crossings ) { - if( crossing.angle > middle_angle ) { + if( crossing.angle > middle_angle || crossing.track->rail_dimensions().grooved ) { continue; } if( crossing.facing < 0.0 ) { @@ -924,6 +920,43 @@ bool goes_through( TTrack const &Track, TTrack const &Other ) { return ( here.x != there.x ? here.x < there.x : here.z < there.z ); } +TTrack::rail_band groove_band( double const At, double const Into, double const Facing, bool const Through, TTrack::rail_size const &Other ) { + + 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 ); + return band; +} + +void vee_fill( std::vector &Fills, double const At, double const Into, double const Facing, TTrack::rail_size const &Own, TTrack::rail_size const &Other, bool const Outwards ) { + + 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 inside { Outwards == ( Facing > 0.0 ) }; + auto const closest { inside ? -Other.rim : -std::max( Other.head, Other.crown ) }; + 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 meets = [&]( double const Width ) { + return At + ( closest + Facing * ( edge + sign * Width ) ) / Into; }; + auto const apex { meets( 0.0 ) }; + auto const end { meets( gap ) }; + 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 ) } }; + 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 } }; + fill.outer = ( Outwards ? line : fixed ); + fill.inner = ( Outwards ? fixed : line ); + Fills.push_back( fill ); +} + // crossings at a middle angle: guard rails along the sides of the quadrilateral between the tracks, glued at its corners to the guard rails of the other track struct frame_corner { TTrack *track { nullptr }; @@ -956,6 +989,19 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { if( Crossings ) { for( auto const &crossing : crossings ) { auto &plan { plans[ crossing.path ][ crossing.side > 0 ] }; + if( std::find( layout.crossed.begin(), layout.crossed.end(), crossing.track ) == layout.crossed.end() ) { + layout.crossed.push_back( crossing.track ); + } + auto const theirs { crossing.track->rail_dimensions() }; + 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 ); + } + continue; + } if( crossing.angle > middle_angle && crossing.angle <= wide_angle ) { corners.push_back( { crossing.track, crossing.path, crossing.side, crossing.otherpath, crossing.at, crossing.into, crossing.facing } ); } @@ -987,13 +1033,13 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { wingends[ crossing.path ][ crossing.side > 0 ].emplace_back( crossing.at - toward * wing_reach( slope ), -toward ); acute( plan, crossing.at, toward, slope, crossing.nose, wing_extension( *this, checks, crossing.path, crossing.side, crossing.at, toward, slope, *crossing.track, crossing.otherpath, crossing.otherside, crossing.otherat, crossing.aligned ? -toward : toward ), size ); + if( crossing.nose ) { + vee_fill( layout.fills[ crossing.path ][ crossing.side > 0 ], crossing.at, crossing.into, crossing.facing, size, crossing.track->rail_dimensions(), true ); + } } else { obtuse( plan, crossing.at, crossing.into, size ); } - if( std::find( layout.crossed.begin(), layout.crossed.end(), crossing.track ) == layout.crossed.end() ) { - layout.crossed.push_back( crossing.track ); - } } for( auto &pathguards : guards ) { std::vector spans[ 2 ]; @@ -1078,11 +1124,32 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { if( eType == tt_Switch ) { auto const frog { this->frog() }; if( frog.valid ) { + glm::dvec3 point, tangents[ 2 ]; + glm::dvec2 normals[ 2 ]; + for( int path = 0; path < 2; ++path ) { + auto const &line { rail_line( *this, path, frog.side[ path ] > 0, false ) }; + line.at( frog.at[ path ], point, tangents[ path ] ); + normals[ path ] = glm::normalize( plan( line.normal( frog.at[ path ], frog.side[ path ] ) ) ); + } + auto const facing { glm::dot( normals[ 0 ], normals[ 1 ] ) }; auto const slope { std::tan( frog.angle ) }; for( int path = 0; path < 2; ++path ) { + auto const into { glm::dot( glm::normalize( plan( tangents[ path ] ) ), normals[ 1 - path ] ) }; + 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 ) ); + 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 ); + } + continue; + } wingends[ path ][ frog.side[ path ] > 0 ].emplace_back( frog.at[ path ] + wing_reach( slope ), 1.0 ); acute( plans[ path ][ frog.side[ path ] > 0 ], frog.at[ path ], -1.0, slope, path == 0, wing_extension( *this, checks, path, frog.side[ path ], frog.at[ path ], -1.0, slope, *this, 1 - path, frog.side[ 1 - path ], frog.at[ 1 - path ], 1.0 ), size ); + if( path == 0 ) { + vee_fill( fills, frog.at[ path ], into, facing, size, size, true ); + } } } } @@ -1114,6 +1181,30 @@ TTrack::rail_drawing TTrack::make_rail_drawing( gfx::geometrybank_handle const & for( auto &shape : drawing.checkshapes ) { shape.resize( checkshape.size() ); } + if( false == m_profiles.empty() ) { + auto const &outline { track_rail_profile( m_profile1.second ) }; + auto texture { 0.f }; + auto highest { -1e9f }; + for( std::size_t i = 0; i < outline.size() / 2; ++i ) { + if( outline[ i ].position.y > highest ) { + highest = outline[ i ].position.y; + texture = outline[ i ].texture.s; + } + } + 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 { + {{ 1.f, bottom, 0.f }, { 1.f, 0.f, 0.f }, { texture, 0.f }}, + {{ 1.f, top, 0.f }, { 1.f, 0.f, 0.f }, { texture, 0.f }}, + {{ 1.f, top, 0.f }, { 0.f, 1.f, 0.f }, { texture, 0.f }}, + {{ -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 }} }; + create_track_rail_profile( drawing.plates[ 0 ], drawing.plates[ 1 ], &plate ); + for( auto &shape : drawing.plates ) { + shape.resize( plate.size() ); + } + } return drawing; } @@ -1128,10 +1219,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 = {} ) { + 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 ) { 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 ) ) { + 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 ) ) { cut_faces( vertices, Shape.size(), Capstart, Capend, Drawing.bank, Cuts ); gfx::vertex_array triangles; gfx::userdata_array triangleuserdata; @@ -1160,7 +1251,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i } } return Knots.back().y; }; - auto const lowest = [&]( std::vector const &A, std::vector const &B ) { + auto const bound = [&]( std::vector const &A, std::vector const &B, bool const Lower ) { if( A.empty() || B.empty() ) { return ( A.empty() ? B : A ); } @@ -1184,7 +1275,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i std::sort( stations.begin(), stations.end() ); std::vector result; for( auto const station : stations ) { - result.emplace_back( station, std::min( level( A, station ), level( B, station ) ) ); + 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 @@ -1198,13 +1289,13 @@ 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 running = [&]( double const Start, double const End, std::vector const &Offsets, std::vector const &Extents, std::vector const &Rolled, bool const Capstart, bool const Capend ) { + 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 ) ) { + if( segment.RenderLoftSpan( vertices, m_origin, Profile, Side, halfwidth, Texturelength, Start, End, Offsets, Extents, 0.f, Inner ) ) { cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, Cuts ); } - piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, {} ); - piece( Start, End, Offsets, Extents, false, false, strip( toplast, Profile.size() - 1 ), true, {} ); + piece( Start, End, Offsets, Extents, false, false, strip( 0, topfirst ), true, Inner ); + piece( Start, End, Offsets, Extents, false, false, strip( toplast, Profile.size() - 1 ), true, Inner ); auto const top { strip( topfirst, toplast ) }; auto low { 1e9 }; auto high { -1e9 }; @@ -1230,18 +1321,18 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i } } std::sort( splits.begin(), splits.end() ); - auto const shiny { lowest( Extents, inner ) }; - auto const blended { lowest( Extents, Rolled ) }; + auto const shiny { bound( Extents, inner, true ) }; + auto const blended { bound( Extents, Rolled, true ) }; for( std::size_t i = 1; i < splits.size(); ++i ) { auto const reach { level( Rolled, 0.5 * ( splits[ i - 1 ] + splits[ i ] ) ) }; if( reach - blend > low ) { - piece( splits[ i - 1 ], splits[ i ], Offsets, shiny, false, false, top, true, {} ); + piece( splits[ i - 1 ], splits[ i ], Offsets, shiny, false, false, top, true, Inner ); } if( reach > low && reach - blend < high ) { - piece( splits[ i - 1 ], splits[ i ], Offsets, blended, false, false, top, false, inner, Rolled ); + piece( splits[ i - 1 ], splits[ i ], Offsets, blended, false, false, top, false, bound( inner, Inner, false ), Rolled ); } if( reach < high ) { - piece( splits[ i - 1 ], splits[ i ], Offsets, Extents, false, false, top, false, Rolled ); + piece( splits[ i - 1 ], splits[ i ], Offsets, Extents, false, false, top, false, bound( Rolled, Inner, false ) ); } } }; @@ -1346,44 +1437,187 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i } piece( a, b, offsets, {}, a > 0.0 || from >= 0.0, to <= length, shape, false, {} ); } - if( plan.cuts.empty() ) { + for( auto const &fill : Layout.fills[ Path ][ Side > 0 ] ) { + 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 ); + } + } + auto const &bands { Layout.bands[ Path ][ Side > 0 ] }; + if( plan.cuts.empty() && bands.empty() ) { return false; } - auto cuts { plan.cuts }; - std::sort( cuts.begin(), cuts.end(), []( rail_cut const &A, rail_cut const &B ) { return A.at < B.at; } ); auto const byposition = []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; }; - auto start { -1e9 }; - std::vector rolledfrom; - std::vector offsets, extents; - for( std::size_t i = 0; i <= cuts.size(); ++i ) { - auto const end { i < cuts.size() ? cuts[ i ].end : 1e9 }; - auto pieceoffsets { offsets }; - auto pieceextents { extents }; - if( i < cuts.size() ) { - pieceoffsets.insert( pieceoffsets.end(), cuts[ i ].endoffsets.begin(), cuts[ i ].endoffsets.end() ); - pieceextents.insert( pieceextents.end(), cuts[ i ].endextents.begin(), cuts[ i ].endextents.end() ); + auto const reaches = [&]( gfx::vertex_array const &Shape ) { + glm::dvec2 result { 1e9, -1e9 }; + for( auto const &point : Shape ) { + auto const reach { static_cast( Side * point.position.x - halfwidth ) }; + result = { std::min( result.x, reach ), std::max( result.y, reach ) }; } - std::sort( pieceoffsets.begin(), pieceoffsets.end(), byposition ); - std::sort( pieceextents.begin(), pieceextents.end(), byposition ); - auto const a { std::max( start, From ) }; - auto const b { std::min( end, length ) }; - auto const rolled { i < cuts.size() ? lowest( rolledfrom, cuts[ i ].endrolled ) : rolledfrom }; - auto const capstart { start > From ? true : Openstart }; - auto const capend { end < length ? true : Openend }; - if( b - a > 0.01 ) { - if( rolled.empty() || topfirst >= toplast ) { - piece( a, b, pieceoffsets, pieceextents, capstart, capend, Profile, true, {} ); + return result; }; + auto const band_cut = [&]( rail_band const &Band, glm::dvec2 const &Reach ) { + rail_cut cut; + cut.at = Band.at; + auto const before { Band.into > 0.0 ? Band.from : Band.to }; + auto const after { Band.into > 0.0 ? Band.to : Band.from }; + auto const meets = [&]( double const Edge, double const Reach ) { + return glm::dvec2{ Band.at + ( Edge + Band.facing * Reach ) / Band.into, Reach }; }; + if( std::abs( Band.facing ) < 1e-3 ) { + cut.end = meets( before, 0.0 ).x; + cut.start = meets( after, 0.0 ).x; + return cut; + } + std::vector endknots { meets( before, Reach.x ), meets( before, Reach.y ) }; + std::vector startknots { meets( after, Reach.x ), meets( after, Reach.y ) }; + std::sort( endknots.begin(), endknots.end(), byposition ); + std::sort( startknots.begin(), startknots.end(), byposition ); + cut.end = endknots.back().x; + cut.start = startknots.front().x; + cut.endcap = false; + cut.startcap = false; + auto const outwards { Band.facing / Band.into > 0.0 }; + ( 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 ) { + 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 }; + std::vector rolledfrom; + std::vector offsets, extents, inner; + for( std::size_t i = 0; i <= cuts.size(); ++i ) { + auto const end { i < cuts.size() ? cuts[ i ].end : 1e9 }; + auto pieceoffsets { offsets }; + auto pieceextents { extents }; + auto pieceinner { inner }; + if( i < cuts.size() ) { + pieceoffsets.insert( pieceoffsets.end(), cuts[ i ].endoffsets.begin(), cuts[ i ].endoffsets.end() ); + pieceextents.insert( pieceextents.end(), cuts[ i ].endextents.begin(), cuts[ i ].endextents.end() ); + pieceinner.insert( pieceinner.end(), cuts[ i ].endinner.begin(), cuts[ i ].endinner.end() ); + } + std::sort( pieceoffsets.begin(), pieceoffsets.end(), byposition ); + std::sort( pieceextents.begin(), pieceextents.end(), byposition ); + 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 capstart { start > From ? startcap : Openstart }; + 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 ); + } + else { + running( a, b, pieceoffsets, pieceextents, pieceinner, rolled, capstart, capend ); + } + } + if( i < cuts.size() ) { + rolledfrom = cuts[ i ].startrolled; + start = cuts[ i ].start; + startcap = cuts[ i ].startcap; + offsets = cuts[ i ].startoffsets; + extents = cuts[ i ].startextents; + inner = cuts[ i ].startinner; + } + } + }; + auto const &outline { track_rail_profile( m_profile1.second ) }; + auto const count { Profile.size() }; + if( false == size.grooved || outline.size() != 2 * count ) { + auto cuts { plan.cuts }; + auto const reach { reaches( Profile ) }; + for( auto const &band : bands ) { + cuts.push_back( band_cut( band, reach ) ); + } + lay( Profile, cuts, true ); + return true; + } + auto const height = [&]( std::size_t const Index ) { + return static_cast( outline[ Side > 0 ? Index : count - 1 - Index ].position.y ) - size.groove; }; + auto const above = [&]( std::size_t const Index ) { + return height( Index ) > 1e-4; }; + auto const floorpoint = [&]( std::size_t const A, std::size_t const B ) { + auto const t { static_cast( height( A ) / ( height( A ) - height( B ) ) ) }; + auto point { Profile[ above( A ) ? A : B ] }; + 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; + gfx::vertex_array body; + std::vector floors; + auto floortexture { 0.f }; + auto floortextures { 0 }; + for( std::size_t i = 0; i < count; ++i ) { + if( false == above( i ) ) { + if( height( i ) > -1e-4 ) { + floors.push_back( body.size() ); + floortexture += Profile[ i ].texture.x; + ++floortextures; + } + body.push_back( Profile[ i ] ); + continue; + } + auto const first { i }; + while( i + 1 < count && above( i + 1 ) ) { + ++i; + } + gfx::vertex_array top; + if( first > 0 ) { + if( height( first - 1 ) > -1e-4 ) { + top.push_back( Profile[ first - 1 ] ); } else { - running( a, b, pieceoffsets, pieceextents, rolled, capstart, capend ); + top.push_back( floorpoint( first - 1, first ) ); + body.push_back( top.back() ); + floors.push_back( body.size() ); + body.push_back( top.back() ); } } - if( i < cuts.size() ) { - rolledfrom = cuts[ i ].startrolled; - start = cuts[ i ].start; - offsets = cuts[ i ].startoffsets; - extents = cuts[ i ].startextents; + top.insert( top.end(), Profile.begin() + first, Profile.begin() + i + 1 ); + if( i + 1 < count ) { + if( height( i + 1 ) > -1e-4 ) { + top.push_back( Profile[ i + 1 ] ); + } + else { + top.push_back( floorpoint( i, i + 1 ) ); + 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() ); + tops.push_back( top ); + } + if( floors.size() > 1 ) { + auto const across { body[ floors.back() ].position - body[ floors.front() ].position }; + auto up { glm::normalize( glm::vec3{ -across.y, across.x, 0.f } ) }; + if( up.y < 0.f ) { + up = -up; + } + for( auto const index : floors ) { + body[ index ].normal = up; + if( floortextures > 0 ) { + body[ index ].texture.x = floortexture / floortextures; + } + } + } + std::vector wholecuts { plan.cuts }; + auto const bodyreach { reaches( body ) }; + for( auto const &band : bands ) { + if( band.whole ) { + wholecuts.push_back( band_cut( band, bodyreach ) ); + } + } + lay( body, wholecuts, false ); + for( auto const &top : tops ) { + auto const reach { reaches( top ) }; + std::vector cuts { plan.cuts }; + for( auto const &band : bands ) { + cuts.push_back( band_cut( band, reach ) ); + } + lay( top, cuts, false ); } return true; }