/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 |