Coverage Report

Created: 2026-04-29 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/PostProcessing/ArmaturePopulate.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
#include "ArmaturePopulate.h"
41
42
#include <assimp/BaseImporter.h>
43
#include <assimp/DefaultLogger.hpp>
44
#include <assimp/postprocess.h>
45
#include <assimp/scene.h>
46
47
namespace Assimp {
48
49
0
static bool IsBoneNode(const aiString &bone_name, std::vector<aiBone *> &bones) {
50
0
    for (aiBone *bone : bones) {
51
0
        if (bone->mName == bone_name) {
52
0
            return true;
53
0
        }
54
0
    }
55
56
0
    return false;
57
0
}
58
59
11.3k
bool ArmaturePopulate::IsActive(unsigned int pFlags) const {
60
11.3k
    return (pFlags & aiProcess_PopulateArmatureData) != 0;
61
11.3k
}
62
63
0
void ArmaturePopulate::SetupProperties(const Importer *) {
64
    // do nothing
65
0
}
66
67
0
void ArmaturePopulate::Execute(aiScene *out) {
68
69
    // Now convert all bone positions to the correct mOffsetMatrix
70
0
    std::vector<aiBone *> bones;
71
0
    std::vector<aiNode *> nodes;
72
0
    std::map<aiBone *, aiNode *> bone_stack;
73
0
    BuildBoneList(out->mRootNode, out->mRootNode, out, bones);
74
0
    BuildNodeList(out->mRootNode, nodes);
75
76
0
    BuildBoneStack(out->mRootNode, out, bones, bone_stack, nodes);
77
78
0
    ASSIMP_LOG_DEBUG("Bone stack size: ", bone_stack.size());
79
80
0
    for (std::pair<aiBone *, aiNode *> kvp : bone_stack) {
81
0
        aiBone *bone = kvp.first;
82
0
        aiNode *bone_node = kvp.second;
83
0
        ASSIMP_LOG_VERBOSE_DEBUG("active node lookup: ", bone->mName.C_Str());
84
85
        // lcl transform grab - done in generate_nodes :)
86
0
        aiNode *armature = GetArmatureRoot(bone_node, bones);
87
88
0
        ai_assert(armature);
89
90
0
#ifndef ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS
91
        // set up bone armature id
92
0
        bone->mArmature = armature;
93
94
        // set this bone node to be referenced properly
95
0
        ai_assert(bone_node);
96
0
        bone->mNode = bone_node;
97
0
#endif
98
0
    }
99
0
}
100
101
102
// Reprocess all nodes to calculate bone transforms properly based on the REAL
103
// mOffsetMatrix not the local.
104
// Before this would use mesh transforms which is wrong for bone transforms
105
// Before this would work for simple character skeletons but not complex meshes
106
// with multiple origins
107
// Source: sketch fab log cutter fbx
108
void ArmaturePopulate::BuildBoneList(aiNode *current_node,
109
                                     const aiNode *root_node,
110
                                     const aiScene *scene,
111
0
                                     std::vector<aiBone *> &bones) {
112
0
    ai_assert(scene);
113
0
    for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) {
114
0
        aiNode *child = current_node->mChildren[nodeId];
115
0
        ai_assert(child);
116
117
        // check for bones
118
0
        for (unsigned int meshId = 0; meshId < child->mNumMeshes; ++meshId) {
119
0
            ai_assert(child->mMeshes);
120
0
            unsigned int mesh_index = child->mMeshes[meshId];
121
0
            aiMesh *mesh = scene->mMeshes[mesh_index];
122
0
            ai_assert(mesh);
123
124
0
            for (unsigned int boneId = 0; boneId < mesh->mNumBones; ++boneId) {
125
0
                aiBone *bone = mesh->mBones[boneId];
126
0
                ai_assert(nullptr != bone);
127
128
                // duplicate mehes exist with the same bones sometimes :)
129
                // so this must be detected
130
0
                if (std::find(bones.begin(), bones.end(), bone) == bones.end()) {
131
                    // add the element once
132
0
                    bones.emplace_back(bone);
133
0
                }
134
0
            }
135
136
            // find mesh and get bones
137
            // then do recursive lookup for bones in root node hierarchy
138
0
        }
139
140
0
        BuildBoneList(child, root_node, scene, bones);
141
0
    }
142
0
}
143
144
// Prepare flat node list which can be used for non recursive lookups later
145
void ArmaturePopulate::BuildNodeList(const aiNode *current_node,
146
0
                                     std::vector<aiNode *> &nodes) {
147
0
    ai_assert(nullptr != current_node);
148
149
0
    for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) {
150
0
        aiNode *child = current_node->mChildren[nodeId];
151
0
        ai_assert(child);
152
153
0
        if (child->mNumMeshes == 0) {
154
0
            nodes.emplace_back(child);
155
0
        }
156
157
0
        BuildNodeList(child, nodes);
158
0
  }
159
0
}
160
161
// A bone stack allows us to have multiple armatures, with the same bone names
162
// A bone stack allows us also to retrieve bones true transform even with
163
// duplicate names :)
164
void ArmaturePopulate::BuildBoneStack(const aiNode *root_node,
165
                                      const aiScene*,
166
                                      const std::vector<aiBone *> &bones,
167
                                      std::map<aiBone *, aiNode *> &bone_stack,
168
0
                                  std::vector<aiNode *> &node_stack) {
169
0
    if (node_stack.empty()) {
170
0
        return;
171
0
    }
172
0
    ai_assert(nullptr != root_node);
173
174
0
    for (aiBone *bone : bones) {
175
0
        ai_assert(bone);
176
0
        aiNode *node = GetNodeFromStack(bone->mName, node_stack);
177
0
        if (node == nullptr) {
178
0
            node_stack.clear();
179
0
            BuildNodeList(root_node, node_stack);
180
0
            ASSIMP_LOG_VERBOSE_DEBUG("Resetting bone stack: nullptr element ", bone->mName.C_Str());
181
182
0
            node = GetNodeFromStack(bone->mName, node_stack);
183
184
0
            if (nullptr == node) {
185
0
                ASSIMP_LOG_ERROR("serious import issue node for bone was not detected");
186
0
                continue;
187
0
            }
188
0
        }
189
190
0
        ASSIMP_LOG_VERBOSE_DEBUG("Successfully added bone[", bone->mName.C_Str(), "] to stack and bone node is: ", node->mName.C_Str());
191
192
0
        bone_stack.insert(std::pair<aiBone *, aiNode *>(bone, node));
193
0
    }
194
0
}
195
196
// Returns the armature root node
197
// This is required to be detected for a bone initially, it will recurse up
198
// until it cannot find another bone and return the node No known failure
199
// points. (yet)
200
0
aiNode *ArmaturePopulate::GetArmatureRoot(aiNode *bone_node, std::vector<aiBone *> &bone_list) {
201
0
    while (nullptr != bone_node) {
202
0
        if (!IsBoneNode(bone_node->mName, bone_list)) {
203
0
            ASSIMP_LOG_VERBOSE_DEBUG("GetArmatureRoot() Found valid armature: ", bone_node->mName.C_Str());
204
0
            return bone_node;
205
0
        }
206
207
0
        bone_node = bone_node->mParent;
208
0
    }
209
210
0
    ASSIMP_LOG_ERROR("GetArmatureRoot() can't find armature!");
211
212
0
    return nullptr;
213
0
}
214
215
// Pop this node by name from the stack if found
216
// Used in multiple armature situations with duplicate node / bone names
217
// Known flaw: cannot have nodes with bone names, will be fixed in later release
218
// (serious to be fixed) Known flaw: nodes which have more than one bone could
219
// be prematurely dropped from stack
220
aiNode *ArmaturePopulate::GetNodeFromStack(const aiString &node_name,
221
0
                                           std::vector<aiNode *> &nodes) {
222
0
    std::vector<aiNode *>::iterator iter;
223
0
    aiNode *found = nullptr;
224
0
    for (iter = nodes.begin(); iter < nodes.end(); ++iter) {
225
0
        aiNode *element = *iter;
226
0
        ai_assert(nullptr != element);
227
        // node valid and node name matches
228
0
        if (element->mName == node_name) {
229
0
            found = element;
230
0
            break;
231
0
        }
232
0
    }
233
234
0
    if (found != nullptr) {
235
0
        ASSIMP_LOG_INFO("Removed node from stack: ", found->mName.C_Str());
236
        // now pop the element from the node list
237
0
        nodes.erase(iter);
238
239
0
        return found;
240
0
    }
241
242
    // unique names can cause this problem
243
0
    ASSIMP_LOG_ERROR("[Serious] GetNodeFromStack() can't find node from stack!");
244
245
0
    return nullptr;
246
0
}
247
248
} // Namespace Assimp