Coverage Report

Created: 2026-01-07 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/PostProcessing/ValidateDataStructure.cpp
Line
Count
Source
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  ValidateDataStructure.cpp
43
 *  @brief Implementation of the post processing step to validate
44
 *    the data structure returned by Assimp.
45
 */
46
47
// internal headers
48
#include "ValidateDataStructure.h"
49
#include "ProcessHelper.h"
50
#include <assimp/BaseImporter.h>
51
#include <assimp/fast_atof.h>
52
#include <memory>
53
54
// CRT headers
55
#include <stdarg.h>
56
57
using namespace Assimp;
58
59
// ------------------------------------------------------------------------------------------------
60
// Constructor to be privately used by Importer
61
130
ValidateDSProcess::ValidateDSProcess() : mScene(nullptr) {}
62
63
// ------------------------------------------------------------------------------------------------
64
// Returns whether the processing step is present in the given flag field.
65
0
bool ValidateDSProcess::IsActive(unsigned int pFlags) const {
66
0
    return (pFlags & aiProcess_ValidateDataStructure) != 0;
67
0
}
68
// ------------------------------------------------------------------------------------------------
69
61
AI_WONT_RETURN void ValidateDSProcess::ReportError(const char *msg, ...) {
70
61
    ai_assert(nullptr != msg);
71
72
61
    va_list args;
73
61
    va_start(args, msg);
74
75
61
    char szBuffer[3000];
76
61
    const int iLen = vsnprintf(szBuffer, sizeof(szBuffer), msg, args);
77
61
    ai_assert(iLen > 0);
78
79
61
    va_end(args);
80
81
61
    throw DeadlyImportError("Validation failed: ", std::string(szBuffer, iLen));
82
61
}
83
// ------------------------------------------------------------------------------------------------
84
204
void ValidateDSProcess::ReportWarning(const char *msg, ...) {
85
204
    ai_assert(nullptr != msg);
86
87
204
    va_list args;
88
204
    va_start(args, msg);
89
90
204
    char szBuffer[3000];
91
204
    const int iLen = vsnprintf(szBuffer, sizeof(szBuffer), msg, args);
92
204
    ai_assert(iLen > 0);
93
94
204
    va_end(args);
95
204
    ASSIMP_LOG_WARN("Validation warning: ", std::string(szBuffer, iLen));
96
204
}
97
98
// ------------------------------------------------------------------------------------------------
99
11.5M
inline int HasNameMatch(const aiString &in, aiNode *node) {
100
11.5M
    int result = (node->mName == in ? 1 : 0);
101
23.1M
    for (unsigned int i = 0; i < node->mNumChildren; ++i) {
102
11.5M
        result += HasNameMatch(in, node->mChildren[i]);
103
11.5M
    }
104
11.5M
    return result;
105
11.5M
}
106
107
// ------------------------------------------------------------------------------------------------
108
template <typename T>
109
140
inline void ValidateDSProcess::DoValidation(T **parray, unsigned int size, const char *firstName, const char *secondName) {
110
    // validate all entries
111
140
    if (size == 0) {
112
0
        return;
113
0
    }
114
115
140
    if (!parray) {
116
0
        ReportError("aiScene::%s is nullptr (aiScene::%s is %i)",
117
0
                firstName, secondName, size);
118
0
    }
119
120
2.34k
    for (unsigned int i = 0; i < size; ++i) {
121
2.20k
        if (!parray[i]) {
122
0
            ReportError("aiScene::%s[%i] is nullptr (aiScene::%s is %i)",
123
0
                    firstName, i, secondName, size);
124
0
        }
125
2.20k
        Validate(parray[i]);
126
2.20k
    }
127
140
}
void Assimp::ValidateDSProcess::DoValidation<aiMesh>(aiMesh**, unsigned int, char const*, char const*)
Line
Count
Source
109
73
inline void ValidateDSProcess::DoValidation(T **parray, unsigned int size, const char *firstName, const char *secondName) {
110
    // validate all entries
111
73
    if (size == 0) {
112
0
        return;
113
0
    }
114
115
73
    if (!parray) {
116
0
        ReportError("aiScene::%s is nullptr (aiScene::%s is %i)",
117
0
                firstName, secondName, size);
118
0
    }
119
120
1.82k
    for (unsigned int i = 0; i < size; ++i) {
121
1.75k
        if (!parray[i]) {
122
0
            ReportError("aiScene::%s[%i] is nullptr (aiScene::%s is %i)",
123
0
                    firstName, i, secondName, size);
124
0
        }
125
1.75k
        Validate(parray[i]);
126
1.75k
    }
127
73
}
void Assimp::ValidateDSProcess::DoValidation<aiAnimation>(aiAnimation**, unsigned int, char const*, char const*)
Line
Count
Source
109
11
inline void ValidateDSProcess::DoValidation(T **parray, unsigned int size, const char *firstName, const char *secondName) {
110
    // validate all entries
111
11
    if (size == 0) {
112
0
        return;
113
0
    }
114
115
11
    if (!parray) {
116
0
        ReportError("aiScene::%s is nullptr (aiScene::%s is %i)",
117
0
                firstName, secondName, size);
118
0
    }
119
120
22
    for (unsigned int i = 0; i < size; ++i) {
121
11
        if (!parray[i]) {
122
0
            ReportError("aiScene::%s[%i] is nullptr (aiScene::%s is %i)",
123
0
                    firstName, i, secondName, size);
124
0
        }
125
11
        Validate(parray[i]);
126
11
    }
127
11
}
Unexecuted instantiation: void Assimp::ValidateDSProcess::DoValidation<aiTexture>(aiTexture**, unsigned int, char const*, char const*)
void Assimp::ValidateDSProcess::DoValidation<aiMaterial>(aiMaterial**, unsigned int, char const*, char const*)
Line
Count
Source
109
56
inline void ValidateDSProcess::DoValidation(T **parray, unsigned int size, const char *firstName, const char *secondName) {
110
    // validate all entries
111
56
    if (size == 0) {
112
0
        return;
113
0
    }
114
115
56
    if (!parray) {
116
0
        ReportError("aiScene::%s is nullptr (aiScene::%s is %i)",
117
0
                firstName, secondName, size);
118
0
    }
119
120
495
    for (unsigned int i = 0; i < size; ++i) {
121
439
        if (!parray[i]) {
122
0
            ReportError("aiScene::%s[%i] is nullptr (aiScene::%s is %i)",
123
0
                    firstName, i, secondName, size);
124
0
        }
125
439
        Validate(parray[i]);
126
439
    }
127
56
}
128
129
// ------------------------------------------------------------------------------------------------
130
template <typename T>
131
inline void ValidateDSProcess::DoValidationEx(T **parray, unsigned int size,
132
5
        const char *firstName, const char *secondName) {
133
    // validate all entries
134
5
    if (size == 0) {
135
0
        return;
136
0
    }
137
138
5
    if (!parray) {
139
0
        ReportError("aiScene::%s is nullptr (aiScene::%s is %i)",
140
0
                firstName, secondName, size);
141
0
    }
142
2.78k
    for (unsigned int i = 0; i < size; ++i) {
143
2.77k
        if (!parray[i]) {
144
0
            ReportError("aiScene::%s[%u] is nullptr (aiScene::%s is %u)",
145
0
                    firstName, i, secondName, size);
146
0
        }
147
2.77k
        Validate(parray[i]);
148
149
        // check whether there are duplicate names
150
3.74M
        for (unsigned int a = i + 1; a < size; ++a) {
151
3.74M
            if (parray[i]->mName == parray[a]->mName) {
152
1
                ReportError("aiScene::%s[%u] has the same name as "
153
1
                            "aiScene::%s[%u]",
154
1
                        firstName, i, secondName, a);
155
1
            }
156
3.74M
        }
157
2.77k
    }
158
5
}
void Assimp::ValidateDSProcess::DoValidationEx<aiCamera>(aiCamera**, unsigned int, char const*, char const*)
Line
Count
Source
132
4
        const char *firstName, const char *secondName) {
133
    // validate all entries
134
4
    if (size == 0) {
135
0
        return;
136
0
    }
137
138
4
    if (!parray) {
139
0
        ReportError("aiScene::%s is nullptr (aiScene::%s is %i)",
140
0
                firstName, secondName, size);
141
0
    }
142
47
    for (unsigned int i = 0; i < size; ++i) {
143
43
        if (!parray[i]) {
144
0
            ReportError("aiScene::%s[%u] is nullptr (aiScene::%s is %u)",
145
0
                    firstName, i, secondName, size);
146
0
        }
147
43
        Validate(parray[i]);
148
149
        // check whether there are duplicate names
150
656
        for (unsigned int a = i + 1; a < size; ++a) {
151
613
            if (parray[i]->mName == parray[a]->mName) {
152
1
                ReportError("aiScene::%s[%u] has the same name as "
153
1
                            "aiScene::%s[%u]",
154
1
                        firstName, i, secondName, a);
155
1
            }
156
613
        }
157
43
    }
158
4
}
void Assimp::ValidateDSProcess::DoValidationEx<aiLight>(aiLight**, unsigned int, char const*, char const*)
Line
Count
Source
132
1
        const char *firstName, const char *secondName) {
133
    // validate all entries
134
1
    if (size == 0) {
135
0
        return;
136
0
    }
137
138
1
    if (!parray) {
139
0
        ReportError("aiScene::%s is nullptr (aiScene::%s is %i)",
140
0
                firstName, secondName, size);
141
0
    }
142
2.73k
    for (unsigned int i = 0; i < size; ++i) {
143
2.73k
        if (!parray[i]) {
144
0
            ReportError("aiScene::%s[%u] is nullptr (aiScene::%s is %u)",
145
0
                    firstName, i, secondName, size);
146
0
        }
147
2.73k
        Validate(parray[i]);
148
149
        // check whether there are duplicate names
150
3.74M
        for (unsigned int a = i + 1; a < size; ++a) {
151
3.74M
            if (parray[i]->mName == parray[a]->mName) {
152
0
                ReportError("aiScene::%s[%u] has the same name as "
153
0
                            "aiScene::%s[%u]",
154
0
                        firstName, i, secondName, a);
155
0
            }
156
3.74M
        }
157
2.73k
    }
158
1
}
159
160
// ------------------------------------------------------------------------------------------------
161
template <typename T>
162
inline void ValidateDSProcess::DoValidationWithNameCheck(T **array, unsigned int size, const char *firstName,
163
5
        const char *secondName) {
164
    // validate all entries
165
5
    DoValidationEx(array, size, firstName, secondName);
166
167
2.78k
    for (unsigned int i = 0; i < size; ++i) {
168
2.77k
        int res = HasNameMatch(array[i]->mName, mScene->mRootNode);
169
2.77k
        if (0 == res) {
170
0
            const std::string name = static_cast<char *>(array[i]->mName.data);
171
0
            ReportError("aiScene::%s[%i] has no corresponding node in the scene graph (%s)",
172
0
                    firstName, i, name.c_str());
173
2.77k
        } else if (1 != res) {
174
0
            const std::string name = static_cast<char *>(array[i]->mName.data);
175
0
            ReportError("aiScene::%s[%i]: there are more than one nodes with %s as name",
176
0
                    firstName, i, name.c_str());
177
0
        }
178
2.77k
    }
179
5
}
void Assimp::ValidateDSProcess::DoValidationWithNameCheck<aiCamera>(aiCamera**, unsigned int, char const*, char const*)
Line
Count
Source
163
4
        const char *secondName) {
164
    // validate all entries
165
4
    DoValidationEx(array, size, firstName, secondName);
166
167
46
    for (unsigned int i = 0; i < size; ++i) {
168
42
        int res = HasNameMatch(array[i]->mName, mScene->mRootNode);
169
42
        if (0 == res) {
170
0
            const std::string name = static_cast<char *>(array[i]->mName.data);
171
0
            ReportError("aiScene::%s[%i] has no corresponding node in the scene graph (%s)",
172
0
                    firstName, i, name.c_str());
173
42
        } else if (1 != res) {
174
0
            const std::string name = static_cast<char *>(array[i]->mName.data);
175
0
            ReportError("aiScene::%s[%i]: there are more than one nodes with %s as name",
176
0
                    firstName, i, name.c_str());
177
0
        }
178
42
    }
179
4
}
void Assimp::ValidateDSProcess::DoValidationWithNameCheck<aiLight>(aiLight**, unsigned int, char const*, char const*)
Line
Count
Source
163
1
        const char *secondName) {
164
    // validate all entries
165
1
    DoValidationEx(array, size, firstName, secondName);
166
167
2.73k
    for (unsigned int i = 0; i < size; ++i) {
168
2.73k
        int res = HasNameMatch(array[i]->mName, mScene->mRootNode);
169
2.73k
        if (0 == res) {
170
0
            const std::string name = static_cast<char *>(array[i]->mName.data);
171
0
            ReportError("aiScene::%s[%i] has no corresponding node in the scene graph (%s)",
172
0
                    firstName, i, name.c_str());
173
2.73k
        } else if (1 != res) {
174
0
            const std::string name = static_cast<char *>(array[i]->mName.data);
175
0
            ReportError("aiScene::%s[%i]: there are more than one nodes with %s as name",
176
0
                    firstName, i, name.c_str());
177
0
        }
178
2.73k
    }
179
1
}
180
181
// ------------------------------------------------------------------------------------------------
182
// Executes the post processing step on the given imported data.
183
130
void ValidateDSProcess::Execute(aiScene *pScene) {
184
130
    mScene = pScene;
185
130
    ASSIMP_LOG_DEBUG("ValidateDataStructureProcess begin");
186
187
    // validate the node graph of the scene
188
130
    Validate(pScene->mRootNode);
189
190
    // validate all meshes
191
130
    if (pScene->mNumMeshes) {
192
73
        DoValidation(pScene->mMeshes, pScene->mNumMeshes, "mMeshes", "mNumMeshes");
193
73
    } else if (!(mScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE)) {
194
28
        ReportError("aiScene::mNumMeshes is 0. At least one mesh must be there");
195
29
    } else if (pScene->mMeshes) {
196
0
        ReportError("aiScene::mMeshes is non-null although there are no meshes");
197
0
    }
198
199
    // validate all animations
200
130
    if (pScene->mNumAnimations) {
201
11
        DoValidation(pScene->mAnimations, pScene->mNumAnimations,
202
11
                "mAnimations", "mNumAnimations");
203
119
    } else if (pScene->mAnimations) {
204
0
        ReportError("aiScene::mAnimations is non-null although there are no animations");
205
0
    }
206
207
    // validate all cameras
208
130
    if (pScene->mNumCameras) {
209
4
        DoValidationWithNameCheck(pScene->mCameras, pScene->mNumCameras,
210
4
                "mCameras", "mNumCameras");
211
126
    } else if (pScene->mCameras) {
212
0
        ReportError("aiScene::mCameras is non-null although there are no cameras");
213
0
    }
214
215
    // validate all lights
216
130
    if (pScene->mNumLights) {
217
1
        DoValidationWithNameCheck(pScene->mLights, pScene->mNumLights,
218
1
                "mLights", "mNumLights");
219
129
    } else if (pScene->mLights) {
220
0
        ReportError("aiScene::mLights is non-null although there are no lights");
221
0
    }
222
223
    // validate all textures
224
130
    if (pScene->mNumTextures) {
225
0
        DoValidation(pScene->mTextures, pScene->mNumTextures,
226
0
                "mTextures", "mNumTextures");
227
130
    } else if (pScene->mTextures) {
228
0
        ReportError("aiScene::mTextures is non-null although there are no textures");
229
0
    }
230
231
    // validate all materials
232
130
    if (pScene->mNumMaterials) {
233
56
        DoValidation(pScene->mMaterials, pScene->mNumMaterials, "mMaterials", "mNumMaterials");
234
56
    }
235
74
    else if (pScene->mMaterials) {
236
0
        ReportError("aiScene::mMaterials is non-null although there are no materials");
237
0
    }
238
239
    //  if (!has)ReportError("The aiScene data structure is empty");
240
130
    ASSIMP_LOG_DEBUG("ValidateDataStructureProcess end");
241
130
}
242
243
// ------------------------------------------------------------------------------------------------
244
2.73k
void ValidateDSProcess::Validate(const aiLight *pLight) {
245
2.73k
    if (pLight->mType == aiLightSource_UNDEFINED)
246
0
        ReportWarning("aiLight::mType is aiLightSource_UNDEFINED");
247
248
2.73k
    if (!pLight->mAttenuationConstant &&
249
2.73k
            !pLight->mAttenuationLinear &&
250
0
            !pLight->mAttenuationQuadratic) {
251
0
        ReportWarning("aiLight::mAttenuationXXX - all are zero");
252
0
    }
253
254
2.73k
    if (pLight->mAngleInnerCone > pLight->mAngleOuterCone)
255
0
        ReportError("aiLight::mAngleInnerCone is larger than aiLight::mAngleOuterCone");
256
257
2.73k
    if (pLight->mColorDiffuse.IsBlack() && pLight->mColorAmbient.IsBlack() && pLight->mColorSpecular.IsBlack()) {
258
144
        ReportWarning("aiLight::mColorXXX - all are black and won't have any influence");
259
144
    }
260
2.73k
}
261
262
// ------------------------------------------------------------------------------------------------
263
43
void ValidateDSProcess::Validate(const aiCamera *pCamera) {
264
43
    if (pCamera->mClipPlaneFar <= pCamera->mClipPlaneNear)
265
0
        ReportError("aiCamera::mClipPlaneFar must be >= aiCamera::mClipPlaneNear");
266
267
    // There are many 3ds files with invalid FOVs. No reason to reject them at all ... a warning is appropriate.
268
43
    if (!pCamera->mHorizontalFOV || pCamera->mHorizontalFOV >= (float)AI_MATH_PI)
269
0
        ReportWarning("%f is not a valid value for aiCamera::mHorizontalFOV", pCamera->mHorizontalFOV);
270
43
}
271
272
// ------------------------------------------------------------------------------------------------
273
1.75k
void ValidateDSProcess::Validate(const aiMesh *pMesh) {
274
    // validate the material index of the mesh
275
1.75k
    if (mScene->mNumMaterials && pMesh->mMaterialIndex >= mScene->mNumMaterials) {
276
0
        ReportError("aiMesh::mMaterialIndex is invalid (value: %i maximum: %i)",
277
0
                pMesh->mMaterialIndex, mScene->mNumMaterials - 1);
278
0
    }
279
280
1.75k
    Validate(&pMesh->mName);
281
282
57.5k
    for (unsigned int i = 0; i < pMesh->mNumFaces; ++i) {
283
55.7k
        aiFace &face = pMesh->mFaces[i];
284
285
55.7k
        if (pMesh->mPrimitiveTypes) {
286
29.0k
            switch (face.mNumIndices) {
287
3
            case 0:
288
3
                ReportError("aiMesh::mFaces[%i].mNumIndices is 0", i);
289
250
            case 1:
290
250
                if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_POINT)) {
291
5
                    ReportError("aiMesh::mFaces[%i] is a POINT but aiMesh::mPrimitiveTypes "
292
5
                                "does not report the POINT flag",
293
5
                            i);
294
5
                }
295
250
                break;
296
1.10k
            case 2:
297
1.10k
                if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_LINE)) {
298
0
                    ReportError("aiMesh::mFaces[%i] is a LINE but aiMesh::mPrimitiveTypes "
299
0
                                "does not report the LINE flag",
300
0
                            i);
301
0
                }
302
1.10k
                break;
303
18.3k
            case 3:
304
18.3k
                if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE)) {
305
0
                    ReportError("aiMesh::mFaces[%i] is a TRIANGLE but aiMesh::mPrimitiveTypes "
306
0
                                "does not report the TRIANGLE flag",
307
0
                            i);
308
0
                }
309
18.3k
                break;
310
9.33k
            default:
311
9.33k
                if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_POLYGON)) {
312
0
                    this->ReportError("aiMesh::mFaces[%i] is a POLYGON but aiMesh::mPrimitiveTypes "
313
0
                                      "does not report the POLYGON flag",
314
0
                            i);
315
0
                }
316
9.33k
                break;
317
29.0k
            };
318
29.0k
        }
319
320
55.7k
        if (!face.mIndices)
321
0
            ReportError("aiMesh::mFaces[%i].mIndices is nullptr", i);
322
55.7k
    }
323
324
    // positions must always be there ...
325
1.74k
    if (!pMesh->mNumVertices || (!pMesh->mVertices && !mScene->mFlags)) {
326
0
        ReportError("The mesh %s contains no vertices", pMesh->mName.C_Str());
327
0
    }
328
329
1.74k
    if (pMesh->mNumVertices > AI_MAX_VERTICES) {
330
0
        ReportError("Mesh has too many vertices: %u, but the limit is %u", pMesh->mNumVertices, AI_MAX_VERTICES);
331
0
    }
332
1.74k
    if (pMesh->mNumFaces > AI_MAX_FACES) {
333
0
        ReportError("Mesh has too many faces: %u, but the limit is %u", pMesh->mNumFaces, AI_MAX_FACES);
334
0
    }
335
336
    // if tangents are there there must also be bitangent vectors ...
337
1.74k
    if ((pMesh->mTangents != nullptr) != (pMesh->mBitangents != nullptr)) {
338
0
        ReportError("If there are tangents, bitangent vectors must be present as well");
339
0
    }
340
341
    // faces, too
342
1.74k
    if (!pMesh->mNumFaces || (!pMesh->mFaces && !mScene->mFlags)) {
343
0
        ReportError("Mesh %s contains no faces", pMesh->mName.C_Str());
344
0
    }
345
346
    // now check whether the face indexing layout is correct:
347
    // unique vertices, pseudo-indexed.
348
1.74k
    std::vector<bool> abRefList;
349
1.74k
    abRefList.resize(pMesh->mNumVertices, false);
350
57.5k
    for (unsigned int i = 0; i < pMesh->mNumFaces; ++i) {
351
55.7k
        aiFace &face = pMesh->mFaces[i];
352
55.7k
        if (face.mNumIndices > AI_MAX_FACE_INDICES) {
353
0
            ReportError("Face %u has too many faces: %u, but the limit is %u", i, face.mNumIndices, AI_MAX_FACE_INDICES);
354
0
        }
355
356
256k
        for (unsigned int a = 0; a < face.mNumIndices; ++a) {
357
200k
            if (face.mIndices[a] >= pMesh->mNumVertices) {
358
0
                ReportError("aiMesh::mFaces[%i]::mIndices[%i] is out of range", i, a);
359
0
            }
360
200k
            abRefList[face.mIndices[a]] = true;
361
200k
        }
362
55.7k
    }
363
364
    // check whether there are vertices that aren't referenced by a face
365
1.74k
    bool b = false;
366
202k
    for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) {
367
200k
        if (!abRefList[i]) b = true;
368
200k
    }
369
1.74k
    abRefList.clear();
370
1.74k
    if (b) {
371
0
        ReportWarning("There are unreferenced vertices");
372
0
    }
373
374
    // vertex color channel 2 may not be set if channel 1 is zero ...
375
1.74k
    {
376
1.74k
        unsigned int i = 0;
377
2.07k
        for (; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) {
378
2.07k
            if (!pMesh->HasVertexColors(i)) break;
379
2.07k
        }
380
15.3k
        for (; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i)
381
13.6k
            if (pMesh->HasVertexColors(i)) {
382
0
                ReportError("Vertex color channel %i is exists "
383
0
                            "although the previous channel was nullptr.",
384
0
                        i);
385
0
            }
386
1.74k
    }
387
388
    // now validate all bones
389
1.74k
    if (pMesh->mNumBones) {
390
17
        if (!pMesh->mBones) {
391
0
            ReportError("aiMesh::mBones is nullptr (aiMesh::mNumBones is %i)",
392
0
                    pMesh->mNumBones);
393
0
        }
394
17
        std::unique_ptr<float[]> afSum(nullptr);
395
17
        if (pMesh->mNumVertices) {
396
17
            afSum.reset(new float[pMesh->mNumVertices]);
397
80.0k
            for (unsigned int i = 0; i < pMesh->mNumVertices; ++i)
398
80.0k
                afSum[i] = 0.0f;
399
17
        }
400
401
        // check whether there are duplicate bone names
402
2.64k
        for (unsigned int i = 0; i < pMesh->mNumBones; ++i) {
403
2.62k
            const aiBone *bone = pMesh->mBones[i];
404
2.62k
            if (bone->mNumWeights > AI_MAX_BONE_WEIGHTS) {
405
0
                ReportError("Bone %u has too many weights: %u, but the limit is %u", i, bone->mNumWeights, AI_MAX_BONE_WEIGHTS);
406
0
            }
407
408
2.62k
            if (!pMesh->mBones[i]) {
409
0
                ReportError("aiMesh::mBones[%i] is nullptr (aiMesh::mNumBones is %i)",
410
0
                        i, pMesh->mNumBones);
411
0
            }
412
2.62k
            Validate(pMesh, pMesh->mBones[i], afSum.get());
413
414
873k
            for (unsigned int a = i + 1; a < pMesh->mNumBones; ++a) {
415
870k
                if (pMesh->mBones[i]->mName == pMesh->mBones[a]->mName) {
416
3
                    const char *name = "unknown";
417
3
                    if (nullptr != pMesh->mBones[i]->mName.C_Str()) {
418
3
                        name = pMesh->mBones[i]->mName.C_Str();
419
3
                    }
420
3
                    ReportError("aiMesh::mBones[%i], name = \"%s\" has the same name as "
421
3
                                "aiMesh::mBones[%i]",
422
3
                            i, name, a);
423
3
                }
424
870k
            }
425
2.62k
        }
426
        // check whether all bone weights for a vertex sum to 1.0 ...
427
63.1k
        for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) {
428
63.1k
            if (afSum[i] && (afSum[i] <= 0.94 || afSum[i] >= 1.05)) {
429
0
                ReportWarning("aiMesh::mVertices[%i]: bone weight sum != 1.0 (sum is %f)", i, afSum[i]);
430
0
            }
431
63.1k
        }
432
1.72k
    } else if (pMesh->mBones) {
433
0
        ReportError("aiMesh::mBones is non-null although there are no bones");
434
0
    }
435
1.74k
}
436
437
// ------------------------------------------------------------------------------------------------
438
2.62k
void ValidateDSProcess::Validate(const aiMesh *pMesh, const aiBone *pBone, float *afSum) {
439
2.62k
    this->Validate(&pBone->mName);
440
441
2.62k
    if (!pBone->mNumWeights) {
442
0
        ReportWarning("aiBone::mNumWeights is zero");
443
0
    }
444
445
    // check whether all vertices affected by this bone are valid
446
65.8k
    for (unsigned int i = 0; i < pBone->mNumWeights; ++i) {
447
63.2k
        if (pBone->mWeights[i].mVertexId >= pMesh->mNumVertices) {
448
0
            ReportError("aiBone::mWeights[%i].mVertexId is out of range", i);
449
63.2k
        } else if (!pBone->mWeights[i].mWeight || pBone->mWeights[i].mWeight > 1.0f) {
450
0
                ReportWarning("aiBone::mWeights[%i].mWeight has an invalid value %i. Value must be greater than zero and less than 1.", i, pBone->mWeights[i].mWeight);
451
0
        }
452
63.2k
        afSum[pBone->mWeights[i].mVertexId] += pBone->mWeights[i].mWeight;
453
63.2k
    }
454
2.62k
}
455
456
// ------------------------------------------------------------------------------------------------
457
11
void ValidateDSProcess::Validate(const aiAnimation *pAnimation) {
458
11
    Validate(&pAnimation->mName);
459
460
    // validate all animations
461
11
    if (pAnimation->mNumChannels || pAnimation->mNumMorphMeshChannels) {
462
11
        if (!pAnimation->mChannels && pAnimation->mNumChannels) {
463
0
            ReportError("aiAnimation::mChannels is nullptr (aiAnimation::mNumChannels is %i)",
464
0
                    pAnimation->mNumChannels);
465
0
        }
466
11
        if (!pAnimation->mMorphMeshChannels && pAnimation->mNumMorphMeshChannels) {
467
0
            ReportError("aiAnimation::mMorphMeshChannels is nullptr (aiAnimation::mNumMorphMeshChannels is %i)",
468
0
                    pAnimation->mNumMorphMeshChannels);
469
0
        }
470
44
        for (unsigned int i = 0; i < pAnimation->mNumChannels; ++i) {
471
33
            if (!pAnimation->mChannels[i]) {
472
0
                ReportError("aiAnimation::mChannels[%i] is nullptr (aiAnimation::mNumChannels is %i)",
473
0
                        i, pAnimation->mNumChannels);
474
0
            }
475
33
            Validate(pAnimation, pAnimation->mChannels[i]);
476
33
        }
477
11
        for (unsigned int i = 0; i < pAnimation->mNumMorphMeshChannels; ++i) {
478
0
            if (!pAnimation->mMorphMeshChannels[i]) {
479
0
                ReportError("aiAnimation::mMorphMeshChannels[%i] is nullptr (aiAnimation::mNumMorphMeshChannels is %i)",
480
0
                        i, pAnimation->mNumMorphMeshChannels);
481
0
            }
482
0
            Validate(pAnimation, pAnimation->mMorphMeshChannels[i]);
483
0
        }
484
11
    } else {
485
0
        ReportError("aiAnimation::mNumChannels is 0. At least one node animation channel must be there.");
486
0
    }
487
11
}
488
489
// ------------------------------------------------------------------------------------------------
490
void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial *pMaterial,
491
7.46k
        aiTextureType type) {
492
7.46k
    const char *szType = aiTextureTypeToString(type);
493
494
    // ****************************************************************************
495
    // Search all keys of the material ...
496
    // textures must be specified with ascending indices
497
    // (e.g. diffuse #2 may not be specified if diffuse #1 is not there ...)
498
    // ****************************************************************************
499
500
7.46k
    int iNumIndices = 0;
501
7.46k
    int iIndex = -1;
502
88.8k
    for (unsigned int i = 0; i < pMaterial->mNumProperties; ++i) {
503
81.4k
        aiMaterialProperty *prop = pMaterial->mProperties[i];
504
81.4k
        ai_assert(nullptr != prop);
505
81.4k
        if (!::strcmp(prop->mKey.data, "$tex.file") && prop->mSemantic == static_cast<unsigned int>(type)) {
506
13
            iIndex = std::max(iIndex, (int)prop->mIndex);
507
13
            ++iNumIndices;
508
509
13
            if (aiPTI_String != prop->mType) {
510
0
                ReportError("Material property %s is expected to be a string", prop->mKey.data);
511
0
            }
512
13
        }
513
81.4k
    }
514
7.46k
    if (iIndex + 1 != iNumIndices) {
515
0
        ReportError("%s #%i is set, but there are only %i %s textures",
516
0
                szType, iIndex, iNumIndices, szType);
517
0
    }
518
7.46k
    if (!iNumIndices) {
519
7.45k
        return;
520
7.45k
    }
521
13
    std::vector<aiTextureMapping> mappings(iNumIndices);
522
523
    // Now check whether all UV indices are valid ...
524
13
    bool bNoSpecified = true;
525
211
    for (unsigned int i = 0; i < pMaterial->mNumProperties; ++i) {
526
198
        aiMaterialProperty *prop = pMaterial->mProperties[i];
527
198
        if (static_cast<aiTextureType>(prop->mSemantic) != type) {
528
172
            continue;
529
172
        }
530
531
26
        if ((int)prop->mIndex >= iNumIndices) {
532
0
            ReportError("Found texture property with index %i, although there "
533
0
                        "are only %i textures of type %s",
534
0
                    prop->mIndex, iNumIndices, szType);
535
0
        }
536
537
26
        if (!::strcmp(prop->mKey.data, "$tex.mapping")) {
538
0
            if (aiPTI_Integer != prop->mType || prop->mDataLength < sizeof(aiTextureMapping)) {
539
0
                ReportError("Material property %s%i is expected to be an integer (size is %i)",
540
0
                        prop->mKey.data, prop->mIndex, prop->mDataLength);
541
0
            }
542
0
            mappings[prop->mIndex] = *((aiTextureMapping *)prop->mData);
543
26
        } else if (!::strcmp(prop->mKey.data, "$tex.uvtrafo")) {
544
0
            if (aiPTI_Float != prop->mType || prop->mDataLength < sizeof(aiUVTransform)) {
545
0
                ReportError("Material property %s%i is expected to be 5 floats large (size is %i)",
546
0
                        prop->mKey.data, prop->mIndex, prop->mDataLength);
547
0
            }
548
            //mappings[prop->mIndex] = ((aiUVTransform*)prop->mData);
549
26
        } else if (!::strcmp(prop->mKey.data, "$tex.uvwsrc")) {
550
13
            if (aiPTI_Integer != prop->mType || sizeof(int) > prop->mDataLength) {
551
0
                ReportError("Material property %s%i is expected to be an integer (size is %i)",
552
0
                        prop->mKey.data, prop->mIndex, prop->mDataLength);
553
0
            }
554
13
            bNoSpecified = false;
555
556
            // Ignore UV indices for texture channels that are not there ...
557
558
            // Get the value
559
13
            iIndex = *((unsigned int *)prop->mData);
560
561
            // Check whether there is a mesh using this material
562
            // which has not enough UV channels ...
563
377
            for (unsigned int a = 0; a < mScene->mNumMeshes; ++a) {
564
364
                aiMesh *mesh = this->mScene->mMeshes[a];
565
364
                if (mesh->mMaterialIndex == (unsigned int)i) {
566
70
                    int iChannels = 0;
567
114
                    while (mesh->HasTextureCoords(iChannels))
568
44
                        ++iChannels;
569
70
                    if (iIndex >= iChannels) {
570
26
                        ReportWarning("Invalid UV index: %i (key %s). Mesh %i has only %i UV channels",
571
26
                                iIndex, prop->mKey.data, a, iChannels);
572
26
                    }
573
70
                }
574
364
            }
575
13
        }
576
26
    }
577
13
    if (bNoSpecified) {
578
        // Assume that all textures are using the first UV channel
579
0
        for (unsigned int a = 0; a < mScene->mNumMeshes; ++a) {
580
0
            aiMesh *mesh = mScene->mMeshes[a];
581
0
            if (mesh->mMaterialIndex == (unsigned int)iIndex && mappings[0] == aiTextureMapping_UV) {
582
0
                if (!mesh->mTextureCoords[0]) {
583
                    // This is a special case ... it could be that the
584
                    // original mesh format intended the use of a special
585
                    // mapping here.
586
0
                    ReportWarning("UV-mapped texture, but there are no UV coords");
587
0
                }
588
0
            }
589
0
        }
590
0
    }
591
13
}
592
// ------------------------------------------------------------------------------------------------
593
439
void ValidateDSProcess::Validate(const aiMaterial *pMaterial) {
594
    // check whether there are material keys that are obviously not legal
595
5.22k
    for (unsigned int i = 0; i < pMaterial->mNumProperties; ++i) {
596
4.79k
        const aiMaterialProperty *prop = pMaterial->mProperties[i];
597
4.79k
        if (!prop) {
598
0
            ReportError("aiMaterial::mProperties[%i] is nullptr (aiMaterial::mNumProperties is %i)",
599
0
                    i, pMaterial->mNumProperties);
600
0
        }
601
4.79k
        if (!prop->mDataLength || !prop->mData) {
602
0
            ReportError("aiMaterial::mProperties[%i].mDataLength or "
603
0
                        "aiMaterial::mProperties[%i].mData is 0",
604
0
                    i, i);
605
0
        }
606
        // check all predefined types
607
4.79k
        if (aiPTI_String == prop->mType) {
608
            // FIX: strings are now stored in a less expensive way, but we can't use the
609
            // validation routine for 'normal' aiStrings
610
452
            if (prop->mDataLength < 5 || prop->mDataLength < 4 + (*reinterpret_cast<uint32_t *>(prop->mData)) + 1) {
611
0
                ReportError("aiMaterial::mProperties[%i].mDataLength is "
612
0
                            "too small to contain a string (%i, needed: %i)",
613
0
                        i, prop->mDataLength, static_cast<int>(sizeof(aiString)));
614
0
            }
615
452
            if (prop->mData[prop->mDataLength - 1]) {
616
0
                ReportError("Missing null-terminator in string material property");
617
0
            }
618
            //  Validate((const aiString*)prop->mData);
619
4.33k
        } else if (aiPTI_Float == prop->mType) {
620
3.54k
            if (prop->mDataLength < sizeof(float)) {
621
0
                ReportError("aiMaterial::mProperties[%i].mDataLength is "
622
0
                            "too small to contain a float (%i, needed: %i)",
623
0
                        i, prop->mDataLength, static_cast<int>(sizeof(float)));
624
0
            }
625
3.54k
        } else if (aiPTI_Integer == prop->mType) {
626
795
            if (prop->mDataLength < sizeof(int)) {
627
0
                ReportError("aiMaterial::mProperties[%i].mDataLength is "
628
0
                            "too small to contain an integer (%i, needed: %i)",
629
0
                        i, prop->mDataLength, static_cast<int>(sizeof(int)));
630
0
            }
631
795
        }
632
        // TODO: check whether there is a key with an unknown name ...
633
4.79k
    }
634
635
    // make some more specific tests
636
439
    ai_real fTemp;
637
439
    int iShading;
638
439
    if (AI_SUCCESS == aiGetMaterialInteger(pMaterial, AI_MATKEY_SHADING_MODEL, &iShading)) {
639
423
        switch ((aiShadingMode)iShading) {
640
0
        case aiShadingMode_Blinn:
641
0
        case aiShadingMode_CookTorrance:
642
0
        case aiShadingMode_Phong:
643
644
0
            if (AI_SUCCESS != aiGetMaterialFloat(pMaterial, AI_MATKEY_SHININESS, &fTemp)) {
645
0
                ReportWarning("A specular shading model is specified but there is no "
646
0
                              "AI_MATKEY_SHININESS key");
647
0
            }
648
0
            if (AI_SUCCESS == aiGetMaterialFloat(pMaterial, AI_MATKEY_SHININESS_STRENGTH, &fTemp) && !fTemp) {
649
0
                ReportWarning("A specular shading model is specified but the value of the "
650
0
                              "AI_MATKEY_SHININESS_STRENGTH key is 0.0");
651
0
            }
652
0
            break;
653
423
        default:
654
423
            break;
655
423
        }
656
423
    }
657
658
439
    if (AI_SUCCESS == aiGetMaterialFloat(pMaterial, AI_MATKEY_OPACITY, &fTemp) && (!fTemp || fTemp > 1.01)) {
659
17
        ReportWarning("Invalid opacity value (must be 0 < opacity < 1.0)");
660
17
    }
661
662
    // Check whether there are invalid texture keys
663
    // TODO: that's a relict of the past, where texture type and index were baked
664
    // into the material string ... we could do that in one single pass.
665
439
    SearchForInvalidTextures(pMaterial, aiTextureType_DIFFUSE);
666
439
    SearchForInvalidTextures(pMaterial, aiTextureType_SPECULAR);
667
439
    SearchForInvalidTextures(pMaterial, aiTextureType_AMBIENT);
668
439
    SearchForInvalidTextures(pMaterial, aiTextureType_EMISSIVE);
669
439
    SearchForInvalidTextures(pMaterial, aiTextureType_OPACITY);
670
439
    SearchForInvalidTextures(pMaterial, aiTextureType_SHININESS);
671
439
    SearchForInvalidTextures(pMaterial, aiTextureType_HEIGHT);
672
439
    SearchForInvalidTextures(pMaterial, aiTextureType_NORMALS);
673
439
    SearchForInvalidTextures(pMaterial, aiTextureType_DISPLACEMENT);
674
439
    SearchForInvalidTextures(pMaterial, aiTextureType_LIGHTMAP);
675
439
    SearchForInvalidTextures(pMaterial, aiTextureType_REFLECTION);
676
439
    SearchForInvalidTextures(pMaterial, aiTextureType_BASE_COLOR);
677
439
    SearchForInvalidTextures(pMaterial, aiTextureType_NORMAL_CAMERA);
678
439
    SearchForInvalidTextures(pMaterial, aiTextureType_EMISSION_COLOR);
679
439
    SearchForInvalidTextures(pMaterial, aiTextureType_METALNESS);
680
439
    SearchForInvalidTextures(pMaterial, aiTextureType_DIFFUSE_ROUGHNESS);
681
439
    SearchForInvalidTextures(pMaterial, aiTextureType_AMBIENT_OCCLUSION);
682
439
}
683
684
// ------------------------------------------------------------------------------------------------
685
0
void ValidateDSProcess::Validate(const aiTexture *pTexture) {
686
    // the data section may NEVER be nullptr
687
0
    if (nullptr == pTexture->pcData) {
688
0
        ReportError("aiTexture::pcData is nullptr");
689
0
    }
690
0
    if (pTexture->mHeight) {
691
0
        if (!pTexture->mWidth) {
692
0
            ReportError("aiTexture::mWidth is zero (aiTexture::mHeight is %i, uncompressed texture)",
693
0
                    pTexture->mHeight);
694
0
        }
695
0
    } else {
696
0
        if (!pTexture->mWidth) {
697
0
            ReportError("aiTexture::mWidth is zero (compressed texture)");
698
0
        }
699
0
        if ('\0' != pTexture->achFormatHint[HINTMAXTEXTURELEN - 1]) {
700
0
            ReportWarning("aiTexture::achFormatHint must be zero-terminated");
701
0
        } else if ('.' == pTexture->achFormatHint[0]) {
702
0
            ReportWarning("aiTexture::achFormatHint should contain a file extension "
703
0
                          "without a leading dot (format hint: %s).",
704
0
                    pTexture->achFormatHint);
705
0
        }
706
0
    }
707
708
0
    const char *sz = pTexture->achFormatHint;
709
0
    if ((sz[0] >= 'A' && sz[0] <= 'Z') ||
710
0
            (sz[1] >= 'A' && sz[1] <= 'Z') ||
711
0
            (sz[2] >= 'A' && sz[2] <= 'Z') ||
712
0
            (sz[3] >= 'A' && sz[3] <= 'Z')) {
713
0
        ReportError("aiTexture::achFormatHint contains non-lowercase letters");
714
0
    }
715
0
}
716
717
// ------------------------------------------------------------------------------------------------
718
void ValidateDSProcess::Validate(const aiAnimation *pAnimation,
719
33
        const aiNodeAnim *pNodeAnim) {
720
33
    Validate(&pNodeAnim->mNodeName);
721
722
33
    if (!pNodeAnim->mNumPositionKeys && !pNodeAnim->mScalingKeys && !pNodeAnim->mNumRotationKeys) {
723
0
        ReportError("Empty node animation channel");
724
0
    }
725
    // otherwise check whether one of the keys exceeds the total duration of the animation
726
33
    if (pNodeAnim->mNumPositionKeys) {
727
29
        if (!pNodeAnim->mPositionKeys) {
728
0
            ReportError("aiNodeAnim::mPositionKeys is nullptr (aiNodeAnim::mNumPositionKeys is %i)",
729
0
                    pNodeAnim->mNumPositionKeys);
730
0
        }
731
29
        double dLast = -10e10;
732
112
        for (unsigned int i = 0; i < pNodeAnim->mNumPositionKeys; ++i) {
733
            // ScenePreprocessor will compute the duration if still the default value
734
            // (Aramis) Add small epsilon, comparison tended to fail if max_time == duration,
735
            //  seems to be due the compilers register usage/width.
736
83
            if (pAnimation->mDuration > 0. && pNodeAnim->mPositionKeys[i].mTime > pAnimation->mDuration + 0.001) {
737
1
                ReportError("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is larger "
738
1
                            "than aiAnimation::mDuration (which is %.5f)",
739
1
                        i,
740
1
                        (float)pNodeAnim->mPositionKeys[i].mTime,
741
1
                        (float)pAnimation->mDuration);
742
1
            }
743
83
            if (i && pNodeAnim->mPositionKeys[i].mTime <= dLast) {
744
11
                ReportWarning("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is smaller "
745
11
                              "than aiAnimation::mPositionKeys[%i] (which is %.5f)",
746
11
                        i,
747
11
                        (float)pNodeAnim->mPositionKeys[i].mTime,
748
11
                        i - 1, (float)dLast);
749
11
            }
750
83
            dLast = pNodeAnim->mPositionKeys[i].mTime;
751
83
        }
752
29
    }
753
    // rotation keys
754
33
    if (pNodeAnim->mNumRotationKeys) {
755
24
        if (!pNodeAnim->mRotationKeys) {
756
0
            ReportError("aiNodeAnim::mRotationKeys is nullptr (aiNodeAnim::mNumRotationKeys is %i)",
757
0
                    pNodeAnim->mNumRotationKeys);
758
0
        }
759
24
        double dLast = -10e10;
760
67
        for (unsigned int i = 0; i < pNodeAnim->mNumRotationKeys; ++i) {
761
43
            if (pAnimation->mDuration > 0. && pNodeAnim->mRotationKeys[i].mTime > pAnimation->mDuration + 0.001) {
762
0
                ReportError("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is larger "
763
0
                            "than aiAnimation::mDuration (which is %.5f)",
764
0
                        i,
765
0
                        (float)pNodeAnim->mRotationKeys[i].mTime,
766
0
                        (float)pAnimation->mDuration);
767
0
            }
768
43
            if (i && pNodeAnim->mRotationKeys[i].mTime <= dLast) {
769
6
                ReportWarning("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is smaller "
770
6
                              "than aiAnimation::mRotationKeys[%i] (which is %.5f)",
771
6
                        i,
772
6
                        (float)pNodeAnim->mRotationKeys[i].mTime,
773
6
                        i - 1, (float)dLast);
774
6
            }
775
43
            dLast = pNodeAnim->mRotationKeys[i].mTime;
776
43
        }
777
24
    }
778
    // scaling keys
779
33
    if (pNodeAnim->mNumScalingKeys) {
780
4
        if (!pNodeAnim->mScalingKeys) {
781
0
            ReportError("aiNodeAnim::mScalingKeys is nullptr (aiNodeAnim::mNumScalingKeys is %i)",
782
0
                    pNodeAnim->mNumScalingKeys);
783
0
        }
784
4
        double dLast = -10e10;
785
12
        for (unsigned int i = 0; i < pNodeAnim->mNumScalingKeys; ++i) {
786
8
            if (pAnimation->mDuration > 0. && pNodeAnim->mScalingKeys[i].mTime > pAnimation->mDuration + 0.001) {
787
4
                ReportError("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is larger "
788
4
                            "than aiAnimation::mDuration (which is %.5f)",
789
4
                        i,
790
4
                        (float)pNodeAnim->mScalingKeys[i].mTime,
791
4
                        (float)pAnimation->mDuration);
792
4
            }
793
8
            if (i && pNodeAnim->mScalingKeys[i].mTime <= dLast) {
794
0
                ReportWarning("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is smaller "
795
0
                              "than aiAnimation::mScalingKeys[%i] (which is %.5f)",
796
0
                        i,
797
0
                        (float)pNodeAnim->mScalingKeys[i].mTime,
798
0
                        i - 1, (float)dLast);
799
0
            }
800
8
            dLast = pNodeAnim->mScalingKeys[i].mTime;
801
8
        }
802
4
    }
803
804
33
    if (!pNodeAnim->mNumScalingKeys && !pNodeAnim->mNumRotationKeys &&
805
4
            !pNodeAnim->mNumPositionKeys) {
806
0
        ReportError("A node animation channel must have at least one subtrack");
807
0
    }
808
33
}
809
810
void ValidateDSProcess::Validate(const aiAnimation *pAnimation,
811
0
        const aiMeshMorphAnim *pMeshMorphAnim) {
812
0
    Validate(&pMeshMorphAnim->mName);
813
814
0
    if (!pMeshMorphAnim->mNumKeys) {
815
0
        ReportWarning("Empty mesh morph animation channel");
816
0
        return;
817
0
    }
818
819
    // otherwise check whether one of the keys exceeds the total duration of the animation
820
0
    if (pMeshMorphAnim->mNumKeys) {
821
0
        if (!pMeshMorphAnim->mKeys) {
822
0
            ReportError("aiMeshMorphAnim::mKeys is nullptr (aiMeshMorphAnim::mNumKeys is %i)",
823
0
                    pMeshMorphAnim->mNumKeys);
824
0
        }
825
0
        double dLast = -10e10;
826
0
        for (unsigned int i = 0; i < pMeshMorphAnim->mNumKeys; ++i) {
827
            // ScenePreprocessor will compute the duration if still the default value
828
            // (Aramis) Add small epsilon, comparison tended to fail if max_time == duration,
829
            //  seems to be due the compilers register usage/width.
830
0
            if (pAnimation->mDuration > 0. && pMeshMorphAnim->mKeys[i].mTime > pAnimation->mDuration + 0.001) {
831
0
                ReportError("aiMeshMorphAnim::mKeys[%i].mTime (%.5f) is larger "
832
0
                            "than aiAnimation::mDuration (which is %.5f)",
833
0
                        i,
834
0
                        (float)pMeshMorphAnim->mKeys[i].mTime,
835
0
                        (float)pAnimation->mDuration);
836
0
            }
837
0
            if (i && pMeshMorphAnim->mKeys[i].mTime <= dLast) {
838
0
                ReportWarning("aiMeshMorphAnim::mKeys[%i].mTime (%.5f) is smaller "
839
0
                              "than aiMeshMorphAnim::mKeys[%i] (which is %.5f)",
840
0
                        i,
841
0
                        (float)pMeshMorphAnim->mKeys[i].mTime,
842
0
                        i - 1, (float)dLast);
843
0
            }
844
0
            dLast = pMeshMorphAnim->mKeys[i].mTime;
845
0
        }
846
0
    }
847
0
}
848
849
// ------------------------------------------------------------------------------------------------
850
26.1k
void ValidateDSProcess::Validate(const aiNode *pNode) {
851
26.1k
    if (!pNode) {
852
14
        ReportError("A node of the scene-graph is nullptr");
853
14
    }
854
    // Validate node name string first so that it's safe to use in below expressions
855
26.1k
    this->Validate(&pNode->mName);
856
26.1k
    const char *nodeName = (&pNode->mName)->C_Str();
857
26.1k
    if (pNode != mScene->mRootNode && !pNode->mParent) {
858
0
        ReportError("Non-root node %s lacks a valid parent (aiNode::mParent is nullptr) ", nodeName);
859
0
    }
860
861
    // validate all meshes
862
26.1k
    if (pNode->mNumMeshes) {
863
4.95k
        if (!pNode->mMeshes) {
864
0
            ReportError("aiNode::mMeshes is nullptr for node %s (aiNode::mNumMeshes is %i)",
865
0
                    nodeName, pNode->mNumMeshes);
866
0
        }
867
4.95k
        std::vector<bool> abHadMesh;
868
4.95k
        abHadMesh.resize(mScene->mNumMeshes, false);
869
11.6k
        for (unsigned int i = 0; i < pNode->mNumMeshes; ++i) {
870
6.68k
            if (pNode->mMeshes[i] >= mScene->mNumMeshes) {
871
0
                ReportError("aiNode::mMeshes[%i] is out of range for node %s (maximum is %i)",
872
0
                        pNode->mMeshes[i], nodeName, mScene->mNumMeshes - 1);
873
0
            }
874
6.68k
            if (abHadMesh[pNode->mMeshes[i]]) {
875
0
                ReportError("aiNode::mMeshes[%i] is already referenced by this node %s (value: %i)",
876
0
                        i, nodeName, pNode->mMeshes[i]);
877
0
            }
878
6.68k
            abHadMesh[pNode->mMeshes[i]] = true;
879
6.68k
        }
880
4.95k
    }
881
26.1k
    if (pNode->mNumChildren) {
882
4.06k
        if (!pNode->mChildren) {
883
0
            ReportError("aiNode::mChildren is nullptr for node %s (aiNode::mNumChildren is %i)",
884
0
                    nodeName, pNode->mNumChildren);
885
0
        }
886
30.0k
        for (unsigned int i = 0; i < pNode->mNumChildren; ++i) {
887
26.0k
            const aiNode *pChild = pNode->mChildren[i];
888
26.0k
            Validate(pChild);
889
26.0k
            if (pChild->mParent != pNode) {
890
0
                const char *parentName = (pChild->mParent != nullptr) ? pChild->mParent->mName.C_Str() : "null";
891
0
                ReportError("aiNode \"%s\" child %i \"%s\" parent is someone else: \"%s\"", pNode->mName.C_Str(), i, pChild->mName.C_Str(), parentName);
892
0
            }
893
26.0k
        }
894
22.0k
    } else if (pNode->mChildren) {
895
0
        ReportError("aiNode::mChildren is not nullptr for empty node %s (aiNode::mNumChildren is %i)",
896
0
                nodeName, pNode->mNumChildren);
897
0
    }
898
26.1k
}
899
900
// ------------------------------------------------------------------------------------------------
901
30.5k
void ValidateDSProcess::Validate(const aiString *pString) {
902
30.5k
    if (pString->length > AI_MAXLEN) {
903
0
        ReportError("aiString::length is too large (%u, maximum is %lu)",
904
0
                pString->length, AI_MAXLEN);
905
0
    }
906
30.5k
    const char *sz = pString->data;
907
557k
    while (true) {
908
557k
        if ('\0' == *sz) {
909
30.5k
            if (pString->length != (unsigned int)(sz - pString->data)) {
910
2
                ReportError("aiString::data is invalid: the terminal zero is at a wrong offset");
911
2
            }
912
30.5k
            break;
913
527k
        } else if (sz >= &pString->data[AI_MAXLEN]) {
914
0
            ReportError("aiString::data is invalid. There is no terminal character");
915
0
        }
916
527k
        ++sz;
917
527k
    }
918
30.5k
}