1/*
2---------------------------------------------------------------------------
3Open Asset Import Library (assimp)
4---------------------------------------------------------------------------
5
6Copyright (c) 2006-2017, assimp team
7
8
9All rights reserved.
10
11Redistribution and use of this software in source and binary forms,
12with or without modification, are permitted provided that the following
13conditions are met:
14
15* Redistributions of source code must retain the above
16 copyright notice, this list of conditions and the
17 following disclaimer.
18
19* Redistributions in binary form must reproduce the above
20 copyright notice, this list of conditions and the
21 following disclaimer in the documentation and/or other
22 materials provided with the distribution.
23
24* Neither the name of the assimp team, nor the names of its
25 contributors may be used to endorse or promote products
26 derived from this software without specific prior
27 written permission of the assimp team.
28
29THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40---------------------------------------------------------------------------
41*/
42
43/** @file Defines a post processing step to search an importer's output
44 for data that is obviously invalid */
45
46
47
48#ifndef ASSIMP_BUILD_NO_FINDINVALIDDATA_PROCESS
49
50// internal headers
51#include "FindInvalidDataProcess.h"
52#include "ProcessHelper.h"
53#include "Macros.h"
54#include "Exceptional.h"
55#include "qnan.h"
56
57using namespace Assimp;
58
59// ------------------------------------------------------------------------------------------------
60// Constructor to be privately used by Importer
61FindInvalidDataProcess::FindInvalidDataProcess()
62 : configEpsilon(0.0)
63{
64 // nothing to do here
65}
66
67// ------------------------------------------------------------------------------------------------
68// Destructor, private as well
69FindInvalidDataProcess::~FindInvalidDataProcess()
70{
71 // nothing to do here
72}
73
74// ------------------------------------------------------------------------------------------------
75// Returns whether the processing step is present in the given flag field.
76bool FindInvalidDataProcess::IsActive( unsigned int pFlags) const
77{
78 return 0 != (pFlags & aiProcess_FindInvalidData);
79}
80
81// ------------------------------------------------------------------------------------------------
82// Setup import configuration
83void FindInvalidDataProcess::SetupProperties(const Importer* pImp)
84{
85 // Get the current value of AI_CONFIG_PP_FID_ANIM_ACCURACY
86 configEpsilon = (0 != pImp->GetPropertyFloat(AI_CONFIG_PP_FID_ANIM_ACCURACY,0.f));
87}
88
89// ------------------------------------------------------------------------------------------------
90// Update mesh references in the node graph
91void UpdateMeshReferences(aiNode* node, const std::vector<unsigned int>& meshMapping)
92{
93 if (node->mNumMeshes) {
94 unsigned int out = 0;
95 for (unsigned int a = 0; a < node->mNumMeshes;++a) {
96
97 unsigned int ref = node->mMeshes[a];
98 if (UINT_MAX != (ref = meshMapping[ref])) {
99 node->mMeshes[out++] = ref;
100 }
101 }
102 // just let the members that are unused, that's much cheaper
103 // than a full array realloc'n'copy party ...
104 if(!(node->mNumMeshes = out)) {
105
106 delete[] node->mMeshes;
107 node->mMeshes = NULL;
108 }
109 }
110 // recursively update all children
111 for (unsigned int i = 0; i < node->mNumChildren;++i) {
112 UpdateMeshReferences(node->mChildren[i],meshMapping);
113 }
114}
115
116// ------------------------------------------------------------------------------------------------
117// Executes the post processing step on the given imported data.
118void FindInvalidDataProcess::Execute( aiScene* pScene)
119{
120 DefaultLogger::get()->debug("FindInvalidDataProcess begin");
121
122 bool out = false;
123 std::vector<unsigned int> meshMapping(pScene->mNumMeshes);
124 unsigned int real = 0;
125
126 // Process meshes
127 for( unsigned int a = 0; a < pScene->mNumMeshes; a++) {
128
129 int result;
130 if ((result = ProcessMesh( pScene->mMeshes[a]))) {
131 out = true;
132
133 if (2 == result) {
134 // remove this mesh
135 delete pScene->mMeshes[a];
136 AI_DEBUG_INVALIDATE_PTR(pScene->mMeshes[a]);
137
138 meshMapping[a] = UINT_MAX;
139 continue;
140 }
141 }
142 pScene->mMeshes[real] = pScene->mMeshes[a];
143 meshMapping[a] = real++;
144 }
145
146 // Process animations
147 for (unsigned int a = 0; a < pScene->mNumAnimations;++a) {
148 ProcessAnimation( pScene->mAnimations[a]);
149 }
150
151
152 if (out) {
153 if ( real != pScene->mNumMeshes) {
154 if (!real) {
155 throw DeadlyImportError("No meshes remaining");
156 }
157
158 // we need to remove some meshes.
159 // therefore we'll also need to remove all references
160 // to them from the scenegraph
161 UpdateMeshReferences(pScene->mRootNode,meshMapping);
162 pScene->mNumMeshes = real;
163 }
164
165 DefaultLogger::get()->info("FindInvalidDataProcess finished. Found issues ...");
166 }
167 else DefaultLogger::get()->debug("FindInvalidDataProcess finished. Everything seems to be OK.");
168}
169
170// ------------------------------------------------------------------------------------------------
171template <typename T>
172inline const char* ValidateArrayContents(const T* /*arr*/, unsigned int /*size*/,
173 const std::vector<bool>& /*dirtyMask*/, bool /*mayBeIdentical = false*/, bool /*mayBeZero = true*/)
174{
175 return NULL;
176}
177
178// ------------------------------------------------------------------------------------------------
179template <>
180inline const char* ValidateArrayContents<aiVector3D>(const aiVector3D* arr, unsigned int size,
181 const std::vector<bool>& dirtyMask, bool mayBeIdentical , bool mayBeZero )
182{
183 bool b = false;
184 unsigned int cnt = 0;
185 for (unsigned int i = 0; i < size;++i) {
186
187 if (dirtyMask.size() && dirtyMask[i]) {
188 continue;
189 }
190 ++cnt;
191
192 const aiVector3D& v = arr[i];
193 if (is_special_float(v.x) || is_special_float(v.y) || is_special_float(v.z)) {
194 return "INF/NAN was found in a vector component";
195 }
196 if (!mayBeZero && !v.x && !v.y && !v.z ) {
197 return "Found zero-length vector";
198 }
199 if (i && v != arr[i-1])b = true;
200 }
201 if (cnt > 1 && !b && !mayBeIdentical) {
202 return "All vectors are identical";
203 }
204 return NULL;
205}
206
207// ------------------------------------------------------------------------------------------------
208template <typename T>
209inline bool ProcessArray(T*& in, unsigned int num,const char* name,
210 const std::vector<bool>& dirtyMask, bool mayBeIdentical = false, bool mayBeZero = true)
211{
212 const char* err = ValidateArrayContents(in,num,dirtyMask,mayBeIdentical,mayBeZero);
213 if (err) {
214 DefaultLogger::get()->error(std::string("FindInvalidDataProcess fails on mesh ") + name + ": " + err);
215
216 delete[] in;
217 in = NULL;
218 return true;
219 }
220 return false;
221}
222
223// ------------------------------------------------------------------------------------------------
224template <typename T>
225AI_FORCE_INLINE bool EpsilonCompare(const T& n, const T& s, ai_real epsilon);
226
227// ------------------------------------------------------------------------------------------------
228AI_FORCE_INLINE bool EpsilonCompare(ai_real n, ai_real s, ai_real epsilon) {
229 return std::fabs(n-s)>epsilon;
230}
231
232// ------------------------------------------------------------------------------------------------
233template <>
234bool EpsilonCompare<aiVectorKey>(const aiVectorKey& n, const aiVectorKey& s, ai_real epsilon) {
235 return
236 EpsilonCompare(n.mValue.x,s.mValue.x,epsilon) &&
237 EpsilonCompare(n.mValue.y,s.mValue.y,epsilon) &&
238 EpsilonCompare(n.mValue.z,s.mValue.z,epsilon);
239}
240
241// ------------------------------------------------------------------------------------------------
242template <>
243bool EpsilonCompare<aiQuatKey>(const aiQuatKey& n, const aiQuatKey& s, ai_real epsilon) {
244 return
245 EpsilonCompare(n.mValue.x,s.mValue.x,epsilon) &&
246 EpsilonCompare(n.mValue.y,s.mValue.y,epsilon) &&
247 EpsilonCompare(n.mValue.z,s.mValue.z,epsilon) &&
248 EpsilonCompare(n.mValue.w,s.mValue.w,epsilon);
249}
250
251// ------------------------------------------------------------------------------------------------
252template <typename T>
253inline bool AllIdentical(T* in, unsigned int num, ai_real epsilon)
254{
255 if (num <= 1) {
256 return true;
257 }
258
259 if (epsilon > 0.f) {
260 for (unsigned int i = 0; i < num-1;++i) {
261
262 if (!EpsilonCompare(in[i],in[i+1],epsilon)) {
263 return false;
264 }
265 }
266 }
267 else {
268 for (unsigned int i = 0; i < num-1;++i) {
269
270 if (in[i] != in[i+1]) {
271 return false;
272 }
273 }
274 }
275 return true;
276}
277
278// ------------------------------------------------------------------------------------------------
279// Search an animation for invalid content
280void FindInvalidDataProcess::ProcessAnimation (aiAnimation* anim)
281{
282 // Process all animation channels
283 for (unsigned int a = 0; a < anim->mNumChannels;++a) {
284 ProcessAnimationChannel( anim->mChannels[a]);
285 }
286}
287
288// ------------------------------------------------------------------------------------------------
289void FindInvalidDataProcess::ProcessAnimationChannel (aiNodeAnim* anim)
290{
291 int i = 0;
292
293 // ScenePreprocessor's work ...
294 ai_assert((0 != anim->mPositionKeys && 0 != anim->mRotationKeys && 0 != anim->mScalingKeys));
295
296 // Check whether all values in a tracks are identical - in this case
297 // we can remove al keys except one.
298 // POSITIONS
299 if (anim->mNumPositionKeys > 1 && AllIdentical(anim->mPositionKeys,anim->mNumPositionKeys,configEpsilon))
300 {
301 aiVectorKey v = anim->mPositionKeys[0];
302
303 // Reallocate ... we need just ONE element, it makes no sense to reuse the array
304 delete[] anim->mPositionKeys;
305 anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys = 1];
306 anim->mPositionKeys[0] = v;
307 i = 1;
308 }
309
310 // ROTATIONS
311 if (anim->mNumRotationKeys > 1 && AllIdentical(anim->mRotationKeys,anim->mNumRotationKeys,configEpsilon))
312 {
313 aiQuatKey v = anim->mRotationKeys[0];
314
315 // Reallocate ... we need just ONE element, it makes no sense to reuse the array
316 delete[] anim->mRotationKeys;
317 anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys = 1];
318 anim->mRotationKeys[0] = v;
319 i = 1;
320 }
321
322 // SCALINGS
323 if (anim->mNumScalingKeys > 1 && AllIdentical(anim->mScalingKeys,anim->mNumScalingKeys,configEpsilon))
324 {
325 aiVectorKey v = anim->mScalingKeys[0];
326
327 // Reallocate ... we need just ONE element, it makes no sense to reuse the array
328 delete[] anim->mScalingKeys;
329 anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys = 1];
330 anim->mScalingKeys[0] = v;
331 i = 1;
332 }
333 if (1 == i)
334 DefaultLogger::get()->warn("Simplified dummy tracks with just one key");
335}
336
337// ------------------------------------------------------------------------------------------------
338// Search a mesh for invalid contents
339int FindInvalidDataProcess::ProcessMesh (aiMesh* pMesh)
340{
341 bool ret = false;
342 std::vector<bool> dirtyMask(pMesh->mNumVertices, pMesh->mNumFaces != 0);
343
344 // Ignore elements that are not referenced by vertices.
345 // (they are, for example, caused by the FindDegenerates step)
346 for (unsigned int m = 0; m < pMesh->mNumFaces; ++m) {
347 const aiFace& f = pMesh->mFaces[m];
348
349 for (unsigned int i = 0; i < f.mNumIndices; ++i) {
350 dirtyMask[f.mIndices[i]] = false;
351 }
352 }
353
354 // Process vertex positions
355 if (pMesh->mVertices && ProcessArray(pMesh->mVertices, pMesh->mNumVertices, "positions", dirtyMask)) {
356 DefaultLogger::get()->error("Deleting mesh: Unable to continue without vertex positions");
357
358 return 2;
359 }
360
361 // process texture coordinates
362 for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS && pMesh->mTextureCoords[i]; ++i) {
363 if (ProcessArray(pMesh->mTextureCoords[i], pMesh->mNumVertices, "uvcoords", dirtyMask)) {
364 pMesh->mNumUVComponents[i] = 0;
365
366 // delete all subsequent texture coordinate sets.
367 for (unsigned int a = i + 1; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
368 delete[] pMesh->mTextureCoords[a];
369 pMesh->mTextureCoords[a] = NULL;
370 pMesh->mNumUVComponents[a] = 0;
371 }
372
373 ret = true;
374 }
375 }
376
377 // -- we don't validate vertex colors, it's difficult to say whether
378 // they are invalid or not.
379
380 // Normals and tangents are undefined for point and line faces.
381 if (pMesh->mNormals || pMesh->mTangents) {
382
383 if (aiPrimitiveType_POINT & pMesh->mPrimitiveTypes ||
384 aiPrimitiveType_LINE & pMesh->mPrimitiveTypes)
385 {
386 if (aiPrimitiveType_TRIANGLE & pMesh->mPrimitiveTypes ||
387 aiPrimitiveType_POLYGON & pMesh->mPrimitiveTypes)
388 {
389 // We need to update the lookup-table
390 for (unsigned int m = 0; m < pMesh->mNumFaces;++m)
391 {
392 const aiFace& f = pMesh->mFaces[m];
393
394 if (f.mNumIndices < 3) {
395 dirtyMask[f.mIndices[0]] = true;
396
397 if (f.mNumIndices == 2) {
398 dirtyMask[f.mIndices[1]] = true;
399 }
400 }
401 }
402 }
403 // Normals, tangents and bitangents are undefined for
404 // the whole mesh (and should not even be there)
405 else return ret;
406 }
407
408 // Process mesh normals
409 if (pMesh->mNormals && ProcessArray(pMesh->mNormals,pMesh->mNumVertices,
410 "normals",dirtyMask,true,false))
411 ret = true;
412
413 // Process mesh tangents
414 if (pMesh->mTangents && ProcessArray(pMesh->mTangents,pMesh->mNumVertices,"tangents",dirtyMask)) {
415 delete[] pMesh->mBitangents; pMesh->mBitangents = NULL;
416 ret = true;
417 }
418
419 // Process mesh bitangents
420 if (pMesh->mBitangents && ProcessArray(pMesh->mBitangents,pMesh->mNumVertices,"bitangents",dirtyMask)) {
421 delete[] pMesh->mTangents; pMesh->mTangents = NULL;
422 ret = true;
423 }
424 }
425 return ret ? 1 : 0;
426}
427
428
429#endif // !! ASSIMP_BUILD_NO_FINDINVALIDDATA_PROCESS
430