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