From 35a044bda37b2bad50a41746622c746acd139631 Mon Sep 17 00:00:00 2001 From: rickomax Date: Sun, 6 Jan 2019 16:37:30 -0200 Subject: [PATCH 1/2] Various additions/fixes (FBX blend-shapes support added) Added animMesh name assignment at ColladaLoader Fixed animMesh post-processing on ConvertToLhProcess (blend-shapes weren't being affected by post-processing) Added WindowsStore define. This is used to change some incompatible WinRT methods Added FBX blend-shapes and blend-shapes animations support Added Maya FBX specific texture slots parsing Added extra FBX metadata parsing Added GLTF2 vertex color parsing Fixed IFC-Loader zip-buffer reading rountine Fixed OBJ file parsing line-breaker bug Fixed IOStreamBuffer cache over-read bug Added mName field to aiAnimMesh Reverted EmissiveFactor, TransparencyFactor and SpecularFactor assignment on FBXConverter. Really, the commit #817 breaks a lot of old code. --- code/CMakeLists.txt | 3 + code/ColladaLoader.cpp | 7 +- code/ConvertToLHProcess.cpp | 66 +- code/DefaultIOSystem.cpp | 12 + code/FBXAnimation.cpp | 4 +- code/FBXConverter.cpp | 5952 ++++++++++++++++--------------- code/FBXConverter.h | 10 + code/FBXDeformer.cpp | 47 + code/FBXDocument.cpp | 9 + code/FBXDocument.h | 43 + code/FBXMeshGeometry.cpp | 47 +- code/FBXMeshGeometry.h | 34 +- code/Importer/IFC/IFCLoader.cpp | 17 +- code/ObjFileParser.cpp | 2 +- code/glTF2Importer.cpp | 13 +- include/assimp/IOStreamBuffer.h | 5 +- include/assimp/mesh.h | 5 +- 17 files changed, 3372 insertions(+), 2904 deletions(-) diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index e29467164..043599aca 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -966,6 +966,9 @@ if( MSVC ) set(LIBRARY_SUFFIX "${ASSIMP_LIBRARY_SUFFIX}-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library") endif() +if (${CMAKE_SYSTEM_NAME} MATCHES "WindowsStore") + set(WindowsStore TRUE) +endif() SET_TARGET_PROPERTIES( assimp PROPERTIES VERSION ${ASSIMP_VERSION} SOVERSION ${ASSIMP_SOVERSION} # use full version diff --git a/code/ColladaLoader.cpp b/code/ColladaLoader.cpp index ab896c000..af04e9a16 100644 --- a/code/ColladaLoader.cpp +++ b/code/ColladaLoader.cpp @@ -728,8 +728,11 @@ aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada:: std::vector animMeshes; for (unsigned int i = 0; i < targetMeshes.size(); i++) { - aiAnimMesh *animMesh = aiCreateAnimMesh(targetMeshes.at(i)); - animMesh->mWeight = targetWeights[i]; + aiMesh* targetMesh = targetMeshes.at(i); + aiAnimMesh *animMesh = aiCreateAnimMesh(targetMesh); + float weight = targetWeights[i]; + animMesh->mWeight = weight == 0 ? 1.0f : weight; + animMesh->mName = targetMesh->mName; animMeshes.push_back(animMesh); } dstMesh->mMethod = (method == Collada::Relative) diff --git a/code/ConvertToLHProcess.cpp b/code/ConvertToLHProcess.cpp index 9cb45cc69..1831739ac 100644 --- a/code/ConvertToLHProcess.cpp +++ b/code/ConvertToLHProcess.cpp @@ -166,8 +166,9 @@ void MakeLeftHandedProcess::ProcessMesh( aiMesh* pMesh) { for( size_t a = 0; a < pMesh->mNumVertices; ++a) { pMesh->mVertices[a].z *= -1.0f; - if( pMesh->HasNormals()) + if (pMesh->HasNormals()) { pMesh->mNormals[a].z *= -1.0f; + } if( pMesh->HasTangentsAndBitangents()) { pMesh->mTangents[a].z *= -1.0f; @@ -175,6 +176,23 @@ void MakeLeftHandedProcess::ProcessMesh( aiMesh* pMesh) { } } + // mirror anim meshes positions, normals and stuff along the Z axis + for (size_t m = 0; m < pMesh->mNumAnimMeshes; ++m) + { + for (size_t a = 0; a < pMesh->mAnimMeshes[m]->mNumVertices; ++a) + { + pMesh->mAnimMeshes[m]->mVertices[a].z *= -1.0f; + if (pMesh->mAnimMeshes[m]->HasNormals()) { + pMesh->mAnimMeshes[m]->mNormals[a].z *= -1.0f; + } + if (pMesh->mAnimMeshes[m]->HasTangentsAndBitangents()) + { + pMesh->mAnimMeshes[m]->mTangents[a].z *= -1.0f; + pMesh->mAnimMeshes[m]->mBitangents[a].z *= -1.0f; + } + } + } + // mirror offset matrices of all bones for( size_t a = 0; a < pMesh->mNumBones; ++a) { @@ -346,8 +364,50 @@ void FlipWindingOrderProcess::ProcessMesh( aiMesh* pMesh) for( unsigned int a = 0; a < pMesh->mNumFaces; a++) { aiFace& face = pMesh->mFaces[a]; - for( unsigned int b = 0; b < face.mNumIndices / 2; b++) - std::swap( face.mIndices[b], face.mIndices[ face.mNumIndices - 1 - b]); + for (unsigned int b = 0; b < face.mNumIndices / 2; b++) { + std::swap(face.mIndices[b], face.mIndices[face.mNumIndices - 1 - b]); + } + } + + // invert the order of all components in this mesh anim meshes + for (unsigned int m = 0; m < pMesh->mNumAnimMeshes; m++) { + aiAnimMesh* animMesh = pMesh->mAnimMeshes[m]; + unsigned int numVertices = animMesh->mNumVertices; + if (animMesh->HasPositions()) { + for (unsigned int a = 0; a < numVertices; a++) + { + std::swap(animMesh->mVertices[a], animMesh->mVertices[numVertices - 1 - a]); + } + } + if (animMesh->HasNormals()) { + for (unsigned int a = 0; a < numVertices; a++) + { + std::swap(animMesh->mNormals[a], animMesh->mNormals[numVertices - 1 - a]); + } + } + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; i++) { + if (animMesh->HasTextureCoords(i)) { + for (unsigned int a = 0; a < numVertices; a++) + { + std::swap(animMesh->mTextureCoords[i][a], animMesh->mTextureCoords[i][numVertices - 1 - a]); + } + } + } + if (animMesh->HasTangentsAndBitangents()) { + for (unsigned int a = 0; a < numVertices; a++) + { + std::swap(animMesh->mTangents[a], animMesh->mTangents[numVertices - 1 - a]); + std::swap(animMesh->mBitangents[a], animMesh->mBitangents[numVertices - 1 - a]); + } + } + for (unsigned int v = 0; v < AI_MAX_NUMBER_OF_COLOR_SETS; v++) { + if (animMesh->HasVertexColors(v)) { + for (unsigned int a = 0; a < numVertices; a++) + { + std::swap(animMesh->mColors[v][a], animMesh->mColors[v][numVertices - 1 - a]); + } + } + } } } diff --git a/code/DefaultIOSystem.cpp b/code/DefaultIOSystem.cpp index 58afe475c..67fd82f17 100644 --- a/code/DefaultIOSystem.cpp +++ b/code/DefaultIOSystem.cpp @@ -76,6 +76,7 @@ bool DefaultIOSystem::Exists( const char* pFile) const #ifdef _WIN32 wchar_t fileName16[PATHLIMIT]; +#ifndef WindowsStore bool isUnicode = IsTextUnicode(pFile, static_cast(strlen(pFile)), NULL) != 0; if (isUnicode) { @@ -85,12 +86,15 @@ bool DefaultIOSystem::Exists( const char* pFile) const return false; } } else { +#endif FILE* file = ::fopen(pFile, "rb"); if (!file) return false; ::fclose(file); +#ifndef WindowsStore } +#endif #else FILE* file = ::fopen( pFile, "rb"); if( !file) @@ -110,14 +114,18 @@ IOStream* DefaultIOSystem::Open( const char* strFile, const char* strMode) FILE* file; #ifdef _WIN32 wchar_t fileName16[PATHLIMIT]; +#ifndef WindowsStore bool isUnicode = IsTextUnicode(strFile, static_cast(strlen(strFile)), NULL) != 0; if (isUnicode) { MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, strFile, -1, fileName16, PATHLIMIT); std::string mode8(strMode); file = ::_wfopen(fileName16, std::wstring(mode8.begin(), mode8.end()).c_str()); } else { +#endif file = ::fopen(strFile, strMode); +#ifndef WindowsStore } +#endif #else file = ::fopen(strFile, strMode); #endif @@ -158,6 +166,7 @@ inline static void MakeAbsolutePath (const char* in, char* _out) { ai_assert(in && _out); #if defined( _MSC_VER ) || defined( __MINGW32__ ) +#ifndef WindowsStore bool isUnicode = IsTextUnicode(in, static_cast(strlen(in)), NULL) != 0; if (isUnicode) { wchar_t out16[PATHLIMIT]; @@ -175,6 +184,7 @@ inline static void MakeAbsolutePath (const char* in, char* _out) } } else { +#endif char* ret = :: _fullpath(_out, in, PATHLIMIT); if (!ret) { // preserve the input path, maybe someone else is able to fix @@ -182,7 +192,9 @@ inline static void MakeAbsolutePath (const char* in, char* _out) ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in)); strcpy(_out, in); } +#ifndef WindowsStore } +#endif #else // use realpath char* ret = realpath(in, _out); diff --git a/code/FBXAnimation.cpp b/code/FBXAnimation.cpp index 24ab9b14b..871d62578 100644 --- a/code/FBXAnimation.cpp +++ b/code/FBXAnimation.cpp @@ -105,8 +105,8 @@ AnimationCurveNode::AnimationCurveNode(uint64_t id, const Element& element, cons const Scope& sc = GetRequiredScope(element); // find target node - const char* whitelist[] = {"Model","NodeAttribute"}; - const std::vector& conns = doc.GetConnectionsBySourceSequenced(ID(),whitelist,2); + const char* whitelist[] = {"Model","NodeAttribute","Deformer"}; + const std::vector& conns = doc.GetConnectionsBySourceSequenced(ID(),whitelist,3); for(const Connection* con : conns) { diff --git a/code/FBXConverter.cpp b/code/FBXConverter.cpp index 7dd9f94b4..cd9ad0966 100644 --- a/code/FBXConverter.cpp +++ b/code/FBXConverter.cpp @@ -53,10 +53,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "FBXUtil.h" #include "FBXProperties.h" #include "FBXImporter.h" + #include #include +#include + #include #include #include @@ -65,3025 +68,3214 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include namespace Assimp { -namespace FBX { + namespace FBX { -using namespace Util; + using namespace Util; #define MAGIC_NODE_TAG "_$AssimpFbx$" #define CONVERT_FBX_TIME(time) static_cast(time) / 46186158000L -FBXConverter::FBXConverter( aiScene* out, const Document& doc ) -: defaultMaterialIndex() -, out( out ) -, doc( doc ) { - // animations need to be converted first since this will - // populate the node_anim_chain_bits map, which is needed - // to determine which nodes need to be generated. - ConvertAnimations(); - ConvertRootNode(); + FBXConverter::FBXConverter(aiScene* out, const Document& doc) + : defaultMaterialIndex() + , out(out) + , doc(doc) { + // animations need to be converted first since this will + // populate the node_anim_chain_bits map, which is needed + // to determine which nodes need to be generated. + ConvertAnimations(); + ConvertRootNode(); - if ( doc.Settings().readAllMaterials ) { - // unfortunately this means we have to evaluate all objects - for( const ObjectMap::value_type& v : doc.Objects() ) { + if (doc.Settings().readAllMaterials) { + // unfortunately this means we have to evaluate all objects + for (const ObjectMap::value_type& v : doc.Objects()) { - const Object* ob = v.second->Get(); - if ( !ob ) { - continue; + const Object* ob = v.second->Get(); + if (!ob) { + continue; + } + + const Material* mat = dynamic_cast(ob); + if (mat) { + + if (materials_converted.find(mat) == materials_converted.end()) { + ConvertMaterial(*mat, 0); + } + } + } } - const Material* mat = dynamic_cast( ob ); - if ( mat ) { + ConvertGlobalSettings(); + TransferDataToScene(); - if ( materials_converted.find( mat ) == materials_converted.end() ) { - ConvertMaterial( *mat, 0 ); - } + // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE + // to make sure the scene passes assimp's validation. FBX files + // need not contain geometry (i.e. camera animations, raw armatures). + if (out->mNumMeshes == 0) { + out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; } } - } - - ConvertGlobalSettings(); - TransferDataToScene(); - - // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE - // to make sure the scene passes assimp's validation. FBX files - // need not contain geometry (i.e. camera animations, raw armatures). - if ( out->mNumMeshes == 0 ) { - out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; - } -} -FBXConverter::~FBXConverter() { - std::for_each( meshes.begin(), meshes.end(), Util::delete_fun() ); - std::for_each( materials.begin(), materials.end(), Util::delete_fun() ); - std::for_each( animations.begin(), animations.end(), Util::delete_fun() ); - std::for_each( lights.begin(), lights.end(), Util::delete_fun() ); - std::for_each( cameras.begin(), cameras.end(), Util::delete_fun() ); - std::for_each( textures.begin(), textures.end(), Util::delete_fun() ); -} + FBXConverter::~FBXConverter() { + std::for_each(meshes.begin(), meshes.end(), Util::delete_fun()); + std::for_each(materials.begin(), materials.end(), Util::delete_fun()); + std::for_each(animations.begin(), animations.end(), Util::delete_fun()); + std::for_each(lights.begin(), lights.end(), Util::delete_fun()); + std::for_each(cameras.begin(), cameras.end(), Util::delete_fun()); + std::for_each(textures.begin(), textures.end(), Util::delete_fun()); + } -void FBXConverter::ConvertRootNode() { - out->mRootNode = new aiNode(); - out->mRootNode->mName.Set( "RootNode" ); + void FBXConverter::ConvertRootNode() { + out->mRootNode = new aiNode(); + out->mRootNode->mName.Set("RootNode"); - // root has ID 0 - ConvertNodes( 0L, *out->mRootNode ); -} + // root has ID 0 + ConvertNodes(0L, *out->mRootNode); + } -void FBXConverter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform ) { - const std::vector& conns = doc.GetConnectionsByDestinationSequenced( id, "Model" ); + void FBXConverter::ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform) { + const std::vector& conns = doc.GetConnectionsByDestinationSequenced(id, "Model"); - std::vector nodes; - nodes.reserve( conns.size() ); + std::vector nodes; + nodes.reserve(conns.size()); - std::vector nodes_chain; - std::vector post_nodes_chain; + std::vector nodes_chain; + std::vector post_nodes_chain; - try { - for( const Connection* con : conns ) { + try { + for (const Connection* con : conns) { - // ignore object-property links - if ( con->PropertyName().length() ) { - continue; - } - - const Object* const object = con->SourceObject(); - if ( nullptr == object ) { - FBXImporter::LogWarn( "failed to convert source object for Model link" ); - continue; - } - - const Model* const model = dynamic_cast( object ); - - if ( nullptr != model ) { - nodes_chain.clear(); - post_nodes_chain.clear(); - - aiMatrix4x4 new_abs_transform = parent_transform; - - // even though there is only a single input node, the design of - // assimp (or rather: the complicated transformation chain that - // is employed by fbx) means that we may need multiple aiNode's - // to represent a fbx node's transformation. - GenerateTransformationNodeChain( *model, nodes_chain, post_nodes_chain ); - - ai_assert( nodes_chain.size() ); - - std::string original_name = FixNodeName( model->Name() ); - - // check if any of the nodes in the chain has the name the fbx node - // is supposed to have. If there is none, add another node to - // preserve the name - people might have scripts etc. that rely - // on specific node names. - aiNode* name_carrier = NULL; - for( aiNode* prenode : nodes_chain ) { - if ( !strcmp( prenode->mName.C_Str(), original_name.c_str() ) ) { - name_carrier = prenode; - break; - } - } - - if ( !name_carrier ) { - std::string old_original_name = original_name; - GetUniqueName(old_original_name, original_name); - nodes_chain.push_back( new aiNode( original_name ) ); - } else { - original_name = nodes_chain.back()->mName.C_Str(); - } - - //setup metadata on newest node - SetupNodeMetadata( *model, *nodes_chain.back() ); - - // link all nodes in a row - aiNode* last_parent = &parent; - for( aiNode* prenode : nodes_chain ) { - ai_assert( prenode ); - - if ( last_parent != &parent ) { - last_parent->mNumChildren = 1; - last_parent->mChildren = new aiNode*[ 1 ]; - last_parent->mChildren[ 0 ] = prenode; + // ignore object-property links + if (con->PropertyName().length()) { + continue; } - prenode->mParent = last_parent; - last_parent = prenode; + const Object* const object = con->SourceObject(); + if (nullptr == object) { + FBXImporter::LogWarn("failed to convert source object for Model link"); + continue; + } - new_abs_transform *= prenode->mTransformation; - } + const Model* const model = dynamic_cast(object); - // attach geometry - ConvertModel( *model, *nodes_chain.back(), new_abs_transform ); + if (nullptr != model) { + nodes_chain.clear(); + post_nodes_chain.clear(); - // check if there will be any child nodes - const std::vector& child_conns - = doc.GetConnectionsByDestinationSequenced( model->ID(), "Model" ); + aiMatrix4x4 new_abs_transform = parent_transform; - // if so, link the geometric transform inverse nodes - // before we attach any child nodes - if (child_conns.size()) { - for( aiNode* postnode : post_nodes_chain ) { - ai_assert( postnode ); + // even though there is only a single input node, the design of + // assimp (or rather: the complicated transformation chain that + // is employed by fbx) means that we may need multiple aiNode's + // to represent a fbx node's transformation. + GenerateTransformationNodeChain(*model, nodes_chain, post_nodes_chain); - if ( last_parent != &parent ) { - last_parent->mNumChildren = 1; - last_parent->mChildren = new aiNode*[ 1 ]; - last_parent->mChildren[ 0 ] = postnode; + ai_assert(nodes_chain.size()); + + std::string original_name = FixNodeName(model->Name()); + + // check if any of the nodes in the chain has the name the fbx node + // is supposed to have. If there is none, add another node to + // preserve the name - people might have scripts etc. that rely + // on specific node names. + aiNode* name_carrier = NULL; + for (aiNode* prenode : nodes_chain) { + if (!strcmp(prenode->mName.C_Str(), original_name.c_str())) { + name_carrier = prenode; + break; + } } - postnode->mParent = last_parent; - last_parent = postnode; - - new_abs_transform *= postnode->mTransformation; - } - } else { - // free the nodes we allocated as we don't need them - Util::delete_fun deleter; - std::for_each( - post_nodes_chain.begin(), - post_nodes_chain.end(), - deleter - ); - } - - // attach sub-nodes (if any) - ConvertNodes( model->ID(), *last_parent, new_abs_transform ); - - if ( doc.Settings().readLights ) { - ConvertLights( *model, original_name ); - } - - if ( doc.Settings().readCameras ) { - ConvertCameras( *model, original_name ); - } - - nodes.push_back( nodes_chain.front() ); - nodes_chain.clear(); - } - } - - if ( nodes.size() ) { - parent.mChildren = new aiNode*[ nodes.size() ](); - parent.mNumChildren = static_cast( nodes.size() ); - - std::swap_ranges( nodes.begin(), nodes.end(), parent.mChildren ); - } - } - catch ( std::exception& ) { - Util::delete_fun deleter; - std::for_each( nodes.begin(), nodes.end(), deleter ); - std::for_each( nodes_chain.begin(), nodes_chain.end(), deleter ); - std::for_each( post_nodes_chain.begin(), post_nodes_chain.end(), deleter ); - } -} - - -void FBXConverter::ConvertLights( const Model& model, const std::string &orig_name ) { - const std::vector& node_attrs = model.GetAttributes(); - for( const NodeAttribute* attr : node_attrs ) { - const Light* const light = dynamic_cast( attr ); - if ( light ) { - ConvertLight( *light, orig_name ); - } - } -} - -void FBXConverter::ConvertCameras( const Model& model, const std::string &orig_name ) { - const std::vector& node_attrs = model.GetAttributes(); - for( const NodeAttribute* attr : node_attrs ) { - const Camera* const cam = dynamic_cast( attr ); - if ( cam ) { - ConvertCamera( *cam, orig_name ); - } - } -} - -void FBXConverter::ConvertLight( const Light& light, const std::string &orig_name ) { - lights.push_back( new aiLight() ); - aiLight* const out_light = lights.back(); - - out_light->mName.Set( orig_name ); - - const float intensity = light.Intensity() / 100.0f; - const aiVector3D& col = light.Color(); - - out_light->mColorDiffuse = aiColor3D( col.x, col.y, col.z ); - out_light->mColorDiffuse.r *= intensity; - out_light->mColorDiffuse.g *= intensity; - out_light->mColorDiffuse.b *= intensity; - - out_light->mColorSpecular = out_light->mColorDiffuse; - - //lights are defined along negative y direction - out_light->mPosition = aiVector3D(0.0f); - out_light->mDirection = aiVector3D(0.0f, -1.0f, 0.0f); - out_light->mUp = aiVector3D(0.0f, 0.0f, -1.0f); - - switch ( light.LightType() ) - { - case Light::Type_Point: - out_light->mType = aiLightSource_POINT; - break; - - case Light::Type_Directional: - out_light->mType = aiLightSource_DIRECTIONAL; - break; - - case Light::Type_Spot: - out_light->mType = aiLightSource_SPOT; - out_light->mAngleOuterCone = AI_DEG_TO_RAD( light.OuterAngle() ); - out_light->mAngleInnerCone = AI_DEG_TO_RAD( light.InnerAngle() ); - break; - - case Light::Type_Area: - FBXImporter::LogWarn( "cannot represent area light, set to UNDEFINED" ); - out_light->mType = aiLightSource_UNDEFINED; - break; - - case Light::Type_Volume: - FBXImporter::LogWarn( "cannot represent volume light, set to UNDEFINED" ); - out_light->mType = aiLightSource_UNDEFINED; - break; - default: - ai_assert( false ); - } - - float decay = light.DecayStart(); - switch ( light.DecayType() ) - { - case Light::Decay_None: - out_light->mAttenuationConstant = decay; - out_light->mAttenuationLinear = 0.0f; - out_light->mAttenuationQuadratic = 0.0f; - break; - case Light::Decay_Linear: - out_light->mAttenuationConstant = 0.0f; - out_light->mAttenuationLinear = 2.0f / decay; - out_light->mAttenuationQuadratic = 0.0f; - break; - case Light::Decay_Quadratic: - out_light->mAttenuationConstant = 0.0f; - out_light->mAttenuationLinear = 0.0f; - out_light->mAttenuationQuadratic = 2.0f / (decay * decay); - break; - case Light::Decay_Cubic: - FBXImporter::LogWarn( "cannot represent cubic attenuation, set to Quadratic" ); - out_light->mAttenuationQuadratic = 1.0f; - break; - default: - ai_assert( false ); - } -} - -void FBXConverter::ConvertCamera( const Camera& cam, const std::string &orig_name ) -{ - cameras.push_back( new aiCamera() ); - aiCamera* const out_camera = cameras.back(); - - out_camera->mName.Set( orig_name ); - - out_camera->mAspect = cam.AspectWidth() / cam.AspectHeight(); - - //cameras are defined along positive x direction - out_camera->mPosition = cam.Position(); - out_camera->mLookAt = ( cam.InterestPosition() - out_camera->mPosition ).Normalize(); - out_camera->mUp = cam.UpVector(); - - out_camera->mHorizontalFOV = AI_DEG_TO_RAD( cam.FieldOfView() ); - out_camera->mClipPlaneNear = cam.NearPlane(); - out_camera->mClipPlaneFar = cam.FarPlane(); -} - -void FBXConverter::GetUniqueName( const std::string &name, std::string &uniqueName ) -{ - int i = 0; - uniqueName = name; - while (mNodeNames.find(uniqueName) != mNodeNames.end()) - { - ++i; - std::stringstream ext; - ext << name << std::setfill('0') << std::setw(3) << i; - uniqueName = ext.str(); - } - mNodeNames.insert(uniqueName); -} - - -const char* FBXConverter::NameTransformationComp( TransformationComp comp ) { - switch ( comp ) { - case TransformationComp_Translation: - return "Translation"; - case TransformationComp_RotationOffset: - return "RotationOffset"; - case TransformationComp_RotationPivot: - return "RotationPivot"; - case TransformationComp_PreRotation: - return "PreRotation"; - case TransformationComp_Rotation: - return "Rotation"; - case TransformationComp_PostRotation: - return "PostRotation"; - case TransformationComp_RotationPivotInverse: - return "RotationPivotInverse"; - case TransformationComp_ScalingOffset: - return "ScalingOffset"; - case TransformationComp_ScalingPivot: - return "ScalingPivot"; - case TransformationComp_Scaling: - return "Scaling"; - case TransformationComp_ScalingPivotInverse: - return "ScalingPivotInverse"; - case TransformationComp_GeometricScaling: - return "GeometricScaling"; - case TransformationComp_GeometricRotation: - return "GeometricRotation"; - case TransformationComp_GeometricTranslation: - return "GeometricTranslation"; - case TransformationComp_GeometricScalingInverse: - return "GeometricScalingInverse"; - case TransformationComp_GeometricRotationInverse: - return "GeometricRotationInverse"; - case TransformationComp_GeometricTranslationInverse: - return "GeometricTranslationInverse"; - case TransformationComp_MAXIMUM: // this is to silence compiler warnings - default: - break; - } - - ai_assert( false ); - - return nullptr; -} - -const char* FBXConverter::NameTransformationCompProperty( TransformationComp comp ) { - switch ( comp ) { - case TransformationComp_Translation: - return "Lcl Translation"; - case TransformationComp_RotationOffset: - return "RotationOffset"; - case TransformationComp_RotationPivot: - return "RotationPivot"; - case TransformationComp_PreRotation: - return "PreRotation"; - case TransformationComp_Rotation: - return "Lcl Rotation"; - case TransformationComp_PostRotation: - return "PostRotation"; - case TransformationComp_RotationPivotInverse: - return "RotationPivotInverse"; - case TransformationComp_ScalingOffset: - return "ScalingOffset"; - case TransformationComp_ScalingPivot: - return "ScalingPivot"; - case TransformationComp_Scaling: - return "Lcl Scaling"; - case TransformationComp_ScalingPivotInverse: - return "ScalingPivotInverse"; - case TransformationComp_GeometricScaling: - return "GeometricScaling"; - case TransformationComp_GeometricRotation: - return "GeometricRotation"; - case TransformationComp_GeometricTranslation: - return "GeometricTranslation"; - case TransformationComp_GeometricScalingInverse: - return "GeometricScalingInverse"; - case TransformationComp_GeometricRotationInverse: - return "GeometricRotationInverse"; - case TransformationComp_GeometricTranslationInverse: - return "GeometricTranslationInverse"; - case TransformationComp_MAXIMUM: // this is to silence compiler warnings - break; - } - - ai_assert( false ); - - return nullptr; -} - -aiVector3D FBXConverter::TransformationCompDefaultValue( TransformationComp comp ) -{ - // XXX a neat way to solve the never-ending special cases for scaling - // would be to do everything in log space! - return comp == TransformationComp_Scaling ? aiVector3D( 1.f, 1.f, 1.f ) : aiVector3D(); -} - -void FBXConverter::GetRotationMatrix( Model::RotOrder mode, const aiVector3D& rotation, aiMatrix4x4& out ) -{ - if ( mode == Model::RotOrder_SphericXYZ ) { - FBXImporter::LogError( "Unsupported RotationMode: SphericXYZ" ); - out = aiMatrix4x4(); - return; - } - - const float angle_epsilon = 1e-6f; - - out = aiMatrix4x4(); - - bool is_id[ 3 ] = { true, true, true }; - - aiMatrix4x4 temp[ 3 ]; - if ( std::fabs( rotation.z ) > angle_epsilon ) { - aiMatrix4x4::RotationZ( AI_DEG_TO_RAD( rotation.z ), temp[ 2 ] ); - is_id[ 2 ] = false; - } - if ( std::fabs( rotation.y ) > angle_epsilon ) { - aiMatrix4x4::RotationY( AI_DEG_TO_RAD( rotation.y ), temp[ 1 ] ); - is_id[ 1 ] = false; - } - if ( std::fabs( rotation.x ) > angle_epsilon ) { - aiMatrix4x4::RotationX( AI_DEG_TO_RAD( rotation.x ), temp[ 0 ] ); - is_id[ 0 ] = false; - } - - int order[ 3 ] = { -1, -1, -1 }; - - // note: rotation order is inverted since we're left multiplying as is usual in assimp - switch ( mode ) - { - case Model::RotOrder_EulerXYZ: - order[ 0 ] = 2; - order[ 1 ] = 1; - order[ 2 ] = 0; - break; - - case Model::RotOrder_EulerXZY: - order[ 0 ] = 1; - order[ 1 ] = 2; - order[ 2 ] = 0; - break; - - case Model::RotOrder_EulerYZX: - order[ 0 ] = 0; - order[ 1 ] = 2; - order[ 2 ] = 1; - break; - - case Model::RotOrder_EulerYXZ: - order[ 0 ] = 2; - order[ 1 ] = 0; - order[ 2 ] = 1; - break; - - case Model::RotOrder_EulerZXY: - order[ 0 ] = 1; - order[ 1 ] = 0; - order[ 2 ] = 2; - break; - - case Model::RotOrder_EulerZYX: - order[ 0 ] = 0; - order[ 1 ] = 1; - order[ 2 ] = 2; - break; - - default: - ai_assert( false ); - break; - } - - ai_assert( order[ 0 ] >= 0 ); - ai_assert( order[ 0 ] <= 2 ); - ai_assert( order[ 1 ] >= 0 ); - ai_assert( order[ 1 ] <= 2 ); - ai_assert( order[ 2 ] >= 0 ); - ai_assert( order[ 2 ] <= 2 ); - - if ( !is_id[ order[ 0 ] ] ) { - out = temp[ order[ 0 ] ]; - } - - if ( !is_id[ order[ 1 ] ] ) { - out = out * temp[ order[ 1 ] ]; - } - - if ( !is_id[ order[ 2 ] ] ) { - out = out * temp[ order[ 2 ] ]; - } -} - -bool FBXConverter::NeedsComplexTransformationChain( const Model& model ) -{ - const PropertyTable& props = model.Props(); - bool ok; - - const float zero_epsilon = 1e-6f; - const aiVector3D all_ones(1.0f, 1.0f, 1.0f); - for ( size_t i = 0; i < TransformationComp_MAXIMUM; ++i ) { - const TransformationComp comp = static_cast< TransformationComp >( i ); - - if ( comp == TransformationComp_Rotation || comp == TransformationComp_Scaling || comp == TransformationComp_Translation ) { - continue; - } - - bool scale_compare = ( comp == TransformationComp_GeometricScaling || comp == TransformationComp_Scaling ); - - const aiVector3D& v = PropertyGet( props, NameTransformationCompProperty( comp ), ok ); - if ( ok && scale_compare ) { - if ( (v - all_ones).SquareLength() > zero_epsilon ) { - return true; - } - } else if ( ok ) { - if ( v.SquareLength() > zero_epsilon ) { - return true; - } - } - } - - return false; -} - -std::string FBXConverter::NameTransformationChainNode( const std::string& name, TransformationComp comp ) -{ - return name + std::string( MAGIC_NODE_TAG ) + "_" + NameTransformationComp( comp ); -} - -void FBXConverter::GenerateTransformationNodeChain( const Model& model, std::vector& output_nodes, - std::vector& post_output_nodes ) { - const PropertyTable& props = model.Props(); - const Model::RotOrder rot = model.RotationOrder(); - - bool ok; - - aiMatrix4x4 chain[ TransformationComp_MAXIMUM ]; - std::fill_n( chain, static_cast( TransformationComp_MAXIMUM ), aiMatrix4x4() ); - - // generate transformation matrices for all the different transformation components - const float zero_epsilon = 1e-6f; - const aiVector3D all_ones(1.0f, 1.0f, 1.0f); - bool is_complex = false; - - const aiVector3D& PreRotation = PropertyGet( props, "PreRotation", ok ); - if ( ok && PreRotation.SquareLength() > zero_epsilon ) { - is_complex = true; - - GetRotationMatrix( Model::RotOrder::RotOrder_EulerXYZ, PreRotation, chain[ TransformationComp_PreRotation ] ); - } - - const aiVector3D& PostRotation = PropertyGet( props, "PostRotation", ok ); - if ( ok && PostRotation.SquareLength() > zero_epsilon ) { - is_complex = true; - - GetRotationMatrix( Model::RotOrder::RotOrder_EulerXYZ, PostRotation, chain[ TransformationComp_PostRotation ] ); - } - - const aiVector3D& RotationPivot = PropertyGet( props, "RotationPivot", ok ); - if ( ok && RotationPivot.SquareLength() > zero_epsilon ) { - is_complex = true; - - aiMatrix4x4::Translation( RotationPivot, chain[ TransformationComp_RotationPivot ] ); - aiMatrix4x4::Translation( -RotationPivot, chain[ TransformationComp_RotationPivotInverse ] ); - } - - const aiVector3D& RotationOffset = PropertyGet( props, "RotationOffset", ok ); - if ( ok && RotationOffset.SquareLength() > zero_epsilon ) { - is_complex = true; - - aiMatrix4x4::Translation( RotationOffset, chain[ TransformationComp_RotationOffset ] ); - } - - const aiVector3D& ScalingOffset = PropertyGet( props, "ScalingOffset", ok ); - if ( ok && ScalingOffset.SquareLength() > zero_epsilon ) { - is_complex = true; - - aiMatrix4x4::Translation( ScalingOffset, chain[ TransformationComp_ScalingOffset ] ); - } - - const aiVector3D& ScalingPivot = PropertyGet( props, "ScalingPivot", ok ); - if ( ok && ScalingPivot.SquareLength() > zero_epsilon ) { - is_complex = true; - - aiMatrix4x4::Translation( ScalingPivot, chain[ TransformationComp_ScalingPivot ] ); - aiMatrix4x4::Translation( -ScalingPivot, chain[ TransformationComp_ScalingPivotInverse ] ); - } - - const aiVector3D& Translation = PropertyGet( props, "Lcl Translation", ok ); - if ( ok && Translation.SquareLength() > zero_epsilon ) { - aiMatrix4x4::Translation( Translation, chain[ TransformationComp_Translation ] ); - } - - const aiVector3D& Scaling = PropertyGet( props, "Lcl Scaling", ok ); - if ( ok && (Scaling - all_ones).SquareLength() > zero_epsilon ) { - aiMatrix4x4::Scaling( Scaling, chain[ TransformationComp_Scaling ] ); - } - - const aiVector3D& Rotation = PropertyGet( props, "Lcl Rotation", ok ); - if ( ok && Rotation.SquareLength() > zero_epsilon ) { - GetRotationMatrix( rot, Rotation, chain[ TransformationComp_Rotation ] ); - } - - const aiVector3D& GeometricScaling = PropertyGet( props, "GeometricScaling", ok ); - if ( ok && (GeometricScaling - all_ones).SquareLength() > zero_epsilon ) { - is_complex = true; - aiMatrix4x4::Scaling( GeometricScaling, chain[ TransformationComp_GeometricScaling ] ); - aiVector3D GeometricScalingInverse = GeometricScaling; - bool canscale = true; - for (unsigned int i = 0; i < 3; ++i) { - if ( std::fabs( GeometricScalingInverse[i] ) > zero_epsilon ) { - GeometricScalingInverse[i] = 1.0f / GeometricScaling[i]; - } else { - FBXImporter::LogError( "cannot invert geometric scaling matrix with a 0.0 scale component" ); - canscale = false; - break; - } - } - if (canscale) { - aiMatrix4x4::Scaling( GeometricScalingInverse, chain[ TransformationComp_GeometricScalingInverse ] ); - } - } - - const aiVector3D& GeometricRotation = PropertyGet( props, "GeometricRotation", ok ); - if ( ok && GeometricRotation.SquareLength() > zero_epsilon ) { - is_complex = true; - GetRotationMatrix( rot, GeometricRotation, chain[ TransformationComp_GeometricRotation ] ); - GetRotationMatrix( rot, GeometricRotation, chain[ TransformationComp_GeometricRotationInverse ] ); - chain[ TransformationComp_GeometricRotationInverse ].Inverse(); - } - - const aiVector3D& GeometricTranslation = PropertyGet( props, "GeometricTranslation", ok ); - if ( ok && GeometricTranslation.SquareLength() > zero_epsilon ) { - is_complex = true; - aiMatrix4x4::Translation( GeometricTranslation, chain[ TransformationComp_GeometricTranslation ] ); - aiMatrix4x4::Translation( -GeometricTranslation, chain[ TransformationComp_GeometricTranslationInverse ] ); - } - - // is_complex needs to be consistent with NeedsComplexTransformationChain() - // or the interplay between this code and the animation converter would - // not be guaranteed. - ai_assert( NeedsComplexTransformationChain( model ) == is_complex ); - - std::string name = FixNodeName( model.Name() ); - - // now, if we have more than just Translation, Scaling and Rotation, - // we need to generate a full node chain to accommodate for assimp's - // lack to express pivots and offsets. - if ( is_complex && doc.Settings().preservePivots ) { - FBXImporter::LogInfo( "generating full transformation chain for node: " + name ); - - // query the anim_chain_bits dictionary to find out which chain elements - // have associated node animation channels. These can not be dropped - // even if they have identity transform in bind pose. - NodeAnimBitMap::const_iterator it = node_anim_chain_bits.find( name ); - const unsigned int anim_chain_bitmask = ( it == node_anim_chain_bits.end() ? 0 : ( *it ).second ); - - unsigned int bit = 0x1; - for ( size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1 ) { - const TransformationComp comp = static_cast( i ); - - if ( chain[ i ].IsIdentity() && ( anim_chain_bitmask & bit ) == 0 ) { - continue; - } - - if ( comp == TransformationComp_PostRotation ) { - chain[ i ] = chain[ i ].Inverse(); - } - - aiNode* nd = new aiNode(); - nd->mName.Set( NameTransformationChainNode( name, comp ) ); - nd->mTransformation = chain[ i ]; - - // geometric inverses go in a post-node chain - if ( comp == TransformationComp_GeometricScalingInverse || - comp == TransformationComp_GeometricRotationInverse || - comp == TransformationComp_GeometricTranslationInverse - ) { - post_output_nodes.push_back( nd ); - } else { - output_nodes.push_back( nd ); - } - } - - ai_assert( output_nodes.size() ); - return; - } - - // else, we can just multiply the matrices together - aiNode* nd = new aiNode(); - output_nodes.push_back( nd ); - std::string uniqueName; - GetUniqueName( name, uniqueName ); - - nd->mName.Set( uniqueName ); - - for (const auto &transform : chain) { - nd->mTransformation = nd->mTransformation * transform; - } -} - -void FBXConverter::SetupNodeMetadata( const Model& model, aiNode& nd ) -{ - const PropertyTable& props = model.Props(); - DirectPropertyMap unparsedProperties = props.GetUnparsedProperties(); - - // create metadata on node - const std::size_t numStaticMetaData = 2; - aiMetadata* data = aiMetadata::Alloc( static_cast(unparsedProperties.size() + numStaticMetaData) ); - nd.mMetaData = data; - int index = 0; - - // find user defined properties (3ds Max) - data->Set( index++, "UserProperties", aiString( PropertyGet( props, "UDP3DSMAX", "" ) ) ); - // preserve the info that a node was marked as Null node in the original file. - data->Set( index++, "IsNull", model.IsNull() ? true : false ); - - // add unparsed properties to the node's metadata - for( const DirectPropertyMap::value_type& prop : unparsedProperties ) { - // Interpret the property as a concrete type - if ( const TypedProperty* interpreted = prop.second->As >() ) { - data->Set( index++, prop.first, interpreted->Value() ); - } else if ( const TypedProperty* interpreted = prop.second->As >() ) { - data->Set( index++, prop.first, interpreted->Value() ); - } else if ( const TypedProperty* interpreted = prop.second->As >() ) { - data->Set( index++, prop.first, interpreted->Value() ); - } else if ( const TypedProperty* interpreted = prop.second->As >() ) { - data->Set( index++, prop.first, interpreted->Value() ); - } else if ( const TypedProperty* interpreted = prop.second->As >() ) { - data->Set( index++, prop.first, aiString( interpreted->Value() ) ); - } else if ( const TypedProperty* interpreted = prop.second->As >() ) { - data->Set( index++, prop.first, interpreted->Value() ); - } else { - ai_assert( false ); - } - } -} - -void FBXConverter::ConvertModel( const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform ) -{ - const std::vector& geos = model.GetGeometry(); - - std::vector meshes; - meshes.reserve( geos.size() ); - - for( const Geometry* geo : geos ) { - - const MeshGeometry* const mesh = dynamic_cast< const MeshGeometry* >( geo ); - if ( mesh ) { - const std::vector& indices = ConvertMesh( *mesh, model, node_global_transform, nd); - std::copy( indices.begin(), indices.end(), std::back_inserter( meshes ) ); - } - else { - FBXImporter::LogWarn( "ignoring unrecognized geometry: " + geo->Name() ); - } - } - - if ( meshes.size() ) { - nd.mMeshes = new unsigned int[ meshes.size() ](); - nd.mNumMeshes = static_cast< unsigned int >( meshes.size() ); - - std::swap_ranges( meshes.begin(), meshes.end(), nd.mMeshes ); - } -} - -std::vector FBXConverter::ConvertMesh( const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd) -{ - std::vector temp; - - MeshMap::const_iterator it = meshes_converted.find( &mesh ); - if ( it != meshes_converted.end() ) { - std::copy( ( *it ).second.begin(), ( *it ).second.end(), std::back_inserter( temp ) ); - return temp; - } - - const std::vector& vertices = mesh.GetVertices(); - const std::vector& faces = mesh.GetFaceIndexCounts(); - if ( vertices.empty() || faces.empty() ) { - FBXImporter::LogWarn( "ignoring empty geometry: " + mesh.Name() ); - return temp; - } - - // one material per mesh maps easily to aiMesh. Multiple material - // meshes need to be split. - const MatIndexArray& mindices = mesh.GetMaterialIndices(); - if ( doc.Settings().readMaterials && !mindices.empty() ) { - const MatIndexArray::value_type base = mindices[ 0 ]; - for( MatIndexArray::value_type index : mindices ) { - if ( index != base ) { - return ConvertMeshMultiMaterial( mesh, model, node_global_transform, nd); - } - } - } - - // faster code-path, just copy the data - temp.push_back( ConvertMeshSingleMaterial( mesh, model, node_global_transform, nd) ); - return temp; -} - -aiMesh* FBXConverter::SetupEmptyMesh( const MeshGeometry& mesh, aiNode& nd) -{ - aiMesh* const out_mesh = new aiMesh(); - meshes.push_back( out_mesh ); - meshes_converted[ &mesh ].push_back( static_cast( meshes.size() - 1 ) ); - - // set name - std::string name = mesh.Name(); - if ( name.substr( 0, 10 ) == "Geometry::" ) { - name = name.substr( 10 ); - } - - if ( name.length() ) { - out_mesh->mName.Set( name ); - } - else - { - out_mesh->mName = nd.mName; - } - - return out_mesh; -} - -unsigned int FBXConverter::ConvertMeshSingleMaterial( const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd) -{ - const MatIndexArray& mindices = mesh.GetMaterialIndices(); - aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd); - - const std::vector& vertices = mesh.GetVertices(); - const std::vector& faces = mesh.GetFaceIndexCounts(); - - // copy vertices - out_mesh->mNumVertices = static_cast( vertices.size() ); - out_mesh->mVertices = new aiVector3D[ vertices.size() ]; - std::copy( vertices.begin(), vertices.end(), out_mesh->mVertices ); - - // generate dummy faces - out_mesh->mNumFaces = static_cast( faces.size() ); - aiFace* fac = out_mesh->mFaces = new aiFace[ faces.size() ](); - - unsigned int cursor = 0; - for( unsigned int pcount : faces ) { - aiFace& f = *fac++; - f.mNumIndices = pcount; - f.mIndices = new unsigned int[ pcount ]; - switch ( pcount ) - { - case 1: - out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT; - break; - case 2: - out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; - break; - case 3: - out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; - break; - default: - out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; - break; - } - for ( unsigned int i = 0; i < pcount; ++i ) { - f.mIndices[ i ] = cursor++; - } - } - - // copy normals - const std::vector& normals = mesh.GetNormals(); - if ( normals.size() ) { - ai_assert( normals.size() == vertices.size() ); - - out_mesh->mNormals = new aiVector3D[ vertices.size() ]; - std::copy( normals.begin(), normals.end(), out_mesh->mNormals ); - } - - // copy tangents - assimp requires both tangents and bitangents (binormals) - // to be present, or neither of them. Compute binormals from normals - // and tangents if needed. - const std::vector& tangents = mesh.GetTangents(); - const std::vector* binormals = &mesh.GetBinormals(); - - if ( tangents.size() ) { - std::vector tempBinormals; - if ( !binormals->size() ) { - if ( normals.size() ) { - tempBinormals.resize( normals.size() ); - for ( unsigned int i = 0; i < tangents.size(); ++i ) { - tempBinormals[ i ] = normals[ i ] ^ tangents[ i ]; - } - - binormals = &tempBinormals; - } - else { - binormals = NULL; - } - } - - if ( binormals ) { - ai_assert( tangents.size() == vertices.size() ); - ai_assert( binormals->size() == vertices.size() ); - - out_mesh->mTangents = new aiVector3D[ vertices.size() ]; - std::copy( tangents.begin(), tangents.end(), out_mesh->mTangents ); - - out_mesh->mBitangents = new aiVector3D[ vertices.size() ]; - std::copy( binormals->begin(), binormals->end(), out_mesh->mBitangents ); - } - } - - // copy texture coords - for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) { - const std::vector& uvs = mesh.GetTextureCoords( i ); - if ( uvs.empty() ) { - break; - } - - aiVector3D* out_uv = out_mesh->mTextureCoords[ i ] = new aiVector3D[ vertices.size() ]; - for( const aiVector2D& v : uvs ) { - *out_uv++ = aiVector3D( v.x, v.y, 0.0f ); - } - - out_mesh->mNumUVComponents[ i ] = 2; - } - - // copy vertex colors - for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i ) { - const std::vector& colors = mesh.GetVertexColors( i ); - if ( colors.empty() ) { - break; - } - - out_mesh->mColors[ i ] = new aiColor4D[ vertices.size() ]; - std::copy( colors.begin(), colors.end(), out_mesh->mColors[ i ] ); - } - - if ( !doc.Settings().readMaterials || mindices.empty() ) { - FBXImporter::LogError( "no material assigned to mesh, setting default material" ); - out_mesh->mMaterialIndex = GetDefaultMaterial(); - } - else { - ConvertMaterialForMesh( out_mesh, model, mesh, mindices[ 0 ] ); - } - - if ( doc.Settings().readWeights && mesh.DeformerSkin() != NULL ) { - ConvertWeights( out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION ); - } - - return static_cast( meshes.size() - 1 ); -} - -std::vector FBXConverter::ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd) -{ - const MatIndexArray& mindices = mesh.GetMaterialIndices(); - ai_assert( mindices.size() ); - - std::set had; - std::vector indices; - - for( MatIndexArray::value_type index : mindices ) { - if ( had.find( index ) == had.end() ) { - - indices.push_back( ConvertMeshMultiMaterial( mesh, model, index, node_global_transform, nd) ); - had.insert( index ); - } - } - - return indices; -} - -unsigned int FBXConverter::ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model, - MatIndexArray::value_type index, - const aiMatrix4x4& node_global_transform, - aiNode& nd) -{ - aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd); - - const MatIndexArray& mindices = mesh.GetMaterialIndices(); - const std::vector& vertices = mesh.GetVertices(); - const std::vector& faces = mesh.GetFaceIndexCounts(); - - const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != NULL; - - unsigned int count_faces = 0; - unsigned int count_vertices = 0; - - // count faces - std::vector::const_iterator itf = faces.begin(); - for ( MatIndexArray::const_iterator it = mindices.begin(), - end = mindices.end(); it != end; ++it, ++itf ) - { - if ( ( *it ) != index ) { - continue; - } - ++count_faces; - count_vertices += *itf; - } - - ai_assert( count_faces ); - ai_assert( count_vertices ); - - // mapping from output indices to DOM indexing, needed to resolve weights - std::vector reverseMapping; - - if ( process_weights ) { - reverseMapping.resize( count_vertices ); - } - - // allocate output data arrays, but don't fill them yet - out_mesh->mNumVertices = count_vertices; - out_mesh->mVertices = new aiVector3D[ count_vertices ]; - - out_mesh->mNumFaces = count_faces; - aiFace* fac = out_mesh->mFaces = new aiFace[ count_faces ](); - - - // allocate normals - const std::vector& normals = mesh.GetNormals(); - if ( normals.size() ) { - ai_assert( normals.size() == vertices.size() ); - out_mesh->mNormals = new aiVector3D[ vertices.size() ]; - } - - // allocate tangents, binormals. - const std::vector& tangents = mesh.GetTangents(); - const std::vector* binormals = &mesh.GetBinormals(); - std::vector tempBinormals; - - if ( tangents.size() ) { - if ( !binormals->size() ) { - if ( normals.size() ) { - // XXX this computes the binormals for the entire mesh, not only - // the part for which we need them. - tempBinormals.resize( normals.size() ); - for ( unsigned int i = 0; i < tangents.size(); ++i ) { - tempBinormals[ i ] = normals[ i ] ^ tangents[ i ]; - } - - binormals = &tempBinormals; - } - else { - binormals = NULL; - } - } - - if ( binormals ) { - ai_assert( tangents.size() == vertices.size() && binormals->size() == vertices.size() ); - - out_mesh->mTangents = new aiVector3D[ vertices.size() ]; - out_mesh->mBitangents = new aiVector3D[ vertices.size() ]; - } - } - - // allocate texture coords - unsigned int num_uvs = 0; - for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i, ++num_uvs ) { - const std::vector& uvs = mesh.GetTextureCoords( i ); - if ( uvs.empty() ) { - break; - } - - out_mesh->mTextureCoords[ i ] = new aiVector3D[ vertices.size() ]; - out_mesh->mNumUVComponents[ i ] = 2; - } - - // allocate vertex colors - unsigned int num_vcs = 0; - for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i, ++num_vcs ) { - const std::vector& colors = mesh.GetVertexColors( i ); - if ( colors.empty() ) { - break; - } - - out_mesh->mColors[ i ] = new aiColor4D[ vertices.size() ]; - } - - unsigned int cursor = 0, in_cursor = 0; - - itf = faces.begin(); - for ( MatIndexArray::const_iterator it = mindices.begin(), - end = mindices.end(); it != end; ++it, ++itf ) - { - const unsigned int pcount = *itf; - if ( ( *it ) != index ) { - in_cursor += pcount; - continue; - } - - aiFace& f = *fac++; - - f.mNumIndices = pcount; - f.mIndices = new unsigned int[ pcount ]; - switch ( pcount ) - { - case 1: - out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT; - break; - case 2: - out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; - break; - case 3: - out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; - break; - default: - out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; - break; - } - for ( unsigned int i = 0; i < pcount; ++i, ++cursor, ++in_cursor ) { - f.mIndices[ i ] = cursor; - - if ( reverseMapping.size() ) { - reverseMapping[ cursor ] = in_cursor; - } - - out_mesh->mVertices[ cursor ] = vertices[ in_cursor ]; - - if ( out_mesh->mNormals ) { - out_mesh->mNormals[ cursor ] = normals[ in_cursor ]; - } - - if ( out_mesh->mTangents ) { - out_mesh->mTangents[ cursor ] = tangents[ in_cursor ]; - out_mesh->mBitangents[ cursor ] = ( *binormals )[ in_cursor ]; - } - - for ( unsigned int j = 0; j < num_uvs; ++j ) { - const std::vector& uvs = mesh.GetTextureCoords( j ); - out_mesh->mTextureCoords[ j ][ cursor ] = aiVector3D( uvs[ in_cursor ].x, uvs[ in_cursor ].y, 0.0f ); - } - - for ( unsigned int j = 0; j < num_vcs; ++j ) { - const std::vector& cols = mesh.GetVertexColors( j ); - out_mesh->mColors[ j ][ cursor ] = cols[ in_cursor ]; - } - } - } - - ConvertMaterialForMesh( out_mesh, model, mesh, index ); - - if ( process_weights ) { - ConvertWeights( out_mesh, model, mesh, node_global_transform, index, &reverseMapping ); - } - - return static_cast( meshes.size() - 1 ); -} - -void FBXConverter::ConvertWeights( aiMesh* out, const Model& model, const MeshGeometry& geo, - const aiMatrix4x4& node_global_transform , - unsigned int materialIndex, - std::vector* outputVertStartIndices ) -{ - ai_assert( geo.DeformerSkin() ); - - std::vector out_indices; - std::vector index_out_indices; - std::vector count_out_indices; - - const Skin& sk = *geo.DeformerSkin(); - - std::vector bones; - bones.reserve( sk.Clusters().size() ); - - const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION; - ai_assert( no_mat_check || outputVertStartIndices ); - - try { - - for( const Cluster* cluster : sk.Clusters() ) { - ai_assert( cluster ); - - const WeightIndexArray& indices = cluster->GetIndices(); - - if ( indices.empty() ) { - continue; - } - - const MatIndexArray& mats = geo.GetMaterialIndices(); - - bool ok = false; - - const size_t no_index_sentinel = std::numeric_limits::max(); - - count_out_indices.clear(); - index_out_indices.clear(); - out_indices.clear(); - - // now check if *any* of these weights is contained in the output mesh, - // taking notes so we don't need to do it twice. - for( WeightIndexArray::value_type index : indices ) { - - unsigned int count = 0; - const unsigned int* const out_idx = geo.ToOutputVertexIndex( index, count ); - // ToOutputVertexIndex only returns NULL if index is out of bounds - // which should never happen - ai_assert( out_idx != NULL ); - - index_out_indices.push_back( no_index_sentinel ); - count_out_indices.push_back( 0 ); - - for ( unsigned int i = 0; i < count; ++i ) { - if ( no_mat_check || static_cast( mats[ geo.FaceForVertexIndex( out_idx[ i ] ) ] ) == materialIndex ) { - - if ( index_out_indices.back() == no_index_sentinel ) { - index_out_indices.back() = out_indices.size(); - - } - - if ( no_mat_check ) { - out_indices.push_back( out_idx[ i ] ); + if (!name_carrier) { + std::string old_original_name = original_name; + GetUniqueName(old_original_name, original_name); + nodes_chain.push_back(new aiNode(original_name)); } else { - // this extra lookup is in O(logn), so the entire algorithm becomes O(nlogn) - const std::vector::iterator it = std::lower_bound( - outputVertStartIndices->begin(), - outputVertStartIndices->end(), - out_idx[ i ] - ); - - out_indices.push_back( std::distance( outputVertStartIndices->begin(), it ) ); + original_name = nodes_chain.back()->mName.C_Str(); } - ++count_out_indices.back(); - ok = true; - } - } - } + //setup metadata on newest node + SetupNodeMetadata(*model, *nodes_chain.back()); - // if we found at least one, generate the output bones - // XXX this could be heavily simplified by collecting the bone - // data in a single step. - if ( ok ) { - ConvertCluster( bones, model, *cluster, out_indices, index_out_indices, - count_out_indices, node_global_transform ); - } - } - } - catch ( std::exception& ) { - std::for_each( bones.begin(), bones.end(), Util::delete_fun() ); - throw; - } + // link all nodes in a row + aiNode* last_parent = &parent; + for (aiNode* prenode : nodes_chain) { + ai_assert(prenode); - if ( bones.empty() ) { - return; - } - - out->mBones = new aiBone*[ bones.size() ](); - out->mNumBones = static_cast( bones.size() ); - - std::swap_ranges( bones.begin(), bones.end(), out->mBones ); -} - -void FBXConverter::ConvertCluster( std::vector& bones, const Model& /*model*/, const Cluster& cl, - std::vector& out_indices, - std::vector& index_out_indices, - std::vector& count_out_indices, - const aiMatrix4x4& node_global_transform ) -{ - - aiBone* const bone = new aiBone(); - bones.push_back( bone ); - - bone->mName = FixNodeName( cl.TargetNode()->Name() ); - - bone->mOffsetMatrix = cl.TransformLink(); - bone->mOffsetMatrix.Inverse(); - - bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform; - - bone->mNumWeights = static_cast( out_indices.size() ); - aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[ out_indices.size() ]; - - const size_t no_index_sentinel = std::numeric_limits::max(); - const WeightArray& weights = cl.GetWeights(); - - const size_t c = index_out_indices.size(); - for ( size_t i = 0; i < c; ++i ) { - const size_t index_index = index_out_indices[ i ]; - - if ( index_index == no_index_sentinel ) { - continue; - } - - const size_t cc = count_out_indices[ i ]; - for ( size_t j = 0; j < cc; ++j ) { - aiVertexWeight& out_weight = *cursor++; - - out_weight.mVertexId = static_cast( out_indices[ index_index + j ] ); - out_weight.mWeight = weights[ i ]; - } - } -} - -void FBXConverter::ConvertMaterialForMesh( aiMesh* out, const Model& model, const MeshGeometry& geo, - MatIndexArray::value_type materialIndex ) -{ - // locate source materials for this mesh - const std::vector& mats = model.GetMaterials(); - if ( static_cast( materialIndex ) >= mats.size() || materialIndex < 0 ) { - FBXImporter::LogError( "material index out of bounds, setting default material" ); - out->mMaterialIndex = GetDefaultMaterial(); - return; - } - - const Material* const mat = mats[ materialIndex ]; - MaterialMap::const_iterator it = materials_converted.find( mat ); - if ( it != materials_converted.end() ) { - out->mMaterialIndex = ( *it ).second; - return; - } - - out->mMaterialIndex = ConvertMaterial( *mat, &geo ); - materials_converted[ mat ] = out->mMaterialIndex; -} - -unsigned int FBXConverter::GetDefaultMaterial() -{ - if ( defaultMaterialIndex ) { - return defaultMaterialIndex - 1; - } - - aiMaterial* out_mat = new aiMaterial(); - materials.push_back( out_mat ); - - const aiColor3D diffuse = aiColor3D( 0.8f, 0.8f, 0.8f ); - out_mat->AddProperty( &diffuse, 1, AI_MATKEY_COLOR_DIFFUSE ); - - aiString s; - s.Set( AI_DEFAULT_MATERIAL_NAME ); - - out_mat->AddProperty( &s, AI_MATKEY_NAME ); - - defaultMaterialIndex = static_cast< unsigned int >( materials.size() ); - return defaultMaterialIndex - 1; -} - - -unsigned int FBXConverter::ConvertMaterial( const Material& material, const MeshGeometry* const mesh ) -{ - const PropertyTable& props = material.Props(); - - // generate empty output material - aiMaterial* out_mat = new aiMaterial(); - materials_converted[ &material ] = static_cast( materials.size() ); - - materials.push_back( out_mat ); - - aiString str; - - // strip Material:: prefix - std::string name = material.Name(); - if ( name.substr( 0, 10 ) == "Material::" ) { - name = name.substr( 10 ); - } - - // set material name if not empty - this could happen - // and there should be no key for it in this case. - if ( name.length() ) { - str.Set( name ); - out_mat->AddProperty( &str, AI_MATKEY_NAME ); - } - - // shading stuff and colors - SetShadingPropertiesCommon( out_mat, props ); - - // texture assignments - SetTextureProperties( out_mat, material.Textures(), mesh ); - SetTextureProperties( out_mat, material.LayeredTextures(), mesh ); - - return static_cast( materials.size() - 1 ); -} - -unsigned int FBXConverter::ConvertVideo( const Video& video ) -{ - // generate empty output texture - aiTexture* out_tex = new aiTexture(); - textures.push_back( out_tex ); - - // assuming the texture is compressed - out_tex->mWidth = static_cast( video.ContentLength() ); // total data size - out_tex->mHeight = 0; // fixed to 0 - - // steal the data from the Video to avoid an additional copy - out_tex->pcData = reinterpret_cast( const_cast( video ).RelinquishContent() ); - - // try to extract a hint from the file extension - const std::string& filename = video.FileName().empty() ? video.RelativeFilename() : video.FileName(); - std::string ext = BaseImporter::GetExtension( filename ); - - if ( ext == "jpeg" ) { - ext = "jpg"; - } - - if ( ext.size() <= 3 ) { - memcpy( out_tex->achFormatHint, ext.c_str(), ext.size() ); - } - - out_tex->mFilename.Set(video.FileName().c_str()); - - return static_cast( textures.size() - 1 ); -} - -aiString FBXConverter::GetTexturePath(const Texture* tex) -{ - aiString path; - path.Set(tex->RelativeFilename()); - - const Video* media = tex->Media(); - if (media != nullptr) { - bool textureReady = false; //tells if our texture is ready (if it was loaded or if it was found) - unsigned int index; - - VideoMap::const_iterator it = textures_converted.find(media); - if (it != textures_converted.end()) { - index = (*it).second; - textureReady = true; - } - else { - if (media->ContentLength() > 0) { - index = ConvertVideo(*media); - textures_converted[media] = index; - textureReady = true; - } - } - - // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture), if the texture is ready - if (doc.Settings().useLegacyEmbeddedTextureNaming) { - if (textureReady) { - // TODO: check the possibility of using the flag "AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING" - // In FBX files textures are now stored internally by Assimp with their filename included - // Now Assimp can lookup through the loaded textures after all data is processed - // We need to load all textures before referencing them, as FBX file format order may reference a texture before loading it - // This may occur on this case too, it has to be studied - path.data[0] = '*'; - path.length = 1 + ASSIMP_itoa10(path.data + 1, MAXLEN - 1, index); - } - } - } - - return path; -} - -void FBXConverter::TrySetTextureProperties( aiMaterial* out_mat, const TextureMap& textures, - const std::string& propName, - aiTextureType target, const MeshGeometry* const mesh ) -{ - TextureMap::const_iterator it = textures.find( propName ); - if ( it == textures.end() ) { - return; - } - - const Texture* const tex = ( *it ).second; - if ( tex != nullptr ) { - aiString path = GetTexturePath(tex); - out_mat->AddProperty( &path, _AI_MATKEY_TEXTURE_BASE, target, 0 ); - - aiUVTransform uvTrafo; - // XXX handle all kinds of UV transformations - uvTrafo.mScaling = tex->UVScaling(); - uvTrafo.mTranslation = tex->UVTranslation(); - out_mat->AddProperty( &uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, 0 ); - - const PropertyTable& props = tex->Props(); - - int uvIndex = 0; - - bool ok; - const std::string& uvSet = PropertyGet( props, "UVSet", ok ); - if ( ok ) { - // "default" is the name which usually appears in the FbxFileTexture template - if ( uvSet != "default" && uvSet.length() ) { - // this is a bit awkward - we need to find a mesh that uses this - // material and scan its UV channels for the given UV name because - // assimp references UV channels by index, not by name. - - // XXX: the case that UV channels may appear in different orders - // in meshes is unhandled. A possible solution would be to sort - // the UV channels alphabetically, but this would have the side - // effect that the primary (first) UV channel would sometimes - // be moved, causing trouble when users read only the first - // UV channel and ignore UV channel assignments altogether. - - const unsigned int matIndex = static_cast( std::distance( materials.begin(), - std::find( materials.begin(), materials.end(), out_mat ) - ) ); - - - uvIndex = -1; - if ( !mesh ) - { - for( const MeshMap::value_type& v : meshes_converted ) { - const MeshGeometry* const mesh = dynamic_cast ( v.first ); - if ( !mesh ) { - continue; - } - - const MatIndexArray& mats = mesh->GetMaterialIndices(); - if ( std::find( mats.begin(), mats.end(), matIndex ) == mats.end() ) { - continue; - } - - int index = -1; - for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) { - if ( mesh->GetTextureCoords( i ).empty() ) { - break; + if (last_parent != &parent) { + last_parent->mNumChildren = 1; + last_parent->mChildren = new aiNode*[1]; + last_parent->mChildren[0] = prenode; } - const std::string& name = mesh->GetTextureCoordChannelName( i ); - if ( name == uvSet ) { - index = static_cast( i ); - break; - } - } - if ( index == -1 ) { - FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" ); - continue; + + prenode->mParent = last_parent; + last_parent = prenode; + + new_abs_transform *= prenode->mTransformation; } - if ( uvIndex == -1 ) { - uvIndex = index; + // attach geometry + ConvertModel(*model, *nodes_chain.back(), new_abs_transform); + + // check if there will be any child nodes + const std::vector& child_conns + = doc.GetConnectionsByDestinationSequenced(model->ID(), "Model"); + + // if so, link the geometric transform inverse nodes + // before we attach any child nodes + if (child_conns.size()) { + for (aiNode* postnode : post_nodes_chain) { + ai_assert(postnode); + + if (last_parent != &parent) { + last_parent->mNumChildren = 1; + last_parent->mChildren = new aiNode*[1]; + last_parent->mChildren[0] = postnode; + } + + postnode->mParent = last_parent; + last_parent = postnode; + + new_abs_transform *= postnode->mTransformation; + } } else { - FBXImporter::LogWarn( "the UV channel named " + uvSet + - " appears at different positions in meshes, results will be wrong" ); + // free the nodes we allocated as we don't need them + Util::delete_fun deleter; + std::for_each( + post_nodes_chain.begin(), + post_nodes_chain.end(), + deleter + ); } - } - } - else - { - int index = -1; - for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) { - if ( mesh->GetTextureCoords( i ).empty() ) { - break; - } - const std::string& name = mesh->GetTextureCoordChannelName( i ); - if ( name == uvSet ) { - index = static_cast( i ); - break; - } - } - if ( index == -1 ) { - FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" ); - } - if ( uvIndex == -1 ) { - uvIndex = index; + // attach sub-nodes (if any) + ConvertNodes(model->ID(), *last_parent, new_abs_transform); + + if (doc.Settings().readLights) { + ConvertLights(*model, original_name); + } + + if (doc.Settings().readCameras) { + ConvertCameras(*model, original_name); + } + + nodes.push_back(nodes_chain.front()); + nodes_chain.clear(); } } - if ( uvIndex == -1 ) { - FBXImporter::LogWarn( "failed to resolve UV channel " + uvSet + ", using first UV channel" ); - uvIndex = 0; + if (nodes.size()) { + parent.mChildren = new aiNode*[nodes.size()](); + parent.mNumChildren = static_cast(nodes.size()); + + std::swap_ranges(nodes.begin(), nodes.end(), parent.mChildren); + } + } + catch (std::exception&) { + Util::delete_fun deleter; + std::for_each(nodes.begin(), nodes.end(), deleter); + std::for_each(nodes_chain.begin(), nodes_chain.end(), deleter); + std::for_each(post_nodes_chain.begin(), post_nodes_chain.end(), deleter); + } + } + + + void FBXConverter::ConvertLights(const Model& model, const std::string &orig_name) { + const std::vector& node_attrs = model.GetAttributes(); + for (const NodeAttribute* attr : node_attrs) { + const Light* const light = dynamic_cast(attr); + if (light) { + ConvertLight(*light, orig_name); } } } - out_mat->AddProperty( &uvIndex, 1, _AI_MATKEY_UVWSRC_BASE, target, 0 ); - } -} - -void FBXConverter::TrySetTextureProperties( aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, - const std::string& propName, - aiTextureType target, const MeshGeometry* const mesh ) { - LayeredTextureMap::const_iterator it = layeredTextures.find( propName ); - if ( it == layeredTextures.end() ) { - return; - } - - int texCount = (*it).second->textureCount(); - - // Set the blend mode for layered textures - int blendmode= (*it).second->GetBlendMode(); - out_mat->AddProperty(&blendmode,1,_AI_MATKEY_TEXOP_BASE,target,0); - - for(int texIndex = 0; texIndex < texCount; texIndex++){ - - const Texture* const tex = ( *it ).second->getTexture(texIndex); - - aiString path = GetTexturePath(tex); - out_mat->AddProperty( &path, _AI_MATKEY_TEXTURE_BASE, target, texIndex ); - - aiUVTransform uvTrafo; - // XXX handle all kinds of UV transformations - uvTrafo.mScaling = tex->UVScaling(); - uvTrafo.mTranslation = tex->UVTranslation(); - out_mat->AddProperty( &uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, texIndex ); - - const PropertyTable& props = tex->Props(); - - int uvIndex = 0; - - bool ok; - const std::string& uvSet = PropertyGet( props, "UVSet", ok ); - if ( ok ) { - // "default" is the name which usually appears in the FbxFileTexture template - if ( uvSet != "default" && uvSet.length() ) { - // this is a bit awkward - we need to find a mesh that uses this - // material and scan its UV channels for the given UV name because - // assimp references UV channels by index, not by name. - - // XXX: the case that UV channels may appear in different orders - // in meshes is unhandled. A possible solution would be to sort - // the UV channels alphabetically, but this would have the side - // effect that the primary (first) UV channel would sometimes - // be moved, causing trouble when users read only the first - // UV channel and ignore UV channel assignments altogether. - - const unsigned int matIndex = static_cast( std::distance( materials.begin(), - std::find( materials.begin(), materials.end(), out_mat ) - ) ); - - uvIndex = -1; - if ( !mesh ) - { - for( const MeshMap::value_type& v : meshes_converted ) { - const MeshGeometry* const mesh = dynamic_cast ( v.first ); - if ( !mesh ) { - continue; - } - - const MatIndexArray& mats = mesh->GetMaterialIndices(); - if ( std::find( mats.begin(), mats.end(), matIndex ) == mats.end() ) { - continue; - } - - int index = -1; - for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) { - if ( mesh->GetTextureCoords( i ).empty() ) { - break; - } - const std::string& name = mesh->GetTextureCoordChannelName( i ); - if ( name == uvSet ) { - index = static_cast( i ); - break; - } - } - if ( index == -1 ) { - FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" ); - continue; - } - - if ( uvIndex == -1 ) { - uvIndex = index; - } - else { - FBXImporter::LogWarn( "the UV channel named " + uvSet + - " appears at different positions in meshes, results will be wrong" ); - } - } - } - else - { - int index = -1; - for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) { - if ( mesh->GetTextureCoords( i ).empty() ) { - break; - } - const std::string& name = mesh->GetTextureCoordChannelName( i ); - if ( name == uvSet ) { - index = static_cast( i ); - break; - } - } - if ( index == -1 ) { - FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" ); - } - - if ( uvIndex == -1 ) { - uvIndex = index; - } - } - - if ( uvIndex == -1 ) { - FBXImporter::LogWarn( "failed to resolve UV channel " + uvSet + ", using first UV channel" ); - uvIndex = 0; + void FBXConverter::ConvertCameras(const Model& model, const std::string &orig_name) { + const std::vector& node_attrs = model.GetAttributes(); + for (const NodeAttribute* attr : node_attrs) { + const Camera* const cam = dynamic_cast(attr); + if (cam) { + ConvertCamera(*cam, orig_name); } } } - out_mat->AddProperty( &uvIndex, 1, _AI_MATKEY_UVWSRC_BASE, target, texIndex ); - } -} + void FBXConverter::ConvertLight(const Light& light, const std::string &orig_name) { + lights.push_back(new aiLight()); + aiLight* const out_light = lights.back(); -void FBXConverter::SetTextureProperties( aiMaterial* out_mat, const TextureMap& textures, const MeshGeometry* const mesh ) -{ - TrySetTextureProperties( out_mat, textures, "DiffuseColor", aiTextureType_DIFFUSE, mesh ); - TrySetTextureProperties( out_mat, textures, "AmbientColor", aiTextureType_AMBIENT, mesh ); - TrySetTextureProperties( out_mat, textures, "EmissiveFactor", aiTextureType_EMISSIVE, mesh ); - TrySetTextureProperties( out_mat, textures, "SpecularFactor", aiTextureType_SPECULAR, mesh); - TrySetTextureProperties( out_mat, textures, "TransparencyFactor", aiTextureType_OPACITY, mesh ); - TrySetTextureProperties( out_mat, textures, "ReflectionColor", aiTextureType_REFLECTION, mesh ); - TrySetTextureProperties( out_mat, textures, "DisplacementColor", aiTextureType_DISPLACEMENT, mesh ); - TrySetTextureProperties( out_mat, textures, "NormalMap", aiTextureType_NORMALS, mesh ); - TrySetTextureProperties( out_mat, textures, "Bump", aiTextureType_HEIGHT, mesh ); - TrySetTextureProperties( out_mat, textures, "ShininessExponent", aiTextureType_SHININESS, mesh ); -} + out_light->mName.Set(orig_name); -void FBXConverter::SetTextureProperties( aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh ) -{ - TrySetTextureProperties( out_mat, layeredTextures, "DiffuseColor", aiTextureType_DIFFUSE, mesh ); - TrySetTextureProperties( out_mat, layeredTextures, "AmbientColor", aiTextureType_AMBIENT, mesh ); - TrySetTextureProperties( out_mat, layeredTextures, "EmissiveFactor", aiTextureType_EMISSIVE, mesh ); - TrySetTextureProperties( out_mat, layeredTextures, "SpecularFactor", aiTextureType_SPECULAR, mesh); - TrySetTextureProperties( out_mat, layeredTextures, "TransparencyFactor", aiTextureType_OPACITY, mesh ); - TrySetTextureProperties( out_mat, layeredTextures, "ReflectionColor", aiTextureType_REFLECTION, mesh ); - TrySetTextureProperties( out_mat, layeredTextures, "DisplacementColor", aiTextureType_DISPLACEMENT, mesh ); - TrySetTextureProperties( out_mat, layeredTextures, "NormalMap", aiTextureType_NORMALS, mesh ); - TrySetTextureProperties( out_mat, layeredTextures, "Bump", aiTextureType_HEIGHT, mesh ); - TrySetTextureProperties( out_mat, layeredTextures, "ShininessExponent", aiTextureType_SHININESS, mesh ); -} + const float intensity = light.Intensity() / 100.0f; + const aiVector3D& col = light.Color(); -aiColor3D FBXConverter::GetColorPropertyFactored( const PropertyTable& props, const std::string& colorName, - const std::string& factorName, bool& result, bool useTemplate ) -{ - result = true; + out_light->mColorDiffuse = aiColor3D(col.x, col.y, col.z); + out_light->mColorDiffuse.r *= intensity; + out_light->mColorDiffuse.g *= intensity; + out_light->mColorDiffuse.b *= intensity; - bool ok; - aiVector3D BaseColor = PropertyGet( props, colorName, ok, useTemplate ); - if ( ! ok ) { - result = false; - return aiColor3D( 0.0f, 0.0f, 0.0f ); - } + out_light->mColorSpecular = out_light->mColorDiffuse; - // if no factor name, return the colour as is - if ( factorName.empty() ) { - return aiColor3D( BaseColor.x, BaseColor.y, BaseColor.z ); - } + //lights are defined along negative y direction + out_light->mPosition = aiVector3D(0.0f); + out_light->mDirection = aiVector3D(0.0f, -1.0f, 0.0f); + out_light->mUp = aiVector3D(0.0f, 0.0f, -1.0f); - // otherwise it should be multiplied by the factor, if found. - float factor = PropertyGet( props, factorName, ok, useTemplate ); - if ( ok ) { - BaseColor *= factor; - } - return aiColor3D( BaseColor.x, BaseColor.y, BaseColor.z ); -} - -aiColor3D FBXConverter::GetColorPropertyFromMaterial( const PropertyTable& props, const std::string& baseName, - bool& result ) -{ - return GetColorPropertyFactored( props, baseName + "Color", baseName + "Factor", result, true ); -} - -aiColor3D FBXConverter::GetColorProperty( const PropertyTable& props, const std::string& colorName, - bool& result, bool useTemplate ) -{ - result = true; - bool ok; - const aiVector3D& ColorVec = PropertyGet( props, colorName, ok, useTemplate ); - if ( ! ok ) { - result = false; - return aiColor3D( 0.0f, 0.0f, 0.0f ); - } - return aiColor3D( ColorVec.x, ColorVec.y, ColorVec.z ); -} - -void FBXConverter::SetShadingPropertiesCommon( aiMaterial* out_mat, const PropertyTable& props ) -{ - // Set shading properties. - // Modern FBX Files have two separate systems for defining these, - // with only the more comprehensive one described in the property template. - // Likely the other values are a legacy system, - // which is still always exported by the official FBX SDK. - // - // Blender's FBX import and export mostly ignore this legacy system, - // and as we only support recent versions of FBX anyway, we can do the same. - bool ok; - - const aiColor3D& Diffuse = GetColorPropertyFromMaterial( props, "Diffuse", ok ); - if ( ok ) { - out_mat->AddProperty( &Diffuse, 1, AI_MATKEY_COLOR_DIFFUSE ); - } - - const aiColor3D& Emissive = GetColorPropertyFromMaterial( props, "Emissive", ok ); - if ( ok ) { - out_mat->AddProperty( &Emissive, 1, AI_MATKEY_COLOR_EMISSIVE ); - } - - const aiColor3D& Ambient = GetColorPropertyFromMaterial( props, "Ambient", ok ); - if ( ok ) { - out_mat->AddProperty( &Ambient, 1, AI_MATKEY_COLOR_AMBIENT ); - } - - // we store specular factor as SHININESS_STRENGTH, so just get the color - const aiColor3D& Specular = GetColorProperty( props, "SpecularColor", ok, true ); - if ( ok ) { - out_mat->AddProperty( &Specular, 1, AI_MATKEY_COLOR_SPECULAR ); - } - - // and also try to get SHININESS_STRENGTH - const float SpecularFactor = PropertyGet( props, "SpecularFactor", ok, true ); - if ( ok ) { - out_mat->AddProperty( &SpecularFactor, 1, AI_MATKEY_SHININESS_STRENGTH ); - } - - // and the specular exponent - const float ShininessExponent = PropertyGet( props, "ShininessExponent", ok ); - if ( ok ) { - out_mat->AddProperty( &ShininessExponent, 1, AI_MATKEY_SHININESS ); - } - - // TransparentColor / TransparencyFactor... gee thanks FBX :rolleyes: - const aiColor3D& Transparent = GetColorPropertyFactored( props, "TransparentColor", "TransparencyFactor", ok ); - float CalculatedOpacity = 1.0f; - if ( ok ) { - out_mat->AddProperty( &Transparent, 1, AI_MATKEY_COLOR_TRANSPARENT ); - // as calculated by FBX SDK 2017: - CalculatedOpacity = 1.0f - ((Transparent.r + Transparent.g + Transparent.b) / 3.0f); - } - - // use of TransparencyFactor is inconsistent. - // Maya always stores it as 1.0, - // so we can't use it to set AI_MATKEY_OPACITY. - // Blender is more sensible and stores it as the alpha value. - // However both the FBX SDK and Blender always write an additional - // legacy "Opacity" field, so we can try to use that. - // - // If we can't find it, - // we can fall back to the value which the FBX SDK calculates - // from transparency colour (RGB) and factor (F) as - // 1.0 - F*((R+G+B)/3). - // - // There's no consistent way to interpret this opacity value, - // so it's up to clients to do the correct thing. - const float Opacity = PropertyGet( props, "Opacity", ok ); - if ( ok ) { - out_mat->AddProperty( &Opacity, 1, AI_MATKEY_OPACITY ); - } - else if ( CalculatedOpacity != 1.0 ) { - out_mat->AddProperty( &CalculatedOpacity, 1, AI_MATKEY_OPACITY ); - } - - // reflection color and factor are stored separately - const aiColor3D& Reflection = GetColorProperty( props, "ReflectionColor", ok, true ); - if ( ok ) { - out_mat->AddProperty( &Reflection, 1, AI_MATKEY_COLOR_REFLECTIVE ); - } - - float ReflectionFactor = PropertyGet( props, "ReflectionFactor", ok, true ); - if ( ok ) { - out_mat->AddProperty( &ReflectionFactor, 1, AI_MATKEY_REFLECTIVITY ); - } - - const float BumpFactor = PropertyGet(props, "BumpFactor", ok); - if (ok) { - out_mat->AddProperty(&BumpFactor, 1, AI_MATKEY_BUMPSCALING); - } - - const float DispFactor = PropertyGet(props, "DisplacementFactor", ok); - if (ok) { - out_mat->AddProperty(&DispFactor, 1, "$mat.displacementscaling", 0, 0); - } -} - - -double FBXConverter::FrameRateToDouble( FileGlobalSettings::FrameRate fp, double customFPSVal ) { - switch ( fp ) { - case FileGlobalSettings::FrameRate_DEFAULT: - return 1.0; - - case FileGlobalSettings::FrameRate_120: - return 120.0; - - case FileGlobalSettings::FrameRate_100: - return 100.0; - - case FileGlobalSettings::FrameRate_60: - return 60.0; - - case FileGlobalSettings::FrameRate_50: - return 50.0; - - case FileGlobalSettings::FrameRate_48: - return 48.0; - - case FileGlobalSettings::FrameRate_30: - case FileGlobalSettings::FrameRate_30_DROP: - return 30.0; - - case FileGlobalSettings::FrameRate_NTSC_DROP_FRAME: - case FileGlobalSettings::FrameRate_NTSC_FULL_FRAME: - return 29.9700262; - - case FileGlobalSettings::FrameRate_PAL: - return 25.0; - - case FileGlobalSettings::FrameRate_CINEMA: - return 24.0; - - case FileGlobalSettings::FrameRate_1000: - return 1000.0; - - case FileGlobalSettings::FrameRate_CINEMA_ND: - return 23.976; - - case FileGlobalSettings::FrameRate_CUSTOM: - return customFPSVal; - - case FileGlobalSettings::FrameRate_MAX: // this is to silence compiler warnings - break; - } - - ai_assert( false ); - - return -1.0f; -} - - -void FBXConverter::ConvertAnimations() -{ - // first of all determine framerate - const FileGlobalSettings::FrameRate fps = doc.GlobalSettings().TimeMode(); - const float custom = doc.GlobalSettings().CustomFrameRate(); - anim_fps = FrameRateToDouble( fps, custom ); - - const std::vector& animations = doc.AnimationStacks(); - for( const AnimationStack* stack : animations ) { - ConvertAnimationStack( *stack ); - } -} - -std::string FBXConverter::FixNodeName( const std::string& name ) { - // strip Model:: prefix, avoiding ambiguities (i.e. don't strip if - // this causes ambiguities, well possible between empty identifiers, - // such as "Model::" and ""). Make sure the behaviour is consistent - // across multiple calls to FixNodeName(). - if ( name.substr( 0, 7 ) == "Model::" ) { - std::string temp = name.substr( 7 ); - return temp; - } - - return name; -} - -void FBXConverter::ConvertAnimationStack( const AnimationStack& st ) -{ - const AnimationLayerList& layers = st.Layers(); - if ( layers.empty() ) { - return; - } - - aiAnimation* const anim = new aiAnimation(); - animations.push_back( anim ); - - // strip AnimationStack:: prefix - std::string name = st.Name(); - if ( name.substr( 0, 16 ) == "AnimationStack::" ) { - name = name.substr( 16 ); - } - else if ( name.substr( 0, 11 ) == "AnimStack::" ) { - name = name.substr( 11 ); - } - - anim->mName.Set( name ); - - // need to find all nodes for which we need to generate node animations - - // it may happen that we need to merge multiple layers, though. - NodeMap node_map; - - // reverse mapping from curves to layers, much faster than querying - // the FBX DOM for it. - LayerMap layer_map; - - const char* prop_whitelist[] = { - "Lcl Scaling", - "Lcl Rotation", - "Lcl Translation" - }; - - for( const AnimationLayer* layer : layers ) { - ai_assert( layer ); - - const AnimationCurveNodeList& nodes = layer->Nodes( prop_whitelist, 3 ); - for( const AnimationCurveNode* node : nodes ) { - ai_assert( node ); - - const Model* const model = dynamic_cast( node->Target() ); - // this can happen - it could also be a NodeAttribute (i.e. for camera animations) - if ( !model ) { - continue; - } - - const std::string& name = FixNodeName( model->Name() ); - node_map[ name ].push_back( node ); - - layer_map[ node ] = layer; - } - } - - // generate node animations - std::vector node_anims; - - double min_time = 1e10; - double max_time = -1e10; - - int64_t start_time = st.LocalStart(); - int64_t stop_time = st.LocalStop(); - bool has_local_startstop = start_time != 0 || stop_time != 0; - if ( !has_local_startstop ) { - // no time range given, so accept every keyframe and use the actual min/max time - // the numbers are INT64_MIN/MAX, the 20000 is for safety because GenerateNodeAnimations uses an epsilon of 10000 - start_time = -9223372036854775807ll + 20000; - stop_time = 9223372036854775807ll - 20000; - } - - try { - for( const NodeMap::value_type& kv : node_map ) { - GenerateNodeAnimations( node_anims, - kv.first, - kv.second, - layer_map, - start_time, stop_time, - max_time, - min_time ); - } - } - catch ( std::exception& ) { - std::for_each( node_anims.begin(), node_anims.end(), Util::delete_fun() ); - throw; - } - - if ( node_anims.size() ) { - anim->mChannels = new aiNodeAnim*[ node_anims.size() ](); - anim->mNumChannels = static_cast( node_anims.size() ); - - std::swap_ranges( node_anims.begin(), node_anims.end(), anim->mChannels ); - } - else { - // empty animations would fail validation, so drop them - delete anim; - animations.pop_back(); - FBXImporter::LogInfo( "ignoring empty AnimationStack (using IK?): " + name ); - return; - } - - double start_time_fps = has_local_startstop ? (CONVERT_FBX_TIME(start_time) * anim_fps) : min_time; - double stop_time_fps = has_local_startstop ? (CONVERT_FBX_TIME(stop_time) * anim_fps) : max_time; - - // adjust relative timing for animation - for ( unsigned int c = 0; c < anim->mNumChannels; c++ ) { - aiNodeAnim* channel = anim->mChannels[ c ]; - for ( uint32_t i = 0; i < channel->mNumPositionKeys; i++ ) - channel->mPositionKeys[ i ].mTime -= start_time_fps; - for ( uint32_t i = 0; i < channel->mNumRotationKeys; i++ ) - channel->mRotationKeys[ i ].mTime -= start_time_fps; - for ( uint32_t i = 0; i < channel->mNumScalingKeys; i++ ) - channel->mScalingKeys[ i ].mTime -= start_time_fps; - } - - // for some mysterious reason, mDuration is simply the maximum key -- the - // validator always assumes animations to start at zero. - anim->mDuration = stop_time_fps - start_time_fps; - anim->mTicksPerSecond = anim_fps; -} - -#ifdef ASSIMP_BUILD_DEBUG -// ------------------------------------------------------------------------------------------------ -// sanity check whether the input is ok -static void validateAnimCurveNodes( const std::vector& curves, - bool strictMode ) { - const Object* target( NULL ); - for( const AnimationCurveNode* node : curves ) { - if ( !target ) { - target = node->Target(); - } - if ( node->Target() != target ) { - FBXImporter::LogWarn( "Node target is nullptr type." ); - } - if ( strictMode ) { - ai_assert( node->Target() == target ); - } - } -} -#endif // ASSIMP_BUILD_DEBUG - -// ------------------------------------------------------------------------------------------------ -void FBXConverter::GenerateNodeAnimations( std::vector& node_anims, - const std::string& fixed_name, - const std::vector& curves, - const LayerMap& layer_map, - int64_t start, int64_t stop, - double& max_time, - double& min_time ) -{ - - NodeMap node_property_map; - ai_assert( curves.size() ); - -#ifdef ASSIMP_BUILD_DEBUG - validateAnimCurveNodes( curves, doc.Settings().strictMode ); -#endif - const AnimationCurveNode* curve_node = NULL; - for( const AnimationCurveNode* node : curves ) { - ai_assert( node ); - - if ( node->TargetProperty().empty() ) { - FBXImporter::LogWarn( "target property for animation curve not set: " + node->Name() ); - continue; - } - - curve_node = node; - if ( node->Curves().empty() ) { - FBXImporter::LogWarn( "no animation curves assigned to AnimationCurveNode: " + node->Name() ); - continue; - } - - node_property_map[ node->TargetProperty() ].push_back( node ); - } - - ai_assert( curve_node ); - ai_assert( curve_node->TargetAsModel() ); - - const Model& target = *curve_node->TargetAsModel(); - - // check for all possible transformation components - NodeMap::const_iterator chain[ TransformationComp_MAXIMUM ]; - - bool has_any = false; - bool has_complex = false; - - for ( size_t i = 0; i < TransformationComp_MAXIMUM; ++i ) { - const TransformationComp comp = static_cast( i ); - - // inverse pivots don't exist in the input, we just generate them - if ( comp == TransformationComp_RotationPivotInverse || comp == TransformationComp_ScalingPivotInverse ) { - chain[ i ] = node_property_map.end(); - continue; - } - - chain[ i ] = node_property_map.find( NameTransformationCompProperty( comp ) ); - if ( chain[ i ] != node_property_map.end() ) { - - // check if this curves contains redundant information by looking - // up the corresponding node's transformation chain. - if ( doc.Settings().optimizeEmptyAnimationCurves && - IsRedundantAnimationData( target, comp, ( *chain[ i ] ).second ) ) { - - FBXImporter::LogDebug( "dropping redundant animation channel for node " + target.Name() ); - continue; - } - - has_any = true; - - if ( comp != TransformationComp_Rotation && comp != TransformationComp_Scaling && comp != TransformationComp_Translation ) + switch (light.LightType()) { - has_complex = true; - } - } - } - - if ( !has_any ) { - FBXImporter::LogWarn( "ignoring node animation, did not find any transformation key frames" ); - return; - } - - // this needs to play nicely with GenerateTransformationNodeChain() which will - // be invoked _later_ (animations come first). If this node has only rotation, - // scaling and translation _and_ there are no animated other components either, - // we can use a single node and also a single node animation channel. - if ( !has_complex && !NeedsComplexTransformationChain( target ) ) { - - aiNodeAnim* const nd = GenerateSimpleNodeAnim( fixed_name, target, chain, - node_property_map.end(), - layer_map, - start, stop, - max_time, - min_time, - true // input is TRS order, assimp is SRT - ); - - ai_assert( nd ); - if ( nd->mNumPositionKeys == 0 && nd->mNumRotationKeys == 0 && nd->mNumScalingKeys == 0 ) { - delete nd; - } - else { - node_anims.push_back( nd ); - } - return; - } - - // otherwise, things get gruesome and we need separate animation channels - // for each part of the transformation chain. Remember which channels - // we generated and pass this information to the node conversion - // code to avoid nodes that have identity transform, but non-identity - // animations, being dropped. - unsigned int flags = 0, bit = 0x1; - for ( size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1 ) { - const TransformationComp comp = static_cast( i ); - - if ( chain[ i ] != node_property_map.end() ) { - flags |= bit; - - ai_assert( comp != TransformationComp_RotationPivotInverse ); - ai_assert( comp != TransformationComp_ScalingPivotInverse ); - - const std::string& chain_name = NameTransformationChainNode( fixed_name, comp ); - - aiNodeAnim* na = nullptr; - switch ( comp ) - { - case TransformationComp_Rotation: - case TransformationComp_PreRotation: - case TransformationComp_PostRotation: - case TransformationComp_GeometricRotation: - na = GenerateRotationNodeAnim( chain_name, - target, - ( *chain[ i ] ).second, - layer_map, - start, stop, - max_time, - min_time ); - + case Light::Type_Point: + out_light->mType = aiLightSource_POINT; break; - case TransformationComp_RotationOffset: - case TransformationComp_RotationPivot: - case TransformationComp_ScalingOffset: - case TransformationComp_ScalingPivot: + case Light::Type_Directional: + out_light->mType = aiLightSource_DIRECTIONAL; + break; + + case Light::Type_Spot: + out_light->mType = aiLightSource_SPOT; + out_light->mAngleOuterCone = AI_DEG_TO_RAD(light.OuterAngle()); + out_light->mAngleInnerCone = AI_DEG_TO_RAD(light.InnerAngle()); + break; + + case Light::Type_Area: + FBXImporter::LogWarn("cannot represent area light, set to UNDEFINED"); + out_light->mType = aiLightSource_UNDEFINED; + break; + + case Light::Type_Volume: + FBXImporter::LogWarn("cannot represent volume light, set to UNDEFINED"); + out_light->mType = aiLightSource_UNDEFINED; + break; + default: + ai_assert(false); + } + + float decay = light.DecayStart(); + switch (light.DecayType()) + { + case Light::Decay_None: + out_light->mAttenuationConstant = decay; + out_light->mAttenuationLinear = 0.0f; + out_light->mAttenuationQuadratic = 0.0f; + break; + case Light::Decay_Linear: + out_light->mAttenuationConstant = 0.0f; + out_light->mAttenuationLinear = 2.0f / decay; + out_light->mAttenuationQuadratic = 0.0f; + break; + case Light::Decay_Quadratic: + out_light->mAttenuationConstant = 0.0f; + out_light->mAttenuationLinear = 0.0f; + out_light->mAttenuationQuadratic = 2.0f / (decay * decay); + break; + case Light::Decay_Cubic: + FBXImporter::LogWarn("cannot represent cubic attenuation, set to Quadratic"); + out_light->mAttenuationQuadratic = 1.0f; + break; + default: + ai_assert(false); + } + } + + void FBXConverter::ConvertCamera(const Camera& cam, const std::string &orig_name) + { + cameras.push_back(new aiCamera()); + aiCamera* const out_camera = cameras.back(); + + out_camera->mName.Set(orig_name); + + out_camera->mAspect = cam.AspectWidth() / cam.AspectHeight(); + + //cameras are defined along positive x direction + out_camera->mPosition = cam.Position(); + out_camera->mLookAt = (cam.InterestPosition() - out_camera->mPosition).Normalize(); + out_camera->mUp = cam.UpVector(); + + out_camera->mHorizontalFOV = AI_DEG_TO_RAD(cam.FieldOfView()); + out_camera->mClipPlaneNear = cam.NearPlane(); + out_camera->mClipPlaneFar = cam.FarPlane(); + } + + void FBXConverter::GetUniqueName(const std::string &name, std::string &uniqueName) + { + int i = 0; + uniqueName = name; + while (mNodeNames.find(uniqueName) != mNodeNames.end()) + { + ++i; + std::stringstream ext; + ext << name << std::setfill('0') << std::setw(3) << i; + uniqueName = ext.str(); + } + mNodeNames.insert(uniqueName); + } + + + const char* FBXConverter::NameTransformationComp(TransformationComp comp) { + switch (comp) { case TransformationComp_Translation: + return "Translation"; + case TransformationComp_RotationOffset: + return "RotationOffset"; + case TransformationComp_RotationPivot: + return "RotationPivot"; + case TransformationComp_PreRotation: + return "PreRotation"; + case TransformationComp_Rotation: + return "Rotation"; + case TransformationComp_PostRotation: + return "PostRotation"; + case TransformationComp_RotationPivotInverse: + return "RotationPivotInverse"; + case TransformationComp_ScalingOffset: + return "ScalingOffset"; + case TransformationComp_ScalingPivot: + return "ScalingPivot"; + case TransformationComp_Scaling: + return "Scaling"; + case TransformationComp_ScalingPivotInverse: + return "ScalingPivotInverse"; + case TransformationComp_GeometricScaling: + return "GeometricScaling"; + case TransformationComp_GeometricRotation: + return "GeometricRotation"; case TransformationComp_GeometricTranslation: - na = GenerateTranslationNodeAnim( chain_name, - target, - ( *chain[ i ] ).second, - layer_map, - start, stop, - max_time, - min_time ); + return "GeometricTranslation"; + case TransformationComp_GeometricScalingInverse: + return "GeometricScalingInverse"; + case TransformationComp_GeometricRotationInverse: + return "GeometricRotationInverse"; + case TransformationComp_GeometricTranslationInverse: + return "GeometricTranslationInverse"; + case TransformationComp_MAXIMUM: // this is to silence compiler warnings + default: + break; + } - // pivoting requires us to generate an implicit inverse channel to undo the pivot translation - if ( comp == TransformationComp_RotationPivot ) { - const std::string& invName = NameTransformationChainNode( fixed_name, - TransformationComp_RotationPivotInverse ); + ai_assert(false); - aiNodeAnim* const inv = GenerateTranslationNodeAnim( invName, - target, - ( *chain[ i ] ).second, - layer_map, - start, stop, - max_time, - min_time, - true ); + return nullptr; + } - ai_assert( inv ); - if ( inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0 ) { - delete inv; - } - else { - node_anims.push_back( inv ); - } + const char* FBXConverter::NameTransformationCompProperty(TransformationComp comp) { + switch (comp) { + case TransformationComp_Translation: + return "Lcl Translation"; + case TransformationComp_RotationOffset: + return "RotationOffset"; + case TransformationComp_RotationPivot: + return "RotationPivot"; + case TransformationComp_PreRotation: + return "PreRotation"; + case TransformationComp_Rotation: + return "Lcl Rotation"; + case TransformationComp_PostRotation: + return "PostRotation"; + case TransformationComp_RotationPivotInverse: + return "RotationPivotInverse"; + case TransformationComp_ScalingOffset: + return "ScalingOffset"; + case TransformationComp_ScalingPivot: + return "ScalingPivot"; + case TransformationComp_Scaling: + return "Lcl Scaling"; + case TransformationComp_ScalingPivotInverse: + return "ScalingPivotInverse"; + case TransformationComp_GeometricScaling: + return "GeometricScaling"; + case TransformationComp_GeometricRotation: + return "GeometricRotation"; + case TransformationComp_GeometricTranslation: + return "GeometricTranslation"; + case TransformationComp_GeometricScalingInverse: + return "GeometricScalingInverse"; + case TransformationComp_GeometricRotationInverse: + return "GeometricRotationInverse"; + case TransformationComp_GeometricTranslationInverse: + return "GeometricTranslationInverse"; + case TransformationComp_MAXIMUM: // this is to silence compiler warnings + break; + } - ai_assert( TransformationComp_RotationPivotInverse > i ); - flags |= bit << ( TransformationComp_RotationPivotInverse - i ); - } - else if ( comp == TransformationComp_ScalingPivot ) { - const std::string& invName = NameTransformationChainNode( fixed_name, - TransformationComp_ScalingPivotInverse ); + ai_assert(false); - aiNodeAnim* const inv = GenerateTranslationNodeAnim( invName, - target, - ( *chain[ i ] ).second, - layer_map, - start, stop, - max_time, - min_time, - true ); + return nullptr; + } - ai_assert( inv ); - if ( inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0 ) { - delete inv; - } - else { - node_anims.push_back( inv ); - } + aiVector3D FBXConverter::TransformationCompDefaultValue(TransformationComp comp) + { + // XXX a neat way to solve the never-ending special cases for scaling + // would be to do everything in log space! + return comp == TransformationComp_Scaling ? aiVector3D(1.f, 1.f, 1.f) : aiVector3D(); + } - ai_assert( TransformationComp_RotationPivotInverse > i ); - flags |= bit << ( TransformationComp_RotationPivotInverse - i ); - } + void FBXConverter::GetRotationMatrix(Model::RotOrder mode, const aiVector3D& rotation, aiMatrix4x4& out) + { + if (mode == Model::RotOrder_SphericXYZ) { + FBXImporter::LogError("Unsupported RotationMode: SphericXYZ"); + out = aiMatrix4x4(); + return; + } + const float angle_epsilon = 1e-6f; + + out = aiMatrix4x4(); + + bool is_id[3] = { true, true, true }; + + aiMatrix4x4 temp[3]; + if (std::fabs(rotation.z) > angle_epsilon) { + aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(rotation.z), temp[2]); + is_id[2] = false; + } + if (std::fabs(rotation.y) > angle_epsilon) { + aiMatrix4x4::RotationY(AI_DEG_TO_RAD(rotation.y), temp[1]); + is_id[1] = false; + } + if (std::fabs(rotation.x) > angle_epsilon) { + aiMatrix4x4::RotationX(AI_DEG_TO_RAD(rotation.x), temp[0]); + is_id[0] = false; + } + + int order[3] = { -1, -1, -1 }; + + // note: rotation order is inverted since we're left multiplying as is usual in assimp + switch (mode) + { + case Model::RotOrder_EulerXYZ: + order[0] = 2; + order[1] = 1; + order[2] = 0; break; - case TransformationComp_Scaling: - case TransformationComp_GeometricScaling: - na = GenerateScalingNodeAnim( chain_name, - target, - ( *chain[ i ] ).second, - layer_map, - start, stop, - max_time, - min_time ); + case Model::RotOrder_EulerXZY: + order[0] = 1; + order[1] = 2; + order[2] = 0; + break; + case Model::RotOrder_EulerYZX: + order[0] = 0; + order[1] = 2; + order[2] = 1; + break; + + case Model::RotOrder_EulerYXZ: + order[0] = 2; + order[1] = 0; + order[2] = 1; + break; + + case Model::RotOrder_EulerZXY: + order[0] = 1; + order[1] = 0; + order[2] = 2; + break; + + case Model::RotOrder_EulerZYX: + order[0] = 0; + order[1] = 1; + order[2] = 2; break; default: - ai_assert( false ); + ai_assert(false); + break; } - ai_assert( na ); - if ( na->mNumPositionKeys == 0 && na->mNumRotationKeys == 0 && na->mNumScalingKeys == 0 ) { - delete na; + ai_assert(order[0] >= 0); + ai_assert(order[0] <= 2); + ai_assert(order[1] >= 0); + ai_assert(order[1] <= 2); + ai_assert(order[2] >= 0); + ai_assert(order[2] <= 2); + + if (!is_id[order[0]]) { + out = temp[order[0]]; } - else { - node_anims.push_back( na ); + + if (!is_id[order[1]]) { + out = out * temp[order[1]]; } - continue; - } - } - node_anim_chain_bits[ fixed_name ] = flags; -} - -bool FBXConverter::IsRedundantAnimationData( const Model& target, - TransformationComp comp, - const std::vector& curves ) { - ai_assert( curves.size() ); - - // look for animation nodes with - // * sub channels for all relevant components set - // * one key/value pair per component - // * combined values match up the corresponding value in the bind pose node transformation - // only such nodes are 'redundant' for this function. - - if ( curves.size() > 1 ) { - return false; - } - - const AnimationCurveNode& nd = *curves.front(); - const AnimationCurveMap& sub_curves = nd.Curves(); - - const AnimationCurveMap::const_iterator dx = sub_curves.find( "d|X" ); - const AnimationCurveMap::const_iterator dy = sub_curves.find( "d|Y" ); - const AnimationCurveMap::const_iterator dz = sub_curves.find( "d|Z" ); - - if ( dx == sub_curves.end() || dy == sub_curves.end() || dz == sub_curves.end() ) { - return false; - } - - const KeyValueList& vx = ( *dx ).second->GetValues(); - const KeyValueList& vy = ( *dy ).second->GetValues(); - const KeyValueList& vz = ( *dz ).second->GetValues(); - - if ( vx.size() != 1 || vy.size() != 1 || vz.size() != 1 ) { - return false; - } - - const aiVector3D dyn_val = aiVector3D( vx[ 0 ], vy[ 0 ], vz[ 0 ] ); - const aiVector3D& static_val = PropertyGet( target.Props(), - NameTransformationCompProperty( comp ), - TransformationCompDefaultValue( comp ) - ); - - const float epsilon = 1e-6f; - return ( dyn_val - static_val ).SquareLength() < epsilon; -} - - -aiNodeAnim* FBXConverter::GenerateRotationNodeAnim( const std::string& name, - const Model& target, - const std::vector& curves, - const LayerMap& layer_map, - int64_t start, int64_t stop, - double& max_time, - double& min_time ) -{ - std::unique_ptr na( new aiNodeAnim() ); - na->mNodeName.Set( name ); - - ConvertRotationKeys( na.get(), curves, layer_map, start, stop, max_time, min_time, target.RotationOrder() ); - - // dummy scaling key - na->mScalingKeys = new aiVectorKey[ 1 ]; - na->mNumScalingKeys = 1; - - na->mScalingKeys[ 0 ].mTime = 0.; - na->mScalingKeys[ 0 ].mValue = aiVector3D( 1.0f, 1.0f, 1.0f ); - - // dummy position key - na->mPositionKeys = new aiVectorKey[ 1 ]; - na->mNumPositionKeys = 1; - - na->mPositionKeys[ 0 ].mTime = 0.; - na->mPositionKeys[ 0 ].mValue = aiVector3D(); - - return na.release(); -} - -aiNodeAnim* FBXConverter::GenerateScalingNodeAnim( const std::string& name, - const Model& /*target*/, - const std::vector& curves, - const LayerMap& layer_map, - int64_t start, int64_t stop, - double& max_time, - double& min_time ) -{ - std::unique_ptr na( new aiNodeAnim() ); - na->mNodeName.Set( name ); - - ConvertScaleKeys( na.get(), curves, layer_map, start, stop, max_time, min_time ); - - // dummy rotation key - na->mRotationKeys = new aiQuatKey[ 1 ]; - na->mNumRotationKeys = 1; - - na->mRotationKeys[ 0 ].mTime = 0.; - na->mRotationKeys[ 0 ].mValue = aiQuaternion(); - - // dummy position key - na->mPositionKeys = new aiVectorKey[ 1 ]; - na->mNumPositionKeys = 1; - - na->mPositionKeys[ 0 ].mTime = 0.; - na->mPositionKeys[ 0 ].mValue = aiVector3D(); - - return na.release(); -} - -aiNodeAnim* FBXConverter::GenerateTranslationNodeAnim( const std::string& name, - const Model& /*target*/, - const std::vector& curves, - const LayerMap& layer_map, - int64_t start, int64_t stop, - double& max_time, - double& min_time, - bool inverse ) { - std::unique_ptr na( new aiNodeAnim() ); - na->mNodeName.Set( name ); - - ConvertTranslationKeys( na.get(), curves, layer_map, start, stop, max_time, min_time ); - - if ( inverse ) { - for ( unsigned int i = 0; i < na->mNumPositionKeys; ++i ) { - na->mPositionKeys[ i ].mValue *= -1.0f; - } - } - - // dummy scaling key - na->mScalingKeys = new aiVectorKey[ 1 ]; - na->mNumScalingKeys = 1; - - na->mScalingKeys[ 0 ].mTime = 0.; - na->mScalingKeys[ 0 ].mValue = aiVector3D( 1.0f, 1.0f, 1.0f ); - - // dummy rotation key - na->mRotationKeys = new aiQuatKey[ 1 ]; - na->mNumRotationKeys = 1; - - na->mRotationKeys[ 0 ].mTime = 0.; - na->mRotationKeys[ 0 ].mValue = aiQuaternion(); - - return na.release(); -} - -aiNodeAnim* FBXConverter::GenerateSimpleNodeAnim( const std::string& name, - const Model& target, - NodeMap::const_iterator chain[ TransformationComp_MAXIMUM ], - NodeMap::const_iterator iter_end, - const LayerMap& layer_map, - int64_t start, int64_t stop, - double& max_time, - double& min_time, - bool reverse_order ) - -{ - std::unique_ptr na( new aiNodeAnim() ); - na->mNodeName.Set( name ); - - const PropertyTable& props = target.Props(); - - // need to convert from TRS order to SRT? - if ( reverse_order ) { - - aiVector3D def_scale = PropertyGet( props, "Lcl Scaling", aiVector3D( 1.f, 1.f, 1.f ) ); - aiVector3D def_translate = PropertyGet( props, "Lcl Translation", aiVector3D( 0.f, 0.f, 0.f ) ); - aiVector3D def_rot = PropertyGet( props, "Lcl Rotation", aiVector3D( 0.f, 0.f, 0.f ) ); - - KeyFrameListList scaling; - KeyFrameListList translation; - KeyFrameListList rotation; - - if ( chain[ TransformationComp_Scaling ] != iter_end ) { - scaling = GetKeyframeList( ( *chain[ TransformationComp_Scaling ] ).second, start, stop ); + if (!is_id[order[2]]) { + out = out * temp[order[2]]; + } } - if ( chain[ TransformationComp_Translation ] != iter_end ) { - translation = GetKeyframeList( ( *chain[ TransformationComp_Translation ] ).second, start, stop ); - } - - if ( chain[ TransformationComp_Rotation ] != iter_end ) { - rotation = GetKeyframeList( ( *chain[ TransformationComp_Rotation ] ).second, start, stop ); - } - - KeyFrameListList joined; - joined.insert( joined.end(), scaling.begin(), scaling.end() ); - joined.insert( joined.end(), translation.begin(), translation.end() ); - joined.insert( joined.end(), rotation.begin(), rotation.end() ); - - const KeyTimeList& times = GetKeyTimeList( joined ); - - aiQuatKey* out_quat = new aiQuatKey[ times.size() ]; - aiVectorKey* out_scale = new aiVectorKey[ times.size() ]; - aiVectorKey* out_translation = new aiVectorKey[ times.size() ]; - - if ( times.size() ) + bool FBXConverter::NeedsComplexTransformationChain(const Model& model) { - ConvertTransformOrder_TRStoSRT( out_quat, out_scale, out_translation, - scaling, - translation, - rotation, - times, - max_time, - min_time, - target.RotationOrder(), - def_scale, - def_translate, - def_rot ); - } + const PropertyTable& props = model.Props(); + bool ok; - // XXX remove duplicates / redundant keys which this operation did - // likely produce if not all three channels were equally dense. + const float zero_epsilon = 1e-6f; + const aiVector3D all_ones(1.0f, 1.0f, 1.0f); + for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) { + const TransformationComp comp = static_cast(i); - na->mNumScalingKeys = static_cast( times.size() ); - na->mNumRotationKeys = na->mNumScalingKeys; - na->mNumPositionKeys = na->mNumScalingKeys; + if (comp == TransformationComp_Rotation || comp == TransformationComp_Scaling || comp == TransformationComp_Translation) { + continue; + } - na->mScalingKeys = out_scale; - na->mRotationKeys = out_quat; - na->mPositionKeys = out_translation; - } - else { + bool scale_compare = (comp == TransformationComp_GeometricScaling || comp == TransformationComp_Scaling); - // if a particular transformation is not given, grab it from - // the corresponding node to meet the semantics of aiNodeAnim, - // which requires all of rotation, scaling and translation - // to be set. - if ( chain[ TransformationComp_Scaling ] != iter_end ) { - ConvertScaleKeys( na.get(), ( *chain[ TransformationComp_Scaling ] ).second, - layer_map, - start, stop, - max_time, - min_time ); - } - else { - na->mScalingKeys = new aiVectorKey[ 1 ]; - na->mNumScalingKeys = 1; - - na->mScalingKeys[ 0 ].mTime = 0.; - na->mScalingKeys[ 0 ].mValue = PropertyGet( props, "Lcl Scaling", - aiVector3D( 1.f, 1.f, 1.f ) ); - } - - if ( chain[ TransformationComp_Rotation ] != iter_end ) { - ConvertRotationKeys( na.get(), ( *chain[ TransformationComp_Rotation ] ).second, - layer_map, - start, stop, - max_time, - min_time, - target.RotationOrder() ); - } - else { - na->mRotationKeys = new aiQuatKey[ 1 ]; - na->mNumRotationKeys = 1; - - na->mRotationKeys[ 0 ].mTime = 0.; - na->mRotationKeys[ 0 ].mValue = EulerToQuaternion( - PropertyGet( props, "Lcl Rotation", aiVector3D( 0.f, 0.f, 0.f ) ), - target.RotationOrder() ); - } - - if ( chain[ TransformationComp_Translation ] != iter_end ) { - ConvertTranslationKeys( na.get(), ( *chain[ TransformationComp_Translation ] ).second, - layer_map, - start, stop, - max_time, - min_time ); - } - else { - na->mPositionKeys = new aiVectorKey[ 1 ]; - na->mNumPositionKeys = 1; - - na->mPositionKeys[ 0 ].mTime = 0.; - na->mPositionKeys[ 0 ].mValue = PropertyGet( props, "Lcl Translation", - aiVector3D( 0.f, 0.f, 0.f ) ); - } - - } - return na.release(); -} - -FBXConverter::KeyFrameListList FBXConverter::GetKeyframeList( const std::vector& nodes, int64_t start, int64_t stop ) -{ - KeyFrameListList inputs; - inputs.reserve( nodes.size() * 3 ); - - //give some breathing room for rounding errors - int64_t adj_start = start - 10000; - int64_t adj_stop = stop + 10000; - - for( const AnimationCurveNode* node : nodes ) { - ai_assert( node ); - - const AnimationCurveMap& curves = node->Curves(); - for( const AnimationCurveMap::value_type& kv : curves ) { - - unsigned int mapto; - if ( kv.first == "d|X" ) { - mapto = 0; - } - else if ( kv.first == "d|Y" ) { - mapto = 1; - } - else if ( kv.first == "d|Z" ) { - mapto = 2; - } - else { - FBXImporter::LogWarn( "ignoring scale animation curve, did not recognize target component" ); - continue; - } - - const AnimationCurve* const curve = kv.second; - ai_assert( curve->GetKeys().size() == curve->GetValues().size() && curve->GetKeys().size() ); - - //get values within the start/stop time window - std::shared_ptr Keys( new KeyTimeList() ); - std::shared_ptr Values( new KeyValueList() ); - const size_t count = curve->GetKeys().size(); - Keys->reserve( count ); - Values->reserve( count ); - for (size_t n = 0; n < count; n++ ) - { - int64_t k = curve->GetKeys().at( n ); - if ( k >= adj_start && k <= adj_stop ) - { - Keys->push_back( k ); - Values->push_back( curve->GetValues().at( n ) ); + const aiVector3D& v = PropertyGet(props, NameTransformationCompProperty(comp), ok); + if (ok && scale_compare) { + if ((v - all_ones).SquareLength() > zero_epsilon) { + return true; + } + } + else if (ok) { + if (v.SquareLength() > zero_epsilon) { + return true; + } } } - inputs.push_back( std::make_tuple( Keys, Values, mapto ) ); - } - } - return inputs; // pray for NRVO :-) -} - - -KeyTimeList FBXConverter::GetKeyTimeList( const KeyFrameListList& inputs ) { - ai_assert( !inputs.empty() ); - - // reserve some space upfront - it is likely that the key-frame lists - // have matching time values, so max(of all key-frame lists) should - // be a good estimate. - KeyTimeList keys; - - size_t estimate = 0; - for( const KeyFrameList& kfl : inputs ) { - estimate = std::max( estimate, std::get<0>(kfl)->size() ); - } - - keys.reserve( estimate ); - - std::vector next_pos; - next_pos.resize( inputs.size(), 0 ); - - const size_t count = inputs.size(); - while ( true ) { - - int64_t min_tick = std::numeric_limits::max(); - for ( size_t i = 0; i < count; ++i ) { - const KeyFrameList& kfl = inputs[ i ]; - - if ( std::get<0>(kfl)->size() > next_pos[ i ] && std::get<0>(kfl)->at( next_pos[ i ] ) < min_tick ) { - min_tick = std::get<0>(kfl)->at( next_pos[ i ] ); - } + return false; } - if ( min_tick == std::numeric_limits::max() ) { - break; - } - keys.push_back( min_tick ); - - for ( size_t i = 0; i < count; ++i ) { - const KeyFrameList& kfl = inputs[ i ]; - - - while ( std::get<0>(kfl)->size() > next_pos[ i ] && std::get<0>(kfl)->at( next_pos[ i ] ) == min_tick ) { - ++next_pos[ i ]; - } - } - } - - return keys; -} - -void FBXConverter::InterpolateKeys( aiVectorKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs, - const aiVector3D& def_value, - double& max_time, - double& min_time ) { - ai_assert( !keys.empty() ); - ai_assert( nullptr != valOut ); - - std::vector next_pos; - const size_t count( inputs.size() ); - - next_pos.resize( inputs.size(), 0 ); - - for( KeyTimeList::value_type time : keys ) { - ai_real result[ 3 ] = { def_value.x, def_value.y, def_value.z }; - - for ( size_t i = 0; i < count; ++i ) { - const KeyFrameList& kfl = inputs[ i ]; - - const size_t ksize = std::get<0>(kfl)->size(); - if (ksize == 0) { - continue; - } - if ( ksize > next_pos[ i ] && std::get<0>(kfl)->at( next_pos[ i ] ) == time ) { - ++next_pos[ i ]; - } - - const size_t id0 = next_pos[ i ]>0 ? next_pos[ i ] - 1 : 0; - const size_t id1 = next_pos[ i ] == ksize ? ksize - 1 : next_pos[ i ]; - - // use lerp for interpolation - const KeyValueList::value_type valueA = std::get<1>(kfl)->at( id0 ); - const KeyValueList::value_type valueB = std::get<1>(kfl)->at( id1 ); - - const KeyTimeList::value_type timeA = std::get<0>(kfl)->at( id0 ); - const KeyTimeList::value_type timeB = std::get<0>(kfl)->at( id1 ); - - const ai_real factor = timeB == timeA ? ai_real(0.) : static_cast( ( time - timeA ) ) / ( timeB - timeA ); - const ai_real interpValue = static_cast( valueA + ( valueB - valueA ) * factor ); - - result[ std::get<2>(kfl) ] = interpValue; - } - - // magic value to convert fbx times to seconds - valOut->mTime = CONVERT_FBX_TIME( time ) * anim_fps; - - min_time = std::min( min_time, valOut->mTime ); - max_time = std::max( max_time, valOut->mTime ); - - valOut->mValue.x = result[ 0 ]; - valOut->mValue.y = result[ 1 ]; - valOut->mValue.z = result[ 2 ]; - - ++valOut; - } -} - -void FBXConverter::InterpolateKeys( aiQuatKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs, - const aiVector3D& def_value, - double& maxTime, - double& minTime, - Model::RotOrder order ) -{ - ai_assert( !keys.empty() ); - ai_assert( nullptr != valOut ); - - std::unique_ptr temp( new aiVectorKey[ keys.size() ] ); - InterpolateKeys( temp.get(), keys, inputs, def_value, maxTime, minTime ); - - aiMatrix4x4 m; - - aiQuaternion lastq; - - for ( size_t i = 0, c = keys.size(); i < c; ++i ) { - - valOut[ i ].mTime = temp[ i ].mTime; - - GetRotationMatrix( order, temp[ i ].mValue, m ); - aiQuaternion quat = aiQuaternion( aiMatrix3x3( m ) ); - - // take shortest path by checking the inner product - // http://www.3dkingdoms.com/weekly/weekly.php?a=36 - if ( quat.x * lastq.x + quat.y * lastq.y + quat.z * lastq.z + quat.w * lastq.w < 0 ) + std::string FBXConverter::NameTransformationChainNode(const std::string& name, TransformationComp comp) { - quat.x = -quat.x; - quat.y = -quat.y; - quat.z = -quat.z; - quat.w = -quat.w; + return name + std::string(MAGIC_NODE_TAG) + "_" + NameTransformationComp(comp); } - lastq = quat; - valOut[ i ].mValue = quat; - } -} + void FBXConverter::GenerateTransformationNodeChain(const Model& model, std::vector& output_nodes, + std::vector& post_output_nodes) { + const PropertyTable& props = model.Props(); + const Model::RotOrder rot = model.RotationOrder(); -void FBXConverter::ConvertTransformOrder_TRStoSRT( aiQuatKey* out_quat, aiVectorKey* out_scale, - aiVectorKey* out_translation, - const KeyFrameListList& scaling, - const KeyFrameListList& translation, - const KeyFrameListList& rotation, - const KeyTimeList& times, - double& maxTime, - double& minTime, - Model::RotOrder order, - const aiVector3D& def_scale, - const aiVector3D& def_translate, - const aiVector3D& def_rotation ) -{ - if ( rotation.size() ) { - InterpolateKeys( out_quat, times, rotation, def_rotation, maxTime, minTime, order ); - } - else { - for ( size_t i = 0; i < times.size(); ++i ) { - out_quat[ i ].mTime = CONVERT_FBX_TIME( times[ i ] ) * anim_fps; - out_quat[ i ].mValue = EulerToQuaternion( def_rotation, order ); + bool ok; + + aiMatrix4x4 chain[TransformationComp_MAXIMUM]; + std::fill_n(chain, static_cast(TransformationComp_MAXIMUM), aiMatrix4x4()); + + // generate transformation matrices for all the different transformation components + const float zero_epsilon = 1e-6f; + const aiVector3D all_ones(1.0f, 1.0f, 1.0f); + bool is_complex = false; + + const aiVector3D& PreRotation = PropertyGet(props, "PreRotation", ok); + if (ok && PreRotation.SquareLength() > zero_epsilon) { + is_complex = true; + + GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PreRotation, chain[TransformationComp_PreRotation]); + } + + const aiVector3D& PostRotation = PropertyGet(props, "PostRotation", ok); + if (ok && PostRotation.SquareLength() > zero_epsilon) { + is_complex = true; + + GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PostRotation, chain[TransformationComp_PostRotation]); + } + + const aiVector3D& RotationPivot = PropertyGet(props, "RotationPivot", ok); + if (ok && RotationPivot.SquareLength() > zero_epsilon) { + is_complex = true; + + aiMatrix4x4::Translation(RotationPivot, chain[TransformationComp_RotationPivot]); + aiMatrix4x4::Translation(-RotationPivot, chain[TransformationComp_RotationPivotInverse]); + } + + const aiVector3D& RotationOffset = PropertyGet(props, "RotationOffset", ok); + if (ok && RotationOffset.SquareLength() > zero_epsilon) { + is_complex = true; + + aiMatrix4x4::Translation(RotationOffset, chain[TransformationComp_RotationOffset]); + } + + const aiVector3D& ScalingOffset = PropertyGet(props, "ScalingOffset", ok); + if (ok && ScalingOffset.SquareLength() > zero_epsilon) { + is_complex = true; + + aiMatrix4x4::Translation(ScalingOffset, chain[TransformationComp_ScalingOffset]); + } + + const aiVector3D& ScalingPivot = PropertyGet(props, "ScalingPivot", ok); + if (ok && ScalingPivot.SquareLength() > zero_epsilon) { + is_complex = true; + + aiMatrix4x4::Translation(ScalingPivot, chain[TransformationComp_ScalingPivot]); + aiMatrix4x4::Translation(-ScalingPivot, chain[TransformationComp_ScalingPivotInverse]); + } + + const aiVector3D& Translation = PropertyGet(props, "Lcl Translation", ok); + if (ok && Translation.SquareLength() > zero_epsilon) { + aiMatrix4x4::Translation(Translation, chain[TransformationComp_Translation]); + } + + const aiVector3D& Scaling = PropertyGet(props, "Lcl Scaling", ok); + if (ok && (Scaling - all_ones).SquareLength() > zero_epsilon) { + aiMatrix4x4::Scaling(Scaling, chain[TransformationComp_Scaling]); + } + + const aiVector3D& Rotation = PropertyGet(props, "Lcl Rotation", ok); + if (ok && Rotation.SquareLength() > zero_epsilon) { + GetRotationMatrix(rot, Rotation, chain[TransformationComp_Rotation]); + } + + const aiVector3D& GeometricScaling = PropertyGet(props, "GeometricScaling", ok); + if (ok && (GeometricScaling - all_ones).SquareLength() > zero_epsilon) { + is_complex = true; + aiMatrix4x4::Scaling(GeometricScaling, chain[TransformationComp_GeometricScaling]); + aiVector3D GeometricScalingInverse = GeometricScaling; + bool canscale = true; + for (unsigned int i = 0; i < 3; ++i) { + if (std::fabs(GeometricScalingInverse[i]) > zero_epsilon) { + GeometricScalingInverse[i] = 1.0f / GeometricScaling[i]; + } + else { + FBXImporter::LogError("cannot invert geometric scaling matrix with a 0.0 scale component"); + canscale = false; + break; + } + } + if (canscale) { + aiMatrix4x4::Scaling(GeometricScalingInverse, chain[TransformationComp_GeometricScalingInverse]); + } + } + + const aiVector3D& GeometricRotation = PropertyGet(props, "GeometricRotation", ok); + if (ok && GeometricRotation.SquareLength() > zero_epsilon) { + is_complex = true; + GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotation]); + GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotationInverse]); + chain[TransformationComp_GeometricRotationInverse].Inverse(); + } + + const aiVector3D& GeometricTranslation = PropertyGet(props, "GeometricTranslation", ok); + if (ok && GeometricTranslation.SquareLength() > zero_epsilon) { + is_complex = true; + aiMatrix4x4::Translation(GeometricTranslation, chain[TransformationComp_GeometricTranslation]); + aiMatrix4x4::Translation(-GeometricTranslation, chain[TransformationComp_GeometricTranslationInverse]); + } + + // is_complex needs to be consistent with NeedsComplexTransformationChain() + // or the interplay between this code and the animation converter would + // not be guaranteed. + ai_assert(NeedsComplexTransformationChain(model) == is_complex); + + std::string name = FixNodeName(model.Name()); + + // now, if we have more than just Translation, Scaling and Rotation, + // we need to generate a full node chain to accommodate for assimp's + // lack to express pivots and offsets. + if (is_complex && doc.Settings().preservePivots) { + FBXImporter::LogInfo("generating full transformation chain for node: " + name); + + // query the anim_chain_bits dictionary to find out which chain elements + // have associated node animation channels. These can not be dropped + // even if they have identity transform in bind pose. + NodeAnimBitMap::const_iterator it = node_anim_chain_bits.find(name); + const unsigned int anim_chain_bitmask = (it == node_anim_chain_bits.end() ? 0 : (*it).second); + + unsigned int bit = 0x1; + for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) { + const TransformationComp comp = static_cast(i); + + if (chain[i].IsIdentity() && (anim_chain_bitmask & bit) == 0) { + continue; + } + + if (comp == TransformationComp_PostRotation) { + chain[i] = chain[i].Inverse(); + } + + aiNode* nd = new aiNode(); + nd->mName.Set(NameTransformationChainNode(name, comp)); + nd->mTransformation = chain[i]; + + // geometric inverses go in a post-node chain + if (comp == TransformationComp_GeometricScalingInverse || + comp == TransformationComp_GeometricRotationInverse || + comp == TransformationComp_GeometricTranslationInverse + ) { + post_output_nodes.push_back(nd); + } + else { + output_nodes.push_back(nd); + } + } + + ai_assert(output_nodes.size()); + return; + } + + // else, we can just multiply the matrices together + aiNode* nd = new aiNode(); + output_nodes.push_back(nd); + std::string uniqueName; + GetUniqueName(name, uniqueName); + + nd->mName.Set(uniqueName); + + for (const auto &transform : chain) { + nd->mTransformation = nd->mTransformation * transform; + } } - } - if ( scaling.size() ) { - InterpolateKeys( out_scale, times, scaling, def_scale, maxTime, minTime ); - } - else { - for ( size_t i = 0; i < times.size(); ++i ) { - out_scale[ i ].mTime = CONVERT_FBX_TIME( times[ i ] ) * anim_fps; - out_scale[ i ].mValue = def_scale; + void FBXConverter::SetupNodeMetadata(const Model& model, aiNode& nd) + { + const PropertyTable& props = model.Props(); + DirectPropertyMap unparsedProperties = props.GetUnparsedProperties(); + + // create metadata on node + const std::size_t numStaticMetaData = 2; + aiMetadata* data = aiMetadata::Alloc(static_cast(unparsedProperties.size() + numStaticMetaData)); + nd.mMetaData = data; + int index = 0; + + // find user defined properties (3ds Max) + data->Set(index++, "UserProperties", aiString(PropertyGet(props, "UDP3DSMAX", ""))); + // preserve the info that a node was marked as Null node in the original file. + data->Set(index++, "IsNull", model.IsNull() ? true : false); + + // add unparsed properties to the node's metadata + for (const DirectPropertyMap::value_type& prop : unparsedProperties) { + // Interpret the property as a concrete type + if (const TypedProperty* interpreted = prop.second->As >()) { + data->Set(index++, prop.first, interpreted->Value()); + } + else if (const TypedProperty* interpreted = prop.second->As >()) { + data->Set(index++, prop.first, interpreted->Value()); + } + else if (const TypedProperty* interpreted = prop.second->As >()) { + data->Set(index++, prop.first, interpreted->Value()); + } + else if (const TypedProperty* interpreted = prop.second->As >()) { + data->Set(index++, prop.first, interpreted->Value()); + } + else if (const TypedProperty* interpreted = prop.second->As >()) { + data->Set(index++, prop.first, aiString(interpreted->Value())); + } + else if (const TypedProperty* interpreted = prop.second->As >()) { + data->Set(index++, prop.first, interpreted->Value()); + } + else { + ai_assert(false); + } + } } - } - if ( translation.size() ) { - InterpolateKeys( out_translation, times, translation, def_translate, maxTime, minTime ); - } - else { - for ( size_t i = 0; i < times.size(); ++i ) { - out_translation[ i ].mTime = CONVERT_FBX_TIME( times[ i ] ) * anim_fps; - out_translation[ i ].mValue = def_translate; + void FBXConverter::ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform) + { + const std::vector& geos = model.GetGeometry(); + + std::vector meshes; + meshes.reserve(geos.size()); + + for (const Geometry* geo : geos) { + + const MeshGeometry* const mesh = dynamic_cast(geo); + if (mesh) { + const std::vector& indices = ConvertMesh(*mesh, model, node_global_transform, nd); + std::copy(indices.begin(), indices.end(), std::back_inserter(meshes)); + } + else { + FBXImporter::LogWarn("ignoring unrecognized geometry: " + geo->Name()); + } + } + + if (meshes.size()) { + nd.mMeshes = new unsigned int[meshes.size()](); + nd.mNumMeshes = static_cast(meshes.size()); + + std::swap_ranges(meshes.begin(), meshes.end(), nd.mMeshes); + } } - } - const size_t count = times.size(); - for ( size_t i = 0; i < count; ++i ) { - aiQuaternion& r = out_quat[ i ].mValue; - aiVector3D& s = out_scale[ i ].mValue; - aiVector3D& t = out_translation[ i ].mValue; + std::vector FBXConverter::ConvertMesh(const MeshGeometry& mesh, const Model& model, + const aiMatrix4x4& node_global_transform, aiNode& nd) + { + std::vector temp; - aiMatrix4x4 mat, temp; - aiMatrix4x4::Translation( t, mat ); - mat *= aiMatrix4x4( r.GetMatrix() ); - mat *= aiMatrix4x4::Scaling( s, temp ); + MeshMap::const_iterator it = meshes_converted.find(&mesh); + if (it != meshes_converted.end()) { + std::copy((*it).second.begin(), (*it).second.end(), std::back_inserter(temp)); + return temp; + } - mat.Decompose( s, r, t ); - } -} + const std::vector& vertices = mesh.GetVertices(); + const std::vector& faces = mesh.GetFaceIndexCounts(); + if (vertices.empty() || faces.empty()) { + FBXImporter::LogWarn("ignoring empty geometry: " + mesh.Name()); + return temp; + } -aiQuaternion FBXConverter::EulerToQuaternion( const aiVector3D& rot, Model::RotOrder order ) -{ - aiMatrix4x4 m; - GetRotationMatrix( order, rot, m ); + // one material per mesh maps easily to aiMesh. Multiple material + // meshes need to be split. + const MatIndexArray& mindices = mesh.GetMaterialIndices(); + if (doc.Settings().readMaterials && !mindices.empty()) { + const MatIndexArray::value_type base = mindices[0]; + for (MatIndexArray::value_type index : mindices) { + if (index != base) { + return ConvertMeshMultiMaterial(mesh, model, node_global_transform, nd); + } + } + } - return aiQuaternion( aiMatrix3x3( m ) ); -} + // faster code-path, just copy the data + temp.push_back(ConvertMeshSingleMaterial(mesh, model, node_global_transform, nd)); + return temp; + } -void FBXConverter::ConvertScaleKeys( aiNodeAnim* na, const std::vector& nodes, const LayerMap& /*layers*/, - int64_t start, int64_t stop, - double& maxTime, - double& minTime ) -{ - ai_assert( nodes.size() ); + aiMesh* FBXConverter::SetupEmptyMesh(const MeshGeometry& mesh, aiNode& nd) + { + aiMesh* const out_mesh = new aiMesh(); + meshes.push_back(out_mesh); + meshes_converted[&mesh].push_back(static_cast(meshes.size() - 1)); - // XXX for now, assume scale should be blended geometrically (i.e. two - // layers should be multiplied with each other). There is a FBX - // property in the layer to specify the behaviour, though. + // set name + std::string name = mesh.Name(); + if (name.substr(0, 10) == "Geometry::") { + name = name.substr(10); + } - const KeyFrameListList& inputs = GetKeyframeList( nodes, start, stop ); - const KeyTimeList& keys = GetKeyTimeList( inputs ); + if (name.length()) { + out_mesh->mName.Set(name); + } + else + { + out_mesh->mName = nd.mName; + } - na->mNumScalingKeys = static_cast( keys.size() ); - na->mScalingKeys = new aiVectorKey[ keys.size() ]; - if ( keys.size() > 0 ) - InterpolateKeys( na->mScalingKeys, keys, inputs, aiVector3D( 1.0f, 1.0f, 1.0f ), maxTime, minTime ); -} + return out_mesh; + } -void FBXConverter::ConvertTranslationKeys( aiNodeAnim* na, const std::vector& nodes, - const LayerMap& /*layers*/, - int64_t start, int64_t stop, - double& maxTime, - double& minTime ) -{ - ai_assert( nodes.size() ); + unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model, + const aiMatrix4x4& node_global_transform, aiNode& nd) + { + const MatIndexArray& mindices = mesh.GetMaterialIndices(); + aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd); - // XXX see notes in ConvertScaleKeys() - const KeyFrameListList& inputs = GetKeyframeList( nodes, start, stop ); - const KeyTimeList& keys = GetKeyTimeList( inputs ); + const std::vector& vertices = mesh.GetVertices(); + const std::vector& faces = mesh.GetFaceIndexCounts(); - na->mNumPositionKeys = static_cast( keys.size() ); - na->mPositionKeys = new aiVectorKey[ keys.size() ]; - if ( keys.size() > 0 ) - InterpolateKeys( na->mPositionKeys, keys, inputs, aiVector3D( 0.0f, 0.0f, 0.0f ), maxTime, minTime ); -} + // copy vertices + out_mesh->mNumVertices = static_cast(vertices.size()); + out_mesh->mVertices = new aiVector3D[vertices.size()]; + std::copy(vertices.begin(), vertices.end(), out_mesh->mVertices); -void FBXConverter::ConvertRotationKeys( aiNodeAnim* na, const std::vector& nodes, - const LayerMap& /*layers*/, - int64_t start, int64_t stop, - double& maxTime, - double& minTime, - Model::RotOrder order ) -{ - ai_assert( nodes.size() ); + // generate dummy faces + out_mesh->mNumFaces = static_cast(faces.size()); + aiFace* fac = out_mesh->mFaces = new aiFace[faces.size()](); - // XXX see notes in ConvertScaleKeys() - const std::vector< KeyFrameList >& inputs = GetKeyframeList( nodes, start, stop ); - const KeyTimeList& keys = GetKeyTimeList( inputs ); + unsigned int cursor = 0; + for (unsigned int pcount : faces) { + aiFace& f = *fac++; + f.mNumIndices = pcount; + f.mIndices = new unsigned int[pcount]; + switch (pcount) + { + case 1: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + case 2: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + case 3: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + default: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + break; + } + for (unsigned int i = 0; i < pcount; ++i) { + f.mIndices[i] = cursor++; + } + } - na->mNumRotationKeys = static_cast( keys.size() ); - na->mRotationKeys = new aiQuatKey[ keys.size() ]; - if (!keys.empty()) { - InterpolateKeys(na->mRotationKeys, keys, inputs, aiVector3D(0.0f, 0.0f, 0.0f), maxTime, minTime, order); - } -} + // copy normals + const std::vector& normals = mesh.GetNormals(); + if (normals.size()) { + ai_assert(normals.size() == vertices.size()); -void FBXConverter::ConvertGlobalSettings() { - if (nullptr == out) { - return; - } + out_mesh->mNormals = new aiVector3D[vertices.size()]; + std::copy(normals.begin(), normals.end(), out_mesh->mNormals); + } - out->mMetaData = aiMetadata::Alloc(1); - unsigned int index(0); - const double unitScalFactor(doc.GlobalSettings().UnitScaleFactor()); - out->mMetaData->Set(index, "UnitScaleFactor", unitScalFactor); -} + // copy tangents - assimp requires both tangents and bitangents (binormals) + // to be present, or neither of them. Compute binormals from normals + // and tangents if needed. + const std::vector& tangents = mesh.GetTangents(); + const std::vector* binormals = &mesh.GetBinormals(); -void FBXConverter::TransferDataToScene() -{ - ai_assert( !out->mMeshes ); - ai_assert( !out->mNumMeshes ); + if (tangents.size()) { + std::vector tempBinormals; + if (!binormals->size()) { + if (normals.size()) { + tempBinormals.resize(normals.size()); + for (unsigned int i = 0; i < tangents.size(); ++i) { + tempBinormals[i] = normals[i] ^ tangents[i]; + } - // note: the trailing () ensures initialization with NULL - not - // many C++ users seem to know this, so pointing it out to avoid - // confusion why this code works. + binormals = &tempBinormals; + } + else { + binormals = NULL; + } + } - if ( meshes.size() ) { - out->mMeshes = new aiMesh*[ meshes.size() ](); - out->mNumMeshes = static_cast( meshes.size() ); + if (binormals) { + ai_assert(tangents.size() == vertices.size()); + ai_assert(binormals->size() == vertices.size()); - std::swap_ranges( meshes.begin(), meshes.end(), out->mMeshes ); - } + out_mesh->mTangents = new aiVector3D[vertices.size()]; + std::copy(tangents.begin(), tangents.end(), out_mesh->mTangents); - if ( materials.size() ) { - out->mMaterials = new aiMaterial*[ materials.size() ](); - out->mNumMaterials = static_cast( materials.size() ); + out_mesh->mBitangents = new aiVector3D[vertices.size()]; + std::copy(binormals->begin(), binormals->end(), out_mesh->mBitangents); + } + } - std::swap_ranges( materials.begin(), materials.end(), out->mMaterials ); - } + // copy texture coords + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + const std::vector& uvs = mesh.GetTextureCoords(i); + if (uvs.empty()) { + break; + } - if ( animations.size() ) { - out->mAnimations = new aiAnimation*[ animations.size() ](); - out->mNumAnimations = static_cast( animations.size() ); + aiVector3D* out_uv = out_mesh->mTextureCoords[i] = new aiVector3D[vertices.size()]; + for (const aiVector2D& v : uvs) { + *out_uv++ = aiVector3D(v.x, v.y, 0.0f); + } - std::swap_ranges( animations.begin(), animations.end(), out->mAnimations ); - } + out_mesh->mNumUVComponents[i] = 2; + } - if ( lights.size() ) { - out->mLights = new aiLight*[ lights.size() ](); - out->mNumLights = static_cast( lights.size() ); + // copy vertex colors + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) { + const std::vector& colors = mesh.GetVertexColors(i); + if (colors.empty()) { + break; + } - std::swap_ranges( lights.begin(), lights.end(), out->mLights ); - } + out_mesh->mColors[i] = new aiColor4D[vertices.size()]; + std::copy(colors.begin(), colors.end(), out_mesh->mColors[i]); + } - if ( cameras.size() ) { - out->mCameras = new aiCamera*[ cameras.size() ](); - out->mNumCameras = static_cast( cameras.size() ); + if (!doc.Settings().readMaterials || mindices.empty()) { + FBXImporter::LogError("no material assigned to mesh, setting default material"); + out_mesh->mMaterialIndex = GetDefaultMaterial(); + } + else { + ConvertMaterialForMesh(out_mesh, model, mesh, mindices[0]); + } - std::swap_ranges( cameras.begin(), cameras.end(), out->mCameras ); - } + if (doc.Settings().readWeights && mesh.DeformerSkin() != NULL) { + ConvertWeights(out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION); + } - if ( textures.size() ) { - out->mTextures = new aiTexture*[ textures.size() ](); - out->mNumTextures = static_cast( textures.size() ); + std::vector animMeshes; + for (const BlendShape* blendShape : mesh.GetBlendShapes()) { + for (const BlendShapeChannel* blendShapeChannel : blendShape->BlendShapeChannels()) { + const std::vector& shapeGeometries = blendShapeChannel->GetShapeGeometries(); + for (size_t i = 0; i < shapeGeometries.size(); i++) { + aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh); + const ShapeGeometry* shapeGeometry = shapeGeometries.at(i); + const std::vector& vertices = shapeGeometry->GetVertices(); + const std::vector& normals = shapeGeometry->GetNormals(); + const std::vector& indices = shapeGeometry->GetIndices(); + animMesh->mName.Set(FixAnimMeshName(shapeGeometry->Name())); + for (size_t j = 0; j < indices.size(); j++) { + unsigned int index = indices.at(j); + aiVector3D vertex = vertices.at(j); + aiVector3D normal = normals.at(j); + unsigned int count = 0; + const unsigned int* outIndices = mesh.ToOutputVertexIndex(index, count); + for (unsigned int k = 0; k < count; k++) { + unsigned int index = outIndices[k]; + animMesh->mVertices[index] += vertex; + if (animMesh->mNormals != nullptr) { + animMesh->mNormals[index] += normal; + animMesh->mNormals[index].NormalizeSafe(); + } + } + } + animMesh->mWeight = shapeGeometries.size() > 1 ? blendShapeChannel->DeformPercent() / 100.0f : 1.0f; + animMeshes.push_back(animMesh); + } + } + } + size_t numAnimMeshes = animMeshes.size(); + if (numAnimMeshes > 0) { + out_mesh->mNumAnimMeshes = static_cast(numAnimMeshes); + out_mesh->mAnimMeshes = new aiAnimMesh*[numAnimMeshes]; + for (size_t i = 0; i < numAnimMeshes; i++) { + out_mesh->mAnimMeshes[i] = animMeshes.at(i); + } + } + return static_cast(meshes.size() - 1); + } - std::swap_ranges( textures.begin(), textures.end(), out->mTextures ); - } -} + std::vector FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, + const aiMatrix4x4& node_global_transform, aiNode& nd) + { + const MatIndexArray& mindices = mesh.GetMaterialIndices(); + ai_assert(mindices.size()); -// ------------------------------------------------------------------------------------------------ -void ConvertToAssimpScene(aiScene* out, const Document& doc) -{ - FBXConverter converter(out,doc); -} + std::set had; + std::vector indices; -} // !FBX + for (MatIndexArray::value_type index : mindices) { + if (had.find(index) == had.end()) { + + indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, node_global_transform, nd)); + had.insert(index); + } + } + + return indices; + } + + unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, + MatIndexArray::value_type index, + const aiMatrix4x4& node_global_transform, + aiNode& nd) + { + aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd); + + const MatIndexArray& mindices = mesh.GetMaterialIndices(); + const std::vector& vertices = mesh.GetVertices(); + const std::vector& faces = mesh.GetFaceIndexCounts(); + + const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != NULL; + + unsigned int count_faces = 0; + unsigned int count_vertices = 0; + + // count faces + std::vector::const_iterator itf = faces.begin(); + for (MatIndexArray::const_iterator it = mindices.begin(), + end = mindices.end(); it != end; ++it, ++itf) + { + if ((*it) != index) { + continue; + } + ++count_faces; + count_vertices += *itf; + } + + ai_assert(count_faces); + ai_assert(count_vertices); + + // mapping from output indices to DOM indexing, needed to resolve weights + std::vector reverseMapping; + + if (process_weights) { + reverseMapping.resize(count_vertices); + } + + // allocate output data arrays, but don't fill them yet + out_mesh->mNumVertices = count_vertices; + out_mesh->mVertices = new aiVector3D[count_vertices]; + + out_mesh->mNumFaces = count_faces; + aiFace* fac = out_mesh->mFaces = new aiFace[count_faces](); + + + // allocate normals + const std::vector& normals = mesh.GetNormals(); + if (normals.size()) { + ai_assert(normals.size() == vertices.size()); + out_mesh->mNormals = new aiVector3D[vertices.size()]; + } + + // allocate tangents, binormals. + const std::vector& tangents = mesh.GetTangents(); + const std::vector* binormals = &mesh.GetBinormals(); + std::vector tempBinormals; + + if (tangents.size()) { + if (!binormals->size()) { + if (normals.size()) { + // XXX this computes the binormals for the entire mesh, not only + // the part for which we need them. + tempBinormals.resize(normals.size()); + for (unsigned int i = 0; i < tangents.size(); ++i) { + tempBinormals[i] = normals[i] ^ tangents[i]; + } + + binormals = &tempBinormals; + } + else { + binormals = NULL; + } + } + + if (binormals) { + ai_assert(tangents.size() == vertices.size() && binormals->size() == vertices.size()); + + out_mesh->mTangents = new aiVector3D[vertices.size()]; + out_mesh->mBitangents = new aiVector3D[vertices.size()]; + } + } + + // allocate texture coords + unsigned int num_uvs = 0; + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i, ++num_uvs) { + const std::vector& uvs = mesh.GetTextureCoords(i); + if (uvs.empty()) { + break; + } + + out_mesh->mTextureCoords[i] = new aiVector3D[vertices.size()]; + out_mesh->mNumUVComponents[i] = 2; + } + + // allocate vertex colors + unsigned int num_vcs = 0; + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i, ++num_vcs) { + const std::vector& colors = mesh.GetVertexColors(i); + if (colors.empty()) { + break; + } + + out_mesh->mColors[i] = new aiColor4D[vertices.size()]; + } + + unsigned int cursor = 0, in_cursor = 0; + + itf = faces.begin(); + for (MatIndexArray::const_iterator it = mindices.begin(), + end = mindices.end(); it != end; ++it, ++itf) + { + const unsigned int pcount = *itf; + if ((*it) != index) { + in_cursor += pcount; + continue; + } + + aiFace& f = *fac++; + + f.mNumIndices = pcount; + f.mIndices = new unsigned int[pcount]; + switch (pcount) + { + case 1: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + case 2: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + case 3: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + default: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + break; + } + for (unsigned int i = 0; i < pcount; ++i, ++cursor, ++in_cursor) { + f.mIndices[i] = cursor; + + if (reverseMapping.size()) { + reverseMapping[cursor] = in_cursor; + } + + out_mesh->mVertices[cursor] = vertices[in_cursor]; + + if (out_mesh->mNormals) { + out_mesh->mNormals[cursor] = normals[in_cursor]; + } + + if (out_mesh->mTangents) { + out_mesh->mTangents[cursor] = tangents[in_cursor]; + out_mesh->mBitangents[cursor] = (*binormals)[in_cursor]; + } + + for (unsigned int j = 0; j < num_uvs; ++j) { + const std::vector& uvs = mesh.GetTextureCoords(j); + out_mesh->mTextureCoords[j][cursor] = aiVector3D(uvs[in_cursor].x, uvs[in_cursor].y, 0.0f); + } + + for (unsigned int j = 0; j < num_vcs; ++j) { + const std::vector& cols = mesh.GetVertexColors(j); + out_mesh->mColors[j][cursor] = cols[in_cursor]; + } + } + } + + ConvertMaterialForMesh(out_mesh, model, mesh, index); + + if (process_weights) { + ConvertWeights(out_mesh, model, mesh, node_global_transform, index, &reverseMapping); + } + + return static_cast(meshes.size() - 1); + } + + void FBXConverter::ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo, + const aiMatrix4x4& node_global_transform, + unsigned int materialIndex, + std::vector* outputVertStartIndices) + { + ai_assert(geo.DeformerSkin()); + + std::vector out_indices; + std::vector index_out_indices; + std::vector count_out_indices; + + const Skin& sk = *geo.DeformerSkin(); + + std::vector bones; + bones.reserve(sk.Clusters().size()); + + const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION; + ai_assert(no_mat_check || outputVertStartIndices); + + try { + + for (const Cluster* cluster : sk.Clusters()) { + ai_assert(cluster); + + const WeightIndexArray& indices = cluster->GetIndices(); + + if (indices.empty()) { + continue; + } + + const MatIndexArray& mats = geo.GetMaterialIndices(); + + bool ok = false; + + const size_t no_index_sentinel = std::numeric_limits::max(); + + count_out_indices.clear(); + index_out_indices.clear(); + out_indices.clear(); + + // now check if *any* of these weights is contained in the output mesh, + // taking notes so we don't need to do it twice. + for (WeightIndexArray::value_type index : indices) { + + unsigned int count = 0; + const unsigned int* const out_idx = geo.ToOutputVertexIndex(index, count); + // ToOutputVertexIndex only returns NULL if index is out of bounds + // which should never happen + ai_assert(out_idx != NULL); + + index_out_indices.push_back(no_index_sentinel); + count_out_indices.push_back(0); + + for (unsigned int i = 0; i < count; ++i) { + if (no_mat_check || static_cast(mats[geo.FaceForVertexIndex(out_idx[i])]) == materialIndex) { + + if (index_out_indices.back() == no_index_sentinel) { + index_out_indices.back() = out_indices.size(); + + } + + if (no_mat_check) { + out_indices.push_back(out_idx[i]); + } + else { + // this extra lookup is in O(logn), so the entire algorithm becomes O(nlogn) + const std::vector::iterator it = std::lower_bound( + outputVertStartIndices->begin(), + outputVertStartIndices->end(), + out_idx[i] + ); + + out_indices.push_back(std::distance(outputVertStartIndices->begin(), it)); + } + + ++count_out_indices.back(); + ok = true; + } + } + } + + // if we found at least one, generate the output bones + // XXX this could be heavily simplified by collecting the bone + // data in a single step. + if (ok) { + ConvertCluster(bones, model, *cluster, out_indices, index_out_indices, + count_out_indices, node_global_transform); + } + } + } + catch (std::exception&) { + std::for_each(bones.begin(), bones.end(), Util::delete_fun()); + throw; + } + + if (bones.empty()) { + return; + } + + out->mBones = new aiBone*[bones.size()](); + out->mNumBones = static_cast(bones.size()); + + std::swap_ranges(bones.begin(), bones.end(), out->mBones); + } + + void FBXConverter::ConvertCluster(std::vector& bones, const Model& /*model*/, const Cluster& cl, + std::vector& out_indices, + std::vector& index_out_indices, + std::vector& count_out_indices, + const aiMatrix4x4& node_global_transform) + { + + aiBone* const bone = new aiBone(); + bones.push_back(bone); + + bone->mName = FixNodeName(cl.TargetNode()->Name()); + + bone->mOffsetMatrix = cl.TransformLink(); + bone->mOffsetMatrix.Inverse(); + + bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform; + + bone->mNumWeights = static_cast(out_indices.size()); + aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[out_indices.size()]; + + const size_t no_index_sentinel = std::numeric_limits::max(); + const WeightArray& weights = cl.GetWeights(); + + const size_t c = index_out_indices.size(); + for (size_t i = 0; i < c; ++i) { + const size_t index_index = index_out_indices[i]; + + if (index_index == no_index_sentinel) { + continue; + } + + const size_t cc = count_out_indices[i]; + for (size_t j = 0; j < cc; ++j) { + aiVertexWeight& out_weight = *cursor++; + + out_weight.mVertexId = static_cast(out_indices[index_index + j]); + out_weight.mWeight = weights[i]; + } + } + } + + void FBXConverter::ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo, + MatIndexArray::value_type materialIndex) + { + // locate source materials for this mesh + const std::vector& mats = model.GetMaterials(); + if (static_cast(materialIndex) >= mats.size() || materialIndex < 0) { + FBXImporter::LogError("material index out of bounds, setting default material"); + out->mMaterialIndex = GetDefaultMaterial(); + return; + } + + const Material* const mat = mats[materialIndex]; + MaterialMap::const_iterator it = materials_converted.find(mat); + if (it != materials_converted.end()) { + out->mMaterialIndex = (*it).second; + return; + } + + out->mMaterialIndex = ConvertMaterial(*mat, &geo); + materials_converted[mat] = out->mMaterialIndex; + } + + unsigned int FBXConverter::GetDefaultMaterial() + { + if (defaultMaterialIndex) { + return defaultMaterialIndex - 1; + } + + aiMaterial* out_mat = new aiMaterial(); + materials.push_back(out_mat); + + const aiColor3D diffuse = aiColor3D(0.8f, 0.8f, 0.8f); + out_mat->AddProperty(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + + aiString s; + s.Set(AI_DEFAULT_MATERIAL_NAME); + + out_mat->AddProperty(&s, AI_MATKEY_NAME); + + defaultMaterialIndex = static_cast(materials.size()); + return defaultMaterialIndex - 1; + } + + + unsigned int FBXConverter::ConvertMaterial(const Material& material, const MeshGeometry* const mesh) + { + const PropertyTable& props = material.Props(); + + // generate empty output material + aiMaterial* out_mat = new aiMaterial(); + materials_converted[&material] = static_cast(materials.size()); + + materials.push_back(out_mat); + + aiString str; + + // strip Material:: prefix + std::string name = material.Name(); + if (name.substr(0, 10) == "Material::") { + name = name.substr(10); + } + + // set material name if not empty - this could happen + // and there should be no key for it in this case. + if (name.length()) { + str.Set(name); + out_mat->AddProperty(&str, AI_MATKEY_NAME); + } + + // shading stuff and colors + SetShadingPropertiesCommon(out_mat, props); + + // texture assignments + SetTextureProperties(out_mat, material.Textures(), mesh); + SetTextureProperties(out_mat, material.LayeredTextures(), mesh); + + return static_cast(materials.size() - 1); + } + + unsigned int FBXConverter::ConvertVideo(const Video& video) + { + // generate empty output texture + aiTexture* out_tex = new aiTexture(); + textures.push_back(out_tex); + + // assuming the texture is compressed + out_tex->mWidth = static_cast(video.ContentLength()); // total data size + out_tex->mHeight = 0; // fixed to 0 + + // steal the data from the Video to avoid an additional copy + out_tex->pcData = reinterpret_cast(const_cast(video).RelinquishContent()); + + // try to extract a hint from the file extension + const std::string& filename = video.FileName().empty() ? video.RelativeFilename() : video.FileName(); + std::string ext = BaseImporter::GetExtension(filename); + + if (ext == "jpeg") { + ext = "jpg"; + } + + if (ext.size() <= 3) { + memcpy(out_tex->achFormatHint, ext.c_str(), ext.size()); + } + + out_tex->mFilename.Set(video.FileName().c_str()); + + return static_cast(textures.size() - 1); + } + + aiString FBXConverter::GetTexturePath(const Texture* tex) + { + aiString path; + path.Set(tex->RelativeFilename()); + + const Video* media = tex->Media(); + if (media != nullptr) { + bool textureReady = false; //tells if our texture is ready (if it was loaded or if it was found) + unsigned int index; + + VideoMap::const_iterator it = textures_converted.find(media); + if (it != textures_converted.end()) { + index = (*it).second; + textureReady = true; + } + else { + if (media->ContentLength() > 0) { + index = ConvertVideo(*media); + textures_converted[media] = index; + textureReady = true; + } + } + + // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture), if the texture is ready + if (doc.Settings().useLegacyEmbeddedTextureNaming) { + if (textureReady) { + // TODO: check the possibility of using the flag "AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING" + // In FBX files textures are now stored internally by Assimp with their filename included + // Now Assimp can lookup through the loaded textures after all data is processed + // We need to load all textures before referencing them, as FBX file format order may reference a texture before loading it + // This may occur on this case too, it has to be studied + path.data[0] = '*'; + path.length = 1 + ASSIMP_itoa10(path.data + 1, MAXLEN - 1, index); + } + } + } + + return path; + } + + void FBXConverter::TrySetTextureProperties(aiMaterial* out_mat, const TextureMap& textures, + const std::string& propName, + aiTextureType target, const MeshGeometry* const mesh) + { + TextureMap::const_iterator it = textures.find(propName); + if (it == textures.end()) { + return; + } + + const Texture* const tex = (*it).second; + if (tex != 0) + { + aiString path = GetTexturePath(tex); + out_mat->AddProperty(&path, _AI_MATKEY_TEXTURE_BASE, target, 0); + + aiUVTransform uvTrafo; + // XXX handle all kinds of UV transformations + uvTrafo.mScaling = tex->UVScaling(); + uvTrafo.mTranslation = tex->UVTranslation(); + out_mat->AddProperty(&uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, 0); + + const PropertyTable& props = tex->Props(); + + int uvIndex = 0; + + bool ok; + const std::string& uvSet = PropertyGet(props, "UVSet", ok); + if (ok) { + // "default" is the name which usually appears in the FbxFileTexture template + if (uvSet != "default" && uvSet.length()) { + // this is a bit awkward - we need to find a mesh that uses this + // material and scan its UV channels for the given UV name because + // assimp references UV channels by index, not by name. + + // XXX: the case that UV channels may appear in different orders + // in meshes is unhandled. A possible solution would be to sort + // the UV channels alphabetically, but this would have the side + // effect that the primary (first) UV channel would sometimes + // be moved, causing trouble when users read only the first + // UV channel and ignore UV channel assignments altogether. + + const unsigned int matIndex = static_cast(std::distance(materials.begin(), + std::find(materials.begin(), materials.end(), out_mat) + )); + + + uvIndex = -1; + if (!mesh) + { + for (const MeshMap::value_type& v : meshes_converted) { + const MeshGeometry* const mesh = dynamic_cast (v.first); + if (!mesh) { + continue; + } + + const MatIndexArray& mats = mesh->GetMaterialIndices(); + if (std::find(mats.begin(), mats.end(), matIndex) == mats.end()) { + continue; + } + + int index = -1; + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (mesh->GetTextureCoords(i).empty()) { + break; + } + const std::string& name = mesh->GetTextureCoordChannelName(i); + if (name == uvSet) { + index = static_cast(i); + break; + } + } + if (index == -1) { + FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material"); + continue; + } + + if (uvIndex == -1) { + uvIndex = index; + } + else { + FBXImporter::LogWarn("the UV channel named " + uvSet + + " appears at different positions in meshes, results will be wrong"); + } + } + } + else + { + int index = -1; + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (mesh->GetTextureCoords(i).empty()) { + break; + } + const std::string& name = mesh->GetTextureCoordChannelName(i); + if (name == uvSet) { + index = static_cast(i); + break; + } + } + if (index == -1) { + FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material"); + } + + if (uvIndex == -1) { + uvIndex = index; + } + } + + if (uvIndex == -1) { + FBXImporter::LogWarn("failed to resolve UV channel " + uvSet + ", using first UV channel"); + uvIndex = 0; + } + } + } + + out_mat->AddProperty(&uvIndex, 1, _AI_MATKEY_UVWSRC_BASE, target, 0); + } + } + + void FBXConverter::TrySetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, + const std::string& propName, + aiTextureType target, const MeshGeometry* const mesh) { + LayeredTextureMap::const_iterator it = layeredTextures.find(propName); + if (it == layeredTextures.end()) { + return; + } + + int texCount = (*it).second->textureCount(); + + // Set the blend mode for layered textures + int blendmode = (*it).second->GetBlendMode(); + out_mat->AddProperty(&blendmode, 1, _AI_MATKEY_TEXOP_BASE, target, 0); + + for (int texIndex = 0; texIndex < texCount; texIndex++) { + + const Texture* const tex = (*it).second->getTexture(texIndex); + + aiString path = GetTexturePath(tex); + out_mat->AddProperty(&path, _AI_MATKEY_TEXTURE_BASE, target, texIndex); + + aiUVTransform uvTrafo; + // XXX handle all kinds of UV transformations + uvTrafo.mScaling = tex->UVScaling(); + uvTrafo.mTranslation = tex->UVTranslation(); + out_mat->AddProperty(&uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, texIndex); + + const PropertyTable& props = tex->Props(); + + int uvIndex = 0; + + bool ok; + const std::string& uvSet = PropertyGet(props, "UVSet", ok); + if (ok) { + // "default" is the name which usually appears in the FbxFileTexture template + if (uvSet != "default" && uvSet.length()) { + // this is a bit awkward - we need to find a mesh that uses this + // material and scan its UV channels for the given UV name because + // assimp references UV channels by index, not by name. + + // XXX: the case that UV channels may appear in different orders + // in meshes is unhandled. A possible solution would be to sort + // the UV channels alphabetically, but this would have the side + // effect that the primary (first) UV channel would sometimes + // be moved, causing trouble when users read only the first + // UV channel and ignore UV channel assignments altogether. + + const unsigned int matIndex = static_cast(std::distance(materials.begin(), + std::find(materials.begin(), materials.end(), out_mat) + )); + + uvIndex = -1; + if (!mesh) + { + for (const MeshMap::value_type& v : meshes_converted) { + const MeshGeometry* const mesh = dynamic_cast (v.first); + if (!mesh) { + continue; + } + + const MatIndexArray& mats = mesh->GetMaterialIndices(); + if (std::find(mats.begin(), mats.end(), matIndex) == mats.end()) { + continue; + } + + int index = -1; + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (mesh->GetTextureCoords(i).empty()) { + break; + } + const std::string& name = mesh->GetTextureCoordChannelName(i); + if (name == uvSet) { + index = static_cast(i); + break; + } + } + if (index == -1) { + FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material"); + continue; + } + + if (uvIndex == -1) { + uvIndex = index; + } + else { + FBXImporter::LogWarn("the UV channel named " + uvSet + + " appears at different positions in meshes, results will be wrong"); + } + } + } + else + { + int index = -1; + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (mesh->GetTextureCoords(i).empty()) { + break; + } + const std::string& name = mesh->GetTextureCoordChannelName(i); + if (name == uvSet) { + index = static_cast(i); + break; + } + } + if (index == -1) { + FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material"); + } + + if (uvIndex == -1) { + uvIndex = index; + } + } + + if (uvIndex == -1) { + FBXImporter::LogWarn("failed to resolve UV channel " + uvSet + ", using first UV channel"); + uvIndex = 0; + } + } + } + + out_mat->AddProperty(&uvIndex, 1, _AI_MATKEY_UVWSRC_BASE, target, texIndex); + } + } + + void FBXConverter::SetTextureProperties(aiMaterial* out_mat, const TextureMap& textures, const MeshGeometry* const mesh) + { + TrySetTextureProperties(out_mat, textures, "DiffuseColor", aiTextureType_DIFFUSE, mesh); + TrySetTextureProperties(out_mat, textures, "AmbientColor", aiTextureType_AMBIENT, mesh); + TrySetTextureProperties(out_mat, textures, "EmissiveColor", aiTextureType_EMISSIVE, mesh); + TrySetTextureProperties(out_mat, textures, "SpecularColor", aiTextureType_SPECULAR, mesh); + TrySetTextureProperties(out_mat, textures, "SpecularFactor", aiTextureType_SPECULAR, mesh); + TrySetTextureProperties(out_mat, textures, "TransparentColor", aiTextureType_OPACITY, mesh); + TrySetTextureProperties(out_mat, textures, "ReflectionColor", aiTextureType_REFLECTION, mesh); + TrySetTextureProperties(out_mat, textures, "DisplacementColor", aiTextureType_DISPLACEMENT, mesh); + TrySetTextureProperties(out_mat, textures, "NormalMap", aiTextureType_NORMALS, mesh); + TrySetTextureProperties(out_mat, textures, "Bump", aiTextureType_HEIGHT, mesh); + TrySetTextureProperties(out_mat, textures, "ShininessExponent", aiTextureType_SHININESS, mesh); + TrySetTextureProperties( out_mat, textures, "TransparencyFactor", aiTextureType_OPACITY, mesh ); + TrySetTextureProperties( out_mat, textures, "EmissiveFactor", aiTextureType_EMISSIVE, mesh ); + //Maya counterparts + TrySetTextureProperties(out_mat, textures, "Maya|DiffuseTexture", aiTextureType_DIFFUSE, mesh); + TrySetTextureProperties(out_mat, textures, "Maya|NormalTexture", aiTextureType_NORMALS, mesh); + TrySetTextureProperties(out_mat, textures, "Maya|SpecularTexture", aiTextureType_SPECULAR, mesh); + TrySetTextureProperties(out_mat, textures, "Maya|FalloffTexture", aiTextureType_OPACITY, mesh); + TrySetTextureProperties(out_mat, textures, "Maya|ReflectionMapTexture", aiTextureType_REFLECTION, mesh); + } + + void FBXConverter::SetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh) + { + TrySetTextureProperties(out_mat, layeredTextures, "DiffuseColor", aiTextureType_DIFFUSE, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "AmbientColor", aiTextureType_AMBIENT, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "EmissiveColor", aiTextureType_EMISSIVE, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "SpecularColor", aiTextureType_SPECULAR, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "SpecularFactor", aiTextureType_SPECULAR, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "TransparentColor", aiTextureType_OPACITY, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "ReflectionColor", aiTextureType_REFLECTION, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "DisplacementColor", aiTextureType_DISPLACEMENT, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "NormalMap", aiTextureType_NORMALS, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "Bump", aiTextureType_HEIGHT, mesh); + TrySetTextureProperties(out_mat, layeredTextures, "ShininessExponent", aiTextureType_SHININESS, mesh); + TrySetTextureProperties( out_mat, layeredTextures, "EmissiveFactor", aiTextureType_EMISSIVE, mesh ); + TrySetTextureProperties( out_mat, layeredTextures, "TransparencyFactor", aiTextureType_OPACITY, mesh ); + } + + aiColor3D FBXConverter::GetColorPropertyFactored(const PropertyTable& props, const std::string& colorName, + const std::string& factorName, bool& result, bool useTemplate) + { + result = true; + + bool ok; + aiVector3D BaseColor = PropertyGet(props, colorName, ok, useTemplate); + if (!ok) { + result = false; + return aiColor3D(0.0f, 0.0f, 0.0f); + } + + // if no factor name, return the colour as is + if (factorName.empty()) { + return aiColor3D(BaseColor.x, BaseColor.y, BaseColor.z); + } + + // otherwise it should be multiplied by the factor, if found. + float factor = PropertyGet(props, factorName, ok, useTemplate); + if (ok) { + BaseColor *= factor; + } + return aiColor3D(BaseColor.x, BaseColor.y, BaseColor.z); + } + + aiColor3D FBXConverter::GetColorPropertyFromMaterial(const PropertyTable& props, const std::string& baseName, + bool& result) + { + return GetColorPropertyFactored(props, baseName + "Color", baseName + "Factor", result, true); + } + + aiColor3D FBXConverter::GetColorProperty(const PropertyTable& props, const std::string& colorName, + bool& result, bool useTemplate) + { + result = true; + bool ok; + const aiVector3D& ColorVec = PropertyGet(props, colorName, ok, useTemplate); + if (!ok) { + result = false; + return aiColor3D(0.0f, 0.0f, 0.0f); + } + return aiColor3D(ColorVec.x, ColorVec.y, ColorVec.z); + } + + void FBXConverter::SetShadingPropertiesCommon(aiMaterial* out_mat, const PropertyTable& props) + { + // Set shading properties. + // Modern FBX Files have two separate systems for defining these, + // with only the more comprehensive one described in the property template. + // Likely the other values are a legacy system, + // which is still always exported by the official FBX SDK. + // + // Blender's FBX import and export mostly ignore this legacy system, + // and as we only support recent versions of FBX anyway, we can do the same. + bool ok; + + const aiColor3D& Diffuse = GetColorPropertyFromMaterial(props, "Diffuse", ok); + if (ok) { + out_mat->AddProperty(&Diffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + } + + const aiColor3D& Emissive = GetColorPropertyFromMaterial(props, "Emissive", ok); + if (ok) { + out_mat->AddProperty(&Emissive, 1, AI_MATKEY_COLOR_EMISSIVE); + } + + const aiColor3D& Ambient = GetColorPropertyFromMaterial(props, "Ambient", ok); + if (ok) { + out_mat->AddProperty(&Ambient, 1, AI_MATKEY_COLOR_AMBIENT); + } + + // we store specular factor as SHININESS_STRENGTH, so just get the color + const aiColor3D& Specular = GetColorProperty(props, "SpecularColor", ok, true); + if (ok) { + out_mat->AddProperty(&Specular, 1, AI_MATKEY_COLOR_SPECULAR); + } + + // and also try to get SHININESS_STRENGTH + const float SpecularFactor = PropertyGet(props, "SpecularFactor", ok, true); + if (ok) { + out_mat->AddProperty(&SpecularFactor, 1, AI_MATKEY_SHININESS_STRENGTH); + } + + // and the specular exponent + const float ShininessExponent = PropertyGet(props, "ShininessExponent", ok); + if (ok) { + out_mat->AddProperty(&ShininessExponent, 1, AI_MATKEY_SHININESS); + } + + // TransparentColor / TransparencyFactor... gee thanks FBX :rolleyes: + const aiColor3D& Transparent = GetColorPropertyFactored(props, "TransparentColor", "TransparencyFactor", ok); + float CalculatedOpacity = 1.0f; + if (ok) { + out_mat->AddProperty(&Transparent, 1, AI_MATKEY_COLOR_TRANSPARENT); + // as calculated by FBX SDK 2017: + CalculatedOpacity = 1.0f - ((Transparent.r + Transparent.g + Transparent.b) / 3.0f); + } + + // use of TransparencyFactor is inconsistent. + // Maya always stores it as 1.0, + // so we can't use it to set AI_MATKEY_OPACITY. + // Blender is more sensible and stores it as the alpha value. + // However both the FBX SDK and Blender always write an additional + // legacy "Opacity" field, so we can try to use that. + // + // If we can't find it, + // we can fall back to the value which the FBX SDK calculates + // from transparency colour (RGB) and factor (F) as + // 1.0 - F*((R+G+B)/3). + // + // There's no consistent way to interpret this opacity value, + // so it's up to clients to do the correct thing. + const float Opacity = PropertyGet(props, "Opacity", ok); + if (ok) { + out_mat->AddProperty(&Opacity, 1, AI_MATKEY_OPACITY); + } + else if (CalculatedOpacity != 1.0) { + out_mat->AddProperty(&CalculatedOpacity, 1, AI_MATKEY_OPACITY); + } + + // reflection color and factor are stored separately + const aiColor3D& Reflection = GetColorProperty(props, "ReflectionColor", ok, true); + if (ok) { + out_mat->AddProperty(&Reflection, 1, AI_MATKEY_COLOR_REFLECTIVE); + } + + float ReflectionFactor = PropertyGet(props, "ReflectionFactor", ok, true); + if (ok) { + out_mat->AddProperty(&ReflectionFactor, 1, AI_MATKEY_REFLECTIVITY); + } + + const float BumpFactor = PropertyGet(props, "BumpFactor", ok); + if (ok) { + out_mat->AddProperty(&BumpFactor, 1, AI_MATKEY_BUMPSCALING); + } + + const float DispFactor = PropertyGet(props, "DisplacementFactor", ok); + if (ok) { + out_mat->AddProperty(&DispFactor, 1, "$mat.displacementscaling", 0, 0); + } + } + + + double FBXConverter::FrameRateToDouble(FileGlobalSettings::FrameRate fp, double customFPSVal) { + switch (fp) { + case FileGlobalSettings::FrameRate_DEFAULT: + return 1.0; + + case FileGlobalSettings::FrameRate_120: + return 120.0; + + case FileGlobalSettings::FrameRate_100: + return 100.0; + + case FileGlobalSettings::FrameRate_60: + return 60.0; + + case FileGlobalSettings::FrameRate_50: + return 50.0; + + case FileGlobalSettings::FrameRate_48: + return 48.0; + + case FileGlobalSettings::FrameRate_30: + case FileGlobalSettings::FrameRate_30_DROP: + return 30.0; + + case FileGlobalSettings::FrameRate_NTSC_DROP_FRAME: + case FileGlobalSettings::FrameRate_NTSC_FULL_FRAME: + return 29.9700262; + + case FileGlobalSettings::FrameRate_PAL: + return 25.0; + + case FileGlobalSettings::FrameRate_CINEMA: + return 24.0; + + case FileGlobalSettings::FrameRate_1000: + return 1000.0; + + case FileGlobalSettings::FrameRate_CINEMA_ND: + return 23.976; + + case FileGlobalSettings::FrameRate_CUSTOM: + return customFPSVal; + + case FileGlobalSettings::FrameRate_MAX: // this is to silence compiler warnings + break; + } + + ai_assert(false); + + return -1.0f; + } + + + void FBXConverter::ConvertAnimations() + { + // first of all determine framerate + const FileGlobalSettings::FrameRate fps = doc.GlobalSettings().TimeMode(); + const float custom = doc.GlobalSettings().CustomFrameRate(); + anim_fps = FrameRateToDouble(fps, custom); + + const std::vector& animations = doc.AnimationStacks(); + for (const AnimationStack* stack : animations) { + ConvertAnimationStack(*stack); + } + } + + std::string FBXConverter::FixNodeName(const std::string& name) { + // strip Model:: prefix, avoiding ambiguities (i.e. don't strip if + // this causes ambiguities, well possible between empty identifiers, + // such as "Model::" and ""). Make sure the behaviour is consistent + // across multiple calls to FixNodeName(). + if (name.substr(0, 7) == "Model::") { + std::string temp = name.substr(7); + return temp; + } + + return name; + } + + std::string FBXConverter::FixAnimMeshName(const std::string& name) { + if (name.length()) { + size_t indexOf = name.find_first_of("::"); + if (indexOf != std::string::npos && indexOf < name.size() - 2) { + return name.substr(indexOf + 2); + } + } + return name.length() ? name : "AnimMesh"; + } + + void FBXConverter::ConvertAnimationStack(const AnimationStack& st) + { + const AnimationLayerList& layers = st.Layers(); + if (layers.empty()) { + return; + } + + aiAnimation* const anim = new aiAnimation(); + animations.push_back(anim); + + // strip AnimationStack:: prefix + std::string name = st.Name(); + if (name.substr(0, 16) == "AnimationStack::") { + name = name.substr(16); + } + else if (name.substr(0, 11) == "AnimStack::") { + name = name.substr(11); + } + + anim->mName.Set(name); + + // need to find all nodes for which we need to generate node animations - + // it may happen that we need to merge multiple layers, though. + NodeMap node_map; + + // reverse mapping from curves to layers, much faster than querying + // the FBX DOM for it. + LayerMap layer_map; + + const char* prop_whitelist[] = { + "Lcl Scaling", + "Lcl Rotation", + "Lcl Translation", + "DeformPercent" + }; + + std::map morphAnimDatas; + + for (const AnimationLayer* layer : layers) { + ai_assert(layer); + const AnimationCurveNodeList& nodes = layer->Nodes(prop_whitelist, 4); + for (const AnimationCurveNode* node : nodes) { + ai_assert(node); + const Model* const model = dynamic_cast(node->Target()); + if (model) { + const std::string& name = FixNodeName(model->Name()); + node_map[name].push_back(node); + layer_map[node] = layer; + continue; + } + const BlendShapeChannel* const bsc = dynamic_cast(node->Target()); + if (bsc) { + ProcessMorphAnimDatas(&morphAnimDatas, bsc, node); + } + } + } + + // generate node animations + std::vector node_anims; + + double min_time = 1e10; + double max_time = -1e10; + + int64_t start_time = st.LocalStart(); + int64_t stop_time = st.LocalStop(); + bool has_local_startstop = start_time != 0 || stop_time != 0; + if (!has_local_startstop) { + // no time range given, so accept every keyframe and use the actual min/max time + // the numbers are INT64_MIN/MAX, the 20000 is for safety because GenerateNodeAnimations uses an epsilon of 10000 + start_time = -9223372036854775807ll + 20000; + stop_time = 9223372036854775807ll - 20000; + } + + try { + for (const NodeMap::value_type& kv : node_map) { + GenerateNodeAnimations(node_anims, + kv.first, + kv.second, + layer_map, + start_time, stop_time, + max_time, + min_time); + } + } + catch (std::exception&) { + std::for_each(node_anims.begin(), node_anims.end(), Util::delete_fun()); + throw; + } + + if (node_anims.size() || morphAnimDatas.size()) { + if (node_anims.size()) { + anim->mChannels = new aiNodeAnim*[node_anims.size()](); + anim->mNumChannels = static_cast(node_anims.size()); + std::swap_ranges(node_anims.begin(), node_anims.end(), anim->mChannels); + } + if (morphAnimDatas.size()) { + unsigned int numMorphMeshChannels = static_cast(morphAnimDatas.size()); + anim->mMorphMeshChannels = new aiMeshMorphAnim*[numMorphMeshChannels]; + anim->mNumMorphMeshChannels = numMorphMeshChannels; + unsigned int i = 0; + for (auto morphAnimIt : morphAnimDatas) { + morphAnimData* animData = morphAnimIt.second; + unsigned int numKeys = static_cast(animData->size()); + aiMeshMorphAnim* meshMorphAnim = new aiMeshMorphAnim(); + meshMorphAnim->mName.Set(morphAnimIt.first); + meshMorphAnim->mNumKeys = numKeys; + meshMorphAnim->mKeys = new aiMeshMorphKey[numKeys]; + unsigned int j = 0; + for (auto animIt : *animData) { + morphKeyData* keyData = animIt.second; + unsigned int numValuesAndWeights = static_cast(keyData->values.size()); + meshMorphAnim->mKeys[j].mNumValuesAndWeights = numValuesAndWeights; + meshMorphAnim->mKeys[j].mValues = new unsigned int[numValuesAndWeights]; + meshMorphAnim->mKeys[j].mWeights = new double[numValuesAndWeights]; + meshMorphAnim->mKeys[j].mTime = CONVERT_FBX_TIME(animIt.first) * anim_fps; + for (unsigned int k = 0; k < numValuesAndWeights; k++) { + meshMorphAnim->mKeys[j].mValues[k] = keyData->values.at(k); + meshMorphAnim->mKeys[j].mWeights[k] = keyData->weights.at(k); + } + j++; + } + anim->mMorphMeshChannels[i++] = meshMorphAnim; + } + } + } + else { + // empty animations would fail validation, so drop them + delete anim; + animations.pop_back(); + FBXImporter::LogInfo("ignoring empty AnimationStack (using IK?): " + name); + return; + } + + double start_time_fps = has_local_startstop ? (CONVERT_FBX_TIME(start_time) * anim_fps) : min_time; + double stop_time_fps = has_local_startstop ? (CONVERT_FBX_TIME(stop_time) * anim_fps) : max_time; + + // adjust relative timing for animation + for (unsigned int c = 0; c < anim->mNumChannels; c++) { + aiNodeAnim* channel = anim->mChannels[c]; + for (uint32_t i = 0; i < channel->mNumPositionKeys; i++) { + channel->mPositionKeys[i].mTime -= start_time_fps; + } + for (uint32_t i = 0; i < channel->mNumRotationKeys; i++) { + channel->mRotationKeys[i].mTime -= start_time_fps; + } + for (uint32_t i = 0; i < channel->mNumScalingKeys; i++) { + channel->mScalingKeys[i].mTime -= start_time_fps; + } + } + for (unsigned int c = 0; c < anim->mNumMorphMeshChannels; c++) { + aiMeshMorphAnim* channel = anim->mMorphMeshChannels[c]; + for (uint32_t i = 0; i < channel->mNumKeys; i++) { + channel->mKeys[i].mTime -= start_time_fps; + } + } + + // for some mysterious reason, mDuration is simply the maximum key -- the + // validator always assumes animations to start at zero. + anim->mDuration = stop_time_fps - start_time_fps; + anim->mTicksPerSecond = anim_fps; + } + + // ------------------------------------------------------------------------------------------------ + void FBXConverter::ProcessMorphAnimDatas(std::map* morphAnimDatas, const BlendShapeChannel* bsc, const AnimationCurveNode* node) { + std::vector bscConnections = doc.GetConnectionsBySourceSequenced(bsc->ID(), "Deformer"); + for (const Connection* bscConnection : bscConnections) { + auto bs = dynamic_cast(bscConnection->DestinationObject()); + if (bs) { + auto channelIt = std::find(bs->BlendShapeChannels().begin(), bs->BlendShapeChannels().end(), bsc); + if (channelIt != bs->BlendShapeChannels().end()) { + auto channelIndex = static_cast(std::distance(bs->BlendShapeChannels().begin(), channelIt)); + std::vector bsConnections = doc.GetConnectionsBySourceSequenced(bs->ID(), "Geometry"); + for (const Connection* bsConnection : bsConnections) { + auto geo = dynamic_cast(bsConnection->DestinationObject()); + if (geo) { + std::vector geoConnections = doc.GetConnectionsBySourceSequenced(geo->ID(), "Model"); + for (const Connection* geoConnection : geoConnections) { + auto model = dynamic_cast(geoConnection->DestinationObject()); + if (model) { + auto geoIt = std::find(model->GetGeometry().begin(), model->GetGeometry().end(), geo); + auto geoIndex = static_cast(std::distance(model->GetGeometry().begin(), geoIt)); + auto name = aiString(FixNodeName(model->Name() + "*")); + name.length = 1 + ASSIMP_itoa10(name.data + name.length, MAXLEN - 1, geoIndex); + morphAnimData* animData; + auto animIt = morphAnimDatas->find(name.C_Str()); + if (animIt == morphAnimDatas->end()) { + animData = new morphAnimData(); + morphAnimDatas->insert(std::make_pair(name.C_Str(), animData)); + } + else { + animData = animIt->second; + } + for (std::pair curvesIt : node->Curves()) { + if (curvesIt.first == "d|DeformPercent") { + const AnimationCurve* animationCurve = curvesIt.second; + const KeyTimeList& keys = animationCurve->GetKeys(); + const KeyValueList& values = animationCurve->GetValues(); + unsigned int k = 0; + for (auto key : keys) { + morphKeyData* keyData; + auto keyIt = animData->find(key); + if (keyIt == animData->end()) { + keyData = new morphKeyData(); + animData->insert(std::make_pair(key, keyData)); + } + else { + keyData = keyIt->second; + } + keyData->values.push_back(channelIndex); + keyData->weights.push_back(values.at(k) / 100.0f); + k++; + } + } + } + } + } + } + } + } + } + } + } + + // ------------------------------------------------------------------------------------------------ +#ifdef ASSIMP_BUILD_DEBUG + // ------------------------------------------------------------------------------------------------ + // sanity check whether the input is ok + static void validateAnimCurveNodes(const std::vector& curves, + bool strictMode) { + const Object* target(NULL); + for (const AnimationCurveNode* node : curves) { + if (!target) { + target = node->Target(); + } + if (node->Target() != target) { + FBXImporter::LogWarn("Node target is nullptr type."); + } + if (strictMode) { + ai_assert(node->Target() == target); + } + } + } +#endif // ASSIMP_BUILD_DEBUG + + // ------------------------------------------------------------------------------------------------ + void FBXConverter::GenerateNodeAnimations(std::vector& node_anims, + const std::string& fixed_name, + const std::vector& curves, + const LayerMap& layer_map, + int64_t start, int64_t stop, + double& max_time, + double& min_time) + { + + NodeMap node_property_map; + ai_assert(curves.size()); + +#ifdef ASSIMP_BUILD_DEBUG + validateAnimCurveNodes(curves, doc.Settings().strictMode); +#endif + const AnimationCurveNode* curve_node = NULL; + for (const AnimationCurveNode* node : curves) { + ai_assert(node); + + if (node->TargetProperty().empty()) { + FBXImporter::LogWarn("target property for animation curve not set: " + node->Name()); + continue; + } + + curve_node = node; + if (node->Curves().empty()) { + FBXImporter::LogWarn("no animation curves assigned to AnimationCurveNode: " + node->Name()); + continue; + } + + node_property_map[node->TargetProperty()].push_back(node); + } + + ai_assert(curve_node); + ai_assert(curve_node->TargetAsModel()); + + const Model& target = *curve_node->TargetAsModel(); + + // check for all possible transformation components + NodeMap::const_iterator chain[TransformationComp_MAXIMUM]; + + bool has_any = false; + bool has_complex = false; + + for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) { + const TransformationComp comp = static_cast(i); + + // inverse pivots don't exist in the input, we just generate them + if (comp == TransformationComp_RotationPivotInverse || comp == TransformationComp_ScalingPivotInverse) { + chain[i] = node_property_map.end(); + continue; + } + + chain[i] = node_property_map.find(NameTransformationCompProperty(comp)); + if (chain[i] != node_property_map.end()) { + + // check if this curves contains redundant information by looking + // up the corresponding node's transformation chain. + if (doc.Settings().optimizeEmptyAnimationCurves && + IsRedundantAnimationData(target, comp, (*chain[i]).second)) { + + FBXImporter::LogDebug("dropping redundant animation channel for node " + target.Name()); + continue; + } + + has_any = true; + + if (comp != TransformationComp_Rotation && comp != TransformationComp_Scaling && comp != TransformationComp_Translation) + { + has_complex = true; + } + } + } + + if (!has_any) { + FBXImporter::LogWarn("ignoring node animation, did not find any transformation key frames"); + return; + } + + // this needs to play nicely with GenerateTransformationNodeChain() which will + // be invoked _later_ (animations come first). If this node has only rotation, + // scaling and translation _and_ there are no animated other components either, + // we can use a single node and also a single node animation channel. + if (!has_complex && !NeedsComplexTransformationChain(target)) { + + aiNodeAnim* const nd = GenerateSimpleNodeAnim(fixed_name, target, chain, + node_property_map.end(), + layer_map, + start, stop, + max_time, + min_time, + true // input is TRS order, assimp is SRT + ); + + ai_assert(nd); + if (nd->mNumPositionKeys == 0 && nd->mNumRotationKeys == 0 && nd->mNumScalingKeys == 0) { + delete nd; + } + else { + node_anims.push_back(nd); + } + return; + } + + // otherwise, things get gruesome and we need separate animation channels + // for each part of the transformation chain. Remember which channels + // we generated and pass this information to the node conversion + // code to avoid nodes that have identity transform, but non-identity + // animations, being dropped. + unsigned int flags = 0, bit = 0x1; + for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) { + const TransformationComp comp = static_cast(i); + + if (chain[i] != node_property_map.end()) { + flags |= bit; + + ai_assert(comp != TransformationComp_RotationPivotInverse); + ai_assert(comp != TransformationComp_ScalingPivotInverse); + + const std::string& chain_name = NameTransformationChainNode(fixed_name, comp); + + aiNodeAnim* na = nullptr; + switch (comp) + { + case TransformationComp_Rotation: + case TransformationComp_PreRotation: + case TransformationComp_PostRotation: + case TransformationComp_GeometricRotation: + na = GenerateRotationNodeAnim(chain_name, + target, + (*chain[i]).second, + layer_map, + start, stop, + max_time, + min_time); + + break; + + case TransformationComp_RotationOffset: + case TransformationComp_RotationPivot: + case TransformationComp_ScalingOffset: + case TransformationComp_ScalingPivot: + case TransformationComp_Translation: + case TransformationComp_GeometricTranslation: + na = GenerateTranslationNodeAnim(chain_name, + target, + (*chain[i]).second, + layer_map, + start, stop, + max_time, + min_time); + + // pivoting requires us to generate an implicit inverse channel to undo the pivot translation + if (comp == TransformationComp_RotationPivot) { + const std::string& invName = NameTransformationChainNode(fixed_name, + TransformationComp_RotationPivotInverse); + + aiNodeAnim* const inv = GenerateTranslationNodeAnim(invName, + target, + (*chain[i]).second, + layer_map, + start, stop, + max_time, + min_time, + true); + + ai_assert(inv); + if (inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0) { + delete inv; + } + else { + node_anims.push_back(inv); + } + + ai_assert(TransformationComp_RotationPivotInverse > i); + flags |= bit << (TransformationComp_RotationPivotInverse - i); + } + else if (comp == TransformationComp_ScalingPivot) { + const std::string& invName = NameTransformationChainNode(fixed_name, + TransformationComp_ScalingPivotInverse); + + aiNodeAnim* const inv = GenerateTranslationNodeAnim(invName, + target, + (*chain[i]).second, + layer_map, + start, stop, + max_time, + min_time, + true); + + ai_assert(inv); + if (inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0) { + delete inv; + } + else { + node_anims.push_back(inv); + } + + ai_assert(TransformationComp_RotationPivotInverse > i); + flags |= bit << (TransformationComp_RotationPivotInverse - i); + } + + break; + + case TransformationComp_Scaling: + case TransformationComp_GeometricScaling: + na = GenerateScalingNodeAnim(chain_name, + target, + (*chain[i]).second, + layer_map, + start, stop, + max_time, + min_time); + + break; + + default: + ai_assert(false); + } + + ai_assert(na); + if (na->mNumPositionKeys == 0 && na->mNumRotationKeys == 0 && na->mNumScalingKeys == 0) { + delete na; + } + else { + node_anims.push_back(na); + } + continue; + } + } + + node_anim_chain_bits[fixed_name] = flags; + } + + + bool FBXConverter::IsRedundantAnimationData(const Model& target, + TransformationComp comp, + const std::vector& curves) { + ai_assert(curves.size()); + + // look for animation nodes with + // * sub channels for all relevant components set + // * one key/value pair per component + // * combined values match up the corresponding value in the bind pose node transformation + // only such nodes are 'redundant' for this function. + + if (curves.size() > 1) { + return false; + } + + const AnimationCurveNode& nd = *curves.front(); + const AnimationCurveMap& sub_curves = nd.Curves(); + + const AnimationCurveMap::const_iterator dx = sub_curves.find("d|X"); + const AnimationCurveMap::const_iterator dy = sub_curves.find("d|Y"); + const AnimationCurveMap::const_iterator dz = sub_curves.find("d|Z"); + + if (dx == sub_curves.end() || dy == sub_curves.end() || dz == sub_curves.end()) { + return false; + } + + const KeyValueList& vx = (*dx).second->GetValues(); + const KeyValueList& vy = (*dy).second->GetValues(); + const KeyValueList& vz = (*dz).second->GetValues(); + + if (vx.size() != 1 || vy.size() != 1 || vz.size() != 1) { + return false; + } + + const aiVector3D dyn_val = aiVector3D(vx[0], vy[0], vz[0]); + const aiVector3D& static_val = PropertyGet(target.Props(), + NameTransformationCompProperty(comp), + TransformationCompDefaultValue(comp) + ); + + const float epsilon = 1e-6f; + return (dyn_val - static_val).SquareLength() < epsilon; + } + + + aiNodeAnim* FBXConverter::GenerateRotationNodeAnim(const std::string& name, + const Model& target, + const std::vector& curves, + const LayerMap& layer_map, + int64_t start, int64_t stop, + double& max_time, + double& min_time) + { + std::unique_ptr na(new aiNodeAnim()); + na->mNodeName.Set(name); + + ConvertRotationKeys(na.get(), curves, layer_map, start, stop, max_time, min_time, target.RotationOrder()); + + // dummy scaling key + na->mScalingKeys = new aiVectorKey[1]; + na->mNumScalingKeys = 1; + + na->mScalingKeys[0].mTime = 0.; + na->mScalingKeys[0].mValue = aiVector3D(1.0f, 1.0f, 1.0f); + + // dummy position key + na->mPositionKeys = new aiVectorKey[1]; + na->mNumPositionKeys = 1; + + na->mPositionKeys[0].mTime = 0.; + na->mPositionKeys[0].mValue = aiVector3D(); + + return na.release(); + } + + aiNodeAnim* FBXConverter::GenerateScalingNodeAnim(const std::string& name, + const Model& /*target*/, + const std::vector& curves, + const LayerMap& layer_map, + int64_t start, int64_t stop, + double& max_time, + double& min_time) + { + std::unique_ptr na(new aiNodeAnim()); + na->mNodeName.Set(name); + + ConvertScaleKeys(na.get(), curves, layer_map, start, stop, max_time, min_time); + + // dummy rotation key + na->mRotationKeys = new aiQuatKey[1]; + na->mNumRotationKeys = 1; + + na->mRotationKeys[0].mTime = 0.; + na->mRotationKeys[0].mValue = aiQuaternion(); + + // dummy position key + na->mPositionKeys = new aiVectorKey[1]; + na->mNumPositionKeys = 1; + + na->mPositionKeys[0].mTime = 0.; + na->mPositionKeys[0].mValue = aiVector3D(); + + return na.release(); + } + + aiNodeAnim* FBXConverter::GenerateTranslationNodeAnim(const std::string& name, + const Model& /*target*/, + const std::vector& curves, + const LayerMap& layer_map, + int64_t start, int64_t stop, + double& max_time, + double& min_time, + bool inverse) { + std::unique_ptr na(new aiNodeAnim()); + na->mNodeName.Set(name); + + ConvertTranslationKeys(na.get(), curves, layer_map, start, stop, max_time, min_time); + + if (inverse) { + for (unsigned int i = 0; i < na->mNumPositionKeys; ++i) { + na->mPositionKeys[i].mValue *= -1.0f; + } + } + + // dummy scaling key + na->mScalingKeys = new aiVectorKey[1]; + na->mNumScalingKeys = 1; + + na->mScalingKeys[0].mTime = 0.; + na->mScalingKeys[0].mValue = aiVector3D(1.0f, 1.0f, 1.0f); + + // dummy rotation key + na->mRotationKeys = new aiQuatKey[1]; + na->mNumRotationKeys = 1; + + na->mRotationKeys[0].mTime = 0.; + na->mRotationKeys[0].mValue = aiQuaternion(); + + return na.release(); + } + + aiNodeAnim* FBXConverter::GenerateSimpleNodeAnim(const std::string& name, + const Model& target, + NodeMap::const_iterator chain[TransformationComp_MAXIMUM], + NodeMap::const_iterator iter_end, + const LayerMap& layer_map, + int64_t start, int64_t stop, + double& max_time, + double& min_time, + bool reverse_order) + + { + std::unique_ptr na(new aiNodeAnim()); + na->mNodeName.Set(name); + + const PropertyTable& props = target.Props(); + + // need to convert from TRS order to SRT? + if (reverse_order) { + + aiVector3D def_scale = PropertyGet(props, "Lcl Scaling", aiVector3D(1.f, 1.f, 1.f)); + aiVector3D def_translate = PropertyGet(props, "Lcl Translation", aiVector3D(0.f, 0.f, 0.f)); + aiVector3D def_rot = PropertyGet(props, "Lcl Rotation", aiVector3D(0.f, 0.f, 0.f)); + + KeyFrameListList scaling; + KeyFrameListList translation; + KeyFrameListList rotation; + + if (chain[TransformationComp_Scaling] != iter_end) { + scaling = GetKeyframeList((*chain[TransformationComp_Scaling]).second, start, stop); + } + + if (chain[TransformationComp_Translation] != iter_end) { + translation = GetKeyframeList((*chain[TransformationComp_Translation]).second, start, stop); + } + + if (chain[TransformationComp_Rotation] != iter_end) { + rotation = GetKeyframeList((*chain[TransformationComp_Rotation]).second, start, stop); + } + + KeyFrameListList joined; + joined.insert(joined.end(), scaling.begin(), scaling.end()); + joined.insert(joined.end(), translation.begin(), translation.end()); + joined.insert(joined.end(), rotation.begin(), rotation.end()); + + const KeyTimeList& times = GetKeyTimeList(joined); + + aiQuatKey* out_quat = new aiQuatKey[times.size()]; + aiVectorKey* out_scale = new aiVectorKey[times.size()]; + aiVectorKey* out_translation = new aiVectorKey[times.size()]; + + if (times.size()) + { + ConvertTransformOrder_TRStoSRT(out_quat, out_scale, out_translation, + scaling, + translation, + rotation, + times, + max_time, + min_time, + target.RotationOrder(), + def_scale, + def_translate, + def_rot); + } + + // XXX remove duplicates / redundant keys which this operation did + // likely produce if not all three channels were equally dense. + + na->mNumScalingKeys = static_cast(times.size()); + na->mNumRotationKeys = na->mNumScalingKeys; + na->mNumPositionKeys = na->mNumScalingKeys; + + na->mScalingKeys = out_scale; + na->mRotationKeys = out_quat; + na->mPositionKeys = out_translation; + } + else { + + // if a particular transformation is not given, grab it from + // the corresponding node to meet the semantics of aiNodeAnim, + // which requires all of rotation, scaling and translation + // to be set. + if (chain[TransformationComp_Scaling] != iter_end) { + ConvertScaleKeys(na.get(), (*chain[TransformationComp_Scaling]).second, + layer_map, + start, stop, + max_time, + min_time); + } + else { + na->mScalingKeys = new aiVectorKey[1]; + na->mNumScalingKeys = 1; + + na->mScalingKeys[0].mTime = 0.; + na->mScalingKeys[0].mValue = PropertyGet(props, "Lcl Scaling", + aiVector3D(1.f, 1.f, 1.f)); + } + + if (chain[TransformationComp_Rotation] != iter_end) { + ConvertRotationKeys(na.get(), (*chain[TransformationComp_Rotation]).second, + layer_map, + start, stop, + max_time, + min_time, + target.RotationOrder()); + } + else { + na->mRotationKeys = new aiQuatKey[1]; + na->mNumRotationKeys = 1; + + na->mRotationKeys[0].mTime = 0.; + na->mRotationKeys[0].mValue = EulerToQuaternion( + PropertyGet(props, "Lcl Rotation", aiVector3D(0.f, 0.f, 0.f)), + target.RotationOrder()); + } + + if (chain[TransformationComp_Translation] != iter_end) { + ConvertTranslationKeys(na.get(), (*chain[TransformationComp_Translation]).second, + layer_map, + start, stop, + max_time, + min_time); + } + else { + na->mPositionKeys = new aiVectorKey[1]; + na->mNumPositionKeys = 1; + + na->mPositionKeys[0].mTime = 0.; + na->mPositionKeys[0].mValue = PropertyGet(props, "Lcl Translation", + aiVector3D(0.f, 0.f, 0.f)); + } + + } + return na.release(); + } + + FBXConverter::KeyFrameListList FBXConverter::GetKeyframeList(const std::vector& nodes, int64_t start, int64_t stop) + { + KeyFrameListList inputs; + inputs.reserve(nodes.size() * 3); + + //give some breathing room for rounding errors + int64_t adj_start = start - 10000; + int64_t adj_stop = stop + 10000; + + for (const AnimationCurveNode* node : nodes) { + ai_assert(node); + + const AnimationCurveMap& curves = node->Curves(); + for (const AnimationCurveMap::value_type& kv : curves) { + + unsigned int mapto; + if (kv.first == "d|X") { + mapto = 0; + } + else if (kv.first == "d|Y") { + mapto = 1; + } + else if (kv.first == "d|Z") { + mapto = 2; + } + else { + FBXImporter::LogWarn("ignoring scale animation curve, did not recognize target component"); + continue; + } + + const AnimationCurve* const curve = kv.second; + ai_assert(curve->GetKeys().size() == curve->GetValues().size() && curve->GetKeys().size()); + + //get values within the start/stop time window + std::shared_ptr Keys(new KeyTimeList()); + std::shared_ptr Values(new KeyValueList()); + const size_t count = curve->GetKeys().size(); + Keys->reserve(count); + Values->reserve(count); + for (size_t n = 0; n < count; n++) + { + int64_t k = curve->GetKeys().at(n); + if (k >= adj_start && k <= adj_stop) + { + Keys->push_back(k); + Values->push_back(curve->GetValues().at(n)); + } + } + + inputs.push_back(std::make_tuple(Keys, Values, mapto)); + } + } + return inputs; // pray for NRVO :-) + } + + + KeyTimeList FBXConverter::GetKeyTimeList(const KeyFrameListList& inputs) { + ai_assert(!inputs.empty()); + + // reserve some space upfront - it is likely that the key-frame lists + // have matching time values, so max(of all key-frame lists) should + // be a good estimate. + KeyTimeList keys; + + size_t estimate = 0; + for (const KeyFrameList& kfl : inputs) { + estimate = std::max(estimate, std::get<0>(kfl)->size()); + } + + keys.reserve(estimate); + + std::vector next_pos; + next_pos.resize(inputs.size(), 0); + + const size_t count = inputs.size(); + while (true) { + + int64_t min_tick = std::numeric_limits::max(); + for (size_t i = 0; i < count; ++i) { + const KeyFrameList& kfl = inputs[i]; + + if (std::get<0>(kfl)->size() > next_pos[i] && std::get<0>(kfl)->at(next_pos[i]) < min_tick) { + min_tick = std::get<0>(kfl)->at(next_pos[i]); + } + } + + if (min_tick == std::numeric_limits::max()) { + break; + } + keys.push_back(min_tick); + + for (size_t i = 0; i < count; ++i) { + const KeyFrameList& kfl = inputs[i]; + + + while (std::get<0>(kfl)->size() > next_pos[i] && std::get<0>(kfl)->at(next_pos[i]) == min_tick) { + ++next_pos[i]; + } + } + } + + return keys; + } + + void FBXConverter::InterpolateKeys(aiVectorKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs, + const aiVector3D& def_value, + double& max_time, + double& min_time) { + ai_assert(!keys.empty()); + ai_assert(nullptr != valOut); + + std::vector next_pos; + const size_t count(inputs.size()); + + next_pos.resize(inputs.size(), 0); + + for (KeyTimeList::value_type time : keys) { + ai_real result[3] = { def_value.x, def_value.y, def_value.z }; + + for (size_t i = 0; i < count; ++i) { + const KeyFrameList& kfl = inputs[i]; + + const size_t ksize = std::get<0>(kfl)->size(); + if (ksize == 0) { + continue; + } + if (ksize > next_pos[i] && std::get<0>(kfl)->at(next_pos[i]) == time) { + ++next_pos[i]; + } + + const size_t id0 = next_pos[i] > 0 ? next_pos[i] - 1 : 0; + const size_t id1 = next_pos[i] == ksize ? ksize - 1 : next_pos[i]; + + // use lerp for interpolation + const KeyValueList::value_type valueA = std::get<1>(kfl)->at(id0); + const KeyValueList::value_type valueB = std::get<1>(kfl)->at(id1); + + const KeyTimeList::value_type timeA = std::get<0>(kfl)->at(id0); + const KeyTimeList::value_type timeB = std::get<0>(kfl)->at(id1); + + const ai_real factor = timeB == timeA ? ai_real(0.) : static_cast((time - timeA)) / (timeB - timeA); + const ai_real interpValue = static_cast(valueA + (valueB - valueA) * factor); + + result[std::get<2>(kfl)] = interpValue; + } + + // magic value to convert fbx times to seconds + valOut->mTime = CONVERT_FBX_TIME(time) * anim_fps; + + min_time = std::min(min_time, valOut->mTime); + max_time = std::max(max_time, valOut->mTime); + + valOut->mValue.x = result[0]; + valOut->mValue.y = result[1]; + valOut->mValue.z = result[2]; + + ++valOut; + } + } + + void FBXConverter::InterpolateKeys(aiQuatKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs, + const aiVector3D& def_value, + double& maxTime, + double& minTime, + Model::RotOrder order) + { + ai_assert(!keys.empty()); + ai_assert(nullptr != valOut); + + std::unique_ptr temp(new aiVectorKey[keys.size()]); + InterpolateKeys(temp.get(), keys, inputs, def_value, maxTime, minTime); + + aiMatrix4x4 m; + + aiQuaternion lastq; + + for (size_t i = 0, c = keys.size(); i < c; ++i) { + + valOut[i].mTime = temp[i].mTime; + + GetRotationMatrix(order, temp[i].mValue, m); + aiQuaternion quat = aiQuaternion(aiMatrix3x3(m)); + + // take shortest path by checking the inner product + // http://www.3dkingdoms.com/weekly/weekly.php?a=36 + if (quat.x * lastq.x + quat.y * lastq.y + quat.z * lastq.z + quat.w * lastq.w < 0) + { + quat.x = -quat.x; + quat.y = -quat.y; + quat.z = -quat.z; + quat.w = -quat.w; + } + lastq = quat; + + valOut[i].mValue = quat; + } + } + + void FBXConverter::ConvertTransformOrder_TRStoSRT(aiQuatKey* out_quat, aiVectorKey* out_scale, + aiVectorKey* out_translation, + const KeyFrameListList& scaling, + const KeyFrameListList& translation, + const KeyFrameListList& rotation, + const KeyTimeList& times, + double& maxTime, + double& minTime, + Model::RotOrder order, + const aiVector3D& def_scale, + const aiVector3D& def_translate, + const aiVector3D& def_rotation) + { + if (rotation.size()) { + InterpolateKeys(out_quat, times, rotation, def_rotation, maxTime, minTime, order); + } + else { + for (size_t i = 0; i < times.size(); ++i) { + out_quat[i].mTime = CONVERT_FBX_TIME(times[i]) * anim_fps; + out_quat[i].mValue = EulerToQuaternion(def_rotation, order); + } + } + + if (scaling.size()) { + InterpolateKeys(out_scale, times, scaling, def_scale, maxTime, minTime); + } + else { + for (size_t i = 0; i < times.size(); ++i) { + out_scale[i].mTime = CONVERT_FBX_TIME(times[i]) * anim_fps; + out_scale[i].mValue = def_scale; + } + } + + if (translation.size()) { + InterpolateKeys(out_translation, times, translation, def_translate, maxTime, minTime); + } + else { + for (size_t i = 0; i < times.size(); ++i) { + out_translation[i].mTime = CONVERT_FBX_TIME(times[i]) * anim_fps; + out_translation[i].mValue = def_translate; + } + } + + const size_t count = times.size(); + for (size_t i = 0; i < count; ++i) { + aiQuaternion& r = out_quat[i].mValue; + aiVector3D& s = out_scale[i].mValue; + aiVector3D& t = out_translation[i].mValue; + + aiMatrix4x4 mat, temp; + aiMatrix4x4::Translation(t, mat); + mat *= aiMatrix4x4(r.GetMatrix()); + mat *= aiMatrix4x4::Scaling(s, temp); + + mat.Decompose(s, r, t); + } + } + + aiQuaternion FBXConverter::EulerToQuaternion(const aiVector3D& rot, Model::RotOrder order) + { + aiMatrix4x4 m; + GetRotationMatrix(order, rot, m); + + return aiQuaternion(aiMatrix3x3(m)); + } + + void FBXConverter::ConvertScaleKeys(aiNodeAnim* na, const std::vector& nodes, const LayerMap& /*layers*/, + int64_t start, int64_t stop, + double& maxTime, + double& minTime) + { + ai_assert(nodes.size()); + + // XXX for now, assume scale should be blended geometrically (i.e. two + // layers should be multiplied with each other). There is a FBX + // property in the layer to specify the behaviour, though. + + const KeyFrameListList& inputs = GetKeyframeList(nodes, start, stop); + const KeyTimeList& keys = GetKeyTimeList(inputs); + + na->mNumScalingKeys = static_cast(keys.size()); + na->mScalingKeys = new aiVectorKey[keys.size()]; + if (keys.size() > 0) + InterpolateKeys(na->mScalingKeys, keys, inputs, aiVector3D(1.0f, 1.0f, 1.0f), maxTime, minTime); + } + + void FBXConverter::ConvertTranslationKeys(aiNodeAnim* na, const std::vector& nodes, + const LayerMap& /*layers*/, + int64_t start, int64_t stop, + double& maxTime, + double& minTime) + { + ai_assert(nodes.size()); + + // XXX see notes in ConvertScaleKeys() + const KeyFrameListList& inputs = GetKeyframeList(nodes, start, stop); + const KeyTimeList& keys = GetKeyTimeList(inputs); + + na->mNumPositionKeys = static_cast(keys.size()); + na->mPositionKeys = new aiVectorKey[keys.size()]; + if (keys.size() > 0) + InterpolateKeys(na->mPositionKeys, keys, inputs, aiVector3D(0.0f, 0.0f, 0.0f), maxTime, minTime); + } + + void FBXConverter::ConvertRotationKeys(aiNodeAnim* na, const std::vector& nodes, + const LayerMap& /*layers*/, + int64_t start, int64_t stop, + double& maxTime, + double& minTime, + Model::RotOrder order) + { + ai_assert(nodes.size()); + + // XXX see notes in ConvertScaleKeys() + const std::vector< KeyFrameList >& inputs = GetKeyframeList(nodes, start, stop); + const KeyTimeList& keys = GetKeyTimeList(inputs); + + na->mNumRotationKeys = static_cast(keys.size()); + na->mRotationKeys = new aiQuatKey[keys.size()]; + if (!keys.empty()) { + InterpolateKeys(na->mRotationKeys, keys, inputs, aiVector3D(0.0f, 0.0f, 0.0f), maxTime, minTime, order); + } + } + + void FBXConverter::ConvertGlobalSettings() { + if (nullptr == out) { + return; + } + + out->mMetaData = aiMetadata::Alloc(15); + out->mMetaData->Set(0, "UpAxis", doc.GlobalSettings().UpAxis()); + out->mMetaData->Set(1, "UpAxisSign", doc.GlobalSettings().UpAxisSign()); + out->mMetaData->Set(2, "FrontAxis", doc.GlobalSettings().FrontAxis()); + out->mMetaData->Set(3, "FrontAxisSign", doc.GlobalSettings().FrontAxisSign()); + out->mMetaData->Set(4, "CoordAxis", doc.GlobalSettings().CoordAxis()); + out->mMetaData->Set(5, "CoordAxisSign", doc.GlobalSettings().CoordAxisSign()); + out->mMetaData->Set(6, "OriginalUpAxis", doc.GlobalSettings().OriginalUpAxis()); + out->mMetaData->Set(7, "OriginalUpAxisSign", doc.GlobalSettings().OriginalUpAxisSign()); + out->mMetaData->Set(8, "UnitScaleFactor", doc.GlobalSettings().UnitScaleFactor()); + out->mMetaData->Set(9, "OriginalUnitScaleFactor", doc.GlobalSettings().OriginalUnitScaleFactor()); + out->mMetaData->Set(10, "AmbientColor", doc.GlobalSettings().AmbientColor()); + out->mMetaData->Set(11, "FrameRate", (int)doc.GlobalSettings().TimeMode()); + out->mMetaData->Set(12, "TimeSpanStart", doc.GlobalSettings().TimeSpanStart()); + out->mMetaData->Set(13, "TimeSpanStop", doc.GlobalSettings().TimeSpanStop()); + out->mMetaData->Set(14, "CustomFrameRate", doc.GlobalSettings().CustomFrameRate()); + } + + void FBXConverter::TransferDataToScene() + { + ai_assert(!out->mMeshes); + ai_assert(!out->mNumMeshes); + + // note: the trailing () ensures initialization with NULL - not + // many C++ users seem to know this, so pointing it out to avoid + // confusion why this code works. + + if (meshes.size()) { + out->mMeshes = new aiMesh*[meshes.size()](); + out->mNumMeshes = static_cast(meshes.size()); + + std::swap_ranges(meshes.begin(), meshes.end(), out->mMeshes); + } + + if (materials.size()) { + out->mMaterials = new aiMaterial*[materials.size()](); + out->mNumMaterials = static_cast(materials.size()); + + std::swap_ranges(materials.begin(), materials.end(), out->mMaterials); + } + + if (animations.size()) { + out->mAnimations = new aiAnimation*[animations.size()](); + out->mNumAnimations = static_cast(animations.size()); + + std::swap_ranges(animations.begin(), animations.end(), out->mAnimations); + } + + if (lights.size()) { + out->mLights = new aiLight*[lights.size()](); + out->mNumLights = static_cast(lights.size()); + + std::swap_ranges(lights.begin(), lights.end(), out->mLights); + } + + if (cameras.size()) { + out->mCameras = new aiCamera*[cameras.size()](); + out->mNumCameras = static_cast(cameras.size()); + + std::swap_ranges(cameras.begin(), cameras.end(), out->mCameras); + } + + if (textures.size()) { + out->mTextures = new aiTexture*[textures.size()](); + out->mNumTextures = static_cast(textures.size()); + + std::swap_ranges(textures.begin(), textures.end(), out->mTextures); + } + } + + // ------------------------------------------------------------------------------------------------ + void ConvertToAssimpScene(aiScene* out, const Document& doc) + { + FBXConverter converter(out, doc); + } + + } // !FBX } // !Assimp #endif diff --git a/code/FBXConverter.h b/code/FBXConverter.h index ca8dcba2e..039422839 100644 --- a/code/FBXConverter.h +++ b/code/FBXConverter.h @@ -63,6 +63,12 @@ struct aiScene; struct aiNode; struct aiMaterial; +struct morphKeyData { + std::vector values; + std::vector weights; +}; +typedef std::map morphAnimData; + namespace Assimp { namespace FBX { @@ -272,6 +278,7 @@ private: // the function is guaranteed to provide consistent results over multiple invocations // UNLESS RenameNode() is called for a particular node name. std::string FixNodeName(const std::string& name); + std::string FixAnimMeshName(const std::string& name); typedef std::map LayerMap; @@ -281,6 +288,9 @@ private: // ------------------------------------------------------------------------------------------------ void ConvertAnimationStack(const AnimationStack& st); + // ------------------------------------------------------------------------------------------------ + void ProcessMorphAnimDatas(std::map* morphAnimDatas, const BlendShapeChannel* bsc, const AnimationCurveNode* node); + // ------------------------------------------------------------------------------------------------ void GenerateNodeAnimations(std::vector& node_anims, const std::string& fixed_name, diff --git a/code/FBXDeformer.cpp b/code/FBXDeformer.cpp index 9c7c7a4c4..fc8b38e57 100644 --- a/code/FBXDeformer.cpp +++ b/code/FBXDeformer.cpp @@ -48,6 +48,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "FBXParser.h" #include "FBXDocument.h" +#include "FBXMeshGeometry.h" #include "FBXImporter.h" #include "FBXDocumentUtil.h" @@ -158,9 +159,55 @@ Skin::~Skin() { } +// ------------------------------------------------------------------------------------------------ +BlendShape::BlendShape(uint64_t id, const Element& element, const Document& doc, const std::string& name) + : Deformer(id, element, doc, name) +{ + const std::vector& conns = doc.GetConnectionsByDestinationSequenced(ID(), "Deformer"); + blendShapeChannels.reserve(conns.size()); + for (const Connection* con : conns) { + const BlendShapeChannel* const bspc = ProcessSimpleConnection(*con, false, "BlendShapeChannel -> BlendShape", element); + if (bspc) { + blendShapeChannels.push_back(bspc); + continue; + } + } +} +// ------------------------------------------------------------------------------------------------ +BlendShape::~BlendShape() +{ } +// ------------------------------------------------------------------------------------------------ +BlendShapeChannel::BlendShapeChannel(uint64_t id, const Element& element, const Document& doc, const std::string& name) + : Deformer(id, element, doc, name) +{ + const Scope& sc = GetRequiredScope(element); + const Element* const DeformPercent = sc["DeformPercent"]; + if (DeformPercent) { + percent = ParseTokenAsFloat(GetRequiredToken(*DeformPercent, 0)); + } + const Element* const FullWeights = sc["FullWeights"]; + if (FullWeights) { + ParseVectorDataArray(fullWeights, *FullWeights); + } + const std::vector& conns = doc.GetConnectionsByDestinationSequenced(ID(), "Geometry"); + shapeGeometries.reserve(conns.size()); + for (const Connection* con : conns) { + const ShapeGeometry* const sg = ProcessSimpleConnection(*con, false, "Shape -> BlendShapeChannel", element); + if (sg) { + shapeGeometries.push_back(sg); + continue; + } + } } +// ------------------------------------------------------------------------------------------------ +BlendShapeChannel::~BlendShapeChannel() +{ +} +// ------------------------------------------------------------------------------------------------ +} +} #endif diff --git a/code/FBXDocument.cpp b/code/FBXDocument.cpp index f53ae4405..29bc052da 100644 --- a/code/FBXDocument.cpp +++ b/code/FBXDocument.cpp @@ -146,6 +146,9 @@ const Object* LazyObject::Get(bool dieOnError) if (!strcmp(classtag.c_str(),"Mesh")) { object.reset(new MeshGeometry(id,element,name,doc)); } + if (!strcmp(classtag.c_str(), "Shape")) { + object.reset(new ShapeGeometry(id, element, name, doc)); + } } else if (!strncmp(obtype,"NodeAttribute",length)) { if (!strcmp(classtag.c_str(),"Camera")) { @@ -171,6 +174,12 @@ const Object* LazyObject::Get(bool dieOnError) else if (!strcmp(classtag.c_str(),"Skin")) { object.reset(new Skin(id,element,doc,name)); } + else if (!strcmp(classtag.c_str(), "BlendShape")) { + object.reset(new BlendShape(id, element, doc, name)); + } + else if (!strcmp(classtag.c_str(), "BlendShapeChannel")) { + object.reset(new BlendShapeChannel(id, element, doc, name)); + } } else if ( !strncmp( obtype, "Model", length ) ) { // FK and IK effectors are not supported diff --git a/code/FBXDocument.h b/code/FBXDocument.h index 654f5bfa8..a7d81cf0a 100644 --- a/code/FBXDocument.h +++ b/code/FBXDocument.h @@ -65,6 +65,7 @@ struct ImportSettings; class PropertyTable; class Document; class Material; +class ShapeGeometry; class Geometry; class Video; @@ -74,6 +75,8 @@ class AnimationCurveNode; class AnimationLayer; class AnimationStack; +class BlendShapeChannel; +class BlendShape; class Skin; class Cluster; @@ -869,6 +872,46 @@ private: typedef std::vector WeightArray; typedef std::vector WeightIndexArray; + +/** DOM class for BlendShapeChannel deformers */ +class BlendShapeChannel : public Deformer +{ +public: + BlendShapeChannel(uint64_t id, const Element& element, const Document& doc, const std::string& name); + virtual ~BlendShapeChannel(); + + float DeformPercent() const { + return percent; + } + + const WeightArray& GetFullWeights() const { + return fullWeights; + } + + const std::vector& GetShapeGeometries() const { + return shapeGeometries; + } +private: + float percent; + WeightArray fullWeights; + std::vector shapeGeometries; +}; + +/** DOM class for BlendShape deformers */ +class BlendShape : public Deformer +{ +public: + BlendShape(uint64_t id, const Element& element, const Document& doc, const std::string& name); + virtual ~BlendShape(); + + const std::vector& BlendShapeChannels() const { + return blendShapeChannels; + } + +private: + std::vector blendShapeChannels; +}; + /** DOM class for skin deformer clusters (aka subdeformers) */ class Cluster : public Deformer { diff --git a/code/FBXMeshGeometry.cpp b/code/FBXMeshGeometry.cpp index aa794d4df..aa60ee184 100644 --- a/code/FBXMeshGeometry.cpp +++ b/code/FBXMeshGeometry.cpp @@ -62,7 +62,7 @@ using namespace Util; // ------------------------------------------------------------------------------------------------ Geometry::Geometry(uint64_t id, const Element& element, const std::string& name, const Document& doc) - : Object(id, element,name) + : Object(id, element, name) , skin() { const std::vector& conns = doc.GetConnectionsByDestinationSequenced(ID(),"Deformer"); @@ -70,18 +70,26 @@ Geometry::Geometry(uint64_t id, const Element& element, const std::string& name, const Skin* const sk = ProcessSimpleConnection(*con, false, "Skin -> Geometry", element); if(sk) { skin = sk; - break; + } + const BlendShape* const bsp = ProcessSimpleConnection(*con, false, "BlendShape -> Geometry", element); + if (bsp) { + blendShapes.push_back(bsp); } } } - // ------------------------------------------------------------------------------------------------ Geometry::~Geometry() { // empty } +// ------------------------------------------------------------------------------------------------ +const std::vector& Geometry::GetBlendShapes() const { + return blendShapes; +} + +// ------------------------------------------------------------------------------------------------ const Skin* Geometry::DeformerSkin() const { return skin; } @@ -232,7 +240,6 @@ const std::vector& MeshGeometry::GetVertexColors( unsigned int index const MatIndexArray& MeshGeometry::GetMaterialIndices() const { return m_materials; } - // ------------------------------------------------------------------------------------------------ const unsigned int* MeshGeometry::ToOutputVertexIndex( unsigned int in_index, unsigned int& count ) const { if ( in_index >= m_mapping_counts.size() ) { @@ -640,9 +647,39 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector& materials_out, cons << MappingInformationType << "," << ReferenceInformationType); } } +// ------------------------------------------------------------------------------------------------ +ShapeGeometry::ShapeGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc) + : Geometry(id, element, name, doc) +{ + const Scope* sc = element.Compound(); + if (!sc) { + DOMError("failed to read Geometry object (class: Shape), no data scope found"); + } + const Element& Indexes = GetRequiredElement(*sc, "Indexes", &element); + const Element& Normals = GetRequiredElement(*sc, "Normals", &element); + const Element& Vertices = GetRequiredElement(*sc, "Vertices", &element); + ParseVectorDataArray(m_indices, Indexes); + ParseVectorDataArray(m_vertices, Vertices); + ParseVectorDataArray(m_normals, Normals); +} +// ------------------------------------------------------------------------------------------------ +ShapeGeometry::~ShapeGeometry() { + // empty +} +// ------------------------------------------------------------------------------------------------ +const std::vector& ShapeGeometry::GetVertices() const { + return m_vertices; +} +// ------------------------------------------------------------------------------------------------ +const std::vector& ShapeGeometry::GetNormals() const { + return m_normals; +} +// ------------------------------------------------------------------------------------------------ +const std::vector& ShapeGeometry::GetIndices() const { + return m_indices; +} } // !FBX } // !Assimp - #endif diff --git a/code/FBXMeshGeometry.h b/code/FBXMeshGeometry.h index acd44668a..3e10d73d3 100644 --- a/code/FBXMeshGeometry.h +++ b/code/FBXMeshGeometry.h @@ -64,8 +64,13 @@ public: /** Get the Skin attached to this geometry or NULL */ const Skin* DeformerSkin() const; + /** Get the BlendShape attached to this geometry or NULL */ + const std::vector& GetBlendShapes() const; + private: const Skin* skin; + std::vector blendShapes; + }; typedef std::vector MatIndexArray; @@ -125,7 +130,6 @@ public: /** Determine the face to which a particular output vertex index belongs. * This mapping is always unique. */ unsigned int FaceForVertexIndex( unsigned int in_index ) const; - private: void ReadLayer( const Scope& layer ); void ReadLayerElement( const Scope& layerElement ); @@ -174,6 +178,34 @@ private: std::vector m_mappings; }; +/** +* DOM class for FBX geometry of type "Shape" +*/ +class ShapeGeometry : public Geometry +{ +public: + /** The class constructor */ + ShapeGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc); + + /** The class destructor */ + virtual ~ShapeGeometry(); + + /** Get a list of all vertex points, non-unique*/ + const std::vector& GetVertices() const; + + /** Get a list of all vertex normals or an empty array if + * no normals are specified. */ + const std::vector& GetNormals() const; + + /** Return list of vertex indices. */ + const std::vector& GetIndices() const; + +private: + std::vector m_vertices; + std::vector m_normals; + std::vector m_indices; +}; + } } diff --git a/code/Importer/IFC/IFCLoader.cpp b/code/Importer/IFC/IFCLoader.cpp index 97c514d44..0b423a4cd 100644 --- a/code/Importer/IFC/IFCLoader.cpp +++ b/code/Importer/IFC/IFCLoader.cpp @@ -207,10 +207,21 @@ void IFCImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS } uint8_t* buff = new uint8_t[fileInfo.uncompressed_size]; LogInfo("Decompressing IFCZIP file"); - unzOpenCurrentFile( zip ); - const int ret = unzReadCurrentFile( zip, buff, fileInfo.uncompressed_size); + unzOpenCurrentFile(zip); + size_t total = 0; + int read = 0; + do { + int bufferSize = fileInfo.uncompressed_size < INT16_MAX ? fileInfo.uncompressed_size : INT16_MAX; + void* buffer = malloc(bufferSize); + read = unzReadCurrentFile(zip, buffer, bufferSize); + if (read > 0) { + memcpy((char*)buff + total, buffer, read); + total += read; + } + free(buffer); + } while (read > 0); size_t filesize = fileInfo.uncompressed_size; - if ( ret < 0 || size_t(ret) != filesize ) + if (total < 0 || size_t(total) != filesize) { delete[] buff; ThrowException("Failed to decompress IFC ZIP file"); diff --git a/code/ObjFileParser.cpp b/code/ObjFileParser.cpp index 7630127fa..62d2ad46a 100644 --- a/code/ObjFileParser.cpp +++ b/code/ObjFileParser.cpp @@ -119,7 +119,7 @@ void ObjFileParser::parseFile( IOStreamBuffer &streamBuffer ) { size_t lastFilePos( 0 ); std::vector buffer; - while ( streamBuffer.getNextDataLine( buffer, '\\' ) ) { + while ( streamBuffer.getNextDataLine( buffer, '\0' ) ) { m_DataIt = buffer.begin(); m_DataItEnd = buffer.end(); diff --git a/code/glTF2Importer.cpp b/code/glTF2Importer.cpp index 20c28f498..c73e1eabb 100755 --- a/code/glTF2Importer.cpp +++ b/code/glTF2Importer.cpp @@ -438,13 +438,16 @@ void glTF2Importer::ImportMeshes(glTF2::Asset& r) } } - for (size_t tc = 0; tc < attr.texcoord.size() && tc < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++tc) { - if (!attr.texcoord[tc]) { - DefaultLogger::get()->warn("NULL texcoord encountered in mesh \"" + mesh.name + - "\" and will be ignored"); + for (size_t c = 0; c < attr.color.size() && c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) { + if (attr.color[c]->count != aim->mNumVertices) { + DefaultLogger::get()->warn("Color stream size in mesh \"" + mesh.name + + "\" does not match the vertex count"); continue; } - + aim->mColors[c] = new aiColor4D[attr.color[c]->count]; + attr.color[c]->ExtractData(aim->mColors[c]); + } + for (size_t tc = 0; tc < attr.texcoord.size() && tc < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++tc) { if (attr.texcoord[tc]->count != aim->mNumVertices) { DefaultLogger::get()->warn("Texcoord stream size in mesh \"" + mesh.name + "\" does not match the vertex count"); diff --git a/include/assimp/IOStreamBuffer.h b/include/assimp/IOStreamBuffer.h index c93a191df..be5e4d0c5 100644 --- a/include/assimp/IOStreamBuffer.h +++ b/include/assimp/IOStreamBuffer.h @@ -243,7 +243,7 @@ template inline bool IOStreamBuffer::getNextDataLine( std::vector &buffer, T continuationToken ) { buffer.resize( m_cacheSize ); - if ( m_cachePos == m_cacheSize || 0 == m_filePos ) { + if ( m_cachePos >= m_cacheSize || 0 == m_filePos ) { if ( !readNextBlock() ) { return false; } @@ -273,6 +273,9 @@ bool IOStreamBuffer::getNextDataLine( std::vector &buffer, T continuationT buffer[ i ] = m_cache[ m_cachePos ]; ++m_cachePos; ++i; + if (m_cachePos >= size()) { + break; + } if ( m_cachePos >= m_cacheSize ) { if ( !readNextBlock() ) { return false; diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h index f47d5fd00..f9c2c1e67 100644 --- a/include/assimp/mesh.h +++ b/include/assimp/mesh.h @@ -402,7 +402,7 @@ enum aiPrimitiveType // --------------------------------------------------------------------------- -/** @brief NOT CURRENTLY IN USE. An AnimMesh is an attachment to an #aiMesh stores per-vertex +/** @brief An AnimMesh is an attachment to an #aiMesh stores per-vertex * animations for a particular frame. * * You may think of an #aiAnimMesh as a `patch` for the host mesh, which @@ -414,6 +414,9 @@ enum aiPrimitiveType */ struct aiAnimMesh { + /**Anim Mesh name */ + C_STRUCT aiString mName; + /** Replacement for aiMesh::mVertices. If this array is non-NULL, * it *must* contain mNumVertices entries. The corresponding * array in the host mesh must be non-NULL as well - animation From f170c842263b0d3ddd3d92e4445175ec9a6ae6dd Mon Sep 17 00:00:00 2001 From: rickomax Date: Sun, 6 Jan 2019 19:38:29 -0200 Subject: [PATCH 2/2] Test fixes Test fixes --- code/FBXConverter.cpp | 2 +- code/Importer/IFC/IFCLoader.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/FBXConverter.cpp b/code/FBXConverter.cpp index cd9ad0966..b14bbfe98 100644 --- a/code/FBXConverter.cpp +++ b/code/FBXConverter.cpp @@ -3208,7 +3208,7 @@ namespace Assimp { out->mMetaData->Set(5, "CoordAxisSign", doc.GlobalSettings().CoordAxisSign()); out->mMetaData->Set(6, "OriginalUpAxis", doc.GlobalSettings().OriginalUpAxis()); out->mMetaData->Set(7, "OriginalUpAxisSign", doc.GlobalSettings().OriginalUpAxisSign()); - out->mMetaData->Set(8, "UnitScaleFactor", doc.GlobalSettings().UnitScaleFactor()); + out->mMetaData->Set(8, "UnitScaleFactor", (double)doc.GlobalSettings().UnitScaleFactor()); out->mMetaData->Set(9, "OriginalUnitScaleFactor", doc.GlobalSettings().OriginalUnitScaleFactor()); out->mMetaData->Set(10, "AmbientColor", doc.GlobalSettings().AmbientColor()); out->mMetaData->Set(11, "FrameRate", (int)doc.GlobalSettings().TimeMode()); diff --git a/code/Importer/IFC/IFCLoader.cpp b/code/Importer/IFC/IFCLoader.cpp index 0b423a4cd..2c95872d3 100644 --- a/code/Importer/IFC/IFCLoader.cpp +++ b/code/Importer/IFC/IFCLoader.cpp @@ -221,7 +221,7 @@ void IFCImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS free(buffer); } while (read > 0); size_t filesize = fileInfo.uncompressed_size; - if (total < 0 || size_t(total) != filesize) + if (total == 0 || size_t(total) != filesize) { delete[] buff; ThrowException("Failed to decompress IFC ZIP file");