1
0
mirror of https://github.com/luanti-org/luanti.git synced 2025-11-24 18:15:26 +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

@@ -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;
};

View File

@@ -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; }
};

View File

@@ -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 <cassert>
namespace scene
@@ -222,6 +225,8 @@ public:
SVertexBuffer *Vertices_Standard;
SIndexBuffer *Indices;
std::optional<WeightBuffer> Weights;
core::matrix4 Transformation;
video::SMaterial Material;

View File

@@ -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<core::matrix4> &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<f32> &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<u32> 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<IBoneSceneNode *> 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 <class T>
struct Channel {
struct Frame {
@@ -329,13 +290,11 @@ public:
//! List of attached meshes
std::vector<u32> AttachedMeshes;
// TODO ^ should turn this into optional meshbuffer parent field?
// Animation keyframes for translation, rotation, scale
Keys keys;
//! Skin weights
std::vector<SWeight> 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<SSkinMeshBuffer *> *SkinningBuffers; // Meshbuffer to skin, default is to skin localBuffers
std::vector<SSkinMeshBuffer *> LocalBuffers;
//! Mapping from meshbuffer number to bindable texture slot
std::vector<u32> TextureSlots;
//! Joints, topologically sorted (parents come before their children).
std::vector<SJoint *> AllJoints;
// bool can't be used here because std::vector<bool>
// doesn't allow taking a reference to individual elements.
std::vector<std::vector<char>> 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<SJoint *> &getAllJoints() {
return AllJoints;
}
std::vector<SJoint *> &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<u32> 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<SkinnedMesh> 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<Weight> weights;
};
} // end namespace scene

View File

@@ -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 <cassert>
#include <memory>
#include <optional>
namespace scene
{
struct WeightBuffer
{
constexpr static u16 MAX_WEIGHTS_PER_VERTEX = 4;
// ID-weight pairs for a joint
struct VertexWeights {
std::array<u16, MAX_WEIGHTS_PER_VERTEX> joint_ids = {};
std::array<f32, MAX_WEIGHTS_PER_VERTEX> 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<core::matrix4> &joint_transforms) const;
};
std::vector<VertexWeights> weights;
std::optional<std::vector<u32>> 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<core::vector3df[]> static_positions;
std::unique_ptr<core::vector3df[]> static_normals;
WeightBuffer(size_t n_verts) : weights(n_verts) {}
const std::array<u16, MAX_WEIGHTS_PER_VERTEX> &getJointIds(u32 vertex_id) const
{ return weights[vertex_id].joint_ids; }
const std::array<f32, MAX_WEIGHTS_PER_VERTEX> &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<core::matrix4> &joint_transforms) const;
/// @note src and dst can be the same buffer
void skin(IVertexBuffer *dst,
const std::vector<core::matrix4> &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

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

View File

@@ -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<bool(const SkinnedMesh::SJoint*)> &predicate) {
const auto find_joint = [&](const std::function<bool(const SkinnedMesh::SJoint*)> &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<scene::SSkinMeshBuffer *>(mesh->getMeshBuffer(0));
REQUIRE(buf);
REQUIRE(buf->Weights.has_value());
std::vector<f32> 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<u32, f32> 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; }));
}
}