From 289fe1fbf4211e9332c12984d865883b4a1e5bf0 Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Thu, 28 Jun 2018 21:17:21 +0800 Subject: [PATCH] Add experiment walk animation clip generation. - Fix intermediate bones remove; - Add locomotion controller; - Add head, tail node marks for animation. --- ACKNOWLEDGEMENTS.html | 10 + dust3d.pro | 9 + src/animationclipgenerator.cpp | 72 ++++--- src/animationclipgenerator.h | 2 + src/animationpanelwidget.cpp | 2 +- src/ccdikresolver.cpp | 4 +- src/dust3dutil.cpp | 9 + src/dust3dutil.h | 1 + src/ikjoint.cpp | 33 ++++ src/ikjoint.h | 8 + src/intermediateboneremover.cpp | 6 +- src/jointnodetree.cpp | 194 +++++++++++++++++++ src/jointnodetree.h | 24 +++ src/locomotioncontroller.cpp | 326 ++++++++++++++++++++++++++++++++ src/locomotioncontroller.h | 28 +++ src/meshresultcontext.cpp | 65 ++++++- src/meshresultcontext.h | 4 + src/pogostick.cpp | 121 ++++++++++++ src/pogostick.h | 46 +++++ src/skeletonbonemark.h | 18 ++ src/skeletondocumentwindow.cpp | 2 + 21 files changed, 948 insertions(+), 36 deletions(-) create mode 100644 src/ikjoint.cpp create mode 100644 src/ikjoint.h create mode 100644 src/locomotioncontroller.cpp create mode 100644 src/locomotioncontroller.h create mode 100644 src/pogostick.cpp create mode 100644 src/pogostick.h diff --git a/ACKNOWLEDGEMENTS.html b/ACKNOWLEDGEMENTS.html index df33c78a..2d51ba8b 100644 --- a/ACKNOWLEDGEMENTS.html +++ b/ACKNOWLEDGEMENTS.html @@ -563,3 +563,13 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode Generic Spine Model with Simple Physics for Life-Like Quadrupeds and Reptiles http://liris.cnrs.fr/Documents/Liris-5778.pdf + +

Hermite Curves

+
+    https://en.wikibooks.org/wiki/Cg_Programming/Unity/Hermite_Curves
+
+ +

AppImage

+
+    https://appimage.org/
+
diff --git a/dust3d.pro b/dust3d.pro index 77ce8ce5..ad6ced1c 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -155,6 +155,15 @@ HEADERS += src/skinnedmesh.h SOURCES += src/ragdoll.cpp HEADERS += src/ragdoll.h +SOURCES += src/pogostick.cpp +HEADERS += src/pogostick.h + +SOURCES += src/ikjoint.cpp +HEADERS += src/ikjoint.h + +SOURCES += src/locomotioncontroller.cpp +HEADERS += src/locomotioncontroller.h + HEADERS += src/qtlightmapper.h SOURCES += src/main.cpp diff --git a/src/animationclipgenerator.cpp b/src/animationclipgenerator.cpp index 3b6ba362..ec310e56 100644 --- a/src/animationclipgenerator.cpp +++ b/src/animationclipgenerator.cpp @@ -4,7 +4,7 @@ const std::vector AnimationClipGenerator::supportedClipNames = { "Idle", - //"Walk", + "Walk", //"Run", //"Attack", //"Hurt", @@ -41,6 +41,7 @@ AnimationClipGenerator::~AnimationClipGenerator() #if USE_RAGDOLL delete m_ragdoll; #endif + delete m_locomotionController; } std::vector> AnimationClipGenerator::takeFrameMeshes() @@ -57,21 +58,26 @@ void AnimationClipGenerator::generateFrame(SkinnedMesh &skinnedMesh, float amoun rigController->resetFrame(); - if (m_clipName == "Idle") - rigController->idle(amount); - RigFrame frame(jointNodeTree->joints().size()); -#if USE_BULLET - if (nullptr == m_ragdoll) { + if (m_clipName == "Idle") { + rigController->idle(amount); rigController->saveFrame(frame); - } else { - m_ragdoll->stepSimulation(duration); - m_ragdoll->saveFrame(frame); - } -#else - rigController->saveFrame(frame); + } else if (m_clipName == "Walk") { + m_locomotionController->simulate(amount); + JointNodeTree outputJointNodeTree = m_locomotionController->outputJointNodeTreeOnlyPositions(); + outputJointNodeTree.recalculateMatricesAfterPositionUpdated(); + m_jointNodeTree.diff(outputJointNodeTree, frame); + } else if (m_clipName == "Die") { +#if USE_BULLET + if (nullptr == m_ragdoll) { + rigController->saveFrame(frame); + } else { + m_ragdoll->stepSimulation(duration); + m_ragdoll->saveFrame(frame); + } #endif + } if (m_wantMesh) { skinnedMesh.applyRigFrameToMesh(frame); @@ -91,8 +97,31 @@ void AnimationClipGenerator::generate() RigController *rigController = skinnedMesh.rigController(); rigController->resetFrame(); + qDebug() << "animation clip name:" << m_clipName; + + if (m_clipName == "Idle") { + float duration = 0.1; + float nextBeginTime = 0; + for (float amount = 0.0; amount <= 0.05; amount += 0.01) { + generateFrame(skinnedMesh, amount, nextBeginTime, duration); + nextBeginTime += duration; + } + for (float amount = 0.05; amount >= 0.0; amount -= 0.01) { + generateFrame(skinnedMesh, amount, nextBeginTime, duration); + nextBeginTime += duration; + } + } else if (m_clipName == "Walk") { + m_locomotionController = new LocomotionController(m_jointNodeTree); + m_locomotionController->prepare(); + + float duration = 0.05; + float nextBeginTime = 0; + for (float amount = 0.0; amount <= 1; amount += duration) { + generateFrame(skinnedMesh, amount, nextBeginTime, duration); + nextBeginTime += duration; + } + } else if (m_clipName == "Die") { #if USE_BULLET - if (m_clipName == "Die") { delete m_ragdoll; float averageLegHeight = rigController->averageLegHeight(); rigController->liftLeftLegs(QVector3D(0, averageLegHeight * 1.2, 0)); @@ -104,27 +133,14 @@ void AnimationClipGenerator::generate() m_ragdoll = new Ragdoll(m_jointNodeTree, ragdollJointNodeTree); m_ragdoll->setGroundPlaneY(rigController->minY()); m_ragdoll->prepare(); - } -#endif - - if (m_clipName == "Die") { + float duration = 1.0 / 120; float nextBeginTime = 0; for (float amount = 0.0; amount <= 3; amount += duration) { generateFrame(skinnedMesh, amount, nextBeginTime, duration); nextBeginTime += duration; } - } else { - float duration = 0.1; - float nextBeginTime = 0; - for (float amount = 0.0; amount <= 0.05; amount += 0.01) { - generateFrame(skinnedMesh, amount, nextBeginTime, duration); - nextBeginTime += duration; - } - for (float amount = 0.05; amount >= 0.0; amount -= 0.01) { - generateFrame(skinnedMesh, amount, nextBeginTime, duration); - nextBeginTime += duration; - } +#endif } } diff --git a/src/animationclipgenerator.h b/src/animationclipgenerator.h index 8341cdb6..9915c93a 100644 --- a/src/animationclipgenerator.h +++ b/src/animationclipgenerator.h @@ -8,6 +8,7 @@ #include "skinnedmesh.h" #include "jointnodetree.h" #include "ragdoll.h" +#include "locomotioncontroller.h" class AnimationClipGenerator : public QObject { @@ -39,6 +40,7 @@ private: #if USE_BULLET Ragdoll *m_ragdoll = nullptr; #endif + LocomotionController *m_locomotionController = nullptr; }; #endif diff --git a/src/animationpanelwidget.cpp b/src/animationpanelwidget.cpp index 1fc753ce..0b9fcbe2 100644 --- a/src/animationpanelwidget.cpp +++ b/src/animationpanelwidget.cpp @@ -20,7 +20,7 @@ AnimationPanelWidget::AnimationPanelWidget(SkeletonDocument *document, QWidget * buttonsLayout->addSpacing(10); for (const auto &clipName: AnimationClipGenerator::supportedClipNames) { - QPushButton *clipButton = new QPushButton(QObject::tr(qPrintable(clipName))); + QPushButton *clipButton = new QPushButton(QObject::tr(qPrintable(clipName + " (Experiment)"))); connect(clipButton, &QPushButton::clicked, [=] { generateClip(clipName); }); diff --git a/src/ccdikresolver.cpp b/src/ccdikresolver.cpp index a63a9b09..4b8a36a1 100644 --- a/src/ccdikresolver.cpp +++ b/src/ccdikresolver.cpp @@ -32,13 +32,13 @@ int CCDIKSolver::addNodeInOrder(const QVector3D &position) void CCDIKSolver::solveTo(const QVector3D &position) { - qDebug() << "solveTo:" << position; + //qDebug() << "solveTo:" << position; m_destination = position; float lastDistance2 = 0; for (int i = 0; i < m_maxRound; i++) { const auto &endEffector = m_nodes[m_nodes.size() - 1]; float distance2 = (endEffector.position - m_destination).lengthSquared(); - qDebug() << "Round:" << i << " distance2:" << distance2; + //qDebug() << "Round:" << i << " distance2:" << distance2; if (distance2 <= m_distanceThreshold2) break; if (lastDistance2 > 0 && abs(distance2 - lastDistance2) <= m_distanceCeaseThreshold2) diff --git a/src/dust3dutil.cpp b/src/dust3dutil.cpp index 782ff152..05051c84 100644 --- a/src/dust3dutil.cpp +++ b/src/dust3dutil.cpp @@ -26,3 +26,12 @@ void qNormalizeAngle(int &angle) while (angle > 360 * 16) angle -= 360 * 16; } + +// https://en.wikibooks.org/wiki/Cg_Programming/Unity/Hermite_Curves +QVector3D pointInHermiteCurve(float t, QVector3D p0, QVector3D m0, QVector3D p1, QVector3D m1) +{ + return (2.0f * t * t * t - 3.0f * t * t + 1.0f) * p0 + + (t * t * t - 2.0f * t * t + t) * m0 + + (-2.0f * t * t * t + 3.0f * t * t) * p1 + + (t * t * t - t * t) * m1; +} diff --git a/src/dust3dutil.h b/src/dust3dutil.h index cbbc80a1..bbec7009 100644 --- a/src/dust3dutil.h +++ b/src/dust3dutil.h @@ -14,5 +14,6 @@ QString valueOfKeyInMapOrEmpty(const std::map &map, const QStr bool isTrueValueString(const QString &str); bool isFloatEqual(float a, float b); void qNormalizeAngle(int &angle); +QVector3D pointInHermiteCurve(float t, QVector3D p0, QVector3D m0, QVector3D p1, QVector3D m1); #endif diff --git a/src/ikjoint.cpp b/src/ikjoint.cpp new file mode 100644 index 00000000..3ac15a60 --- /dev/null +++ b/src/ikjoint.cpp @@ -0,0 +1,33 @@ +#include "ikjoint.h" +#include "ccdikresolver.h" + +void moveIkJoints(const JointNodeTree &inputJointNodeTree, JointNodeTree &outputJointNodeTree, + int startJointIndex, int endJointIndex, QVector3D destination) +{ + CCDIKSolver ikSolver; + ikSolver.setMaxRound(10); + int loopIndex = endJointIndex; + + std::vector ikSolvingIndicies; + while (-1 != loopIndex) { + const auto &joint = inputJointNodeTree.joints()[loopIndex]; + ikSolvingIndicies.push_back(loopIndex); + if (loopIndex == startJointIndex) + break; + loopIndex = joint.parentIndex; + } + std::reverse(std::begin(ikSolvingIndicies), std::end(ikSolvingIndicies)); + for (const auto &jointIndex: ikSolvingIndicies) { + ikSolver.addNodeInOrder(inputJointNodeTree.joints()[jointIndex].position); + } + + ikSolver.solveTo(destination); + int nodeCount = ikSolver.getNodeCount(); + Q_ASSERT(nodeCount == (int)ikSolvingIndicies.size()); + + for (int i = 0; i < nodeCount; i++) { + int jointIndex = ikSolvingIndicies[i]; + const QVector3D &newPosition = ikSolver.getNodeSolvedPosition(i); + outputJointNodeTree.joints()[jointIndex].position = newPosition; + } +} diff --git a/src/ikjoint.h b/src/ikjoint.h new file mode 100644 index 00000000..585f3b88 --- /dev/null +++ b/src/ikjoint.h @@ -0,0 +1,8 @@ +#ifndef IK_JOINT_H +#define IK_JOINT_H +#include "jointnodetree.h" + +void moveIkJoints(const JointNodeTree &inputJointNodeTree, JointNodeTree &outputJointNodeTree, + int startJointIndex, int endJointIndex, QVector3D destination); + +#endif diff --git a/src/intermediateboneremover.cpp b/src/intermediateboneremover.cpp index a0e2906e..5827e3e4 100644 --- a/src/intermediateboneremover.cpp +++ b/src/intermediateboneremover.cpp @@ -127,11 +127,9 @@ void IntermediateBoneRemover::solveTrivialFromPairAndSaveTraceTo(std::pair= 0; i--) { if (m_trivialNodeSet.find(history[i]) == m_trivialNodeSet.end()) { bool addChildren = false; diff --git a/src/jointnodetree.cpp b/src/jointnodetree.cpp index eb77ae77..d4dd5b55 100644 --- a/src/jointnodetree.cpp +++ b/src/jointnodetree.cpp @@ -9,6 +9,8 @@ JointNodeTree::JointNodeTree(MeshResultContext &resultContext) return; } + isVerticalSpine = resultContext.isVerticalSpine(); + JointInfo rootCenterJoint; { rootCenterJoint.jointIndex = m_tracedJoints.size(); @@ -30,6 +32,101 @@ JointNodeTree::JointNodeTree(MeshResultContext &resultContext) traceBoneFromJoint(resultContext, std::make_pair(rootNode->bmeshId, rootNode->nodeId), visitedNodes, connections, rootCenterJoint.jointIndex); calculateMatricesByPosition(); + + collectParts(); +} + +const std::vector> &JointNodeTree::legs() const +{ + return m_legs; +} + +const std::vector &JointNodeTree::spine() const +{ + return m_spine; +} + +const std::vector> &JointNodeTree::leftLegs() const +{ + return m_leftLegs; +} + +const std::vector> &JointNodeTree::rightLegs() const +{ + return m_rightLegs; +} + +void JointNodeTree::collectParts() +{ + m_legs.clear(); + m_spine.clear(); + for (const auto &node: joints()) { + if (node.boneMark == SkeletonBoneMark::Spine) { + m_spine.push_back(node.jointIndex); + } + if (node.boneMark == SkeletonBoneMark::Head) { + if (-1 != head) { + qDebug() << "Get multiple head markers"; + } + head = node.jointIndex; + } + if (node.boneMark == SkeletonBoneMark::Tail) { + if (-1 != tail) { + qDebug() << "Get multiple tail markers"; + } + tail = node.jointIndex; + } + if (node.boneMark == SkeletonBoneMark::LegStart && node.children.size() == 1) { + const auto legStart = node.jointIndex; + const JointInfo *loopNode = &joints()[node.children[0]]; + while (loopNode->boneMark != SkeletonBoneMark::LegEnd && + loopNode->children.size() == 1) { + loopNode = &joints()[loopNode->children[0]]; + } + if (loopNode->boneMark == SkeletonBoneMark::LegEnd) { + const auto legEnd = loopNode->jointIndex; + addLeg(legStart, legEnd); + } else { + qDebug() << "Find leg" << node.partId << "'s end failed"; + } + } + } + sortLegs(m_leftLegs); + sortLegs(m_rightLegs); + sortSpine(m_spine); +} + +void JointNodeTree::sortLegs(std::vector> &legs) +{ + const auto &that = this; + std::sort(legs.begin(), legs.end(), [&that](const std::pair &a, const std::pair &b) -> bool { + const auto &firstLegEnd = that->joints()[a.second]; + const auto &secondLegEnd = that->joints()[b.second]; + return firstLegEnd.position.z() > secondLegEnd.position.z(); + }); +} + +void JointNodeTree::sortSpine(std::vector &spine) +{ + const auto &that = this; + std::sort(spine.begin(), spine.end(), [&that](const int &a, const int &b) -> bool { + const auto &firstNode = that->joints()[a]; + const auto &secondNode = that->joints()[b]; + if (that->isVerticalSpine) + return firstNode.position.y() > secondNode.position.y(); + else + return firstNode.position.z() > secondNode.position.z(); + }); +} + +void JointNodeTree::addLeg(int legStart, int legEnd) +{ + const auto &legEndJoint = joints()[legEnd]; + m_legs.push_back(std::make_pair(legStart, legEnd)); + if (legEndJoint.position.x() > 0) + m_leftLegs.push_back(std::make_pair(legStart, legEnd)); + else + m_rightLegs.push_back(std::make_pair(legStart, legEnd)); } void JointNodeTree::traceBoneFromJoint(MeshResultContext &resultContext, std::pair node, std::set> &visitedNodes, std::set, std::pair>> &connections, int parentIndex) @@ -65,6 +162,7 @@ void JointNodeTree::traceBoneFromJoint(MeshResultContext &resultContext, std::pa joint.boneMark = toNode->second->boneMark; joint.scale = QVector3D(1.0, 1.0, 1.0); joint.radius = toNode->second->radius; + joint.parentIndex = parentIndex; m_tracedNodeToJointIndexMap[std::make_pair(it.first, it.second)] = joint.jointIndex; @@ -184,3 +282,99 @@ void JointNodeTree::recalculateMatricesAfterTransformUpdated() calculateMatricesByTransform(); } +void JointNodeTree::diff(const JointNodeTree &another, RigFrame &rigFrame) +{ + if (rigFrame.translations.size() != joints().size()) + rigFrame = RigFrame(joints().size()); + for (const auto &first: joints()) { + const auto &second = another.joints()[first.jointIndex]; + if (!qFuzzyCompare(first.translation, second.translation)) { + rigFrame.updateTranslation(first.jointIndex, second.translation); + } + if (!qFuzzyCompare(first.scale, second.scale)) { + rigFrame.updateScale(first.jointIndex, second.scale); + } + if (!qFuzzyCompare(first.rotation, second.rotation)) { + rigFrame.updateRotation(first.jointIndex, second.rotation); + } + } +} + +int JointNodeTree::findHubJoint(int jointIndex, std::vector *tracePath) const +{ + int loopJointIndex = joints()[jointIndex].parentIndex; + while (-1 != loopJointIndex) { + if (nullptr != tracePath) + tracePath->push_back(loopJointIndex); + const auto &joint = joints()[loopJointIndex]; + if (joint.children.size() > 1 || -1 == joint.parentIndex) + return joint.jointIndex; + loopJointIndex = joint.parentIndex; + } + return -1; +} + +int JointNodeTree::findParent(int jointIndex, int parentIndex, std::vector *tracePath) const +{ + int loopJointIndex = joints()[jointIndex].parentIndex; + while (-1 != loopJointIndex) { + if (nullptr != tracePath) + tracePath->push_back(loopJointIndex); + const auto &joint = joints()[loopJointIndex]; + if (joint.jointIndex == parentIndex) + return joint.jointIndex; + loopJointIndex = joint.parentIndex; + } + return -1; +} + +int JointNodeTree::findSpineFromChild(int jointIndex) +{ + int loopJointIndex = joints()[jointIndex].parentIndex; + while (-1 != loopJointIndex) { + const auto &joint = joints()[loopJointIndex]; + if (joint.boneMark == SkeletonBoneMark::Spine) + return joint.jointIndex; + loopJointIndex = joint.parentIndex; + } + return -1; +} + +void JointNodeTree::collectChildren(int jointIndex, std::vector &children) const +{ + for (const auto &child: joints()[jointIndex].children) { + children.push_back(child); + } + for (const auto &child: joints()[jointIndex].children) { + collectChildren(child, children); + } +} + +void JointNodeTree::collectTrivialChildren(int jointIndex, std::vector &children) const +{ + for (const auto &child: joints()[jointIndex].children) { + if (joints()[child].boneMark != SkeletonBoneMark::None) { + continue; + } + std::vector subChildren; + bool hasNoTrivialNode = false; + collectTrivialChildrenStopEarlyOnNoTrivial(child, subChildren, hasNoTrivialNode); + if (hasNoTrivialNode) + continue; + children.push_back(child); + children.insert(children.end(), subChildren.begin(), subChildren.end()); + } +} + +void JointNodeTree::collectTrivialChildrenStopEarlyOnNoTrivial(int jointIndex, std::vector &children, bool &hasNoTrivialNode) const +{ + for (const auto &child: joints()[jointIndex].children) { + if (joints()[child].boneMark != SkeletonBoneMark::None) { + hasNoTrivialNode = true; + return; + } + children.push_back(child); + collectTrivialChildrenStopEarlyOnNoTrivial(child, children, hasNoTrivialNode); + } +} + diff --git a/src/jointnodetree.h b/src/jointnodetree.h index 52c54be7..e3f84e81 100644 --- a/src/jointnodetree.h +++ b/src/jointnodetree.h @@ -5,6 +5,7 @@ #include #include "meshresultcontext.h" #include "skeletonbonemark.h" +#include "rigframe.h" struct JointInfo { @@ -33,6 +34,19 @@ public: int nodeToJointIndex(int partId, int nodeId) const; void recalculateMatricesAfterPositionUpdated(); void recalculateMatricesAfterTransformUpdated(); + const std::vector> &legs() const; + const std::vector &spine() const; + const std::vector> &leftLegs() const; + const std::vector> &rightLegs() const; + void diff(const JointNodeTree &another, RigFrame &rigFrame); + int findHubJoint(int jointIndex, std::vector *tracePath=nullptr) const; + void collectChildren(int jointIndex, std::vector &children) const; + void collectTrivialChildren(int jointIndex, std::vector &children) const; + int findParent(int jointIndex, int parentIndex, std::vector *tracePath=nullptr) const; + int findSpineFromChild(int jointIndex); + bool isVerticalSpine = false; + int head = -1; + int tail = -1; private: void calculateMatricesByTransform(); void calculateMatricesByPosition(); @@ -40,6 +54,16 @@ private: 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); + void collectParts(); + void addLeg(int legStart, int legEnd); + void sortLegs(std::vector> &legs); + void sortSpine(std::vector &spine); + void collectTrivialChildrenStopEarlyOnNoTrivial(int jointIndex, std::vector &children, bool &hasNoTrivialNode) const; +private: + std::vector> m_legs; + std::vector> m_leftLegs; + std::vector> m_rightLegs; + std::vector m_spine; }; #endif diff --git a/src/locomotioncontroller.cpp b/src/locomotioncontroller.cpp new file mode 100644 index 00000000..1530e9d4 --- /dev/null +++ b/src/locomotioncontroller.cpp @@ -0,0 +1,326 @@ +#include +#include "locomotioncontroller.h" +#include "ikjoint.h" +#include "dust3dutil.h" + +LocomotionController::LocomotionController(const JointNodeTree &jointNodeTree) : + m_inputJointNodeTree(jointNodeTree), + m_outputJointNodeTree(jointNodeTree) +{ +} + +LocomotionController::~LocomotionController() +{ + release(); +} + +void LocomotionController::release() +{ + m_leftPogoSticks.clear(); + m_rightPogoSticks.clear(); +} + +void LocomotionController::prepare() +{ + release(); + + for (const auto &leg: m_inputJointNodeTree.leftLegs()) { + const auto &legStart = m_inputJointNodeTree.joints()[leg.first]; + const auto &legEnd = m_inputJointNodeTree.joints()[leg.second]; + + std::vector childrenOfLegEnd; + m_inputJointNodeTree.collectChildren(leg.second, childrenOfLegEnd); + m_childrenOfLeftLegEnds.push_back(childrenOfLegEnd); + + PogoStick pogoStick; + pogoStick.setGroundLocation(legEnd.position.y()); + pogoStick.setRestPelvisLocation(legStart.position.y()); + m_leftPogoSticks.push_back(pogoStick); + } + + for (const auto &leg: m_inputJointNodeTree.rightLegs()) { + const auto &legStart = m_inputJointNodeTree.joints()[leg.first]; + const auto &legEnd = m_inputJointNodeTree.joints()[leg.second]; + + std::vector childrenOfLegEnd; + m_inputJointNodeTree.collectChildren(leg.second, childrenOfLegEnd); + m_childrenOfRightLegEnds.push_back(childrenOfLegEnd); + + PogoStick pogoStick; + pogoStick.setGroundLocation(legEnd.position.y()); + pogoStick.setRestPelvisLocation(legStart.position.y()); + m_rightPogoSticks.push_back(pogoStick); + } +} + +void LocomotionController::simulateLeg(PogoStick *pogoStick, const std::vector &childrenOfLegEnd, std::pair leg, float amount, QVector3D *footDirection, QVector3D *finalLegStartPosition, float *finalLegStartOffsetY) +{ + float time = amount; + + pogoStick->simulate(time); + + const auto &legStart = m_outputJointNodeTree.joints()[leg.first]; + const auto &legEnd = m_outputJointNodeTree.joints()[leg.second]; + + float targetLegStartY = legStart.position.y() + (pogoStick->currentPelvisLocation() - legStart.position.y()) * 0.2; + float targetLegEndY = pogoStick->currentFootLocation(); + float targetLegStartZ = legStart.position.z() + pogoStick->currentFootHorizontalOffset() * 0.05; + float targetLegEndZ = legEnd.position.z() + pogoStick->currentFootHorizontalOffset(); + + QVector3D targetLegStartPosition = QVector3D(legStart.position.x(), targetLegStartY, targetLegStartZ); + QVector3D targetLegEndPosition = QVector3D(legEnd.position.x(), targetLegEndY, targetLegEndZ); + QVector3D initialLegStartPosition = legStart.position; + QVector3D initialLegEndPosition = legEnd.position; + + std::vector tracePath; + int legParentIndex = m_inputJointNodeTree.findHubJoint(leg.first, &tracePath); + if (-1 == legParentIndex) { + m_outputJointNodeTree.joints()[leg.first].position = targetLegStartPosition; + } else { + moveIkJoints(m_outputJointNodeTree, m_outputJointNodeTree, legParentIndex, leg.first, targetLegStartPosition); + } + + moveIkJoints(m_outputJointNodeTree, m_outputJointNodeTree, leg.first, leg.second, targetLegEndPosition); + + QVector3D finalLegEndTranslation = legEnd.position - initialLegEndPosition; + + for (const auto &child: childrenOfLegEnd) { + m_outputJointNodeTree.joints()[child].position += finalLegEndTranslation; + } + + if (m_outputJointNodeTree.joints()[leg.first].children.size() > 0) { + int theNodeIndexDirectBelowLegStart = m_outputJointNodeTree.joints()[leg.first].children[0]; + if (footDirection) + *footDirection = m_outputJointNodeTree.joints()[theNodeIndexDirectBelowLegStart].position - legStart.position; + } else { + if (footDirection) + *footDirection = legEnd.position - legStart.position; + } + + if (finalLegStartPosition) + *finalLegStartPosition = legEnd.position; + if (finalLegStartOffsetY) + *finalLegStartOffsetY = legStart.position.y() - initialLegStartPosition.y(); +} + +void LocomotionController::simulate(float amount) +{ + const auto pointerFront = QVector3D(0, 0, 1); + const auto pointerOutFromCanvas = QVector3D(1, 0, 0); + const auto pointerUp = QVector3D(0, 1, 0); + float offset = 0.3; + float delays[2] = {0}; + m_outputJointNodeTree = m_inputJointNodeTree; + std::vector leftPitches; + std::vector rightPitches; + std::vector leftLegStartPositions; + std::vector rightLegStartPositions; + std::vector leftLegStartOffsetYs; + std::vector rightLegStartOffsetYs; + leftPitches.resize(m_leftPogoSticks.size()); + leftLegStartPositions.resize(m_leftPogoSticks.size()); + leftLegStartOffsetYs.resize(m_leftPogoSticks.size()); + for (int i = 0; i < (int)m_leftPogoSticks.size(); i++) { + const auto &leg = m_inputJointNodeTree.leftLegs()[i]; + auto pogoStick = &m_leftPogoSticks[i]; + QVector3D footDirection; + simulateLeg(pogoStick, m_childrenOfLeftLegEnds[i], leg, amount + delays[i % 2], &footDirection, &leftLegStartPositions[i], &leftLegStartOffsetYs[i]); + leftPitches[i] = -QVector3D::crossProduct(-footDirection, pointerOutFromCanvas); + delays[i % 2] += offset; + } + delays[0] = 0.5; + delays[1] = 0.5; + rightPitches.resize(m_rightPogoSticks.size()); + rightLegStartPositions.resize(m_rightPogoSticks.size()); + rightLegStartOffsetYs.resize(m_rightPogoSticks.size()); + for (int i = 0; i < (int)m_rightPogoSticks.size(); i++) { + const auto &leg = m_inputJointNodeTree.rightLegs()[i]; + auto pogoStick = &m_rightPogoSticks[i]; + QVector3D footDirection; + simulateLeg(pogoStick, m_childrenOfRightLegEnds[i], leg, amount + delays[i % 2], &footDirection, &rightLegStartPositions[i], &rightLegStartOffsetYs[i]); + rightPitches[i] = -QVector3D::crossProduct(-footDirection, pointerOutFromCanvas); + delays[i % 2] += offset; + } + + if (m_inputJointNodeTree.spine().empty()) + return; + + // Calculate orientation for each leg pair + std::vector spineOrientations; + spineOrientations.resize(std::max(leftLegStartPositions.size(), rightLegStartPositions.size()), pointerFront); + for (int i = 0; i < (int)leftLegStartPositions.size() && i < (int)rightLegStartPositions.size(); i++) { + spineOrientations[i] = -QVector3D::crossProduct(leftLegStartPositions[i] - rightLegStartPositions[i], pointerUp); + } + + // Calculate average pitch for each leg + std::vector spinePitches; + spinePitches.resize(std::max(leftPitches.size(), rightPitches.size())); + for (int i = 0; i < (int)m_leftPogoSticks.size(); i++) { + spinePitches[i] = leftPitches[i]; + } + for (int i = 0; i < (int)m_rightPogoSticks.size(); i++) { + spinePitches[i] += rightPitches[i]; + } + for (int i = 0; i < (int)spinePitches.size(); i++) { + spinePitches[i].normalize(); + if (i < (int)spineOrientations.size()) { + QQuaternion rotation; + rotation.rotationTo(pointerFront, spineOrientations[i]); + spinePitches[i] = rotation.rotatedVector(spinePitches[i]); + } + } + + // Find spine node for each leg pair + std::vector spineNodes; + spineNodes.resize(spinePitches.size(), -1); + for (int i = 0; i < (int)spinePitches.size(); i++) { + int leftSpine = -1; + if (i < (int)m_inputJointNodeTree.leftLegs().size()) { + leftSpine = m_inputJointNodeTree.findSpineFromChild(m_inputJointNodeTree.leftLegs()[i].first); + } + int rightSpine = -1; + if (i < (int)m_inputJointNodeTree.rightLegs().size()) { + rightSpine = m_inputJointNodeTree.findSpineFromChild(m_inputJointNodeTree.rightLegs()[i].first); + } + if (-1 == leftSpine && -1 == rightSpine) + continue; + if (leftSpine == rightSpine) { + spineNodes[i] = leftSpine; + continue; + } + if (-1 == rightSpine) { + spineNodes[i] = leftSpine; + continue; + } + if (-1 == leftSpine) { + spineNodes[i] = rightSpine; + continue; + } + qDebug() << "leftSpine:" << leftSpine << " not equal rightSpine:" << rightSpine; + if (m_inputJointNodeTree.joints()[leftSpine].position.z() > m_inputJointNodeTree.joints()[rightSpine].position.z()) { + spineNodes[i] = leftSpine; + continue; + } + spineNodes[i] = rightSpine; + } + + // Merge leg start offsets + std::vector legStartOffsets; + legStartOffsets.resize(std::max(leftLegStartOffsetYs.size(), rightLegStartOffsetYs.size())); + for (int i = 0; i < (int)spineNodes.size(); i++) { + float offsetY = 0; + if (i < (int)leftLegStartOffsetYs.size()) { + offsetY += leftLegStartOffsetYs[i]; + } + if (i < (int)rightLegStartOffsetYs.size()) { + offsetY += rightLegStartOffsetYs[i]; + } + offsetY /= 2; + offsetY *= 0.5; + legStartOffsets[i] = QVector3D(0, offsetY, 0); + } + + // Move spine nodes + for (int i = 0; i < (int)spineNodes.size(); i++) { + if (i < (int)legStartOffsets.size()) { + QVector3D offset = legStartOffsets[i]; + m_outputJointNodeTree.joints()[spineNodes[i]].position += offset; + std::vector children; + m_inputJointNodeTree.collectTrivialChildren(spineNodes[i], children); + for (const auto &child: children) { + m_outputJointNodeTree.joints()[child].position += offset; + } + } + } + + // Move tail + if (-1 != m_inputJointNodeTree.tail && spineNodes.size() > 0) { + int lastSpineJointIndex = spineNodes[spineNodes.size() - 1]; + std::vector spineIndicies; + if (lastSpineJointIndex == m_inputJointNodeTree.findParent(m_inputJointNodeTree.tail, + lastSpineJointIndex, &spineIndicies)) { + std::vector tailChildren; + m_inputJointNodeTree.collectChildren(m_inputJointNodeTree.tail, tailChildren); + const auto offset = legStartOffsets[spineNodes.size() - 1]; + for (int i = (int)spineIndicies.size() - 2; i >= 0; i--) { + m_outputJointNodeTree.joints()[spineIndicies[i]].position += offset; + } + m_outputJointNodeTree.joints()[m_inputJointNodeTree.tail].position += offset; + for (const auto &child: tailChildren) { + m_outputJointNodeTree.joints()[child].position += offset; + } + } + } + + // Adjust neck + if (-1 != m_inputJointNodeTree.head && spineNodes.size() > 0 && spinePitches.size() > 0) { + int firstSpineJointIndex = spineNodes[0]; + int headParent = m_inputJointNodeTree.joints()[m_inputJointNodeTree.head].parentIndex; + if (-1 != headParent) { + makeInbetweenNodesInHermiteCurve(m_inputJointNodeTree.head, + m_inputJointNodeTree.joints()[m_inputJointNodeTree.head].position - m_inputJointNodeTree.joints()[headParent].position, + firstSpineJointIndex, + spinePitches[0]); + } + } + + // Adjust spine inbetween legs + for (int i = 1; i < (int)spineNodes.size() && i < (int)spinePitches.size(); i++) { + makeInbetweenNodesInHermiteCurve(spineNodes[i - 1], spinePitches[i - 1], + spineNodes[i], spinePitches[i]); + } +} + +void LocomotionController::makeInbetweenNodesInHermiteCurve(int firstJointIndex, QVector3D firstPitch, int secondJointIndex, QVector3D secondPitch) +{ + int child = firstJointIndex; + int parent = secondJointIndex; + QVector3D childPitch = firstPitch; + QVector3D parentPitch = secondPitch; + if (child < parent) { + child = secondJointIndex; + parent = firstJointIndex; + childPitch = secondPitch; + parentPitch = firstPitch; + } + + std::vector inbetweenIndicies; + if (parent != m_inputJointNodeTree.findParent(child, + parent, &inbetweenIndicies)) { + return; + } + if (inbetweenIndicies.size() - 1 <= 0) + return; + + // Calculate total length + float length = m_inputJointNodeTree.joints()[inbetweenIndicies[0]].position.distanceToPoint(m_inputJointNodeTree.joints()[child].position); + std::vector lengths; + float totalLength = length; + lengths.push_back(length); + for (int i = 1; i <= (int)inbetweenIndicies.size() - 1; i++) { + length = m_inputJointNodeTree.joints()[inbetweenIndicies[i - 1]].position.distanceToPoint(m_inputJointNodeTree.joints()[inbetweenIndicies[i]].position); + lengths.push_back(length); + totalLength += length; + } + if (qFuzzyIsNull(totalLength)) { + qDebug() << "Inbetween nodes is too short"; + } else { + float accumLength = 0; + const QVector3D &p0 = m_inputJointNodeTree.joints()[parent].position; + const QVector3D &m0 = parentPitch; + const QVector3D &p1 = m_inputJointNodeTree.joints()[child].position; + const QVector3D &m1 = childPitch; + for (int i = (int)inbetweenIndicies.size() - 2; i >= 0; i--) { + accumLength += lengths[i + 1]; + float t = accumLength / totalLength; + QVector3D revisedPosition = pointInHermiteCurve(t, p0, m0, p1, m1); + qDebug() << "pointInHermiteCurve(t:<<" << t << ", p0:" << p0 << ", m0:" << m0 << ", p1:" << p1 << ", m1:" << m1 << ") result:" << revisedPosition; + m_outputJointNodeTree.joints()[inbetweenIndicies[i]].position = revisedPosition; + } + } +} + +const JointNodeTree &LocomotionController::outputJointNodeTreeOnlyPositions() const +{ + return m_outputJointNodeTree; +} diff --git a/src/locomotioncontroller.h b/src/locomotioncontroller.h new file mode 100644 index 00000000..35bc433f --- /dev/null +++ b/src/locomotioncontroller.h @@ -0,0 +1,28 @@ +#ifndef LOCOMOTION_CONTROLLER_H +#define LOCOMOTION_CONTROLLER_H +#include "jointnodetree.h" +#include "pogostick.h" + +class LocomotionController +{ +public: + LocomotionController(const JointNodeTree &jointNodeTree); + ~LocomotionController(); + void prepare(); + void release(); + void simulate(float amount); + const JointNodeTree &outputJointNodeTreeOnlyPositions() const; +private: + void simulateLeg(PogoStick *pogoStick, const std::vector &childrenOfLegEnd, std::pair leg, float amount, + QVector3D *footDirection, QVector3D *finalLegStartPosition, float *finalLegStartOffsetY); + void makeInbetweenNodesInHermiteCurve(int firstJointIndex, QVector3D firstPitch, int secondJointIndex, QVector3D secondPitch); +private: + JointNodeTree m_inputJointNodeTree; + JointNodeTree m_outputJointNodeTree; + std::vector m_leftPogoSticks; + std::vector m_rightPogoSticks; + std::vector> m_childrenOfLeftLegEnds; + std::vector> m_childrenOfRightLegEnds; +}; + +#endif diff --git a/src/meshresultcontext.cpp b/src/meshresultcontext.cpp index dfa182ae..a7e0493c 100644 --- a/src/meshresultcontext.cpp +++ b/src/meshresultcontext.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "theme.h" #include "meshresultcontext.h" #include "thekla_atlas.h" @@ -35,7 +36,9 @@ MeshResultContext::MeshResultContext() : m_centerBmeshNode(nullptr), m_resultPartsResolved(false), m_resultTriangleUvsResolved(false), - m_resultRearrangedVerticesResolved(false) + m_resultRearrangedVerticesResolved(false), + m_isVerticalSpine(false), + m_spineDirectionResolved(false) { } @@ -95,6 +98,15 @@ const BmeshNode *MeshResultContext::centerBmeshNode() return m_centerBmeshNode; } +bool MeshResultContext::isVerticalSpine() +{ + if (!m_spineDirectionResolved) { + calculateSpineDirection(m_isVerticalSpine); + m_spineDirectionResolved = true; + } + return m_isVerticalSpine; +} + void MeshResultContext::resolveBmeshConnectivity() { if (!m_bmeshConnectivityResolved) { @@ -416,6 +428,34 @@ void MeshResultContext::calculateBmeshNodeMap(std::map, Bmes } } +void MeshResultContext::calculateSpineDirection(bool &isVerticalSpine) +{ + float minZ = 10000; + float maxZ = -10000; + float minY = 10000; + float maxY = -10000; + int spineNodeNum = 0; + for (auto i = 0u; i < bmeshNodes.size(); i++) { + BmeshNode *bmeshNode = &bmeshNodes[i]; + if (bmeshNode->boneMark != SkeletonBoneMark::Spine) + continue; + if (bmeshNode->origin.y() < minY) + minY = bmeshNode->origin.y(); + if (bmeshNode->origin.y() > maxY) + maxY = bmeshNode->origin.y(); + if (bmeshNode->origin.z() < minZ) + minZ = bmeshNode->origin.z(); + if (bmeshNode->origin.z() > maxZ) + maxZ = bmeshNode->origin.z(); + spineNodeNum++; + } + if (spineNodeNum < 2) + isVerticalSpine = true; + else + isVerticalSpine = fabs(maxY - minY) > fabs(maxZ - minZ); + qDebug() << "calculateSpineDirection isVerticalSpine:" << isVerticalSpine; +} + struct BmeshNodeDistWithWorldCenter { BmeshNode *bmeshNode; @@ -424,6 +464,29 @@ struct BmeshNodeDistWithWorldCenter BmeshNode *MeshResultContext::calculateCenterBmeshNode() { + // If there are marked spine node, we pick the last spine node as center + bool pickVerticalLast = isVerticalSpine(); + float minLocation = 10000; + BmeshNode *lastSpineNode = nullptr; + for (auto i = 0u; i < bmeshNodes.size(); i++) { + BmeshNode *bmeshNode = &bmeshNodes[i]; + if (bmeshNode->boneMark != SkeletonBoneMark::Spine) + continue; + if (pickVerticalLast) { + if (bmeshNode->origin.y() < minLocation) { + minLocation = bmeshNode->origin.y(); + lastSpineNode = bmeshNode; + } + } else { + if (bmeshNode->origin.z() < minLocation) { + minLocation = bmeshNode->origin.z(); + lastSpineNode = bmeshNode; + } + } + } + if (nullptr != lastSpineNode) + return lastSpineNode; + // Sort all the nodes by distance with world center, excluding leg start nodes std::vector nodesOrderByDistWithWorldCenter; for (auto i = 0u; i < bmeshNodes.size(); i++) { diff --git a/src/meshresultcontext.h b/src/meshresultcontext.h index b1ff0aa4..1870ff07 100644 --- a/src/meshresultcontext.h +++ b/src/meshresultcontext.h @@ -120,6 +120,7 @@ public: const std::vector &rearrangedTriangles(); const std::map> &vertexSourceMap(); void removeIntermediateBones(); + bool isVerticalSpine(); private: bool m_triangleSourceResolved; bool m_triangleColorResolved; @@ -134,6 +135,8 @@ private: bool m_resultPartsResolved; bool m_resultTriangleUvsResolved; bool m_resultRearrangedVerticesResolved; + bool m_isVerticalSpine; + bool m_spineDirectionResolved; private: std::vector> m_triangleSourceNodes; std::vector m_triangleColors; @@ -164,6 +167,7 @@ private: void calculateResultTriangleUvs(std::vector &uvs, std::set &seamVertices); void calculateResultRearrangedVertices(std::vector &rearrangedVertices, std::vector &rearrangedTriangles); void calculateConnectionPossibleRscore(BmeshConnectionPossible &possible); + void calculateSpineDirection(bool &isVerticalSpine); }; #endif diff --git a/src/pogostick.cpp b/src/pogostick.cpp new file mode 100644 index 00000000..e6f8e056 --- /dev/null +++ b/src/pogostick.cpp @@ -0,0 +1,121 @@ +#include +#include +#include "pogostick.h" + +float PogoStick::m_gravitationalAcceleration = 9.8; + +PogoStick::PogoStick() +{ + setStancePhaseDuration(0.8); +} + +void PogoStick::setGroundLocation(float groundLocation) +{ + m_groundLocation = groundLocation; + needCalculateParameters = true; +} + +void PogoStick::setRestPelvisLocation(float restPelvisLocation) +{ + m_restPelvisLocation = restPelvisLocation; + needCalculateParameters = true; +} + +void PogoStick::setStancePhaseDuration(float duration) +{ + m_halfStancePhaseDuration = duration * 0.5; + m_halfSwingPhaseDuration = (1 - duration) * 0.5; + needCalculateParameters = true; +} + +void PogoStick::setAcceleration(float acceleration) +{ + m_accelerationExcludedGravity = acceleration - m_gravitationalAcceleration; + needCalculateParameters = true; +} + +void PogoStick::calculateParametersForSimulation() +{ + m_restBodyHeight = abs(m_restPelvisLocation - m_groundLocation); + + float maxAcceleration = m_restBodyHeight / (0.5 * (m_halfStancePhaseDuration * m_halfStancePhaseDuration)); + if (m_accelerationExcludedGravity > maxAcceleration) { + qDebug() << "Reset acceleration from" << m_accelerationExcludedGravity << "to maxAcceleration:" << maxAcceleration; + m_accelerationExcludedGravity = maxAcceleration; + } + + m_stancePhaseHeight = calculateDisplacement(0, m_accelerationExcludedGravity, m_halfStancePhaseDuration); + m_velocity = m_accelerationExcludedGravity * m_halfStancePhaseDuration; + m_airAcceleration = m_velocity / m_halfSwingPhaseDuration; + m_swingPhaseHeight = calculateDisplacement(0, m_airAcceleration, m_halfSwingPhaseDuration); +} + +float PogoStick::calculateDisplacement(float initialVelocity, float acceleration, float time) +{ + return 0.5 * acceleration * (time * time) + initialVelocity * time; +} + +float PogoStick::currentPelvisLocation() +{ + return m_pelviosLocation; +} + +float PogoStick::currentFootLocation() +{ + if (PogoStickPhase::Reception == m_currentPhase || PogoStickPhase::Propulsion == m_currentPhase) + return m_groundLocation; + return (m_pelviosLocation - m_restBodyHeight); +} + +float PogoStick::currentFootHorizontalOffset() +{ + return m_footHorizontalOffset; +} + +void PogoStick::simulate(float time) +{ + if (time > 1) + time -= 1; + + if (needCalculateParameters) { + needCalculateParameters = false; + calculateParametersForSimulation(); + } + + float stancePhaseDuration = m_halfStancePhaseDuration + m_halfStancePhaseDuration; + float swingPhaseBegin = stancePhaseDuration; + float swingPhaseDuration = 1 - stancePhaseDuration; + if (time <= stancePhaseDuration) { + if (time <= m_halfStancePhaseDuration) { + // Reception Phase (down) + m_currentPhase = PogoStickPhase::Reception; + float displacement = calculateDisplacement(m_velocity, m_accelerationExcludedGravity, time); + qDebug() << "Reception Phase (down) displacement:" << displacement; + m_pelviosLocation = m_restPelvisLocation - displacement; + } else { + // Propulsion Phase (up) + m_currentPhase = PogoStickPhase::Propulsion; + float displacement = calculateDisplacement(0, m_accelerationExcludedGravity, time - m_halfStancePhaseDuration); + qDebug() << "Propulsion Phase (up) displacement:" << displacement; + m_pelviosLocation = m_restPelvisLocation - (m_stancePhaseHeight - displacement); + } + m_footHorizontalOffset = -time * 0.25 / stancePhaseDuration; + } else { + float swingPhaseMiddle = swingPhaseBegin + (1 - swingPhaseBegin) * 0.5; + // Swing Phase + if (time < swingPhaseMiddle) { + // up + m_currentPhase = PogoStickPhase::SwingUp; + float displacement = calculateDisplacement(m_velocity, -m_airAcceleration, time - swingPhaseBegin); + qDebug() << "up displacement:" << displacement; + m_pelviosLocation = m_restPelvisLocation + displacement; + } else { + // down + m_currentPhase = PogoStickPhase::SwingDown; + float displacement = calculateDisplacement(0, m_airAcceleration, time - swingPhaseMiddle); + qDebug() << "down displacement:" << displacement; + m_pelviosLocation = m_restPelvisLocation + (m_swingPhaseHeight - displacement); + } + m_footHorizontalOffset = (time - swingPhaseBegin) * 0.5 / swingPhaseDuration; + } +} diff --git a/src/pogostick.h b/src/pogostick.h new file mode 100644 index 00000000..10d06e5c --- /dev/null +++ b/src/pogostick.h @@ -0,0 +1,46 @@ +#ifndef POGO_STICK_H +#define POGO_STICK_H +#include + +enum class PogoStickPhase +{ + Reception, + Propulsion, + SwingUp, + SwingDown +}; + +class PogoStick +{ +public: + PogoStick(); + void setGroundLocation(float groundLocation); + void setRestPelvisLocation(float restPelvisLocation); + void setStancePhaseDuration(float duration); + void setAcceleration(float acceleration); + void simulate(float time); + float currentPelvisLocation(); + float currentFootLocation(); + float currentFootHorizontalOffset(); +private: + void calculateParametersForSimulation(); + float calculateDisplacement(float initialVelocity, float acceleration, float time); +private: + float m_restBodyHeight = 0; + float m_halfStancePhaseDuration = 0; + float m_halfSwingPhaseDuration = 0; + static float m_gravitationalAcceleration; + float m_airAcceleration = 0; + float m_accelerationExcludedGravity = 20; + float m_stancePhaseHeight = 0; + float m_swingPhaseHeight = 0; + float m_velocity = 0; + float m_pelviosLocation = 0; + float m_groundLocation = 0; + float m_restPelvisLocation = 0; + float m_footHorizontalOffset = 0; + PogoStickPhase m_currentPhase; + bool needCalculateParameters = true; +}; + +#endif diff --git a/src/skeletonbonemark.h b/src/skeletonbonemark.h index bde357fb..7bf30e59 100644 --- a/src/skeletonbonemark.h +++ b/src/skeletonbonemark.h @@ -6,10 +6,12 @@ enum class SkeletonBoneMark { None = 0, + Head, LegStart, LegJoint, LegEnd, Spine, + Tail, Max }; #define SKELETON_BONE_MARK_TYPE_NUM ((int)SkeletonBoneMark::Max - 1) @@ -19,6 +21,8 @@ QColor SkeletonBoneMarkToColor(SkeletonBoneMark mark); QColor SkeletonBoneMarkToColor(SkeletonBoneMark mark) \ { \ switch (mark) { \ + case SkeletonBoneMark::Head: \ + return QColor(0x00, 0x00, 0xff); \ case SkeletonBoneMark::LegStart: \ return QColor(0xf4, 0xcd, 0x56); \ case SkeletonBoneMark::LegJoint: \ @@ -27,6 +31,8 @@ QColor SkeletonBoneMarkToColor(SkeletonBoneMark mark) \ return QColor(0xd0, 0x8c, 0xe0); \ case SkeletonBoneMark::Spine: \ return QColor(0xfc, 0x63, 0x60); \ + case SkeletonBoneMark::Tail: \ + return QColor(0x00, 0xff, 0x00); \ case SkeletonBoneMark::None: \ return Qt::transparent; \ default: \ @@ -38,6 +44,8 @@ const char *SkeletonBoneMarkToString(SkeletonBoneMark mark); const char *SkeletonBoneMarkToString(SkeletonBoneMark mark) \ { \ switch (mark) { \ + case SkeletonBoneMark::Head: \ + return "Head"; \ case SkeletonBoneMark::LegStart: \ return "LegStart"; \ case SkeletonBoneMark::LegJoint: \ @@ -46,6 +54,8 @@ const char *SkeletonBoneMarkToString(SkeletonBoneMark mark) \ return "LegEnd"; \ case SkeletonBoneMark::Spine: \ return "Spine"; \ + case SkeletonBoneMark::Tail: \ + return "Tail"; \ case SkeletonBoneMark::None: \ return "None"; \ default: \ @@ -57,6 +67,8 @@ SkeletonBoneMark SkeletonBoneMarkFromString(const char *markString); SkeletonBoneMark SkeletonBoneMarkFromString(const char *markString) \ { \ QString mark = markString; \ + if (mark == "Head") \ + return SkeletonBoneMark::Head; \ if (mark == "LegStart") \ return SkeletonBoneMark::LegStart; \ if (mark == "LegJoint") \ @@ -65,6 +77,8 @@ SkeletonBoneMark SkeletonBoneMarkFromString(const char *markString) \ return SkeletonBoneMark::LegEnd; \ if (mark == "Spine") \ return SkeletonBoneMark::Spine; \ + if (mark == "Tail") \ + return SkeletonBoneMark::Tail; \ if (mark == "None") \ return SkeletonBoneMark::None; \ return SkeletonBoneMark::None; \ @@ -74,6 +88,8 @@ const char *SkeletonBoneMarkToDispName(SkeletonBoneMark mark); const char *SkeletonBoneMarkToDispName(SkeletonBoneMark mark) \ { \ switch (mark) { \ + case SkeletonBoneMark::Head: \ + return "Head"; \ case SkeletonBoneMark::LegStart: \ return "Leg (Start)"; \ case SkeletonBoneMark::LegJoint: \ @@ -82,6 +98,8 @@ const char *SkeletonBoneMarkToDispName(SkeletonBoneMark mark) \ return "Leg (End)"; \ case SkeletonBoneMark::Spine: \ return "Spine"; \ + case SkeletonBoneMark::Tail: \ + return "Tail"; \ case SkeletonBoneMark::None: \ return ""; \ default: \ diff --git a/src/skeletondocumentwindow.cpp b/src/skeletondocumentwindow.cpp index dec265d4..bce83a05 100644 --- a/src/skeletondocumentwindow.cpp +++ b/src/skeletondocumentwindow.cpp @@ -905,6 +905,8 @@ void SkeletonDocumentWindow::exportModelResult() return; } QApplication::setOverrideCursor(Qt::WaitCursor); + // Must update mesh before export, because there may be an animation clip playing which breaks the original mesh. + m_modelRenderWidget->updateMesh(m_document->takeResultMesh()); m_modelRenderWidget->exportMeshAsObj(filename); QApplication::restoreOverrideCursor(); }