Coverage Report

Created: 2026-05-23 06:04

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