Add end-effector pulling (Shift + Left Button on end-effector).
- Implement CCD (Cyclic Coordinate Descent) IK (Inverse Kinematics) algorithm - Add end-effector pulling operation, this would be useful when you want to pull a group of nodes in line to align straight or mimic curl.master
parent
b45014e1b8
commit
df98193091
|
@ -74,7 +74,7 @@ Mouse
|
||||||
+----------------------------+--------------------------------------------------------------------------+
|
+----------------------------+--------------------------------------------------------------------------+
|
||||||
| CTRL + ALT + LEFT BUTTON | Multiple Unselect |
|
| CTRL + ALT + LEFT BUTTON | Multiple Unselect |
|
||||||
+----------------------------+--------------------------------------------------------------------------+
|
+----------------------------+--------------------------------------------------------------------------+
|
||||||
| SHIFT + LEFT BUTTON | Rotate |
|
| SHIFT + LEFT BUTTON | Rotate / Pull (End Effector) |
|
||||||
+----------------------------+--------------------------------------------------------------------------+
|
+----------------------------+--------------------------------------------------------------------------+
|
||||||
| RIGHT BUTTON | Context Menu |
|
| RIGHT BUTTON | Context Menu |
|
||||||
+----------------------------+--------------------------------------------------------------------------+
|
+----------------------------+--------------------------------------------------------------------------+
|
||||||
|
|
|
@ -116,6 +116,12 @@ HEADERS += src/exportpreviewwidget.h
|
||||||
SOURCES += src/ambientocclusionbaker.cpp
|
SOURCES += src/ambientocclusionbaker.cpp
|
||||||
HEADERS += src/ambientocclusionbaker.h
|
HEADERS += src/ambientocclusionbaker.h
|
||||||
|
|
||||||
|
SOURCES += src/ccdikresolver.cpp
|
||||||
|
HEADERS += src/ccdikresolver.h
|
||||||
|
|
||||||
|
SOURCES += src/skeletonikmover.cpp
|
||||||
|
HEADERS += src/skeletonikmover.h
|
||||||
|
|
||||||
HEADERS += src/qtlightmapper.h
|
HEADERS += src/qtlightmapper.h
|
||||||
|
|
||||||
SOURCES += src/main.cpp
|
SOURCES += src/main.cpp
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QMatrix4x4>
|
||||||
|
#include <QDebug>
|
||||||
|
#include "ccdikresolver.h"
|
||||||
|
|
||||||
|
CCDIKSolver::CCDIKSolver() :
|
||||||
|
m_maxRound(5),
|
||||||
|
m_distanceThreshold2(0.001 * 0.001)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void CCDIKSolver::setMaxRound(int maxRound)
|
||||||
|
{
|
||||||
|
m_maxRound = maxRound;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CCDIKSolver::setDistanceThreshod(float threshold)
|
||||||
|
{
|
||||||
|
m_distanceThreshold2 = threshold * threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CCDIKSolver::addNodeInOrder(const QVector3D &position)
|
||||||
|
{
|
||||||
|
CCDIKNode node;
|
||||||
|
node.position = position;
|
||||||
|
int nodeCount = m_nodes.size();
|
||||||
|
m_nodes.push_back(node);
|
||||||
|
return nodeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CCDIKSolver::solveTo(const QVector3D &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;
|
||||||
|
if (distance2 <= m_distanceThreshold2)
|
||||||
|
break;
|
||||||
|
if (lastDistance2 > 0 && distance2 >= lastDistance2)
|
||||||
|
break;
|
||||||
|
lastDistance2 = distance2;
|
||||||
|
iterate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const QVector3D &CCDIKSolver::getNodeSolvedPosition(int index)
|
||||||
|
{
|
||||||
|
Q_ASSERT(index >= 0 && index < m_nodes.size());
|
||||||
|
return m_nodes[index].position;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CCDIKSolver::getNodeCount(void)
|
||||||
|
{
|
||||||
|
return m_nodes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef CCD_IK_SOLVER_H
|
||||||
|
#define CCD_IK_SOLVER_H
|
||||||
|
#include <vector>
|
||||||
|
#include <QVector3D>
|
||||||
|
#include <QQuaternion>
|
||||||
|
|
||||||
|
struct CCDIKNode
|
||||||
|
{
|
||||||
|
QVector3D position;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CCDIKSolver
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CCDIKSolver();
|
||||||
|
void setMaxRound(int maxRound);
|
||||||
|
void setDistanceThreshod(float threshold);
|
||||||
|
int addNodeInOrder(const QVector3D &position);
|
||||||
|
void solveTo(const QVector3D &position);
|
||||||
|
const QVector3D &getNodeSolvedPosition(int index);
|
||||||
|
int getNodeCount(void);
|
||||||
|
private:
|
||||||
|
void iterate();
|
||||||
|
private:
|
||||||
|
std::vector<CCDIKNode> m_nodes;
|
||||||
|
QVector3D m_destination;
|
||||||
|
int m_maxRound;
|
||||||
|
float m_distanceThreshold2;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -454,6 +454,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::addNode, m_document, &SkeletonDocument::addNode);
|
connect(graphicsWidget, &SkeletonGraphicsWidget::addNode, m_document, &SkeletonDocument::addNode);
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::scaleNodeByAddRadius, m_document, &SkeletonDocument::scaleNodeByAddRadius);
|
connect(graphicsWidget, &SkeletonGraphicsWidget::scaleNodeByAddRadius, m_document, &SkeletonDocument::scaleNodeByAddRadius);
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::moveNodeBy, m_document, &SkeletonDocument::moveNodeBy);
|
connect(graphicsWidget, &SkeletonGraphicsWidget::moveNodeBy, m_document, &SkeletonDocument::moveNodeBy);
|
||||||
|
connect(graphicsWidget, &SkeletonGraphicsWidget::setNodeOrigin, m_document, &SkeletonDocument::setNodeOrigin);
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::removeNode, m_document, &SkeletonDocument::removeNode);
|
connect(graphicsWidget, &SkeletonGraphicsWidget::removeNode, m_document, &SkeletonDocument::removeNode);
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::setEditMode, m_document, &SkeletonDocument::setEditMode);
|
connect(graphicsWidget, &SkeletonGraphicsWidget::setEditMode, m_document, &SkeletonDocument::setEditMode);
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::removeEdge, m_document, &SkeletonDocument::removeEdge);
|
connect(graphicsWidget, &SkeletonGraphicsWidget::removeEdge, m_document, &SkeletonDocument::removeEdge);
|
||||||
|
|
|
@ -37,7 +37,9 @@ SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document)
|
||||||
m_mainOriginItem(nullptr),
|
m_mainOriginItem(nullptr),
|
||||||
m_sideOriginItem(nullptr),
|
m_sideOriginItem(nullptr),
|
||||||
m_hoveredOriginItem(nullptr),
|
m_hoveredOriginItem(nullptr),
|
||||||
m_checkedOriginItem(nullptr)
|
m_checkedOriginItem(nullptr),
|
||||||
|
m_ikMoveUpdateVersion(0),
|
||||||
|
m_ikMover(nullptr)
|
||||||
{
|
{
|
||||||
setRenderHint(QPainter::Antialiasing, false);
|
setRenderHint(QPainter::Antialiasing, false);
|
||||||
setBackgroundBrush(QBrush(QWidget::palette().color(QWidget::backgroundRole()), Qt::SolidPattern));
|
setBackgroundBrush(QBrush(QWidget::palette().color(QWidget::backgroundRole()), Qt::SolidPattern));
|
||||||
|
@ -609,7 +611,30 @@ bool SkeletonGraphicsWidget::mouseMove(QMouseEvent *event)
|
||||||
float byX = mouseScenePos.x() - m_lastScenePos.x();
|
float byX = mouseScenePos.x() - m_lastScenePos.x();
|
||||||
float byY = mouseScenePos.y() - m_lastScenePos.y();
|
float byY = mouseScenePos.y() - m_lastScenePos.y();
|
||||||
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) {
|
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) {
|
||||||
rotateSelected(byX);
|
std::set<SkeletonGraphicsNodeItem *> nodeItems;
|
||||||
|
readMergedSkeletonNodeSetFromRangeSelection(&nodeItems);
|
||||||
|
if (nodeItems.size() == 1) {
|
||||||
|
auto &nodeItem = *nodeItems.begin();
|
||||||
|
const SkeletonNode *node = m_document->findNode(nodeItem->id());
|
||||||
|
if (node->edgeIds.size() == 1) {
|
||||||
|
const auto origin = nodeItem->origin();
|
||||||
|
byX = mouseScenePos.x() - origin.x();
|
||||||
|
byY = mouseScenePos.y() - origin.y();
|
||||||
|
byX = sceneRadiusToUnified(byX);
|
||||||
|
byY = sceneRadiusToUnified(byY);
|
||||||
|
QVector3D target = QVector3D(node->x, node->y, node->z);
|
||||||
|
if (SkeletonProfile::Main == nodeItem->profile()) {
|
||||||
|
target.setX(target.x() + byX);
|
||||||
|
target.setY(target.y() + byY);
|
||||||
|
} else {
|
||||||
|
target.setY(target.y() + byY);
|
||||||
|
target.setZ(target.z() + byX);
|
||||||
|
}
|
||||||
|
emit ikMove(nodeItem->id(), target);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rotateSelected(byX);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
moveSelected(byX, byY);
|
moveSelected(byX, byY);
|
||||||
}
|
}
|
||||||
|
@ -671,6 +696,8 @@ void SkeletonGraphicsWidget::moveSelected(float byX, float byY)
|
||||||
if (m_rangeSelectionSet.empty())
|
if (m_rangeSelectionSet.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
m_ikMoveEndEffectorId = QUuid();
|
||||||
|
|
||||||
byX = sceneRadiusToUnified(byX);
|
byX = sceneRadiusToUnified(byX);
|
||||||
byY = sceneRadiusToUnified(byY);
|
byY = sceneRadiusToUnified(byY);
|
||||||
std::set<SkeletonGraphicsNodeItem *> nodeItems;
|
std::set<SkeletonGraphicsNodeItem *> nodeItems;
|
||||||
|
@ -1837,4 +1864,79 @@ void SkeletonGraphicsWidget::setItemHoveredOnAllProfiles(QGraphicsItem *item, bo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SkeletonGraphicsWidget::ikMoveReady()
|
||||||
|
{
|
||||||
|
unsigned long long movedUpdateVersion = m_ikMover->getUpdateVersion();
|
||||||
|
|
||||||
|
if (movedUpdateVersion == m_ikMoveUpdateVersion &&
|
||||||
|
!m_ikMoveEndEffectorId.isNull()) {
|
||||||
|
emit batchChangeBegin();
|
||||||
|
for (const auto &it: m_ikMover->ikNodes()) {
|
||||||
|
emit setNodeOrigin(it.id, it.newPosition.x(), it.newPosition.y(), it.newPosition.z());
|
||||||
|
}
|
||||||
|
emit batchChangeEnd();
|
||||||
|
emit groupOperationAdded();
|
||||||
|
}
|
||||||
|
|
||||||
|
delete m_ikMover;
|
||||||
|
m_ikMover = nullptr;
|
||||||
|
|
||||||
|
if (movedUpdateVersion != m_ikMoveUpdateVersion &&
|
||||||
|
!m_ikMoveEndEffectorId.isNull()) {
|
||||||
|
ikMove(m_ikMoveEndEffectorId, m_ikMoveTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonGraphicsWidget::ikMove(QUuid endEffectorId, QVector3D target)
|
||||||
|
{
|
||||||
|
m_ikMoveEndEffectorId = endEffectorId;
|
||||||
|
m_ikMoveTarget = target;
|
||||||
|
m_ikMoveUpdateVersion++;
|
||||||
|
if (nullptr != m_ikMover) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QThread *thread = new QThread;
|
||||||
|
|
||||||
|
m_ikMover = new SkeletonIkMover();
|
||||||
|
m_ikMover->setUpdateVersion(m_ikMoveUpdateVersion);
|
||||||
|
m_ikMover->setTarget(m_ikMoveTarget);
|
||||||
|
QUuid nodeId = endEffectorId;
|
||||||
|
std::set<QUuid> historyNodeIds;
|
||||||
|
std::vector<std::pair<QUuid, QVector3D>> appendNodes;
|
||||||
|
for (;;) {
|
||||||
|
historyNodeIds.insert(nodeId);
|
||||||
|
const auto node = m_document->findNode(nodeId);
|
||||||
|
if (nullptr == node)
|
||||||
|
break;
|
||||||
|
appendNodes.push_back(std::make_pair(nodeId, QVector3D(node->x, node->y, node->z)));
|
||||||
|
if (node->edgeIds.size() < 1 || node->edgeIds.size() > 2)
|
||||||
|
break;
|
||||||
|
QUuid choosenNodeId;
|
||||||
|
for (const auto &edgeId: node->edgeIds) {
|
||||||
|
const auto edge = m_document->findEdge(edgeId);
|
||||||
|
if (nullptr == edge)
|
||||||
|
break;
|
||||||
|
QUuid neighborNodeId = edge->neighborOf(nodeId);
|
||||||
|
if (historyNodeIds.find(neighborNodeId) != historyNodeIds.end())
|
||||||
|
continue;
|
||||||
|
choosenNodeId = neighborNodeId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (choosenNodeId.isNull())
|
||||||
|
break;
|
||||||
|
nodeId = choosenNodeId;
|
||||||
|
}
|
||||||
|
qDebug() << "ik move nodes:";
|
||||||
|
for (int i = appendNodes.size() - 1; i >= 0; i--) {
|
||||||
|
qDebug() << i << appendNodes[i].first << appendNodes[i].second;
|
||||||
|
m_ikMover->addNode(appendNodes[i].first, appendNodes[i].second);
|
||||||
|
}
|
||||||
|
qDebug() << "target:" << m_ikMoveTarget;
|
||||||
|
m_ikMover->moveToThread(thread);
|
||||||
|
connect(thread, &QThread::started, m_ikMover, &SkeletonIkMover::process);
|
||||||
|
connect(m_ikMover, &SkeletonIkMover::finished, this, &SkeletonGraphicsWidget::ikMoveReady);
|
||||||
|
connect(m_ikMover, &SkeletonIkMover::finished, thread, &QThread::quit);
|
||||||
|
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||||
|
thread->start();
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "turnaroundloader.h"
|
#include "turnaroundloader.h"
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
#include "dust3dutil.h"
|
#include "dust3dutil.h"
|
||||||
|
#include "skeletonikmover.h"
|
||||||
|
|
||||||
class SkeletonGraphicsOriginItem : public QGraphicsPolygonItem
|
class SkeletonGraphicsOriginItem : public QGraphicsPolygonItem
|
||||||
{
|
{
|
||||||
|
@ -366,6 +367,7 @@ signals:
|
||||||
void setXlockState(bool locked);
|
void setXlockState(bool locked);
|
||||||
void setYlockState(bool locked);
|
void setYlockState(bool locked);
|
||||||
void setZlockState(bool locked);
|
void setZlockState(bool locked);
|
||||||
|
void setNodeOrigin(QUuid nodeId, float x, float y, float z);
|
||||||
public:
|
public:
|
||||||
SkeletonGraphicsWidget(const SkeletonDocument *document);
|
SkeletonGraphicsWidget(const SkeletonDocument *document);
|
||||||
std::map<QUuid, std::pair<SkeletonGraphicsNodeItem *, SkeletonGraphicsNodeItem *>> nodeItemMap;
|
std::map<QUuid, std::pair<SkeletonGraphicsNodeItem *, SkeletonGraphicsNodeItem *>> nodeItemMap;
|
||||||
|
@ -431,6 +433,8 @@ public slots:
|
||||||
void addSelectEdge(QUuid edgeId);
|
void addSelectEdge(QUuid edgeId);
|
||||||
void enableBackgroundBlur();
|
void enableBackgroundBlur();
|
||||||
void disableBackgroundBlur();
|
void disableBackgroundBlur();
|
||||||
|
void ikMove(QUuid endEffectorId, QVector3D target);
|
||||||
|
void ikMoveReady();
|
||||||
private slots:
|
private slots:
|
||||||
void turnaroundImageReady();
|
void turnaroundImageReady();
|
||||||
private:
|
private:
|
||||||
|
@ -475,6 +479,10 @@ private: //need initalize
|
||||||
SkeletonGraphicsOriginItem *m_sideOriginItem;
|
SkeletonGraphicsOriginItem *m_sideOriginItem;
|
||||||
SkeletonGraphicsOriginItem *m_hoveredOriginItem;
|
SkeletonGraphicsOriginItem *m_hoveredOriginItem;
|
||||||
SkeletonGraphicsOriginItem *m_checkedOriginItem;
|
SkeletonGraphicsOriginItem *m_checkedOriginItem;
|
||||||
|
unsigned long long m_ikMoveUpdateVersion;
|
||||||
|
SkeletonIkMover *m_ikMover;
|
||||||
|
QVector3D m_ikMoveTarget;
|
||||||
|
QUuid m_ikMoveEndEffectorId;
|
||||||
private:
|
private:
|
||||||
std::set<QGraphicsItem *> m_rangeSelectionSet;
|
std::set<QGraphicsItem *> m_rangeSelectionSet;
|
||||||
QPoint m_lastGlobalPos;
|
QPoint m_lastGlobalPos;
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include "skeletonikmover.h"
|
||||||
|
#include "ccdikresolver.h"
|
||||||
|
|
||||||
|
SkeletonIkMover::SkeletonIkMover() :
|
||||||
|
m_updateVersion(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SkeletonIkMover::~SkeletonIkMover()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonIkMover::setTarget(const QVector3D &target)
|
||||||
|
{
|
||||||
|
m_target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonIkMover::setUpdateVersion(unsigned long long version)
|
||||||
|
{
|
||||||
|
m_updateVersion = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long SkeletonIkMover::getUpdateVersion()
|
||||||
|
{
|
||||||
|
return m_updateVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonIkMover::addNode(QUuid id, QVector3D position)
|
||||||
|
{
|
||||||
|
SkeletonIkNode ikNode;
|
||||||
|
ikNode.id = id;
|
||||||
|
ikNode.position = ikNode.newPosition = position;
|
||||||
|
m_ikNodes.push_back(ikNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<SkeletonIkNode> &SkeletonIkMover::ikNodes()
|
||||||
|
{
|
||||||
|
return m_ikNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonIkMover::process()
|
||||||
|
{
|
||||||
|
resolve();
|
||||||
|
|
||||||
|
this->moveToThread(QGuiApplication::instance()->thread());
|
||||||
|
emit finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonIkMover::resolve()
|
||||||
|
{
|
||||||
|
CCDIKSolver solver;
|
||||||
|
for (auto i = 0u; i < m_ikNodes.size(); i++) {
|
||||||
|
solver.addNodeInOrder(m_ikNodes[i].position);
|
||||||
|
}
|
||||||
|
solver.solveTo(m_target);
|
||||||
|
for (auto i = 0u; i < m_ikNodes.size(); i++) {
|
||||||
|
m_ikNodes[i].newPosition = solver.getNodeSolvedPosition(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
#ifndef SKELETON_IK_MOVER_H
|
||||||
|
#define SKELETON_IK_MOVER_H
|
||||||
|
#include <QObject>
|
||||||
|
#include <vector>
|
||||||
|
#include <QVector3D>
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
|
struct SkeletonIkNode
|
||||||
|
{
|
||||||
|
QUuid id;
|
||||||
|
QVector3D position;
|
||||||
|
QVector3D newPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SkeletonIkMover : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
SkeletonIkMover();
|
||||||
|
~SkeletonIkMover();
|
||||||
|
void setTarget(const QVector3D &target);
|
||||||
|
void setUpdateVersion(unsigned long long version);
|
||||||
|
unsigned long long getUpdateVersion();
|
||||||
|
void addNode(QUuid id, QVector3D position);
|
||||||
|
const std::vector<SkeletonIkNode> &ikNodes();
|
||||||
|
signals:
|
||||||
|
void finished();
|
||||||
|
public slots:
|
||||||
|
void process();
|
||||||
|
private:
|
||||||
|
void resolve();
|
||||||
|
private:
|
||||||
|
unsigned long long m_updateVersion;
|
||||||
|
std::vector<SkeletonIkNode> m_ikNodes;
|
||||||
|
QVector3D m_target;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue