diff --git a/code/glTFAsset.h b/code/glTFAsset.h index 247dfd148..2e49b0d8f 100644 --- a/code/glTFAsset.h +++ b/code/glTFAsset.h @@ -811,6 +811,8 @@ namespace glTF Ref skin; //!< The ID of the skin referenced by this node. std::string jointName; //!< Name used when this node is a joint in a skin. + Ref parent; //!< This is not part of the glTF specification. Used as a helper. + Node() {} void Read(Value& obj, Asset& r); }; diff --git a/code/glTFExporter.cpp b/code/glTFExporter.cpp index 2db050b81..07d8f8c23 100644 --- a/code/glTFExporter.cpp +++ b/code/glTFExporter.cpp @@ -132,7 +132,7 @@ glTFExporter::glTFExporter(const char* filename, IOSystem* pIOSystem, const aiSc ExportMaterials(); if (mScene->mRootNode) { - ExportNode(mScene->mRootNode); + ExportNodeHierarchy(mScene->mRootNode); } ExportMeshes(); @@ -164,6 +164,14 @@ static void CopyValue(const aiMatrix4x4& v, glTF::mat4& o) o[12] = v.a4; o[13] = v.b4; o[14] = v.c4; o[15] = v.d4; } +static void CopyValue(const aiMatrix4x4& v, aiMatrix4x4& o) +{ + o.a1 = v.a1; o.a2 = v.a2; o.a3 = v.a3; o.a4 = v.a4; + o.b1 = v.b1; o.b2 = v.b2; o.b3 = v.b3; o.b4 = v.b4; + o.c1 = v.c1; o.c2 = v.c2; o.c3 = v.c3; o.c4 = v.c4; + o.d1 = v.d1; o.d2 = v.d2; o.d3 = v.d3; o.d4 = v.d4; +} + static void IdentityMatrix4(glTF::mat4& o) { o[ 0] = 1; o[ 1] = 0; o[ 2] = 0; o[ 3] = 0; @@ -390,50 +398,37 @@ bool FindMeshNode(Ref& nodeIn, Ref& meshNode, std::string meshID) /* * Find the root joint of the skeleton. + * Starts will any joint node and traces up the tree, + * until a parent is found that does not have a jointName. + * Returns the first parent Ref found that does not have a jointName. */ Ref FindSkeletonRootJoint(Ref& skinRef) { - Ref candidateNodeRef; - Ref testNodeRef; + Ref startNodeRef; + Ref parentNodeRef; - for (unsigned int i = 0; i < skinRef->jointNames.size(); ++i) { - candidateNodeRef = skinRef->jointNames[i]; - bool candidateIsRoot = true; + // Arbitrarily use the first joint to start the search. + startNodeRef = skinRef->jointNames[0]; + parentNodeRef = skinRef->jointNames[0]; - for (unsigned int j = 0; j < skinRef->jointNames.size(); ++j) { - if (i == j) continue; + do { + startNodeRef = parentNodeRef; + parentNodeRef = startNodeRef->parent; + } while (!parentNodeRef->jointName.empty()); - testNodeRef = skinRef->jointNames[j]; - for (unsigned int k = 0; k < testNodeRef->children.size(); ++k) { - std::string childNodeRefID = testNodeRef->children[k]->id; - - if (childNodeRefID.compare(candidateNodeRef->id) == 0) { - candidateIsRoot = false; - } - } - } - - if(candidateIsRoot == true) { - return candidateNodeRef; - } - } - - return candidateNodeRef; + return parentNodeRef; } -void ExportSkin(Asset& mAsset, const aiMesh* aim, Ref& meshRef, Ref& bufferRef) +void ExportSkin(Asset& mAsset, const aiMesh* aim, Ref& meshRef, Ref& bufferRef, Ref& skinRef, std::vector& inverseBindMatricesData) { - std::string skinName = aim->mName.C_Str(); - skinName = mAsset.FindUniqueID(skinName, "skin"); - Ref skinRef = mAsset.skins.Create(skinName); - skinRef->name = skinName; - - mat4* inverseBindMatricesData = new mat4[aim->mNumBones]; + if (aim->mNumBones < 1) { + return; + } // Store the vertex joint and weight data. vec4* vertexJointData = new vec4[aim->mNumVertices]; vec4* vertexWeightData = new vec4[aim->mNumVertices]; - unsigned int* jointsPerVertex = new unsigned int[aim->mNumVertices]; + int* jointsPerVertex = new int[aim->mNumVertices]; for (size_t i = 0; i < aim->mNumVertices; ++i) { jointsPerVertex[i] = 0; for (size_t j = 0; j < 4; ++j) { @@ -448,49 +443,49 @@ void ExportSkin(Asset& mAsset, const aiMesh* aim, Ref& meshRef, RefmName =====> skinRef->jointNames // Find the node with id = mName. Ref nodeRef = mAsset.nodes.Get(aib->mName.C_Str()); - nodeRef->jointName = "joint_" + to_string(idx_bone); - skinRef->jointNames.push_back(nodeRef); + nodeRef->jointName = nodeRef->id; - // Identity Matrix =====> skinRef->bindShapeMatrix - // Temporary. Hard-coded identity matrix here - skinRef->bindShapeMatrix.isPresent = true; - IdentityMatrix4(skinRef->bindShapeMatrix.value); + unsigned int jointNamesIndex; + bool addJointToJointNames = true; + for (int idx_joint = 0; idx_joint < skinRef->jointNames.size(); ++idx_joint) { + if (skinRef->jointNames[idx_joint]->jointName.compare(nodeRef->jointName) == 0) { + addJointToJointNames = false; + jointNamesIndex = idx_joint; + } + } - // aib->mOffsetMatrix =====> skinRef->inverseBindMatrices - CopyValue(aib->mOffsetMatrix, inverseBindMatricesData[idx_bone]); + if (addJointToJointNames) { + skinRef->jointNames.push_back(nodeRef); + + // aib->mOffsetMatrix =====> skinRef->inverseBindMatrices + aiMatrix4x4 tmpMatrix4; + CopyValue(aib->mOffsetMatrix, tmpMatrix4); + inverseBindMatricesData.push_back(tmpMatrix4); + jointNamesIndex = inverseBindMatricesData.size() - 1; + } // aib->mWeights =====> vertexWeightData for (unsigned int idx_weights = 0; idx_weights < aib->mNumWeights; ++idx_weights) { - aiVertexWeight tmpVertWeight = aib->mWeights[idx_weights]; - vertexJointData[tmpVertWeight.mVertexId][jointsPerVertex[tmpVertWeight.mVertexId]] = idx_bone; - vertexWeightData[tmpVertWeight.mVertexId][jointsPerVertex[tmpVertWeight.mVertexId]] = tmpVertWeight.mWeight; + unsigned int vertexId = aib->mWeights[idx_weights].mVertexId; + float vertWeight = aib->mWeights[idx_weights].mWeight; - jointsPerVertex[tmpVertWeight.mVertexId] += 1; + // A vertex can only have at most four joint weights. Ignore all others. + if (jointsPerVertex[vertexId] > 3) { continue; } + + vertexJointData[vertexId][jointsPerVertex[vertexId]] = jointNamesIndex; + vertexWeightData[vertexId][jointsPerVertex[vertexId]] = vertWeight; + + jointsPerVertex[vertexId] += 1; } } // End: for-loop mNumMeshes - // Create the Accessor for skinRef->inverseBindMatrices - Ref invBindMatrixAccessor = ExportData(mAsset, skinName, bufferRef, aim->mNumBones, inverseBindMatricesData, AttribType::MAT4, AttribType::MAT4, ComponentType_FLOAT); - if (invBindMatrixAccessor) skinRef->inverseBindMatrices = invBindMatrixAccessor; - - Mesh::Primitive& p = meshRef->primitives.back(); - Ref vertexJointAccessor = ExportData(mAsset, skinName, bufferRef, aim->mNumVertices, vertexJointData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); + Ref vertexJointAccessor = ExportData(mAsset, skinRef->id, bufferRef, aim->mNumVertices, vertexJointData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); if (vertexJointAccessor) p.attributes.joint.push_back(vertexJointAccessor); - Ref vertexWeightAccessor = ExportData(mAsset, skinName, bufferRef, aim->mNumVertices, vertexWeightData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); + Ref vertexWeightAccessor = ExportData(mAsset, skinRef->id, bufferRef, aim->mNumVertices, vertexWeightData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT); if (vertexWeightAccessor) p.attributes.weight.push_back(vertexWeightAccessor); - - - // Find node that contains this mesh and add "skeletons" and "skin" attributes to that node. - Ref rootNode = mAsset.nodes.Get(unsigned(0)); - Ref meshNode; - FindMeshNode(rootNode, meshNode, meshRef->id); - - Ref rootJoint = FindSkeletonRootJoint(skinRef); - meshNode->skeletons.push_back(rootJoint); - meshNode->skin = skinRef; } void glTFExporter::ExportMeshes() @@ -520,6 +515,26 @@ void glTFExporter::ExportMeshes() b = mAsset->buffers.Create(bufferId); } + //---------------------------------------- + // Initialize variables for the skin + bool createSkin = false; + for (unsigned int idx_mesh = 0; idx_mesh < mScene->mNumMeshes; ++idx_mesh) { + const aiMesh* aim = mScene->mMeshes[idx_mesh]; + if(aim->HasBones()) { + createSkin = true; + break; + } + } + + Ref skinRef; + std::string skinName = mAsset->FindUniqueID("skin", "skin"); + std::vector inverseBindMatricesData; + if(createSkin) { + skinRef = mAsset->skins.Create(skinName); + skinRef->name = skinName; + } + //---------------------------------------- + for (unsigned int idx_mesh = 0; idx_mesh < mScene->mNumMeshes; ++idx_mesh) { const aiMesh* aim = mScene->mMeshes[idx_mesh]; @@ -616,7 +631,7 @@ void glTFExporter::ExportMeshes() /*************** Skins ****************/ if(aim->HasBones()) { - ExportSkin(*mAsset, aim, m, b); + ExportSkin(*mAsset, aim, m, b, skinRef, inverseBindMatricesData); } /****************** Compression ******************/ @@ -711,9 +726,41 @@ void glTFExporter::ExportMeshes() #endif }// if(comp_allow) }// for (unsigned int i = 0; i < mScene->mNumMeshes; ++i) + + //---------------------------------------- + // Finish the skin + // Create the Accessor for skinRef->inverseBindMatrices + if (createSkin) { + mat4* invBindMatrixData = new mat4[inverseBindMatricesData.size()]; + for (int idx_joint = 0; idx_joint < inverseBindMatricesData.size(); ++idx_joint) { + CopyValue(inverseBindMatricesData[idx_joint], invBindMatrixData[idx_joint]); + } + + Ref invBindMatrixAccessor = ExportData(*mAsset, skinName, b, inverseBindMatricesData.size(), invBindMatrixData, AttribType::MAT4, AttribType::MAT4, ComponentType_FLOAT); + if (invBindMatrixAccessor) skinRef->inverseBindMatrices = invBindMatrixAccessor; + + // Identity Matrix =====> skinRef->bindShapeMatrix + // Temporary. Hard-coded identity matrix here + skinRef->bindShapeMatrix.isPresent = true; + IdentityMatrix4(skinRef->bindShapeMatrix.value); + + // Find node that contains this mesh and add "skeletons" and "skin" attributes to that node. + Ref rootNode = mAsset->nodes.Get(unsigned(0)); + Ref meshNode; + std::string meshID = mAsset->meshes.Get(unsigned(0))->id; + FindMeshNode(rootNode, meshNode, meshID); + + Ref rootJoint = FindSkeletonRootJoint(skinRef); + meshNode->skeletons.push_back(rootJoint); + meshNode->skin = skinRef; + } } -unsigned int glTFExporter::ExportNode(const aiNode* n) +/* + * Export the root node of the node hierarchy. + * Calls ExportNode for all children. + */ +unsigned int glTFExporter::ExportNodeHierarchy(const aiNode* n) { Ref node = mAsset->nodes.Create(mAsset->FindUniqueID(n->mName.C_Str(), "node")); @@ -727,7 +774,34 @@ unsigned int glTFExporter::ExportNode(const aiNode* n) } for (unsigned int i = 0; i < n->mNumChildren; ++i) { - unsigned int idx = ExportNode(n->mChildren[i]); + unsigned int idx = ExportNode(n->mChildren[i], node); + node->children.push_back(mAsset->nodes.Get(idx)); + } + + return node.GetIndex(); +} + +/* + * Export node and recursively calls ExportNode for all children. + * Since these nodes are not the root node, we also export the parent Ref + */ +unsigned int glTFExporter::ExportNode(const aiNode* n, Ref& parent) +{ + Ref node = mAsset->nodes.Create(mAsset->FindUniqueID(n->mName.C_Str(), "node")); + + node->parent = parent; + + if (!n->mTransformation.IsIdentity()) { + node->matrix.isPresent = true; + CopyValue(n->mTransformation, node->matrix.value); + } + + for (unsigned int i = 0; i < n->mNumMeshes; ++i) { + node->meshes.push_back(mAsset->meshes.Get(n->mMeshes[i])); + } + + for (unsigned int i = 0; i < n->mNumChildren; ++i) { + unsigned int idx = ExportNode(n->mChildren[i], node); node->children.push_back(mAsset->nodes.Get(idx)); } diff --git a/code/glTFExporter.h b/code/glTFExporter.h index 0f9c169e5..49df9193e 100644 --- a/code/glTFExporter.h +++ b/code/glTFExporter.h @@ -58,8 +58,12 @@ struct aiMaterial; namespace glTF { + template + class Ref; + class Asset; struct TexProperty; + struct Node; } namespace Assimp @@ -98,7 +102,8 @@ namespace Assimp void ExportMetadata(); void ExportMaterials(); void ExportMeshes(); - unsigned int ExportNode(const aiNode* node); + unsigned int ExportNodeHierarchy(const aiNode* n); + unsigned int ExportNode(const aiNode* node, glTF::Ref& parent); void ExportScene(); void ExportAnimations(); };