Coverage Report

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