From 7e20023d07ca3a001fb460cac141c274da0f4050 Mon Sep 17 00:00:00 2001 From: maj00r Date: Tue, 13 Oct 2026 22:36:09 +0200 Subject: [PATCH] feat(track): where two rails share a running surface it takes the texture whose running surface is brighter; the brightness is read from the texture file over the u range of the rail head top, the shared parts are the block of crossing grooved rails, the nose of an acute frog from its tip to the end of the V, the through rail across a wide or grooved crossing, the frog of a switch, and the stock rails of a switch with frogs up to half the blade length where the blade grows to a full head --- application/editormodeselftest.cpp | 8 +- rendering/opengl33renderer.cpp | 10 +- rendering/openglrenderer.cpp | 11 +- world/Track.cpp | 16 +- world/Track.h | 16 ++ world/TrackRails.cpp | 268 ++++++++++++++++++++++++++--- world/TrackRails.h | 1 + 7 files changed, 303 insertions(+), 27 deletions(-) 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 );