Coverage Report

Created: 2026-09-14 07:44

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