From d00c26ca084ac315ba74a48ab72711e72c6d560a Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Sat, 27 Oct 2018 10:29:51 +0800 Subject: [PATCH] Export motions to glTF(.glb) Each motion will be exported as an animtion of glTF --- src/documentwindow.cpp | 21 +++--- src/glbfile.cpp | 144 +++++++++++++++++++++++++++++++++++++++-- src/glbfile.h | 3 +- 3 files changed, 149 insertions(+), 19 deletions(-) diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index 10935d99..2e6927a6 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -1275,21 +1275,16 @@ void SkeletonDocumentWindow::exportGlbResult() } QApplication::setOverrideCursor(Qt::WaitCursor); Outcome skeletonResult = m_document->currentPostProcessedOutcome(); + std::vector>>> exportMotions; + for (const auto &motionId: m_document->motionIdList) { + const Motion *motion = m_document->findMotion(motionId); + if (nullptr == motion) + continue; + exportMotions.push_back({motion->name, motion->jointNodeTrees}); + } GlbFileWriter glbFileWriter(skeletonResult, m_document->resultRigBones(), m_document->resultRigWeights(), filename, - m_document->textureImage); + m_document->textureImage, exportMotions.empty() ? nullptr : &exportMotions); glbFileWriter.save(); - /* - QFileInfo nameInfo(filename); - QString borderFilename = nameInfo.path() + QDir::separator() + nameInfo.completeBaseName() + "-BORDER.png"; - if (m_document->textureBorderImage) - m_document->textureBorderImage->save(borderFilename); - QString ambientOcclusionFilename = nameInfo.path() + QDir::separator() + nameInfo.completeBaseName() + "-AMBIENT-OCCLUSION.png"; - if (m_document->textureAmbientOcclusionImage) - m_document->textureAmbientOcclusionImage->save(ambientOcclusionFilename); - QString colorFilename = nameInfo.path() + QDir::separator() + nameInfo.completeBaseName() + "-COLOR.png"; - if (m_document->textureColorImage) - m_document->textureColorImage->save(colorFilename); - */ QApplication::restoreOverrideCursor(); } diff --git a/src/glbfile.cpp b/src/glbfile.cpp index 3c4b4d6c..1c0c352d 100644 --- a/src/glbfile.cpp +++ b/src/glbfile.cpp @@ -26,7 +26,8 @@ GlbFileWriter::GlbFileWriter(Outcome &outcome, const std::vector *resultRigBones, const std::map *resultRigWeights, const QString &filename, - QImage *textureImage) : + QImage *textureImage, + const std::vector>>> *motions) : m_filename(filename), m_outputNormal(true), m_outputAnimation(true), @@ -41,6 +42,10 @@ GlbFileWriter::GlbFileWriter(Outcome &outcome, if (m_outputUv) { m_outputUv = nullptr != triangleVertexUvs; } + + if (m_outputAnimation) { + m_outputAnimation = nullptr != motions && !motions->empty(); + } QDataStream binStream(&m_binByteArray, QIODevice::WriteOnly); binStream.setFloatingPointPrecision(QDataStream::SinglePrecision); @@ -72,10 +77,9 @@ GlbFileWriter::GlbFileWriter(Outcome &outcome, m_json["asset"]["generator"] = APP_NAME " " APP_HUMAN_VER; m_json["scenes"][0]["nodes"] = {0}; + constexpr int skeletonNodeStartIndex = 2; + if (resultRigBones && resultRigWeights && !resultRigBones->empty()) { - - constexpr int skeletonNodeStartIndex = 2; - m_json["nodes"][0]["children"] = { 1, skeletonNodeStartIndex @@ -348,6 +352,136 @@ GlbFileWriter::GlbFileWriter(Outcome &outcome, } } + if (m_outputAnimation) { + for (int animationIndex = 0; animationIndex < (int)motions->size(); ++animationIndex) { + const auto &motion = (*motions)[animationIndex]; + + m_json["animations"][animationIndex]["name"] = motion.first.toUtf8().constData(); + + int input = bufferViewIndex; + bufferViewFromOffset = (int)m_binByteArray.size(); + m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; + m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; + float minTime = 1000000.0; + float maxTime = 0.0; + QStringList timeList; + float timePoint = 0; + for (const auto &keyframe: motion.second) { + binStream << (float)timePoint; + if (timePoint < minTime) + minTime = timePoint; + if (timePoint > maxTime) + maxTime = timePoint; + if (m_enableComment) + timeList.append(QString::number(timePoint)); + timePoint += keyframe.first; + } + m_json["bufferViews"][bufferViewIndex]["byteLength"] = m_binByteArray.size() - bufferViewFromOffset; + alignBin(); + if (m_enableComment) + m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: times %2").arg(QString::number(bufferViewIndex)).arg(timeList.join(" ")).toUtf8().constData(); + m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex; + m_json["accessors"][bufferViewIndex]["byteOffset"] = 0; + m_json["accessors"][bufferViewIndex]["componentType"] = 5126; + m_json["accessors"][bufferViewIndex]["count"] = motion.second.size(); + m_json["accessors"][bufferViewIndex]["type"] = "SCALAR"; + m_json["accessors"][bufferViewIndex]["max"][0] = maxTime; + m_json["accessors"][bufferViewIndex]["min"][0] = minTime; + bufferViewIndex++; + + std::set rotatedJoints; + std::set translatedJoints; + + for (const auto &keyframe: motion.second) { + for (int i = 0; i < (int)keyframe.second.nodes().size() && i < (int)boneNodes.size(); ++i) { + const auto &src = boneNodes[i]; + const auto &dest = keyframe.second.nodes()[i]; + if (!qFuzzyCompare(src.rotation, dest.rotation)) + rotatedJoints.insert(i); + if (!qFuzzyCompare(src.translation, dest.translation)) + translatedJoints.insert(i); + } + } + + int sampler = 0; + int channel = 0; + + for (const auto &jointIndex: rotatedJoints) { + int output = bufferViewIndex; + bufferViewFromOffset = (int)m_binByteArray.size(); + m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; + m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; + QStringList rotationList; + for (int frame = 0; frame < (int)motion.second.size(); frame++) { + const auto &keyframe = motion.second[frame]; + const auto &rotation = keyframe.second.nodes()[jointIndex].rotation; + float x = rotation.x(); + float y = rotation.y(); + float z = rotation.z(); + float w = rotation.scalar(); + binStream << (float)x << (float)y << (float)z << (float)w; + if (m_enableComment) + rotationList.append(QString("%1:<%2,%3,%4,%5>").arg(QString::number(frame)).arg(QString::number(x)).arg(QString::number(y)).arg(QString::number(z)).arg(QString::number(w))); + } + m_json["bufferViews"][bufferViewIndex]["byteLength"] = m_binByteArray.size() - bufferViewFromOffset; + alignBin(); + if (m_enableComment) + m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: rotation %2").arg(QString::number(bufferViewIndex)).arg(rotationList.join(" ")).toUtf8().constData(); + m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex; + m_json["accessors"][bufferViewIndex]["byteOffset"] = 0; + m_json["accessors"][bufferViewIndex]["componentType"] = 5126; + m_json["accessors"][bufferViewIndex]["count"] = motion.second.size(); + m_json["accessors"][bufferViewIndex]["type"] = "VEC4"; + bufferViewIndex++; + + m_json["animations"][animationIndex]["samplers"][sampler]["input"] = input; + m_json["animations"][animationIndex]["samplers"][sampler]["interpolation"] = "LINEAR"; + m_json["animations"][animationIndex]["samplers"][sampler]["output"] = output; + + m_json["animations"][animationIndex]["channels"][channel]["sampler"] = sampler; + m_json["animations"][animationIndex]["channels"][channel]["target"]["node"] = skeletonNodeStartIndex + jointIndex; + m_json["animations"][animationIndex]["channels"][channel]["target"]["path"] = "rotation"; + + sampler++; + channel++; + + for (const auto &jointIndex: translatedJoints) { + int output = bufferViewIndex; + bufferViewFromOffset = (int)m_binByteArray.size(); + m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; + m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; + for (int frame = 0; frame < (int)motion.second.size(); frame++) { + const auto &keyframe = motion.second[frame]; + const auto &translation = keyframe.second.nodes()[jointIndex].translation; + binStream << (float)translation.x() << (float)translation.y() << (float)translation.z(); + } + m_json["bufferViews"][bufferViewIndex]["byteLength"] = m_binByteArray.size() - bufferViewFromOffset; + alignBin(); + if (m_enableComment) + m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: translation").arg(QString::number(bufferViewIndex)).toUtf8().constData(); + m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex; + m_json["accessors"][bufferViewIndex]["byteOffset"] = 0; + m_json["accessors"][bufferViewIndex]["componentType"] = 5126; + m_json["accessors"][bufferViewIndex]["count"] = motion.second.size(); + m_json["accessors"][bufferViewIndex]["type"] = "VEC3"; + bufferViewIndex++; + + m_json["animations"][animationIndex]["samplers"][sampler]["input"] = input; + m_json["animations"][animationIndex]["samplers"][sampler]["interpolation"] = "LINEAR"; + m_json["animations"][animationIndex]["samplers"][sampler]["output"] = output; + + m_json["animations"][animationIndex]["channels"][channel]["sampler"] = sampler; + m_json["animations"][animationIndex]["channels"][channel]["target"]["node"] = skeletonNodeStartIndex + jointIndex; + m_json["animations"][animationIndex]["channels"][channel]["target"]["path"] = "translation"; + + sampler++; + channel++; + } + } + } + } + + // Images should be put in the end of the buffer, because we are not using accessors if (nullptr != textureImage) { m_json["textures"][0]["sampler"] = 0; m_json["textures"][0]["source"] = 0; @@ -373,7 +507,7 @@ GlbFileWriter::GlbFileWriter(Outcome &outcome, m_json["buffers"][0]["byteLength"] = m_binByteArray.size(); - auto jsonString = m_json.dump(4); + auto jsonString = m_enableComment ? m_json.dump(4) : m_json.dump(); jsonStream.writeRawData(jsonString.data(), jsonString.size()); alignJson(); } diff --git a/src/glbfile.h b/src/glbfile.h index b84cc045..ba90a07a 100644 --- a/src/glbfile.h +++ b/src/glbfile.h @@ -19,7 +19,8 @@ public: const std::vector *resultRigBones, const std::map *resultRigWeights, const QString &filename, - QImage *textureImage=nullptr); + QImage *textureImage=nullptr, + const std::vector>>> *motions=nullptr); bool save(); private: QString m_filename;