summaryrefslogtreecommitdiffstats
path: root/src/3rdparty/assimp/code/FBXConverter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/3rdparty/assimp/code/FBXConverter.cpp')
-rw-r--r--src/3rdparty/assimp/code/FBXConverter.cpp6062
1 files changed, 3196 insertions, 2866 deletions
diff --git a/src/3rdparty/assimp/code/FBXConverter.cpp b/src/3rdparty/assimp/code/FBXConverter.cpp
index c6ef8c908..692cda836 100644
--- a/src/3rdparty/assimp/code/FBXConverter.cpp
+++ b/src/3rdparty/assimp/code/FBXConverter.cpp
@@ -2,11 +2,11 @@
Open Asset Import Library (assimp)
----------------------------------------------------------------------
-Copyright (c) 2006-2012, assimp team
+Copyright (c) 2006-2016, assimp team
All rights reserved.
-Redistribution and use of this software in source and binary forms,
-with or without modification, are permitted provided that the
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
@@ -24,15 +24,15 @@ following conditions are met:
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
@@ -41,2939 +41,3269 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/** @file FBXConverter.cpp
* @brief Implementation of the FBX DOM -> aiScene converter
*/
-#include "AssimpPCH.h"
#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
-#include <iterator>
-#include <sstream>
-#include <boost/tuple/tuple.hpp>
-
-#include "FBXParser.h"
#include "FBXConverter.h"
+#include "FBXParser.h"
+#include "FBXMeshGeometry.h"
#include "FBXDocument.h"
#include "FBXUtil.h"
#include "FBXProperties.h"
#include "FBXImporter.h"
+#include "StringComparison.h"
+
+#include <assimp/scene.h>
+#include <tuple>
+#include <memory>
+
+#include <iterator>
+#include <sstream>
+#include <vector>
namespace Assimp {
namespace FBX {
- using namespace Util;
+using namespace Util;
#define MAGIC_NODE_TAG "_$AssimpFbx$"
#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000L
- // XXX vc9's debugger won't step into anonymous namespaces
+ // XXX vc9's debugger won't step into anonymous namespaces
//namespace {
/** Dummy class to encapsulate the conversion process */
class Converter
{
public:
-
- /** the different parts that make up the final local transformation of a fbx node */
- enum TransformationComp
- {
- TransformationComp_Translation = 0,
- TransformationComp_RotationOffset,
- TransformationComp_RotationPivot,
- TransformationComp_PreRotation,
- TransformationComp_Rotation,
- TransformationComp_PostRotation,
- TransformationComp_RotationPivotInverse,
- TransformationComp_ScalingOffset,
- TransformationComp_ScalingPivot,
- TransformationComp_Scaling,
- TransformationComp_ScalingPivotInverse,
- TransformationComp_GeometricTranslation,
- TransformationComp_GeometricRotation,
- TransformationComp_GeometricScaling,
-
- TransformationComp_MAXIMUM
- };
+ /**
+ * The different parts that make up the final local transformation of a fbx-node
+ */
+ enum TransformationComp
+ {
+ TransformationComp_Translation = 0,
+ TransformationComp_RotationOffset,
+ TransformationComp_RotationPivot,
+ TransformationComp_PreRotation,
+ TransformationComp_Rotation,
+ TransformationComp_PostRotation,
+ TransformationComp_RotationPivotInverse,
+ TransformationComp_ScalingOffset,
+ TransformationComp_ScalingPivot,
+ TransformationComp_Scaling,
+ TransformationComp_ScalingPivotInverse,
+ TransformationComp_GeometricTranslation,
+ TransformationComp_GeometricRotation,
+ TransformationComp_GeometricScaling,
+
+ TransformationComp_MAXIMUM
+ };
public:
+ Converter( aiScene* out, const Document& doc );
+ ~Converter();
+
+private:
+ // ------------------------------------------------------------------------------------------------
+ // find scene root and trigger recursive scene conversion
+ void ConvertRootNode();
+
+ // ------------------------------------------------------------------------------------------------
+ // collect and assign child nodes
+ void ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4() );
+
+
+ // ------------------------------------------------------------------------------------------------
+ void ConvertLights( const Model& model );
+
+
+ // ------------------------------------------------------------------------------------------------
+ void ConvertCameras( const Model& model );
+
+ // ------------------------------------------------------------------------------------------------
+ void ConvertLight( const Model& model, const Light& light );
+
+ // ------------------------------------------------------------------------------------------------
+ void ConvertCamera( const Model& model, const Camera& cam );
+
+ // ------------------------------------------------------------------------------------------------
+ // this returns unified names usable within assimp identifiers (i.e. no space characters -
+ // while these would be allowed, they are a potential trouble spot so better not use them).
+ const char* NameTransformationComp( TransformationComp comp );
+
+ // ------------------------------------------------------------------------------------------------
+ // note: this returns the REAL fbx property names
+ const char* NameTransformationCompProperty( TransformationComp comp );
+
+ // ------------------------------------------------------------------------------------------------
+ aiVector3D TransformationCompDefaultValue( TransformationComp comp );
- Converter(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
- BOOST_FOREACH(const ObjectMap::value_type& v,doc.Objects()) {
-
- const Object* ob = v.second->Get();
- if(!ob) {
- continue;
- }
-
- const Material* mat = dynamic_cast<const Material*>(ob);
- if(mat) {
-
- if (materials_converted.find(mat) == materials_converted.end()) {
- ConvertMaterial(*mat, 0);
- }
- }
- }
- }
-
- 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;
- }
- }
-
-
- ~Converter()
- {
- std::for_each(meshes.begin(),meshes.end(),Util::delete_fun<aiMesh>());
- std::for_each(materials.begin(),materials.end(),Util::delete_fun<aiMaterial>());
- std::for_each(animations.begin(),animations.end(),Util::delete_fun<aiAnimation>());
- std::for_each(lights.begin(),lights.end(),Util::delete_fun<aiLight>());
- std::for_each(cameras.begin(),cameras.end(),Util::delete_fun<aiCamera>());
- }
+ // ------------------------------------------------------------------------------------------------
+ void GetRotationMatrix( Model::RotOrder mode, const aiVector3D& rotation, aiMatrix4x4& out );
+ // ------------------------------------------------------------------------------------------------
+ /**
+ * checks if a node has more than just scaling, rotation and translation components
+ */
+ bool NeedsComplexTransformationChain( const Model& model );
+ // ------------------------------------------------------------------------------------------------
+ // note: name must be a FixNodeName() result
+ std::string NameTransformationChainNode( const std::string& name, TransformationComp comp );
+
+ // ------------------------------------------------------------------------------------------------
+ /**
+ * note: memory for output_nodes will be managed by the caller
+ */
+ void GenerateTransformationNodeChain( const Model& model, std::vector<aiNode*>& output_nodes );
+
+ // ------------------------------------------------------------------------------------------------
+ void SetupNodeMetadata( const Model& model, aiNode& nd );
+
+ // ------------------------------------------------------------------------------------------------
+ void ConvertModel( const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform );
+
+ // ------------------------------------------------------------------------------------------------
+ // MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed
+ std::vector<unsigned int> ConvertMesh( const MeshGeometry& mesh, const Model& model,
+ const aiMatrix4x4& node_global_transform );
+
+ // ------------------------------------------------------------------------------------------------
+ aiMesh* SetupEmptyMesh( const MeshGeometry& mesh );
+
+ // ------------------------------------------------------------------------------------------------
+ unsigned int ConvertMeshSingleMaterial( const MeshGeometry& mesh, const Model& model,
+ const aiMatrix4x4& node_global_transform );
+
+ // ------------------------------------------------------------------------------------------------
+ std::vector<unsigned int> ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model,
+ const aiMatrix4x4& node_global_transform );
+
+ // ------------------------------------------------------------------------------------------------
+ unsigned int ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model,
+ MatIndexArray::value_type index,
+ const aiMatrix4x4& node_global_transform );
+
+ // ------------------------------------------------------------------------------------------------
+ static const unsigned int NO_MATERIAL_SEPARATION = /* std::numeric_limits<unsigned int>::max() */
+ static_cast<unsigned int>(-1);
+
+
+ // ------------------------------------------------------------------------------------------------
+ /**
+ * - if materialIndex == NO_MATERIAL_SEPARATION, materials are not taken into
+ * account when determining which weights to include.
+ * - outputVertStartIndices is only used when a material index is specified, it gives for
+ * each output vertex the DOM index it maps to.
+ */
+ void ConvertWeights( aiMesh* out, const Model& model, const MeshGeometry& geo,
+ const aiMatrix4x4& node_global_transform = aiMatrix4x4(),
+ unsigned int materialIndex = NO_MATERIAL_SEPARATION,
+ std::vector<unsigned int>* outputVertStartIndices = NULL );
+
+ // ------------------------------------------------------------------------------------------------
+ void ConvertCluster( std::vector<aiBone*>& bones, const Model& /*model*/, const Cluster& cl,
+ std::vector<size_t>& out_indices,
+ std::vector<size_t>& index_out_indices,
+ std::vector<size_t>& count_out_indices,
+ const aiMatrix4x4& node_global_transform );
+
+ // ------------------------------------------------------------------------------------------------
+ void ConvertMaterialForMesh( aiMesh* out, const Model& model, const MeshGeometry& geo,
+ MatIndexArray::value_type materialIndex );
+
+ // ------------------------------------------------------------------------------------------------
+ unsigned int GetDefaultMaterial();
+
+
+ // ------------------------------------------------------------------------------------------------
+ // Material -> aiMaterial
+ unsigned int ConvertMaterial( const Material& material, const MeshGeometry* const mesh );
+
+ // ------------------------------------------------------------------------------------------------
+ // Video -> aiTexture
+ unsigned int ConvertVideo( const Video& video );
+
+ // ------------------------------------------------------------------------------------------------
+ void TrySetTextureProperties( aiMaterial* out_mat, const TextureMap& textures,
+ const std::string& propName,
+ aiTextureType target, const MeshGeometry* const mesh );
+
+ // ------------------------------------------------------------------------------------------------
+ void TrySetTextureProperties( aiMaterial* out_mat, const LayeredTextureMap& layeredTextures,
+ const std::string& propName,
+ aiTextureType target, const MeshGeometry* const mesh );
+
+ // ------------------------------------------------------------------------------------------------
+ void SetTextureProperties( aiMaterial* out_mat, const TextureMap& textures, const MeshGeometry* const mesh );
+
+ // ------------------------------------------------------------------------------------------------
+ void SetTextureProperties( aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh );
+
+ // ------------------------------------------------------------------------------------------------
+ aiColor3D GetColorPropertyFromMaterial( const PropertyTable& props, const std::string& baseName,
+ bool& result );
+
+ // ------------------------------------------------------------------------------------------------
+ void SetShadingPropertiesCommon( aiMaterial* out_mat, const PropertyTable& props );
+
+ // ------------------------------------------------------------------------------------------------
+ // get the number of fps for a FrameRate enumerated value
+ static double FrameRateToDouble( FileGlobalSettings::FrameRate fp, double customFPSVal = -1.0 );
+
+ // ------------------------------------------------------------------------------------------------
+ // convert animation data to aiAnimation et al
+ void ConvertAnimations();
+
+ // ------------------------------------------------------------------------------------------------
+ // rename a node already partially converted. fixed_name is a string previously returned by
+ // FixNodeName, new_name specifies the string FixNodeName should return on all further invocations
+ // which would previously have returned the old value.
+ //
+ // this also updates names in node animations, cameras and light sources and is thus slow.
+ //
+ // NOTE: the caller is responsible for ensuring that the new name is unique and does
+ // not collide with any other identifiers. The best way to ensure this is to only
+ // append to the old name, which is guaranteed to match these requirements.
+ void RenameNode( const std::string& fixed_name, const std::string& new_name );
+
+ // ------------------------------------------------------------------------------------------------
+ // takes a fbx node name and returns the identifier to be used in the assimp output scene.
+ // 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 );
+
+ typedef std::map<const AnimationCurveNode*, const AnimationLayer*> LayerMap;
+
+ // XXX: better use multi_map ..
+ typedef std::map<std::string, std::vector<const AnimationCurveNode*> > NodeMap;
+
+
+ // ------------------------------------------------------------------------------------------------
+ void ConvertAnimationStack( const AnimationStack& st );
+
+ // ------------------------------------------------------------------------------------------------
+ void GenerateNodeAnimations( std::vector<aiNodeAnim*>& node_anims,
+ const std::string& fixed_name,
+ const std::vector<const AnimationCurveNode*>& curves,
+ const LayerMap& layer_map,
+ int64_t start, int64_t stop,
+ double& max_time,
+ double& min_time );
+
+ // ------------------------------------------------------------------------------------------------
+ bool IsRedundantAnimationData( const Model& target,
+ TransformationComp comp,
+ const std::vector<const AnimationCurveNode*>& curves );
+
+ // ------------------------------------------------------------------------------------------------
+ aiNodeAnim* GenerateRotationNodeAnim( const std::string& name,
+ const Model& target,
+ const std::vector<const AnimationCurveNode*>& curves,
+ const LayerMap& layer_map,
+ int64_t start, int64_t stop,
+ double& max_time,
+ double& min_time );
+
+ // ------------------------------------------------------------------------------------------------
+ aiNodeAnim* GenerateScalingNodeAnim( const std::string& name,
+ const Model& /*target*/,
+ const std::vector<const AnimationCurveNode*>& curves,
+ const LayerMap& layer_map,
+ int64_t start, int64_t stop,
+ double& max_time,
+ double& min_time );
+
+ // ------------------------------------------------------------------------------------------------
+ aiNodeAnim* GenerateTranslationNodeAnim( const std::string& name,
+ const Model& /*target*/,
+ const std::vector<const AnimationCurveNode*>& curves,
+ const LayerMap& layer_map,
+ int64_t start, int64_t stop,
+ double& max_time,
+ double& min_time,
+ bool inverse = false );
+
+ // ------------------------------------------------------------------------------------------------
+ // generate node anim, extracting only Rotation, Scaling and Translation from the given chain
+ aiNodeAnim* 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 = false );
+
+ // key (time), value, mapto (component index)
+ typedef std::tuple<std::shared_ptr<KeyTimeList>, std::shared_ptr<KeyValueList>, unsigned int > KeyFrameList;
+ typedef std::vector<KeyFrameList> KeyFrameListList;
+
+
+
+ // ------------------------------------------------------------------------------------------------
+ KeyFrameListList GetKeyframeList( const std::vector<const AnimationCurveNode*>& nodes, int64_t start, int64_t stop );
+
+ // ------------------------------------------------------------------------------------------------
+ KeyTimeList GetKeyTimeList( const KeyFrameListList& inputs );
+
+ // ------------------------------------------------------------------------------------------------
+ void InterpolateKeys( aiVectorKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
+ const aiVector3D& def_value,
+ double& max_time,
+ double& min_time );
+
+ // ------------------------------------------------------------------------------------------------
+ void InterpolateKeys( aiQuatKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
+ const aiVector3D& def_value,
+ double& maxTime,
+ double& minTime,
+ Model::RotOrder order );
+
+ // ------------------------------------------------------------------------------------------------
+ void 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 );
+
+ // ------------------------------------------------------------------------------------------------
+ // euler xyz -> quat
+ aiQuaternion EulerToQuaternion( const aiVector3D& rot, Model::RotOrder order );
+
+ // ------------------------------------------------------------------------------------------------
+ void ConvertScaleKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, const LayerMap& /*layers*/,
+ int64_t start, int64_t stop,
+ double& maxTime,
+ double& minTime );
+
+ // ------------------------------------------------------------------------------------------------
+ void ConvertTranslationKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
+ const LayerMap& /*layers*/,
+ int64_t start, int64_t stop,
+ double& maxTime,
+ double& minTime );
+
+ // ------------------------------------------------------------------------------------------------
+ void ConvertRotationKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
+ const LayerMap& /*layers*/,
+ int64_t start, int64_t stop,
+ double& maxTime,
+ double& minTime,
+ Model::RotOrder order );
+
+ // ------------------------------------------------------------------------------------------------
+ // copy generated meshes, animations, lights, cameras and textures to the output scene
+ void TransferDataToScene();
private:
- // ------------------------------------------------------------------------------------------------
- // find scene root and trigger recursive scene conversion
- void ConvertRootNode()
- {
- out->mRootNode = new aiNode();
- out->mRootNode->mName.Set("RootNode");
+ // 0: not assigned yet, others: index is value - 1
+ unsigned int defaultMaterialIndex;
+
+ std::vector<aiMesh*> meshes;
+ std::vector<aiMaterial*> materials;
+ std::vector<aiAnimation*> animations;
+ std::vector<aiLight*> lights;
+ std::vector<aiCamera*> cameras;
+ std::vector<aiTexture*> textures;
+
+ typedef std::map<const Material*, unsigned int> MaterialMap;
+ MaterialMap materials_converted;
+
+ typedef std::map<const Video*, unsigned int> VideoMap;
+ VideoMap textures_converted;
+
+ typedef std::map<const Geometry*, std::vector<unsigned int> > MeshMap;
+ MeshMap meshes_converted;
+
+ // fixed node name -> which trafo chain components have animations?
+ typedef std::map<std::string, unsigned int> NodeAnimBitMap;
+ NodeAnimBitMap node_anim_chain_bits;
+
+ // name -> has had its prefix_stripped?
+ typedef std::map<std::string, bool> NodeNameMap;
+ NodeNameMap node_names;
+
+ typedef std::map<std::string, std::string> NameNameMap;
+ NameNameMap renamed_nodes;
+
+ double anim_fps;
+
+ aiScene* const out;
+ const FBX::Document& doc;
+};
+
+Converter::Converter( 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() ) {
+
+ const Object* ob = v.second->Get();
+ if ( !ob ) {
+ continue;
+ }
+
+ const Material* mat = dynamic_cast<const Material*>( ob );
+ if ( mat ) {
+
+ if ( materials_converted.find( mat ) == materials_converted.end() ) {
+ ConvertMaterial( *mat, 0 );
+ }
+ }
+ }
+ }
+
+ 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;
+ }
+}
+
+
+Converter::~Converter()
+{
+ std::for_each( meshes.begin(), meshes.end(), Util::delete_fun<aiMesh>() );
+ std::for_each( materials.begin(), materials.end(), Util::delete_fun<aiMaterial>() );
+ std::for_each( animations.begin(), animations.end(), Util::delete_fun<aiAnimation>() );
+ std::for_each( lights.begin(), lights.end(), Util::delete_fun<aiLight>() );
+ std::for_each( cameras.begin(), cameras.end(), Util::delete_fun<aiCamera>() );
+ std::for_each( textures.begin(), textures.end(), Util::delete_fun<aiTexture>() );
+}
+
+void Converter::ConvertRootNode()
+{
+ out->mRootNode = new aiNode();
+ out->mRootNode->mName.Set( "RootNode" );
+
+ // root has ID 0
+ ConvertNodes( 0L, *out->mRootNode );
+}
+
+
+void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform )
+{
+ const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced( id, "Model" );
+
+ std::vector<aiNode*> nodes;
+ nodes.reserve( conns.size() );
+
+ std::vector<aiNode*> nodes_chain;
+
+ try {
+ for( const Connection* con : conns ) {
+
+ // ignore object-property links
+ if ( con->PropertyName().length() ) {
+ continue;
+ }
+
+ const Object* const object = con->SourceObject();
+ if ( !object ) {
+ FBXImporter::LogWarn( "failed to convert source object for Model link" );
+ continue;
+ }
- // root has ID 0
- ConvertNodes(0L, *out->mRootNode);
- }
+ const Model* const model = dynamic_cast<const Model*>( object );
+ if ( model ) {
+ nodes_chain.clear();
- // ------------------------------------------------------------------------------------------------
- // collect and assign child nodes
- void ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4())
- {
- const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(id, "Model");
+ aiMatrix4x4 new_abs_transform = parent_transform;
- std::vector<aiNode*> nodes;
- nodes.reserve(conns.size());
-
- std::vector<aiNode*> nodes_chain;
-
- try {
- BOOST_FOREACH(const Connection* con, conns) {
+ // 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 );
- // ignore object-property links
- if(con->PropertyName().length()) {
- continue;
- }
-
- const Object* const object = con->SourceObject();
- if(!object) {
- FBXImporter::LogWarn("failed to convert source object for Model link");
- continue;
- }
-
- const Model* const model = dynamic_cast<const Model*>(object);
-
- if(model) {
- 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);
-
- ai_assert(nodes_chain.size());
-
- const 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;
- BOOST_FOREACH(aiNode* prenode, nodes_chain) {
- if ( !strcmp(prenode->mName.C_Str(), original_name.c_str()) ) {
- name_carrier = prenode;
- break;
- }
- }
-
- if(!name_carrier) {
- nodes_chain.push_back(new aiNode(original_name));
- name_carrier = nodes_chain.back();
- }
-
- //setup metadata on newest node
- SetupNodeMetadata(*model, *nodes_chain.back());
-
- // link all nodes in a row
- aiNode* last_parent = &parent;
- BOOST_FOREACH(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;
- }
-
- prenode->mParent = last_parent;
- last_parent = prenode;
-
- new_abs_transform *= prenode->mTransformation;
- }
-
- // attach geometry
- ConvertModel(*model, *nodes_chain.back(), new_abs_transform);
-
- // attach sub-nodes
- ConvertNodes(model->ID(), *nodes_chain.back(), new_abs_transform);
-
- if(doc.Settings().readLights) {
- ConvertLights(*model);
- }
-
- if(doc.Settings().readCameras) {
- ConvertCameras(*model);
- }
-
- nodes.push_back(nodes_chain.front());
- nodes_chain.clear();
- }
- }
-
- if(nodes.size()) {
- parent.mChildren = new aiNode*[nodes.size()]();
- parent.mNumChildren = static_cast<unsigned int>(nodes.size());
-
- std::swap_ranges(nodes.begin(),nodes.end(),parent.mChildren);
- }
- }
- catch(std::exception&) {
- Util::delete_fun<aiNode> deleter;
- std::for_each(nodes.begin(),nodes.end(),deleter);
- std::for_each(nodes_chain.begin(),nodes_chain.end(),deleter);
- }
- }
-
-
- // ------------------------------------------------------------------------------------------------
- void ConvertLights(const Model& model)
- {
- const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
- BOOST_FOREACH(const NodeAttribute* attr, node_attrs) {
- const Light* const light = dynamic_cast<const Light*>(attr);
- if(light) {
- ConvertLight(model, *light);
- }
- }
- }
-
-
- // ------------------------------------------------------------------------------------------------
- void ConvertCameras(const Model& model)
- {
- const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
- BOOST_FOREACH(const NodeAttribute* attr, node_attrs) {
- const Camera* const cam = dynamic_cast<const Camera*>(attr);
- if(cam) {
- ConvertCamera(model, *cam);
- }
- }
- }
-
-
- // ------------------------------------------------------------------------------------------------
- void ConvertLight(const Model& model, const Light& light)
- {
- lights.push_back(new aiLight());
- aiLight* const out_light = lights.back();
-
- out_light->mName.Set(FixNodeName(model.Name()));
-
- const float intensity = light.Intensity();
- 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;
-
- 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);
- }
-
- // XXX: how to best convert the near and far decay ranges?
- switch(light.DecayType())
- {
- case Light::Decay_None:
- out_light->mAttenuationConstant = 1.0f;
- break;
- case Light::Decay_Linear:
- out_light->mAttenuationLinear = 1.0f;
- break;
- case Light::Decay_Quadratic:
- out_light->mAttenuationQuadratic = 1.0f;
- 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 ConvertCamera(const Model& model, const Camera& cam)
- {
- cameras.push_back(new aiCamera());
- aiCamera* const out_camera = cameras.back();
-
- out_camera->mName.Set(FixNodeName(model.Name()));
-
- out_camera->mAspect = cam.AspectWidth() / cam.AspectHeight();
- out_camera->mPosition = cam.Position();
- out_camera->mLookAt = cam.InterestPosition() - out_camera->mPosition;
-
- // BUG HERE cam.FieldOfView() returns 1.0f every time. 1.0f is default value.
- out_camera->mHorizontalFOV = AI_DEG_TO_RAD(cam.FieldOfView());
- }
-
-
- // ------------------------------------------------------------------------------------------------
- // this returns unified names usable within assimp identifiers (i.e. no space characters -
- // while these would be allowed, they are a potential trouble spot so better not use them).
- const char* 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_MAXIMUM: // this is to silence compiler warnings
- break;
- }
-
- ai_assert(false);
- return NULL;
- }
-
-
- // ------------------------------------------------------------------------------------------------
- // note: this returns the REAL fbx property names
- const char* 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_MAXIMUM: // this is to silence compiler warnings
- break;
- }
-
- ai_assert(false);
- return NULL;
- }
-
-
- // ------------------------------------------------------------------------------------------------
- aiVector3D 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 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);
- }
-
- ai_assert((order[0] >= 0) && (order[0] <= 2));
- ai_assert((order[1] >= 0) && (order[1] <= 2));
- ai_assert((order[2] >= 0) && (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]];
- }
- }
-
-
- // ------------------------------------------------------------------------------------------------
- /** checks if a node has more than just scaling, rotation and translation components */
- bool NeedsComplexTransformationChain(const Model& model)
- {
- const PropertyTable& props = model.Props();
- bool ok;
-
- const float zero_epsilon = 1e-6f;
- 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 ||
- comp == TransformationComp_GeometricScaling || comp == TransformationComp_GeometricRotation || comp == TransformationComp_GeometricTranslation ) {
- continue;
- }
-
- const aiVector3D& v = PropertyGet<aiVector3D>(props,NameTransformationCompProperty(comp),ok);
- if(ok && v.SquareLength() > zero_epsilon) {
- return true;
- }
- }
-
- return false;
- }
-
-
- // ------------------------------------------------------------------------------------------------
- // note: name must be a FixNodeName() result
- std::string NameTransformationChainNode(const std::string& name, TransformationComp comp)
- {
- return name + std::string(MAGIC_NODE_TAG) + "_" + NameTransformationComp(comp);
- }
-
-
- // ------------------------------------------------------------------------------------------------
- /** note: memory for output_nodes will be managed by the caller */
- void GenerateTransformationNodeChain(const Model& model,
- std::vector<aiNode*>& 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<unsigned int>(TransformationComp_MAXIMUM), aiMatrix4x4());
-
- // generate transformation matrices for all the different transformation components
- const float zero_epsilon = 1e-6f;
- bool is_complex = false;
-
- const aiVector3D& PreRotation = PropertyGet<aiVector3D>(props,"PreRotation",ok);
- if(ok && PreRotation.SquareLength() > zero_epsilon) {
- is_complex = true;
-
- GetRotationMatrix(rot, PreRotation, chain[TransformationComp_PreRotation]);
- }
-
- const aiVector3D& PostRotation = PropertyGet<aiVector3D>(props,"PostRotation",ok);
- if(ok && PostRotation.SquareLength() > zero_epsilon) {
- is_complex = true;
-
- GetRotationMatrix(rot, PostRotation, chain[TransformationComp_PostRotation]);
- }
-
- const aiVector3D& RotationPivot = PropertyGet<aiVector3D>(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<aiVector3D>(props,"RotationOffset",ok);
- if(ok && RotationOffset.SquareLength() > zero_epsilon) {
- is_complex = true;
-
- aiMatrix4x4::Translation(RotationOffset,chain[TransformationComp_RotationOffset]);
- }
-
- const aiVector3D& ScalingOffset = PropertyGet<aiVector3D>(props,"ScalingOffset",ok);
- if(ok && ScalingOffset.SquareLength() > zero_epsilon) {
- is_complex = true;
-
- aiMatrix4x4::Translation(ScalingOffset,chain[TransformationComp_ScalingOffset]);
- }
-
- const aiVector3D& ScalingPivot = PropertyGet<aiVector3D>(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<aiVector3D>(props,"Lcl Translation",ok);
- if(ok && Translation.SquareLength() > zero_epsilon) {
- aiMatrix4x4::Translation(Translation,chain[TransformationComp_Translation]);
- }
-
- const aiVector3D& Scaling = PropertyGet<aiVector3D>(props,"Lcl Scaling",ok);
- if(ok && std::fabs(Scaling.SquareLength()-1.0f) > zero_epsilon) {
- aiMatrix4x4::Scaling(Scaling,chain[TransformationComp_Scaling]);
- }
-
- const aiVector3D& Rotation = PropertyGet<aiVector3D>(props,"Lcl Rotation",ok);
- if(ok && Rotation.SquareLength() > zero_epsilon) {
- GetRotationMatrix(rot, Rotation, chain[TransformationComp_Rotation]);
- }
-
- const aiVector3D& GeometricScaling = PropertyGet<aiVector3D>(props, "GeometricScaling", ok);
- if (ok && std::fabs(GeometricScaling.SquareLength() - 1.0f) > zero_epsilon) {
- aiMatrix4x4::Scaling(GeometricScaling, chain[TransformationComp_GeometricScaling]);
- }
-
- const aiVector3D& GeometricRotation = PropertyGet<aiVector3D>(props, "GeometricRotation", ok);
- if (ok && GeometricRotation.SquareLength() > zero_epsilon) {
- GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotation]);
- }
-
- const aiVector3D& GeometricTranslation = PropertyGet<aiVector3D>(props, "GeometricTranslation", ok);
- if (ok && GeometricTranslation.SquareLength() > zero_epsilon){
- aiMatrix4x4::Translation(GeometricTranslation, chain[TransformationComp_GeometricTranslation]);
- }
-
- // 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);
-
- const 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<TransformationComp>(i);
-
- if (chain[i].IsIdentity() && (anim_chain_bitmask & bit) == 0) {
- continue;
- }
-
- aiNode* nd = new aiNode();
- output_nodes.push_back(nd);
-
- nd->mName.Set(NameTransformationChainNode(name, comp));
- nd->mTransformation = chain[i];
- }
-
- ai_assert(output_nodes.size());
- return;
- }
-
- // else, we can just multiply the matrices together
- aiNode* nd = new aiNode();
- output_nodes.push_back(nd);
-
- nd->mName.Set(name);
-
- for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) {
- nd->mTransformation = nd->mTransformation * chain[i];
- }
- }
-
- // ------------------------------------------------------------------------------------------------
-
- void SetupNodeMetadata(const Model& model, aiNode& nd)
- {
- const PropertyTable& props = model.Props();
- DirectPropertyMap unparsedProperties = props.GetUnparsedProperties();
-
- // create metadata on node
- std::size_t numStaticMetaData = 2;
- aiMetadata* data = new aiMetadata();
- data->mNumProperties = unparsedProperties.size() + numStaticMetaData;
- data->mKeys = new aiString[data->mNumProperties]();
- data->mValues = new aiMetadataEntry[data->mNumProperties]();
- nd.mMetaData = data;
- int index = 0;
-
- // find user defined properties (3ds Max)
- data->Set(index++, "UserProperties", aiString(PropertyGet<std::string>(props, "UDP3DSMAX", "")));
- unparsedProperties.erase("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
- BOOST_FOREACH(const DirectPropertyMap::value_type& prop, unparsedProperties) {
-
- // Interpret the property as a concrete type
- if (const TypedProperty<bool>* interpreted = prop.second->As<TypedProperty<bool> >())
- data->Set(index++, prop.first, interpreted->Value());
- else if (const TypedProperty<int>* interpreted = prop.second->As<TypedProperty<int> >())
- data->Set(index++, prop.first, interpreted->Value());
- else if (const TypedProperty<uint64_t>* interpreted = prop.second->As<TypedProperty<uint64_t> >())
- data->Set(index++, prop.first, interpreted->Value());
- else if (const TypedProperty<float>* interpreted = prop.second->As<TypedProperty<float> >())
- data->Set(index++, prop.first, interpreted->Value());
- else if (const TypedProperty<std::string>* interpreted = prop.second->As<TypedProperty<std::string> >())
- data->Set(index++, prop.first, aiString(interpreted->Value()));
- else if (const TypedProperty<aiVector3D>* interpreted = prop.second->As<TypedProperty<aiVector3D> >())
- data->Set(index++, prop.first, interpreted->Value());
- else
- assert(false);
- }
- }
-
- // ------------------------------------------------------------------------------------------------
- void ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform)
- {
- const std::vector<const Geometry*>& geos = model.GetGeometry();
-
- std::vector<unsigned int> meshes;
- meshes.reserve(geos.size());
-
- BOOST_FOREACH(const Geometry* geo, geos) {
-
- const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*>(geo);
- if(mesh) {
- const std::vector<unsigned int>& indices = ConvertMesh(*mesh, model, node_global_transform);
- 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);
- }
- }
-
-
- // ------------------------------------------------------------------------------------------------
- // MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed
- std::vector<unsigned int> ConvertMesh(const MeshGeometry& mesh,const Model& model,
- const aiMatrix4x4& node_global_transform)
- {
- std::vector<unsigned int> 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<aiVector3D>& vertices = mesh.GetVertices();
- const std::vector<unsigned int>& 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];
- BOOST_FOREACH(MatIndexArray::value_type index, mindices) {
- if(index != base) {
- return ConvertMeshMultiMaterial(mesh, model, node_global_transform);
- }
- }
- }
-
- // faster codepath, just copy the data
- temp.push_back(ConvertMeshSingleMaterial(mesh, model, node_global_transform));
- return temp;
- }
-
-
- // ------------------------------------------------------------------------------------------------
- aiMesh* SetupEmptyMesh(const MeshGeometry& mesh)
- {
- aiMesh* const out_mesh = new aiMesh();
- meshes.push_back(out_mesh);
- meshes_converted[&mesh].push_back(static_cast<unsigned int>(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);
- }
-
- return out_mesh;
- }
-
-
- // ------------------------------------------------------------------------------------------------
- unsigned int ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model,
- const aiMatrix4x4& node_global_transform)
- {
- const MatIndexArray& mindices = mesh.GetMaterialIndices();
- aiMesh* const out_mesh = SetupEmptyMesh(mesh);
-
- const std::vector<aiVector3D>& vertices = mesh.GetVertices();
- const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
-
- // copy vertices
- out_mesh->mNumVertices = static_cast<unsigned int>(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<unsigned int>(faces.size());
- aiFace* fac = out_mesh->mFaces = new aiFace[faces.size()]();
-
- unsigned int cursor = 0;
- BOOST_FOREACH(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<aiVector3D>& 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<aiVector3D>& tangents = mesh.GetTangents();
- const std::vector<aiVector3D>* binormals = &mesh.GetBinormals();
-
- if(tangents.size()) {
- std::vector<aiVector3D> 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() && 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<aiVector2D>& uvs = mesh.GetTextureCoords(i);
- if(uvs.empty()) {
- break;
- }
-
- aiVector3D* out_uv = out_mesh->mTextureCoords[i] = new aiVector3D[vertices.size()];
- BOOST_FOREACH(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<aiColor4D>& 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<unsigned int>(meshes.size() - 1);
- }
-
-
- // ------------------------------------------------------------------------------------------------
- std::vector<unsigned int> ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
- const aiMatrix4x4& node_global_transform)
- {
- const MatIndexArray& mindices = mesh.GetMaterialIndices();
- ai_assert(mindices.size());
-
- std::set<MatIndexArray::value_type> had;
- std::vector<unsigned int> indices;
-
- BOOST_FOREACH(MatIndexArray::value_type index, mindices) {
- if(had.find(index) == had.end()) {
-
- indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, node_global_transform));
- had.insert(index);
- }
- }
-
- return indices;
- }
-
-
- // ------------------------------------------------------------------------------------------------
- unsigned int ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
- MatIndexArray::value_type index,
- const aiMatrix4x4& node_global_transform)
- {
- aiMesh* const out_mesh = SetupEmptyMesh(mesh);
-
- const MatIndexArray& mindices = mesh.GetMaterialIndices();
- const std::vector<aiVector3D>& vertices = mesh.GetVertices();
- const std::vector<unsigned int>& 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<unsigned int>::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<unsigned int> 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<aiVector3D>& 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<aiVector3D>& tangents = mesh.GetTangents();
- const std::vector<aiVector3D>* binormals = &mesh.GetBinormals();
-
- if(tangents.size()) {
- std::vector<aiVector3D> tempBinormals;
- 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<aiVector2D>& 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<aiColor4D>& 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 i = 0; i < num_uvs; ++i) {
- const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords(i);
- out_mesh->mTextureCoords[i][cursor] = aiVector3D(uvs[in_cursor].x,uvs[in_cursor].y, 0.0f);
- }
-
- for (unsigned int i = 0; i < num_vcs; ++i) {
- const std::vector<aiColor4D>& cols = mesh.GetVertexColors(i);
- out_mesh->mColors[i][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<unsigned int>(meshes.size() - 1);
- }
-
- static const unsigned int NO_MATERIAL_SEPARATION = /* std::numeric_limits<unsigned int>::max() */
- static_cast<unsigned int>(-1);
-
-
- // ------------------------------------------------------------------------------------------------
- /** - if materialIndex == NO_MATERIAL_SEPARATION, materials are not taken into
- * account when determining which weights to include.
- * - outputVertStartIndices is only used when a material index is specified, it gives for
- * each output vertex the DOM index it maps to. */
- void ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo,
- const aiMatrix4x4& node_global_transform = aiMatrix4x4(),
- unsigned int materialIndex = NO_MATERIAL_SEPARATION,
- std::vector<unsigned int>* outputVertStartIndices = NULL)
- {
- ai_assert(geo.DeformerSkin());
-
- std::vector<size_t> out_indices;
- std::vector<size_t> index_out_indices;
- std::vector<size_t> count_out_indices;
-
- const Skin& sk = *geo.DeformerSkin();
-
- std::vector<aiBone*> bones;
- bones.reserve(sk.Clusters().size());
-
- const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION;
- ai_assert(no_mat_check || outputVertStartIndices);
-
- try {
-
- BOOST_FOREACH(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<size_t>::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.
- BOOST_FOREACH(WeightIndexArray::value_type index, indices) {
-
- unsigned int count = 0;
- const unsigned int* const out_idx = geo.ToOutputVertexIndex(index, count);
-
- 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<size_t>(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<unsigned int>::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<aiBone>());
- throw;
- }
-
- if(bones.empty()) {
- return;
- }
-
- out->mBones = new aiBone*[bones.size()]();
- out->mNumBones = static_cast<unsigned int>(bones.size());
-
- std::swap_ranges(bones.begin(),bones.end(),out->mBones);
- }
+ ai_assert( nodes_chain.size() );
+ const 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;
+ }
+ }
- // ------------------------------------------------------------------------------------------------
- void ConvertCluster(std::vector<aiBone*>& bones, const Model& model, const Cluster& cl,
- std::vector<size_t>& out_indices,
- std::vector<size_t>& index_out_indices,
- std::vector<size_t>& count_out_indices,
- const aiMatrix4x4& node_global_transform)
- {
+ if ( !name_carrier ) {
+ nodes_chain.push_back( new aiNode( original_name ) );
+ name_carrier = nodes_chain.back();
+ }
- aiBone* const bone = new aiBone();
- bones.push_back(bone);
+ //setup metadata on newest node
+ SetupNodeMetadata( *model, *nodes_chain.back() );
- bone->mName = FixNodeName(cl.TargetNode()->Name());
+ // link all nodes in a row
+ aiNode* last_parent = &parent;
+ for( aiNode* prenode : nodes_chain ) {
+ ai_assert( prenode );
- bone->mOffsetMatrix = cl.TransformLink();
- bone->mOffsetMatrix.Inverse();
-
- bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform;
-
- bone->mNumWeights = static_cast<unsigned int>(out_indices.size());
- aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[out_indices.size()];
-
- const size_t no_index_sentinel = std::numeric_limits<size_t>::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<unsigned int>(out_indices[index_index + j]);
- out_weight.mWeight = weights[i];
- }
- }
- }
-
-
- // ------------------------------------------------------------------------------------------------
- void ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo,
- MatIndexArray::value_type materialIndex)
- {
- // locate source materials for this mesh
- const std::vector<const Material*>& mats = model.GetMaterials();
- if (static_cast<unsigned int>(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 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;
- }
-
-
- // ------------------------------------------------------------------------------------------------
- // Material -> aiMaterial
- unsigned int 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<unsigned int>(materials.size());
-
- materials.push_back(out_mat);
-
- aiString str;
-
- // stip 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<unsigned int>(materials.size() - 1);
- }
-
-
- // ------------------------------------------------------------------------------------------------
- void 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;
- path.Set(tex->RelativeFilename());
-
- 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<std::string>(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<unsigned int>(std::distance(materials.begin(),
- std::find(materials.begin(),materials.end(),out_mat)
- ));
-
-
- uvIndex = -1;
- if (!mesh)
- {
- BOOST_FOREACH(const MeshMap::value_type& v,meshes_converted) {
- const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*> (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<int>(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<int>(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 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;
- }
-
- const Texture* const tex = (*it).second->getTexture();
-
- aiString path;
- path.Set(tex->RelativeFilename());
-
- 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<std::string>(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<unsigned int>(std::distance(materials.begin(),
- std::find(materials.begin(),materials.end(),out_mat)
- ));
-
- uvIndex = -1;
- if (!mesh)
- {
- BOOST_FOREACH(const MeshMap::value_type& v,meshes_converted) {
- const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*> (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<int>(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");
- }
- }
+ if ( last_parent != &parent ) {
+ last_parent->mNumChildren = 1;
+ last_parent->mChildren = new aiNode*[ 1 ];
+ last_parent->mChildren[ 0 ] = prenode;
+ }
+
+ prenode->mParent = last_parent;
+ last_parent = prenode;
+
+ new_abs_transform *= prenode->mTransformation;
+ }
+
+ // attach geometry
+ ConvertModel( *model, *nodes_chain.back(), new_abs_transform );
+
+ // attach sub-nodes
+ ConvertNodes( model->ID(), *nodes_chain.back(), new_abs_transform );
+
+ if ( doc.Settings().readLights ) {
+ ConvertLights( *model );
+ }
+
+ if ( doc.Settings().readCameras ) {
+ ConvertCameras( *model );
+ }
+
+ nodes.push_back( nodes_chain.front() );
+ nodes_chain.clear();
+ }
+ }
+
+ if ( nodes.size() ) {
+ parent.mChildren = new aiNode*[ nodes.size() ]();
+ parent.mNumChildren = static_cast<unsigned int>( nodes.size() );
+
+ std::swap_ranges( nodes.begin(), nodes.end(), parent.mChildren );
}
+ }
+ catch ( std::exception& ) {
+ Util::delete_fun<aiNode> deleter;
+ std::for_each( nodes.begin(), nodes.end(), deleter );
+ std::for_each( nodes_chain.begin(), nodes_chain.end(), deleter );
+ }
+}
+
+
+void Converter::ConvertLights( const Model& model )
+{
+ const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
+ for( const NodeAttribute* attr : node_attrs ) {
+ const Light* const light = dynamic_cast<const Light*>( attr );
+ if ( light ) {
+ ConvertLight( model, *light );
+ }
+ }
+}
+
+void Converter::ConvertCameras( const Model& model )
+{
+ const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
+ for( const NodeAttribute* attr : node_attrs ) {
+ const Camera* const cam = dynamic_cast<const Camera*>( attr );
+ if ( cam ) {
+ ConvertCamera( model, *cam );
+ }
+ }
+}
+
+
+void Converter::ConvertLight( const Model& model, const Light& light )
+{
+ lights.push_back( new aiLight() );
+ aiLight* const out_light = lights.back();
+
+ out_light->mName.Set( FixNodeName( model.Name() ) );
+
+ const float intensity = light.Intensity();
+ 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;
+
+ 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 );
+ }
+
+ // XXX: how to best convert the near and far decay ranges?
+ switch ( light.DecayType() )
+ {
+ case Light::Decay_None:
+ out_light->mAttenuationConstant = 1.0f;
+ break;
+ case Light::Decay_Linear:
+ out_light->mAttenuationLinear = 1.0f;
+ break;
+ case Light::Decay_Quadratic:
+ out_light->mAttenuationQuadratic = 1.0f;
+ 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 Converter::ConvertCamera( const Model& model, const Camera& cam )
+{
+ cameras.push_back( new aiCamera() );
+ aiCamera* const out_camera = cameras.back();
+
+ out_camera->mName.Set( FixNodeName( model.Name() ) );
+
+ out_camera->mAspect = cam.AspectWidth() / cam.AspectHeight();
+ out_camera->mPosition = cam.Position();
+ out_camera->mUp = cam.UpVector();
+ out_camera->mLookAt = cam.InterestPosition() - out_camera->mPosition;
+ out_camera->mHorizontalFOV = AI_DEG_TO_RAD( cam.FieldOfView() );
+}
+
+
+const char* Converter::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_MAXIMUM: // this is to silence compiler warnings
+ default:
+ break;
+ }
+
+ ai_assert( false );
+ return NULL;
+}
+
+
+const char* Converter::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_MAXIMUM: // this is to silence compiler warnings
+ break;
+ }
+
+ ai_assert( false );
+ return NULL;
+}
+
+aiVector3D Converter::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 Converter::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 );
+ }
+
+ ai_assert( ( order[ 0 ] >= 0 ) && ( order[ 0 ] <= 2 ) );
+ ai_assert( ( order[ 1 ] >= 0 ) && ( order[ 1 ] <= 2 ) );
+ ai_assert( ( order[ 2 ] >= 0 ) && ( 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 Converter::NeedsComplexTransformationChain( const Model& model )
+{
+ const PropertyTable& props = model.Props();
+ bool ok;
+
+ const float zero_epsilon = 1e-6f;
+ 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 ||
+ comp == TransformationComp_GeometricScaling || comp == TransformationComp_GeometricRotation || comp == TransformationComp_GeometricTranslation ) {
+ continue;
+ }
+
+ const aiVector3D& v = PropertyGet<aiVector3D>( props, NameTransformationCompProperty( comp ), ok );
+ if ( ok && v.SquareLength() > zero_epsilon ) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+std::string Converter::NameTransformationChainNode( const std::string& name, TransformationComp comp )
+{
+ return name + std::string( MAGIC_NODE_TAG ) + "_" + NameTransformationComp( comp );
+}
+
+void Converter::GenerateTransformationNodeChain( const Model& model,
+ std::vector<aiNode*>& 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<unsigned int>( TransformationComp_MAXIMUM ), aiMatrix4x4() );
+
+ // generate transformation matrices for all the different transformation components
+ const float zero_epsilon = 1e-6f;
+ bool is_complex = false;
+
+ const aiVector3D& PreRotation = PropertyGet<aiVector3D>( props, "PreRotation", ok );
+ if ( ok && PreRotation.SquareLength() > zero_epsilon ) {
+ is_complex = true;
+
+ GetRotationMatrix( rot, PreRotation, chain[ TransformationComp_PreRotation ] );
+ }
+
+ const aiVector3D& PostRotation = PropertyGet<aiVector3D>( props, "PostRotation", ok );
+ if ( ok && PostRotation.SquareLength() > zero_epsilon ) {
+ is_complex = true;
+
+ GetRotationMatrix( rot, PostRotation, chain[ TransformationComp_PostRotation ] );
+ }
+
+ const aiVector3D& RotationPivot = PropertyGet<aiVector3D>( 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<aiVector3D>( props, "RotationOffset", ok );
+ if ( ok && RotationOffset.SquareLength() > zero_epsilon ) {
+ is_complex = true;
+
+ aiMatrix4x4::Translation( RotationOffset, chain[ TransformationComp_RotationOffset ] );
+ }
+
+ const aiVector3D& ScalingOffset = PropertyGet<aiVector3D>( props, "ScalingOffset", ok );
+ if ( ok && ScalingOffset.SquareLength() > zero_epsilon ) {
+ is_complex = true;
+
+ aiMatrix4x4::Translation( ScalingOffset, chain[ TransformationComp_ScalingOffset ] );
+ }
+
+ const aiVector3D& ScalingPivot = PropertyGet<aiVector3D>( 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<aiVector3D>( props, "Lcl Translation", ok );
+ if ( ok && Translation.SquareLength() > zero_epsilon ) {
+ aiMatrix4x4::Translation( Translation, chain[ TransformationComp_Translation ] );
+ }
+
+ const aiVector3D& Scaling = PropertyGet<aiVector3D>( props, "Lcl Scaling", ok );
+ if ( ok && std::fabs( Scaling.SquareLength() - 1.0f ) > zero_epsilon ) {
+ aiMatrix4x4::Scaling( Scaling, chain[ TransformationComp_Scaling ] );
+ }
+
+ const aiVector3D& Rotation = PropertyGet<aiVector3D>( props, "Lcl Rotation", ok );
+ if ( ok && Rotation.SquareLength() > zero_epsilon ) {
+ GetRotationMatrix( rot, Rotation, chain[ TransformationComp_Rotation ] );
+ }
+
+ const aiVector3D& GeometricScaling = PropertyGet<aiVector3D>( props, "GeometricScaling", ok );
+ if ( ok && std::fabs( GeometricScaling.SquareLength() - 1.0f ) > zero_epsilon ) {
+ aiMatrix4x4::Scaling( GeometricScaling, chain[ TransformationComp_GeometricScaling ] );
+ }
+
+ const aiVector3D& GeometricRotation = PropertyGet<aiVector3D>( props, "GeometricRotation", ok );
+ if ( ok && GeometricRotation.SquareLength() > zero_epsilon ) {
+ GetRotationMatrix( rot, GeometricRotation, chain[ TransformationComp_GeometricRotation ] );
+ }
+
+ const aiVector3D& GeometricTranslation = PropertyGet<aiVector3D>( props, "GeometricTranslation", ok );
+ if ( ok && GeometricTranslation.SquareLength() > zero_epsilon ) {
+ aiMatrix4x4::Translation( GeometricTranslation, chain[ TransformationComp_GeometricTranslation ] );
+ }
+
+ // 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 );
+
+ const 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<TransformationComp>( i );
+
+ if ( chain[ i ].IsIdentity() && ( anim_chain_bitmask & bit ) == 0 ) {
+ continue;
+ }
+
+ aiNode* nd = new aiNode();
+ output_nodes.push_back( nd );
+
+ nd->mName.Set( NameTransformationChainNode( name, comp ) );
+ nd->mTransformation = chain[ i ];
+ }
+
+ ai_assert( output_nodes.size() );
+ return;
+ }
+
+ // else, we can just multiply the matrices together
+ aiNode* nd = new aiNode();
+ output_nodes.push_back( nd );
+
+ nd->mName.Set( name );
+
+ for (const auto &transform : chain) {
+ nd->mTransformation = nd->mTransformation * transform;
+ }
+}
+
+
+void Converter::SetupNodeMetadata( const Model& model, aiNode& nd )
+{
+ const PropertyTable& props = model.Props();
+ DirectPropertyMap unparsedProperties = props.GetUnparsedProperties();
+
+ // create metadata on node
+ std::size_t numStaticMetaData = 2;
+ aiMetadata* data = new aiMetadata();
+ data->mNumProperties = unparsedProperties.size() + numStaticMetaData;
+ data->mKeys = new aiString[ data->mNumProperties ]();
+ data->mValues = new aiMetadataEntry[ data->mNumProperties ]();
+ nd.mMetaData = data;
+ int index = 0;
+
+ // find user defined properties (3ds Max)
+ data->Set( index++, "UserProperties", aiString( PropertyGet<std::string>( 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<bool>* interpreted = prop.second->As<TypedProperty<bool> >() )
+ data->Set( index++, prop.first, interpreted->Value() );
+ else if ( const TypedProperty<int>* interpreted = prop.second->As<TypedProperty<int> >() )
+ data->Set( index++, prop.first, interpreted->Value() );
+ else if ( const TypedProperty<uint64_t>* interpreted = prop.second->As<TypedProperty<uint64_t> >() )
+ data->Set( index++, prop.first, interpreted->Value() );
+ else if ( const TypedProperty<float>* interpreted = prop.second->As<TypedProperty<float> >() )
+ data->Set( index++, prop.first, interpreted->Value() );
+ else if ( const TypedProperty<std::string>* interpreted = prop.second->As<TypedProperty<std::string> >() )
+ data->Set( index++, prop.first, aiString( interpreted->Value() ) );
+ else if ( const TypedProperty<aiVector3D>* interpreted = prop.second->As<TypedProperty<aiVector3D> >() )
+ data->Set( index++, prop.first, interpreted->Value() );
else
+ assert( false );
+ }
+}
+
+void Converter::ConvertModel( const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform )
+{
+ const std::vector<const Geometry*>& geos = model.GetGeometry();
+
+ std::vector<unsigned int> meshes;
+ meshes.reserve( geos.size() );
+
+ for( const Geometry* geo : geos ) {
+
+ const MeshGeometry* const mesh = dynamic_cast< const MeshGeometry* >( geo );
+ if ( mesh ) {
+ const std::vector<unsigned int>& indices = ConvertMesh( *mesh, model, node_global_transform );
+ 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<unsigned int> Converter::ConvertMesh( const MeshGeometry& mesh, const Model& model,
+ const aiMatrix4x4& node_global_transform )
+{
+ std::vector<unsigned int> 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<aiVector3D>& vertices = mesh.GetVertices();
+ const std::vector<unsigned int>& 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 );
+ }
+ }
+ }
+
+ // faster code-path, just copy the data
+ temp.push_back( ConvertMeshSingleMaterial( mesh, model, node_global_transform ) );
+ return temp;
+}
+
+
+aiMesh* Converter::SetupEmptyMesh( const MeshGeometry& mesh )
+{
+ aiMesh* const out_mesh = new aiMesh();
+ meshes.push_back( out_mesh );
+ meshes_converted[ &mesh ].push_back( static_cast<unsigned int>( 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 );
+ }
+
+ return out_mesh;
+}
+
+
+unsigned int Converter::ConvertMeshSingleMaterial( const MeshGeometry& mesh, const Model& model,
+ const aiMatrix4x4& node_global_transform )
+{
+ const MatIndexArray& mindices = mesh.GetMaterialIndices();
+ aiMesh* const out_mesh = SetupEmptyMesh( mesh );
+
+ const std::vector<aiVector3D>& vertices = mesh.GetVertices();
+ const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
+
+ // copy vertices
+ out_mesh->mNumVertices = static_cast<unsigned int>( 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<unsigned int>( 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 )
{
- 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<int>(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 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, "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);
- }
-
- // ------------------------------------------------------------------------------------------------
- void 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, "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);
- }
-
-
- // ------------------------------------------------------------------------------------------------
- aiColor3D GetColorPropertyFromMaterial(const PropertyTable& props, const std::string& baseName,
- bool& result)
- {
- result = true;
-
- bool ok;
- const aiVector3D& Diffuse = PropertyGet<aiVector3D>(props,baseName,ok);
- if(ok) {
- return aiColor3D(Diffuse.x,Diffuse.y,Diffuse.z);
- }
- else {
- aiVector3D DiffuseColor = PropertyGet<aiVector3D>(props,baseName + "Color",ok);
- if(ok) {
- float DiffuseFactor = PropertyGet<float>(props,baseName + "Factor",ok);
- if(ok) {
- DiffuseColor *= DiffuseFactor;
- }
-
- return aiColor3D(DiffuseColor.x,DiffuseColor.y,DiffuseColor.z);
- }
- }
- result = false;
- return aiColor3D(0.0f,0.0f,0.0f);
- }
-
-
- // ------------------------------------------------------------------------------------------------
- void SetShadingPropertiesCommon(aiMaterial* out_mat, const PropertyTable& props)
- {
- // set shading properties. There are various, redundant ways in which FBX materials
- // specify their shading settings (depending on shading models, prop
- // template etc.). No idea which one is right in a particular context.
- // Just try to make sense of it - there's no spec to verify this against,
- // so why should we.
- 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);
- }
-
- const aiColor3D& Specular = GetColorPropertyFromMaterial(props,"Specular",ok);
- if(ok) {
- out_mat->AddProperty(&Specular,1,AI_MATKEY_COLOR_SPECULAR);
- }
-
- const float Opacity = PropertyGet<float>(props,"Opacity",ok);
- if(ok) {
- out_mat->AddProperty(&Opacity,1,AI_MATKEY_OPACITY);
- }
-
- const float Reflectivity = PropertyGet<float>(props,"Reflectivity",ok);
- if(ok) {
- out_mat->AddProperty(&Reflectivity,1,AI_MATKEY_REFLECTIVITY);
- }
-
- const float Shininess = PropertyGet<float>(props,"Shininess",ok);
- if(ok) {
- out_mat->AddProperty(&Shininess,1,AI_MATKEY_SHININESS_STRENGTH);
- }
-
- const float ShininessExponent = PropertyGet<float>(props,"ShininessExponent",ok);
- if(ok) {
- out_mat->AddProperty(&ShininessExponent,1,AI_MATKEY_SHININESS);
- }
- }
-
-
- // ------------------------------------------------------------------------------------------------
- // get the number of fps for a FrameRate enumerated value
- static double FrameRateToDouble(FileGlobalSettings::FrameRate fp, double customFPSVal = -1.0)
- {
- 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;
- }
-
-
- // ------------------------------------------------------------------------------------------------
- // convert animation data to aiAnimation et al
- void 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<const AnimationStack*>& animations = doc.AnimationStacks();
- BOOST_FOREACH(const AnimationStack* stack, animations) {
- ConvertAnimationStack(*stack);
- }
- }
-
-
- // ------------------------------------------------------------------------------------------------
- // rename a node already partially converted. fixed_name is a string previously returned by
- // FixNodeName, new_name specifies the string FixNodeName should return on all further invocations
- // which would previously have returned the old value.
- //
- // this also updates names in node animations, cameras and light sources and is thus slow.
- //
- // NOTE: the caller is responsible for ensuring that the new name is unique and does
- // not collide with any other identifiers. The best way to ensure this is to only
- // append to the old name, which is guaranteed to match these requirements.
- void RenameNode(const std::string& fixed_name, const std::string& new_name)
- {
- ai_assert(node_names.find(fixed_name) != node_names.end());
- ai_assert(node_names.find(new_name) == node_names.end());
-
- renamed_nodes[fixed_name] = new_name;
-
- const aiString fn(fixed_name);
-
- BOOST_FOREACH(aiCamera* cam, cameras) {
- if (cam->mName == fn) {
- cam->mName.Set(new_name);
- break;
- }
- }
-
- BOOST_FOREACH(aiLight* light, lights) {
- if (light->mName == fn) {
- light->mName.Set(new_name);
- break;
- }
- }
-
- BOOST_FOREACH(aiAnimation* anim, animations) {
- for (unsigned int i = 0; i < anim->mNumChannels; ++i) {
- aiNodeAnim* const na = anim->mChannels[i];
- if (na->mNodeName == fn) {
- na->mNodeName.Set(new_name);
- break;
- }
- }
- }
- }
-
-
- // ------------------------------------------------------------------------------------------------
- // takes a fbx node name and returns the identifier to be used in the assimp output scene.
- // 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)
- {
- // 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);
-
- const NodeNameMap::const_iterator it = node_names.find(temp);
- if (it != node_names.end()) {
- if (!(*it).second) {
- return FixNodeName(name + "_");
- }
- }
- node_names[temp] = true;
-
- const NameNameMap::const_iterator rit = renamed_nodes.find(temp);
- return rit == renamed_nodes.end() ? temp : (*rit).second;
- }
-
- const NodeNameMap::const_iterator it = node_names.find(name);
- if (it != node_names.end()) {
- if ((*it).second) {
- return FixNodeName(name + "_");
- }
- }
- node_names[name] = false;
-
- const NameNameMap::const_iterator rit = renamed_nodes.find(name);
- return rit == renamed_nodes.end() ? name : (*rit).second;
- }
-
-
- typedef std::map<const AnimationCurveNode*, const AnimationLayer*> LayerMap;
-
- // XXX: better use multi_map ..
- typedef std::map<std::string, std::vector<const AnimationCurveNode*> > NodeMap;
-
-
- // ------------------------------------------------------------------------------------------------
- void 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);
- }
-
- 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"
- };
-
- BOOST_FOREACH(const AnimationLayer* layer, layers) {
- ai_assert(layer);
-
- const AnimationCurveNodeList& nodes = layer->Nodes(prop_whitelist, 3);
- BOOST_FOREACH(const AnimationCurveNode* node, nodes) {
- ai_assert(node);
-
- const Model* const model = dynamic_cast<const Model*>(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<aiNodeAnim*> node_anims;
-
- double min_time = 1e10;
- double max_time = -1e10;
-
- try {
- BOOST_FOREACH(const NodeMap::value_type& kv, node_map) {
- GenerateNodeAnimations(node_anims,
- kv.first,
- kv.second,
- layer_map,
- max_time,
- min_time);
- }
- }
- catch(std::exception&) {
- std::for_each(node_anims.begin(), node_anims.end(), Util::delete_fun<aiNodeAnim>());
- throw;
- }
-
- if(node_anims.size()) {
- anim->mChannels = new aiNodeAnim*[node_anims.size()]();
- anim->mNumChannels = static_cast<unsigned int>(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;
- }
-
- // for some mysterious reason, mDuration is simply the maximum key -- the
- // validator always assumes animations to start at zero.
- anim->mDuration = max_time /*- min_time */;
- anim->mTicksPerSecond = anim_fps;
- }
-
-
- // ------------------------------------------------------------------------------------------------
- void GenerateNodeAnimations(std::vector<aiNodeAnim*>& node_anims,
- const std::string& fixed_name,
- const std::vector<const AnimationCurveNode*>& curves,
- const LayerMap& layer_map,
- double& max_time,
- double& min_time)
- {
-
- NodeMap node_property_map;
- ai_assert(curves.size());
-
- // sanity check whether the input is ok
+ 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<aiVector3D>& 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<aiVector3D>& tangents = mesh.GetTangents();
+ const std::vector<aiVector3D>* binormals = &mesh.GetBinormals();
+
+ if ( tangents.size() ) {
+ std::vector<aiVector3D> 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<aiVector2D>& 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<aiColor4D>& 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<unsigned int>( meshes.size() - 1 );
+}
+
+std::vector<unsigned int> Converter::ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model,
+ const aiMatrix4x4& node_global_transform )
+{
+ const MatIndexArray& mindices = mesh.GetMaterialIndices();
+ ai_assert( mindices.size() );
+
+ std::set<MatIndexArray::value_type> had;
+ std::vector<unsigned int> indices;
+
+ for( MatIndexArray::value_type index : mindices ) {
+ if ( had.find( index ) == had.end() ) {
+
+ indices.push_back( ConvertMeshMultiMaterial( mesh, model, index, node_global_transform ) );
+ had.insert( index );
+ }
+ }
+
+ return indices;
+}
+
+unsigned int Converter::ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model,
+ MatIndexArray::value_type index,
+ const aiMatrix4x4& node_global_transform )
+{
+ aiMesh* const out_mesh = SetupEmptyMesh( mesh );
+
+ const MatIndexArray& mindices = mesh.GetMaterialIndices();
+ const std::vector<aiVector3D>& vertices = mesh.GetVertices();
+ const std::vector<unsigned int>& 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<unsigned int>::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<unsigned int> 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<aiVector3D>& 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<aiVector3D>& tangents = mesh.GetTangents();
+ const std::vector<aiVector3D>* binormals = &mesh.GetBinormals();
+
+ if ( tangents.size() ) {
+ std::vector<aiVector3D> tempBinormals;
+ 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<aiVector2D>& 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<aiColor4D>& 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 i = 0; i < num_uvs; ++i ) {
+ const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords( i );
+ out_mesh->mTextureCoords[ i ][ cursor ] = aiVector3D( uvs[ in_cursor ].x, uvs[ in_cursor ].y, 0.0f );
+ }
+
+ for ( unsigned int i = 0; i < num_vcs; ++i ) {
+ const std::vector<aiColor4D>& cols = mesh.GetVertexColors( i );
+ out_mesh->mColors[ i ][ 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<unsigned int>( meshes.size() - 1 );
+}
+
+
+void Converter::ConvertWeights( aiMesh* out, const Model& model, const MeshGeometry& geo,
+ const aiMatrix4x4& node_global_transform ,
+ unsigned int materialIndex,
+ std::vector<unsigned int>* outputVertStartIndices )
+{
+ ai_assert( geo.DeformerSkin() );
+
+ std::vector<size_t> out_indices;
+ std::vector<size_t> index_out_indices;
+ std::vector<size_t> count_out_indices;
+
+ const Skin& sk = *geo.DeformerSkin();
+
+ std::vector<aiBone*> 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<size_t>::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<size_t>( 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<unsigned int>::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<aiBone>() );
+ throw;
+ }
+
+ if ( bones.empty() ) {
+ return;
+ }
+
+ out->mBones = new aiBone*[ bones.size() ]();
+ out->mNumBones = static_cast<unsigned int>( bones.size() );
+
+ std::swap_ranges( bones.begin(), bones.end(), out->mBones );
+}
+
+void Converter::ConvertCluster( std::vector<aiBone*>& bones, const Model& /*model*/, const Cluster& cl,
+ std::vector<size_t>& out_indices,
+ std::vector<size_t>& index_out_indices,
+ std::vector<size_t>& 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<unsigned int>( out_indices.size() );
+ aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[ out_indices.size() ];
+
+ const size_t no_index_sentinel = std::numeric_limits<size_t>::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<unsigned int>( out_indices[ index_index + j ] );
+ out_weight.mWeight = weights[ i ];
+ }
+ }
+}
+
+
+void Converter::ConvertMaterialForMesh( aiMesh* out, const Model& model, const MeshGeometry& geo,
+ MatIndexArray::value_type materialIndex )
+{
+ // locate source materials for this mesh
+ const std::vector<const Material*>& mats = model.GetMaterials();
+ if ( static_cast<unsigned int>( 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 Converter::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 Converter::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<unsigned int>( materials.size() );
+
+ materials.push_back( out_mat );
+
+ aiString str;
+
+ // stip 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<unsigned int>( materials.size() - 1 );
+}
+
+unsigned int Converter::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<unsigned int>( 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<aiTexel*>( const_cast<Video&>( 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() );
+ }
+
+ return static_cast<unsigned int>( textures.size() - 1 );
+}
+
+void Converter::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;
+ path.Set( tex->RelativeFilename() );
+
+ const Video* media = tex->Media();
+ if ( media != 0 && media->ContentLength() > 0 ) {
+ unsigned int index;
+
+ VideoMap::const_iterator it = textures_converted.find( media );
+ if ( it != textures_converted.end() ) {
+ index = ( *it ).second;
+ }
+ else {
+ index = ConvertVideo( *media );
+ textures_converted[ media ] = index;
+ }
+
+ // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture)
+ path.data[ 0 ] = '*';
+ path.length = 1 + ASSIMP_itoa10( path.data + 1, MAXLEN - 1, index );
+ }
+
+ 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<std::string>( 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<unsigned int>( 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<const MeshGeometry*> ( 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<int>( 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<int>( 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 Converter::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;
+ }
+
+ const Texture* const tex = ( *it ).second->getTexture();
+
+ aiString path;
+ path.Set( tex->RelativeFilename() );
+
+ 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<std::string>( 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<unsigned int>( 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<const MeshGeometry*> ( 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<int>( 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<int>( 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 Converter::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, "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 );
+}
+
+void Converter::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, "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 );
+}
+
+aiColor3D Converter::GetColorPropertyFromMaterial( const PropertyTable& props, const std::string& baseName,
+ bool& result )
+{
+ result = true;
+
+ bool ok;
+ const aiVector3D& Diffuse = PropertyGet<aiVector3D>( props, baseName, ok );
+ if ( ok ) {
+ return aiColor3D( Diffuse.x, Diffuse.y, Diffuse.z );
+ }
+ else {
+ aiVector3D DiffuseColor = PropertyGet<aiVector3D>( props, baseName + "Color", ok );
+ if ( ok ) {
+ float DiffuseFactor = PropertyGet<float>( props, baseName + "Factor", ok );
+ if ( ok ) {
+ DiffuseColor *= DiffuseFactor;
+ }
+
+ return aiColor3D( DiffuseColor.x, DiffuseColor.y, DiffuseColor.z );
+ }
+ }
+ result = false;
+ return aiColor3D( 0.0f, 0.0f, 0.0f );
+}
+
+
+void Converter::SetShadingPropertiesCommon( aiMaterial* out_mat, const PropertyTable& props )
+{
+ // set shading properties. There are various, redundant ways in which FBX materials
+ // specify their shading settings (depending on shading models, prop
+ // template etc.). No idea which one is right in a particular context.
+ // Just try to make sense of it - there's no spec to verify this against,
+ // so why should we.
+ 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 );
+ }
+
+ const aiColor3D& Specular = GetColorPropertyFromMaterial( props, "Specular", ok );
+ if ( ok ) {
+ out_mat->AddProperty( &Specular, 1, AI_MATKEY_COLOR_SPECULAR );
+ }
+
+ const float Opacity = PropertyGet<float>( props, "Opacity", ok );
+ if ( ok ) {
+ out_mat->AddProperty( &Opacity, 1, AI_MATKEY_OPACITY );
+ }
+
+ const float Reflectivity = PropertyGet<float>( props, "Reflectivity", ok );
+ if ( ok ) {
+ out_mat->AddProperty( &Reflectivity, 1, AI_MATKEY_REFLECTIVITY );
+ }
+
+ const float Shininess = PropertyGet<float>( props, "Shininess", ok );
+ if ( ok ) {
+ out_mat->AddProperty( &Shininess, 1, AI_MATKEY_SHININESS_STRENGTH );
+ }
+
+ const float ShininessExponent = PropertyGet<float>( props, "ShininessExponent", ok );
+ if ( ok ) {
+ out_mat->AddProperty( &ShininessExponent, 1, AI_MATKEY_SHININESS );
+ }
+}
+
+
+double Converter::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 Converter::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<const AnimationStack*>& animations = doc.AnimationStacks();
+ for( const AnimationStack* stack : animations ) {
+ ConvertAnimationStack( *stack );
+ }
+}
+
+
+void Converter::RenameNode( const std::string& fixed_name, const std::string& new_name )
+{
+ ai_assert( node_names.find( fixed_name ) != node_names.end() );
+ ai_assert( node_names.find( new_name ) == node_names.end() );
+
+ renamed_nodes[ fixed_name ] = new_name;
+
+ const aiString fn( fixed_name );
+
+ for( aiCamera* cam : cameras ) {
+ if ( cam->mName == fn ) {
+ cam->mName.Set( new_name );
+ break;
+ }
+ }
+
+ for( aiLight* light : lights ) {
+ if ( light->mName == fn ) {
+ light->mName.Set( new_name );
+ break;
+ }
+ }
+
+ for( aiAnimation* anim : animations ) {
+ for ( unsigned int i = 0; i < anim->mNumChannels; ++i ) {
+ aiNodeAnim* const na = anim->mChannels[ i ];
+ if ( na->mNodeName == fn ) {
+ na->mNodeName.Set( new_name );
+ break;
+ }
+ }
+ }
+}
+
+
+std::string Converter::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 );
+
+ const NodeNameMap::const_iterator it = node_names.find( temp );
+ if ( it != node_names.end() ) {
+ if ( !( *it ).second ) {
+ return FixNodeName( name + "_" );
+ }
+ }
+ node_names[ temp ] = true;
+
+ const NameNameMap::const_iterator rit = renamed_nodes.find( temp );
+ return rit == renamed_nodes.end() ? temp : ( *rit ).second;
+ }
+
+ const NodeNameMap::const_iterator it = node_names.find( name );
+ if ( it != node_names.end() ) {
+ if ( ( *it ).second ) {
+ return FixNodeName( name + "_" );
+ }
+ }
+ node_names[ name ] = false;
+
+ const NameNameMap::const_iterator rit = renamed_nodes.find( name );
+ return rit == renamed_nodes.end() ? name : ( *rit ).second;
+}
+
+void Converter::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<const Model*>( 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<aiNodeAnim*> node_anims;
+
+ double min_time = 1e10;
+ double max_time = -1e10;
+
+ int64_t start_time = st.LocalStart();
+ int64_t stop_time = st.LocalStop();
+ double start_timeF = CONVERT_FBX_TIME( start_time );
+ double stop_timeF = CONVERT_FBX_TIME( stop_time );
+
+ 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<aiNodeAnim>() );
+ throw;
+ }
+
+ if ( node_anims.size() ) {
+ anim->mChannels = new aiNodeAnim*[ node_anims.size() ]();
+ anim->mNumChannels = static_cast<unsigned int>( 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;
+ }
+
+ //adjust relative timing for animation
+ {
+ double start_fps = start_timeF * anim_fps;
+
+ 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_fps;
+ for ( uint32_t i = 0; i < channel->mNumRotationKeys; i++ )
+ channel->mRotationKeys[ i ].mTime -= start_fps;
+ for ( uint32_t i = 0; i < channel->mNumScalingKeys; i++ )
+ channel->mScalingKeys[ i ].mTime -= start_fps;
+ }
+
+ max_time -= min_time;
+ }
+
+ // for some mysterious reason, mDuration is simply the maximum key -- the
+ // validator always assumes animations to start at zero.
+ anim->mDuration = ( stop_timeF - start_timeF ) * anim_fps;
+ anim->mTicksPerSecond = anim_fps;
+}
+
+// ------------------------------------------------------------------------------------------------
+// sanity check whether the input is ok
+static void validateAnimCurveNodes( const std::vector<const AnimationCurveNode*>& 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 );
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Converter::GenerateNodeAnimations( std::vector<aiNodeAnim*>& node_anims,
+ const std::string& fixed_name,
+ const std::vector<const AnimationCurveNode*>& 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
- { const Object* target = NULL;
- BOOST_FOREACH(const AnimationCurveNode* node, curves) {
- if(!target) {
- target = node->Target();
- }
- ai_assert(node->Target() == target);
- }}
+ validateAnimCurveNodes( curves, doc.Settings().strictMode );
#endif
+ const AnimationCurveNode* curve_node = NULL;
+ for( const AnimationCurveNode* node : curves ) {
+ ai_assert( node );
- const AnimationCurveNode* curve_node = NULL;
- BOOST_FOREACH(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<TransformationComp>(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 &&
- comp != TransformationComp_GeometricScaling && comp != TransformationComp_GeometricRotation && comp != TransformationComp_GeometricTranslation )
- {
- 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,
- max_time,
- min_time,
- true // input is TRS order, assimp is SRT
- );
-
- ai_assert(nd);
- 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<TransformationComp>(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;
- switch(comp)
- {
- case TransformationComp_Rotation:
- case TransformationComp_PreRotation:
- case TransformationComp_PostRotation:
- case TransformationComp_GeometricRotation:
- na = GenerateRotationNodeAnim(chain_name,
- target,
- (*chain[i]).second,
- layer_map,
- 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,
- 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,
- max_time,
- min_time,
- true);
-
- ai_assert(inv);
- 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,
- max_time,
- min_time,
- true);
-
- ai_assert(inv);
- 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,
- max_time,
- min_time);
-
- break;
-
- default:
- ai_assert(false);
- }
-
- ai_assert(na);
- node_anims.push_back(na);
- continue;
- }
- }
-
- node_anim_chain_bits[fixed_name] = flags;
- }
-
-
- // ------------------------------------------------------------------------------------------------
- bool IsRedundantAnimationData(const Model& target,
- TransformationComp comp,
- const std::vector<const AnimationCurveNode*>& 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<aiVector3D>(target.Props(),
- NameTransformationCompProperty(comp),
- TransformationCompDefaultValue(comp)
- );
-
- const float epsilon = 1e-6f;
- return (dyn_val - static_val).SquareLength() < epsilon;
- }
-
-
- // ------------------------------------------------------------------------------------------------
- aiNodeAnim* GenerateRotationNodeAnim(const std::string& name,
- const Model& target,
- const std::vector<const AnimationCurveNode*>& curves,
- const LayerMap& layer_map,
- double& max_time,
- double& min_time)
- {
- ScopeGuard<aiNodeAnim> na(new aiNodeAnim());
- na->mNodeName.Set(name);
-
- ConvertRotationKeys(na, curves, layer_map, 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.dismiss();
- }
-
-
- // ------------------------------------------------------------------------------------------------
- aiNodeAnim* GenerateScalingNodeAnim(const std::string& name,
- const Model& target,
- const std::vector<const AnimationCurveNode*>& curves,
- const LayerMap& layer_map,
- double& max_time,
- double& min_time)
- {
- ScopeGuard<aiNodeAnim> na(new aiNodeAnim());
- na->mNodeName.Set(name);
-
- ConvertScaleKeys(na, curves, layer_map, 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.dismiss();
- }
-
-
- // ------------------------------------------------------------------------------------------------
- aiNodeAnim* GenerateTranslationNodeAnim(const std::string& name,
- const Model& target,
- const std::vector<const AnimationCurveNode*>& curves,
- const LayerMap& layer_map,
- double& max_time,
- double& min_time,
- bool inverse = false)
- {
- ScopeGuard<aiNodeAnim> na(new aiNodeAnim());
- na->mNodeName.Set(name);
-
- ConvertTranslationKeys(na, curves, layer_map, 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.dismiss();
- }
-
-
- // ------------------------------------------------------------------------------------------------
- // generate node anim, extracting only Rotation, Scaling and Translation from the given chain
- aiNodeAnim* GenerateSimpleNodeAnim(const std::string& name,
- const Model& target,
- NodeMap::const_iterator chain[TransformationComp_MAXIMUM],
- NodeMap::const_iterator iter_end,
- const LayerMap& layer_map,
- double& max_time,
- double& min_time,
- bool reverse_order = false)
-
- {
- ScopeGuard<aiNodeAnim> 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, def_translate;
- aiQuaternion def_rot;
-
- KeyFrameListList scaling;
- KeyFrameListList translation;
- KeyFrameListList rotation;
-
- if(chain[TransformationComp_Scaling] != iter_end) {
- scaling = GetKeyframeList((*chain[TransformationComp_Scaling]).second);
- }
- else {
- def_scale = PropertyGet(props,"Lcl Scaling",aiVector3D(1.f,1.f,1.f));
- }
-
- if(chain[TransformationComp_Translation] != iter_end) {
- translation = GetKeyframeList((*chain[TransformationComp_Translation]).second);
- }
- else {
- def_translate = PropertyGet(props,"Lcl Translation",aiVector3D(0.f,0.f,0.f));
- }
-
- if(chain[TransformationComp_Rotation] != iter_end) {
- rotation = GetKeyframeList((*chain[TransformationComp_Rotation]).second);
- }
- else {
- def_rot = EulerToQuaternion(PropertyGet(props,"Lcl Rotation",aiVector3D(0.f,0.f,0.f)),
- target.RotationOrder());
- }
-
- 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()];
-
- 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<unsigned int>(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, (*chain[TransformationComp_Scaling]).second,
- layer_map,
- 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, (*chain[TransformationComp_Rotation]).second,
- layer_map,
- 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, (*chain[TransformationComp_Translation]).second,
- layer_map,
- 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.dismiss();
- }
-
-
-
- // key (time), value, mapto (component index)
- typedef boost::tuple< const KeyTimeList*, const KeyValueList*, unsigned int > KeyFrameList;
- typedef std::vector<KeyFrameList> KeyFrameListList;
-
-
-
- // ------------------------------------------------------------------------------------------------
- KeyFrameListList GetKeyframeList(const std::vector<const AnimationCurveNode*>& nodes)
- {
- KeyFrameListList inputs;
- inputs.reserve(nodes.size()*3);
-
- BOOST_FOREACH(const AnimationCurveNode* node, nodes) {
- ai_assert(node);
-
- const AnimationCurveMap& curves = node->Curves();
- BOOST_FOREACH(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());
-
- inputs.push_back(boost::make_tuple(&curve->GetKeys(), &curve->GetValues(), mapto));
- }
- }
- return inputs; // pray for NRVO :-)
- }
-
-
- // ------------------------------------------------------------------------------------------------
- KeyTimeList GetKeyTimeList(const KeyFrameListList& inputs)
- {
- ai_assert(inputs.size());
-
- // reserve some space upfront - it is likely that the keyframe lists
- // have matching time values, so max(of all keyframe lists) should
- // be a good estimate.
- KeyTimeList keys;
-
- size_t estimate = 0;
- BOOST_FOREACH(const KeyFrameList& kfl, inputs) {
- estimate = std::max(estimate, kfl.get<0>()->size());
- }
-
- keys.reserve(estimate);
-
- std::vector<unsigned int> next_pos;
- next_pos.resize(inputs.size(),0);
-
- const size_t count = inputs.size();
- while(true) {
-
- uint64_t min_tick = std::numeric_limits<uint64_t>::max();
- for (size_t i = 0; i < count; ++i) {
- const KeyFrameList& kfl = inputs[i];
-
- if (kfl.get<0>()->size() > next_pos[i] && kfl.get<0>()->at(next_pos[i]) < min_tick) {
- min_tick = kfl.get<0>()->at(next_pos[i]);
- }
- }
-
- if (min_tick == std::numeric_limits<uint64_t>::max()) {
- break;
- }
- keys.push_back(min_tick);
-
- for (size_t i = 0; i < count; ++i) {
- const KeyFrameList& kfl = inputs[i];
-
-
- while(kfl.get<0>()->size() > next_pos[i] && kfl.get<0>()->at(next_pos[i]) == min_tick) {
- ++next_pos[i];
- }
- }
- }
-
- return keys;
- }
-
-
- // ------------------------------------------------------------------------------------------------
- void InterpolateKeys(aiVectorKey* valOut,const KeyTimeList& keys, const KeyFrameListList& inputs,
- const bool geom,
- double& max_time,
- double& min_time)
-
- {
- ai_assert(keys.size());
- ai_assert(valOut);
-
- std::vector<unsigned int> next_pos;
- const size_t count = inputs.size();
-
- next_pos.resize(inputs.size(),0);
-
- BOOST_FOREACH(KeyTimeList::value_type time, keys) {
- float result[3] = {0.0f, 0.0f, 0.0f};
- if(geom) {
- result[0] = result[1] = result[2] = 1.0f;
- }
-
- for (size_t i = 0; i < count; ++i) {
- const KeyFrameList& kfl = inputs[i];
-
- const size_t ksize = kfl.get<0>()->size();
- if (ksize > next_pos[i] && kfl.get<0>()->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 = kfl.get<1>()->at(id0);
- const KeyValueList::value_type valueB = kfl.get<1>()->at(id1);
-
- const KeyTimeList::value_type timeA = kfl.get<0>()->at(id0);
- const KeyTimeList::value_type timeB = kfl.get<0>()->at(id1);
-
- // do the actual interpolation in double-precision arithmetics
- // because it is a bit sensitive to rounding errors.
- const double factor = timeB == timeA ? 0. : static_cast<double>((time - timeA) / (timeB - timeA));
- const float interpValue = static_cast<float>(valueA + (valueB - valueA) * factor);
-
- if(geom) {
- result[kfl.get<2>()] *= interpValue;
- }
- else {
- result[kfl.get<2>()] += 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 InterpolateKeys(aiQuatKey* valOut,const KeyTimeList& keys, const KeyFrameListList& inputs,
- const bool geom,
- double& maxTime,
- double& minTime,
- Model::RotOrder order)
- {
- ai_assert(keys.size());
- ai_assert(valOut);
-
- boost::scoped_array<aiVectorKey> temp(new aiVectorKey[keys.size()]);
- InterpolateKeys(temp.get(),keys,inputs,geom,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 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 aiQuaternion& def_rotation)
- {
- if (rotation.size()) {
- InterpolateKeys(out_quat, times, rotation, false, 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 = def_rotation;
- }
- }
-
- if (scaling.size()) {
- InterpolateKeys(out_scale, times, scaling, true, 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, false, 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);
- }
- }
-
-
- // ------------------------------------------------------------------------------------------------
- // euler xyz -> quat
- aiQuaternion EulerToQuaternion(const aiVector3D& rot, Model::RotOrder order)
- {
- aiMatrix4x4 m;
- GetRotationMatrix(order, rot, m);
-
- return aiQuaternion(aiMatrix3x3(m));
- }
-
-
- // ------------------------------------------------------------------------------------------------
- void ConvertScaleKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, const LayerMap& layers,
- 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);
- const KeyTimeList& keys = GetKeyTimeList(inputs);
-
- na->mNumScalingKeys = static_cast<unsigned int>(keys.size());
- na->mScalingKeys = new aiVectorKey[keys.size()];
- InterpolateKeys(na->mScalingKeys, keys, inputs, true, maxTime, minTime);
- }
-
-
- // ------------------------------------------------------------------------------------------------
- void ConvertTranslationKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
- const LayerMap& layers,
- double& maxTime,
- double& minTime)
- {
- ai_assert(nodes.size());
-
- // XXX see notes in ConvertScaleKeys()
- const KeyFrameListList& inputs = GetKeyframeList(nodes);
- const KeyTimeList& keys = GetKeyTimeList(inputs);
-
- na->mNumPositionKeys = static_cast<unsigned int>(keys.size());
- na->mPositionKeys = new aiVectorKey[keys.size()];
- InterpolateKeys(na->mPositionKeys, keys, inputs, false, maxTime, minTime);
- }
-
-
- // ------------------------------------------------------------------------------------------------
- void ConvertRotationKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
- const LayerMap& layers,
- double& maxTime,
- double& minTime,
- Model::RotOrder order)
- {
- ai_assert(nodes.size());
-
- // XXX see notes in ConvertScaleKeys()
- const std::vector< KeyFrameList >& inputs = GetKeyframeList(nodes);
- const KeyTimeList& keys = GetKeyTimeList(inputs);
-
- na->mNumRotationKeys = static_cast<unsigned int>(keys.size());
- na->mRotationKeys = new aiQuatKey[keys.size()];
- InterpolateKeys(na->mRotationKeys, keys, inputs, false, maxTime, minTime, order);
- }
-
-
- // ------------------------------------------------------------------------------------------------
- // copy generated meshes, animations, lights, cameras and textures to the output scene
- void TransferDataToScene()
- {
- ai_assert(!out->mMeshes && !out->mNumMeshes);
+ if ( node->TargetProperty().empty() ) {
+ FBXImporter::LogWarn( "target property for animation curve not set: " + node->Name() );
+ continue;
+ }
- // 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.
+ curve_node = node;
+ if ( node->Curves().empty() ) {
+ FBXImporter::LogWarn( "no animation curves assigned to AnimationCurveNode: " + node->Name() );
+ continue;
+ }
- if(meshes.size()) {
- out->mMeshes = new aiMesh*[meshes.size()]();
- out->mNumMeshes = static_cast<unsigned int>(meshes.size());
+ node_property_map[ node->TargetProperty() ].push_back( node );
+ }
- std::swap_ranges(meshes.begin(),meshes.end(),out->mMeshes);
- }
+ ai_assert( curve_node );
+ ai_assert( curve_node->TargetAsModel() );
- if(materials.size()) {
- out->mMaterials = new aiMaterial*[materials.size()]();
- out->mNumMaterials = static_cast<unsigned int>(materials.size());
+ const Model& target = *curve_node->TargetAsModel();
- std::swap_ranges(materials.begin(),materials.end(),out->mMaterials);
- }
+ // check for all possible transformation components
+ NodeMap::const_iterator chain[ TransformationComp_MAXIMUM ];
- if(animations.size()) {
- out->mAnimations = new aiAnimation*[animations.size()]();
- out->mNumAnimations = static_cast<unsigned int>(animations.size());
+ bool has_any = false;
+ bool has_complex = false;
- std::swap_ranges(animations.begin(),animations.end(),out->mAnimations);
- }
+ for ( size_t i = 0; i < TransformationComp_MAXIMUM; ++i ) {
+ const TransformationComp comp = static_cast<TransformationComp>( i );
- if(lights.size()) {
- out->mLights = new aiLight*[lights.size()]();
- out->mNumLights = static_cast<unsigned int>(lights.size());
+ // 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;
+ }
- std::swap_ranges(lights.begin(),lights.end(),out->mLights);
- }
+ chain[ i ] = node_property_map.find( NameTransformationCompProperty( comp ) );
+ if ( chain[ i ] != node_property_map.end() ) {
- if(cameras.size()) {
- out->mCameras = new aiCamera*[cameras.size()]();
- out->mNumCameras = static_cast<unsigned int>(cameras.size());
+ // 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 ) ) {
- std::swap_ranges(cameras.begin(),cameras.end(),out->mCameras);
- }
- }
+ FBXImporter::LogDebug( "dropping redundant animation channel for node " + target.Name() );
+ continue;
+ }
+ has_any = true;
-private:
+ if ( comp != TransformationComp_Rotation && comp != TransformationComp_Scaling && comp != TransformationComp_Translation &&
+ comp != TransformationComp_GeometricScaling && comp != TransformationComp_GeometricRotation && comp != TransformationComp_GeometricTranslation )
+ {
+ 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<TransformationComp>( 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;
+ }
+ }
- // 0: not assigned yet, others: index is value - 1
- unsigned int defaultMaterialIndex;
+ node_anim_chain_bits[ fixed_name ] = flags;
+}
- std::vector<aiMesh*> meshes;
- std::vector<aiMaterial*> materials;
- std::vector<aiAnimation*> animations;
- std::vector<aiLight*> lights;
- std::vector<aiCamera*> cameras;
+bool Converter::IsRedundantAnimationData( const Model& target,
+ TransformationComp comp,
+ const std::vector<const AnimationCurveNode*>& curves )
+{
+ ai_assert( curves.size() );
- typedef std::map<const Material*, unsigned int> MaterialMap;
- MaterialMap materials_converted;
+ // 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.
- typedef std::map<const Geometry*, std::vector<unsigned int> > MeshMap;
- MeshMap meshes_converted;
+ if ( curves.size() > 1 ) {
+ return false;
+ }
- // fixed node name -> which trafo chain components have animations?
- typedef std::map<std::string, unsigned int> NodeAnimBitMap;
- NodeAnimBitMap node_anim_chain_bits;
+ const AnimationCurveNode& nd = *curves.front();
+ const AnimationCurveMap& sub_curves = nd.Curves();
- // name -> has had its prefix_stripped?
- typedef std::map<std::string, bool> NodeNameMap;
- NodeNameMap node_names;
+ 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" );
- typedef std::map<std::string, std::string> NameNameMap;
- NameNameMap renamed_nodes;
+ if ( dx == sub_curves.end() || dy == sub_curves.end() || dz == sub_curves.end() ) {
+ return false;
+ }
- double anim_fps;
+ const KeyValueList& vx = ( *dx ).second->GetValues();
+ const KeyValueList& vy = ( *dy ).second->GetValues();
+ const KeyValueList& vz = ( *dz ).second->GetValues();
- aiScene* const out;
- const FBX::Document& doc;
-};
+ 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<aiVector3D>( target.Props(),
+ NameTransformationCompProperty( comp ),
+ TransformationCompDefaultValue( comp )
+ );
+
+ const float epsilon = 1e-6f;
+ return ( dyn_val - static_val ).SquareLength() < epsilon;
+}
+
+
+aiNodeAnim* Converter::GenerateRotationNodeAnim( const std::string& name,
+ const Model& target,
+ const std::vector<const AnimationCurveNode*>& curves,
+ const LayerMap& layer_map,
+ int64_t start, int64_t stop,
+ double& max_time,
+ double& min_time )
+{
+ ScopeGuard<aiNodeAnim> na( new aiNodeAnim() );
+ na->mNodeName.Set( name );
+
+ ConvertRotationKeys( na, 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.dismiss();
+}
+
+aiNodeAnim* Converter::GenerateScalingNodeAnim( const std::string& name,
+ const Model& /*target*/,
+ const std::vector<const AnimationCurveNode*>& curves,
+ const LayerMap& layer_map,
+ int64_t start, int64_t stop,
+ double& max_time,
+ double& min_time )
+{
+ ScopeGuard<aiNodeAnim> na( new aiNodeAnim() );
+ na->mNodeName.Set( name );
+
+ ConvertScaleKeys( na, 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.dismiss();
+}
+
+
+aiNodeAnim* Converter::GenerateTranslationNodeAnim( const std::string& name,
+ const Model& /*target*/,
+ const std::vector<const AnimationCurveNode*>& curves,
+ const LayerMap& layer_map,
+ int64_t start, int64_t stop,
+ double& max_time,
+ double& min_time,
+ bool inverse )
+{
+ ScopeGuard<aiNodeAnim> na( new aiNodeAnim() );
+ na->mNodeName.Set( name );
+
+ ConvertTranslationKeys( na, 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.dismiss();
+}
+
+aiNodeAnim* Converter::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 )
+
+{
+ ScopeGuard<aiNodeAnim> 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<unsigned int>( 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, ( *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, ( *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, ( *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.dismiss();
+}
+
+Converter::KeyFrameListList Converter::GetKeyframeList( const std::vector<const AnimationCurveNode*>& 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<KeyTimeList> Keys( new KeyTimeList() );
+ std::shared_ptr<KeyValueList> Values( new KeyValueList() );
+ const int count = curve->GetKeys().size();
+ Keys->reserve( count );
+ Values->reserve( count );
+ for ( int 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 Converter::GetKeyTimeList( const KeyFrameListList& inputs )
+{
+ ai_assert( inputs.size() );
+
+ // reserve some space upfront - it is likely that the keyframe lists
+ // have matching time values, so max(of all keyframe 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<unsigned int> next_pos;
+ next_pos.resize( inputs.size(), 0 );
+
+ const size_t count = inputs.size();
+ while ( true ) {
+
+ int64_t min_tick = std::numeric_limits<int64_t>::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<int64_t>::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 Converter::InterpolateKeys( aiVectorKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
+ const aiVector3D& def_value,
+ double& max_time,
+ double& min_time )
+
+{
+ ai_assert( keys.size() );
+ ai_assert( valOut );
+
+ std::vector<unsigned int> next_pos;
+ const size_t count = inputs.size();
+
+ next_pos.resize( inputs.size(), 0 );
+
+ for( KeyTimeList::value_type time : keys ) {
+ float 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 > 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 );
+
+ // do the actual interpolation in double-precision arithmetics
+ // because it is a bit sensitive to rounding errors.
+ const double factor = timeB == timeA ? 0. : static_cast<double>( ( time - timeA ) / ( timeB - timeA ) );
+ const float interpValue = static_cast<float>( 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 Converter::InterpolateKeys( aiQuatKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
+ const aiVector3D& def_value,
+ double& maxTime,
+ double& minTime,
+ Model::RotOrder order )
+{
+ ai_assert( keys.size() );
+ ai_assert( valOut );
+
+ std::unique_ptr<aiVectorKey[]> 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 Converter::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 Converter::EulerToQuaternion( const aiVector3D& rot, Model::RotOrder order )
+{
+ aiMatrix4x4 m;
+ GetRotationMatrix( order, rot, m );
+
+ return aiQuaternion( aiMatrix3x3( m ) );
+}
+
+
+void Converter::ConvertScaleKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& 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<unsigned int>( 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 Converter::ConvertTranslationKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& 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<unsigned int>( 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 Converter::ConvertRotationKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& 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<unsigned int>( keys.size() );
+ na->mRotationKeys = new aiQuatKey[ keys.size() ];
+ if ( keys.size() > 0 )
+ InterpolateKeys( na->mRotationKeys, keys, inputs, aiVector3D( 0.0f, 0.0f, 0.0f ), maxTime, minTime, order );
+}
+
+void Converter::TransferDataToScene()
+{
+ ai_assert( !out->mMeshes && !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<unsigned int>( meshes.size() );
+
+ std::swap_ranges( meshes.begin(), meshes.end(), out->mMeshes );
+ }
+
+ if ( materials.size() ) {
+ out->mMaterials = new aiMaterial*[ materials.size() ]();
+ out->mNumMaterials = static_cast<unsigned int>( materials.size() );
+
+ std::swap_ranges( materials.begin(), materials.end(), out->mMaterials );
+ }
+
+ if ( animations.size() ) {
+ out->mAnimations = new aiAnimation*[ animations.size() ]();
+ out->mNumAnimations = static_cast<unsigned int>( animations.size() );
+
+ std::swap_ranges( animations.begin(), animations.end(), out->mAnimations );
+ }
+
+ if ( lights.size() ) {
+ out->mLights = new aiLight*[ lights.size() ]();
+ out->mNumLights = static_cast<unsigned int>( lights.size() );
+
+ std::swap_ranges( lights.begin(), lights.end(), out->mLights );
+ }
+
+ if ( cameras.size() ) {
+ out->mCameras = new aiCamera*[ cameras.size() ]();
+ out->mNumCameras = static_cast<unsigned int>( cameras.size() );
+
+ std::swap_ranges( cameras.begin(), cameras.end(), out->mCameras );
+ }
+
+ if ( textures.size() ) {
+ out->mTextures = new aiTexture*[ textures.size() ]();
+ out->mNumTextures = static_cast<unsigned int>( textures.size() );
+
+ std::swap_ranges( textures.begin(), textures.end(), out->mTextures );
+ }
+}
//} // !anon
// ------------------------------------------------------------------------------------------------
void ConvertToAssimpScene(aiScene* out, const Document& doc)
{
- Converter converter(out,doc);
+ Converter converter(out,doc);
}
} // !FBX