From 666f2776d7525766d18c29362757353c77b1d5e1 Mon Sep 17 00:00:00 2001 From: Alexander Gessler Date: Thu, 26 Jul 2012 22:53:31 +0200 Subject: [PATCH] - fbx: convert fbx deformers to assimp bones. --- code/FBXConverter.cpp | 127 +++++++++++++++++++++++++++++++++++++++ code/FBXDeformer.cpp | 8 +++ code/FBXDocument.h | 55 +++++++++++++++-- code/FBXDocumentUtil.h | 4 +- code/FBXImportSettings.h | 7 ++- 5 files changed, 193 insertions(+), 8 deletions(-) diff --git a/code/FBXConverter.cpp b/code/FBXConverter.cpp index fae14004c..dc2e7a7d7 100644 --- a/code/FBXConverter.cpp +++ b/code/FBXConverter.cpp @@ -414,6 +414,10 @@ private: ConvertMaterialForMesh(out_mesh,model,mesh,mindices[0]); } + if(doc.Settings().readWeights && mesh.DeformerSkin() != NULL) { + ConvertWeights(out_mesh, model, mesh, std::numeric_limits::max()); + } + return static_cast(meshes.size() - 1); } @@ -594,6 +598,129 @@ private: } + // ------------------------------------------------------------------------------------------------ + void ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo, unsigned int materialIndex) + { + 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 == std::numeric_limits::max(); + + try { + + BOOST_FOREACH(const Cluster* cluster, sk.Clusters()) { + ai_assert(cluster); + + const WeightIndexArray& indices = cluster->GetIndices(); + const WeightArray& weights = cluster->GetWeights(); + + 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; + const unsigned int* const out_idx = geo.ToOutputVertexIndex(index, count); + + index_out_indices.push_back(no_index_sentinel); + + for(unsigned int i = 0; i < count; ++i) { + const unsigned int out_face_idx = geo.FaceForVertexIndex(out_idx[i]); + ai_assert(out_face_idx <= mats.size()); + + if (no_mat_check || mats[out_face_idx] == materialIndex) { + + + if (index_out_indices.back() == no_index_sentinel) { + index_out_indices.back() = out_indices.size(); + } + + out_indices.push_back(out_idx[i]); + + ++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, *cluster, out_indices, index_out_indices, count_out_indices); + } + } + } + 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 ConvertCluster(std::vector& bones, const Cluster& cl, + std::vector& out_indices, + std::vector& index_out_indices, + std::vector& count_out_indices + ) + { + aiBone* const bone = new aiBone(); + bones.push_back(bone); + + bone->mName = FixNodeName(cl.TargetNode()->Name()); + + 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 ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo, unsigned int materialIndex) { diff --git a/code/FBXDeformer.cpp b/code/FBXDeformer.cpp index 561e9ebf4..7f66f1520 100644 --- a/code/FBXDeformer.cpp +++ b/code/FBXDeformer.cpp @@ -93,6 +93,14 @@ Cluster::Cluster(uint64_t id, const Element& element, const Document& doc, const ReadVectorDataArray(indices,Indexes); ReadVectorDataArray(weights,Weights); + if(indices.size() != weights.size()) { + DOMError("sizes of index and weight array don't match up",&element); + } + + if(!indices.size()) { + DOMWarning("encountered empty deformer",&element); + } + // read assigned node const std::vector& conns = doc.GetConnectionsByDestinationSequenced(ID(),"Deformer"); BOOST_FOREACH(const Connection* con, conns) { diff --git a/code/FBXDocument.h b/code/FBXDocument.h index 1d3bfc1ae..06e96fcab 100644 --- a/code/FBXDocument.h +++ b/code/FBXDocument.h @@ -387,6 +387,9 @@ private: }; +typedef std::vector MatIndexArray; + + /** DOM class for FBX geometry of type "Mesh"*/ class MeshGeometry : public Geometry { @@ -451,10 +454,51 @@ public: /** Get per-face-vertex material assignments */ - const std::vector& GetMaterialIndices() const { + const MatIndexArray& GetMaterialIndices() const { return materials; } + + /** Convert from a fbx file vertex index (for example from a #Cluster weight) or NULL + * if the vertex index is not valid. */ + const unsigned int* ToOutputVertexIndex(unsigned int in_index, unsigned int& count) const { + if(in_index >= mapping_counts.size()) { + return NULL; + } + + ai_assert(mapping_counts.size() == mapping_offsets.size()); + count = mapping_counts[in_index]; + + ai_assert(count != 0); + ai_assert(mapping_offsets[in_index] + count <= mappings.size()); + + return &mappings[mapping_offsets[in_index]]; + } + + + /** Determine the face to which a particular output vertex index belongs. + * This mapping is always unique. */ + unsigned int FaceForVertexIndex(unsigned int in_index) const { + ai_assert(in_index < vertices.size()); + + // in the current conversion pattern this will only be needed if + // weights are present, so no need to always pre-compute this table + if (facesVertexStartIndices.empty()) { + facesVertexStartIndices.resize(faces.size()); + + std::partial_sum(faces.begin(), faces.end(), facesVertexStartIndices.begin()); + } + + ai_assert(facesVertexStartIndices.size() == faces.size()); + const std::vector::const_iterator it = std::upper_bound( + facesVertexStartIndices.begin(), + facesVertexStartIndices.end(), + in_index + ); + + return *(it - 1); + } + public: private: @@ -490,9 +534,10 @@ private: private: // cached data arrays - std::vector materials; + MatIndexArray materials; std::vector vertices; std::vector faces; + mutable std::vector facesVertexStartIndices; std::vector tangents; std::vector binormals; std::vector normals; @@ -680,7 +725,7 @@ private: boost::shared_ptr props; }; -typedef std::vector WeightList; +typedef std::vector WeightArray; typedef std::vector WeightIndexArray; @@ -695,7 +740,7 @@ public: public: /** get the list of deformer weights associated with this cluster */ - const WeightList& GetWeights() const { + const WeightArray& GetWeights() const { return weights; } @@ -720,7 +765,7 @@ public: private: - WeightList weights; + WeightArray weights; WeightIndexArray indices; aiMatrix4x4 transform; diff --git a/code/FBXDocumentUtil.h b/code/FBXDocumentUtil.h index 8e395bfca..f563d9876 100644 --- a/code/FBXDocumentUtil.h +++ b/code/FBXDocumentUtil.h @@ -119,14 +119,14 @@ inline const T* ProcessSimpleConnection(const Connection& con, const Element& element, const char** propNameOut = NULL) { - if (is_object_property_conn && con.PropertyName().length()) { + if (is_object_property_conn && !con.PropertyName().length()) { DOMWarning("expected incoming " + std::string(name) + " link to be an object-object connection, ignoring", &element ); return NULL; } - else if (!is_object_property_conn && !con.PropertyName().length()) { + else if (!is_object_property_conn && con.PropertyName().length()) { DOMWarning("expected incoming " + std::string(name) + " link to be an object-property connection, ignoring", &element diff --git a/code/FBXImportSettings.h b/code/FBXImportSettings.h index 794e2183d..2b60671a3 100644 --- a/code/FBXImportSettings.h +++ b/code/FBXImportSettings.h @@ -58,6 +58,7 @@ struct ImportSettings , readLights(true) , readAnimations(true) , strictMode(true) + , readWeights(true) {} @@ -96,8 +97,12 @@ struct ImportSettings bool readLights; /** import animations (i.e. animation curves, the node - * skeleton is always imported)? Default value is true. */ + * skeleton is always imported). Default value is true. */ bool readAnimations; + + /** read bones (vertex weights and deform info). + * Default value is true. */ + bool readWeights; };