diff options
Diffstat (limited to 'src/3rdparty/assimp/code/FBXConverter.cpp')
-rw-r--r-- | src/3rdparty/assimp/code/FBXConverter.cpp | 2933 |
1 files changed, 2933 insertions, 0 deletions
diff --git a/src/3rdparty/assimp/code/FBXConverter.cpp b/src/3rdparty/assimp/code/FBXConverter.cpp new file mode 100644 index 000000000..f623c3ae7 --- /dev/null +++ b/src/3rdparty/assimp/code/FBXConverter.cpp @@ -0,0 +1,2933 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2012, 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 +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + 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 +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +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 +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 +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 "FBXDocument.h" +#include "FBXUtil.h" +#include "FBXProperties.h" +#include "FBXImporter.h" + +namespace Assimp { +namespace FBX { + + 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 +//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 + }; + +public: + + 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); + } + } + } + } + + 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>()); + } + + +private: + + // ------------------------------------------------------------------------------------------------ + // find scene root and trigger recursive scene conversion + void ConvertRootNode() + { + out->mRootNode = new aiNode(); + out->mRootNode->mName.Set("RootNode"); + + // root has ID 0 + ConvertNodes(0L, *out->mRootNode); + } + + + // ------------------------------------------------------------------------------------------------ + // 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"); + + std::vector<aiNode*> nodes; + nodes.reserve(conns.size()); + + std::vector<aiNode*> nodes_chain; + + try { + BOOST_FOREACH(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; + } + + 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(fabs(rotation.z) > angle_epsilon) { + aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(rotation.z),temp[2]); + is_id[2] = false; + } + if(fabs(rotation.y) > angle_epsilon) { + aiMatrix4x4::RotationY(AI_DEG_TO_RAD(rotation.y),temp[1]); + is_id[1] = false; + } + if(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 && 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 && 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<aiString>* interpreted = prop.second->As<TypedProperty<aiString> >()) + data->Set(index++, prop.first, 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; + 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); + } + + + + // ------------------------------------------------------------------------------------------------ + 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) + { + + 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 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); + 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 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()); + SetTextureProperties(out_mat,material.LayeredTextures()); + + return static_cast<unsigned int>(materials.size() - 1); + } + + + // ------------------------------------------------------------------------------------------------ + void TrySetTextureProperties(aiMaterial* out_mat, const TextureMap& textures, + const std::string& propName, + aiTextureType target) + { + 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; + 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(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) + { + 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; + 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(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) + { + TrySetTextureProperties(out_mat, textures, "DiffuseColor", aiTextureType_DIFFUSE); + TrySetTextureProperties(out_mat, textures, "AmbientColor", aiTextureType_AMBIENT); + TrySetTextureProperties(out_mat, textures, "EmissiveColor", aiTextureType_EMISSIVE); + TrySetTextureProperties(out_mat, textures, "SpecularColor", aiTextureType_SPECULAR); + TrySetTextureProperties(out_mat, textures, "TransparentColor", aiTextureType_OPACITY); + TrySetTextureProperties(out_mat, textures, "ReflectionColor", aiTextureType_REFLECTION); + TrySetTextureProperties(out_mat, textures, "DisplacementColor", aiTextureType_DISPLACEMENT); + TrySetTextureProperties(out_mat, textures, "NormalMap", aiTextureType_NORMALS); + TrySetTextureProperties(out_mat, textures, "Bump", aiTextureType_HEIGHT); + TrySetTextureProperties(out_mat, textures, "ShininessExponent", aiTextureType_SHININESS); + } + + // ------------------------------------------------------------------------------------------------ + void SetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures) + { + TrySetTextureProperties(out_mat, layeredTextures, "DiffuseColor", aiTextureType_DIFFUSE); + TrySetTextureProperties(out_mat, layeredTextures, "AmbientColor", aiTextureType_AMBIENT); + TrySetTextureProperties(out_mat, layeredTextures, "EmissiveColor", aiTextureType_EMISSIVE); + TrySetTextureProperties(out_mat, layeredTextures, "SpecularColor", aiTextureType_SPECULAR); + TrySetTextureProperties(out_mat, layeredTextures, "TransparentColor", aiTextureType_OPACITY); + TrySetTextureProperties(out_mat, layeredTextures, "ReflectionColor", aiTextureType_REFLECTION); + TrySetTextureProperties(out_mat, layeredTextures, "DisplacementColor", aiTextureType_DISPLACEMENT); + TrySetTextureProperties(out_mat, layeredTextures, "NormalMap", aiTextureType_NORMALS); + TrySetTextureProperties(out_mat, layeredTextures, "Bump", aiTextureType_HEIGHT); + TrySetTextureProperties(out_mat, layeredTextures, "ShininessExponent", aiTextureType_SHININESS); + } + + + // ------------------------------------------------------------------------------------------------ + 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 +#ifdef ASSIMP_BUILD_DEBUG + { const Object* target = NULL; + BOOST_FOREACH(const AnimationCurveNode* node, curves) { + if(!target) { + target = node->Target(); + } + ai_assert(node->Target() == target); + }} +#endif + + 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); + + // 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); + } + } + + +private: + + // 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; + + typedef std::map<const Material*, unsigned int> MaterialMap; + MaterialMap materials_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; +}; + +//} // !anon + +// ------------------------------------------------------------------------------------------------ +void ConvertToAssimpScene(aiScene* out, const Document& doc) +{ + Converter converter(out,doc); +} + +} // !FBX +} // !Assimp + +#endif |