/* 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/. */ #include "stdafx.h" #include "world/TrackRails.h" #include "rendering/renderer.h" namespace track_rails { // in the colour of the top of the rail foot void cap_loft( gfx::vertex_array const &Strip, std::size_t const Ring, bool const Start, bool const End, gfx::vertex_array &Triangles ) { if( Ring < 3 || Strip.size() < 2 * Ring ) { return; } std::vector, glm::vec3>> caps; if( Start ) { std::vector ring; for( std::size_t j = 0; j < Ring; ++j ) { ring.emplace_back( Strip[ 2 * j ] ); } caps.emplace_back( ring, Strip[ 0 ].position - Strip[ 1 ].position ); } if( End ) { auto const base { Strip.size() - 2 * Ring }; std::vector ring; for( std::size_t j = 0; j < Ring; ++j ) { ring.emplace_back( Strip[ base + 2 * j + 1 ] ); } caps.emplace_back( ring, Strip[ base + 1 ].position - Strip[ base ].position ); } for( auto const &cap : caps ) { auto const &ring { cap.first }; if( glm::length2( cap.second ) == 0.f ) { continue; } auto const outward { glm::normalize( cap.second ) }; auto across { glm::vec3{ -outward.z, 0.f, outward.x } }; if( glm::length2( across ) == 0.f ) { continue; } across = glm::normalize( across ); std::vector plan; auto bottom { 0.0 }, top { 0.0 }; for( auto const &vertex : ring ) { auto const offset { vertex.position - ring[ 0 ].position }; plan.emplace_back( glm::dot( offset, across ), offset.y ); bottom = std::min( bottom, offset.y ); top = std::max( top, offset.y ); } auto web { ring[ 0 ].texture }; auto nearest { std::numeric_limits::max() }; for( std::size_t j = 0; j < Ring / 2; ++j ) { auto const distance { std::abs( plan[ j ].y - 0.5 * ( bottom + top ) ) }; if( distance < nearest ) { nearest = distance; web = ring[ j ].texture; } } auto lowest { std::numeric_limits::max() }; for( std::size_t j = 0; j < Ring / 2; ++j ) { if( ring[ j ].normal.y > 0.5f && plan[ j ].y < lowest ) { lowest = plan[ j ].y; web = ring[ j ].texture; } } auto const cross = []( glm::dvec2 const &A, glm::dvec2 const &B, glm::dvec2 const &C ) { return ( B.x - A.x ) * ( C.y - A.y ) - ( B.y - A.y ) * ( C.x - A.x ); }; double area { 0.0 }; for( std::size_t j = 0; j < plan.size(); ++j ) { auto const &a { plan[ j ] }; auto const &b { plan[ ( j + 1 ) % plan.size() ] }; area += a.x * b.y - b.x * a.y; } auto const orientation { area < 0.0 ? -1.0 : 1.0 }; std::vector left( Ring ); for( std::size_t j = 0; j < Ring; ++j ) { left[ j ] = j; } std::vector> triangles; while( left.size() > 3 ) { auto ear { left.size() }; for( std::size_t j = 0; j < left.size() && ear == left.size(); ++j ) { auto const &a { plan[ left[ ( j + left.size() - 1 ) % left.size() ] ] }; auto const &b { plan[ left[ j ] ] }; auto const &c { plan[ left[ ( j + 1 ) % left.size() ] ] }; auto const turn { cross( a, b, c ) * orientation }; if( turn < -1e-12 ) { continue; } auto inside { false }; if( turn > 1e-12 ) { for( auto const k : left ) { auto const &p { plan[ k ] }; if( p == a || p == b || p == c ) { continue; } if( cross( a, b, p ) * orientation > 1e-12 && cross( b, c, p ) * orientation > 1e-12 && cross( c, a, p ) * orientation > 1e-12 ) { inside = true; break; } } } if( false == inside ) { ear = j; } } if( ear == left.size() ) { ear = 0; } triangles.push_back( { left[ ( ear + left.size() - 1 ) % left.size() ], left[ ear ], left[ ( ear + 1 ) % left.size() ] } ); left.erase( left.begin() + ear ); } triangles.push_back( { left[ 0 ], left[ 1 ], left[ 2 ] } ); for( auto const &triangle : triangles ) { for( auto const &order : { std::array{ 0, 1, 2 }, std::array{ 0, 2, 1 } } ) { for( auto const k : order ) { auto const index { triangle[ k ] }; Triangles.emplace_back( ring[ index ].position, outward, web ); } } } } } gfx::userdata_array worn_userdata( std::size_t const Count ) { return gfx::userdata_array( Count, gfx::vertex_userdata{ glm::vec4{ 1.f, 0.f, 0.f, 0.f } } ); } void cut_faces( gfx::vertex_array const &Strip, std::size_t const Ring, bool const Start, bool const End, gfx::geometrybank_handle const &Bank, TTrack::geometryhandle_sequence &Geometry ) { gfx::vertex_array triangles; cap_loft( Strip, Ring, Start, End, triangles ); if( false == triangles.empty() ) { auto userdata { worn_userdata( triangles.size() ) }; Geometry.emplace_back( GfxRenderer->Insert( triangles, userdata, Bank, GL_TRIANGLES ) ); } } } // namespace track_rails using namespace track_rails; TTrack::rail_drawing TTrack::make_rail_drawing( gfx::geometrybank_handle const &Bank ) { rail_drawing drawing; drawing.bank = Bank; drawing.size = rail_dimensions(); drawing.halfwidth = 0.5f * std::abs( fTrackWidth ); // check rail: a narrow head facing the running rail across the flangeway, on a thin web with a foot turned away from it, sized after the rail gfx::vertex_array checkshape { {{ -0.041f, -0.170f, 0.f }, { 1.000f, 0.000f, 0.f }, { 0.15f, 0.f }}, {{ -0.041f, -0.010f, 0.f }, { 0.707f, 0.707f, 0.f }, { 0.40f, 0.f }}, {{ -0.051f, -0.000f, 0.f }, { 0.000f, 1.000f, 0.f }, { 0.45f, 0.f }}, {{ -0.071f, -0.000f, 0.f }, { 0.000f, 1.000f, 0.f }, { 0.55f, 0.f }}, {{ -0.081f, -0.010f, 0.f }, { -0.707f, 0.707f, 0.f }, { 0.60f, 0.f }}, {{ -0.081f, -0.045f, 0.f }, { -0.707f, -0.707f, 0.f }, { 0.65f, 0.f }}, {{ -0.066f, -0.060f, 0.f }, { -0.707f, -0.707f, 0.f }, { 0.75f, 0.f }}, {{ -0.066f, -0.150f, 0.f }, { -0.707f, 0.707f, 0.f }, { 0.85f, 0.f }}, {{ -0.101f, -0.170f, 0.f }, { -1.000f, 0.000f, 0.f }, { 1.00f, 0.f }} }; for( auto &point : checkshape ) { point.position.x = -0.041f + ( point.position.x + 0.041f ) * static_cast( drawing.size.head / 0.0735 ); point.position.y *= static_cast( drawing.size.height / 0.172 ); } create_track_rail_profile( drawing.checkshapes[ 0 ], drawing.checkshapes[ 1 ], &checkshape ); 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; } } 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 { {{ 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 }} }; drawing.platetexture = texture; create_track_rail_profile( drawing.plates[ 0 ], drawing.plates[ 1 ], &plate ); for( auto &shape : drawing.plates ) { shape.resize( plate.size() ); } auto const neck { static_cast( drawing.size.neck ) }; auto webtexture { texture }; for( std::size_t i = 0; i < outline.size() / 2; ++i ) { auto const &point { outline[ i ] }; if( point.position.y < neck && point.normal.y > 0.9f ) { webtexture = point.texture.s; break; } } gfx::vertex_array filler { {{ 1.f, bottom, 0.f }, { 1.f, 0.f, 0.f }, { webtexture, 0.f }}, {{ 1.f, neck, 0.f }, { 1.f, 0.f, 0.f }, { webtexture, 0.f }}, {{ 1.f, neck, 0.f }, { 0.f, 1.f, 0.f }, { webtexture, 0.f }}, {{ -1.f, neck, 0.f }, { 0.f, 1.f, 0.f }, { webtexture, 0.f }}, {{ -1.f, neck, 0.f }, { -1.f, 0.f, 0.f }, { webtexture, 0.f }}, {{ -1.f, bottom, 0.f }, { -1.f, 0.f, 0.f }, { webtexture, 0.f }} }; create_track_rail_profile( drawing.fillers[ 0 ], drawing.fillers[ 1 ], &filler ); for( auto &shape : drawing.fillers ) { shape.resize( filler.size() ); } } return drawing; } bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, int const Path, gfx::vertex_array const &Profile, int const Side, float const Texturelength, double const From, bool const Openstart, bool const Openend, geometryhandle_sequence &Geometry, geometryhandle_sequence &Cuts ) { gfx::userdata_array empty_userdata; auto const &size { Drawing.size }; auto const halfwidth { Drawing.halfwidth }; auto const railhead { size.head }; auto const guardoffset { -( flangeway + railhead ) }; auto const &segment { path_segment( *this, Path ) }; auto const &plan { Layout.plans[ Path ][ Side > 0 ] }; auto const length { segment.GetLength() }; auto const &treads { Layout.treads[ Path ][ Side > 0 ] }; material_handle current { null_handle }; std::vector tints; static auto const marked { std::getenv( "EU07_EDITOR_TREADS" ) != nullptr }; auto const paint = [&]( material_handle const Material, glm::vec3 const &Colour ) { auto const tinted { tint_material( Material, Colour ) }; if( tinted != Material ) { tints.push_back( tinted ); } return tinted; }; std::vector const unramped; auto const sink = [&]( bool const Matte ) -> geometryhandle_sequence & { if( current == null_handle ) { return Matte ? Cuts : Geometry; } if( current == m_material1 ) { return Matte ? GeometryCut1 : Geometry1; } if( eType == tt_Switch && current == m_material2 ) { return Matte ? GeometryCut2 : Geometry2; } auto const tinted { std::find( tints.begin(), tints.end(), current ) != tints.end() }; auto found { std::find_if( GeometryTreads.begin(), GeometryTreads.end(), [&]( tread_geometry const &Tread ) { return Tread.material == current; } ) }; if( found == GeometryTreads.end() ) { found = GeometryTreads.insert( GeometryTreads.end(), { current, tinted ? current : cut_material( current ), {}, {} } ); } return Matte && false == tinted ? found->cuts : found->geometry; }; auto const texturelength = [&]() { return ( current == null_handle ? Texturelength : texture_length( current ) ); }; auto const aim = [&]( std::vector const &Spans, double const Middle ) -> rail_tread const * { current = null_handle; for( auto const &span : Spans ) { if( Middle > span.from && Middle < span.to ) { current = span.material; return &span; } } return nullptr; }; // parts no wheel rolls on go in the matte material of the cut faces glm::dvec2 bendfrom { 0.0 }; glm::dvec2 bendto { 0.0 }; auto const bends = [&]( double const Start, double const End ) { 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 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; 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; auto const ring { 2 * Shape.size() }; for( std::size_t block = 0; block + ring <= vertices.size(); block += ring ) { for( std::size_t j = 0; j + 1 < Shape.size(); ++j ) { for( auto const index : { 0, 1, 2, 1, 3, 2 } ) { auto const vertex { block + 2 * j + index }; triangles.push_back( vertices[ vertex ] ); if( false == Rolled ) { triangleuserdata.push_back( userdata[ vertex ] ); } } } } sink( false == Rolled ).emplace_back( GfxRenderer->Insert( triangles, Rolled ? empty_userdata : triangleuserdata, Drawing.bank, GL_TRIANGLES ) ); } }; // 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 }; for( std::size_t j = 0; j < Profile.size(); ++j ) { if( Profile[ j ].position.y > static_cast( -0.087 * size.height ) ) { topfirst = std::min( topfirst, j ); toplast = std::max( toplast, j ); } } 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 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, 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 ); 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 }; for( auto const &point : top ) { 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 { 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 } }; for( auto const *knots : { &Rolled, &inner } ) { for( auto const *limit : { &lowline, &highline, &Extents, &Inner } ) { crossings( *knots, *limit, Start, End, 0.0, splits ); } } std::sort( splits.begin(), splits.end() ); 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 { 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 ) ) { piece( splits[ i - 1 ], splits[ i ], Offsets, shiny, false, false, top, true, Inner ); } auto const plain { current }; 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, 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, knots::bound( Rolled, Inner, false ) ); } current = plain; } }; 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, 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 ); 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 }; for( auto const &point : top ) { low = std::min( low, static_cast( Side * point.position.x - halfwidth ) ); high = std::max( high, static_cast( Side * point.position.x - halfwidth ) ); } auto const &lower { Span.below ? Span.gauge : Span.edge }; auto const &upper { Span.below ? Span.edge : Span.gauge }; auto const own { rail_material( Path ) }; auto const colour { 0.5f * ( tread_colour( Span.material, tread_band() ) + tread_colour( own, tread_band() ) ) }; auto const tint = [&]( material_handle const Material, glm::vec3 const &Mark ) { return paint( Material, marked ? Mark : colour ); }; auto const owntint { tint( own, { 1.f, 0.f, 0.f } ) }; auto const sharedtint { tint( Span.material, { 0.f, 1.f, 0.f } ) }; 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; }; 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 } }; for( auto const *knots : { &lower, &upper, &lowfade, &highfade, &beneath, &beyond } ) { for( auto const *limit : { &lowline, &highline, &Extents, &Inner } ) { crossings( *knots, *limit, 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 { 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, knots::bound( Extents, beneath, true ), false, false, top, true, Inner ); } if( shown( from - tread_fade, from ) ) { current = owntint; 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, 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, 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, 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, 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, knots::bound( Inner, beyond, false ) ); } } current = null_handle; }; for( auto const &guard : Layout.guards[ Path ][ Side > 0 ] ) { auto const flare { std::min( 0.3, 0.25 * ( guard.to - guard.from ) ) }; std::vector offsets; if( guard.flarefrom ) { offsets.insert( offsets.end(), { { guard.from, guardoffset - 0.02 }, { guard.from + flare, guardoffset } } ); } else { offsets.push_back( { guard.from, guardoffset } ); } if( guard.flareto ) { offsets.insert( offsets.end(), { { guard.to - flare, guardoffset }, { guard.to, guardoffset - 0.02 } } ); } else { offsets.push_back( { guard.to, guardoffset } ); } auto const top { flangeway + railhead + size.low - 0.002 }; auto const mitered = [&]( glm::dvec2 const &Miter, double const Square, bool const Latest ) { if( Miter.y == 0.0 ) { return Square; } auto const inside { Miter.x + Miter.y * ( size.low + guardoffset ) }; auto const outside { Miter.x + Miter.y * ( top + guardoffset ) }; return Latest ? std::max( inside, outside ) : std::min( inside, outside ); }; auto const envelope = [&]( std::vector const &Lines, bool const Upper, double const A, double const B ) { std::vector knots; if( Lines.empty() ) { return knots; } std::vector stations { A, B }; for( std::size_t i = 0; i < Lines.size(); ++i ) { for( std::size_t j = i + 1; j < Lines.size(); ++j ) { if( std::abs( Lines[ i ].x - Lines[ j ].x ) > 1e-12 ) { auto const d { ( Lines[ j ].y - Lines[ i ].y ) / ( Lines[ i ].x - Lines[ j ].x ) }; if( d > A && d < B ) { stations.push_back( d ); } } } } std::sort( stations.begin(), stations.end() ); for( auto const d : stations ) { auto value { Upper ? 1e6 : -1e6 }; for( auto const &line : Lines ) { value = Upper ? std::min( value, line.x * d + line.y ) : std::max( value, line.x * d + line.y ); } knots.emplace_back( d, value ); } return knots; }; auto cuts { guard.plan.cuts }; 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 ) { std::vector uppers { { 0.0, top } }; std::vector lowers; for( auto const &[ miter, ending ] : { std::pair{ startmiter, false }, std::pair{ endmiter, true } } ) { if( miter.y != 0.0 ) { ( ending == ( miter.y < 0.0 ) ? uppers : lowers ).emplace_back( 1.0 / miter.y, -miter.x / miter.y - guardoffset ); } } 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 ]; } } } auto const &shape { Drawing.checkshapes[ Side > 0 ? 0 : 1 ] }; for( auto const &check : Layout.checks[ Path ][ Side > 0 ] ) { auto const replaced { std::any_of( Layout.wingends[ Path ][ Side > 0 ].begin(), Layout.wingends[ Path ][ Side > 0 ].end(), [&]( glm::dvec2 const &Wing ) { auto farend { 0.0 }; return replaced_check( { check }, Wing.x, Wing.y, farend ); } ) }; auto const from { check.from }; auto const to { check.to }; auto const a { std::max( from, From ) }; auto const b { std::min( to, length ) }; if( replaced || b - a <= 0.01 ) { continue; } auto const flare { std::min( 0.6, 0.4 * ( to - from ) ) }; std::vector offsets; if( check.flarefrom > 0.0 ) { offsets.insert( offsets.end(), { { from, -check.flarefrom }, { from + flare, 0.0 } } ); } else { offsets.push_back( { from, 0.0 } ); } if( check.flareto > 0.0 ) { offsets.insert( offsets.end(), { { to - flare, 0.0 }, { to, -check.flareto } } ); } else { offsets.push_back( { to, 0.0 } ); } bendfrom = { a, a == from ? check.frombend : 0.0 }; bendto = { b, b == to ? check.tobend : 0.0 }; piece( a, b, offsets, {}, a > 0.0 || from >= 0.0, to <= length, shape, false, {} ); bendfrom = bendto = glm::dvec2 { 0.0 }; } 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 ) { current = null_handle; if( false == fill.web ) { aim( treads, 0.5 * ( a + b ) ); } auto const &shapes { fill.web ? Drawing.fillers : Drawing.plates }; piece( a, b, {}, fill.outer, a == fill.from && fill.capfrom, b == fill.to && fill.capto, shapes[ Side > 0 ? 0 : 1 ], true, fill.inner, {}, true, static_cast( fill.lift ) ); current = null_handle; } } auto const &bands { Layout.bands[ Path ][ Side > 0 ] }; if( plan.cuts.empty() && bands.empty() && treads.empty() ) { return false; } auto const byposition = []( glm::dvec2 const &A, glm::dvec2 const &B ) { return A.x < B.x; }; 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 ) }; } 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::vector const &Spans, 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 }; 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 ) { 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() ? 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 }; 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 ) { if( span.anchor == 0 || ( span.anchor > 0 && b > span.from + 0.01 && b < span.to + 0.01 ) || ( span.anchor < 0 && a > span.from - 0.01 && a < span.to - 0.01 ) ) { active.push_back( span ); } } std::vector splits; for( auto const &span : active ) { for( auto const edge : { span.from, span.to } ) { if( edge > a + 0.01 && edge < b - 0.01 ) { splits.push_back( edge ); } } } std::sort( splits.begin(), splits.end() ); splits.erase( std::unique( splits.begin(), splits.end(), []( double const A, double const B ) { return B - A < 0.002; } ), splits.end() ); splits.insert( splits.begin(), a ); splits.push_back( b ); for( std::size_t k = 1; k < splits.size(); ++k ) { auto const first { k == 1 }; auto const last { k + 1 == splits.size() }; auto const *span { aim( active, 0.5 * ( splits[ k - 1 ] + splits[ k ] ) ) }; if( Wheels && span != nullptr && false == span->edge.empty() && topfirst < toplast ) { treaded( splits[ k - 1 ], splits[ k ], pieceoffsets, pieceextents, pieceinner, first && capstart, last && capend, *span ); } else if( false == Wheels || rolled.empty() || topfirst >= toplast ) { piece( splits[ k - 1 ], splits[ k ], pieceoffsets, pieceextents, first && capstart, last && capend, Shape, true, pieceinner, {}, false == Wheels, 0.f, Wallu ); } else { running( splits[ k - 1 ], splits[ k ], pieceoffsets, pieceextents, pieceinner, rolled, first && capstart, last && capend ); } } 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; } } }; 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, treads ); 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; }; 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 }; 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, 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() ); } } 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() ); } } 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 }; 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, {} ); 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 ) { 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 ); } 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, treads, wallu ); continue; } std::vector blocktreads; for( auto const &span : merged ) { for( auto const &tread : treads ) { if( tread.from < span.y && tread.to > span.x ) { blocktreads.push_back( { span.x, span.y, tread.material } ); break; } } } 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, treads, wallu ); lay( flats[ k ], inside, false, blocktreads, wallu ); } return true; }