/src/assimp/code/PostProcessing/FindInvalidDataProcess.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | --------------------------------------------------------------------------- |
3 | | Open Asset Import Library (assimp) |
4 | | --------------------------------------------------------------------------- |
5 | | |
6 | | Copyright (c) 2006-2025, 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 Defines a post processing step to search an importer's output |
43 | | for data that is obviously invalid */ |
44 | | |
45 | | #ifndef ASSIMP_BUILD_NO_FINDINVALIDDATA_PROCESS |
46 | | |
47 | | // internal headers |
48 | | #include "FindInvalidDataProcess.h" |
49 | | #include "ProcessHelper.h" |
50 | | |
51 | | #include <assimp/Exceptional.h> |
52 | | #include <assimp/qnan.h> |
53 | | |
54 | | using namespace Assimp; |
55 | | |
56 | | // ------------------------------------------------------------------------------------------------ |
57 | | // Constructor to be privately used by Importer |
58 | | FindInvalidDataProcess::FindInvalidDataProcess() : |
59 | 502 | configEpsilon(0.0), mIgnoreTexCoods(false) { |
60 | | // nothing to do here |
61 | 502 | } |
62 | | |
63 | | // ------------------------------------------------------------------------------------------------ |
64 | | // Returns whether the processing step is present in the given flag field. |
65 | 161 | bool FindInvalidDataProcess::IsActive(unsigned int pFlags) const { |
66 | 161 | return 0 != (pFlags & aiProcess_FindInvalidData); |
67 | 161 | } |
68 | | |
69 | | // ------------------------------------------------------------------------------------------------ |
70 | | // Setup import configuration |
71 | 99 | void FindInvalidDataProcess::SetupProperties(const Importer *pImp) { |
72 | | // Get the current value of AI_CONFIG_PP_FID_ANIM_ACCURACY |
73 | 99 | configEpsilon = (0 != pImp->GetPropertyFloat(AI_CONFIG_PP_FID_ANIM_ACCURACY, 0.f)); |
74 | 99 | mIgnoreTexCoods = pImp->GetPropertyBool(AI_CONFIG_PP_FID_IGNORE_TEXTURECOORDS, false); |
75 | 99 | } |
76 | | |
77 | | // ------------------------------------------------------------------------------------------------ |
78 | | // Update mesh references in the node graph |
79 | 5.75k | void UpdateMeshReferences(aiNode *node, const std::vector<unsigned int> &meshMapping) { |
80 | 5.75k | if (node->mNumMeshes) { |
81 | 2.01k | unsigned int out = 0; |
82 | 4.31k | for (unsigned int a = 0; a < node->mNumMeshes; ++a) { |
83 | | |
84 | 2.29k | unsigned int ref = node->mMeshes[a]; |
85 | 2.29k | if (ref >= meshMapping.size()) |
86 | 0 | throw DeadlyImportError("Invalid mesh ref"); |
87 | | |
88 | 2.29k | if (UINT_MAX != (ref = meshMapping[ref])) { |
89 | 1.68k | node->mMeshes[out++] = ref; |
90 | 1.68k | } |
91 | 2.29k | } |
92 | | // just let the members that are unused, that's much cheaper |
93 | | // than a full array realloc'n'copy party ... |
94 | 2.01k | node->mNumMeshes = out; |
95 | 2.01k | if (0 == out) { |
96 | 508 | delete[] node->mMeshes; |
97 | 508 | node->mMeshes = nullptr; |
98 | 508 | } |
99 | 2.01k | } |
100 | | // recursively update all children |
101 | 11.4k | for (unsigned int i = 0; i < node->mNumChildren; ++i) { |
102 | 5.74k | UpdateMeshReferences(node->mChildren[i], meshMapping); |
103 | 5.74k | } |
104 | 5.75k | } |
105 | | |
106 | | // ------------------------------------------------------------------------------------------------ |
107 | | // Executes the post processing step on the given imported data. |
108 | 99 | void FindInvalidDataProcess::Execute(aiScene *pScene) { |
109 | 99 | ASSIMP_LOG_DEBUG("FindInvalidDataProcess begin"); |
110 | | |
111 | 99 | bool out = false; |
112 | 99 | std::vector<unsigned int> meshMapping(pScene->mNumMeshes); |
113 | 99 | unsigned int real = 0; |
114 | | |
115 | | // Process meshes |
116 | 4.38k | for (unsigned int a = 0; a < pScene->mNumMeshes; a++) { |
117 | 4.28k | int result = ProcessMesh(pScene->mMeshes[a]); |
118 | 4.28k | if (0 == result) { |
119 | 3.30k | out = true; |
120 | 3.30k | } |
121 | 4.28k | if (2 == result) { |
122 | | // remove this mesh |
123 | 627 | delete pScene->mMeshes[a]; |
124 | 627 | pScene->mMeshes[a] = nullptr; |
125 | | |
126 | 627 | meshMapping[a] = UINT_MAX; |
127 | 627 | out = true; |
128 | 627 | continue; |
129 | 627 | } |
130 | | |
131 | 3.65k | pScene->mMeshes[real] = pScene->mMeshes[a]; |
132 | 3.65k | meshMapping[a] = real++; |
133 | 3.65k | } |
134 | | |
135 | | // Process animations |
136 | 103 | for (unsigned int animIdx = 0; animIdx < pScene->mNumAnimations; ++animIdx) { |
137 | 4 | ProcessAnimation(pScene->mAnimations[animIdx]); |
138 | 4 | } |
139 | | |
140 | 99 | if (out) { |
141 | 86 | if (real != pScene->mNumMeshes) { |
142 | 28 | if (!real) { |
143 | 11 | throw DeadlyImportError("No meshes remaining"); |
144 | 11 | } |
145 | | |
146 | | // we need to remove some meshes. |
147 | | // therefore we'll also need to remove all references |
148 | | // to them from the scenegraph |
149 | 17 | try { |
150 | 17 | UpdateMeshReferences(pScene->mRootNode, meshMapping); |
151 | 17 | } catch (const std::exception&) { |
152 | | // fix the real number of meshes otherwise we'll get double free in the scene destructor |
153 | 0 | pScene->mNumMeshes = real; |
154 | 0 | throw; |
155 | 0 | } |
156 | 17 | pScene->mNumMeshes = real; |
157 | 17 | } |
158 | | |
159 | 75 | ASSIMP_LOG_INFO("FindInvalidDataProcess finished. Found issues ..."); |
160 | 75 | } else { |
161 | 13 | ASSIMP_LOG_DEBUG("FindInvalidDataProcess finished. Everything seems to be OK."); |
162 | 13 | } |
163 | 99 | } |
164 | | |
165 | | // ------------------------------------------------------------------------------------------------ |
166 | | template <typename T> |
167 | | inline const char *ValidateArrayContents(const T * /*arr*/, unsigned int /*size*/, |
168 | | const std::vector<bool> & /*dirtyMask*/, bool /*mayBeIdentical = false*/, bool /*mayBeZero = true*/) { |
169 | | return nullptr; |
170 | | } |
171 | | |
172 | | // ------------------------------------------------------------------------------------------------ |
173 | | template <> |
174 | | inline const char *ValidateArrayContents<aiVector3D>(const aiVector3D *arr, unsigned int size, |
175 | 4.67k | const std::vector<bool> &dirtyMask, bool mayBeIdentical, bool mayBeZero) { |
176 | 4.67k | bool b = false; |
177 | 4.67k | unsigned int cnt = 0; |
178 | 383k | for (unsigned int i = 0; i < size; ++i) { |
179 | | |
180 | 379k | if (dirtyMask.size() && dirtyMask[i]) { |
181 | 16.8k | continue; |
182 | 16.8k | } |
183 | 362k | ++cnt; |
184 | | |
185 | 362k | const aiVector3D &v = arr[i]; |
186 | 362k | if (is_special_float(v.x) || is_special_float(v.y) || is_special_float(v.z)) { |
187 | 0 | return "INF/NAN was found in a vector component"; |
188 | 0 | } |
189 | 362k | if (!mayBeZero && !v.x && !v.y && !v.z) { |
190 | 344 | return "Found zero-length vector"; |
191 | 344 | } |
192 | 361k | if (i && v != arr[i - 1]) b = true; |
193 | 361k | } |
194 | 4.33k | if (cnt > 1 && !b && !mayBeIdentical) { |
195 | 642 | return "All vectors are identical"; |
196 | 642 | } |
197 | 3.68k | return nullptr; |
198 | 4.33k | } |
199 | | |
200 | | // ------------------------------------------------------------------------------------------------ |
201 | | template <typename T> |
202 | | inline bool ProcessArray(T *&in, unsigned int num, const char *name, |
203 | 4.67k | const std::vector<bool> &dirtyMask, bool mayBeIdentical = false, bool mayBeZero = true) { |
204 | 4.67k | const char *err = ValidateArrayContents(in, num, dirtyMask, mayBeIdentical, mayBeZero); |
205 | 4.67k | if (err) { |
206 | 986 | ASSIMP_LOG_ERROR("FindInvalidDataProcess fails on mesh ", name, ": ", err); |
207 | 986 | delete[] in; |
208 | 986 | in = nullptr; |
209 | 986 | return true; |
210 | 986 | } |
211 | 3.68k | return false; |
212 | 4.67k | } |
213 | | |
214 | | // ------------------------------------------------------------------------------------------------ |
215 | | template <typename T> |
216 | | AI_FORCE_INLINE bool EpsilonCompare(const T &n, const T &s, ai_real epsilon); |
217 | | |
218 | | // ------------------------------------------------------------------------------------------------ |
219 | 0 | AI_FORCE_INLINE bool EpsilonCompare(ai_real n, ai_real s, ai_real epsilon) { |
220 | 0 | return std::fabs(n - s) > epsilon; |
221 | 0 | } |
222 | | |
223 | | // ------------------------------------------------------------------------------------------------ |
224 | | template <> |
225 | 0 | bool EpsilonCompare<aiVectorKey>(const aiVectorKey &n, const aiVectorKey &s, ai_real epsilon) { |
226 | 0 | return EpsilonCompare(n.mValue.x, s.mValue.x, epsilon) && |
227 | 0 | EpsilonCompare(n.mValue.y, s.mValue.y, epsilon) && |
228 | 0 | EpsilonCompare(n.mValue.z, s.mValue.z, epsilon); |
229 | 0 | } |
230 | | |
231 | | // ------------------------------------------------------------------------------------------------ |
232 | | template <> |
233 | 0 | bool EpsilonCompare<aiQuatKey>(const aiQuatKey &n, const aiQuatKey &s, ai_real epsilon) { |
234 | 0 | return EpsilonCompare(n.mValue.x, s.mValue.x, epsilon) && |
235 | 0 | EpsilonCompare(n.mValue.y, s.mValue.y, epsilon) && |
236 | 0 | EpsilonCompare(n.mValue.z, s.mValue.z, epsilon) && |
237 | 0 | EpsilonCompare(n.mValue.w, s.mValue.w, epsilon); |
238 | 0 | } |
239 | | |
240 | | // ------------------------------------------------------------------------------------------------ |
241 | | template <typename T> |
242 | 8 | inline bool AllIdentical(T *in, unsigned int num, ai_real epsilon) { |
243 | 8 | if (num <= 1) { |
244 | 0 | return true; |
245 | 0 | } |
246 | | |
247 | 8 | if (fabs(epsilon) > 0.f) { |
248 | 0 | for (unsigned int i = 0; i < num - 1; ++i) { |
249 | 0 | if (!EpsilonCompare(in[i], in[i + 1], epsilon)) { |
250 | 0 | return false; |
251 | 0 | } |
252 | 0 | } |
253 | 8 | } else { |
254 | 8 | for (unsigned int i = 0; i < num - 1; ++i) { |
255 | 8 | if (in[i] != in[i + 1]) { |
256 | 8 | return false; |
257 | 8 | } |
258 | 8 | } |
259 | 8 | } |
260 | 0 | return true; |
261 | 8 | } bool AllIdentical<aiVectorKey>(aiVectorKey*, unsigned int, float) Line | Count | Source | 242 | 5 | inline bool AllIdentical(T *in, unsigned int num, ai_real epsilon) { | 243 | 5 | if (num <= 1) { | 244 | 0 | return true; | 245 | 0 | } | 246 | | | 247 | 5 | if (fabs(epsilon) > 0.f) { | 248 | 0 | for (unsigned int i = 0; i < num - 1; ++i) { | 249 | 0 | if (!EpsilonCompare(in[i], in[i + 1], epsilon)) { | 250 | 0 | return false; | 251 | 0 | } | 252 | 0 | } | 253 | 5 | } else { | 254 | 5 | for (unsigned int i = 0; i < num - 1; ++i) { | 255 | 5 | if (in[i] != in[i + 1]) { | 256 | 5 | return false; | 257 | 5 | } | 258 | 5 | } | 259 | 5 | } | 260 | 0 | return true; | 261 | 5 | } |
bool AllIdentical<aiQuatKey>(aiQuatKey*, unsigned int, float) Line | Count | Source | 242 | 3 | inline bool AllIdentical(T *in, unsigned int num, ai_real epsilon) { | 243 | 3 | if (num <= 1) { | 244 | 0 | return true; | 245 | 0 | } | 246 | | | 247 | 3 | if (fabs(epsilon) > 0.f) { | 248 | 0 | for (unsigned int i = 0; i < num - 1; ++i) { | 249 | 0 | if (!EpsilonCompare(in[i], in[i + 1], epsilon)) { | 250 | 0 | return false; | 251 | 0 | } | 252 | 0 | } | 253 | 3 | } else { | 254 | 3 | for (unsigned int i = 0; i < num - 1; ++i) { | 255 | 3 | if (in[i] != in[i + 1]) { | 256 | 3 | return false; | 257 | 3 | } | 258 | 3 | } | 259 | 3 | } | 260 | 0 | return true; | 261 | 3 | } |
|
262 | | |
263 | | // ------------------------------------------------------------------------------------------------ |
264 | | // Search an animation for invalid content |
265 | 4 | void FindInvalidDataProcess::ProcessAnimation(aiAnimation *anim) { |
266 | | // Process all animation channels |
267 | 10 | for (unsigned int a = 0; a < anim->mNumChannels; ++a) { |
268 | 6 | ProcessAnimationChannel(anim->mChannels[a]); |
269 | 6 | } |
270 | 4 | } |
271 | | |
272 | | // ------------------------------------------------------------------------------------------------ |
273 | 6 | void FindInvalidDataProcess::ProcessAnimationChannel(aiNodeAnim *anim) { |
274 | 6 | ai_assert(nullptr != anim); |
275 | 6 | if (anim->mNumPositionKeys == 0 && anim->mNumRotationKeys == 0 && anim->mNumScalingKeys == 0) { |
276 | 0 | ASSIMP_LOG_ERROR("Invalid node anuimation instance detected."); |
277 | |
|
278 | 0 | return; |
279 | 0 | } |
280 | | |
281 | | // Check whether all values in a tracks are identical - in this case |
282 | | // we can remove al keys except one. |
283 | | // POSITIONS |
284 | 6 | int i = 0; |
285 | 6 | if (anim->mNumPositionKeys > 1 && AllIdentical(anim->mPositionKeys, anim->mNumPositionKeys, configEpsilon)) { |
286 | 0 | aiVectorKey v = anim->mPositionKeys[0]; |
287 | | |
288 | | // Reallocate ... we need just ONE element, it makes no sense to reuse the array |
289 | 0 | delete[] anim->mPositionKeys; |
290 | 0 | anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys = 1]; |
291 | 0 | anim->mPositionKeys[0] = v; |
292 | 0 | i = 1; |
293 | 0 | } |
294 | | |
295 | | // ROTATIONS |
296 | 6 | if (anim->mNumRotationKeys > 1 && AllIdentical(anim->mRotationKeys, anim->mNumRotationKeys, configEpsilon)) { |
297 | 0 | aiQuatKey v = anim->mRotationKeys[0]; |
298 | | |
299 | | // Reallocate ... we need just ONE element, it makes no sense to reuse the array |
300 | 0 | delete[] anim->mRotationKeys; |
301 | 0 | anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys = 1]; |
302 | 0 | anim->mRotationKeys[0] = v; |
303 | 0 | i = 1; |
304 | 0 | } |
305 | | |
306 | | // SCALINGS |
307 | 6 | if (anim->mNumScalingKeys > 1 && AllIdentical(anim->mScalingKeys, anim->mNumScalingKeys, configEpsilon)) { |
308 | 0 | aiVectorKey v = anim->mScalingKeys[0]; |
309 | | |
310 | | // Reallocate ... we need just ONE element, it makes no sense to reuse the array |
311 | 0 | delete[] anim->mScalingKeys; |
312 | 0 | anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys = 1]; |
313 | 0 | anim->mScalingKeys[0] = v; |
314 | 0 | i = 1; |
315 | 0 | } |
316 | 6 | if (1 == i) { |
317 | 0 | ASSIMP_LOG_WARN("Simplified dummy tracks with just one key"); |
318 | 0 | } |
319 | 6 | } |
320 | | |
321 | | // ------------------------------------------------------------------------------------------------ |
322 | | // Search a mesh for invalid contents |
323 | 4.28k | int FindInvalidDataProcess::ProcessMesh(aiMesh *pMesh) { |
324 | 4.28k | bool ret = false; |
325 | 4.28k | std::vector<bool> dirtyMask(pMesh->mNumVertices, pMesh->mNumFaces != 0); |
326 | | |
327 | | // Ignore elements that are not referenced by vertices. |
328 | | // (they are, for example, caused by the FindDegenerates step) |
329 | 134k | for (unsigned int m = 0; m < pMesh->mNumFaces; ++m) { |
330 | 130k | const aiFace &f = pMesh->mFaces[m]; |
331 | | |
332 | 487k | for (unsigned int i = 0; i < f.mNumIndices; ++i) { |
333 | 356k | dirtyMask[f.mIndices[i]] = false; |
334 | 356k | } |
335 | 130k | } |
336 | | |
337 | | // Process vertex positions |
338 | 4.28k | if (pMesh->mVertices && ProcessArray(pMesh->mVertices, pMesh->mNumVertices, "positions", dirtyMask)) { |
339 | 627 | ASSIMP_LOG_ERROR("Deleting mesh: Unable to continue without vertex positions"); |
340 | | |
341 | 627 | return 2; |
342 | 627 | } |
343 | | |
344 | | // process texture coordinates |
345 | 3.65k | if (!mIgnoreTexCoods) { |
346 | 3.69k | for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS && pMesh->mTextureCoords[i]; ++i) { |
347 | 40 | if (ProcessArray(pMesh->mTextureCoords[i], pMesh->mNumVertices, "uvcoords", dirtyMask)) { |
348 | 15 | pMesh->mNumUVComponents[i] = 0; |
349 | | |
350 | | // delete all subsequent texture coordinate sets. |
351 | 120 | for (unsigned int a = i + 1; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { |
352 | 105 | delete[] pMesh->mTextureCoords[a]; |
353 | 105 | pMesh->mTextureCoords[a] = nullptr; |
354 | 105 | pMesh->mNumUVComponents[a] = 0; |
355 | 105 | } |
356 | | |
357 | 15 | ret = true; |
358 | 15 | } |
359 | 40 | } |
360 | 3.65k | } |
361 | | |
362 | | // -- we don't validate vertex colors, it's difficult to say whether |
363 | | // they are invalid or not. |
364 | | |
365 | | // Normals and tangents are undefined for point and line faces. |
366 | 3.65k | if (pMesh->mNormals || pMesh->mTangents) { |
367 | | |
368 | 381 | if (aiPrimitiveType_POINT & pMesh->mPrimitiveTypes || |
369 | 381 | aiPrimitiveType_LINE & pMesh->mPrimitiveTypes) { |
370 | 32 | if (aiPrimitiveType_TRIANGLE & pMesh->mPrimitiveTypes || |
371 | 32 | aiPrimitiveType_POLYGON & pMesh->mPrimitiveTypes) { |
372 | | // We need to update the lookup-table |
373 | 0 | for (unsigned int m = 0; m < pMesh->mNumFaces; ++m) { |
374 | 0 | const aiFace &f = pMesh->mFaces[m]; |
375 | |
|
376 | 0 | if (f.mNumIndices < 3) { |
377 | 0 | dirtyMask[f.mIndices[0]] = true; |
378 | 0 | if (f.mNumIndices == 2) { |
379 | 0 | dirtyMask[f.mIndices[1]] = true; |
380 | 0 | } |
381 | 0 | } |
382 | 0 | } |
383 | 0 | } |
384 | | // Normals, tangents and bitangents are undefined for |
385 | | // the whole mesh (and should not even be there) |
386 | 32 | else { |
387 | 32 | return ret; |
388 | 32 | } |
389 | 32 | } |
390 | | |
391 | | // Process mesh normals |
392 | 349 | if (pMesh->mNormals && ProcessArray(pMesh->mNormals, pMesh->mNumVertices, |
393 | 349 | "normals", dirtyMask, true, false)) |
394 | 344 | ret = true; |
395 | | |
396 | | // Process mesh tangents |
397 | 349 | if (pMesh->mTangents && ProcessArray(pMesh->mTangents, pMesh->mNumVertices, "tangents", dirtyMask)) { |
398 | 0 | delete[] pMesh->mBitangents; |
399 | 0 | pMesh->mBitangents = nullptr; |
400 | 0 | ret = true; |
401 | 0 | } |
402 | | |
403 | | // Process mesh bitangents |
404 | 349 | if (pMesh->mBitangents && ProcessArray(pMesh->mBitangents, pMesh->mNumVertices, "bitangents", dirtyMask)) { |
405 | 0 | delete[] pMesh->mTangents; |
406 | 0 | pMesh->mTangents = nullptr; |
407 | 0 | ret = true; |
408 | 0 | } |
409 | 349 | } |
410 | 3.62k | return ret ? 1 : 0; |
411 | 3.65k | } |
412 | | |
413 | | #endif // !! ASSIMP_BUILD_NO_FINDINVALIDDATA_PROCESS |