Coverage Report

Created: 2026-06-15 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/PostProcessing/OptimizeGraph.cpp
Line
Count
Source
1
/*
2
---------------------------------------------------------------------------
3
Open Asset Import Library (assimp)
4
---------------------------------------------------------------------------
5
6
Copyright (c) 2006-2026, assimp team
7
8
All rights reserved.
9
10
Redistribution and use of this software in source and binary forms,
11
with or without modification, are permitted provided that the following
12
conditions are met:
13
14
* Redistributions of source code must retain the above
15
  copyright notice, this list of conditions and the
16
  following disclaimer.
17
18
* Redistributions in binary form must reproduce the above
19
  copyright notice, this list of conditions and the
20
  following disclaimer in the documentation and/or other
21
  materials provided with the distribution.
22
23
* Neither the name of the assimp team, nor the names of its
24
  contributors may be used to endorse or promote products
25
  derived from this software without specific prior
26
  written permission of the assimp team.
27
28
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
---------------------------------------------------------------------------
40
*/
41
42
/** @file  OptimizeGraph.cpp
43
 *  @brief Implementation of the aiProcess_OptimizGraph step
44
 */
45
46
#ifndef ASSIMP_BUILD_NO_OPTIMIZEGRAPH_PROCESS
47
48
#include "OptimizeGraph.h"
49
#include "ProcessHelper.h"
50
#include "ConvertToLHProcess.h"
51
#include <assimp/Exceptional.h>
52
#include <assimp/SceneCombiner.h>
53
#include <stdio.h>
54
55
using namespace Assimp;
56
57
0
#define AI_RESERVED_NODE_NAME "$Reserved_And_Evil"
58
59
/* AI_OG_USE_HASHING enables the use of hashing to speed-up std::set lookups.
60
 * The unhashed variant should be faster, except for *very* large data sets
61
 */
62
#ifdef AI_OG_USE_HASHING
63
// Use our standard hashing function to compute the hash
64
#define AI_OG_GETKEY(str) SuperFastHash(str.data, str.length)
65
#else
66
// Otherwise hope that std::string will utilize a static buffer
67
// for shorter node names. This would avoid endless heap copying.
68
0
#define AI_OG_GETKEY(str) std::string(str.data)
69
#endif
70
71
// ------------------------------------------------------------------------------------------------
72
// Constructor to be privately used by Importer
73
OptimizeGraphProcess::OptimizeGraphProcess() :
74
    mScene(),
75
    nodes_in(),
76
    nodes_out(),
77
63.7k
    count_merged() {
78
  // empty
79
63.7k
}
80
81
// ------------------------------------------------------------------------------------------------
82
// Returns whether the processing step is present in the given flag field.
83
22.0k
bool OptimizeGraphProcess::IsActive(unsigned int pFlags) const {
84
22.0k
  return (0 != (pFlags & aiProcess_OptimizeGraph));
85
22.0k
}
86
87
// ------------------------------------------------------------------------------------------------
88
// Setup properties for the post-processing step
89
0
void OptimizeGraphProcess::SetupProperties(const Importer *pImp) {
90
  // Get value of AI_CONFIG_PP_OG_EXCLUDE_LIST
91
0
  std::string tmp = pImp->GetPropertyString(AI_CONFIG_PP_OG_EXCLUDE_LIST, "");
92
0
  AddLockedNodeList(tmp);
93
0
}
94
95
// ------------------------------------------------------------------------------------------------
96
// Collect new children
97
0
void OptimizeGraphProcess::CollectNewChildren(aiNode *nd, std::list<aiNode *> &nodes) {
98
0
  nodes_in += nd->mNumChildren;
99
100
  // Process children
101
0
  std::list<aiNode *> child_nodes;
102
0
  for (unsigned int i = 0; i < nd->mNumChildren; ++i) {
103
0
    CollectNewChildren(nd->mChildren[i], child_nodes);
104
0
    nd->mChildren[i] = nullptr;
105
0
  }
106
107
  // Check whether we need this node; if not we can replace it by our own children (warn, danger of incest).
108
0
  if (locked.find(AI_OG_GETKEY(nd->mName)) == locked.end()) {
109
0
    for (std::list<aiNode *>::iterator it = child_nodes.begin(); it != child_nodes.end();) {
110
111
0
      if (locked.find(AI_OG_GETKEY((*it)->mName)) == locked.end()) {
112
0
        (*it)->mTransformation = nd->mTransformation * (*it)->mTransformation;
113
0
        nodes.push_back(*it);
114
115
0
        it = child_nodes.erase(it);
116
0
        continue;
117
0
      }
118
0
      ++it;
119
0
    }
120
121
0
    if (nd->mNumMeshes || !child_nodes.empty()) {
122
0
      nodes.push_back(nd);
123
0
    } else {
124
0
      delete nd; /* bye, node */
125
0
      return;
126
0
    }
127
0
  } else {
128
129
    // Retain our current position in the hierarchy
130
0
    nodes.push_back(nd);
131
132
    // Now check for possible optimizations in our list of child nodes. join as many as possible
133
0
    aiNode *join_master = nullptr;
134
0
    aiMatrix4x4 inv;
135
136
0
    const LockedSetType::const_iterator end = locked.end();
137
138
0
    std::list<aiNode *> join;
139
0
    for (std::list<aiNode *>::iterator it = child_nodes.begin(); it != child_nodes.end();) {
140
0
      aiNode *child = *it;
141
0
      if (child->mNumChildren == 0 && locked.find(AI_OG_GETKEY(child->mName)) == end) {
142
143
        // There may be no instanced meshes
144
0
        unsigned int n = 0;
145
0
        for (; n < child->mNumMeshes; ++n) {
146
0
          if (meshes[child->mMeshes[n]] > 1) {
147
0
            break;
148
0
          }
149
0
        }
150
0
        if (n == child->mNumMeshes) {
151
0
          if (!join_master) {
152
0
            join_master = child;
153
0
            inv = join_master->mTransformation;
154
0
            inv.Inverse();
155
0
          } else {
156
0
            child->mTransformation = inv * child->mTransformation;
157
158
0
            join.push_back(child);
159
0
            it = child_nodes.erase(it);
160
0
            continue;
161
0
          }
162
0
        }
163
0
      }
164
0
      ++it;
165
0
    }
166
0
    if (join_master && !join.empty()) {
167
0
            join_master->mName.length = ::ai_snprintf(join_master->mName.data, AI_MAXLEN, "$MergedNode_%u", count_merged++);
168
169
0
      unsigned int out_meshes = 0;
170
0
      for (std::list<aiNode *>::const_iterator it = join.cbegin(); it != join.cend(); ++it) {
171
0
        out_meshes += (*it)->mNumMeshes;
172
0
      }
173
174
      // copy all mesh references in one array
175
0
      if (out_meshes) {
176
0
        unsigned int *meshIdxs = new unsigned int[out_meshes + join_master->mNumMeshes], *tmp = meshIdxs;
177
0
        for (unsigned int n = 0; n < join_master->mNumMeshes; ++n) {
178
0
          *tmp++ = join_master->mMeshes[n];
179
0
        }
180
181
0
        for (const aiNode *join_node : join) {
182
0
          for (unsigned int n = 0; n < join_node->mNumMeshes; ++n) {
183
184
0
            *tmp = join_node->mMeshes[n];
185
0
            aiMesh *mesh = mScene->mMeshes[*tmp++];
186
187
            // Assume the transformation is affine
188
            // manually move the mesh into the right coordinate system
189
190
            // Check for odd negative scale (mirror)
191
0
            if (join_node->mTransformation.Determinant() < 0) {
192
              // Reverse the mesh face winding order
193
0
                            FlipWindingOrderProcess::ProcessMesh(mesh);
194
0
            }
195
196
                        // Update positions, normals and tangents
197
0
            const aiMatrix3x3 IT = aiMatrix3x3(join_node->mTransformation).Inverse().Transpose();
198
0
            for (unsigned int a = 0; a < mesh->mNumVertices; ++a) {
199
200
0
              mesh->mVertices[a] *= join_node->mTransformation;
201
202
0
              if (mesh->HasNormals())
203
0
                mesh->mNormals[a] *= IT;
204
205
0
              if (mesh->HasTangentsAndBitangents()) {
206
0
                mesh->mTangents[a] *= IT;
207
0
                mesh->mBitangents[a] *= IT;
208
0
              }
209
0
            }
210
0
          }
211
0
          delete join_node; // bye, node
212
0
        }
213
0
        delete[] join_master->mMeshes;
214
0
        join_master->mMeshes = meshIdxs;
215
0
        join_master->mNumMeshes += out_meshes;
216
0
      }
217
0
    }
218
0
  }
219
  // reassign children if something changed
220
0
  if (child_nodes.empty() || child_nodes.size() > nd->mNumChildren) {
221
222
0
    delete[] nd->mChildren;
223
224
0
    if (!child_nodes.empty()) {
225
0
      nd->mChildren = new aiNode *[child_nodes.size()];
226
0
    } else
227
0
      nd->mChildren = nullptr;
228
0
  }
229
230
0
  nd->mNumChildren = static_cast<unsigned int>(child_nodes.size());
231
232
0
  if (nd->mChildren) {
233
0
    aiNode **tmp = nd->mChildren;
234
0
    for (std::list<aiNode *>::iterator it = child_nodes.begin(); it != child_nodes.end(); ++it) {
235
0
      aiNode *node = *tmp++ = *it;
236
0
      node->mParent = nd;
237
0
    }
238
0
  }
239
240
0
  nodes_out += static_cast<unsigned int>(child_nodes.size());
241
0
}
242
243
// ------------------------------------------------------------------------------------------------
244
// Execute the post-processing step on the given scene
245
0
void OptimizeGraphProcess::Execute(aiScene *pScene) {
246
0
  ASSIMP_LOG_DEBUG("OptimizeGraphProcess begin");
247
0
  nodes_in = nodes_out = count_merged = 0;
248
0
  mScene = pScene;
249
250
0
  meshes.resize(pScene->mNumMeshes, 0);
251
0
  FindInstancedMeshes(pScene->mRootNode);
252
253
  // build a blacklist of identifiers. If the name of a node matches one of these, we won't touch it
254
0
  locked.clear();
255
0
  for (std::list<std::string>::const_iterator it = locked_nodes.begin(); it != locked_nodes.end(); ++it) {
256
#ifdef AI_OG_USE_HASHING
257
    locked.insert(SuperFastHash((*it).c_str()));
258
#else
259
0
    locked.insert(*it);
260
0
#endif
261
0
  }
262
263
0
  for (unsigned int i = 0; i < pScene->mNumAnimations; ++i) {
264
0
    for (unsigned int a = 0; a < pScene->mAnimations[i]->mNumChannels; ++a) {
265
0
      aiNodeAnim *anim = pScene->mAnimations[i]->mChannels[a];
266
0
      locked.insert(AI_OG_GETKEY(anim->mNodeName));
267
0
    }
268
0
  }
269
270
0
  for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
271
0
    for (unsigned int a = 0; a < pScene->mMeshes[i]->mNumBones; ++a) {
272
273
0
      aiBone *bone = pScene->mMeshes[i]->mBones[a];
274
0
      locked.insert(AI_OG_GETKEY(bone->mName));
275
276
      // HACK: Meshes referencing bones may not be transformed; we need to look them.
277
      // The easiest way to do this is to increase their reference counters ...
278
0
      meshes[i] += 2;
279
0
    }
280
0
  }
281
282
0
  for (unsigned int i = 0; i < pScene->mNumCameras; ++i) {
283
0
    aiCamera *cam = pScene->mCameras[i];
284
0
    locked.insert(AI_OG_GETKEY(cam->mName));
285
0
  }
286
287
0
  for (unsigned int i = 0; i < pScene->mNumLights; ++i) {
288
0
    aiLight *lgh = pScene->mLights[i];
289
0
    locked.insert(AI_OG_GETKEY(lgh->mName));
290
0
  }
291
292
  // Insert a dummy master node and make it read-only
293
0
  aiNode *dummy_root = new aiNode(AI_RESERVED_NODE_NAME);
294
0
  locked.insert(AI_OG_GETKEY(dummy_root->mName));
295
296
0
  const aiString prev = pScene->mRootNode->mName;
297
0
  pScene->mRootNode->mParent = dummy_root;
298
299
0
  dummy_root->mChildren = new aiNode *[dummy_root->mNumChildren = 1];
300
0
  dummy_root->mChildren[0] = pScene->mRootNode;
301
302
  // Do our recursive processing of scenegraph nodes. For each node collect
303
  // a fully new list of children and allow their children to place themselves
304
  // on the same hierarchy layer as their parents.
305
0
  std::list<aiNode *> nodes;
306
0
  CollectNewChildren(dummy_root, nodes);
307
308
0
  ai_assert(nodes.size() == 1);
309
310
0
  if (dummy_root->mNumChildren == 0) {
311
0
    pScene->mRootNode = nullptr;
312
0
    throw DeadlyImportError("After optimizing the scene graph, no data remains");
313
0
  }
314
315
0
  if (dummy_root->mNumChildren > 1) {
316
0
    pScene->mRootNode = dummy_root;
317
318
    // Keep the dummy node but assign the name of the old root node to it
319
0
    pScene->mRootNode->mName = prev;
320
0
  } else {
321
322
    // Remove the dummy root node again.
323
0
    pScene->mRootNode = dummy_root->mChildren[0];
324
325
0
    dummy_root->mChildren[0] = nullptr;
326
0
    delete dummy_root;
327
0
  }
328
329
0
  pScene->mRootNode->mParent = nullptr;
330
0
  if (!DefaultLogger::isNullLogger()) {
331
0
    if (nodes_in != nodes_out) {
332
0
      ASSIMP_LOG_INFO("OptimizeGraphProcess finished; Input nodes: ", nodes_in, ", Output nodes: ", nodes_out);
333
0
    } else {
334
0
      ASSIMP_LOG_DEBUG("OptimizeGraphProcess finished");
335
0
    }
336
0
  }
337
0
  meshes.clear();
338
0
  locked.clear();
339
0
}
340
341
// ------------------------------------------------------------------------------------------------
342
// Build a LUT of all instanced meshes
343
0
void OptimizeGraphProcess::FindInstancedMeshes(aiNode *pNode) {
344
0
  for (unsigned int i = 0; i < pNode->mNumMeshes; ++i) {
345
0
    ++meshes[pNode->mMeshes[i]];
346
0
  }
347
348
0
  for (unsigned int i = 0; i < pNode->mNumChildren; ++i)
349
0
    FindInstancedMeshes(pNode->mChildren[i]);
350
0
}
351
352
#endif // !! ASSIMP_BUILD_NO_OPTIMIZEGRAPH_PROCESS