17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 12:09:19 +02:00

feat(materials): texture randomization for loads

This commit is contained in:
jerrrrycho
2026-09-10 17:48:38 +02:00
parent d9eb705d4a
commit 277bc46a21
12 changed files with 316 additions and 21 deletions

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)
{
auto const &vertices{(Submodel->m_geometry.handle != null_handle ? GfxRenderer->Vertices(Submodel->m_geometry.handle) : Submodel->Vertices)};
if (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) -> int {
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);