diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index b8d4e53b65..1699722215 100755 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -711,19 +711,19 @@ glm::mat4 GLTFSerializer::getModelTransform(const GLTFNode& node) { node.matrix[12], node.matrix[13], node.matrix[14], node.matrix[15]); } else { - if (node.defined["rotation"] && node.rotation.size() == 4) { - //quat(x,y,z,w) to quat(w,x,y,z) - glm::quat rotquat = glm::quat(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]); - tmat = glm::mat4_cast(rotquat) * tmat; - } - if (node.defined["scale"] && node.scale.size() == 3) { glm::vec3 scale = glm::vec3(node.scale[0], node.scale[1], node.scale[2]); glm::mat4 s = glm::mat4(1.0); s = glm::scale(s, scale); tmat = s * tmat; } - + + if (node.defined["rotation"] && node.rotation.size() == 4) { + //quat(x,y,z,w) to quat(w,x,y,z) + glm::quat rotquat = glm::quat(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]); + tmat = glm::mat4_cast(rotquat) * tmat; + } + if (node.defined["translation"] && node.translation.size() == 3) { glm::vec3 trans = glm::vec3(node.translation[0], node.translation[1], node.translation[2]); glm::mat4 t = glm::mat4(1.0); @@ -734,15 +734,54 @@ glm::mat4 GLTFSerializer::getModelTransform(const GLTFNode& node) { return tmat; } +void GLTFSerializer::getSkinInverseBindMatrices(std::vector>& inverseBindMatrixValues) { + for (auto &skin : _file.skins) { + GLTFAccessor& indicesAccessor = _file.accessors[skin.inverseBindMatrices]; + GLTFBufferView& indicesBufferview = _file.bufferviews[indicesAccessor.bufferView]; + GLTFBuffer& indicesBuffer = _file.buffers[indicesBufferview.buffer]; + int accBoffset = indicesAccessor.defined["byteOffset"] ? indicesAccessor.byteOffset : 0; + QVector matrices; + addArrayOfType(indicesBuffer.blob, + indicesBufferview.byteOffset + accBoffset, + indicesAccessor.count, + matrices, + indicesAccessor.type, + indicesAccessor.componentType); + inverseBindMatrixValues.push_back(matrices.toStdVector()); + } +} + +void GLTFSerializer::getNodeQueueByDepthFirstChildren(std::vector& children, int stride, std::vector& result) { + int startingIndex = 0; + int finalIndex = (int)children.size(); + if (stride == -1) { + startingIndex = (int)children.size() - 1; + finalIndex = -1; + } + for (int index = startingIndex; index != finalIndex; index += stride) { + int c = children[index]; + result.push_back(c); + std::vector nested = _file.nodes[c].children.toStdVector(); + if (nested.size() != 0) { + std::sort(nested.begin(), nested.end()); + for (int r : nested) { + if (result.end() == std::find(result.begin(), result.end(), r)) { + getNodeQueueByDepthFirstChildren(nested, stride, result); + } + } + } + } +} + + bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { + int numNodes = _file.nodes.size(); //Build dependencies - QVector> nodeDependencies(_file.nodes.size()); + QVector> nodeDependencies(numNodes); int nodecount = 0; - bool hasChildren = false; foreach(auto &node, _file.nodes) { //nodes_transforms.push_back(getModelTransform(node)); - hasChildren |= !node.children.isEmpty(); foreach(int child, node.children) nodeDependencies[child].push_back(nodecount); nodecount++; } @@ -764,26 +803,98 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { nodecount++; } - - HFMJoint joint; - joint.isSkeletonJoint = true; - joint.bindTransformFoundInCluster = false; - joint.distanceToParent = 0; - joint.parentIndex = -1; - hfmModel.joints.resize(_file.nodes.size()); - hfmModel.jointIndices["x"] = _file.nodes.size(); - int jointInd = 0; - for (auto& node : _file.nodes) { - int size = node.transforms.size(); - if (hasChildren) { size--; } - joint.preTransform = glm::mat4(1); - for (int i = 0; i < size; i++) { - joint.preTransform = node.transforms[i] * joint.preTransform; - } - joint.name = node.name; - hfmModel.joints[jointInd] = joint; - jointInd++; + + + // initialize order in which nodes will be parsed + std::vector nodeQueue; + nodeQueue.reserve(numNodes); + int rootNode = 0; + int finalNode = numNodes; + if (!_file.scenes[_file.scene].nodes.contains(0)) { + rootNode = numNodes - 1; + finalNode = -1; } + bool rootAtStartOfList = rootNode < finalNode; + int nodeListStride = 1; + if (!rootAtStartOfList) { nodeListStride = -1; } + + QVector initialSceneNodes = _file.scenes[_file.scene].nodes; + std::sort(initialSceneNodes.begin(), initialSceneNodes.end()); + int sceneRootNode = 0; + int sceneFinalNode = initialSceneNodes.size(); + if (!rootAtStartOfList) { + sceneRootNode = initialSceneNodes.size() - 1; + sceneFinalNode = -1; + } + for (int index = sceneRootNode; index != sceneFinalNode; index += nodeListStride) { + int i = initialSceneNodes[index]; + nodeQueue.push_back(i); + std::vector children = _file.nodes[i].children.toStdVector(); + std::sort(children.begin(), children.end()); + getNodeQueueByDepthFirstChildren(children, nodeListStride, nodeQueue); + } + + // Build joints + HFMJoint joint; + joint.distanceToParent = 0; + hfmModel.jointIndices["x"] = numNodes; + hfmModel.hasSkeletonJoints = false; + + for (int nodeIndex : nodeQueue) { + auto& node = _file.nodes[nodeIndex]; + + joint.parentIndex = -1; + if (!_file.scenes[_file.scene].nodes.contains(nodeIndex)) { + joint.parentIndex = std::distance(nodeQueue.begin(), std::find(nodeQueue.begin(), nodeQueue.end(), nodeDependencies[nodeIndex][0])); + } + joint.transform = node.transforms.first(); + joint.translation = extractTranslation(joint.transform); + joint.rotation = glmExtractRotation(joint.transform); + glm::vec3 scale = extractScale(joint.transform); + joint.postTransform = glm::scale(glm::mat4(), scale); + + joint.name = node.name; + joint.isSkeletonJoint = false; + hfmModel.joints.push_back(joint); + } + + + // Build skeleton + std::vector jointInverseBindTransforms; + jointInverseBindTransforms.resize(numNodes); + if (!_file.skins.isEmpty()) { + std::vector> inverseBindValues; + getSkinInverseBindMatrices(inverseBindValues); + + int jointIndex = finalNode; + while (jointIndex != rootNode) { + rootAtStartOfList ? jointIndex-- : jointIndex++; + int jOffset = nodeQueue[jointIndex]; + auto joint = hfmModel.joints[jointIndex]; + + hfmModel.hasSkeletonJoints = true; + for (int s = 0; s < _file.skins.size(); s++) { + auto skin = _file.skins[s]; + joint.isSkeletonJoint = skin.joints.contains(jOffset); + + if (joint.isSkeletonJoint) { + std::vector value = inverseBindValues[s]; + int matrixCount = 16 * skin.joints.indexOf(jOffset); + jointInverseBindTransforms[jointIndex] = + glm::mat4(value[matrixCount], value[matrixCount + 1], value[matrixCount + 2], value[matrixCount + 3], + value[matrixCount + 4], value[matrixCount + 5], value[matrixCount + 6], value[matrixCount + 7], + value[matrixCount + 8], value[matrixCount + 9], value[matrixCount + 10], value[matrixCount + 11], + value[matrixCount + 12], value[matrixCount + 13], value[matrixCount + 14], value[matrixCount + 15]); + } else { + jointInverseBindTransforms[jointIndex] = glm::mat4(); + } + glm::vec3 bindTranslation = extractTranslation(hfmModel.offset * glm::inverse(jointInverseBindTransforms[jointIndex])); + hfmModel.bindExtents.addPoint(bindTranslation); + } + hfmModel.joints[jointIndex] = joint; + } + } + //Build materials QVector materialIDs; @@ -803,23 +914,39 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { } - - nodecount = 0; // Build meshes - foreach(auto &node, _file.nodes) { + nodecount = 0; + for (int nodeIndex = rootNode; nodeIndex != finalNode; nodeIndex += nodeListStride) { + auto& node = _file.nodes[nodeIndex]; if (node.defined["mesh"]) { qCDebug(modelformat) << "node_transforms" << node.transforms; foreach(auto &primitive, _file.meshes[node.mesh].primitives) { hfmModel.meshes.append(HFMMesh()); HFMMesh& mesh = hfmModel.meshes[hfmModel.meshes.size() - 1]; - HFMCluster cluster; - cluster.jointIndex = nodecount; - cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); - mesh.clusters.append(cluster); + if (!hfmModel.hasSkeletonJoints) { + HFMCluster cluster; + cluster.jointIndex = nodecount; + cluster.inverseBindMatrix = glm::mat4(); + cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix); + mesh.clusters.append(cluster); + } else { + for (int j = rootNode; j != finalNode; j += nodeListStride) { + HFMCluster cluster; + cluster.jointIndex = j; + cluster.inverseBindMatrix = jointInverseBindTransforms[j]; + cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix); + mesh.clusters.append(cluster); + } + } + HFMCluster root; + root.jointIndex = rootNode; + if (root.jointIndex == -1) { + root.jointIndex = 0; + } + root.inverseBindMatrix = jointInverseBindTransforms[root.jointIndex]; + root.inverseBindTransform = Transform(root.inverseBindMatrix); + mesh.clusters.append(root); HFMMeshPart part = HFMMeshPart(); @@ -848,6 +975,8 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { } QList keys = primitive.attributes.values.keys(); + QVector clusterJoints; + QVector clusterWeights; foreach(auto &key, keys) { int accessorIdx = primitive.attributes.values[key]; @@ -949,8 +1078,69 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { for (int n = 0; n < texcoords.size(); n = n + 2) { mesh.texCoords1.push_back(glm::vec2(texcoords[n], texcoords[n + 1])); } + } else if (key == "JOINTS_0") { + QVector joints; + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + joints, + accessor.type, + accessor.componentType); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF JOINTS_0 data for model " << _url; + continue; + } + for (int n = 0; n < joints.size(); n++) { + clusterJoints.push_back(joints[n]); + } + } else if (key == "WEIGHTS_0") { + QVector weights; + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + weights, + accessor.type, + accessor.componentType); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF WEIGHTS_0 data for model " << _url; + continue; + } + for (int n = 0; n < weights.size(); n++) { + clusterWeights.push_back(weights[n]); + } + } + } + + // adapted from FBXSerializer.cpp + if (hfmModel.hasSkeletonJoints) { + int numClusterIndices = clusterJoints.size(); + const int WEIGHTS_PER_VERTEX = 4; + const float ALMOST_HALF = 0.499f; + int numVertices = mesh.vertices.size(); + mesh.clusterIndices.fill(mesh.clusters.size() - 1, numClusterIndices); + mesh.clusterWeights.fill(0, numClusterIndices); + + for (int c = 0; c < clusterJoints.size(); c++) { + mesh.clusterIndices[c] = _file.skins[node.skin].joints[clusterJoints[c]]; } + // normalize and compress to 16-bits + for (int i = 0; i < numVertices; ++i) { + int j = i * WEIGHTS_PER_VERTEX; + + float totalWeight = 0.0f; + for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { + totalWeight += clusterWeights[k]; + } + if (totalWeight > 0.0f) { + float weightScalingFactor = (float)(UINT16_MAX) / totalWeight; + for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { + mesh.clusterWeights[k] = (uint16_t)(weightScalingFactor * clusterWeights[k] + ALMOST_HALF); + } + } else { + mesh.clusterWeights[j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF); + } + } } if (primitive.defined["material"]) { @@ -959,8 +1149,8 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { mesh.parts.push_back(part); // populate the texture coordinates if they don't exist - if (mesh.texCoords.size() == 0) { - for (int i = 0; i < part.triangleIndices.size(); i++) mesh.texCoords.push_back(glm::vec2(0.0, 1.0)); + if (mesh.texCoords.size() == 0 && !hfmModel.hasSkeletonJoints) { + for (int i = 0; i < part.triangleIndices.size(); i++) { mesh.texCoords.push_back(glm::vec2(0.0, 1.0)); } } mesh.meshExtents.reset(); foreach(const glm::vec3& vertex, mesh.vertices) { diff --git a/libraries/fbx/src/GLTFSerializer.h b/libraries/fbx/src/GLTFSerializer.h index 05dc526f79..d9c477bd99 100755 --- a/libraries/fbx/src/GLTFSerializer.h +++ b/libraries/fbx/src/GLTFSerializer.h @@ -712,6 +712,8 @@ private: hifi::ByteArray _glbBinary; glm::mat4 getModelTransform(const GLTFNode& node); + void getSkinInverseBindMatrices(std::vector>& inverseBindMatrixValues); + void getNodeQueueByDepthFirstChildren(std::vector& children, int stride, std::vector& result); bool buildGeometry(HFMModel& hfmModel, const hifi::URL& url); bool parseGLTF(const hifi::ByteArray& data);