diff --git a/code/FBXConverter.cpp b/code/FBXConverter.cpp index 200853a92..383ac65f0 100644 --- a/code/FBXConverter.cpp +++ b/code/FBXConverter.cpp @@ -103,1115 +103,90 @@ public: }; 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(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()); - std::for_each(materials.begin(),materials.end(),Util::delete_fun()); - std::for_each(animations.begin(),animations.end(),Util::delete_fun()); - std::for_each(lights.begin(),lights.end(),Util::delete_fun()); - std::for_each(cameras.begin(),cameras.end(),Util::delete_fun()); - std::for_each(textures.begin(),textures.end(),Util::delete_fun()); - } - + Converter( aiScene* out, const Document& doc ); + ~Converter(); 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); - } - + void ConvertRootNode(); // ------------------------------------------------------------------------------------------------ // collect and assign child nodes - void ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4()) - { - const std::vector& conns = doc.GetConnectionsByDestinationSequenced(id, "Model"); - - std::vector nodes; - nodes.reserve(conns.size()); - - std::vector 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(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(nodes.size()); - - std::swap_ranges(nodes.begin(),nodes.end(),parent.mChildren); - } - } - catch(std::exception&) { - Util::delete_fun deleter; - std::for_each(nodes.begin(),nodes.end(),deleter); - std::for_each(nodes_chain.begin(),nodes_chain.end(),deleter); - } - } + void ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4() ); // ------------------------------------------------------------------------------------------------ - void ConvertLights(const Model& model) - { - const std::vector& node_attrs = model.GetAttributes(); - BOOST_FOREACH(const NodeAttribute* attr, node_attrs) { - const Light* const light = dynamic_cast(attr); - if(light) { - ConvertLight(model, *light); - } - } - } + void ConvertLights( const Model& model ); // ------------------------------------------------------------------------------------------------ - void ConvertCameras(const Model& model) - { - const std::vector& node_attrs = model.GetAttributes(); - BOOST_FOREACH(const NodeAttribute* attr, node_attrs) { - const Camera* const cam = dynamic_cast(attr); - if(cam) { - ConvertCamera(model, *cam); - } - } - } - + void ConvertCameras( const Model& model ); // ------------------------------------------------------------------------------------------------ - 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 ConvertLight( const Model& model, const Light& light ); // ------------------------------------------------------------------------------------------------ - 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->mUp = cam.UpVector(); - out_camera->mLookAt = cam.InterestPosition() - out_camera->mPosition; - out_camera->mHorizontalFOV = AI_DEG_TO_RAD(cam.FieldOfView()); - } - + 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) - { - 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* NameTransformationComp( TransformationComp comp ); // ------------------------------------------------------------------------------------------------ // 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; - } - + const char* NameTransformationCompProperty( TransformationComp comp ); // ------------------------------------------------------------------------------------------------ - 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(); - } - + aiVector3D TransformationCompDefaultValue( TransformationComp comp ); // ------------------------------------------------------------------------------------------------ - 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]]; - } - } - - + 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) - { - 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(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(props,NameTransformationCompProperty(comp),ok); - if(ok && v.SquareLength() > zero_epsilon) { - return true; - } - } - - return false; - } - + /** + * 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) - { - return name + std::string(MAGIC_NODE_TAG) + "_" + NameTransformationComp(comp); - } - + 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& output_nodes) - { - const PropertyTable& props = model.Props(); - const Model::RotOrder rot = model.RotationOrder(); - - bool ok; - - aiMatrix4x4 chain[TransformationComp_MAXIMUM]; - std::fill_n(chain, static_cast(TransformationComp_MAXIMUM), aiMatrix4x4()); - - // generate transformation matrices for all the different transformation components - const float zero_epsilon = 1e-6f; - bool is_complex = false; - - const aiVector3D& PreRotation = PropertyGet(props,"PreRotation",ok); - if(ok && PreRotation.SquareLength() > zero_epsilon) { - is_complex = true; - - GetRotationMatrix(rot, PreRotation, chain[TransformationComp_PreRotation]); - } - - const aiVector3D& PostRotation = PropertyGet(props,"PostRotation",ok); - if(ok && PostRotation.SquareLength() > zero_epsilon) { - is_complex = true; - - GetRotationMatrix(rot, PostRotation, chain[TransformationComp_PostRotation]); - } - - const aiVector3D& RotationPivot = PropertyGet(props,"RotationPivot",ok); - if(ok && RotationPivot.SquareLength() > zero_epsilon) { - is_complex = true; - - aiMatrix4x4::Translation(RotationPivot,chain[TransformationComp_RotationPivot]); - aiMatrix4x4::Translation(-RotationPivot,chain[TransformationComp_RotationPivotInverse]); - } - - const aiVector3D& RotationOffset = PropertyGet(props,"RotationOffset",ok); - if(ok && RotationOffset.SquareLength() > zero_epsilon) { - is_complex = true; - - aiMatrix4x4::Translation(RotationOffset,chain[TransformationComp_RotationOffset]); - } - - const aiVector3D& ScalingOffset = PropertyGet(props,"ScalingOffset",ok); - if(ok && ScalingOffset.SquareLength() > zero_epsilon) { - is_complex = true; - - aiMatrix4x4::Translation(ScalingOffset,chain[TransformationComp_ScalingOffset]); - } - - const aiVector3D& ScalingPivot = PropertyGet(props,"ScalingPivot",ok); - if(ok && ScalingPivot.SquareLength() > zero_epsilon) { - is_complex = true; - - aiMatrix4x4::Translation(ScalingPivot,chain[TransformationComp_ScalingPivot]); - aiMatrix4x4::Translation(-ScalingPivot,chain[TransformationComp_ScalingPivotInverse]); - } - - const aiVector3D& Translation = PropertyGet(props,"Lcl Translation",ok); - if(ok && Translation.SquareLength() > zero_epsilon) { - aiMatrix4x4::Translation(Translation,chain[TransformationComp_Translation]); - } - - const aiVector3D& Scaling = PropertyGet(props,"Lcl Scaling",ok); - if(ok && std::fabs(Scaling.SquareLength()-1.0f) > zero_epsilon) { - aiMatrix4x4::Scaling(Scaling,chain[TransformationComp_Scaling]); - } - - const aiVector3D& Rotation = PropertyGet(props,"Lcl Rotation",ok); - if(ok && Rotation.SquareLength() > zero_epsilon) { - GetRotationMatrix(rot, Rotation, chain[TransformationComp_Rotation]); - } - - const aiVector3D& GeometricScaling = PropertyGet(props, "GeometricScaling", ok); - if (ok && std::fabs(GeometricScaling.SquareLength() - 1.0f) > zero_epsilon) { - aiMatrix4x4::Scaling(GeometricScaling, chain[TransformationComp_GeometricScaling]); - } - - const aiVector3D& GeometricRotation = PropertyGet(props, "GeometricRotation", ok); - if (ok && GeometricRotation.SquareLength() > zero_epsilon) { - GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotation]); - } - - const aiVector3D& GeometricTranslation = PropertyGet(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(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]; - } - } + /** + * note: memory for output_nodes will be managed by the caller + */ + void GenerateTransformationNodeChain( const Model& model, std::vector& output_nodes ); // ------------------------------------------------------------------------------------------------ - - 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(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 - BOOST_FOREACH(const DirectPropertyMap::value_type& prop, unparsedProperties) { - - // Interpret the property as a concrete type - if (const TypedProperty* interpreted = prop.second->As >()) - data->Set(index++, prop.first, interpreted->Value()); - else if (const TypedProperty* interpreted = prop.second->As >()) - data->Set(index++, prop.first, interpreted->Value()); - else if (const TypedProperty* interpreted = prop.second->As >()) - data->Set(index++, prop.first, interpreted->Value()); - else if (const TypedProperty* interpreted = prop.second->As >()) - data->Set(index++, prop.first, interpreted->Value()); - else if (const TypedProperty* interpreted = prop.second->As >()) - data->Set(index++, prop.first, aiString(interpreted->Value())); - else if (const TypedProperty* interpreted = prop.second->As >()) - data->Set(index++, prop.first, interpreted->Value()); - else - assert(false); - } - } + void SetupNodeMetadata( const Model& model, aiNode& nd ); // ------------------------------------------------------------------------------------------------ - void ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform) - { - const std::vector& geos = model.GetGeometry(); - - std::vector meshes; - meshes.reserve(geos.size()); - - BOOST_FOREACH(const Geometry* geo, geos) { - - const MeshGeometry* const mesh = dynamic_cast(geo); - if(mesh) { - const std::vector& 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(meshes.size()); - - std::swap_ranges(meshes.begin(),meshes.end(),nd.mMeshes); - } - } - + 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 ConvertMesh(const MeshGeometry& mesh,const Model& model, - const aiMatrix4x4& node_global_transform) - { - std::vector temp; - - MeshMap::const_iterator it = meshes_converted.find(&mesh); - if (it != meshes_converted.end()) { - std::copy((*it).second.begin(),(*it).second.end(),std::back_inserter(temp)); - return temp; - } - - const std::vector& vertices = mesh.GetVertices(); - const std::vector& faces = mesh.GetFaceIndexCounts(); - if(vertices.empty() || faces.empty()) { - FBXImporter::LogWarn("ignoring empty geometry: " + mesh.Name()); - return temp; - } - - // one material per mesh maps easily to aiMesh. Multiple material - // meshes need to be split. - const MatIndexArray& mindices = mesh.GetMaterialIndices(); - if (doc.Settings().readMaterials && !mindices.empty()) { - const MatIndexArray::value_type base = mindices[0]; - BOOST_FOREACH(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; - } - + std::vector ConvertMesh( const MeshGeometry& mesh, const Model& model, + const aiMatrix4x4& node_global_transform ); // ------------------------------------------------------------------------------------------------ - aiMesh* SetupEmptyMesh(const MeshGeometry& mesh) - { - aiMesh* const out_mesh = new aiMesh(); - meshes.push_back(out_mesh); - meshes_converted[&mesh].push_back(static_cast(meshes.size()-1)); - - // set name - std::string name = mesh.Name(); - if (name.substr(0,10) == "Geometry::") { - name = name.substr(10); - } - - if(name.length()) { - out_mesh->mName.Set(name); - } - - return out_mesh; - } - + aiMesh* SetupEmptyMesh( const MeshGeometry& 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& vertices = mesh.GetVertices(); - const std::vector& faces = mesh.GetFaceIndexCounts(); - - // copy vertices - out_mesh->mNumVertices = static_cast(vertices.size()); - out_mesh->mVertices = new aiVector3D[vertices.size()]; - std::copy(vertices.begin(),vertices.end(),out_mesh->mVertices); - - // generate dummy faces - out_mesh->mNumFaces = static_cast(faces.size()); - aiFace* fac = out_mesh->mFaces = new aiFace[faces.size()](); - - unsigned int cursor = 0; - 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& normals = mesh.GetNormals(); - if(normals.size()) { - ai_assert(normals.size() == vertices.size()); - - out_mesh->mNormals = new aiVector3D[vertices.size()]; - std::copy(normals.begin(),normals.end(),out_mesh->mNormals); - } - - // copy tangents - assimp requires both tangents and bitangents (binormals) - // to be present, or neither of them. Compute binormals from normals - // and tangents if needed. - const std::vector& tangents = mesh.GetTangents(); - const std::vector* binormals = &mesh.GetBinormals(); - - if(tangents.size()) { - std::vector tempBinormals; - if (!binormals->size()) { - if (normals.size()) { - tempBinormals.resize(normals.size()); - for (unsigned int i = 0; i < tangents.size(); ++i) { - tempBinormals[i] = normals[i] ^ tangents[i]; - } - - binormals = &tempBinormals; - } - else { - binormals = NULL; - } - } - - if(binormals) { - ai_assert( tangents.size() == vertices.size() ); - ai_assert( binormals->size() == vertices.size() ); - - out_mesh->mTangents = new aiVector3D[vertices.size()]; - std::copy(tangents.begin(),tangents.end(),out_mesh->mTangents); - - out_mesh->mBitangents = new aiVector3D[vertices.size()]; - std::copy(binormals->begin(),binormals->end(),out_mesh->mBitangents); - } - } - - // copy texture coords - for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { - const std::vector& uvs = mesh.GetTextureCoords(i); - if(uvs.empty()) { - break; - } - - aiVector3D* out_uv = out_mesh->mTextureCoords[i] = new aiVector3D[vertices.size()]; - 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& colors = mesh.GetVertexColors(i); - if(colors.empty()) { - break; - } - - out_mesh->mColors[i] = new aiColor4D[vertices.size()]; - std::copy(colors.begin(),colors.end(),out_mesh->mColors[i]); - } - - if(!doc.Settings().readMaterials || mindices.empty()) { - FBXImporter::LogError("no material assigned to mesh, setting default material"); - out_mesh->mMaterialIndex = GetDefaultMaterial(); - } - else { - ConvertMaterialForMesh(out_mesh,model,mesh,mindices[0]); - } - - if(doc.Settings().readWeights && mesh.DeformerSkin() != NULL) { - ConvertWeights(out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION); - } - - return static_cast(meshes.size() - 1); - } - + unsigned int ConvertMeshSingleMaterial( const MeshGeometry& mesh, const Model& model, + const aiMatrix4x4& node_global_transform ); // ------------------------------------------------------------------------------------------------ - std::vector ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform) - { - const MatIndexArray& mindices = mesh.GetMaterialIndices(); - ai_assert(mindices.size()); - - std::set had; - std::vector 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; - } - + std::vector ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model, + const aiMatrix4x4& node_global_transform ); // ------------------------------------------------------------------------------------------------ - unsigned int ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, + 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& vertices = mesh.GetVertices(); - const std::vector& faces = mesh.GetFaceIndexCounts(); - - const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != NULL; - - unsigned int count_faces = 0; - unsigned int count_vertices = 0; - - // count faces - std::vector::const_iterator itf = faces.begin(); - for(MatIndexArray::const_iterator it = mindices.begin(), - end = mindices.end(); it != end; ++it, ++itf) - { - if ((*it) != index) { - continue; - } - ++count_faces; - count_vertices += *itf; - } - - ai_assert(count_faces); - ai_assert(count_vertices); - - // mapping from output indices to DOM indexing, needed to resolve weights - std::vector reverseMapping; - - if (process_weights) { - reverseMapping.resize(count_vertices); - } - - // allocate output data arrays, but don't fill them yet - out_mesh->mNumVertices = count_vertices; - out_mesh->mVertices = new aiVector3D[count_vertices]; - - out_mesh->mNumFaces = count_faces; - aiFace* fac = out_mesh->mFaces = new aiFace[count_faces](); - - - // allocate normals - const std::vector& normals = mesh.GetNormals(); - if(normals.size()) { - ai_assert(normals.size() == vertices.size()); - out_mesh->mNormals = new aiVector3D[vertices.size()]; - } - - // allocate tangents, binormals. - const std::vector& tangents = mesh.GetTangents(); - const std::vector* binormals = &mesh.GetBinormals(); - - if(tangents.size()) { - std::vector 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& uvs = mesh.GetTextureCoords(i); - if(uvs.empty()) { - break; - } - - out_mesh->mTextureCoords[i] = new aiVector3D[vertices.size()]; - out_mesh->mNumUVComponents[i] = 2; - } - - // allocate vertex colors - unsigned int num_vcs = 0; - for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i, ++num_vcs) { - const std::vector& colors = mesh.GetVertexColors(i); - if(colors.empty()) { - break; - } - - out_mesh->mColors[i] = new aiColor4D[vertices.size()]; - } - - unsigned int cursor = 0, in_cursor = 0; - - itf = faces.begin(); - for(MatIndexArray::const_iterator it = mindices.begin(), - end = mindices.end(); it != end; ++it, ++itf) - { - const unsigned int pcount = *itf; - if ((*it) != index) { - in_cursor += pcount; - continue; - } - - aiFace& f = *fac++; - - f.mNumIndices = pcount; - f.mIndices = new unsigned int[pcount]; - switch(pcount) - { - case 1: - out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT; - break; - case 2: - out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; - break; - case 3: - out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; - break; - default: - out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; - break; - } - for (unsigned int i = 0; i < pcount; ++i, ++cursor, ++in_cursor) { - f.mIndices[i] = cursor; - - if(reverseMapping.size()) { - reverseMapping[cursor] = in_cursor; - } - - out_mesh->mVertices[cursor] = vertices[in_cursor]; - - if(out_mesh->mNormals) { - out_mesh->mNormals[cursor] = normals[in_cursor]; - } - - if(out_mesh->mTangents) { - out_mesh->mTangents[cursor] = tangents[in_cursor]; - out_mesh->mBitangents[cursor] = (*binormals)[in_cursor]; - } - - for (unsigned int i = 0; i < num_uvs; ++i) { - const std::vector& 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& 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(meshes.size() - 1); - } + const aiMatrix4x4& node_global_transform ); + // ------------------------------------------------------------------------------------------------ static const unsigned int NO_MATERIAL_SEPARATION = /* std::numeric_limits::max() */ static_cast(-1); @@ -1223,707 +198,64 @@ private: * - 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, + void ConvertWeights( aiMesh* out, const Model& model, const MeshGeometry& geo, const aiMatrix4x4& node_global_transform = aiMatrix4x4(), unsigned int materialIndex = NO_MATERIAL_SEPARATION, - std::vector* outputVertStartIndices = NULL) - { - ai_assert(geo.DeformerSkin()); - - std::vector out_indices; - std::vector index_out_indices; - std::vector count_out_indices; - - const Skin& sk = *geo.DeformerSkin(); - - std::vector bones; - bones.reserve(sk.Clusters().size()); - - const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION; - ai_assert(no_mat_check || outputVertStartIndices); - - try { - - 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::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); - // ToOutputVertexIndex only returns NULL if index is out of bounds - // which should never happen - ai_assert(out_idx != NULL); - - index_out_indices.push_back(no_index_sentinel); - count_out_indices.push_back(0); - - for(unsigned int i = 0; i < count; ++i) { - if (no_mat_check || static_cast(mats[geo.FaceForVertexIndex(out_idx[i])]) == materialIndex) { - - if (index_out_indices.back() == no_index_sentinel) { - index_out_indices.back() = out_indices.size(); - - } - - if (no_mat_check) { - out_indices.push_back(out_idx[i]); - } - else { - // this extra lookup is in O(logn), so the entire algorithm becomes O(nlogn) - const std::vector::iterator it = std::lower_bound( - outputVertStartIndices->begin(), - outputVertStartIndices->end(), - out_idx[i] - ); - - out_indices.push_back(std::distance(outputVertStartIndices->begin(), it)); - } - - ++count_out_indices.back(); - ok = true; - } - } - } - - // if we found at least one, generate the output bones - // XXX this could be heavily simplified by collecting the bone - // data in a single step. - if (ok) { - ConvertCluster(bones, model, *cluster, out_indices, index_out_indices, - count_out_indices, node_global_transform); - } - } - } - catch (std::exception&) { - std::for_each(bones.begin(),bones.end(),Util::delete_fun()); - throw; - } - - if(bones.empty()) { - return; - } - - out->mBones = new aiBone*[bones.size()](); - out->mNumBones = static_cast(bones.size()); - - std::swap_ranges(bones.begin(),bones.end(),out->mBones); - } - - + std::vector* outputVertStartIndices = NULL ); // ------------------------------------------------------------------------------------------------ - void ConvertCluster(std::vector& bones, const Model& /*model*/, const Cluster& cl, + void ConvertCluster( std::vector& bones, const Model& /*model*/, const Cluster& cl, std::vector& out_indices, std::vector& index_out_indices, std::vector& count_out_indices, - const aiMatrix4x4& node_global_transform) - { - - aiBone* const bone = new aiBone(); - bones.push_back(bone); - - bone->mName = FixNodeName(cl.TargetNode()->Name()); - - bone->mOffsetMatrix = cl.TransformLink(); - bone->mOffsetMatrix.Inverse(); - - bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform; - - bone->mNumWeights = static_cast(out_indices.size()); - aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[out_indices.size()]; - - const size_t no_index_sentinel = std::numeric_limits::max(); - const WeightArray& weights = cl.GetWeights(); - - const size_t c = index_out_indices.size(); - for (size_t i = 0; i < c; ++i) { - const size_t index_index = index_out_indices[i]; - - if (index_index == no_index_sentinel) { - continue; - } - - const size_t cc = count_out_indices[i]; - for (size_t j = 0; j < cc; ++j) { - aiVertexWeight& out_weight = *cursor++; - - out_weight.mVertexId = static_cast(out_indices[index_index + j]); - out_weight.mWeight = weights[i]; - } - } - } - + const aiMatrix4x4& node_global_transform ); // ------------------------------------------------------------------------------------------------ - void ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo, - MatIndexArray::value_type materialIndex) - { - // locate source materials for this mesh - const std::vector& mats = model.GetMaterials(); - if (static_cast(materialIndex) >= mats.size() || materialIndex < 0) { - FBXImporter::LogError("material index out of bounds, setting default material"); - out->mMaterialIndex = GetDefaultMaterial(); - return; - } - - const Material* const mat = mats[materialIndex]; - MaterialMap::const_iterator it = materials_converted.find(mat); - if (it != materials_converted.end()) { - out->mMaterialIndex = (*it).second; - return; - } - - out->mMaterialIndex = ConvertMaterial(*mat, &geo); - materials_converted[mat] = out->mMaterialIndex; - } - + void ConvertMaterialForMesh( aiMesh* out, const Model& model, const MeshGeometry& geo, + MatIndexArray::value_type materialIndex ); // ------------------------------------------------------------------------------------------------ - 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(materials.size()); - return defaultMaterialIndex - 1; - } + unsigned int GetDefaultMaterial(); // ------------------------------------------------------------------------------------------------ // 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(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(materials.size() - 1); - } + unsigned int ConvertMaterial( const Material& material, const MeshGeometry* const mesh ); // ------------------------------------------------------------------------------------------------ // Video -> aiTexture - unsigned int ConvertVideo(const Video& video) - { - // generate empty output texture - aiTexture* out_tex = new aiTexture(); - textures.push_back(out_tex); - - // assuming the texture is compressed - out_tex->mWidth = static_cast(video.ContentLength()); // total data size - out_tex->mHeight = 0; // fixed to 0 - - // steal the data from the Video to avoid an additional copy - out_tex->pcData = reinterpret_cast( const_cast(video).RelinquishContent() ); - - // try to extract a hint from the file extension - const std::string& filename = video.FileName().empty() ? video.RelativeFilename() : video.FileName(); - std::string ext = BaseImporter::GetExtension(filename); - - if(ext == "jpeg") { - ext = "jpg"; - } - - if(ext.size() <= 3) { - memcpy(out_tex->achFormatHint, ext.c_str(), ext.size()); - } - - return static_cast(textures.size() - 1); - } - + unsigned int ConvertVideo( const Video& video ); // ------------------------------------------------------------------------------------------------ - void TrySetTextureProperties(aiMaterial* out_mat, const TextureMap& textures, + 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()); - - 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(props,"UVSet",ok); - if(ok) { - // "default" is the name which usually appears in the FbxFileTexture template - if(uvSet != "default" && uvSet.length()) { - // this is a bit awkward - we need to find a mesh that uses this - // material and scan its UV channels for the given UV name because - // assimp references UV channels by index, not by name. - - // XXX: the case that UV channels may appear in different orders - // in meshes is unhandled. A possible solution would be to sort - // the UV channels alphabetically, but this would have the side - // effect that the primary (first) UV channel would sometimes - // be moved, causing trouble when users read only the first - // UV channel and ignore UV channel assignments altogether. - - const unsigned int matIndex = static_cast(std::distance(materials.begin(), - std::find(materials.begin(),materials.end(),out_mat) - )); - - - uvIndex = -1; - if (!mesh) - { - BOOST_FOREACH(const MeshMap::value_type& v,meshes_converted) { - const MeshGeometry* const mesh = dynamic_cast (v.first); - if(!mesh) { - continue; - } - - const MatIndexArray& mats = mesh->GetMaterialIndices(); - if(std::find(mats.begin(),mats.end(),matIndex) == mats.end()) { - continue; - } - - int index = -1; - for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { - if(mesh->GetTextureCoords(i).empty()) { - break; - } - const std::string& name = mesh->GetTextureCoordChannelName(i); - if(name == uvSet) { - index = static_cast(i); - break; - } - } - if(index == -1) { - FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material"); - continue; - } - - if(uvIndex == -1) { - uvIndex = index; - } - else { - FBXImporter::LogWarn("the UV channel named " + uvSet + - " appears at different positions in meshes, results will be wrong"); - } - } - } - else - { - int index = -1; - for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { - if(mesh->GetTextureCoords(i).empty()) { - break; - } - const std::string& name = mesh->GetTextureCoordChannelName(i); - if(name == uvSet) { - index = static_cast(i); - break; - } - } - if(index == -1) { - FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material"); - } - - if(uvIndex == -1) { - uvIndex = index; - } - } - - if(uvIndex == -1) { - FBXImporter::LogWarn("failed to resolve UV channel " + uvSet + ", using first UV channel"); - uvIndex = 0; - } - } - } - - out_mat->AddProperty(&uvIndex,1,_AI_MATKEY_UVWSRC_BASE,target,0); - } - } + aiTextureType target, const MeshGeometry* const mesh ); // ------------------------------------------------------------------------------------------------ - void TrySetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, + 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(props,"UVSet",ok); - if(ok) { - // "default" is the name which usually appears in the FbxFileTexture template - if(uvSet != "default" && uvSet.length()) { - // this is a bit awkward - we need to find a mesh that uses this - // material and scan its UV channels for the given UV name because - // assimp references UV channels by index, not by name. - - // XXX: the case that UV channels may appear in different orders - // in meshes is unhandled. A possible solution would be to sort - // the UV channels alphabetically, but this would have the side - // effect that the primary (first) UV channel would sometimes - // be moved, causing trouble when users read only the first - // UV channel and ignore UV channel assignments altogether. - - const unsigned int matIndex = static_cast(std::distance(materials.begin(), - std::find(materials.begin(),materials.end(),out_mat) - )); - - uvIndex = -1; - if (!mesh) - { - BOOST_FOREACH(const MeshMap::value_type& v,meshes_converted) { - const MeshGeometry* const mesh = dynamic_cast (v.first); - if(!mesh) { - continue; - } - - const MatIndexArray& mats = mesh->GetMaterialIndices(); - if(std::find(mats.begin(),mats.end(),matIndex) == mats.end()) { - continue; - } - - int index = -1; - for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { - if(mesh->GetTextureCoords(i).empty()) { - break; - } - const std::string& name = mesh->GetTextureCoordChannelName(i); - if(name == uvSet) { - index = static_cast(i); - break; - } - } - if(index == -1) { - FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material"); - continue; - } - - if(uvIndex == -1) { - uvIndex = index; - } - else { - FBXImporter::LogWarn("the UV channel named " + uvSet + - " appears at different positions in meshes, results will be wrong"); - } - } - } - else - { - int index = -1; - for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { - if(mesh->GetTextureCoords(i).empty()) { - break; - } - const std::string& name = mesh->GetTextureCoordChannelName(i); - if(name == uvSet) { - index = static_cast(i); - break; - } - } - if(index == -1) { - FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material"); - } - - if(uvIndex == -1) { - uvIndex = index; - } - } - - if(uvIndex == -1) { - FBXImporter::LogWarn("failed to resolve UV channel " + uvSet + ", using first UV channel"); - uvIndex = 0; - } - } - } - - out_mat->AddProperty(&uvIndex,1,_AI_MATKEY_UVWSRC_BASE,target,0); - } + aiTextureType target, const MeshGeometry* const mesh ); // ------------------------------------------------------------------------------------------------ - 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 TextureMap& textures, const MeshGeometry* const 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); - } - + void SetTextureProperties( aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh ); // ------------------------------------------------------------------------------------------------ - aiColor3D GetColorPropertyFromMaterial(const PropertyTable& props, const std::string& baseName, - bool& result) - { - result = true; - - bool ok; - const aiVector3D& Diffuse = PropertyGet(props,baseName,ok); - if(ok) { - return aiColor3D(Diffuse.x,Diffuse.y,Diffuse.z); - } - else { - aiVector3D DiffuseColor = PropertyGet(props,baseName + "Color",ok); - if(ok) { - float DiffuseFactor = PropertyGet(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); - } - + aiColor3D GetColorPropertyFromMaterial( const PropertyTable& props, const std::string& baseName, + bool& result ); // ------------------------------------------------------------------------------------------------ - 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(props,"Opacity",ok); - if(ok) { - out_mat->AddProperty(&Opacity,1,AI_MATKEY_OPACITY); - } - - const float Reflectivity = PropertyGet(props,"Reflectivity",ok); - if(ok) { - out_mat->AddProperty(&Reflectivity,1,AI_MATKEY_REFLECTIVITY); - } - - const float Shininess = PropertyGet(props,"Shininess",ok); - if(ok) { - out_mat->AddProperty(&Shininess,1,AI_MATKEY_SHININESS_STRENGTH); - } - - const float ShininessExponent = PropertyGet(props,"ShininessExponent",ok); - if(ok) { - out_mat->AddProperty(&ShininessExponent,1,AI_MATKEY_SHININESS); - } - } - + 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) - { - 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; - } - + static double FrameRateToDouble( FileGlobalSettings::FrameRate fp, double customFPSVal = -1.0 ); // ------------------------------------------------------------------------------------------------ // 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& animations = doc.AnimationStacks(); - BOOST_FOREACH(const AnimationStack* stack, animations) { - ConvertAnimationStack(*stack); - } - } - + void ConvertAnimations(); // ------------------------------------------------------------------------------------------------ // rename a node already partially converted. fixed_name is a string previously returned by @@ -1935,78 +267,13 @@ private: // 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; - } - } - } - } - + 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) - { - // 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; - } - + std::string FixNodeName( const std::string& name ); typedef std::map LayerMap; @@ -2015,661 +282,61 @@ private: // ------------------------------------------------------------------------------------------------ - 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); - } - 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" - }; - - 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(node->Target()); - // this can happen - it could also be a NodeAttribute (i.e. for camera animations) - if(!model) { - continue; - } - - const std::string& name = FixNodeName(model->Name()); - node_map[name].push_back(node); - - layer_map[node] = layer; - } - } - - // generate node animations - std::vector node_anims; - - double min_time = 1e10; - double max_time = -1e10; - - int64_t start_time = st.LocalStart(); - int64_t stop_time = st.LocalStop(); - double start_timeF = CONVERT_FBX_TIME(start_time); - double stop_timeF = CONVERT_FBX_TIME(stop_time); - - try { - BOOST_FOREACH(const NodeMap::value_type& kv, node_map) { - GenerateNodeAnimations(node_anims, - kv.first, - kv.second, - layer_map, - start_time, stop_time, - max_time, - min_time); - } - } - catch(std::exception&) { - std::for_each(node_anims.begin(), node_anims.end(), Util::delete_fun()); - throw; - } - - if(node_anims.size()) { - anim->mChannels = new aiNodeAnim*[node_anims.size()](); - anim->mNumChannels = static_cast(node_anims.size()); - - std::swap_ranges(node_anims.begin(),node_anims.end(),anim->mChannels); - } - else { - // empty animations would fail validation, so drop them - delete anim; - animations.pop_back(); - FBXImporter::LogInfo("ignoring empty AnimationStack (using IK?): " + name); - return; - } - - //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; - } - + void ConvertAnimationStack( const AnimationStack& st ); // ------------------------------------------------------------------------------------------------ - void GenerateNodeAnimations(std::vector& node_anims, + void GenerateNodeAnimations( std::vector& node_anims, const std::string& fixed_name, const std::vector& curves, const LayerMap& layer_map, int64_t start, int64_t stop, double& max_time, - double& min_time) - { - - NodeMap node_property_map; - ai_assert(curves.size()); - - // 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(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, - start, stop, - max_time, - min_time, - true // input is TRS order, assimp is SRT - ); - - ai_assert(nd); - if (nd->mNumPositionKeys == 0 && nd->mNumRotationKeys == 0 && nd->mNumScalingKeys == 0) { - delete nd; - } - else { - node_anims.push_back(nd); - } - return; - } - - // otherwise, things get gruesome and we need separate animation channels - // for each part of the transformation chain. Remember which channels - // we generated and pass this information to the node conversion - // code to avoid nodes that have identity transform, but non-identity - // animations, being dropped. - unsigned int flags = 0, bit = 0x1; - for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) { - const TransformationComp comp = static_cast(i); - - if (chain[i] != node_property_map.end()) { - flags |= bit; - - ai_assert(comp != TransformationComp_RotationPivotInverse); - ai_assert(comp != TransformationComp_ScalingPivotInverse); - - const std::string& chain_name = NameTransformationChainNode(fixed_name, comp); - - aiNodeAnim* na; - switch(comp) - { - case TransformationComp_Rotation: - case TransformationComp_PreRotation: - case TransformationComp_PostRotation: - case TransformationComp_GeometricRotation: - na = GenerateRotationNodeAnim(chain_name, - target, - (*chain[i]).second, - layer_map, - start, stop, - max_time, - min_time); - - break; - - case TransformationComp_RotationOffset: - case TransformationComp_RotationPivot: - case TransformationComp_ScalingOffset: - case TransformationComp_ScalingPivot: - case TransformationComp_Translation: - case TransformationComp_GeometricTranslation: - na = GenerateTranslationNodeAnim(chain_name, - target, - (*chain[i]).second, - layer_map, - start, stop, - max_time, - min_time); - - // pivoting requires us to generate an implicit inverse channel to undo the pivot translation - if (comp == TransformationComp_RotationPivot) { - const std::string& invName = NameTransformationChainNode(fixed_name, - TransformationComp_RotationPivotInverse); - - aiNodeAnim* const inv = GenerateTranslationNodeAnim(invName, - target, - (*chain[i]).second, - layer_map, - start, stop, - max_time, - min_time, - true); - - ai_assert(inv); - if (inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0) { - delete inv; - } - else { - node_anims.push_back(inv); - } - - ai_assert(TransformationComp_RotationPivotInverse > i); - flags |= bit << (TransformationComp_RotationPivotInverse - i); - } - else if (comp == TransformationComp_ScalingPivot) { - const std::string& invName = NameTransformationChainNode(fixed_name, - TransformationComp_ScalingPivotInverse); - - aiNodeAnim* const inv = GenerateTranslationNodeAnim(invName, - target, - (*chain[i]).second, - layer_map, - start, stop, - max_time, - min_time, - true); - - ai_assert(inv); - if (inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0) { - delete inv; - } - else { - node_anims.push_back(inv); - } - - ai_assert(TransformationComp_RotationPivotInverse > i); - flags |= bit << (TransformationComp_RotationPivotInverse - i); - } - - break; - - case TransformationComp_Scaling: - case TransformationComp_GeometricScaling: - na = GenerateScalingNodeAnim(chain_name, - target, - (*chain[i]).second, - layer_map, - start, stop, - max_time, - min_time); - - break; - - default: - ai_assert(false); - } - - ai_assert(na); - if (na->mNumPositionKeys == 0 && na->mNumRotationKeys == 0 && na->mNumScalingKeys == 0) { - delete na; - } - else { - node_anims.push_back(na); - } - continue; - } - } - - node_anim_chain_bits[fixed_name] = flags; - } - + double& min_time ); // ------------------------------------------------------------------------------------------------ - bool IsRedundantAnimationData(const Model& target, + bool IsRedundantAnimationData( const Model& target, TransformationComp comp, - const std::vector& curves) - { - ai_assert(curves.size()); - - // look for animation nodes with - // * sub channels for all relevant components set - // * one key/value pair per component - // * combined values match up the corresponding value in the bind pose node transformation - // only such nodes are 'redundant' for this function. - - if (curves.size() > 1) { - return false; - } - - const AnimationCurveNode& nd = *curves.front(); - const AnimationCurveMap& sub_curves = nd.Curves(); - - const AnimationCurveMap::const_iterator dx = sub_curves.find("d|X"); - const AnimationCurveMap::const_iterator dy = sub_curves.find("d|Y"); - const AnimationCurveMap::const_iterator dz = sub_curves.find("d|Z"); - - if (dx == sub_curves.end() || dy == sub_curves.end() || dz == sub_curves.end()) { - return false; - } - - const KeyValueList& vx = (*dx).second->GetValues(); - const KeyValueList& vy = (*dy).second->GetValues(); - const KeyValueList& vz = (*dz).second->GetValues(); - - if(vx.size() != 1 || vy.size() != 1 || vz.size() != 1) { - return false; - } - - const aiVector3D dyn_val = aiVector3D(vx[0], vy[0], vz[0]); - const aiVector3D& static_val = PropertyGet(target.Props(), - NameTransformationCompProperty(comp), - TransformationCompDefaultValue(comp) - ); - - const float epsilon = 1e-6f; - return (dyn_val - static_val).SquareLength() < epsilon; - } - + const std::vector& curves ); // ------------------------------------------------------------------------------------------------ - aiNodeAnim* GenerateRotationNodeAnim(const std::string& name, + aiNodeAnim* GenerateRotationNodeAnim( const std::string& name, const Model& target, const std::vector& curves, const LayerMap& layer_map, int64_t start, int64_t stop, double& max_time, - double& min_time) - { - ScopeGuard 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(); - } - + double& min_time ); // ------------------------------------------------------------------------------------------------ - aiNodeAnim* GenerateScalingNodeAnim(const std::string& name, + aiNodeAnim* GenerateScalingNodeAnim( const std::string& name, const Model& /*target*/, const std::vector& curves, const LayerMap& layer_map, int64_t start, int64_t stop, double& max_time, - double& min_time) - { - ScopeGuard 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(); - } - + double& min_time ); // ------------------------------------------------------------------------------------------------ - aiNodeAnim* GenerateTranslationNodeAnim(const std::string& name, + aiNodeAnim* GenerateTranslationNodeAnim( const std::string& name, const Model& /*target*/, const std::vector& curves, const LayerMap& layer_map, int64_t start, int64_t stop, double& max_time, double& min_time, - bool inverse = false) - { - ScopeGuard 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(); - } - + bool inverse = false ); // ------------------------------------------------------------------------------------------------ // generate node anim, extracting only Rotation, Scaling and Translation from the given chain - aiNodeAnim* GenerateSimpleNodeAnim(const std::string& name, + aiNodeAnim* GenerateSimpleNodeAnim( const std::string& name, const Model& target, - NodeMap::const_iterator chain[TransformationComp_MAXIMUM], + 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) - - { - ScopeGuard na(new aiNodeAnim()); - na->mNodeName.Set(name); - - const PropertyTable& props = target.Props(); - - // need to convert from TRS order to SRT? - if(reverse_order) { - - aiVector3D def_scale = PropertyGet(props,"Lcl Scaling",aiVector3D(1.f,1.f,1.f)); - aiVector3D def_translate = PropertyGet(props,"Lcl Translation",aiVector3D(0.f,0.f,0.f)); - aiVector3D def_rot = PropertyGet(props,"Lcl Rotation",aiVector3D(0.f,0.f,0.f)); - - KeyFrameListList scaling; - KeyFrameListList translation; - KeyFrameListList rotation; - - if(chain[TransformationComp_Scaling] != iter_end) { - scaling = GetKeyframeList((*chain[TransformationComp_Scaling]).second, start, stop); - } - - if(chain[TransformationComp_Translation] != iter_end) { - translation = GetKeyframeList((*chain[TransformationComp_Translation]).second, start, stop); - } - - if(chain[TransformationComp_Rotation] != iter_end) { - rotation = GetKeyframeList((*chain[TransformationComp_Rotation]).second, start, stop); - } - - KeyFrameListList joined; - joined.insert(joined.end(), scaling.begin(), scaling.end()); - joined.insert(joined.end(), translation.begin(), translation.end()); - joined.insert(joined.end(), rotation.begin(), rotation.end()); - - const KeyTimeList& times = GetKeyTimeList(joined); - - aiQuatKey* out_quat = new aiQuatKey[times.size()]; - aiVectorKey* out_scale = new aiVectorKey[times.size()]; - aiVectorKey* out_translation = new aiVectorKey[times.size()]; - - if (times.size()) - { - ConvertTransformOrder_TRStoSRT(out_quat, out_scale, out_translation, - scaling, - translation, - rotation, - times, - max_time, - min_time, - target.RotationOrder(), - def_scale, - def_translate, - def_rot); - } - - // XXX remove duplicates / redundant keys which this operation did - // likely produce if not all three channels were equally dense. - - na->mNumScalingKeys = static_cast(times.size()); - na->mNumRotationKeys = na->mNumScalingKeys; - na->mNumPositionKeys = na->mNumScalingKeys; - - na->mScalingKeys = out_scale; - na->mRotationKeys = out_quat; - na->mPositionKeys = out_translation; - } - else { - - // if a particular transformation is not given, grab it from - // the corresponding node to meet the semantics of aiNodeAnim, - // which requires all of rotation, scaling and translation - // to be set. - if(chain[TransformationComp_Scaling] != iter_end) { - ConvertScaleKeys(na, (*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(); - } - - + bool reverse_order = false ); // key (time), value, mapto (component index) typedef boost::tuple, boost::shared_ptr, unsigned int > KeyFrameList; @@ -2678,215 +345,26 @@ private: // ------------------------------------------------------------------------------------------------ - KeyFrameListList GetKeyframeList(const std::vector& nodes, int64_t start, int64_t stop) - { - KeyFrameListList inputs; - inputs.reserve(nodes.size()*3); - - //give some breathing room for rounding errors - int64_t adj_start = start - 10000; - int64_t adj_stop = stop + 10000; - - 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()); - - //get values within the start/stop time window - boost::shared_ptr Keys(new KeyTimeList()); - boost::shared_ptr 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(boost::make_tuple(Keys, Values, mapto)); - } - } - return inputs; // pray for NRVO :-) - } - + KeyFrameListList GetKeyframeList( const std::vector& nodes, int64_t start, int64_t stop ); // ------------------------------------------------------------------------------------------------ - 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 next_pos; - next_pos.resize(inputs.size(),0); - - const size_t count = inputs.size(); - while(true) { - - int64_t min_tick = std::numeric_limits::max(); - for (size_t i = 0; i < count; ++i) { - const KeyFrameList& kfl = inputs[i]; - - if (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::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; - } - + KeyTimeList GetKeyTimeList( const KeyFrameListList& inputs ); // ------------------------------------------------------------------------------------------------ - void InterpolateKeys(aiVectorKey* valOut,const KeyTimeList& keys, const KeyFrameListList& inputs, + void 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 next_pos; - const size_t count = inputs.size(); - - next_pos.resize(inputs.size(),0); - - BOOST_FOREACH(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 = 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((time - timeA) / (timeB - timeA)); - const float interpValue = static_cast(valueA + (valueB - valueA) * factor); - - 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; - } - } - + double& min_time ); // ------------------------------------------------------------------------------------------------ - void InterpolateKeys(aiQuatKey* valOut,const KeyTimeList& keys, const KeyFrameListList& inputs, + void 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); - - boost::scoped_array 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; - } - } - + Model::RotOrder order ); // ------------------------------------------------------------------------------------------------ - void ConvertTransformOrder_TRStoSRT(aiQuatKey* out_quat, aiVectorKey* out_scale, + void ConvertTransformOrder_TRStoSRT( aiQuatKey* out_quat, aiVectorKey* out_scale, aiVectorKey* out_translation, const KeyFrameListList& scaling, const KeyFrameListList& translation, @@ -2897,181 +375,36 @@ private: 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); - } - } - + const aiVector3D& def_rotation ); // ------------------------------------------------------------------------------------------------ // euler xyz -> quat - aiQuaternion EulerToQuaternion(const aiVector3D& rot, Model::RotOrder order) - { - aiMatrix4x4 m; - GetRotationMatrix(order, rot, m); - - return aiQuaternion(aiMatrix3x3(m)); - } - + aiQuaternion EulerToQuaternion( const aiVector3D& rot, Model::RotOrder order ); // ------------------------------------------------------------------------------------------------ - void ConvertScaleKeys(aiNodeAnim* na, const std::vector& nodes, const LayerMap& /*layers*/, + void ConvertScaleKeys( aiNodeAnim* na, const std::vector& nodes, const LayerMap& /*layers*/, int64_t start, int64_t stop, double& maxTime, - double& minTime) - { - ai_assert(nodes.size()); - - // XXX for now, assume scale should be blended geometrically (i.e. two - // layers should be multiplied with each other). There is a FBX - // property in the layer to specify the behaviour, though. - - const KeyFrameListList& inputs = GetKeyframeList(nodes, start, stop); - const KeyTimeList& keys = GetKeyTimeList(inputs); - - na->mNumScalingKeys = static_cast(keys.size()); - na->mScalingKeys = new aiVectorKey[keys.size()]; - if (keys.size() > 0) - InterpolateKeys(na->mScalingKeys, keys, inputs, aiVector3D(1.0f, 1.0f, 1.0f), maxTime, minTime); - } - + double& minTime ); // ------------------------------------------------------------------------------------------------ - void ConvertTranslationKeys(aiNodeAnim* na, const std::vector& nodes, + void ConvertTranslationKeys( aiNodeAnim* na, const std::vector& nodes, const LayerMap& /*layers*/, int64_t start, int64_t stop, double& maxTime, - double& minTime) - { - ai_assert(nodes.size()); - - // XXX see notes in ConvertScaleKeys() - const KeyFrameListList& inputs = GetKeyframeList(nodes, start, stop); - const KeyTimeList& keys = GetKeyTimeList(inputs); - - na->mNumPositionKeys = static_cast(keys.size()); - na->mPositionKeys = new aiVectorKey[keys.size()]; - if (keys.size() > 0) - InterpolateKeys(na->mPositionKeys, keys, inputs, aiVector3D(0.0f, 0.0f, 0.0f), maxTime, minTime); - } - + double& minTime ); // ------------------------------------------------------------------------------------------------ - void ConvertRotationKeys(aiNodeAnim* na, const std::vector& nodes, + void ConvertRotationKeys( aiNodeAnim* na, const std::vector& nodes, const LayerMap& /*layers*/, int64_t start, int64_t stop, double& maxTime, double& minTime, - Model::RotOrder order) - { - ai_assert(nodes.size()); - - // XXX see notes in ConvertScaleKeys() - const std::vector< KeyFrameList >& inputs = GetKeyframeList(nodes, start, stop); - const KeyTimeList& keys = GetKeyTimeList(inputs); - - na->mNumRotationKeys = static_cast(keys.size()); - na->mRotationKeys = new aiQuatKey[keys.size()]; - if (keys.size() > 0) - InterpolateKeys(na->mRotationKeys, keys, inputs, aiVector3D(0.0f, 0.0f, 0.0f), maxTime, minTime, order); - } - + Model::RotOrder 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(meshes.size()); - - std::swap_ranges(meshes.begin(),meshes.end(),out->mMeshes); - } - - if(materials.size()) { - out->mMaterials = new aiMaterial*[materials.size()](); - out->mNumMaterials = static_cast(materials.size()); - - std::swap_ranges(materials.begin(),materials.end(),out->mMaterials); - } - - if(animations.size()) { - out->mAnimations = new aiAnimation*[animations.size()](); - out->mNumAnimations = static_cast(animations.size()); - - std::swap_ranges(animations.begin(),animations.end(),out->mAnimations); - } - - if(lights.size()) { - out->mLights = new aiLight*[lights.size()](); - out->mNumLights = static_cast(lights.size()); - - std::swap_ranges(lights.begin(),lights.end(),out->mLights); - } - - if(cameras.size()) { - out->mCameras = new aiCamera*[cameras.size()](); - out->mNumCameras = static_cast(cameras.size()); - - std::swap_ranges(cameras.begin(),cameras.end(),out->mCameras); - } - - if(textures.size()) { - out->mTextures = new aiTexture*[textures.size()](); - out->mNumTextures = static_cast(textures.size()); - - std::swap_ranges(textures.begin(),textures.end(),out->mTextures); - } - } - + void TransferDataToScene(); private: @@ -3111,6 +444,2861 @@ private: 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 + BOOST_FOREACH( const ObjectMap::value_type& v, doc.Objects() ) { + + const Object* ob = v.second->Get(); + if ( !ob ) { + continue; + } + + const Material* mat = dynamic_cast( ob ); + if ( mat ) { + + if ( materials_converted.find( mat ) == materials_converted.end() ) { + ConvertMaterial( *mat, 0 ); + } + } + } + } + + 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() ); + std::for_each( materials.begin(), materials.end(), Util::delete_fun() ); + std::for_each( animations.begin(), animations.end(), Util::delete_fun() ); + std::for_each( lights.begin(), lights.end(), Util::delete_fun() ); + std::for_each( cameras.begin(), cameras.end(), Util::delete_fun() ); + std::for_each( textures.begin(), textures.end(), Util::delete_fun() ); +} + +void 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& conns = doc.GetConnectionsByDestinationSequenced( id, "Model" ); + + std::vector nodes; + nodes.reserve( conns.size() ); + + std::vector 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( 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( nodes.size() ); + + std::swap_ranges( nodes.begin(), nodes.end(), parent.mChildren ); + } + } + catch ( std::exception& ) { + Util::delete_fun deleter; + std::for_each( nodes.begin(), nodes.end(), deleter ); + std::for_each( nodes_chain.begin(), nodes_chain.end(), deleter ); + } +} + + +void Converter::ConvertLights( const Model& model ) +{ + const std::vector& node_attrs = model.GetAttributes(); + BOOST_FOREACH( const NodeAttribute* attr, node_attrs ) { + const Light* const light = dynamic_cast( attr ); + if ( light ) { + ConvertLight( model, *light ); + } + } +} + +void Converter::ConvertCameras( const Model& model ) +{ + const std::vector& node_attrs = model.GetAttributes(); + BOOST_FOREACH( const NodeAttribute* attr, node_attrs ) { + const Camera* const cam = dynamic_cast( 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( 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& output_nodes ) +{ + const PropertyTable& props = model.Props(); + const Model::RotOrder rot = model.RotationOrder(); + + bool ok; + + aiMatrix4x4 chain[ TransformationComp_MAXIMUM ]; + std::fill_n( chain, static_cast( TransformationComp_MAXIMUM ), aiMatrix4x4() ); + + // generate transformation matrices for all the different transformation components + const float zero_epsilon = 1e-6f; + bool is_complex = false; + + const aiVector3D& PreRotation = PropertyGet( props, "PreRotation", ok ); + if ( ok && PreRotation.SquareLength() > zero_epsilon ) { + is_complex = true; + + GetRotationMatrix( rot, PreRotation, chain[ TransformationComp_PreRotation ] ); + } + + const aiVector3D& PostRotation = PropertyGet( props, "PostRotation", ok ); + if ( ok && PostRotation.SquareLength() > zero_epsilon ) { + is_complex = true; + + GetRotationMatrix( rot, PostRotation, chain[ TransformationComp_PostRotation ] ); + } + + const aiVector3D& RotationPivot = PropertyGet( props, "RotationPivot", ok ); + if ( ok && RotationPivot.SquareLength() > zero_epsilon ) { + is_complex = true; + + aiMatrix4x4::Translation( RotationPivot, chain[ TransformationComp_RotationPivot ] ); + aiMatrix4x4::Translation( -RotationPivot, chain[ TransformationComp_RotationPivotInverse ] ); + } + + const aiVector3D& RotationOffset = PropertyGet( props, "RotationOffset", ok ); + if ( ok && RotationOffset.SquareLength() > zero_epsilon ) { + is_complex = true; + + aiMatrix4x4::Translation( RotationOffset, chain[ TransformationComp_RotationOffset ] ); + } + + const aiVector3D& ScalingOffset = PropertyGet( props, "ScalingOffset", ok ); + if ( ok && ScalingOffset.SquareLength() > zero_epsilon ) { + is_complex = true; + + aiMatrix4x4::Translation( ScalingOffset, chain[ TransformationComp_ScalingOffset ] ); + } + + const aiVector3D& ScalingPivot = PropertyGet( props, "ScalingPivot", ok ); + if ( ok && ScalingPivot.SquareLength() > zero_epsilon ) { + is_complex = true; + + aiMatrix4x4::Translation( ScalingPivot, chain[ TransformationComp_ScalingPivot ] ); + aiMatrix4x4::Translation( -ScalingPivot, chain[ TransformationComp_ScalingPivotInverse ] ); + } + + const aiVector3D& Translation = PropertyGet( props, "Lcl Translation", ok ); + if ( ok && Translation.SquareLength() > zero_epsilon ) { + aiMatrix4x4::Translation( Translation, chain[ TransformationComp_Translation ] ); + } + + const aiVector3D& Scaling = PropertyGet( props, "Lcl Scaling", ok ); + if ( ok && std::fabs( Scaling.SquareLength() - 1.0f ) > zero_epsilon ) { + aiMatrix4x4::Scaling( Scaling, chain[ TransformationComp_Scaling ] ); + } + + const aiVector3D& Rotation = PropertyGet( props, "Lcl Rotation", ok ); + if ( ok && Rotation.SquareLength() > zero_epsilon ) { + GetRotationMatrix( rot, Rotation, chain[ TransformationComp_Rotation ] ); + } + + const aiVector3D& GeometricScaling = PropertyGet( props, "GeometricScaling", ok ); + if ( ok && std::fabs( GeometricScaling.SquareLength() - 1.0f ) > zero_epsilon ) { + aiMatrix4x4::Scaling( GeometricScaling, chain[ TransformationComp_GeometricScaling ] ); + } + + const aiVector3D& GeometricRotation = PropertyGet( props, "GeometricRotation", ok ); + if ( ok && GeometricRotation.SquareLength() > zero_epsilon ) { + GetRotationMatrix( rot, GeometricRotation, chain[ TransformationComp_GeometricRotation ] ); + } + + const aiVector3D& GeometricTranslation = PropertyGet( 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( 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 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( 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 + BOOST_FOREACH( const DirectPropertyMap::value_type& prop, unparsedProperties ) { + + // Interpret the property as a concrete type + if ( const TypedProperty* interpreted = prop.second->As >() ) + data->Set( index++, prop.first, interpreted->Value() ); + else if ( const TypedProperty* interpreted = prop.second->As >() ) + data->Set( index++, prop.first, interpreted->Value() ); + else if ( const TypedProperty* interpreted = prop.second->As >() ) + data->Set( index++, prop.first, interpreted->Value() ); + else if ( const TypedProperty* interpreted = prop.second->As >() ) + data->Set( index++, prop.first, interpreted->Value() ); + else if ( const TypedProperty* interpreted = prop.second->As >() ) + data->Set( index++, prop.first, aiString( interpreted->Value() ) ); + else if ( const TypedProperty* interpreted = prop.second->As >() ) + data->Set( index++, prop.first, interpreted->Value() ); + else + assert( false ); + } +} + +void Converter::ConvertModel( const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform ) +{ + const std::vector& geos = model.GetGeometry(); + + std::vector 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& 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 Converter::ConvertMesh( const MeshGeometry& mesh, const Model& model, + const aiMatrix4x4& node_global_transform ) +{ + std::vector temp; + + MeshMap::const_iterator it = meshes_converted.find( &mesh ); + if ( it != meshes_converted.end() ) { + std::copy( ( *it ).second.begin(), ( *it ).second.end(), std::back_inserter( temp ) ); + return temp; + } + + const std::vector& vertices = mesh.GetVertices(); + const std::vector& faces = mesh.GetFaceIndexCounts(); + if ( vertices.empty() || faces.empty() ) { + FBXImporter::LogWarn( "ignoring empty geometry: " + mesh.Name() ); + return temp; + } + + // one material per mesh maps easily to aiMesh. Multiple material + // meshes need to be split. + const MatIndexArray& mindices = mesh.GetMaterialIndices(); + if ( doc.Settings().readMaterials && !mindices.empty() ) { + const MatIndexArray::value_type base = mindices[ 0 ]; + BOOST_FOREACH( 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( 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& vertices = mesh.GetVertices(); + const std::vector& faces = mesh.GetFaceIndexCounts(); + + // copy vertices + out_mesh->mNumVertices = static_cast( vertices.size() ); + out_mesh->mVertices = new aiVector3D[ vertices.size() ]; + std::copy( vertices.begin(), vertices.end(), out_mesh->mVertices ); + + // generate dummy faces + out_mesh->mNumFaces = static_cast( faces.size() ); + aiFace* fac = out_mesh->mFaces = new aiFace[ faces.size() ](); + + unsigned int cursor = 0; + 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& normals = mesh.GetNormals(); + if ( normals.size() ) { + ai_assert( normals.size() == vertices.size() ); + + out_mesh->mNormals = new aiVector3D[ vertices.size() ]; + std::copy( normals.begin(), normals.end(), out_mesh->mNormals ); + } + + // copy tangents - assimp requires both tangents and bitangents (binormals) + // to be present, or neither of them. Compute binormals from normals + // and tangents if needed. + const std::vector& tangents = mesh.GetTangents(); + const std::vector* binormals = &mesh.GetBinormals(); + + if ( tangents.size() ) { + std::vector tempBinormals; + if ( !binormals->size() ) { + if ( normals.size() ) { + tempBinormals.resize( normals.size() ); + for ( unsigned int i = 0; i < tangents.size(); ++i ) { + tempBinormals[ i ] = normals[ i ] ^ tangents[ i ]; + } + + binormals = &tempBinormals; + } + else { + binormals = NULL; + } + } + + if ( binormals ) { + ai_assert( tangents.size() == vertices.size() ); + ai_assert( binormals->size() == vertices.size() ); + + out_mesh->mTangents = new aiVector3D[ vertices.size() ]; + std::copy( tangents.begin(), tangents.end(), out_mesh->mTangents ); + + out_mesh->mBitangents = new aiVector3D[ vertices.size() ]; + std::copy( binormals->begin(), binormals->end(), out_mesh->mBitangents ); + } + } + + // copy texture coords + for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) { + const std::vector& uvs = mesh.GetTextureCoords( i ); + if ( uvs.empty() ) { + break; + } + + aiVector3D* out_uv = out_mesh->mTextureCoords[ i ] = new aiVector3D[ vertices.size() ]; + 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& colors = mesh.GetVertexColors( i ); + if ( colors.empty() ) { + break; + } + + out_mesh->mColors[ i ] = new aiColor4D[ vertices.size() ]; + std::copy( colors.begin(), colors.end(), out_mesh->mColors[ i ] ); + } + + if ( !doc.Settings().readMaterials || mindices.empty() ) { + FBXImporter::LogError( "no material assigned to mesh, setting default material" ); + out_mesh->mMaterialIndex = GetDefaultMaterial(); + } + else { + ConvertMaterialForMesh( out_mesh, model, mesh, mindices[ 0 ] ); + } + + if ( doc.Settings().readWeights && mesh.DeformerSkin() != NULL ) { + ConvertWeights( out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION ); + } + + return static_cast( meshes.size() - 1 ); +} + +std::vector Converter::ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model, + const aiMatrix4x4& node_global_transform ) +{ + const MatIndexArray& mindices = mesh.GetMaterialIndices(); + ai_assert( mindices.size() ); + + std::set had; + std::vector 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 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& vertices = mesh.GetVertices(); + const std::vector& faces = mesh.GetFaceIndexCounts(); + + const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != NULL; + + unsigned int count_faces = 0; + unsigned int count_vertices = 0; + + // count faces + std::vector::const_iterator itf = faces.begin(); + for ( MatIndexArray::const_iterator it = mindices.begin(), + end = mindices.end(); it != end; ++it, ++itf ) + { + if ( ( *it ) != index ) { + continue; + } + ++count_faces; + count_vertices += *itf; + } + + ai_assert( count_faces ); + ai_assert( count_vertices ); + + // mapping from output indices to DOM indexing, needed to resolve weights + std::vector reverseMapping; + + if ( process_weights ) { + reverseMapping.resize( count_vertices ); + } + + // allocate output data arrays, but don't fill them yet + out_mesh->mNumVertices = count_vertices; + out_mesh->mVertices = new aiVector3D[ count_vertices ]; + + out_mesh->mNumFaces = count_faces; + aiFace* fac = out_mesh->mFaces = new aiFace[ count_faces ](); + + + // allocate normals + const std::vector& normals = mesh.GetNormals(); + if ( normals.size() ) { + ai_assert( normals.size() == vertices.size() ); + out_mesh->mNormals = new aiVector3D[ vertices.size() ]; + } + + // allocate tangents, binormals. + const std::vector& tangents = mesh.GetTangents(); + const std::vector* binormals = &mesh.GetBinormals(); + + if ( tangents.size() ) { + std::vector 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& uvs = mesh.GetTextureCoords( i ); + if ( uvs.empty() ) { + break; + } + + out_mesh->mTextureCoords[ i ] = new aiVector3D[ vertices.size() ]; + out_mesh->mNumUVComponents[ i ] = 2; + } + + // allocate vertex colors + unsigned int num_vcs = 0; + for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i, ++num_vcs ) { + const std::vector& colors = mesh.GetVertexColors( i ); + if ( colors.empty() ) { + break; + } + + out_mesh->mColors[ i ] = new aiColor4D[ vertices.size() ]; + } + + unsigned int cursor = 0, in_cursor = 0; + + itf = faces.begin(); + for ( MatIndexArray::const_iterator it = mindices.begin(), + end = mindices.end(); it != end; ++it, ++itf ) + { + const unsigned int pcount = *itf; + if ( ( *it ) != index ) { + in_cursor += pcount; + continue; + } + + aiFace& f = *fac++; + + f.mNumIndices = pcount; + f.mIndices = new unsigned int[ pcount ]; + switch ( pcount ) + { + case 1: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + case 2: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + case 3: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + default: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + break; + } + for ( unsigned int i = 0; i < pcount; ++i, ++cursor, ++in_cursor ) { + f.mIndices[ i ] = cursor; + + if ( reverseMapping.size() ) { + reverseMapping[ cursor ] = in_cursor; + } + + out_mesh->mVertices[ cursor ] = vertices[ in_cursor ]; + + if ( out_mesh->mNormals ) { + out_mesh->mNormals[ cursor ] = normals[ in_cursor ]; + } + + if ( out_mesh->mTangents ) { + out_mesh->mTangents[ cursor ] = tangents[ in_cursor ]; + out_mesh->mBitangents[ cursor ] = ( *binormals )[ in_cursor ]; + } + + for ( unsigned int i = 0; i < num_uvs; ++i ) { + const std::vector& 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& 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( meshes.size() - 1 ); +} + + +void Converter::ConvertWeights( aiMesh* out, const Model& model, const MeshGeometry& geo, + const aiMatrix4x4& node_global_transform , + unsigned int materialIndex, + std::vector* outputVertStartIndices ) +{ + ai_assert( geo.DeformerSkin() ); + + std::vector out_indices; + std::vector index_out_indices; + std::vector count_out_indices; + + const Skin& sk = *geo.DeformerSkin(); + + std::vector bones; + bones.reserve( sk.Clusters().size() ); + + const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION; + ai_assert( no_mat_check || outputVertStartIndices ); + + try { + + 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::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 ); + // ToOutputVertexIndex only returns NULL if index is out of bounds + // which should never happen + ai_assert( out_idx != NULL ); + + index_out_indices.push_back( no_index_sentinel ); + count_out_indices.push_back( 0 ); + + for ( unsigned int i = 0; i < count; ++i ) { + if ( no_mat_check || static_cast( mats[ geo.FaceForVertexIndex( out_idx[ i ] ) ] ) == materialIndex ) { + + if ( index_out_indices.back() == no_index_sentinel ) { + index_out_indices.back() = out_indices.size(); + + } + + if ( no_mat_check ) { + out_indices.push_back( out_idx[ i ] ); + } + else { + // this extra lookup is in O(logn), so the entire algorithm becomes O(nlogn) + const std::vector::iterator it = std::lower_bound( + outputVertStartIndices->begin(), + outputVertStartIndices->end(), + out_idx[ i ] + ); + + out_indices.push_back( std::distance( outputVertStartIndices->begin(), it ) ); + } + + ++count_out_indices.back(); + ok = true; + } + } + } + + // if we found at least one, generate the output bones + // XXX this could be heavily simplified by collecting the bone + // data in a single step. + if ( ok ) { + ConvertCluster( bones, model, *cluster, out_indices, index_out_indices, + count_out_indices, node_global_transform ); + } + } + } + catch ( std::exception& ) { + std::for_each( bones.begin(), bones.end(), Util::delete_fun() ); + throw; + } + + if ( bones.empty() ) { + return; + } + + out->mBones = new aiBone*[ bones.size() ](); + out->mNumBones = static_cast( bones.size() ); + + std::swap_ranges( bones.begin(), bones.end(), out->mBones ); +} + +void Converter::ConvertCluster( std::vector& bones, const Model& /*model*/, const Cluster& cl, + std::vector& out_indices, + std::vector& index_out_indices, + std::vector& count_out_indices, + const aiMatrix4x4& node_global_transform ) +{ + + aiBone* const bone = new aiBone(); + bones.push_back( bone ); + + bone->mName = FixNodeName( cl.TargetNode()->Name() ); + + bone->mOffsetMatrix = cl.TransformLink(); + bone->mOffsetMatrix.Inverse(); + + bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform; + + bone->mNumWeights = static_cast( out_indices.size() ); + aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[ out_indices.size() ]; + + const size_t no_index_sentinel = std::numeric_limits::max(); + const WeightArray& weights = cl.GetWeights(); + + const size_t c = index_out_indices.size(); + for ( size_t i = 0; i < c; ++i ) { + const size_t index_index = index_out_indices[ i ]; + + if ( index_index == no_index_sentinel ) { + continue; + } + + const size_t cc = count_out_indices[ i ]; + for ( size_t j = 0; j < cc; ++j ) { + aiVertexWeight& out_weight = *cursor++; + + out_weight.mVertexId = static_cast( out_indices[ index_index + j ] ); + out_weight.mWeight = weights[ i ]; + } + } +} + + +void Converter::ConvertMaterialForMesh( aiMesh* out, const Model& model, const MeshGeometry& geo, + MatIndexArray::value_type materialIndex ) +{ + // locate source materials for this mesh + const std::vector& mats = model.GetMaterials(); + if ( static_cast( materialIndex ) >= mats.size() || materialIndex < 0 ) { + FBXImporter::LogError( "material index out of bounds, setting default material" ); + out->mMaterialIndex = GetDefaultMaterial(); + return; + } + + const Material* const mat = mats[ materialIndex ]; + MaterialMap::const_iterator it = materials_converted.find( mat ); + if ( it != materials_converted.end() ) { + out->mMaterialIndex = ( *it ).second; + return; + } + + out->mMaterialIndex = ConvertMaterial( *mat, &geo ); + materials_converted[ mat ] = out->mMaterialIndex; +} + +unsigned int 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( 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( 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( video.ContentLength() ); // total data size + out_tex->mHeight = 0; // fixed to 0 + + // steal the data from the Video to avoid an additional copy + out_tex->pcData = reinterpret_cast( const_cast( video ).RelinquishContent() ); + + // try to extract a hint from the file extension + const std::string& filename = video.FileName().empty() ? video.RelativeFilename() : video.FileName(); + std::string ext = BaseImporter::GetExtension( filename ); + + if ( ext == "jpeg" ) { + ext = "jpg"; + } + + if ( ext.size() <= 3 ) { + memcpy( out_tex->achFormatHint, ext.c_str(), ext.size() ); + } + + return static_cast( 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( props, "UVSet", ok ); + if ( ok ) { + // "default" is the name which usually appears in the FbxFileTexture template + if ( uvSet != "default" && uvSet.length() ) { + // this is a bit awkward - we need to find a mesh that uses this + // material and scan its UV channels for the given UV name because + // assimp references UV channels by index, not by name. + + // XXX: the case that UV channels may appear in different orders + // in meshes is unhandled. A possible solution would be to sort + // the UV channels alphabetically, but this would have the side + // effect that the primary (first) UV channel would sometimes + // be moved, causing trouble when users read only the first + // UV channel and ignore UV channel assignments altogether. + + const unsigned int matIndex = static_cast( std::distance( materials.begin(), + std::find( materials.begin(), materials.end(), out_mat ) + ) ); + + + uvIndex = -1; + if ( !mesh ) + { + BOOST_FOREACH( const MeshMap::value_type& v, meshes_converted ) { + const MeshGeometry* const mesh = dynamic_cast ( v.first ); + if ( !mesh ) { + continue; + } + + const MatIndexArray& mats = mesh->GetMaterialIndices(); + if ( std::find( mats.begin(), mats.end(), matIndex ) == mats.end() ) { + continue; + } + + int index = -1; + for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) { + if ( mesh->GetTextureCoords( i ).empty() ) { + break; + } + const std::string& name = mesh->GetTextureCoordChannelName( i ); + if ( name == uvSet ) { + index = static_cast( i ); + break; + } + } + if ( index == -1 ) { + FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" ); + continue; + } + + if ( uvIndex == -1 ) { + uvIndex = index; + } + else { + FBXImporter::LogWarn( "the UV channel named " + uvSet + + " appears at different positions in meshes, results will be wrong" ); + } + } + } + else + { + int index = -1; + for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) { + if ( mesh->GetTextureCoords( i ).empty() ) { + break; + } + const std::string& name = mesh->GetTextureCoordChannelName( i ); + if ( name == uvSet ) { + index = static_cast( i ); + break; + } + } + if ( index == -1 ) { + FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" ); + } + + if ( uvIndex == -1 ) { + uvIndex = index; + } + } + + if ( uvIndex == -1 ) { + FBXImporter::LogWarn( "failed to resolve UV channel " + uvSet + ", using first UV channel" ); + uvIndex = 0; + } + } + } + + out_mat->AddProperty( &uvIndex, 1, _AI_MATKEY_UVWSRC_BASE, target, 0 ); + } +} + +void 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( props, "UVSet", ok ); + if ( ok ) { + // "default" is the name which usually appears in the FbxFileTexture template + if ( uvSet != "default" && uvSet.length() ) { + // this is a bit awkward - we need to find a mesh that uses this + // material and scan its UV channels for the given UV name because + // assimp references UV channels by index, not by name. + + // XXX: the case that UV channels may appear in different orders + // in meshes is unhandled. A possible solution would be to sort + // the UV channels alphabetically, but this would have the side + // effect that the primary (first) UV channel would sometimes + // be moved, causing trouble when users read only the first + // UV channel and ignore UV channel assignments altogether. + + const unsigned int matIndex = static_cast( std::distance( materials.begin(), + std::find( materials.begin(), materials.end(), out_mat ) + ) ); + + uvIndex = -1; + if ( !mesh ) + { + BOOST_FOREACH( const MeshMap::value_type& v, meshes_converted ) { + const MeshGeometry* const mesh = dynamic_cast ( v.first ); + if ( !mesh ) { + continue; + } + + const MatIndexArray& mats = mesh->GetMaterialIndices(); + if ( std::find( mats.begin(), mats.end(), matIndex ) == mats.end() ) { + continue; + } + + int index = -1; + for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) { + if ( mesh->GetTextureCoords( i ).empty() ) { + break; + } + const std::string& name = mesh->GetTextureCoordChannelName( i ); + if ( name == uvSet ) { + index = static_cast( i ); + break; + } + } + if ( index == -1 ) { + FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" ); + continue; + } + + if ( uvIndex == -1 ) { + uvIndex = index; + } + else { + FBXImporter::LogWarn( "the UV channel named " + uvSet + + " appears at different positions in meshes, results will be wrong" ); + } + } + } + else + { + int index = -1; + for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) { + if ( mesh->GetTextureCoords( i ).empty() ) { + break; + } + const std::string& name = mesh->GetTextureCoordChannelName( i ); + if ( name == uvSet ) { + index = static_cast( i ); + break; + } + } + if ( index == -1 ) { + FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" ); + } + + if ( uvIndex == -1 ) { + uvIndex = index; + } + } + + if ( uvIndex == -1 ) { + FBXImporter::LogWarn( "failed to resolve UV channel " + uvSet + ", using first UV channel" ); + uvIndex = 0; + } + } + } + + out_mat->AddProperty( &uvIndex, 1, _AI_MATKEY_UVWSRC_BASE, target, 0 ); +} + +void 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( props, baseName, ok ); + if ( ok ) { + return aiColor3D( Diffuse.x, Diffuse.y, Diffuse.z ); + } + else { + aiVector3D DiffuseColor = PropertyGet( props, baseName + "Color", ok ); + if ( ok ) { + float DiffuseFactor = PropertyGet( 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( props, "Opacity", ok ); + if ( ok ) { + out_mat->AddProperty( &Opacity, 1, AI_MATKEY_OPACITY ); + } + + const float Reflectivity = PropertyGet( props, "Reflectivity", ok ); + if ( ok ) { + out_mat->AddProperty( &Reflectivity, 1, AI_MATKEY_REFLECTIVITY ); + } + + const float Shininess = PropertyGet( props, "Shininess", ok ); + if ( ok ) { + out_mat->AddProperty( &Shininess, 1, AI_MATKEY_SHININESS_STRENGTH ); + } + + const float ShininessExponent = PropertyGet( 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& animations = doc.AnimationStacks(); + BOOST_FOREACH( 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 ); + + 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; + } + } + } +} + + +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" + }; + + 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( node->Target() ); + // this can happen - it could also be a NodeAttribute (i.e. for camera animations) + if ( !model ) { + continue; + } + + const std::string& name = FixNodeName( model->Name() ); + node_map[ name ].push_back( node ); + + layer_map[ node ] = layer; + } + } + + // generate node animations + std::vector node_anims; + + double min_time = 1e10; + double max_time = -1e10; + + int64_t start_time = st.LocalStart(); + int64_t stop_time = st.LocalStop(); + double start_timeF = CONVERT_FBX_TIME( start_time ); + double stop_timeF = CONVERT_FBX_TIME( stop_time ); + + try { + BOOST_FOREACH( const NodeMap::value_type& kv, node_map ) { + GenerateNodeAnimations( node_anims, + kv.first, + kv.second, + layer_map, + start_time, stop_time, + max_time, + min_time ); + } + } + catch ( std::exception& ) { + std::for_each( node_anims.begin(), node_anims.end(), Util::delete_fun() ); + throw; + } + + if ( node_anims.size() ) { + anim->mChannels = new aiNodeAnim*[ node_anims.size() ](); + anim->mNumChannels = static_cast( node_anims.size() ); + + std::swap_ranges( node_anims.begin(), node_anims.end(), anim->mChannels ); + } + else { + // empty animations would fail validation, so drop them + delete anim; + animations.pop_back(); + FBXImporter::LogInfo( "ignoring empty AnimationStack (using IK?): " + name ); + return; + } + + //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& curves, + bool strictMode ) { + const Object* target( NULL ); + BOOST_FOREACH( 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& node_anims, + const std::string& fixed_name, + const std::vector& curves, + const LayerMap& layer_map, + int64_t start, int64_t stop, + double& max_time, + double& min_time ) +{ + + NodeMap node_property_map; + ai_assert( curves.size() ); + +#ifdef ASSIMP_BUILD_DEBUG + validateAnimCurveNodes( curves, doc.Settings().strictMode ); +#endif + const AnimationCurveNode* curve_node = NULL; + 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( 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, + start, stop, + max_time, + min_time, + true // input is TRS order, assimp is SRT + ); + + ai_assert( nd ); + if ( nd->mNumPositionKeys == 0 && nd->mNumRotationKeys == 0 && nd->mNumScalingKeys == 0 ) { + delete nd; + } + else { + node_anims.push_back( nd ); + } + return; + } + + // otherwise, things get gruesome and we need separate animation channels + // for each part of the transformation chain. Remember which channels + // we generated and pass this information to the node conversion + // code to avoid nodes that have identity transform, but non-identity + // animations, being dropped. + unsigned int flags = 0, bit = 0x1; + for ( size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1 ) { + const TransformationComp comp = static_cast( i ); + + if ( chain[ i ] != node_property_map.end() ) { + flags |= bit; + + ai_assert( comp != TransformationComp_RotationPivotInverse ); + ai_assert( comp != TransformationComp_ScalingPivotInverse ); + + const std::string& chain_name = NameTransformationChainNode( fixed_name, comp ); + + aiNodeAnim* na; + switch ( comp ) + { + case TransformationComp_Rotation: + case TransformationComp_PreRotation: + case TransformationComp_PostRotation: + case TransformationComp_GeometricRotation: + na = GenerateRotationNodeAnim( chain_name, + target, + ( *chain[ i ] ).second, + layer_map, + start, stop, + max_time, + min_time ); + + break; + + case TransformationComp_RotationOffset: + case TransformationComp_RotationPivot: + case TransformationComp_ScalingOffset: + case TransformationComp_ScalingPivot: + case TransformationComp_Translation: + case TransformationComp_GeometricTranslation: + na = GenerateTranslationNodeAnim( chain_name, + target, + ( *chain[ i ] ).second, + layer_map, + start, stop, + max_time, + min_time ); + + // pivoting requires us to generate an implicit inverse channel to undo the pivot translation + if ( comp == TransformationComp_RotationPivot ) { + const std::string& invName = NameTransformationChainNode( fixed_name, + TransformationComp_RotationPivotInverse ); + + aiNodeAnim* const inv = GenerateTranslationNodeAnim( invName, + target, + ( *chain[ i ] ).second, + layer_map, + start, stop, + max_time, + min_time, + true ); + + ai_assert( inv ); + if ( inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0 ) { + delete inv; + } + else { + node_anims.push_back( inv ); + } + + ai_assert( TransformationComp_RotationPivotInverse > i ); + flags |= bit << ( TransformationComp_RotationPivotInverse - i ); + } + else if ( comp == TransformationComp_ScalingPivot ) { + const std::string& invName = NameTransformationChainNode( fixed_name, + TransformationComp_ScalingPivotInverse ); + + aiNodeAnim* const inv = GenerateTranslationNodeAnim( invName, + target, + ( *chain[ i ] ).second, + layer_map, + start, stop, + max_time, + min_time, + true ); + + ai_assert( inv ); + if ( inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0 ) { + delete inv; + } + else { + node_anims.push_back( inv ); + } + + ai_assert( TransformationComp_RotationPivotInverse > i ); + flags |= bit << ( TransformationComp_RotationPivotInverse - i ); + } + + break; + + case TransformationComp_Scaling: + case TransformationComp_GeometricScaling: + na = GenerateScalingNodeAnim( chain_name, + target, + ( *chain[ i ] ).second, + layer_map, + start, stop, + max_time, + min_time ); + + break; + + default: + ai_assert( false ); + } + + ai_assert( na ); + if ( na->mNumPositionKeys == 0 && na->mNumRotationKeys == 0 && na->mNumScalingKeys == 0 ) { + delete na; + } + else { + node_anims.push_back( na ); + } + continue; + } + } + + node_anim_chain_bits[ fixed_name ] = flags; +} + +bool Converter::IsRedundantAnimationData( const Model& target, + TransformationComp comp, + const std::vector& curves ) +{ + ai_assert( curves.size() ); + + // look for animation nodes with + // * sub channels for all relevant components set + // * one key/value pair per component + // * combined values match up the corresponding value in the bind pose node transformation + // only such nodes are 'redundant' for this function. + + if ( curves.size() > 1 ) { + return false; + } + + const AnimationCurveNode& nd = *curves.front(); + const AnimationCurveMap& sub_curves = nd.Curves(); + + const AnimationCurveMap::const_iterator dx = sub_curves.find( "d|X" ); + const AnimationCurveMap::const_iterator dy = sub_curves.find( "d|Y" ); + const AnimationCurveMap::const_iterator dz = sub_curves.find( "d|Z" ); + + if ( dx == sub_curves.end() || dy == sub_curves.end() || dz == sub_curves.end() ) { + return false; + } + + const KeyValueList& vx = ( *dx ).second->GetValues(); + const KeyValueList& vy = ( *dy ).second->GetValues(); + const KeyValueList& vz = ( *dz ).second->GetValues(); + + if ( vx.size() != 1 || vy.size() != 1 || vz.size() != 1 ) { + return false; + } + + const aiVector3D dyn_val = aiVector3D( vx[ 0 ], vy[ 0 ], vz[ 0 ] ); + const aiVector3D& static_val = PropertyGet( target.Props(), + NameTransformationCompProperty( comp ), + TransformationCompDefaultValue( comp ) + ); + + const float epsilon = 1e-6f; + return ( dyn_val - static_val ).SquareLength() < epsilon; +} + + +aiNodeAnim* Converter::GenerateRotationNodeAnim( const std::string& name, + const Model& target, + const std::vector& curves, + const LayerMap& layer_map, + int64_t start, int64_t stop, + double& max_time, + double& min_time ) +{ + ScopeGuard 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& curves, + const LayerMap& layer_map, + int64_t start, int64_t stop, + double& max_time, + double& min_time ) +{ + ScopeGuard 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& curves, + const LayerMap& layer_map, + int64_t start, int64_t stop, + double& max_time, + double& min_time, + bool inverse ) +{ + ScopeGuard 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 na( new aiNodeAnim() ); + na->mNodeName.Set( name ); + + const PropertyTable& props = target.Props(); + + // need to convert from TRS order to SRT? + if ( reverse_order ) { + + aiVector3D def_scale = PropertyGet( props, "Lcl Scaling", aiVector3D( 1.f, 1.f, 1.f ) ); + aiVector3D def_translate = PropertyGet( props, "Lcl Translation", aiVector3D( 0.f, 0.f, 0.f ) ); + aiVector3D def_rot = PropertyGet( props, "Lcl Rotation", aiVector3D( 0.f, 0.f, 0.f ) ); + + KeyFrameListList scaling; + KeyFrameListList translation; + KeyFrameListList rotation; + + if ( chain[ TransformationComp_Scaling ] != iter_end ) { + scaling = GetKeyframeList( ( *chain[ TransformationComp_Scaling ] ).second, start, stop ); + } + + if ( chain[ TransformationComp_Translation ] != iter_end ) { + translation = GetKeyframeList( ( *chain[ TransformationComp_Translation ] ).second, start, stop ); + } + + if ( chain[ TransformationComp_Rotation ] != iter_end ) { + rotation = GetKeyframeList( ( *chain[ TransformationComp_Rotation ] ).second, start, stop ); + } + + KeyFrameListList joined; + joined.insert( joined.end(), scaling.begin(), scaling.end() ); + joined.insert( joined.end(), translation.begin(), translation.end() ); + joined.insert( joined.end(), rotation.begin(), rotation.end() ); + + const KeyTimeList& times = GetKeyTimeList( joined ); + + aiQuatKey* out_quat = new aiQuatKey[ times.size() ]; + aiVectorKey* out_scale = new aiVectorKey[ times.size() ]; + aiVectorKey* out_translation = new aiVectorKey[ times.size() ]; + + if ( times.size() ) + { + ConvertTransformOrder_TRStoSRT( out_quat, out_scale, out_translation, + scaling, + translation, + rotation, + times, + max_time, + min_time, + target.RotationOrder(), + def_scale, + def_translate, + def_rot ); + } + + // XXX remove duplicates / redundant keys which this operation did + // likely produce if not all three channels were equally dense. + + na->mNumScalingKeys = static_cast( times.size() ); + na->mNumRotationKeys = na->mNumScalingKeys; + na->mNumPositionKeys = na->mNumScalingKeys; + + na->mScalingKeys = out_scale; + na->mRotationKeys = out_quat; + na->mPositionKeys = out_translation; + } + else { + + // if a particular transformation is not given, grab it from + // the corresponding node to meet the semantics of aiNodeAnim, + // which requires all of rotation, scaling and translation + // to be set. + if ( chain[ TransformationComp_Scaling ] != iter_end ) { + ConvertScaleKeys( na, ( *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& 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; + + 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() ); + + //get values within the start/stop time window + boost::shared_ptr Keys( new KeyTimeList() ); + boost::shared_ptr 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( boost::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; + BOOST_FOREACH( const KeyFrameList& kfl, inputs ) { + estimate = std::max( estimate, kfl.get<0>()->size() ); + } + + keys.reserve( estimate ); + + std::vector next_pos; + next_pos.resize( inputs.size(), 0 ); + + const size_t count = inputs.size(); + while ( true ) { + + int64_t min_tick = std::numeric_limits::max(); + for ( size_t i = 0; i < count; ++i ) { + const KeyFrameList& kfl = inputs[ i ]; + + if ( 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::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 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 next_pos; + const size_t count = inputs.size(); + + next_pos.resize( inputs.size(), 0 ); + + BOOST_FOREACH( 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 = 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( ( time - timeA ) / ( timeB - timeA ) ); + const float interpValue = static_cast( valueA + ( valueB - valueA ) * factor ); + + 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 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 ); + + boost::scoped_array 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& nodes, const LayerMap& /*layers*/, + int64_t start, int64_t stop, + double& maxTime, + double& minTime ) +{ + ai_assert( nodes.size() ); + + // XXX for now, assume scale should be blended geometrically (i.e. two + // layers should be multiplied with each other). There is a FBX + // property in the layer to specify the behaviour, though. + + const KeyFrameListList& inputs = GetKeyframeList( nodes, start, stop ); + const KeyTimeList& keys = GetKeyTimeList( inputs ); + + na->mNumScalingKeys = static_cast( keys.size() ); + na->mScalingKeys = new aiVectorKey[ keys.size() ]; + if ( keys.size() > 0 ) + InterpolateKeys( na->mScalingKeys, keys, inputs, aiVector3D( 1.0f, 1.0f, 1.0f ), maxTime, minTime ); +} + + +void Converter::ConvertTranslationKeys( aiNodeAnim* na, const std::vector& nodes, + const LayerMap& /*layers*/, + int64_t start, int64_t stop, + double& maxTime, + double& minTime ) +{ + ai_assert( nodes.size() ); + + // XXX see notes in ConvertScaleKeys() + const KeyFrameListList& inputs = GetKeyframeList( nodes, start, stop ); + const KeyTimeList& keys = GetKeyTimeList( inputs ); + + na->mNumPositionKeys = static_cast( keys.size() ); + na->mPositionKeys = new aiVectorKey[ keys.size() ]; + if ( keys.size() > 0 ) + InterpolateKeys( na->mPositionKeys, keys, inputs, aiVector3D( 0.0f, 0.0f, 0.0f ), maxTime, minTime ); +} + + +void Converter::ConvertRotationKeys( aiNodeAnim* na, const std::vector& nodes, + const LayerMap& /*layers*/, + int64_t start, int64_t stop, + double& maxTime, + double& minTime, + Model::RotOrder order ) +{ + ai_assert( nodes.size() ); + + // XXX see notes in ConvertScaleKeys() + const std::vector< KeyFrameList >& inputs = GetKeyframeList( nodes, start, stop ); + const KeyTimeList& keys = GetKeyTimeList( inputs ); + + na->mNumRotationKeys = static_cast( keys.size() ); + na->mRotationKeys = new aiQuatKey[ keys.size() ]; + if ( keys.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( meshes.size() ); + + std::swap_ranges( meshes.begin(), meshes.end(), out->mMeshes ); + } + + if ( materials.size() ) { + out->mMaterials = new aiMaterial*[ materials.size() ](); + out->mNumMaterials = static_cast( materials.size() ); + + std::swap_ranges( materials.begin(), materials.end(), out->mMaterials ); + } + + if ( animations.size() ) { + out->mAnimations = new aiAnimation*[ animations.size() ](); + out->mNumAnimations = static_cast( animations.size() ); + + std::swap_ranges( animations.begin(), animations.end(), out->mAnimations ); + } + + if ( lights.size() ) { + out->mLights = new aiLight*[ lights.size() ](); + out->mNumLights = static_cast( lights.size() ); + + std::swap_ranges( lights.begin(), lights.end(), out->mLights ); + } + + if ( cameras.size() ) { + out->mCameras = new aiCamera*[ cameras.size() ](); + out->mNumCameras = static_cast( cameras.size() ); + + std::swap_ranges( cameras.begin(), cameras.end(), out->mCameras ); + } + + if ( textures.size() ) { + out->mTextures = new aiTexture*[ textures.size() ](); + out->mNumTextures = static_cast( textures.size() ); + + std::swap_ranges( textures.begin(), textures.end(), out->mTextures ); + } +} + //} // !anon // ------------------------------------------------------------------------------------------------