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 |
64 | ValidateDSProcess::ValidateDSProcess() : |
65 | mScene() |
66 | {} |
67 | |
68 | // ------------------------------------------------------------------------------------------------ |
69 | // Destructor, private as well |
70 | ValidateDSProcess::~ValidateDSProcess() |
71 | {} |
72 | |
73 | // ------------------------------------------------------------------------------------------------ |
74 | // Returns whether the processing step is present in the given flag field. |
75 | bool ValidateDSProcess::IsActive( unsigned int pFlags) const |
76 | { |
77 | return (pFlags & aiProcess_ValidateDataStructure) != 0; |
78 | } |
79 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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> |
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> |
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> |
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. |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | |