FEATURE: FindInvalidData step now provides a configuration option to fine-tune the accuracy of floating-point comparisons.

Ogre-Importer now compiles with -noboost.
Fixing GCC complaints about inproper 'address of temporary' in Ogre-Importer.


git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@480 67173fc5-114c-0410-ac8e-9d2fd5bffc1f
pull/1/head
aramis_acg 2009-09-21 17:41:57 +00:00
parent 29bb83ebb6
commit c89107a117
6 changed files with 183 additions and 130 deletions

View File

@ -73,32 +73,39 @@ bool FindInvalidDataProcess::IsActive( unsigned int pFlags) const
return 0 != (pFlags & aiProcess_FindInvalidData);
}
// ------------------------------------------------------------------------------------------------
// Setup import configuration
void FindInvalidDataProcess::SetupProperties(const Importer* pImp)
{
// Get the current value of AI_CONFIG_PP_FID_ANIM_ACCURACY
configEpsilon = (0 != pImp->GetPropertyFloat(AI_CONFIG_PP_FID_ANIM_ACCURACY,0.f));
}
// ------------------------------------------------------------------------------------------------
// Update mesh references in the node graph
void UpdateMeshReferences(aiNode* node, const std::vector<unsigned int>& meshMapping)
{
if (node->mNumMeshes)
{
if (node->mNumMeshes) {
unsigned int out = 0;
for (unsigned int a = 0; a < node->mNumMeshes;++a)
{
for (unsigned int a = 0; a < node->mNumMeshes;++a) {
register unsigned int ref = node->mMeshes[a];
if (0xffffffff != (ref = meshMapping[ref]))
{
if (0xffffffff != (ref = meshMapping[ref])) {
node->mMeshes[out++] = ref;
}
}
// just let the members that are unused, that's much cheaper
// than a full array realloc'n'copy party ...
if(!(node->mNumMeshes = out))
{
if(!(node->mNumMeshes = out)) {
delete[] node->mMeshes;
node->mMeshes = NULL;
}
}
// recursively update all children
for (unsigned int i = 0; i < node->mNumChildren;++i)
for (unsigned int i = 0; i < node->mNumChildren;++i) {
UpdateMeshReferences(node->mChildren[i],meshMapping);
}
}
// ------------------------------------------------------------------------------------------------
@ -112,14 +119,13 @@ void FindInvalidDataProcess::Execute( aiScene* pScene)
unsigned int real = 0;
// Process meshes
for( unsigned int a = 0; a < pScene->mNumMeshes; a++)
{
for( unsigned int a = 0; a < pScene->mNumMeshes; a++) {
int result;
if ((result = ProcessMesh( pScene->mMeshes[a])))
{
if ((result = ProcessMesh( pScene->mMeshes[a]))) {
out = true;
if (2 == result)
{
if (2 == result) {
// remove this mesh
delete pScene->mMeshes[a];
AI_DEBUG_INVALIDATE_PTR(pScene->mMeshes[a]);
@ -133,17 +139,16 @@ void FindInvalidDataProcess::Execute( aiScene* pScene)
}
// Process animations
for (unsigned int a = 0; a < pScene->mNumAnimations;++a)
for (unsigned int a = 0; a < pScene->mNumAnimations;++a) {
ProcessAnimation( pScene->mAnimations[a]);
}
if (out)
{
if ( real != pScene->mNumMeshes)
{
if (!real)
if (out) {
if ( real != pScene->mNumMeshes) {
if (!real) {
throw new ImportErrorException("No meshes remaining");
}
// we need to remove some meshes.
// therefore we'll also need to remove all references
@ -172,24 +177,25 @@ inline const char* ValidateArrayContents<aiVector3D>(const aiVector3D* arr, unsi
{
bool b = false;
unsigned int cnt = 0;
for (unsigned int i = 0; i < size;++i)
{
if (dirtyMask.size() && dirtyMask[i])continue;
for (unsigned int i = 0; i < size;++i) {
if (dirtyMask.size() && dirtyMask[i]) {
continue;
}
++cnt;
const aiVector3D& v = arr[i];
if (is_special_float(v.x) || is_special_float(v.y) || is_special_float(v.z))
{
if (is_special_float(v.x) || is_special_float(v.y) || is_special_float(v.z)) {
return "INF/NAN was found in a vector component";
}
if (!mayBeZero && !v.x && !v.y && !v.z )
{
if (!mayBeZero && !v.x && !v.y && !v.z ) {
return "Found zero-length vector";
}
if (i && v != arr[i-1])b = true;
}
if (cnt > 1 && !b && !mayBeIdentical)
if (cnt > 1 && !b && !mayBeIdentical) {
return "All vectors are identical";
}
return NULL;
}
@ -198,10 +204,8 @@ template <typename T>
inline bool ProcessArray(T*& in, unsigned int num,const char* name,
const std::vector<bool>& dirtyMask, bool mayBeIdentical = false, bool mayBeZero = true)
{
const char* err = ValidateArrayContents(in,num,dirtyMask,
mayBeIdentical,mayBeZero);
if (err)
{
const char* err = ValidateArrayContents(in,num,dirtyMask,mayBeIdentical,mayBeZero);
if (err) {
DefaultLogger::get()->error(std::string("FindInvalidDataProcess fails on mesh ") + name + ": " + err);
delete[] in;
@ -213,12 +217,55 @@ inline bool ProcessArray(T*& in, unsigned int num,const char* name,
// ------------------------------------------------------------------------------------------------
template <typename T>
inline bool AllIdentical(T* in, unsigned int num)
AI_FORCE_INLINE bool EpsilonCompare(const T& n, const T& s, float epsilon);
// ------------------------------------------------------------------------------------------------
AI_FORCE_INLINE bool EpsilonCompare(float n, float s, float epsilon) {
return fabs(n-s)>epsilon;
}
// ------------------------------------------------------------------------------------------------
template <>
bool EpsilonCompare<aiVectorKey>(const aiVectorKey& n, const aiVectorKey& s, float epsilon) {
return
EpsilonCompare(n.mValue.x,s.mValue.x,epsilon) &&
EpsilonCompare(n.mValue.y,s.mValue.y,epsilon) &&
EpsilonCompare(n.mValue.z,s.mValue.z,epsilon);
}
// ------------------------------------------------------------------------------------------------
template <>
bool EpsilonCompare<aiQuatKey>(const aiQuatKey& n, const aiQuatKey& s, float epsilon) {
return
EpsilonCompare(n.mValue.x,s.mValue.x,epsilon) &&
EpsilonCompare(n.mValue.y,s.mValue.y,epsilon) &&
EpsilonCompare(n.mValue.z,s.mValue.z,epsilon) &&
EpsilonCompare(n.mValue.w,s.mValue.w,epsilon);
}
// ------------------------------------------------------------------------------------------------
template <typename T>
inline bool AllIdentical(T* in, unsigned int num, float epsilon)
{
if (num <= 1)return true;
for (unsigned int i = 0; i < num-1;++i)
{
if (in[i] != in[i+1])return false;
if (num <= 1) {
return true;
}
if (epsilon > 0.f) {
for (unsigned int i = 0; i < num-1;++i) {
if (!EpsilonCompare(in[i],in[i+1],epsilon)) {
return false;
}
}
}
else {
for (unsigned int i = 0; i < num-1;++i) {
if (in[i] != in[i+1]) {
return false;
}
}
}
return true;
}
@ -228,8 +275,9 @@ inline bool AllIdentical(T* in, unsigned int num)
void FindInvalidDataProcess::ProcessAnimation (aiAnimation* anim)
{
// Process all animation channels
for (unsigned int a = 0; a < anim->mNumChannels;++a)
for (unsigned int a = 0; a < anim->mNumChannels;++a) {
ProcessAnimationChannel( anim->mChannels[a]);
}
}
// ------------------------------------------------------------------------------------------------
@ -243,7 +291,7 @@ void FindInvalidDataProcess::ProcessAnimationChannel (aiNodeAnim* anim)
// Check whether all values in a tracks are identical - in this case
// we can remove al keys except one.
// POSITIONS
if (anim->mNumPositionKeys > 1 && AllIdentical(anim->mPositionKeys,anim->mNumPositionKeys))
if (anim->mNumPositionKeys > 1 && AllIdentical(anim->mPositionKeys,anim->mNumPositionKeys,configEpsilon))
{
aiVectorKey v = anim->mPositionKeys[0];
@ -255,7 +303,7 @@ void FindInvalidDataProcess::ProcessAnimationChannel (aiNodeAnim* anim)
}
// ROTATIONS
if (anim->mNumRotationKeys > 1 && AllIdentical(anim->mRotationKeys,anim->mNumRotationKeys))
if (anim->mNumRotationKeys > 1 && AllIdentical(anim->mRotationKeys,anim->mNumRotationKeys,configEpsilon))
{
aiQuatKey v = anim->mRotationKeys[0];
@ -267,7 +315,7 @@ void FindInvalidDataProcess::ProcessAnimationChannel (aiNodeAnim* anim)
}
// SCALINGS
if (anim->mNumScalingKeys > 1 && AllIdentical(anim->mScalingKeys,anim->mNumScalingKeys))
if (anim->mNumScalingKeys > 1 && AllIdentical(anim->mScalingKeys,anim->mNumScalingKeys,configEpsilon))
{
aiVectorKey v = anim->mScalingKeys[0];
@ -290,29 +338,26 @@ int FindInvalidDataProcess::ProcessMesh (aiMesh* pMesh)
// Ignore elements that are not referenced by vertices.
// (they are, for example, caused by the FindDegenerates step)
for (unsigned int m = 0; m < pMesh->mNumFaces;++m)
{
for (unsigned int m = 0; m < pMesh->mNumFaces;++m) {
const aiFace& f = pMesh->mFaces[m];
for (unsigned int i = 0; i < f.mNumIndices;++i)
for (unsigned int i = 0; i < f.mNumIndices;++i) {
dirtyMask[f.mIndices[i]] = false;
}
}
// Process vertex positions
if(pMesh->mVertices && ProcessArray(pMesh->mVertices,pMesh->mNumVertices,"positions",dirtyMask))
{
if(pMesh->mVertices && ProcessArray(pMesh->mVertices,pMesh->mNumVertices,"positions",dirtyMask)) {
DefaultLogger::get()->error("Deleting mesh: Unable to continue without vertex positions");
return 2;
}
// process texture coordinates
for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS;++i)
{
if (!pMesh->mTextureCoords[i])break;
if (ProcessArray(pMesh->mTextureCoords[i],pMesh->mNumVertices,"uvcoords",dirtyMask))
{
for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS && pMesh->mTextureCoords[i];++i) {
if (ProcessArray(pMesh->mTextureCoords[i],pMesh->mNumVertices,"uvcoords",dirtyMask)) {
// delete all subsequent texture coordinate sets.
for (unsigned int a = i+1; a < AI_MAX_NUMBER_OF_TEXTURECOORDS;++a)
{
for (unsigned int a = i+1; a < AI_MAX_NUMBER_OF_TEXTURECOORDS;++a) {
delete[] pMesh->mTextureCoords[a]; pMesh->mTextureCoords[a] = NULL;
}
ret = true;
@ -323,8 +368,8 @@ int FindInvalidDataProcess::ProcessMesh (aiMesh* pMesh)
// they are invalid or not.
// Normals and tangents are undefined for point and line faces.
if (pMesh->mNormals || pMesh->mTangents)
{
if (pMesh->mNormals || pMesh->mTangents) {
if (aiPrimitiveType_POINT & pMesh->mPrimitiveTypes ||
aiPrimitiveType_LINE & pMesh->mPrimitiveTypes)
{
@ -336,11 +381,12 @@ int FindInvalidDataProcess::ProcessMesh (aiMesh* pMesh)
{
const aiFace& f = pMesh->mFaces[m];
if (f.mNumIndices < 3)
{
if (f.mNumIndices < 3) {
dirtyMask[f.mIndices[0]] = true;
if (f.mNumIndices == 2)
if (f.mNumIndices == 2) {
dirtyMask[f.mIndices[1]] = true;
}
}
}
}
@ -355,17 +401,13 @@ int FindInvalidDataProcess::ProcessMesh (aiMesh* pMesh)
ret = true;
// Process mesh tangents
if (pMesh->mTangents && ProcessArray(pMesh->mTangents,pMesh->mNumVertices,
"tangents",dirtyMask))
{
if (pMesh->mTangents && ProcessArray(pMesh->mTangents,pMesh->mNumVertices,"tangents",dirtyMask)) {
delete[] pMesh->mBitangents; pMesh->mBitangents = NULL;
ret = true;
}
// Process mesh bitangents
if (pMesh->mBitangents && ProcessArray(pMesh->mBitangents,pMesh->mNumVertices,
"bitangents",dirtyMask))
{
if (pMesh->mBitangents && ProcessArray(pMesh->mBitangents,pMesh->mNumVertices,"bitangents",dirtyMask)) {
delete[] pMesh->mTangents; pMesh->mTangents = NULL;
ret = true;
}

View File

@ -48,18 +48,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
struct aiMesh;
class FindInvalidDataProcessTest;
namespace Assimp
{
namespace Assimp {
// ---------------------------------------------------------------------------
/** The FindInvalidData postprocessing step. It searches the mesh data
* for parts that are obviously invalid and removes them.
*
* Originally this was a workaround for some models written by Blender
* which have zero normal vectors.
*/
class ASSIMP_API FindInvalidDataProcess : public BaseProcess
* which have zero normal vectors. */
class ASSIMP_API FindInvalidDataProcess
: public BaseProcess
{
friend class Importer;
friend class ::FindInvalidDataProcessTest;
@ -75,13 +73,15 @@ protected:
public:
// -------------------------------------------------------------------
/**
*/
//
bool IsActive( unsigned int pFlags) const;
// -------------------------------------------------------------------
/**
*/
// Setup import settings
void SetupProperties(const Importer* pImp);
// -------------------------------------------------------------------
// Run the step
void Execute( aiScene* pScene);
protected:
@ -89,22 +89,21 @@ protected:
// -------------------------------------------------------------------
/** Executes the postprocessing step on the given mesh
* @param pMesh The mesh to process.
* @return 0 - nothing, 1 - removed sth, 2 - please delete me
*/
* @return 0 - nothing, 1 - removed sth, 2 - please delete me */
int ProcessMesh( aiMesh* pMesh);
// -------------------------------------------------------------------
/** Executes the postprocessing step on the given animation
* @param anim The animation to process.
*/
* @param anim The animation to process. */
void ProcessAnimation (aiAnimation* anim);
// -------------------------------------------------------------------
/** Executes the postprocessing step on the given anim channel
* @param anim The animation channel to process.
*/
* @param anim The animation channel to process.*/
void ProcessAnimationChannel (aiNodeAnim* anim);
private:
float configEpsilon;
};
} // end of namespace Assimp

View File

@ -6,8 +6,8 @@
#include <sstream>
using namespace std;
#include "boost/format.hpp"
#include "boost/foreach.hpp"
//#include "boost/format.hpp"
//#include "boost/foreach.hpp"
using namespace boost;
#include "OgreImporter.h"
@ -340,7 +340,9 @@ void OgreImporter::ReadSubMesh(SubMesh &theSubMesh, XmlReader *Reader)
aiMaterial* OgreImporter::LoadMaterial(std::string MaterialName)
{
MaterialHelper *NewMaterial=new MaterialHelper();
NewMaterial->AddProperty(&aiString(MaterialName.c_str()), AI_MATKEY_NAME);
aiString ts(MaterialName.c_str());
NewMaterial->AddProperty(&ts, AI_MATKEY_NAME);
/*For bettetr understanding of the material parser, here is a material example file:
material Sarg
@ -449,7 +451,8 @@ aiMaterial* OgreImporter::LoadMaterial(std::string MaterialName)
if(Line=="texture")
{
ss >> Line;
NewMaterial->AddProperty(&aiString(Line.c_str()), AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0));
aiString ts(Line.c_str());
NewMaterial->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0));
}
}//end of texture unit
}
@ -478,12 +481,14 @@ aiMaterial* OgreImporter::LoadMaterial(std::string MaterialName)
if(Line=="$colormap")
{
ss >> Line;
NewMaterial->AddProperty(&aiString(Line.c_str()), AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0));
aiString ts(Line.c_str());
NewMaterial->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0));
}
if(Line=="$normalmap")
{
ss >> Line;
NewMaterial->AddProperty(&aiString(Line.c_str()), AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0));
aiString ts(Line.c_str());
NewMaterial->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0));
}
}
}//end of material
@ -569,7 +574,7 @@ void OgreImporter::LoadSkeleton(std::string FileName)
XmlRead(SkeletonFile);
}
//The bones in the file a not neccesarly ordered by there id's so we do it now:
sort(Bones.begin(), Bones.end());
std::sort(Bones.begin(), Bones.end());
//now the id of each bone should be equal to its position in the vector:
//so we do a simple check:
{
@ -779,10 +784,12 @@ aiNode* CreateAiNodeFromBone(int BoneId, std::vector<Bone> Bones, aiNode* Parent
//----Create the node for this bone and set its values-----
aiNode* NewNode=new aiNode(Bones[BoneId].Name);
NewNode->mParent=ParentNode;
aiMatrix4x4 t0,t1;
//create a matrix from the transformation values of the ogre bone
NewNode->mTransformation=aiMatrix4x4::Translation(Bones[BoneId].Position, aiMatrix4x4())
NewNode->mTransformation=aiMatrix4x4::Translation(Bones[BoneId].Position, t0)
*
aiMatrix4x4::Rotation(Bones[BoneId].RotationAngle, Bones[BoneId].RotationAxis, aiMatrix4x4())
aiMatrix4x4::Rotation(Bones[BoneId].RotationAngle, Bones[BoneId].RotationAxis, t1)
;
//__________________________________________________________

View File

@ -146,10 +146,10 @@ struct Bone
///ctor
Bone(): Id(-1), ParentId(-1), RotationAngle(0.0f) {}
///this operator is needed to sort the bones after Id's
bool operator<(const Bone& rval)
bool operator<(const Bone& rval) const
{return Id<rval.Id; }
///this operator is needed to find a bone by its name in a vector<Bone>
bool operator==(const std::string& rval)
bool operator==(const std::string& rval) const
{return Name==rval; }
};

View File

@ -197,7 +197,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* This is used by the "SplitLargeMeshes" PostProcess-Step to determine
* whether a mesh must be split or not.
* @note The default value is AI_SLM_DEFAULT_MAX_VERTICES
* Property type: integer.
* Property type: integer.
*/
#define AI_CONFIG_PP_SLM_VERTEX_LIMIT \
"PP_SLM_VERTEX_LIMIT"
@ -212,8 +212,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This is used by the #aiProcess_LimitBoneWeights PostProcess-Step.
* @note The default value is AI_LBW_MAX_WEIGHTS
* Property type: integer.
*/
* Property type: integer.*/
#define AI_CONFIG_PP_LBW_MAX_WEIGHTS \
"PP_LBW_MAX_WEIGHTS"
@ -243,71 +242,59 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// ---------------------------------------------------------------------------
/** @brief Enumerates components of the aiScene and aiMesh data structures
* that can be excluded from the import by using the RemoveComponent step.
* that can be excluded from the import using the #aiPrpcess_RemoveComponent step.
*
* See the documentation to #aiProcess_RemoveComponent for more details.
*/
enum aiComponent
{
/** Normal vectors
*/
/** Normal vectors */
aiComponent_NORMALS = 0x2u,
/** Tangents and bitangents go always together ...
*/
/** Tangents and bitangents go always together ... */
aiComponent_TANGENTS_AND_BITANGENTS = 0x4u,
/** ALL color sets
* Use aiComponent_COLORn(N) to specify the N'th set
*/
* Use aiComponent_COLORn(N) to specify the N'th set */
aiComponent_COLORS = 0x8,
/** ALL texture UV sets
* aiComponent_TEXCOORDn(N) to specify the N'th set
*/
* aiComponent_TEXCOORDn(N) to specify the N'th set */
aiComponent_TEXCOORDS = 0x10,
/** Removes all bone weights from all meshes.
* The scenegraph nodes corresponding to the bones are NOT removed.
* use the #aiProcess_OptimizeGraph step to do this
*/
* use the #aiProcess_OptimizeGraph step to do this */
aiComponent_BONEWEIGHTS = 0x20,
/** Removes all node animations (aiScene::mAnimations).
* The corresponding scenegraph nodes are NOT removed.
* use the #aiProcess_OptimizeGraph step to do this
*/
* use the #aiProcess_OptimizeGraph step to do this */
aiComponent_ANIMATIONS = 0x40,
/** Removes all embedded textures (aiScene::mTextures)
*/
/** Removes all embedded textures (aiScene::mTextures) */
aiComponent_TEXTURES = 0x80,
/** Removes all light sources (aiScene::mLights).
* The corresponding scenegraph nodes are NOT removed.
* use the #aiProcess_OptimizeGraph step to do this
*/
* use the #aiProcess_OptimizeGraph step to do this */
aiComponent_LIGHTS = 0x100,
/** Removes all light sources (aiScene::mCameras).
* The corresponding scenegraph nodes are NOT removed.
* use the #aiProcess_OptimizeGraph step to do this
*/
* use the #aiProcess_OptimizeGraph step to do this */
aiComponent_CAMERAS = 0x200,
/** Removes all meshes (aiScene::mMeshes).
*/
/** Removes all meshes (aiScene::mMeshes). */
aiComponent_MESHES = 0x400,
/** Removes all materials. One default material will
* be generated, so aiScene::mNumMaterials will be 1.
*/
* be generated, so aiScene::mNumMaterials will be 1. */
aiComponent_MATERIALS = 0x800,
/** This value is not used. It is just there to force the
* compiler to map this enum to a 32 Bit integer.
*/
* compiler to map this enum to a 32 Bit integer. */
_aiComponent_Force32Bit = 0x9fffffff
};
@ -344,6 +331,18 @@ enum aiComponent
#define AI_CONFIG_PP_SBP_REMOVE \
"PP_SBP_REMOVE"
// ---------------------------------------------------------------------------
/** @brief Input parameter to the #aiProcess_FindInvalidData step:
* Specifies the floating-point accuracy for animation values. The step
* checks for animation tracks where all frame values are absolutely equal
* and removes them. This tweakable controls the epsilon for floating-point
* comparisons - two keys are considered equal if the invariant
* abs(n0-n1)>epsilon holds true for all vector respectively quaternion
* components. The default value is 0.f - comparisons are exact then.
*/
#define AI_CONFIG_PP_FID_ANIM_ACCURACY \
"PP_FID_ANIM_ACCURACY"
// TransformUVCoords evaluates UV scalings
#define AI_UVTRAFO_SCALING 0x1
@ -552,11 +551,13 @@ enum aiComponent
#define AI_CONFIG_IMPORT_IRR_ANIM_FPS \
"IMPORT_IRR_ANIM_FPS"
/// Ogre Importer will try to load this Materialfile
/**
Ogre Mehs contain only the MaterialName, not the MaterialFile. If there is no material file
with the same name as the material, Ogre Importer will try to load this file and search the material in it.
*/
// ---------------------------------------------------------------------------
/** Ogre Importer will try to load this Materialfile
* Ogre Mehs contain only the MaterialName, not the MaterialFile. If there
* is no material file with the same name as the material, Ogre Importer will
* try to load this file and search the material in it.
*/
#define AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE "IMPORT_OGRE_MATERIAL_FILE"

View File

@ -358,12 +358,16 @@ enum aiPostProcessSteps
// -------------------------------------------------------------------------
/** <hr>This step searches all meshes for invalid data, such as zeroed
* normal vectors or invalid UV coords and removes them.
* normal vectors or invalid UV coords and removes/fixes them. This is
* intended to get rid of some common exporter errors.
*
* This is especially useful for normals. If they are invalid, and
* the step recognizes this, they will be removed and can later
* be computed by one of the other steps.<br>
* The step will also remove meshes that are infinitely small.
* be recomputed, i.e. by the #aiProcess_GenSmoothNormals flag.<br>
* The step will also remove meshes that are infinitely small and reduce
* animation tracks consisting of hundreds if redundant keys to a single
* key. The <tt>AI_CONFIG_PP_FID_ANIM_ACCURACY</tt> config property decides
* the accuracy of the check for duplicate animation tracks.
*/
aiProcess_FindInvalidData = 0x20000,