diff --git a/dust3d.pro b/dust3d.pro index 3e748574..e1666491 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -131,6 +131,21 @@ HEADERS += src/skeletonbonemark.h SOURCES += src/intermediateboneremover.cpp HEADERS += src/intermediateboneremover.h +SOURCES += src/animationpanelwidget.cpp +HEADERS += src/animationpanelwidget.h + +SOURCES += src/rigcontroller.cpp +HEADERS += src/rigcontroller.h + +SOURCES += src/jointnodetree.cpp +HEADERS += src/jointnodetree.h + +SOURCES += src/animationclipgenerator.cpp +HEADERS += src/animationclipgenerator.h + +SOURCES += src/skinnedmesh.cpp +HEADERS += src/skinnedmesh.h + HEADERS += src/qtlightmapper.h SOURCES += src/main.cpp diff --git a/src/animationclipgenerator.cpp b/src/animationclipgenerator.cpp new file mode 100644 index 00000000..9cbdff15 --- /dev/null +++ b/src/animationclipgenerator.cpp @@ -0,0 +1,49 @@ +#include +#include "animationclipgenerator.h" +#include "skinnedmesh.h" + +AnimationClipGenerator::AnimationClipGenerator(const MeshResultContext &resultContext, + const QString &motionName, const std::map ¶meters) : + m_resultContext(resultContext), + m_motionName(motionName), + m_parameters(parameters) +{ +} + +AnimationClipGenerator::~AnimationClipGenerator() +{ + for (auto &mesh: m_frameMeshes) { + delete mesh.second; + } +} + +std::vector> AnimationClipGenerator::takeFrameMeshes() +{ + std::vector> result = m_frameMeshes; + m_frameMeshes.clear(); + return result; +} + +void AnimationClipGenerator::generate() +{ + SkinnedMesh skinnedMesh(m_resultContext); + 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())); + } +} + +void AnimationClipGenerator::process() +{ + generate(); + + this->moveToThread(QGuiApplication::instance()->thread()); + emit finished(); +} diff --git a/src/animationclipgenerator.h b/src/animationclipgenerator.h new file mode 100644 index 00000000..a8ee70d3 --- /dev/null +++ b/src/animationclipgenerator.h @@ -0,0 +1,29 @@ +#ifndef ANIMATION_CLIP_GENERATOR_H +#define ANIMATION_CLIP_GENERATOR_H +#include +#include +#include +#include "meshresultcontext.h" +#include "meshloader.h" + +class AnimationClipGenerator : public QObject +{ + Q_OBJECT +signals: + void finished(); +public slots: + void process(); +public: + AnimationClipGenerator(const MeshResultContext &resultContext, + const QString &motionName, const std::map ¶meters); + ~AnimationClipGenerator(); + std::vector> takeFrameMeshes(); + void generate(); +private: + MeshResultContext m_resultContext; + QString m_motionName; + std::map m_parameters; + std::vector> m_frameMeshes; +}; + +#endif diff --git a/src/animationpanelwidget.cpp b/src/animationpanelwidget.cpp new file mode 100644 index 00000000..e5531c19 --- /dev/null +++ b/src/animationpanelwidget.cpp @@ -0,0 +1,166 @@ +#include +#include +#include "animationpanelwidget.h" +#include "version.h" + +AnimationPanelWidget::AnimationPanelWidget(SkeletonDocument *document, QWidget *parent) : + QWidget(parent), + m_document(document), + m_animationClipGenerator(nullptr), + m_lastFrameMesh(nullptr), + m_sourceMeshReady(false) +{ + QHBoxLayout *moveControlButtonLayout = new QHBoxLayout; + QHBoxLayout *fightControlButtonLayout = new QHBoxLayout; + + QPushButton *resetButton = new QPushButton(tr("Reset")); + connect(resetButton, &QPushButton::clicked, [=] { + m_lastMotionName.clear(); + emit panelClosed(); + }); + + QPushButton *walkButton = new QPushButton(tr("Walk")); + connect(walkButton, &QPushButton::clicked, [=] { + generateClip("Walk"); + }); + + 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(); + + QVBoxLayout *controlLayout = new QVBoxLayout; + controlLayout->setSpacing(0); + controlLayout->setContentsMargins(0, 0, 0, 0); + controlLayout->addLayout(moveControlButtonLayout); + controlLayout->addLayout(fightControlButtonLayout); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(controlLayout); + + setLayout(mainLayout); + + m_countForFrame.start(); + + 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()) + return; + + generateClip(m_nextMotionName); +} + +void AnimationPanelWidget::hideEvent(QHideEvent *event) +{ + m_lastMotionName.clear(); + emit panelClosed(); +} + +void AnimationPanelWidget::generateClip(QString motionName) +{ + if (nullptr != m_animationClipGenerator || !m_sourceMeshReady) { + m_nextMotionName = motionName; + return; + } + m_lastMotionName = motionName; + m_nextMotionName.clear(); + + qDebug() << "Animation clip generating.."; + + QThread *thread = new QThread; + + std::map parameters; + m_animationClipGenerator = new AnimationClipGenerator(m_document->currentPostProcessedResultContext(), + m_nextMotionName, parameters); + m_animationClipGenerator->moveToThread(thread); + connect(thread, &QThread::started, m_animationClipGenerator, &AnimationClipGenerator::process); + connect(m_animationClipGenerator, &AnimationClipGenerator::finished, this, &AnimationPanelWidget::clipReady); + connect(m_animationClipGenerator, &AnimationClipGenerator::finished, thread, &QThread::quit); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); +} + +void AnimationPanelWidget::clipReady() +{ + m_frameMeshes = m_animationClipGenerator->takeFrameMeshes(); + + 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()) + generateClip(m_nextMotionName); +} + +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; +} diff --git a/src/animationpanelwidget.h b/src/animationpanelwidget.h new file mode 100644 index 00000000..1bcf928f --- /dev/null +++ b/src/animationpanelwidget.h @@ -0,0 +1,37 @@ +#ifndef ANIMATION_PANEL_WIDGET_H +#define ANIMATION_PANEL_WIDGET_H +#include +#include +#include +#include "skeletondocument.h" +#include "animationclipgenerator.h" + +class AnimationPanelWidget : public QWidget +{ + Q_OBJECT +signals: + void frameReadyToShow(); + void panelClosed(); +public: + AnimationPanelWidget(SkeletonDocument *document, QWidget *parent=nullptr); + ~AnimationPanelWidget(); + MeshLoader *takeFrameMesh(); +protected: + void hideEvent(QHideEvent *event); +public slots: + void generateClip(QString motionName); + void clipReady(); + void sourceMeshChanged(); +private: + SkeletonDocument *m_document; + AnimationClipGenerator *m_animationClipGenerator; + MeshLoader *m_lastFrameMesh; + bool m_sourceMeshReady; +private: + std::vector> m_frameMeshes; + QTime m_countForFrame; + QString m_nextMotionName; + QString m_lastMotionName; +}; + +#endif diff --git a/src/ccdikresolver.cpp b/src/ccdikresolver.cpp index 08fae987..aa75915b 100644 --- a/src/ccdikresolver.cpp +++ b/src/ccdikresolver.cpp @@ -1,11 +1,13 @@ #include #include #include +#include #include "ccdikresolver.h" CCDIKSolver::CCDIKSolver() : - m_maxRound(5), - m_distanceThreshold2(0.001 * 0.001) + m_maxRound(4), + m_distanceThreshold2(0.01 * 0.01), + m_distanceCeaseThreshold2(0.01 * 0.01) { } @@ -39,7 +41,7 @@ void CCDIKSolver::solveTo(const QVector3D &position) qDebug() << "Round:" << i << " distance2:" << distance2; if (distance2 <= m_distanceThreshold2) break; - if (lastDistance2 > 0 && distance2 >= lastDistance2) + if (lastDistance2 > 0 && abs(distance2 - lastDistance2) <= m_distanceCeaseThreshold2) break; lastDistance2 = distance2; iterate(); @@ -48,7 +50,7 @@ void CCDIKSolver::solveTo(const QVector3D &position) const QVector3D &CCDIKSolver::getNodeSolvedPosition(int index) { - Q_ASSERT(index >= 0 && index < m_nodes.size()); + Q_ASSERT(index >= 0 && index < (int)m_nodes.size()); return m_nodes[index].position; } @@ -62,13 +64,13 @@ void CCDIKSolver::iterate() for (int i = m_nodes.size() - 2; i >= 0; i--) { const auto &origin = m_nodes[i]; const auto &endEffector = m_nodes[m_nodes.size() - 1]; - QVector3D from = endEffector.position - origin.position; - QVector3D to = m_destination - origin.position; + QVector3D from = (endEffector.position - origin.position).normalized(); + QVector3D to = (m_destination - origin.position).normalized(); auto quaternion = QQuaternion::rotationTo(from, to); for (size_t j = i + 1; j <= m_nodes.size() - 1; j++) { auto &next = m_nodes[j]; const auto offset = next.position - origin.position; - next.position = origin.position + quaternion.rotatedVector(offset).normalized() * offset.length(); + next.position = origin.position + quaternion.rotatedVector(offset); } } } diff --git a/src/ccdikresolver.h b/src/ccdikresolver.h index c5af0bbc..0485aafb 100644 --- a/src/ccdikresolver.h +++ b/src/ccdikresolver.h @@ -26,6 +26,7 @@ private: QVector3D m_destination; int m_maxRound; float m_distanceThreshold2; + float m_distanceCeaseThreshold2; }; #endif diff --git a/src/gltffile.cpp b/src/gltffile.cpp index 5b16e55d..175c3ded 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/ @@ -32,60 +33,34 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & QFileInfo nameInfo(filename); QString textureFilenameWithoutPath = nameInfo.completeBaseName() + ".png"; m_textureFilename = nameInfo.path() + QDir::separator() + textureFilenameWithoutPath; - - JointInfo rootHandleJoint; - { - rootHandleJoint.jointIndex = m_tracedJoints.size(); - QMatrix4x4 localMatrix; - rootHandleJoint.translation = QVector3D(0, - 1, 0); - localMatrix.translate(rootHandleJoint.translation); - rootHandleJoint.worldMatrix = localMatrix; - rootHandleJoint.inverseBindMatrix = rootHandleJoint.worldMatrix.inverted(); - m_tracedJoints.push_back(rootHandleJoint); - } - JointInfo rootCenterJoint; - { - rootCenterJoint.jointIndex = m_tracedJoints.size(); - QMatrix4x4 localMatrix; - rootCenterJoint.translation = QVector3D(rootNode->origin.x(), rootNode->origin.y() + 1, rootNode->origin.z()); - localMatrix.translate(rootCenterJoint.translation); - rootCenterJoint.worldMatrix = rootHandleJoint.worldMatrix * localMatrix; - rootCenterJoint.direction = QVector3D(0, 1, 0); - rootCenterJoint.inverseBindMatrix = rootCenterJoint.worldMatrix.inverted(); - m_tracedJoints[rootHandleJoint.jointIndex].children.push_back(rootCenterJoint.jointIndex); - m_tracedJoints.push_back(rootCenterJoint); - } - - std::set> visitedNodes; - std::set, std::pair>> connections; - m_tracedNodeToJointIndexMap[std::make_pair(rootNode->bmeshId, rootNode->nodeId)] = rootCenterJoint.jointIndex; - traceBoneFromJoint(resultContext, std::make_pair(rootNode->bmeshId, rootNode->nodeId), visitedNodes, connections, rootCenterJoint.jointIndex); + JointNodeTree jointNodeTree(resultContext); + const std::vector &tracedJoints = jointNodeTree.joints(); m_json["asset"]["version"] = "2.0"; m_json["asset"]["generator"] = APP_NAME " " APP_HUMAN_VER; m_json["scenes"][0]["nodes"] = {0}; - m_json["nodes"][0]["children"] = {1, 2}; - m_json["nodes"][1]["mesh"] = 0; - m_json["nodes"][1]["skin"] = 0; + m_json["nodes"][0]["mesh"] = 0; + m_json["nodes"][0]["skin"] = 0; + m_json["nodes"][0]["children"] = {1}; - int skeletonNodeStartIndex = 2; + int skeletonNodeStartIndex = 1; - for (auto i = 0u; i < m_tracedJoints.size(); i++) { - m_json["nodes"][skeletonNodeStartIndex + i]["translation"] = {m_tracedJoints[i].translation.x(), - m_tracedJoints[i].translation.y(), - m_tracedJoints[i].translation.z() + for (auto i = 0u; i < tracedJoints.size(); i++) { + m_json["nodes"][skeletonNodeStartIndex + i]["translation"] = {tracedJoints[i].translation.x(), + tracedJoints[i].translation.y(), + tracedJoints[i].translation.z() }; - m_json["nodes"][skeletonNodeStartIndex + i]["rotation"] = {m_tracedJoints[i].rotation.x(), - m_tracedJoints[i].rotation.y(), - m_tracedJoints[i].rotation.z(), - m_tracedJoints[i].rotation.scalar() + m_json["nodes"][skeletonNodeStartIndex + i]["rotation"] = {tracedJoints[i].rotation.x(), + tracedJoints[i].rotation.y(), + tracedJoints[i].rotation.z(), + tracedJoints[i].rotation.scalar() }; - if (m_tracedJoints[i].children.empty()) + if (tracedJoints[i].children.empty()) continue; m_json["nodes"][skeletonNodeStartIndex + i]["children"] = {}; - for (const auto &it: m_tracedJoints[i].children) { + for (const auto &it: tracedJoints[i].children) { m_json["nodes"][skeletonNodeStartIndex + i]["children"] += skeletonNodeStartIndex + it; } } @@ -93,7 +68,7 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & m_json["skins"][0]["inverseBindMatrices"] = 0; m_json["skins"][0]["skeleton"] = skeletonNodeStartIndex; m_json["skins"][0]["joints"] = {}; - for (auto i = 0u; i < m_tracedJoints.size(); i++) { + for (auto i = 0u; i < tracedJoints.size(); i++) { m_json["skins"][0]["joints"] += skeletonNodeStartIndex + i; } @@ -115,13 +90,13 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex; m_json["accessors"][bufferViewIndex]["byteOffset"] = 0; m_json["accessors"][bufferViewIndex]["componentType"] = 5126; - m_json["accessors"][bufferViewIndex]["count"] = m_tracedJoints.size(); + 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 < m_tracedJoints.size(); i++) { - const float *floatArray = m_tracedJoints[i].inverseBindMatrix.constData(); + 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]; } @@ -254,7 +229,7 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & for (const auto &it: part.second.weights) { auto i = 0u; for (; i < it.size() && i < MAX_WEIGHT_NUM; i++) { - stream << (quint16)m_tracedNodeToJointIndexMap[it[i].sourceNode]; + stream << (quint16)jointNodeTree.nodeToJointIndex(it[i].sourceNode.first, it[i].sourceNode.second); } for (; i < MAX_WEIGHT_NUM; i++) { stream << (quint16)0; @@ -277,7 +252,7 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & for (const auto &it: part.second.weights) { auto i = 0u; for (; i < it.size() && i < MAX_WEIGHT_NUM; i++) { - stream << (quint16)it[i].weight; + stream << (float)it[i].weight; } for (; i < MAX_WEIGHT_NUM; i++) { stream << (float)0.0; @@ -321,68 +296,6 @@ const QString &GLTFFileWriter::textureFilenameInGltf() return m_textureFilename; } -void GLTFFileWriter::traceBoneFromJoint(MeshResultContext &resultContext, std::pair node, std::set> &visitedNodes, std::set, std::pair>> &connections, int parentIndex) -{ - if (visitedNodes.find(node) != visitedNodes.end()) - return; - visitedNodes.insert(node); - const auto &neighbors = resultContext.nodeNeighbors().find(node); - if (neighbors == resultContext.nodeNeighbors().end()) { - return; - } - 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; - connections.insert(std::make_pair(std::make_pair(node.first, node.second), std::make_pair(it.first, it.second))); - connections.insert(std::make_pair(std::make_pair(it.first, it.second), std::make_pair(node.first, node.second))); - const auto &fromNode = resultContext.bmeshNodeMap().find(std::make_pair(node.first, node.second)); - if (fromNode == resultContext.bmeshNodeMap().end()) { - qDebug() << "bmeshNodeMap find failed:" << node.first << node.second; - continue; - } - const auto &toNode = resultContext.bmeshNodeMap().find(std::make_pair(it.first, it.second)); - if (toNode == resultContext.bmeshNodeMap().end()) { - qDebug() << "bmeshNodeMap find failed:" << it.first << it.second; - continue; - } - QVector3D boneDirect = toNode->second->origin - fromNode->second->origin; - QVector3D normalizedBoneDirect = boneDirect.normalized(); - QMatrix4x4 translateMat; - translateMat.translate(boneDirect); - - QQuaternion rotation; - QMatrix4x4 rotateMat; - - QVector3D cross = QVector3D::crossProduct(normalizedBoneDirect, m_tracedJoints[parentIndex].direction).normalized(); - float dot = QVector3D::dotProduct(normalizedBoneDirect, m_tracedJoints[parentIndex].direction); - float angle = acos(dot); - rotation = QQuaternion::fromAxisAndAngle(cross, angle); - rotateMat.rotate(rotation); - - QMatrix4x4 localMatrix; - localMatrix = translateMat * rotateMat; - - QMatrix4x4 worldMatrix; - worldMatrix = m_tracedJoints[parentIndex].worldMatrix * localMatrix; - - JointInfo joint; - joint.position = toNode->second->origin; - joint.direction = normalizedBoneDirect; - joint.translation = boneDirect; - joint.rotation = rotation; - joint.jointIndex = m_tracedJoints.size(); - joint.worldMatrix = worldMatrix; - joint.inverseBindMatrix = worldMatrix.inverted(); - - 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); - } -} - bool GLTFFileWriter::save() { QFile file(m_filename); diff --git a/src/gltffile.h b/src/gltffile.h index 79339e10..61e199aa 100644 --- a/src/gltffile.h +++ b/src/gltffile.h @@ -8,31 +8,15 @@ #include "meshresultcontext.h" #include "json.hpp" -struct JointInfo -{ - int jointIndex; - QVector3D position; - QVector3D direction; - QVector3D translation; - QQuaternion rotation; - QMatrix4x4 worldMatrix; - QMatrix4x4 inverseBindMatrix; - std::vector children; -}; - class GLTFFileWriter : public QObject { Q_OBJECT public: GLTFFileWriter(MeshResultContext &resultContext, const QString &filename); bool save(); - void traceBones(MeshResultContext &resultContext); - void traceBoneFromJoint(MeshResultContext &resultContext, std::pair node, std::set> &visitedNodes, std::set, std::pair>> &connections, int parentIndex); const QString &textureFilenameInGltf(); private: QByteArray m_data; - std::vector m_tracedJoints; - std::map, int> m_tracedNodeToJointIndexMap; QString getMatrixStringInColumnOrder(const QMatrix4x4 &mat); QString m_filename; QString m_textureFilename; diff --git a/src/jointnodetree.cpp b/src/jointnodetree.cpp new file mode 100644 index 00000000..a2d1896c --- /dev/null +++ b/src/jointnodetree.cpp @@ -0,0 +1,126 @@ +#include +#include "jointnodetree.h" + +JointNodeTree::JointNodeTree(MeshResultContext &resultContext) +{ + const BmeshNode *rootNode = resultContext.centerBmeshNode(); + if (!rootNode) { + qDebug() << "Cannot construct JointNodeTree because lack of root node"; + return; + } + + JointInfo rootCenterJoint; + { + rootCenterJoint.jointIndex = m_tracedJoints.size(); + QMatrix4x4 localMatrix; + rootCenterJoint.partId = rootNode->bmeshId; + rootCenterJoint.nodeId = rootNode->nodeId; + rootCenterJoint.position = rootNode->origin; + rootCenterJoint.boneMark = rootNode->boneMark; + m_tracedJoints.push_back(rootCenterJoint); + } + + //qDebug() << "Root node partId:" << rootNode->bmeshId << "nodeId:" << rootNode->nodeId; + + std::set> visitedNodes; + std::set, std::pair>> connections; + 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(); +} + +void JointNodeTree::traceBoneFromJoint(MeshResultContext &resultContext, std::pair node, std::set> &visitedNodes, std::set, std::pair>> &connections, int parentIndex) +{ + if (visitedNodes.find(node) != visitedNodes.end()) + return; + visitedNodes.insert(node); + const auto &neighbors = resultContext.nodeNeighbors().find(node); + if (neighbors == resultContext.nodeNeighbors().end()) + return; + 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; + connections.insert(std::make_pair(std::make_pair(node.first, node.second), std::make_pair(it.first, it.second))); + connections.insert(std::make_pair(std::make_pair(it.first, it.second), std::make_pair(node.first, node.second))); + const auto &fromNode = resultContext.bmeshNodeMap().find(std::make_pair(node.first, node.second)); + if (fromNode == resultContext.bmeshNodeMap().end()) { + qDebug() << "bmeshNodeMap find failed:" << node.first << node.second; + continue; + } + const auto &toNode = resultContext.bmeshNodeMap().find(std::make_pair(it.first, it.second)); + if (toNode == resultContext.bmeshNodeMap().end()) { + qDebug() << "bmeshNodeMap find failed:" << it.first << it.second; + continue; + } + + JointInfo joint; + joint.position = toNode->second->origin; + joint.jointIndex = m_tracedJoints.size(); + joint.partId = toNode->second->bmeshId; + joint.nodeId = toNode->second->nodeId; + joint.boneMark = toNode->second->boneMark; + + 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); + } +} + +std::vector &JointNodeTree::joints() +{ + return m_tracedJoints; +} + +int JointNodeTree::nodeToJointIndex(int partId, int nodeId) +{ + const auto &findIt = m_tracedNodeToJointIndexMap.find(std::make_pair(partId, nodeId)); + if (findIt == m_tracedNodeToJointIndexMap.end()) + return 0; + return findIt->second; +} + +void JointNodeTree::calculateMatrices() +{ + if (joints().empty()) + return; + calculateMatricesFrom(0, QVector3D(), QVector3D(), QMatrix4x4()); +} + +void JointNodeTree::calculateMatricesFrom(int jointIndex, const QVector3D &parentPosition, const QVector3D &parentDirection, const QMatrix4x4 &parentMatrix) +{ + auto &joint = joints()[jointIndex]; + QVector3D translation = joint.position - parentPosition; + QVector3D direction = translation.normalized(); + + 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)); + + QMatrix4x4 localMatrix = translateMatrix * rotateMatrix; + QMatrix4x4 bindMatrix = parentMatrix * localMatrix; + + joint.localMatrix = localMatrix; + joint.translation = translation; + joint.rotation = rotation; + joint.bindMatrix = bindMatrix; + joint.inverseBindMatrix = joint.bindMatrix.inverted(); + + for (const auto &child: joint.children) { + calculateMatricesFrom(child, joint.position, direction, bindMatrix); + } +} + +void JointNodeTree::recalculateMatricesAfterPositionsUpdated() +{ + calculateMatrices(); +} diff --git a/src/jointnodetree.h b/src/jointnodetree.h new file mode 100644 index 00000000..5056e573 --- /dev/null +++ b/src/jointnodetree.h @@ -0,0 +1,39 @@ +#ifndef JOINT_NODE_TREE_H +#define JOINT_NODE_TREE_H +#include +#include +#include +#include "meshresultcontext.h" +#include "skeletonbonemark.h" + +struct JointInfo +{ + int jointIndex = 0; + int partId = 0; + int nodeId = 0; + SkeletonBoneMark boneMark = SkeletonBoneMark::None; + QVector3D position; + QVector3D translation; + QQuaternion rotation; + QMatrix4x4 localMatrix; + QMatrix4x4 bindMatrix; + QMatrix4x4 inverseBindMatrix; + std::vector children; +}; + +class JointNodeTree +{ +public: + JointNodeTree(MeshResultContext &resultContext); + std::vector &joints(); + int nodeToJointIndex(int partId, int nodeId); + void recalculateMatricesAfterPositionsUpdated(); +private: + void calculateMatrices(); + void calculateMatricesFrom(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); +}; + +#endif diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index 06ad3a7a..a34119cf 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -326,12 +326,15 @@ void MeshGenerator::process() //qDebug() << "bmeshId[" << bmeshId << "] add lonely node[" << bmeshNodeId << "]" << radius << x << y << z; bmeshNodeMap[nodeIt.first] = bmeshNodeId; + SkeletonBoneMark boneMark = SkeletonBoneMarkFromString(valueOfKeyInMapOrEmpty(nodeIt.second, "boneMark").toUtf8().constData()); + BmeshNode bmeshNode; bmeshNode.bmeshId = bmeshId; bmeshNode.origin = QVector3D(x, y, z); bmeshNode.radius = radius; bmeshNode.nodeId = bmeshNodeId; bmeshNode.color = partColorMap[partId]; + bmeshNode.boneMark = boneMark; m_meshResultContext->bmeshNodes.push_back(bmeshNode); if (partMirrorFlagMap[partId]) { diff --git a/src/meshloader.cpp b/src/meshloader.cpp index 7380e54f..4812339d 100644 --- a/src/meshloader.cpp +++ b/src/meshloader.cpp @@ -140,34 +140,44 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColo delete[] edgeNormals; } -/* -MeshLoader::MeshLoader(const std::vector &vertices, const std::vector &indicies, const std::vector &normals) : +MeshLoader::MeshLoader(const MeshLoader &mesh) : m_triangleVertices(nullptr), m_triangleVertexCount(0), m_edgeVertices(nullptr), m_edgeVertexCount(0), m_textureImage(nullptr) { - m_triangleVertexCount = indicies.size(); - m_triangleVertices = new Vertex[indicies.size()]; - for (auto i = 0u; i < indicies.size(); i++) { - Vertex *dest = &m_triangleVertices[i]; - const vertex_t *srcVert = &vertices[indicies[i]]; - const QVector3D *srcNormal = &normals[indicies[i]]; - dest->colorR = 0; - dest->colorG = 0; - dest->colorB = 0; - dest->posX = srcVert->p[0]; - dest->posY = srcVert->p[1]; - dest->posZ = srcVert->p[2]; - dest->texU = srcVert->t[0]; - dest->texV = srcVert->t[1]; - dest->normX = srcNormal->x(); - dest->normY = srcNormal->y(); - dest->normZ = srcNormal->z(); + if (nullptr != mesh.m_triangleVertices && + mesh.m_triangleVertexCount > 0) { + this->m_triangleVertices = new Vertex[mesh.m_triangleVertexCount]; + this->m_triangleVertexCount = mesh.m_triangleVertexCount; + for (int i = 0; i < mesh.m_triangleVertexCount; i++) + this->m_triangleVertices[i] = mesh.m_triangleVertices[i]; } + if (nullptr != mesh.m_edgeVertices && + mesh.m_edgeVertexCount > 0) { + this->m_edgeVertices = new Vertex[mesh.m_edgeVertexCount]; + this->m_edgeVertexCount = mesh.m_edgeVertexCount; + for (int i = 0; i < mesh.m_edgeVertexCount; i++) + this->m_edgeVertices[i] = mesh.m_edgeVertices[i]; + } + if (nullptr != mesh.m_textureImage) { + this->m_textureImage = new QImage(*mesh.m_textureImage); + } + this->m_vertices = mesh.m_vertices; + this->m_faces = mesh.m_faces; + this->m_triangulatedVertices = mesh.m_triangulatedVertices; + this->m_triangulatedFaces = mesh.m_triangulatedFaces; +} + +MeshLoader::MeshLoader(Vertex *triangleVertices, int vertexNum) : + m_triangleVertices(triangleVertices), + m_triangleVertexCount(vertexNum), + m_edgeVertices(nullptr), + m_edgeVertexCount(0), + m_textureImage(nullptr) +{ } -*/ MeshLoader::MeshLoader(MeshResultContext &resultContext) : m_triangleVertices(nullptr), @@ -210,6 +220,8 @@ MeshLoader::~MeshLoader() { delete[] m_triangleVertices; m_triangleVertexCount = 0; + delete[] m_edgeVertices; + m_edgeVertexCount = 0; delete m_textureImage; } diff --git a/src/meshloader.h b/src/meshloader.h index ce322d66..5a10809a 100644 --- a/src/meshloader.h +++ b/src/meshloader.h @@ -39,8 +39,9 @@ class MeshLoader { public: MeshLoader(void *meshlite, int meshId, int triangulatedMeshId = -1, QColor modelColor=Theme::white, const std::vector *triangleColors=nullptr); - //MeshLoader(const std::vector &vertices, const std::vector &indicies, const std::vector &normals); MeshLoader(MeshResultContext &resultContext); + MeshLoader(Vertex *triangleVertices, int vertexNum); + MeshLoader(const MeshLoader &mesh); ~MeshLoader(); Vertex *triangleVertices(); int triangleVertexCount(); diff --git a/src/meshresultcontext.cpp b/src/meshresultcontext.cpp index 5230b199..75124197 100644 --- a/src/meshresultcontext.cpp +++ b/src/meshresultcontext.cpp @@ -42,12 +42,23 @@ MeshResultContext::MeshResultContext() : const std::vector> &MeshResultContext::triangleSourceNodes() { if (!m_triangleSourceResolved) { - calculateTriangleSourceNodes(m_triangleSourceNodes); m_triangleSourceResolved = true; + calculateTriangleSourceNodes(m_triangleSourceNodes, m_vertexSourceMap); + calculateRemainingVertexSourceNodesAfterTriangleSourceNodesSolved(m_vertexSourceMap); } return m_triangleSourceNodes; } +const std::map> &MeshResultContext::vertexSourceMap() +{ + if (!m_triangleSourceResolved) { + m_triangleSourceResolved = true; + calculateTriangleSourceNodes(m_triangleSourceNodes, m_vertexSourceMap); + calculateRemainingVertexSourceNodesAfterTriangleSourceNodesSolved(m_vertexSourceMap); + } + return m_vertexSourceMap; +} + const std::vector &MeshResultContext::triangleColors() { if (!m_triangleColorResolved) { @@ -92,7 +103,7 @@ void MeshResultContext::resolveBmeshConnectivity() } } -void MeshResultContext::calculateTriangleSourceNodes(std::vector> &triangleSourceNodes) +void MeshResultContext::calculateTriangleSourceNodes(std::vector> &triangleSourceNodes, std::map> &vertexSourceMap) { PositionMap> positionMap; std::map, HalfColorEdge> halfColorEdgeMap; @@ -101,14 +112,21 @@ void MeshResultContext::calculateTriangleSourceNodes(std::vector source; + if (positionMap.findPosition(resultVertex->position.x(), resultVertex->position.y(), resultVertex->position.z(), &source)) { + vertexSourceMap[x] = source; + } + } for (auto x = 0u; x < triangles.size(); x++) { const auto triangle = &triangles[x]; std::vector, int>> colorTypes; for (int i = 0; i < 3; i++) { int index = triangle->indicies[i]; - ResultVertex *resultVertex = &vertices[index]; - std::pair source; - if (positionMap.findPosition(resultVertex->position.x(), resultVertex->position.y(), resultVertex->position.z(), &source)) { + const auto &findResult = vertexSourceMap.find(index); + if (findResult != vertexSourceMap.end()) { + std::pair source = findResult->second; bool colorExisted = false; for (auto j = 0u; j < colorTypes.size(); j++) { if (colorTypes[j].first == source) { @@ -228,6 +246,37 @@ void MeshResultContext::calculateTriangleSourceNodes(std::vector> &vertexSourceMap) +{ + std::map>> remainings; + for (auto x = 0u; x < triangles.size(); x++) { + const auto triangle = &triangles[x]; + for (int i = 0; i < 3; i++) { + int index = triangle->indicies[i]; + const auto &findResult = vertexSourceMap.find(index); + if (findResult == vertexSourceMap.end()) { + remainings[index].insert(triangleSourceNodes()[x]); + } + } + } + for (const auto &it: remainings) { + float minDist2 = 100000000; + std::pair minSource = std::make_pair(0, 0); + for (const auto &source: it.second) { + const auto &vertex = vertices[it.first]; + const auto &findNode = bmeshNodeMap().find(source); + if (findNode != bmeshNodeMap().end()) { + float dist2 = (vertex.position - findNode->second->origin).lengthSquared(); + if (dist2 < minDist2) { + minSource = source; + minDist2 = dist2; + } + } + } + vertexSourceMap[it.first] = minSource; + } +} + void MeshResultContext::calculateTriangleColors(std::vector &triangleColors) { std::map, QColor> nodeColorMap; @@ -373,10 +422,12 @@ struct BmeshNodeDistWithWorldCenter BmeshNode *MeshResultContext::calculateCenterBmeshNode() { - // Sort all the nodes by distance with world center. + // Sort all the nodes by distance with world center, excluding leg start nodes std::vector nodesOrderByDistWithWorldCenter; for (auto i = 0u; i < bmeshNodes.size(); i++) { BmeshNode *bmeshNode = &bmeshNodes[i]; + if (SkeletonBoneMarkIsStart(bmeshNode->boneMark)) + continue; BmeshNodeDistWithWorldCenter distNode; distNode.bmeshNode = bmeshNode; distNode.dist2 = bmeshNode->origin.lengthSquared(); @@ -480,6 +531,7 @@ void MeshResultContext::calculateVertexWeights(std::vectorfind(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 &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()) { @@ -514,7 +569,7 @@ void MeshResultContext::calculateVertexWeights(std::vectorfirst, 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)); } @@ -527,9 +582,11 @@ void MeshResultContext::calculateVertexWeights(std::vector 0 ? weights[i].second / total : 0; } for (auto i = 0u; i < MAX_WEIGHT_NUM && i < weights.size(); i++) { + if (i >= it.size()) + it.push_back(ResultVertexWeight()); it[i].sourceNode = weights[i].first; it[i].weight = weights[i].second; it[i].count = -1; // no use @@ -788,8 +845,10 @@ void MeshResultContext::removeIntermediateBones() bmeshEdges.clear(); for (const auto &old: oldEdges) { if (intermediateNodes.find(std::make_pair(old.fromBmeshId, old.fromNodeId)) != intermediateNodes.end() || - intermediateNodes.find(std::make_pair(old.toBmeshId, old.toNodeId)) != intermediateNodes.end()) + intermediateNodes.find(std::make_pair(old.toBmeshId, old.toNodeId)) != intermediateNodes.end()) { + qDebug() << "Intermediate node from:" << old.fromBmeshId << old.fromNodeId << "to:" << old.toBmeshId << old.toNodeId; continue; + } bmeshEdges.push_back(old); } for (const auto &edge: remover.newEdges()) { @@ -798,6 +857,7 @@ void MeshResultContext::removeIntermediateBones() newEdge.fromNodeId = std::get<1>(edge); newEdge.toBmeshId = std::get<2>(edge); newEdge.toNodeId = std::get<3>(edge); + qDebug() << "New edge from:" << newEdge.fromBmeshId << newEdge.fromNodeId << "to:" << newEdge.toBmeshId << newEdge.toNodeId; bmeshEdges.push_back(newEdge); } calculateBmeshNodeNeighbors(m_nodeNeighbors); diff --git a/src/meshresultcontext.h b/src/meshresultcontext.h index 2ecd8ceb..b1ff0aa4 100644 --- a/src/meshresultcontext.h +++ b/src/meshresultcontext.h @@ -13,27 +13,27 @@ struct BmeshNode { - int bmeshId; - int nodeId; + int bmeshId = 0; + int nodeId = 0; QVector3D origin; - float radius; + float radius = 0; QColor color; - SkeletonBoneMark boneMark; + SkeletonBoneMark boneMark = SkeletonBoneMark::None; }; struct BmeshVertex { QVector3D position; - int bmeshId; - int nodeId; + int bmeshId = 0; + int nodeId = 0; }; struct BmeshEdge { - int fromBmeshId; - int fromNodeId; - int toBmeshId; - int toNodeId; + int fromBmeshId = 0; + int fromNodeId = 0; + int toBmeshId = 0; + int toNodeId = 0; }; struct ResultVertex @@ -43,26 +43,26 @@ struct ResultVertex struct ResultTriangle { - int indicies[3]; + int indicies[3] = {0, 0, 0}; QVector3D normal; }; struct ResultVertexWeight { - std::pair sourceNode; - int count; - float weight; + std::pair sourceNode = std::make_pair(0, 0); + int count = 0; + float weight = 0; }; struct ResultTriangleUv { - float uv[3][2]; - bool resolved; + float uv[3][2] = {{0, 0}, {0, 0}, {0, 0}}; + bool resolved = false; }; struct ResultVertexUv { - float uv[2]; + float uv[2] = {0, 0}; }; struct ResultPart @@ -118,6 +118,7 @@ public: const std::vector &triangleUvs(); const std::vector &rearrangedVertices(); const std::vector &rearrangedTriangles(); + const std::map> &vertexSourceMap(); void removeIntermediateBones(); private: bool m_triangleSourceResolved; @@ -145,8 +146,10 @@ private: std::set m_seamVertices; std::vector m_rearrangedVertices; std::vector m_rearrangedTriangles; + std::map> m_vertexSourceMap; private: - void calculateTriangleSourceNodes(std::vector> &triangleSourceNodes); + void calculateTriangleSourceNodes(std::vector> &triangleSourceNodes, std::map> &vertexSourceMap); + void calculateRemainingVertexSourceNodesAfterTriangleSourceNodesSolved(std::map> &vertexSourceMap); void calculateTriangleColors(std::vector &triangleColors); void calculateTriangleEdgeSourceMap(std::map, std::pair> &triangleEdgeSourceMap); void calculateBmeshNodeMap(std::map, BmeshNode *> &bmeshNodeMap); diff --git a/src/rigcontroller.cpp b/src/rigcontroller.cpp new file mode 100644 index 00000000..6a6c00c8 --- /dev/null +++ b/src/rigcontroller.cpp @@ -0,0 +1,199 @@ +#include +#include "rigcontroller.h" +#include "ccdikresolver.h" + +RigController::RigController(const JointNodeTree &jointNodeTree) : + m_inputJointNodeTree(jointNodeTree), + m_prepared(false), + m_legHeight(0) +{ +} + +void RigController::saveFrame(RigFrame &frame) +{ + frame = m_rigFrame; +} + +void RigController::collectLegs() +{ + m_legs.clear(); + for (const auto &node: m_inputJointNodeTree.joints()) { + 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]]; + while (loopNode->boneMark != SkeletonBoneMark::LegEnd && + loopNode->children.size() == 1) { + loopNode = &m_inputJointNodeTree.joints()[loopNode->children[0]]; + } + if (loopNode->boneMark == SkeletonBoneMark::LegEnd) { + const auto legEnd = std::make_pair(loopNode->partId, loopNode->nodeId); + addLeg(legStart, legEnd); + } else { + qDebug() << "Find leg" << node.partId << "'s end failed"; + } + } + } +} + +int RigController::addLeg(std::pair legStart, std::pair legEnd) +{ + int legIndex = m_legs.size(); + m_legs.push_back(std::make_tuple(legStart.first, legStart.second, legEnd.first, legEnd.second)); + return legIndex; +} + +void RigController::prepare() +{ + if (m_prepared) + return; + m_prepared = true; + + collectLegs(); + m_rigFrame = RigFrame(m_inputJointNodeTree.joints().size()); + calculateAverageLegHeight(); +} + +void RigController::lift(QVector3D offset) +{ + if (m_inputJointNodeTree.joints().empty()) + return; + m_rigFrame.translations[0] = offset; +} + +void RigController::liftLegs(QVector3D offset, QVector3D &effectedOffset) +{ + if (m_legs.empty()) + return; + QVector3D effectedOffsetSum; + for (auto i = 0u; i < m_legs.size(); i++) { + QVector3D subEffectedOffset; + liftLegEnd(i, offset, subEffectedOffset); + effectedOffsetSum += subEffectedOffset; + } + effectedOffset = effectedOffsetSum / m_legs.size(); +} + +void RigController::liftLegEnd(int leg, QVector3D offset, QVector3D &effectedOffset) +{ + Q_ASSERT(leg >= 0 && leg < (int)m_legs.size()); + int legStartPartId = std::get<0>(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]; + auto destPosition = legEnd.position + offset; + qDebug() << "dest move:" << destPosition.distanceToPoint(legEnd.position); + + CCDIKSolver ikSolver; + ikSolver.setMaxRound(10); + int loopIndex = legStartIndex; + std::vector ikSolvingIndicies; + for (;;) { + const auto &legJoint = m_inputJointNodeTree.joints()[loopIndex]; + ikSolvingIndicies.push_back(loopIndex); + ikSolver.addNodeInOrder(legJoint.position); + if (loopIndex == legEndIndex) + break; + if (legJoint.children.empty()) + break; + Q_ASSERT(legStart.children.size() <= 1); + loopIndex = legJoint.children[0]; + } + + ikSolver.solveTo(destPosition); + int nodeCount = ikSolver.getNodeCount(); + Q_ASSERT(nodeCount == (int)ikSolvingIndicies.size()); + + JointNodeTree outputJointNodeTree = m_inputJointNodeTree; + for (int i = 0; i < nodeCount; i++) { + int jointIndex = ikSolvingIndicies[i]; + const QVector3D &newPosition = ikSolver.getNodeSolvedPosition(i); + const QVector3D &oldPosition = outputJointNodeTree.joints()[jointIndex].position; + qDebug() << i << "position moved:" << oldPosition.distanceToPoint(newPosition); + outputJointNodeTree.joints()[jointIndex].position = newPosition; + } + 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; + 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; + } +} + +void RigController::frameToMatrices(const RigFrame &frame, std::vector &matrices) +{ + if (m_inputJointNodeTree.joints().empty()) + return; + matrices.clear(); + matrices.resize(m_inputJointNodeTree.joints().size()); + + frameToMatricesAtJoint(frame, matrices, 0, QMatrix4x4()); +} + +void RigController::frameToMatricesAtJoint(const RigFrame &frame, std::vector &matrices, int jointIndex, const QMatrix4x4 &parentWorldMatrix) +{ + const auto &joint = m_inputJointNodeTree.joints()[jointIndex]; + + QMatrix4x4 translateMatrix; + translateMatrix.translate(frame.translations[jointIndex]); + + QMatrix4x4 rotateMatrix; + rotateMatrix.rotate(frame.rotations[jointIndex]); + + QMatrix4x4 worldMatrix = parentWorldMatrix * translateMatrix * rotateMatrix; + matrices[jointIndex] = worldMatrix; + + for (const auto &child: joint.children) { + frameToMatricesAtJoint(frame, matrices, child, worldMatrix); + } +} + +void RigController::squat(float amount) +{ + prepare(); + + QVector3D wantOffset; + wantOffset.setY(m_legHeight * amount); + QVector3D effectedOffset; + liftLegs(wantOffset, effectedOffset); + lift(-effectedOffset); +} + +void RigController::calculateAverageLegHeight() +{ + QVector3D averageLegPlaneTop = QVector3D(); + QVector3D averageLegPlaneBottom = QVector3D(); + if (m_legs.empty()) + return; + for (auto leg = 0u; leg < m_legs.size(); leg++) { + int legStartPartId = std::get<0>(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; + averageLegPlaneTop += legStart.position; + averageLegPlaneBottom += legEnd.position; + } + averageLegPlaneTop /= m_legs.size(); + averageLegPlaneBottom /= m_legs.size(); + m_legHeight = abs(averageLegPlaneBottom.y() - averageLegPlaneTop.y()); +} diff --git a/src/rigcontroller.h b/src/rigcontroller.h new file mode 100644 index 00000000..b447c68f --- /dev/null +++ b/src/rigcontroller.h @@ -0,0 +1,52 @@ +#ifndef RIG_CONTROLLER_H +#define RIG_CONTROLLER_H +#include +#include +#include +#include +#include "jointnodetree.h" + +class RigFrame +{ +public: + RigFrame() + { + } + RigFrame(int jointNum) + { + rotations.clear(); + rotations.resize(jointNum); + + translations.clear(); + translations.resize(jointNum); + } + std::vector rotations; + std::vector translations; +}; + +class RigController +{ +public: + RigController(const JointNodeTree &jointNodeTree); + void saveFrame(RigFrame &frame); + void frameToMatrices(const RigFrame &frame, std::vector &matrices); + void squat(float amount); +private: + void prepare(); + void collectLegs(); + 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(); + void frameToMatricesAtJoint(const RigFrame &frame, std::vector &matrices, int jointIndex, const QMatrix4x4 &parentWorldMatrix); +private: + JointNodeTree m_inputJointNodeTree; + bool m_prepared; + float m_legHeight; +private: + std::vector> m_legs; + RigFrame m_rigFrame; +}; + +#endif diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp index ea666248..60ae1a16 100644 --- a/src/skeletondocument.cpp +++ b/src/skeletondocument.cpp @@ -839,8 +839,9 @@ void SkeletonDocument::fromSnapshot(const SkeletonSnapshot &snapshot) MeshLoader *SkeletonDocument::takeResultMesh() { - MeshLoader *resultMesh = m_resultMesh; - m_resultMesh = nullptr; + if (nullptr == m_resultMesh) + return nullptr; + MeshLoader *resultMesh = new MeshLoader(*m_resultMesh); return resultMesh; } diff --git a/src/skeletondocumentwindow.cpp b/src/skeletondocumentwindow.cpp index 91d1f084..5bc69d0e 100644 --- a/src/skeletondocumentwindow.cpp +++ b/src/skeletondocumentwindow.cpp @@ -77,7 +77,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_document(nullptr), m_firstShow(true), m_documentSaved(true), - m_exportPreviewWidget(nullptr) + m_exportPreviewWidget(nullptr), + m_animationPanelWidget(nullptr) { if (!g_logBrowser) { g_logBrowser = new LogBrowser; @@ -419,12 +420,19 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_viewMenu->addSeparator(); + m_showAnimationPanelAction = new QAction(tr("Show Animation Panel"), this); + connect(m_showAnimationPanelAction, &QAction::triggered, this, &SkeletonDocumentWindow::showAnimationPanel); + //m_viewMenu->addAction(m_showAnimationPanelAction); + + m_viewMenu->addSeparator(); + m_showDebugDialogAction = new QAction(tr("Show Debug Dialog"), this); connect(m_showDebugDialogAction, &QAction::triggered, g_logBrowser, &LogBrowser::showDialog); m_viewMenu->addAction(m_showDebugDialogAction); connect(m_viewMenu, &QMenu::aboutToShow, [=]() { m_showPartsListAction->setEnabled(partListDocker->isHidden()); + m_showAnimationPanelAction->setEnabled(nullptr == m_animationPanelWidget || m_animationPanelWidget->isHidden()); m_resetModelWidgetPosAction->setEnabled(!modelIsSitInVisibleArea(m_modelRenderWidget)); }); @@ -576,7 +584,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(m_document, &SkeletonDocument::skeletonChanged, m_document, &SkeletonDocument::generateMesh); connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() { - if (m_exportPreviewWidget && m_exportPreviewWidget->isVisible()) { + if ((m_exportPreviewWidget && m_exportPreviewWidget->isVisible()) || + (m_animationPanelWidget && m_animationPanelWidget->isVisible())) { m_document->postProcess(); } }); @@ -877,6 +886,26 @@ void SkeletonDocumentWindow::exportModelResult() QApplication::restoreOverrideCursor(); } +void SkeletonDocumentWindow::showAnimationPanel() +{ + if (nullptr == m_animationPanelWidget) { + m_animationPanelWidget = new AnimationPanelWidget(m_document, this); + m_animationPanelWidget->setWindowFlags(Qt::Tool); + connect(m_animationPanelWidget, &AnimationPanelWidget::panelClosed, [=] { + m_modelRenderWidget->updateMesh(m_document->takeResultMesh()); + }); + connect(m_animationPanelWidget, &AnimationPanelWidget::frameReadyToShow, [=] { + if (m_animationPanelWidget->isVisible()) + m_modelRenderWidget->updateMesh(m_animationPanelWidget->takeFrameMesh()); + }); + connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_animationPanelWidget, &AnimationPanelWidget::sourceMeshChanged); + } + if (m_animationPanelWidget->isHidden()) { + m_document->postProcess(); + } + m_animationPanelWidget->show(); +} + void SkeletonDocumentWindow::showExportPreview() { if (nullptr == m_exportPreviewWidget) { @@ -890,7 +919,9 @@ void SkeletonDocumentWindow::showExportPreview() connect(m_document, &SkeletonDocument::resultBakedTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview); connect(m_document, &SkeletonDocument::resultSkeletonChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateSkeleton); } - m_document->postProcess(); + if (m_exportPreviewWidget->isHidden()) { + m_document->postProcess(); + } m_exportPreviewWidget->show(); } diff --git a/src/skeletondocumentwindow.h b/src/skeletondocumentwindow.h index 6e15fc1c..f13c1450 100644 --- a/src/skeletondocumentwindow.h +++ b/src/skeletondocumentwindow.h @@ -10,6 +10,7 @@ #include "skeletondocument.h" #include "modelwidget.h" #include "exportpreviewwidget.h" +#include "animationpanelwidget.h" class SkeletonGraphicsWidget; @@ -36,6 +37,7 @@ public slots: void exportModelResult(); void exportGltfResult(); void showExportPreview(); + void showAnimationPanel(); void newWindow(); void newDocument(); void saveAs(); @@ -59,6 +61,7 @@ private: bool m_firstShow; bool m_documentSaved; ExportPreviewWidget *m_exportPreviewWidget; + AnimationPanelWidget *m_animationPanelWidget; private: QString m_currentFilename; @@ -113,6 +116,7 @@ private: QAction *m_showPartsListAction; QAction *m_showDebugDialogAction; QAction *m_toggleWireframeAction; + QAction *m_showAnimationPanelAction; QMenu *m_helpMenu; QAction *m_viewSourceAction; diff --git a/src/skeletongenerator.cpp b/src/skeletongenerator.cpp index 15869ab5..d62dea2d 100644 --- a/src/skeletongenerator.cpp +++ b/src/skeletongenerator.cpp @@ -70,15 +70,15 @@ MeshLoader *SkeletonGenerator::createSkeletonMesh() return skeletonMesh; } -struct BmeshNodeDistWithWorldCenter +void SkeletonGenerator::generate() { - BmeshNode *bmeshNode; - float dist2; -}; + Q_ASSERT(nullptr == m_resultSkeletonMesh); + m_resultSkeletonMesh = createSkeletonMesh(); +} void SkeletonGenerator::process() { - m_resultSkeletonMesh = createSkeletonMesh(); + generate(); this->moveToThread(QGuiApplication::instance()->thread()); emit finished(); diff --git a/src/skeletongenerator.h b/src/skeletongenerator.h index fa878699..21928805 100644 --- a/src/skeletongenerator.h +++ b/src/skeletongenerator.h @@ -12,6 +12,7 @@ class SkeletonGenerator : public QObject public: SkeletonGenerator(const MeshResultContext &meshResultContext); ~SkeletonGenerator(); + void generate(); MeshLoader *takeResultSkeletonMesh(); MeshResultContext *takeResultContext(); signals: @@ -19,7 +20,6 @@ signals: public slots: void process(); private: - void combineAllBmeshSkeletons(); MeshLoader *createSkeletonMesh(); private: MeshResultContext *m_meshResultContext; diff --git a/src/skinnedmesh.cpp b/src/skinnedmesh.cpp new file mode 100644 index 00000000..a83f6e35 --- /dev/null +++ b/src/skinnedmesh.cpp @@ -0,0 +1,107 @@ +#include "skinnedmesh.h" + +SkinnedMesh::SkinnedMesh(const MeshResultContext &resultContext) : + m_resultContext(resultContext), + m_rigController(nullptr), + m_jointNodeTree(nullptr) +{ +} + +SkinnedMesh::~SkinnedMesh() +{ + delete m_rigController; + delete m_jointNodeTree; +} + +RigController *SkinnedMesh::rigController() +{ + return m_rigController; +} + +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); +} + +void SkinnedMesh::applyRigFrameToMesh(const RigFrame &frame) +{ + std::vector matrices; + m_rigController->frameToMatrices(frame, matrices); + 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; + } + vert.position = matrix * vert.posPosition; + vert.normal = (matrix * vert.posNormal).normalized(); + } +} + +void SkinnedMesh::fromMeshResultContext(MeshResultContext &resultContext) +{ + m_vertices.clear(); + m_triangles.clear(); + for (const auto &part: resultContext.parts()) { + std::map oldToNewIndexMap; + for (int vertexIndex = 0; vertexIndex < (int)part.second.vertices.size(); vertexIndex++) { + const ResultVertex *srcVert = &part.second.vertices[vertexIndex]; + const QVector3D *srcNormal = &part.second.interpolatedVertexNormals[vertexIndex]; + SkinnedMeshVertex vertex; + vertex.position = vertex.posPosition = srcVert->position; + vertex.normal = vertex.posNormal = *srcNormal; + const auto &weights = part.second.weights[vertexIndex]; + for (int i = 0; i < (int)weights.size() && i < MAX_WEIGHT_NUM; i++) { + vertex.weights[i].amount = weights[i].weight; + vertex.weights[i].jointIndex = m_jointNodeTree->nodeToJointIndex(weights[i].sourceNode.first, weights[i].sourceNode.second); + } + for (int i = weights.size(); i < MAX_WEIGHT_NUM; i++) { + vertex.weights[i].amount = 0; + vertex.weights[i].jointIndex = 0; + } + oldToNewIndexMap[vertexIndex] = m_vertices.size(); + m_vertices.push_back(vertex); + } + for (const auto &it: part.second.triangles) { + SkinnedMeshTriangle triangle; + for (auto i = 0; i < 3; i++) { + int vertexIndex = it.indicies[i]; + triangle.indicies[i] = oldToNewIndexMap[vertexIndex]; + } + triangle.color = part.second.color; + m_triangles.push_back(triangle); + } + } +} + +MeshLoader *SkinnedMesh::toMeshLoader() +{ + int vertexNum = m_triangles.size() * 3; + Vertex *triangleVertices = new Vertex[vertexNum]; + int destIndex = 0; + for (const auto &triangle: m_triangles) { + for (auto i = 0; i < 3; i++) { + int srcVertexIndex = triangle.indicies[i]; + const SkinnedMeshVertex *srcVertex = &m_vertices[srcVertexIndex]; + Vertex *dest = &triangleVertices[destIndex]; + dest->colorR = triangle.color.redF(); + dest->colorG = triangle.color.greenF(); + dest->colorB = triangle.color.blueF(); + dest->texU = 0; + dest->texV = 0; + dest->normX = srcVertex->normal.x(); + dest->normY = srcVertex->normal.y(); + dest->normZ = srcVertex->normal.z(); + dest->posX = srcVertex->position.x(); + dest->posY = srcVertex->position.y(); + dest->posZ = srcVertex->position.z(); + destIndex++; + } + } + return new MeshLoader(triangleVertices, vertexNum); +} + diff --git a/src/skinnedmesh.h b/src/skinnedmesh.h new file mode 100644 index 00000000..be77844d --- /dev/null +++ b/src/skinnedmesh.h @@ -0,0 +1,51 @@ +#ifndef SKINNED_MESH_H +#define SKINNED_MESH_H +#include +#include +#include "meshresultcontext.h" +#include "rigcontroller.h" +#include "meshloader.h" + +struct SkinnedMeshWeight +{ + int jointIndex; + float amount; +}; + +struct SkinnedMeshVertex +{ + QVector3D position; + QVector3D normal; + QVector3D posPosition; + QVector3D posNormal; + SkinnedMeshWeight weights[MAX_WEIGHT_NUM]; +}; + +struct SkinnedMeshTriangle +{ + int indicies[3]; + QColor color; +}; + +class SkinnedMesh +{ +public: + SkinnedMesh(const MeshResultContext &resultContext); + ~SkinnedMesh(); + void startRig(); + RigController *rigController(); + void applyRigFrameToMesh(const RigFrame &frame); + MeshLoader *toMeshLoader(); +private: + void fromMeshResultContext(MeshResultContext &resultContext); +private: + MeshResultContext m_resultContext; + RigController *m_rigController; + JointNodeTree *m_jointNodeTree; +private: + Q_DISABLE_COPY(SkinnedMesh); + std::vector m_vertices; + std::vector m_triangles; +}; + +#endif