Skip to content

Commit e7f305f

Browse files
authored
Refactor skinned mesh weight data structure (#16655)
1 parent 05f161c commit e7f305f

17 files changed

+581
-620
lines changed

irr/include/IAnimatedMesh.h

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,6 @@ class IAnimatedMesh : public IMesh
1919
//! Gets the maximum frame number, 0 if the mesh is static.
2020
virtual f32 getMaxFrameNumber() const = 0;
2121

22-
//! Gets the animation speed of the animated mesh.
23-
/** \return The number of frames per second to play the
24-
animation with by default. If the amount is 0,
25-
it is a static, non animated mesh. */
26-
virtual f32 getAnimationSpeed() const = 0;
27-
28-
//! Sets the animation speed of the animated mesh.
29-
/** \param fps Number of frames per second to play the
30-
animation with by default. If the amount is 0,
31-
it is not animated. The actual speed is set in the
32-
scene node the mesh is instantiated in.*/
33-
virtual void setAnimationSpeed(f32 fps) = 0;
34-
3522
//! Returns the type of the animated mesh. Useful for safe downcasts.
3623
E_ANIMATED_MESH_TYPE getMeshType() const = 0;
3724
};

irr/include/SMesh.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,6 @@ struct SMesh final : public IAnimatedMesh
138138
// with all the animation-related parts behind an optional.
139139

140140
virtual f32 getMaxFrameNumber() const override { return 0.0f; }
141-
virtual f32 getAnimationSpeed() const override { return 0.0f; }
142-
virtual void setAnimationSpeed(f32 fps) override {}
143141
E_ANIMATED_MESH_TYPE getMeshType() const override { return EAMT_STATIC; }
144142
};
145143

irr/include/SSkinMeshBuffer.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
#include "IMeshBuffer.h"
88
#include "CVertexBuffer.h"
99
#include "CIndexBuffer.h"
10+
#include "WeightBuffer.h"
11+
#include "IVertexBuffer.h"
1012
#include "S3DVertex.h"
13+
#include "vector3d.h"
1114
#include <cassert>
1215

1316
namespace scene
@@ -222,6 +225,8 @@ struct SSkinMeshBuffer final : public IMeshBuffer
222225
SVertexBuffer *Vertices_Standard;
223226
SIndexBuffer *Indices;
224227

228+
std::optional<WeightBuffer> Weights;
229+
225230
core::matrix4 Transformation;
226231

227232
video::SMaterial Material;

irr/include/SkinnedMesh.h

Lines changed: 55 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "aabbox3d.h"
1212
#include "irrMath.h"
1313
#include "irrTypes.h"
14+
#include "irr_ptr.h"
1415
#include "matrix4.h"
1516
#include "quaternion.h"
1617
#include "vector3d.h"
@@ -43,7 +44,6 @@ class SkinnedMesh : public IAnimatedMesh
4344
SkinnedMesh(SourceFormat src_format) :
4445
EndFrame(0.f), FramesPerSecond(25.f),
4546
HasAnimation(false), PreparedForSkinning(false),
46-
AnimateNormals(true),
4747
SrcFormat(src_format)
4848
{
4949
SkinningBuffers = &LocalBuffers;
@@ -59,15 +59,6 @@ class SkinnedMesh : public IAnimatedMesh
5959
//! If the duration is 0, it is a static (=non animated) mesh.
6060
f32 getMaxFrameNumber() const override;
6161

62-
//! Gets the default animation speed of the animated mesh.
63-
/** \return Amount of frames per second. If the amount is 0, it is a static, non animated mesh. */
64-
f32 getAnimationSpeed() const override;
65-
66-
//! Gets the frame count of the animated mesh.
67-
/** \param fps Frames per second to play the animation with. If the amount is 0, it is not animated.
68-
The actual speed is set in the scene node the mesh is instantiated in.*/
69-
void setAnimationSpeed(f32 fps) override;
70-
7162
//! Turns the given array of local matrices into an array of global matrices
7263
//! by multiplying with respective parent matrices.
7364
void calculateGlobalMatrices(std::vector<core::matrix4> &matrices) const;
@@ -89,8 +80,6 @@ class SkinnedMesh : public IAnimatedMesh
8980

9081
u32 getTextureSlot(u32 meshbufNr) const override;
9182

92-
void setTextureSlot(u32 meshbufNr, u32 textureSlot);
93-
9483
//! Returns bounding box of the mesh *in static pose*.
9584
const core::aabbox3d<f32> &getBoundingBox() const override {
9685
// TODO ideally we shouldn't be forced to implement this
@@ -126,14 +115,6 @@ class SkinnedMesh : public IAnimatedMesh
126115
\return Number of the joint or std::nullopt if not found. */
127116
std::optional<u32> getJointNumber(const std::string &name) const;
128117

129-
//! Update Normals when Animating
130-
/** \param on If false don't animate, which is faster.
131-
Else update normals, which allows for proper lighting of
132-
animated meshes (default). */
133-
void updateNormalsWhenAnimating(bool on) {
134-
AnimateNormals = on;
135-
}
136-
137118
//! converts the vertex type of all meshbuffers to tangents.
138119
/** E.g. used for bump mapping. */
139120
void convertMeshToTangents();
@@ -143,8 +124,8 @@ class SkinnedMesh : public IAnimatedMesh
143124
return !HasAnimation;
144125
}
145126

146-
//! Refreshes vertex data cached in joints such as positions and normals
147-
void refreshJointCache();
127+
//! Back up static pose after local buffers have been modified directly
128+
void updateStaticPose();
148129

149130
//! Moves the mesh into static position.
150131
void resetAnimation();
@@ -153,26 +134,6 @@ class SkinnedMesh : public IAnimatedMesh
153134
std::vector<IBoneSceneNode *> addJoints(
154135
AnimatedMeshSceneNode *node, ISceneManager *smgr);
155136

156-
//! A vertex weight
157-
struct SWeight
158-
{
159-
//! Index of the mesh buffer
160-
u16 buffer_id; // I doubt 32bits is needed
161-
162-
//! Index of the vertex
163-
u32 vertex_id; // Store global ID here
164-
165-
//! Weight Strength/Percentage (0-1)
166-
f32 strength;
167-
168-
private:
169-
//! Internal members used by SkinnedMesh
170-
friend class SkinnedMesh;
171-
char *Moved;
172-
core::vector3df StaticPos;
173-
core::vector3df StaticNormal;
174-
};
175-
176137
template <class T>
177138
struct Channel {
178139
struct Frame {
@@ -329,13 +290,11 @@ class SkinnedMesh : public IAnimatedMesh
329290

330291
//! List of attached meshes
331292
std::vector<u32> AttachedMeshes;
293+
// TODO ^ should turn this into optional meshbuffer parent field?
332294

333295
// Animation keyframes for translation, rotation, scale
334296
Keys keys;
335297

336-
//! Skin weights
337-
std::vector<SWeight> Weights;
338-
339298
//! Bounding box of all affected vertices, in local space
340299
core::aabbox3df LocalBoundingBox{{0, 0, 0}};
341300

@@ -369,34 +328,29 @@ class SkinnedMesh : public IAnimatedMesh
369328
protected:
370329
bool checkForAnimation() const;
371330

372-
void topoSortJoints();
373-
374331
void prepareForSkinning();
375332

376333
void calculateStaticBoundingBox();
377334
void calculateJointBoundingBoxes();
378335
void calculateBufferBoundingBoxes();
379336

380-
void normalizeWeights();
381-
382337
void calculateTangents(core::vector3df &normal,
383338
core::vector3df &tangent, core::vector3df &binormal,
384339
const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3,
385340
const core::vector2df &tc1, const core::vector2df &tc2, const core::vector2df &tc3);
386341

342+
friend class SkinnedMeshBuilder;
343+
387344
std::vector<SSkinMeshBuffer *> *SkinningBuffers; // Meshbuffer to skin, default is to skin localBuffers
388345

389346
std::vector<SSkinMeshBuffer *> LocalBuffers;
347+
390348
//! Mapping from meshbuffer number to bindable texture slot
391349
std::vector<u32> TextureSlots;
392350

393351
//! Joints, topologically sorted (parents come before their children).
394352
std::vector<SJoint *> AllJoints;
395353

396-
// bool can't be used here because std::vector<bool>
397-
// doesn't allow taking a reference to individual elements.
398-
std::vector<std::vector<char>> Vertices_Moved;
399-
400354
//! Bounding box of just the static parts of the mesh
401355
core::aabbox3df StaticPartsBox{{0, 0, 0}};
402356

@@ -408,40 +362,76 @@ class SkinnedMesh : public IAnimatedMesh
408362

409363
bool HasAnimation;
410364
bool PreparedForSkinning;
411-
bool AnimateNormals;
412365

413366
SourceFormat SrcFormat;
414367
};
415368

416369
// Interface for mesh loaders
417-
class SkinnedMeshBuilder : public SkinnedMesh {
370+
class SkinnedMeshBuilder {
371+
using SJoint = SkinnedMesh::SJoint;
372+
418373
public:
419-
SkinnedMeshBuilder(SourceFormat src_format) : SkinnedMesh(src_format) {}
374+
375+
// HACK the .x and .b3d loader do not separate the "loader" class from an "extractor" class
376+
// used and destroyed in a specific loading process (contrast with the .gltf mesh loader).
377+
// This means we need an empty skinned mesh builder.
378+
SkinnedMeshBuilder() {}
379+
380+
SkinnedMeshBuilder(SkinnedMesh::SourceFormat src_format)
381+
: mesh(new SkinnedMesh(src_format))
382+
{}
420383

421384
//! loaders should call this after populating the mesh
422-
// returns *this, so do not try to drop the mesh builder instance
423-
SkinnedMesh *finalize();
385+
SkinnedMesh *finalize() &&;
424386

425387
//! alternative method for adding joints
426-
std::vector<SJoint *> &getAllJoints() {
427-
return AllJoints;
428-
}
388+
std::vector<SJoint *> &getJoints() { return mesh->AllJoints; }
429389

430390
//! Adds a new meshbuffer to the mesh, access it as last one
431391
SSkinMeshBuffer *addMeshBuffer();
432392

433-
//! Adds a new meshbuffer to the mesh, access it as last one
434-
void addMeshBuffer(SSkinMeshBuffer *meshbuf);
393+
//! Adds a new meshbuffer to the mesh, returns ID
394+
u32 addMeshBuffer(SSkinMeshBuffer *meshbuf);
395+
396+
u32 getMeshBufferCount() { return mesh->getMeshBufferCount(); }
397+
398+
void setTextureSlot(u32 meshbufNr, u32 textureSlot)
399+
{
400+
mesh->TextureSlots.at(meshbufNr) = textureSlot;
401+
}
435402

436403
//! Adds a new joint to the mesh, access it as last one
437404
SJoint *addJoint(SJoint *parent = nullptr);
438405

406+
std::optional<u32> getJointNumber(const std::string &name) const
407+
{
408+
return mesh->getJointNumber(name);
409+
}
410+
439411
void addPositionKey(SJoint *joint, f32 frame, core::vector3df pos);
440412
void addRotationKey(SJoint *joint, f32 frame, core::quaternion rotation);
441413
void addScaleKey(SJoint *joint, f32 frame, core::vector3df scale);
442414

443-
//! Adds a new weight to the mesh, access it as last one
444-
SWeight *addWeight(SJoint *joint);
415+
//! Adds a new weight to the mesh
416+
void addWeight(SJoint *joint, u16 buf, u32 vert_id, f32 strength);
417+
418+
private:
419+
420+
void topoSortJoints();
421+
422+
//! The mesh that is being built
423+
irr_ptr<SkinnedMesh> mesh;
424+
425+
struct Weight {
426+
u16 joint_id;
427+
u16 buffer_id;
428+
u32 vertex_id;
429+
f32 strength;
430+
};
431+
432+
//! Weights to be added once all mesh buffers have been loaded
433+
std::vector<Weight> weights;
434+
445435
};
446436

447437
} // end namespace scene

irr/include/WeightBuffer.h

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (C) 2025 Lars Müller
2+
// For conditions of distribution and use, see copyright notice in irrlicht.h
3+
4+
#pragma once
5+
6+
#include "vector3d.h"
7+
#include "matrix4.h"
8+
#include "IVertexBuffer.h"
9+
10+
#include <cassert>
11+
#include <memory>
12+
#include <optional>
13+
14+
namespace scene
15+
{
16+
17+
struct WeightBuffer
18+
{
19+
constexpr static u16 MAX_WEIGHTS_PER_VERTEX = 4;
20+
// ID-weight pairs for a joint
21+
struct VertexWeights {
22+
std::array<u16, MAX_WEIGHTS_PER_VERTEX> joint_ids = {};
23+
std::array<f32, MAX_WEIGHTS_PER_VERTEX> weights = {};
24+
void addWeight(u16 joint_id, f32 weight);
25+
/// Transform given position and normal with these weights
26+
void skinVertex(core::vector3df &pos, core::vector3df &normal,
27+
const std::vector<core::matrix4> &joint_transforms) const;
28+
};
29+
std::vector<VertexWeights> weights;
30+
31+
std::optional<std::vector<u32>> animated_vertices;
32+
33+
// A bit of a hack for now: Store static positions here so we can use them for skinning.
34+
// Ideally we might want a design where we do not mutate the original vertex buffer at all.
35+
std::unique_ptr<core::vector3df[]> static_positions;
36+
std::unique_ptr<core::vector3df[]> static_normals;
37+
38+
WeightBuffer(size_t n_verts) : weights(n_verts) {}
39+
40+
const std::array<u16, MAX_WEIGHTS_PER_VERTEX> &getJointIds(u32 vertex_id) const
41+
{ return weights[vertex_id].joint_ids; }
42+
43+
const std::array<f32, MAX_WEIGHTS_PER_VERTEX> &getWeights(u32 vertex_id) const
44+
{ return weights[vertex_id].weights; }
45+
46+
size_t size() const
47+
{ return weights.size(); }
48+
49+
void addWeight(u32 vertex_id, u16 joint_id, f32 weight);
50+
51+
/// Transform position and normal using the weights of the given vertex
52+
void skinVertex(u32 vertex_id, core::vector3df &pos, core::vector3df &normal,
53+
const std::vector<core::matrix4> &joint_transforms) const;
54+
55+
/// @note src and dst can be the same buffer
56+
void skin(IVertexBuffer *dst,
57+
const std::vector<core::matrix4> &joint_transforms) const;
58+
59+
/// Prepares this buffer for use in skinning.
60+
void finalize();
61+
62+
void updateStaticPose(const IVertexBuffer *vbuf);
63+
64+
void resetToStatic(IVertexBuffer *vbuf) const;
65+
};
66+
67+
} // end namespace scene

irr/src/AnimatedMeshSceneNode.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -514,10 +514,6 @@ void AnimatedMeshSceneNode::setMesh(IAnimatedMesh *mesh)
514514
JointsUsed = false;
515515
checkJoints();
516516
}
517-
518-
// get start and begin time
519-
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.
520-
setFrameLoop(0, Mesh->getMaxFrameNumber());
521517
}
522518

523519
//! updates the absolute position based on the relative and the parents position

0 commit comments

Comments
 (0)