diff --git a/interface/src/avatar/BlendFace.cpp b/interface/src/avatar/BlendFace.cpp index ce6422dcb4..cd2d3aa59f 100644 --- a/interface/src/avatar/BlendFace.cpp +++ b/interface/src/avatar/BlendFace.cpp @@ -59,6 +59,10 @@ void BlendFace::simulate(float deltaTime) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); if (_meshStates.isEmpty()) { QVector vertices; + foreach (const FBXJoint& joint, geometry.joints) { + JointState state; + _jointStates.append(state); + } foreach (const FBXMesh& mesh, geometry.meshes) { MeshState state; if (mesh.springiness > 0.0f) { @@ -365,5 +369,6 @@ void BlendFace::deleteGeometry() { glDeleteBuffers(1, &id); } _blendedVertexBufferIDs.clear(); + _jointStates.clear(); _meshStates.clear(); } diff --git a/interface/src/avatar/BlendFace.h b/interface/src/avatar/BlendFace.h index 2a954cc249..62b04ca19e 100644 --- a/interface/src/avatar/BlendFace.h +++ b/interface/src/avatar/BlendFace.h @@ -58,6 +58,13 @@ private: QSharedPointer _geometry; + class JointState { + public: + glm::quat rotation; + }; + + QVector _jointStates; + class MeshState { public: QVector worldSpaceVertices; diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index 8091687488..c55872dd1a 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -298,7 +298,7 @@ const char* FACESHIFT_BLENDSHAPES[] = { "" }; -class Transform { +class Model { public: QByteArray name; bool inheritScale; @@ -306,20 +306,20 @@ public: glm::mat4 withoutScale; }; -glm::mat4 getGlobalTransform(const QMultiHash& parentMap, const QHash& localTransforms, +glm::mat4 getGlobalTransform(const QMultiHash& parentMap, const QHash& models, qint64 nodeID, bool forceScale = true) { glm::mat4 globalTransform; bool useScale = true; while (nodeID != 0) { - const Transform& localTransform = localTransforms.value(nodeID); - globalTransform = (useScale ? localTransform.withScale : localTransform.withoutScale) * globalTransform; - useScale = (useScale && localTransform.inheritScale) || forceScale; + const Model& model = models.value(nodeID); + globalTransform = (useScale ? model.withScale : model.withoutScale) * globalTransform; + useScale = (useScale && model.inheritScale) || forceScale; QList parentIDs = parentMap.values(nodeID); nodeID = 0; foreach (qint64 parentID, parentIDs) { - if (localTransforms.contains(parentID)) { + if (models.contains(parentID)) { nodeID = parentID; break; } @@ -354,13 +354,31 @@ public: float shininess; }; +class Cluster { +public: + QVector indices; + QVector weights; + glm::mat4 transformLink; +}; + +void appendModelIDs(qint64 parentID, const QMultiHash& childMap, QHash& models, QVector& modelIDs) { + if (parentID != 0) { + modelIDs.append(parentID); + } + foreach (qint64 childID, childMap.values(parentID)) { + if (models.contains(childID)) { + appendModelIDs(childID, childMap, models, modelIDs); + } + } +} + FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) { QHash meshes; QVector blendshapes; QMultiHash parentMap; QMultiHash childMap; - QHash localTransforms; - QHash transformLinkMatrices; + QHash models; + QHash clusters; QHash textureFilenames; QHash materials; QHash diffuseTextures; @@ -536,7 +554,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) glm::vec3 preRotation, rotation, postRotation; glm::vec3 scale = glm::vec3(1.0f, 1.0f, 1.0f); glm::vec3 scalePivot, rotationPivot; - Transform transform = { name, true }; + Model model = { name, true }; foreach (const FBXNode& subobject, object.children) { if (subobject.name == "Properties70") { foreach (const FBXNode& property, subobject.children) { @@ -577,20 +595,20 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) property.properties.at(6).value()); } else if (property.properties.at(0) == "InheritType") { - transform.inheritScale = property.properties.at(4) != 2; + model.inheritScale = property.properties.at(4) != 2; } } } } } // see FBX documentation, http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/index.html - transform.withoutScale = glm::translate(translation) * glm::translate(rotationPivot) * + model.withoutScale = glm::translate(translation) * glm::translate(rotationPivot) * glm::mat4_cast(glm::quat(glm::radians(preRotation))) * glm::mat4_cast(glm::quat(glm::radians(rotation))) * glm::mat4_cast(glm::quat(glm::radians(postRotation))) * glm::translate(-rotationPivot); - transform.withScale = transform.withoutScale * glm::translate(scalePivot) * glm::scale(scale) * + model.withScale = model.withoutScale * glm::translate(scalePivot) * glm::scale(scale) * glm::translate(-scalePivot); - localTransforms.insert(object.properties.at(0).value(), transform); + models.insert(object.properties.at(0).value(), model); } else if (object.name == "Texture") { foreach (const FBXNode& subobject, object.children) { @@ -628,12 +646,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } else if (object.name == "Deformer") { if (object.properties.at(2) == "Cluster") { + Cluster cluster; foreach (const FBXNode& subobject, object.children) { - if (subobject.name == "TransformLink") { + if (subobject.name == "Indexes") { + cluster.indices = subobject.properties.at(0).value >(); + + } else if (subobject.name == "Weights") { + cluster.weights = subobject.properties.at(0).value >(); + + } else if (subobject.name == "TransformLink") { QVector values = subobject.properties.at(0).value >(); - transformLinkMatrices.insert(object.properties.at(0).value(), createMat4(values)); + cluster.transformLink = createMat4(values); } } + clusters.insert(object.properties.at(0).value(), cluster); + } else if (object.properties.at(2) == "BlendShapeChannel") { QByteArray name = object.properties.at(1).toByteArray(); name = name.left(name.indexOf('\0')); @@ -678,13 +705,24 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } // get offset transform from mapping + FBXGeometry geometry; float offsetScale = mapping.value("scale", 1.0f).toFloat(); - glm::mat4 offset = glm::translate(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(), + geometry.offset = glm::translate(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(), mapping.value("tz").toFloat()) * glm::mat4_cast(glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(), mapping.value("ry").toFloat(), mapping.value("rz").toFloat())))) * glm::scale(offsetScale, offsetScale, offsetScale); - FBXGeometry geometry; + // get the list of models in depth-first traversal order + QVector modelIDs; + appendModelIDs(0, childMap, models, modelIDs); + + // convert the models to joints + foreach (qint64 modelID, modelIDs) { + FBXJoint joint; + joint.parentIndex = modelIDs.indexOf(parentMap.value(modelID)); + geometry.joints.append(joint); + } + QVariantHash springs = mapping.value("spring").toHash(); QVariant defaultSpring = springs.value("default"); for (QHash::iterator it = meshes.begin(); it != meshes.end(); it++) { @@ -692,8 +730,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) // accumulate local transforms qint64 modelID = parentMap.value(it.key()); - mesh.springiness = springs.value(localTransforms.value(modelID).name, defaultSpring).toFloat(); - glm::mat4 modelTransform = getGlobalTransform(parentMap, localTransforms, modelID); + mesh.springiness = springs.value(models.value(modelID).name, defaultSpring).toFloat(); + glm::mat4 modelTransform = getGlobalTransform(parentMap, models, modelID); // look for textures, material properties foreach (qint64 childID, childMap.values(modelID)) { @@ -714,29 +752,68 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } } - // look for a limb pivot + // find the clusters with which the mesh is associated mesh.isEye = false; + QVector clusterIDs; foreach (qint64 childID, childMap.values(it.key())) { foreach (qint64 clusterID, childMap.values(childID)) { - if (!transformLinkMatrices.contains(clusterID)) { + if (!clusters.contains(clusterID)) { continue; } + FBXCluster fbxCluster; + const Cluster& cluster = clusters[clusterID]; + clusterIDs.append(clusterID); + qint64 jointID = childMap.value(clusterID); if (jointID == jointEyeLeftID || jointID == jointEyeRightID) { mesh.isEye = true; } + fbxCluster.jointIndex = modelIDs.indexOf(jointID); + fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform; + mesh.clusters.append(fbxCluster); // see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion // of skinning information in FBX - glm::mat4 jointTransform = offset * getGlobalTransform(parentMap, localTransforms, jointID); - mesh.transform = jointTransform * glm::inverse(transformLinkMatrices.value(clusterID)) * modelTransform; + glm::mat4 jointTransform = geometry.offset * getGlobalTransform(parentMap, models, jointID); + mesh.transform = jointTransform * glm::inverse(cluster.transformLink) * modelTransform; // extract translation component for pivot - glm::mat4 jointTransformScaled = offset * getGlobalTransform(parentMap, localTransforms, jointID, true); + glm::mat4 jointTransformScaled = geometry.offset * getGlobalTransform(parentMap, models, jointID, true); mesh.pivot = glm::vec3(jointTransformScaled[3][0], jointTransformScaled[3][1], jointTransformScaled[3][2]); } } + // if we don't have a skinned joint, parent to the model itself + if (mesh.clusters.isEmpty()) { + FBXCluster cluster; + cluster.jointIndex = modelIDs.indexOf(modelID); + mesh.clusters.append(cluster); + } + + // whether we're skinned depends on how many clusters are attached + if (clusterIDs.size() > 1) { + mesh.clusterIndices.resize(mesh.vertices.size()); + mesh.clusterWeights.resize(mesh.vertices.size()); + for (int i = 0; i < clusterIDs.size(); i++) { + qint64 clusterID = clusterIDs.at(i); + const Cluster& cluster = clusters[clusterID]; + qint64 jointID = childMap.value(clusterID); + for (int j = 0; j < cluster.indices.size(); j++) { + int index = cluster.indices.at(j); + glm::vec4& weights = mesh.clusterWeights[index]; + + // look for an unused slot in the weights vector + for (int k = 0; k < 4; k++) { + if (weights[k] == 0.0f) { + mesh.clusterIndices[index][k] = i; + weights[k] = cluster.weights.at(j); + break; + } + } + } + } + } + // extract spring edges, connections if springy if (mesh.springiness > 0.0f) { QSet > edges; @@ -781,7 +858,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } // extract translation component for neck pivot - glm::mat4 neckTransform = offset * getGlobalTransform(parentMap, localTransforms, jointNeckID, true); + glm::mat4 neckTransform = geometry.offset * getGlobalTransform(parentMap, models, jointNeckID, true); geometry.neckPivot = glm::vec3(neckTransform[3][0], neckTransform[3][1], neckTransform[3][2]); return geometry; diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h index 5c2ffd9ee0..3d8c3d0fa2 100644 --- a/interface/src/renderer/FBXReader.h +++ b/interface/src/renderer/FBXReader.h @@ -37,6 +37,22 @@ public: QVector normals; }; +/// A single joint (transformation node) extracted from an FBX document. +class FBXJoint { +public: + + int parentIndex; + glm::quat rotation; +}; + +/// A single binding to a joint in an FBX document. +class FBXCluster { +public: + + int jointIndex; + glm::mat4 inverseBindMatrix; +}; + /// A single mesh (with optional blendshapes) extracted from an FBX document. class FBXMesh { public: @@ -46,6 +62,10 @@ public: QVector vertices; QVector normals; QVector texCoords; + QVector clusterIndices; + QVector clusterWeights; + + QVector clusters; glm::vec3 pivot; glm::mat4 transform; @@ -70,8 +90,12 @@ public: class FBXGeometry { public: + QVector joints; + QVector meshes; + glm::mat4 offset; + glm::vec3 neckPivot; };