Coverage Report

Created: 2025-08-03 06:54

/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