1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2016, 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 
48 
49 // internal headers
50 #include "ValidateDataStructure.h"
51 #include "BaseImporter.h"
52 #include "fast_atof.h"
53 #include "ProcessHelper.h"
54 #include <memory>
55 
56 // CRT headers
57 #include <stdarg.h>
58 
59 using namespace Assimp;
60 
61 // ------------------------------------------------------------------------------------------------
62 // Constructor to be privately used by Importer
ValidateDSProcess()63 ValidateDSProcess::ValidateDSProcess() :
64     mScene()
65 {}
66 
67 // ------------------------------------------------------------------------------------------------
68 // Destructor, private as well
~ValidateDSProcess()69 ValidateDSProcess::~ValidateDSProcess()
70 {}
71 
72 // ------------------------------------------------------------------------------------------------
73 // Returns whether the processing step is present in the given flag field.
IsActive(unsigned int pFlags) const74 bool ValidateDSProcess::IsActive( unsigned int pFlags) const
75 {
76     return (pFlags & aiProcess_ValidateDataStructure) != 0;
77 }
78 // ------------------------------------------------------------------------------------------------
ReportError(const char * msg,...)79 AI_WONT_RETURN void ValidateDSProcess::ReportError(const char* msg,...)
80 {
81     ai_assert(NULL != msg);
82 
83     va_list args;
84     va_start(args,msg);
85 
86     char szBuffer[3000];
87     const int iLen = vsprintf(szBuffer,msg,args);
88     ai_assert(iLen > 0);
89 
90     va_end(args);
91 #ifdef ASSIMP_BUILD_DEBUG
92     ai_assert( false );
93 #endif
94     throw DeadlyImportError("Validation failed: " + std::string(szBuffer,iLen));
95 }
96 // ------------------------------------------------------------------------------------------------
ReportWarning(const char * msg,...)97 void ValidateDSProcess::ReportWarning(const char* msg,...)
98 {
99     ai_assert(NULL != msg);
100 
101     va_list args;
102     va_start(args,msg);
103 
104     char szBuffer[3000];
105     const int iLen = vsprintf(szBuffer,msg,args);
106     ai_assert(iLen > 0);
107 
108     va_end(args);
109     DefaultLogger::get()->warn("Validation warning: " + std::string(szBuffer,iLen));
110 }
111 
112 // ------------------------------------------------------------------------------------------------
HasNameMatch(const aiString & in,aiNode * node)113 inline int HasNameMatch(const aiString& in, aiNode* node)
114 {
115     int result = (node->mName == in ? 1 : 0 );
116     for (unsigned int i = 0; i < node->mNumChildren;++i)    {
117         result += HasNameMatch(in,node->mChildren[i]);
118     }
119     return result;
120 }
121 
122 // ------------------------------------------------------------------------------------------------
123 template <typename T>
DoValidation(T ** parray,unsigned int size,const char * firstName,const char * secondName)124 inline void ValidateDSProcess::DoValidation(T** parray, unsigned int size,
125     const char* firstName, const char* secondName)
126 {
127     // validate all entries
128     if (size)
129     {
130         if (!parray)
131         {
132             ReportError("aiScene::%s is NULL (aiScene::%s is %i)",
133                 firstName, secondName, size);
134         }
135         for (unsigned int i = 0; i < size;++i)
136         {
137             if (!parray[i])
138             {
139                 ReportError("aiScene::%s[%i] is NULL (aiScene::%s is %i)",
140                     firstName,i,secondName,size);
141             }
142             Validate(parray[i]);
143         }
144     }
145 }
146 
147 // ------------------------------------------------------------------------------------------------
148 template <typename T>
DoValidationEx(T ** parray,unsigned int size,const char * firstName,const char * secondName)149 inline void ValidateDSProcess::DoValidationEx(T** parray, unsigned int size,
150     const char* firstName, const char* secondName)
151 {
152     // validate all entries
153     if (size)
154     {
155         if (!parray)    {
156             ReportError("aiScene::%s is NULL (aiScene::%s is %i)",
157                 firstName, secondName, size);
158         }
159         for (unsigned int i = 0; i < size;++i)
160         {
161             if (!parray[i])
162             {
163                 ReportError("aiScene::%s[%i] is NULL (aiScene::%s is %i)",
164                     firstName,i,secondName,size);
165             }
166             Validate(parray[i]);
167 
168             // check whether there are duplicate names
169             for (unsigned int a = i+1; a < size;++a)
170             {
171                 if (parray[i]->mName == parray[a]->mName)
172                 {
173                     this->ReportError("aiScene::%s[%i] has the same name as "
174                         "aiScene::%s[%i]",firstName, i,secondName, a);
175                 }
176             }
177         }
178     }
179 }
180 
181 // ------------------------------------------------------------------------------------------------
182 template <typename T>
DoValidationWithNameCheck(T ** array,unsigned int size,const char * firstName,const char * secondName)183 inline void ValidateDSProcess::DoValidationWithNameCheck(T** array,
184     unsigned int size, const char* firstName,
185     const char* secondName)
186 {
187     // validate all entries
188     DoValidationEx(array,size,firstName,secondName);
189 
190     for (unsigned int i = 0; i < size;++i)
191     {
192         int res = HasNameMatch(array[i]->mName,mScene->mRootNode);
193         if (!res)   {
194             ReportError("aiScene::%s[%i] has no corresponding node in the scene graph (%s)",
195                 firstName,i,array[i]->mName.data);
196         }
197         else if (1 != res)  {
198             ReportError("aiScene::%s[%i]: there are more than one nodes with %s as name",
199                 firstName,i,array[i]->mName.data);
200         }
201     }
202 }
203 
204 // ------------------------------------------------------------------------------------------------
205 // Executes the post processing step on the given imported data.
Execute(aiScene * pScene)206 void ValidateDSProcess::Execute( aiScene* pScene)
207 {
208     this->mScene = pScene;
209     DefaultLogger::get()->debug("ValidateDataStructureProcess begin");
210 
211     // validate the node graph of the scene
212     Validate(pScene->mRootNode);
213 
214     // validate all meshes
215     if (pScene->mNumMeshes) {
216         DoValidation(pScene->mMeshes,pScene->mNumMeshes,"mMeshes","mNumMeshes");
217     }
218     else if (!(mScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE)) {
219         ReportError("aiScene::mNumMeshes is 0. At least one mesh must be there");
220     }
221     else if (pScene->mMeshes)   {
222         ReportError("aiScene::mMeshes is non-null although there are no meshes");
223     }
224 
225     // validate all animations
226     if (pScene->mNumAnimations) {
227         DoValidation(pScene->mAnimations,pScene->mNumAnimations,
228             "mAnimations","mNumAnimations");
229     }
230     else if (pScene->mAnimations)   {
231         ReportError("aiScene::mAnimations is non-null although there are no animations");
232     }
233 
234     // validate all cameras
235     if (pScene->mNumCameras) {
236         DoValidationWithNameCheck(pScene->mCameras,pScene->mNumCameras,
237             "mCameras","mNumCameras");
238     }
239     else if (pScene->mCameras)  {
240         ReportError("aiScene::mCameras is non-null although there are no cameras");
241     }
242 
243     // validate all lights
244     if (pScene->mNumLights) {
245         DoValidationWithNameCheck(pScene->mLights,pScene->mNumLights,
246             "mLights","mNumLights");
247     }
248     else if (pScene->mLights)   {
249         ReportError("aiScene::mLights is non-null although there are no lights");
250     }
251 
252     // validate all textures
253     if (pScene->mNumTextures) {
254         DoValidation(pScene->mTextures,pScene->mNumTextures,
255             "mTextures","mNumTextures");
256     }
257     else if (pScene->mTextures) {
258         ReportError("aiScene::mTextures is non-null although there are no textures");
259     }
260 
261     // validate all materials
262     if (pScene->mNumMaterials) {
263         DoValidation(pScene->mMaterials,pScene->mNumMaterials,"mMaterials","mNumMaterials");
264     }
265 #if 0
266     // NOTE: ScenePreprocessor generates a default material if none is there
267     else if (!(mScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE)) {
268         ReportError("aiScene::mNumMaterials is 0. At least one material must be there");
269     }
270 #endif
271     else if (pScene->mMaterials)    {
272         ReportError("aiScene::mMaterials is non-null although there are no materials");
273     }
274 
275 //  if (!has)ReportError("The aiScene data structure is empty");
276     DefaultLogger::get()->debug("ValidateDataStructureProcess end");
277 }
278 
279 // ------------------------------------------------------------------------------------------------
Validate(const aiLight * pLight)280 void ValidateDSProcess::Validate( const aiLight* pLight)
281 {
282     if (pLight->mType == aiLightSource_UNDEFINED)
283         ReportWarning("aiLight::mType is aiLightSource_UNDEFINED");
284 
285     if (!pLight->mAttenuationConstant &&
286         !pLight->mAttenuationLinear   &&
287         !pLight->mAttenuationQuadratic) {
288         ReportWarning("aiLight::mAttenuationXXX - all are zero");
289     }
290 
291     if (pLight->mAngleInnerCone > pLight->mAngleOuterCone)
292         ReportError("aiLight::mAngleInnerCone is larger than aiLight::mAngleOuterCone");
293 
294     if (pLight->mColorDiffuse.IsBlack() && pLight->mColorAmbient.IsBlack()
295         && pLight->mColorSpecular.IsBlack())
296     {
297         ReportWarning("aiLight::mColorXXX - all are black and won't have any influence");
298     }
299 }
300 
301 // ------------------------------------------------------------------------------------------------
Validate(const aiCamera * pCamera)302 void ValidateDSProcess::Validate( const aiCamera* pCamera)
303 {
304     if (pCamera->mClipPlaneFar <= pCamera->mClipPlaneNear)
305         ReportError("aiCamera::mClipPlaneFar must be >= aiCamera::mClipPlaneNear");
306 
307     // FIX: there are many 3ds files with invalid FOVs. No reason to
308     // reject them at all ... a warning is appropriate.
309     if (!pCamera->mHorizontalFOV || pCamera->mHorizontalFOV >= (float)AI_MATH_PI)
310         ReportWarning("%f is not a valid value for aiCamera::mHorizontalFOV",pCamera->mHorizontalFOV);
311 }
312 
313 // ------------------------------------------------------------------------------------------------
Validate(const aiMesh * pMesh)314 void ValidateDSProcess::Validate( const aiMesh* pMesh)
315 {
316     // validate the material index of the mesh
317     if (mScene->mNumMaterials && pMesh->mMaterialIndex >= mScene->mNumMaterials)
318     {
319         ReportError("aiMesh::mMaterialIndex is invalid (value: %i maximum: %i)",
320             pMesh->mMaterialIndex,mScene->mNumMaterials-1);
321     }
322 
323     Validate(&pMesh->mName);
324 
325     for (unsigned int i = 0; i < pMesh->mNumFaces; ++i)
326     {
327         aiFace& face = pMesh->mFaces[i];
328 
329         if (pMesh->mPrimitiveTypes)
330         {
331             switch (face.mNumIndices)
332             {
333             case 0:
334                 ReportError("aiMesh::mFaces[%i].mNumIndices is 0",i);
335             case 1:
336                 if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_POINT))
337                 {
338                     ReportError("aiMesh::mFaces[%i] is a POINT but aiMesh::mPrimtiveTypes "
339                         "does not report the POINT flag",i);
340                 }
341                 break;
342             case 2:
343                 if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_LINE))
344                 {
345                     ReportError("aiMesh::mFaces[%i] is a LINE but aiMesh::mPrimtiveTypes "
346                         "does not report the LINE flag",i);
347                 }
348                 break;
349             case 3:
350                 if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE))
351                 {
352                     ReportError("aiMesh::mFaces[%i] is a TRIANGLE but aiMesh::mPrimtiveTypes "
353                         "does not report the TRIANGLE flag",i);
354                 }
355                 break;
356             default:
357                 if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_POLYGON))
358                 {
359                     this->ReportError("aiMesh::mFaces[%i] is a POLYGON but aiMesh::mPrimtiveTypes "
360                         "does not report the POLYGON flag",i);
361                 }
362                 break;
363             };
364         }
365 
366         if (!face.mIndices)
367             ReportError("aiMesh::mFaces[%i].mIndices is NULL",i);
368     }
369 
370     // positions must always be there ...
371     if (!pMesh->mNumVertices || (!pMesh->mVertices && !mScene->mFlags)) {
372         ReportError("The mesh contains no vertices");
373     }
374 
375     if (pMesh->mNumVertices > AI_MAX_VERTICES) {
376         ReportError("Mesh has too many vertices: %u, but the limit is %u",pMesh->mNumVertices,AI_MAX_VERTICES);
377     }
378     if (pMesh->mNumFaces > AI_MAX_FACES) {
379         ReportError("Mesh has too many faces: %u, but the limit is %u",pMesh->mNumFaces,AI_MAX_FACES);
380     }
381 
382     // if tangents are there there must also be bitangent vectors ...
383     if ((pMesh->mTangents != NULL) != (pMesh->mBitangents != NULL)) {
384         ReportError("If there are tangents, bitangent vectors must be present as well");
385     }
386 
387     // faces, too
388     if (!pMesh->mNumFaces || (!pMesh->mFaces && !mScene->mFlags))   {
389         ReportError("Mesh contains no faces");
390     }
391 
392     // now check whether the face indexing layout is correct:
393     // unique vertices, pseudo-indexed.
394     std::vector<bool> abRefList;
395     abRefList.resize(pMesh->mNumVertices,false);
396     for (unsigned int i = 0; i < pMesh->mNumFaces;++i)
397     {
398         aiFace& face = pMesh->mFaces[i];
399         if (face.mNumIndices > AI_MAX_FACE_INDICES) {
400             ReportError("Face %u has too many faces: %u, but the limit is %u",i,face.mNumIndices,AI_MAX_FACE_INDICES);
401         }
402 
403         for (unsigned int a = 0; a < face.mNumIndices;++a)
404         {
405             if (face.mIndices[a] >= pMesh->mNumVertices)    {
406                 ReportError("aiMesh::mFaces[%i]::mIndices[%i] is out of range",i,a);
407             }
408             // the MSB flag is temporarily used by the extra verbose
409             // mode to tell us that the JoinVerticesProcess might have
410             // been executed already.
411             if ( !(this->mScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT ) && 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     float 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.01f)) {
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