From e7f305fedd87085d217d6f904ebfcfe6c04daf25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Sun, 23 Nov 2025 21:17:58 +0100 Subject: [PATCH] Refactor skinned mesh weight data structure (#16655) --- irr/include/IAnimatedMesh.h | 13 - irr/include/SMesh.h | 2 - irr/include/SSkinMeshBuffer.h | 5 + irr/include/SkinnedMesh.h | 120 ++++--- irr/include/WeightBuffer.h | 67 ++++ irr/src/AnimatedMeshSceneNode.cpp | 4 - irr/src/CB3DMeshFileLoader.cpp | 50 ++- irr/src/CB3DMeshFileLoader.h | 2 +- irr/src/CGLTFMeshFileLoader.cpp | 26 +- irr/src/CGLTFMeshFileLoader.h | 11 +- irr/src/CMakeLists.txt | 1 + irr/src/CMeshManipulator.cpp | 2 +- irr/src/CXMeshFileLoader.cpp | 349 ++++++++------------- irr/src/CXMeshFileLoader.h | 10 +- irr/src/SkinnedMesh.cpp | 349 +++++++-------------- irr/src/WeightBuffer.cpp | 124 ++++++++ src/unittest/test_irr_gltf_mesh_loader.cpp | 72 +++-- 17 files changed, 584 insertions(+), 623 deletions(-) create mode 100644 irr/include/WeightBuffer.h create mode 100644 irr/src/WeightBuffer.cpp diff --git a/irr/include/IAnimatedMesh.h b/irr/include/IAnimatedMesh.h index 36bf97dd65..af08b31e63 100644 --- a/irr/include/IAnimatedMesh.h +++ b/irr/include/IAnimatedMesh.h @@ -19,19 +19,6 @@ public: //! Gets the maximum frame number, 0 if the mesh is static. virtual f32 getMaxFrameNumber() const = 0; - //! Gets the animation speed of the animated mesh. - /** \return The number of frames per second to play the - animation with by default. If the amount is 0, - it is a static, non animated mesh. */ - virtual f32 getAnimationSpeed() const = 0; - - //! Sets the animation speed of the animated mesh. - /** \param fps Number of frames per second to play the - animation with by default. If the amount is 0, - it is not animated. The actual speed is set in the - scene node the mesh is instantiated in.*/ - virtual void setAnimationSpeed(f32 fps) = 0; - //! Returns the type of the animated mesh. Useful for safe downcasts. E_ANIMATED_MESH_TYPE getMeshType() const = 0; }; diff --git a/irr/include/SMesh.h b/irr/include/SMesh.h index f180922bf5..8ce9b8223b 100644 --- a/irr/include/SMesh.h +++ b/irr/include/SMesh.h @@ -138,8 +138,6 @@ struct SMesh final : public IAnimatedMesh // with all the animation-related parts behind an optional. virtual f32 getMaxFrameNumber() const override { return 0.0f; } - virtual f32 getAnimationSpeed() const override { return 0.0f; } - virtual void setAnimationSpeed(f32 fps) override {} E_ANIMATED_MESH_TYPE getMeshType() const override { return EAMT_STATIC; } }; diff --git a/irr/include/SSkinMeshBuffer.h b/irr/include/SSkinMeshBuffer.h index 72a3d3caed..8f7f84e47b 100644 --- a/irr/include/SSkinMeshBuffer.h +++ b/irr/include/SSkinMeshBuffer.h @@ -7,7 +7,10 @@ #include "IMeshBuffer.h" #include "CVertexBuffer.h" #include "CIndexBuffer.h" +#include "WeightBuffer.h" +#include "IVertexBuffer.h" #include "S3DVertex.h" +#include "vector3d.h" #include namespace scene @@ -222,6 +225,8 @@ public: SVertexBuffer *Vertices_Standard; SIndexBuffer *Indices; + std::optional Weights; + core::matrix4 Transformation; video::SMaterial Material; diff --git a/irr/include/SkinnedMesh.h b/irr/include/SkinnedMesh.h index 91ecda7212..7995084321 100644 --- a/irr/include/SkinnedMesh.h +++ b/irr/include/SkinnedMesh.h @@ -11,6 +11,7 @@ #include "aabbox3d.h" #include "irrMath.h" #include "irrTypes.h" +#include "irr_ptr.h" #include "matrix4.h" #include "quaternion.h" #include "vector3d.h" @@ -43,7 +44,6 @@ public: SkinnedMesh(SourceFormat src_format) : EndFrame(0.f), FramesPerSecond(25.f), HasAnimation(false), PreparedForSkinning(false), - AnimateNormals(true), SrcFormat(src_format) { SkinningBuffers = &LocalBuffers; @@ -59,15 +59,6 @@ public: //! If the duration is 0, it is a static (=non animated) mesh. f32 getMaxFrameNumber() const override; - //! 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 getAnimationSpeed() const override; - - //! 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 setAnimationSpeed(f32 fps) override; - //! Turns the given array of local matrices into an array of global matrices //! by multiplying with respective parent matrices. void calculateGlobalMatrices(std::vector &matrices) const; @@ -89,8 +80,6 @@ public: u32 getTextureSlot(u32 meshbufNr) const override; - void setTextureSlot(u32 meshbufNr, u32 textureSlot); - //! Returns bounding box of the mesh *in static pose*. const core::aabbox3d &getBoundingBox() const override { // TODO ideally we shouldn't be forced to implement this @@ -126,14 +115,6 @@ public: \return Number of the joint or std::nullopt if not found. */ std::optional getJointNumber(const std::string &name) const; - //! Update Normals when Animating - /** \param on If false don't animate, which is faster. - Else update normals, which allows for proper lighting of - animated meshes (default). */ - void updateNormalsWhenAnimating(bool on) { - AnimateNormals = on; - } - //! converts the vertex type of all meshbuffers to tangents. /** E.g. used for bump mapping. */ void convertMeshToTangents(); @@ -143,8 +124,8 @@ public: return !HasAnimation; } - //! Refreshes vertex data cached in joints such as positions and normals - void refreshJointCache(); + //! Back up static pose after local buffers have been modified directly + void updateStaticPose(); //! Moves the mesh into static position. void resetAnimation(); @@ -153,26 +134,6 @@ public: std::vector addJoints( AnimatedMeshSceneNode *node, ISceneManager *smgr); - //! A vertex weight - struct SWeight - { - //! Index of the mesh buffer - u16 buffer_id; // I doubt 32bits is needed - - //! Index of the vertex - u32 vertex_id; // Store global ID here - - //! Weight Strength/Percentage (0-1) - f32 strength; - - private: - //! Internal members used by SkinnedMesh - friend class SkinnedMesh; - char *Moved; - core::vector3df StaticPos; - core::vector3df StaticNormal; - }; - template struct Channel { struct Frame { @@ -329,13 +290,11 @@ public: //! List of attached meshes std::vector AttachedMeshes; + // TODO ^ should turn this into optional meshbuffer parent field? // Animation keyframes for translation, rotation, scale Keys keys; - //! Skin weights - std::vector Weights; - //! Bounding box of all affected vertices, in local space core::aabbox3df LocalBoundingBox{{0, 0, 0}}; @@ -369,34 +328,29 @@ public: protected: bool checkForAnimation() const; - void topoSortJoints(); - void prepareForSkinning(); void calculateStaticBoundingBox(); void calculateJointBoundingBoxes(); void calculateBufferBoundingBoxes(); - void normalizeWeights(); - void calculateTangents(core::vector3df &normal, core::vector3df &tangent, core::vector3df &binormal, const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3, const core::vector2df &tc1, const core::vector2df &tc2, const core::vector2df &tc3); + friend class SkinnedMeshBuilder; + std::vector *SkinningBuffers; // Meshbuffer to skin, default is to skin localBuffers std::vector LocalBuffers; + //! Mapping from meshbuffer number to bindable texture slot std::vector TextureSlots; //! Joints, topologically sorted (parents come before their children). std::vector AllJoints; - // bool can't be used here because std::vector - // doesn't allow taking a reference to individual elements. - std::vector> Vertices_Moved; - //! Bounding box of just the static parts of the mesh core::aabbox3df StaticPartsBox{{0, 0, 0}}; @@ -408,40 +362,76 @@ protected: bool HasAnimation; bool PreparedForSkinning; - bool AnimateNormals; SourceFormat SrcFormat; }; // Interface for mesh loaders -class SkinnedMeshBuilder : public SkinnedMesh { +class SkinnedMeshBuilder { + using SJoint = SkinnedMesh::SJoint; + public: - SkinnedMeshBuilder(SourceFormat src_format) : SkinnedMesh(src_format) {} + + // HACK the .x and .b3d loader do not separate the "loader" class from an "extractor" class + // used and destroyed in a specific loading process (contrast with the .gltf mesh loader). + // This means we need an empty skinned mesh builder. + SkinnedMeshBuilder() {} + + SkinnedMeshBuilder(SkinnedMesh::SourceFormat src_format) + : mesh(new SkinnedMesh(src_format)) + {} //! loaders should call this after populating the mesh - // returns *this, so do not try to drop the mesh builder instance - SkinnedMesh *finalize(); + SkinnedMesh *finalize() &&; //! alternative method for adding joints - std::vector &getAllJoints() { - return AllJoints; - } + std::vector &getJoints() { return mesh->AllJoints; } //! Adds a new meshbuffer to the mesh, access it as last one SSkinMeshBuffer *addMeshBuffer(); - //! Adds a new meshbuffer to the mesh, access it as last one - void addMeshBuffer(SSkinMeshBuffer *meshbuf); + //! Adds a new meshbuffer to the mesh, returns ID + u32 addMeshBuffer(SSkinMeshBuffer *meshbuf); + + u32 getMeshBufferCount() { return mesh->getMeshBufferCount(); } + + void setTextureSlot(u32 meshbufNr, u32 textureSlot) + { + mesh->TextureSlots.at(meshbufNr) = textureSlot; + } //! Adds a new joint to the mesh, access it as last one SJoint *addJoint(SJoint *parent = nullptr); + std::optional getJointNumber(const std::string &name) const + { + return mesh->getJointNumber(name); + } + void addPositionKey(SJoint *joint, f32 frame, core::vector3df pos); void addRotationKey(SJoint *joint, f32 frame, core::quaternion rotation); void addScaleKey(SJoint *joint, f32 frame, core::vector3df scale); - //! Adds a new weight to the mesh, access it as last one - SWeight *addWeight(SJoint *joint); + //! Adds a new weight to the mesh + void addWeight(SJoint *joint, u16 buf, u32 vert_id, f32 strength); + +private: + + void topoSortJoints(); + + //! The mesh that is being built + irr_ptr mesh; + + struct Weight { + u16 joint_id; + u16 buffer_id; + u32 vertex_id; + f32 strength; + }; + + //! Weights to be added once all mesh buffers have been loaded + std::vector weights; + }; } // end namespace scene diff --git a/irr/include/WeightBuffer.h b/irr/include/WeightBuffer.h new file mode 100644 index 0000000000..073a73987b --- /dev/null +++ b/irr/include/WeightBuffer.h @@ -0,0 +1,67 @@ +// Copyright (C) 2025 Lars Müller +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#pragma once + +#include "vector3d.h" +#include "matrix4.h" +#include "IVertexBuffer.h" + +#include +#include +#include + +namespace scene +{ + +struct WeightBuffer +{ + constexpr static u16 MAX_WEIGHTS_PER_VERTEX = 4; + // ID-weight pairs for a joint + struct VertexWeights { + std::array joint_ids = {}; + std::array weights = {}; + void addWeight(u16 joint_id, f32 weight); + /// Transform given position and normal with these weights + void skinVertex(core::vector3df &pos, core::vector3df &normal, + const std::vector &joint_transforms) const; + }; + std::vector weights; + + std::optional> animated_vertices; + + // A bit of a hack for now: Store static positions here so we can use them for skinning. + // Ideally we might want a design where we do not mutate the original vertex buffer at all. + std::unique_ptr static_positions; + std::unique_ptr static_normals; + + WeightBuffer(size_t n_verts) : weights(n_verts) {} + + const std::array &getJointIds(u32 vertex_id) const + { return weights[vertex_id].joint_ids; } + + const std::array &getWeights(u32 vertex_id) const + { return weights[vertex_id].weights; } + + size_t size() const + { return weights.size(); } + + void addWeight(u32 vertex_id, u16 joint_id, f32 weight); + + /// Transform position and normal using the weights of the given vertex + void skinVertex(u32 vertex_id, core::vector3df &pos, core::vector3df &normal, + const std::vector &joint_transforms) const; + + /// @note src and dst can be the same buffer + void skin(IVertexBuffer *dst, + const std::vector &joint_transforms) const; + + /// Prepares this buffer for use in skinning. + void finalize(); + + void updateStaticPose(const IVertexBuffer *vbuf); + + void resetToStatic(IVertexBuffer *vbuf) const; +}; + +} // end namespace scene diff --git a/irr/src/AnimatedMeshSceneNode.cpp b/irr/src/AnimatedMeshSceneNode.cpp index 219adba5e5..6c69e3df05 100644 --- a/irr/src/AnimatedMeshSceneNode.cpp +++ b/irr/src/AnimatedMeshSceneNode.cpp @@ -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 diff --git a/irr/src/CB3DMeshFileLoader.cpp b/irr/src/CB3DMeshFileLoader.cpp index d55ba753d3..d79702a030 100644 --- a/irr/src/CB3DMeshFileLoader.cpp +++ b/irr/src/CB3DMeshFileLoader.cpp @@ -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__ diff --git a/irr/src/CB3DMeshFileLoader.h b/irr/src/CB3DMeshFileLoader.h index 59dc61c968..4ae516943e 100644 --- a/irr/src/CB3DMeshFileLoader.h +++ b/irr/src/CB3DMeshFileLoader.h @@ -60,7 +60,7 @@ private: core::array BaseVertices; - SkinnedMeshBuilder *AnimatedMesh; + SkinnedMeshBuilder AnimatedMesh; io::IReadFile *B3DFile; // B3Ds have Vertex ID's local within the mesh I don't want this diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index 238be28d8e..8fc6bd6b63 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -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 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(texture->index)); + m_irr_model.setTextureSlot(meshbufNr, static_cast(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) { diff --git a/irr/src/CGLTFMeshFileLoader.h b/irr/src/CGLTFMeshFileLoader.h index c15307b220..f82f96de5a 100644 --- a/irr/src/CGLTFMeshFileLoader.h +++ b/irr/src/CGLTFMeshFileLoader.h @@ -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 &getWarnings() { return warnings; } private: const tiniergltf::GlTF m_gltf_model; - SkinnedMeshBuilder *m_irr_model; + SkinnedMeshBuilder m_irr_model; std::vector> m_mesh_loaders; std::vector m_loaded_nodes; diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index 4a6eba525e..9b30beb663 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -273,6 +273,7 @@ set(IRRMESHLOADER add_library(IRRMESHOBJ OBJECT CMeshSceneNode.h + WeightBuffer.cpp SkinnedMesh.cpp CMeshSceneNode.cpp AnimatedMeshSceneNode.cpp diff --git a/irr/src/CMeshManipulator.cpp b/irr/src/CMeshManipulator.cpp index edb787f397..91e52c02c8 100644 --- a/irr/src/CMeshManipulator.cpp +++ b/irr/src/CMeshManipulator.cpp @@ -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(); } } diff --git a/irr/src/CXMeshFileLoader.cpp b/irr/src/CXMeshFileLoader.cpp index b5dea4c7cc..43f61fa22f 100644 --- a/irr/src/CXMeshFileLoader.cpp +++ b/irr/src/CXMeshFileLoader.cpp @@ -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 verticesLinkIndex; + core::array verticesLinkBuffer; + verticesLinkBuffer.set_used(mesh->Vertices.size()); - core::array> verticesLinkIndex; - verticesLinkIndex.reallocate(mesh->Vertices.size()); - core::array> 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()); - verticesLinkBuffer.push_back(core::array()); - } - - for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) { - for (u32 id = i * 3 + 0; id <= i * 3 + 2; ++id) { - core::array &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 &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 &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 verticesLinkIndex; - core::array 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 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(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(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(); diff --git a/irr/src/CXMeshFileLoader.h b/irr/src/CXMeshFileLoader.h index 268a2ee3bb..2237c0f6dd 100644 --- a/irr/src/CXMeshFileLoader.h +++ b/irr/src/CXMeshFileLoader.h @@ -63,8 +63,12 @@ public: core::array Materials; // material array - core::array WeightJoint; - core::array WeightNum; + struct Weight { + u16 joint_id; + u32 global_vertex_id; + f32 strength; + }; + std::vector 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; diff --git a/irr/src/SkinnedMesh.cpp b/irr/src/SkinnedMesh.cpp index b03e0c2746..ab6791a296 100644 --- a/irr/src/SkinnedMesh.cpp +++ b/irr/src/SkinnedMesh.cpp @@ -14,6 +14,7 @@ #include #include #include +#include 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 &global_matrices) } } - // clear skinning helper array - for (std::vector &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 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> animated(getMeshBufferCount()); - for (u32 mb = 0; mb < getMeshBufferCount(); mb++) - animated[mb] = std::vector(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 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> 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 new_to_old_id; std::vector> 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 joints(n); + std::vector 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 matrices; - matrices.reserve(AllJoints.size()); - for (auto *joint : AllJoints) { + matrices.reserve(getJoints().size()); + for (auto *joint : getJoints()) { if (const auto *matrix = std::get_if(&joint->transform)) matrices.push_back(*matrix); else matrices.push_back(std::get(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> 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() diff --git a/irr/src/WeightBuffer.cpp b/irr/src/WeightBuffer.cpp new file mode 100644 index 0000000000..0595f67c4f --- /dev/null +++ b/irr/src/WeightBuffer.cpp @@ -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 +#include + +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 &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 &joint_transforms) const +{ + return weights[vertex_id].skinVertex(pos, normal, joint_transforms); +} + +void WeightBuffer::skin(IVertexBuffer *dst, + const std::vector &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(animated_vertices->size()); + if (!static_positions) + static_positions = std::make_unique(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 diff --git a/src/unittest/test_irr_gltf_mesh_loader.cpp b/src/unittest/test_irr_gltf_mesh_loader.cpp index 8a14dc32ad..2aaeb55540 100644 --- a/src/unittest/test_irr_gltf_mesh_loader.cpp +++ b/src/unittest/test_irr_gltf_mesh_loader.cpp @@ -1,4 +1,4 @@ -// Minetest +// Luanti // SPDX-License-Identifier: LGPL-2.1-or-later #include "content/subgames.h" @@ -13,6 +13,7 @@ #include "IReadFile.h" #include "ISceneManager.h" #include "SkinnedMesh.h" +#include "SSkinMeshBuffer.h" #include "irrlicht.h" #include "catch.h" @@ -394,7 +395,7 @@ SECTION("simple skin") const auto joints = csm->getAllJoints(); REQUIRE(joints.size() == 3); - const auto findJoint = [&](const std::function &predicate) { + const auto find_joint = [&](const std::function &predicate) { for (const auto *joint : joints) { if (predicate(joint)) { return joint; @@ -404,7 +405,7 @@ SECTION("simple skin") }; // Check the node hierarchy - const auto child = findJoint([&](auto *joint) { + const auto child = find_joint([&](auto *joint) { return !!joint->ParentJointID; }); const auto *parent = joints.at(*child->ParentJointID); @@ -430,46 +431,51 @@ SECTION("simple skin") } } + const auto weights = [&](const SkinnedMesh::SJoint *joint) { + // Find the mesh buffer and search the weights + const auto *buf = dynamic_cast(mesh->getMeshBuffer(0)); + REQUIRE(buf); + REQUIRE(buf->Weights.has_value()); + std::vector weights(buf->getVertexCount(), 0.0f); + for (size_t i = 0; i < buf->getVertexCount(); ++i) { + const auto &joint_ids = buf->Weights->getJointIds(i); + const auto it = std::find(joint_ids.begin(), joint_ids.end(), joint->JointID); + if (it == joint_ids.end()) + continue; + weights[i] = buf->Weights->getWeights(i)[std::distance(joint_ids.begin(), it)]; + } + return weights; + }; + SECTION("weights are correct") { - const auto weights = [&](const SkinnedMesh::SJoint *joint) { - std::unordered_map weights; - for (std::size_t i = 0; i < joint->Weights.size(); ++i) { - const auto weight = joint->Weights[i]; - REQUIRE(weight.buffer_id == 0); - weights[weight.vertex_id] = weight.strength; - } - return weights; - }; - const auto parentWeights = weights(parent); - const auto childWeights = weights(child); + const auto parent_weights = weights(parent); + const auto child_weights = weights(child); - const auto checkWeights = [&](u32 index, f32 parentWeight, f32 childWeight) { - const auto getWeight = [](auto weights, auto index) { - const auto it = weights.find(index); - return it == weights.end() ? 0.0f : it->second; - }; - CHECK(getWeight(parentWeights, index) == parentWeight); - CHECK(getWeight(childWeights, index) == childWeight); + const auto check_weights = [&](u32 vert_idx, f32 parent_weight, f32 child_weight) { + CHECK(parent_weights[vert_idx] == parent_weight); + CHECK(child_weights[vert_idx] == child_weight); }; - checkWeights(0, 1.00, 0.00); - checkWeights(1, 1.00, 0.00); - checkWeights(2, 0.75, 0.25); - checkWeights(3, 0.75, 0.25); - checkWeights(4, 0.50, 0.50); - checkWeights(5, 0.50, 0.50); - checkWeights(6, 0.25, 0.75); - checkWeights(7, 0.25, 0.75); - checkWeights(8, 0.00, 1.00); - checkWeights(9, 0.00, 1.00); + check_weights(0, 1.00, 0.00); + check_weights(1, 1.00, 0.00); + check_weights(2, 0.75, 0.25); + check_weights(3, 0.75, 0.25); + check_weights(4, 0.50, 0.50); + check_weights(5, 0.50, 0.50); + check_weights(6, 0.25, 0.75); + check_weights(7, 0.25, 0.75); + check_weights(8, 0.00, 1.00); + check_weights(9, 0.00, 1.00); } SECTION("there should be a third node not involved in skinning") { - const auto other = findJoint([&](auto joint) { + const auto other = find_joint([&](auto joint) { return joint != child && joint != parent; }); - CHECK(other->Weights.empty()); + const auto other_weights = weights(other); + CHECK(std::all_of(other_weights.begin(), other_weights.end(), + [](f32 weight) { return weight == 0.0f; })); } }