diff --git a/dust3d.pro b/dust3d.pro index 83c617d2..a269a1ef 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -164,6 +164,27 @@ HEADERS += src/riggenerator.h SOURCES += src/meshquadify.cpp HEADERS += src/meshquadify.h +SOURCES += src/skinnedmeshcreator.cpp +HEADERS += src/skinnedmeshcreator.h + +SOURCES += src/jointnodetree.cpp +HEADERS += src/jointnodetree.h + +SOURCES += src/tetrapodposer.cpp +HEADERS += src/tetrapodposer.h + +SOURCES += src/poser.cpp +HEADERS += src/poser.h + +SOURCES += src/posemeshcreator.cpp +HEADERS += src/posemeshcreator.h + +SOURCES += src/posepreviewmanager.cpp +HEADERS += src/posepreviewmanager.h + +SOURCES += src/tetrapodposeeditwidget.cpp +HEADERS += src/tetrapodposeeditwidget.h + SOURCES += src/main.cpp HEADERS += src/version.h diff --git a/src/animationclipplayer.cpp b/src/animationclipplayer.cpp deleted file mode 100644 index aad3a3ad..00000000 --- a/src/animationclipplayer.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#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 deleted file mode 100644 index 095d9742..00000000 --- a/src/animationclipplayer.h +++ /dev/null @@ -1,29 +0,0 @@ -#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/autorigger.cpp b/src/autorigger.cpp index 8c644bc0..2eddfa46 100644 --- a/src/autorigger.cpp +++ b/src/autorigger.cpp @@ -249,7 +249,8 @@ void AutoRigger::addVerticesToWeights(const std::set &vertices, int boneInd { for (const auto &vertexIndex: vertices) { auto &weights = m_resultWeights[vertexIndex]; - float distance = m_verticesPositions[vertexIndex].distanceToPoint(m_resultBones[boneIndex].headPosition); + auto strongestPoint = (m_resultBones[boneIndex].headPosition * 3 + m_resultBones[boneIndex].tailPosition) / 4; + float distance = m_verticesPositions[vertexIndex].distanceToPoint(strongestPoint); weights.addBone(boneIndex, distance); } } @@ -307,6 +308,7 @@ bool AutoRigger::rig() std::set headVertices; addTrianglesToVertices(m_marks[neckIndicies->second[0]].smallGroup(), headVertices); + addTrianglesToVertices(m_marks[neckIndicies->second[0]].markTriangles, headVertices); QVector3D headBoneStopPosition; if (isMainBodyVerticalAligned) { QVector3D maxY = findMaxY(headVertices); @@ -379,6 +381,9 @@ bool AutoRigger::rig() // 2.1 Collect vertices for neck bone: std::set bodyVertices; addTrianglesToVertices(bodyTriangles, bodyVertices); + addTrianglesToVertices(m_marks[leftShoulderIndicies->second[0]].markTriangles, bodyVertices); + addTrianglesToVertices(m_marks[rightShoulderIndicies->second[0]].markTriangles, bodyVertices); + std::set bodyVerticesAfterShoulder; std::set neckVertices; { @@ -447,11 +452,9 @@ bool AutoRigger::rig() { std::set leftArmVerticesBeforeElbow; if (isLeftArmVerticalAligned) { - QVector3D maxY = findMaxY(leftElbowMarkVertices); - splitVerticesByY(leftArmVertices, maxY.y(), leftArmVerticesBeforeElbow, leftArmVerticesSinceElbow); + splitVerticesByY(leftArmVertices, m_marks[leftElbowIndicies->second[0]].bonePosition.y(), leftArmVerticesBeforeElbow, leftArmVerticesSinceElbow); } else { - QVector3D minX = findMinX(leftElbowMarkVertices); - splitVerticesByX(leftArmVertices, minX.x(), leftArmVerticesSinceElbow, leftArmVerticesBeforeElbow); + splitVerticesByX(leftArmVertices, m_marks[leftElbowIndicies->second[0]].bonePosition.x(), leftArmVerticesSinceElbow, leftArmVerticesBeforeElbow); } } std::set leftWristMarkVertices; @@ -472,11 +475,9 @@ bool AutoRigger::rig() { std::set leftArmVerticesBeforeWrist; if (isLeftArmVerticalAligned) { - QVector3D maxY = findMaxY(leftWristMarkVertices); - splitVerticesByY(leftArmVerticesSinceElbow, maxY.y(), leftArmVerticesBeforeWrist, leftHandVertices); + splitVerticesByY(leftArmVerticesSinceElbow, m_marks[leftWristIndicies->second[0]].bonePosition.y(), leftArmVerticesBeforeWrist, leftHandVertices); } else { - QVector3D minX = findMinX(leftWristMarkVertices); - splitVerticesByX(leftArmVerticesSinceElbow, minX.x(), leftHandVertices, leftArmVerticesBeforeWrist); + splitVerticesByX(leftArmVerticesSinceElbow, m_marks[leftWristIndicies->second[0]].bonePosition.x(), leftHandVertices, leftArmVerticesBeforeWrist); } } @@ -501,11 +502,9 @@ bool AutoRigger::rig() { std::set rightArmVerticesBeforeElbow; if (isRightArmVerticalAligned) { - QVector3D maxY = findMaxY(rightElbowMarkVertices); - splitVerticesByY(rightArmVertices, maxY.y(), rightArmVerticesBeforeElbow, rightArmVerticesSinceElbow); + splitVerticesByY(rightArmVertices, m_marks[rightElbowIndicies->second[0]].bonePosition.y(), rightArmVerticesBeforeElbow, rightArmVerticesSinceElbow); } else { - QVector3D maxX = findMaxX(rightElbowMarkVertices); - splitVerticesByX(rightArmVertices, maxX.x(), rightArmVerticesBeforeElbow, rightArmVerticesSinceElbow); + splitVerticesByX(rightArmVertices, m_marks[rightElbowIndicies->second[0]].bonePosition.x(), rightArmVerticesBeforeElbow, rightArmVerticesSinceElbow); } } std::set rightWristMarkVertices; @@ -526,11 +525,9 @@ bool AutoRigger::rig() { std::set rightArmVerticesBeforeWrist; if (isRightArmVerticalAligned) { - QVector3D maxY = findMaxY(rightWristMarkVertices); - splitVerticesByY(rightArmVerticesSinceElbow, maxY.y(), rightArmVerticesBeforeWrist, rightHandVertices); + splitVerticesByY(rightArmVerticesSinceElbow, m_marks[rightWristIndicies->second[0]].bonePosition.y(), rightArmVerticesBeforeWrist, rightHandVertices); } else { - QVector3D maxX = findMaxX(rightWristMarkVertices); - splitVerticesByX(rightArmVerticesSinceElbow, maxX.x(), rightArmVerticesBeforeWrist, rightHandVertices); + splitVerticesByX(rightArmVerticesSinceElbow, m_marks[rightWristIndicies->second[0]].bonePosition.x(), rightArmVerticesBeforeWrist, rightHandVertices); } } @@ -566,8 +563,7 @@ bool AutoRigger::rig() std::set leftFootVertices; { std::set leftLegVerticesBeforeAnkle; - QVector3D maxY = findMaxY(leftAnkleMarkVertices); - splitVerticesByY(leftLegVerticesSinceKnee, maxY.y(), leftLegVerticesBeforeAnkle, leftFootVertices); + splitVerticesByY(leftLegVerticesSinceKnee, m_marks[leftAnkleIndicies->second[0]].bonePosition.y(), leftLegVerticesBeforeAnkle, leftFootVertices); } // 4.2.1 Collect vertices for right upper leg: @@ -600,8 +596,7 @@ bool AutoRigger::rig() std::set rightFootVertices; { std::set rightLegVerticesBeforeAnkle; - QVector3D maxY = findMaxY(rightAnkleMarkVertices); - splitVerticesByY(rightLegVerticesSinceKnee, maxY.y(), rightLegVerticesBeforeAnkle, rightFootVertices); + splitVerticesByY(rightLegVerticesSinceKnee, m_marks[rightAnkleIndicies->second[0]].bonePosition.y(), rightLegVerticesBeforeAnkle, rightFootVertices); } // 5. Generate bones diff --git a/src/autorigger.h b/src/autorigger.h index 1b9991fc..c57956eb 100644 --- a/src/autorigger.h +++ b/src/autorigger.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "meshsplitter.h" #include "skeletonbonemark.h" #include "rigtype.h" @@ -55,7 +56,7 @@ public: void addBone(int boneIndex, float distance) { if (qFuzzyIsNull(distance)) - distance = 0.01; + distance = 0.0001; m_boneRawWeights.push_back(std::make_pair(boneIndex, 1.0 / distance)); } void finalizeWeights() diff --git a/src/ccdikresolver.h b/src/ccdikresolver.h index 3f895a20..0485aafb 100644 --- a/src/ccdikresolver.h +++ b/src/ccdikresolver.h @@ -7,7 +7,6 @@ struct CCDIKNode { QVector3D position; - bool hasConstraint = false; }; class CCDIKSolver diff --git a/src/floatnumberwidget.cpp b/src/floatnumberwidget.cpp index 2ff3365f..2a3297bd 100644 --- a/src/floatnumberwidget.cpp +++ b/src/floatnumberwidget.cpp @@ -1,7 +1,9 @@ #include +#include +#include #include "floatnumberwidget.h" -FloatNumberWidget::FloatNumberWidget(QWidget *parent) : +FloatNumberWidget::FloatNumberWidget(QWidget *parent, bool singleLine) : QWidget(parent) { m_slider = new QSlider(Qt::Horizontal, this); @@ -16,11 +18,19 @@ FloatNumberWidget::FloatNumberWidget(QWidget *parent) : updateValueLabel(fvalue); emit valueChanged(fvalue); }); - - QBoxLayout *popupLayout = new QHBoxLayout(this); - popupLayout->setMargin(2); - popupLayout->addWidget(m_slider); - popupLayout->addWidget(m_label); + + QBoxLayout *layout = nullptr; + if (singleLine) { + layout = new QHBoxLayout(this); + layout->setMargin(2); + layout->addWidget(m_slider); + layout->addWidget(m_label); + } else { + layout = new QVBoxLayout(this); + layout->setMargin(2); + layout->addWidget(m_label); + layout->addWidget(m_slider); + } } void FloatNumberWidget::updateValueLabel(float value) diff --git a/src/floatnumberwidget.h b/src/floatnumberwidget.h index 48df208d..066ea4be 100644 --- a/src/floatnumberwidget.h +++ b/src/floatnumberwidget.h @@ -9,7 +9,7 @@ class FloatNumberWidget : public QWidget { Q_OBJECT public: - explicit FloatNumberWidget(QWidget *parent = nullptr); + explicit FloatNumberWidget(QWidget *parent = nullptr, bool singleLine=true); void setRange(float min, float max); float value() const; void setItemName(const QString &name); diff --git a/src/gltffile.cpp b/src/gltffile.cpp index 55d94e4a..807c8cfb 100644 --- a/src/gltffile.cpp +++ b/src/gltffile.cpp @@ -7,6 +7,7 @@ #include "gltffile.h" #include "version.h" #include "dust3dutil.h" +#include "jointnodetree.h" // Play with glTF online: // https://gltf-viewer.donmccurdy.com/ @@ -47,14 +48,15 @@ GltfFileWriter::GltfFileWriter(MeshResultContext &resultContext, int bufferViewIndex = 0; int bufferViewFromOffset; + JointNodeTree tailNodeTree(resultRigBones); + const auto &boneNodes = tailNodeTree.nodes(); + m_json["asset"]["version"] = "2.0"; m_json["asset"]["generator"] = APP_NAME " " APP_HUMAN_VER; m_json["scenes"][0]["nodes"] = {0}; if (resultRigBones && resultRigWeights && !resultRigBones->empty()) { - - calculateMatrices(resultRigBones); - + constexpr int skeletonNodeStartIndex = 2; m_json["nodes"][0]["children"] = { @@ -66,25 +68,25 @@ GltfFileWriter::GltfFileWriter(MeshResultContext &resultContext, m_json["nodes"][1]["skin"] = 0; m_json["skins"][0]["joints"] = {}; - for (size_t i = 0; i < m_boneNodes.size(); i++) { + for (size_t i = 0; i < boneNodes.size(); i++) { m_json["skins"][0]["joints"] += skeletonNodeStartIndex + i; - m_json["nodes"][skeletonNodeStartIndex + i]["name"] = m_boneNodes[i].name.toUtf8().constData(); + m_json["nodes"][skeletonNodeStartIndex + i]["name"] = boneNodes[i].name.toUtf8().constData(); m_json["nodes"][skeletonNodeStartIndex + i]["translation"] = { - m_boneNodes[i].translation.x(), - m_boneNodes[i].translation.y(), - m_boneNodes[i].translation.z() + boneNodes[i].translation.x(), + boneNodes[i].translation.y(), + boneNodes[i].translation.z() }; m_json["nodes"][skeletonNodeStartIndex + i]["rotation"] = { - m_boneNodes[i].rotation.x(), - m_boneNodes[i].rotation.y(), - m_boneNodes[i].rotation.z(), - m_boneNodes[i].rotation.scalar() + boneNodes[i].rotation.x(), + boneNodes[i].rotation.y(), + boneNodes[i].rotation.z(), + boneNodes[i].rotation.scalar() }; - if (!m_boneNodes[i].children.empty()) { + if (!boneNodes[i].children.empty()) { m_json["nodes"][skeletonNodeStartIndex + i]["children"] = {}; - for (const auto &it: m_boneNodes[i].children) { + for (const auto &it: boneNodes[i].children) { m_json["nodes"][skeletonNodeStartIndex + i]["children"] += skeletonNodeStartIndex + it; } } @@ -95,21 +97,21 @@ GltfFileWriter::GltfFileWriter(MeshResultContext &resultContext, bufferViewFromOffset = (int)binaries.size(); m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; - for (auto i = 0u; i < m_boneNodes.size(); i++) { - const float *floatArray = m_boneNodes[i].inverseBindMatrix.constData(); + for (auto i = 0u; i < boneNodes.size(); i++) { + const float *floatArray = boneNodes[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)m_boneNodes.size() * 16 * sizeof(float) == binaries.size() - bufferViewFromOffset); + Q_ASSERT((int)boneNodes.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; m_json["accessors"][bufferViewIndex]["byteOffset"] = 0; m_json["accessors"][bufferViewIndex]["componentType"] = 5126; - m_json["accessors"][bufferViewIndex]["count"] = m_boneNodes.size(); + m_json["accessors"][bufferViewIndex]["count"] = boneNodes.size(); m_json["accessors"][bufferViewIndex]["type"] = "MAT4"; bufferViewIndex++; } else { @@ -312,7 +314,7 @@ GltfFileWriter::GltfFileWriter(MeshResultContext &resultContext, auto findWeight = resultRigWeights->find(oldIndex); if (findWeight != resultRigWeights->end()) { for (; i < MAX_WEIGHT_NUM; i++) { - float weight = (quint16)findWeight->second.boneIndicies[i]; + float weight = (float)findWeight->second.boneWeights[i]; stream << (float)weight; if (m_enableComment) weightList.append(QString("%1").arg(QString::number((float)weight))); @@ -358,37 +360,3 @@ bool GltfFileWriter::save() file.write(QString::fromStdString(m_json.dump(4)).toUtf8()); return true; } - -void GltfFileWriter::calculateMatrices(const std::vector *resultRigBones) -{ - if (nullptr == resultRigBones) - return; - - m_boneNodes.resize(resultRigBones->size()); - - m_boneNodes[0].parentIndex = -1; - for (decltype(resultRigBones->size()) i = 0; i < resultRigBones->size(); i++) { - const auto &bone = (*resultRigBones)[i]; - auto &node = m_boneNodes[i]; - node.name = bone.name; - node.position = bone.tailPosition; - node.children = bone.children; - for (const auto &childIndex: bone.children) - m_boneNodes[childIndex].parentIndex = i; - } - - for (decltype(resultRigBones->size()) i = 0; i < resultRigBones->size(); i++) { - const auto &bone = (*resultRigBones)[i]; - QMatrix4x4 parentBindMatrix; - auto &node = m_boneNodes[i]; - node.translation = bone.tailPosition - bone.headPosition; - if (node.parentIndex != -1) { - const auto &parent = m_boneNodes[node.parentIndex]; - parentBindMatrix = parent.bindMatrix; - } - QMatrix4x4 translateMatrix; - translateMatrix.translate(node.translation); - node.bindMatrix = parentBindMatrix * translateMatrix; - node.inverseBindMatrix = node.bindMatrix.inverted(); - } -} diff --git a/src/gltffile.h b/src/gltffile.h index 9286781a..8ccafc7c 100644 --- a/src/gltffile.h +++ b/src/gltffile.h @@ -10,18 +10,6 @@ #include "json.hpp" #include "skeletondocument.h" -struct GltfNode -{ - int parentIndex; - QString name; - QVector3D position; - QVector3D translation; - QQuaternion rotation; - QMatrix4x4 bindMatrix; - QMatrix4x4 inverseBindMatrix; - std::vector children; -}; - class GltfFileWriter : public QObject { Q_OBJECT @@ -33,14 +21,12 @@ public: bool save(); const QString &textureFilenameInGltf(); private: - void calculateMatrices(const std::vector *resultRigBones); QString m_filename; QString m_textureFilename; bool m_outputNormal; bool m_outputAnimation; bool m_outputUv; bool m_testOutputAsWhole; - std::vector m_boneNodes; private: nlohmann::json m_json; public: diff --git a/src/jointnodetree.cpp b/src/jointnodetree.cpp new file mode 100644 index 00000000..0dd31d01 --- /dev/null +++ b/src/jointnodetree.cpp @@ -0,0 +1,74 @@ +#include "jointnodetree.h" + +const std::vector &JointNodeTree::nodes() const +{ + return m_boneNodes; +} + +void JointNodeTree::updateRotation(int index, QQuaternion rotation) +{ + m_boneNodes[index].rotation = rotation; +} + +void JointNodeTree::reset() +{ + for (auto &node: m_boneNodes) { + node.rotation = QQuaternion(); + } +} + +void JointNodeTree::recalculateTransformMatrices() +{ + for (decltype(m_boneNodes.size()) i = 0; i < m_boneNodes.size(); i++) { + QMatrix4x4 parentTransformMatrix; + auto &node = m_boneNodes[i]; + if (node.parentIndex != -1) { + const auto &parent = m_boneNodes[node.parentIndex]; + parentTransformMatrix = parent.transformMatrix; + } + QMatrix4x4 translateMatrix; + translateMatrix.translate(node.translation); + QMatrix4x4 rotationMatrix; + rotationMatrix.rotate(node.rotation); + node.transformMatrix = parentTransformMatrix * translateMatrix * rotationMatrix; + } + for (decltype(m_boneNodes.size()) i = 0; i < m_boneNodes.size(); i++) { + auto &node = m_boneNodes[i]; + node.transformMatrix *= node.inverseBindMatrix; + } +} + +JointNodeTree::JointNodeTree(const std::vector *resultRigBones) +{ + if (nullptr == resultRigBones || resultRigBones->empty()) + return; + + m_boneNodes.resize(resultRigBones->size()); + + m_boneNodes[0].parentIndex = -1; + for (decltype(resultRigBones->size()) i = 0; i < resultRigBones->size(); i++) { + const auto &bone = (*resultRigBones)[i]; + auto &node = m_boneNodes[i]; + node.name = bone.name; + node.position = bone.headPosition; + node.children = bone.children; + for (const auto &childIndex: bone.children) + m_boneNodes[childIndex].parentIndex = i; + } + + for (decltype(resultRigBones->size()) i = 0; i < resultRigBones->size(); i++) { + QMatrix4x4 parentTransformMatrix; + auto &node = m_boneNodes[i]; + if (node.parentIndex != -1) { + const auto &parent = m_boneNodes[node.parentIndex]; + parentTransformMatrix = parent.transformMatrix; + node.translation = node.position - parent.position; + } else { + node.translation = node.position; + } + QMatrix4x4 translateMatrix; + translateMatrix.translate(node.translation); + node.transformMatrix = parentTransformMatrix * translateMatrix; + node.inverseBindMatrix = node.transformMatrix.inverted(); + } +} diff --git a/src/jointnodetree.h b/src/jointnodetree.h new file mode 100644 index 00000000..41ee9a81 --- /dev/null +++ b/src/jointnodetree.h @@ -0,0 +1,32 @@ +#ifndef JOINT_NODE_TREE_H +#define JOINT_NODE_TREE_H +#include +#include +#include +#include "autorigger.h" + +struct JointNode +{ + int parentIndex; + QString name; + QVector3D position; + QVector3D translation; + QQuaternion rotation; + QMatrix4x4 transformMatrix; + QMatrix4x4 inverseBindMatrix; + std::vector children; +}; + +class JointNodeTree +{ +public: + const std::vector &nodes() const; + JointNodeTree(const std::vector *resultRigBones); + void updateRotation(int index, QQuaternion rotation); + void reset(); + void recalculateTransformMatrices(); +private: + std::vector m_boneNodes; +}; + +#endif diff --git a/src/posemeshcreator.cpp b/src/posemeshcreator.cpp new file mode 100644 index 00000000..683db135 --- /dev/null +++ b/src/posemeshcreator.cpp @@ -0,0 +1,47 @@ +#include +#include "posemeshcreator.h" +#include "skinnedmeshcreator.h" + +PoseMeshCreator::PoseMeshCreator(const Poser &poser, + const MeshResultContext &meshResultContext, + const std::map &resultWeights) : + m_resultNodes(poser.resultNodes()), + m_meshResultContext(meshResultContext), + m_resultWeights(resultWeights) +{ +} + +PoseMeshCreator::~PoseMeshCreator() +{ + delete m_resultMesh; +} + +MeshLoader *PoseMeshCreator::takeResultMesh() +{ + MeshLoader *resultMesh = m_resultMesh; + m_resultMesh = nullptr; + return resultMesh; +} + +void PoseMeshCreator::createMesh() +{ + SkinnedMeshCreator skinnedMeshCreator(m_meshResultContext, m_resultWeights); + + std::vector matricies; + matricies.resize(m_resultNodes.size()); + for (decltype(m_resultNodes.size()) i = 0; i < m_resultNodes.size(); i++) { + const auto &node = m_resultNodes[i]; + matricies[i] = node.transformMatrix; + } + + delete m_resultMesh; + m_resultMesh = skinnedMeshCreator.createMeshFromTransform(matricies); +} + +void PoseMeshCreator::process() +{ + createMesh(); + + this->moveToThread(QGuiApplication::instance()->thread()); + emit finished(); +} diff --git a/src/posemeshcreator.h b/src/posemeshcreator.h new file mode 100644 index 00000000..99f7878d --- /dev/null +++ b/src/posemeshcreator.h @@ -0,0 +1,30 @@ +#ifndef POSE_MESH_CREATOR_H +#define POSE_MESH_CREATOR_H +#include +#include "poser.h" +#include "meshloader.h" +#include "jointnodetree.h" +#include "meshresultcontext.h" + +class PoseMeshCreator : public QObject +{ + Q_OBJECT +signals: + void finished(); +public: + PoseMeshCreator(const Poser &poser, + const MeshResultContext &meshResultContext, + const std::map &resultWeights); + ~PoseMeshCreator(); + void createMesh(); + MeshLoader *takeResultMesh(); +public slots: + void process(); +private: + std::vector m_resultNodes; + MeshResultContext m_meshResultContext; + std::map m_resultWeights; + MeshLoader *m_resultMesh = nullptr; +}; + +#endif diff --git a/src/posepreviewmanager.cpp b/src/posepreviewmanager.cpp new file mode 100644 index 00000000..cb148544 --- /dev/null +++ b/src/posepreviewmanager.cpp @@ -0,0 +1,60 @@ +#include +#include +#include "posepreviewmanager.h" + +PosePreviewManager::PosePreviewManager() +{ +} + +PosePreviewManager::~PosePreviewManager() +{ + delete m_previewMesh; +} + +bool PosePreviewManager::isRendering() +{ + return nullptr != m_poseMeshCreator; +} + +bool PosePreviewManager::postUpdate(const Poser &poser, + const MeshResultContext &meshResultContext, + const std::map &resultWeights) +{ + if (nullptr != m_poseMeshCreator) + return false; + + qDebug() << "Pose mesh generating.."; + + QThread *thread = new QThread; + m_poseMeshCreator = new PoseMeshCreator(poser, meshResultContext, resultWeights); + m_poseMeshCreator->moveToThread(thread); + connect(thread, &QThread::started, m_poseMeshCreator, &PoseMeshCreator::process); + connect(m_poseMeshCreator, &PoseMeshCreator::finished, this, &PosePreviewManager::poseMeshReady); + connect(m_poseMeshCreator, &PoseMeshCreator::finished, thread, &QThread::quit); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); + + return true; +} + +MeshLoader *PosePreviewManager::takeResultPreviewMesh() +{ + if (nullptr == m_previewMesh) + return nullptr; + return new MeshLoader(*m_previewMesh); +} + +void PosePreviewManager::poseMeshReady() +{ + delete m_previewMesh; + m_previewMesh = m_poseMeshCreator->takeResultMesh(); + + emit resultPreviewMeshChanged(); + + qDebug() << "Pose mesh generation done"; + + delete m_poseMeshCreator; + m_poseMeshCreator = nullptr; + + emit renderDone(); +} diff --git a/src/posepreviewmanager.h b/src/posepreviewmanager.h new file mode 100644 index 00000000..46a96abf --- /dev/null +++ b/src/posepreviewmanager.h @@ -0,0 +1,30 @@ +#ifndef POSE_PREVIEW_MANAGER_H +#define POSE_PREVIEW_MANAGER_H +#include +#include "skeletondocument.h" +#include "poser.h" +#include "posemeshcreator.h" +#include "meshloader.h" + +class PosePreviewManager : public QObject +{ + Q_OBJECT +public: + PosePreviewManager(); + ~PosePreviewManager(); + bool isRendering(); + bool postUpdate(const Poser &poser, + const MeshResultContext &meshResultContext, + const std::map &resultWeights); + MeshLoader *takeResultPreviewMesh(); +private slots: + void poseMeshReady(); +signals: + void resultPreviewMeshChanged(); + void renderDone(); +private: + PoseMeshCreator *m_poseMeshCreator = nullptr; + MeshLoader *m_previewMesh = nullptr; +}; + +#endif diff --git a/src/poser.cpp b/src/poser.cpp new file mode 100644 index 00000000..8c462b10 --- /dev/null +++ b/src/poser.cpp @@ -0,0 +1,56 @@ +#include +#include "poser.h" + +Poser::Poser(const std::vector &bones) : + m_bones(bones), + m_jointNodeTree(&bones) +{ + for (decltype(m_bones.size()) i = 0; i < m_bones.size(); i++) { + m_boneNameToIndexMap[m_bones[i].name] = i; + } +} + +Poser::~Poser() +{ +} + +int Poser::findBoneIndex(const QString &name) +{ + auto findResult = m_boneNameToIndexMap.find(name); + if (findResult == m_boneNameToIndexMap.end()) + return -1; + return findResult->second; +} + +const AutoRiggerBone *Poser::findBone(const QString &name) +{ + auto findResult = m_boneNameToIndexMap.find(name); + if (findResult == m_boneNameToIndexMap.end()) + return nullptr; + return &m_bones[findResult->second]; +} + +const std::vector &Poser::bones() const +{ + return m_bones; +} + +const std::vector &Poser::resultNodes() const +{ + return m_jointNodeTree.nodes(); +} + +void Poser::commit() +{ + m_jointNodeTree.recalculateTransformMatrices(); +} + +void Poser::reset() +{ + m_jointNodeTree.reset(); +} + +std::map> &Poser::parameters() +{ + return m_parameters; +} diff --git a/src/poser.h b/src/poser.h new file mode 100644 index 00000000..953edbf8 --- /dev/null +++ b/src/poser.h @@ -0,0 +1,28 @@ +#ifndef POSER_H +#define POSER_H +#include +#include "autorigger.h" +#include "jointnodetree.h" +#include "dust3dutil.h" + +class Poser : public QObject +{ + Q_OBJECT +public: + Poser(const std::vector &bones); + ~Poser(); + const AutoRiggerBone *findBone(const QString &name); + int findBoneIndex(const QString &name); + const std::vector &bones() const; + const std::vector &resultNodes() const; + std::map> ¶meters(); + void commit(); + void reset(); +protected: + std::vector m_bones; + std::map m_boneNameToIndexMap; + JointNodeTree m_jointNodeTree; + std::map> m_parameters; +}; + +#endif diff --git a/src/riggenerator.cpp b/src/riggenerator.cpp index af9cf34c..56d34805 100644 --- a/src/riggenerator.cpp +++ b/src/riggenerator.cpp @@ -18,6 +18,13 @@ RigGenerator::~RigGenerator() delete m_resultWeights; } +MeshResultContext *RigGenerator::takeMeshResultContext() +{ + MeshResultContext *resultContext = m_meshResultContext; + m_meshResultContext = nullptr; + return resultContext; +} + std::vector *RigGenerator::takeResultBones() { std::vector *resultBones = m_resultBones; @@ -114,18 +121,17 @@ void RigGenerator::process() m_resultBones = new std::vector; *m_resultBones = resultBones; - for (size_t vertexIndex = 0; vertexIndex < inputVerticesColors.size(); vertexIndex++) { - auto findResult = resultWeights.find((int)vertexIndex); + for (const auto &weightItem: resultWeights) { + size_t vertexIndex = weightItem.first; + const auto &weight = weightItem.second; int blendR = 0, blendG = 0, blendB = 0; - if (findResult != resultWeights.end()) { - for (int i = 0; i < 4; i++) { - int boneIndex = findResult->second.boneIndicies[i]; - if (boneIndex > 0) { - const auto &bone = resultBones[boneIndex]; - blendR += bone.color.red() * findResult->second.boneWeights[i]; - blendG += bone.color.green() * findResult->second.boneWeights[i]; - blendB += bone.color.blue() * findResult->second.boneWeights[i]; - } + for (int i = 0; i < 4; i++) { + int boneIndex = weight.boneIndicies[i]; + if (boneIndex > 0) { + const auto &bone = resultBones[boneIndex]; + blendR += bone.color.red() * weight.boneWeights[i]; + blendG += bone.color.green() * weight.boneWeights[i]; + blendB += bone.color.blue() * weight.boneWeights[i]; } } QColor blendColor = QColor(blendR, blendG, blendB, 255); @@ -135,14 +141,15 @@ void RigGenerator::process() // Smooth normals - std::map vertexNormalMap; + std::vector vertexNormalMap; + vertexNormalMap.resize(inputVerticesPositions.size()); for (size_t triangleIndex = 0; triangleIndex < m_meshResultContext->triangles.size(); triangleIndex++) { const auto &sourceTriangle = m_meshResultContext->triangles[triangleIndex]; for (int i = 0; i < 3; i++) vertexNormalMap[sourceTriangle.indicies[i]] += sourceTriangle.normal; } for (auto &item: vertexNormalMap) - item.second.normalize(); + item.normalize(); // Create mesh for demo diff --git a/src/riggenerator.h b/src/riggenerator.h index 15eda13a..ae8c8090 100644 --- a/src/riggenerator.h +++ b/src/riggenerator.h @@ -18,6 +18,7 @@ public: std::map *takeResultWeights(); const std::vector &missingMarkNames(); const std::vector &errorMarkNames(); + MeshResultContext *takeMeshResultContext(); signals: void finished(); public slots: diff --git a/src/rigwidget.cpp b/src/rigwidget.cpp index 9f0a389d..ae08e76d 100644 --- a/src/rigwidget.cpp +++ b/src/rigwidget.cpp @@ -1,8 +1,10 @@ #include #include +#include #include "rigwidget.h" #include "rigtype.h" #include "infolabel.h" +#include "theme.h" RigWidget::RigWidget(const SkeletonDocument *document, QWidget *parent) : QWidget(parent), @@ -17,7 +19,15 @@ RigWidget::RigWidget(const SkeletonDocument *document, QWidget *parent) : m_rigTypeBox->addItem(RigTypeToDispName(rigType)); } - formLayout->addRow(tr("Type"), m_rigTypeBox); + //m_poseCheckButton = new QPushButton(QChar(fa::child)); + //Theme::initAwesomeButton(m_poseCheckButton); + //m_poseCheckButton->hide(); + + QHBoxLayout *controlsLayout = new QHBoxLayout; + controlsLayout->addWidget(m_rigTypeBox); + //controlsLayout->addWidget(m_poseCheckButton); + + formLayout->addRow(tr("Type"), controlsLayout); m_rigTypeBox->setCurrentIndex((int)m_document->rigType); @@ -28,6 +38,8 @@ RigWidget::RigWidget(const SkeletonDocument *document, QWidget *parent) : m_rigWeightRenderWidget = new ModelWidget(this); m_rigWeightRenderWidget->setMinimumSize(128, 128); + //m_rigWeightRenderWidget->resize(256, 256); + //m_rigWeightRenderWidget->move(-64, 0); m_rigWeightRenderWidget->setXRotation(0); m_rigWeightRenderWidget->setYRotation(0); m_rigWeightRenderWidget->setZRotation(0); @@ -50,6 +62,7 @@ RigWidget::RigWidget(const SkeletonDocument *document, QWidget *parent) : void RigWidget::rigTypeChanged() { + //m_poseCheckButton->setVisible(RigType::None != m_document->rigType); m_rigTypeBox->setCurrentIndex((int)m_document->rigType); } diff --git a/src/rigwidget.h b/src/rigwidget.h index 0ce5fca1..4cd40df4 100644 --- a/src/rigwidget.h +++ b/src/rigwidget.h @@ -2,6 +2,7 @@ #define RIG_WIDGET_H #include #include +#include #include "skeletondocument.h" #include "rigtype.h" #include "modelwidget.h" diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp index d205046c..9175d84f 100644 --- a/src/skeletondocument.cpp +++ b/src/skeletondocument.cpp @@ -50,7 +50,8 @@ SkeletonDocument::SkeletonDocument() : m_resultRigWeightMesh(nullptr), m_resultRigBones(nullptr), m_resultRigWeights(nullptr), - m_isRigObsolete(false) + m_isRigObsolete(false), + m_riggedResultContext(new MeshResultContext) { } @@ -2263,6 +2264,11 @@ void SkeletonDocument::rigReady() m_resultRigMissingMarkNames = m_rigGenerator->missingMarkNames(); m_resultRigErrorMarkNames = m_rigGenerator->errorMarkNames(); + delete m_riggedResultContext; + m_riggedResultContext = m_rigGenerator->takeMeshResultContext(); + if (nullptr == m_riggedResultContext) + m_riggedResultContext = new MeshResultContext; + delete m_rigGenerator; m_rigGenerator = nullptr; @@ -2277,12 +2283,12 @@ void SkeletonDocument::rigReady() } } -const std::vector *SkeletonDocument::resultRigBones() +const std::vector *SkeletonDocument::resultRigBones() const { return m_resultRigBones; } -const std::map *SkeletonDocument::resultRigWeights() +const std::map *SkeletonDocument::resultRigWeights() const { return m_resultRigWeights; } @@ -2328,3 +2334,8 @@ const std::vector &SkeletonDocument::resultRigErrorMarkNames() const { return m_resultRigErrorMarkNames; } + +const MeshResultContext &SkeletonDocument::currentRiggedResultContext() const +{ + return *m_riggedResultContext; +} diff --git a/src/skeletondocument.h b/src/skeletondocument.h index fd3ed727..3b75d26f 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -427,8 +427,8 @@ public: MeshLoader *takeResultMesh(); MeshLoader *takeResultTextureMesh(); MeshLoader *takeResultRigWeightMesh(); - const std::vector *resultRigBones(); - const std::map *resultRigWeights(); + const std::vector *resultRigBones() const; + const std::map *resultRigWeights() const; void updateTurnaround(const QImage &image); void setSharedContextWidget(QOpenGLWidget *sharedContextWidget); bool hasPastableContentInClipboard() const; @@ -445,6 +445,7 @@ public: void collectComponentDescendantComponents(QUuid componentId, std::vector &componentIds) const; const std::vector &resultRigMissingMarkNames() const; const std::vector &resultRigErrorMarkNames() const; + const MeshResultContext ¤tRiggedResultContext() const; public slots: void removeNode(QUuid nodeId); void removeEdge(QUuid edgeId); @@ -563,14 +564,15 @@ private: // need initialize MeshLoader *m_resultRigWeightMesh; std::vector *m_resultRigBones; std::map *m_resultRigWeights; + MeshResultContext *m_riggedResultContext; bool m_isRigObsolete; - std::vector m_resultRigMissingMarkNames; - std::vector m_resultRigErrorMarkNames; private: static unsigned long m_maxSnapshot; std::deque m_undoItems; std::deque m_redoItems; GeneratedCacheContext m_generatedCacheContext; + std::vector m_resultRigMissingMarkNames; + std::vector m_resultRigErrorMarkNames; }; #endif diff --git a/src/skeletondocumentwindow.cpp b/src/skeletondocumentwindow.cpp index 8ca86e28..0ace9b5a 100644 --- a/src/skeletondocumentwindow.cpp +++ b/src/skeletondocumentwindow.cpp @@ -31,6 +31,7 @@ #include "rigwidget.h" #include "modelofflinerender.h" #include "markiconcreator.h" +#include "tetrapodposeeditwidget.h" int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16; int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16; @@ -209,7 +210,6 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : partTreeWidget->setGraphicsFunctions(graphicsWidget); partTreeDocker->setWidget(partTreeWidget); addDockWidget(Qt::RightDockWidgetArea, partTreeDocker); - //partTreeDocker->hide(); QDockWidget *rigDocker = new QDockWidget(tr("Rig"), this); rigDocker->setAllowedAreas(Qt::RightDockWidgetArea); @@ -217,7 +217,16 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : rigDocker->setWidget(rigWidget); addDockWidget(Qt::RightDockWidgetArea, rigDocker); + //QDockWidget *animationDocker = new QDockWidget(tr("Animation"), this); + //animationDocker->setAllowedAreas(Qt::RightDockWidgetArea); + //TetrapodPoseEditWidget *tetrapodPoseEditWidget = new TetrapodPoseEditWidget(m_document, animationDocker); + //animationDocker->setWidget(tetrapodPoseEditWidget); + //AnimationListWidget *animationListWidget = new AnimationListWidget(m_document, animationDocker); + //animationDocker->setWidget(animationListWidget); + //addDockWidget(Qt::RightDockWidgetArea, animationDocker); + tabifyDockWidget(partTreeDocker, rigDocker); + //tabifyDockWidget(rigDocker, animationDocker); partTreeDocker->raise(); @@ -495,6 +504,13 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : }); m_windowMenu->addAction(m_showRigAction); + //m_showAnimationAction = new QAction(tr("Animation"), this); + //connect(m_showAnimationAction, &QAction::triggered, [=]() { + // animationDocker->show(); + // animationDocker->raise(); + //}); + //m_windowMenu->addAction(m_showAnimationAction); + m_showDebugDialogAction = new QAction(tr("Debug"), this); connect(m_showDebugDialogAction, &QAction::triggered, g_logBrowser, &LogBrowser::showDialog); m_windowMenu->addAction(m_showDebugDialogAction); @@ -747,6 +763,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : rigWidget->rigWeightRenderWidget()->update(); } }); + + //connect(m_document, &SkeletonDocument::resultRigChanged, tetrapodPoseEditWidget, &TetrapodPoseEditWidget::updatePreview); connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady); diff --git a/src/skeletondocumentwindow.h b/src/skeletondocumentwindow.h index 38c5d5ee..ebfa8936 100644 --- a/src/skeletondocumentwindow.h +++ b/src/skeletondocumentwindow.h @@ -129,6 +129,7 @@ private: QAction *m_showPartsListAction; QAction *m_showDebugDialogAction; QAction *m_showRigAction; + QAction *m_showAnimationAction; QMenu *m_helpMenu; QAction *m_viewSourceAction; diff --git a/src/skinnedmeshcreator.cpp b/src/skinnedmeshcreator.cpp new file mode 100644 index 00000000..01424718 --- /dev/null +++ b/src/skinnedmeshcreator.cpp @@ -0,0 +1,69 @@ +#include "skinnedmeshcreator.h" + +SkinnedMeshCreator::SkinnedMeshCreator(const MeshResultContext &meshResultContext, + const std::map &resultWeights) : + m_meshResultContext(meshResultContext), + m_resultWeights(resultWeights) +{ + for (const auto &vertex: m_meshResultContext.vertices) { + m_verticesBindPositions.push_back(vertex.position); + } + + m_verticesBindNormals.resize(m_meshResultContext.vertices.size()); + for (size_t triangleIndex = 0; triangleIndex < m_meshResultContext.triangles.size(); triangleIndex++) { + const auto &sourceTriangle = m_meshResultContext.triangles[triangleIndex]; + for (int i = 0; i < 3; i++) + m_verticesBindNormals[sourceTriangle.indicies[i]] += sourceTriangle.normal; + } + for (auto &item: m_verticesBindNormals) + item.normalize(); +} + +MeshLoader *SkinnedMeshCreator::createMeshFromTransform(const std::vector &matricies) +{ + std::vector transformedPositions = m_verticesBindPositions; + std::vector transformedPoseNormals = m_verticesBindNormals; + + if (!matricies.empty()) { + for (const auto &weightItem: m_resultWeights) { + size_t vertexIndex = weightItem.first; + const auto &weight = weightItem.second; + QMatrix4x4 mixedMatrix; + transformedPositions[vertexIndex] = QVector3D(); + transformedPoseNormals[vertexIndex] = QVector3D(); + for (int i = 0; i < 4; i++) { + float factor = weight.boneWeights[i]; + if (factor > 0) { + transformedPositions[vertexIndex] += matricies[weight.boneIndicies[i]] * m_verticesBindPositions[vertexIndex] * factor; + transformedPoseNormals[vertexIndex] += matricies[weight.boneIndicies[i]] * m_verticesBindNormals[vertexIndex] * factor; + } + } + } + } + + Vertex *triangleVertices = new Vertex[m_meshResultContext.triangles.size() * 3]; + int triangleVerticesNum = 0; + for (size_t triangleIndex = 0; triangleIndex < m_meshResultContext.triangles.size(); triangleIndex++) { + const auto &sourceTriangle = m_meshResultContext.triangles[triangleIndex]; + QColor triangleColor = m_meshResultContext.triangleColors()[triangleIndex]; + for (int i = 0; i < 3; i++) { + Vertex ¤tVertex = triangleVertices[triangleVerticesNum++]; + const auto &sourcePosition = transformedPositions[sourceTriangle.indicies[i]]; + const auto &sourceColor = triangleColor; + const auto &sourceNormal = transformedPoseNormals[sourceTriangle.indicies[i]]; + currentVertex.posX = sourcePosition.x(); + currentVertex.posY = sourcePosition.y(); + currentVertex.posZ = sourcePosition.z(); + currentVertex.texU = 0; + currentVertex.texV = 0; + currentVertex.colorR = sourceColor.redF(); + currentVertex.colorG = sourceColor.greenF(); + currentVertex.colorB = sourceColor.blueF(); + currentVertex.normX = sourceNormal.x(); + currentVertex.normY = sourceNormal.y(); + currentVertex.normZ = sourceNormal.z(); + } + } + + return new MeshLoader(triangleVertices, triangleVerticesNum); +} diff --git a/src/skinnedmeshcreator.h b/src/skinnedmeshcreator.h new file mode 100644 index 00000000..4a2d27ce --- /dev/null +++ b/src/skinnedmeshcreator.h @@ -0,0 +1,23 @@ +#ifndef SKINNED_MESH_CREATOR_H +#define SKINNED_MESH_CREATOR_H +#include +#include +#include +#include "meshloader.h" +#include "meshresultcontext.h" +#include "jointnodetree.h" + +class SkinnedMeshCreator +{ +public: + SkinnedMeshCreator(const MeshResultContext &meshResultContext, + const std::map &resultWeights); + MeshLoader *createMeshFromTransform(const std::vector &matricies); +private: + MeshResultContext m_meshResultContext; + std::map m_resultWeights; + std::vector m_verticesBindPositions; + std::vector m_verticesBindNormals; +}; + +#endif diff --git a/src/tetrapodposeeditwidget.cpp b/src/tetrapodposeeditwidget.cpp new file mode 100644 index 00000000..74f3acc7 --- /dev/null +++ b/src/tetrapodposeeditwidget.cpp @@ -0,0 +1,205 @@ +#include +#include +#include +#include +#include +#include "theme.h" +#include "tetrapodposeeditwidget.h" +#include "floatnumberwidget.h" + +TetrapodPoseEditWidget::TetrapodPoseEditWidget(const SkeletonDocument *document, QWidget *parent) : + QWidget(parent), + m_document(document) +{ + m_posePreviewManager = new PosePreviewManager(); + connect(m_posePreviewManager, &PosePreviewManager::renderDone, [=]() { + if (m_isPreviewDirty) + updatePreview(); + }); + + std::map> buttons; + buttons["Head"] = std::make_tuple(new QPushButton(tr("Head")), PopupWidgetType::PitchYawRoll); + buttons["Neck"] = std::make_tuple(new QPushButton(tr("Neck")), PopupWidgetType::PitchYawRoll); + buttons["RightUpperArm"] = std::make_tuple(new QPushButton(tr("UpperArm")), PopupWidgetType::PitchYawRoll); + buttons["RightLowerArm"] = std::make_tuple(new QPushButton(tr("LowerArm")), PopupWidgetType::Intersection); + buttons["RightHand"] = std::make_tuple(new QPushButton(tr("Hand")), PopupWidgetType::Intersection); + buttons["LeftUpperArm"] = std::make_tuple(new QPushButton(tr("UpperArm")), PopupWidgetType::PitchYawRoll); + buttons["LeftLowerArm"] = std::make_tuple(new QPushButton(tr("LowerArm")), PopupWidgetType::Intersection); + buttons["LeftHand"] = std::make_tuple(new QPushButton(tr("Hand")), PopupWidgetType::Intersection); + buttons["Chest"] = std::make_tuple(new QPushButton(tr("Chest")), PopupWidgetType::PitchYawRoll); + buttons["Spine"] = std::make_tuple(new QPushButton(tr("Spine")), PopupWidgetType::PitchYawRoll); + buttons["RightUpperLeg"] = std::make_tuple(new QPushButton(tr("UpperLeg")), PopupWidgetType::PitchYawRoll); + buttons["RightLowerLeg"] = std::make_tuple(new QPushButton(tr("LowerLeg")), PopupWidgetType::Intersection); + buttons["RightFoot"] = std::make_tuple(new QPushButton(tr("Foot")), PopupWidgetType::Intersection); + buttons["LeftUpperLeg"] = std::make_tuple(new QPushButton(tr("UpperLeg")), PopupWidgetType::PitchYawRoll); + buttons["LeftLowerLeg"] = std::make_tuple(new QPushButton(tr("LowerLeg")), PopupWidgetType::Intersection); + buttons["LeftFoot"] = std::make_tuple(new QPushButton(tr("Foot")), PopupWidgetType::Intersection); + + QGridLayout *marksContainerLayout = new QGridLayout; + marksContainerLayout->setContentsMargins(0, 0, 0, 0); + marksContainerLayout->setSpacing(0); + marksContainerLayout->addWidget(std::get<0>(buttons["Head"]), 0, 1); + marksContainerLayout->addWidget(std::get<0>(buttons["Neck"]), 1, 1); + marksContainerLayout->addWidget(std::get<0>(buttons["RightUpperArm"]), 1, 0); + marksContainerLayout->addWidget(std::get<0>(buttons["RightLowerArm"]), 2, 0); + marksContainerLayout->addWidget(std::get<0>(buttons["RightHand"]), 3, 0); + marksContainerLayout->addWidget(std::get<0>(buttons["LeftUpperArm"]), 1, 2); + marksContainerLayout->addWidget(std::get<0>(buttons["LeftLowerArm"]), 2, 2); + marksContainerLayout->addWidget(std::get<0>(buttons["LeftHand"]), 3, 2); + marksContainerLayout->addWidget(std::get<0>(buttons["Chest"]), 2, 1); + marksContainerLayout->addWidget(std::get<0>(buttons["Spine"]), 3, 1); + + QGridLayout *lowerMarksContainerLayout = new QGridLayout; + lowerMarksContainerLayout->setContentsMargins(0, 0, 0, 0); + lowerMarksContainerLayout->setSpacing(0); + lowerMarksContainerLayout->addWidget(std::get<0>(buttons["RightUpperLeg"]), 0, 1); + lowerMarksContainerLayout->addWidget(std::get<0>(buttons["RightLowerLeg"]), 1, 1); + lowerMarksContainerLayout->addWidget(std::get<0>(buttons["RightFoot"]), 2, 0); + lowerMarksContainerLayout->addWidget(std::get<0>(buttons["LeftUpperLeg"]), 0, 2); + lowerMarksContainerLayout->addWidget(std::get<0>(buttons["LeftLowerLeg"]), 1, 2); + lowerMarksContainerLayout->addWidget(std::get<0>(buttons["LeftFoot"]), 2, 3); + + QFont buttonFont; + buttonFont.setWeight(QFont::Light); + buttonFont.setPixelSize(7); + buttonFont.setBold(false); + for (const auto &item: buttons) { + QString boneName = item.first; + QPushButton *buttonWidget = std::get<0>(item.second); + PopupWidgetType widgetType = std::get<1>(item.second); + buttonWidget->setFont(buttonFont); + buttonWidget->setMaximumWidth(45); + connect(buttonWidget, &QPushButton::clicked, [this, boneName, widgetType]() { + emit showPopupAngleDialog(boneName, widgetType, mapFromGlobal(QCursor::pos())); + }); + } + + QVBoxLayout *layout = new QVBoxLayout; + layout->addLayout(marksContainerLayout); + layout->addLayout(lowerMarksContainerLayout); + layout->addStretch(); + + setLayout(layout); + + connect(m_document, &SkeletonDocument::resultRigChanged, this, &TetrapodPoseEditWidget::updatePreview); +} + +TetrapodPoseEditWidget::~TetrapodPoseEditWidget() +{ + delete m_posePreviewManager; +} + +void TetrapodPoseEditWidget::updatePreview() +{ + if (m_posePreviewManager->isRendering()) { + m_isPreviewDirty = true; + return; + } + + const std::vector *rigBones = m_document->resultRigBones(); + const std::map *rigWeights = m_document->resultRigWeights(); + + m_isPreviewDirty = false; + + if (nullptr == rigBones || nullptr == rigWeights) { + return; + } + + delete m_poser; + m_poser = new TetrapodPoser(*rigBones); + m_poser->parameters() = m_parameters; + m_poser->commit(); + m_posePreviewManager->postUpdate(*m_poser, m_document->currentRiggedResultContext(), *rigWeights); +} + +void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos) +{ + QMenu popupMenu; + + QWidget *popup = new QWidget; + + QVBoxLayout *layout = new QVBoxLayout; + + if (PopupWidgetType::PitchYawRoll == popupWidgetType) { + FloatNumberWidget *pitchWidget = new FloatNumberWidget; + pitchWidget->setItemName(tr("Pitch")); + pitchWidget->setRange(-180, 180); + pitchWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "pitch").toFloat()); + connect(pitchWidget, &FloatNumberWidget::valueChanged, [=](float value) { + m_parameters[boneName]["pitch"] = QString::number(value); + updatePreview(); + }); + QPushButton *pitchEraser = new QPushButton(QChar(fa::eraser)); + Theme::initAwesomeMiniButton(pitchEraser); + connect(pitchEraser, &QPushButton::clicked, [=]() { + pitchWidget->setValue(0.0); + }); + QHBoxLayout *pitchLayout = new QHBoxLayout; + pitchLayout->addWidget(pitchEraser); + pitchLayout->addWidget(pitchWidget); + layout->addLayout(pitchLayout); + + FloatNumberWidget *yawWidget = new FloatNumberWidget; + yawWidget->setItemName(tr("Yaw")); + yawWidget->setRange(-180, 180); + yawWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "yaw").toFloat()); + connect(yawWidget, &FloatNumberWidget::valueChanged, [=](float value) { + m_parameters[boneName]["yaw"] = QString::number(value); + updatePreview(); + }); + QPushButton *yawEraser = new QPushButton(QChar(fa::eraser)); + Theme::initAwesomeMiniButton(yawEraser); + connect(yawEraser, &QPushButton::clicked, [=]() { + yawWidget->setValue(0.0); + }); + QHBoxLayout *yawLayout = new QHBoxLayout; + yawLayout->addWidget(yawEraser); + yawLayout->addWidget(yawWidget); + layout->addLayout(yawLayout); + + FloatNumberWidget *rollWidget = new FloatNumberWidget; + rollWidget->setItemName(tr("Roll")); + rollWidget->setRange(-180, 180); + rollWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "roll").toFloat()); + connect(rollWidget, &FloatNumberWidget::valueChanged, [=](float value) { + m_parameters[boneName]["roll"] = QString::number(value); + updatePreview(); + }); + QPushButton *rollEraser = new QPushButton(QChar(fa::eraser)); + Theme::initAwesomeMiniButton(rollEraser); + connect(rollEraser, &QPushButton::clicked, [=]() { + rollWidget->setValue(0.0); + }); + QHBoxLayout *rollLayout = new QHBoxLayout; + rollLayout->addWidget(rollEraser); + rollLayout->addWidget(rollWidget); + layout->addLayout(rollLayout); + } else { + FloatNumberWidget *intersectionWidget = new FloatNumberWidget; + intersectionWidget->setItemName(tr("Intersection")); + intersectionWidget->setRange(-180, 180); + intersectionWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "intersection").toFloat()); + connect(intersectionWidget, &FloatNumberWidget::valueChanged, [=](float value) { + m_parameters[boneName]["intersection"] = QString::number(value); + updatePreview(); + }); + QPushButton *intersectionEraser = new QPushButton(QChar(fa::eraser)); + Theme::initAwesomeMiniButton(intersectionEraser); + connect(intersectionEraser, &QPushButton::clicked, [=]() { + intersectionWidget->setValue(0.0); + }); + QHBoxLayout *intersectionLayout = new QHBoxLayout; + intersectionLayout->addWidget(intersectionEraser); + intersectionLayout->addWidget(intersectionWidget); + layout->addLayout(intersectionLayout); + } + + popup->setLayout(layout); + + QWidgetAction action(this); + action.setDefaultWidget(popup); + + popupMenu.addAction(&action); + + popupMenu.exec(mapToGlobal(pos)); +} diff --git a/src/tetrapodposeeditwidget.h b/src/tetrapodposeeditwidget.h new file mode 100644 index 00000000..0e650cbf --- /dev/null +++ b/src/tetrapodposeeditwidget.h @@ -0,0 +1,33 @@ +#ifndef TETRAPOD_POSE_EDIT_WIDGET_H +#define TETRAPOD_POSE_EDIT_WIDGET_H +#include +#include +#include +#include "posepreviewmanager.h" +#include "tetrapodposer.h" +#include "skeletondocument.h" + +enum class PopupWidgetType +{ + PitchYawRoll, + Intersection +}; + +class TetrapodPoseEditWidget : public QWidget +{ + Q_OBJECT +public: + TetrapodPoseEditWidget(const SkeletonDocument *document, QWidget *parent=nullptr); + ~TetrapodPoseEditWidget(); +public slots: + void updatePreview(); + void showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos); +private: + const SkeletonDocument *m_document = nullptr; + PosePreviewManager *m_posePreviewManager = nullptr; + TetrapodPoser *m_poser = nullptr; + bool m_isPreviewDirty = false; + std::map> m_parameters; +}; + +#endif diff --git a/src/tetrapodposer.cpp b/src/tetrapodposer.cpp new file mode 100644 index 00000000..7291fe78 --- /dev/null +++ b/src/tetrapodposer.cpp @@ -0,0 +1,66 @@ +#include "tetrapodposer.h" + +TetrapodPoser::TetrapodPoser(const std::vector &bones) : + Poser(bones) +{ +} + +void TetrapodPoser::commit() +{ + for (const auto &item: parameters()) { + int boneIndex = findBoneIndex(item.first); + if (-1 == boneIndex) { + continue; + } + auto findPitchResult = item.second.find("pitch"); + auto findYawResult = item.second.find("yaw"); + auto findRollResult = item.second.find("roll"); + if (findPitchResult != item.second.end() || + findYawResult != item.second.end() || + findRollResult != item.second.end()) { + float yawAngle = valueOfKeyInMapOrEmpty(item.second, "yaw").toFloat(); + if (item.first.startsWith("Left")) { + yawAngle = -yawAngle; + } + QQuaternion rotation = QQuaternion::fromEulerAngles(valueOfKeyInMapOrEmpty(item.second, "pitch").toFloat(), + yawAngle, + valueOfKeyInMapOrEmpty(item.second, "roll").toFloat()); + m_jointNodeTree.updateRotation(boneIndex, rotation); + continue; + } + auto findIntersectionResult = item.second.find("intersection"); + if (findIntersectionResult != item.second.end()) { + float intersectionAngle = valueOfKeyInMapOrEmpty(item.second, "intersection").toFloat(); + const AutoRiggerBone &bone = bones()[boneIndex]; + if (bone.name == "LeftHand" || bone.name == "RightHand") { + QVector3D handDirection = bone.tailPosition - bone.headPosition; + QVector3D rotateAxis = QVector3D::crossProduct(handDirection, bone.name == "RightHand" ? QVector3D(1, 0, 0) : QVector3D(-1, 0, 0)).normalized(); + QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle); + m_jointNodeTree.updateRotation(boneIndex, rotation); + continue; + } else if (bone.name == "LeftLowerArm" || bone.name == "RightLowerArm") { + QVector3D lowerArmDirection = bone.tailPosition - bone.headPosition; + QVector3D rotateAxis = QVector3D::crossProduct(lowerArmDirection, QVector3D(0, 0, 1)).normalized(); + QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle); + m_jointNodeTree.updateRotation(boneIndex, rotation); + continue; + } else if (bone.name == "LeftFoot" || bone.name == "RightFoot") { + QVector3D footDirection = bone.tailPosition - bone.headPosition; + QVector3D rotateAxis = QVector3D::crossProduct(footDirection, QVector3D(0, 1, 0)).normalized(); + QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle); + m_jointNodeTree.updateRotation(boneIndex, rotation); + continue; + } else if (bone.name == "LeftLowerLeg" || bone.name == "RightLowerLeg") { + QVector3D lowerLegDirection = bone.tailPosition - bone.headPosition; + QVector3D rotateAxis = QVector3D::crossProduct(lowerLegDirection, QVector3D(0, 0, -1)).normalized(); + QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle); + m_jointNodeTree.updateRotation(boneIndex, rotation); + continue; + } + continue; + } + } + + Poser::commit(); +} + diff --git a/src/tetrapodposer.h b/src/tetrapodposer.h new file mode 100644 index 00000000..18219615 --- /dev/null +++ b/src/tetrapodposer.h @@ -0,0 +1,14 @@ +#ifndef TETRAPOD_POSER_H +#define TETRAPOD_POSER_H +#include "poser.h" + +class TetrapodPoser : public Poser +{ + Q_OBJECT +public: + TetrapodPoser(const std::vector &bones); +public: + void commit(); +}; + +#endif