1
0
mirror of https://github.com/luanti-org/luanti.git synced 2025-11-28 11:45:29 +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