diff --git a/model/material.cpp b/model/material.cpp index a375e85e..b36e5cb2 100644 --- a/model/material.cpp +++ b/model/material.cpp @@ -662,6 +662,29 @@ material_manager::create_from_text( std::string const &Name, std::string const & return materialhandle; } +namespace { + +opengl_material +variant_of( opengl_material const &Source, std::string const &Suffix ) { + + opengl_material variant; + variant.textures = Source.textures; + variant.texture_variants = Source.texture_variants; + variant.params = Source.params; + variant.params_state = Source.params_state; + variant.shader = Source.shader; + variant.opacity = Source.opacity; + variant.selfillum = Source.selfillum; + variant.shadow_rank = Source.shadow_rank; + variant.name = Source.name + Suffix; + variant.size = Source.size; + variant.is_good = Source.is_good; + variant.path = Source.path; + return variant; +} + +} // namespace + material_handle material_manager::create_matte( material_handle const Material ) { @@ -672,21 +695,8 @@ material_manager::create_matte( material_handle const Material ) { if( lookup != m_mattes.end() ) { return lookup->second; } - auto const &source { m_materials[ Material ] }; - opengl_material matte; - matte.textures = source.textures; - matte.texture_variants = source.texture_variants; - matte.params = source.params; - matte.params_state = source.params_state; - matte.shader = source.shader; - matte.opacity = source.opacity; - matte.selfillum = source.selfillum; + auto matte { variant_of( m_materials[ Material ], ":matte" ) }; matte.glossiness = 0.1f; - matte.shadow_rank = source.shadow_rank; - matte.name = source.name + ":matte"; - matte.size = source.size; - matte.is_good = source.is_good; - matte.path = source.path; if( matte.shader ) { for( auto const &[ key, value ] : std::vector>{ { "weathering", glm::vec4{ 1.f } } } ) { auto const entry { matte.shader->param_conf.find( key ) }; @@ -710,6 +720,42 @@ material_manager::create_matte( material_handle const Material ) { return mattehandle; } +material_handle +material_manager::create_tinted( material_handle const Material, glm::vec3 const &Colour ) { + + if( Material == null_handle || Material >= m_materials.size() || !m_materials[ Material ].shader ) { + return Material; + } + auto const key { std::make_tuple( Material, Colour.r, Colour.g, Colour.b ) }; + if( auto const lookup { m_tints.find( key ) }; lookup != m_tints.end() ) { + return lookup->second; + } + auto const &conf { m_materials[ Material ].shader->param_conf }; + auto const weathering { conf.find( "weathering" ) }; + if( weathering == conf.end() + || weathering->second.offset != 3 + || std::any_of( + std::begin( conf ), std::end( conf ), + [&]( auto const &Entry ) { + return Entry.first != weathering->first && Entry.second.location == weathering->second.location; } ) ) { + return Material; + } + auto tinted { variant_of( m_materials[ Material ], ":tinted" ) }; + tinted.glossiness = m_materials[ Material ].glossiness; + auto const location { weathering->second.location }; + tinted.params[ location ] = glm::vec4{ Colour, -1.f }; + tinted.params_state.erase( + std::remove_if( + std::begin( tinted.params_state ), std::end( tinted.params_state ), + [&]( gl::shader::param_entry const &State ) { + return State.location == location; } ), + std::end( tinted.params_state ) ); + auto const tintedhandle { static_cast( m_materials.size() ) }; + m_materials.emplace_back( std::move( tinted ) ); + m_tints.emplace( key, tintedhandle ); + return tintedhandle; +} + void material_manager::on_weather_change() { diff --git a/model/material.h b/model/material.h index 20d2022a..05a68880 100644 --- a/model/material.h +++ b/model/material.h @@ -145,6 +145,8 @@ public: // copy of provided material with its specular highlights and reflections damped. returns: handle to the copy, made once per material material_handle create_matte( material_handle const Material ); + material_handle + create_tinted( material_handle const Material, glm::vec3 const &Colour ); opengl_material const & material( material_handle const Material ) const { return m_materials[ Material ]; } opengl_material & @@ -174,6 +176,7 @@ private: material_sequence m_materials; index_map m_materialmappings; std::unordered_map m_mattes; + std::map, material_handle> m_tints; }; //--------------------------------------------------------------------------- diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index 95deddf1..800b49ec 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -2231,6 +2231,12 @@ material_handle opengl33_renderer::Matte_Material(material_handle const Material return m_materials.create_matte(Material); } +material_handle opengl33_renderer::Tinted_Material(material_handle const Material, glm::vec3 const &Colour) +{ + + return m_materials.create_tinted(Material, Colour); +} + std::shared_ptr opengl33_renderer::Fetch_Shader(const std::string &name) { auto it = m_shaders.find(name); diff --git a/rendering/opengl33renderer.h b/rendering/opengl33renderer.h index b2359c55..9a314986 100644 --- a/rendering/opengl33renderer.h +++ b/rendering/opengl33renderer.h @@ -80,6 +80,8 @@ class opengl33_renderer : public gfx_renderer { Fetch_Material( std::string const &Filename, bool const Loadnow = true ) override; material_handle Matte_Material( material_handle const Material ) override; + material_handle + Tinted_Material( material_handle const Material, glm::vec3 const &Colour ) override; void Bind_Material( material_handle const Material, TSubModel const *sm = nullptr, lighting_data const *lighting = nullptr ) override; IMaterial const * diff --git a/rendering/renderer.h b/rendering/renderer.h index 12ca8540..83020add 100644 --- a/rendering/renderer.h +++ b/rendering/renderer.h @@ -71,6 +71,7 @@ public: virtual auto Material( material_handle const Material ) const -> IMaterial const * = 0; // the same material with its highlights damped, for faces which aren't polished like the surfaces they are cut from. backends which can't make one give back the material itself virtual auto Matte_Material( material_handle const Material ) -> material_handle { return Material; } + virtual auto Tinted_Material( material_handle const Material, glm::vec3 const &Colour ) -> material_handle { return Material; } // heightmap terrain: material of a chunk blending the materials of up to 8 layers by their weights. // Weights: (Samples + 1)^2 values per layer, planar, rows of growing z, the weights of a sample adding up to 255; null with a single layer. // Placement: corner of the chunk (x, z) in the texture coordinates of its mesh, and the size of the chunk. diff --git a/shaders/light_common.glsl b/shaders/light_common.glsl index bc25b246..3fbeb713 100644 --- a/shaders/light_common.glsl +++ b/shaders/light_common.glsl @@ -288,11 +288,13 @@ vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float refl // Diffuse colour is applied once, to the albedo; the light terms below are not scaled by it again texturecolor *= material_diffuse(); float weathered = (param[2].w > 0.0 ? clamp(param[2].w * UserData.x, 0.0, 1.0) : 0.0); - float polished = weathered * smoothstep(0.03, 0.12, dot(texturecolor, vec3(0.2126, 0.7152, 0.0722))); + float tinted = (param[2].w < 0.0 ? clamp(-param[2].w * UserData.x, 0.0, 1.0) : 0.0); + float polished = max(weathered * smoothstep(0.03, 0.12, dot(texturecolor, vec3(0.2126, 0.7152, 0.0722))), tinted); float weatheredluma = min(dot(texturecolor, vec3(0.2126, 0.7152, 0.0722)), 0.05); vec3 unused = texturecolor * (weatheredluma / max(dot(texturecolor, vec3(0.2126, 0.7152, 0.0722)), 1e-4)); unused = mix(unused, vec3(1.0, 0.82, 0.74) * (weatheredluma / 0.8525), 0.5); texturecolor = mix(texturecolor, unused, smoothstep(0.0, 0.4, weathered)); + texturecolor = (tinted > 0.0 ? mix(texturecolor, param[2].rgb, tinted) : texturecolor); specularity = mix(specularity, min(specularity, 0.03), smoothstep(0.0, 0.4, polished)); metalic = mix(metalic, 0.0, polished); glossiness = mix(glossiness, min(glossiness, 0.06 * abs(param[1].w)), polished); diff --git a/world/Segment.cpp b/world/Segment.cpp index d7da147c..f07aa2b7 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -721,7 +721,7 @@ bool TSegment::RenderLoftSpan( gfx::vertex_array &Output, glm::dvec3 const &Orig } } Ring.emplace_back( pt, glm::normalize( norm ), glm::vec2{ shape.texture.x, 0.f } ); - Wear.push_back( Rampwidth > 0.0 ? static_cast( std::clamp( ( outside - worn + Rampwidth ) / Rampwidth, 0.0, 1.0 ) ) : 1.f ); + Wear.push_back( Rampwidth != 0.0 ? static_cast( std::clamp( ( Rampwidth > 0.0 ? outside - worn : worn - outside ) / std::abs( Rampwidth ) + 1.0, 0.0, 1.0 ) ) : 1.f ); } }; diff --git a/world/TrackRails.cpp b/world/TrackRails.cpp index b1667214..5bd2436f 100644 --- a/world/TrackRails.cpp +++ b/world/TrackRails.cpp @@ -197,15 +197,20 @@ 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 ) { +material_handle tint_material( material_handle const Material, glm::vec3 const &Colour ) { - static std::map, float> brightness; + return GfxRenderer->Tinted_Material( Material, Colour ); +} + +std::pair const &tread_sample( material_handle const Material, glm::vec2 const &Band ) { + + static std::map, std::pair> samples; auto const key { std::make_tuple( Material, Band.x, Band.y ) }; - if( auto const lookup { brightness.find( key ) }; lookup != brightness.end() ) { + if( auto const lookup { samples.find( key ) }; lookup != samples.end() ) { return lookup->second; } - auto &result { brightness[ key ] }; - result = -1.f; + auto &result { samples[ key ] }; + result = { -1.f, glm::vec3{ 0.f } }; if( Material == null_handle ) { return result; } @@ -221,9 +226,11 @@ float tread_brightness( material_handle const Material, glm::vec2 const &Band ) 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 }; + glm::dvec3 colour { 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; + colour += glm::pow( glm::dvec3{ Red, Green, Blue } / 255.0, glm::dvec3{ 2.2 } ); ++count; }; if( type == ".dds" ) { std::ifstream input( file, std::ios::binary ); @@ -283,11 +290,21 @@ float tread_brightness( material_handle const Material, glm::vec2 const &Band ) } } if( count > 0 ) { - result = static_cast( sum / count ); + result = { static_cast( sum / count ), glm::vec3{ colour / static_cast( count ) } }; } return result; } +float tread_brightness( material_handle const Material, glm::vec2 const &Band ) { + + return tread_sample( Material, Band ).first; +} + +glm::vec3 tread_colour( material_handle const Material, glm::vec2 const &Band ) { + + return tread_sample( Material, Band ).second; +} + // 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 ) { @@ -1481,6 +1498,7 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i auto const length { segment.GetLength() }; auto const &treads { Layout.treads[ Path ][ Side > 0 ] }; material_handle current { null_handle }; + std::vector tints; auto const sink = [&]( bool const Matte ) -> geometryhandle_sequence & { if( current == null_handle ) { return Matte ? Cuts : Geometry; @@ -1491,11 +1509,12 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i 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, cut_material( current ), {}, {} } ); + found = GeometryTreads.insert( GeometryTreads.end(), { current, tinted ? current : cut_material( current ), {}, {} } ); } - return Matte ? found->cuts : found->geometry; }; + 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 * { @@ -1509,10 +1528,10 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i return nullptr; }; // 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 ) { + 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; - 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 ) ) { + 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 : Fade, Walls, Wallu ) ) { cut_faces( vertices, Shape.size(), Capstart, Capend, Drawing.bank, sink( true ) ); gfx::vertex_array triangles; gfx::userdata_array triangleuserdata; @@ -1643,8 +1662,40 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i } 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 ) { + auto const tinted { tint_material( Material, colour ) }; + if( tinted != Material ) { + tints.push_back( tinted ); + } + return tinted; }; + auto const owntint { tint( own ) }; + auto const sharedtint { tint( Span.material ) }; + auto const shifted = []( std::vector Knots, double const Lateral ) { + for( auto &knot : Knots ) { + knot.y += Lateral; + } + return Knots; }; + std::vector centre; + { + std::vector stations; + for( auto const *knots : { &lower, &upper } ) { + for( auto const &knot : *knots ) { + stations.push_back( knot.x ); + } + } + std::sort( stations.begin(), stations.end() ); + for( auto const station : stations ) { + centre.emplace_back( station, 0.5 * ( level( lower, station ) + level( upper, station ) ) ); + } + } + auto const beneath { shifted( lower, -tread_fade ) }; + auto const beyond { shifted( upper, tread_fade ) }; + auto const lowfade { bound( shifted( lower, tread_fade ), centre, true ) }; + auto const highfade { bound( shifted( upper, -tread_fade ), centre, false ) }; std::vector splits { Start, End }; - for( auto const *knots : { &lower, &upper } ) { + for( auto const *knots : { &lower, &upper, &lowfade, &highfade, &beneath, &beyond } ) { for( auto const edge : { low, high } ) { for( std::size_t i = 1; i < knots->size(); ++i ) { auto const &a { ( *knots )[ i - 1 ] }; @@ -1659,22 +1710,41 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i } } std::sort( splits.begin(), splits.end() ); - auto const inside { bound( Extents, upper, true ) }; for( std::size_t i = 1; i < splits.size(); ++i ) { - auto const middle { 0.5 * ( splits[ i - 1 ] + splits[ i ] ) }; + auto const a { splits[ i - 1 ] }; + auto const b { splits[ i ] }; + auto const middle { 0.5 * ( a + b ) }; auto const from { level( lower, middle ) }; auto const to { level( upper, middle ) }; - if( from > low ) { + auto const inner { level( lowfade, middle ) }; + auto const outer { level( highfade, middle ) }; + if( from - tread_fade > low ) { current = null_handle; - piece( splits[ i - 1 ], splits[ i ], Offsets, bound( Extents, lower, true ), false, false, top, true, Inner ); + piece( a, b, Offsets, bound( Extents, beneath, true ), false, false, top, true, Inner ); } - if( to > low && from < high ) { + if( from > low && from - tread_fade < high ) { + current = owntint; + piece( a, b, Offsets, bound( Extents, lower, true ), false, false, top, false, bound( Inner, beneath, false ), lower, false, 0.f, -1.f, tread_fade ); + } + if( inner > low && from < high ) { + current = sharedtint; + piece( a, b, Offsets, bound( Extents, lowfade, true ), false, false, top, false, bound( Inner, lower, false ), lower, false, 0.f, -1.f, -tread_fade ); + } + if( outer > low && inner < high ) { current = Span.material; - piece( splits[ i - 1 ], splits[ i ], Offsets, inside, false, false, top, true, bound( Inner, lower, false ) ); + piece( a, b, Offsets, bound( Extents, highfade, true ), false, false, top, true, bound( Inner, lowfade, false ) ); } - if( to < high ) { + if( to > low && outer < high ) { + current = sharedtint; + piece( a, b, Offsets, bound( Extents, upper, true ), false, false, top, false, bound( Inner, highfade, false ), upper, false, 0.f, -1.f, tread_fade ); + } + if( to + tread_fade > low && to < high ) { + current = owntint; + piece( a, b, Offsets, bound( Extents, beyond, true ), false, false, top, false, bound( Inner, upper, false ), upper, false, 0.f, -1.f, -tread_fade ); + } + if( to + tread_fade < high ) { current = null_handle; - piece( splits[ i - 1 ], splits[ i ], Offsets, Extents, false, false, top, true, bound( Inner, upper, false ) ); + piece( a, b, Offsets, Extents, false, false, top, true, bound( Inner, beyond, false ) ); } } current = null_handle; diff --git a/world/TrackRails.h b/world/TrackRails.h index 10b547b5..01362933 100644 --- a/world/TrackRails.h +++ b/world/TrackRails.h @@ -15,6 +15,7 @@ namespace track_rails { double constexpr flangeway { 0.045 }; double constexpr blend { 0.015 }; +double constexpr tread_fade { 0.02 }; double constexpr rolled_band { 0.1 }; double constexpr nose_tip { 0.012 }; double constexpr middle_angle { 0.35 }; @@ -30,6 +31,8 @@ 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 ); +glm::vec3 tread_colour( material_handle const Material, glm::vec2 const &Band ); +material_handle tint_material( material_handle const Material, glm::vec3 const &Colour ); // 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 );