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