17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-30 10:50:28 +01:00

feat(materials): texture randomization for loads

* feat(materials): texture randomization for loads
This commit is contained in:
jerrrrycho
2026-09-27 00:09:16 +02:00
committed by GitHub
parent b1bdd42178
commit 8fbb482350
12 changed files with 379 additions and 63 deletions

View File

@@ -187,6 +187,14 @@ bool MaterialAdapterLegacyMatFile::ParseLevel(cParser& parser, int priority) {
else if (key.compare(0, 7, "texture") == 0) {
key.erase(0, 7);
// "textureN_variants:" declares a pool of textures picked per model instance. this renderer
// doesn't support per instance variants yet, so the pool is reduced to a single entry drawn
// when the material is loaded, which is how such texture used to be picked before variants
if (auto const variantmarker{key.find("_variants")};
variantmarker != std::string::npos) {
key.erase(variantmarker, std::string("_variants").size());
}
auto value{deserialize_random_set(parser)};
replace_slashes(value);
auto& [tex_priority, texture] = m_texture_mapping[key];

View File

@@ -627,6 +627,10 @@ void TAnimModel::RaPrepare()
}
}
TSubModel::iInstance = reinterpret_cast<std::uintptr_t>( this ); //żeby nie robić cudzych animacji
// NOTE: scenery models don't pick texture variants of their own, so we clear the seed left by the
// last rendered vehicle. instanced draws share a single material bind anyway, which would make
// per instance variants inconsistent between batched and individually drawn models
TSubModel::iVariantSeed = 0;
TSubModel::pasText = &asText; // przekazanie tekstu do wyświetlacza (!!!! do przemyślenia)
for (auto entry : m_animlist) {

View File

@@ -30,6 +30,7 @@ using namespace Mtable;
float TSubModel::fSquareDist = 0.f;
std::uintptr_t TSubModel::iInstance; // numer renderowanego egzemplarza obiektu
std::uint32_t TSubModel::iVariantSeed = 0; // ziarno losowania wariantów tekstur renderowanego egzemplarza
texture_handle const *TSubModel::ReplacableSkinId = nullptr;
int TSubModel::iAlpha = 0x30300030; // maska do testowania flag tekstur wymiennych
TModel3d *TSubModel::pRoot; // Ra: tymczasowo wskaźnik na model widoczny z submodelu
@@ -2460,6 +2461,120 @@ void TModel3d::LoadFromTextFile(std::string const &FileName, bool dynamic)
}
}
std::uint32_t TSubModel::VariantId(TSubModel const *Submodel)
{ // the seed is already well scattered, so the group is merely added to it. this way branches of one model
// are guaranteed to land on different variants, as long as the pool holds at least as many entries as
// the model has branches -- two containers carried by the same wagon won't come up with the same number
return iVariantSeed + static_cast<std::uint32_t>(Submodel != nullptr ? Submodel->m_variantgroup : 0);
}
namespace
{
// two branches sitting no farther than this from each other are treated as one object.
// generous, a simplified lod mesh doesn't have to sit exactly where the detailed one does
float const variantgrouptolerance{0.25f};
// assigns provided variant group id to the submodel and all of its children
void mark_variant_group(TSubModel *Submodel, int const Group, bool const Includesiblings)
{
while (Submodel != nullptr)
{
Submodel->m_variantgroup = Group;
mark_variant_group(Submodel->ChildGet(), Group, true);
if (false == Includesiblings)
{
break;
}
Submodel = Submodel->NextGet();
}
}
// expands provided bounding area to cover geometry carried by the submodel and all of its children
void accumulate_mesh_bounds(TSubModel const *Submodel, glm::vec3 &Minimum, glm::vec3 &Maximum, bool &Isempty, bool const Includesiblings)
{
while (Submodel != nullptr)
{
if (auto const &vertices{(Submodel->m_geometry.handle != null_handle ? GfxRenderer->Vertices(Submodel->m_geometry.handle) : Submodel->Vertices)};
false == vertices.empty())
{
// the transformation chain can still hold a rotation, so the vertices go through it
// rather than get taken at their face value
float4x4 parentmatrix;
Submodel->ParentMatrix(&parentmatrix);
auto const transformationmatrix{glm::make_mat4(parentmatrix.readArray())};
for (auto const &vertex : vertices)
{
auto const position{glm::vec3{transformationmatrix * glm::vec4{vertex.position, 1}}};
Minimum = glm::min(Minimum, position);
Maximum = glm::max(Maximum, position);
}
Isempty = false;
}
accumulate_mesh_bounds(Submodel->Child, Minimum, Maximum, Isempty, true);
if (false == Includesiblings)
{
break;
}
Submodel = Submodel->Next;
}
}
// returns position of provided branch of the model, relative to the model root
glm::vec3 branch_position(TSubModel const *Branch)
{
// the transformation chain is the cheap way to get there, as it doesn't touch the geometry,
// which keeps this affordable even for models made of thousands of branches...
auto const offset{Branch->offset()};
if (glm::length2(offset) > variantgrouptolerance * variantgrouptolerance)
{
return offset;
}
// ...but it only tells us anything for branches which actually carry a transform. models loaded from
// text format can have theirs baked into the vertices instead, which leaves the whole model piled up
// at the origin and, without the fallback below, all of its branches in a single variant group.
// NOTE: it's the centre of the bounding area we're after, and not the average vertex position offset()
// would give us -- the latter drifts with vertex density, which puts each lod mesh of one object in
// a group of its own and has the object change its texture variant as the lods get swapped
glm::vec3 minimum{std::numeric_limits<float>::max()};
glm::vec3 maximum{std::numeric_limits<float>::lowest()};
bool isempty{true};
accumulate_mesh_bounds(Branch, minimum, maximum, isempty, false);
return (false == isempty ? (minimum + maximum) * 0.5f : offset);
}
} // namespace
void TModel3d::assign_variant_groups()
{ // each top level branch of the model gets a group id, which it passes down to all of its children.
// for load models a branch is a single load chunk, and the group id is what makes two chunks of the same
// model pick different texture variants, while keeping all parts of one chunk on the same variant.
// the id comes from the position of the branch, not from its ordinal: lod variants of one object are
// separate branches drawn in disjoint distance ranges, and grouping them by position is what keeps the
// object on the same variant no matter which of them is currently on screen
std::vector<glm::vec3> grouppositions; // position of each group, indexed by its id
auto const groupid = [&grouppositions](TSubModel const *Branch) {
auto const position{branch_position(Branch)};
for (std::size_t index = 0; index < grouppositions.size(); ++index)
{
if (glm::length2(grouppositions[index] - position) < variantgrouptolerance * variantgrouptolerance)
{
return static_cast<int>(index);
}
}
grouppositions.emplace_back(position);
return static_cast<int>(grouppositions.size() - 1);
};
for (auto *trunk = Root; trunk != nullptr; trunk = trunk->NextGet())
{
// root level submodels are usually mere containers, but can carry geometry of their own
trunk->m_variantgroup = groupid(trunk);
for (auto *branch = trunk->ChildGet(); branch != nullptr; branch = branch->NextGet())
{
mark_variant_group(branch, groupid(branch), false);
}
}
}
void TModel3d::Init()
{ // obrócenie początkowe układu współrzędnych, dla
// pojazdów wykonywane po analizie animacji
@@ -2492,6 +2607,8 @@ void TModel3d::Init()
Root->m_boundingradius = std::max(Root->m_boundingradius, root->m_boundingradius);
}
assign_variant_groups();
if (Global.iConvertModels & 1 && false == asBinary.empty())
{
SaveToBinFile(asBinary);

View File

@@ -137,6 +137,9 @@ public:
TSubModel *Child { nullptr };
public:
material_handle m_material { null_handle }; // numer tekstury, -1 wymienna, 0 brak
// index of the top level branch of the model this submodel belongs to. submodels sharing a branch
// also share the texture variant picked for them, so a multi-mesh load chunk stays consistent
int m_variantgroup { 0 };
bool bWire { false }; // nie używane, ale wczytywane
float Opacity { 1.0f };
float f_Angle { 0.0f };
@@ -168,6 +171,9 @@ public:
public:
static size_t iInstance; // identyfikator egzemplarza, który aktualnie renderuje model
// seed of the currently rendered model instance, combined with m_variantgroup it picks
// texture variants for materials which declare them. 0 for instances without own seed
static std::uint32_t iVariantSeed;
static material_handle const *ReplacableSkinId;
static int iAlpha; // maska bitowa dla danego przebiegu
static float fSquareDist;
@@ -211,6 +217,8 @@ public:
static void ReplacableSet(material_handle const *r, int a) {
ReplacableSkinId = r;
iAlpha = a; };
// returns id of texture variant to use for provided submodel of the currently rendered instance
static std::uint32_t VariantId(TSubModel const *Submodel);
void Name_Material( std::string const &Name );
void Name( std::string const &Name );
// Ra: funkcje do budowania terenu z E3D
@@ -291,6 +299,8 @@ public:
void SaveToBinFile(std::string const &FileName);
uint32_t Flags() const { return iFlags; };
void Init();
// assigns each top level branch of the model, and everything below it, its own variant group id
void assign_variant_groups();
std::string NameGet() const { return m_filename; };
nameoffset_sequence const & smoke_sources() const {
return m_smokesources; }

View File

@@ -64,55 +64,96 @@ std::map<std::string, int> texture_bindings {
{ "normalmap", 1 }
};
int opengl_material::texture_slot( std::string const &Key )
{
auto key { Key };
if( ( key.size() > 0 ) && ( key[ 0 ] != '_' ) )
{
// numbered binding, "textureN:" fills the slot N-1
key.erase( key.find_first_not_of( "-1234567890" ) );
size_t const num = std::stoi( key ) - 1;
if( num < gl::MAX_TEXTURES ) {
return static_cast<int>( num );
}
log_error( "invalid texture binding: " + std::to_string( num ) );
is_good = false;
return -1;
}
if( key.size() > 2 )
{
// named binding, the slot it fills is up to the shader
key.erase( 0, 1 );
key.pop_back();
if( shader && ( shader->texture_conf.find( key ) != shader->texture_conf.end() ) ) {
return static_cast<int>( shader->texture_conf[ key ].id );
}
auto const lookup { texture_bindings.find( key ) };
if( ( shader == nullptr ) && ( lookup != texture_bindings.end() ) ) {
return lookup->second;
}
// ignore unrecognized texture bindings in legacy render mode, it's most likely data for more advanced shaders
if( Global.GfxRenderer == "default" ) {
log_error( "unknown texture binding: " + key );
is_good = false;
}
return -1;
}
log_error( "unrecognized texture binding: " + key );
is_good = false;
return -1;
}
void opengl_material::build_texture_variants( std::vector<variant_binding> const &Bindings, bool const Loadnow )
{
if( true == Bindings.empty() ) { return; }
// build a texture set for each entry of the largest of the declared pools.
// pools of different size can be combined, shorter ones simply repeat from the start
std::size_t variantcount { 0 };
for( auto const &[ slot, pool ] : Bindings ) {
variantcount = std::max( variantcount, pool->size() );
}
texture_variants.resize( variantcount, textures );
for( auto const &[ slot, pool ] : Bindings ) {
for( std::size_t variantindex = 0; variantindex < variantcount; ++variantindex ) {
auto const &texturename { ( *pool )[ variantindex % pool->size() ] };
auto const texturehandle { GfxRenderer->Fetch_Texture( texturename, Loadnow ) };
if( texturehandle == null_handle ) {
// a single bad entry shouldn't take down the whole material, fall back to the basic texture
log_error( "missing texture variant: " + texturename );
continue;
}
texture_variants[ variantindex ][ slot ] = texturehandle;
}
}
}
void opengl_material::finalize(bool Loadnow)
{
is_good = true;
if (parse_info)
{
for (auto it : parse_info->tex_mapping)
{
std::string key = it.first;
std::string value = it.second.name;
// texture bindings which come with a pool of alternatives, gathered while resolving regular bindings
std::vector<variant_binding> variantbindings;
if (key.size() > 0 && key[0] != '_')
{
key.erase( key.find_first_not_of( "-1234567890" ) );
size_t num = std::stoi(key) - 1;
if (num < gl::MAX_TEXTURES) {
textures[num] = GfxRenderer->Fetch_Texture(value, Loadnow);
}
else {
log_error("invalid texture binding: " + std::to_string(num));
is_good = false;
}
}
else if (key.size() > 2)
{
key.erase(0, 1);
key.pop_back();
std::map<std::string, int>::iterator lookup;
if( shader && shader->texture_conf.find( key ) != shader->texture_conf.end() ) {
textures[ shader->texture_conf[ key ].id ] = GfxRenderer->Fetch_Texture( value, Loadnow );
}
else if( shader == nullptr
&& ( lookup = texture_bindings.find( key ) ) != texture_bindings.end() ) {
textures[ lookup->second ] = GfxRenderer->Fetch_Texture( value, Loadnow );
}
else {
// ignore unrecognized texture bindings in legacy render mode, it's most likely data for more advanced shaders
if( Global.GfxRenderer == "default" ) {
log_error( "unknown texture binding: " + key );
is_good = false;
}
}
}
else {
log_error("unrecognized texture binding: " + key);
is_good = false;
for( auto const &[ bindingkey, binding ] : parse_info->tex_mapping )
{
auto const slot { texture_slot( bindingkey ) };
if( slot < 0 ) { continue; }
textures[ slot ] = GfxRenderer->Fetch_Texture( binding.name, Loadnow );
if( binding.variants.size() > 1 ) {
variantbindings.emplace_back( static_cast<std::size_t>( slot ), &( binding.variants ) );
}
}
build_texture_variants( variantbindings, Loadnow );
if (!shader)
{
// TODO: add error severity to logging, re-enable these errors as low severity messages
@@ -289,6 +330,44 @@ bool is_weather( std::string const &String ) {
}
// imports member data pair from the config file
void opengl_material::deserialize_texture_mapping( cParser &Input, std::string const &Key, int const Priority ) {
// a texture binding can be provided as a pool of alternatives, "textureN_variants: [ a b c ]".
// unlike a regular random set, which is resolved once when the material is loaded and thus shared
// by everything using it, such pool is preserved whole and one entry is picked per model instance
auto key { Key };
auto const variantmarker { key.find( "_variants" ) };
auto const isvariantset { variantmarker != std::string::npos };
if( true == isvariantset ) {
// reduce the key to its plain form, "N_variants:" becomes "N:"
key.erase( variantmarker, std::string( "_variants" ).size() );
}
std::vector<std::string> variants;
std::string value;
if( false == isvariantset ) {
value = deserialize_random_set( Input );
replace_slashes( value );
}
else {
variants = deserialize_set( Input );
for( auto &variant : variants ) {
replace_slashes( variant );
}
if( true == variants.empty() ) {
log_error( "empty texture variant pool: " + key );
return;
}
// first entry doubles as the regular, variant-agnostic texture of the material
value = variants.front();
}
auto const [ it, inserted ] { parse_info->tex_mapping.try_emplace( key, parse_info_s::tex_def{ value, Priority, variants } ) };
if( ( false == inserted ) && ( Priority > it->second.priority ) ) {
it->second = parse_info_s::tex_def{ value, Priority, variants };
}
}
bool
opengl_material::deserialize_mapping( cParser &Input, int const Priority, bool const Loadnow ) {
@@ -324,17 +403,7 @@ opengl_material::deserialize_mapping( cParser &Input, int const Priority, bool c
else if (key.compare(0, 7, "texture") == 0) {
key.erase(0, 7);
auto value { deserialize_random_set( Input ) };
replace_slashes( value );
auto it = parse_info->tex_mapping.find(key);
if (it == parse_info->tex_mapping.end())
parse_info->tex_mapping.emplace(std::make_pair(key, parse_info_s::tex_def({ value, Priority })));
else if (Priority > it->second.priority)
{
parse_info->tex_mapping.erase(it);
parse_info->tex_mapping.emplace(std::make_pair(key, parse_info_s::tex_def({ value, Priority })));
}
deserialize_texture_mapping( Input, key, Priority );
}
else if (key.compare(0, 5, "param") == 0) {
key.erase(0, 5);

View File

@@ -18,7 +18,13 @@ http://mozilla.org/MPL/2.0/.
// a collection of parameters for the rendering setup.
// for modern opengl this translates to set of attributes for shaders
struct opengl_material : public IMaterial {
std::array<texture_handle, gl::MAX_TEXTURES> textures = { null_handle };
using texture_set = std::array<texture_handle, gl::MAX_TEXTURES>;
texture_set textures = { null_handle };
// alternative texture sets, defined with "textureN_variants:" key.
// one of them is picked per rendered model instance, empty when the material declares no variants.
// NOTE: all variants share the remaining material properties, opacity and translucency included
std::vector<texture_set> texture_variants;
std::array<glm::vec4, gl::MAX_PARAMS> params;
std::vector<gl::shader::param_entry> params_state;
@@ -60,6 +66,14 @@ struct opengl_material : public IMaterial {
{
return textures[slot];
}
// returns texture set to use for model instance identified by provided variant id
texture_set const &GetTextures(std::uint32_t const Variant) const
{
return (
texture_variants.empty() ?
textures :
texture_variants[Variant % texture_variants.size()]);
}
// members
static struct path_data {
std::unordered_map<std::string, int> index_map;
@@ -71,10 +85,23 @@ struct opengl_material : public IMaterial {
bool update_on_season_change{ false };
private:
// types
// texture binding which comes with a pool of alternatives: slot the binding fills, and the pool itself
using variant_binding = std::pair<std::size_t, std::vector<std::string> const *>;
// methods
// imports member data pair from the config file
bool
deserialize_mapping( cParser &Input, int const Priority, bool const Loadnow );
// imports texture binding, potentially carrying a pool of alternatives, from the config file
void
deserialize_texture_mapping( cParser &Input, std::string const &Key, int const Priority );
// returns texture slot provided binding key resolves to, or -1 if it doesn't resolve to any
int
texture_slot( std::string const &Key );
// fills the alternative texture sets from provided pools, one set per entry of the longest pool
void
build_texture_variants( std::vector<variant_binding> const &Bindings, bool const Loadnow );
void log_error(const std::string &str);
// members
@@ -90,6 +117,9 @@ private:
{
std::string name;
int priority;
// optional pool of alternative textures for the same binding, one picked per model instance.
// empty for regular, single texture bindings
std::vector<std::string> variants;
};
struct param_def
{

View File

@@ -1286,6 +1286,7 @@ bool opengl33_renderer::Render_lowpoly( TDynamicObject *Dynamic, float const Squ
if( Setup ) {
TSubModel::iInstance = reinterpret_cast<std::uintptr_t>( Dynamic ); //żeby nie robić cudzych animacji
TSubModel::iVariantSeed = Dynamic->variant_seed();
glm::dvec3 const originoffset{ Dynamic->vPosition - m_renderpass.pass_camera.position() };
Dynamic->ABuLittleUpdate( Squaredistance ); // ustawianie zmiennych submodeli dla wspólnego modelu
@@ -2262,14 +2263,21 @@ void opengl33_renderer::Bind_Material( material_handle const Material, TSubModel
else
model_ubs.alpha_mult = 1.0f;
// materials which declare texture variants provide a different texture set per model instance,
// picked from the seed of the rendered instance and the variant group of the submodel
auto const &materialtextures{
material.texture_variants.empty() ?
material.textures :
material.GetTextures(TSubModel::VariantId(sm))};
if (GLAD_GL_ARB_multi_bind)
{
GLuint lastdiff = 0;
size_t i;
for (i = 0; i < gl::MAX_TEXTURES; i++)
if (material.textures[i] != null_handle)
if (materialtextures[i] != null_handle)
{
opengl_texture &tex = m_textures.mark_as_used(material.textures[i]);
opengl_texture &tex = m_textures.mark_as_used(materialtextures[i]);
tex.create();
if (opengl_texture::units[i] != tex.id)
{
@@ -2286,7 +2294,7 @@ void opengl33_renderer::Bind_Material( material_handle const Material, TSubModel
else
{
size_t unit = 0;
for (auto &tex : material.textures)
for (auto &tex : materialtextures)
{
if (tex == null_handle)
break;
@@ -3351,6 +3359,7 @@ bool opengl33_renderer::Render(TDynamicObject *Dynamic)
// setup
TSubModel::iInstance = reinterpret_cast<std::uintptr_t>(Dynamic); //żeby nie robić cudzych animacji
TSubModel::iVariantSeed = Dynamic->variant_seed();
glm::dvec3 const originoffset = Dynamic->vPosition - m_renderpass.pass_camera.position();
// lod visibility ranges are defined for base (x 1.0) viewing distance. for render we adjust them for actual range multiplier and zoom
float squaredistance;
@@ -3518,10 +3527,12 @@ bool opengl33_renderer::Render_cab(TDynamicObject const *Dynamic, float const Li
{
TSubModel::iInstance = 0;
TSubModel::iVariantSeed = 0;
return false;
}
TSubModel::iInstance = reinterpret_cast<std::uintptr_t>(Dynamic);
TSubModel::iVariantSeed = Dynamic->variant_seed();
if ((true == FreeFlyModeFlag) || (false == Dynamic->bDisplayCab) || (Dynamic->mdKabina == Dynamic->mdModel))
{
@@ -4445,6 +4456,7 @@ bool opengl33_renderer::Render_Alpha(TDynamicObject *Dynamic)
// setup
TSubModel::iInstance = (size_t)Dynamic; //żeby nie robić cudzych animacji
TSubModel::iVariantSeed = Dynamic->variant_seed();
glm::dvec3 const originoffset = Dynamic->vPosition - m_renderpass.pass_camera.position();
// lod visibility ranges are defined for base (x 1.0) viewing distance. for render we adjust them for actual range multiplier and zoom
float squaredistance;

View File

@@ -801,6 +801,7 @@ bool opengl_renderer::Render_lowpoly( TDynamicObject *Dynamic, float const Squar
if( Setup ) {
TSubModel::iInstance = reinterpret_cast<std::uintptr_t>( Dynamic ); //żeby nie robić cudzych animacji
TSubModel::iVariantSeed = Dynamic->variant_seed();
Dynamic->ABuLittleUpdate( Squaredistance ); // ustawianie zmiennych submodeli dla wspólnego modelu
::glPushMatrix();
@@ -1759,10 +1760,17 @@ opengl_renderer::Bind_Material( material_handle const Material, TSubModel const
return;
}
// materials which declare texture variants provide a different texture set per model instance,
// picked from the seed of the rendered instance and the variant group of the submodel
auto const &materialtextures {
material.texture_variants.empty() ?
material.textures :
material.GetTextures( TSubModel::VariantId( sm ) ) };
if( false == Global.BasicRenderer ) {
m_textures.bind( m_normaltextureunit, material.textures[1] );
m_textures.bind( m_normaltextureunit, materialtextures[1] );
}
m_textures.bind( m_diffusetextureunit, material.textures[0] );
m_textures.bind( m_diffusetextureunit, materialtextures[0] );
}
else {
// null material, unbind all textures
@@ -2475,6 +2483,7 @@ opengl_renderer::Render( TDynamicObject *Dynamic ) {
// setup
TSubModel::iInstance = reinterpret_cast<std::uintptr_t>( Dynamic ); //żeby nie robić cudzych animacji
TSubModel::iVariantSeed = Dynamic->variant_seed();
Dynamic->ABuLittleUpdate( squaredistance ); // ustawianie zmiennych submodeli dla wspólnego modelu
::glPushMatrix();
@@ -2574,10 +2583,12 @@ opengl_renderer::Render_cab( TDynamicObject const *Dynamic, float const Lightlev
if( Dynamic == nullptr ) {
TSubModel::iInstance = 0;
TSubModel::iVariantSeed = 0;
return false;
}
TSubModel::iInstance = reinterpret_cast<std::uintptr_t>( Dynamic );
TSubModel::iVariantSeed = Dynamic->variant_seed();
if( ( true == FreeFlyModeFlag )
|| ( false == Dynamic->bDisplayCab )
@@ -2866,11 +2877,11 @@ opengl_renderer::Render( TSubModel *Submodel ) {
// material configuration:
// textures...
if( Submodel->m_material < 0 ) { // zmienialne skóry
Bind_Material( Submodel->ReplacableSkinId[ -Submodel->m_material ] );
Bind_Material( TSubModel::ReplacableSkinId[ -Submodel->m_material ], Submodel );
}
else {
// również 0
Bind_Material( Submodel->m_material );
Bind_Material( Submodel->m_material, Submodel );
}
// main draw call
m_geometry.draw( Submodel->m_geometry.handle );
@@ -2884,11 +2895,11 @@ opengl_renderer::Render( TSubModel *Submodel ) {
::glColor3fv( glm::value_ptr( pick_color( m_pickcontrolsitems.size() ) ) );
// textures...
if( Submodel->m_material < 0 ) { // zmienialne skóry
Bind_Material( Submodel->ReplacableSkinId[ -Submodel->m_material ] );
Bind_Material( TSubModel::ReplacableSkinId[ -Submodel->m_material ], Submodel );
}
else {
// również 0
Bind_Material( Submodel->m_material );
Bind_Material( Submodel->m_material, Submodel );
}
// main draw call
m_geometry.draw( Submodel->m_geometry.handle );
@@ -3657,6 +3668,7 @@ opengl_renderer::Render_Alpha( TDynamicObject *Dynamic ) {
// setup
TSubModel::iInstance = ( size_t )Dynamic; //żeby nie robić cudzych animacji
TSubModel::iVariantSeed = Dynamic->variant_seed();
glm::dvec3 const originoffset = Dynamic->vPosition - m_renderpass.camera.position();
// lod visibility ranges are defined for base (x 1.0) viewing distance. for render we adjust them for actual range multiplier and zoom
float squaredistance;
@@ -3909,11 +3921,11 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) {
// material configuration:
// textures...
if( Submodel->m_material < 0 ) { // zmienialne skóry
Bind_Material( Submodel->ReplacableSkinId[ -Submodel->m_material ] );
Bind_Material( TSubModel::ReplacableSkinId[ -Submodel->m_material ], Submodel );
}
else {
// również 0
Bind_Material( Submodel->m_material );
Bind_Material( Submodel->m_material, Submodel );
}
// main draw call
m_geometry.draw( Submodel->m_geometry.handle );

View File

@@ -527,4 +527,42 @@ std::string deserialize_random_set(cParser &Input, char const *Break)
// shouldn't ever get here but, eh
return "";
}
}
}
// returns hash of provided string, combined with provided seed
std::uint32_t hash_string(std::string const &String, std::uint32_t const Seed)
{
// FNV-1a, 32 bit, with the seed mixed into the standard offset basis
std::uint32_t hash{2166136261u ^ Seed};
for (auto const character : String)
{
hash ^= static_cast<std::uint32_t>(static_cast<unsigned char>(character));
hash *= 16777619u;
}
return hash;
}
// extracts a group of tokens from provided data stream, returns all of them
std::vector<std::string> deserialize_set(cParser &Input, char const *Break)
{
std::vector<std::string> tokens;
auto token{Input.getToken<std::string>(true, Break)};
std::ranges::replace(token, '\\', '/');
if (token != "[")
{
// simple case, single token
if (false == token.empty())
{
tokens.emplace_back(token);
}
return tokens;
}
// '[' marks a beginning of a set, retrieve all entries until it's closed
while ((token = deserialize_random_set(Input, Break)) != "" && token != "]")
{
tokens.emplace_back(token);
}
return tokens;
}

View File

@@ -377,6 +377,14 @@ glm::dvec3 LoadPoint(class cParser &Input);
// extracts a group of tokens from provided data stream
std::string deserialize_random_set(cParser &Input, char const *Break = "\n\r\t ;");
// extracts a group of tokens from provided data stream, returns all of them.
// a single token, not enclosed in "[]", is returned as one element sequence
std::vector<std::string> deserialize_set(cParser &Input, char const *Break = "\n\r\t ;");
// returns hash of provided string, combined with provided seed. FNV-1a, picked for having
// identical result regardless of platform and standard library, unlike std::hash
std::uint32_t hash_string(std::string const &String, std::uint32_t const Seed = 0);
// extracts a group of <key, value> pairs from provided data stream
// NOTE: expects no more than single pair per line
template <typename MapType_> void deserialize_map(MapType_ &Map, cParser &Input)

View File

@@ -1934,6 +1934,10 @@ TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424"
iDirection = Reversed ? 0 : 1; // Ra: 0, jeśli ma być wstawiony jako obrócony tyłem
asBaseDir = paths::dynamic + BaseDir + "/"; // McZapkie-310302
asName = Name;
// derived from the name and the seed of the simulation rather than drawn at random, so that the vehicle
// keeps the same look for the whole session and looks the same for everyone in a multiplayer game, while
// another run of the same scenario, with another seed, dresses it differently
m_variantseed = hash_string( asName, Global.random_seed );
std::string asAnimName; // zmienna robocza do wyszukiwania osi i wózków
// Ra: zmieniamy znaczenie obsady na jednoliterowe, żeby dosadzić kierownika
if (DriverType == "headdriver")

View File

@@ -634,6 +634,10 @@ private:
std::string asName;
const std::string &name() const {
return asName; }
// seed for texture variants of materials used by this vehicle and its load
std::uint32_t m_variantseed { 0 };
std::uint32_t variant_seed() const {
return m_variantseed; }
std::string asBaseDir;
// std::ofstream PneuLogFile; //zapis parametrow pneumatycznych