1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2017, assimp team
7 
8 
9 All rights reserved.
10 
11 Redistribution and use of this software in source and binary forms,
12 with or without modification, are permitted provided that the following
13 conditions 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 
29 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39 OF 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 
60 using namespace Assimp;
61 
62 // ------------------------------------------------------------------------------------------------
63 // Constructor to be privately used by Importer
ValidateDSProcess()64 ValidateDSProcess::ValidateDSProcess() :
65     mScene()
66 {}
67 
68 // ------------------------------------------------------------------------------------------------
69 // Destructor, private as well
~ValidateDSProcess()70 ValidateDSProcess::~ValidateDSProcess()
71 {}
72 
73 // ------------------------------------------------------------------------------------------------
74 // Returns whether the processing step is present in the given flag field.
IsActive(unsigned int pFlags) const75 bool ValidateDSProcess::IsActive( unsigned int pFlags) const
76 {
77     return (pFlags & aiProcess_ValidateDataStructure) != 0;
78 }
79 // ------------------------------------------------------------------------------------------------
ReportError(const char * msg,...)80 AI_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 // ------------------------------------------------------------------------------------------------
ReportWarning(const char * msg,...)96 void 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 // ------------------------------------------------------------------------------------------------
HasNameMatch(const aiString & in,aiNode * node)112 inline 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 // ------------------------------------------------------------------------------------------------
122 template <typename T>
DoValidation(T ** parray,unsigned int size,const char * firstName,const char * secondName)123 inline 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 // ------------------------------------------------------------------------------------------------
147 template <typename T>
DoValidationEx(T ** parray,unsigned int size,const char * firstName,const char * secondName)148 inline 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 // ------------------------------------------------------------------------------------------------
181 template <typename T>
DoValidationWithNameCheck(T ** array,unsigned int size,const char * firstName,const char * secondName)182 inline 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.
Execute(aiScene * pScene)205 void 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 // ------------------------------------------------------------------------------------------------
Validate(const aiLight * pLight)279 void 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 // ------------------------------------------------------------------------------------------------
Validate(const aiCamera * pCamera)301 void 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 // ------------------------------------------------------------------------------------------------
Validate(const aiMesh * pMesh)313 void 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 // ------------------------------------------------------------------------------------------------
Validate(const aiMesh * pMesh,const aiBone * pBone,float * afSum)513 void 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 // ------------------------------------------------------------------------------------------------
Validate(const aiAnimation * pAnimation)536 void 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 // ------------------------------------------------------------------------------------------------
SearchForInvalidTextures(const aiMaterial * pMaterial,aiTextureType type)564 void 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 // ------------------------------------------------------------------------------------------------
Validate(const aiMaterial * pMaterial)676 void 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 // ------------------------------------------------------------------------------------------------
Validate(const aiTexture * pTexture)765 void 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 // ------------------------------------------------------------------------------------------------
Validate(const aiAnimation * pAnimation,const aiNodeAnim * pNodeAnim)800 void 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 // ------------------------------------------------------------------------------------------------
Validate(const aiNode * pNode)903 void 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 // ------------------------------------------------------------------------------------------------
Validate(const aiString * pString)949 void 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