Coverage Report

Created: 2026-02-05 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/PostProcessing/DeboneProcess.cpp
Line
Count
Source
1
/*
2
Open Asset Import Library (assimp)
3
----------------------------------------------------------------------
4
5
Copyright (c) 2006-2026, 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 DeboneProcess.cpp
43
/** Implementation of the DeboneProcess post processing step */
44
45
// internal headers of the post-processing framework
46
#include "ProcessHelper.h"
47
#include "DeboneProcess.h"
48
#include <stdio.h>
49
50
using namespace Assimp;
51
52
// ------------------------------------------------------------------------------------------------
53
// Constructor to be privately used by Importer
54
31.9k
DeboneProcess::DeboneProcess() : mNumBones(0), mNumBonesCanDoWithout(0), mThreshold(AI_DEBONE_THRESHOLD), mAllOrNone(false) {}
55
56
// ------------------------------------------------------------------------------------------------
57
// Returns whether the processing step is present in the given flag field.
58
9.32k
bool DeboneProcess::IsActive( unsigned int pFlags) const {
59
9.32k
    return (pFlags & aiProcess_Debone) != 0;
60
9.32k
}
61
62
// ------------------------------------------------------------------------------------------------
63
// Executes the post processing step on the given imported data.
64
0
void DeboneProcess::SetupProperties(const Importer* pImp) {
65
    // get the current value of the property
66
0
    mAllOrNone = pImp->GetPropertyInteger(AI_CONFIG_PP_DB_ALL_OR_NONE,0)?true:false;
67
0
    mThreshold = pImp->GetPropertyFloat(AI_CONFIG_PP_DB_THRESHOLD,AI_DEBONE_THRESHOLD);
68
0
}
69
70
// ------------------------------------------------------------------------------------------------
71
// Executes the post processing step on the given imported data.
72
0
void DeboneProcess::Execute( aiScene* pScene) {
73
0
    ASSIMP_LOG_DEBUG("DeboneProcess begin");
74
75
0
    if(!pScene->mNumMeshes) {
76
0
        return;
77
0
    }
78
79
0
    std::vector<bool> splitList(pScene->mNumMeshes);
80
0
    for( unsigned int a = 0; a < pScene->mNumMeshes; a++) {
81
0
        splitList[a] = ConsiderMesh( pScene->mMeshes[a] );
82
0
    }
83
84
0
    int numSplits = 0;
85
86
0
    if(!!mNumBonesCanDoWithout && (!mAllOrNone||mNumBonesCanDoWithout==mNumBones))  {
87
0
        for(unsigned int a = 0; a < pScene->mNumMeshes; a++)    {
88
0
            if(splitList[a]) {
89
0
                ++numSplits;
90
0
            }
91
0
        }
92
0
    }
93
94
0
    if(numSplits)   {
95
        // we need to do something. Let's go.
96
        //mSubMeshIndices.clear();                  // really needed?
97
0
        mSubMeshIndices.resize(pScene->mNumMeshes); // because we're doing it here anyway
98
99
        // build a new array of meshes for the scene
100
0
        std::vector<aiMesh*> meshes;
101
102
0
        for (unsigned int a=0;a<pScene->mNumMeshes; ++a) {
103
0
            aiMesh* srcMesh = pScene->mMeshes[a];
104
0
            std::vector<std::pair<aiMesh*,const aiBone*> > newMeshes;
105
106
0
            if(splitList[a]) {
107
0
                SplitMesh(srcMesh,newMeshes);
108
0
            }
109
110
            // mesh was split
111
0
            if(!newMeshes.empty())  {
112
0
                unsigned int out = 0, in = srcMesh->mNumBones;
113
114
                // store new meshes and indices of the new meshes
115
0
                for(unsigned int b=0;b<newMeshes.size();b++)    {
116
0
                    const aiString *find = newMeshes[b].second ? &newMeshes[b].second->mName : nullptr;
117
118
0
                    aiNode *theNode = find ? pScene->mRootNode->FindNode(*find) : nullptr;
119
0
                    std::pair<unsigned int,aiNode*> push_pair(static_cast<unsigned int>(meshes.size()),theNode);
120
121
0
                    mSubMeshIndices[a].emplace_back(push_pair);
122
0
                    meshes.emplace_back(newMeshes[b].first);
123
124
0
                    out+=newMeshes[b].first->mNumBones;
125
0
                }
126
127
0
                if(!DefaultLogger::isNullLogger()) {
128
0
                    ASSIMP_LOG_INFO("Removed %u bones. Input bones:", in - out, ". Output bones: ", out);
129
0
                }
130
131
                // and destroy the source mesh. It should be completely contained inside the new submeshes
132
0
                delete srcMesh;
133
0
            } else {
134
                // Mesh is kept unchanged - store it's new place in the mesh array
135
0
                mSubMeshIndices[a].emplace_back(static_cast<unsigned int>(meshes.size()), (aiNode *)nullptr);
136
0
                meshes.push_back(srcMesh);
137
0
            }
138
0
        }
139
140
        // rebuild the scene's mesh array
141
0
        pScene->mNumMeshes = static_cast<unsigned int>(meshes.size());
142
0
        delete [] pScene->mMeshes;
143
0
        pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
144
0
        std::copy( meshes.begin(), meshes.end(), pScene->mMeshes);
145
146
        // recurse through all nodes and translate the node's mesh indices to fit the new mesh array
147
0
        UpdateNode( pScene->mRootNode);
148
0
    }
149
150
0
    ASSIMP_LOG_DEBUG("DeboneProcess end");
151
0
}
152
153
// ------------------------------------------------------------------------------------------------
154
// Counts bones total/removable in a given mesh.
155
0
bool DeboneProcess::ConsiderMesh(const aiMesh* pMesh) {
156
0
    if(!pMesh->HasBones()) {
157
0
        return false;
158
0
    }
159
160
0
    bool split = false;
161
162
    //interstitial faces not permitted
163
0
    bool isInterstitialRequired = false;
164
165
0
    std::vector<bool> isBoneNecessary(pMesh->mNumBones,false);
166
0
    std::vector<unsigned int> vertexBones(pMesh->mNumVertices,UINT_MAX);
167
168
0
    const unsigned int cUnowned = UINT_MAX;
169
0
    const unsigned int cCoowned = UINT_MAX-1;
170
171
0
    for(unsigned int i=0;i<pMesh->mNumBones;i++)    {
172
0
        for(unsigned int j=0;j<pMesh->mBones[i]->mNumWeights;j++)   {
173
0
            float w = pMesh->mBones[i]->mWeights[j].mWeight;
174
0
            if (w == 0.0f) {
175
0
                continue;
176
0
            }
177
178
0
            unsigned int vid = pMesh->mBones[i]->mWeights[j].mVertexId;
179
0
            if (w >= mThreshold)   {
180
0
                if (vertexBones[vid] != cUnowned) {
181
                    //double entry
182
0
                    if(vertexBones[vid]==i)  {
183
0
                        ASSIMP_LOG_WARN("Encountered double entry in bone weights");
184
0
                    } else  {
185
                        //TODO: track attraction in order to break tie
186
0
                        vertexBones[vid] = cCoowned;
187
0
                    }
188
0
                } else {
189
0
                    vertexBones[vid] = i;
190
0
                }
191
0
            }
192
193
0
            if(!isBoneNecessary[i]) {
194
0
                isBoneNecessary[i] = w<mThreshold;
195
0
            }
196
0
        }
197
198
0
        if(!isBoneNecessary[i])  {
199
0
            isInterstitialRequired = true;
200
0
        }
201
0
    }
202
203
0
    if(isInterstitialRequired) {
204
0
        for(unsigned int i=0;i<pMesh->mNumFaces;i++) {
205
0
            unsigned int v = vertexBones[pMesh->mFaces[i].mIndices[0]];
206
0
            for (unsigned int j=1;j<pMesh->mFaces[i].mNumIndices;j++) {
207
0
                unsigned int w = vertexBones[pMesh->mFaces[i].mIndices[j]];
208
209
0
                if (v != w) {
210
0
                    if(v<pMesh->mNumBones) {
211
0
                        isBoneNecessary[v] = true;
212
0
                    }
213
0
                    if (w<pMesh->mNumBones) {
214
0
                        isBoneNecessary[w] = true;
215
0
                    }
216
0
                }
217
0
            }
218
0
        }
219
0
    }
220
221
0
    for(unsigned int i=0;i<pMesh->mNumBones;i++)    {
222
0
        if(!isBoneNecessary[i]) {
223
0
            mNumBonesCanDoWithout++;
224
0
            split = true;
225
0
        }
226
227
0
        mNumBones++;
228
0
    }
229
0
    return split;
230
0
}
231
232
// ------------------------------------------------------------------------------------------------
233
// Splits the given mesh by bone count.
234
0
void DeboneProcess::SplitMesh( const aiMesh* pMesh, std::vector< std::pair< aiMesh*,const aiBone* > >& poNewMeshes) const {
235
    // same deal here as ConsiderMesh basically
236
237
0
    std::vector<bool> isBoneNecessary(pMesh->mNumBones,false);
238
0
    std::vector<unsigned int> vertexBones(pMesh->mNumVertices,UINT_MAX);
239
240
0
    const unsigned int cUnowned = UINT_MAX;
241
0
    const unsigned int cCoowned = UINT_MAX-1;
242
243
0
    for(unsigned int i=0;i<pMesh->mNumBones;i++)    {
244
0
        for(unsigned int j=0;j<pMesh->mBones[i]->mNumWeights;j++)   {
245
0
            float w = pMesh->mBones[i]->mWeights[j].mWeight;
246
247
0
            if(w==0.0f) {
248
0
                continue;
249
0
            }
250
251
0
            unsigned int vid = pMesh->mBones[i]->mWeights[j].mVertexId;
252
253
0
            if(w>=mThreshold) {
254
0
                if(vertexBones[vid]!=cUnowned)  {
255
0
                    if(vertexBones[vid]==i) //double entry
256
0
                    {
257
0
                        ASSIMP_LOG_WARN("Encountered double entry in bone weights");
258
0
                    }
259
0
                    else //TODO: track attraction in order to break tie
260
0
                    {
261
0
                        vertexBones[vid] = cCoowned;
262
0
                    }
263
0
                }
264
0
                else vertexBones[vid] = i;
265
0
            }
266
267
0
            if(!isBoneNecessary[i]) {
268
0
                isBoneNecessary[i] = w<mThreshold;
269
0
            }
270
0
        }
271
0
    }
272
273
0
    unsigned int nFacesUnowned = 0;
274
275
0
    std::vector<unsigned int> faceBones(pMesh->mNumFaces,UINT_MAX);
276
0
    std::vector<unsigned int> facesPerBone(pMesh->mNumBones,0);
277
278
0
    for(unsigned int i=0;i<pMesh->mNumFaces;i++) {
279
0
        unsigned int nInterstitial = 1;
280
281
0
        unsigned int v = vertexBones[pMesh->mFaces[i].mIndices[0]];
282
283
0
        for(unsigned int j=1;j<pMesh->mFaces[i].mNumIndices;j++) {
284
0
            unsigned int w = vertexBones[pMesh->mFaces[i].mIndices[j]];
285
286
0
            if(v!=w)    {
287
0
                if(v<pMesh->mNumBones) isBoneNecessary[v] = true;
288
0
                if(w<pMesh->mNumBones) isBoneNecessary[w] = true;
289
0
            }
290
0
            else nInterstitial++;
291
0
        }
292
293
0
        if(v<pMesh->mNumBones &&nInterstitial==pMesh->mFaces[i].mNumIndices)    {
294
0
            faceBones[i] = v; //primitive belongs to bone #v
295
0
            facesPerBone[v]++;
296
0
        }
297
0
        else nFacesUnowned++;
298
0
    }
299
300
    // invalidate any "cojoined" faces
301
0
    for(unsigned int i=0;i<pMesh->mNumFaces;i++) {
302
0
        if(faceBones[i]<pMesh->mNumBones&&isBoneNecessary[faceBones[i]])
303
0
        {
304
0
            ai_assert(facesPerBone[faceBones[i]]>0);
305
0
            facesPerBone[faceBones[i]]--;
306
307
0
            nFacesUnowned++;
308
0
            faceBones[i] = cUnowned;
309
0
        }
310
0
    }
311
312
0
    if(nFacesUnowned) {
313
0
        std::vector<unsigned int> subFaces;
314
315
0
        for(unsigned int i=0;i<pMesh->mNumFaces;i++)    {
316
0
            if(faceBones[i]==cUnowned) {
317
0
                subFaces.push_back(i);
318
0
            }
319
0
        }
320
321
0
        aiMesh *baseMesh = MakeSubmesh(pMesh,subFaces,0);
322
0
        std::pair<aiMesh *, const aiBone *> push_pair(baseMesh, (const aiBone *)nullptr);
323
324
0
        poNewMeshes.push_back(push_pair);
325
0
    }
326
327
0
    for(unsigned int i=0;i<pMesh->mNumBones;i++) {
328
329
0
        if(!isBoneNecessary[i]&&facesPerBone[i]>0)  {
330
0
            std::vector<unsigned int> subFaces;
331
332
0
            for(unsigned int j=0;j<pMesh->mNumFaces;j++)    {
333
0
                if(faceBones[j]==i) {
334
0
                    subFaces.push_back(j);
335
0
                }
336
0
            }
337
338
0
            unsigned int f = AI_SUBMESH_FLAGS_SANS_BONES;
339
0
            aiMesh *subMesh =MakeSubmesh(pMesh,subFaces,f);
340
341
            //Lifted from PretransformVertices.cpp
342
0
            ApplyTransform(subMesh,pMesh->mBones[i]->mOffsetMatrix);
343
0
            std::pair<aiMesh*,const aiBone*> push_pair(subMesh,pMesh->mBones[i]);
344
345
0
            poNewMeshes.push_back(push_pair);
346
0
        }
347
0
    }
348
0
}
349
350
// ------------------------------------------------------------------------------------------------
351
// Recursively updates the node's mesh list to account for the changed mesh list
352
0
void DeboneProcess::UpdateNode(aiNode* pNode) const {
353
    // rebuild the node's mesh index list
354
355
0
    std::vector<unsigned int> newMeshList;
356
357
    // this will require two passes
358
359
0
    unsigned int m = static_cast<unsigned int>(pNode->mNumMeshes), n = static_cast<unsigned int>(mSubMeshIndices.size());
360
361
    // first pass, look for meshes which have not moved
362
0
    for(unsigned int a=0;a<m;a++)   {
363
0
        unsigned int srcIndex = pNode->mMeshes[a];
364
0
        const std::vector< std::pair< unsigned int,aiNode* > > &subMeshes = mSubMeshIndices[srcIndex];
365
0
        unsigned int nSubmeshes = static_cast<unsigned int>(subMeshes.size());
366
367
0
        for(unsigned int b=0;b<nSubmeshes;b++) {
368
0
            if(!subMeshes[b].second) {
369
0
                newMeshList.push_back(subMeshes[b].first);
370
0
            }
371
0
        }
372
0
    }
373
374
    // second pass, collect deboned meshes
375
376
0
    for(unsigned int a=0;a<n;a++) {
377
0
        const std::vector< std::pair< unsigned int,aiNode* > > &subMeshes = mSubMeshIndices[a];
378
0
        unsigned int nSubmeshes = static_cast<unsigned int>(subMeshes.size());
379
380
0
        for(unsigned int b=0;b<nSubmeshes;b++) {
381
0
            if(subMeshes[b].second == pNode)    {
382
0
                newMeshList.push_back(subMeshes[b].first);
383
0
            }
384
0
        }
385
0
    }
386
387
0
    if( pNode->mNumMeshes > 0 ) {
388
0
        delete[] pNode->mMeshes;
389
0
        pNode->mMeshes = nullptr;
390
0
    }
391
392
0
    pNode->mNumMeshes = static_cast<unsigned int>(newMeshList.size());
393
394
0
    if(pNode->mNumMeshes)   {
395
0
        pNode->mMeshes = new unsigned int[pNode->mNumMeshes];
396
0
        std::copy( newMeshList.begin(), newMeshList.end(), pNode->mMeshes);
397
0
    }
398
399
    // do that also recursively for all children
400
0
    for( unsigned int a = 0; a < pNode->mNumChildren; ++a ) {
401
0
        UpdateNode( pNode->mChildren[a]);
402
0
    }
403
0
}
404
405
// ------------------------------------------------------------------------------------------------
406
// Apply the node transformation to a mesh
407
0
void DeboneProcess::ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat)const {
408
    // Check whether we need to transform the coordinates at all
409
0
    if (!mat.IsIdentity()) {
410
411
0
        if (mesh->HasPositions()) {
412
0
            for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
413
0
                mesh->mVertices[i] = mat * mesh->mVertices[i];
414
0
            }
415
0
        }
416
0
        if (mesh->HasNormals() || mesh->HasTangentsAndBitangents()) {
417
0
            aiMatrix4x4 mWorldIT = mat;
418
0
            mWorldIT.Inverse().Transpose();
419
420
            // TODO: implement Inverse() for aiMatrix3x3
421
0
            aiMatrix3x3 m = aiMatrix3x3(mWorldIT);
422
423
0
            if (mesh->HasNormals()) {
424
0
                for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
425
0
                    mesh->mNormals[i] = (m * mesh->mNormals[i]).Normalize();
426
0
                }
427
0
            }
428
0
            if (mesh->HasTangentsAndBitangents()) {
429
0
                for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
430
0
                    mesh->mTangents[i]   = (m * mesh->mTangents[i]).Normalize();
431
0
                    mesh->mBitangents[i] = (m * mesh->mBitangents[i]).Normalize();
432
0
                }
433
0
            }
434
0
        }
435
0
    }
436
0
}