Coverage Report

Created: 2026-05-04 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/PostProcessing/FindInvalidDataProcess.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 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
38.9k
        configEpsilon(0.0), mIgnoreTexCoods(false) {
60
    // nothing to do here
61
38.9k
}
62
63
// ------------------------------------------------------------------------------------------------
64
// Returns whether the processing step is present in the given flag field.
65
11.5k
bool FindInvalidDataProcess::IsActive(unsigned int pFlags) const {
66
11.5k
    return 0 != (pFlags & aiProcess_FindInvalidData);
67
11.5k
}
68
69
// ------------------------------------------------------------------------------------------------
70
// Setup import configuration
71
11.5k
void FindInvalidDataProcess::SetupProperties(const Importer *pImp) {
72
    // Get the current value of AI_CONFIG_PP_FID_ANIM_ACCURACY
73
11.5k
    configEpsilon = (0 != pImp->GetPropertyFloat(AI_CONFIG_PP_FID_ANIM_ACCURACY, 0.f));
74
11.5k
    mIgnoreTexCoods = pImp->GetPropertyBool(AI_CONFIG_PP_FID_IGNORE_TEXTURECOORDS, false);
75
11.5k
}
76
77
// ------------------------------------------------------------------------------------------------
78
// Update mesh references in the node graph
79
207k
void UpdateMeshReferences(aiNode *node, const std::vector<unsigned int> &meshMapping) {
80
207k
    if (node->mNumMeshes) {
81
91.3k
        unsigned int out = 0;
82
253k
        for (unsigned int a = 0; a < node->mNumMeshes; ++a) {
83
84
162k
            unsigned int ref = node->mMeshes[a];
85
162k
            if (ref >= meshMapping.size())
86
0
                throw DeadlyImportError("Invalid mesh ref");
87
88
162k
            if (UINT_MAX != (ref = meshMapping[ref])) {
89
146k
                node->mMeshes[out++] = ref;
90
146k
            }
91
162k
        }
92
        // just let the members that are unused, that's much cheaper
93
        // than a full array realloc'n'copy party ...
94
91.3k
        node->mNumMeshes = out;
95
91.3k
        if (0 == out) {
96
7.62k
            delete[] node->mMeshes;
97
7.62k
            node->mMeshes = nullptr;
98
7.62k
        }
99
91.3k
    }
100
    // recursively update all children
101
414k
    for (unsigned int i = 0; i < node->mNumChildren; ++i) {
102
206k
        UpdateMeshReferences(node->mChildren[i], meshMapping);
103
206k
    }
104
207k
}
105
106
// ------------------------------------------------------------------------------------------------
107
// Executes the post processing step on the given imported data.
108
11.5k
void FindInvalidDataProcess::Execute(aiScene *pScene) {
109
11.5k
    ASSIMP_LOG_DEBUG("FindInvalidDataProcess begin");
110
111
11.5k
    bool out = false;
112
11.5k
    std::vector<unsigned int> meshMapping(pScene->mNumMeshes);
113
11.5k
    unsigned int real = 0;
114
115
    // Process meshes
116
294k
    for (unsigned int a = 0; a < pScene->mNumMeshes; a++) {
117
283k
        int result = ProcessMesh(pScene->mMeshes[a]);
118
283k
        if (0 == result) {
119
243k
            out = true;
120
243k
        }
121
283k
        if (2 == result) {
122
            // remove this mesh
123
19.2k
            delete pScene->mMeshes[a];
124
19.2k
            pScene->mMeshes[a] = nullptr;
125
126
19.2k
            meshMapping[a] = UINT_MAX;
127
19.2k
            out = true;
128
19.2k
            continue;
129
19.2k
        }
130
131
263k
        pScene->mMeshes[real] = pScene->mMeshes[a];
132
263k
        meshMapping[a] = real++;
133
263k
    }
134
135
    // Process animations
136
12.8k
    for (unsigned int animIdx = 0; animIdx < pScene->mNumAnimations; ++animIdx) {
137
1.21k
        ProcessAnimation(pScene->mAnimations[animIdx]);
138
1.21k
    }
139
140
11.5k
    if (out) {
141
10.1k
        if (real != pScene->mNumMeshes) {
142
2.55k
            if (!real) {
143
1.16k
                throw DeadlyImportError("No meshes remaining");
144
1.16k
            }
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
1.39k
            try {
150
1.39k
                UpdateMeshReferences(pScene->mRootNode, meshMapping);
151
1.39k
            } 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
1.39k
            pScene->mNumMeshes = real;
157
1.39k
        }
158
159
10.1k
        ASSIMP_LOG_INFO("FindInvalidDataProcess finished. Found issues ...");
160
8.98k
    } else {
161
1.44k
        ASSIMP_LOG_DEBUG("FindInvalidDataProcess finished. Everything seems to be OK.");
162
1.44k
    }
163
11.5k
}
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
349k
        const std::vector<bool> &dirtyMask, bool mayBeIdentical, bool mayBeZero) {
176
349k
    bool b = false;
177
349k
    unsigned int cnt = 0;
178
23.5M
    for (unsigned int i = 0; i < size; ++i) {
179
180
23.2M
        if (dirtyMask.size() && dirtyMask[i]) {
181
9.43M
            continue;
182
9.43M
        }
183
13.8M
        ++cnt;
184
185
13.8M
        const aiVector3D &v = arr[i];
186
13.8M
        if (is_special_float(v.x) || is_special_float(v.y) || is_special_float(v.z)) {
187
10.2k
            return "INF/NAN was found in a vector component";
188
10.2k
        }
189
13.8M
        if (!mayBeZero && !v.x && !v.y && !v.z) {
190
14.0k
            return "Found zero-length vector";
191
14.0k
        }
192
13.7M
        if (i && v != arr[i - 1]) b = true;
193
13.7M
    }
194
325k
    if (cnt > 1 && !b && !mayBeIdentical) {
195
15.7k
        return "All vectors are identical";
196
15.7k
    }
197
309k
    return nullptr;
198
325k
}
199
200
// ------------------------------------------------------------------------------------------------
201
template <typename T>
202
inline bool ProcessArray(T *&in, unsigned int num, const char *name,
203
349k
        const std::vector<bool> &dirtyMask, bool mayBeIdentical = false, bool mayBeZero = true) {
204
349k
    const char *err = ValidateArrayContents(in, num, dirtyMask, mayBeIdentical, mayBeZero);
205
349k
    if (err) {
206
39.9k
        ASSIMP_LOG_ERROR("FindInvalidDataProcess fails on mesh ", name, ": ", err);
207
39.9k
        delete[] in;
208
39.9k
        in = nullptr;
209
39.9k
        return true;
210
39.9k
    }
211
309k
    return false;
212
349k
}
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
15.2k
inline bool AllIdentical(T *in, unsigned int num, ai_real epsilon) {
243
15.2k
    if (num <= 1) {
244
0
        return true;
245
0
    }
246
247
15.2k
    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
15.2k
    } else {
254
3.85M
        for (unsigned int i = 0; i < num - 1; ++i) {
255
3.84M
            if (in[i] != in[i + 1]) {
256
3.89k
                return false;
257
3.89k
            }
258
3.84M
        }
259
15.2k
    }
260
11.3k
    return true;
261
15.2k
}
bool AllIdentical<aiVectorKey>(aiVectorKey*, unsigned int, float)
Line
Count
Source
242
10.1k
inline bool AllIdentical(T *in, unsigned int num, ai_real epsilon) {
243
10.1k
    if (num <= 1) {
244
0
        return true;
245
0
    }
246
247
10.1k
    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
10.1k
    } else {
254
3.84M
        for (unsigned int i = 0; i < num - 1; ++i) {
255
3.83M
            if (in[i] != in[i + 1]) {
256
2.12k
                return false;
257
2.12k
            }
258
3.83M
        }
259
10.1k
    }
260
8.00k
    return true;
261
10.1k
}
bool AllIdentical<aiQuatKey>(aiQuatKey*, unsigned int, float)
Line
Count
Source
242
5.11k
inline bool AllIdentical(T *in, unsigned int num, ai_real epsilon) {
243
5.11k
    if (num <= 1) {
244
0
        return true;
245
0
    }
246
247
5.11k
    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.11k
    } else {
254
10.0k
        for (unsigned int i = 0; i < num - 1; ++i) {
255
6.71k
            if (in[i] != in[i + 1]) {
256
1.76k
                return false;
257
1.76k
            }
258
6.71k
        }
259
5.11k
    }
260
3.34k
    return true;
261
5.11k
}
262
263
// ------------------------------------------------------------------------------------------------
264
// Search an animation for invalid content
265
1.21k
void FindInvalidDataProcess::ProcessAnimation(aiAnimation *anim) {
266
    // Process all animation channels
267
7.07k
    for (unsigned int a = 0; a < anim->mNumChannels; ++a) {
268
5.85k
        ProcessAnimationChannel(anim->mChannels[a]);
269
5.85k
    }
270
1.21k
}
271
272
// ------------------------------------------------------------------------------------------------
273
5.85k
void FindInvalidDataProcess::ProcessAnimationChannel(aiNodeAnim *anim) {
274
5.85k
    ai_assert(nullptr != anim);
275
5.85k
    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
5.85k
    int i = 0;
285
5.85k
    if (anim->mNumPositionKeys > 1 && AllIdentical(anim->mPositionKeys, anim->mNumPositionKeys, configEpsilon)) {
286
4.09k
        aiVectorKey v = anim->mPositionKeys[0];
287
288
        // Reallocate ... we need just ONE element, it makes no sense to reuse the array
289
4.09k
        delete[] anim->mPositionKeys;
290
4.09k
        anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys = 1];
291
4.09k
        anim->mPositionKeys[0] = v;
292
4.09k
        i = 1;
293
4.09k
    }
294
295
    // ROTATIONS
296
5.85k
    if (anim->mNumRotationKeys > 1 && AllIdentical(anim->mRotationKeys, anim->mNumRotationKeys, configEpsilon)) {
297
3.34k
        aiQuatKey v = anim->mRotationKeys[0];
298
299
        // Reallocate ... we need just ONE element, it makes no sense to reuse the array
300
3.34k
        delete[] anim->mRotationKeys;
301
3.34k
        anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys = 1];
302
3.34k
        anim->mRotationKeys[0] = v;
303
3.34k
        i = 1;
304
3.34k
    }
305
306
    // SCALINGS
307
5.85k
    if (anim->mNumScalingKeys > 1 && AllIdentical(anim->mScalingKeys, anim->mNumScalingKeys, configEpsilon)) {
308
3.91k
        aiVectorKey v = anim->mScalingKeys[0];
309
310
        // Reallocate ... we need just ONE element, it makes no sense to reuse the array
311
3.91k
        delete[] anim->mScalingKeys;
312
3.91k
        anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys = 1];
313
3.91k
        anim->mScalingKeys[0] = v;
314
3.91k
        i = 1;
315
3.91k
    }
316
5.85k
    if (1 == i) {
317
4.61k
        ASSIMP_LOG_WARN("Simplified dummy tracks with just one key");
318
4.61k
    }
319
5.85k
}
320
321
// ------------------------------------------------------------------------------------------------
322
// Search a mesh for invalid contents
323
283k
int FindInvalidDataProcess::ProcessMesh(aiMesh *pMesh) {
324
283k
    bool ret = false;
325
283k
    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
5.43M
    for (unsigned int m = 0; m < pMesh->mNumFaces; ++m) {
330
5.15M
        const aiFace &f = pMesh->mFaces[m];
331
332
17.1M
        for (unsigned int i = 0; i < f.mNumIndices; ++i) {
333
11.9M
            dirtyMask[f.mIndices[i]] = false;
334
11.9M
        }
335
5.15M
    }
336
337
    // Process vertex positions
338
283k
    if (pMesh->mVertices && ProcessArray(pMesh->mVertices, pMesh->mNumVertices, "positions", dirtyMask)) {
339
19.2k
        ASSIMP_LOG_ERROR("Deleting mesh: Unable to continue without vertex positions");
340
341
19.2k
        return 2;
342
19.2k
    }
343
344
    // process texture coordinates
345
263k
    if (!mIgnoreTexCoods) {
346
278k
        for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS && pMesh->mTextureCoords[i]; ++i) {
347
14.9k
            if (ProcessArray(pMesh->mTextureCoords[i], pMesh->mNumVertices, "uvcoords", dirtyMask)) {
348
5.81k
                pMesh->mNumUVComponents[i] = 0;
349
350
                // delete all subsequent texture coordinate sets.
351
46.5k
                for (unsigned int a = i + 1; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
352
40.7k
                    delete[] pMesh->mTextureCoords[a];
353
40.7k
                    pMesh->mTextureCoords[a] = nullptr;
354
40.7k
                    pMesh->mNumUVComponents[a] = 0;
355
40.7k
                }
356
357
5.81k
                ret = true;
358
5.81k
            }
359
14.9k
        }
360
263k
    }
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
263k
    if (pMesh->mNormals || pMesh->mTangents) {
367
368
185k
        if (aiPrimitiveType_POINT & pMesh->mPrimitiveTypes ||
369
134k
                aiPrimitiveType_LINE & pMesh->mPrimitiveTypes) {
370
134k
            if (aiPrimitiveType_TRIANGLE & pMesh->mPrimitiveTypes ||
371
134k
                    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
134k
            else {
387
134k
                return ret;
388
134k
            }
389
134k
        }
390
391
        // Process mesh normals
392
51.4k
        if (pMesh->mNormals && ProcessArray(pMesh->mNormals, pMesh->mNumVertices,
393
51.4k
                                       "normals", dirtyMask, true, false))
394
14.9k
            ret = true;
395
396
        // Process mesh tangents
397
51.4k
        if (pMesh->mTangents && ProcessArray(pMesh->mTangents, pMesh->mNumVertices, "tangents", dirtyMask)) {
398
5
            delete[] pMesh->mBitangents;
399
5
            pMesh->mBitangents = nullptr;
400
5
            ret = true;
401
5
        }
402
403
        // Process mesh bitangents
404
51.4k
        if (pMesh->mBitangents && ProcessArray(pMesh->mBitangents, pMesh->mNumVertices, "bitangents", dirtyMask)) {
405
12
            delete[] pMesh->mTangents;
406
12
            pMesh->mTangents = nullptr;
407
12
            ret = true;
408
12
        }
409
51.4k
    }
410
129k
    return ret ? 1 : 0;
411
263k
}
412
413
#endif // !! ASSIMP_BUILD_NO_FINDINVALIDDATA_PROCESS