mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-24 00:06:13 +02:00
feat(track): rails and ballast blended into those of the tracks joined, where their textures differ
This commit is contained in:
@@ -96,6 +96,7 @@ set(SOURCES
|
||||
"utilities/Timer.cpp"
|
||||
"world/Track.cpp"
|
||||
"world/TrackSleepers.cpp"
|
||||
"world/TrackBlend.cpp"
|
||||
"world/SleeperPaths.cpp"
|
||||
"world/SleeperFeatures.cpp"
|
||||
"world/SleeperLines.cpp"
|
||||
|
||||
@@ -1797,7 +1797,7 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
void copy_track_style(TTrack const &Track);
|
||||
std::size_t paste_track_style(std::vector<TTrack *> const &Tracks);
|
||||
// which groups of the properties are pasted or put on the tracks by a preset, in the order of the groups of editormodetrackprops.cpp
|
||||
using track_groups = std::array<bool, 11>;
|
||||
using track_groups = std::array<bool, 12>;
|
||||
// the values of the properties of the chosen groups put on the tracks, in one step of the undo. returns: the number of the
|
||||
// tracks changed, none when no value is of the chosen groups
|
||||
std::optional<std::size_t> apply_track_values(std::vector<std::pair<std::string, std::string>> const &Values, track_groups const &Groups, std::vector<TTrack *> const &Tracks, char const *What);
|
||||
@@ -1850,8 +1850,8 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
{
|
||||
std::string from;
|
||||
std::vector<std::pair<std::string, std::string>> values;
|
||||
// speed, environment, overhead line, track circuits, rails, trackbed, its shape, sleepers, ride and sound, frogs, sleeper placement
|
||||
track_groups groups{false, false, false, false, true, true, true, true, false, true, true};
|
||||
// speed, environment, overhead line, track circuits, rails, trackbed, its shape, sleepers, ride and sound, frogs, sleeper placement, texture blending
|
||||
track_groups groups{false, false, false, false, true, true, true, true, false, true, true, true};
|
||||
bool open{false};
|
||||
bool picking{false}; // the next click on a track in the view copies its properties
|
||||
};
|
||||
|
||||
@@ -226,12 +226,13 @@ enum group : int
|
||||
ride_sound,
|
||||
frogs,
|
||||
sleeper_placement, // spacing and shift of the sleepers and of the trackbed under them, apart from their models
|
||||
blending, // how far from the ends the textures blend into those of the tracks joined there
|
||||
group_count
|
||||
};
|
||||
|
||||
char const *const kGroupLabels[group_count] = {"Speed", "Environment", "Overhead line", "Track circuits", "Rails", "Trackbed", "Shape of the trackbed", "Sleepers", "Ride and sound", "Frog and check rails", "Sleeper placement"};
|
||||
char const *const kGroupLabels[group_count] = {"Speed", "Environment", "Overhead line", "Track circuits", "Rails", "Trackbed", "Shape of the trackbed", "Sleepers", "Ride and sound", "Frog and check rails", "Sleeper placement", "Texture blending"};
|
||||
// the groups as the presets keep them in their file, by names which don't change with the order of the groups
|
||||
char const *const kGroupKeys[group_count] = {"speed", "environment", "overhead", "circuits", "rails", "trackbed", "bedshape", "sleepers", "ride", "frogs", "sleeperplacement"};
|
||||
char const *const kGroupKeys[group_count] = {"speed", "environment", "overhead", "circuits", "rails", "trackbed", "bedshape", "sleepers", "ride", "frogs", "sleeperplacement", "blending"};
|
||||
|
||||
struct track_property
|
||||
{
|
||||
@@ -504,6 +505,30 @@ std::vector<track_property> make_properties()
|
||||
blades("bladeheel", "Blade heel (m)", "Where the moving blades end, from the start of the switch, as the template of the switch gives it.\n0: as long as the flangeway of 58 mm behind them needs", &TSwitchExtension::m_bladeheel);
|
||||
blades("bladethrow", "Blade throw (m)", "How far the tips of the blades move over between the two paths.\n0: the usual 0.160 m", &TSwitchExtension::m_bladethrow);
|
||||
}
|
||||
{
|
||||
// the ends of a plain track are p1 and p2, a switch has p4 as the end of its second path
|
||||
char const *const ends[][3] = {{"blend1", "Texture blending at p1 (m)", "How far from the end p1 the rails and the ballast blend into those of the track joined there, where their textures differ.\nThe ballast comes in as stones mixed together, the rails fade into each other; 0: not blended"},
|
||||
{"blend2", "Texture blending at p2 (m)", "How far from the end p2 the rails and the ballast blend into those of the track joined there, where their textures differ.\nThe ballast comes in as stones mixed together, the rails fade into each other; 0: not blended"},
|
||||
{"blend3", "Texture blending at p4 (m)", "How far from the end p4 of the switch its trackbed blends into the ballast of the track joined there, where their textures differ; 0: not blended"}};
|
||||
for (int end = 0; end < 3; ++end)
|
||||
{
|
||||
auto &property{add(list, ends[end][0], field::number, section::look, group::blending, ends[end][1], ends[end][2])};
|
||||
property.rebuild = true;
|
||||
property.more = true;
|
||||
property.applies = [end](TTrack const &Track) { return Track.m_visible && (Track.iCategoryFlag & 15) == kRailCategory && (Track.eType == tt_Switch || (Track.eType == tt_Normal && end < 2)); };
|
||||
property.get = [end](TTrack const &Track) { return decimal(Track.m_blend[end], 2); };
|
||||
property.set = [end](TTrack &Track, std::string const &Text) {
|
||||
double length;
|
||||
if (false == parse_number(Text, length))
|
||||
return false;
|
||||
auto const clamped{static_cast<float>(std::max(0.0, length))};
|
||||
if (decimal(clamped, 2) == decimal(Track.m_blend[end], 2))
|
||||
return false;
|
||||
Track.m_blend[end] = clamped;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
add_float(list, "texlength", section::look_bed, group::trackbed_shape, "Texture length (m)", "Length after which the texture of the rails and the trackbed repeats", &TTrack::fTexLength, 2, 0.01f, true);
|
||||
{
|
||||
auto &property{add(list, "bedheight", field::number, section::look_bed, group::trackbed_shape, "Trackbed height (m)", "Height of the trackbed edge below the rail top")};
|
||||
@@ -1466,7 +1491,7 @@ void editor_mode::render_track_presets(std::vector<TTrack *> const &Tracks)
|
||||
{
|
||||
// rails, trackbed, its shape, sleepers, frogs and sleeper placement at first, the look of the track
|
||||
std::array<bool, group_count> groups{};
|
||||
for (auto const group : {group::rails, group::trackbed, group::trackbed_shape, group::sleepers, group::frogs, group::sleeper_placement})
|
||||
for (auto const group : {group::rails, group::trackbed, group::trackbed_shape, group::sleepers, group::frogs, group::sleeper_placement, group::blending})
|
||||
groups[group] = true;
|
||||
panel.selected = EditorTrackPresets.create(STR_C("New preset"), name_of(*Tracks.front()), values_of_track(*Tracks.front()), group_keys(groups));
|
||||
panel.nameid = 0;
|
||||
@@ -1855,7 +1880,7 @@ editor_mode::selftest_result editor_mode::selftest_props(std::string const &Comm
|
||||
}
|
||||
else if (action == "groups")
|
||||
{
|
||||
// the groups pasted, as their numbers: 0 speed ... 9 frogs, 10 sleeper placement
|
||||
// the groups pasted, as their numbers: 0 speed ... 9 frogs, 10 sleeper placement, 11 texture blending
|
||||
m_track_style.groups.fill(false);
|
||||
for (int group; Words >> group;)
|
||||
if (group >= 0 && group < group_count)
|
||||
|
||||
@@ -157,6 +157,7 @@ editor_track::state editor_track::capture(TTrack const &Track)
|
||||
result.sleeper_ballast = Track.m_sleeper_ballast_z;
|
||||
result.timber_model = Track.m_timber_model_name;
|
||||
result.timber_skin = Track.m_timber_skin_name;
|
||||
result.blend = Track.m_blend;
|
||||
result.rail_profile = Track.m_profile1;
|
||||
for (int i = 0; i < 6; ++i)
|
||||
result.events[i] = events(const_cast<TTrack &>(Track), i);
|
||||
@@ -262,6 +263,7 @@ void editor_track::apply(TTrack &Track, state const &State)
|
||||
Track.m_sleeper_ballast_z = State.sleeper_ballast;
|
||||
Track.m_timber_model_name = State.timber_model;
|
||||
Track.m_timber_skin_name = State.timber_skin;
|
||||
Track.m_blend = State.blend;
|
||||
Track.m_profile1 = State.rail_profile;
|
||||
for (int i = 0; i < 6; ++i)
|
||||
events(Track, i) = State.events[i];
|
||||
@@ -3033,6 +3035,9 @@ void write_ending(std::ostream &Text, TTrack const &Style)
|
||||
Text << "sleepermodel " << Style.m_sleeper_frequency << ' ' << Style.m_sleeper_model_name << ' ' << (Style.m_sleeper_skin_name.empty() ? std::string{"none"} : Style.m_sleeper_skin_name) << ' ' << Style.m_sleeper_offset.x << ' ' << Style.m_sleeper_offset.y << ' ' << Style.m_sleeper_offset.z << ' ' << Style.m_sleeper_ballast_z << ' ';
|
||||
if (false == Style.m_timber_model_name.empty())
|
||||
Text << "timbermodel " << Style.m_timber_model_name << ' ' << (Style.m_timber_skin_name.empty() ? std::string{"none"} : Style.m_timber_skin_name) << ' ';
|
||||
for (std::size_t end = 0; end < Style.m_blend.size(); ++end)
|
||||
if (Style.m_blend[end] != track_blend::default_length)
|
||||
Text << "blend " << end + 1 << ' ' << Style.m_blend[end] << ' ';
|
||||
Text << "endtrack\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ http:
|
||||
#include "editor/editorAlignment.hpp"
|
||||
#include "editor/editorSwitchDesign.hpp"
|
||||
#include "utilities/Classes.h"
|
||||
#include "world/TrackBlend.h"
|
||||
|
||||
class TTrack;
|
||||
class basic_event;
|
||||
@@ -70,6 +71,7 @@ class editor_track
|
||||
float sleeper_ballast{0.f};
|
||||
std::string timber_model;
|
||||
std::string timber_skin;
|
||||
std::array<float, 3> blend{track_blend::default_length, track_blend::default_length, track_blend::default_length};
|
||||
std::pair<std::string, int> rail_profile;
|
||||
std::array<std::vector<std::pair<std::string, basic_event *>>, 6> events;
|
||||
std::string name;
|
||||
|
||||
@@ -10255,3 +10255,26 @@ msgid_plural "Applies the preset on %d selected tracks"
|
||||
msgstr[0] "Stosuje zestaw na %d zaznaczonym torze"
|
||||
msgstr[1] "Stosuje zestaw na %d zaznaczonych torach"
|
||||
msgstr[2] "Stosuje zestaw na %d zaznaczonych torach"
|
||||
|
||||
# scenery editor: texture blending of the tracks
|
||||
|
||||
msgid "Texture blending"
|
||||
msgstr "Mieszanie tekstur"
|
||||
|
||||
msgid "Texture blending at p1 (m)"
|
||||
msgstr "Mieszanie tekstur przy p1 (m)"
|
||||
|
||||
msgid "Texture blending at p2 (m)"
|
||||
msgstr "Mieszanie tekstur przy p2 (m)"
|
||||
|
||||
msgid "Texture blending at p4 (m)"
|
||||
msgstr "Mieszanie tekstur przy p4 (m)"
|
||||
|
||||
msgid "How far from the end p1 the rails and the ballast blend into those of the track joined there, where their textures differ.\nThe ballast comes in as stones mixed together, the rails fade into each other; 0: not blended"
|
||||
msgstr "Na jakiej długości od końca p1 szyny i podsypka przechodzą w te z toru tam dołączonego, gdy ich tekstury są różne.\nPodsypka miesza się kamieniami, szyny przenikają się płynnie; 0: bez mieszania"
|
||||
|
||||
msgid "How far from the end p2 the rails and the ballast blend into those of the track joined there, where their textures differ.\nThe ballast comes in as stones mixed together, the rails fade into each other; 0: not blended"
|
||||
msgstr "Na jakiej długości od końca p2 szyny i podsypka przechodzą w te z toru tam dołączonego, gdy ich tekstury są różne.\nPodsypka miesza się kamieniami, szyny przenikają się płynnie; 0: bez mieszania"
|
||||
|
||||
msgid "How far from the end p4 of the switch its trackbed blends into the ballast of the track joined there, where their textures differ; 0: not blended"
|
||||
msgstr "Na jakiej długości od końca p4 rozjazdu jego podsypka przechodzi w podsypkę toru tam dołączonego, gdy ich tekstury są różne; 0: bez mieszania"
|
||||
|
||||
@@ -756,6 +756,90 @@ material_manager::create_tinted( material_handle const Material, glm::vec3 const
|
||||
return tintedhandle;
|
||||
}
|
||||
|
||||
bool
|
||||
material_manager::blendable( material_handle const Material ) {
|
||||
|
||||
if( Material == null_handle || Material >= m_materials.size() ) {
|
||||
return false;
|
||||
}
|
||||
if( auto const lookup { m_blendables.find( Material ) }; lookup != m_blendables.end() ) {
|
||||
return lookup->second;
|
||||
}
|
||||
auto result { false };
|
||||
auto const &material { m_materials[ Material ] };
|
||||
if( material.is_good
|
||||
&& material.shader
|
||||
&& material.texture_variants.empty()
|
||||
&& false == material.update_on_weather_change
|
||||
&& false == material.update_on_season_change
|
||||
&& material.shader->texture_conf.size() == 1
|
||||
&& material.shader->texture_conf.count( "diffuse" ) == 1
|
||||
&& material.textures[ material.shader->texture_conf.at( "diffuse" ).id ] != null_handle ) {
|
||||
try {
|
||||
// the parameters of the material go to the blending one as they are, so they have to be in the same places in both
|
||||
auto const blend { GfxRenderer->Fetch_Shader( "trackblend" ) };
|
||||
result = std::all_of(
|
||||
std::begin( material.shader->param_conf ), std::end( material.shader->param_conf ),
|
||||
[&]( auto const &Entry ) {
|
||||
auto const found { blend->param_conf.find( Entry.first ) };
|
||||
return found != blend->param_conf.end()
|
||||
&& found->second.location == Entry.second.location
|
||||
&& found->second.offset == Entry.second.offset
|
||||
&& found->second.size == Entry.second.size; } );
|
||||
}
|
||||
catch( gl::shader_exception const &e ) {
|
||||
ErrorLog( "invalid shader: " + std::string( e.what() ) );
|
||||
}
|
||||
}
|
||||
m_blendables.emplace( Material, result );
|
||||
return result;
|
||||
}
|
||||
|
||||
material_handle
|
||||
material_manager::create_blend( material_handle const Material, std::array<material_handle, 3> const &Others, bool const Grain ) {
|
||||
|
||||
if( false == blendable( Material ) ) {
|
||||
return Material;
|
||||
}
|
||||
auto const key { std::make_tuple( Material, Others[ 0 ], Others[ 1 ], Others[ 2 ], Grain ) };
|
||||
if( auto const lookup { m_blends.find( key ) }; lookup != m_blends.end() ) {
|
||||
return lookup->second;
|
||||
}
|
||||
auto const diffuse_of = [&]( material_handle const Source ) -> texture_handle {
|
||||
if( false == blendable( Source ) ) {
|
||||
return null_handle;
|
||||
}
|
||||
auto const &source { m_materials[ Source ] };
|
||||
return source.textures[ source.shader->texture_conf.at( "diffuse" ).id ];
|
||||
};
|
||||
auto blend { variant_of( m_materials[ Material ], Grain ? ":blendgrain" : ":blend" ) };
|
||||
auto result { Material };
|
||||
try {
|
||||
blend.shader = GfxRenderer->Fetch_Shader( Grain ? "trackblend_grain" : "trackblend" );
|
||||
blend.texture_variants.clear();
|
||||
blend.textures.fill( null_handle );
|
||||
auto const own { diffuse_of( Material ) };
|
||||
blend.textures[ 0 ] = own;
|
||||
for( std::size_t other = 0; other < Others.size(); ++other ) {
|
||||
// the slots of the ends nothing is blended at take the own texture, the shader samples them only with a weight
|
||||
auto const texture { diffuse_of( Others[ other ] ) };
|
||||
blend.textures[ 1 + other ] = ( texture != null_handle ? texture : own );
|
||||
}
|
||||
// the parameters taken from the bindings again, by the shader
|
||||
blend.params_state.clear();
|
||||
blend.finalize( true );
|
||||
if( blend.is_good ) {
|
||||
result = static_cast<material_handle>( m_materials.size() );
|
||||
m_materials.emplace_back( std::move( blend ) );
|
||||
}
|
||||
}
|
||||
catch( gl::shader_exception const &e ) {
|
||||
ErrorLog( "invalid shader: " + std::string( e.what() ) );
|
||||
}
|
||||
m_blends.emplace( key, result );
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
material_manager::on_weather_change() {
|
||||
|
||||
|
||||
@@ -147,6 +147,14 @@ public:
|
||||
create_matte( material_handle const Material );
|
||||
material_handle
|
||||
create_tinted( material_handle const Material, glm::vec3 const &Colour );
|
||||
// the material of a track blending its texture with those of up to three others (null: none) by the weights in y, z and w of
|
||||
// the user data of the vertices (mat_trackblend), as grains with Grain. returns: handle to it, made once, or the material
|
||||
// itself where it can't be blended
|
||||
material_handle
|
||||
create_blend( material_handle const Material, std::array<material_handle, 3> const &Others, bool const Grain );
|
||||
// whether the material can be blended so: one diffuse texture, lit as mat_default, not changing with the weather and seasons
|
||||
bool
|
||||
blendable( material_handle const Material );
|
||||
opengl_material const &
|
||||
material( material_handle const Material ) const { return m_materials[ Material ]; }
|
||||
opengl_material &
|
||||
@@ -177,6 +185,8 @@ private:
|
||||
index_map m_materialmappings;
|
||||
std::unordered_map<material_handle, material_handle> m_mattes;
|
||||
std::map<std::tuple<material_handle, float, float, float>, material_handle> m_tints;
|
||||
std::map<std::tuple<material_handle, material_handle, material_handle, material_handle, bool>, material_handle> m_blends;
|
||||
std::unordered_map<material_handle, bool> m_blendables;
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@@ -2850,6 +2850,16 @@ material_handle opengl46_renderer::Tinted_Material(material_handle const Materia
|
||||
return m_materials.create_tinted(Material, Colour);
|
||||
}
|
||||
|
||||
material_handle opengl46_renderer::Blend_Material(material_handle const Material, std::array<material_handle, 3> const &Others, bool const Grain)
|
||||
{
|
||||
return m_materials.create_blend(Material, Others, Grain);
|
||||
}
|
||||
|
||||
bool opengl46_renderer::Blendable_Material(material_handle const Material)
|
||||
{
|
||||
return m_materials.blendable(Material);
|
||||
}
|
||||
|
||||
std::shared_ptr<gl::program> opengl46_renderer::Fetch_Shader(const std::string &name)
|
||||
{
|
||||
auto it = m_shaders.find(name);
|
||||
@@ -6611,13 +6621,13 @@ void opengl46_renderer::Render(scene::basic_cell::path_sequence::const_iterator
|
||||
// track with light level of its own is drawn on its own
|
||||
flush_batch();
|
||||
setup_environment_light(track->eEnvironment);
|
||||
Bind_Material(track->m_material1);
|
||||
Bind_Material(track->m_blendmaterial1 != null_handle ? track->m_blendmaterial1 : track->m_material1);
|
||||
draw(std::begin(track->Geometry1), std::end(track->Geometry1));
|
||||
// restore default lighting
|
||||
setup_environment_light();
|
||||
break;
|
||||
}
|
||||
if (false == begin_batch(track->m_material1))
|
||||
if (false == begin_batch(track->m_blendmaterial1 != null_handle ? track->m_blendmaterial1 : track->m_material1))
|
||||
{
|
||||
// the track is drawn with the call of its predecessor
|
||||
--m_renderpass.draw_stats.drawcalls;
|
||||
@@ -6680,13 +6690,13 @@ void opengl46_renderer::Render(scene::basic_cell::path_sequence::const_iterator
|
||||
{
|
||||
flush_batch();
|
||||
setup_environment_light(track->eEnvironment);
|
||||
Bind_Material(track->m_material2);
|
||||
Bind_Material(track->m_blendmaterial2 != null_handle ? track->m_blendmaterial2 : track->m_material2);
|
||||
draw(std::begin(track->Geometry2), std::end(track->Geometry2));
|
||||
// restore default lighting
|
||||
setup_environment_light();
|
||||
break;
|
||||
}
|
||||
begin_batch(track->m_material2);
|
||||
begin_batch(track->m_blendmaterial2 != null_handle ? track->m_blendmaterial2 : track->m_material2);
|
||||
m_pathbatch.insert(std::end(m_pathbatch), std::begin(track->Geometry2), std::end(track->Geometry2));
|
||||
break;
|
||||
}
|
||||
@@ -6747,13 +6757,13 @@ void opengl46_renderer::Render(scene::basic_cell::path_sequence::const_iterator
|
||||
{
|
||||
flush_batch();
|
||||
setup_environment_light(track->eEnvironment);
|
||||
Bind_Material(track->SwitchExtension->m_material3);
|
||||
Bind_Material(track->m_blendmaterial3 != null_handle ? track->m_blendmaterial3 : track->SwitchExtension->m_material3);
|
||||
draw(track->SwitchExtension->Geometry3);
|
||||
// restore default lighting
|
||||
setup_environment_light();
|
||||
break;
|
||||
}
|
||||
begin_batch(track->SwitchExtension->m_material3);
|
||||
begin_batch(track->m_blendmaterial3 != null_handle ? track->m_blendmaterial3 : track->SwitchExtension->m_material3);
|
||||
m_pathbatch.emplace_back(track->SwitchExtension->Geometry3);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -101,6 +101,10 @@ class opengl46_renderer : public gfx_renderer {
|
||||
Matte_Material( material_handle const Material ) override;
|
||||
material_handle
|
||||
Tinted_Material( material_handle const Material, glm::vec3 const &Colour ) override;
|
||||
material_handle
|
||||
Blend_Material( material_handle const Material, std::array<material_handle, 3> const &Others, bool const Grain ) override;
|
||||
bool
|
||||
Blendable_Material( material_handle const Material ) override;
|
||||
void
|
||||
Bind_Material( material_handle const Material, TSubModel const *sm = nullptr, lighting_data const *lighting = nullptr ) override;
|
||||
IMaterial const *
|
||||
|
||||
@@ -111,6 +111,11 @@ public:
|
||||
// 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; }
|
||||
// the material of a track blending its texture with those of up to three others (null: none) by the weights in y, z and w of the user data
|
||||
// of the vertices, the others as grains with Grain (world/TrackBlend.h). backends which can't make one give back the material itself
|
||||
virtual auto Blend_Material( material_handle const Material, std::array<material_handle, 3> const &Others, bool const Grain ) -> material_handle { return Material; }
|
||||
// whether a material can be blended so
|
||||
virtual auto Blendable_Material( material_handle const Material ) -> bool { return false; }
|
||||
// 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.
|
||||
|
||||
2
shaders/mat_trackblend.frag
Normal file
2
shaders/mat_trackblend.frag
Normal file
@@ -0,0 +1,2 @@
|
||||
// the rails of a track faded into those of the tracks joined at its ends, see trackblend.glsl
|
||||
#include <trackblend.glsl>
|
||||
3
shaders/mat_trackblend_grain.frag
Normal file
3
shaders/mat_trackblend_grain.frag
Normal file
@@ -0,0 +1,3 @@
|
||||
// the ballast of a track mixed in grains into that of the tracks joined at its ends, see trackblend.glsl
|
||||
#define TRACKBLEND_GRAIN 1
|
||||
#include <trackblend.glsl>
|
||||
144
shaders/trackblend.glsl
Normal file
144
shaders/trackblend.glsl
Normal file
@@ -0,0 +1,144 @@
|
||||
// a track drawn with the textures of the tracks joined at its ends blended in near them (world/TrackBlend.h): the weights of the
|
||||
// tracks joined at the ends p1, p2 and p4 of a switch in UserData.yzw, their textures in diffuse1..3, lit as mat_default.
|
||||
// TRACKBLEND_GRAIN: the others come in as grains, as the stones of two ballasts would mix, rather than faded in
|
||||
in vec3 f_normal;
|
||||
in vec2 f_coord;
|
||||
in vec4 f_pos;
|
||||
|
||||
in vec4 f_clip_pos;
|
||||
in vec4 f_clip_future_pos;
|
||||
|
||||
#include <common>
|
||||
|
||||
#param (color, 0, 0, 4, diffuse)
|
||||
#param (diffuse, 1, 0, 1, diffuse)
|
||||
#param (specular, 1, 1, 1, specular)
|
||||
#param (reflection, 1, 2, 1, zero)
|
||||
#param (glossiness, 1, 3, 1, glossiness)
|
||||
|
||||
#texture (diffuse, 0, sRGB_A)
|
||||
uniform sampler2D diffuse;
|
||||
#texture (diffuse1, 1, sRGB_A)
|
||||
uniform sampler2D diffuse1;
|
||||
#texture (diffuse2, 2, sRGB_A)
|
||||
uniform sampler2D diffuse2;
|
||||
#texture (diffuse3, 3, sRGB_A)
|
||||
uniform sampler2D diffuse3;
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
#if MOTIONBLUR_ENABLED
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
#endif
|
||||
|
||||
#include <light_common.glsl>
|
||||
#include <apply_fog.glsl>
|
||||
#include <tonemapping.glsl>
|
||||
|
||||
#if TRACKBLEND_GRAIN
|
||||
// grains per repeat of the texture: a ballast texture repeats every few metres, a grain is a handful of its stones
|
||||
const float GRAIN_CELLS = 28.0;
|
||||
|
||||
vec2 grain_hash2(vec2 p)
|
||||
{
|
||||
vec3 q = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973));
|
||||
q += dot(q, q.yzx + 33.33);
|
||||
return fract((q.xx + q.yz) * q.zy);
|
||||
}
|
||||
|
||||
float grain_hash1(vec2 p)
|
||||
{
|
||||
vec3 q = fract(vec3(p.xyx) * 0.1031);
|
||||
q += dot(q, q.yzx + 33.33);
|
||||
return fract((q.x + q.y) * q.z);
|
||||
}
|
||||
|
||||
// the value of the grain the point lies in, 0..1: cells around points jittered on a grid, each of one value
|
||||
float grain_value(vec2 p)
|
||||
{
|
||||
vec2 cell = floor(p);
|
||||
vec2 inside = fract(p);
|
||||
float nearest = 8.0;
|
||||
float value = 0.0;
|
||||
for (int y = -1; y <= 1; ++y)
|
||||
{
|
||||
for (int x = -1; x <= 1; ++x)
|
||||
{
|
||||
vec2 offset = vec2(float(x), float(y));
|
||||
vec2 r = offset + grain_hash2(cell + offset) - inside;
|
||||
float d = dot(r, r);
|
||||
if (d < nearest)
|
||||
{
|
||||
nearest = d;
|
||||
value = grain_hash1(cell + offset + 17.31);
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
#endif
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 weights = clamp(UserData.yzw, 0.0, 1.0);
|
||||
float total = weights.x + weights.y + weights.z;
|
||||
if (total > 1.0)
|
||||
{
|
||||
weights /= total;
|
||||
total = 1.0;
|
||||
}
|
||||
// the gradients are taken outside the branches the textures are sampled in
|
||||
vec2 dx = dFdx(f_coord);
|
||||
vec2 dy = dFdy(f_coord);
|
||||
#if TRACKBLEND_GRAIN
|
||||
vec2 grainpos = f_coord * GRAIN_CELLS;
|
||||
// grains smaller than a pixel would shimmer, there the textures fade into each other instead
|
||||
float footprint = max(length(dx), length(dy)) * GRAIN_CELLS;
|
||||
float grained = 1.0 - smoothstep(0.35, 0.9, footprint);
|
||||
#endif
|
||||
vec4 tex_color = textureGrad(diffuse, f_coord, dx, dy);
|
||||
if (total > 0.0)
|
||||
{
|
||||
#if TRACKBLEND_GRAIN
|
||||
// the grain is of the track joined at the end whose share its value falls in, of the track itself past them
|
||||
float pick = grain_value(grainpos);
|
||||
float first = weights.x;
|
||||
float second = weights.x + weights.y;
|
||||
vec3 grain = vec3(float(pick < first), float(pick >= first && pick < second), float(pick >= second && pick < total));
|
||||
weights = mix(weights, grain, grained);
|
||||
total = weights.x + weights.y + weights.z;
|
||||
#endif
|
||||
vec4 blended = tex_color * (1.0 - total);
|
||||
if (weights.x > 0.0)
|
||||
blended += textureGrad(diffuse1, f_coord, dx, dy) * weights.x;
|
||||
if (weights.y > 0.0)
|
||||
blended += textureGrad(diffuse2, f_coord, dx, dy) * weights.y;
|
||||
if (weights.z > 0.0)
|
||||
blended += textureGrad(diffuse3, f_coord, dx, dy) * weights.z;
|
||||
tex_color = blended;
|
||||
}
|
||||
|
||||
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
|
||||
if(alphatestfail)
|
||||
discard;
|
||||
|
||||
vec3 fragcolor = ambient;
|
||||
vec3 fragnormal = normalize(f_normal);
|
||||
matte = true;
|
||||
|
||||
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, 0.0, 0.0, shadow_tone);
|
||||
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
|
||||
#if POSTFX_ENABLED
|
||||
out_color = color;
|
||||
#else
|
||||
out_color = tonemap(color);
|
||||
#endif
|
||||
|
||||
#if MOTIONBLUR_ENABLED
|
||||
{
|
||||
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;
|
||||
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;
|
||||
|
||||
out_motion = vec4(a - b, 0.0f, tex_color.a * alpha_mult);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "world/TrackRails.h"
|
||||
#include "world/TrackBlend.h"
|
||||
#include "rendering/renderer.h"
|
||||
|
||||
namespace track_rails {
|
||||
@@ -213,7 +214,9 @@ reused_chunks::~reused_chunks() {
|
||||
reused = m_previous;
|
||||
}
|
||||
|
||||
gfx::geometry_handle insert_chunk( gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Bank, int const Type ) {
|
||||
namespace {
|
||||
|
||||
gfx::geometry_handle insert_weighed( gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Bank, int const Type ) {
|
||||
|
||||
if( reused == nullptr ) {
|
||||
return GfxRenderer->Insert( Vertices, Userdata, Bank, Type );
|
||||
@@ -237,6 +240,19 @@ gfx::geometry_handle insert_chunk( gfx::vertex_array &Vertices, gfx::userdata_ar
|
||||
return handle;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
gfx::geometry_handle insert_chunk( gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Bank, int const Type ) {
|
||||
|
||||
if( auto const *blending { track_blend::weighed() }; blending != nullptr ) {
|
||||
// the weights of the textures the track blends into near its ends; the user data given are the caller's own, often shared
|
||||
auto weighed { Userdata };
|
||||
blending->weigh( Vertices, weighed );
|
||||
return insert_weighed( Vertices, weighed, Bank, Type );
|
||||
}
|
||||
return insert_weighed( Vertices, Userdata, Bank, Type );
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -701,6 +701,18 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin)
|
||||
m_timber_model_name = modelpath;
|
||||
m_timber_skin_name = ( skinpath == "none" ? std::string{} : skinpath );
|
||||
}
|
||||
else if( str == "blend" ) {
|
||||
// blend <end> <length>: how far from the end 1 (p1), 2 (p2) or 3 (p4 of a switch) the rails and the ballast blend into those
|
||||
// of the track joined there, where their textures differ; 0: not at all
|
||||
auto const end { parser->getToken<int>() };
|
||||
auto const length { parser->getToken<double>() };
|
||||
if( end >= 1 && end <= static_cast<int>( m_blend.size() ) ) {
|
||||
m_blend[ end - 1 ] = static_cast<float>( std::max( 0.0, length ) );
|
||||
}
|
||||
else {
|
||||
ErrorLog( "Bad track: blend at end " + std::to_string( end ) + " of track \"" + m_name + "\" which has no such end" );
|
||||
}
|
||||
}
|
||||
else if( str == "friction" ) {
|
||||
// memory cell holding friction value modifiers
|
||||
m_friction.first = parser->getToken<std::string>();
|
||||
@@ -1546,6 +1558,24 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
|
||||
if( m_sleeper_enabled && false == m_sleepers3d.active && m_sleeper_local_transforms.empty() ) {
|
||||
build_sleeper_transforms();
|
||||
}
|
||||
// the rails and the ballast blended into those of the tracks joined at the ends, where their textures differ. the weights go with
|
||||
// the geometry as it's made, so the materials change only with it; not where the chunks are laid again as a movable nose moves
|
||||
track_blend::blending railsblend;
|
||||
track_blend::blending ballastblend;
|
||||
if( Bank != null_handle && false == movable ) {
|
||||
if( Geometry1.empty() ) {
|
||||
railsblend = track_blend::setup( *this, track_blend::kind::rails );
|
||||
m_blendmaterial1 = railsblend.material;
|
||||
}
|
||||
if( eType == tt_Switch ) {
|
||||
ballastblend = track_blend::setup( *this, track_blend::kind::ballast );
|
||||
m_blendmaterial3 = ballastblend.material;
|
||||
}
|
||||
else if( Geometry2.empty() ) {
|
||||
ballastblend = track_blend::setup( *this, track_blend::kind::ballast );
|
||||
m_blendmaterial2 = ballastblend.material;
|
||||
}
|
||||
}
|
||||
switch (iCategoryFlag & 15)
|
||||
{
|
||||
case 1: // tor
|
||||
@@ -1583,6 +1613,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
|
||||
gfx::vertex_array vertices;
|
||||
Segment->RenderLoft(vertices, m_origin, bpts1, iTrapezoid > 0, texturelength);
|
||||
if( Bank != 0 && true == Geometry2.empty() ) {
|
||||
track_blend::weighing const weighing { &ballastblend };
|
||||
Geometry2.emplace_back(track_rails::insert_chunk(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP));
|
||||
}
|
||||
if( Bank == 0 && false == Geometry2.empty() ) {
|
||||
@@ -1597,6 +1628,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
|
||||
gfx::vertex_array vertices;
|
||||
auto const open { eType == tt_Normal && ( trPrev == nullptr || trNext == nullptr ) };
|
||||
if( Bank != 0 && true == Geometry1.empty() ) {
|
||||
track_blend::weighing const weighing { &railsblend };
|
||||
for( auto const side : { 1, -1 } ) {
|
||||
auto const &profile { side > 0 ? rpts1 : rpts2 };
|
||||
if( eType == tt_Normal && iTrapezoid == 0 && lay_rail( 0, profile, side, texturelength, track_rails::blade_runon( *this, side ), trPrev == nullptr, trNext == nullptr, Geometry1, GeometryCut1 ) ) {
|
||||
@@ -1695,6 +1727,7 @@ void TTrack::create_geometry( gfx::geometrybank_handle const &Bank ) {
|
||||
if( true == Global.CreateSwitchTrackbeds ) {
|
||||
gfx::vertex_array vertices;
|
||||
create_switch_trackbed( vertices );
|
||||
track_blend::weighing const weighing { &ballastblend };
|
||||
SwitchExtension->Geometry3 = track_rails::insert_chunk(vertices, empty_userdata, Bank, GL_TRIANGLE_STRIP);
|
||||
vertices.clear();
|
||||
}
|
||||
@@ -2715,6 +2748,11 @@ TTrack::export_as_text_( std::ostream &Output ) const {
|
||||
<< m_timber_model_name << ' '
|
||||
<< ( m_timber_skin_name.empty() ? std::string{ "none" } : m_timber_skin_name ) << ' ';
|
||||
}
|
||||
for( std::size_t end = 0; end < m_blend.size(); ++end ) {
|
||||
if( m_blend[ end ] != track_blend::default_length ) {
|
||||
Output << "blend " << end + 1 << ' ' << m_blend[ end ] << ' ';
|
||||
}
|
||||
}
|
||||
// footer
|
||||
Output
|
||||
<< "endtrack"
|
||||
|
||||
@@ -21,6 +21,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "world/Segment.h"
|
||||
#include "world/RailLayout.h"
|
||||
#include "world/TrackSleepers.h"
|
||||
#include "world/TrackBlend.h"
|
||||
#include "model/material.h"
|
||||
#include "scene/scenenode.h"
|
||||
#include "utilities/Names.h"
|
||||
@@ -237,6 +238,12 @@ public:
|
||||
// timbermodel <model> <skin>
|
||||
std::string m_timber_model_name;
|
||||
std::string m_timber_skin_name; // replacable skin, empty for the textures of the model
|
||||
// how far from its ends p1, p2 (and p4 of a switch) the rails and the ballast blend into those of the tracks joined there (blend)
|
||||
std::array<float, 3> m_blend { track_blend::default_length, track_blend::default_length, track_blend::default_length };
|
||||
// the materials the blending is drawn with, null where nothing's blended: the rails of a plain track, its ballast, the trackbed of a switch
|
||||
material_handle m_blendmaterial1 { null_handle };
|
||||
material_handle m_blendmaterial2 { null_handle };
|
||||
material_handle m_blendmaterial3 { null_handle };
|
||||
TModel3d *m_sleeper_model { nullptr }; // resolved on init; nullptr means no model / failed to load
|
||||
material_handle m_sleeper_skin { 0 }; // resolved replacable skin handle, 0 = use model defaults
|
||||
// precomputed local-space transforms (relative to m_origin) for every sleeper instance along the path.
|
||||
|
||||
258
world/TrackBlend.cpp
Normal file
258
world/TrackBlend.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
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/TrackBlend.h"
|
||||
#include "world/Track.h"
|
||||
#include "world/TrackSleepers.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "utilities/Globals.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace track_blend {
|
||||
|
||||
namespace {
|
||||
|
||||
// how far aside of the line of an end its weight reaches, past the widest trackbeds
|
||||
double constexpr lateral_reach { 6.0 };
|
||||
|
||||
blending const *current { nullptr };
|
||||
|
||||
int end_count( TTrack const &Track ) {
|
||||
|
||||
switch( Track.eType ) {
|
||||
case tt_Normal: { return 2; }
|
||||
case tt_Switch: { return ( Track.SwitchExtension != nullptr ? 3 : 0 ); }
|
||||
default: { return 0; }
|
||||
}
|
||||
}
|
||||
|
||||
// the segment an end of the track is the start (end 0) or the end of: p1, p2 of the first path, p4 of the second of a switch
|
||||
TSegment const *end_segment( TTrack const &Track, int const End ) {
|
||||
|
||||
if( Track.eType == tt_Switch ) {
|
||||
return ( End == 2 ? Track.SwitchExtension->Segments[ 1 ].get() : Track.SwitchExtension->Segments[ 0 ].get() );
|
||||
}
|
||||
return Track.Segment.get();
|
||||
}
|
||||
|
||||
bool end_frame( TTrack const &Track, int const End, glm::dvec3 &Point, glm::dvec3 &Inward ) {
|
||||
|
||||
auto const *segment { end_segment( Track, End ) };
|
||||
if( segment == nullptr ) {
|
||||
return false;
|
||||
}
|
||||
auto const length { segment->GetLength() };
|
||||
if( length < 0.01 ) {
|
||||
return false;
|
||||
}
|
||||
auto const step { std::clamp( 0.25 / length, 0.0001, 0.5 ) };
|
||||
auto const atstart { End == 0 };
|
||||
Point = ( atstart ? segment->FastGetPoint_0() : segment->FastGetPoint_1() );
|
||||
auto const way { segment->FastGetPoint( atstart ? step : 1.0 - step ) - Point };
|
||||
if( glm::length( way ) < 1e-6 ) {
|
||||
return false;
|
||||
}
|
||||
Inward = glm::normalize( way );
|
||||
return true;
|
||||
}
|
||||
|
||||
TTrack *neighbour( TTrack const &Track, int const End ) {
|
||||
|
||||
if( Track.eType == tt_Switch ) {
|
||||
switch( End ) {
|
||||
case 0: { return Track.SwitchExtension->pPrevs[ 0 ]; }
|
||||
case 1: { return Track.SwitchExtension->pNexts[ 0 ]; }
|
||||
case 2: { return Track.SwitchExtension->pNexts[ 1 ]; }
|
||||
default: { return nullptr; }
|
||||
}
|
||||
}
|
||||
return ( End == 0 ? Track.trPrev : ( End == 1 ? Track.trNext : nullptr ) );
|
||||
}
|
||||
|
||||
// the end of the track the other one is joined at, the nearest to the point of those joined to it; -1 where it isn't joined
|
||||
int end_toward( TTrack const &Track, TTrack const *Other, glm::dvec3 const &Point ) {
|
||||
|
||||
auto result { -1 };
|
||||
auto nearest { std::numeric_limits<double>::max() };
|
||||
for( int end = 0; end < end_count( Track ); ++end ) {
|
||||
auto joined { neighbour( Track, end ) == Other };
|
||||
if( Track.eType == tt_Switch && end == 0 ) {
|
||||
// the second path of a switch starts at p3, its p1 as well
|
||||
joined = joined || Track.SwitchExtension->pPrevs[ 1 ] == Other;
|
||||
}
|
||||
glm::dvec3 point, inward;
|
||||
if( false == joined || false == end_frame( Track, end, point, inward ) ) {
|
||||
continue;
|
||||
}
|
||||
auto const distance { glm::length( point - Point ) };
|
||||
if( distance < nearest ) {
|
||||
nearest = distance;
|
||||
result = end;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// the trackbed of a switch, taken from the plain tracks joined to it where it has none of its own, as create_switch_trackbed() does
|
||||
material_handle switch_trackbed( TTrack const &Switch ) {
|
||||
|
||||
auto material { Switch.SwitchExtension->m_material3 };
|
||||
if( material == null_handle && Switch.trPrev != nullptr && Switch.trPrev->eType == tt_Normal ) {
|
||||
material = Switch.trPrev->m_material2;
|
||||
}
|
||||
if( material == null_handle && Switch.trNext != nullptr && Switch.trNext->eType == tt_Normal ) {
|
||||
material = Switch.trNext->m_material2;
|
||||
}
|
||||
return material;
|
||||
}
|
||||
|
||||
// the material the track draws its rails or its ballast with at the end, null where it has none there
|
||||
material_handle material_of( TTrack const &Track, kind const Kind, int const End ) {
|
||||
|
||||
if( false == Track.m_visible || Track.m_editorremoved || ( Track.iCategoryFlag & 15 ) != 1 ) {
|
||||
return null_handle;
|
||||
}
|
||||
if( Kind == kind::rails ) {
|
||||
switch( Track.eType ) {
|
||||
case tt_Normal: { return Track.m_material1; }
|
||||
case tt_Switch: { return ( End == 2 ? Track.m_material2 : Track.m_material1 ); }
|
||||
default: { return null_handle; }
|
||||
}
|
||||
}
|
||||
// no ballast is drawn under the 3D sleepers laid on the ground alone
|
||||
if( false == Global.Sleepers3DBed && track_sleepers::lays( Track ) ) {
|
||||
return null_handle;
|
||||
}
|
||||
switch( Track.eType ) {
|
||||
case tt_Normal: { return Track.m_material2; }
|
||||
case tt_Switch: { return ( Global.CreateSwitchTrackbeds && Track.SwitchExtension != nullptr ? switch_trackbed( Track ) : null_handle ); }
|
||||
default: { return null_handle; }
|
||||
}
|
||||
}
|
||||
|
||||
// whether the track blends its rails or ballast at the end into those of the track joined there, and what with. the answer is
|
||||
// the same for that track back, but for the lengths and the kinds of tracks which blend: switches don't blend their rails
|
||||
bool blends( TTrack const &Track, kind const Kind, int const End, TTrack const **Other = nullptr, int *Otherend = nullptr, material_handle *Othermaterial = nullptr ) {
|
||||
|
||||
if( End < 0 || End >= end_count( Track ) ) {
|
||||
return false;
|
||||
}
|
||||
if( Kind == kind::rails && Track.eType != tt_Normal ) {
|
||||
return false;
|
||||
}
|
||||
if( Track.m_blend[ End ] <= 0.f ) {
|
||||
return false;
|
||||
}
|
||||
auto const own { material_of( Track, Kind, End ) };
|
||||
if( own == null_handle ) {
|
||||
return false;
|
||||
}
|
||||
auto const *other { neighbour( Track, End ) };
|
||||
glm::dvec3 point, inward;
|
||||
if( other == nullptr || other == &Track || false == end_frame( Track, End, point, inward ) ) {
|
||||
return false;
|
||||
}
|
||||
auto const otherend { end_toward( *other, &Track, point ) };
|
||||
if( otherend < 0 ) {
|
||||
return false;
|
||||
}
|
||||
auto const theirs { material_of( *other, Kind, otherend ) };
|
||||
if( theirs == null_handle || theirs == own ) {
|
||||
return false;
|
||||
}
|
||||
if( false == GfxRenderer->Blendable_Material( own ) || false == GfxRenderer->Blendable_Material( theirs ) ) {
|
||||
return false;
|
||||
}
|
||||
if( Other != nullptr ) { *Other = other; }
|
||||
if( Otherend != nullptr ) { *Otherend = otherend; }
|
||||
if( Othermaterial != nullptr ) { *Othermaterial = theirs; }
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void blending::weigh( gfx::vertex_array const &Vertices, gfx::userdata_array &Userdata ) const {
|
||||
|
||||
Userdata.resize( Vertices.size(), gfx::vertex_userdata { glm::vec4 { 0.f } } );
|
||||
for( std::size_t index = 0; index < Vertices.size(); ++index ) {
|
||||
glm::dvec3 const position { Vertices[ index ].position };
|
||||
auto &data { Userdata[ index ].data };
|
||||
for( int end = 0; end < 3; ++end ) {
|
||||
auto const &blend { ends[ end ] };
|
||||
auto weight { 0.f };
|
||||
if( blend.active ) {
|
||||
// measured along the line of the end, so both tracks give the vertices of their joint the same weight
|
||||
auto const offset { position - blend.point };
|
||||
auto const along { glm::dot( offset, blend.inward ) };
|
||||
auto const aside { glm::length( offset - blend.inward * along ) };
|
||||
if( along < blend.length && aside < lateral_reach ) {
|
||||
weight = blend.peak * static_cast<float>( 1.0 - std::max( 0.0, along ) / blend.length );
|
||||
}
|
||||
}
|
||||
data[ 1 + end ] = weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blending setup( TTrack const &Track, kind const Kind ) {
|
||||
|
||||
blending result;
|
||||
std::array<material_handle, 3> others { null_handle, null_handle, null_handle };
|
||||
auto found { false };
|
||||
for( int end = 0; end < end_count( Track ); ++end ) {
|
||||
TTrack const *other { nullptr };
|
||||
auto otherend { -1 };
|
||||
auto theirs { null_handle };
|
||||
if( false == blends( Track, Kind, end, &other, &otherend, &theirs ) ) {
|
||||
continue;
|
||||
}
|
||||
auto &blend { result.ends[ end ] };
|
||||
end_frame( Track, end, blend.point, blend.inward );
|
||||
blend.point -= Track.m_origin;
|
||||
blend.length = Track.m_blend[ end ];
|
||||
// where both tracks blend they meet half way at the joint, otherwise this one goes all the way to the other one there
|
||||
blend.peak = ( blends( *other, Kind, otherend ) ? 0.5f : 1.f );
|
||||
blend.active = true;
|
||||
others[ end ] = theirs;
|
||||
found = true;
|
||||
}
|
||||
if( false == found ) {
|
||||
return {};
|
||||
}
|
||||
auto const own { material_of( Track, Kind, 0 ) };
|
||||
auto const material { GfxRenderer->Blend_Material( own, others, Kind == kind::ballast ) };
|
||||
if( material == own ) {
|
||||
// the renderer can't blend them
|
||||
return {};
|
||||
}
|
||||
result.material = material;
|
||||
return result;
|
||||
}
|
||||
|
||||
weighing::weighing( blending const *Blending ) :
|
||||
m_previous { current } {
|
||||
|
||||
current = ( Blending != nullptr && Blending->any() ? Blending : nullptr );
|
||||
}
|
||||
|
||||
weighing::~weighing() {
|
||||
|
||||
current = m_previous;
|
||||
}
|
||||
|
||||
blending const *weighed() {
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
} // namespace track_blend
|
||||
70
world/TrackBlend.h
Normal file
70
world/TrackBlend.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "utilities/Classes.h"
|
||||
#include "interfaces/ITexture.h"
|
||||
#include "rendering/geometrybank.h"
|
||||
|
||||
class TTrack;
|
||||
|
||||
// the rails and the ballast of a track blended into those of the tracks joined at its ends, where their textures differ: near
|
||||
// an end the vertices carry the weight of the track joined there (y, z, w of their user data for the ends p1, p2 and p4 of a
|
||||
// switch) and the track is drawn with a material blending its texture with theirs. the ballast comes in as grains, the rails
|
||||
// fade into each other. a plain track blends its rails and its ballast, a switch the trackbed made for it
|
||||
namespace track_blend {
|
||||
|
||||
// how far from an end the textures blend, by default
|
||||
float constexpr default_length { 3.f };
|
||||
|
||||
enum class kind {
|
||||
rails,
|
||||
ballast
|
||||
};
|
||||
|
||||
struct blending {
|
||||
struct end {
|
||||
bool active { false };
|
||||
glm::dvec3 point { 0.0 }; // relative to the origin of the track
|
||||
glm::dvec3 inward { 0.0 }; // unit vector from the end into the track
|
||||
double length { 0.0 };
|
||||
float peak { 0.f }; // the weight at the end: a half where the track joined there blends back, whole where it doesn't
|
||||
};
|
||||
std::array<end, 3> ends;
|
||||
material_handle material { null_handle }; // the material the blending is drawn with, null where nothing's blended
|
||||
|
||||
bool any() const {
|
||||
return material != null_handle; }
|
||||
// the weights of the ends put in the user data of the vertices (relative to the origin of the track), made as long as the
|
||||
// vertices where it's shorter; what the user data hold at x is kept
|
||||
void weigh( gfx::vertex_array const &Vertices, gfx::userdata_array &Userdata ) const;
|
||||
};
|
||||
|
||||
// how the rails or the ballast of the track blend into those of the tracks joined to it, nothing for the tracks which don't
|
||||
blending setup( TTrack const &Track, kind const Kind );
|
||||
|
||||
// while one is alive, the chunks of geometry inserted with track_rails::insert_chunk() get the weights of the blending
|
||||
class weighing {
|
||||
public:
|
||||
explicit weighing( blending const *Blending );
|
||||
~weighing();
|
||||
weighing( weighing const & ) = delete;
|
||||
weighing &operator=( weighing const & ) = delete;
|
||||
private:
|
||||
blending const *m_previous { nullptr };
|
||||
};
|
||||
// the blending the chunks inserted now are weighed by, null when they aren't
|
||||
blending const *weighed();
|
||||
|
||||
} // namespace track_blend
|
||||
Reference in New Issue
Block a user