From d05c43d714b945fab72d6712840bfdfc8ade4062 Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Fri, 15 Jun 2018 13:34:41 +0800 Subject: [PATCH] Add animation clip: Idle This is an experiment test of animation clip generation. Mark leg and spine nodes to enable it or check it with the latest mosquito.ds3 example; Skeleton offset is been fixed in this commit. --- docs/examples/modeling-mosquito/mosquito.ds3 | 199 ++++++------ dust3d.pro | 3 + src/animationclipgenerator.cpp | 75 ++++- src/animationclipgenerator.h | 21 +- src/animationclipplayer.cpp | 59 ++++ src/animationclipplayer.h | 29 ++ src/animationpanelwidget.cpp | 130 +++----- src/animationpanelwidget.h | 8 +- src/ccdikresolver.cpp | 4 +- src/dust3dutil.cpp | 1 - src/dust3dutil.h | 1 + src/exportpreviewwidget.cpp | 3 + src/gltffile.cpp | 314 ++++++++++++++++--- src/gltffile.h | 6 +- src/jointnodetree.cpp | 87 ++++- src/jointnodetree.h | 10 +- src/meshresultcontext.cpp | 38 +-- src/meshresultpostprocessor.cpp | 10 + src/meshresultpostprocessor.h | 5 +- src/rigcontroller.cpp | 140 +++++++-- src/rigcontroller.h | 34 +- src/skeletondocument.cpp | 100 +++++- src/skeletondocument.h | 21 ++ src/skeletondocumentwindow.cpp | 17 +- src/skeletongenerator.cpp | 31 +- src/skeletongraphicswidget.cpp | 5 - src/skeletonsnapshot.h | 1 + src/skeletonxml.cpp | 27 +- src/skinnedmesh.cpp | 14 +- src/skinnedmesh.h | 3 +- 30 files changed, 1021 insertions(+), 375 deletions(-) create mode 100644 src/animationclipplayer.cpp create mode 100644 src/animationclipplayer.h diff --git a/docs/examples/modeling-mosquito/mosquito.ds3 b/docs/examples/modeling-mosquito/mosquito.ds3 index c32ec05b..567a628c 100644 --- a/docs/examples/modeling-mosquito/mosquito.ds3 +++ b/docs/examples/modeling-mosquito/mosquito.ds3 @@ -1,116 +1,117 @@ DUST3D 1.0 xml 0000000193 - - + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + ‰PNG  diff --git a/dust3d.pro b/dust3d.pro index e1666491..ed017c2e 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -143,6 +143,9 @@ HEADERS += src/jointnodetree.h SOURCES += src/animationclipgenerator.cpp HEADERS += src/animationclipgenerator.h +SOURCES += src/animationclipplayer.cpp +HEADERS += src/animationclipplayer.h + SOURCES += src/skinnedmesh.cpp HEADERS += src/skinnedmesh.h diff --git a/src/animationclipgenerator.cpp b/src/animationclipgenerator.cpp index 9cbdff15..12226864 100644 --- a/src/animationclipgenerator.cpp +++ b/src/animationclipgenerator.cpp @@ -2,14 +2,35 @@ #include "animationclipgenerator.h" #include "skinnedmesh.h" -AnimationClipGenerator::AnimationClipGenerator(const MeshResultContext &resultContext, - const QString &motionName, const std::map ¶meters) : +const std::vector AnimationClipGenerator::supportedClipNames = { + "Idle", + //"Walk", + //"Run", + //"Attack", + //"Hurt", + //"Die", +}; + +AnimationClipGenerator::AnimationClipGenerator(const MeshResultContext &resultContext, const JointNodeTree &jointNodeTree, + const QString &clipName, const std::map ¶meters, bool wantMesh) : m_resultContext(resultContext), - m_motionName(motionName), - m_parameters(parameters) + m_jointNodeTree(jointNodeTree), + m_clipName(clipName), + m_parameters(parameters), + m_wantMesh(wantMesh) { } +const std::vector &AnimationClipGenerator::times() +{ + return m_times; +} + +const std::vector &AnimationClipGenerator::frames() +{ + return m_frames; +} + AnimationClipGenerator::~AnimationClipGenerator() { for (auto &mesh: m_frameMeshes) { @@ -17,26 +38,48 @@ AnimationClipGenerator::~AnimationClipGenerator() } } -std::vector> AnimationClipGenerator::takeFrameMeshes() +std::vector> AnimationClipGenerator::takeFrameMeshes() { - std::vector> result = m_frameMeshes; + std::vector> result = m_frameMeshes; m_frameMeshes.clear(); return result; } +void AnimationClipGenerator::generateFrame(SkinnedMesh &skinnedMesh, float amount, float beginTime, float duration) +{ + RigController *rigController = skinnedMesh.rigController(); + JointNodeTree *jointNodeTree = skinnedMesh.jointNodeTree(); + + rigController->resetFrame(); + + if (m_clipName == "Idle") + rigController->idle(amount); + + RigFrame frame(jointNodeTree->joints().size()); + rigController->saveFrame(frame); + + if (m_wantMesh) { + skinnedMesh.applyRigFrameToMesh(frame); + m_frameMeshes.push_back(std::make_pair(duration, skinnedMesh.toMeshLoader())); + } + + m_times.push_back(beginTime); + m_frames.push_back(frame); +} + void AnimationClipGenerator::generate() { - SkinnedMesh skinnedMesh(m_resultContext); + SkinnedMesh skinnedMesh(m_resultContext, m_jointNodeTree); skinnedMesh.startRig(); - - RigController *rigController = skinnedMesh.rigController(); - - for (float amount = 0.0; amount <= 0.5; amount += 0.05) { - rigController->squat(amount); - RigFrame frame; - rigController->saveFrame(frame); - skinnedMesh.applyRigFrameToMesh(frame); - m_frameMeshes.push_back(std::make_pair(10, skinnedMesh.toMeshLoader())); + float duration = 0.1; + float nextBeginTime = 0; + for (float amount = 0.0; amount <= 0.05; amount += 0.01) { + generateFrame(skinnedMesh, amount, nextBeginTime, duration); + nextBeginTime += duration; + } + for (float amount = 0.05; amount >= 0.0; amount -= 0.01) { + generateFrame(skinnedMesh, amount, nextBeginTime, duration); + nextBeginTime += duration; } } diff --git a/src/animationclipgenerator.h b/src/animationclipgenerator.h index a8ee70d3..8227df56 100644 --- a/src/animationclipgenerator.h +++ b/src/animationclipgenerator.h @@ -5,6 +5,8 @@ #include #include "meshresultcontext.h" #include "meshloader.h" +#include "skinnedmesh.h" +#include "jointnodetree.h" class AnimationClipGenerator : public QObject { @@ -14,16 +16,25 @@ signals: public slots: void process(); public: - AnimationClipGenerator(const MeshResultContext &resultContext, - const QString &motionName, const std::map ¶meters); + AnimationClipGenerator(const MeshResultContext &resultContext, const JointNodeTree &jointNodeTree, + const QString &clipName, const std::map ¶meters, bool wantMesh=true); ~AnimationClipGenerator(); - std::vector> takeFrameMeshes(); + std::vector> takeFrameMeshes(); + const std::vector ×(); + const std::vector &frames(); void generate(); + static const std::vector supportedClipNames; +private: + void generateFrame(SkinnedMesh &skinnedMesh, float amount, float beginTime, float duration); private: MeshResultContext m_resultContext; - QString m_motionName; + JointNodeTree m_jointNodeTree; + QString m_clipName; std::map m_parameters; - std::vector> m_frameMeshes; + bool m_wantMesh = true; + std::vector> m_frameMeshes; + std::vector m_times; + std::vector m_frames; }; #endif diff --git a/src/animationclipplayer.cpp b/src/animationclipplayer.cpp new file mode 100644 index 00000000..aad3a3ad --- /dev/null +++ b/src/animationclipplayer.cpp @@ -0,0 +1,59 @@ +#include "animationclipplayer.h" + +AnimationClipPlayer::~AnimationClipPlayer() +{ + clear(); +} + +void AnimationClipPlayer::updateFrameMeshes(std::vector> &frameMeshes) +{ + clear(); + + m_frameMeshes = frameMeshes; + frameMeshes.clear(); + + m_currentPlayIndex = 0; + m_countForFrame.restart(); + + if (!m_frameMeshes.empty()) + m_timerForFrame.singleShot(0, this, &AnimationClipPlayer::frameReadyToShow); +} + +void AnimationClipPlayer::clear() +{ + freeFrames(); + delete m_lastFrameMesh; + m_lastFrameMesh = nullptr; +} + +void AnimationClipPlayer::freeFrames() +{ + for (auto &it: m_frameMeshes) { + delete it.second; + } + m_frameMeshes.clear(); +} + +MeshLoader *AnimationClipPlayer::takeFrameMesh() +{ + if (m_currentPlayIndex >= (int)m_frameMeshes.size()) { + if (nullptr != m_lastFrameMesh) + return new MeshLoader(*m_lastFrameMesh); + return nullptr; + } + int millis = m_frameMeshes[m_currentPlayIndex].first * 1000 - m_countForFrame.elapsed(); + if (millis > 0) { + m_timerForFrame.singleShot(millis, this, &AnimationClipPlayer::frameReadyToShow); + if (nullptr != m_lastFrameMesh) + return new MeshLoader(*m_lastFrameMesh); + return nullptr; + } + m_currentPlayIndex = (m_currentPlayIndex + 1) % m_frameMeshes.size(); + m_countForFrame.restart(); + + MeshLoader *mesh = new MeshLoader(*m_frameMeshes[m_currentPlayIndex].second); + m_timerForFrame.singleShot(m_frameMeshes[m_currentPlayIndex].first * 1000, this, &AnimationClipPlayer::frameReadyToShow); + delete m_lastFrameMesh; + m_lastFrameMesh = new MeshLoader(*mesh); + return mesh; +} diff --git a/src/animationclipplayer.h b/src/animationclipplayer.h new file mode 100644 index 00000000..095d9742 --- /dev/null +++ b/src/animationclipplayer.h @@ -0,0 +1,29 @@ +#ifndef ANIMATION_PLAYER_H +#define ANIMATION_PLAYER_H +#include +#include +#include +#include "meshloader.h" + +class AnimationClipPlayer : public QObject +{ + Q_OBJECT +signals: + void frameReadyToShow(); +public: + ~AnimationClipPlayer(); + MeshLoader *takeFrameMesh(); + void updateFrameMeshes(std::vector> &frameMeshes); + void clear(); +private: + void freeFrames(); +private: + MeshLoader *m_lastFrameMesh = nullptr; + int m_currentPlayIndex = 0; +private: + std::vector> m_frameMeshes; + QTime m_countForFrame; + QTimer m_timerForFrame; +}; + +#endif diff --git a/src/animationpanelwidget.cpp b/src/animationpanelwidget.cpp index e5531c19..1fc753ce 100644 --- a/src/animationpanelwidget.cpp +++ b/src/animationpanelwidget.cpp @@ -6,98 +6,78 @@ AnimationPanelWidget::AnimationPanelWidget(SkeletonDocument *document, QWidget *parent) : QWidget(parent), m_document(document), - m_animationClipGenerator(nullptr), - m_lastFrameMesh(nullptr), - m_sourceMeshReady(false) + m_animationClipGenerator(nullptr) { - QHBoxLayout *moveControlButtonLayout = new QHBoxLayout; - QHBoxLayout *fightControlButtonLayout = new QHBoxLayout; + connect(&m_clipPlayer, &AnimationClipPlayer::frameReadyToShow, this, &AnimationPanelWidget::frameReadyToShow); + + QVBoxLayout *buttonsLayout = new QVBoxLayout; + buttonsLayout->addStretch(); QPushButton *resetButton = new QPushButton(tr("Reset")); - connect(resetButton, &QPushButton::clicked, [=] { - m_lastMotionName.clear(); - emit panelClosed(); - }); + connect(resetButton, &QPushButton::clicked, this, &AnimationPanelWidget::reset); + buttonsLayout->addWidget(resetButton); - QPushButton *walkButton = new QPushButton(tr("Walk")); - connect(walkButton, &QPushButton::clicked, [=] { - generateClip("Walk"); - }); + buttonsLayout->addSpacing(10); - QPushButton *runButton = new QPushButton(tr("Run")); - connect(runButton, &QPushButton::clicked, [=] { - generateClip("Run"); - }); - - QPushButton *attackButton = new QPushButton(tr("Attack")); - connect(attackButton, &QPushButton::clicked, [=] { - generateClip("Attack"); - }); - - QPushButton *hurtButton = new QPushButton(tr("Hurt")); - connect(hurtButton, &QPushButton::clicked, [=] { - generateClip("Hurt"); - }); - - QPushButton *dieButton = new QPushButton(tr("Die")); - connect(dieButton, &QPushButton::clicked, [=] { - generateClip("Die"); - }); - - moveControlButtonLayout->addStretch(); - moveControlButtonLayout->addWidget(resetButton); - moveControlButtonLayout->addWidget(walkButton); - moveControlButtonLayout->addWidget(runButton); - moveControlButtonLayout->addStretch(); - - fightControlButtonLayout->addStretch(); - fightControlButtonLayout->addWidget(attackButton); - fightControlButtonLayout->addWidget(hurtButton); - fightControlButtonLayout->addWidget(dieButton); - fightControlButtonLayout->addStretch(); + for (const auto &clipName: AnimationClipGenerator::supportedClipNames) { + QPushButton *clipButton = new QPushButton(QObject::tr(qPrintable(clipName))); + connect(clipButton, &QPushButton::clicked, [=] { + generateClip(clipName); + }); + buttonsLayout->addWidget(clipButton); + } + buttonsLayout->addStretch(); QVBoxLayout *controlLayout = new QVBoxLayout; controlLayout->setSpacing(0); controlLayout->setContentsMargins(0, 0, 0, 0); - controlLayout->addLayout(moveControlButtonLayout); - controlLayout->addLayout(fightControlButtonLayout); + controlLayout->addLayout(buttonsLayout); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(controlLayout); setLayout(mainLayout); - m_countForFrame.start(); + setMinimumWidth(200); setWindowTitle(APP_NAME); } AnimationPanelWidget::~AnimationPanelWidget() { - delete m_lastFrameMesh; - for (auto &it: m_frameMeshes) { - delete it.second; - } } void AnimationPanelWidget::sourceMeshChanged() { - m_sourceMeshReady = true; - if (m_nextMotionName.isEmpty()) + if (m_nextMotionName.isEmpty() && m_lastMotionName.isEmpty()) return; - - generateClip(m_nextMotionName); + + if (!m_nextMotionName.isEmpty()) { + generateClip(m_nextMotionName); + return; + } + + if (!m_lastMotionName.isEmpty()) { + generateClip(m_lastMotionName); + return; + } } void AnimationPanelWidget::hideEvent(QHideEvent *event) +{ + reset(); +} + +void AnimationPanelWidget::reset() { m_lastMotionName.clear(); + m_clipPlayer.clear(); emit panelClosed(); } void AnimationPanelWidget::generateClip(QString motionName) { - if (nullptr != m_animationClipGenerator || !m_sourceMeshReady) { + if (nullptr != m_animationClipGenerator) { m_nextMotionName = motionName; return; } @@ -110,7 +90,8 @@ void AnimationPanelWidget::generateClip(QString motionName) std::map parameters; m_animationClipGenerator = new AnimationClipGenerator(m_document->currentPostProcessedResultContext(), - m_nextMotionName, parameters); + m_document->currentJointNodeTree(), + motionName, parameters, true); m_animationClipGenerator->moveToThread(thread); connect(thread, &QThread::started, m_animationClipGenerator, &AnimationClipGenerator::process); connect(m_animationClipGenerator, &AnimationClipGenerator::finished, this, &AnimationPanelWidget::clipReady); @@ -121,16 +102,12 @@ void AnimationPanelWidget::generateClip(QString motionName) void AnimationPanelWidget::clipReady() { - m_frameMeshes = m_animationClipGenerator->takeFrameMeshes(); + auto frameMeshes = m_animationClipGenerator->takeFrameMeshes(); + m_clipPlayer.updateFrameMeshes(frameMeshes); delete m_animationClipGenerator; m_animationClipGenerator = nullptr; - - m_countForFrame.restart(); - - if (!m_frameMeshes.empty()) - QTimer::singleShot(m_frameMeshes[0].first, this, &AnimationPanelWidget::frameReadyToShow); - + qDebug() << "Animation clip generation done"; if (!m_nextMotionName.isEmpty()) @@ -139,28 +116,5 @@ void AnimationPanelWidget::clipReady() MeshLoader *AnimationPanelWidget::takeFrameMesh() { - if (m_lastMotionName.isEmpty()) - return m_document->takeResultMesh(); - - if (m_frameMeshes.empty()) { - if (nullptr != m_lastFrameMesh) - return new MeshLoader(*m_lastFrameMesh); - return nullptr; - } - int millis = m_frameMeshes[0].first - m_countForFrame.elapsed(); - if (millis > 0) { - QTimer::singleShot(millis, this, &AnimationPanelWidget::frameReadyToShow); - if (nullptr != m_lastFrameMesh) - return new MeshLoader(*m_lastFrameMesh); - return nullptr; - } - MeshLoader *mesh = m_frameMeshes[0].second; - m_frameMeshes.erase(m_frameMeshes.begin()); - m_countForFrame.restart(); - if (!m_frameMeshes.empty()) { - QTimer::singleShot(m_frameMeshes[0].first, this, &AnimationPanelWidget::frameReadyToShow); - } - delete m_lastFrameMesh; - m_lastFrameMesh = new MeshLoader(*mesh); - return mesh; + return m_clipPlayer.takeFrameMesh(); } diff --git a/src/animationpanelwidget.h b/src/animationpanelwidget.h index 1bcf928f..09fdbc4f 100644 --- a/src/animationpanelwidget.h +++ b/src/animationpanelwidget.h @@ -5,6 +5,7 @@ #include #include "skeletondocument.h" #include "animationclipgenerator.h" +#include "animationclipplayer.h" class AnimationPanelWidget : public QWidget { @@ -22,14 +23,13 @@ public slots: void generateClip(QString motionName); void clipReady(); void sourceMeshChanged(); +private: + void reset(); private: SkeletonDocument *m_document; AnimationClipGenerator *m_animationClipGenerator; - MeshLoader *m_lastFrameMesh; - bool m_sourceMeshReady; private: - std::vector> m_frameMeshes; - QTime m_countForFrame; + AnimationClipPlayer m_clipPlayer; QString m_nextMotionName; QString m_lastMotionName; }; diff --git a/src/ccdikresolver.cpp b/src/ccdikresolver.cpp index aa75915b..a63a9b09 100644 --- a/src/ccdikresolver.cpp +++ b/src/ccdikresolver.cpp @@ -6,8 +6,8 @@ CCDIKSolver::CCDIKSolver() : m_maxRound(4), - m_distanceThreshold2(0.01 * 0.01), - m_distanceCeaseThreshold2(0.01 * 0.01) + m_distanceThreshold2(0.001 * 0.001), + m_distanceCeaseThreshold2(0.001 * 0.001) { } diff --git a/src/dust3dutil.cpp b/src/dust3dutil.cpp index 70bd8347..782ff152 100644 --- a/src/dust3dutil.cpp +++ b/src/dust3dutil.cpp @@ -26,4 +26,3 @@ void qNormalizeAngle(int &angle) while (angle > 360 * 16) angle -= 360 * 16; } - diff --git a/src/dust3dutil.h b/src/dust3dutil.h index 208a6625..cbbc80a1 100644 --- a/src/dust3dutil.h +++ b/src/dust3dutil.h @@ -4,6 +4,7 @@ #include #include #include +#include #ifndef M_PI #define M_PI 3.14159265358979323846 diff --git a/src/exportpreviewwidget.cpp b/src/exportpreviewwidget.cpp index 3699e32a..7fe4b6dd 100644 --- a/src/exportpreviewwidget.cpp +++ b/src/exportpreviewwidget.cpp @@ -72,6 +72,9 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa m_spinnerWidget->hide(); setWindowTitle(APP_NAME); + + emit updateTexturePreview(); + emit updateSkeleton(); } void ExportPreviewWidget::updateTexturePreviewImage(const QImage &image) diff --git a/src/gltffile.cpp b/src/gltffile.cpp index 175c3ded..4b0df609 100644 --- a/src/gltffile.cpp +++ b/src/gltffile.cpp @@ -20,9 +20,12 @@ bool GLTFFileWriter::m_enableComment = false; -GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &filename) : +GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const std::map &animationClipContexts, const QString &filename) : m_filename(filename), - m_outputNormal(true) + m_outputNormal(true), + m_outputAnimation(true), + m_outputUv(true), + m_testOutputAsWhole(false) { const BmeshNode *rootNode = resultContext.centerBmeshNode(); if (!rootNode) { @@ -41,11 +44,12 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & m_json["asset"]["generator"] = APP_NAME " " APP_HUMAN_VER; m_json["scenes"][0]["nodes"] = {0}; - m_json["nodes"][0]["mesh"] = 0; - m_json["nodes"][0]["skin"] = 0; - m_json["nodes"][0]["children"] = {1}; + m_json["nodes"][0]["children"] = {1, 2}; - int skeletonNodeStartIndex = 1; + m_json["nodes"][1]["mesh"] = 0; + m_json["nodes"][1]["skin"] = 0; + + int skeletonNodeStartIndex = 2; for (auto i = 0u; i < tracedJoints.size(); i++) { m_json["nodes"][skeletonNodeStartIndex + i]["translation"] = {tracedJoints[i].translation.x(), @@ -84,7 +88,20 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & }; int bufferViewIndex = 0; + int bufferViewFromOffset; + bufferViewFromOffset = (int)binaries.size(); + m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; + m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; + for (auto i = 0u; i < tracedJoints.size(); i++) { + const float *floatArray = tracedJoints[i].inverseBindMatrix.constData(); + for (auto j = 0u; j < 16; j++) { + stream << (float)floatArray[j]; + } + } + m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset; + Q_ASSERT((int)tracedJoints.size() * 16 * sizeof(float) == binaries.size() - bufferViewFromOffset); + alignBinaries(); if (m_enableComment) m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: mat").arg(QString::number(bufferViewIndex)).toUtf8().constData(); m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex; @@ -92,17 +109,6 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & m_json["accessors"][bufferViewIndex]["componentType"] = 5126; m_json["accessors"][bufferViewIndex]["count"] = tracedJoints.size(); m_json["accessors"][bufferViewIndex]["type"] = "MAT4"; - m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; - m_json["bufferViews"][bufferViewIndex]["byteOffset"] = (int)binaries.size(); - int bufferViews0FromOffset = (int)binaries.size(); - for (auto i = 0u; i < tracedJoints.size(); i++) { - const float *floatArray = tracedJoints[i].inverseBindMatrix.constData(); - for (auto j = 0u; j < 16; j++) { - stream << (float)floatArray[j]; - } - } - m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViews0FromOffset; - alignBinaries(); bufferViewIndex++; m_json["textures"][0]["sampler"] = 0; @@ -114,10 +120,23 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & m_json["samplers"][0]["minFilter"] = 9987; m_json["samplers"][0]["wrapS"] = 33648; m_json["samplers"][0]["wrapT"] = 33648; + + const std::map *parts = &resultContext.parts(); + + std::map testParts; + if (m_testOutputAsWhole) { + testParts[0].vertices = resultContext.vertices; + testParts[0].triangles = resultContext.triangles; + testParts[0].weights = resultContext.vertexWeights(); + + m_outputNormal = false; + m_outputUv = false; + + parts = &testParts; + } int primitiveIndex = 0; - for (const auto &part: resultContext.parts()) { - int bufferViewFromOffset; + for (const auto &part: *parts) { m_json["meshes"][0]["primitives"][primitiveIndex]["indices"] = bufferViewIndex; m_json["meshes"][0]["primitives"][primitiveIndex]["material"] = primitiveIndex; @@ -127,7 +146,8 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["NORMAL"] = bufferViewIndex + (++attributeIndex); m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["JOINTS_0"] = bufferViewIndex + (++attributeIndex); m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["WEIGHTS_0"] = bufferViewIndex + (++attributeIndex); - m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["TEXCOORD_0"] = bufferViewIndex + (++attributeIndex); + if (m_outputUv) + m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["TEXCOORD_0"] = bufferViewIndex + (++attributeIndex); /* m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["baseColorFactor"] = { part.second.color.redF(), @@ -149,7 +169,7 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; m_json["bufferViews"][bufferViewIndex]["byteLength"] = (int)part.second.triangles.size() * 3 * sizeof(quint16); m_json["bufferViews"][bufferViewIndex]["target"] = 34963; - Q_ASSERT(part.second.triangles.size() * 3 * sizeof(quint16) == binaries.size() - bufferViewFromOffset); + Q_ASSERT((int)part.second.triangles.size() * 3 * sizeof(quint16) == binaries.size() - bufferViewFromOffset); alignBinaries(); if (m_enableComment) m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: triangle indicies").arg(QString::number(bufferViewIndex)).toUtf8().constData(); @@ -184,7 +204,7 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & maxZ = it.position.z(); stream << (float)it.position.x() << (float)it.position.y() << (float)it.position.z(); } - Q_ASSERT( part.second.vertices.size() * 3 * sizeof(float) == binaries.size() - bufferViewFromOffset); + Q_ASSERT((int)part.second.vertices.size() * 3 * sizeof(float) == binaries.size() - bufferViewFromOffset); m_json["bufferViews"][bufferViewIndex]["byteLength"] = part.second.vertices.size() * 3 * sizeof(float); m_json["bufferViews"][bufferViewIndex]["target"] = 34962; alignBinaries(); @@ -206,10 +226,10 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & QStringList normalList; for (const auto &it: part.second.interpolatedVertexNormals) { stream << (float)it.x() << (float)it.y() << (float)it.z(); - if (m_outputNormal) + if (m_enableComment && m_outputNormal) normalList.append(QString("<%1,%2,%3>").arg(QString::number(it.x())).arg(QString::number(it.y())).arg(QString::number(it.z()))); } - Q_ASSERT( part.second.interpolatedVertexNormals.size() * 3 * sizeof(float) == binaries.size() - bufferViewFromOffset); + Q_ASSERT((int)part.second.interpolatedVertexNormals.size() * 3 * sizeof(float) == binaries.size() - bufferViewFromOffset); m_json["bufferViews"][bufferViewIndex]["byteLength"] = part.second.vertices.size() * 3 * sizeof(float); m_json["bufferViews"][bufferViewIndex]["target"] = 34962; alignBinaries(); @@ -226,19 +246,31 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & bufferViewFromOffset = (int)binaries.size(); m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; + QStringList boneList; + int weightItIndex = 0; for (const auto &it: part.second.weights) { auto i = 0u; + if (m_enableComment) + boneList.append(QString("%1:<").arg(QString::number(weightItIndex))); for (; i < it.size() && i < MAX_WEIGHT_NUM; i++) { - stream << (quint16)jointNodeTree.nodeToJointIndex(it[i].sourceNode.first, it[i].sourceNode.second); + quint16 nodeIndex = (quint16)(jointNodeTree.nodeToJointIndex(it[i].sourceNode.first, it[i].sourceNode.second)); + stream << nodeIndex; + if (m_enableComment) + boneList.append(QString("%1").arg(nodeIndex)); } for (; i < MAX_WEIGHT_NUM; i++) { stream << (quint16)0; + if (m_enableComment) + boneList.append(QString("%1").arg(0)); } + if (m_enableComment) + boneList.append(QString(">")); + weightItIndex++; } m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset; alignBinaries(); if (m_enableComment) - m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: bone indicies").arg(QString::number(bufferViewIndex)).toUtf8().constData(); + m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: bone indicies %2").arg(QString::number(bufferViewIndex)).arg(boneList.join(" ")).toUtf8().constData(); m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex; m_json["accessors"][bufferViewIndex]["byteOffset"] = 0; m_json["accessors"][bufferViewIndex]["componentType"] = 5123; @@ -249,19 +281,30 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & bufferViewFromOffset = (int)binaries.size(); m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; + QStringList weightList; + weightItIndex = 0; for (const auto &it: part.second.weights) { auto i = 0u; + if (m_enableComment) + weightList.append(QString("%1:<").arg(QString::number(weightItIndex))); for (; i < it.size() && i < MAX_WEIGHT_NUM; i++) { stream << (float)it[i].weight; + if (m_enableComment) + weightList.append(QString("%1").arg(QString::number((float)it[i].weight))); } for (; i < MAX_WEIGHT_NUM; i++) { stream << (float)0.0; + if (m_enableComment) + weightList.append(QString("%1").arg(QString::number(0.0))); } + if (m_enableComment) + weightList.append(QString(">")); + weightItIndex++; } m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset; alignBinaries(); if (m_enableComment) - m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: bone weights").arg(QString::number(bufferViewIndex)).toUtf8().constData(); + m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: bone weights %2").arg(QString::number(bufferViewIndex)).arg(weightList.join(" ")).toUtf8().constData(); m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex; m_json["accessors"][bufferViewIndex]["byteOffset"] = 0; m_json["accessors"][bufferViewIndex]["componentType"] = 5126; @@ -269,22 +312,209 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & m_json["accessors"][bufferViewIndex]["type"] = "VEC4"; bufferViewIndex++; - bufferViewFromOffset = (int)binaries.size(); - m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; - m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; - for (const auto &it: part.second.vertexUvs) { - stream << (float)it.uv[0] << (float)it.uv[1]; + if (m_outputUv) { + bufferViewFromOffset = (int)binaries.size(); + m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; + m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; + for (const auto &it: part.second.vertexUvs) { + stream << (float)it.uv[0] << (float)it.uv[1]; + } + m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset; + alignBinaries(); + if (m_enableComment) + m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: uv").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"] = part.second.vertexUvs.size(); + m_json["accessors"][bufferViewIndex]["type"] = "VEC2"; + bufferViewIndex++; + } + } + + if (m_outputAnimation) { + int animationIndex = 0; + for (const auto &animationClip: animationClipContexts) { + const auto &ctx = animationClip.second; + + int input = bufferViewIndex; + bufferViewFromOffset = (int)binaries.size(); + m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; + m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; + float minTime = 1000000.0; + float maxTime = 0.0; + QStringList timeList; + for (const auto &timePoint: ctx.times) { + stream << (float)timePoint; + if (timePoint < minTime) + minTime = timePoint; + if (timePoint > maxTime) + maxTime = timePoint; + if (m_enableComment) + timeList.append(QString::number(timePoint)); + } + m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset; + alignBinaries(); + 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"] = ctx.times.size(); + m_json["accessors"][bufferViewIndex]["type"] = "SCALAR"; + m_json["accessors"][bufferViewIndex]["max"][0] = maxTime; + m_json["accessors"][bufferViewIndex]["min"][0] = minTime; + bufferViewIndex++; + + Q_ASSERT(ctx.frames.size() == ctx.times.size()); + + std::set rotatedJoints; + std::set translatedJoints; + std::set scaledJoints; + for (const auto &rigFrame: ctx.frames) { + for (int i = 0; i < (int)rigFrame.rotations.size(); i++) { + if (rigFrame.rotatedIndicies.find(i) == rigFrame.rotatedIndicies.end()) + continue; + rotatedJoints.insert(i); + } + for (int i = 0; i < (int)rigFrame.translations.size(); i++) { + if (rigFrame.translatedIndicies.find(i) == rigFrame.translatedIndicies.end()) + continue; + translatedJoints.insert(i); + } + for (int i = 0; i < (int)rigFrame.scales.size(); i++) { + if (rigFrame.scaledIndicies.find(i) == rigFrame.scaledIndicies.end()) + continue; + scaledJoints.insert(i); + } + } + + int sampler = 0; + int channel = 0; + + for (const auto &jointIndex: rotatedJoints) { + int output = bufferViewIndex; + bufferViewFromOffset = (int)binaries.size(); + m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; + m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; + QStringList rotationList; + for (int frame = 0; frame < (int)ctx.frames.size(); frame++) { + const auto &rotation = ctx.frames[frame].rotatedIndicies.find(jointIndex) != ctx.frames[frame].rotatedIndicies.end() ? + ctx.frames[frame].rotations[jointIndex] : + tracedJoints[jointIndex].rotation; + float x = rotation.x(); + float y = rotation.y(); + float z = rotation.z(); + float w = rotation.scalar(); + stream << (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"] = binaries.size() - bufferViewFromOffset; + alignBinaries(); + 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"] = ctx.frames.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)binaries.size(); + m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; + m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; + for (int frame = 0; frame < (int)ctx.frames.size(); frame++) { + const auto &translation = ctx.frames[frame].translatedIndicies.find(jointIndex) != ctx.frames[frame].translatedIndicies.end() ? + ctx.frames[frame].translations[jointIndex] : + tracedJoints[jointIndex].translation; + stream << (float)translation.x() << (float)translation.y() << (float)translation.z(); + } + m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset; + alignBinaries(); + 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"] = ctx.frames.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++; + } + + for (const auto &jointIndex: scaledJoints) { + int output = bufferViewIndex; + bufferViewFromOffset = (int)binaries.size(); + m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; + m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; + QStringList scaleList; + for (int frame = 0; frame < (int)ctx.frames.size(); frame++) { + const auto &scale = ctx.frames[frame].scaledIndicies.find(jointIndex) != ctx.frames[frame].scaledIndicies.end() ? + ctx.frames[frame].scales[jointIndex] : + tracedJoints[jointIndex].scale; + float x = scale.x(); + float y = scale.y(); + float z = scale.z(); + stream << (float)x << (float)y << (float)z; + if (m_enableComment) { + scaleList.append(QString("%1:<%2,%3,%4>") + .arg(QString::number(frame)) + .arg(QString::number(x)) + .arg(QString::number(y)) + .arg(QString::number(z)) + ); + } + } + m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset; + alignBinaries(); + if (m_enableComment) + m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: scale %2").arg(QString::number(bufferViewIndex)).arg(scaleList.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"] = ctx.frames.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"] = "scale"; + + sampler++; + channel++; + } + + animationIndex++; } - m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset; - alignBinaries(); - if (m_enableComment) - m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: uv").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"] = part.second.vertexUvs.size(); - m_json["accessors"][bufferViewIndex]["type"] = "VEC2"; - bufferViewIndex++; } m_json["buffers"][0]["uri"] = QString("data:application/octet-stream;base64," + binaries.toBase64()).toUtf8().constData(); diff --git a/src/gltffile.h b/src/gltffile.h index 61e199aa..74086320 100644 --- a/src/gltffile.h +++ b/src/gltffile.h @@ -7,12 +7,13 @@ #include #include "meshresultcontext.h" #include "json.hpp" +#include "skeletondocument.h" class GLTFFileWriter : public QObject { Q_OBJECT public: - GLTFFileWriter(MeshResultContext &resultContext, const QString &filename); + GLTFFileWriter(MeshResultContext &resultContext, const std::map &animationClipContexts, const QString &filename); bool save(); const QString &textureFilenameInGltf(); private: @@ -21,6 +22,9 @@ private: QString m_filename; QString m_textureFilename; bool m_outputNormal; + bool m_outputAnimation; + bool m_outputUv; + bool m_testOutputAsWhole; private: nlohmann::json m_json; public: diff --git a/src/jointnodetree.cpp b/src/jointnodetree.cpp index a2d1896c..c61ee1dd 100644 --- a/src/jointnodetree.cpp +++ b/src/jointnodetree.cpp @@ -17,6 +17,7 @@ JointNodeTree::JointNodeTree(MeshResultContext &resultContext) rootCenterJoint.nodeId = rootNode->nodeId; rootCenterJoint.position = rootNode->origin; rootCenterJoint.boneMark = rootNode->boneMark; + rootCenterJoint.scale = QVector3D(1.0, 1.0, 1.0); m_tracedJoints.push_back(rootCenterJoint); } @@ -27,7 +28,7 @@ JointNodeTree::JointNodeTree(MeshResultContext &resultContext) m_tracedNodeToJointIndexMap[std::make_pair(rootNode->bmeshId, rootNode->nodeId)] = rootCenterJoint.jointIndex; traceBoneFromJoint(resultContext, std::make_pair(rootNode->bmeshId, rootNode->nodeId), visitedNodes, connections, rootCenterJoint.jointIndex); - calculateMatrices(); + calculateMatricesByPosition(); } void JointNodeTree::traceBoneFromJoint(MeshResultContext &resultContext, std::pair node, std::set> &visitedNodes, std::set, std::pair>> &connections, int parentIndex) @@ -38,6 +39,7 @@ void JointNodeTree::traceBoneFromJoint(MeshResultContext &resultContext, std::pa const auto &neighbors = resultContext.nodeNeighbors().find(node); if (neighbors == resultContext.nodeNeighbors().end()) return; + std::vector>> neighborJoints; for (const auto &it: neighbors->second) { if (connections.find(std::make_pair(std::make_pair(node.first, node.second), std::make_pair(it.first, it.second))) != connections.end()) continue; @@ -60,13 +62,18 @@ void JointNodeTree::traceBoneFromJoint(MeshResultContext &resultContext, std::pa joint.partId = toNode->second->bmeshId; joint.nodeId = toNode->second->nodeId; joint.boneMark = toNode->second->boneMark; + joint.scale = QVector3D(1.0, 1.0, 1.0); m_tracedNodeToJointIndexMap[std::make_pair(it.first, it.second)] = joint.jointIndex; m_tracedJoints.push_back(joint); m_tracedJoints[parentIndex].children.push_back(joint.jointIndex); - traceBoneFromJoint(resultContext, it, visitedNodes, connections, joint.jointIndex); + neighborJoints.push_back(std::make_pair(joint.jointIndex, it)); + } + + for (const auto &joint: neighborJoints) { + traceBoneFromJoint(resultContext, joint.second, visitedNodes, connections, joint.first); } } @@ -78,49 +85,95 @@ std::vector &JointNodeTree::joints() int JointNodeTree::nodeToJointIndex(int partId, int nodeId) { const auto &findIt = m_tracedNodeToJointIndexMap.find(std::make_pair(partId, nodeId)); - if (findIt == m_tracedNodeToJointIndexMap.end()) + if (findIt == m_tracedNodeToJointIndexMap.end()) { + qDebug() << "node to joint index map failed, partId:" << partId << "nodeId:" << nodeId; return 0; + } return findIt->second; } -void JointNodeTree::calculateMatrices() +void JointNodeTree::calculateMatricesByPosition() { if (joints().empty()) return; - calculateMatricesFrom(0, QVector3D(), QVector3D(), QMatrix4x4()); + calculateMatricesByPositionFrom(0, QVector3D(), QVector3D(), QMatrix4x4()); } -void JointNodeTree::calculateMatricesFrom(int jointIndex, const QVector3D &parentPosition, const QVector3D &parentDirection, const QMatrix4x4 &parentMatrix) +void JointNodeTree::calculateMatricesByTransform() +{ + for (auto &joint: joints()) { + QMatrix4x4 translateMatrix; + translateMatrix.translate(joint.translation); + + QMatrix4x4 rotateMatrix; + rotateMatrix.rotate(joint.rotation); + + QMatrix4x4 scaleMatrix; + scaleMatrix.scale(joint.scale); + + QMatrix4x4 localMatrix = translateMatrix * rotateMatrix * scaleMatrix; + QMatrix4x4 bindMatrix = joint.parentIndex == -1 ? localMatrix : (joints()[joint.parentIndex].bindMatrix * localMatrix); + + bool invertible = true; + + joint.bindMatrix = bindMatrix; + + joint.position = joint.inverseBindMatrix * joint.bindMatrix * joint.position; + + joint.inverseBindMatrix = joint.bindMatrix.inverted(&invertible); + + if (!invertible) + qDebug() << "jointIndex:" << joint.jointIndex << "invertible:" << invertible; + } +} + +void JointNodeTree::calculateMatricesByPositionFrom(int jointIndex, const QVector3D &parentPosition, const QVector3D &parentDirection, const QMatrix4x4 &parentMatrix) { auto &joint = joints()[jointIndex]; QVector3D translation = joint.position - parentPosition; - QVector3D direction = translation.normalized(); + QVector3D direction = QVector3D(); QMatrix4x4 translateMatrix; translateMatrix.translate(translation); QMatrix4x4 rotateMatrix; - QVector3D cross = QVector3D::crossProduct(parentDirection, direction).normalized(); - float dot = QVector3D::dotProduct(parentDirection, direction); - float angle = acos(dot); - QQuaternion rotation = QQuaternion::fromAxisAndAngle(cross, angle); - rotateMatrix.rotate(QQuaternion::fromAxisAndAngle(cross, angle)); + QQuaternion rotation; + if (!parentDirection.isNull()) { + direction = translation.normalized(); + + rotation = QQuaternion::rotationTo(parentDirection, direction); + rotateMatrix.rotate(rotation); + } - QMatrix4x4 localMatrix = translateMatrix * rotateMatrix; + QMatrix4x4 scaleMatrix; + scaleMatrix.scale(joint.scale); + + QMatrix4x4 localMatrix = translateMatrix * rotateMatrix * scaleMatrix; QMatrix4x4 bindMatrix = parentMatrix * localMatrix; + bool invertible = true; + joint.localMatrix = localMatrix; joint.translation = translation; joint.rotation = rotation; joint.bindMatrix = bindMatrix; - joint.inverseBindMatrix = joint.bindMatrix.inverted(); + joint.inverseBindMatrix = joint.bindMatrix.inverted(&invertible); + + if (!invertible) + qDebug() << "jointIndex:" << jointIndex << "invertible:" << invertible; for (const auto &child: joint.children) { - calculateMatricesFrom(child, joint.position, direction, bindMatrix); + calculateMatricesByPositionFrom(child, joint.position, direction, bindMatrix); } } -void JointNodeTree::recalculateMatricesAfterPositionsUpdated() +void JointNodeTree::recalculateMatricesAfterPositionUpdated() { - calculateMatrices(); + calculateMatricesByPosition(); } + +void JointNodeTree::recalculateMatricesAfterTransformUpdated() +{ + calculateMatricesByTransform(); +} + diff --git a/src/jointnodetree.h b/src/jointnodetree.h index 5056e573..c8bb8fb7 100644 --- a/src/jointnodetree.h +++ b/src/jointnodetree.h @@ -9,11 +9,13 @@ struct JointInfo { int jointIndex = 0; + int parentIndex = -1; int partId = 0; int nodeId = 0; SkeletonBoneMark boneMark = SkeletonBoneMark::None; QVector3D position; QVector3D translation; + QVector3D scale; QQuaternion rotation; QMatrix4x4 localMatrix; QMatrix4x4 bindMatrix; @@ -27,10 +29,12 @@ public: JointNodeTree(MeshResultContext &resultContext); std::vector &joints(); int nodeToJointIndex(int partId, int nodeId); - void recalculateMatricesAfterPositionsUpdated(); + void recalculateMatricesAfterPositionUpdated(); + void recalculateMatricesAfterTransformUpdated(); private: - void calculateMatrices(); - void calculateMatricesFrom(int jointIndex, const QVector3D &parentPosition, const QVector3D &parentDirection, const QMatrix4x4 &parentMatrix); + void calculateMatricesByTransform(); + void calculateMatricesByPosition(); + void calculateMatricesByPositionFrom(int jointIndex, const QVector3D &parentPosition, const QVector3D &parentDirection, const QMatrix4x4 &parentMatrix); std::vector m_tracedJoints; std::map, int> m_tracedNodeToJointIndexMap; void traceBoneFromJoint(MeshResultContext &resultContext, std::pair node, std::set> &visitedNodes, std::set, std::pair>> &connections, int parentIndex); diff --git a/src/meshresultcontext.cpp b/src/meshresultcontext.cpp index 75124197..bf448aa0 100644 --- a/src/meshresultcontext.cpp +++ b/src/meshresultcontext.cpp @@ -515,10 +515,23 @@ void MeshResultContext::calculateVertexWeights(std::vector solvedVertices; + for (auto i = 0u; i < vertices.size(); i++) { + const auto &findSourceNode = vertexSourceMap().find(i); + if (findSourceNode != vertexSourceMap().end()) { + ResultVertexWeight vertexWeight; + vertexWeight.sourceNode = findSourceNode->second; + vertexWeight.count = 1; + vertexWeights[i].push_back(vertexWeight); + solvedVertices.insert(i); + } + } for (auto i = 0u; i < triangles.size(); i++) { std::pair sourceNode = triangleSourceNodes()[i]; for (int j = 0; j < 3; j++) { int vertexIndex = triangles[i].indicies[j]; + if (solvedVertices.find(vertexIndex) != solvedVertices.end()) + continue; Q_ASSERT(vertexIndex < (int)vertexWeights.size()); int foundSourceNodeIndex = -1; for (auto k = 0u; k < vertexWeights[vertexIndex].size(); k++) { @@ -546,30 +559,16 @@ void MeshResultContext::calculateVertexWeights(std::vectorempty()) { // We removed some intermediate nodes, so we should recalculate the vertex weights. for (auto &it: vertexWeights) { std::vector, float>> weights; for (auto i = 0u; i < it.size(); i++) { const auto &findInter = intermediateNodes->find(it[i].sourceNode); if (findInter != intermediateNodes->end()) { - //const auto &interBmeshNode = bmeshNodeMap().find(findInter->first); - //const auto &attachedFromBmeshNode = bmeshNodeMap().find(std::make_pair(findInter->second.attachedFromPartId, findInter->second.attachedFromNodeId)); const auto &attachedToBmeshNode = bmeshNodeMap().find(std::make_pair(findInter->second.attachedToPartId, findInter->second.attachedToNodeId)); if (attachedToBmeshNode != bmeshNodeMap().end()) weights.push_back(std::make_pair(attachedToBmeshNode->first, it[i].weight)); - /* - if (interBmeshNode != bmeshNodeMap().end() && - attachedFromBmeshNode != bmeshNodeMap().end() && - attachedToBmeshNode != bmeshNodeMap().end()) { - float distWithFrom = interBmeshNode->second->origin.distanceToPoint(attachedFromBmeshNode->second->origin); - float distWithTo = interBmeshNode->second->origin.distanceToPoint(attachedToBmeshNode->second->origin); - float distTotal = distWithFrom + distWithTo; - if (distTotal > 0) { - weights.push_back(std::make_pair(attachedFromBmeshNode->first, it[i].weight * distWithFrom / distTotal)); - weights.push_back(std::make_pair(attachedToBmeshNode->first, it[i].weight * distWithTo / distTotal)); - } - }*/ } else { weights.push_back(std::make_pair(it[i].sourceNode, it[i].weight)); } @@ -631,7 +630,9 @@ void MeshResultContext::calculateResultParts(std::map &parts) for (auto i = 0u; i < 3; i++) { auto key = std::make_pair(sourceNode.first, triangle.indicies[i]); const auto &it = oldVertexToNewMap.find(key); - if (it == oldVertexToNewMap.end() || m_seamVertices.end() != m_seamVertices.find(triangle.indicies[i])) { + bool isNewVertex = it == oldVertexToNewMap.end(); + bool isSeamVertex = m_seamVertices.end() != m_seamVertices.find(triangle.indicies[i]); + if (isNewVertex || isSeamVertex) { int newIndex = resultPart.vertices.size(); resultPart.interpolatedVertexNormals.push_back(newTriangle.normal); resultPart.vertices.push_back(vertices[triangle.indicies[i]]); @@ -639,8 +640,9 @@ void MeshResultContext::calculateResultParts(std::map &parts) vertexUv.uv[0] = triangleUvs()[x].uv[i][0]; vertexUv.uv[1] = triangleUvs()[x].uv[i][1]; resultPart.vertexUvs.push_back(vertexUv); - resultPart.weights.push_back(vertexWeights()[triangle.indicies[i]]); - if (it == oldVertexToNewMap.end()) + const auto &weight = vertexWeights()[triangle.indicies[i]]; + resultPart.weights.push_back(weight); + if (isNewVertex && !isSeamVertex) oldVertexToNewMap.insert(std::make_pair(key, newIndex)); newTriangle.indicies[i] = newIndex; } else { diff --git a/src/meshresultpostprocessor.cpp b/src/meshresultpostprocessor.cpp index c1f598a4..f49418c3 100644 --- a/src/meshresultpostprocessor.cpp +++ b/src/meshresultpostprocessor.cpp @@ -10,6 +10,7 @@ MeshResultPostProcessor::MeshResultPostProcessor(const MeshResultContext &meshRe MeshResultPostProcessor::~MeshResultPostProcessor() { delete m_meshResultContext; + delete m_jointNodeTree; } MeshResultContext *MeshResultPostProcessor::takePostProcessedResultContext() @@ -19,6 +20,13 @@ MeshResultContext *MeshResultPostProcessor::takePostProcessedResultContext() return resultContext; } +JointNodeTree *MeshResultPostProcessor::takeJointNodeTree() +{ + JointNodeTree *jointNodeTree = m_jointNodeTree; + m_jointNodeTree = nullptr; + return jointNodeTree; +} + void MeshResultPostProcessor::process() { if (!m_meshResultContext->bmeshNodes.empty()) { @@ -31,6 +39,8 @@ void MeshResultPostProcessor::process() m_meshResultContext->parts(); } + m_jointNodeTree = new JointNodeTree(*m_meshResultContext); + this->moveToThread(QGuiApplication::instance()->thread()); emit finished(); } diff --git a/src/meshresultpostprocessor.h b/src/meshresultpostprocessor.h index 973876fb..fee7d485 100644 --- a/src/meshresultpostprocessor.h +++ b/src/meshresultpostprocessor.h @@ -2,6 +2,7 @@ #define MESH_RESULT_POST_PROCESSOR_H #include #include "meshresultcontext.h" +#include "jointnodetree.h" class MeshResultPostProcessor : public QObject { @@ -10,12 +11,14 @@ public: MeshResultPostProcessor(const MeshResultContext &meshResultContext); ~MeshResultPostProcessor(); MeshResultContext *takePostProcessedResultContext(); + JointNodeTree *takeJointNodeTree(); signals: void finished(); public slots: void process(); private: - MeshResultContext *m_meshResultContext; + MeshResultContext *m_meshResultContext = nullptr; + JointNodeTree *m_jointNodeTree = nullptr; }; #endif diff --git a/src/rigcontroller.cpp b/src/rigcontroller.cpp index 6a6c00c8..361c0838 100644 --- a/src/rigcontroller.cpp +++ b/src/rigcontroller.cpp @@ -5,7 +5,8 @@ RigController::RigController(const JointNodeTree &jointNodeTree) : m_inputJointNodeTree(jointNodeTree), m_prepared(false), - m_legHeight(0) + m_legHeight(0), + m_averageLegEndY(0) { } @@ -14,10 +15,14 @@ void RigController::saveFrame(RigFrame &frame) frame = m_rigFrame; } -void RigController::collectLegs() +void RigController::collectParts() { m_legs.clear(); + m_spine.clear(); for (const auto &node: m_inputJointNodeTree.joints()) { + if (node.boneMark == SkeletonBoneMark::Spine) { + m_spine.push_back(std::make_pair(node.partId, node.nodeId)); + } if (node.boneMark == SkeletonBoneMark::LegStart && node.children.size() == 1) { const auto legStart = std::make_pair(node.partId, node.nodeId); const JointInfo *loopNode = &m_inputJointNodeTree.joints()[node.children[0]]; @@ -48,16 +53,43 @@ void RigController::prepare() return; m_prepared = true; - collectLegs(); - m_rigFrame = RigFrame(m_inputJointNodeTree.joints().size()); + collectParts(); calculateAverageLegHeight(); } +void RigController::resetFrame() +{ + m_rigFrame = RigFrame(m_inputJointNodeTree.joints().size()); +} + void RigController::lift(QVector3D offset) { if (m_inputJointNodeTree.joints().empty()) return; - m_rigFrame.translations[0] = offset; + if (m_rigFrame.translatedIndicies.find(0) != m_rigFrame.translatedIndicies.end()) + m_rigFrame.updateTranslation(0, m_rigFrame.translations[0] + offset); + else + m_rigFrame.updateTranslation(0, m_inputJointNodeTree.joints()[0].translation + offset); +} + +void RigController::breathe(float amount) +{ + if (m_spine.empty() || amount <= 0) + return; + std::vector spineJoints; + for (auto i = 0u; i < m_spine.size(); i++) { + int jointIndex = m_inputJointNodeTree.nodeToJointIndex(m_spine[i].first, m_spine[i].second); + spineJoints.push_back(jointIndex); + } + // make sure parent get processed first + std::sort(spineJoints.begin(), spineJoints.end()); + float inverseAmount = 1 / amount; + for (const auto &jointIndex: spineJoints) { + m_rigFrame.updateScale(jointIndex, m_rigFrame.scales[jointIndex] * amount); + for (const auto &child: m_inputJointNodeTree.joints()[jointIndex].children) { + m_rigFrame.updateScale(child, m_rigFrame.scales[child] * inverseAmount); + } + } } void RigController::liftLegs(QVector3D offset, QVector3D &effectedOffset) @@ -118,20 +150,12 @@ void RigController::liftLegEnd(int leg, QVector3D offset, QVector3D &effectedOff effectedOffset = ikSolver.getNodeSolvedPosition(nodeCount - 1) - m_inputJointNodeTree.joints()[ikSolvingIndicies[nodeCount - 1]].position; qDebug() << "end effector offset:" << destPosition.distanceToPoint(ikSolver.getNodeSolvedPosition(nodeCount - 1)); - outputJointNodeTree.recalculateMatricesAfterPositionsUpdated(); - QMatrix4x4 parentMatrix; + outputJointNodeTree.recalculateMatricesAfterPositionUpdated(); for (int i = 0; i < nodeCount; i++) { int jointIndex = ikSolvingIndicies[i]; - const auto &inputJoint = m_inputJointNodeTree.joints()[jointIndex]; const auto &outputJoint = outputJointNodeTree.joints()[jointIndex]; - - QMatrix4x4 worldMatrix = outputJoint.bindMatrix * inputJoint.inverseBindMatrix; - QMatrix4x4 trMatrix = worldMatrix * parentMatrix.inverted(); - - m_rigFrame.rotations[jointIndex] = QQuaternion::fromRotationMatrix(trMatrix.normalMatrix()); - m_rigFrame.translations[jointIndex] = QVector3D(trMatrix(0, 3), trMatrix(1, 3), trMatrix(2, 3)); - - parentMatrix = worldMatrix; + m_rigFrame.updateRotation(jointIndex, outputJoint.rotation); + m_rigFrame.updateTranslation(jointIndex, outputJoint.translation); } } @@ -150,20 +174,32 @@ void RigController::frameToMatricesAtJoint(const RigFrame &frame, std::vector(m_legs[leg]); int legStartNodeId = std::get<1>(m_legs[leg]); - int legEndPartId = std::get<2>(m_legs[leg]); - int legEndNodeId = std::get<3>(m_legs[leg]); - int legStartIndex = m_inputJointNodeTree.nodeToJointIndex(legStartPartId, legStartNodeId); - int legEndIndex = m_inputJointNodeTree.nodeToJointIndex(legEndPartId, legEndNodeId); - const auto &legStart = m_inputJointNodeTree.joints()[legStartIndex]; - const auto &legEnd = m_inputJointNodeTree.joints()[legEndIndex]; - //qDebug() << "leg:" << leg << "legStartPartId:" << legStartPartId << "legEndPartId:" << legEndPartId << legStart.position << legEnd.position; + int legStartIndex = jointNodeTree.nodeToJointIndex(legStartPartId, legStartNodeId); + const auto &legStart = jointNodeTree.joints()[legStartIndex]; averageLegPlaneTop += legStart.position; - averageLegPlaneBottom += legEnd.position; } averageLegPlaneTop /= m_legs.size(); - averageLegPlaneBottom /= m_legs.size(); - m_legHeight = abs(averageLegPlaneBottom.y() - averageLegPlaneTop.y()); + return averageLegPlaneTop; } + +QVector3D RigController::calculateAverageLegEndPosition(JointNodeTree &jointNodeTree) +{ + QVector3D averageLegPlaneBottom = QVector3D(); + if (m_legs.empty()) + return QVector3D(); + for (auto leg = 0u; leg < m_legs.size(); leg++) { + int legEndPartId = std::get<2>(m_legs[leg]); + int legEndNodeId = std::get<3>(m_legs[leg]); + int legEndIndex = jointNodeTree.nodeToJointIndex(legEndPartId, legEndNodeId); + const auto &legEnd = jointNodeTree.joints()[legEndIndex]; + averageLegPlaneBottom += legEnd.position; + } + averageLegPlaneBottom /= m_legs.size(); + return averageLegPlaneBottom; +} + +void RigController::applyRigFrameToJointNodeTree(JointNodeTree &jointNodeTree, const RigFrame &frame) +{ + for (const auto &jointIndex: frame.translatedIndicies) { + jointNodeTree.joints()[jointIndex].translation = frame.translations[jointIndex]; + } + for (const auto &jointIndex: frame.rotatedIndicies) { + jointNodeTree.joints()[jointIndex].rotation = frame.rotations[jointIndex]; + } + for (const auto &jointIndex: frame.scaledIndicies) { + jointNodeTree.joints()[jointIndex].scale = frame.scales[jointIndex]; + } + jointNodeTree.recalculateMatricesAfterTransformUpdated(); +} + diff --git a/src/rigcontroller.h b/src/rigcontroller.h index b447c68f..5b9c4060 100644 --- a/src/rigcontroller.h +++ b/src/rigcontroller.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "jointnodetree.h" class RigFrame @@ -19,9 +20,31 @@ public: translations.clear(); translations.resize(jointNum); + + scales.clear(); + scales.resize(jointNum, QVector3D(1.0, 1.0, 1.0)); + } + void updateRotation(int index, const QQuaternion &rotation) + { + rotations[index] = rotation; + rotatedIndicies.insert(index); + } + void updateTranslation(int index, const QVector3D &translation) + { + translations[index] = translation; + translatedIndicies.insert(index); + } + void updateScale(int index, const QVector3D &scale) + { + scales[index] = scale; + scaledIndicies.insert(index); } std::vector rotations; std::vector translations; + std::vector scales; + std::set rotatedIndicies; + std::set translatedIndicies; + std::set scaledIndicies; }; class RigController @@ -30,22 +53,29 @@ public: RigController(const JointNodeTree &jointNodeTree); void saveFrame(RigFrame &frame); void frameToMatrices(const RigFrame &frame, std::vector &matrices); - void squat(float amount); + void idle(float amount); + void breathe(float amount); + void resetFrame(); private: void prepare(); - void collectLegs(); + void collectParts(); int addLeg(std::pair legStart, std::pair legEnd); void liftLegEnd(int leg, QVector3D offset, QVector3D &effectedOffset); void liftLegs(QVector3D offset, QVector3D &effectedOffset); void lift(QVector3D offset); void calculateAverageLegHeight(); + QVector3D calculateAverageLegStartPosition(JointNodeTree &jointNodeTree); + QVector3D calculateAverageLegEndPosition(JointNodeTree &jointNodeTree); void frameToMatricesAtJoint(const RigFrame &frame, std::vector &matrices, int jointIndex, const QMatrix4x4 &parentWorldMatrix); + void applyRigFrameToJointNodeTree(JointNodeTree &jointNodeTree, const RigFrame &frame); private: JointNodeTree m_inputJointNodeTree; bool m_prepared; float m_legHeight; + float m_averageLegEndY; private: std::vector> m_legs; + std::vector> m_spine; RigFrame m_rigFrame; }; diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp index 60ae1a16..d3157a46 100644 --- a/src/skeletondocument.cpp +++ b/src/skeletondocument.cpp @@ -40,6 +40,7 @@ SkeletonDocument::SkeletonDocument() : m_postProcessResultIsObsolete(false), m_postProcessor(nullptr), m_postProcessedResultContext(new MeshResultContext), + m_jointNodeTree(new JointNodeTree(*m_postProcessedResultContext)), m_resultTextureMesh(nullptr), m_textureImageUpdateVersion(0), m_ambientOcclusionBaker(nullptr), @@ -52,6 +53,7 @@ SkeletonDocument::~SkeletonDocument() delete m_resultMesh; delete m_resultSkeletonMesh; delete m_postProcessedResultContext; + delete m_jointNodeTree; delete textureGuideImage; delete textureImage; delete textureBorderImage; @@ -685,6 +687,9 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::setpartIdList.push_back(partIdIt.toString()); } + + snapshot->animationParameters = animationParameters; + std::map canvas; canvas["originX"] = QString::number(originX); canvas["originY"] = QString::number(originY); @@ -705,6 +710,12 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot) originZ = originZit->second.toFloat(); } + for (const auto &ani: snapshot.animationParameters) { + for (const auto ¶m: ani.second) { + animationParameters[ani.first][param.first] = param.second; + } + } + std::set newAddedNodeIds; std::set newAddedEdgeIds; std::set newAddedPartIds; @@ -894,6 +905,8 @@ void SkeletonDocument::meshReady() qDebug() << "MeshLoader generation done"; + m_postProcessResultIsObsolete = true; + emit resultMeshChanged(); if (m_resultMeshIsObsolete) { @@ -901,6 +914,11 @@ void SkeletonDocument::meshReady() } } +bool SkeletonDocument::postProcessResultIsObsolete() const +{ + return m_postProcessResultIsObsolete; +} + void SkeletonDocument::batchChangeBegin() { m_batchChangeRefCount++; @@ -1140,6 +1158,9 @@ void SkeletonDocument::postProcessedMeshResultReady() { delete m_postProcessedResultContext; m_postProcessedResultContext = m_postProcessor->takePostProcessedResultContext(); + + delete m_jointNodeTree; + m_jointNodeTree = m_postProcessor->takeJointNodeTree(); delete m_postProcessor; m_postProcessor = nullptr; @@ -1158,6 +1179,11 @@ MeshResultContext &SkeletonDocument::currentPostProcessedResultContext() return *m_postProcessedResultContext; } +JointNodeTree &SkeletonDocument::currentJointNodeTree() +{ + return *m_jointNodeTree; +} + void SkeletonDocument::setPartLockState(QUuid partId, bool locked) { auto part = partMap.find(partId); @@ -1427,13 +1453,85 @@ bool SkeletonDocument::isExportReady() const m_meshGenerator || m_skeletonGenerator || m_textureGenerator || - m_postProcessor) + m_postProcessor || + !allAnimationClipsReady()) return false; return true; } +bool SkeletonDocument::allAnimationClipsReady() const +{ + for (const auto &clipName: AnimationClipGenerator::supportedClipNames) { + const auto &findClip = m_animationClipContexts.find(clipName); + if (findClip == m_animationClipContexts.end()) + return false; + const auto &clipContext = findClip->second; + if (nullptr != clipContext.clipGenerator) + return false; + if (clipContext.isObsolete) + return false; + } + return true; +} + void SkeletonDocument::checkExportReadyState() { if (isExportReady()) emit exportReady(); } + +void SkeletonDocument::generateAnimationClip(QString clipName) +{ + auto &clipContext = m_animationClipContexts[clipName]; + if (nullptr != clipContext.clipGenerator) { + clipContext.isObsolete = true; + return; + } + + qDebug() << "Animation clip [" << clipName << "] generating for document.."; + + clipContext.isObsolete = false; + + QThread *thread = new QThread; + clipContext.clipGenerator = new AnimationClipGenerator(currentPostProcessedResultContext(), + currentJointNodeTree(), + clipName, animationParameters[clipName], false); + clipContext.clipGenerator->moveToThread(thread); + connect(thread, &QThread::started, clipContext.clipGenerator, &AnimationClipGenerator::process); + connect(clipContext.clipGenerator, &AnimationClipGenerator::finished, [=] { + animationClipReady(clipName); + }); + connect(clipContext.clipGenerator, &AnimationClipGenerator::finished, thread, &QThread::quit); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); +} + +void SkeletonDocument::animationClipReady(QString clipName) +{ + auto &clipContext = m_animationClipContexts[clipName]; + + clipContext.times = clipContext.clipGenerator->times(); + clipContext.frames = clipContext.clipGenerator->frames(); + + delete clipContext.clipGenerator; + clipContext.clipGenerator = nullptr; + + qDebug() << "Animation clip [" << clipName << "] generation done"; + + if (clipContext.isObsolete) + generateAnimationClip(clipName); + else + checkExportReadyState(); +} + +void SkeletonDocument::generateAllAnimationClips() +{ + for (const auto &clipName: AnimationClipGenerator::supportedClipNames) + generateAnimationClip(clipName); +} + +const std::map &SkeletonDocument::animationClipContexts() +{ + return m_animationClipContexts; +} + diff --git a/src/skeletondocument.h b/src/skeletondocument.h index 586792e9..a64e2dd8 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -17,6 +17,8 @@ #include "meshresultpostprocessor.h" #include "ambientocclusionbaker.h" #include "skeletonbonemark.h" +#include "animationclipgenerator.h" +#include "jointnodetree.h" class SkeletonNode { @@ -171,6 +173,15 @@ enum class SkeletonDocumentEditMode ZoomOut }; +class AnimationClipContext +{ +public: + bool isObsolete = true; + AnimationClipGenerator *clipGenerator = nullptr; + std::vector times; + std::vector frames; +}; + class SkeletonDocument : public QObject { Q_OBJECT @@ -240,6 +251,7 @@ public: std::map nodeMap; std::map edgeMap; std::vector partIds; + std::map> animationParameters; QImage turnaround; QImage preview; void toSnapshot(SkeletonSnapshot *snapshot, const std::set &limitNodeIds=std::set()) const; @@ -260,7 +272,11 @@ public: bool isEdgeEditable(QUuid edgeId) const; bool originSettled() const; MeshResultContext ¤tPostProcessedResultContext(); + JointNodeTree ¤tJointNodeTree(); bool isExportReady() const; + bool allAnimationClipsReady() const; + bool postProcessResultIsObsolete() const; + const std::map &animationClipContexts(); public slots: void removeNode(QUuid nodeId); void removeEdge(QUuid edgeId); @@ -282,9 +298,12 @@ public slots: void generateTexture(); void textureReady(); void postProcess(); + void generateAnimationClip(QString clipName); + void generateAllAnimationClips(); void postProcessedMeshResultReady(); void bakeAmbientOcclusionTexture(); void ambientOcclusionTextureReady(); + void animationClipReady(QString clipName); void setPartLockState(QUuid partId, bool locked); void setPartVisibleState(QUuid partId, bool visible); void setPartSubdivState(QUuid partId, bool subdived); @@ -329,10 +348,12 @@ private: // need initialize bool m_postProcessResultIsObsolete; MeshResultPostProcessor *m_postProcessor; MeshResultContext *m_postProcessedResultContext; + JointNodeTree *m_jointNodeTree; MeshLoader *m_resultTextureMesh; unsigned long long m_textureImageUpdateVersion; AmbientOcclusionBaker *m_ambientOcclusionBaker; unsigned long long m_ambientOcclusionBakedImageUpdateVersion; + std::map m_animationClipContexts; private: static unsigned long m_maxSnapshot; std::deque m_undoItems; diff --git a/src/skeletondocumentwindow.cpp b/src/skeletondocumentwindow.cpp index 5bc69d0e..ac95d81c 100644 --- a/src/skeletondocumentwindow.cpp +++ b/src/skeletondocumentwindow.cpp @@ -422,7 +422,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_showAnimationPanelAction = new QAction(tr("Show Animation Panel"), this); connect(m_showAnimationPanelAction, &QAction::triggered, this, &SkeletonDocumentWindow::showAnimationPanel); - //m_viewMenu->addAction(m_showAnimationPanelAction); + m_viewMenu->addAction(m_showAnimationPanelAction); m_viewMenu->addSeparator(); @@ -592,6 +592,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_document, &SkeletonDocument::generateSkeleton); connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_document, &SkeletonDocument::generateTexture); connect(m_document, &SkeletonDocument::resultTextureChanged, m_document, &SkeletonDocument::bakeAmbientOcclusionTexture); + connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_document, &SkeletonDocument::generateAllAnimationClips); connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() { m_modelRenderWidget->updateMesh(m_document->takeResultMesh()); @@ -895,12 +896,16 @@ void SkeletonDocumentWindow::showAnimationPanel() m_modelRenderWidget->updateMesh(m_document->takeResultMesh()); }); connect(m_animationPanelWidget, &AnimationPanelWidget::frameReadyToShow, [=] { - if (m_animationPanelWidget->isVisible()) - m_modelRenderWidget->updateMesh(m_animationPanelWidget->takeFrameMesh()); + if (m_animationPanelWidget->isVisible()) { + auto mesh = m_animationPanelWidget->takeFrameMesh(); + if (nullptr == mesh) + mesh = m_document->takeResultMesh(); + m_modelRenderWidget->updateMesh(mesh); + } }); connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_animationPanelWidget, &AnimationPanelWidget::sourceMeshChanged); } - if (m_animationPanelWidget->isHidden()) { + if (m_document->postProcessResultIsObsolete()) { m_document->postProcess(); } m_animationPanelWidget->show(); @@ -919,7 +924,7 @@ void SkeletonDocumentWindow::showExportPreview() connect(m_document, &SkeletonDocument::resultBakedTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview); connect(m_document, &SkeletonDocument::resultSkeletonChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateSkeleton); } - if (m_exportPreviewWidget->isHidden()) { + if (m_document->postProcessResultIsObsolete()) { m_document->postProcess(); } m_exportPreviewWidget->show(); @@ -934,7 +939,7 @@ void SkeletonDocumentWindow::exportGltfResult() } QApplication::setOverrideCursor(Qt::WaitCursor); MeshResultContext skeletonResult = m_document->currentPostProcessedResultContext(); - GLTFFileWriter gltfFileWriter(skeletonResult, filename); + GLTFFileWriter gltfFileWriter(skeletonResult, m_document->animationClipContexts(),filename); gltfFileWriter.save(); if (m_document->textureImage) m_document->textureImage->save(gltfFileWriter.textureFilenameInGltf()); diff --git a/src/skeletongenerator.cpp b/src/skeletongenerator.cpp index d62dea2d..3aed5023 100644 --- a/src/skeletongenerator.cpp +++ b/src/skeletongenerator.cpp @@ -5,6 +5,7 @@ #include "skeletongenerator.h" #include "positionmap.h" #include "meshlite.h" +#include "jointnodetree.h" SkeletonGenerator::SkeletonGenerator(const MeshResultContext &meshResultContext) : m_resultSkeletonMesh(nullptr) @@ -38,30 +39,16 @@ MeshLoader *SkeletonGenerator::createSkeletonMesh() void *meshliteContext = meshlite_create_context(); int sklt = meshlite_skeletonmesh_create(meshliteContext); - std::map, BmeshNode *> nodeIndexMap; - for (auto i = 0u; i < m_meshResultContext->bmeshNodes.size(); i++) { - BmeshNode *bmeshNode = &m_meshResultContext->bmeshNodes[i]; - nodeIndexMap[std::make_pair(bmeshNode->bmeshId, bmeshNode->nodeId)] = bmeshNode; - } - - for (const auto &it: m_meshResultContext->bmeshEdges) { - const auto &from = nodeIndexMap.find(std::make_pair(it.fromBmeshId, it.fromNodeId)); - const auto &to = nodeIndexMap.find(std::make_pair(it.toBmeshId, it.toNodeId)); - if (from == nodeIndexMap.end()) { - qDebug() << "fromNodeId not found in nodeIndexMap:" << it.fromBmeshId << it.fromNodeId; - continue; + JointNodeTree jointNodeTree(*m_meshResultContext); + for (const auto &joint: jointNodeTree.joints()) { + for (const auto &childIndex: joint.children) { + const auto &child = jointNodeTree.joints()[childIndex]; + meshlite_skeletonmesh_add_bone(meshliteContext, sklt, + joint.position.x(), joint.position.y(), joint.position.z(), + child.position.x(), child.position.y(), child.position.z()); } - if (to == nodeIndexMap.end()) { - qDebug() << "toNodeId not found in nodeIndexMap:" << it.toBmeshId << it.toNodeId; - continue; - } - BmeshNode *fromNode = from->second; - BmeshNode *toNode = to->second; - meshlite_skeletonmesh_add_bone(meshliteContext, sklt, - fromNode->origin.x(), fromNode->origin.y(), fromNode->origin.z(), - toNode->origin.x(), toNode->origin.y(), toNode->origin.z()); } - + int meshId = meshlite_skeletonmesh_generate_mesh(meshliteContext, sklt); MeshLoader *skeletonMesh = new MeshLoader(meshliteContext, meshId, -1, Theme::green); diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index 1a2193ff..bceecca1 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -174,11 +174,6 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos) contextMenu.addAction(&flipVerticallyAction); } - QAction alignToCenterAction(tr("Align to Center"), this); - if (hasSelection() && m_document->originSettled()) { - connect(&alignToCenterAction, &QAction::triggered, this, &SkeletonGraphicsWidget::alignSelectedToGlobalVerticalCenter); - contextMenu.addAction(&alignToCenterAction); - } QAction alignToLocalCenterAction(tr("Local Center"), this); QAction alignToLocalVerticalCenterAction(tr("Local Vertical Center"), this); QAction alignToLocalHorizontalCenterAction(tr("Local Horizontal Center"), this); diff --git a/src/skeletonsnapshot.h b/src/skeletonsnapshot.h index 38555eb6..78c2f2e4 100644 --- a/src/skeletonsnapshot.h +++ b/src/skeletonsnapshot.h @@ -14,6 +14,7 @@ public: std::map> edges; std::map> parts; std::vector partIdList; + std::map> animationParameters; public: void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId=QString()); }; diff --git a/src/skeletonxml.cpp b/src/skeletonxml.cpp index 08da900f..9e0dc7a7 100644 --- a/src/skeletonxml.cpp +++ b/src/skeletonxml.cpp @@ -55,6 +55,23 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write writer->writeEndElement(); } writer->writeEndElement(); + + if (!snapshot->animationParameters.empty()) { + writer->writeStartElement("animations"); + std::map>::iterator animationIterator; + for (animationIterator = snapshot->animationParameters.begin(); animationIterator != snapshot->animationParameters.end(); animationIterator++) { + std::map::iterator animationParamterIterator; + if (animationIterator->second.find("clip") != animationIterator->second.end()) { + writer->writeStartElement("animation"); + for (animationParamterIterator = animationIterator->second.begin(); animationParamterIterator != animationIterator->second.end(); animationParamterIterator++) { + writer->writeAttribute(animationParamterIterator->first, animationParamterIterator->second); + } + writer->writeEndElement(); + } + } + writer->writeEndElement(); + } + writer->writeEndElement(); writer->writeEndDocument(); @@ -93,7 +110,15 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea foreach(const QXmlStreamAttribute &attr, reader.attributes()) { (*partMap)[attr.name().toString()] = attr.value().toString(); } - } else if (reader.name() == "partId") { + } else if (reader.name() == "animation") { + QString animationClipName = reader.attributes().value("clip").toString(); + if (animationClipName.isEmpty()) + continue; + std::map *animationParameterMap = &snapshot->animationParameters[animationClipName]; + foreach(const QXmlStreamAttribute &attr, reader.attributes()) { + (*animationParameterMap)[attr.name().toString()] = attr.value().toString(); + } + } else if (reader.name() == "partId") { QString partId = reader.attributes().value("id").toString(); if (partId.isEmpty()) continue; diff --git a/src/skinnedmesh.cpp b/src/skinnedmesh.cpp index a83f6e35..9e44665c 100644 --- a/src/skinnedmesh.cpp +++ b/src/skinnedmesh.cpp @@ -1,9 +1,9 @@ #include "skinnedmesh.h" -SkinnedMesh::SkinnedMesh(const MeshResultContext &resultContext) : +SkinnedMesh::SkinnedMesh(const MeshResultContext &resultContext, const JointNodeTree &jointNodeTree) : m_resultContext(resultContext), m_rigController(nullptr), - m_jointNodeTree(nullptr) + m_jointNodeTree(new JointNodeTree(jointNodeTree)) { } @@ -18,11 +18,14 @@ RigController *SkinnedMesh::rigController() return m_rigController; } +JointNodeTree *SkinnedMesh::jointNodeTree() +{ + return m_jointNodeTree; +} + void SkinnedMesh::startRig() { Q_ASSERT(nullptr == m_rigController); - Q_ASSERT(nullptr == m_jointNodeTree); - m_jointNodeTree = new JointNodeTree(m_resultContext); m_rigController = new RigController(*m_jointNodeTree); fromMeshResultContext(m_resultContext); } @@ -34,8 +37,7 @@ void SkinnedMesh::applyRigFrameToMesh(const RigFrame &frame) for (auto &vert: m_vertices) { QMatrix4x4 matrix; for (int i = 0; i < MAX_WEIGHT_NUM; i++) { - if (vert.weights[i].amount > 0) - matrix += matrices[vert.weights[i].jointIndex] * vert.weights[i].amount; + matrix += matrices[vert.weights[i].jointIndex] * vert.weights[i].amount; } vert.position = matrix * vert.posPosition; vert.normal = (matrix * vert.posNormal).normalized(); diff --git a/src/skinnedmesh.h b/src/skinnedmesh.h index be77844d..4f988af4 100644 --- a/src/skinnedmesh.h +++ b/src/skinnedmesh.h @@ -30,10 +30,11 @@ struct SkinnedMeshTriangle class SkinnedMesh { public: - SkinnedMesh(const MeshResultContext &resultContext); + SkinnedMesh(const MeshResultContext &resultContext, const JointNodeTree &jointNodeTree); ~SkinnedMesh(); void startRig(); RigController *rigController(); + JointNodeTree *jointNodeTree(); void applyRigFrameToMesh(const RigFrame &frame); MeshLoader *toMeshLoader(); private: