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