Skip to content

Commit 16fbf49

Browse files
committed
Refactor skinned mesh weight data structure
in preparation for GPU-based skinning
1 parent 232c833 commit 16fbf49

File tree

9 files changed

+396
-466
lines changed

9 files changed

+396
-466
lines changed

irr/include/SSkinMeshBuffer.h

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,153 @@
77
#include "IMeshBuffer.h"
88
#include "CVertexBuffer.h"
99
#include "CIndexBuffer.h"
10+
#include "IVertexBuffer.h"
1011
#include "S3DVertex.h"
12+
#include "vector3d.h"
13+
#include <algorithm>
1114
#include <cassert>
15+
#include <memory>
16+
#include <numeric>
17+
#include <optional>
1218

1319
namespace scene
1420
{
1521

22+
struct WeightBuffer
23+
{
24+
constexpr static u16 MAX_WEIGHTS_PER_VERTEX = 4;
25+
size_t n_verts;
26+
std::unique_ptr<u16[]> joint_idxs;
27+
std::unique_ptr<f32[]> weights;
28+
29+
std::optional<std::vector<u32>> animated_vertices;
30+
31+
// A bit of a hack for now: Store static positions here so we can use them for skinning.
32+
// Ideally we might want a design where we do not mutate the original vertex buffer at all.
33+
std::unique_ptr<core::vector3df[]> static_positions;
34+
std::unique_ptr<core::vector3df[]> static_normals;
35+
36+
WeightBuffer(size_t n_verts) :
37+
n_verts(n_verts),
38+
joint_idxs(std::make_unique<u16[]>(MAX_WEIGHTS_PER_VERTEX * n_verts)),
39+
weights(std::make_unique<f32[]>(MAX_WEIGHTS_PER_VERTEX * n_verts))
40+
{}
41+
42+
u16 *getJointIndices(u32 vertex_id)
43+
{ return &joint_idxs[vertex_id * MAX_WEIGHTS_PER_VERTEX]; }
44+
const u16 *getJointIndices(u32 vertex_id) const
45+
{ return &joint_idxs[vertex_id * MAX_WEIGHTS_PER_VERTEX]; }
46+
47+
f32 *getWeights(u32 vertex_id)
48+
{ return &weights[vertex_id * MAX_WEIGHTS_PER_VERTEX]; }
49+
const f32 *getWeights(u32 vertex_id) const
50+
{ return &weights[vertex_id * MAX_WEIGHTS_PER_VERTEX]; }
51+
52+
void addWeight(u32 vertex_id, u16 joint_id, f32 weight)
53+
{
54+
assert(vertex_id < n_verts);
55+
assert(weight >= 0.f);
56+
f32 *weights = getWeights(vertex_id);
57+
f32 *min_weight = std::min_element(weights, weights + MAX_WEIGHTS_PER_VERTEX);
58+
if (*min_weight > weight)
59+
return;
60+
61+
*min_weight = weight;
62+
getJointIndices(vertex_id)[min_weight - weights] = joint_id;
63+
}
64+
65+
void skinVertex(u32 vertex_id, core::vector3df &pos, core::vector3df &normal,
66+
const std::vector<core::matrix4> &joint_transforms) const
67+
{
68+
const u16 *joint_idxs = getJointIndices(vertex_id);
69+
const f32 *weights = getWeights(vertex_id);
70+
f32 total_weight = 0.f;
71+
core::vector3df skinned_pos;
72+
core::vector3df skinned_normal;
73+
for (u16 i = 0; i < MAX_WEIGHTS_PER_VERTEX; ++i) {
74+
u16 joint_id = joint_idxs[i];
75+
f32 weight = weights[i];
76+
if (weight <= 0.f)
77+
continue;
78+
79+
const auto &transform = joint_transforms[joint_id];
80+
core::vector3df transformed_pos = pos;
81+
transform.transformVect(transformed_pos);
82+
skinned_pos += weight * transformed_pos;
83+
skinned_normal += weight * transform.rotateAndScaleVect(normal);
84+
total_weight += weight;
85+
}
86+
if (core::equals(total_weight, 0.f))
87+
return;
88+
89+
pos = skinned_pos;
90+
// Need to renormalize normal after potentially scaling
91+
normal = skinned_normal.normalize();
92+
}
93+
94+
/// @note src and dst can be the same buffer
95+
void skin(IVertexBuffer *dst,
96+
const std::vector<core::matrix4> &joint_transforms) const
97+
{
98+
assert(animated_vertices.has_value());
99+
for (u32 i = 0; i < animated_vertices->size(); ++i) {
100+
101+
u32 vertex_id = (*animated_vertices)[i];
102+
auto pos = static_positions[i];
103+
auto normal = static_normals[i];
104+
skinVertex(vertex_id, pos, normal, joint_transforms);
105+
dst->getPosition(vertex_id) = pos;
106+
dst->getNormal(vertex_id) = normal;
107+
}
108+
if (!animated_vertices->empty())
109+
dst->setDirty();
110+
}
111+
112+
/// Normalizes weights so that they sum to 1.0 per vertex,
113+
/// stores which vertices are animated.
114+
void finalize() {
115+
assert(!animated_vertices.has_value());
116+
animated_vertices.emplace();
117+
for (u32 i = 0; i < n_verts; ++i) {
118+
auto *weights_i = getWeights(i);
119+
f32 total_weight = std::accumulate(weights_i, weights_i + MAX_WEIGHTS_PER_VERTEX, 0.0f);
120+
if (core::equals(total_weight, 0.0f)) {
121+
std::fill(weights_i, weights_i + MAX_WEIGHTS_PER_VERTEX, 0.0f);
122+
continue;
123+
}
124+
animated_vertices->emplace_back(i);
125+
if (core::equals(total_weight, 1.0f))
126+
continue;
127+
for (u16 j = 0; j < MAX_WEIGHTS_PER_VERTEX; ++j)
128+
weights_i[j] /= total_weight;
129+
}
130+
animated_vertices->shrink_to_fit();
131+
}
132+
133+
void updateStaticPose(const IVertexBuffer *vbuf)
134+
{
135+
static_normals = std::make_unique<core::vector3df[]>(animated_vertices->size());
136+
static_positions = std::make_unique<core::vector3df[]>(animated_vertices->size());
137+
for (size_t idx = 0; idx < animated_vertices->size(); ++idx) {
138+
u32 vertex_id = (*animated_vertices)[idx];
139+
static_positions[idx] = vbuf->getPosition(vertex_id);
140+
static_normals[idx] = vbuf->getNormal(vertex_id);
141+
}
142+
}
143+
144+
void resetToStatic(IVertexBuffer *vbuf) const
145+
{
146+
assert(animated_vertices.has_value());
147+
for (size_t idx = 0; idx < animated_vertices->size(); ++idx) {
148+
u32 vertex_id = (*animated_vertices)[idx];
149+
vbuf->getPosition(vertex_id) = static_positions[idx];
150+
vbuf->getNormal(vertex_id) = static_normals[idx];
151+
}
152+
if (!animated_vertices->empty())
153+
vbuf->setDirty();
154+
}
155+
};
156+
16157
//! A mesh buffer able to choose between S3DVertex2TCoords, S3DVertex and S3DVertexTangents at runtime
17158
struct SSkinMeshBuffer final : public IMeshBuffer
18159
{
@@ -222,6 +363,8 @@ struct SSkinMeshBuffer final : public IMeshBuffer
222363
SVertexBuffer *Vertices_Standard;
223364
SIndexBuffer *Indices;
224365

366+
std::optional<WeightBuffer> Weights;
367+
225368
core::matrix4 Transformation;
226369

227370
video::SMaterial Material;

irr/include/SkinnedMesh.h

Lines changed: 20 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ class SkinnedMesh : public IAnimatedMesh
4343
SkinnedMesh(SourceFormat src_format) :
4444
EndFrame(0.f), FramesPerSecond(25.f),
4545
HasAnimation(false), PreparedForSkinning(false),
46-
AnimateNormals(true),
4746
SrcFormat(src_format)
4847
{
4948
SkinningBuffers = &LocalBuffers;
@@ -126,14 +125,6 @@ class SkinnedMesh : public IAnimatedMesh
126125
\return Number of the joint or std::nullopt if not found. */
127126
std::optional<u32> getJointNumber(const std::string &name) const;
128127

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-
137128
//! converts the vertex type of all meshbuffers to tangents.
138129
/** E.g. used for bump mapping. */
139130
void convertMeshToTangents();
@@ -143,8 +134,8 @@ class SkinnedMesh : public IAnimatedMesh
143134
return !HasAnimation;
144135
}
145136

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

149140
//! Moves the mesh into static position.
150141
void resetAnimation();
@@ -153,26 +144,6 @@ class SkinnedMesh : public IAnimatedMesh
153144
std::vector<IBoneSceneNode *> addJoints(
154145
AnimatedMeshSceneNode *node, ISceneManager *smgr);
155146

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-
176147
template <class T>
177148
struct Channel {
178149
struct Frame {
@@ -329,13 +300,11 @@ class SkinnedMesh : public IAnimatedMesh
329300

330301
//! List of attached meshes
331302
std::vector<u32> AttachedMeshes;
303+
// TODO ^ should turn this into optional meshbuffer parent field?
332304

333305
// Animation keyframes for translation, rotation, scale
334306
Keys keys;
335307

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

@@ -369,16 +338,12 @@ class SkinnedMesh : public IAnimatedMesh
369338
protected:
370339
bool checkForAnimation() const;
371340

372-
void topoSortJoints();
373-
374341
void prepareForSkinning();
375342

376343
void calculateStaticBoundingBox();
377344
void calculateJointBoundingBoxes();
378345
void calculateBufferBoundingBoxes();
379346

380-
void normalizeWeights();
381-
382347
void calculateTangents(core::vector3df &normal,
383348
core::vector3df &tangent, core::vector3df &binormal,
384349
const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3,
@@ -387,16 +352,13 @@ class SkinnedMesh : public IAnimatedMesh
387352
std::vector<SSkinMeshBuffer *> *SkinningBuffers; // Meshbuffer to skin, default is to skin localBuffers
388353

389354
std::vector<SSkinMeshBuffer *> LocalBuffers;
355+
390356
//! Mapping from meshbuffer number to bindable texture slot
391357
std::vector<u32> TextureSlots;
392358

393359
//! Joints, topologically sorted (parents come before their children).
394360
std::vector<SJoint *> AllJoints;
395361

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-
400362
//! Bounding box of just the static parts of the mesh
401363
core::aabbox3df StaticPartsBox{{0, 0, 0}};
402364

@@ -408,7 +370,6 @@ class SkinnedMesh : public IAnimatedMesh
408370

409371
bool HasAnimation;
410372
bool PreparedForSkinning;
411-
bool AnimateNormals;
412373

413374
SourceFormat SrcFormat;
414375
};
@@ -440,8 +401,22 @@ class SkinnedMeshBuilder : public SkinnedMesh {
440401
void addRotationKey(SJoint *joint, f32 frame, core::quaternion rotation);
441402
void addScaleKey(SJoint *joint, f32 frame, core::vector3df scale);
442403

443-
//! Adds a new weight to the mesh, access it as last one
444-
SWeight *addWeight(SJoint *joint);
404+
//! Adds a new weight to the mesh
405+
void addWeight(SJoint *joint, u16 buf, u32 vert_id, f32 strength);
406+
407+
private:
408+
409+
void topoSortJoints();
410+
411+
struct Weight {
412+
u16 joint_id;
413+
u16 buffer_id;
414+
u32 vertex_id;
415+
f32 strength;
416+
};
417+
418+
std::vector<Weight> Weights;
419+
445420
};
446421

447422
} // end namespace scene

irr/src/CB3DMeshFileLoader.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -541,11 +541,10 @@ bool CB3DMeshFileLoader::readChunkBONE(SkinnedMesh::SJoint *inJoint)
541541
if (AnimatedVertices_VertexID[globalVertexID] == -1) {
542542
os::Printer::log("B3dMeshLoader: Weight has bad vertex id (no link to meshbuffer index found)");
543543
} else if (strength > 0) {
544-
SkinnedMesh::SWeight *weight = AnimatedMesh->addWeight(inJoint);
545-
weight->strength = strength;
546-
// Find the meshbuffer and Vertex index from the Global Vertex ID:
547-
weight->vertex_id = AnimatedVertices_VertexID[globalVertexID];
548-
weight->buffer_id = AnimatedVertices_BufferID[globalVertexID];
544+
AnimatedMesh->addWeight(inJoint,
545+
AnimatedVertices_BufferID[globalVertexID],
546+
AnimatedVertices_VertexID[globalVertexID],
547+
strength);
549548
}
550549
}
551550
}

irr/src/CGLTFMeshFileLoader.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -501,10 +501,8 @@ void SelfType::MeshExtractor::addPrimitive(
501501
if (strength <= 0)
502502
continue; // note: also ignores negative weights
503503

504-
SkinnedMesh::SWeight *weight = m_irr_model->addWeight(m_loaded_nodes.at(skin.joints.at(jointIdx)));
505-
weight->buffer_id = meshbufNr;
506-
weight->vertex_id = v;
507-
weight->strength = strength;
504+
m_irr_model->addWeight(m_loaded_nodes.at(skin.joints.at(jointIdx)),
505+
meshbufNr, v, strength);
508506
}
509507
}
510508
if (negative_weights)

irr/src/CMeshManipulator.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ void CMeshManipulator::recalculateNormals(scene::IMesh *mesh, bool smooth, bool
110110

111111
if (mesh->getMeshType() == EAMT_SKINNED) {
112112
auto *smesh = (SkinnedMesh *)mesh;
113-
smesh->refreshJointCache();
113+
smesh->updateStaticPose();
114114
}
115115
}
116116

0 commit comments

Comments
 (0)