mirror of
https://github.com/luanti-org/luanti.git
synced 2026-01-14 13:25:21 +01:00
Refactor skinned mesh weight data structure (#16655)
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace scene
|
||||
{
|
||||
@@ -35,21 +36,6 @@ f32 SkinnedMesh::getMaxFrameNumber() const
|
||||
return EndFrame;
|
||||
}
|
||||
|
||||
//! Gets the default animation speed of the animated mesh.
|
||||
/** \return Amount of frames per second. If the amount is 0, it is a static, non animated mesh. */
|
||||
f32 SkinnedMesh::getAnimationSpeed() const
|
||||
{
|
||||
return FramesPerSecond;
|
||||
}
|
||||
|
||||
//! Gets the frame count of the animated mesh.
|
||||
/** \param fps Frames per second to play the animation with. If the amount is 0, it is not animated.
|
||||
The actual speed is set in the scene node the mesh is instantiated in.*/
|
||||
void SkinnedMesh::setAnimationSpeed(f32 fps)
|
||||
{
|
||||
FramesPerSecond = fps;
|
||||
}
|
||||
|
||||
// Keyframe Animation
|
||||
|
||||
|
||||
@@ -102,56 +88,19 @@ void SkinnedMesh::skinMesh(const std::vector<core::matrix4> &global_matrices)
|
||||
}
|
||||
}
|
||||
|
||||
// clear skinning helper array
|
||||
for (std::vector<char> &buf : Vertices_Moved)
|
||||
std::fill(buf.begin(), buf.end(), false);
|
||||
|
||||
// skin starting with the root joints
|
||||
for (size_t i = 0; i < AllJoints.size(); ++i) {
|
||||
// Premultiply with global inversed matrices, if present
|
||||
// (which they should be for joints with weights)
|
||||
std::vector<core::matrix4> joint_transforms = global_matrices;
|
||||
for (u16 i = 0; i < AllJoints.size(); ++i) {
|
||||
auto *joint = AllJoints[i];
|
||||
if (joint->Weights.empty())
|
||||
continue;
|
||||
|
||||
// Find this joints pull on vertices
|
||||
// Note: It is assumed that the global inversed matrix has been calculated at this point.
|
||||
core::matrix4 jointVertexPull = global_matrices[i] * joint->GlobalInversedMatrix.value();
|
||||
|
||||
core::vector3df thisVertexMove, thisNormalMove;
|
||||
|
||||
auto &buffersUsed = *SkinningBuffers;
|
||||
|
||||
// Skin Vertices, Positions and Normals
|
||||
for (const auto &weight : joint->Weights) {
|
||||
// Pull this vertex...
|
||||
jointVertexPull.transformVect(thisVertexMove, weight.StaticPos);
|
||||
|
||||
if (AnimateNormals) {
|
||||
thisNormalMove = jointVertexPull.rotateAndScaleVect(weight.StaticNormal);
|
||||
thisNormalMove.normalize(); // must renormalize after potentially scaling
|
||||
}
|
||||
|
||||
if (!(*(weight.Moved))) {
|
||||
*(weight.Moved) = true;
|
||||
|
||||
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos = thisVertexMove * weight.strength;
|
||||
|
||||
if (AnimateNormals)
|
||||
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal = thisNormalMove * weight.strength;
|
||||
|
||||
//*(weight._Pos) = thisVertexMove * weight.strength;
|
||||
} else {
|
||||
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos += thisVertexMove * weight.strength;
|
||||
|
||||
if (AnimateNormals)
|
||||
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal += thisNormalMove * weight.strength;
|
||||
|
||||
//*(weight._Pos) += thisVertexMove * weight.strength;
|
||||
}
|
||||
}
|
||||
if (joint->GlobalInversedMatrix)
|
||||
joint_transforms[i] = joint_transforms[i] * (*joint->GlobalInversedMatrix);
|
||||
}
|
||||
|
||||
for (auto *buffer : *SkinningBuffers)
|
||||
buffer->setDirty(EBT_VERTEX);
|
||||
for (auto *buffer : *SkinningBuffers) {
|
||||
if (buffer->Weights)
|
||||
buffer->Weights->skin(buffer->getVertexBuffer(), joint_transforms);
|
||||
}
|
||||
}
|
||||
|
||||
//! Gets joint count.
|
||||
@@ -211,10 +160,6 @@ u32 SkinnedMesh::getTextureSlot(u32 meshbufNr) const
|
||||
return TextureSlots.at(meshbufNr);
|
||||
}
|
||||
|
||||
void SkinnedMesh::setTextureSlot(u32 meshbufNr, u32 textureSlot) {
|
||||
TextureSlots.at(meshbufNr) = textureSlot;
|
||||
}
|
||||
|
||||
//! set the hardware mapping hint, for driver
|
||||
void SkinnedMesh::setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint,
|
||||
E_BUFFER_TYPE buffer)
|
||||
@@ -230,29 +175,20 @@ void SkinnedMesh::setDirty(E_BUFFER_TYPE buffer)
|
||||
LocalBuffers[i]->setDirty(buffer);
|
||||
}
|
||||
|
||||
void SkinnedMesh::refreshJointCache()
|
||||
void SkinnedMesh::updateStaticPose()
|
||||
{
|
||||
// copy cache from the mesh...
|
||||
for (auto *joint : AllJoints) {
|
||||
for (auto &weight : joint->Weights) {
|
||||
const u16 buffer_id = weight.buffer_id;
|
||||
const u32 vertex_id = weight.vertex_id;
|
||||
weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
|
||||
weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
|
||||
}
|
||||
for (auto *buf : LocalBuffers) {
|
||||
if (buf->Weights)
|
||||
buf->Weights->updateStaticPose(buf->getVertexBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
void SkinnedMesh::resetAnimation()
|
||||
{
|
||||
// copy from the cache to the mesh...
|
||||
for (auto *joint : AllJoints) {
|
||||
for (const auto &weight : joint->Weights) {
|
||||
const u16 buffer_id = weight.buffer_id;
|
||||
const u32 vertex_id = weight.vertex_id;
|
||||
LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = weight.StaticPos;
|
||||
LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = weight.StaticNormal;
|
||||
}
|
||||
for (auto *buf : LocalBuffers) {
|
||||
if (buf->Weights)
|
||||
buf->Weights->resetToStatic(buf->getVertexBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,8 +213,8 @@ bool SkinnedMesh::checkForAnimation() const
|
||||
}
|
||||
|
||||
// meshes with weights are animatable
|
||||
for (auto *joint : AllJoints) {
|
||||
if (!joint->Weights.empty()) {
|
||||
for (auto *buf : LocalBuffers) {
|
||||
if (buf->Weights) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -299,40 +235,6 @@ void SkinnedMesh::prepareForSkinning()
|
||||
EndFrame = std::max(EndFrame, joint->keys.getEndFrame());
|
||||
}
|
||||
|
||||
for (auto *joint : AllJoints) {
|
||||
for (auto &weight : joint->Weights) {
|
||||
const u16 buffer_id = weight.buffer_id;
|
||||
const u32 vertex_id = weight.vertex_id;
|
||||
|
||||
// check for invalid ids
|
||||
if (buffer_id >= LocalBuffers.size()) {
|
||||
os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING);
|
||||
weight.buffer_id = weight.vertex_id = 0;
|
||||
} else if (vertex_id >= LocalBuffers[buffer_id]->getVertexCount()) {
|
||||
os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING);
|
||||
weight.buffer_id = weight.vertex_id = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < Vertices_Moved.size(); ++i)
|
||||
for (u32 j = 0; j < Vertices_Moved[i].size(); ++j)
|
||||
Vertices_Moved[i][j] = false;
|
||||
|
||||
// For skinning: cache weight values for speed
|
||||
for (auto *joint : AllJoints) {
|
||||
for (auto &weight : joint->Weights) {
|
||||
const u16 buffer_id = weight.buffer_id;
|
||||
const u32 vertex_id = weight.vertex_id;
|
||||
|
||||
weight.Moved = &Vertices_Moved[buffer_id][vertex_id];
|
||||
weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
|
||||
weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
|
||||
}
|
||||
}
|
||||
|
||||
normalizeWeights();
|
||||
|
||||
for (auto *joint : AllJoints) {
|
||||
joint->keys.cleanup();
|
||||
}
|
||||
@@ -340,29 +242,27 @@ void SkinnedMesh::prepareForSkinning()
|
||||
|
||||
void SkinnedMesh::calculateStaticBoundingBox()
|
||||
{
|
||||
std::vector<std::vector<bool>> animated(getMeshBufferCount());
|
||||
for (u32 mb = 0; mb < getMeshBufferCount(); mb++)
|
||||
animated[mb] = std::vector<bool>(getMeshBuffer(mb)->getVertexCount());
|
||||
|
||||
for (auto *joint : AllJoints) {
|
||||
for (auto &weight : joint->Weights) {
|
||||
const u16 buffer_id = weight.buffer_id;
|
||||
const u32 vertex_id = weight.vertex_id;
|
||||
animated[buffer_id][vertex_id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool first = true;
|
||||
std::vector<bool> animated;
|
||||
for (u16 mb = 0; mb < getMeshBufferCount(); mb++) {
|
||||
for (u32 v = 0; v < getMeshBuffer(mb)->getVertexCount(); v++) {
|
||||
if (!animated[mb][v]) {
|
||||
auto pos = getMeshBuffer(mb)->getVertexBuffer()->getPosition(v);
|
||||
if (!first) {
|
||||
StaticPartsBox.addInternalPoint(pos);
|
||||
} else {
|
||||
StaticPartsBox.reset(pos);
|
||||
first = false;
|
||||
}
|
||||
auto *buf = LocalBuffers[mb];
|
||||
animated.clear();
|
||||
animated.resize(buf->getVertexCount(), false);
|
||||
if (buf->Weights) {
|
||||
for (u32 vert_id : buf->Weights->animated_vertices.value()) {
|
||||
animated[vert_id] = true;
|
||||
}
|
||||
}
|
||||
for (u32 v = 0; v < buf->getVertexCount(); v++) {
|
||||
if (animated[v])
|
||||
continue;
|
||||
|
||||
auto pos = getMeshBuffer(mb)->getVertexBuffer()->getPosition(v);
|
||||
if (!first) {
|
||||
StaticPartsBox.addInternalPoint(pos);
|
||||
} else {
|
||||
StaticPartsBox.reset(pos);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,21 +270,33 @@ void SkinnedMesh::calculateStaticBoundingBox()
|
||||
|
||||
void SkinnedMesh::calculateJointBoundingBoxes()
|
||||
{
|
||||
for (auto *joint : AllJoints) {
|
||||
bool first = true;
|
||||
for (auto &weight : joint->Weights) {
|
||||
if (weight.strength < 1e-6)
|
||||
continue;
|
||||
auto pos = weight.StaticPos;
|
||||
joint->GlobalInversedMatrix.value().transformVect(pos);
|
||||
if (!first) {
|
||||
joint->LocalBoundingBox.addInternalPoint(pos);
|
||||
} else {
|
||||
joint->LocalBoundingBox.reset(pos);
|
||||
first = false;
|
||||
std::vector<std::optional<core::aabbox3df>> joint_boxes(AllJoints.size());
|
||||
for (auto *buf : LocalBuffers) {
|
||||
const auto &weights = buf->Weights;
|
||||
if (!weights)
|
||||
continue;
|
||||
for (u32 vert_id : weights->animated_vertices.value()) {
|
||||
const auto pos = buf->getVertex(vert_id)->Pos;
|
||||
for (u16 j = 0; j < WeightBuffer::MAX_WEIGHTS_PER_VERTEX; j++) {
|
||||
const u16 joint_id = weights->getJointIds(vert_id)[j];
|
||||
const SJoint *joint = AllJoints[joint_id];
|
||||
const f32 weight = weights->getWeights(vert_id)[j];
|
||||
if (core::equals(weight, 0.0f))
|
||||
continue;
|
||||
auto trans_pos = pos;
|
||||
joint->GlobalInversedMatrix.value().transformVect(trans_pos);
|
||||
if (joint_boxes[joint_id]) {
|
||||
joint_boxes[joint_id]->addInternalPoint(trans_pos);
|
||||
} else {
|
||||
joint_boxes[joint_id] = core::aabbox3df{trans_pos};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (u16 joint_id = 0; joint_id < AllJoints.size(); joint_id++) {
|
||||
auto *joint = AllJoints[joint_id];
|
||||
joint->LocalBoundingBox = joint_boxes[joint_id].value_or(core::aabbox3df{{0,0,0}});
|
||||
}
|
||||
}
|
||||
|
||||
void SkinnedMesh::calculateBufferBoundingBoxes()
|
||||
@@ -402,15 +314,16 @@ void SkinnedMesh::recalculateBaseBoundingBoxes() {
|
||||
calculateBufferBoundingBoxes();
|
||||
}
|
||||
|
||||
void SkinnedMesh::topoSortJoints()
|
||||
void SkinnedMeshBuilder::topoSortJoints()
|
||||
{
|
||||
size_t n = AllJoints.size();
|
||||
auto &joints = getJoints();
|
||||
const size_t n = joints.size();
|
||||
|
||||
std::vector<u16> new_to_old_id;
|
||||
|
||||
std::vector<std::vector<u16>> children(n);
|
||||
for (u16 i = 0; i < n; ++i) {
|
||||
if (auto parentId = AllJoints[i]->ParentJointID)
|
||||
if (auto parentId = joints[i]->ParentJointID)
|
||||
children[*parentId].push_back(i);
|
||||
else
|
||||
new_to_old_id.push_back(i);
|
||||
@@ -427,76 +340,93 @@ void SkinnedMesh::topoSortJoints()
|
||||
for (u16 i = 0; i < n; ++i)
|
||||
old_to_new_id[new_to_old_id[i]] = i;
|
||||
|
||||
std::vector<SJoint *> joints(n);
|
||||
std::vector<SJoint *> sorted_joints(n);
|
||||
for (u16 i = 0; i < n; ++i) {
|
||||
joints[i] = AllJoints[new_to_old_id[i]];
|
||||
joints[i]->JointID = i;
|
||||
if (auto parentId = joints[i]->ParentJointID)
|
||||
joints[i]->ParentJointID = old_to_new_id[*parentId];
|
||||
auto *joint = joints[new_to_old_id[i]];
|
||||
if (auto parentId = joint->ParentJointID)
|
||||
joint->ParentJointID = old_to_new_id[*parentId];
|
||||
sorted_joints[i] = joint;
|
||||
joint->JointID = i;
|
||||
}
|
||||
AllJoints = std::move(joints);
|
||||
|
||||
// Verify that the topological ordering is correct
|
||||
for (u16 i = 0; i < n; ++i) {
|
||||
if (auto pjid = AllJoints[i]->ParentJointID)
|
||||
if (auto pjid = sorted_joints[i]->ParentJointID)
|
||||
assert(*pjid < i);
|
||||
}
|
||||
getJoints() = std::move(sorted_joints);
|
||||
|
||||
for (auto &weight : weights) {
|
||||
weight.joint_id = old_to_new_id[weight.joint_id];
|
||||
}
|
||||
}
|
||||
|
||||
//! called by loader after populating with mesh and bone data
|
||||
SkinnedMesh *SkinnedMeshBuilder::finalize()
|
||||
SkinnedMesh *SkinnedMeshBuilder::finalize() &&
|
||||
{
|
||||
os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG);
|
||||
|
||||
// Topologically sort the joints such that parents come before their children.
|
||||
// From this point on, transformations can be calculated in linear order.
|
||||
// (see e.g. SkinnedMesh::calculateGlobalMatrices)
|
||||
topoSortJoints();
|
||||
|
||||
// Set array sizes
|
||||
for (u32 i = 0; i < LocalBuffers.size(); ++i) {
|
||||
Vertices_Moved.emplace_back(LocalBuffers[i]->getVertexCount());
|
||||
}
|
||||
|
||||
prepareForSkinning();
|
||||
mesh->prepareForSkinning();
|
||||
|
||||
std::vector<core::matrix4> matrices;
|
||||
matrices.reserve(AllJoints.size());
|
||||
for (auto *joint : AllJoints) {
|
||||
matrices.reserve(getJoints().size());
|
||||
for (auto *joint : getJoints()) {
|
||||
if (const auto *matrix = std::get_if<core::matrix4>(&joint->transform))
|
||||
matrices.push_back(*matrix);
|
||||
else
|
||||
matrices.push_back(std::get<core::Transform>(joint->transform).buildMatrix());
|
||||
}
|
||||
calculateGlobalMatrices(matrices);
|
||||
mesh->calculateGlobalMatrices(matrices);
|
||||
|
||||
for (size_t i = 0; i < AllJoints.size(); ++i) {
|
||||
auto *joint = AllJoints[i];
|
||||
for (size_t i = 0; i < getJoints().size(); ++i) {
|
||||
auto *joint = getJoints()[i];
|
||||
if (!joint->GlobalInversedMatrix) {
|
||||
joint->GlobalInversedMatrix = matrices[i];
|
||||
joint->GlobalInversedMatrix->makeInverse();
|
||||
}
|
||||
// rigid animation for non animated meshes
|
||||
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
|
||||
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
|
||||
SSkinMeshBuffer *Buffer = (*mesh->SkinningBuffers)[attachedMeshIdx];
|
||||
Buffer->Transformation = matrices[i];
|
||||
}
|
||||
}
|
||||
|
||||
recalculateBaseBoundingBoxes();
|
||||
StaticPoseBox = calculateBoundingBox(matrices);
|
||||
for (const auto &weight : weights) {
|
||||
auto *buf = mesh->LocalBuffers.at(weight.buffer_id);
|
||||
if (!buf->Weights)
|
||||
buf->Weights = WeightBuffer(buf->getVertexCount());
|
||||
buf->Weights->addWeight(weight.vertex_id, weight.joint_id, weight.strength);
|
||||
}
|
||||
|
||||
return this;
|
||||
for (auto *buffer : mesh->LocalBuffers) {
|
||||
if (buffer->Weights)
|
||||
buffer->Weights->finalize();
|
||||
}
|
||||
mesh->updateStaticPose();
|
||||
|
||||
mesh->recalculateBaseBoundingBoxes();
|
||||
mesh->StaticPoseBox = mesh->calculateBoundingBox(matrices);
|
||||
|
||||
return mesh.release();
|
||||
}
|
||||
|
||||
scene::SSkinMeshBuffer *SkinnedMeshBuilder::addMeshBuffer()
|
||||
{
|
||||
scene::SSkinMeshBuffer *buffer = new scene::SSkinMeshBuffer();
|
||||
TextureSlots.push_back(LocalBuffers.size());
|
||||
LocalBuffers.push_back(buffer);
|
||||
mesh->TextureSlots.push_back(mesh->LocalBuffers.size());
|
||||
mesh->LocalBuffers.push_back(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void SkinnedMeshBuilder::addMeshBuffer(SSkinMeshBuffer *meshbuf)
|
||||
u32 SkinnedMeshBuilder::addMeshBuffer(SSkinMeshBuffer *meshbuf)
|
||||
{
|
||||
TextureSlots.push_back(LocalBuffers.size());
|
||||
LocalBuffers.push_back(meshbuf);
|
||||
mesh->TextureSlots.push_back(mesh->LocalBuffers.size());
|
||||
mesh->LocalBuffers.push_back(meshbuf);
|
||||
return mesh->getMeshBufferCount() - 1;
|
||||
}
|
||||
|
||||
SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent)
|
||||
@@ -504,8 +434,8 @@ SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent)
|
||||
SJoint *joint = new SJoint;
|
||||
joint->setParent(parent);
|
||||
|
||||
joint->JointID = AllJoints.size();
|
||||
AllJoints.push_back(joint);
|
||||
joint->JointID = getJoints().size();
|
||||
getJoints().push_back(joint);
|
||||
|
||||
return joint;
|
||||
}
|
||||
@@ -528,51 +458,12 @@ void SkinnedMeshBuilder::addRotationKey(SJoint *joint, f32 frame, core::quaterni
|
||||
joint->keys.rotation.pushBack(frame, rot);
|
||||
}
|
||||
|
||||
SkinnedMesh::SWeight *SkinnedMeshBuilder::addWeight(SJoint *joint)
|
||||
void SkinnedMeshBuilder::addWeight(SJoint *joint, u16 buf_id, u32 vert_id, f32 strength)
|
||||
{
|
||||
if (!joint)
|
||||
return nullptr;
|
||||
|
||||
joint->Weights.emplace_back();
|
||||
return &joint->Weights.back();
|
||||
}
|
||||
|
||||
void SkinnedMesh::normalizeWeights()
|
||||
{
|
||||
// note: unsure if weights ids are going to be used.
|
||||
|
||||
// Normalise the weights on bones....
|
||||
|
||||
std::vector<std::vector<f32>> verticesTotalWeight;
|
||||
|
||||
verticesTotalWeight.reserve(LocalBuffers.size());
|
||||
for (u32 i = 0; i < LocalBuffers.size(); ++i) {
|
||||
verticesTotalWeight.emplace_back(LocalBuffers[i]->getVertexCount());
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < verticesTotalWeight.size(); ++i)
|
||||
for (u32 j = 0; j < verticesTotalWeight[i].size(); ++j)
|
||||
verticesTotalWeight[i][j] = 0;
|
||||
|
||||
for (auto *joint : AllJoints) {
|
||||
auto &weights = joint->Weights;
|
||||
|
||||
weights.erase(std::remove_if(weights.begin(), weights.end(), [](const auto &weight) {
|
||||
return weight.strength <= 0;
|
||||
}), weights.end());
|
||||
|
||||
for (const auto &weight : weights) {
|
||||
verticesTotalWeight[weight.buffer_id][weight.vertex_id] += weight.strength;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto *joint : AllJoints) {
|
||||
for (auto &weight : joint->Weights) {
|
||||
const f32 total = verticesTotalWeight[weight.buffer_id][weight.vertex_id];
|
||||
if (total != 0 && total != 1)
|
||||
weight.strength /= total;
|
||||
}
|
||||
}
|
||||
assert(joint);
|
||||
if (strength <= 0.0f)
|
||||
return;
|
||||
weights.emplace_back(Weight{joint->JointID, buf_id, vert_id, strength});
|
||||
}
|
||||
|
||||
void SkinnedMesh::convertMeshToTangents()
|
||||
|
||||
Reference in New Issue
Block a user