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 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 | |
57 | using namespace Assimp; |
58 | |
59 | // ------------------------------------------------------------------------------------------------ |
60 | // Constructor to be privately used by Importer |
61 | FindInvalidDataProcess::FindInvalidDataProcess() |
62 | : configEpsilon(0.0) |
63 | { |
64 | // nothing to do here |
65 | } |
66 | |
67 | // ------------------------------------------------------------------------------------------------ |
68 | // Destructor, private as well |
69 | FindInvalidDataProcess::~FindInvalidDataProcess() |
70 | { |
71 | // nothing to do here |
72 | } |
73 | |
74 | // ------------------------------------------------------------------------------------------------ |
75 | // Returns whether the processing step is present in the given flag field. |
76 | bool FindInvalidDataProcess::IsActive( unsigned int pFlags) const |
77 | { |
78 | return 0 != (pFlags & aiProcess_FindInvalidData); |
79 | } |
80 | |
81 | // ------------------------------------------------------------------------------------------------ |
82 | // Setup import configuration |
83 | void 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 |
91 | void 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. |
118 | void 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 | // ------------------------------------------------------------------------------------------------ |
171 | template <typename T> |
172 | inline 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 | // ------------------------------------------------------------------------------------------------ |
179 | template <> |
180 | inline 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 | // ------------------------------------------------------------------------------------------------ |
208 | template <typename T> |
209 | inline 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 | // ------------------------------------------------------------------------------------------------ |
224 | template <typename T> |
225 | AI_FORCE_INLINE bool EpsilonCompare(const T& n, const T& s, ai_real epsilon); |
226 | |
227 | // ------------------------------------------------------------------------------------------------ |
228 | AI_FORCE_INLINE bool EpsilonCompare(ai_real n, ai_real s, ai_real epsilon) { |
229 | return std::fabs(n-s)>epsilon; |
230 | } |
231 | |
232 | // ------------------------------------------------------------------------------------------------ |
233 | template <> |
234 | bool 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 | // ------------------------------------------------------------------------------------------------ |
242 | template <> |
243 | bool 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 | // ------------------------------------------------------------------------------------------------ |
252 | template <typename T> |
253 | inline 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 |
280 | void 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 | // ------------------------------------------------------------------------------------------------ |
289 | void 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 |
339 | int 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 | |