Add motion manager
Added motions tab, hermite curve editor, quaternion overshoot blend and so on.master
parent
e2f44dded7
commit
ee92472027
|
@ -544,3 +544,15 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode
|
||||||
<pre>
|
<pre>
|
||||||
https://appimage.org/
|
https://appimage.org/
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
<h1>Sam Hocevar</h1>
|
||||||
|
<pre>
|
||||||
|
https://gamedev.stackexchange.com/questions/98246/quaternion-slerp-and-lerp-implementation-with-overshoot
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>David Rosen</h1>
|
||||||
|
<pre>
|
||||||
|
An Indie Approach to Procedural Animation
|
||||||
|
http://www.gdcvault.com/play/1020583/Animation-Bootcamp-An-Indie-Approach
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
|
31
dust3d.pro
31
dust3d.pro
|
@ -200,6 +200,30 @@ HEADERS += src/meshweldseam.h
|
||||||
SOURCES += src/advancesettingwidget.cpp
|
SOURCES += src/advancesettingwidget.cpp
|
||||||
HEADERS += src/advancesettingwidget.h
|
HEADERS += src/advancesettingwidget.h
|
||||||
|
|
||||||
|
SOURCES += src/curveutil.cpp
|
||||||
|
HEADERS += src/curveutil.h
|
||||||
|
|
||||||
|
SOURCES += src/interpolationgraphicswidget.cpp
|
||||||
|
HEADERS += src/interpolationgraphicswidget.h
|
||||||
|
|
||||||
|
SOURCES += src/motioneditwidget.cpp
|
||||||
|
HEADERS += src/motioneditwidget.h
|
||||||
|
|
||||||
|
SOURCES += src/motionmanagewidget.cpp
|
||||||
|
HEADERS += src/motionmanagewidget.h
|
||||||
|
|
||||||
|
SOURCES += src/motionlistwidget.cpp
|
||||||
|
HEADERS += src/motionlistwidget.h
|
||||||
|
|
||||||
|
SOURCES += src/motionwidget.cpp
|
||||||
|
HEADERS += src/motionwidget.h
|
||||||
|
|
||||||
|
SOURCES += src/motionpreviewsgenerator.cpp
|
||||||
|
HEADERS += src/motionpreviewsgenerator.h
|
||||||
|
|
||||||
|
SOURCES += src/animationclipplayer.cpp
|
||||||
|
HEADERS += src/animationclipplayer.h
|
||||||
|
|
||||||
SOURCES += src/main.cpp
|
SOURCES += src/main.cpp
|
||||||
|
|
||||||
HEADERS += src/version.h
|
HEADERS += src/version.h
|
||||||
|
@ -436,13 +460,6 @@ macx {
|
||||||
GMP_LIBDIR = /usr/local/opt/gmp/lib
|
GMP_LIBDIR = /usr/local/opt/gmp/lib
|
||||||
MPFR_INCLUDEDIR = /usr/local/opt/mpfr/include
|
MPFR_INCLUDEDIR = /usr/local/opt/mpfr/include
|
||||||
MPFR_LIBDIR = /usr/local/opt/mpfr/lib
|
MPFR_LIBDIR = /usr/local/opt/mpfr/lib
|
||||||
|
|
||||||
exists(/usr/local/opt/opencv) {
|
|
||||||
INCLUDEPATH += /usr/local/opt/opencv/include
|
|
||||||
LIBS += -L/usr/local/opt/opencv/lib -lopencv_core -lopencv_videoio -lopencv_imgproc
|
|
||||||
|
|
||||||
DEFINES += "USE_OPENCV=1"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unix:!macx {
|
unix:!macx {
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
#include "animationclipplayer.h"
|
||||||
|
|
||||||
|
AnimationClipPlayer::~AnimationClipPlayer()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationClipPlayer::updateFrameMeshes(std::vector<std::pair<float, MeshLoader *>> &frameMeshes)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
|
||||||
|
m_frameMeshes = frameMeshes;
|
||||||
|
frameMeshes.clear();
|
||||||
|
|
||||||
|
m_currentPlayIndex = 0;
|
||||||
|
m_countForFrame.restart();
|
||||||
|
|
||||||
|
if (!m_frameMeshes.empty())
|
||||||
|
m_timerForFrame.singleShot(0, this, &AnimationClipPlayer::frameReadyToShow);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationClipPlayer::clear()
|
||||||
|
{
|
||||||
|
freeFrames();
|
||||||
|
delete m_lastFrameMesh;
|
||||||
|
m_lastFrameMesh = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationClipPlayer::freeFrames()
|
||||||
|
{
|
||||||
|
for (auto &it: m_frameMeshes) {
|
||||||
|
delete it.second;
|
||||||
|
}
|
||||||
|
m_frameMeshes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshLoader *AnimationClipPlayer::takeFrameMesh()
|
||||||
|
{
|
||||||
|
if (m_currentPlayIndex >= (int)m_frameMeshes.size()) {
|
||||||
|
if (nullptr != m_lastFrameMesh)
|
||||||
|
return new MeshLoader(*m_lastFrameMesh);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
int millis = m_frameMeshes[m_currentPlayIndex].first * 1000 - m_countForFrame.elapsed();
|
||||||
|
if (millis > 0) {
|
||||||
|
m_timerForFrame.singleShot(millis, this, &AnimationClipPlayer::frameReadyToShow);
|
||||||
|
if (nullptr != m_lastFrameMesh)
|
||||||
|
return new MeshLoader(*m_lastFrameMesh);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
m_currentPlayIndex = (m_currentPlayIndex + 1) % m_frameMeshes.size();
|
||||||
|
m_countForFrame.restart();
|
||||||
|
|
||||||
|
MeshLoader *mesh = new MeshLoader(*m_frameMeshes[m_currentPlayIndex].second);
|
||||||
|
m_timerForFrame.singleShot(m_frameMeshes[m_currentPlayIndex].first * 1000, this, &AnimationClipPlayer::frameReadyToShow);
|
||||||
|
delete m_lastFrameMesh;
|
||||||
|
m_lastFrameMesh = new MeshLoader(*mesh);
|
||||||
|
return mesh;
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
#ifndef ANIMATION_PLAYER_H
|
||||||
|
#define ANIMATION_PLAYER_H
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QTime>
|
||||||
|
#include "meshloader.h"
|
||||||
|
|
||||||
|
class AnimationClipPlayer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
signals:
|
||||||
|
void frameReadyToShow();
|
||||||
|
public:
|
||||||
|
~AnimationClipPlayer();
|
||||||
|
MeshLoader *takeFrameMesh();
|
||||||
|
void updateFrameMeshes(std::vector<std::pair<float, MeshLoader *>> &frameMeshes);
|
||||||
|
void clear();
|
||||||
|
private:
|
||||||
|
void freeFrames();
|
||||||
|
private:
|
||||||
|
MeshLoader *m_lastFrameMesh = nullptr;
|
||||||
|
int m_currentPlayIndex = 0;
|
||||||
|
private:
|
||||||
|
std::vector<std::pair<float, MeshLoader *>> m_frameMeshes;
|
||||||
|
QTime m_countForFrame;
|
||||||
|
QTimer m_timerForFrame;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,63 @@
|
||||||
|
#include "curveutil.h"
|
||||||
|
|
||||||
|
void hermiteCurveToPainterPath(const std::vector<HermiteControlNode> &hermiteControlNodes,
|
||||||
|
QPainterPath &toPath)
|
||||||
|
{
|
||||||
|
if (!hermiteControlNodes.empty()) {
|
||||||
|
QPainterPath path(QPointF(hermiteControlNodes[0].position.x(),
|
||||||
|
hermiteControlNodes[0].position.y()));
|
||||||
|
for (size_t i = 1; i < hermiteControlNodes.size(); i++) {
|
||||||
|
const auto &beginHermite = hermiteControlNodes[i - 1];
|
||||||
|
const auto &endHermite = hermiteControlNodes[i];
|
||||||
|
BezierControlNode bezier(beginHermite, endHermite);
|
||||||
|
path.cubicTo(QPointF(bezier.handles[0].x(), bezier.handles[0].y()),
|
||||||
|
QPointF(bezier.handles[1].x(), bezier.handles[1].y()),
|
||||||
|
QPointF(bezier.endpoint.x(), bezier.endpoint.y()));
|
||||||
|
}
|
||||||
|
toPath = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector2D calculateHermiteInterpolation(const std::vector<HermiteControlNode> &hermiteControlNodes,
|
||||||
|
float knot)
|
||||||
|
{
|
||||||
|
if (hermiteControlNodes.size() < 2)
|
||||||
|
return QVector2D();
|
||||||
|
|
||||||
|
int startControlIndex = 0;
|
||||||
|
int stopControlIndex = hermiteControlNodes.size() - 1;
|
||||||
|
for (size_t i = 0; i < hermiteControlNodes.size(); i++) {
|
||||||
|
if (hermiteControlNodes[i].position.x() > knot)
|
||||||
|
break;
|
||||||
|
startControlIndex = i;
|
||||||
|
}
|
||||||
|
for (int i = (int)hermiteControlNodes.size() - 1; i >= 0; i--) {
|
||||||
|
if (hermiteControlNodes[i].position.x() < knot)
|
||||||
|
break;
|
||||||
|
stopControlIndex = i;
|
||||||
|
}
|
||||||
|
if (startControlIndex >= stopControlIndex)
|
||||||
|
return QVector2D();
|
||||||
|
|
||||||
|
const auto &startControlNode = hermiteControlNodes[startControlIndex];
|
||||||
|
const auto &stopControlNode = hermiteControlNodes[stopControlIndex];
|
||||||
|
|
||||||
|
if (startControlNode.position.x() >= stopControlNode.position.x())
|
||||||
|
return startControlNode.position;
|
||||||
|
|
||||||
|
float length = (float)(stopControlNode.position.x() - startControlNode.position.x());
|
||||||
|
float t = (knot - startControlNode.position.x()) / length;
|
||||||
|
float t2 = t * t;
|
||||||
|
float t3 = t2 * t;
|
||||||
|
float h1 = 2 * t3 - 3 * t2 + 1;
|
||||||
|
float h2 = -2 * t3 + 3 * t2;
|
||||||
|
float h3 = t3 - 2 * t2 + t;
|
||||||
|
float h4 = t3 - t2;
|
||||||
|
|
||||||
|
QVector2D interpolatedPosition = h1 * startControlNode.position +
|
||||||
|
h2 * stopControlNode.position +
|
||||||
|
h3 * startControlNode.outTangent +
|
||||||
|
h4 * stopControlNode.inTangent;
|
||||||
|
|
||||||
|
return interpolatedPosition;
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
#ifndef CURVE_UTIL_H
|
||||||
|
#define CURVE_UTIL_H
|
||||||
|
#include <QPointF>
|
||||||
|
#include <vector>
|
||||||
|
#include <QPainterPath>
|
||||||
|
#include <QVector2D>
|
||||||
|
|
||||||
|
class HermiteControlNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QVector2D position;
|
||||||
|
QVector2D inTangent;
|
||||||
|
QVector2D outTangent;
|
||||||
|
|
||||||
|
HermiteControlNode(const QVector2D &p, const QVector2D &tin, const QVector2D &tout)
|
||||||
|
{
|
||||||
|
position = p;
|
||||||
|
inTangent = tin;
|
||||||
|
outTangent = tout;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class BezierControlNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QVector2D endpoint;
|
||||||
|
QVector2D handles[2];
|
||||||
|
|
||||||
|
BezierControlNode(const HermiteControlNode &beginHermite,
|
||||||
|
const HermiteControlNode &endHermite)
|
||||||
|
{
|
||||||
|
endpoint = endHermite.position;
|
||||||
|
handles[0] = beginHermite.position + beginHermite.outTangent / 3;
|
||||||
|
handles[1] = endHermite.position - endHermite.inTangent / 3;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void hermiteCurveToPainterPath(const std::vector<HermiteControlNode> &hermiteControlNodes,
|
||||||
|
QPainterPath &toPath);
|
||||||
|
QVector2D calculateHermiteInterpolation(const std::vector<HermiteControlNode> &hermiteControlNodes,
|
||||||
|
float knot);
|
||||||
|
|
||||||
|
#endif
|
|
@ -56,3 +56,21 @@ QString unifiedWindowTitle(const QString &text)
|
||||||
{
|
{
|
||||||
return text + QObject::tr(" - ") + APP_NAME;
|
return text + QObject::tr(" - ") + APP_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sam Hocevar's answer
|
||||||
|
// https://gamedev.stackexchange.com/questions/98246/quaternion-slerp-and-lerp-implementation-with-overshoot
|
||||||
|
QQuaternion quaternionOvershootSlerp(const QQuaternion &q0, const QQuaternion &q1, float t)
|
||||||
|
{
|
||||||
|
// If t is too large, divide it by two recursively
|
||||||
|
if (t > 1.0) {
|
||||||
|
auto tmp = quaternionOvershootSlerp(q0, q1, t / 2);
|
||||||
|
return tmp * q0.inverted() * tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It’s easier to handle negative t this way
|
||||||
|
if (t < 0.0)
|
||||||
|
return quaternionOvershootSlerp(q1, q0, 1.0 - t);
|
||||||
|
|
||||||
|
return QQuaternion::slerp(q0, q1, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,5 +18,6 @@ QVector3D pointInHermiteCurve(float t, QVector3D p0, QVector3D m0, QVector3D p1,
|
||||||
float angleInRangle360BetweenTwoVectors(QVector3D a, QVector3D b, QVector3D planeNormal);
|
float angleInRangle360BetweenTwoVectors(QVector3D a, QVector3D b, QVector3D planeNormal);
|
||||||
QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal);
|
QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal);
|
||||||
QString unifiedWindowTitle(const QString &text);
|
QString unifiedWindowTitle(const QString &text);
|
||||||
|
QQuaternion quaternionOvershootSlerp(const QQuaternion &q0, const QQuaternion &q1, float t);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -15,6 +15,7 @@ InfoLabel::InfoLabel(const QString &text, QWidget *parent) :
|
||||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||||
mainLayout->addWidget(m_icon);
|
mainLayout->addWidget(m_icon);
|
||||||
mainLayout->addWidget(m_label);
|
mainLayout->addWidget(m_label);
|
||||||
|
mainLayout->addStretch();
|
||||||
|
|
||||||
setLayout(mainLayout);
|
setLayout(mainLayout);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,680 @@
|
||||||
|
#include <QGraphicsScene>
|
||||||
|
#include <QBrush>
|
||||||
|
#include <QGraphicsPathItem>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QLabel>
|
||||||
|
#include "interpolationgraphicswidget.h"
|
||||||
|
#include "theme.h"
|
||||||
|
|
||||||
|
const float InterpolationGraphicsWidget::m_anchorRadius = 5;
|
||||||
|
const float InterpolationGraphicsWidget::m_handleRadius = 7;
|
||||||
|
const int InterpolationGraphicsWidget::m_gridColumns = 20;
|
||||||
|
const int InterpolationGraphicsWidget::m_gridRows = 10;
|
||||||
|
const int InterpolationGraphicsWidget::m_sceneWidth = 640;
|
||||||
|
const int InterpolationGraphicsWidget::m_sceneHeight = 480;
|
||||||
|
const float InterpolationGraphicsWidget::m_tangentMagnitudeScaleFactor = 9;
|
||||||
|
|
||||||
|
InterpolationGraphicsWidget::InterpolationGraphicsWidget(QWidget *parent) :
|
||||||
|
QGraphicsView(parent)
|
||||||
|
{
|
||||||
|
setRenderHint(QPainter::Antialiasing);
|
||||||
|
|
||||||
|
setObjectName("interpolationGraphics");
|
||||||
|
setStyleSheet("#interpolationGraphics {background: transparent}");
|
||||||
|
|
||||||
|
setScene(new QGraphicsScene());
|
||||||
|
scene()->setSceneRect(QRectF(0, 0, InterpolationGraphicsWidget::m_sceneWidth, InterpolationGraphicsWidget::m_sceneHeight));
|
||||||
|
|
||||||
|
QColor curveColor = Theme::green;
|
||||||
|
QColor gridColor = QColor(0x19, 0x19, 0x19);
|
||||||
|
|
||||||
|
QPen curvePen;
|
||||||
|
curvePen.setColor(curveColor);
|
||||||
|
curvePen.setWidth(3);
|
||||||
|
|
||||||
|
QPen gridPen;
|
||||||
|
gridPen.setColor(gridColor);
|
||||||
|
gridPen.setWidth(0);
|
||||||
|
|
||||||
|
m_cursorItem = new InterpolationGraphicsCursorItem;
|
||||||
|
m_cursorItem->setFlag(QGraphicsItem::ItemSendsScenePositionChanges, !m_previewOnly);
|
||||||
|
m_cursorItem->setRect(0, 0, 0, InterpolationGraphicsWidget::m_sceneHeight);
|
||||||
|
m_cursorItem->setFlags(m_cursorItem->flags() & ~(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable));
|
||||||
|
m_cursorItem->setZValue(-1);
|
||||||
|
scene()->addItem(m_cursorItem);
|
||||||
|
|
||||||
|
if (m_gridEnabled) {
|
||||||
|
m_gridRowLineItems.resize(m_gridRows + 1);
|
||||||
|
float rowHeight = scene()->sceneRect().height() / m_gridRows;
|
||||||
|
for (int row = 0; row <= m_gridRows; row++) {
|
||||||
|
m_gridRowLineItems[row] = new QGraphicsLineItem();
|
||||||
|
m_gridRowLineItems[row]->setFlags(m_gridRowLineItems[row]->flags() & ~(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable));
|
||||||
|
m_gridRowLineItems[row]->setPen(gridPen);
|
||||||
|
float y = scene()->sceneRect().top() + row * rowHeight;
|
||||||
|
m_gridRowLineItems[row]->setLine(scene()->sceneRect().left(), y, scene()->sceneRect().right(), y);
|
||||||
|
scene()->addItem(m_gridRowLineItems[row]);
|
||||||
|
}
|
||||||
|
m_gridColumnLineItems.resize(m_gridColumns + 1);
|
||||||
|
float columnWidth = scene()->sceneRect().width() / m_gridColumns;
|
||||||
|
for (int column = 0; column <= m_gridColumns; column++) {
|
||||||
|
m_gridColumnLineItems[column] = new QGraphicsLineItem();
|
||||||
|
m_gridColumnLineItems[column]->setFlags(m_gridColumnLineItems[column]->flags() & ~(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable));
|
||||||
|
m_gridColumnLineItems[column]->setPen(gridPen);
|
||||||
|
float x = scene()->sceneRect().left() + column * columnWidth;
|
||||||
|
m_gridColumnLineItems[column]->setLine(x, scene()->sceneRect().top(), x, scene()->sceneRect().bottom());
|
||||||
|
scene()->addItem(m_gridColumnLineItems[column]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pathItem = new QGraphicsPathItem;
|
||||||
|
m_pathItem->setPen(curvePen);
|
||||||
|
scene()->addItem(m_pathItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::toNormalizedControlNodes(std::vector<HermiteControlNode> &snapshot)
|
||||||
|
{
|
||||||
|
snapshot = m_controlNodes;
|
||||||
|
for (auto &item: snapshot) {
|
||||||
|
item.position.setX(item.position.x() / scene()->sceneRect().width());
|
||||||
|
item.position.setY(item.position.y() / scene()->sceneRect().height());
|
||||||
|
item.inTangent.setX(item.inTangent.x() / scene()->sceneRect().width());
|
||||||
|
item.inTangent.setY(item.inTangent.y() / scene()->sceneRect().height());
|
||||||
|
item.outTangent.setX(item.outTangent.x() / scene()->sceneRect().width());
|
||||||
|
item.outTangent.setY(item.outTangent.y() / scene()->sceneRect().height());
|
||||||
|
item.inTangent *= InterpolationGraphicsWidget::m_tangentMagnitudeScaleFactor;
|
||||||
|
item.outTangent *= InterpolationGraphicsWidget::m_tangentMagnitudeScaleFactor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::fromNormalizedControlNodes(const std::vector<HermiteControlNode> &snapshot)
|
||||||
|
{
|
||||||
|
m_controlNodes = snapshot;
|
||||||
|
for (auto &item: m_controlNodes) {
|
||||||
|
item.position.setX(item.position.x() * scene()->sceneRect().width());
|
||||||
|
item.position.setY(item.position.y() * scene()->sceneRect().height());
|
||||||
|
item.inTangent.setX(item.inTangent.x() * scene()->sceneRect().width());
|
||||||
|
item.inTangent.setY(item.inTangent.y() * scene()->sceneRect().height());
|
||||||
|
item.outTangent.setX(item.outTangent.x() * scene()->sceneRect().width());
|
||||||
|
item.outTangent.setY(item.outTangent.y() * scene()->sceneRect().height());
|
||||||
|
item.inTangent /= InterpolationGraphicsWidget::m_tangentMagnitudeScaleFactor;
|
||||||
|
item.outTangent /= InterpolationGraphicsWidget::m_tangentMagnitudeScaleFactor;
|
||||||
|
}
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::resizeEvent(QResizeEvent *event)
|
||||||
|
{
|
||||||
|
QGraphicsView::resizeEvent(event);
|
||||||
|
if (scene())
|
||||||
|
fitInView(scene()->sceneRect(), Qt::KeepAspectRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::setControlNodes(const std::vector<HermiteControlNode> &nodes)
|
||||||
|
{
|
||||||
|
invalidateControlSelection();
|
||||||
|
fromNormalizedControlNodes(nodes);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::setKeyframes(const std::vector<std::pair<float, QString>> &keyframes)
|
||||||
|
{
|
||||||
|
invalidateKeyframeSelection();
|
||||||
|
m_keyframes = keyframes;
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::refreshControlAnchor(int index)
|
||||||
|
{
|
||||||
|
if (index >= (int)m_controlNodes.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (nullptr == m_anchorItems[index]) {
|
||||||
|
m_anchorItems[index] = new InterpolationGraphicsCircleItem(m_anchorRadius);
|
||||||
|
m_anchorItems[index]->setFlag(QGraphicsItem::ItemIsMovable, !m_previewOnly);
|
||||||
|
m_anchorItems[index]->setFlag(QGraphicsItem::ItemSendsScenePositionChanges, !m_previewOnly);
|
||||||
|
connect(m_anchorItems[index]->proxy(), &InterpolationGraphicsProxyObject::itemMoved, this, [=](QPointF point) {
|
||||||
|
QVector2D newControlPosition = QVector2D(point.x(), point.y());
|
||||||
|
if (index - 1 >= 0 && newControlPosition.x() < m_controlNodes[index - 1].position.x())
|
||||||
|
newControlPosition.setX(m_controlNodes[index - 1].position.x());
|
||||||
|
else if (index + 1 <= (int)m_controlNodes.size() - 1 && newControlPosition.x() > m_controlNodes[index + 1].position.x())
|
||||||
|
newControlPosition.setX(m_controlNodes[index + 1].position.x());
|
||||||
|
m_controlNodes[index].position = newControlPosition;
|
||||||
|
refreshCurve();
|
||||||
|
refreshControlNode(index);
|
||||||
|
refreshKeyframes();
|
||||||
|
emit controlNodesChanged();
|
||||||
|
});
|
||||||
|
connect(m_anchorItems[index]->proxy(), &InterpolationGraphicsProxyObject::itemHoverEnter, this, [=]() {
|
||||||
|
hoverControlNode(index);
|
||||||
|
});
|
||||||
|
connect(m_anchorItems[index]->proxy(), &InterpolationGraphicsProxyObject::itemHoverLeave, this, [=]() {
|
||||||
|
unhoverControlNode(index);
|
||||||
|
});
|
||||||
|
scene()->addItem(m_anchorItems[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &controlNode = m_controlNodes[index];
|
||||||
|
m_anchorItems[index]->setOrigin(QPointF(controlNode.position.x(), controlNode.position.y()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::refreshControlEdges(int index)
|
||||||
|
{
|
||||||
|
if (index >= (int)m_controlNodes.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (nullptr == m_lineItems[index].first) {
|
||||||
|
Q_ASSERT(nullptr == m_lineItems[index].second);
|
||||||
|
m_lineItems[index] = std::make_pair(new InterpolationGraphicsEdgeItem(),
|
||||||
|
new InterpolationGraphicsEdgeItem());
|
||||||
|
scene()->addItem(m_lineItems[index].first);
|
||||||
|
scene()->addItem(m_lineItems[index].second);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &controlNode = m_controlNodes[index];
|
||||||
|
|
||||||
|
QVector2D inHandlePosition = controlNode.position + controlNode.inTangent;
|
||||||
|
m_lineItems[index].first->setLine(controlNode.position.x(), controlNode.position.y(),
|
||||||
|
inHandlePosition.x(), inHandlePosition.y());
|
||||||
|
|
||||||
|
QVector2D outHandlePosition = controlNode.position - controlNode.outTangent;
|
||||||
|
m_lineItems[index].second->setLine(controlNode.position.x(), controlNode.position.y(),
|
||||||
|
outHandlePosition.x(), outHandlePosition.y());
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::refreshControlHandles(int index)
|
||||||
|
{
|
||||||
|
if (index >= (int)m_controlNodes.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (nullptr == m_handleItems[index].first) {
|
||||||
|
Q_ASSERT(nullptr == m_handleItems[index].second);
|
||||||
|
m_handleItems[index] = std::make_pair(new InterpolationGraphicsCircleItem(m_handleRadius, false),
|
||||||
|
new InterpolationGraphicsCircleItem(m_handleRadius, false));
|
||||||
|
m_handleItems[index].first->setFlag(QGraphicsItem::ItemIsMovable, !m_previewOnly);
|
||||||
|
m_handleItems[index].first->setFlag(QGraphicsItem::ItemSendsScenePositionChanges, !m_previewOnly);
|
||||||
|
m_handleItems[index].second->setFlag(QGraphicsItem::ItemIsMovable, !m_previewOnly);
|
||||||
|
m_handleItems[index].second->setFlag(QGraphicsItem::ItemSendsScenePositionChanges, !m_previewOnly);
|
||||||
|
connect(m_handleItems[index].first->proxy(), &InterpolationGraphicsProxyObject::itemMoved, this, [=](QPointF point) {
|
||||||
|
auto newHandlePosition = QVector2D(point.x(), point.y());
|
||||||
|
m_controlNodes[index].inTangent = newHandlePosition - m_controlNodes[index].position;
|
||||||
|
refreshCurve();
|
||||||
|
refreshControlEdges(index);
|
||||||
|
refreshKeyframes();
|
||||||
|
emit controlNodesChanged();
|
||||||
|
});
|
||||||
|
connect(m_handleItems[index].second->proxy(), &InterpolationGraphicsProxyObject::itemMoved, this, [=](QPointF point) {
|
||||||
|
auto newHandlePosition = QVector2D(point.x(), point.y());
|
||||||
|
m_controlNodes[index].outTangent = m_controlNodes[index].position - newHandlePosition;
|
||||||
|
refreshCurve();
|
||||||
|
refreshControlEdges(index);
|
||||||
|
refreshKeyframes();
|
||||||
|
emit controlNodesChanged();
|
||||||
|
});
|
||||||
|
connect(m_handleItems[index].first->proxy(), &InterpolationGraphicsProxyObject::itemHoverEnter, this, [=]() {
|
||||||
|
hoverControlNode(index);
|
||||||
|
});
|
||||||
|
connect(m_handleItems[index].second->proxy(), &InterpolationGraphicsProxyObject::itemHoverEnter, this, [=]() {
|
||||||
|
hoverControlNode(index);
|
||||||
|
});
|
||||||
|
connect(m_handleItems[index].first->proxy(), &InterpolationGraphicsProxyObject::itemHoverLeave, this, [=]() {
|
||||||
|
unhoverControlNode(index);
|
||||||
|
});
|
||||||
|
connect(m_handleItems[index].second->proxy(), &InterpolationGraphicsProxyObject::itemHoverLeave, this, [=]() {
|
||||||
|
unhoverControlNode(index);
|
||||||
|
});
|
||||||
|
scene()->addItem(m_handleItems[index].first);
|
||||||
|
scene()->addItem(m_handleItems[index].second);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &controlNode = m_controlNodes[index];
|
||||||
|
|
||||||
|
QVector2D inHandlePosition = controlNode.position + controlNode.inTangent;
|
||||||
|
m_handleItems[index].first->setOrigin(QPointF(inHandlePosition.x(), inHandlePosition.y()));
|
||||||
|
|
||||||
|
QVector2D outHandlePosition = controlNode.position - controlNode.outTangent;
|
||||||
|
m_handleItems[index].second->setOrigin(QPointF(outHandlePosition.x(), outHandlePosition.y()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::refreshControlNode(int index)
|
||||||
|
{
|
||||||
|
if (index >= (int)m_controlNodes.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
refreshControlAnchor(index);
|
||||||
|
refreshControlEdges(index);
|
||||||
|
refreshControlHandles(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::refreshCurve()
|
||||||
|
{
|
||||||
|
QPainterPath path;
|
||||||
|
std::vector<HermiteControlNode> controlNodes;
|
||||||
|
toNormalizedControlNodes(controlNodes);
|
||||||
|
for (auto &item: controlNodes) {
|
||||||
|
item.position.setX(item.position.x() * scene()->sceneRect().width());
|
||||||
|
item.position.setY(item.position.y() * scene()->sceneRect().height());
|
||||||
|
item.inTangent.setX(item.inTangent.x() * scene()->sceneRect().width());
|
||||||
|
item.inTangent.setY(item.inTangent.y() * scene()->sceneRect().height());
|
||||||
|
item.outTangent.setX(item.outTangent.x() * scene()->sceneRect().width());
|
||||||
|
item.outTangent.setY(item.outTangent.y() * scene()->sceneRect().height());
|
||||||
|
}
|
||||||
|
hermiteCurveToPainterPath(controlNodes, path);
|
||||||
|
m_pathItem->setPath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::refreshKeyframeKnot(int index)
|
||||||
|
{
|
||||||
|
if (index >= (int)m_keyframes.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::vector<HermiteControlNode> controlNodes;
|
||||||
|
toNormalizedControlNodes(controlNodes);
|
||||||
|
if (controlNodes.size() < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (scene()->sceneRect().width() <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto &knot = m_keyframes[index].first;
|
||||||
|
QVector2D interpolatedPosition = calculateHermiteInterpolation(controlNodes, knot);
|
||||||
|
|
||||||
|
if (nullptr == m_keyframeKnotItems[index]) {
|
||||||
|
m_keyframeKnotItems[index] = new InterpolationGraphicsKeyframeItem;
|
||||||
|
scene()->addItem(m_keyframeKnotItems[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPointF knotPos = QPointF(interpolatedPosition.x() * scene()->sceneRect().width(),
|
||||||
|
interpolatedPosition.y() * scene()->sceneRect().height());
|
||||||
|
m_keyframeKnotItems[index]->setPos(knotPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::refreshKeyframeLabel(int index)
|
||||||
|
{
|
||||||
|
if (index >= (int)m_keyframes.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::vector<HermiteControlNode> controlNodes;
|
||||||
|
toNormalizedControlNodes(controlNodes);
|
||||||
|
if (controlNodes.size() < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (scene()->sceneRect().width() <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto &knot = m_keyframes[index].first;
|
||||||
|
QVector2D interpolatedPosition = calculateHermiteInterpolation(controlNodes, knot);
|
||||||
|
|
||||||
|
if (nullptr == m_keyframeNameItems[index]) {
|
||||||
|
InterpolationGraphicsLabelParentWidget *parentWidget = new InterpolationGraphicsLabelParentWidget;
|
||||||
|
parentWidget->setFlag(QGraphicsItem::ItemIsMovable, !m_previewOnly);
|
||||||
|
parentWidget->setFlag(QGraphicsItem::ItemSendsScenePositionChanges, !m_previewOnly);
|
||||||
|
parentWidget->setAutoFillBackground(true);
|
||||||
|
parentWidget->setZValue(1);
|
||||||
|
scene()->addItem(parentWidget);
|
||||||
|
|
||||||
|
QLabel *nameLabel = new QLabel;
|
||||||
|
nameLabel->setFocusPolicy(Qt::NoFocus);
|
||||||
|
m_keyframeNameItems[index] = scene()->addWidget(nameLabel);
|
||||||
|
m_keyframeNameItems[index]->setParentItem(parentWidget);
|
||||||
|
connect(parentWidget->proxy(), &InterpolationGraphicsProxyObject::itemMoved, this, [=](QPointF point) {
|
||||||
|
QVector2D newKnotPosition = QVector2D(point.x(), point.y());
|
||||||
|
float knot = point.x() / scene()->sceneRect().width();
|
||||||
|
if (index - 1 >= 0 && knot < m_keyframes[index - 1].first)
|
||||||
|
newKnotPosition.setX(m_keyframes[index - 1].first * scene()->sceneRect().width());
|
||||||
|
else if (index + 1 <= (int)m_keyframes.size() - 1 && knot > m_keyframes[index + 1].first)
|
||||||
|
newKnotPosition.setX(m_keyframes[index + 1].first * scene()->sceneRect().width());
|
||||||
|
knot = newKnotPosition.x() / scene()->sceneRect().width();
|
||||||
|
m_keyframes[index].first = knot;
|
||||||
|
refreshKeyframe(index);
|
||||||
|
emit keyframeKnotChanged(index, knot);
|
||||||
|
});
|
||||||
|
connect(parentWidget->proxy(), &InterpolationGraphicsProxyObject::itemHoverEnter, this, [=]() {
|
||||||
|
hoverKeyframe(index);
|
||||||
|
});
|
||||||
|
connect(parentWidget->proxy(), &InterpolationGraphicsProxyObject::itemHoverLeave, this, [=]() {
|
||||||
|
unhoverKeyframe(index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QPointF knotPos = QPointF(interpolatedPosition.x() * scene()->sceneRect().width(),
|
||||||
|
interpolatedPosition.y() * scene()->sceneRect().height());
|
||||||
|
|
||||||
|
QLabel *nameLabel = (QLabel *)m_keyframeNameItems[index]->widget();
|
||||||
|
nameLabel->setText(m_keyframes[index].second);
|
||||||
|
nameLabel->adjustSize();
|
||||||
|
QPointF textPos(knotPos.x(), knotPos.y());
|
||||||
|
InterpolationGraphicsLabelParentWidget *parentWidget = (InterpolationGraphicsLabelParentWidget *)(m_keyframeNameItems[index]->parentItem());
|
||||||
|
QSize sizeHint = nameLabel->sizeHint();
|
||||||
|
nameLabel->resize(sizeHint);
|
||||||
|
parentWidget->resize(sizeHint);
|
||||||
|
parentWidget->setPosWithoutEmitSignal(textPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::refreshKeyframe(int index)
|
||||||
|
{
|
||||||
|
refreshKeyframeKnot(index);
|
||||||
|
refreshKeyframeLabel(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::refresh()
|
||||||
|
{
|
||||||
|
refreshCurve();
|
||||||
|
|
||||||
|
for (size_t i = m_controlNodes.size(); i < m_anchorItems.size(); i++)
|
||||||
|
delete m_anchorItems[i];
|
||||||
|
m_anchorItems.resize(m_controlNodes.size());
|
||||||
|
|
||||||
|
for (size_t i = m_controlNodes.size(); i < m_lineItems.size(); i++) {
|
||||||
|
delete m_lineItems[i].first;
|
||||||
|
delete m_lineItems[i].second;
|
||||||
|
}
|
||||||
|
m_lineItems.resize(m_controlNodes.size());
|
||||||
|
|
||||||
|
for (size_t i = m_controlNodes.size(); i < m_handleItems.size(); i++) {
|
||||||
|
delete m_handleItems[i].first;
|
||||||
|
delete m_handleItems[i].second;
|
||||||
|
}
|
||||||
|
m_handleItems.resize(m_controlNodes.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < m_controlNodes.size(); i++)
|
||||||
|
refreshControlNode(i);
|
||||||
|
|
||||||
|
for (size_t i = m_keyframes.size(); i < m_keyframeKnotItems.size(); i++) {
|
||||||
|
delete m_keyframeKnotItems[i];
|
||||||
|
delete m_keyframeNameItems[i]->parentItem();
|
||||||
|
}
|
||||||
|
m_keyframeKnotItems.resize(m_keyframes.size());
|
||||||
|
m_keyframeNameItems.resize(m_keyframes.size());
|
||||||
|
|
||||||
|
refreshKeyframes();
|
||||||
|
|
||||||
|
fitInView(scene()->sceneRect(), Qt::KeepAspectRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
float InterpolationGraphicsWidget::cursorKnot() const
|
||||||
|
{
|
||||||
|
return m_cursorItem->pos().x() / scene()->sceneRect().width();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::refreshKeyframes()
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < m_keyframes.size(); i++)
|
||||||
|
refreshKeyframe(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::mouseMoveEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
QGraphicsView::mouseMoveEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
QGraphicsView::mouseReleaseEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::mousePressEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
QGraphicsView::mousePressEvent(event);
|
||||||
|
|
||||||
|
if (m_previewOnly)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (event->button() == Qt::LeftButton) {
|
||||||
|
auto cursorPos = mapToScene(event->pos());
|
||||||
|
cursorPos.setY(0);
|
||||||
|
m_cursorItem->updateCursorPosition(cursorPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event->button() == Qt::LeftButton ||
|
||||||
|
event->button() == Qt::RightButton) {
|
||||||
|
selectControlNode(m_currentHoveringControlIndex);
|
||||||
|
selectKeyframe(m_currentHoveringKeyframeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event->button() == Qt::RightButton) {
|
||||||
|
showContextMenu(mapFromGlobal(event->globalPos()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::deleteControlNode(int index)
|
||||||
|
{
|
||||||
|
if (index >= (int)m_controlNodes.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
invalidateControlSelection();
|
||||||
|
|
||||||
|
m_controlNodes.erase(m_controlNodes.begin() + index);
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
emit controlNodesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::deleteKeyframe(int index)
|
||||||
|
{
|
||||||
|
if (index >= (int)m_keyframes.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
emit removeKeyframe(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::addControlNodeAfter(int index)
|
||||||
|
{
|
||||||
|
if (index >= (int)m_controlNodes.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
invalidateControlSelection();
|
||||||
|
|
||||||
|
const auto ¤t = m_controlNodes[index];
|
||||||
|
int newX = current.position.x();
|
||||||
|
auto inTangent = current.outTangent;
|
||||||
|
if (index + 1 >= (int)m_controlNodes.size()) {
|
||||||
|
newX = (current.position.x() + scene()->sceneRect().right()) / 2;
|
||||||
|
} else {
|
||||||
|
const auto &next = m_controlNodes[index + 1];
|
||||||
|
newX = (current.position.x() + next.position.x()) / 2;
|
||||||
|
inTangent = (current.outTangent + next.inTangent) / 2;
|
||||||
|
}
|
||||||
|
auto outTangent = inTangent;
|
||||||
|
|
||||||
|
HermiteControlNode newNode(QVector2D(newX, current.position.y()), inTangent, outTangent);
|
||||||
|
m_controlNodes.insert(m_controlNodes.begin() + index + 1, newNode);
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
emit controlNodesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::addControlNodeBefore(int index)
|
||||||
|
{
|
||||||
|
if (index >= (int)m_controlNodes.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
invalidateControlSelection();
|
||||||
|
|
||||||
|
const auto ¤t = m_controlNodes[index];
|
||||||
|
int newX = current.position.x();
|
||||||
|
auto inTangent = current.outTangent;
|
||||||
|
if (index - 1 < 0) {
|
||||||
|
newX = (current.position.x() + scene()->sceneRect().left()) / 2;
|
||||||
|
} else {
|
||||||
|
const auto &previous= m_controlNodes[index - 1];
|
||||||
|
newX = (current.position.x() + previous.position.x()) / 2;
|
||||||
|
inTangent = (current.inTangent + previous.outTangent) / 2;
|
||||||
|
}
|
||||||
|
auto outTangent = inTangent;
|
||||||
|
|
||||||
|
HermiteControlNode newNode(QVector2D(newX, current.position.y()), inTangent, outTangent);
|
||||||
|
m_controlNodes.insert(m_controlNodes.begin() + index, newNode);
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
emit controlNodesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::invalidateControlSelection()
|
||||||
|
{
|
||||||
|
m_currentHoveringControlIndex = -1;
|
||||||
|
if (-1 != m_currentSelectedControlIndex)
|
||||||
|
selectControlNode(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::invalidateKeyframeSelection()
|
||||||
|
{
|
||||||
|
m_currentHoveringKeyframeIndex = -1;
|
||||||
|
if (-1 != m_currentSelectedKeyframeIndex)
|
||||||
|
selectKeyframe(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::addControlNodeAtPosition(const QPointF &pos)
|
||||||
|
{
|
||||||
|
auto inTangent = QVector2D(0, 50);
|
||||||
|
auto outTangent = inTangent;
|
||||||
|
HermiteControlNode newNode(QVector2D(pos.x(), pos.y()), inTangent, outTangent);
|
||||||
|
|
||||||
|
invalidateControlSelection();
|
||||||
|
|
||||||
|
m_controlNodes.insert(m_controlNodes.end(), newNode);
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
emit controlNodesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::hoverControlNode(int index)
|
||||||
|
{
|
||||||
|
if (index >= (int)m_controlNodes.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_currentHoveringControlIndex == index)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_currentHoveringControlIndex = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::unhoverControlNode(int index)
|
||||||
|
{
|
||||||
|
if (index >= (int)m_controlNodes.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_currentHoveringControlIndex == index) {
|
||||||
|
m_currentHoveringControlIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::hoverKeyframe(int index)
|
||||||
|
{
|
||||||
|
if (index >= (int)m_keyframes.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_currentHoveringKeyframeIndex == index)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_currentHoveringKeyframeIndex = index;
|
||||||
|
|
||||||
|
QLabel *nameLabel = (QLabel *)m_keyframeNameItems[m_currentHoveringKeyframeIndex]->widget();
|
||||||
|
nameLabel->setStyleSheet("QLabel {color : " + Theme::red.name() + ";}");
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::unhoverKeyframe(int index)
|
||||||
|
{
|
||||||
|
if (index >= (int)m_keyframes.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_currentHoveringKeyframeIndex == index) {
|
||||||
|
if (m_currentSelectedKeyframeIndex != m_currentHoveringKeyframeIndex) {
|
||||||
|
QLabel *nameLabel = (QLabel *)m_keyframeNameItems[m_currentHoveringKeyframeIndex]->widget();
|
||||||
|
nameLabel->setStyleSheet("QLabel {color : " + Theme::white.name() + ";}");
|
||||||
|
}
|
||||||
|
m_currentHoveringKeyframeIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::selectKeyframe(int index)
|
||||||
|
{
|
||||||
|
if (m_currentSelectedKeyframeIndex == index)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (-1 != m_currentSelectedKeyframeIndex && m_currentSelectedKeyframeIndex < (int)m_keyframes.size()) {
|
||||||
|
QLabel *nameLabel = (QLabel *)m_keyframeNameItems[m_currentSelectedKeyframeIndex]->widget();
|
||||||
|
nameLabel->setStyleSheet("QLabel {color : " + Theme::white.name() + ";}");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentSelectedKeyframeIndex = index;
|
||||||
|
|
||||||
|
if (-1 != m_currentSelectedKeyframeIndex && m_currentSelectedKeyframeIndex < (int)m_keyframes.size()) {
|
||||||
|
QLabel *nameLabel = (QLabel *)m_keyframeNameItems[m_currentSelectedKeyframeIndex]->widget();
|
||||||
|
nameLabel->setStyleSheet("QLabel {color : " + Theme::red.name() + ";}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::selectControlNode(int index)
|
||||||
|
{
|
||||||
|
if (m_currentSelectedControlIndex == index)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (-1 != m_currentSelectedControlIndex && m_currentSelectedControlIndex < (int)m_controlNodes.size()) {
|
||||||
|
m_anchorItems[m_currentSelectedControlIndex]->setChecked(false);
|
||||||
|
m_handleItems[m_currentSelectedControlIndex].first->setChecked(false);
|
||||||
|
m_handleItems[m_currentSelectedControlIndex].second->setChecked(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentSelectedControlIndex = index;
|
||||||
|
|
||||||
|
if (-1 != m_currentSelectedControlIndex && m_currentSelectedControlIndex < (int)m_controlNodes.size()) {
|
||||||
|
m_anchorItems[m_currentSelectedControlIndex]->setChecked(true);
|
||||||
|
m_handleItems[m_currentSelectedControlIndex].first->setChecked(true);
|
||||||
|
m_handleItems[m_currentSelectedControlIndex].second->setChecked(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::showContextMenu(const QPoint &pos)
|
||||||
|
{
|
||||||
|
if (m_previewOnly)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QMenu contextMenu(this);
|
||||||
|
|
||||||
|
QAction addAction(tr("Add Control Node"), this);
|
||||||
|
QAction addAfterAction(tr("Add Control Node After"), this);
|
||||||
|
QAction addBeforeAction(tr("Add Control Node Before"), this);
|
||||||
|
if (-1 != m_currentSelectedControlIndex) {
|
||||||
|
connect(&addAfterAction, &QAction::triggered, this, [=]() {
|
||||||
|
addControlNodeAfter(m_currentSelectedControlIndex);
|
||||||
|
});
|
||||||
|
contextMenu.addAction(&addAfterAction);
|
||||||
|
|
||||||
|
connect(&addBeforeAction, &QAction::triggered, this, [=]() {
|
||||||
|
addControlNodeBefore(m_currentSelectedControlIndex);
|
||||||
|
});
|
||||||
|
contextMenu.addAction(&addBeforeAction);
|
||||||
|
} else if (m_controlNodes.empty()) {
|
||||||
|
connect(&addAction, &QAction::triggered, this, [=]() {
|
||||||
|
addControlNodeAtPosition(mapToScene(pos));
|
||||||
|
});
|
||||||
|
contextMenu.addAction(&addAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction deleteControlNodeAction(tr("Delete Control Node"), this);
|
||||||
|
if (-1 != m_currentSelectedControlIndex) {
|
||||||
|
connect(&deleteControlNodeAction, &QAction::triggered, this, [=]() {
|
||||||
|
deleteControlNode(m_currentSelectedControlIndex);
|
||||||
|
});
|
||||||
|
contextMenu.addAction(&deleteControlNodeAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction deleteKeyframeAction(tr("Delete Keyframe"), this);
|
||||||
|
if (-1 != m_currentSelectedKeyframeIndex) {
|
||||||
|
connect(&deleteKeyframeAction, &QAction::triggered, this, [=]() {
|
||||||
|
deleteKeyframe(m_currentSelectedKeyframeIndex);
|
||||||
|
});
|
||||||
|
contextMenu.addAction(&deleteKeyframeAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
contextMenu.exec(mapToGlobal(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterpolationGraphicsWidget::setPreviewOnly(bool previewOnly)
|
||||||
|
{
|
||||||
|
if (m_previewOnly == previewOnly)
|
||||||
|
return;
|
||||||
|
m_previewOnly = previewOnly;
|
||||||
|
m_cursorItem->setVisible(!m_previewOnly);
|
||||||
|
}
|
|
@ -0,0 +1,302 @@
|
||||||
|
#ifndef INTERPOLATION_GRAPHICS_WIDGET_H
|
||||||
|
#define INTERPOLATION_GRAPHICS_WIDGET_H
|
||||||
|
#include <QGraphicsView>
|
||||||
|
#include <QBrush>
|
||||||
|
#include <QPen>
|
||||||
|
#include <QGraphicsItemGroup>
|
||||||
|
#include <QGraphicsEllipseItem>
|
||||||
|
#include <QGraphicsLineItem>
|
||||||
|
#include <QPointF>
|
||||||
|
#include <QResizeEvent>
|
||||||
|
#include <QGraphicsSceneHoverEvent>
|
||||||
|
#include <QGraphicsProxyWidget>
|
||||||
|
#include <QGraphicsWidget>
|
||||||
|
#include "curveutil.h"
|
||||||
|
#include "theme.h"
|
||||||
|
|
||||||
|
class InterpolationGraphicsProxyObject : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
signals:
|
||||||
|
void itemMoved(QPointF point);
|
||||||
|
void itemHoverEnter();
|
||||||
|
void itemHoverLeave();
|
||||||
|
};
|
||||||
|
|
||||||
|
class InterpolationGraphicsCircleItem : public QGraphicsEllipseItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InterpolationGraphicsCircleItem(float toRadius, bool limitMoveRange=true) :
|
||||||
|
m_limitMoveRange(limitMoveRange)
|
||||||
|
{
|
||||||
|
setAcceptHoverEvents(true);
|
||||||
|
setRect(QRectF(-toRadius, -toRadius, toRadius * 2, toRadius * 2));
|
||||||
|
updateAppearance();
|
||||||
|
}
|
||||||
|
~InterpolationGraphicsCircleItem()
|
||||||
|
{
|
||||||
|
delete m_object;
|
||||||
|
}
|
||||||
|
InterpolationGraphicsProxyObject *proxy()
|
||||||
|
{
|
||||||
|
return m_object;
|
||||||
|
}
|
||||||
|
void setOrigin(QPointF point)
|
||||||
|
{
|
||||||
|
m_disableEmitSignal = true;
|
||||||
|
setPos(point.x(), point.y());
|
||||||
|
m_disableEmitSignal = false;
|
||||||
|
}
|
||||||
|
QPointF origin()
|
||||||
|
{
|
||||||
|
return QPointF(pos().x(), pos().y());
|
||||||
|
}
|
||||||
|
float radius()
|
||||||
|
{
|
||||||
|
return rect().width() / 2;
|
||||||
|
}
|
||||||
|
void setChecked(bool checked)
|
||||||
|
{
|
||||||
|
m_checked = checked;
|
||||||
|
updateAppearance();
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
|
||||||
|
{
|
||||||
|
switch (change) {
|
||||||
|
case ItemPositionHasChanged:
|
||||||
|
{
|
||||||
|
QPointF newPos = value.toPointF();
|
||||||
|
if (m_limitMoveRange) {
|
||||||
|
QRectF rect = scene()->sceneRect();
|
||||||
|
if (!rect.contains(newPos)) {
|
||||||
|
newPos.setX(qMin(rect.right(), qMax(newPos.x(), rect.left())));
|
||||||
|
newPos.setY(qMin(rect.bottom(), qMax(newPos.y(), rect.top())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!m_disableEmitSignal)
|
||||||
|
emit m_object->itemMoved(newPos);
|
||||||
|
return newPos;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
return QGraphicsItem::itemChange(change, value);
|
||||||
|
}
|
||||||
|
virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override
|
||||||
|
{
|
||||||
|
QGraphicsItem::hoverEnterEvent(event);
|
||||||
|
m_hovered = true;
|
||||||
|
updateAppearance();
|
||||||
|
emit m_object->itemHoverEnter();
|
||||||
|
}
|
||||||
|
virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override
|
||||||
|
{
|
||||||
|
QGraphicsItem::hoverLeaveEvent(event);
|
||||||
|
m_hovered = false;
|
||||||
|
updateAppearance();
|
||||||
|
emit m_object->itemHoverLeave();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
InterpolationGraphicsProxyObject *m_object = new InterpolationGraphicsProxyObject();
|
||||||
|
bool m_hovered = false;
|
||||||
|
bool m_checked = false;
|
||||||
|
bool m_limitMoveRange = true;
|
||||||
|
bool m_disableEmitSignal = false;
|
||||||
|
void updateAppearance()
|
||||||
|
{
|
||||||
|
QColor color = Theme::white;
|
||||||
|
if (m_hovered || m_checked) {
|
||||||
|
setBrush(color);
|
||||||
|
} else {
|
||||||
|
setBrush(Qt::transparent);
|
||||||
|
}
|
||||||
|
QPen pen;
|
||||||
|
pen.setWidth(0);
|
||||||
|
pen.setColor(color);
|
||||||
|
setPen(pen);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class InterpolationGraphicsEdgeItem : public QGraphicsLineItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InterpolationGraphicsEdgeItem()
|
||||||
|
{
|
||||||
|
QPen linePen;
|
||||||
|
linePen.setColor(Theme::white);
|
||||||
|
linePen.setWidth(0);
|
||||||
|
setPen(linePen);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class InterpolationGraphicsLabelParentWidget : public QGraphicsWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InterpolationGraphicsLabelParentWidget()
|
||||||
|
{
|
||||||
|
setAcceptHoverEvents(true);
|
||||||
|
}
|
||||||
|
~InterpolationGraphicsLabelParentWidget()
|
||||||
|
{
|
||||||
|
delete m_object;
|
||||||
|
}
|
||||||
|
InterpolationGraphicsProxyObject *proxy()
|
||||||
|
{
|
||||||
|
return m_object;
|
||||||
|
}
|
||||||
|
void setPosWithoutEmitSignal(QPointF pos)
|
||||||
|
{
|
||||||
|
m_disableEmitSignal = true;
|
||||||
|
setPos(pos);
|
||||||
|
m_disableEmitSignal = false;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
|
||||||
|
{
|
||||||
|
switch (change) {
|
||||||
|
case ItemPositionHasChanged:
|
||||||
|
{
|
||||||
|
QPointF newPos = value.toPointF();
|
||||||
|
QRectF rect = scene()->sceneRect();
|
||||||
|
if (!rect.contains(newPos)) {
|
||||||
|
newPos.setX(qMin(rect.right(), qMax(newPos.x(), rect.left())));
|
||||||
|
newPos.setY(qMin(rect.bottom(), qMax(newPos.y(), rect.top())));
|
||||||
|
}
|
||||||
|
if (!m_disableEmitSignal)
|
||||||
|
emit m_object->itemMoved(newPos);
|
||||||
|
return newPos;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
return QGraphicsItem::itemChange(change, value);
|
||||||
|
}
|
||||||
|
virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override
|
||||||
|
{
|
||||||
|
QGraphicsWidget::hoverEnterEvent(event);
|
||||||
|
emit m_object->itemHoverEnter();
|
||||||
|
}
|
||||||
|
virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override
|
||||||
|
{
|
||||||
|
QGraphicsWidget::hoverLeaveEvent(event);
|
||||||
|
emit m_object->itemHoverLeave();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
InterpolationGraphicsProxyObject *m_object = new InterpolationGraphicsProxyObject();
|
||||||
|
bool m_disableEmitSignal = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InterpolationGraphicsCursorItem : public QGraphicsRectItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InterpolationGraphicsCursorItem()
|
||||||
|
{
|
||||||
|
QPen linePen;
|
||||||
|
linePen.setColor(Theme::red);
|
||||||
|
linePen.setWidth(0);
|
||||||
|
setPen(linePen);
|
||||||
|
}
|
||||||
|
void updateCursorPosition(const QPointF &pos)
|
||||||
|
{
|
||||||
|
QPointF newPos = pos;
|
||||||
|
QRectF rect = scene()->sceneRect();
|
||||||
|
if (!rect.contains(newPos)) {
|
||||||
|
newPos.setX(qMin(rect.right(), qMax(newPos.x(), rect.left())));
|
||||||
|
newPos.setY(qMin(rect.bottom(), qMax(newPos.y(), rect.top())));
|
||||||
|
}
|
||||||
|
setPos(newPos);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class InterpolationGraphicsKeyframeItem : public QGraphicsRectItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InterpolationGraphicsKeyframeItem()
|
||||||
|
{
|
||||||
|
QPen linePen;
|
||||||
|
linePen.setColor(Theme::red);
|
||||||
|
linePen.setWidth(0);
|
||||||
|
setPen(linePen);
|
||||||
|
|
||||||
|
setBrush(QBrush(Theme::red));
|
||||||
|
|
||||||
|
setRect(-4, -4, 8, 8);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class InterpolationGraphicsWidget : public QGraphicsView
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
signals:
|
||||||
|
void controlNodesChanged();
|
||||||
|
void keyframeKnotChanged(int index, float knot);
|
||||||
|
void removeKeyframe(int index);
|
||||||
|
public:
|
||||||
|
InterpolationGraphicsWidget(QWidget *parent=nullptr);
|
||||||
|
void toNormalizedControlNodes(std::vector<HermiteControlNode> &snapshot);
|
||||||
|
void fromNormalizedControlNodes(const std::vector<HermiteControlNode> &snapshot);
|
||||||
|
float cursorKnot() const;
|
||||||
|
public slots:
|
||||||
|
void setControlNodes(const std::vector<HermiteControlNode> &nodes);
|
||||||
|
void setKeyframes(const std::vector<std::pair<float, QString>> &keyframes);
|
||||||
|
void refresh();
|
||||||
|
void refreshControlNode(int index);
|
||||||
|
void refreshControlAnchor(int index);
|
||||||
|
void refreshControlEdges(int index);
|
||||||
|
void refreshControlHandles(int index);
|
||||||
|
void refreshCurve();
|
||||||
|
void refreshKeyframe(int index);
|
||||||
|
void refreshKeyframeKnot(int index);
|
||||||
|
void refreshKeyframeLabel(int index);
|
||||||
|
void refreshKeyframes();
|
||||||
|
void showContextMenu(const QPoint &pos);
|
||||||
|
void deleteControlNode(int index);
|
||||||
|
void deleteKeyframe(int index);
|
||||||
|
void addControlNodeAfter(int index);
|
||||||
|
void addControlNodeBefore(int index);
|
||||||
|
void addControlNodeAtPosition(const QPointF &pos);
|
||||||
|
void hoverControlNode(int index);
|
||||||
|
void unhoverControlNode(int index);
|
||||||
|
void selectControlNode(int index);
|
||||||
|
void selectKeyframe(int index);
|
||||||
|
void hoverKeyframe(int index);
|
||||||
|
void unhoverKeyframe(int index);
|
||||||
|
void invalidateControlSelection();
|
||||||
|
void invalidateKeyframeSelection();
|
||||||
|
void setPreviewOnly(bool previewOnly);
|
||||||
|
protected:
|
||||||
|
virtual void resizeEvent(QResizeEvent *event) override;
|
||||||
|
virtual void mouseMoveEvent(QMouseEvent *event) override;
|
||||||
|
virtual void mouseReleaseEvent(QMouseEvent *event) override;
|
||||||
|
virtual void mousePressEvent(QMouseEvent *event) override;
|
||||||
|
private:
|
||||||
|
std::vector<HermiteControlNode> m_controlNodes;
|
||||||
|
std::vector<std::pair<float, QString>> m_keyframes;
|
||||||
|
QGraphicsPathItem *m_pathItem = nullptr;
|
||||||
|
std::vector<InterpolationGraphicsCircleItem *> m_anchorItems;
|
||||||
|
std::vector<std::pair<InterpolationGraphicsEdgeItem *, InterpolationGraphicsEdgeItem *>> m_lineItems;
|
||||||
|
std::vector<std::pair<InterpolationGraphicsCircleItem *, InterpolationGraphicsCircleItem *>> m_handleItems;
|
||||||
|
std::vector<QGraphicsLineItem *> m_gridRowLineItems;
|
||||||
|
std::vector<QGraphicsLineItem *> m_gridColumnLineItems;
|
||||||
|
std::vector<InterpolationGraphicsKeyframeItem *> m_keyframeKnotItems;
|
||||||
|
std::vector<QGraphicsProxyWidget *> m_keyframeNameItems;
|
||||||
|
InterpolationGraphicsCursorItem *m_cursorItem = nullptr;
|
||||||
|
bool m_gridEnabled = true;
|
||||||
|
int m_currentHoveringControlIndex = -1;
|
||||||
|
int m_currentSelectedControlIndex = -1;
|
||||||
|
int m_currentHoveringKeyframeIndex = -1;
|
||||||
|
int m_currentSelectedKeyframeIndex = -1;
|
||||||
|
bool m_previewOnly = false;
|
||||||
|
static const float m_anchorRadius;
|
||||||
|
static const float m_handleRadius;
|
||||||
|
static const int m_gridColumns;
|
||||||
|
static const int m_gridRows;
|
||||||
|
static const int m_sceneWidth;
|
||||||
|
static const int m_sceneHeight;
|
||||||
|
static const float m_tangentMagnitudeScaleFactor;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,4 +1,5 @@
|
||||||
#include "jointnodetree.h"
|
#include "jointnodetree.h"
|
||||||
|
#include "dust3dutil.h"
|
||||||
|
|
||||||
const std::vector<JointNode> &JointNodeTree::nodes() const
|
const std::vector<JointNode> &JointNodeTree::nodes() const
|
||||||
{
|
{
|
||||||
|
@ -72,3 +73,15 @@ JointNodeTree::JointNodeTree(const std::vector<AutoRiggerBone> *resultRigBones)
|
||||||
node.inverseBindMatrix = node.transformMatrix.inverted();
|
node.inverseBindMatrix = node.transformMatrix.inverted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JointNodeTree JointNodeTree::slerp(const JointNodeTree &first, const JointNodeTree &second, float t)
|
||||||
|
{
|
||||||
|
JointNodeTree slerpResult = first;
|
||||||
|
for (decltype(first.nodes().size()) i = 0; i < first.nodes().size() && i < second.nodes().size(); i++) {
|
||||||
|
slerpResult.updateRotation(i,
|
||||||
|
quaternionOvershootSlerp(first.nodes()[i].rotation, second.nodes()[i].rotation, t));
|
||||||
|
}
|
||||||
|
slerpResult.recalculateTransformMatrices();
|
||||||
|
return slerpResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ public:
|
||||||
void updateRotation(int index, QQuaternion rotation);
|
void updateRotation(int index, QQuaternion rotation);
|
||||||
void reset();
|
void reset();
|
||||||
void recalculateTransformMatrices();
|
void recalculateTransformMatrices();
|
||||||
|
static JointNodeTree slerp(const JointNodeTree &first, const JointNodeTree &second, float t);
|
||||||
private:
|
private:
|
||||||
std::vector<JointNode> m_boneNodes;
|
std::vector<JointNode> m_boneNodes;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,298 @@
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QDebug>
|
||||||
|
#include "motioneditwidget.h"
|
||||||
|
#include "dust3dutil.h"
|
||||||
|
#include "poselistwidget.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
MotionEditWidget::MotionEditWidget(const SkeletonDocument *document, QWidget *parent) :
|
||||||
|
QDialog(parent),
|
||||||
|
m_document(document)
|
||||||
|
{
|
||||||
|
m_clipPlayer = new AnimationClipPlayer;
|
||||||
|
|
||||||
|
m_graphicsWidget = new InterpolationGraphicsWidget(this);
|
||||||
|
m_graphicsWidget->setMinimumSize(256, 128);
|
||||||
|
|
||||||
|
connect(m_graphicsWidget, &InterpolationGraphicsWidget::controlNodesChanged, this, &MotionEditWidget::setUnsavedState);
|
||||||
|
connect(m_graphicsWidget, &InterpolationGraphicsWidget::controlNodesChanged, this, &MotionEditWidget::generatePreviews);
|
||||||
|
connect(m_graphicsWidget, &InterpolationGraphicsWidget::removeKeyframe, this, [=](int index) {
|
||||||
|
m_keyframes.erase(m_keyframes.begin() + index);
|
||||||
|
syncKeyframesToGraphicsView();
|
||||||
|
setUnsavedState();
|
||||||
|
generatePreviews();
|
||||||
|
});
|
||||||
|
connect(m_graphicsWidget, &InterpolationGraphicsWidget::keyframeKnotChanged, this, [=](int index, float knot) {
|
||||||
|
m_keyframes[index].first = knot;
|
||||||
|
setUnsavedState();
|
||||||
|
generatePreviews();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_previewWidget = new ModelWidget(this);
|
||||||
|
m_previewWidget->setMinimumSize(128, 128);
|
||||||
|
m_previewWidget->resize(384, 384);
|
||||||
|
m_previewWidget->move(-64, 0);
|
||||||
|
|
||||||
|
connect(m_clipPlayer, &AnimationClipPlayer::frameReadyToShow, this, [=]() {
|
||||||
|
m_previewWidget->updateMesh(m_clipPlayer->takeFrameMesh());
|
||||||
|
});
|
||||||
|
|
||||||
|
PoseListWidget *poseListWidget = new PoseListWidget(document);
|
||||||
|
poseListWidget->setCornerButtonVisible(true);
|
||||||
|
poseListWidget->setHasContextMenu(false);
|
||||||
|
QWidget *poseListContainerWidget = new QWidget;
|
||||||
|
QGridLayout *poseListLayoutForContainer = new QGridLayout;
|
||||||
|
poseListLayoutForContainer->addWidget(poseListWidget);
|
||||||
|
poseListContainerWidget->setLayout(poseListLayoutForContainer);
|
||||||
|
|
||||||
|
poseListContainerWidget->resize(512, Theme::posePreviewImageSize);
|
||||||
|
|
||||||
|
connect(poseListWidget, &PoseListWidget::cornerButtonClicked, this, [=](QUuid poseId) {
|
||||||
|
addKeyframe(m_graphicsWidget->cursorKnot(), poseId);
|
||||||
|
});
|
||||||
|
|
||||||
|
//QLabel *interpolationTypeItemName = new QLabel(tr("Interpolation:"));
|
||||||
|
//QLabel *interpolationTypeValue = new QLabel(tr("Spring"));
|
||||||
|
|
||||||
|
//QPushButton *interpolationTypeButton = new QPushButton();
|
||||||
|
//interpolationTypeButton->setText(tr("Browse..."));
|
||||||
|
|
||||||
|
//QHBoxLayout *interpolationButtonsLayout = new QHBoxLayout;
|
||||||
|
//interpolationButtonsLayout->addStretch();
|
||||||
|
//interpolationButtonsLayout->addWidget(interpolationTypeItemName);
|
||||||
|
//interpolationButtonsLayout->addWidget(interpolationTypeValue);
|
||||||
|
//interpolationButtonsLayout->addWidget(interpolationTypeButton);
|
||||||
|
//interpolationButtonsLayout->addStretch();
|
||||||
|
|
||||||
|
QVBoxLayout *motionEditLayout = new QVBoxLayout;
|
||||||
|
motionEditLayout->addWidget(poseListContainerWidget);
|
||||||
|
motionEditLayout->addWidget(Theme::createHorizontalLineWidget());
|
||||||
|
motionEditLayout->addWidget(m_graphicsWidget);
|
||||||
|
//motionEditLayout->addLayout(interpolationButtonsLayout);
|
||||||
|
|
||||||
|
QHBoxLayout *topLayout = new QHBoxLayout;
|
||||||
|
topLayout->setContentsMargins(256, 0, 0, 0);
|
||||||
|
topLayout->addWidget(Theme::createVerticalLineWidget());
|
||||||
|
topLayout->addLayout(motionEditLayout);
|
||||||
|
|
||||||
|
m_nameEdit = new QLineEdit;
|
||||||
|
connect(m_nameEdit, &QLineEdit::textChanged, this, &MotionEditWidget::setUnsavedState);
|
||||||
|
QPushButton *saveButton = new QPushButton(tr("Save"));
|
||||||
|
connect(saveButton, &QPushButton::clicked, this, &MotionEditWidget::save);
|
||||||
|
saveButton->setDefault(true);
|
||||||
|
|
||||||
|
QHBoxLayout *baseInfoLayout = new QHBoxLayout;
|
||||||
|
baseInfoLayout->addWidget(new QLabel(tr("Name")));
|
||||||
|
baseInfoLayout->addWidget(m_nameEdit);
|
||||||
|
baseInfoLayout->addStretch();
|
||||||
|
baseInfoLayout->addWidget(saveButton);
|
||||||
|
|
||||||
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||||
|
mainLayout->addLayout(topLayout);
|
||||||
|
mainLayout->addWidget(Theme::createHorizontalLineWidget());
|
||||||
|
mainLayout->addLayout(baseInfoLayout);
|
||||||
|
|
||||||
|
setLayout(mainLayout);
|
||||||
|
|
||||||
|
connect(this, &MotionEditWidget::addMotion, m_document, &SkeletonDocument::addMotion);
|
||||||
|
connect(this, &MotionEditWidget::renameMotion, m_document, &SkeletonDocument::renameMotion);
|
||||||
|
connect(this, &MotionEditWidget::setMotionControlNodes, m_document, &SkeletonDocument::setMotionControlNodes);
|
||||||
|
connect(this, &MotionEditWidget::setMotionKeyframes, m_document, &SkeletonDocument::setMotionKeyframes);
|
||||||
|
|
||||||
|
updateTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEditWidget::~MotionEditWidget()
|
||||||
|
{
|
||||||
|
delete m_clipPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize MotionEditWidget::sizeHint() const
|
||||||
|
{
|
||||||
|
return QSize(800, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEditWidget::reject()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEditWidget::closeEvent(QCloseEvent *event)
|
||||||
|
{
|
||||||
|
if (m_unsaved && !m_closed) {
|
||||||
|
QMessageBox::StandardButton answer = QMessageBox::question(this,
|
||||||
|
APP_NAME,
|
||||||
|
tr("Do you really want to close while there are unsaved changes?"),
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
if (answer != QMessageBox::Yes) {
|
||||||
|
event->ignore();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_closed = true;
|
||||||
|
hide();
|
||||||
|
if (nullptr != m_previewsGenerator) {
|
||||||
|
event->ignore();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEditWidget::save()
|
||||||
|
{
|
||||||
|
std::vector<HermiteControlNode> controlNodes;
|
||||||
|
m_graphicsWidget->toNormalizedControlNodes(controlNodes);
|
||||||
|
if (m_motionId.isNull()) {
|
||||||
|
emit addMotion(m_nameEdit->text(), controlNodes, m_keyframes);
|
||||||
|
} else if (m_unsaved) {
|
||||||
|
emit renameMotion(m_motionId, m_nameEdit->text());
|
||||||
|
emit setMotionControlNodes(m_motionId, controlNodes);
|
||||||
|
emit setMotionKeyframes(m_motionId, m_keyframes);
|
||||||
|
}
|
||||||
|
m_unsaved = false;
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEditWidget::clearUnsaveState()
|
||||||
|
{
|
||||||
|
m_unsaved = false;
|
||||||
|
updateTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEditWidget::setUnsavedState()
|
||||||
|
{
|
||||||
|
m_unsaved = true;
|
||||||
|
updateTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEditWidget::setEditMotionId(QUuid motionId)
|
||||||
|
{
|
||||||
|
if (m_motionId == motionId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_motionId = motionId;
|
||||||
|
updateTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEditWidget::setEditMotionName(QString name)
|
||||||
|
{
|
||||||
|
m_nameEdit->setText(name);
|
||||||
|
updateTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEditWidget::updateTitle()
|
||||||
|
{
|
||||||
|
if (m_motionId.isNull()) {
|
||||||
|
setWindowTitle(unifiedWindowTitle(tr("New") + (m_unsaved ? "*" : "")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const SkeletonMotion *motion = m_document->findMotion(m_motionId);
|
||||||
|
if (nullptr == motion) {
|
||||||
|
qDebug() << "Find motion failed:" << m_motionId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setWindowTitle(unifiedWindowTitle(motion->name + (m_unsaved ? "*" : "")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEditWidget::setEditMotionControlNodes(std::vector<HermiteControlNode> controlNodes)
|
||||||
|
{
|
||||||
|
m_graphicsWidget->setControlNodes(controlNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEditWidget::syncKeyframesToGraphicsView()
|
||||||
|
{
|
||||||
|
std::vector<std::pair<float, QString>> keyframesForGraphicsView;
|
||||||
|
for (const auto &frame: m_keyframes) {
|
||||||
|
QString poseName;
|
||||||
|
const SkeletonPose *pose = m_document->findPose(frame.second);
|
||||||
|
if (nullptr == pose) {
|
||||||
|
qDebug() << "Find pose failed:" << frame.second;
|
||||||
|
} else {
|
||||||
|
poseName = pose->name;
|
||||||
|
}
|
||||||
|
keyframesForGraphicsView.push_back({frame.first, poseName});
|
||||||
|
}
|
||||||
|
m_graphicsWidget->setKeyframes(keyframesForGraphicsView);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEditWidget::setEditMotionKeyframes(std::vector<std::pair<float, QUuid>> keyframes)
|
||||||
|
{
|
||||||
|
m_keyframes = keyframes;
|
||||||
|
syncKeyframesToGraphicsView();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEditWidget::addKeyframe(float knot, QUuid poseId)
|
||||||
|
{
|
||||||
|
m_keyframes.push_back({knot, poseId});
|
||||||
|
std::sort(m_keyframes.begin(), m_keyframes.end(),
|
||||||
|
[](const std::pair<float, QUuid> &first, const std::pair<float, QUuid> &second) {
|
||||||
|
return first.first < second.first;
|
||||||
|
});
|
||||||
|
syncKeyframesToGraphicsView();
|
||||||
|
setUnsavedState();
|
||||||
|
generatePreviews();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEditWidget::updateKeyframeKnot(int index, float knot)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= (int)m_keyframes.size())
|
||||||
|
return;
|
||||||
|
m_keyframes[index].first = knot;
|
||||||
|
setUnsavedState();
|
||||||
|
generatePreviews();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEditWidget::generatePreviews()
|
||||||
|
{
|
||||||
|
if (nullptr != m_previewsGenerator) {
|
||||||
|
m_isPreviewsObsolete = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_isPreviewsObsolete = false;
|
||||||
|
|
||||||
|
const std::vector<AutoRiggerBone> *rigBones = m_document->resultRigBones();
|
||||||
|
const std::map<int, AutoRiggerVertexWeights> *rigWeights = m_document->resultRigWeights();
|
||||||
|
|
||||||
|
if (nullptr == rigBones || nullptr == rigWeights) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_previewsGenerator = new MotionPreviewsGenerator(rigBones, rigWeights,
|
||||||
|
m_document->currentRiggedResultContext());
|
||||||
|
for (const auto &pose: m_document->poseMap)
|
||||||
|
m_previewsGenerator->addPoseToLibrary(pose.first, pose.second.parameters);
|
||||||
|
//for (const auto &motion: m_document->motionMap)
|
||||||
|
// m_previewsGenerator->addMotionToLibrary(motion.first, motion.second.controlNodes, motion.second.keyframes);
|
||||||
|
std::vector<HermiteControlNode> controlNodes;
|
||||||
|
m_graphicsWidget->toNormalizedControlNodes(controlNodes);
|
||||||
|
m_previewsGenerator->addMotionToLibrary(QUuid(), controlNodes, m_keyframes);
|
||||||
|
m_previewsGenerator->addPreviewRequirement(QUuid());
|
||||||
|
QThread *thread = new QThread;
|
||||||
|
m_previewsGenerator->moveToThread(thread);
|
||||||
|
connect(thread, &QThread::started, m_previewsGenerator, &MotionPreviewsGenerator::process);
|
||||||
|
connect(m_previewsGenerator, &MotionPreviewsGenerator::finished, this, &MotionEditWidget::previewsReady);
|
||||||
|
connect(m_previewsGenerator, &MotionPreviewsGenerator::finished, thread, &QThread::quit);
|
||||||
|
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||||
|
thread->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionEditWidget::previewsReady()
|
||||||
|
{
|
||||||
|
auto resultPreviewMeshs = m_previewsGenerator->takeResultPreviewMeshs(QUuid());
|
||||||
|
m_clipPlayer->updateFrameMeshes(resultPreviewMeshs);
|
||||||
|
|
||||||
|
delete m_previewsGenerator;
|
||||||
|
m_previewsGenerator = nullptr;
|
||||||
|
|
||||||
|
if (m_closed) {
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_isPreviewsObsolete)
|
||||||
|
generatePreviews();
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
#ifndef MOTION_EDIT_WIDGET_H
|
||||||
|
#define MOTION_EDIT_WIDGET_H
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QCloseEvent>
|
||||||
|
#include "skeletondocument.h"
|
||||||
|
#include "interpolationgraphicswidget.h"
|
||||||
|
#include "modelwidget.h"
|
||||||
|
#include "curveutil.h"
|
||||||
|
#include "motionpreviewsgenerator.h"
|
||||||
|
#include "animationclipplayer.h"
|
||||||
|
|
||||||
|
class MotionEditWidget : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
signals:
|
||||||
|
void addMotion(QString name, std::vector<HermiteControlNode> controlNodes, std::vector<std::pair<float, QUuid>> keyframes);
|
||||||
|
void setMotionControlNodes(QUuid motionId, std::vector<HermiteControlNode> controlNodes);
|
||||||
|
void setMotionKeyframes(QUuid motionId, std::vector<std::pair<float, QUuid>> keyframes);
|
||||||
|
void renameMotion(QUuid motionId, QString name);
|
||||||
|
public:
|
||||||
|
MotionEditWidget(const SkeletonDocument *document, QWidget *parent=nullptr);
|
||||||
|
~MotionEditWidget();
|
||||||
|
protected:
|
||||||
|
void closeEvent(QCloseEvent *event) override;
|
||||||
|
void reject() override;
|
||||||
|
QSize sizeHint() const override;
|
||||||
|
public slots:
|
||||||
|
void updateTitle();
|
||||||
|
void save();
|
||||||
|
void clearUnsaveState();
|
||||||
|
void setEditMotionId(QUuid poseId);
|
||||||
|
void setEditMotionName(QString name);
|
||||||
|
void setEditMotionControlNodes(std::vector<HermiteControlNode> controlNodes);
|
||||||
|
void setEditMotionKeyframes(std::vector<std::pair<float, QUuid>> keyframes);
|
||||||
|
void setUnsavedState();
|
||||||
|
void addKeyframe(float knot, QUuid poseId);
|
||||||
|
void syncKeyframesToGraphicsView();
|
||||||
|
void generatePreviews();
|
||||||
|
void previewsReady();
|
||||||
|
void updateKeyframeKnot(int index, float knot);
|
||||||
|
private:
|
||||||
|
const SkeletonDocument *m_document = nullptr;
|
||||||
|
InterpolationGraphicsWidget *m_graphicsWidget = nullptr;
|
||||||
|
ModelWidget *m_previewWidget = nullptr;
|
||||||
|
QUuid m_motionId;
|
||||||
|
QLineEdit *m_nameEdit = nullptr;
|
||||||
|
std::vector<std::pair<float, QUuid>> m_keyframes;
|
||||||
|
bool m_unsaved = false;
|
||||||
|
bool m_closed = false;
|
||||||
|
bool m_isPreviewsObsolete = false;
|
||||||
|
MotionPreviewsGenerator *m_previewsGenerator = nullptr;
|
||||||
|
AnimationClipPlayer *m_clipPlayer = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,315 @@
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QXmlStreamWriter>
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QApplication>
|
||||||
|
#include "skeletonxml.h"
|
||||||
|
#include "motionlistwidget.h"
|
||||||
|
|
||||||
|
MotionListWidget::MotionListWidget(const SkeletonDocument *document, QWidget *parent) :
|
||||||
|
QTreeWidget(parent),
|
||||||
|
m_document(document)
|
||||||
|
{
|
||||||
|
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||||
|
setFocusPolicy(Qt::NoFocus);
|
||||||
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
|
|
||||||
|
setAutoScroll(false);
|
||||||
|
|
||||||
|
setHeaderHidden(true);
|
||||||
|
|
||||||
|
QPalette palette = this->palette();
|
||||||
|
palette.setColor(QPalette::Window, Qt::transparent);
|
||||||
|
palette.setColor(QPalette::Base, Qt::transparent);
|
||||||
|
setPalette(palette);
|
||||||
|
|
||||||
|
setStyleSheet("QTreeView {qproperty-indentation: 0;}");
|
||||||
|
|
||||||
|
setContentsMargins(0, 0, 0, 0);
|
||||||
|
|
||||||
|
connect(document, &SkeletonDocument::motionListChanged, this, &MotionListWidget::reload);
|
||||||
|
connect(document, &SkeletonDocument::cleanup, this, &MotionListWidget::removeAllContent);
|
||||||
|
|
||||||
|
connect(this, &MotionListWidget::removeMotion, document, &SkeletonDocument::removeMotion);
|
||||||
|
|
||||||
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
connect(this, &QTreeWidget::customContextMenuRequested, this, &MotionListWidget::showContextMenu);
|
||||||
|
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionListWidget::motionRemoved(QUuid motionId)
|
||||||
|
{
|
||||||
|
if (m_currentSelectedMotionId == motionId)
|
||||||
|
m_currentSelectedMotionId = QUuid();
|
||||||
|
m_selectedMotionIds.erase(motionId);
|
||||||
|
m_itemMap.erase(motionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionListWidget::updateMotionSelectState(QUuid motionId, bool selected)
|
||||||
|
{
|
||||||
|
auto findItemResult = m_itemMap.find(motionId);
|
||||||
|
if (findItemResult == m_itemMap.end()) {
|
||||||
|
qDebug() << "Find motion item failed:" << motionId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MotionWidget *motionWidget = (MotionWidget *)itemWidget(findItemResult->second.first, findItemResult->second.second);
|
||||||
|
motionWidget->updateCheckedState(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionListWidget::selectMotion(QUuid motionId, bool multiple)
|
||||||
|
{
|
||||||
|
if (multiple) {
|
||||||
|
if (!m_currentSelectedMotionId.isNull()) {
|
||||||
|
m_selectedMotionIds.insert(m_currentSelectedMotionId);
|
||||||
|
m_currentSelectedMotionId = QUuid();
|
||||||
|
}
|
||||||
|
if (m_selectedMotionIds.find(motionId) != m_selectedMotionIds.end()) {
|
||||||
|
updateMotionSelectState(motionId, false);
|
||||||
|
m_selectedMotionIds.erase(motionId);
|
||||||
|
} else {
|
||||||
|
updateMotionSelectState(motionId, true);
|
||||||
|
m_selectedMotionIds.insert(motionId);
|
||||||
|
}
|
||||||
|
if (m_selectedMotionIds.size() > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_selectedMotionIds.size() == 1)
|
||||||
|
motionId = *m_selectedMotionIds.begin();
|
||||||
|
else
|
||||||
|
motionId = QUuid();
|
||||||
|
}
|
||||||
|
if (!m_selectedMotionIds.empty()) {
|
||||||
|
for (const auto &id: m_selectedMotionIds) {
|
||||||
|
updateMotionSelectState(id, false);
|
||||||
|
}
|
||||||
|
m_selectedMotionIds.clear();
|
||||||
|
}
|
||||||
|
if (m_currentSelectedMotionId != motionId) {
|
||||||
|
if (!m_currentSelectedMotionId.isNull()) {
|
||||||
|
updateMotionSelectState(m_currentSelectedMotionId, false);
|
||||||
|
}
|
||||||
|
m_currentSelectedMotionId = motionId;
|
||||||
|
if (!m_currentSelectedMotionId.isNull()) {
|
||||||
|
updateMotionSelectState(m_currentSelectedMotionId, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionListWidget::mousePressEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
QModelIndex itemIndex = indexAt(event->pos());
|
||||||
|
QTreeView::mousePressEvent(event);
|
||||||
|
if (event->button() == Qt::LeftButton) {
|
||||||
|
bool multiple = QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier);
|
||||||
|
if (itemIndex.isValid()) {
|
||||||
|
QTreeWidgetItem *item = itemFromIndex(itemIndex);
|
||||||
|
auto motionId = QUuid(item->data(itemIndex.column(), Qt::UserRole).toString());
|
||||||
|
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) {
|
||||||
|
bool startAdd = false;
|
||||||
|
bool stopAdd = false;
|
||||||
|
std::vector<QUuid> waitQueue;
|
||||||
|
for (const auto &childId: m_document->motionIdList) {
|
||||||
|
if (m_shiftStartMotionId == childId || motionId == childId) {
|
||||||
|
if (startAdd) {
|
||||||
|
stopAdd = true;
|
||||||
|
} else {
|
||||||
|
startAdd = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (startAdd)
|
||||||
|
waitQueue.push_back(childId);
|
||||||
|
if (stopAdd)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (stopAdd && !waitQueue.empty()) {
|
||||||
|
if (!m_selectedMotionIds.empty()) {
|
||||||
|
for (const auto &id: m_selectedMotionIds) {
|
||||||
|
updateMotionSelectState(id, false);
|
||||||
|
}
|
||||||
|
m_selectedMotionIds.clear();
|
||||||
|
}
|
||||||
|
if (!m_currentSelectedMotionId.isNull()) {
|
||||||
|
m_currentSelectedMotionId = QUuid();
|
||||||
|
}
|
||||||
|
for (const auto &waitId: waitQueue) {
|
||||||
|
selectMotion(waitId, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
m_shiftStartMotionId = motionId;
|
||||||
|
}
|
||||||
|
selectMotion(motionId, multiple);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!multiple)
|
||||||
|
selectMotion(QUuid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MotionListWidget::isMotionSelected(QUuid motionId)
|
||||||
|
{
|
||||||
|
return (m_currentSelectedMotionId == motionId ||
|
||||||
|
m_selectedMotionIds.find(motionId) != m_selectedMotionIds.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionListWidget::showContextMenu(const QPoint &pos)
|
||||||
|
{
|
||||||
|
QMenu contextMenu(this);
|
||||||
|
|
||||||
|
std::set<QUuid> unorderedMotionIds = m_selectedMotionIds;
|
||||||
|
if (!m_currentSelectedMotionId.isNull())
|
||||||
|
unorderedMotionIds.insert(m_currentSelectedMotionId);
|
||||||
|
|
||||||
|
std::vector<QUuid> motionIds;
|
||||||
|
for (const auto &cand: m_document->motionIdList) {
|
||||||
|
if (unorderedMotionIds.find(cand) != unorderedMotionIds.end())
|
||||||
|
motionIds.push_back(cand);
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction modifyAction(tr("Modify"), this);
|
||||||
|
if (motionIds.size() == 1) {
|
||||||
|
connect(&modifyAction, &QAction::triggered, this, [=]() {
|
||||||
|
emit modifyMotion(*motionIds.begin());
|
||||||
|
});
|
||||||
|
contextMenu.addAction(&modifyAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction copyAction(tr("Copy"), this);
|
||||||
|
if (!motionIds.empty()) {
|
||||||
|
connect(©Action, &QAction::triggered, this, &MotionListWidget::copy);
|
||||||
|
contextMenu.addAction(©Action);
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction pasteAction(tr("Paste"), this);
|
||||||
|
if (m_document->hasPastableMotionsInClipboard()) {
|
||||||
|
connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste);
|
||||||
|
contextMenu.addAction(&pasteAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction deleteAction(tr("Delete"), this);
|
||||||
|
if (!motionIds.empty()) {
|
||||||
|
connect(&deleteAction, &QAction::triggered, [=]() {
|
||||||
|
for (const auto &motionId: motionIds)
|
||||||
|
emit removeMotion(motionId);
|
||||||
|
});
|
||||||
|
contextMenu.addAction(&deleteAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
contextMenu.exec(mapToGlobal(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionListWidget::resizeEvent(QResizeEvent *event)
|
||||||
|
{
|
||||||
|
QTreeWidget::resizeEvent(event);
|
||||||
|
if (calculateColumnCount() != columnCount())
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
int MotionListWidget::calculateColumnCount()
|
||||||
|
{
|
||||||
|
if (nullptr == parentWidget())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int columns = parentWidget()->width() / Theme::motionPreviewImageSize;
|
||||||
|
if (0 == columns)
|
||||||
|
columns = 1;
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionListWidget::reload()
|
||||||
|
{
|
||||||
|
removeAllContent();
|
||||||
|
|
||||||
|
int columns = calculateColumnCount();
|
||||||
|
if (0 == columns)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int columnWidth = parentWidget()->width() / columns;
|
||||||
|
|
||||||
|
//qDebug() << "parentWidth:" << parentWidget()->width() << "columnWidth:" << columnWidth << "columns:" << columns;
|
||||||
|
|
||||||
|
setColumnCount(columns);
|
||||||
|
for (int i = 0; i < columns; i++)
|
||||||
|
setColumnWidth(i, columnWidth);
|
||||||
|
|
||||||
|
decltype(m_document->motionIdList.size()) motionIndex = 0;
|
||||||
|
while (motionIndex < m_document->motionIdList.size()) {
|
||||||
|
QTreeWidgetItem *item = new QTreeWidgetItem(this);
|
||||||
|
item->setFlags((item->flags() | Qt::ItemIsEnabled) & ~(Qt::ItemIsSelectable) & ~(Qt::ItemIsEditable));
|
||||||
|
for (int col = 0; col < columns && motionIndex < m_document->motionIdList.size(); col++, motionIndex++) {
|
||||||
|
const auto &motionId = m_document->motionIdList[motionIndex];
|
||||||
|
item->setSizeHint(col, QSize(columnWidth, MotionWidget::preferredHeight() + 2));
|
||||||
|
item->setData(col, Qt::UserRole, motionId.toString());
|
||||||
|
MotionWidget *widget = new MotionWidget(m_document, motionId);
|
||||||
|
connect(widget, &MotionWidget::modifyMotion, this, &MotionListWidget::modifyMotion);
|
||||||
|
setItemWidget(item, col, widget);
|
||||||
|
widget->reload();
|
||||||
|
widget->updateCheckedState(isMotionSelected(motionId));
|
||||||
|
m_itemMap[motionId] = std::make_pair(item, col);
|
||||||
|
}
|
||||||
|
invisibleRootItem()->addChild(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionListWidget::removeAllContent()
|
||||||
|
{
|
||||||
|
m_itemMap.clear();
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MotionListWidget::mouseMove(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MotionListWidget::wheel(QWheelEvent *event)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MotionListWidget::mouseRelease(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MotionListWidget::mousePress(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
if (event->button() == Qt::RightButton) {
|
||||||
|
showContextMenu(mapFromGlobal(event->globalPos()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MotionListWidget::mouseDoubleClick(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MotionListWidget::keyPress(QKeyEvent *event)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionListWidget::copy()
|
||||||
|
{
|
||||||
|
if (m_selectedMotionIds.empty() && m_currentSelectedMotionId.isNull())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::set<QUuid> limitMotionIds = m_selectedMotionIds;
|
||||||
|
if (!m_currentSelectedMotionId.isNull())
|
||||||
|
limitMotionIds.insert(m_currentSelectedMotionId);
|
||||||
|
|
||||||
|
std::set<QUuid> emptySet;
|
||||||
|
|
||||||
|
SkeletonSnapshot snapshot;
|
||||||
|
m_document->toSnapshot(&snapshot, emptySet, SkeletonDocumentToSnapshotFor::Motions,
|
||||||
|
emptySet, limitMotionIds);
|
||||||
|
QString snapshotXml;
|
||||||
|
QXmlStreamWriter xmlStreamWriter(&snapshotXml);
|
||||||
|
saveSkeletonToXmlStream(&snapshot, &xmlStreamWriter);
|
||||||
|
QClipboard *clipboard = QApplication::clipboard();
|
||||||
|
clipboard->setText(snapshotXml);
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
#ifndef MOTION_LIST_WIDGET_H
|
||||||
|
#define MOTION_LIST_WIDGET_H
|
||||||
|
#include <QTreeWidget>
|
||||||
|
#include <map>
|
||||||
|
#include "skeletondocument.h"
|
||||||
|
#include "motionwidget.h"
|
||||||
|
#include "skeletongraphicswidget.h"
|
||||||
|
|
||||||
|
class MotionListWidget : public QTreeWidget, public SkeletonGraphicsFunctions
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
signals:
|
||||||
|
void removeMotion(QUuid motionId);
|
||||||
|
void modifyMotion(QUuid motionId);
|
||||||
|
public:
|
||||||
|
MotionListWidget(const SkeletonDocument *document, QWidget *parent=nullptr);
|
||||||
|
bool isMotionSelected(QUuid motionId);
|
||||||
|
public slots:
|
||||||
|
void reload();
|
||||||
|
void removeAllContent();
|
||||||
|
void motionRemoved(QUuid motionId);
|
||||||
|
void showContextMenu(const QPoint &pos);
|
||||||
|
void selectMotion(QUuid motionId, bool multiple=false);
|
||||||
|
void copy();
|
||||||
|
protected:
|
||||||
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
|
void mousePressEvent(QMouseEvent *event) override;
|
||||||
|
bool mouseMove(QMouseEvent *event) override;
|
||||||
|
bool wheel(QWheelEvent *event) override;
|
||||||
|
bool mouseRelease(QMouseEvent *event) override;
|
||||||
|
bool mousePress(QMouseEvent *event) override;
|
||||||
|
bool mouseDoubleClick(QMouseEvent *event) override;
|
||||||
|
bool keyPress(QKeyEvent *event) override;
|
||||||
|
private:
|
||||||
|
int calculateColumnCount();
|
||||||
|
void updateMotionSelectState(QUuid motionId, bool selected);
|
||||||
|
const SkeletonDocument *m_document = nullptr;
|
||||||
|
std::map<QUuid, std::pair<QTreeWidgetItem *, int>> m_itemMap;
|
||||||
|
std::set<QUuid> m_selectedMotionIds;
|
||||||
|
QUuid m_currentSelectedMotionId;
|
||||||
|
QUuid m_shiftStartMotionId;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,76 @@
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include "motionmanagewidget.h"
|
||||||
|
#include "motioneditwidget.h"
|
||||||
|
#include "theme.h"
|
||||||
|
#include "infolabel.h"
|
||||||
|
|
||||||
|
MotionManageWidget::MotionManageWidget(const SkeletonDocument *document, QWidget *parent) :
|
||||||
|
QWidget(parent),
|
||||||
|
m_document(document)
|
||||||
|
{
|
||||||
|
QPushButton *addMotionButton = new QPushButton(Theme::awesome()->icon(fa::plus), tr("Add Motion..."));
|
||||||
|
addMotionButton->hide();
|
||||||
|
|
||||||
|
connect(addMotionButton, &QPushButton::clicked, this, &MotionManageWidget::showAddMotionDialog);
|
||||||
|
|
||||||
|
QHBoxLayout *toolsLayout = new QHBoxLayout;
|
||||||
|
toolsLayout->addWidget(addMotionButton);
|
||||||
|
|
||||||
|
m_motionListWidget = new MotionListWidget(document);
|
||||||
|
connect(m_motionListWidget, &MotionListWidget::modifyMotion, this, &MotionManageWidget::showMotionDialog);
|
||||||
|
|
||||||
|
InfoLabel *infoLabel = new InfoLabel;
|
||||||
|
infoLabel->setText(tr("Missing Rig"));
|
||||||
|
infoLabel->show();
|
||||||
|
|
||||||
|
connect(m_document, &SkeletonDocument::resultRigChanged, this, [=]() {
|
||||||
|
if (m_document->currentRigSucceed()) {
|
||||||
|
infoLabel->hide();
|
||||||
|
addMotionButton->show();
|
||||||
|
} else {
|
||||||
|
infoLabel->show();
|
||||||
|
addMotionButton->hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||||
|
mainLayout->addWidget(infoLabel);
|
||||||
|
mainLayout->addLayout(toolsLayout);
|
||||||
|
mainLayout->addWidget(m_motionListWidget);
|
||||||
|
|
||||||
|
setLayout(mainLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize MotionManageWidget::sizeHint() const
|
||||||
|
{
|
||||||
|
return QSize(Theme::sidebarPreferredWidth, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionManageWidget::showAddMotionDialog()
|
||||||
|
{
|
||||||
|
showMotionDialog(QUuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionManageWidget::showMotionDialog(QUuid motionId)
|
||||||
|
{
|
||||||
|
MotionEditWidget *motionEditWidget = new MotionEditWidget(m_document);
|
||||||
|
motionEditWidget->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
if (!motionId.isNull()) {
|
||||||
|
const SkeletonMotion *motion = m_document->findMotion(motionId);
|
||||||
|
if (nullptr != motion) {
|
||||||
|
motionEditWidget->setEditMotionId(motionId);
|
||||||
|
motionEditWidget->setEditMotionName(motion->name);
|
||||||
|
motionEditWidget->setEditMotionControlNodes(motion->controlNodes);
|
||||||
|
motionEditWidget->setEditMotionKeyframes(motion->keyframes);
|
||||||
|
motionEditWidget->clearUnsaveState();
|
||||||
|
motionEditWidget->generatePreviews();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
motionEditWidget->show();
|
||||||
|
connect(motionEditWidget, &QDialog::destroyed, [=]() {
|
||||||
|
emit unregisterDialog((QWidget *)motionEditWidget);
|
||||||
|
});
|
||||||
|
emit registerDialog((QWidget *)motionEditWidget);
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef MOTION_MANAGE_WIDGET_H
|
||||||
|
#define MOTION_MANAGE_WIDGET_H
|
||||||
|
#include <QWidget>
|
||||||
|
#include "skeletondocument.h"
|
||||||
|
#include "motionlistwidget.h"
|
||||||
|
|
||||||
|
class MotionManageWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
signals:
|
||||||
|
void registerDialog(QWidget *widget);
|
||||||
|
void unregisterDialog(QWidget *widget);
|
||||||
|
public:
|
||||||
|
MotionManageWidget(const SkeletonDocument *document, QWidget *parent=nullptr);
|
||||||
|
protected:
|
||||||
|
virtual QSize sizeHint() const;
|
||||||
|
public slots:
|
||||||
|
void showAddMotionDialog();
|
||||||
|
void showMotionDialog(QUuid motionId);
|
||||||
|
private:
|
||||||
|
const SkeletonDocument *m_document = nullptr;
|
||||||
|
MotionListWidget *m_motionListWidget = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,153 @@
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#include "motionpreviewsgenerator.h"
|
||||||
|
#include "tetrapodposer.h"
|
||||||
|
#include "posemeshcreator.h"
|
||||||
|
|
||||||
|
MotionPreviewsGenerator::MotionPreviewsGenerator(const std::vector<AutoRiggerBone> *rigBones,
|
||||||
|
const std::map<int, AutoRiggerVertexWeights> *rigWeights,
|
||||||
|
const MeshResultContext &meshResultContext) :
|
||||||
|
m_rigBones(*rigBones),
|
||||||
|
m_rigWeights(*rigWeights),
|
||||||
|
m_meshResultContext(meshResultContext)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionPreviewsGenerator::~MotionPreviewsGenerator()
|
||||||
|
{
|
||||||
|
for (auto &item: m_resultPreviewMeshs) {
|
||||||
|
for (auto &subItem: item.second) {
|
||||||
|
delete subItem.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionPreviewsGenerator::addPoseToLibrary(const QUuid &poseId, const std::map<QString, std::map<QString, QString>> ¶meters)
|
||||||
|
{
|
||||||
|
m_poses[poseId] = parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionPreviewsGenerator::addMotionToLibrary(const QUuid &motionId, const std::vector<HermiteControlNode> &controlNodes,
|
||||||
|
const std::vector<std::pair<float, QUuid>> &keyframes)
|
||||||
|
{
|
||||||
|
m_motions[motionId] = {controlNodes, keyframes};
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionPreviewsGenerator::addPreviewRequirement(const QUuid &motionId)
|
||||||
|
{
|
||||||
|
m_requiredMotionIds.insert(motionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::set<QUuid> &MotionPreviewsGenerator::requiredMotionIds()
|
||||||
|
{
|
||||||
|
return m_requiredMotionIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::set<QUuid> &MotionPreviewsGenerator::generatedMotionIds()
|
||||||
|
{
|
||||||
|
return m_generatedMotionIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionPreviewsGenerator::generateForMotion(const QUuid &motionId)
|
||||||
|
{
|
||||||
|
auto findMotionResult = m_motions.find(motionId);
|
||||||
|
if (findMotionResult == m_motions.end())
|
||||||
|
return;
|
||||||
|
const std::vector<HermiteControlNode> &controlNodes = findMotionResult->second.first;
|
||||||
|
std::vector<std::pair<float, QUuid>> keyframes = findMotionResult->second.second;
|
||||||
|
if (keyframes.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto firstFrame = keyframes[0];
|
||||||
|
auto lastFrame = keyframes[keyframes.size() - 1];
|
||||||
|
|
||||||
|
if (keyframes[0].first > 0) {
|
||||||
|
// Insert the last keyframe as start frame
|
||||||
|
keyframes.insert(keyframes.begin(), {0, lastFrame.second});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyframes[keyframes.size() - 1].first < 1) {
|
||||||
|
// Insert the first keyframe as stop frame
|
||||||
|
keyframes.push_back({1.0, firstFrame.second});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::map<QString, std::map<QString, QString>>> keyframesParameters;
|
||||||
|
for (const auto &item: keyframes) {
|
||||||
|
const auto &poseId = item.second;
|
||||||
|
keyframesParameters.push_back(m_poses[poseId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto findLboundKeyframes = [=, &keyframes](float knot) {
|
||||||
|
for (decltype(keyframes.size()) i = 0; i < keyframes.size(); i++) {
|
||||||
|
//qDebug() << "Compare knot:" << knot << "keyframe[" << i << "] knot:" << keyframes[i].first;
|
||||||
|
if (knot >= keyframes[i].first && i + 1 < keyframes.size() && knot <= keyframes[i + 1].first)
|
||||||
|
return (int)i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
TetrapodPoser *poser = new TetrapodPoser(m_rigBones);
|
||||||
|
float interval = 1.0 / m_fps;
|
||||||
|
float lastKnot = 0;
|
||||||
|
for (float knot = 0; knot <= 1; knot += interval) {
|
||||||
|
int firstKeyframeIndex = findLboundKeyframes(knot);
|
||||||
|
if (-1 == firstKeyframeIndex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
poser->parameters() = keyframesParameters[firstKeyframeIndex];
|
||||||
|
poser->commit();
|
||||||
|
auto firstKeyframeJointNodeTree = poser->resultJointNodeTree();
|
||||||
|
poser->reset();
|
||||||
|
poser->parameters() = keyframesParameters[firstKeyframeIndex + 1];
|
||||||
|
poser->commit();
|
||||||
|
auto secondKeyframeJointNodeTree = poser->resultJointNodeTree();
|
||||||
|
float firstKeyframeKnot = keyframes[firstKeyframeIndex].first;
|
||||||
|
float secondKeyframeKnot = keyframes[firstKeyframeIndex + 1].first;
|
||||||
|
QVector2D firstKeyframePosition = calculateHermiteInterpolation(controlNodes, firstKeyframeKnot);
|
||||||
|
QVector2D secondKeyframePosition = calculateHermiteInterpolation(controlNodes, secondKeyframeKnot);
|
||||||
|
QVector2D currentFramePosition = calculateHermiteInterpolation(controlNodes, knot);
|
||||||
|
float length = secondKeyframePosition.y() - firstKeyframePosition.y();
|
||||||
|
if (qFuzzyIsNull(length))
|
||||||
|
continue;
|
||||||
|
float t = (currentFramePosition.y() - firstKeyframePosition.y()) / length;
|
||||||
|
auto resultJointNodeTree = JointNodeTree::slerp(firstKeyframeJointNodeTree, secondKeyframeJointNodeTree, t);
|
||||||
|
|
||||||
|
PoseMeshCreator *poseMeshCreator = new PoseMeshCreator(resultJointNodeTree.nodes(), m_meshResultContext, m_rigWeights);
|
||||||
|
poseMeshCreator->createMesh();
|
||||||
|
m_resultPreviewMeshs[motionId].push_back({(knot - lastKnot) * 2, poseMeshCreator->takeResultMesh()});
|
||||||
|
lastKnot = knot;
|
||||||
|
delete poseMeshCreator;
|
||||||
|
}
|
||||||
|
delete poser;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<float, MeshLoader *>> MotionPreviewsGenerator::takeResultPreviewMeshs(const QUuid &motionId)
|
||||||
|
{
|
||||||
|
auto findResult = m_resultPreviewMeshs.find(motionId);
|
||||||
|
if (findResult == m_resultPreviewMeshs.end())
|
||||||
|
return {};
|
||||||
|
auto result = findResult->second;
|
||||||
|
m_resultPreviewMeshs.erase(findResult);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionPreviewsGenerator::generate()
|
||||||
|
{
|
||||||
|
for (const auto &motionId: m_requiredMotionIds) {
|
||||||
|
generateForMotion(motionId);
|
||||||
|
m_generatedMotionIds.insert(motionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionPreviewsGenerator::process()
|
||||||
|
{
|
||||||
|
QElapsedTimer countTimeConsumed;
|
||||||
|
countTimeConsumed.start();
|
||||||
|
|
||||||
|
generate();
|
||||||
|
|
||||||
|
qDebug() << "The motion previews generation took" << countTimeConsumed.elapsed() << "milliseconds";
|
||||||
|
|
||||||
|
this->moveToThread(QGuiApplication::instance()->thread());
|
||||||
|
emit finished();
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
#ifndef MOTION_PREVIEWS_GENERATOR_H
|
||||||
|
#define MOTION_PREVIEWS_GENERATOR_H
|
||||||
|
#include <QObject>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include "meshloader.h"
|
||||||
|
#include "autorigger.h"
|
||||||
|
#include "curveutil.h"
|
||||||
|
#include "jointnodetree.h"
|
||||||
|
|
||||||
|
class MotionPreviewsGenerator : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
MotionPreviewsGenerator(const std::vector<AutoRiggerBone> *rigBones,
|
||||||
|
const std::map<int, AutoRiggerVertexWeights> *rigWeights,
|
||||||
|
const MeshResultContext &meshResultContext);
|
||||||
|
~MotionPreviewsGenerator();
|
||||||
|
void addPoseToLibrary(const QUuid &poseId, const std::map<QString, std::map<QString, QString>> ¶meters);
|
||||||
|
void addMotionToLibrary(const QUuid &motionId, const std::vector<HermiteControlNode> &controlNodes,
|
||||||
|
const std::vector<std::pair<float, QUuid>> &keyframes);
|
||||||
|
void addPreviewRequirement(const QUuid &motionId);
|
||||||
|
std::vector<std::pair<float, MeshLoader *>> takeResultPreviewMeshs(const QUuid &motionId);
|
||||||
|
const std::set<QUuid> &requiredMotionIds();
|
||||||
|
const std::set<QUuid> &generatedMotionIds();
|
||||||
|
void generateForMotion(const QUuid &motionId);
|
||||||
|
void generate();
|
||||||
|
signals:
|
||||||
|
void finished();
|
||||||
|
public slots:
|
||||||
|
void process();
|
||||||
|
private:
|
||||||
|
std::vector<AutoRiggerBone> m_rigBones;
|
||||||
|
std::map<int, AutoRiggerVertexWeights> m_rigWeights;
|
||||||
|
MeshResultContext m_meshResultContext;
|
||||||
|
std::map<QUuid, std::map<QString, std::map<QString, QString>>> m_poses;
|
||||||
|
std::map<QUuid, std::pair<std::vector<HermiteControlNode>, std::vector<std::pair<float, QUuid>>>> m_motions;
|
||||||
|
std::set<QUuid> m_requiredMotionIds;
|
||||||
|
std::set<QUuid> m_generatedMotionIds;
|
||||||
|
std::map<QUuid, std::vector<std::pair<float, MeshLoader *>>> m_resultPreviewMeshs;
|
||||||
|
int m_fps = 24;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,104 @@
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include "motionwidget.h"
|
||||||
|
|
||||||
|
MotionWidget::MotionWidget(const SkeletonDocument *document, QUuid motionId) :
|
||||||
|
m_motionId(motionId),
|
||||||
|
m_document(document)
|
||||||
|
{
|
||||||
|
setObjectName("MotionFrame");
|
||||||
|
|
||||||
|
m_previewWidget = new InterpolationGraphicsWidget(this);
|
||||||
|
m_previewWidget->setFixedSize(Theme::motionPreviewImageSize, Theme::motionPreviewImageSize);
|
||||||
|
m_previewWidget->setPreviewOnly(true);
|
||||||
|
|
||||||
|
m_nameLabel = new QLabel;
|
||||||
|
m_nameLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
m_nameLabel->setStyleSheet("background: qlineargradient(x1:0.5 y1:-15.5, x2:0.5 y2:1, stop:0 " + Theme::white.name() + ", stop:1 #252525);");
|
||||||
|
|
||||||
|
QFont nameFont;
|
||||||
|
nameFont.setWeight(QFont::Light);
|
||||||
|
nameFont.setPixelSize(9);
|
||||||
|
nameFont.setBold(false);
|
||||||
|
m_nameLabel->setFont(nameFont);
|
||||||
|
|
||||||
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||||
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
mainLayout->addStretch();
|
||||||
|
mainLayout->addWidget(m_nameLabel);
|
||||||
|
|
||||||
|
setLayout(mainLayout);
|
||||||
|
|
||||||
|
setFixedSize(Theme::motionPreviewImageSize, MotionWidget::preferredHeight());
|
||||||
|
|
||||||
|
connect(document, &SkeletonDocument::motionNameChanged, this, &MotionWidget::updateName);
|
||||||
|
connect(document, &SkeletonDocument::motionControlNodesChanged, this, &MotionWidget::updatePreview);
|
||||||
|
connect(document, &SkeletonDocument::motionKeyframesChanged, this, &MotionWidget::updatePreview);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionWidget::resizeEvent(QResizeEvent *event)
|
||||||
|
{
|
||||||
|
QWidget::resizeEvent(event);
|
||||||
|
m_previewWidget->move((width() - Theme::motionPreviewImageSize) / 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int MotionWidget::preferredHeight()
|
||||||
|
{
|
||||||
|
return Theme::motionPreviewImageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionWidget::reload()
|
||||||
|
{
|
||||||
|
updatePreview();
|
||||||
|
updateName();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionWidget::updatePreview()
|
||||||
|
{
|
||||||
|
const SkeletonMotion *motion = m_document->findMotion(m_motionId);
|
||||||
|
if (!motion) {
|
||||||
|
qDebug() << "Motion not found:" << m_motionId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::vector<std::pair<float, QString>> keyframesForGraphicsView;
|
||||||
|
for (const auto &frame: motion->keyframes) {
|
||||||
|
QString poseName;
|
||||||
|
const SkeletonPose *pose = m_document->findPose(frame.second);
|
||||||
|
if (nullptr == pose) {
|
||||||
|
qDebug() << "Find pose failed:" << frame.second;
|
||||||
|
} else {
|
||||||
|
poseName = pose->name;
|
||||||
|
}
|
||||||
|
keyframesForGraphicsView.push_back({frame.first, poseName});
|
||||||
|
}
|
||||||
|
m_previewWidget->setControlNodes(motion->controlNodes);
|
||||||
|
m_previewWidget->setKeyframes(keyframesForGraphicsView);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionWidget::updateName()
|
||||||
|
{
|
||||||
|
const SkeletonMotion *motion = m_document->findMotion(m_motionId);
|
||||||
|
if (!motion) {
|
||||||
|
qDebug() << "Motion not found:" << m_motionId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_nameLabel->setText(motion->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionWidget::updateCheckedState(bool checked)
|
||||||
|
{
|
||||||
|
if (checked)
|
||||||
|
setStyleSheet("#MotionFrame {border: 1px solid " + Theme::red.name() + ";}");
|
||||||
|
else
|
||||||
|
setStyleSheet("#MotionFrame {border: 1px solid transparent;}");
|
||||||
|
}
|
||||||
|
|
||||||
|
InterpolationGraphicsWidget *MotionWidget::previewWidget()
|
||||||
|
{
|
||||||
|
return m_previewWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MotionWidget::mouseDoubleClickEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
QFrame::mouseDoubleClickEvent(event);
|
||||||
|
emit modifyMotion(m_motionId);
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
#ifndef MOTION_WIDGET_H
|
||||||
|
#define MOTION_WIDGET_H
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QIcon>
|
||||||
|
#include "skeletondocument.h"
|
||||||
|
#include "interpolationgraphicswidget.h"
|
||||||
|
|
||||||
|
class MotionWidget : public QFrame
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
signals:
|
||||||
|
void modifyMotion(QUuid motionId);
|
||||||
|
public:
|
||||||
|
MotionWidget(const SkeletonDocument *document, QUuid motionId);
|
||||||
|
static int preferredHeight();
|
||||||
|
InterpolationGraphicsWidget *previewWidget();
|
||||||
|
protected:
|
||||||
|
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||||
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
|
public slots:
|
||||||
|
void reload();
|
||||||
|
void updatePreview();
|
||||||
|
void updateName();
|
||||||
|
void updateCheckedState(bool checked);
|
||||||
|
private:
|
||||||
|
QUuid m_motionId;
|
||||||
|
const SkeletonDocument *m_document = nullptr;
|
||||||
|
InterpolationGraphicsWidget *m_previewWidget = nullptr;
|
||||||
|
QLabel *m_nameLabel = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -55,6 +55,9 @@ void PoseListWidget::updatePoseSelectState(QUuid poseId, bool selected)
|
||||||
}
|
}
|
||||||
PoseWidget *poseWidget = (PoseWidget *)itemWidget(findItemResult->second.first, findItemResult->second.second);
|
PoseWidget *poseWidget = (PoseWidget *)itemWidget(findItemResult->second.first, findItemResult->second.second);
|
||||||
poseWidget->updateCheckedState(selected);
|
poseWidget->updateCheckedState(selected);
|
||||||
|
if (m_cornerButtonVisible) {
|
||||||
|
poseWidget->setCornerButtonVisible(selected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PoseListWidget::selectPose(QUuid poseId, bool multiple)
|
void PoseListWidget::selectPose(QUuid poseId, bool multiple)
|
||||||
|
@ -156,6 +159,9 @@ bool PoseListWidget::isPoseSelected(QUuid poseId)
|
||||||
|
|
||||||
void PoseListWidget::showContextMenu(const QPoint &pos)
|
void PoseListWidget::showContextMenu(const QPoint &pos)
|
||||||
{
|
{
|
||||||
|
if (!m_hasContextMenu)
|
||||||
|
return;
|
||||||
|
|
||||||
QMenu contextMenu(this);
|
QMenu contextMenu(this);
|
||||||
|
|
||||||
std::set<QUuid> unorderedPoseIds = m_selectedPoseIds;
|
std::set<QUuid> unorderedPoseIds = m_selectedPoseIds;
|
||||||
|
@ -244,6 +250,7 @@ void PoseListWidget::reload()
|
||||||
item->setData(col, Qt::UserRole, poseId.toString());
|
item->setData(col, Qt::UserRole, poseId.toString());
|
||||||
PoseWidget *widget = new PoseWidget(m_document, poseId);
|
PoseWidget *widget = new PoseWidget(m_document, poseId);
|
||||||
connect(widget, &PoseWidget::modifyPose, this, &PoseListWidget::modifyPose);
|
connect(widget, &PoseWidget::modifyPose, this, &PoseListWidget::modifyPose);
|
||||||
|
connect(widget, &PoseWidget::cornerButtonClicked, this, &PoseListWidget::cornerButtonClicked);
|
||||||
widget->previewWidget()->setGraphicsFunctions(this);
|
widget->previewWidget()->setGraphicsFunctions(this);
|
||||||
setItemWidget(item, col, widget);
|
setItemWidget(item, col, widget);
|
||||||
widget->reload();
|
widget->reload();
|
||||||
|
@ -254,6 +261,16 @@ void PoseListWidget::reload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PoseListWidget::setCornerButtonVisible(bool visible)
|
||||||
|
{
|
||||||
|
m_cornerButtonVisible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PoseListWidget::setHasContextMenu(bool hasContextMenu)
|
||||||
|
{
|
||||||
|
m_hasContextMenu = hasContextMenu;
|
||||||
|
}
|
||||||
|
|
||||||
void PoseListWidget::removeAllContent()
|
void PoseListWidget::removeAllContent()
|
||||||
{
|
{
|
||||||
m_itemMap.clear();
|
m_itemMap.clear();
|
||||||
|
|
|
@ -12,6 +12,7 @@ class PoseListWidget : public QTreeWidget, public SkeletonGraphicsFunctions
|
||||||
signals:
|
signals:
|
||||||
void removePose(QUuid poseId);
|
void removePose(QUuid poseId);
|
||||||
void modifyPose(QUuid poseId);
|
void modifyPose(QUuid poseId);
|
||||||
|
void cornerButtonClicked(QUuid poseId);
|
||||||
public:
|
public:
|
||||||
PoseListWidget(const SkeletonDocument *document, QWidget *parent=nullptr);
|
PoseListWidget(const SkeletonDocument *document, QWidget *parent=nullptr);
|
||||||
bool isPoseSelected(QUuid poseId);
|
bool isPoseSelected(QUuid poseId);
|
||||||
|
@ -22,15 +23,17 @@ public slots:
|
||||||
void showContextMenu(const QPoint &pos);
|
void showContextMenu(const QPoint &pos);
|
||||||
void selectPose(QUuid poseId, bool multiple=false);
|
void selectPose(QUuid poseId, bool multiple=false);
|
||||||
void copy();
|
void copy();
|
||||||
|
void setCornerButtonVisible(bool visible);
|
||||||
|
void setHasContextMenu(bool hasContextMenu);
|
||||||
protected:
|
protected:
|
||||||
void resizeEvent(QResizeEvent *event) override;
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
void mousePressEvent(QMouseEvent *event) override;
|
void mousePressEvent(QMouseEvent *event) override;
|
||||||
bool mouseMove(QMouseEvent *event);
|
bool mouseMove(QMouseEvent *event) override;
|
||||||
bool wheel(QWheelEvent *event);
|
bool wheel(QWheelEvent *event) override;
|
||||||
bool mouseRelease(QMouseEvent *event);
|
bool mouseRelease(QMouseEvent *event) override;
|
||||||
bool mousePress(QMouseEvent *event);
|
bool mousePress(QMouseEvent *event) override;
|
||||||
bool mouseDoubleClick(QMouseEvent *event);
|
bool mouseDoubleClick(QMouseEvent *event) override;
|
||||||
bool keyPress(QKeyEvent *event);
|
bool keyPress(QKeyEvent *event) override;
|
||||||
private:
|
private:
|
||||||
int calculateColumnCount();
|
int calculateColumnCount();
|
||||||
void updatePoseSelectState(QUuid poseId, bool selected);
|
void updatePoseSelectState(QUuid poseId, bool selected);
|
||||||
|
@ -39,6 +42,8 @@ private:
|
||||||
std::set<QUuid> m_selectedPoseIds;
|
std::set<QUuid> m_selectedPoseIds;
|
||||||
QUuid m_currentSelectedPoseId;
|
QUuid m_currentSelectedPoseId;
|
||||||
QUuid m_shiftStartPoseId;
|
QUuid m_shiftStartPoseId;
|
||||||
|
bool m_cornerButtonVisible = false;
|
||||||
|
bool m_hasContextMenu = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -4,12 +4,14 @@
|
||||||
#include "posemanagewidget.h"
|
#include "posemanagewidget.h"
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
#include "poseeditwidget.h"
|
#include "poseeditwidget.h"
|
||||||
|
#include "infolabel.h"
|
||||||
|
|
||||||
PoseManageWidget::PoseManageWidget(const SkeletonDocument *document, QWidget *parent) :
|
PoseManageWidget::PoseManageWidget(const SkeletonDocument *document, QWidget *parent) :
|
||||||
QWidget(parent),
|
QWidget(parent),
|
||||||
m_document(document)
|
m_document(document)
|
||||||
{
|
{
|
||||||
QPushButton *addPoseButton = new QPushButton(Theme::awesome()->icon(fa::plus), tr("Add Pose..."));
|
QPushButton *addPoseButton = new QPushButton(Theme::awesome()->icon(fa::plus), tr("Add Pose..."));
|
||||||
|
addPoseButton->hide();
|
||||||
|
|
||||||
connect(addPoseButton, &QPushButton::clicked, this, &PoseManageWidget::showAddPoseDialog);
|
connect(addPoseButton, &QPushButton::clicked, this, &PoseManageWidget::showAddPoseDialog);
|
||||||
|
|
||||||
|
@ -19,7 +21,22 @@ PoseManageWidget::PoseManageWidget(const SkeletonDocument *document, QWidget *pa
|
||||||
m_poseListWidget = new PoseListWidget(document);
|
m_poseListWidget = new PoseListWidget(document);
|
||||||
connect(m_poseListWidget, &PoseListWidget::modifyPose, this, &PoseManageWidget::showPoseDialog);
|
connect(m_poseListWidget, &PoseListWidget::modifyPose, this, &PoseManageWidget::showPoseDialog);
|
||||||
|
|
||||||
|
InfoLabel *infoLabel = new InfoLabel;
|
||||||
|
infoLabel->setText(tr("Missing Rig"));
|
||||||
|
infoLabel->show();
|
||||||
|
|
||||||
|
connect(m_document, &SkeletonDocument::resultRigChanged, this, [=]() {
|
||||||
|
if (m_document->currentRigSucceed()) {
|
||||||
|
infoLabel->hide();
|
||||||
|
addPoseButton->show();
|
||||||
|
} else {
|
||||||
|
infoLabel->show();
|
||||||
|
addPoseButton->hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||||
|
mainLayout->addWidget(infoLabel);
|
||||||
mainLayout->addLayout(toolsLayout);
|
mainLayout->addLayout(toolsLayout);
|
||||||
mainLayout->addWidget(m_poseListWidget);
|
mainLayout->addWidget(m_poseListWidget);
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
#include "posemeshcreator.h"
|
#include "posemeshcreator.h"
|
||||||
#include "skinnedmeshcreator.h"
|
#include "skinnedmeshcreator.h"
|
||||||
|
|
||||||
PoseMeshCreator::PoseMeshCreator(const Poser &poser,
|
PoseMeshCreator::PoseMeshCreator(const std::vector<JointNode> &resultNodes,
|
||||||
const MeshResultContext &meshResultContext,
|
const MeshResultContext &meshResultContext,
|
||||||
const std::map<int, AutoRiggerVertexWeights> &resultWeights) :
|
const std::map<int, AutoRiggerVertexWeights> &resultWeights) :
|
||||||
m_resultNodes(poser.resultNodes()),
|
m_resultNodes(resultNodes),
|
||||||
m_meshResultContext(meshResultContext),
|
m_meshResultContext(meshResultContext),
|
||||||
m_resultWeights(resultWeights)
|
m_resultWeights(resultWeights)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#ifndef POSE_MESH_CREATOR_H
|
#ifndef POSE_MESH_CREATOR_H
|
||||||
#define POSE_MESH_CREATOR_H
|
#define POSE_MESH_CREATOR_H
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include "poser.h"
|
|
||||||
#include "meshloader.h"
|
#include "meshloader.h"
|
||||||
#include "jointnodetree.h"
|
#include "jointnodetree.h"
|
||||||
#include "meshresultcontext.h"
|
#include "meshresultcontext.h"
|
||||||
|
@ -12,7 +11,7 @@ class PoseMeshCreator : public QObject
|
||||||
signals:
|
signals:
|
||||||
void finished();
|
void finished();
|
||||||
public:
|
public:
|
||||||
PoseMeshCreator(const Poser &poser,
|
PoseMeshCreator(const std::vector<JointNode> &resultNodes,
|
||||||
const MeshResultContext &meshResultContext,
|
const MeshResultContext &meshResultContext,
|
||||||
const std::map<int, AutoRiggerVertexWeights> &resultWeights);
|
const std::map<int, AutoRiggerVertexWeights> &resultWeights);
|
||||||
~PoseMeshCreator();
|
~PoseMeshCreator();
|
||||||
|
|
|
@ -26,7 +26,7 @@ bool PosePreviewManager::postUpdate(const Poser &poser,
|
||||||
qDebug() << "Pose mesh generating..";
|
qDebug() << "Pose mesh generating..";
|
||||||
|
|
||||||
QThread *thread = new QThread;
|
QThread *thread = new QThread;
|
||||||
m_poseMeshCreator = new PoseMeshCreator(poser, meshResultContext, resultWeights);
|
m_poseMeshCreator = new PoseMeshCreator(poser.resultNodes(), meshResultContext, resultWeights);
|
||||||
m_poseMeshCreator->moveToThread(thread);
|
m_poseMeshCreator->moveToThread(thread);
|
||||||
connect(thread, &QThread::started, m_poseMeshCreator, &PoseMeshCreator::process);
|
connect(thread, &QThread::started, m_poseMeshCreator, &PoseMeshCreator::process);
|
||||||
connect(m_poseMeshCreator, &PoseMeshCreator::finished, this, &PosePreviewManager::poseMeshReady);
|
connect(m_poseMeshCreator, &PoseMeshCreator::finished, this, &PosePreviewManager::poseMeshReady);
|
||||||
|
|
|
@ -48,7 +48,7 @@ void PosePreviewsGenerator::process()
|
||||||
poser->parameters() = pose.second;
|
poser->parameters() = pose.second;
|
||||||
poser->commit();
|
poser->commit();
|
||||||
|
|
||||||
PoseMeshCreator *poseMeshCreator = new PoseMeshCreator(*poser, *m_meshResultContext, m_rigWeights);
|
PoseMeshCreator *poseMeshCreator = new PoseMeshCreator(poser->resultNodes(), *m_meshResultContext, m_rigWeights);
|
||||||
poseMeshCreator->createMesh();
|
poseMeshCreator->createMesh();
|
||||||
m_previews[pose.first] = poseMeshCreator->takeResultMesh();
|
m_previews[pose.first] = poseMeshCreator->takeResultMesh();
|
||||||
delete poseMeshCreator;
|
delete poseMeshCreator;
|
||||||
|
|
|
@ -40,6 +40,11 @@ const std::vector<JointNode> &Poser::resultNodes() const
|
||||||
return m_jointNodeTree.nodes();
|
return m_jointNodeTree.nodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const JointNodeTree &Poser::resultJointNodeTree() const
|
||||||
|
{
|
||||||
|
return m_jointNodeTree;
|
||||||
|
}
|
||||||
|
|
||||||
void Poser::commit()
|
void Poser::commit()
|
||||||
{
|
{
|
||||||
m_jointNodeTree.recalculateTransformMatrices();
|
m_jointNodeTree.recalculateTransformMatrices();
|
||||||
|
|
|
@ -15,6 +15,7 @@ public:
|
||||||
int findBoneIndex(const QString &name);
|
int findBoneIndex(const QString &name);
|
||||||
const std::vector<AutoRiggerBone> &bones() const;
|
const std::vector<AutoRiggerBone> &bones() const;
|
||||||
const std::vector<JointNode> &resultNodes() const;
|
const std::vector<JointNode> &resultNodes() const;
|
||||||
|
const JointNodeTree &resultJointNodeTree() const;
|
||||||
std::map<QString, std::map<QString, QString>> ¶meters();
|
std::map<QString, std::map<QString, QString>> ¶meters();
|
||||||
virtual void commit();
|
virtual void commit();
|
||||||
void reset();
|
void reset();
|
||||||
|
|
|
@ -35,6 +35,20 @@ PoseWidget::PoseWidget(const SkeletonDocument *document, QUuid poseId) :
|
||||||
connect(document, &SkeletonDocument::posePreviewChanged, this, &PoseWidget::updatePreview);
|
connect(document, &SkeletonDocument::posePreviewChanged, this, &PoseWidget::updatePreview);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PoseWidget::setCornerButtonVisible(bool visible)
|
||||||
|
{
|
||||||
|
if (nullptr == m_cornerButton) {
|
||||||
|
m_cornerButton = new QPushButton(this);
|
||||||
|
m_cornerButton->move(Theme::posePreviewImageSize - Theme::miniIconSize - 2, 2);
|
||||||
|
Theme::initAwesomeMiniButton(m_cornerButton);
|
||||||
|
m_cornerButton->setText(QChar(fa::plussquare));
|
||||||
|
connect(m_cornerButton, &QPushButton::clicked, this, [=]() {
|
||||||
|
emit cornerButtonClicked(m_poseId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
m_cornerButton->setVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
void PoseWidget::resizeEvent(QResizeEvent *event)
|
void PoseWidget::resizeEvent(QResizeEvent *event)
|
||||||
{
|
{
|
||||||
QWidget::resizeEvent(event);
|
QWidget::resizeEvent(event);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#define POSE_WIDGET_H
|
#define POSE_WIDGET_H
|
||||||
#include <QFrame>
|
#include <QFrame>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
#include <QIcon>
|
||||||
#include "skeletondocument.h"
|
#include "skeletondocument.h"
|
||||||
#include "modelwidget.h"
|
#include "modelwidget.h"
|
||||||
|
|
||||||
|
@ -10,23 +11,26 @@ class PoseWidget : public QFrame
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
signals:
|
signals:
|
||||||
void modifyPose(QUuid poseId);
|
void modifyPose(QUuid poseId);
|
||||||
|
void cornerButtonClicked(QUuid poseId);
|
||||||
public:
|
public:
|
||||||
PoseWidget(const SkeletonDocument *document, QUuid poseId);
|
PoseWidget(const SkeletonDocument *document, QUuid poseId);
|
||||||
static int preferredHeight();
|
static int preferredHeight();
|
||||||
ModelWidget *previewWidget();
|
ModelWidget *previewWidget();
|
||||||
protected:
|
protected:
|
||||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||||
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
public slots:
|
public slots:
|
||||||
void reload();
|
void reload();
|
||||||
void updatePreview();
|
void updatePreview();
|
||||||
void updateName();
|
void updateName();
|
||||||
void updateCheckedState(bool checked);
|
void updateCheckedState(bool checked);
|
||||||
void resizeEvent(QResizeEvent *event) override;
|
void setCornerButtonVisible(bool visible);
|
||||||
private:
|
private:
|
||||||
QUuid m_poseId;
|
QUuid m_poseId;
|
||||||
const SkeletonDocument *m_document = nullptr;
|
const SkeletonDocument *m_document = nullptr;
|
||||||
ModelWidget *m_previewWidget = nullptr;
|
ModelWidget *m_previewWidget = nullptr;
|
||||||
QLabel *m_nameLabel = nullptr;
|
QLabel *m_nameLabel = nullptr;
|
||||||
|
QPushButton *m_cornerButton = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -46,6 +46,11 @@ MeshLoader *RigGenerator::takeResultMesh()
|
||||||
return resultMesh;
|
return resultMesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RigGenerator::isSucceed()
|
||||||
|
{
|
||||||
|
return m_isSucceed;
|
||||||
|
}
|
||||||
|
|
||||||
const std::vector<QString> &RigGenerator::missingMarkNames()
|
const std::vector<QString> &RigGenerator::missingMarkNames()
|
||||||
{
|
{
|
||||||
return m_missingMarkNames;
|
return m_missingMarkNames;
|
||||||
|
@ -95,9 +100,9 @@ void RigGenerator::process()
|
||||||
std::get<0>(marks.second) / std::get<1>(marks.second),
|
std::get<0>(marks.second) / std::get<1>(marks.second),
|
||||||
std::get<2>(marks.second));
|
std::get<2>(marks.second));
|
||||||
}
|
}
|
||||||
bool rigSucceed = m_autoRigger->rig();
|
m_isSucceed = m_autoRigger->rig();
|
||||||
|
|
||||||
if (rigSucceed) {
|
if (m_isSucceed) {
|
||||||
qDebug() << "Rig succeed";
|
qDebug() << "Rig succeed";
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Rig failed";
|
qDebug() << "Rig failed";
|
||||||
|
@ -111,7 +116,7 @@ void RigGenerator::process()
|
||||||
// Blend vertices colors according to bone weights
|
// Blend vertices colors according to bone weights
|
||||||
|
|
||||||
std::vector<QColor> inputVerticesColors(m_meshResultContext->vertices.size());
|
std::vector<QColor> inputVerticesColors(m_meshResultContext->vertices.size());
|
||||||
if (rigSucceed) {
|
if (m_isSucceed) {
|
||||||
const auto &resultWeights = m_autoRigger->resultWeights();
|
const auto &resultWeights = m_autoRigger->resultWeights();
|
||||||
const auto &resultBones = m_autoRigger->resultBones();
|
const auto &resultBones = m_autoRigger->resultBones();
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ public:
|
||||||
const std::vector<QString> &missingMarkNames();
|
const std::vector<QString> &missingMarkNames();
|
||||||
const std::vector<QString> &errorMarkNames();
|
const std::vector<QString> &errorMarkNames();
|
||||||
MeshResultContext *takeMeshResultContext();
|
MeshResultContext *takeMeshResultContext();
|
||||||
|
bool isSucceed();
|
||||||
signals:
|
signals:
|
||||||
void finished();
|
void finished();
|
||||||
public slots:
|
public slots:
|
||||||
|
@ -31,6 +32,7 @@ private:
|
||||||
std::map<int, AutoRiggerVertexWeights> *m_resultWeights = nullptr;
|
std::map<int, AutoRiggerVertexWeights> *m_resultWeights = nullptr;
|
||||||
std::vector<QString> m_missingMarkNames;
|
std::vector<QString> m_missingMarkNames;
|
||||||
std::vector<QString> m_errorMarkNames;
|
std::vector<QString> m_errorMarkNames;
|
||||||
|
bool m_isSucceed = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -53,7 +53,8 @@ SkeletonDocument::SkeletonDocument() :
|
||||||
m_resultRigWeights(nullptr),
|
m_resultRigWeights(nullptr),
|
||||||
m_isRigObsolete(false),
|
m_isRigObsolete(false),
|
||||||
m_riggedResultContext(new MeshResultContext),
|
m_riggedResultContext(new MeshResultContext),
|
||||||
m_posePreviewsGenerator(nullptr)
|
m_posePreviewsGenerator(nullptr),
|
||||||
|
m_currentRigSucceed(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,6 +327,80 @@ void SkeletonDocument::addPose(QString name, std::map<QString, std::map<QString,
|
||||||
emit optionsChanged();
|
emit optionsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SkeletonDocument::addMotion(QString name, std::vector<HermiteControlNode> controlNodes, std::vector<std::pair<float, QUuid>> keyframes)
|
||||||
|
{
|
||||||
|
QUuid newMotionId = QUuid::createUuid();
|
||||||
|
auto &motion = motionMap[newMotionId];
|
||||||
|
motion.id = newMotionId;
|
||||||
|
|
||||||
|
motion.name = name;
|
||||||
|
motion.controlNodes = controlNodes;
|
||||||
|
motion.keyframes = keyframes;
|
||||||
|
motion.dirty = true;
|
||||||
|
|
||||||
|
motionIdList.push_back(newMotionId);
|
||||||
|
|
||||||
|
emit motionAdded(newMotionId);
|
||||||
|
emit motionListChanged();
|
||||||
|
emit optionsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocument::removeMotion(QUuid motionId)
|
||||||
|
{
|
||||||
|
auto findMotionResult = motionMap.find(motionId);
|
||||||
|
if (findMotionResult == motionMap.end()) {
|
||||||
|
qDebug() << "Remove a none exist motion:" << motionId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
motionIdList.erase(std::remove(motionIdList.begin(), motionIdList.end(), motionId), motionIdList.end());
|
||||||
|
motionMap.erase(findMotionResult);
|
||||||
|
|
||||||
|
emit motionListChanged();
|
||||||
|
emit motionRemoved(motionId);
|
||||||
|
emit optionsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocument::setMotionControlNodes(QUuid motionId, std::vector<HermiteControlNode> controlNodes)
|
||||||
|
{
|
||||||
|
auto findMotionResult = motionMap.find(motionId);
|
||||||
|
if (findMotionResult == motionMap.end()) {
|
||||||
|
qDebug() << "Find motion failed:" << motionId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
findMotionResult->second.controlNodes = controlNodes;
|
||||||
|
findMotionResult->second.dirty = true;
|
||||||
|
emit motionControlNodesChanged(motionId);
|
||||||
|
emit optionsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocument::setMotionKeyframes(QUuid motionId, std::vector<std::pair<float, QUuid>> keyframes)
|
||||||
|
{
|
||||||
|
auto findMotionResult = motionMap.find(motionId);
|
||||||
|
if (findMotionResult == motionMap.end()) {
|
||||||
|
qDebug() << "Find motion failed:" << motionId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
findMotionResult->second.keyframes = keyframes;
|
||||||
|
findMotionResult->second.dirty = true;
|
||||||
|
emit motionKeyframesChanged(motionId);
|
||||||
|
emit optionsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocument::renameMotion(QUuid motionId, QString name)
|
||||||
|
{
|
||||||
|
auto findMotionResult = motionMap.find(motionId);
|
||||||
|
if (findMotionResult == motionMap.end()) {
|
||||||
|
qDebug() << "Find motion failed:" << motionId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (findMotionResult->second.name == name)
|
||||||
|
return;
|
||||||
|
|
||||||
|
findMotionResult->second.name = name;
|
||||||
|
emit motionNameChanged(motionId);
|
||||||
|
emit optionsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
void SkeletonDocument::removePose(QUuid poseId)
|
void SkeletonDocument::removePose(QUuid poseId)
|
||||||
{
|
{
|
||||||
auto findPoseResult = poseMap.find(poseId);
|
auto findPoseResult = poseMap.find(poseId);
|
||||||
|
@ -517,6 +592,14 @@ const SkeletonPose *SkeletonDocument::findPose(QUuid poseId) const
|
||||||
return &it->second;
|
return &it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SkeletonMotion *SkeletonDocument::findMotion(QUuid motionId) const
|
||||||
|
{
|
||||||
|
auto it = motionMap.find(motionId);
|
||||||
|
if (it == motionMap.end())
|
||||||
|
return nullptr;
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
void SkeletonDocument::scaleNodeByAddRadius(QUuid nodeId, float amount)
|
void SkeletonDocument::scaleNodeByAddRadius(QUuid nodeId, float amount)
|
||||||
{
|
{
|
||||||
auto it = nodeMap.find(nodeId);
|
auto it = nodeMap.find(nodeId);
|
||||||
|
@ -747,7 +830,7 @@ void SkeletonDocument::markAllDirty()
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds,
|
void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds,
|
||||||
SkeletonDocumentToSnapshotFor forWhat, const std::set<QUuid> &limitPoseIds) const
|
SkeletonDocumentToSnapshotFor forWhat, const std::set<QUuid> &limitPoseIds, const std::set<QUuid> &limitMotionIds) const
|
||||||
{
|
{
|
||||||
if (SkeletonDocumentToSnapshotFor::Document == forWhat ||
|
if (SkeletonDocumentToSnapshotFor::Document == forWhat ||
|
||||||
SkeletonDocumentToSnapshotFor::Nodes == forWhat) {
|
SkeletonDocumentToSnapshotFor::Nodes == forWhat) {
|
||||||
|
@ -883,6 +966,43 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUu
|
||||||
snapshot->poses.push_back(std::make_pair(pose, poseIt.second.parameters));
|
snapshot->poses.push_back(std::make_pair(pose, poseIt.second.parameters));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (SkeletonDocumentToSnapshotFor::Document == forWhat ||
|
||||||
|
SkeletonDocumentToSnapshotFor::Motions == forWhat) {
|
||||||
|
for (const auto &motionId: motionIdList) {
|
||||||
|
if (!limitMotionIds.empty() && limitMotionIds.find(motionId) == limitMotionIds.end())
|
||||||
|
continue;
|
||||||
|
auto findMotionResult = motionMap.find(motionId);
|
||||||
|
if (findMotionResult == motionMap.end()) {
|
||||||
|
qDebug() << "Find motion failed:" << motionId;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto &motionIt = *findMotionResult;
|
||||||
|
std::map<QString, QString> motion;
|
||||||
|
motion["id"] = motionIt.second.id.toString();
|
||||||
|
if (!motionIt.second.name.isEmpty())
|
||||||
|
motion["name"] = motionIt.second.name;
|
||||||
|
std::vector<std::map<QString, QString>> controlNodesAttributes;
|
||||||
|
std::vector<std::map<QString, QString>> keyframesAttributes;
|
||||||
|
for (const auto &controlNode: motionIt.second.controlNodes) {
|
||||||
|
std::map<QString, QString> attributes;
|
||||||
|
attributes["x"] = QString::number(controlNode.position.x());
|
||||||
|
attributes["y"] = QString::number(controlNode.position.y());
|
||||||
|
attributes["inTangentX"] = QString::number(controlNode.inTangent.x());
|
||||||
|
attributes["inTangentY"] = QString::number(controlNode.inTangent.y());
|
||||||
|
attributes["outTangentX"] = QString::number(controlNode.outTangent.x());
|
||||||
|
attributes["outTangentY"] = QString::number(controlNode.outTangent.y());
|
||||||
|
controlNodesAttributes.push_back(attributes);
|
||||||
|
}
|
||||||
|
for (const auto &keyframe: motionIt.second.keyframes) {
|
||||||
|
std::map<QString, QString> attributes;
|
||||||
|
attributes["knot"] = QString::number(keyframe.first);
|
||||||
|
attributes["linkDataType"] = "poseId";
|
||||||
|
attributes["linkData"] = keyframe.second.toString();
|
||||||
|
keyframesAttributes.push_back(attributes);
|
||||||
|
}
|
||||||
|
snapshot->motions.push_back(std::make_tuple(motion, controlNodesAttributes, keyframesAttributes));
|
||||||
|
}
|
||||||
|
}
|
||||||
if (SkeletonDocumentToSnapshotFor::Document == forWhat) {
|
if (SkeletonDocumentToSnapshotFor::Document == forWhat) {
|
||||||
std::map<QString, QString> canvas;
|
std::map<QString, QString> canvas;
|
||||||
canvas["originX"] = QString::number(originX);
|
canvas["originX"] = QString::number(originX);
|
||||||
|
@ -1065,6 +1185,44 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
|
||||||
poseIdList.push_back(newPoseId);
|
poseIdList.push_back(newPoseId);
|
||||||
emit poseAdded(newPoseId);
|
emit poseAdded(newPoseId);
|
||||||
}
|
}
|
||||||
|
for (const auto &motionIt: snapshot.motions) {
|
||||||
|
QUuid newMotionId = QUuid::createUuid();
|
||||||
|
auto &newMotion = motionMap[newMotionId];
|
||||||
|
newMotion.id = newMotionId;
|
||||||
|
const auto &motionAttributes = std::get<0>(motionIt);
|
||||||
|
newMotion.name = valueOfKeyInMapOrEmpty(motionAttributes, "name");
|
||||||
|
for (const auto &attributes: std::get<1>(motionIt)) {
|
||||||
|
float x = valueOfKeyInMapOrEmpty(attributes, "x").toFloat();
|
||||||
|
float y = valueOfKeyInMapOrEmpty(attributes, "y").toFloat();
|
||||||
|
float inTangentX = valueOfKeyInMapOrEmpty(attributes, "inTangentX").toFloat();
|
||||||
|
float inTangentY = valueOfKeyInMapOrEmpty(attributes, "inTangentY").toFloat();
|
||||||
|
float outTangentX = valueOfKeyInMapOrEmpty(attributes, "outTangentX").toFloat();
|
||||||
|
float outTangentY = valueOfKeyInMapOrEmpty(attributes, "outTangentY").toFloat();
|
||||||
|
HermiteControlNode hermite(QVector2D(x, y),
|
||||||
|
QVector2D(inTangentX, inTangentY), QVector2D(outTangentX, outTangentY));
|
||||||
|
newMotion.controlNodes.push_back(hermite);
|
||||||
|
}
|
||||||
|
for (const auto &attributes: std::get<2>(motionIt)) {
|
||||||
|
float knot = valueOfKeyInMapOrEmpty(attributes, "knot").toFloat();
|
||||||
|
QString linkDataType = valueOfKeyInMapOrEmpty(attributes, "linkDataType");
|
||||||
|
if ("poseId" != linkDataType) {
|
||||||
|
qDebug() << "Encounter unknown linkDataType:" << linkDataType;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QUuid linkToPoseId = QUuid(valueOfKeyInMapOrEmpty(attributes, "linkData"));
|
||||||
|
auto findPoseResult = poseMap.find(linkToPoseId);
|
||||||
|
if (findPoseResult != poseMap.end()) {
|
||||||
|
newMotion.keyframes.push_back({knot, findPoseResult->first});
|
||||||
|
} else {
|
||||||
|
auto findInOldNewIdMapResult = oldNewIdMap.find(linkToPoseId);
|
||||||
|
if (findInOldNewIdMapResult != oldNewIdMap.end())
|
||||||
|
newMotion.keyframes.push_back({knot, findInOldNewIdMapResult->second});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(motionAttributes, "id"))] = newMotionId;
|
||||||
|
motionIdList.push_back(newMotionId);
|
||||||
|
emit motionAdded(newMotionId);
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto &nodeIt: newAddedNodeIds) {
|
for (const auto &nodeIt: newAddedNodeIds) {
|
||||||
emit nodeAdded(nodeIt);
|
emit nodeAdded(nodeIt);
|
||||||
|
@ -1097,6 +1255,8 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
|
||||||
|
|
||||||
if (!snapshot.poses.empty())
|
if (!snapshot.poses.empty())
|
||||||
emit poseListChanged();
|
emit poseListChanged();
|
||||||
|
if (!snapshot.motions.empty())
|
||||||
|
emit motionListChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkeletonDocument::reset()
|
void SkeletonDocument::reset()
|
||||||
|
@ -1111,6 +1271,8 @@ void SkeletonDocument::reset()
|
||||||
componentMap.clear();
|
componentMap.clear();
|
||||||
poseMap.clear();
|
poseMap.clear();
|
||||||
poseIdList.clear();
|
poseIdList.clear();
|
||||||
|
motionMap.clear();
|
||||||
|
motionIdList.clear();
|
||||||
rootComponent = SkeletonComponent();
|
rootComponent = SkeletonComponent();
|
||||||
emit cleanup();
|
emit cleanup();
|
||||||
emit skeletonChanged();
|
emit skeletonChanged();
|
||||||
|
@ -2076,7 +2238,7 @@ bool SkeletonDocument::hasPastableNodesInClipboard() const
|
||||||
const QClipboard *clipboard = QApplication::clipboard();
|
const QClipboard *clipboard = QApplication::clipboard();
|
||||||
const QMimeData *mimeData = clipboard->mimeData();
|
const QMimeData *mimeData = clipboard->mimeData();
|
||||||
if (mimeData->hasText()) {
|
if (mimeData->hasText()) {
|
||||||
if (-1 != mimeData->text().left(1000).indexOf("<node "))
|
if (-1 != mimeData->text().indexOf("<node "))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -2087,7 +2249,18 @@ bool SkeletonDocument::hasPastablePosesInClipboard() const
|
||||||
const QClipboard *clipboard = QApplication::clipboard();
|
const QClipboard *clipboard = QApplication::clipboard();
|
||||||
const QMimeData *mimeData = clipboard->mimeData();
|
const QMimeData *mimeData = clipboard->mimeData();
|
||||||
if (mimeData->hasText()) {
|
if (mimeData->hasText()) {
|
||||||
if (-1 != mimeData->text().right(1000).indexOf("<pose "))
|
if (-1 != mimeData->text().indexOf("<pose "))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SkeletonDocument::hasPastableMotionsInClipboard() const
|
||||||
|
{
|
||||||
|
const QClipboard *clipboard = QApplication::clipboard();
|
||||||
|
const QMimeData *mimeData = clipboard->mimeData();
|
||||||
|
if (mimeData->hasText()) {
|
||||||
|
if (-1 != mimeData->text().indexOf("<motion "))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -2378,6 +2551,8 @@ void SkeletonDocument::generateRig()
|
||||||
|
|
||||||
void SkeletonDocument::rigReady()
|
void SkeletonDocument::rigReady()
|
||||||
{
|
{
|
||||||
|
m_currentRigSucceed = m_rigGenerator->isSucceed();
|
||||||
|
|
||||||
delete m_resultRigWeightMesh;
|
delete m_resultRigWeightMesh;
|
||||||
m_resultRigWeightMesh = m_rigGenerator->takeResultMesh();
|
m_resultRigWeightMesh = m_rigGenerator->takeResultMesh();
|
||||||
|
|
||||||
|
@ -2466,6 +2641,11 @@ const MeshResultContext &SkeletonDocument::currentRiggedResultContext() const
|
||||||
return *m_riggedResultContext;
|
return *m_riggedResultContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SkeletonDocument::currentRigSucceed() const
|
||||||
|
{
|
||||||
|
return m_currentRigSucceed;
|
||||||
|
}
|
||||||
|
|
||||||
void SkeletonDocument::generatePosePreviews()
|
void SkeletonDocument::generatePosePreviews()
|
||||||
{
|
{
|
||||||
if (nullptr != m_posePreviewsGenerator) {
|
if (nullptr != m_posePreviewsGenerator) {
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "riggenerator.h"
|
#include "riggenerator.h"
|
||||||
#include "rigtype.h"
|
#include "rigtype.h"
|
||||||
#include "posepreviewsgenerator.h"
|
#include "posepreviewsgenerator.h"
|
||||||
|
#include "curveutil.h"
|
||||||
|
|
||||||
class SkeletonNode
|
class SkeletonNode
|
||||||
{
|
{
|
||||||
|
@ -377,11 +378,27 @@ private:
|
||||||
MeshLoader *m_previewMesh = nullptr;
|
MeshLoader *m_previewMesh = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SkeletonMotion
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SkeletonMotion()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
QUuid id;
|
||||||
|
QString name;
|
||||||
|
bool dirty = true;
|
||||||
|
std::vector<HermiteControlNode> controlNodes;
|
||||||
|
std::vector<std::pair<float, QUuid>> keyframes; //std::pair<timeslot:0~1, poseId>
|
||||||
|
private:
|
||||||
|
Q_DISABLE_COPY(SkeletonMotion);
|
||||||
|
};
|
||||||
|
|
||||||
enum class SkeletonDocumentToSnapshotFor
|
enum class SkeletonDocumentToSnapshotFor
|
||||||
{
|
{
|
||||||
Document = 0,
|
Document = 0,
|
||||||
Nodes,
|
Nodes,
|
||||||
Poses
|
Poses,
|
||||||
|
Motions
|
||||||
};
|
};
|
||||||
|
|
||||||
class SkeletonDocument : public QObject
|
class SkeletonDocument : public QObject
|
||||||
|
@ -451,6 +468,12 @@ signals:
|
||||||
void poseNameChanged(QUuid poseId);
|
void poseNameChanged(QUuid poseId);
|
||||||
void poseParametersChanged(QUuid poseId);
|
void poseParametersChanged(QUuid poseId);
|
||||||
void posePreviewChanged(QUuid poseId);
|
void posePreviewChanged(QUuid poseId);
|
||||||
|
void motionAdded(QUuid motionId);
|
||||||
|
void motionRemoved(QUuid motionId);
|
||||||
|
void motionListChanged();
|
||||||
|
void motionNameChanged(QUuid motionId);
|
||||||
|
void motionControlNodesChanged(QUuid motionId);
|
||||||
|
void motionKeyframesChanged(QUuid motionId);
|
||||||
public: // need initialize
|
public: // need initialize
|
||||||
float originX;
|
float originX;
|
||||||
float originY;
|
float originY;
|
||||||
|
@ -476,12 +499,15 @@ public:
|
||||||
std::map<QUuid, SkeletonComponent> componentMap;
|
std::map<QUuid, SkeletonComponent> componentMap;
|
||||||
std::map<QUuid, SkeletonPose> poseMap;
|
std::map<QUuid, SkeletonPose> poseMap;
|
||||||
std::vector<QUuid> poseIdList;
|
std::vector<QUuid> poseIdList;
|
||||||
|
std::map<QUuid, SkeletonMotion> motionMap;
|
||||||
|
std::vector<QUuid> motionIdList;
|
||||||
SkeletonComponent rootComponent;
|
SkeletonComponent rootComponent;
|
||||||
QImage turnaround;
|
QImage turnaround;
|
||||||
QImage preview;
|
QImage preview;
|
||||||
void toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>(),
|
void toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>(),
|
||||||
SkeletonDocumentToSnapshotFor forWhat=SkeletonDocumentToSnapshotFor::Document,
|
SkeletonDocumentToSnapshotFor forWhat=SkeletonDocumentToSnapshotFor::Document,
|
||||||
const std::set<QUuid> &limitPoseIds=std::set<QUuid>()) const;
|
const std::set<QUuid> &limitPoseIds=std::set<QUuid>(),
|
||||||
|
const std::set<QUuid> &limitMotionIds=std::set<QUuid>()) const;
|
||||||
void fromSnapshot(const SkeletonSnapshot &snapshot);
|
void fromSnapshot(const SkeletonSnapshot &snapshot);
|
||||||
void addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste=true);
|
void addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste=true);
|
||||||
const SkeletonNode *findNode(QUuid nodeId) const;
|
const SkeletonNode *findNode(QUuid nodeId) const;
|
||||||
|
@ -492,6 +518,7 @@ public:
|
||||||
const SkeletonComponent *findComponentParent(QUuid componentId) const;
|
const SkeletonComponent *findComponentParent(QUuid componentId) const;
|
||||||
QUuid findComponentParentId(QUuid componentId) const;
|
QUuid findComponentParentId(QUuid componentId) const;
|
||||||
const SkeletonPose *findPose(QUuid poseId) const;
|
const SkeletonPose *findPose(QUuid poseId) const;
|
||||||
|
const SkeletonMotion *findMotion(QUuid motionId) const;
|
||||||
MeshLoader *takeResultMesh();
|
MeshLoader *takeResultMesh();
|
||||||
MeshLoader *takeResultTextureMesh();
|
MeshLoader *takeResultTextureMesh();
|
||||||
MeshLoader *takeResultRigWeightMesh();
|
MeshLoader *takeResultRigWeightMesh();
|
||||||
|
@ -501,6 +528,7 @@ public:
|
||||||
void setSharedContextWidget(QOpenGLWidget *sharedContextWidget);
|
void setSharedContextWidget(QOpenGLWidget *sharedContextWidget);
|
||||||
bool hasPastableNodesInClipboard() const;
|
bool hasPastableNodesInClipboard() const;
|
||||||
bool hasPastablePosesInClipboard() const;
|
bool hasPastablePosesInClipboard() const;
|
||||||
|
bool hasPastableMotionsInClipboard() const;
|
||||||
bool undoable() const;
|
bool undoable() const;
|
||||||
bool redoable() const;
|
bool redoable() const;
|
||||||
bool isNodeEditable(QUuid nodeId) const;
|
bool isNodeEditable(QUuid nodeId) const;
|
||||||
|
@ -515,6 +543,7 @@ public:
|
||||||
const std::vector<QString> &resultRigMissingMarkNames() const;
|
const std::vector<QString> &resultRigMissingMarkNames() const;
|
||||||
const std::vector<QString> &resultRigErrorMarkNames() const;
|
const std::vector<QString> &resultRigErrorMarkNames() const;
|
||||||
const MeshResultContext ¤tRiggedResultContext() const;
|
const MeshResultContext ¤tRiggedResultContext() const;
|
||||||
|
bool currentRigSucceed() const;
|
||||||
public slots:
|
public slots:
|
||||||
void removeNode(QUuid nodeId);
|
void removeNode(QUuid nodeId);
|
||||||
void removeEdge(QUuid edgeId);
|
void removeEdge(QUuid edgeId);
|
||||||
|
@ -602,6 +631,11 @@ public slots:
|
||||||
void removePose(QUuid poseId);
|
void removePose(QUuid poseId);
|
||||||
void setPoseParameters(QUuid poseId, std::map<QString, std::map<QString, QString>> parameters);
|
void setPoseParameters(QUuid poseId, std::map<QString, std::map<QString, QString>> parameters);
|
||||||
void renamePose(QUuid poseId, QString name);
|
void renamePose(QUuid poseId, QString name);
|
||||||
|
void addMotion(QString name, std::vector<HermiteControlNode> controlNodes, std::vector<std::pair<float, QUuid>> keyframes);
|
||||||
|
void removeMotion(QUuid motionId);
|
||||||
|
void setMotionControlNodes(QUuid motionId, std::vector<HermiteControlNode> controlNodes);
|
||||||
|
void setMotionKeyframes(QUuid motionId, std::vector<std::pair<float, QUuid>> keyframes);
|
||||||
|
void renameMotion(QUuid motionId, QString name);
|
||||||
private:
|
private:
|
||||||
void splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId);
|
void splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId);
|
||||||
void joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId=QUuid());
|
void joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId=QUuid());
|
||||||
|
@ -643,6 +677,7 @@ private: // need initialize
|
||||||
bool m_isRigObsolete;
|
bool m_isRigObsolete;
|
||||||
MeshResultContext *m_riggedResultContext;
|
MeshResultContext *m_riggedResultContext;
|
||||||
PosePreviewsGenerator *m_posePreviewsGenerator;
|
PosePreviewsGenerator *m_posePreviewsGenerator;
|
||||||
|
bool m_currentRigSucceed;
|
||||||
private:
|
private:
|
||||||
static unsigned long m_maxSnapshot;
|
static unsigned long m_maxSnapshot;
|
||||||
std::deque<SkeletonHistoryItem> m_undoItems;
|
std::deque<SkeletonHistoryItem> m_undoItems;
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include "skeletonparttreewidget.h"
|
#include "skeletonparttreewidget.h"
|
||||||
#include "rigwidget.h"
|
#include "rigwidget.h"
|
||||||
#include "markiconcreator.h"
|
#include "markiconcreator.h"
|
||||||
|
#include "motionmanagewidget.h"
|
||||||
|
|
||||||
int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16;
|
int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16;
|
||||||
int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16;
|
int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16;
|
||||||
|
@ -238,8 +239,17 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||||
emit m_document->posePreviewChanged(pose.first);
|
emit m_document->posePreviewChanged(pose.first);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QDockWidget *motionDocker = new QDockWidget(tr("Motions"), this);
|
||||||
|
motionDocker->setAllowedAreas(Qt::RightDockWidgetArea);
|
||||||
|
MotionManageWidget *motionManageWidget = new MotionManageWidget(m_document, motionDocker);
|
||||||
|
motionDocker->setWidget(motionManageWidget);
|
||||||
|
connect(motionManageWidget, &MotionManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog);
|
||||||
|
connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog);
|
||||||
|
addDockWidget(Qt::RightDockWidgetArea, motionDocker);
|
||||||
|
|
||||||
tabifyDockWidget(partTreeDocker, rigDocker);
|
tabifyDockWidget(partTreeDocker, rigDocker);
|
||||||
tabifyDockWidget(rigDocker, poseDocker);
|
tabifyDockWidget(rigDocker, poseDocker);
|
||||||
|
tabifyDockWidget(poseDocker, motionDocker);
|
||||||
|
|
||||||
partTreeDocker->raise();
|
partTreeDocker->raise();
|
||||||
|
|
||||||
|
@ -524,6 +534,13 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||||
});
|
});
|
||||||
m_windowMenu->addAction(m_showPosesAction);
|
m_windowMenu->addAction(m_showPosesAction);
|
||||||
|
|
||||||
|
m_showMotionsAction = new QAction(tr("Motions"), this);
|
||||||
|
connect(m_showMotionsAction, &QAction::triggered, [=]() {
|
||||||
|
motionDocker->show();
|
||||||
|
motionDocker->raise();
|
||||||
|
});
|
||||||
|
m_windowMenu->addAction(m_showMotionsAction);
|
||||||
|
|
||||||
QMenu *dialogsMenu = m_windowMenu->addMenu(tr("Dialogs"));
|
QMenu *dialogsMenu = m_windowMenu->addMenu(tr("Dialogs"));
|
||||||
connect(dialogsMenu, &QMenu::aboutToShow, [=]() {
|
connect(dialogsMenu, &QMenu::aboutToShow, [=]() {
|
||||||
dialogsMenu->clear();
|
dialogsMenu->clear();
|
||||||
|
|
|
@ -137,6 +137,7 @@ private:
|
||||||
QAction *m_showDebugDialogAction;
|
QAction *m_showDebugDialogAction;
|
||||||
QAction *m_showRigAction;
|
QAction *m_showRigAction;
|
||||||
QAction *m_showPosesAction;
|
QAction *m_showPosesAction;
|
||||||
|
QAction *m_showMotionsAction;
|
||||||
QAction *m_showAdvanceSettingAction;
|
QAction *m_showAdvanceSettingAction;
|
||||||
|
|
||||||
QMenu *m_helpMenu;
|
QMenu *m_helpMenu;
|
||||||
|
|
|
@ -16,6 +16,7 @@ public:
|
||||||
std::map<QString, std::map<QString, QString>> components;
|
std::map<QString, std::map<QString, QString>> components;
|
||||||
std::map<QString, QString> rootComponent;
|
std::map<QString, QString> rootComponent;
|
||||||
std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> poses; // std::pair<Pose attributes, Bone attributes>
|
std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> poses; // std::pair<Pose attributes, Bone attributes>
|
||||||
|
std::vector<std::tuple<std::map<QString, QString>, std::vector<std::map<QString, QString>>, std::vector<std::map<QString, QString>>>> motions; // std::tuple<Motion attributes, controlNodes, keyframes>
|
||||||
public:
|
public:
|
||||||
void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId=QString());
|
void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId=QString());
|
||||||
};
|
};
|
||||||
|
|
|
@ -99,6 +99,7 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write
|
||||||
for (poseAttributeIterator = poseIterator->first.begin(); poseAttributeIterator != poseIterator->first.end(); poseAttributeIterator++) {
|
for (poseAttributeIterator = poseIterator->first.begin(); poseAttributeIterator != poseIterator->first.end(); poseAttributeIterator++) {
|
||||||
writer->writeAttribute(poseAttributeIterator->first, poseAttributeIterator->second);
|
writer->writeAttribute(poseAttributeIterator->first, poseAttributeIterator->second);
|
||||||
}
|
}
|
||||||
|
writer->writeStartElement("parameters");
|
||||||
std::map<QString, std::map<QString, QString>>::iterator itemsIterator;
|
std::map<QString, std::map<QString, QString>>::iterator itemsIterator;
|
||||||
for (itemsIterator = poseIterator->second.begin(); itemsIterator != poseIterator->second.end(); itemsIterator++) {
|
for (itemsIterator = poseIterator->second.begin(); itemsIterator != poseIterator->second.end(); itemsIterator++) {
|
||||||
std::map<QString, QString>::iterator parametersIterator;
|
std::map<QString, QString>::iterator parametersIterator;
|
||||||
|
@ -111,6 +112,47 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write
|
||||||
writer->writeEndElement();
|
writer->writeEndElement();
|
||||||
}
|
}
|
||||||
writer->writeEndElement();
|
writer->writeEndElement();
|
||||||
|
writer->writeEndElement();
|
||||||
|
}
|
||||||
|
writer->writeEndElement();
|
||||||
|
|
||||||
|
writer->writeStartElement("motions");
|
||||||
|
std::vector<std::tuple<std::map<QString, QString>, std::vector<std::map<QString, QString>>, std::vector<std::map<QString, QString>>>>::iterator motionIterator;
|
||||||
|
for (motionIterator = snapshot->motions.begin(); motionIterator != snapshot->motions.end(); motionIterator++) {
|
||||||
|
std::map<QString, QString>::iterator motionAttributeIterator;
|
||||||
|
writer->writeStartElement("motion");
|
||||||
|
for (motionAttributeIterator = std::get<0>(*motionIterator).begin(); motionAttributeIterator != std::get<0>(*motionIterator).end(); motionAttributeIterator++) {
|
||||||
|
writer->writeAttribute(motionAttributeIterator->first, motionAttributeIterator->second);
|
||||||
|
}
|
||||||
|
writer->writeStartElement("controlNodes");
|
||||||
|
{
|
||||||
|
std::vector<std::map<QString, QString>>::iterator itemsIterator;
|
||||||
|
for (itemsIterator = std::get<1>(*motionIterator).begin(); itemsIterator != std::get<1>(*motionIterator).end(); itemsIterator++) {
|
||||||
|
std::map<QString, QString>::iterator attributesIterator;
|
||||||
|
writer->writeStartElement("controlNode");
|
||||||
|
for (attributesIterator = itemsIterator->begin(); attributesIterator != itemsIterator->end();
|
||||||
|
attributesIterator++) {
|
||||||
|
writer->writeAttribute(attributesIterator->first, attributesIterator->second);
|
||||||
|
}
|
||||||
|
writer->writeEndElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer->writeEndElement();
|
||||||
|
writer->writeStartElement("keyframes");
|
||||||
|
{
|
||||||
|
std::vector<std::map<QString, QString>>::iterator itemsIterator;
|
||||||
|
for (itemsIterator = std::get<2>(*motionIterator).begin(); itemsIterator != std::get<2>(*motionIterator).end(); itemsIterator++) {
|
||||||
|
std::map<QString, QString>::iterator attributesIterator;
|
||||||
|
writer->writeStartElement("keyframe");
|
||||||
|
for (attributesIterator = itemsIterator->begin(); attributesIterator != itemsIterator->end();
|
||||||
|
attributesIterator++) {
|
||||||
|
writer->writeAttribute(attributesIterator->first, attributesIterator->second);
|
||||||
|
}
|
||||||
|
writer->writeEndElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer->writeEndElement();
|
||||||
|
writer->writeEndElement();
|
||||||
}
|
}
|
||||||
writer->writeEndElement();
|
writer->writeEndElement();
|
||||||
|
|
||||||
|
@ -124,6 +166,7 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
|
||||||
std::stack<QString> componentStack;
|
std::stack<QString> componentStack;
|
||||||
std::vector<QString> elementNameStack;
|
std::vector<QString> elementNameStack;
|
||||||
std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>> currentPose;
|
std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>> currentPose;
|
||||||
|
std::tuple<std::map<QString, QString>, std::vector<std::map<QString, QString>>, std::vector<std::map<QString, QString>>> currentMotion;
|
||||||
while (!reader.atEnd()) {
|
while (!reader.atEnd()) {
|
||||||
reader.readNext();
|
reader.readNext();
|
||||||
if (!reader.isStartElement() && !reader.isEndElement()) {
|
if (!reader.isStartElement() && !reader.isEndElement()) {
|
||||||
|
@ -208,7 +251,8 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
|
||||||
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
|
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
|
||||||
currentPose.first[attr.name().toString()] = attr.value().toString();
|
currentPose.first[attr.name().toString()] = attr.value().toString();
|
||||||
}
|
}
|
||||||
} else if (fullName == "canvas.poses.pose.parameter") {
|
} else if (fullName == "canvas.poses.pose.parameter" ||
|
||||||
|
fullName == "canvas.poses.pose.parameters.parameter") {
|
||||||
QString forWhat = reader.attributes().value("for").toString();
|
QString forWhat = reader.attributes().value("for").toString();
|
||||||
if (forWhat.isEmpty())
|
if (forWhat.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
|
@ -217,12 +261,34 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
|
||||||
continue;
|
continue;
|
||||||
currentPose.second[forWhat][attr.name().toString()] = attr.value().toString();
|
currentPose.second[forWhat][attr.name().toString()] = attr.value().toString();
|
||||||
}
|
}
|
||||||
|
} else if (fullName == "canvas.motions.motion") {
|
||||||
|
QString motionId = reader.attributes().value("id").toString();
|
||||||
|
if (motionId.isEmpty())
|
||||||
|
continue;
|
||||||
|
currentMotion = decltype(currentMotion)();
|
||||||
|
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
|
||||||
|
std::get<0>(currentMotion)[attr.name().toString()] = attr.value().toString();
|
||||||
|
}
|
||||||
|
} else if (fullName == "canvas.motions.motion.controlNodes.controlNode") {
|
||||||
|
std::map<QString, QString> attributes;
|
||||||
|
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
|
||||||
|
attributes[attr.name().toString()] = attr.value().toString();
|
||||||
|
}
|
||||||
|
std::get<1>(currentMotion).push_back(attributes);
|
||||||
|
} else if (fullName == "canvas.motions.motion.keyframes.keyframe") {
|
||||||
|
std::map<QString, QString> attributes;
|
||||||
|
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
|
||||||
|
attributes[attr.name().toString()] = attr.value().toString();
|
||||||
|
}
|
||||||
|
std::get<2>(currentMotion).push_back(attributes);
|
||||||
}
|
}
|
||||||
} else if (reader.isEndElement()) {
|
} else if (reader.isEndElement()) {
|
||||||
if (fullName.startsWith("canvas.components.component")) {
|
if (fullName.startsWith("canvas.components.component")) {
|
||||||
componentStack.pop();
|
componentStack.pop();
|
||||||
} else if (fullName == "canvas.poses.pose") {
|
} else if (fullName == "canvas.poses.pose") {
|
||||||
snapshot->poses.push_back(currentPose);
|
snapshot->poses.push_back(currentPose);
|
||||||
|
} else if (fullName == "canvas.motions.motion") {
|
||||||
|
snapshot->motions.push_back(currentMotion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ int Theme::miniIconFontSize = 9;
|
||||||
int Theme::miniIconSize = 15;
|
int Theme::miniIconSize = 15;
|
||||||
int Theme::partPreviewImageSize = (Theme::miniIconSize * 3);
|
int Theme::partPreviewImageSize = (Theme::miniIconSize * 3);
|
||||||
int Theme::posePreviewImageSize = 75;
|
int Theme::posePreviewImageSize = 75;
|
||||||
|
int Theme::motionPreviewImageSize = 75;
|
||||||
int Theme::sidebarPreferredWidth = 200;
|
int Theme::sidebarPreferredWidth = 200;
|
||||||
|
|
||||||
QtAwesome *Theme::awesome()
|
QtAwesome *Theme::awesome()
|
||||||
|
|
|
@ -36,6 +36,7 @@ public:
|
||||||
static int toolIconSize;
|
static int toolIconSize;
|
||||||
static int posePreviewImageSize;
|
static int posePreviewImageSize;
|
||||||
static int partPreviewImageSize;
|
static int partPreviewImageSize;
|
||||||
|
static int motionPreviewImageSize;
|
||||||
static int miniIconFontSize;
|
static int miniIconFontSize;
|
||||||
static int miniIconSize;
|
static int miniIconSize;
|
||||||
static int sidebarPreferredWidth;
|
static int sidebarPreferredWidth;
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
#ifndef VIDEO_FRAME_EXTRACTOR_H
|
|
||||||
#define VIDEO_FRAME_EXTRACTOR_H
|
|
||||||
#include <QObject>
|
|
||||||
#include <QImage>
|
|
||||||
#include <QTemporaryFile>
|
|
||||||
|
|
||||||
struct VideoFrame
|
|
||||||
{
|
|
||||||
QImage image;
|
|
||||||
QImage thumbnail;
|
|
||||||
qint64 position;
|
|
||||||
float durationSeconds = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class VideoFrameExtractor : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
VideoFrameExtractor(const QString &fileName, const QString &realPath, QTemporaryFile *fileHandle, float thumbnailHeight, int maxFrames=(10 * 60));
|
|
||||||
~VideoFrameExtractor();
|
|
||||||
const QString &fileName();
|
|
||||||
const QString &realPath();
|
|
||||||
QTemporaryFile *fileHandle();
|
|
||||||
void extract();
|
|
||||||
std::vector<VideoFrame> *takeResultFrames();
|
|
||||||
signals:
|
|
||||||
void finished();
|
|
||||||
public slots:
|
|
||||||
void process();
|
|
||||||
private:
|
|
||||||
void release();
|
|
||||||
private:
|
|
||||||
QString m_fileName;
|
|
||||||
QString m_realPath;
|
|
||||||
QTemporaryFile *m_fileHandle = nullptr;
|
|
||||||
std::vector<VideoFrame> *m_resultFrames = nullptr;
|
|
||||||
int m_maxFrames;
|
|
||||||
float m_thumbnailHeight = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
Loading…
Reference in New Issue