1
0
mirror of https://github.com/luanti-org/luanti.git synced 2026-01-13 20:55:30 +01:00

Refactor skinned mesh weight data structure (#16655)

This commit is contained in:
Lars Müller
2025-11-23 21:17:58 +01:00
committed by GitHub
parent 05f161cf9c
commit e7f305fedd
17 changed files with 584 additions and 623 deletions

124
irr/src/WeightBuffer.cpp Normal file
View File

@@ -0,0 +1,124 @@
// Copyright (C) 2025 Lars Müller
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "WeightBuffer.h"
#include <algorithm>
#include <numeric>
namespace scene {
void WeightBuffer::VertexWeights::addWeight(u16 joint_id, f32 weight)
{
assert(weight >= 0.0f);
auto min_weight = std::min_element(weights.begin(), weights.end());
if (*min_weight > weight)
return;
*min_weight = weight;
joint_ids[std::distance(weights.begin(), min_weight)] = joint_id;
}
void WeightBuffer::addWeight(u32 vertex_id, u16 joint_id, f32 weight)
{
weights.at(vertex_id).addWeight(joint_id, weight);
}
void WeightBuffer::VertexWeights::skinVertex(core::vector3df &pos, core::vector3df &normal,
const std::vector<core::matrix4> &joint_transforms) const
{
f32 total_weight = 0.0f;
core::vector3df skinned_pos;
core::vector3df skinned_normal;
for (u16 i = 0; i < MAX_WEIGHTS_PER_VERTEX; ++i) {
const u16 joint_id = joint_ids[i];
const f32 weight = weights[i];
if (core::equals(weight, 0.0f))
continue;
const auto &transform = joint_transforms[joint_id];
core::vector3df transformed_pos = pos;
transform.transformVect(transformed_pos);
skinned_pos += weight * transformed_pos;
skinned_normal += weight * transform.rotateAndScaleVect(normal);
total_weight += weight;
}
if (core::equals(total_weight, 0.0f))
return;
pos = skinned_pos;
// Need to renormalize normal after potentially scaling
normal = skinned_normal.normalize();
}
void WeightBuffer::skinVertex(u32 vertex_id, core::vector3df &pos, core::vector3df &normal,
const std::vector<core::matrix4> &joint_transforms) const
{
return weights[vertex_id].skinVertex(pos, normal, joint_transforms);
}
void WeightBuffer::skin(IVertexBuffer *dst,
const std::vector<core::matrix4> &joint_transforms) const
{
assert(animated_vertices.has_value());
for (u32 i = 0; i < animated_vertices->size(); ++i) {
u32 vertex_id = (*animated_vertices)[i];
auto pos = static_positions[i];
auto normal = static_normals[i];
skinVertex(vertex_id, pos, normal, joint_transforms);
dst->getPosition(vertex_id) = pos;
dst->getNormal(vertex_id) = normal;
}
if (!animated_vertices->empty())
dst->setDirty();
}
void WeightBuffer::finalize()
{
// Normalizes weights so that they sum to 1.0 per vertex,
// stores which vertices are animated.
assert(!animated_vertices.has_value());
animated_vertices.emplace();
for (u32 i = 0; i < size(); ++i) {
auto &weights_i = weights[i].weights;
f32 total_weight = std::accumulate(weights_i.begin(), weights_i.end(), 0.0f);
if (core::equals(total_weight, 0.0f)) {
std::fill(weights_i.begin(), weights_i.end(), 0.0f);
continue;
}
animated_vertices->emplace_back(i);
if (core::equals(total_weight, 1.0f))
continue;
for (auto &strength : weights_i)
strength /= total_weight;
}
animated_vertices->shrink_to_fit();
}
void WeightBuffer::updateStaticPose(const IVertexBuffer *vbuf)
{
if (!static_normals)
static_normals = std::make_unique<core::vector3df[]>(animated_vertices->size());
if (!static_positions)
static_positions = std::make_unique<core::vector3df[]>(animated_vertices->size());
for (size_t idx = 0; idx < animated_vertices->size(); ++idx) {
u32 vertex_id = (*animated_vertices)[idx];
static_positions[idx] = vbuf->getPosition(vertex_id);
static_normals[idx] = vbuf->getNormal(vertex_id);
}
}
void WeightBuffer::resetToStatic(IVertexBuffer *vbuf) const
{
assert(animated_vertices.has_value());
for (size_t idx = 0; idx < animated_vertices->size(); ++idx) {
u32 vertex_id = (*animated_vertices)[idx];
vbuf->getPosition(vertex_id) = static_positions[idx];
vbuf->getNormal(vertex_id) = static_normals[idx];
}
if (!animated_vertices->empty())
vbuf->setDirty();
}
} // end namespace scene