Coverage Report

Created: 2026-04-29 07:04

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-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  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
20.3k
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
8.99k
AI_WONT_RETURN void ValidateDSProcess::ReportError(const char *msg, ...) {
70
8.99k
    ai_assert(nullptr != msg);
71
72
8.99k
    va_list args;
73
8.99k
    va_start(args, msg);
74
75
8.99k
    char szBuffer[3000];
76
8.99k
    const int iLen = vsnprintf(szBuffer, sizeof(szBuffer), msg, args);
77
8.99k
    ai_assert(iLen > 0);
78
79
8.99k
    va_end(args);
80
81
8.99k
    throw DeadlyImportError("Validation failed: ", std::string(szBuffer, iLen));
82
8.99k
}
83
// ------------------------------------------------------------------------------------------------
84
305k
void ValidateDSProcess::ReportWarning(const char *msg, ...) {
85
305k
    ai_assert(nullptr != msg);
86
87
305k
    va_list args;
88
305k
    va_start(args, msg);
89
90
305k
    char szBuffer[3000];
91
305k
    const int iLen = vsnprintf(szBuffer, sizeof(szBuffer), msg, args);
92
305k
    ai_assert(iLen > 0);
93
94
305k
    va_end(args);
95
305k
    ASSIMP_LOG_WARN("Validation warning: ", std::string(szBuffer, iLen));
96
305k
}
97
98
// ------------------------------------------------------------------------------------------------
99
23.7k
inline int HasNameMatch(const aiString &in, aiNode *node) {
100
23.7k
    int result = (node->mName == in ? 1 : 0);
101
46.2k
    for (unsigned int i = 0; i < node->mNumChildren; ++i) {
102
22.5k
        result += HasNameMatch(in, node->mChildren[i]);
103
22.5k
    }
104
23.7k
    return result;
105
23.7k
}
106
107
// ------------------------------------------------------------------------------------------------
108
template <typename T>
109
24.1k
inline void ValidateDSProcess::DoValidation(T **parray, unsigned int size, const char *firstName, const char *secondName) {
110
    // validate all entries
111
24.1k
    if (size == 0) {
112
0
        return;
113
0
    }
114
115
24.1k
    if (!parray) {
116
0
        ReportError("aiScene::%s is nullptr (aiScene::%s is %i)",
117
0
                firstName, secondName, size);
118
0
    }
119
120
259k
    for (unsigned int i = 0; i < size; ++i) {
121
235k
        if (!parray[i]) {
122
0
            ReportError("aiScene::%s[%i] is nullptr (aiScene::%s is %i)",
123
0
                    firstName, i, secondName, size);
124
0
        }
125
235k
        Validate(parray[i]);
126
235k
    }
127
24.1k
}
void Assimp::ValidateDSProcess::DoValidation<aiMesh>(aiMesh**, unsigned int, char const*, char const*)
Line
Count
Source
109
12.4k
inline void ValidateDSProcess::DoValidation(T **parray, unsigned int size, const char *firstName, const char *secondName) {
110
    // validate all entries
111
12.4k
    if (size == 0) {
112
0
        return;
113
0
    }
114
115
12.4k
    if (!parray) {
116
0
        ReportError("aiScene::%s is nullptr (aiScene::%s is %i)",
117
0
                firstName, secondName, size);
118
0
    }
119
120
217k
    for (unsigned int i = 0; i < size; ++i) {
121
204k
        if (!parray[i]) {
122
0
            ReportError("aiScene::%s[%i] is nullptr (aiScene::%s is %i)",
123
0
                    firstName, i, secondName, size);
124
0
        }
125
204k
        Validate(parray[i]);
126
204k
    }
127
12.4k
}
void Assimp::ValidateDSProcess::DoValidation<aiAnimation>(aiAnimation**, unsigned int, char const*, char const*)
Line
Count
Source
109
523
inline void ValidateDSProcess::DoValidation(T **parray, unsigned int size, const char *firstName, const char *secondName) {
110
    // validate all entries
111
523
    if (size == 0) {
112
0
        return;
113
0
    }
114
115
523
    if (!parray) {
116
0
        ReportError("aiScene::%s is nullptr (aiScene::%s is %i)",
117
0
                firstName, secondName, size);
118
0
    }
119
120
1.84k
    for (unsigned int i = 0; i < size; ++i) {
121
1.31k
        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.31k
        Validate(parray[i]);
126
1.31k
    }
127
523
}
void Assimp::ValidateDSProcess::DoValidation<aiTexture>(aiTexture**, unsigned int, char const*, char const*)
Line
Count
Source
109
130
inline void ValidateDSProcess::DoValidation(T **parray, unsigned int size, const char *firstName, const char *secondName) {
110
    // validate all entries
111
130
    if (size == 0) {
112
0
        return;
113
0
    }
114
115
130
    if (!parray) {
116
0
        ReportError("aiScene::%s is nullptr (aiScene::%s is %i)",
117
0
                firstName, secondName, size);
118
0
    }
119
120
260
    for (unsigned int i = 0; i < size; ++i) {
121
130
        if (!parray[i]) {
122
0
            ReportError("aiScene::%s[%i] is nullptr (aiScene::%s is %i)",
123
0
                    firstName, i, secondName, size);
124
0
        }
125
130
        Validate(parray[i]);
126
130
    }
127
130
}
void Assimp::ValidateDSProcess::DoValidation<aiMaterial>(aiMaterial**, unsigned int, char const*, char const*)
Line
Count
Source
109
11.0k
inline void ValidateDSProcess::DoValidation(T **parray, unsigned int size, const char *firstName, const char *secondName) {
110
    // validate all entries
111
11.0k
    if (size == 0) {
112
0
        return;
113
0
    }
114
115
11.0k
    if (!parray) {
116
0
        ReportError("aiScene::%s is nullptr (aiScene::%s is %i)",
117
0
                firstName, secondName, size);
118
0
    }
119
120
39.8k
    for (unsigned int i = 0; i < size; ++i) {
121
28.8k
        if (!parray[i]) {
122
0
            ReportError("aiScene::%s[%i] is nullptr (aiScene::%s is %i)",
123
0
                    firstName, i, secondName, size);
124
0
        }
125
28.8k
        Validate(parray[i]);
126
28.8k
    }
127
11.0k
}
128
129
// ------------------------------------------------------------------------------------------------
130
template <typename T>
131
inline void ValidateDSProcess::DoValidationEx(T **parray, unsigned int size,
132
860
        const char *firstName, const char *secondName) {
133
    // validate all entries
134
860
    if (size == 0) {
135
0
        return;
136
0
    }
137
138
860
    if (!parray) {
139
0
        ReportError("aiScene::%s is nullptr (aiScene::%s is %i)",
140
0
                firstName, secondName, size);
141
0
    }
142
2.13k
    for (unsigned int i = 0; i < size; ++i) {
143
1.27k
        if (!parray[i]) {
144
0
            ReportError("aiScene::%s[%u] is nullptr (aiScene::%s is %u)",
145
0
                    firstName, i, secondName, size);
146
0
        }
147
1.27k
        Validate(parray[i]);
148
149
        // check whether there are duplicate names
150
4.09k
        for (unsigned int a = i + 1; a < size; ++a) {
151
2.82k
            if (parray[i]->mName == parray[a]->mName) {
152
9
                ReportError("aiScene::%s[%u] has the same name as "
153
9
                            "aiScene::%s[%u]",
154
9
                        firstName, i, secondName, a);
155
9
            }
156
2.82k
        }
157
1.27k
    }
158
860
}
void Assimp::ValidateDSProcess::DoValidationEx<aiCamera>(aiCamera**, unsigned int, char const*, char const*)
Line
Count
Source
132
474
        const char *firstName, const char *secondName) {
133
    // validate all entries
134
474
    if (size == 0) {
135
0
        return;
136
0
    }
137
138
474
    if (!parray) {
139
0
        ReportError("aiScene::%s is nullptr (aiScene::%s is %i)",
140
0
                firstName, secondName, size);
141
0
    }
142
1.13k
    for (unsigned int i = 0; i < size; ++i) {
143
659
        if (!parray[i]) {
144
0
            ReportError("aiScene::%s[%u] is nullptr (aiScene::%s is %u)",
145
0
                    firstName, i, secondName, size);
146
0
        }
147
659
        Validate(parray[i]);
148
149
        // check whether there are duplicate names
150
1.15k
        for (unsigned int a = i + 1; a < size; ++a) {
151
499
            if (parray[i]->mName == parray[a]->mName) {
152
6
                ReportError("aiScene::%s[%u] has the same name as "
153
6
                            "aiScene::%s[%u]",
154
6
                        firstName, i, secondName, a);
155
6
            }
156
499
        }
157
659
    }
158
474
}
void Assimp::ValidateDSProcess::DoValidationEx<aiLight>(aiLight**, unsigned int, char const*, char const*)
Line
Count
Source
132
386
        const char *firstName, const char *secondName) {
133
    // validate all entries
134
386
    if (size == 0) {
135
0
        return;
136
0
    }
137
138
386
    if (!parray) {
139
0
        ReportError("aiScene::%s is nullptr (aiScene::%s is %i)",
140
0
                firstName, secondName, size);
141
0
    }
142
999
    for (unsigned int i = 0; i < size; ++i) {
143
613
        if (!parray[i]) {
144
0
            ReportError("aiScene::%s[%u] is nullptr (aiScene::%s is %u)",
145
0
                    firstName, i, secondName, size);
146
0
        }
147
613
        Validate(parray[i]);
148
149
        // check whether there are duplicate names
150
2.94k
        for (unsigned int a = i + 1; a < size; ++a) {
151
2.32k
            if (parray[i]->mName == parray[a]->mName) {
152
3
                ReportError("aiScene::%s[%u] has the same name as "
153
3
                            "aiScene::%s[%u]",
154
3
                        firstName, i, secondName, a);
155
3
            }
156
2.32k
        }
157
613
    }
158
386
}
159
160
// ------------------------------------------------------------------------------------------------
161
template <typename T>
162
inline void ValidateDSProcess::DoValidationWithNameCheck(T **array, unsigned int size, const char *firstName,
163
860
        const char *secondName) {
164
    // validate all entries
165
860
    DoValidationEx(array, size, firstName, secondName);
166
167
2.09k
    for (unsigned int i = 0; i < size; ++i) {
168
1.23k
        int res = HasNameMatch(array[i]->mName, mScene->mRootNode);
169
1.23k
        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
1.23k
        } else if (1 != res) {
174
27
            const std::string name = static_cast<char *>(array[i]->mName.data);
175
27
            ReportError("aiScene::%s[%i]: there are more than one nodes with %s as name",
176
27
                    firstName, i, name.c_str());
177
27
        }
178
1.23k
    }
179
860
}
void Assimp::ValidateDSProcess::DoValidationWithNameCheck<aiCamera>(aiCamera**, unsigned int, char const*, char const*)
Line
Count
Source
163
474
        const char *secondName) {
164
    // validate all entries
165
474
    DoValidationEx(array, size, firstName, secondName);
166
167
1.10k
    for (unsigned int i = 0; i < size; ++i) {
168
627
        int res = HasNameMatch(array[i]->mName, mScene->mRootNode);
169
627
        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
627
        } else if (1 != res) {
174
24
            const std::string name = static_cast<char *>(array[i]->mName.data);
175
24
            ReportError("aiScene::%s[%i]: there are more than one nodes with %s as name",
176
24
                    firstName, i, name.c_str());
177
24
        }
178
627
    }
179
474
}
void Assimp::ValidateDSProcess::DoValidationWithNameCheck<aiLight>(aiLight**, unsigned int, char const*, char const*)
Line
Count
Source
163
386
        const char *secondName) {
164
    // validate all entries
165
386
    DoValidationEx(array, size, firstName, secondName);
166
167
990
    for (unsigned int i = 0; i < size; ++i) {
168
604
        int res = HasNameMatch(array[i]->mName, mScene->mRootNode);
169
604
        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
604
        } else if (1 != res) {
174
3
            const std::string name = static_cast<char *>(array[i]->mName.data);
175
3
            ReportError("aiScene::%s[%i]: there are more than one nodes with %s as name",
176
3
                    firstName, i, name.c_str());
177
3
        }
178
604
    }
179
386
}
180
181
// ------------------------------------------------------------------------------------------------
182
// Executes the post processing step on the given imported data.
183
20.3k
void ValidateDSProcess::Execute(aiScene *pScene) {
184
20.3k
    mScene = pScene;
185
20.3k
    ASSIMP_LOG_DEBUG("ValidateDataStructureProcess begin");
186
187
    // validate the node graph of the scene
188
20.3k
    Validate(pScene->mRootNode);
189
190
    // validate all meshes
191
20.3k
    if (pScene->mNumMeshes) {
192
12.4k
        DoValidation(pScene->mMeshes, pScene->mNumMeshes, "mMeshes", "mNumMeshes");
193
12.4k
    } else if (!(mScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE)) {
194
6.68k
        ReportError("aiScene::mNumMeshes is 0. At least one mesh must be there");
195
6.68k
    } 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
20.3k
    if (pScene->mNumAnimations) {
201
523
        DoValidation(pScene->mAnimations, pScene->mNumAnimations,
202
523
                "mAnimations", "mNumAnimations");
203
19.8k
    } 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
20.3k
    if (pScene->mNumCameras) {
209
474
        DoValidationWithNameCheck(pScene->mCameras, pScene->mNumCameras,
210
474
                "mCameras", "mNumCameras");
211
19.9k
    } 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
20.3k
    if (pScene->mNumLights) {
217
386
        DoValidationWithNameCheck(pScene->mLights, pScene->mNumLights,
218
386
                "mLights", "mNumLights");
219
19.9k
    } 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
20.3k
    if (pScene->mNumTextures) {
225
130
        DoValidation(pScene->mTextures, pScene->mNumTextures,
226
130
                "mTextures", "mNumTextures");
227
20.2k
    } 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
20.3k
    if (pScene->mNumMaterials) {
233
11.0k
        DoValidation(pScene->mMaterials, pScene->mNumMaterials, "mMaterials", "mNumMaterials");
234
11.0k
    }
235
9.37k
    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
20.3k
    ASSIMP_LOG_DEBUG("ValidateDataStructureProcess end");
241
20.3k
}
242
243
// ------------------------------------------------------------------------------------------------
244
613
void ValidateDSProcess::Validate(const aiLight *pLight) {
245
613
    if (pLight->mType == aiLightSource_UNDEFINED)
246
12
        ReportWarning("aiLight::mType is aiLightSource_UNDEFINED");
247
248
613
    if (!pLight->mAttenuationConstant &&
249
287
            !pLight->mAttenuationLinear &&
250
286
            !pLight->mAttenuationQuadratic) {
251
7
        ReportWarning("aiLight::mAttenuationXXX - all are zero");
252
7
    }
253
254
613
    if (pLight->mAngleInnerCone > pLight->mAngleOuterCone)
255
0
        ReportError("aiLight::mAngleInnerCone is larger than aiLight::mAngleOuterCone");
256
257
613
    if (pLight->mColorDiffuse.IsBlack() && pLight->mColorAmbient.IsBlack() && pLight->mColorSpecular.IsBlack()) {
258
12
        ReportWarning("aiLight::mColorXXX - all are black and won't have any influence");
259
12
    }
260
613
}
261
262
// ------------------------------------------------------------------------------------------------
263
659
void ValidateDSProcess::Validate(const aiCamera *pCamera) {
264
659
    if (pCamera->mClipPlaneFar <= pCamera->mClipPlaneNear)
265
10
        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
659
    if (!pCamera->mHorizontalFOV || pCamera->mHorizontalFOV >= (float)AI_MATH_PI)
269
43
        ReportWarning("%f is not a valid value for aiCamera::mHorizontalFOV", pCamera->mHorizontalFOV);
270
659
}
271
272
// ------------------------------------------------------------------------------------------------
273
204k
void ValidateDSProcess::Validate(const aiMesh *pMesh) {
274
    // validate the material index of the mesh
275
204k
    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
204k
    Validate(&pMesh->mName);
281
282
4.06M
    for (unsigned int i = 0; i < pMesh->mNumFaces; ++i) {
283
3.86M
        aiFace &face = pMesh->mFaces[i];
284
285
3.86M
        if (pMesh->mPrimitiveTypes) {
286
887k
            switch (face.mNumIndices) {
287
0
            case 0:
288
0
                ReportError("aiMesh::mFaces[%i].mNumIndices is 0", i);
289
29.0k
            case 1:
290
29.0k
                if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_POINT)) {
291
40
                    ReportError("aiMesh::mFaces[%i] is a POINT but aiMesh::mPrimitiveTypes "
292
40
                                "does not report the POINT flag",
293
40
                            i);
294
40
                }
295
29.0k
                break;
296
70.0k
            case 2:
297
70.0k
                if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_LINE)) {
298
24
                    ReportError("aiMesh::mFaces[%i] is a LINE but aiMesh::mPrimitiveTypes "
299
24
                                "does not report the LINE flag",
300
24
                            i);
301
24
                }
302
70.0k
                break;
303
591k
            case 3:
304
591k
                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
591k
                break;
310
196k
            default:
311
196k
                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
196k
                break;
317
887k
            };
318
887k
        }
319
320
3.86M
        if (!face.mIndices)
321
0
            ReportError("aiMesh::mFaces[%i].mIndices is nullptr", i);
322
3.86M
    }
323
324
    // positions must always be there ...
325
204k
    if (!pMesh->mNumVertices || (!pMesh->mVertices && !mScene->mFlags)) {
326
867
        ReportError("The mesh %s contains no vertices", pMesh->mName.C_Str());
327
867
    }
328
329
204k
    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
204k
    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
204k
    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
204k
    if (!pMesh->mNumFaces || (!pMesh->mFaces && !mScene->mFlags)) {
343
398
        ReportError("Mesh %s contains no faces", pMesh->mName.C_Str());
344
398
    }
345
346
    // now check whether the face indexing layout is correct:
347
    // unique vertices, pseudo-indexed.
348
204k
    std::vector<bool> abRefList;
349
204k
    abRefList.resize(pMesh->mNumVertices, false);
350
4.06M
    for (unsigned int i = 0; i < pMesh->mNumFaces; ++i) {
351
3.85M
        aiFace &face = pMesh->mFaces[i];
352
3.85M
        if (face.mNumIndices > AI_MAX_FACE_INDICES) {
353
6
            ReportError("Face %u has too many faces: %u, but the limit is %u", i, face.mNumIndices, AI_MAX_FACE_INDICES);
354
6
        }
355
356
19.1M
        for (unsigned int a = 0; a < face.mNumIndices; ++a) {
357
15.2M
            if (face.mIndices[a] >= pMesh->mNumVertices) {
358
0
                ReportError("aiMesh::mFaces[%i]::mIndices[%i] is out of range", i, a);
359
0
            }
360
15.2M
            abRefList[face.mIndices[a]] = true;
361
15.2M
        }
362
3.85M
    }
363
364
    // check whether there are vertices that aren't referenced by a face
365
204k
    bool b = false;
366
23.4M
    for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) {
367
23.2M
        if (!abRefList[i]) b = true;
368
23.2M
    }
369
204k
    abRefList.clear();
370
204k
    if (b) {
371
578
        ReportWarning("There are unreferenced vertices");
372
578
    }
373
374
    // vertex color channel 2 may not be set if channel 1 is zero ...
375
204k
    {
376
204k
        unsigned int i = 0;
377
234k
        for (; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) {
378
232k
            if (!pMesh->HasVertexColors(i)) break;
379
232k
        }
380
1.80M
        for (; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i)
381
1.59M
            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
204k
    }
387
388
    // now validate all bones
389
204k
    if (pMesh->mNumBones) {
390
3.00k
        if (!pMesh->mBones) {
391
0
            ReportError("aiMesh::mBones is nullptr (aiMesh::mNumBones is %i)",
392
0
                    pMesh->mNumBones);
393
0
        }
394
3.00k
        std::unique_ptr<float[]> afSum(nullptr);
395
3.00k
        if (pMesh->mNumVertices) {
396
3.00k
            afSum.reset(new float[pMesh->mNumVertices]);
397
6.23M
            for (unsigned int i = 0; i < pMesh->mNumVertices; ++i)
398
6.23M
                afSum[i] = 0.0f;
399
3.00k
        }
400
401
        // check whether there are duplicate bone names
402
231k
        for (unsigned int i = 0; i < pMesh->mNumBones; ++i) {
403
228k
            const aiBone *bone = pMesh->mBones[i];
404
228k
            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
228k
            if (!pMesh->mBones[i]) {
409
0
                ReportError("aiMesh::mBones[%i] is nullptr (aiMesh::mNumBones is %i)",
410
0
                        i, pMesh->mNumBones);
411
0
            }
412
228k
            Validate(pMesh, pMesh->mBones[i], afSum.get());
413
414
467M
            for (unsigned int a = i + 1; a < pMesh->mNumBones; ++a) {
415
467M
                if (pMesh->mBones[i]->mName == pMesh->mBones[a]->mName) {
416
361
                    const char *name = "unknown";
417
361
                    if (nullptr != pMesh->mBones[i]->mName.C_Str()) {
418
361
                        name = pMesh->mBones[i]->mName.C_Str();
419
361
                    }
420
361
                    ReportError("aiMesh::mBones[%i], name = \"%s\" has the same name as "
421
361
                                "aiMesh::mBones[%i]",
422
361
                            i, name, a);
423
361
                }
424
467M
            }
425
228k
        }
426
        // check whether all bone weights for a vertex sum to 1.0 ...
427
5.87M
        for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) {
428
5.86M
            if (afSum[i] && (afSum[i] <= 0.94 || afSum[i] >= 1.05)) {
429
297k
                ReportWarning("aiMesh::mVertices[%i]: bone weight sum != 1.0 (sum is %f)", i, afSum[i]);
430
297k
            }
431
5.86M
        }
432
201k
    } else if (pMesh->mBones) {
433
6
        ReportError("aiMesh::mBones is non-null although there are no bones");
434
6
    }
435
204k
}
436
437
// ------------------------------------------------------------------------------------------------
438
228k
void ValidateDSProcess::Validate(const aiMesh *pMesh, const aiBone *pBone, float *afSum) {
439
228k
    this->Validate(&pBone->mName);
440
441
228k
    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
6.94M
    for (unsigned int i = 0; i < pBone->mNumWeights; ++i) {
447
6.71M
        if (pBone->mWeights[i].mVertexId >= pMesh->mNumVertices) {
448
6
            ReportError("aiBone::mWeights[%i].mVertexId is out of range", i);
449
6.71M
        } else if (!pBone->mWeights[i].mWeight || pBone->mWeights[i].mWeight > 1.0f) {
450
3.33k
                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
3.33k
        }
452
6.71M
        afSum[pBone->mWeights[i].mVertexId] += pBone->mWeights[i].mWeight;
453
6.71M
    }
454
228k
}
455
456
// ------------------------------------------------------------------------------------------------
457
1.31k
void ValidateDSProcess::Validate(const aiAnimation *pAnimation) {
458
1.31k
    Validate(&pAnimation->mName);
459
460
    // validate all animations
461
1.31k
    if (pAnimation->mNumChannels || pAnimation->mNumMorphMeshChannels) {
462
1.24k
        if (!pAnimation->mChannels && pAnimation->mNumChannels) {
463
0
            ReportError("aiAnimation::mChannels is nullptr (aiAnimation::mNumChannels is %i)",
464
0
                    pAnimation->mNumChannels);
465
0
        }
466
1.24k
        if (!pAnimation->mMorphMeshChannels && pAnimation->mNumMorphMeshChannels) {
467
0
            ReportError("aiAnimation::mMorphMeshChannels is nullptr (aiAnimation::mNumMorphMeshChannels is %i)",
468
0
                    pAnimation->mNumMorphMeshChannels);
469
0
        }
470
7.11k
        for (unsigned int i = 0; i < pAnimation->mNumChannels; ++i) {
471
5.87k
            if (!pAnimation->mChannels[i]) {
472
0
                ReportError("aiAnimation::mChannels[%i] is nullptr (aiAnimation::mNumChannels is %i)",
473
0
                        i, pAnimation->mNumChannels);
474
0
            }
475
5.87k
            Validate(pAnimation, pAnimation->mChannels[i]);
476
5.87k
        }
477
1.24k
        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
1.24k
    } else {
485
71
        ReportError("aiAnimation::mNumChannels is 0. At least one node animation channel must be there.");
486
71
    }
487
1.31k
}
488
489
// ------------------------------------------------------------------------------------------------
490
void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial *pMaterial,
491
490k
        aiTextureType type) {
492
490k
    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
490k
    int iNumIndices = 0;
501
490k
    int iIndex = -1;
502
6.14M
    for (unsigned int i = 0; i < pMaterial->mNumProperties; ++i) {
503
5.65M
        aiMaterialProperty *prop = pMaterial->mProperties[i];
504
5.65M
        ai_assert(nullptr != prop);
505
5.65M
        if (!::strcmp(prop->mKey.data, "$tex.file") && prop->mSemantic == static_cast<unsigned int>(type)) {
506
6.11k
            iIndex = std::max(iIndex, (int)prop->mIndex);
507
6.11k
            ++iNumIndices;
508
509
6.11k
            if (aiPTI_String != prop->mType) {
510
0
                ReportError("Material property %s is expected to be a string", prop->mKey.data);
511
0
            }
512
6.11k
        }
513
5.65M
    }
514
490k
    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
490k
    if (!iNumIndices) {
519
486k
        return;
520
486k
    }
521
4.96k
    std::vector<aiTextureMapping> mappings(iNumIndices);
522
523
    // Now check whether all UV indices are valid ...
524
4.96k
    bool bNoSpecified = true;
525
104k
    for (unsigned int i = 0; i < pMaterial->mNumProperties; ++i) {
526
99.8k
        aiMaterialProperty *prop = pMaterial->mProperties[i];
527
99.8k
        if (static_cast<aiTextureType>(prop->mSemantic) != type) {
528
82.4k
            continue;
529
82.4k
        }
530
531
17.4k
        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
17.4k
        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
17.4k
        } else if (!::strcmp(prop->mKey.data, "$tex.uvtrafo")) {
544
992
            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
16.4k
        } else if (!::strcmp(prop->mKey.data, "$tex.uvwsrc")) {
550
6.11k
            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
6.11k
            bNoSpecified = false;
555
556
            // Ignore UV indices for texture channels that are not there ...
557
558
            // Get the value
559
6.11k
            iIndex = *((unsigned int *)prop->mData);
560
561
            // Check whether there is a mesh using this material
562
            // which has not enough UV channels ...
563
224k
            for (unsigned int a = 0; a < mScene->mNumMeshes; ++a) {
564
218k
                aiMesh *mesh = this->mScene->mMeshes[a];
565
218k
                if (mesh->mMaterialIndex == (unsigned int)i) {
566
2.68k
                    int iChannels = 0;
567
3.01k
                    while (mesh->HasTextureCoords(iChannels))
568
336
                        ++iChannels;
569
2.68k
                    if (iIndex >= iChannels) {
570
2.34k
                        ReportWarning("Invalid UV index: %i (key %s). Mesh %i has only %i UV channels",
571
2.34k
                                iIndex, prop->mKey.data, a, iChannels);
572
2.34k
                    }
573
2.68k
                }
574
218k
            }
575
6.11k
        }
576
17.4k
    }
577
4.96k
    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
4.96k
}
592
// ------------------------------------------------------------------------------------------------
593
28.8k
void ValidateDSProcess::Validate(const aiMaterial *pMaterial) {
594
    // check whether there are material keys that are obviously not legal
595
361k
    for (unsigned int i = 0; i < pMaterial->mNumProperties; ++i) {
596
332k
        const aiMaterialProperty *prop = pMaterial->mProperties[i];
597
332k
        if (!prop) {
598
0
            ReportError("aiMaterial::mProperties[%i] is nullptr (aiMaterial::mNumProperties is %i)",
599
0
                    i, pMaterial->mNumProperties);
600
0
        }
601
332k
        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
332k
        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
37.9k
            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
37.9k
            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
294k
        } else if (aiPTI_Float == prop->mType) {
620
228k
            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
228k
        } else if (aiPTI_Integer == prop->mType) {
626
54.5k
            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
54.5k
        }
632
        // TODO: check whether there is a key with an unknown name ...
633
332k
    }
634
635
    // make some more specific tests
636
28.8k
    ai_real fTemp;
637
28.8k
    int iShading;
638
28.8k
    if (AI_SUCCESS == aiGetMaterialInteger(pMaterial, AI_MATKEY_SHADING_MODEL, &iShading)) {
639
25.2k
        switch ((aiShadingMode)iShading) {
640
3.73k
        case aiShadingMode_Blinn:
641
3.73k
        case aiShadingMode_CookTorrance:
642
4.87k
        case aiShadingMode_Phong:
643
644
4.87k
            if (AI_SUCCESS != aiGetMaterialFloat(pMaterial, AI_MATKEY_SHININESS, &fTemp)) {
645
2
                ReportWarning("A specular shading model is specified but there is no "
646
2
                              "AI_MATKEY_SHININESS key");
647
2
            }
648
4.87k
            if (AI_SUCCESS == aiGetMaterialFloat(pMaterial, AI_MATKEY_SHININESS_STRENGTH, &fTemp) && !fTemp) {
649
11
                ReportWarning("A specular shading model is specified but the value of the "
650
11
                              "AI_MATKEY_SHININESS_STRENGTH key is 0.0");
651
11
            }
652
4.87k
            break;
653
20.3k
        default:
654
20.3k
            break;
655
25.2k
        }
656
25.2k
    }
657
658
28.8k
    if (AI_SUCCESS == aiGetMaterialFloat(pMaterial, AI_MATKEY_OPACITY, &fTemp) && (!fTemp || fTemp > 1.01)) {
659
848
        ReportWarning("Invalid opacity value (must be 0 < opacity < 1.0)");
660
848
    }
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
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_DIFFUSE);
666
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_SPECULAR);
667
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_AMBIENT);
668
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_EMISSIVE);
669
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_OPACITY);
670
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_SHININESS);
671
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_HEIGHT);
672
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_NORMALS);
673
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_DISPLACEMENT);
674
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_LIGHTMAP);
675
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_REFLECTION);
676
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_BASE_COLOR);
677
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_NORMAL_CAMERA);
678
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_EMISSION_COLOR);
679
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_METALNESS);
680
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_DIFFUSE_ROUGHNESS);
681
28.8k
    SearchForInvalidTextures(pMaterial, aiTextureType_AMBIENT_OCCLUSION);
682
28.8k
}
683
684
// ------------------------------------------------------------------------------------------------
685
130
void ValidateDSProcess::Validate(const aiTexture *pTexture) {
686
    // the data section may NEVER be nullptr
687
130
    if (nullptr == pTexture->pcData) {
688
0
        ReportError("aiTexture::pcData is nullptr");
689
0
    }
690
130
    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
130
    } else {
696
130
        if (!pTexture->mWidth) {
697
0
            ReportError("aiTexture::mWidth is zero (compressed texture)");
698
0
        }
699
130
        if ('\0' != pTexture->achFormatHint[HINTMAXTEXTURELEN - 1]) {
700
0
            ReportWarning("aiTexture::achFormatHint must be zero-terminated");
701
130
        } else if ('.' == pTexture->achFormatHint[0]) {
702
3
            ReportWarning("aiTexture::achFormatHint should contain a file extension "
703
3
                          "without a leading dot (format hint: %s).",
704
3
                    pTexture->achFormatHint);
705
3
        }
706
130
    }
707
708
130
    const char *sz = pTexture->achFormatHint;
709
130
    if ((sz[0] >= 'A' && sz[0] <= 'Z') ||
710
127
            (sz[1] >= 'A' && sz[1] <= 'Z') ||
711
125
            (sz[2] >= 'A' && sz[2] <= 'Z') ||
712
123
            (sz[3] >= 'A' && sz[3] <= 'Z')) {
713
7
        ReportError("aiTexture::achFormatHint contains non-lowercase letters");
714
7
    }
715
130
}
716
717
// ------------------------------------------------------------------------------------------------
718
void ValidateDSProcess::Validate(const aiAnimation *pAnimation,
719
5.87k
        const aiNodeAnim *pNodeAnim) {
720
5.87k
    Validate(&pNodeAnim->mNodeName);
721
722
5.87k
    if (!pNodeAnim->mNumPositionKeys && !pNodeAnim->mScalingKeys && !pNodeAnim->mNumRotationKeys) {
723
3
        ReportError("Empty node animation channel");
724
3
    }
725
    // otherwise check whether one of the keys exceeds the total duration of the animation
726
5.87k
    if (pNodeAnim->mNumPositionKeys) {
727
5.77k
        if (!pNodeAnim->mPositionKeys) {
728
0
            ReportError("aiNodeAnim::mPositionKeys is nullptr (aiNodeAnim::mNumPositionKeys is %i)",
729
0
                    pNodeAnim->mNumPositionKeys);
730
0
        }
731
5.77k
        double dLast = -10e10;
732
3.84M
        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
3.84M
            if (pAnimation->mDuration > 0. && pNodeAnim->mPositionKeys[i].mTime > pAnimation->mDuration + 0.001) {
737
10
                ReportError("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is larger "
738
10
                            "than aiAnimation::mDuration (which is %.5f)",
739
10
                        i,
740
10
                        (float)pNodeAnim->mPositionKeys[i].mTime,
741
10
                        (float)pAnimation->mDuration);
742
10
            }
743
3.84M
            if (i && pNodeAnim->mPositionKeys[i].mTime <= dLast) {
744
273
                ReportWarning("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is smaller "
745
273
                              "than aiAnimation::mPositionKeys[%i] (which is %.5f)",
746
273
                        i,
747
273
                        (float)pNodeAnim->mPositionKeys[i].mTime,
748
273
                        i - 1, (float)dLast);
749
273
            }
750
3.84M
            dLast = pNodeAnim->mPositionKeys[i].mTime;
751
3.84M
        }
752
5.77k
    }
753
    // rotation keys
754
5.87k
    if (pNodeAnim->mNumRotationKeys) {
755
5.84k
        if (!pNodeAnim->mRotationKeys) {
756
0
            ReportError("aiNodeAnim::mRotationKeys is nullptr (aiNodeAnim::mNumRotationKeys is %i)",
757
0
                    pNodeAnim->mNumRotationKeys);
758
0
        }
759
5.84k
        double dLast = -10e10;
760
4.59M
        for (unsigned int i = 0; i < pNodeAnim->mNumRotationKeys; ++i) {
761
4.59M
            if (pAnimation->mDuration > 0. && pNodeAnim->mRotationKeys[i].mTime > pAnimation->mDuration + 0.001) {
762
10
                ReportError("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is larger "
763
10
                            "than aiAnimation::mDuration (which is %.5f)",
764
10
                        i,
765
10
                        (float)pNodeAnim->mRotationKeys[i].mTime,
766
10
                        (float)pAnimation->mDuration);
767
10
            }
768
4.59M
            if (i && pNodeAnim->mRotationKeys[i].mTime <= dLast) {
769
111
                ReportWarning("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is smaller "
770
111
                              "than aiAnimation::mRotationKeys[%i] (which is %.5f)",
771
111
                        i,
772
111
                        (float)pNodeAnim->mRotationKeys[i].mTime,
773
111
                        i - 1, (float)dLast);
774
111
            }
775
4.59M
            dLast = pNodeAnim->mRotationKeys[i].mTime;
776
4.59M
        }
777
5.84k
    }
778
    // scaling keys
779
5.87k
    if (pNodeAnim->mNumScalingKeys) {
780
5.72k
        if (!pNodeAnim->mScalingKeys) {
781
0
            ReportError("aiNodeAnim::mScalingKeys is nullptr (aiNodeAnim::mNumScalingKeys is %i)",
782
0
                    pNodeAnim->mNumScalingKeys);
783
0
        }
784
5.72k
        double dLast = -10e10;
785
3.84M
        for (unsigned int i = 0; i < pNodeAnim->mNumScalingKeys; ++i) {
786
3.84M
            if (pAnimation->mDuration > 0. && pNodeAnim->mScalingKeys[i].mTime > pAnimation->mDuration + 0.001) {
787
0
                ReportError("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is larger "
788
0
                            "than aiAnimation::mDuration (which is %.5f)",
789
0
                        i,
790
0
                        (float)pNodeAnim->mScalingKeys[i].mTime,
791
0
                        (float)pAnimation->mDuration);
792
0
            }
793
3.84M
            if (i && pNodeAnim->mScalingKeys[i].mTime <= dLast) {
794
1
                ReportWarning("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is smaller "
795
1
                              "than aiAnimation::mScalingKeys[%i] (which is %.5f)",
796
1
                        i,
797
1
                        (float)pNodeAnim->mScalingKeys[i].mTime,
798
1
                        i - 1, (float)dLast);
799
1
            }
800
3.84M
            dLast = pNodeAnim->mScalingKeys[i].mTime;
801
3.84M
        }
802
5.72k
    }
803
804
5.87k
    if (!pNodeAnim->mNumScalingKeys && !pNodeAnim->mNumRotationKeys &&
805
17
            !pNodeAnim->mNumPositionKeys) {
806
0
        ReportError("A node animation channel must have at least one subtrack");
807
0
    }
808
5.87k
}
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
755k
void ValidateDSProcess::Validate(const aiNode *pNode) {
851
755k
    if (!pNode) {
852
445
        ReportError("A node of the scene-graph is nullptr");
853
445
    }
854
    // Validate node name string first so that it's safe to use in below expressions
855
755k
    this->Validate(&pNode->mName);
856
755k
    const char *nodeName = (&pNode->mName)->C_Str();
857
755k
    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
755k
    if (pNode->mNumMeshes) {
863
353k
        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
353k
        std::vector<bool> abHadMesh;
868
353k
        abHadMesh.resize(mScene->mNumMeshes, false);
869
738k
        for (unsigned int i = 0; i < pNode->mNumMeshes; ++i) {
870
385k
            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
385k
            if (abHadMesh[pNode->mMeshes[i]]) {
875
1
                ReportError("aiNode::mMeshes[%i] is already referenced by this node %s (value: %i)",
876
1
                        i, nodeName, pNode->mMeshes[i]);
877
1
            }
878
385k
            abHadMesh[pNode->mMeshes[i]] = true;
879
385k
        }
880
353k
    }
881
755k
    if (pNode->mNumChildren) {
882
38.3k
        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
773k
        for (unsigned int i = 0; i < pNode->mNumChildren; ++i) {
887
735k
            const aiNode *pChild = pNode->mChildren[i];
888
735k
            Validate(pChild);
889
735k
            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
735k
        }
894
717k
    } 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
755k
}
899
900
// ------------------------------------------------------------------------------------------------
901
1.19M
void ValidateDSProcess::Validate(const aiString *pString) {
902
1.19M
    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
1.19M
    const char *sz = pString->data;
907
15.2M
    while (true) {
908
15.2M
        if ('\0' == *sz) {
909
1.19M
            if (pString->length != (unsigned int)(sz - pString->data)) {
910
12
                ReportError("aiString::data is invalid: the terminal zero is at a wrong offset");
911
12
            }
912
1.19M
            break;
913
14.0M
        } else if (sz >= &pString->data[AI_MAXLEN]) {
914
0
            ReportError("aiString::data is invalid. There is no terminal character");
915
0
        }
916
14.0M
        ++sz;
917
14.0M
    }
918
1.19M
}