Coverage Report

Created: 2025-06-22 07:30

/src/assimp/code/PostProcessing/SplitByBoneCountProcess.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
Open Asset Import Library (assimp)
3
----------------------------------------------------------------------
4
5
Copyright (c) 2006-2025, assimp team
6
7
All rights reserved.
8
9
Redistribution and use of this software in source and binary forms,
10
with or without modification, are permitted provided that the
11
following conditions are met:
12
13
* Redistributions of source code must retain the above
14
  copyright notice, this list of conditions and the
15
  following disclaimer.
16
17
* Redistributions in binary form must reproduce the above
18
  copyright notice, this list of conditions and the
19
  following disclaimer in the documentation and/or other
20
  materials provided with the distribution.
21
22
* Neither the name of the assimp team, nor the names of its
23
  contributors may be used to endorse or promote products
24
  derived from this software without specific prior
25
  written permission of the assimp team.
26
27
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39
----------------------------------------------------------------------
40
*/
41
42
/// @file SplitByBoneCountProcess.cpp
43
/// Implementation of the SplitByBoneCount postprocessing step
44
45
// internal headers of the post-processing framework
46
#include "SplitByBoneCountProcess.h"
47
#include <assimp/postprocess.h>
48
#include <assimp/DefaultLogger.hpp>
49
50
#include <limits>
51
#include <assimp/TinyFormatter.h>
52
#include <assimp/Exceptional.h>
53
#include <set>
54
55
using namespace Assimp;
56
using namespace Assimp::Formatter;
57
58
// ------------------------------------------------------------------------------------------------
59
// Constructor
60
1.77k
SplitByBoneCountProcess::SplitByBoneCountProcess() : mMaxBoneCount(AI_SBBC_DEFAULT_MAX_BONES) {}
61
62
// ------------------------------------------------------------------------------------------------
63
// Returns whether the processing step is present in the given flag.
64
435
bool SplitByBoneCountProcess::IsActive( unsigned int pFlags) const {
65
435
    return !!(pFlags & aiProcess_SplitByBoneCount);
66
435
}
67
68
// ------------------------------------------------------------------------------------------------
69
// Updates internal properties
70
0
void SplitByBoneCountProcess::SetupProperties(const Importer* pImp) {
71
0
    mMaxBoneCount = pImp->GetPropertyInteger(AI_CONFIG_PP_SBBC_MAX_BONES,AI_SBBC_DEFAULT_MAX_BONES);
72
0
}
73
74
// ------------------------------------------------------------------------------------------------
75
// Executes the post processing step on the given imported data.
76
0
void SplitByBoneCountProcess::Execute( aiScene* pScene) {
77
0
    ASSIMP_LOG_DEBUG("SplitByBoneCountProcess begin");
78
79
    // early out
80
0
    bool isNecessary = false;
81
0
    for( unsigned int a = 0; a < pScene->mNumMeshes; ++a)
82
0
        if( pScene->mMeshes[a]->mNumBones > mMaxBoneCount ) {
83
0
            isNecessary = true;
84
0
            break;
85
0
        }
86
87
0
    if( !isNecessary ) {
88
0
        ASSIMP_LOG_DEBUG("SplitByBoneCountProcess early-out: no meshes with more than ", mMaxBoneCount, " bones." );
89
0
        return;
90
0
    }
91
92
    // we need to do something. Let's go.
93
0
    mSubMeshIndices.clear();
94
0
    mSubMeshIndices.resize( pScene->mNumMeshes);
95
96
    // build a new array of meshes for the scene
97
0
    std::vector<aiMesh*> meshes;
98
99
0
    for( unsigned int a = 0; a < pScene->mNumMeshes; ++a) {
100
0
        aiMesh* srcMesh = pScene->mMeshes[a];
101
102
0
        std::vector<aiMesh*> newMeshes;
103
0
        SplitMesh( pScene->mMeshes[a], newMeshes);
104
105
        // mesh was split
106
0
        if( !newMeshes.empty() ) {
107
            // store new meshes and indices of the new meshes
108
0
            for( unsigned int b = 0; b < newMeshes.size(); ++b) {
109
0
                mSubMeshIndices[a].push_back( static_cast<unsigned int>(meshes.size()));
110
0
                meshes.push_back( newMeshes[b]);
111
0
            }
112
113
            // and destroy the source mesh. It should be completely contained inside the new submeshes
114
0
            delete srcMesh;
115
0
        } else {
116
            // Mesh is kept unchanged - store it's new place in the mesh array
117
0
            mSubMeshIndices[a].push_back( static_cast<unsigned int>(meshes.size()));
118
0
            meshes.push_back( srcMesh);
119
0
        }
120
0
    }
121
122
    // rebuild the scene's mesh array
123
0
    pScene->mNumMeshes = static_cast<unsigned int>(meshes.size());
124
0
    delete [] pScene->mMeshes;
125
0
    pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
126
0
    std::copy( meshes.begin(), meshes.end(), pScene->mMeshes);
127
128
    // recurse through all nodes and translate the node's mesh indices to fit the new mesh array
129
0
    UpdateNode( pScene->mRootNode);
130
131
0
    ASSIMP_LOG_DEBUG( "SplitByBoneCountProcess end: split ", mSubMeshIndices.size(), " meshes into ", meshes.size(), " submeshes." );
132
0
}
133
134
// ------------------------------------------------------------------------------------------------
135
// Splits the given mesh by bone count.
136
0
void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vector<aiMesh*>& poNewMeshes) const {
137
    // skip if not necessary
138
0
    if( pMesh->mNumBones <= mMaxBoneCount ) {
139
0
        return;
140
0
    }
141
142
    // necessary optimisation: build a list of all affecting bones for each vertex
143
    // TODO: (thom) maybe add a custom allocator here to avoid allocating tens of thousands of small arrays
144
0
    typedef std::pair<unsigned int, float> BoneWeight;
145
0
    std::vector< std::vector<BoneWeight> > vertexBones( pMesh->mNumVertices);
146
0
    for( unsigned int a = 0; a < pMesh->mNumBones; ++a) {
147
0
        const aiBone* bone = pMesh->mBones[a];
148
0
        for( unsigned int b = 0; b < bone->mNumWeights; ++b) {
149
0
            if (bone->mWeights[b].mWeight > 0.0f) {
150
0
                int vertexId = bone->mWeights[b].mVertexId;
151
0
                vertexBones[vertexId].emplace_back(a, bone->mWeights[b].mWeight);
152
0
                if (vertexBones[vertexId].size() > mMaxBoneCount) {
153
0
                    throw DeadlyImportError("SplitByBoneCountProcess: Single face requires more bones than specified max bone count!");
154
0
                }
155
0
            }
156
0
        }
157
0
    }
158
159
0
    unsigned int numFacesHandled = 0;
160
0
    std::vector<bool> isFaceHandled( pMesh->mNumFaces, false);
161
0
    while( numFacesHandled < pMesh->mNumFaces ) {
162
        // which bones are used in the current submesh
163
0
        unsigned int numBones = 0;
164
0
        std::vector<bool> isBoneUsed( pMesh->mNumBones, false);
165
        // indices of the faces which are going to go into this submesh
166
0
        IndexArray subMeshFaces;
167
0
        subMeshFaces.reserve( pMesh->mNumFaces);
168
        // accumulated vertex count of all the faces in this submesh
169
0
        unsigned int numSubMeshVertices = 0;
170
171
        // add faces to the new submesh as long as all bones affecting the faces' vertices fit in the limit
172
0
        for( unsigned int a = 0; a < pMesh->mNumFaces; ++a) {
173
            // skip if the face is already stored in a submesh
174
0
            if( isFaceHandled[a] ) {
175
0
                continue;
176
0
            }
177
            // a small local set of new bones for the current face. State of all used bones for that face
178
            // can only be updated AFTER the face is completely analysed. Thanks to imre for the fix.
179
0
            std::set<unsigned int> newBonesAtCurrentFace;
180
181
0
            const aiFace& face = pMesh->mFaces[a];
182
            // check every vertex if its bones would still fit into the current submesh
183
0
            for( unsigned int b = 0; b < face.mNumIndices; ++b ) {
184
0
                const std::vector<BoneWeight>& vb = vertexBones[face.mIndices[b]];
185
0
                for( unsigned int c = 0; c < vb.size(); ++c) {
186
0
                    unsigned int boneIndex = vb[c].first;
187
0
                    if( !isBoneUsed[boneIndex] ) {
188
0
                        newBonesAtCurrentFace.insert(boneIndex);
189
0
                    }
190
0
                }
191
0
            }
192
193
            // leave out the face if the new bones required for this face don't fit the bone count limit anymore
194
0
            if( numBones + newBonesAtCurrentFace.size() > mMaxBoneCount ) {
195
0
                continue;
196
0
            }
197
198
            // mark all new bones as necessary
199
0
            for (std::set<unsigned int>::iterator it = newBonesAtCurrentFace.begin(); it != newBonesAtCurrentFace.end(); ++it) {
200
0
                if (!isBoneUsed[*it]) {
201
0
                    isBoneUsed[*it] = true;
202
0
                    ++numBones;
203
0
                }
204
0
            }
205
206
            // store the face index and the vertex count
207
0
            subMeshFaces.push_back( a);
208
0
            numSubMeshVertices += face.mNumIndices;
209
210
            // remember that this face is handled
211
0
            isFaceHandled[a] = true;
212
0
            ++numFacesHandled;
213
0
        }
214
215
        // create a new mesh to hold this subset of the source mesh
216
0
        aiMesh* newMesh = new aiMesh;
217
0
        if( pMesh->mName.length > 0 ) {
218
0
            newMesh->mName.Set( format() << pMesh->mName.data << "_sub" << poNewMeshes.size());
219
0
        }
220
0
        newMesh->mMaterialIndex = pMesh->mMaterialIndex;
221
0
        newMesh->mPrimitiveTypes = pMesh->mPrimitiveTypes;
222
0
        poNewMeshes.emplace_back( newMesh);
223
224
        // create all the arrays for this mesh if the old mesh contained them
225
0
        newMesh->mNumVertices = numSubMeshVertices;
226
0
        newMesh->mNumFaces = static_cast<unsigned int>(subMeshFaces.size());
227
0
        newMesh->mVertices = new aiVector3D[newMesh->mNumVertices];
228
0
        if( pMesh->HasNormals() ) {
229
0
            newMesh->mNormals = new aiVector3D[newMesh->mNumVertices];
230
0
        }
231
0
        if( pMesh->HasTangentsAndBitangents() ) {
232
0
            newMesh->mTangents = new aiVector3D[newMesh->mNumVertices];
233
0
            newMesh->mBitangents = new aiVector3D[newMesh->mNumVertices];
234
0
        }
235
0
        for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) {
236
0
            if( pMesh->HasTextureCoords( a) ) {
237
0
                newMesh->mTextureCoords[a] = new aiVector3D[newMesh->mNumVertices];
238
0
            }
239
0
            newMesh->mNumUVComponents[a] = pMesh->mNumUVComponents[a];
240
0
        }
241
0
        for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a ) {
242
0
            if( pMesh->HasVertexColors( a) ) {
243
0
                newMesh->mColors[a] = new aiColor4D[newMesh->mNumVertices];
244
0
            }
245
0
        }
246
247
        // and copy over the data, generating faces with linear indices along the way
248
0
        newMesh->mFaces = new aiFace[subMeshFaces.size()];
249
0
        unsigned int nvi = 0; // next vertex index
250
0
        IndexArray previousVertexIndices( numSubMeshVertices, std::numeric_limits<unsigned int>::max()); // per new vertex: its index in the source mesh
251
0
        for( unsigned int a = 0; a < subMeshFaces.size(); ++a ) {
252
0
            const aiFace& srcFace = pMesh->mFaces[subMeshFaces[a]];
253
0
            aiFace& dstFace = newMesh->mFaces[a];
254
0
            dstFace.mNumIndices = srcFace.mNumIndices;
255
0
            dstFace.mIndices = new unsigned int[dstFace.mNumIndices];
256
257
            // accumulate linearly all the vertices of the source face
258
0
            for( unsigned int b = 0; b < dstFace.mNumIndices; ++b ) {
259
0
                unsigned int srcIndex = srcFace.mIndices[b];
260
0
                dstFace.mIndices[b] = nvi;
261
0
                previousVertexIndices[nvi] = srcIndex;
262
263
0
                newMesh->mVertices[nvi] = pMesh->mVertices[srcIndex];
264
0
                if( pMesh->HasNormals() ) {
265
0
                    newMesh->mNormals[nvi] = pMesh->mNormals[srcIndex];
266
0
                }
267
0
                if( pMesh->HasTangentsAndBitangents() ) {
268
0
                    newMesh->mTangents[nvi] = pMesh->mTangents[srcIndex];
269
0
                    newMesh->mBitangents[nvi] = pMesh->mBitangents[srcIndex];
270
0
                }
271
0
                for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c ) {
272
0
                    if( pMesh->HasTextureCoords( c) ) {
273
0
                        newMesh->mTextureCoords[c][nvi] = pMesh->mTextureCoords[c][srcIndex];
274
0
                    }
275
0
                }
276
0
                for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c ) {
277
0
                    if( pMesh->HasVertexColors( c) ) {
278
0
                        newMesh->mColors[c][nvi] = pMesh->mColors[c][srcIndex];
279
0
                    }
280
0
                }
281
282
0
                nvi++;
283
0
            }
284
0
        }
285
286
0
        ai_assert( nvi == numSubMeshVertices );
287
288
        // Create the bones for the new submesh: first create the bone array
289
0
        newMesh->mNumBones = 0;
290
0
        newMesh->mBones = new aiBone*[numBones];
291
292
0
        std::vector<unsigned int> mappedBoneIndex( pMesh->mNumBones, std::numeric_limits<unsigned int>::max());
293
0
        for( unsigned int a = 0; a < pMesh->mNumBones; ++a ) {
294
0
            if( !isBoneUsed[a] ) {
295
0
                continue;
296
0
            }
297
298
            // create the new bone
299
0
            const aiBone* srcBone = pMesh->mBones[a];
300
0
            aiBone* dstBone = new aiBone;
301
0
            mappedBoneIndex[a] = newMesh->mNumBones;
302
0
            newMesh->mBones[newMesh->mNumBones++] = dstBone;
303
0
            dstBone->mName = srcBone->mName;
304
0
            dstBone->mOffsetMatrix = srcBone->mOffsetMatrix;
305
0
            dstBone->mNumWeights = 0;
306
0
        }
307
308
0
        ai_assert( newMesh->mNumBones == numBones );
309
310
        // iterate over all new vertices and count which bones affected its old vertex in the source mesh
311
0
        for( unsigned int a = 0; a < numSubMeshVertices; ++a ) {
312
0
            unsigned int oldIndex = previousVertexIndices[a];
313
0
            const std::vector<BoneWeight>& bonesOnThisVertex = vertexBones[oldIndex];
314
315
0
            for( unsigned int b = 0; b < bonesOnThisVertex.size(); ++b ) {
316
0
                unsigned int newBoneIndex = mappedBoneIndex[ bonesOnThisVertex[b].first ];
317
0
                if( newBoneIndex != std::numeric_limits<unsigned int>::max() ) {
318
0
                    newMesh->mBones[newBoneIndex]->mNumWeights++;
319
0
                }
320
0
            }
321
0
        }
322
323
        // allocate all bone weight arrays accordingly
324
0
        for( unsigned int a = 0; a < newMesh->mNumBones; ++a ) {
325
0
            aiBone* bone = newMesh->mBones[a];
326
0
            ai_assert( bone->mNumWeights > 0 );
327
0
            bone->mWeights = new aiVertexWeight[bone->mNumWeights];
328
0
            bone->mNumWeights = 0; // for counting up in the next step
329
0
        }
330
331
        // now copy all the bone vertex weights for all the vertices which made it into the new submesh
332
0
        for( unsigned int a = 0; a < numSubMeshVertices; ++a) {
333
            // find the source vertex for it in the source mesh
334
0
            unsigned int previousIndex = previousVertexIndices[a];
335
            // these bones were affecting it
336
0
            const std::vector<BoneWeight>& bonesOnThisVertex = vertexBones[previousIndex];
337
            // all of the bones affecting it should be present in the new submesh, or else
338
            // the face it comprises shouldn't be present
339
0
            for( unsigned int b = 0; b < bonesOnThisVertex.size(); ++b) {
340
0
                unsigned int newBoneIndex = mappedBoneIndex[ bonesOnThisVertex[b].first ];
341
0
                ai_assert( newBoneIndex != std::numeric_limits<unsigned int>::max() );
342
0
                aiVertexWeight* dstWeight = newMesh->mBones[newBoneIndex]->mWeights + newMesh->mBones[newBoneIndex]->mNumWeights;
343
0
                newMesh->mBones[newBoneIndex]->mNumWeights++;
344
345
0
                dstWeight->mVertexId = a;
346
0
                dstWeight->mWeight = bonesOnThisVertex[b].second;
347
0
            }
348
0
        }
349
350
        // ... and copy all the morph targets for all the vertices which made it into the new submesh
351
0
        if (pMesh->mNumAnimMeshes > 0) {
352
0
            newMesh->mNumAnimMeshes = pMesh->mNumAnimMeshes;
353
0
            newMesh->mAnimMeshes = new aiAnimMesh*[newMesh->mNumAnimMeshes];
354
355
0
            for (unsigned int morphIdx = 0; morphIdx < newMesh->mNumAnimMeshes; ++morphIdx) {
356
0
                aiAnimMesh* origTarget = pMesh->mAnimMeshes[morphIdx];
357
0
                aiAnimMesh* newTarget = new aiAnimMesh;
358
0
                newTarget->mName = origTarget->mName;
359
0
                newTarget->mWeight = origTarget->mWeight;
360
0
                newTarget->mNumVertices = numSubMeshVertices;
361
0
                newTarget->mVertices = new aiVector3D[numSubMeshVertices];
362
0
                newMesh->mAnimMeshes[morphIdx] = newTarget;
363
364
0
                if (origTarget->HasNormals()) {
365
0
                    newTarget->mNormals = new aiVector3D[numSubMeshVertices];
366
0
                }
367
368
0
                if (origTarget->HasTangentsAndBitangents()) {
369
0
                    newTarget->mTangents = new aiVector3D[numSubMeshVertices];
370
0
                    newTarget->mBitangents = new aiVector3D[numSubMeshVertices];
371
0
                }
372
373
0
                for( unsigned int vi = 0; vi < numSubMeshVertices; ++vi) {
374
                    // find the source vertex for it in the source mesh
375
0
                    unsigned int previousIndex = previousVertexIndices[vi];
376
0
                    newTarget->mVertices[vi] = origTarget->mVertices[previousIndex];
377
378
0
                    if (newTarget->HasNormals()) {
379
0
                        newTarget->mNormals[vi] = origTarget->mNormals[previousIndex];
380
0
                    }
381
0
                    if (newTarget->HasTangentsAndBitangents()) {
382
0
                        newTarget->mTangents[vi] = origTarget->mTangents[previousIndex];
383
0
                        newTarget->mBitangents[vi] = origTarget->mBitangents[previousIndex];
384
0
                    }
385
0
                }
386
0
            }
387
0
        }
388
389
        // I have the strange feeling that this will break apart at some point in time...
390
0
    }
391
0
}
392
393
// ------------------------------------------------------------------------------------------------
394
// Recursively updates the node's mesh list to account for the changed mesh list
395
0
void SplitByBoneCountProcess::UpdateNode( aiNode* pNode) const {
396
    // rebuild the node's mesh index list
397
0
    if( pNode->mNumMeshes != 0 ) {
398
0
        IndexArray newMeshList;
399
0
        for( unsigned int a = 0; a < pNode->mNumMeshes; ++a) {
400
0
            unsigned int srcIndex = pNode->mMeshes[a];
401
0
            const IndexArray& replaceMeshes = mSubMeshIndices[srcIndex];
402
0
            newMeshList.insert( newMeshList.end(), replaceMeshes.begin(), replaceMeshes.end());
403
0
        }
404
405
0
        delete [] pNode->mMeshes;
406
0
        pNode->mNumMeshes = static_cast<unsigned int>(newMeshList.size());
407
0
        pNode->mMeshes = new unsigned int[pNode->mNumMeshes];
408
0
        std::copy( newMeshList.begin(), newMeshList.end(), pNode->mMeshes);
409
0
    }
410
411
    // do that also recursively for all children
412
0
    for( unsigned int a = 0; a < pNode->mNumChildren; ++a ) {
413
0
        UpdateNode( pNode->mChildren[a]);
414
0
    }
415
0
}