diff --git a/application/editormodeselftest.cpp b/application/editormodeselftest.cpp index 6c9551d2..ed079b9b 100644 --- a/application/editormodeselftest.cpp +++ b/application/editormodeselftest.cpp @@ -196,7 +196,13 @@ void editor_mode::selftest_step() std::uint64_t hash{1469598103934665603ull}; std::size_t count{0}; int chunks{0}; - for (auto const *sequence : {&track->Geometry1, &track->Geometry2, &track->GeometryCut1, &track->GeometryCut2}) + std::vector sequences{&track->Geometry1, &track->Geometry2, &track->GeometryCut1, &track->GeometryCut2}; + for (auto const &tread : track->GeometryTreads) + { + sequences.push_back(&tread.geometry); + sequences.push_back(&tread.cuts); + } + for (auto const *sequence : sequences) { for (auto const &geometry : *sequence) { diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index 3c5d93e1..95deddf1 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -4235,6 +4235,8 @@ void opengl33_renderer::Render(TTrack *Track) draw(std::begin(Track->Geometry1), std::end(Track->Geometry1)); draw(std::begin(Track->Geometry2), std::end(Track->Geometry2)); + for (auto &tread : Track->GeometryTreads) + draw(std::begin(tread.geometry), std::end(tread.geometry)); if (Track->eType == tt_Switch) draw(Track->SwitchExtension->Geometry3); break; @@ -4493,7 +4495,13 @@ void opengl33_renderer::Render(scene::basic_cell::path_sequence::const_iterator { continue; } - for (auto const &cuts : {std::make_pair(track->m_materialcut1, &track->GeometryCut1), std::make_pair(track->m_materialcut2, &track->GeometryCut2)}) + std::vector> parts{{track->m_materialcut1, &track->GeometryCut1}, {track->m_materialcut2, &track->GeometryCut2}}; + for (auto &tread : track->GeometryTreads) + { + parts.emplace_back(tread.material, &tread.geometry); + parts.emplace_back(tread.matte, &tread.cuts); + } + for (auto const &cuts : parts) { if ((cuts.first == null_handle) || cuts.second->empty()) { diff --git a/rendering/openglrenderer.cpp b/rendering/openglrenderer.cpp index 9c7821ee..d66e7bf6 100644 --- a/rendering/openglrenderer.cpp +++ b/rendering/openglrenderer.cpp @@ -3140,6 +3140,10 @@ opengl_renderer::Render( TTrack *Track ) { Bind_Material( Track->m_material2 ); m_geometry.draw( std::begin( Track->Geometry2 ), std::end( Track->Geometry2 ) ); } + for( auto const &tread : Track->GeometryTreads ) { + Bind_Material( tread.material ); + m_geometry.draw( std::begin( tread.geometry ), std::end( tread.geometry ) ); + } if( ( Track->eType == tt_Switch ) && ( Track->SwitchExtension->m_material3 != 0 ) ) { Bind_Material( Track->SwitchExtension->m_material3 ); @@ -3337,7 +3341,12 @@ opengl_renderer::Render( scene::basic_cell::path_sequence::const_iterator First, if( false == track->m_visible ) { continue; } - for( auto const &cuts : { std::make_pair( track->m_materialcut1, &track->GeometryCut1 ), std::make_pair( track->m_materialcut2, &track->GeometryCut2 ) } ) { + std::vector> parts { { track->m_materialcut1, &track->GeometryCut1 }, { track->m_materialcut2, &track->GeometryCut2 } }; + for( auto const &tread : track->GeometryTreads ) { + parts.emplace_back( tread.material, &tread.geometry ); + parts.emplace_back( tread.matte, &tread.cuts ); + } + for( auto const &cuts : parts ) { if( ( cuts.first == null_handle ) || ( cuts.second->empty() ) ) { continue; diff --git a/world/Track.cpp b/world/Track.cpp index bdcf8d17..f3574e88 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -1368,6 +1368,7 @@ void TTrack::rebuild_geometry( gfx::geometrybank_handle const &Bank ) { Geometry2.clear(); GeometryCut1.clear(); GeometryCut2.clear(); + GeometryTreads.clear(); if( SwitchExtension ) { SwitchExtension->Geometry3 = gfx::geometry_handle{}; } @@ -1531,6 +1532,17 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { } }; gfx::vertex_array vertices; + for( int path = 0; path < 2; ++path ) { + auto const material { path ? m_material2 : m_material1 }; + if( material == 0 ) { + continue; + } + auto const side { blade_side( path ) }; + loft_blade( path, side > 0 ? rpts1 : rpts2, side > 0 ? rpts3 : rpts4, texture_length( material ), vertices ); + cut_faces( vertices, nnumPts, true, false, path ? GeometryCut2 : GeometryCut1 ); + ( path ? Geometry2 : Geometry1 ).emplace_back( GfxRenderer->Insert( vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP ) ); + vertices.clear(); + } for( int path = 0; path < 2; ++path ) { auto const material { path ? m_material2 : m_material1 }; if( material == 0 ) { @@ -1541,10 +1553,6 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) { auto const texturelength { texture_length( material ) }; auto const side { blade_side( path ) }; auto const &rail { side > 0 ? rpts1 : rpts2 }; - loft_blade( path, rail, side > 0 ? rpts3 : rpts4, texturelength, vertices ); - cut_faces( vertices, nnumPts, true, false, cuts ); - geometry.emplace_back( GfxRenderer->Insert( vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP ) ); - vertices.clear(); // fixed parts // szyna za iglicą fixed_rail( path, rail, side, texturelength, geometry, cuts ); diff --git a/world/Track.h b/world/Track.h index 98bda210..1fa6d428 100644 --- a/world/Track.h +++ b/world/Track.h @@ -189,6 +189,13 @@ public: material_handle m_materialcut2 = 0; // matte variant of texture 2, for the cut faces of rails of the other switch path geometryhandle_sequence GeometryCut1; // cut faces of rails textured with texture 1, the blade tip first for a switch geometryhandle_sequence GeometryCut2; // cut faces of rails textured with texture 2, the blade tip first + struct tread_geometry { + material_handle material { 0 }; + material_handle matte { 0 }; + geometryhandle_sequence geometry; + geometryhandle_sequence cuts; + }; + std::vector GeometryTreads; std::vector m_paths; // source data for owned paths bool m_editorremoved { false }; @@ -387,11 +394,17 @@ public: bool capto { false }; double lift { 0.0 }; }; + struct rail_tread { + double from { 0.0 }; + double to { 0.0 }; + material_handle material { 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 treads[ 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; @@ -504,6 +517,9 @@ private: // 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 bool 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 ); int blade_side( int const Path ) const; + material_handle rail_material( int const Path ) const; + glm::vec2 tread_band() const; + material_handle shared_tread( int const Path, TTrack const &Other, int const Otherpath ) const; void loft_blade( int const Path, gfx::vertex_array const &Rail, gfx::vertex_array const &Blade, float const Texturelength, gfx::vertex_array &Vertices ) const; // members static profiles_array m_profiles; // shared database of path element profiles diff --git a/world/TrackRails.cpp b/world/TrackRails.cpp index 3035d627..d9afb2b3 100644 --- a/world/TrackRails.cpp +++ b/world/TrackRails.cpp @@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/. #include "simulation/simulation.h" #include "scene/scene.h" #include "rendering/renderer.h" +#include "stb/stb_image.h" namespace { @@ -196,6 +197,97 @@ material_handle cut_material( material_handle const Material ) { return unrolled ? GfxRenderer->Fetch_Material( "editor_unrolled" ) : GfxRenderer->Matte_Material( Material ); } +float tread_brightness( material_handle const Material, glm::vec2 const &Band ) { + + static std::map, float> brightness; + auto const key { std::make_tuple( Material, Band.x, Band.y ) }; + if( auto const lookup { brightness.find( key ) }; lookup != brightness.end() ) { + return lookup->second; + } + auto &result { brightness[ key ] }; + result = -1.f; + if( Material == null_handle ) { + return result; + } + auto const texture { GfxRenderer->Material( Material )->GetTexture( 0 ) }; + if( texture == null_handle ) { + return result; + } + auto const &image { GfxRenderer->Texture( texture ) }; + auto const type { std::string( image.get_type() ) }; + auto const file { std::string( image.get_name() ) + type }; + auto const columns = [&]( int const Width ) { + auto const first { std::clamp( static_cast( std::floor( std::min( Band.x, Band.y ) * Width ) ), 0, Width - 1 ) }; + auto const last { std::clamp( static_cast( std::ceil( std::max( Band.x, Band.y ) * Width ) ) - 1, first, Width - 1 ) }; + return std::pair{ first, last }; }; + auto sum { 0.0 }; + std::size_t count { 0 }; + auto const add = [&]( float const Red, float const Green, float const Blue ) { + sum += 0.2126 * Red + 0.7152 * Green + 0.0722 * Blue; + ++count; }; + if( type == ".dds" ) { + std::ifstream input( file, std::ios::binary ); + std::array header {}; + if( input.read( reinterpret_cast( header.data() ), header.size() ) && std::equal( header.begin(), header.begin() + 4, "DDS " ) ) { + auto const word = [&]( std::size_t const Offset ) { + return static_cast( header[ Offset ] ) | static_cast( header[ Offset + 1 ] ) << 8 | static_cast( header[ Offset + 2 ] ) << 16 | static_cast( header[ Offset + 3 ] ) << 24; }; + auto const height { static_cast( word( 12 ) ) }; + auto const width { static_cast( word( 16 ) ) }; + auto const fourcc { word( 84 ) }; + auto const blocksize { fourcc == 0x31545844u ? 8 : ( fourcc == 0x33545844u || fourcc == 0x35545844u ) ? 16 : 0 }; + if( blocksize > 0 && width > 0 && height > 0 ) { + auto const wide { ( width + 3 ) / 4 }; + auto const high { ( height + 3 ) / 4 }; + std::vector data( static_cast( wide ) * high * blocksize ); + if( input.read( reinterpret_cast( data.data() ), data.size() ) ) { + auto const [ first, last ] { columns( width ) }; + auto const unpack = []( unsigned const Color ) { + return glm::vec3{ ( ( Color >> 11 ) & 31 ) * 255.f / 31.f, ( ( Color >> 5 ) & 63 ) * 255.f / 63.f, ( Color & 31 ) * 255.f / 31.f }; }; + for( int row = 0; row < high; ++row ) { + for( int column = first / 4; column <= last / 4; ++column ) { + auto const *block { &data[ ( static_cast( row ) * wide + column ) * blocksize + ( blocksize - 8 ) ] }; + auto const low { static_cast( block[ 0 ] | block[ 1 ] << 8 ) }; + auto const top { static_cast( block[ 2 ] | block[ 3 ] << 8 ) }; + auto const opaque { blocksize == 16 || low > top }; + glm::vec3 const colors[ 4 ] { + unpack( low ), + unpack( top ), + opaque ? ( 2.f * unpack( low ) + unpack( top ) ) / 3.f : 0.5f * ( unpack( low ) + unpack( top ) ), + opaque ? ( unpack( low ) + 2.f * unpack( top ) ) / 3.f : glm::vec3{ 0.f } }; + auto const bits { static_cast( block[ 4 ] ) | static_cast( block[ 5 ] ) << 8 | static_cast( block[ 6 ] ) << 16 | static_cast( block[ 7 ] ) << 24 }; + for( int texel = 0; texel < 16; ++texel ) { + auto const x { column * 4 + texel % 4 }; + auto const index { ( bits >> ( 2 * texel ) ) & 3 }; + if( x < first || x > last || ( false == opaque && index == 3 ) ) { + continue; + } + add( colors[ index ].r, colors[ index ].g, colors[ index ].b ); + } + } + } + } + } + } + } + else { + int width, height, components; + if( auto *pixels { stbi_load( file.c_str(), &width, &height, &components, 4 ) } ) { + auto const [ first, last ] { columns( width ) }; + for( int y = 0; y < height; ++y ) { + for( int x = first; x <= last; ++x ) { + auto const *pixel { &pixels[ ( static_cast( y ) * width + x ) * 4 ] }; + add( pixel[ 0 ], pixel[ 1 ], pixel[ 2 ] ); + } + } + stbi_image_free( pixels ); + } + } + if( count > 0 ) { + result = static_cast( sum / count ); + } + return result; +} + // 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 ) { @@ -978,6 +1070,47 @@ struct frame_corner { } // namespace +material_handle TTrack::rail_material( int const Path ) const { + + return ( Path == 1 && eType == tt_Switch ? m_material2 : m_material1 ); +} + +glm::vec2 TTrack::tread_band() const { + + glm::vec2 band { 0.45f, 0.55f }; + if( m_profiles.empty() ) { + return band; + } + auto const &outline { track_rail_profile( m_profile1.second ) }; + auto const count { outline.size() / 2 }; + auto top { -1e9f }; + for( std::size_t i = 0; i < count; ++i ) { + top = std::max( top, outline[ i ].position.y ); + } + glm::vec2 found { 1e9f, -1e9f }; + for( std::size_t i = 0; i < count; ++i ) { + if( outline[ i ].position.y > top - 0.0005f ) { + found = { std::min( found.x, outline[ i ].texture.s ), std::max( found.y, outline[ i ].texture.s ) }; + } + } + if( found.x > found.y ) { + return band; + } + auto const middle { 0.5f * ( found.x + found.y ) }; + auto const half { std::max( 0.5f * ( found.y - found.x ), 0.01f ) }; + return { middle - half, middle + half }; +} + +material_handle TTrack::shared_tread( int const Path, TTrack const &Other, int const Otherpath ) const { + + auto const own { rail_material( Path ) }; + auto const theirs { Other.rail_material( Otherpath ) }; + if( theirs == null_handle || theirs == own ) { + return own; + } + return ( track_rails::tread_brightness( theirs, Other.tread_band() ) > track_rails::tread_brightness( own, tread_band() ) ? theirs : own ); +} + TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { rail_layout layout; @@ -993,6 +1126,21 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { auto &guards { layout.guards }; auto &wingends { layout.wingends }; auto const guardoffset { -( flangeway + railhead ) }; + auto const tread = [&]( int const Path, int const Side, glm::dvec2 const &Span, TTrack const &Other, int const Otherpath ) { + auto const material { shared_tread( Path, Other, Otherpath ) }; + if( material != rail_material( Path ) && Span.y > Span.x ) { + layout.treads[ Path ][ Side > 0 ].push_back( { Span.x, Span.y, material } ); + } + }; + auto const across = [&]( rail_crossing const &Crossing, rail_size const &Other ) { + glm::dvec2 span { 1e9, -1e9 }; + for( auto const theirs : { Other.grooved ? Other.ledge : 0.0, Other.grooved ? Other.tread : Other.head } ) { + for( auto const ours : { size.grooved ? size.ledge : 0.0, size.grooved ? size.tread : size.head } ) { + auto const distance { Crossing.at + ( Crossing.facing * ours - theirs ) / Crossing.into }; + span = { std::min( span.x, distance ), std::max( span.y, distance ) }; + } + } + return span; }; std::vector corners; if( Crossings ) { for( auto const &crossing : crossings ) { @@ -1008,12 +1156,17 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { 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 ) ); + glm::dvec2 const block { std::min( vee[ 0 ].from, vee[ 1 ].from ), std::max( vee[ 0 ].to, vee[ 1 ].to ) }; + layout.blocks[ crossing.path ][ crossing.side > 0 ].push_back( block ); + tread( crossing.path, crossing.side, block, *crossing.track, crossing.otherpath ); if( through ) { auto &fills { layout.fills[ crossing.path ][ crossing.side > 0 ] }; fills.insert( fills.end(), vee.begin(), vee.end() ); } } + else if( through ) { + tread( crossing.path, crossing.side, across( crossing, theirs ), *crossing.track, crossing.otherpath ); + } continue; } if( crossing.angle > middle_angle && crossing.angle <= wide_angle ) { @@ -1023,6 +1176,9 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { auto const other { crossing.track->rail_dimensions() }; auto const through { goes_through( *this, *crossing.track ) }; wide( plan, crossing.at, crossing.into, crossing.facing, through ? 0.0 : -other.head, flangeway, 0.0, railhead ); + if( through ) { + tread( crossing.path, crossing.side, across( crossing, other ), *crossing.track, crossing.otherpath ); + } auto const reach { ( flangeway + railhead + 0.3 ) / std::abs( crossing.into ) }; auto &spans { guards[ crossing.path ][ crossing.side > 0 ] }; TTrack::guard_rail guard { crossing.at - reach, crossing.at + reach }; @@ -1047,8 +1203,12 @@ 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 ); + auto const nose { toward < 0.0 ? plan.cuts.back().start : plan.cuts.back().end }; + std::vector vee; + vee_fill( vee, crossing.at, crossing.into, crossing.facing, size, crossing.track->rail_dimensions(), true ); + tread( crossing.path, crossing.side, { std::min( nose, vee.front().from ), std::max( nose, vee.front().to ) }, *crossing.track, crossing.otherpath ); if( crossing.nose ) { - vee_fill( layout.fills[ crossing.path ][ crossing.side > 0 ], crossing.at, crossing.into, crossing.facing, size, crossing.track->rail_dimensions(), true ); + layout.fills[ crossing.path ][ crossing.side > 0 ].push_back( vee.front() ); } } else { @@ -1135,6 +1295,11 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { } } } + if( eType == tt_Switch && cut_rails( *this ) ) { + for( int path = 0; path < 2; ++path ) { + tread( path, -blade_side( path ), { 0.0, 0.5 * blade_length( *this, path ) }, *this, 1 - path ); + } + } if( eType == tt_Switch ) { auto const frog { this->frog() }; if( frog.valid ) { @@ -1155,7 +1320,9 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { 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 ) ); + glm::dvec2 const block { std::min( vee[ 0 ].from, vee[ 1 ].from ), std::max( vee[ 0 ].to, vee[ 1 ].to ) }; + layout.blocks[ path ][ frog.side[ path ] > 0 ].push_back( block ); + tread( path, frog.side[ path ], block, *this, 1 - path ); if( path == 0 ) { fills.insert( fills.end(), vee.begin(), vee.end() ); } @@ -1164,8 +1331,12 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { 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 ); + auto const nose { plans[ path ][ frog.side[ path ] > 0 ].cuts.back().start }; + std::vector vee; + vee_fill( vee, frog.at[ path ], into, facing, size, size, true ); + tread( path, frog.side[ path ], { std::min( nose, vee.front().from ), std::max( nose, vee.front().to ) }, *this, 1 - path ); if( path == 0 ) { - vee_fill( fills, frog.at[ path ], into, facing, size, size, true ); + fills.push_back( vee.front() ); } } } @@ -1247,12 +1418,40 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i 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 }; + 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 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, cut_material( current ), {}, {} } ); + } + return Matte ? 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 ) { + current = null_handle; + for( auto const &span : Spans ) { + if( Middle > span.from && Middle < span.to ) { + current = span.material; + break; + } + } + }; // 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, 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, Lift, Inner, Rolled ? nullptr : &userdata, Ramp, Ramp.empty() ? 0.0 : blend, Walls, Wallu ) ) { - cut_faces( vertices, Shape.size(), Capstart, Capend, Drawing.bank, Cuts ); + 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, sink( true ) ); gfx::vertex_array triangles; gfx::userdata_array triangleuserdata; auto const ring { 2 * Shape.size() }; @@ -1267,7 +1466,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i } } } - ( Rolled ? Geometry : Cuts ).emplace_back( GfxRenderer->Insert( triangles, Rolled ? empty_userdata : triangleuserdata, Drawing.bank, GL_TRIANGLES ) ); + sink( false == Rolled ).emplace_back( GfxRenderer->Insert( triangles, Rolled ? empty_userdata : triangleuserdata, Drawing.bank, GL_TRIANGLES ) ); } }; auto const level = []( std::vector const &Knots, double const Distance ) { @@ -1320,8 +1519,8 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i 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 &Inner, std::vector const &Rolled, bool const Capstart, bool const Capend ) { gfx::vertex_array vertices; - if( segment.RenderLoftSpan( vertices, m_origin, Profile, Side, halfwidth, Texturelength, Start, End, Offsets, Extents, 0.f, Inner ) ) { - cut_faces( vertices, Profile.size(), Capstart, Capend, Drawing.bank, Cuts ); + 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, 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 ); @@ -1470,11 +1669,13 @@ 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 ) { + aim( treads, 0.5 * ( a + b ) ); 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 ) ); + current = null_handle; } } auto const &bands { Layout.bands[ Path ][ Side > 0 ] }; - if( plan.cuts.empty() && bands.empty() ) { + 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; }; @@ -1509,7 +1710,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, float const Wallu = -1.f ) { + 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 }; @@ -1534,12 +1735,30 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i 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, 0.f, Wallu ); + std::vector splits; + for( auto const &span : Spans ) { + for( auto const edge : { span.from, span.to } ) { + if( edge > a + 0.01 && edge < b - 0.01 ) { + splits.push_back( edge ); + } + } } - else { - running( a, b, pieceoffsets, pieceextents, pieceinner, rolled, capstart, capend ); + 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() }; + aim( Spans, 0.5 * ( splits[ k - 1 ] + splits[ k ] ) ); + 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; } if( i < cuts.size() ) { rolledfrom = cuts[ i ].startrolled; @@ -1559,7 +1778,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i for( auto const &band : bands ) { cuts.push_back( band_cut( band, reach ) ); } - lay( Profile, cuts, true ); + lay( Profile, cuts, true, treads ); return true; } auto const height = [&]( std::size_t const Index ) { @@ -1661,7 +1880,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i wholecuts.push_back( band_cut( band, bodyreach ) ); } } - lay( body, wholecuts, false ); + 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 ] ) }; @@ -1708,9 +1927,18 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i } } if( merged.empty() ) { - lay( tops[ k ], cuts, false, wallu ); + 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; @@ -1731,8 +1959,8 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i 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 ); + lay( tops[ k ], outside, false, treads, wallu ); + lay( flats[ k ], inside, false, blocktreads, wallu ); } return true; } diff --git a/world/TrackRails.h b/world/TrackRails.h index ff8cff95..10b547b5 100644 --- a/world/TrackRails.h +++ b/world/TrackRails.h @@ -29,6 +29,7 @@ bool cut_rails( TTrack const &Track ); int blade_steps( TTrack const &Track ); double blade_length( TTrack const &Track, int const Path ); material_handle cut_material( material_handle const Material ); +float tread_brightness( material_handle const Material, glm::vec2 const &Band ); // triangles closing the first and/or the last cross-section of a lofted strip, made of pairs of points for each of Ring profile points, both sides of each void cap_loft( gfx::vertex_array const &Strip, std::size_t const Ring, bool const Start, bool const End, gfx::vertex_array &Triangles ); gfx::userdata_array worn_userdata( std::size_t const Count );