1
0
mirror of https://github.com/luanti-org/luanti.git synced 2025-12-21 06:05:33 +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

View File

@@ -514,10 +514,6 @@ void AnimatedMeshSceneNode::setMesh(IAnimatedMesh *mesh)
JointsUsed = false;
checkJoints();
}
// get start and begin time
setAnimationSpeed(Mesh->getAnimationSpeed()); // NOTE: This had been commented out (but not removed!) in r3526. Which caused meshloader-values for speed to be ignored unless users specified explicitly. Missing a test-case where this could go wrong so I put the code back in.
setFrameLoop(0, Mesh->getMaxFrameNumber());
}
//! updates the absolute position based on the relative and the parents position

View File

@@ -25,8 +25,11 @@ namespace scene
//! Constructor
CB3DMeshFileLoader::CB3DMeshFileLoader(scene::ISceneManager *smgr) :
AnimatedMesh(0), B3DFile(0), VerticesStart(0), NormalsInFile(false),
HasVertexColors(false), ShowWarning(true)
B3DFile(nullptr),
VerticesStart(0),
NormalsInFile(false),
HasVertexColors(false),
ShowWarning(true)
{}
//! returns true if the file maybe is able to be loaded by this class
@@ -43,21 +46,17 @@ bool CB3DMeshFileLoader::isALoadableFileExtension(const io::path &filename) cons
IAnimatedMesh *CB3DMeshFileLoader::createMesh(io::IReadFile *file)
{
if (!file)
return 0;
return nullptr;
B3DFile = file;
AnimatedMesh = new scene::SkinnedMeshBuilder(SkinnedMesh::SourceFormat::B3D);
AnimatedMesh = scene::SkinnedMeshBuilder(SkinnedMesh::SourceFormat::B3D);
ShowWarning = true; // If true a warning is issued if too many textures are used
VerticesStart = 0;
if (load()) {
return AnimatedMesh->finalize();
} else {
AnimatedMesh->drop();
AnimatedMesh = 0;
}
if (!load())
return nullptr;
return AnimatedMesh;
return std::move(AnimatedMesh).finalize();
}
bool CB3DMeshFileLoader::load()
@@ -130,7 +129,7 @@ bool CB3DMeshFileLoader::load()
bool CB3DMeshFileLoader::readChunkNODE(SkinnedMesh::SJoint *inJoint)
{
SkinnedMesh::SJoint *joint = AnimatedMesh->addJoint(inJoint);
SkinnedMesh::SJoint *joint = AnimatedMesh.addJoint(inJoint);
joint->Name = readString();
#ifdef _B3D_READER_DEBUG
@@ -233,7 +232,7 @@ bool CB3DMeshFileLoader::readChunkMESH(SkinnedMesh::SJoint *inJoint)
if (!readChunkVRTS(inJoint))
return false;
} else if (strncmp(B3dStack.getLast().name, "TRIS", 4) == 0) {
scene::SSkinMeshBuffer *meshBuffer = AnimatedMesh->addMeshBuffer();
scene::SSkinMeshBuffer *meshBuffer = AnimatedMesh.addMeshBuffer();
if (brushID == -1) { /* ok */
} else if (brushID < 0 || (u32)brushID >= Materials.size()) {
@@ -243,7 +242,7 @@ bool CB3DMeshFileLoader::readChunkMESH(SkinnedMesh::SJoint *inJoint)
meshBuffer->Material = Materials[brushID].Material;
}
if (readChunkTRIS(meshBuffer, AnimatedMesh->getMeshBufferCount() - 1, VerticesStart) == false)
if (readChunkTRIS(meshBuffer, AnimatedMesh.getMeshBufferCount() - 1, VerticesStart) == false)
return false;
if (!NormalsInFile) {
@@ -541,11 +540,10 @@ bool CB3DMeshFileLoader::readChunkBONE(SkinnedMesh::SJoint *inJoint)
if (AnimatedVertices_VertexID[globalVertexID] == -1) {
os::Printer::log("B3dMeshLoader: Weight has bad vertex id (no link to meshbuffer index found)");
} else if (strength > 0) {
SkinnedMesh::SWeight *weight = AnimatedMesh->addWeight(inJoint);
weight->strength = strength;
// Find the meshbuffer and Vertex index from the Global Vertex ID:
weight->vertex_id = AnimatedVertices_VertexID[globalVertexID];
weight->buffer_id = AnimatedVertices_BufferID[globalVertexID];
AnimatedMesh.addWeight(inJoint,
AnimatedVertices_BufferID[globalVertexID],
AnimatedVertices_VertexID[globalVertexID],
strength);
}
}
}
@@ -591,15 +589,15 @@ bool CB3DMeshFileLoader::readChunkKEYS(SkinnedMesh::SJoint *inJoint)
f32 data[4];
if (flags & 1) {
readFloats(data, 3);
AnimatedMesh->addPositionKey(inJoint, frame - 1, {data[0], data[1], data[2]});
AnimatedMesh.addPositionKey(inJoint, frame - 1, {data[0], data[1], data[2]});
}
if (flags & 2) {
readFloats(data, 3);
AnimatedMesh->addScaleKey(inJoint, frame - 1, {data[0], data[1], data[2]});
AnimatedMesh.addScaleKey(inJoint, frame - 1, {data[0], data[1], data[2]});
}
if (flags & 4) {
readFloats(data, 4);
AnimatedMesh->addRotationKey(inJoint, frame - 1, core::quaternion(data[1], data[2], data[3], data[0]));
AnimatedMesh.addRotationKey(inJoint, frame - 1, core::quaternion(data[1], data[2], data[3], data[0]));
}
}
@@ -617,15 +615,13 @@ bool CB3DMeshFileLoader::readChunkANIM()
os::Printer::log(logStr.c_str(), ELL_DEBUG);
#endif
s32 animFlags; // not stored\used
s32 animFrames; // not stored\used
f32 animFPS; // not stored\used
s32 animFlags; // not stored/used
s32 animFrames; // not stored/used
f32 animFPS; // not stored/used
B3DFile->read(&animFlags, sizeof(s32));
B3DFile->read(&animFrames, sizeof(s32));
readFloats(&animFPS, 1);
if (animFPS > 0.f)
AnimatedMesh->setAnimationSpeed(animFPS);
os::Printer::log("FPS", io::path((double)animFPS), ELL_DEBUG);
#ifdef __BIG_ENDIAN__

View File

@@ -60,7 +60,7 @@ private:
core::array<video::S3DVertex2TCoords> BaseVertices;
SkinnedMeshBuilder *AnimatedMesh;
SkinnedMeshBuilder AnimatedMesh;
io::IReadFile *B3DFile;
// B3Ds have Vertex ID's local within the mesh I don't want this

View File

@@ -345,15 +345,14 @@ IAnimatedMesh* SelfType::createMesh(io::IReadFile* file)
const char *filename = file->getFileName().c_str();
try {
tiniergltf::GlTF model = parseGLTF(file);
irr_ptr<SkinnedMeshBuilder> mesh(new SkinnedMeshBuilder(
SkinnedMesh::SourceFormat::GLTF));
MeshExtractor extractor(std::move(model), mesh.get());
SkinnedMeshBuilder mesh(SkinnedMesh::SourceFormat::GLTF);
MeshExtractor extractor(std::move(model));
try {
extractor.load();
auto *res = extractor.load();
for (const auto &warning : extractor.getWarnings()) {
os::Printer::log(filename, warning.c_str(), ELL_WARNING);
}
return mesh.release()->finalize();
return res;
} catch (const std::runtime_error &e) {
os::Printer::log("error converting gltf to irrlicht mesh", e.what(), ELL_ERROR);
}
@@ -423,15 +422,14 @@ void SelfType::MeshExtractor::addPrimitive(
}
auto *meshbuf = new SSkinMeshBuffer(std::move(*vertices), std::move(indices));
m_irr_model->addMeshBuffer(meshbuf);
const auto meshbufNr = m_irr_model->getMeshBufferCount() - 1;
const auto meshbufNr = m_irr_model.addMeshBuffer(meshbuf);
if (primitive.material.has_value()) {
const auto &material = m_gltf_model.materials->at(*primitive.material);
if (material.pbrMetallicRoughness.has_value()) {
const auto &texture = material.pbrMetallicRoughness->baseColorTexture;
if (texture.has_value()) {
m_irr_model->setTextureSlot(meshbufNr, static_cast<u32>(texture->index));
m_irr_model.setTextureSlot(meshbufNr, static_cast<u32>(texture->index));
const auto samplerIdx = m_gltf_model.textures->at(texture->index).sampler;
if (samplerIdx.has_value()) {
auto &sampler = m_gltf_model.samplers->at(*samplerIdx);
@@ -501,10 +499,8 @@ void SelfType::MeshExtractor::addPrimitive(
if (strength <= 0)
continue; // note: also ignores negative weights
SkinnedMesh::SWeight *weight = m_irr_model->addWeight(m_loaded_nodes.at(skin.joints.at(jointIdx)));
weight->buffer_id = meshbufNr;
weight->vertex_id = v;
weight->strength = strength;
m_irr_model.addWeight(m_loaded_nodes.at(skin.joints.at(jointIdx)),
meshbufNr, v, strength);
}
}
if (negative_weights)
@@ -571,7 +567,7 @@ void SelfType::MeshExtractor::loadNode(
SkinnedMesh::SJoint *parent)
{
const auto &node = m_gltf_model.nodes->at(nodeIdx);
auto *joint = m_irr_model->addJoint(parent);
auto *joint = m_irr_model.addJoint(parent);
const core::matrix4 transform = loadTransform(node.transform, joint);
joint->GlobalMatrix = parent ? parent->GlobalMatrix * transform : transform;
if (node.name.has_value()) {
@@ -695,7 +691,7 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx)
}
}
void SelfType::MeshExtractor::load()
SkinnedMesh *SelfType::MeshExtractor::load()
{
if (m_gltf_model.extensionsRequired)
throw std::runtime_error("model requires extensions, but we support none");
@@ -723,8 +719,8 @@ void SelfType::MeshExtractor::load()
warn("multiple animations are not supported");
loadAnimation(0);
m_irr_model->setAnimationSpeed(1);
}
return std::move(m_irr_model).finalize();
} catch (const std::out_of_range &e) {
throw std::runtime_error(e.what());
} catch (const std::bad_optional_access &e) {

View File

@@ -96,9 +96,10 @@ private:
class MeshExtractor
{
public:
MeshExtractor(tiniergltf::GlTF &&model,
SkinnedMeshBuilder *mesh) noexcept
: m_gltf_model(std::move(model)), m_irr_model(mesh) {};
MeshExtractor(tiniergltf::GlTF &&model) noexcept
: m_gltf_model(std::move(model))
, m_irr_model(SkinnedMesh::SourceFormat::GLTF)
{}
/* Gets indices for the given mesh/primitive.
*
@@ -114,14 +115,14 @@ private:
std::size_t getPrimitiveCount(const std::size_t meshIdx) const;
void load();
SkinnedMesh *load();
const std::unordered_set<std::string> &getWarnings() {
return warnings;
}
private:
const tiniergltf::GlTF m_gltf_model;
SkinnedMeshBuilder *m_irr_model;
SkinnedMeshBuilder m_irr_model;
std::vector<std::function<void()>> m_mesh_loaders;
std::vector<SkinnedMesh::SJoint *> m_loaded_nodes;

View File

@@ -273,6 +273,7 @@ set(IRRMESHLOADER
add_library(IRRMESHOBJ OBJECT
CMeshSceneNode.h
WeightBuffer.cpp
SkinnedMesh.cpp
CMeshSceneNode.cpp
AnimatedMeshSceneNode.cpp

View File

@@ -110,7 +110,7 @@ void CMeshManipulator::recalculateNormals(scene::IMesh *mesh, bool smooth, bool
if (mesh->getMeshType() == EAMT_SKINNED) {
auto *smesh = (SkinnedMesh *)mesh;
smesh->refreshJointCache();
smesh->updateStaticPose();
}
}

View File

@@ -4,7 +4,6 @@
#include "CXMeshFileLoader.h"
#include "SkinnedMesh.h"
#include "Transform.h"
#include "os.h"
#include "fast_atof.h"
@@ -16,7 +15,6 @@
#ifdef _DEBUG
#define _XREADER_DEBUG
#endif
// #define BETTER_MESHBUFFER_SPLITTING_FOR_X
#define SET_ERR_AND_RETURN() \
do { \
@@ -29,8 +27,17 @@ namespace scene
//! Constructor
CXMeshFileLoader::CXMeshFileLoader(scene::ISceneManager *smgr) :
AnimatedMesh(0), Buffer(0), P(0), End(0), BinaryNumCount(0), Line(0), ErrorState(false),
CurFrame(0), MajorVersion(0), MinorVersion(0), BinaryFormat(false), FloatSize(0)
Buffer(nullptr),
P(nullptr),
End(nullptr),
BinaryNumCount(0),
Line(0),
ErrorState(false),
CurFrame(nullptr),
MajorVersion(0),
MinorVersion(0),
BinaryFormat(false),
FloatSize(0)
{}
//! returns true if the file maybe is able to be loaded by this class
@@ -47,20 +54,17 @@ bool CXMeshFileLoader::isALoadableFileExtension(const io::path &filename) const
IAnimatedMesh *CXMeshFileLoader::createMesh(io::IReadFile *file)
{
if (!file)
return 0;
return nullptr;
#ifdef _XREADER_DEBUG
u32 time = os::Timer::getRealTime();
#endif
AnimatedMesh = new SkinnedMeshBuilder(SkinnedMesh::SourceFormat::X);
AnimatedMesh = SkinnedMeshBuilder(SkinnedMesh::SourceFormat::X);
SkinnedMesh *res = nullptr;
if (load(file)) {
res = AnimatedMesh->finalize();
} else {
AnimatedMesh->drop();
AnimatedMesh = 0;
res = std::move(AnimatedMesh).finalize();
}
#ifdef _XREADER_DEBUG
time = os::Timer::getRealTime() - time;
@@ -71,7 +75,7 @@ IAnimatedMesh *CXMeshFileLoader::createMesh(io::IReadFile *file)
tmpString += "ms";
os::Printer::log(tmpString.c_str());
#endif
// Clear up
// Clean up
MajorVersion = 0;
MinorVersion = 0;
@@ -111,17 +115,15 @@ bool CXMeshFileLoader::load(io::IReadFile *file)
u32 i;
mesh->Buffers.reallocate(mesh->Materials.size());
#ifndef BETTER_MESHBUFFER_SPLITTING_FOR_X
const u32 bufferOffset = AnimatedMesh->getMeshBufferCount();
#endif
const u32 bufferOffset = AnimatedMesh.getMeshBufferCount();
for (i = 0; i < mesh->Materials.size(); ++i) {
mesh->Buffers.push_back(AnimatedMesh->addMeshBuffer());
mesh->Buffers.push_back(AnimatedMesh.addMeshBuffer());
mesh->Buffers.getLast()->Material = mesh->Materials[i];
if (!mesh->HasSkinning) {
// Set up rigid animation
if (mesh->AttachedJointID != -1) {
AnimatedMesh->getAllJoints()[mesh->AttachedJointID]->AttachedMeshes.push_back(AnimatedMesh->getMeshBufferCount() - 1);
AnimatedMesh.getJoints()[mesh->AttachedJointID]->AttachedMeshes.push_back(AnimatedMesh.getMeshBufferCount() - 1);
}
}
}
@@ -140,192 +142,103 @@ bool CXMeshFileLoader::load(io::IReadFile *file)
}
}
#ifdef BETTER_MESHBUFFER_SPLITTING_FOR_X
{
// the same vertex can be used in many different meshbuffers, but it's slow to work out
core::array<u32> verticesLinkIndex;
core::array<s16> verticesLinkBuffer;
verticesLinkBuffer.set_used(mesh->Vertices.size());
core::array<core::array<u32>> verticesLinkIndex;
verticesLinkIndex.reallocate(mesh->Vertices.size());
core::array<core::array<u16>> verticesLinkBuffer;
verticesLinkBuffer.reallocate(mesh->Vertices.size());
// init with 0
for (i = 0; i < mesh->Vertices.size(); ++i) {
// watch out for vertices which are not part of the mesh
// they will keep the -1 and can lead to out-of-bounds access
verticesLinkBuffer[i] = -1;
}
for (i = 0; i < mesh->Vertices.size(); ++i) {
verticesLinkIndex.push_back(core::array<u32>());
verticesLinkBuffer.push_back(core::array<u16>());
}
for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) {
for (u32 id = i * 3 + 0; id <= i * 3 + 2; ++id) {
core::array<u16> &Array = verticesLinkBuffer[mesh->Indices[id]];
bool found = false;
for (u32 j = 0; j < Array.size(); ++j) {
if (Array[j] == mesh->FaceMaterialIndices[i]) {
found = true;
break;
}
bool warned = false;
// store meshbuffer number per vertex
for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) {
for (u32 id = i * 3 + 0; id <= i * 3 + 2; ++id) {
if ((verticesLinkBuffer[mesh->Indices[id]] != -1) && (verticesLinkBuffer[mesh->Indices[id]] != (s16)mesh->FaceMaterialIndices[i])) {
if (!warned) {
os::Printer::log("X loader", "Duplicated vertex, animation might be corrupted.", ELL_WARNING);
warned = true;
}
if (!found)
Array.push_back(mesh->FaceMaterialIndices[i]);
const u32 tmp = mesh->Vertices.size();
mesh->Vertices.push_back(mesh->Vertices[mesh->Indices[id]]);
mesh->Indices[id] = tmp;
verticesLinkBuffer.set_used(mesh->Vertices.size());
}
verticesLinkBuffer[mesh->Indices[id]] = mesh->FaceMaterialIndices[i];
}
}
for (i = 0; i < verticesLinkBuffer.size(); ++i) {
if (!verticesLinkBuffer[i].size())
verticesLinkBuffer[i].push_back(0);
}
if (mesh->FaceMaterialIndices.size() != 0) {
// store vertices in buffers and remember relation in verticesLinkIndex
u32 *vCountArray = new u32[mesh->Buffers.size()];
memset(vCountArray, 0, mesh->Buffers.size() * sizeof(u32));
// count vertices in each buffer and reallocate
for (i = 0; i < mesh->Vertices.size(); ++i) {
core::array<u16> &Array = verticesLinkBuffer[i];
verticesLinkIndex[i].reallocate(Array.size());
for (u32 j = 0; j < Array.size(); ++j) {
scene::SSkinMeshBuffer *buffer = mesh->Buffers[Array[j]];
verticesLinkIndex[i].push_back(buffer->Vertices_Standard.size());
buffer->Vertices_Standard.push_back(mesh->Vertices[i]);
if (verticesLinkBuffer[i] != -1)
++vCountArray[verticesLinkBuffer[i]];
}
if (mesh->TCoords2.size()) {
for (i = 0; i != mesh->Buffers.size(); ++i) {
mesh->Buffers[i]->Vertices_2TCoords->Data.reserve(vCountArray[i]);
mesh->Buffers[i]->VertexType = video::EVT_2TCOORDS;
}
} else {
for (i = 0; i != mesh->Buffers.size(); ++i)
mesh->Buffers[i]->Vertices_Standard->Data.reserve(vCountArray[i]);
}
verticesLinkIndex.set_used(mesh->Vertices.size());
// actually store vertices
for (i = 0; i < mesh->Vertices.size(); ++i) {
// if a vertex is missing for some reason, just skip it
if (verticesLinkBuffer[i] == -1)
continue;
scene::SSkinMeshBuffer *buffer = mesh->Buffers[verticesLinkBuffer[i]];
if (mesh->TCoords2.size()) {
verticesLinkIndex[i] = buffer->Vertices_2TCoords->getCount();
buffer->Vertices_2TCoords->Data.emplace_back(mesh->Vertices[i]);
// We have a problem with correct tcoord2 handling here
// crash fixed for now by checking the values
buffer->Vertices_2TCoords->Data.back().TCoords2 = (i < mesh->TCoords2.size()) ? mesh->TCoords2[i] : mesh->Vertices[i].TCoords;
} else {
verticesLinkIndex[i] = buffer->Vertices_Standard->getCount();
buffer->Vertices_Standard->Data.push_back(mesh->Vertices[i]);
}
}
// count indices per buffer and reallocate
memset(vCountArray, 0, mesh->Buffers.size() * sizeof(u32));
for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i)
++vCountArray[mesh->FaceMaterialIndices[i]];
for (i = 0; i != mesh->Buffers.size(); ++i)
mesh->Buffers[i]->Indices->Data.reserve(vCountArray[i]);
delete[] vCountArray;
// create indices per buffer
for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) {
scene::SSkinMeshBuffer *buffer = mesh->Buffers[mesh->FaceMaterialIndices[i]];
for (u32 id = i * 3 + 0; id <= i * 3 + 2; ++id) {
core::array<u16> &Array = verticesLinkBuffer[mesh->Indices[id]];
for (u32 j = 0; j < Array.size(); ++j) {
if (Array[j] == mesh->FaceMaterialIndices[i])
buffer->Indices.push_back(verticesLinkIndex[mesh->Indices[id]][j]);
}
}
}
for (u32 j = 0; j < mesh->WeightJoint.size(); ++j) {
SkinnedMesh::SJoint *joint = AnimatedMesh->getAllJoints()[mesh->WeightJoint[j]];
SkinnedMesh::SWeight &weight = joint->Weights[mesh->WeightNum[j]];
u32 id = weight.vertex_id;
if (id >= verticesLinkIndex.size()) {
os::Printer::log("X loader: Weight id out of range", ELL_WARNING);
id = 0;
weight.strength = 0.f;
}
if (verticesLinkBuffer[id].size() == 1) {
weight.vertex_id = verticesLinkIndex[id][0];
weight.buffer_id = verticesLinkBuffer[id][0];
} else if (verticesLinkBuffer[id].size() != 0) {
for (u32 k = 1; k < verticesLinkBuffer[id].size(); ++k) {
SkinnedMesh::SWeight *WeightClone = AnimatedMesh->addWeight(joint);
WeightClone->strength = weight.strength;
WeightClone->vertex_id = verticesLinkIndex[id][k];
WeightClone->buffer_id = verticesLinkBuffer[id][k];
}
for (u32 id = i * 3 + 0; id != i * 3 + 3; ++id) {
buffer->Indices->Data.push_back(verticesLinkIndex[mesh->Indices[id]]);
}
}
}
#else
{
core::array<u32> verticesLinkIndex;
core::array<s16> verticesLinkBuffer;
verticesLinkBuffer.set_used(mesh->Vertices.size());
// init with 0
for (i = 0; i < mesh->Vertices.size(); ++i) {
// watch out for vertices which are not part of the mesh
// they will keep the -1 and can lead to out-of-bounds access
verticesLinkBuffer[i] = -1;
for (const auto &weight : mesh->Weights) {
u32 id = weight.global_vertex_id;
if (id >= verticesLinkIndex.size()) {
os::Printer::log("X loader: Weight id out of range", ELL_WARNING);
continue;
}
bool warned = false;
// store meshbuffer number per vertex
for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) {
for (u32 id = i * 3 + 0; id <= i * 3 + 2; ++id) {
if ((verticesLinkBuffer[mesh->Indices[id]] != -1) && (verticesLinkBuffer[mesh->Indices[id]] != (s16)mesh->FaceMaterialIndices[i])) {
if (!warned) {
os::Printer::log("X loader", "Duplicated vertex, animation might be corrupted.", ELL_WARNING);
warned = true;
}
const u32 tmp = mesh->Vertices.size();
mesh->Vertices.push_back(mesh->Vertices[mesh->Indices[id]]);
mesh->Indices[id] = tmp;
verticesLinkBuffer.set_used(mesh->Vertices.size());
}
verticesLinkBuffer[mesh->Indices[id]] = mesh->FaceMaterialIndices[i];
}
}
if (mesh->FaceMaterialIndices.size() != 0) {
// store vertices in buffers and remember relation in verticesLinkIndex
u32 *vCountArray = new u32[mesh->Buffers.size()];
memset(vCountArray, 0, mesh->Buffers.size() * sizeof(u32));
// count vertices in each buffer and reallocate
for (i = 0; i < mesh->Vertices.size(); ++i) {
if (verticesLinkBuffer[i] != -1)
++vCountArray[verticesLinkBuffer[i]];
}
if (mesh->TCoords2.size()) {
for (i = 0; i != mesh->Buffers.size(); ++i) {
mesh->Buffers[i]->Vertices_2TCoords->Data.reserve(vCountArray[i]);
mesh->Buffers[i]->VertexType = video::EVT_2TCOORDS;
}
} else {
for (i = 0; i != mesh->Buffers.size(); ++i)
mesh->Buffers[i]->Vertices_Standard->Data.reserve(vCountArray[i]);
}
verticesLinkIndex.set_used(mesh->Vertices.size());
// actually store vertices
for (i = 0; i < mesh->Vertices.size(); ++i) {
// if a vertex is missing for some reason, just skip it
if (verticesLinkBuffer[i] == -1)
continue;
scene::SSkinMeshBuffer *buffer = mesh->Buffers[verticesLinkBuffer[i]];
if (mesh->TCoords2.size()) {
verticesLinkIndex[i] = buffer->Vertices_2TCoords->getCount();
buffer->Vertices_2TCoords->Data.emplace_back(mesh->Vertices[i]);
// We have a problem with correct tcoord2 handling here
// crash fixed for now by checking the values
buffer->Vertices_2TCoords->Data.back().TCoords2 = (i < mesh->TCoords2.size()) ? mesh->TCoords2[i] : mesh->Vertices[i].TCoords;
} else {
verticesLinkIndex[i] = buffer->Vertices_Standard->getCount();
buffer->Vertices_Standard->Data.push_back(mesh->Vertices[i]);
}
}
// count indices per buffer and reallocate
memset(vCountArray, 0, mesh->Buffers.size() * sizeof(u32));
for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i)
++vCountArray[mesh->FaceMaterialIndices[i]];
for (i = 0; i != mesh->Buffers.size(); ++i)
mesh->Buffers[i]->Indices->Data.reserve(vCountArray[i]);
delete[] vCountArray;
// create indices per buffer
for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) {
scene::SSkinMeshBuffer *buffer = mesh->Buffers[mesh->FaceMaterialIndices[i]];
for (u32 id = i * 3 + 0; id != i * 3 + 3; ++id) {
buffer->Indices->Data.push_back(verticesLinkIndex[mesh->Indices[id]]);
}
}
}
for (u32 j = 0; j < mesh->WeightJoint.size(); ++j) {
SkinnedMesh::SWeight &weight = (AnimatedMesh->getAllJoints()[mesh->WeightJoint[j]]->Weights[mesh->WeightNum[j]]);
u32 id = weight.vertex_id;
if (id >= verticesLinkIndex.size()) {
os::Printer::log("X loader: Weight id out of range", ELL_WARNING);
id = 0;
weight.strength = 0.f;
}
weight.vertex_id = verticesLinkIndex[id];
weight.buffer_id = verticesLinkBuffer[id] + bufferOffset;
}
u16 buf_id = verticesLinkBuffer[id] + bufferOffset;
u32 vert_id = verticesLinkIndex[id];
auto *joint = AnimatedMesh.getJoints()[weight.joint_id];
AnimatedMesh.addWeight(joint, buf_id, vert_id, weight.strength);
}
#endif
}
return true;
@@ -508,10 +421,10 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
SkinnedMesh::SJoint *joint = 0;
if (name.size()) {
auto n = AnimatedMesh->getJointNumber(name.c_str());
auto n = AnimatedMesh.getJointNumber(name.c_str());
if (n.has_value()) {
JointID = *n;
joint = AnimatedMesh->getAllJoints()[JointID];
joint = AnimatedMesh.getJoints()[JointID];
joint->setParent(Parent);
}
}
@@ -520,9 +433,9 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
#ifdef _XREADER_DEBUG
os::Printer::log("creating joint ", name.c_str(), ELL_DEBUG);
#endif
joint = AnimatedMesh->addJoint(Parent);
joint = AnimatedMesh.addJoint(Parent);
joint->Name = name.c_str();
JointID = AnimatedMesh->getAllJoints().size() - 1;
JointID = AnimatedMesh.getJoints().size() - 1;
} else {
#ifdef _XREADER_DEBUG
os::Printer::log("using joint ", name.c_str(), ELL_DEBUG);
@@ -940,46 +853,34 @@ bool CXMeshFileLoader::parseDataObjectSkinWeights(SXMesh &mesh)
mesh.HasSkinning = true;
auto n = AnimatedMesh->getJointNumber(TransformNodeName.c_str());
SkinnedMesh::SJoint *joint = n.has_value() ? AnimatedMesh->getAllJoints()[*n] : nullptr;
auto joint_id = AnimatedMesh.getJointNumber(TransformNodeName.c_str());
SkinnedMesh::SJoint *joint = joint_id.has_value() ? AnimatedMesh.getJoints()[*joint_id] : nullptr;
if (!joint) {
#ifdef _XREADER_DEBUG
os::Printer::log("creating joint for skinning ", TransformNodeName.c_str(), ELL_DEBUG);
#endif
n = AnimatedMesh->getAllJoints().size();
joint = AnimatedMesh->addJoint(0);
joint = AnimatedMesh.addJoint(nullptr);
joint->Name = TransformNodeName.c_str();
joint_id = joint->JointID;
}
// read vertex weights
const u32 nWeights = readInt();
// read vertex indices
u32 i;
mesh.Weights.reserve(mesh.Weights.size() + nWeights);
const u32 jointStart = joint->Weights.size();
joint->Weights.reserve(jointStart + nWeights);
std::vector<u32> vertex_ids;
vertex_ids.reserve(nWeights);
for (u32 i = 0; i < nWeights; ++i)
vertex_ids.push_back(readInt());
mesh.WeightJoint.reallocate(mesh.WeightJoint.size() + nWeights);
mesh.WeightNum.reallocate(mesh.WeightNum.size() + nWeights);
for (i = 0; i < nWeights; ++i) {
mesh.WeightJoint.push_back(*n);
mesh.WeightNum.push_back(joint->Weights.size()); // id of weight
// Note: This adds a weight to joint->Weights
SkinnedMesh::SWeight *weight = AnimatedMesh->addWeight(joint);
weight->buffer_id = 0;
weight->vertex_id = readInt();
for (u32 i = 0; i < nWeights; ++i) {
f32 strength = readFloat();
mesh.Weights.emplace_back(SXMesh::Weight{
(u16) *joint_id, vertex_ids[i], strength});
}
// read vertex weights
for (i = jointStart; i < jointStart + nWeights; ++i)
joint->Weights[i].strength = readFloat();
// read matrix offset
// transforms the mesh vertices to the space of the bone
@@ -1320,7 +1221,7 @@ bool CXMeshFileLoader::parseDataObjectAnimationTicksPerSecond()
SET_ERR_AND_RETURN();
}
const u32 ticks = readInt();
static_cast<void>(readInt());
if (!checkForOneFollowingSemicolons()) {
os::Printer::log("No closing semicolon in AnimationTicksPerSecond in x file", ELL_WARNING);
@@ -1334,8 +1235,6 @@ bool CXMeshFileLoader::parseDataObjectAnimationTicksPerSecond()
SET_ERR_AND_RETURN();
}
AnimatedMesh->setAnimationSpeed(static_cast<f32>(ticks));
return true;
}
@@ -1393,16 +1292,16 @@ bool CXMeshFileLoader::parseDataObjectAnimation()
#ifdef _XREADER_DEBUG
os::Printer::log("frame name", FrameName.c_str(), ELL_DEBUG);
#endif
auto n = AnimatedMesh->getJointNumber(FrameName.c_str());
auto joint_id = AnimatedMesh.getJointNumber(FrameName.c_str());
SkinnedMesh::SJoint *joint;
if (n.has_value()) {
joint = AnimatedMesh->getAllJoints()[*n];
if (joint_id.has_value()) {
joint = AnimatedMesh.getJoints()[*joint_id];
} else {
#ifdef _XREADER_DEBUG
os::Printer::log("creating joint for animation ", FrameName.c_str(), ELL_DEBUG);
#endif
joint = AnimatedMesh->addJoint(0);
joint = AnimatedMesh.addJoint();
joint->Name = FrameName.c_str();
}
@@ -1472,7 +1371,7 @@ bool CXMeshFileLoader::parseDataObjectAnimationKey(SkinnedMesh::SJoint *joint)
core::quaternion rotation(X, Y, Z, W);
rotation.normalize();
AnimatedMesh->addRotationKey(joint, time, rotation);
AnimatedMesh.addRotationKey(joint, time, rotation);
} break;
case 1: // scale
case 2: // position
@@ -1495,9 +1394,9 @@ bool CXMeshFileLoader::parseDataObjectAnimationKey(SkinnedMesh::SJoint *joint)
}
if (keyType == 2) {
AnimatedMesh->addPositionKey(joint, time, vector);
AnimatedMesh.addPositionKey(joint, time, vector);
} else {
AnimatedMesh->addScaleKey(joint, time, vector);
AnimatedMesh.addScaleKey(joint, time, vector);
}
} break;
case 3:
@@ -1522,8 +1421,8 @@ bool CXMeshFileLoader::parseDataObjectAnimationKey(SkinnedMesh::SJoint *joint)
os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING);
}
AnimatedMesh->addRotationKey(joint, time, core::quaternion(mat.getTransposed()));
AnimatedMesh->addPositionKey(joint, time, mat.getTranslation());
AnimatedMesh.addRotationKey(joint, time, core::quaternion(mat.getTransposed()));
AnimatedMesh.addPositionKey(joint, time, mat.getTranslation());
/*
core::vector3df scale=mat.getScale();

View File

@@ -63,8 +63,12 @@ public:
core::array<video::SMaterial> Materials; // material array
core::array<u32> WeightJoint;
core::array<u32> WeightNum;
struct Weight {
u16 joint_id;
u32 global_vertex_id;
f32 strength;
};
std::vector<Weight> Weights;
s32 AttachedJointID;
@@ -152,7 +156,7 @@ private:
bool readRGB(video::SColor &color);
bool readRGBA(video::SColor &color);
SkinnedMeshBuilder *AnimatedMesh;
SkinnedMeshBuilder AnimatedMesh;
c8 *Buffer;
const c8 *P;

View File

@@ -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()

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