Rewrite pose editor

This commit include lots of changes for pose editor.
Before, we use buttons to represent the transform controller for bones;
Now, we use nodes and edges to represent the bones, just like the nodes and edges for representing the guide spheres for mesh generating. There are advantages by doing this. Firstly, The edit UI for both mesh and pose are unified, secondly, it is possible to set a reference sheet for pose editing now, this is very important.
This new pose editor is inspired by the Eadweard Muybridge's work.
master
Jeremy Hu 2018-11-05 23:47:21 +08:00
parent a4aefdd308
commit 70f075a36e
27 changed files with 1085 additions and 1890 deletions

View File

@ -1133,3 +1133,8 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode
<pre> <pre>
http://bediyap.com/programming/convert-quaternion-to-euler-rotations/ http://bediyap.com/programming/convert-quaternion-to-euler-rotations/
</pre> </pre>
<h1>Eadweard Muybridge</h1>
<pre>
https://en.wikipedia.org/wiki/Eadweard_Muybridge
</pre>

View File

@ -294,6 +294,9 @@ HEADERS += src/poserconstruct.h
SOURCES += src/skeletondocument.cpp SOURCES += src/skeletondocument.cpp
HEADERS += src/skeletondocument.h HEADERS += src/skeletondocument.h
SOURCES += src/posedocument.cpp
HEADERS += src/posedocument.h
SOURCES += src/main.cpp SOURCES += src/main.cpp
HEADERS += src/version.h HEADERS += src/version.h

View File

@ -416,6 +416,20 @@ void Document::setPoseParameters(QUuid poseId, std::map<QString, std::map<QStrin
emit optionsChanged(); emit optionsChanged();
} }
void Document::setPoseAttributes(QUuid poseId, std::map<QString, QString> attributes)
{
auto findPoseResult = poseMap.find(poseId);
if (findPoseResult == poseMap.end()) {
qDebug() << "Find pose failed:" << poseId;
return;
}
findPoseResult->second.attributes = attributes;
findPoseResult->second.dirty = true;
emit posesChanged();
emit poseAttributesChanged(poseId);
emit optionsChanged();
}
void Document::renamePose(QUuid poseId, QString name) void Document::renamePose(QUuid poseId, QString name)
{ {
auto findPoseResult = poseMap.find(poseId); auto findPoseResult = poseMap.find(poseId);
@ -431,26 +445,6 @@ void Document::renamePose(QUuid poseId, QString name)
emit optionsChanged(); emit optionsChanged();
} }
const SkeletonEdge *Document::findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const
{
const SkeletonNode *firstNode = nullptr;
firstNode = findNode(firstNodeId);
if (nullptr == firstNode) {
qDebug() << "Find node failed:" << firstNodeId;
return nullptr;
}
for (auto edgeIdIt = firstNode->edgeIds.begin(); edgeIdIt != firstNode->edgeIds.end(); edgeIdIt++) {
auto edgeIt = edgeMap.find(*edgeIdIt);
if (edgeIt == edgeMap.end()) {
qDebug() << "Find edge failed:" << *edgeIdIt;
continue;
}
if (std::find(edgeIt->second.nodeIds.begin(), edgeIt->second.nodeIds.end(), secondNodeId) != edgeIt->second.nodeIds.end())
return &edgeIt->second;
}
return nullptr;
}
bool Document::originSettled() const bool Document::originSettled() const
{ {
return !qFuzzyIsNull(originX) && !qFuzzyIsNull(originY) && !qFuzzyIsNull(originZ); return !qFuzzyIsNull(originX) && !qFuzzyIsNull(originY) && !qFuzzyIsNull(originZ);
@ -537,30 +531,6 @@ void Document::addEdge(QUuid fromNodeId, QUuid toNodeId)
emit skeletonChanged(); emit skeletonChanged();
} }
const SkeletonNode *Document::findNode(QUuid nodeId) const
{
auto it = nodeMap.find(nodeId);
if (it == nodeMap.end())
return nullptr;
return &it->second;
}
const SkeletonEdge *Document::findEdge(QUuid edgeId) const
{
auto it = edgeMap.find(edgeId);
if (it == edgeMap.end())
return nullptr;
return &it->second;
}
const SkeletonPart *Document::findPart(QUuid partId) const
{
auto it = partMap.find(partId);
if (it == partMap.end())
return nullptr;
return &it->second;
}
const Component *Document::findComponent(QUuid componentId) const const Component *Document::findComponent(QUuid componentId) const
{ {
if (componentId.isNull()) if (componentId.isNull())
@ -1001,7 +971,7 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeId
continue; continue;
} }
auto &poseIt = *findPoseResult; auto &poseIt = *findPoseResult;
std::map<QString, QString> pose; std::map<QString, QString> pose = poseIt.second.attributes;
pose["id"] = poseIt.second.id.toString(); pose["id"] = poseIt.second.id.toString();
if (!poseIt.second.name.isEmpty()) if (!poseIt.second.name.isEmpty())
pose["name"] = poseIt.second.name; pose["name"] = poseIt.second.name;
@ -1251,6 +1221,13 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
newPose.id = newPoseId; newPose.id = newPoseId;
const auto &poseAttributes = poseIt.first; const auto &poseAttributes = poseIt.first;
newPose.name = valueOfKeyInMapOrEmpty(poseAttributes, "name"); newPose.name = valueOfKeyInMapOrEmpty(poseAttributes, "name");
for (const auto &attribute: poseAttributes) {
if (attribute.first == "name" ||
attribute.first == "id") {
continue;
}
newPose.attributes.insert({attribute.first, attribute.second});
}
newPose.parameters = poseIt.second; newPose.parameters = poseIt.second;
oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(poseAttributes, "id"))] = newPoseId; oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(poseAttributes, "id"))] = newPoseId;
poseIdList.push_back(newPoseId); poseIdList.push_back(newPoseId);
@ -2394,32 +2371,6 @@ void Document::checkExportReadyState()
emit exportReady(); emit exportReady();
} }
void Document::findAllNeighbors(QUuid nodeId, std::set<QUuid> &neighbors) const
{
const auto &node = findNode(nodeId);
if (nullptr == node) {
qDebug() << "findNode:" << nodeId << "failed";
return;
}
for (const auto &edgeId: node->edgeIds) {
const auto &edge = findEdge(edgeId);
if (nullptr == edge) {
qDebug() << "findEdge:" << edgeId << "failed";
continue;
}
const auto &neighborNodeId = edge->neighborOf(nodeId);
if (neighborNodeId.isNull()) {
qDebug() << "neighborOf:" << nodeId << "is null from edge:" << edgeId;
continue;
}
if (neighbors.find(neighborNodeId) != neighbors.end()) {
continue;
}
neighbors.insert(neighborNodeId);
findAllNeighbors(neighborNodeId, neighbors);
}
}
void Document::setSharedContextWidget(QOpenGLWidget *sharedContextWidget) void Document::setSharedContextWidget(QOpenGLWidget *sharedContextWidget)
{ {
m_sharedContextWidget = sharedContextWidget; m_sharedContextWidget = sharedContextWidget;

View File

@ -195,6 +195,7 @@ public:
QUuid id; QUuid id;
QString name; QString name;
bool dirty = true; bool dirty = true;
std::map<QString, QString> attributes;
std::map<QString, std::map<QString, QString>> parameters; std::map<QString, std::map<QString, QString>> parameters;
void updatePreviewMesh(MeshLoader *previewMesh) void updatePreviewMesh(MeshLoader *previewMesh)
{ {
@ -424,6 +425,7 @@ signals:
void poseListChanged(); void poseListChanged();
void poseNameChanged(QUuid poseId); void poseNameChanged(QUuid poseId);
void poseParametersChanged(QUuid poseId); void poseParametersChanged(QUuid poseId);
void poseAttributesChanged(QUuid poseId);
void posePreviewChanged(QUuid poseId); void posePreviewChanged(QUuid poseId);
void motionAdded(QUuid motionId); void motionAdded(QUuid motionId);
void motionRemoved(QUuid motionId); void motionRemoved(QUuid motionId);
@ -453,9 +455,6 @@ public: // need initialize
public: public:
Document(); Document();
~Document(); ~Document();
std::map<QUuid, SkeletonPart> partMap;
std::map<QUuid, SkeletonNode> nodeMap;
std::map<QUuid, SkeletonEdge> edgeMap;
std::map<QUuid, Component> componentMap; std::map<QUuid, Component> componentMap;
std::map<QUuid, Material> materialMap; std::map<QUuid, Material> materialMap;
std::vector<QUuid> materialIdList; std::vector<QUuid> materialIdList;
@ -465,17 +464,12 @@ public:
std::vector<QUuid> motionIdList; std::vector<QUuid> motionIdList;
Component rootComponent; Component rootComponent;
QImage preview; QImage preview;
const SkeletonNode *findNode(QUuid nodeId) const override;
const SkeletonEdge *findEdge(QUuid edgeId) const override;
const SkeletonPart *findPart(QUuid partId) const override;
const SkeletonEdge *findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const override;
bool undoable() const override; bool undoable() const override;
bool redoable() const override; bool redoable() const override;
bool hasPastableNodesInClipboard() const override; bool hasPastableNodesInClipboard() const override;
bool originSettled() const override; bool originSettled() const override;
bool isNodeEditable(QUuid nodeId) const override; bool isNodeEditable(QUuid nodeId) const override;
bool isEdgeEditable(QUuid edgeId) const override; bool isEdgeEditable(QUuid edgeId) const override;
void findAllNeighbors(QUuid nodeId, std::set<QUuid> &neighbors) const override;
void copyNodes(std::set<QUuid> nodeIdSet) const override; void copyNodes(std::set<QUuid> nodeIdSet) const override;
void toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>(), void toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>(),
DocumentToSnapshotFor forWhat=DocumentToSnapshotFor::Document, DocumentToSnapshotFor forWhat=DocumentToSnapshotFor::Document,
@ -601,6 +595,7 @@ public slots:
void addPose(QString name, std::map<QString, std::map<QString, QString>> parameters); void addPose(QString name, std::map<QString, std::map<QString, QString>> parameters);
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 setPoseAttributes(QUuid poseId, std::map<QString, QString> attributes);
void renamePose(QUuid poseId, QString name); void renamePose(QUuid poseId, QString name);
void addMotion(QString name, std::vector<MotionClip> clips); void addMotion(QString name, std::vector<MotionClip> clips);
void removeMotion(QUuid motionId); void removeMotion(QUuid motionId);

File diff suppressed because it is too large Load Diff

View File

@ -37,15 +37,15 @@
#include "fbxfile.h" #include "fbxfile.h"
#include "shortcuts.h" #include "shortcuts.h"
int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16; int DocumentWindow::m_modelRenderWidgetInitialX = 16;
int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16; int DocumentWindow::m_modelRenderWidgetInitialY = 16;
int SkeletonDocumentWindow::m_modelRenderWidgetInitialSize = 128; int DocumentWindow::m_modelRenderWidgetInitialSize = 128;
int SkeletonDocumentWindow::m_skeletonRenderWidgetInitialX = SkeletonDocumentWindow::m_modelRenderWidgetInitialX + SkeletonDocumentWindow::m_modelRenderWidgetInitialSize + 16; int DocumentWindow::m_skeletonRenderWidgetInitialX = DocumentWindow::m_modelRenderWidgetInitialX + DocumentWindow::m_modelRenderWidgetInitialSize + 16;
int SkeletonDocumentWindow::m_skeletonRenderWidgetInitialY = SkeletonDocumentWindow::m_modelRenderWidgetInitialY; int DocumentWindow::m_skeletonRenderWidgetInitialY = DocumentWindow::m_modelRenderWidgetInitialY;
int SkeletonDocumentWindow::m_skeletonRenderWidgetInitialSize = SkeletonDocumentWindow::m_modelRenderWidgetInitialSize; int DocumentWindow::m_skeletonRenderWidgetInitialSize = DocumentWindow::m_modelRenderWidgetInitialSize;
LogBrowser *g_logBrowser = nullptr; LogBrowser *g_logBrowser = nullptr;
std::set<SkeletonDocumentWindow *> g_documentWindows; std::set<DocumentWindow *> g_documentWindows;
QTextBrowser *g_acknowlegementsWidget = nullptr; QTextBrowser *g_acknowlegementsWidget = nullptr;
AboutWidget *g_aboutWidget = nullptr; AboutWidget *g_aboutWidget = nullptr;
QTextBrowser *g_contributorsWidget = nullptr; QTextBrowser *g_contributorsWidget = nullptr;
@ -56,7 +56,7 @@ void outputMessage(QtMsgType type, const QMessageLogContext &context, const QStr
g_logBrowser->outputMessage(type, msg, context.file, context.line); g_logBrowser->outputMessage(type, msg, context.file, context.line);
} }
void SkeletonDocumentWindow::showAcknowlegements() void DocumentWindow::showAcknowlegements()
{ {
if (!g_acknowlegementsWidget) { if (!g_acknowlegementsWidget) {
g_acknowlegementsWidget = new QTextBrowser; g_acknowlegementsWidget = new QTextBrowser;
@ -72,7 +72,7 @@ void SkeletonDocumentWindow::showAcknowlegements()
g_acknowlegementsWidget->raise(); g_acknowlegementsWidget->raise();
} }
void SkeletonDocumentWindow::showContributors() void DocumentWindow::showContributors()
{ {
if (!g_contributorsWidget) { if (!g_contributorsWidget) {
g_contributorsWidget = new QTextBrowser; g_contributorsWidget = new QTextBrowser;
@ -89,7 +89,7 @@ void SkeletonDocumentWindow::showContributors()
g_contributorsWidget->raise(); g_contributorsWidget->raise();
} }
void SkeletonDocumentWindow::showAbout() void DocumentWindow::showAbout()
{ {
if (!g_aboutWidget) { if (!g_aboutWidget) {
g_aboutWidget = new AboutWidget; g_aboutWidget = new AboutWidget;
@ -99,7 +99,7 @@ void SkeletonDocumentWindow::showAbout()
g_aboutWidget->raise(); g_aboutWidget->raise();
} }
SkeletonDocumentWindow::SkeletonDocumentWindow() : DocumentWindow::DocumentWindow() :
m_document(nullptr), m_document(nullptr),
m_firstShow(true), m_firstShow(true),
m_documentSaved(true), m_documentSaved(true),
@ -220,9 +220,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_modelRenderWidget = new ModelWidget(containerWidget); m_modelRenderWidget = new ModelWidget(containerWidget);
m_modelRenderWidget->setAttribute(Qt::WA_TransparentForMouseEvents); m_modelRenderWidget->setAttribute(Qt::WA_TransparentForMouseEvents);
m_modelRenderWidget->setMinimumSize(SkeletonDocumentWindow::m_modelRenderWidgetInitialSize, SkeletonDocumentWindow::m_modelRenderWidgetInitialSize); m_modelRenderWidget->setMinimumSize(DocumentWindow::m_modelRenderWidgetInitialSize, DocumentWindow::m_modelRenderWidgetInitialSize);
m_modelRenderWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_modelRenderWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_modelRenderWidget->move(SkeletonDocumentWindow::m_modelRenderWidgetInitialX, SkeletonDocumentWindow::m_modelRenderWidgetInitialY); m_modelRenderWidget->move(DocumentWindow::m_modelRenderWidgetInitialX, DocumentWindow::m_modelRenderWidgetInitialY);
m_graphicsWidget->setModelWidget(m_modelRenderWidget); m_graphicsWidget->setModelWidget(m_modelRenderWidget);
containerWidget->setModelWidget(m_modelRenderWidget); containerWidget->setModelWidget(m_modelRenderWidget);
@ -246,8 +246,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
materialDocker->setAllowedAreas(Qt::RightDockWidgetArea); materialDocker->setAllowedAreas(Qt::RightDockWidgetArea);
MaterialManageWidget *materialManageWidget = new MaterialManageWidget(m_document, materialDocker); MaterialManageWidget *materialManageWidget = new MaterialManageWidget(m_document, materialDocker);
materialDocker->setWidget(materialManageWidget); materialDocker->setWidget(materialManageWidget);
connect(materialManageWidget, &MaterialManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog); connect(materialManageWidget, &MaterialManageWidget::registerDialog, this, &DocumentWindow::registerDialog);
connect(materialManageWidget, &MaterialManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog); connect(materialManageWidget, &MaterialManageWidget::unregisterDialog, this, &DocumentWindow::unregisterDialog);
addDockWidget(Qt::RightDockWidgetArea, materialDocker); addDockWidget(Qt::RightDockWidgetArea, materialDocker);
connect(materialDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) { connect(materialDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) {
Q_UNUSED(topLevel); Q_UNUSED(topLevel);
@ -269,8 +269,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
poseDocker->setAllowedAreas(Qt::RightDockWidgetArea); poseDocker->setAllowedAreas(Qt::RightDockWidgetArea);
PoseManageWidget *poseManageWidget = new PoseManageWidget(m_document, poseDocker); PoseManageWidget *poseManageWidget = new PoseManageWidget(m_document, poseDocker);
poseDocker->setWidget(poseManageWidget); poseDocker->setWidget(poseManageWidget);
connect(poseManageWidget, &PoseManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog); connect(poseManageWidget, &PoseManageWidget::registerDialog, this, &DocumentWindow::registerDialog);
connect(poseManageWidget, &PoseManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog); connect(poseManageWidget, &PoseManageWidget::unregisterDialog, this, &DocumentWindow::unregisterDialog);
addDockWidget(Qt::RightDockWidgetArea, poseDocker); addDockWidget(Qt::RightDockWidgetArea, poseDocker);
connect(poseDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) { connect(poseDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) {
Q_UNUSED(topLevel); Q_UNUSED(topLevel);
@ -282,8 +282,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
motionDocker->setAllowedAreas(Qt::RightDockWidgetArea); motionDocker->setAllowedAreas(Qt::RightDockWidgetArea);
MotionManageWidget *motionManageWidget = new MotionManageWidget(m_document, motionDocker); MotionManageWidget *motionManageWidget = new MotionManageWidget(m_document, motionDocker);
motionDocker->setWidget(motionManageWidget); motionDocker->setWidget(motionManageWidget);
connect(motionManageWidget, &MotionManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog); connect(motionManageWidget, &MotionManageWidget::registerDialog, this, &DocumentWindow::registerDialog);
connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog); connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &DocumentWindow::unregisterDialog);
addDockWidget(Qt::RightDockWidgetArea, motionDocker); addDockWidget(Qt::RightDockWidgetArea, motionDocker);
tabifyDockWidget(partTreeDocker, materialDocker); tabifyDockWidget(partTreeDocker, materialDocker);
@ -309,27 +309,27 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_fileMenu = menuBar()->addMenu(tr("File")); m_fileMenu = menuBar()->addMenu(tr("File"));
m_newWindowAction = new QAction(tr("New Window"), this); m_newWindowAction = new QAction(tr("New Window"), this);
connect(m_newWindowAction, &QAction::triggered, this, &SkeletonDocumentWindow::newWindow, Qt::QueuedConnection); connect(m_newWindowAction, &QAction::triggered, this, &DocumentWindow::newWindow, Qt::QueuedConnection);
m_fileMenu->addAction(m_newWindowAction); m_fileMenu->addAction(m_newWindowAction);
m_newDocumentAction = new QAction(tr("New"), this); m_newDocumentAction = new QAction(tr("New"), this);
connect(m_newDocumentAction, &QAction::triggered, this, &SkeletonDocumentWindow::newDocument); connect(m_newDocumentAction, &QAction::triggered, this, &DocumentWindow::newDocument);
m_fileMenu->addAction(m_newDocumentAction); m_fileMenu->addAction(m_newDocumentAction);
m_openAction = new QAction(tr("Open..."), this); m_openAction = new QAction(tr("Open..."), this);
connect(m_openAction, &QAction::triggered, this, &SkeletonDocumentWindow::open, Qt::QueuedConnection); connect(m_openAction, &QAction::triggered, this, &DocumentWindow::open, Qt::QueuedConnection);
m_fileMenu->addAction(m_openAction); m_fileMenu->addAction(m_openAction);
m_saveAction = new QAction(tr("Save"), this); m_saveAction = new QAction(tr("Save"), this);
connect(m_saveAction, &QAction::triggered, this, &SkeletonDocumentWindow::save, Qt::QueuedConnection); connect(m_saveAction, &QAction::triggered, this, &DocumentWindow::save, Qt::QueuedConnection);
m_fileMenu->addAction(m_saveAction); m_fileMenu->addAction(m_saveAction);
m_saveAsAction = new QAction(tr("Save As..."), this); m_saveAsAction = new QAction(tr("Save As..."), this);
connect(m_saveAsAction, &QAction::triggered, this, &SkeletonDocumentWindow::saveAs, Qt::QueuedConnection); connect(m_saveAsAction, &QAction::triggered, this, &DocumentWindow::saveAs, Qt::QueuedConnection);
m_fileMenu->addAction(m_saveAsAction); m_fileMenu->addAction(m_saveAsAction);
m_saveAllAction = new QAction(tr("Save All"), this); m_saveAllAction = new QAction(tr("Save All"), this);
connect(m_saveAllAction, &QAction::triggered, this, &SkeletonDocumentWindow::saveAll, Qt::QueuedConnection); connect(m_saveAllAction, &QAction::triggered, this, &DocumentWindow::saveAll, Qt::QueuedConnection);
m_fileMenu->addAction(m_saveAllAction); m_fileMenu->addAction(m_saveAllAction);
m_fileMenu->addSeparator(); m_fileMenu->addSeparator();
@ -337,11 +337,11 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
//m_exportMenu = m_fileMenu->addMenu(tr("Export")); //m_exportMenu = m_fileMenu->addMenu(tr("Export"));
m_exportAction = new QAction(tr("Export..."), this); m_exportAction = new QAction(tr("Export..."), this);
connect(m_exportAction, &QAction::triggered, this, &SkeletonDocumentWindow::showExportPreview, Qt::QueuedConnection); connect(m_exportAction, &QAction::triggered, this, &DocumentWindow::showExportPreview, Qt::QueuedConnection);
m_fileMenu->addAction(m_exportAction); m_fileMenu->addAction(m_exportAction);
m_exportAsObjAction = new QAction(tr("Export as OBJ..."), this); m_exportAsObjAction = new QAction(tr("Export as OBJ..."), this);
connect(m_exportAsObjAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportObjResult, Qt::QueuedConnection); connect(m_exportAsObjAction, &QAction::triggered, this, &DocumentWindow::exportObjResult, Qt::QueuedConnection);
m_fileMenu->addAction(m_exportAsObjAction); m_fileMenu->addAction(m_exportAsObjAction);
//m_exportRenderedAsImageAction = new QAction(tr("Export as PNG..."), this); //m_exportRenderedAsImageAction = new QAction(tr("Export as PNG..."), this);
@ -355,7 +355,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_fileMenu->addSeparator(); m_fileMenu->addSeparator();
m_changeTurnaroundAction = new QAction(tr("Change Reference Sheet..."), this); m_changeTurnaroundAction = new QAction(tr("Change Reference Sheet..."), this);
connect(m_changeTurnaroundAction, &QAction::triggered, this, &SkeletonDocumentWindow::changeTurnaround, Qt::QueuedConnection); connect(m_changeTurnaroundAction, &QAction::triggered, this, &DocumentWindow::changeTurnaround, Qt::QueuedConnection);
m_fileMenu->addAction(m_changeTurnaroundAction); m_fileMenu->addAction(m_changeTurnaroundAction);
m_fileMenu->addSeparator(); m_fileMenu->addSeparator();
@ -530,7 +530,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_resetModelWidgetPosAction = new QAction(tr("Show Model"), this); m_resetModelWidgetPosAction = new QAction(tr("Show Model"), this);
connect(m_resetModelWidgetPosAction, &QAction::triggered, [=]() { connect(m_resetModelWidgetPosAction, &QAction::triggered, [=]() {
if (!isModelSitInVisibleArea(m_modelRenderWidget)) { if (!isModelSitInVisibleArea(m_modelRenderWidget)) {
m_modelRenderWidget->move(SkeletonDocumentWindow::m_modelRenderWidgetInitialX, SkeletonDocumentWindow::m_modelRenderWidgetInitialY); m_modelRenderWidget->move(DocumentWindow::m_modelRenderWidgetInitialX, DocumentWindow::m_modelRenderWidgetInitialY);
} }
}); });
m_viewMenu->addAction(m_resetModelWidgetPosAction); m_viewMenu->addAction(m_resetModelWidgetPosAction);
@ -610,7 +610,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_windowMenu->addAction(m_showDebugDialogAction); m_windowMenu->addAction(m_showDebugDialogAction);
m_showAdvanceSettingAction = new QAction(tr("Advance"), this); m_showAdvanceSettingAction = new QAction(tr("Advance"), this);
connect(m_showAdvanceSettingAction, &QAction::triggered, this, &SkeletonDocumentWindow::showAdvanceSetting); connect(m_showAdvanceSettingAction, &QAction::triggered, this, &DocumentWindow::showAdvanceSetting);
#ifndef NDEBUG #ifndef NDEBUG
m_windowMenu->addAction(m_showAdvanceSettingAction); m_windowMenu->addAction(m_showAdvanceSettingAction);
#endif #endif
@ -618,33 +618,33 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_helpMenu = menuBar()->addMenu(tr("Help")); m_helpMenu = menuBar()->addMenu(tr("Help"));
m_viewSourceAction = new QAction(tr("Fork me on GitHub"), this); m_viewSourceAction = new QAction(tr("Fork me on GitHub"), this);
connect(m_viewSourceAction, &QAction::triggered, this, &SkeletonDocumentWindow::viewSource); connect(m_viewSourceAction, &QAction::triggered, this, &DocumentWindow::viewSource);
m_helpMenu->addAction(m_viewSourceAction); m_helpMenu->addAction(m_viewSourceAction);
m_helpMenu->addSeparator(); m_helpMenu->addSeparator();
m_seeReferenceGuideAction = new QAction(tr("Reference Guide"), this); m_seeReferenceGuideAction = new QAction(tr("Reference Guide"), this);
connect(m_seeReferenceGuideAction, &QAction::triggered, this, &SkeletonDocumentWindow::seeReferenceGuide); connect(m_seeReferenceGuideAction, &QAction::triggered, this, &DocumentWindow::seeReferenceGuide);
m_helpMenu->addAction(m_seeReferenceGuideAction); m_helpMenu->addAction(m_seeReferenceGuideAction);
m_helpMenu->addSeparator(); m_helpMenu->addSeparator();
m_aboutAction = new QAction(tr("About"), this); m_aboutAction = new QAction(tr("About"), this);
connect(m_aboutAction, &QAction::triggered, this, &SkeletonDocumentWindow::about); connect(m_aboutAction, &QAction::triggered, this, &DocumentWindow::about);
m_helpMenu->addAction(m_aboutAction); m_helpMenu->addAction(m_aboutAction);
m_reportIssuesAction = new QAction(tr("Report Issues"), this); m_reportIssuesAction = new QAction(tr("Report Issues"), this);
connect(m_reportIssuesAction, &QAction::triggered, this, &SkeletonDocumentWindow::reportIssues); connect(m_reportIssuesAction, &QAction::triggered, this, &DocumentWindow::reportIssues);
m_helpMenu->addAction(m_reportIssuesAction); m_helpMenu->addAction(m_reportIssuesAction);
m_helpMenu->addSeparator(); m_helpMenu->addSeparator();
m_seeContributorsAction = new QAction(tr("Contributors"), this); m_seeContributorsAction = new QAction(tr("Contributors"), this);
connect(m_seeContributorsAction, &QAction::triggered, this, &SkeletonDocumentWindow::seeContributors); connect(m_seeContributorsAction, &QAction::triggered, this, &DocumentWindow::seeContributors);
m_helpMenu->addAction(m_seeContributorsAction); m_helpMenu->addAction(m_seeContributorsAction);
m_seeAcknowlegementsAction = new QAction(tr("Acknowlegements"), this); m_seeAcknowlegementsAction = new QAction(tr("Acknowlegements"), this);
connect(m_seeAcknowlegementsAction, &QAction::triggered, this, &SkeletonDocumentWindow::seeAcknowlegements); connect(m_seeAcknowlegementsAction, &QAction::triggered, this, &DocumentWindow::seeAcknowlegements);
m_helpMenu->addAction(m_seeAcknowlegementsAction); m_helpMenu->addAction(m_seeAcknowlegementsAction);
connect(containerWidget, &GraphicsContainerWidget::containerSizeChanged, connect(containerWidget, &GraphicsContainerWidget::containerSizeChanged,
@ -737,9 +737,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
connect(graphicsWidget, &SkeletonGraphicsWidget::enableAllPositionRelatedLocks, m_document, &Document::enableAllPositionRelatedLocks); connect(graphicsWidget, &SkeletonGraphicsWidget::enableAllPositionRelatedLocks, m_document, &Document::enableAllPositionRelatedLocks);
connect(graphicsWidget, &SkeletonGraphicsWidget::disableAllPositionRelatedLocks, m_document, &Document::disableAllPositionRelatedLocks); connect(graphicsWidget, &SkeletonGraphicsWidget::disableAllPositionRelatedLocks, m_document, &Document::disableAllPositionRelatedLocks);
connect(graphicsWidget, &SkeletonGraphicsWidget::changeTurnaround, this, &SkeletonDocumentWindow::changeTurnaround); connect(graphicsWidget, &SkeletonGraphicsWidget::changeTurnaround, this, &DocumentWindow::changeTurnaround);
connect(graphicsWidget, &SkeletonGraphicsWidget::save, this, &SkeletonDocumentWindow::save); connect(graphicsWidget, &SkeletonGraphicsWidget::save, this, &DocumentWindow::save);
connect(graphicsWidget, &SkeletonGraphicsWidget::open, this, &SkeletonDocumentWindow::open); connect(graphicsWidget, &SkeletonGraphicsWidget::open, this, &DocumentWindow::open);
connect(m_document, &Document::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded); connect(m_document, &Document::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded);
connect(m_document, &Document::nodeRemoved, graphicsWidget, &SkeletonGraphicsWidget::nodeRemoved); connect(m_document, &Document::nodeRemoved, graphicsWidget, &SkeletonGraphicsWidget::nodeRemoved);
@ -848,25 +848,25 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
//m_skeletonRenderWidget->setCursor(graphicsWidget->cursor()); //m_skeletonRenderWidget->setCursor(graphicsWidget->cursor());
}); });
connect(m_document, &Document::skeletonChanged, this, &SkeletonDocumentWindow::documentChanged); connect(m_document, &Document::skeletonChanged, this, &DocumentWindow::documentChanged);
connect(m_document, &Document::turnaroundChanged, this, &SkeletonDocumentWindow::documentChanged); connect(m_document, &Document::turnaroundChanged, this, &DocumentWindow::documentChanged);
connect(m_document, &Document::optionsChanged, this, &SkeletonDocumentWindow::documentChanged); connect(m_document, &Document::optionsChanged, this, &DocumentWindow::documentChanged);
connect(m_document, &Document::rigChanged, this, &SkeletonDocumentWindow::documentChanged); connect(m_document, &Document::rigChanged, this, &DocumentWindow::documentChanged);
connect(m_modelRenderWidget, &ModelWidget::customContextMenuRequested, [=](const QPoint &pos) { connect(m_modelRenderWidget, &ModelWidget::customContextMenuRequested, [=](const QPoint &pos) {
graphicsWidget->showContextMenu(graphicsWidget->mapFromGlobal(m_modelRenderWidget->mapToGlobal(pos))); graphicsWidget->showContextMenu(graphicsWidget->mapFromGlobal(m_modelRenderWidget->mapToGlobal(pos)));
}); });
connect(m_document, &Document::xlockStateChanged, this, &SkeletonDocumentWindow::updateXlockButtonState); connect(m_document, &Document::xlockStateChanged, this, &DocumentWindow::updateXlockButtonState);
connect(m_document, &Document::ylockStateChanged, this, &SkeletonDocumentWindow::updateYlockButtonState); connect(m_document, &Document::ylockStateChanged, this, &DocumentWindow::updateYlockButtonState);
connect(m_document, &Document::zlockStateChanged, this, &SkeletonDocumentWindow::updateZlockButtonState); connect(m_document, &Document::zlockStateChanged, this, &DocumentWindow::updateZlockButtonState);
connect(m_document, &Document::radiusLockStateChanged, this, &SkeletonDocumentWindow::updateRadiusLockButtonState); connect(m_document, &Document::radiusLockStateChanged, this, &DocumentWindow::updateRadiusLockButtonState);
connect(m_rigWidget, &RigWidget::setRigType, m_document, &Document::setRigType); connect(m_rigWidget, &RigWidget::setRigType, m_document, &Document::setRigType);
connect(m_document, &Document::rigTypeChanged, m_rigWidget, &RigWidget::rigTypeChanged); connect(m_document, &Document::rigTypeChanged, m_rigWidget, &RigWidget::rigTypeChanged);
connect(m_document, &Document::resultRigChanged, m_rigWidget, &RigWidget::updateResultInfo); connect(m_document, &Document::resultRigChanged, m_rigWidget, &RigWidget::updateResultInfo);
connect(m_document, &Document::resultRigChanged, this, &SkeletonDocumentWindow::updateRigWeightRenderWidget); connect(m_document, &Document::resultRigChanged, this, &DocumentWindow::updateRigWeightRenderWidget);
//connect(m_document, &SkeletonDocument::resultRigChanged, tetrapodPoseEditWidget, &TetrapodPoseEditWidget::updatePreview); //connect(m_document, &SkeletonDocument::resultRigChanged, tetrapodPoseEditWidget, &TetrapodPoseEditWidget::updatePreview);
@ -893,7 +893,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
initShortCuts(this, m_graphicsWidget); initShortCuts(this, m_graphicsWidget);
connect(this, &SkeletonDocumentWindow::initialized, m_document, &Document::uiReady); connect(this, &DocumentWindow::initialized, m_document, &Document::uiReady);
QTimer *timer = new QTimer(this); QTimer *timer = new QTimer(this);
timer->setInterval(250); timer->setInterval(250);
@ -906,15 +906,15 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
timer->start(); timer->start();
} }
SkeletonDocumentWindow *SkeletonDocumentWindow::createDocumentWindow() DocumentWindow *DocumentWindow::createDocumentWindow()
{ {
SkeletonDocumentWindow *documentWindow = new SkeletonDocumentWindow(); DocumentWindow *documentWindow = new DocumentWindow();
documentWindow->setAttribute(Qt::WA_DeleteOnClose); documentWindow->setAttribute(Qt::WA_DeleteOnClose);
documentWindow->showMaximized(); documentWindow->showMaximized();
return documentWindow; return documentWindow;
} }
void SkeletonDocumentWindow::closeEvent(QCloseEvent *event) void DocumentWindow::closeEvent(QCloseEvent *event)
{ {
if (m_documentSaved) { if (m_documentSaved) {
event->accept(); event->accept();
@ -931,21 +931,21 @@ void SkeletonDocumentWindow::closeEvent(QCloseEvent *event)
event->ignore(); event->ignore();
} }
void SkeletonDocumentWindow::setCurrentFilename(const QString &filename) void DocumentWindow::setCurrentFilename(const QString &filename)
{ {
m_currentFilename = filename; m_currentFilename = filename;
m_documentSaved = true; m_documentSaved = true;
updateTitle(); updateTitle();
} }
void SkeletonDocumentWindow::updateTitle() void DocumentWindow::updateTitle()
{ {
QString appName = APP_NAME; QString appName = APP_NAME;
QString appVer = APP_HUMAN_VER; QString appVer = APP_HUMAN_VER;
setWindowTitle(QString("%1 %2 %3%4").arg(appName).arg(appVer).arg(m_currentFilename).arg(m_documentSaved ? "" : "*")); setWindowTitle(QString("%1 %2 %3%4").arg(appName).arg(appVer).arg(m_currentFilename).arg(m_documentSaved ? "" : "*"));
} }
void SkeletonDocumentWindow::documentChanged() void DocumentWindow::documentChanged()
{ {
if (m_documentSaved) { if (m_documentSaved) {
m_documentSaved = false; m_documentSaved = false;
@ -953,12 +953,12 @@ void SkeletonDocumentWindow::documentChanged()
} }
} }
void SkeletonDocumentWindow::newWindow() void DocumentWindow::newWindow()
{ {
SkeletonDocumentWindow::createDocumentWindow(); DocumentWindow::createDocumentWindow();
} }
void SkeletonDocumentWindow::newDocument() void DocumentWindow::newDocument()
{ {
if (!m_documentSaved) { if (!m_documentSaved) {
QMessageBox::StandardButton answer = QMessageBox::question(this, QMessageBox::StandardButton answer = QMessageBox::question(this,
@ -973,7 +973,7 @@ void SkeletonDocumentWindow::newDocument()
m_document->saveSnapshot(); m_document->saveSnapshot();
} }
void SkeletonDocumentWindow::saveAs() void DocumentWindow::saveAs()
{ {
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
tr("Dust3D Document (*.ds3)")); tr("Dust3D Document (*.ds3)"));
@ -983,50 +983,50 @@ void SkeletonDocumentWindow::saveAs()
saveTo(filename); saveTo(filename);
} }
void SkeletonDocumentWindow::saveAll() void DocumentWindow::saveAll()
{ {
for (auto &window: g_documentWindows) { for (auto &window: g_documentWindows) {
window->save(); window->save();
} }
} }
void SkeletonDocumentWindow::viewSource() void DocumentWindow::viewSource()
{ {
QString url = APP_REPOSITORY_URL; QString url = APP_REPOSITORY_URL;
qDebug() << "viewSource:" << url; qDebug() << "viewSource:" << url;
QDesktopServices::openUrl(QUrl(url)); QDesktopServices::openUrl(QUrl(url));
} }
void SkeletonDocumentWindow::about() void DocumentWindow::about()
{ {
SkeletonDocumentWindow::showAbout(); DocumentWindow::showAbout();
} }
void SkeletonDocumentWindow::reportIssues() void DocumentWindow::reportIssues()
{ {
QString url = APP_ISSUES_URL; QString url = APP_ISSUES_URL;
qDebug() << "reportIssues:" << url; qDebug() << "reportIssues:" << url;
QDesktopServices::openUrl(QUrl(url)); QDesktopServices::openUrl(QUrl(url));
} }
void SkeletonDocumentWindow::seeReferenceGuide() void DocumentWindow::seeReferenceGuide()
{ {
QString url = APP_REFERENCE_GUIDE_URL; QString url = APP_REFERENCE_GUIDE_URL;
qDebug() << "referenceGuide:" << url; qDebug() << "referenceGuide:" << url;
QDesktopServices::openUrl(QUrl(url)); QDesktopServices::openUrl(QUrl(url));
} }
void SkeletonDocumentWindow::seeAcknowlegements() void DocumentWindow::seeAcknowlegements()
{ {
SkeletonDocumentWindow::showAcknowlegements(); DocumentWindow::showAcknowlegements();
} }
void SkeletonDocumentWindow::seeContributors() void DocumentWindow::seeContributors()
{ {
SkeletonDocumentWindow::showContributors(); DocumentWindow::showContributors();
} }
void SkeletonDocumentWindow::initLockButton(QPushButton *button) void DocumentWindow::initLockButton(QPushButton *button)
{ {
QFont font; QFont font;
font.setWeight(QFont::Light); font.setWeight(QFont::Light);
@ -1039,12 +1039,12 @@ void SkeletonDocumentWindow::initLockButton(QPushButton *button)
button->setFocusPolicy(Qt::NoFocus); button->setFocusPolicy(Qt::NoFocus);
} }
SkeletonDocumentWindow::~SkeletonDocumentWindow() DocumentWindow::~DocumentWindow()
{ {
g_documentWindows.erase(this); g_documentWindows.erase(this);
} }
void SkeletonDocumentWindow::showEvent(QShowEvent *event) void DocumentWindow::showEvent(QShowEvent *event)
{ {
QMainWindow::showEvent(event); QMainWindow::showEvent(event);
if (m_firstShow) { if (m_firstShow) {
@ -1056,12 +1056,12 @@ void SkeletonDocumentWindow::showEvent(QShowEvent *event)
} }
} }
void SkeletonDocumentWindow::mousePressEvent(QMouseEvent *event) void DocumentWindow::mousePressEvent(QMouseEvent *event)
{ {
QMainWindow::mousePressEvent(event); QMainWindow::mousePressEvent(event);
} }
void SkeletonDocumentWindow::changeTurnaround() void DocumentWindow::changeTurnaround()
{ {
QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(), QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(),
tr("Image Files (*.png *.jpg *.bmp)")).trimmed(); tr("Image Files (*.png *.jpg *.bmp)")).trimmed();
@ -1073,12 +1073,12 @@ void SkeletonDocumentWindow::changeTurnaround()
m_document->updateTurnaround(image); m_document->updateTurnaround(image);
} }
void SkeletonDocumentWindow::save() void DocumentWindow::save()
{ {
saveTo(m_currentFilename); saveTo(m_currentFilename);
} }
void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename) void DocumentWindow::saveTo(const QString &saveAsFilename)
{ {
QString filename = saveAsFilename; QString filename = saveAsFilename;
@ -1111,6 +1111,7 @@ void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename)
ds3Writer.add("canvas.png", "asset", &imageByteArray); ds3Writer.add("canvas.png", "asset", &imageByteArray);
} }
std::set<QUuid> imageIds;
for (auto &material: snapshot.materials) { for (auto &material: snapshot.materials) {
for (auto &layer: material.second) { for (auto &layer: material.second) {
for (auto &mapItem: layer.second) { for (auto &mapItem: layer.second) {
@ -1118,19 +1119,31 @@ void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename)
if (findImageIdString == mapItem.end()) if (findImageIdString == mapItem.end())
continue; continue;
QUuid imageId = QUuid(findImageIdString->second); QUuid imageId = QUuid(findImageIdString->second);
const QImage *image = ImageForever::get(imageId); imageIds.insert(imageId);
if (nullptr == image)
continue;
QByteArray imageByteArray;
QBuffer pngBuffer(&imageByteArray);
pngBuffer.open(QIODevice::WriteOnly);
image->save(&pngBuffer, "PNG");
if (imageByteArray.size() > 0)
ds3Writer.add("images/" + imageId.toString() + ".png", "asset", &imageByteArray);
} }
} }
} }
for (auto &pose: snapshot.poses) {
auto findCanvasImageId = pose.first.find("canvasImageId");
if (findCanvasImageId != pose.first.end()) {
QUuid imageId = QUuid(findCanvasImageId->second);
imageIds.insert(imageId);
}
}
for (const auto &imageId: imageIds) {
const QImage *image = ImageForever::get(imageId);
if (nullptr == image)
continue;
QByteArray imageByteArray;
QBuffer pngBuffer(&imageByteArray);
pngBuffer.open(QIODevice::WriteOnly);
image->save(&pngBuffer, "PNG");
if (imageByteArray.size() > 0)
ds3Writer.add("images/" + imageId.toString() + ".png", "asset", &imageByteArray);
}
if (ds3Writer.save(filename)) { if (ds3Writer.save(filename)) {
setCurrentFilename(filename); setCurrentFilename(filename);
} }
@ -1138,7 +1151,7 @@ void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename)
QApplication::restoreOverrideCursor(); QApplication::restoreOverrideCursor();
} }
void SkeletonDocumentWindow::open() void DocumentWindow::open()
{ {
if (!m_documentSaved) { if (!m_documentSaved) {
QMessageBox::StandardButton answer = QMessageBox::question(this, QMessageBox::StandardButton answer = QMessageBox::question(this,
@ -1202,7 +1215,7 @@ void SkeletonDocumentWindow::open()
setCurrentFilename(filename); setCurrentFilename(filename);
} }
void SkeletonDocumentWindow::showAdvanceSetting() void DocumentWindow::showAdvanceSetting()
{ {
if (nullptr == m_advanceSettingWidget) { if (nullptr == m_advanceSettingWidget) {
m_advanceSettingWidget = new AdvanceSettingWidget(m_document, this); m_advanceSettingWidget = new AdvanceSettingWidget(m_document, this);
@ -1211,7 +1224,7 @@ void SkeletonDocumentWindow::showAdvanceSetting()
m_advanceSettingWidget->raise(); m_advanceSettingWidget->raise();
} }
void SkeletonDocumentWindow::exportObjResult() void DocumentWindow::exportObjResult()
{ {
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
tr("Wavefront (*.obj)")); tr("Wavefront (*.obj)"));
@ -1227,13 +1240,13 @@ void SkeletonDocumentWindow::exportObjResult()
QApplication::restoreOverrideCursor(); QApplication::restoreOverrideCursor();
} }
void SkeletonDocumentWindow::showExportPreview() void DocumentWindow::showExportPreview()
{ {
if (nullptr == m_exportPreviewWidget) { if (nullptr == m_exportPreviewWidget) {
m_exportPreviewWidget = new ExportPreviewWidget(m_document, this); m_exportPreviewWidget = new ExportPreviewWidget(m_document, this);
connect(m_exportPreviewWidget, &ExportPreviewWidget::regenerate, m_document, &Document::regenerateMesh); connect(m_exportPreviewWidget, &ExportPreviewWidget::regenerate, m_document, &Document::regenerateMesh);
connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsGlb, this, &SkeletonDocumentWindow::exportGlbResult); connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsGlb, this, &DocumentWindow::exportGlbResult);
connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsFbx, this, &SkeletonDocumentWindow::exportFbxResult); connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsFbx, this, &DocumentWindow::exportFbxResult);
connect(m_document, &Document::resultMeshChanged, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner); connect(m_document, &Document::resultMeshChanged, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner);
connect(m_document, &Document::exportReady, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner); connect(m_document, &Document::exportReady, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner);
connect(m_document, &Document::resultTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview); connect(m_document, &Document::resultTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
@ -1244,7 +1257,7 @@ void SkeletonDocumentWindow::showExportPreview()
m_exportPreviewWidget->raise(); m_exportPreviewWidget->raise();
} }
void SkeletonDocumentWindow::exportFbxResult() void DocumentWindow::exportFbxResult()
{ {
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
tr("Autodesk FBX (.fbx)")); tr("Autodesk FBX (.fbx)"));
@ -1270,7 +1283,7 @@ void SkeletonDocumentWindow::exportFbxResult()
QApplication::restoreOverrideCursor(); QApplication::restoreOverrideCursor();
} }
void SkeletonDocumentWindow::exportGlbResult() void DocumentWindow::exportGlbResult()
{ {
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
tr("glTF Binary Format (.glb)")); tr("glTF Binary Format (.glb)"));
@ -1296,7 +1309,7 @@ void SkeletonDocumentWindow::exportGlbResult()
QApplication::restoreOverrideCursor(); QApplication::restoreOverrideCursor();
} }
void SkeletonDocumentWindow::updateXlockButtonState() void DocumentWindow::updateXlockButtonState()
{ {
if (m_document->xlocked) if (m_document->xlocked)
m_xlockButton->setStyleSheet("QPushButton {color: #252525}"); m_xlockButton->setStyleSheet("QPushButton {color: #252525}");
@ -1304,7 +1317,7 @@ void SkeletonDocumentWindow::updateXlockButtonState()
m_xlockButton->setStyleSheet("QPushButton {color: #fc6621}"); m_xlockButton->setStyleSheet("QPushButton {color: #fc6621}");
} }
void SkeletonDocumentWindow::updateYlockButtonState() void DocumentWindow::updateYlockButtonState()
{ {
if (m_document->ylocked) if (m_document->ylocked)
m_ylockButton->setStyleSheet("QPushButton {color: #252525}"); m_ylockButton->setStyleSheet("QPushButton {color: #252525}");
@ -1312,7 +1325,7 @@ void SkeletonDocumentWindow::updateYlockButtonState()
m_ylockButton->setStyleSheet("QPushButton {color: #2a5aac}"); m_ylockButton->setStyleSheet("QPushButton {color: #2a5aac}");
} }
void SkeletonDocumentWindow::updateZlockButtonState() void DocumentWindow::updateZlockButtonState()
{ {
if (m_document->zlocked) if (m_document->zlocked)
m_zlockButton->setStyleSheet("QPushButton {color: #252525}"); m_zlockButton->setStyleSheet("QPushButton {color: #252525}");
@ -1320,7 +1333,7 @@ void SkeletonDocumentWindow::updateZlockButtonState()
m_zlockButton->setStyleSheet("QPushButton {color: #aaebc4}"); m_zlockButton->setStyleSheet("QPushButton {color: #aaebc4}");
} }
void SkeletonDocumentWindow::updateRadiusLockButtonState() void DocumentWindow::updateRadiusLockButtonState()
{ {
if (m_document->radiusLocked) if (m_document->radiusLocked)
m_radiusLockButton->setStyleSheet("QPushButton {color: #252525}"); m_radiusLockButton->setStyleSheet("QPushButton {color: #252525}");
@ -1328,7 +1341,7 @@ void SkeletonDocumentWindow::updateRadiusLockButtonState()
m_radiusLockButton->setStyleSheet("QPushButton {color: " + Theme::white.name() + "}"); m_radiusLockButton->setStyleSheet("QPushButton {color: " + Theme::white.name() + "}");
} }
void SkeletonDocumentWindow::updateRigWeightRenderWidget() void DocumentWindow::updateRigWeightRenderWidget()
{ {
MeshLoader *resultRigWeightMesh = m_document->takeResultRigWeightMesh(); MeshLoader *resultRigWeightMesh = m_document->takeResultRigWeightMesh();
if (nullptr == resultRigWeightMesh) { if (nullptr == resultRigWeightMesh) {
@ -1340,12 +1353,12 @@ void SkeletonDocumentWindow::updateRigWeightRenderWidget()
} }
} }
void SkeletonDocumentWindow::registerDialog(QWidget *widget) void DocumentWindow::registerDialog(QWidget *widget)
{ {
m_dialogs.push_back(widget); m_dialogs.push_back(widget);
} }
void SkeletonDocumentWindow::unregisterDialog(QWidget *widget) void DocumentWindow::unregisterDialog(QWidget *widget)
{ {
m_dialogs.erase(std::remove(m_dialogs.begin(), m_dialogs.end(), widget), m_dialogs.end()); m_dialogs.erase(std::remove(m_dialogs.begin(), m_dialogs.end(), widget), m_dialogs.end());
} }

View File

@ -17,15 +17,15 @@
class SkeletonGraphicsWidget; class SkeletonGraphicsWidget;
class SkeletonDocumentWindow : public QMainWindow class DocumentWindow : public QMainWindow
{ {
Q_OBJECT Q_OBJECT
signals: signals:
void initialized(); void initialized();
public: public:
SkeletonDocumentWindow(); DocumentWindow();
~SkeletonDocumentWindow(); ~DocumentWindow();
static SkeletonDocumentWindow *createDocumentWindow(); static DocumentWindow *createDocumentWindow();
static void showAcknowlegements(); static void showAcknowlegements();
static void showContributors(); static void showContributors();
static void showAbout(); static void showAbout();

View File

@ -8,50 +8,7 @@ GenericPoser::GenericPoser(const std::vector<RiggerBone> &bones) :
void GenericPoser::commit() void GenericPoser::commit()
{ {
for (const auto &item: parameters()) { // TODO:
int boneIndex = findBoneIndex(item.first);
if (-1 == boneIndex) {
continue;
}
auto findPitchResult = item.second.find("pitch");
auto findYawResult = item.second.find("yaw");
auto findRollResult = item.second.find("roll");
if (findPitchResult != item.second.end() ||
findYawResult != item.second.end() ||
findRollResult != item.second.end()) {
float yawAngle = valueOfKeyInMapOrEmpty(item.second, "yaw").toFloat();
if (item.first.startsWith("Left")) {
yawAngle = -yawAngle;
}
QQuaternion rotation = eulerAnglesToQuaternion(valueOfKeyInMapOrEmpty(item.second, "pitch").toFloat(),
yawAngle,
valueOfKeyInMapOrEmpty(item.second, "roll").toFloat());
m_jointNodeTree.updateRotation(boneIndex, rotation);
continue;
}
auto findIntersectionResult = item.second.find("intersection");
if (findIntersectionResult != item.second.end()) {
float intersectionAngle = valueOfKeyInMapOrEmpty(item.second, "intersection").toFloat();
const RiggerBone &bone = bones()[boneIndex];
QVector3D axis = bone.baseNormal;
QQuaternion rotation = QQuaternion::fromAxisAndAngle(axis, intersectionAngle);
m_jointNodeTree.updateRotation(boneIndex, rotation);
continue;
}
auto findXResult = item.second.find("x");
auto findYResult = item.second.find("y");
auto findZResult = item.second.find("z");
if (findXResult != item.second.end() ||
findYResult != item.second.end() ||
findZResult != item.second.end()) {
float x = valueOfKeyInMapOrEmpty(item.second, "x").toFloat();
float y = valueOfKeyInMapOrEmpty(item.second, "y").toFloat();
float z = valueOfKeyInMapOrEmpty(item.second, "z").toFloat();
QVector3D translation = {x, y, z};
m_jointNodeTree.addTranslation(boneIndex, translation);
continue;
}
}
Poser::commit(); Poser::commit();
} }

View File

@ -302,7 +302,7 @@ bool GenericRigger::rig()
m_resultBones.push_back(RiggerBone()); m_resultBones.push_back(RiggerBone());
RiggerBone &bodyBone = m_resultBones.back(); RiggerBone &bodyBone = m_resultBones.back();
bodyBone.index = m_resultBones.size() - 1; bodyBone.index = m_resultBones.size() - 1;
bodyBone.name = "Body"; bodyBone.name = Rigger::rootBoneName;
bodyBone.headPosition = QVector3D(0, 0, 0); bodyBone.headPosition = QVector3D(0, 0, 0);
bodyBone.hasButton = true; bodyBone.hasButton = true;
bodyBone.button = {spineNodes.size(), 0}; bodyBone.button = {spineNodes.size(), 0};
@ -378,8 +378,8 @@ bool GenericRigger::rig()
//qDebug() << spineBone.name << "head:" << spineBone.headPosition << "tail:" << spineBone.tailPosition; //qDebug() << spineBone.name << "head:" << spineBone.headPosition << "tail:" << spineBone.tailPosition;
if (1 == spineGenerateOrder) { if (1 == spineGenerateOrder) {
m_resultBones[boneIndexMap["Body"]].tailPosition = spineBone.headPosition; m_resultBones[boneIndexMap[Rigger::rootBoneName]].tailPosition = spineBone.headPosition;
m_resultBones[boneIndexMap["Body"]].children.push_back(spineBone.index); m_resultBones[boneIndexMap[Rigger::rootBoneName]].children.push_back(spineBone.index);
} else { } else {
m_resultBones[boneIndexMap["Spine" + QString::number(spineGenerateOrder - 1)]].tailPosition = spineBone.headPosition; m_resultBones[boneIndexMap["Spine" + QString::number(spineGenerateOrder - 1)]].tailPosition = spineBone.headPosition;
m_resultBones[boneIndexMap["Spine" + QString::number(spineGenerateOrder - 1)]].children.push_back(spineBone.index); m_resultBones[boneIndexMap["Spine" + QString::number(spineGenerateOrder - 1)]].children.push_back(spineBone.index);
@ -396,7 +396,7 @@ bool GenericRigger::rig()
ribBone.headPosition = spineBoneHeadPosition; ribBone.headPosition = spineBoneHeadPosition;
boneIndexMap[ribBone.name] = ribBone.index; boneIndexMap[ribBone.name] = ribBone.index;
if (1 == spineGenerateOrder) { if (1 == spineGenerateOrder) {
m_resultBones[boneIndexMap["Body"]].children.push_back(ribBone.index); m_resultBones[boneIndexMap[Rigger::rootBoneName]].children.push_back(ribBone.index);
} else { } else {
m_resultBones[boneIndexMap["Spine" + QString::number(spineGenerateOrder)]].children.push_back(ribBone.index); m_resultBones[boneIndexMap["Spine" + QString::number(spineGenerateOrder)]].children.push_back(ribBone.index);
} }

View File

@ -46,7 +46,7 @@ int main(int argc, char ** argv)
font.setBold(false); font.setBold(false);
QApplication::setFont(font); QApplication::setFont(font);
SkeletonDocumentWindow::createDocumentWindow(); DocumentWindow::createDocumentWindow();
return app.exec(); return app.exec();
} }

View File

@ -22,14 +22,21 @@ MotionManageWidget::MotionManageWidget(const Document *document, QWidget *parent
connect(m_motionListWidget, &MotionListWidget::modifyMotion, this, &MotionManageWidget::showMotionDialog); connect(m_motionListWidget, &MotionListWidget::modifyMotion, this, &MotionManageWidget::showMotionDialog);
InfoLabel *infoLabel = new InfoLabel; InfoLabel *infoLabel = new InfoLabel;
infoLabel->setText(tr("Missing Rig"));
infoLabel->show(); infoLabel->show();
auto refreshInfoLabel = [=]() { auto refreshInfoLabel = [=]() {
if (m_document->currentRigSucceed()) { if (m_document->currentRigSucceed()) {
infoLabel->hide(); if (m_document->rigType == RigType::Tetrapod) {
addMotionButton->show(); infoLabel->setText("");
infoLabel->hide();
addMotionButton->show();
} else {
infoLabel->setText(tr("Motion editor doesn't support this rig type yet: ") + RigTypeToDispName(m_document->rigType));
infoLabel->show();
addMotionButton->hide();
}
} else { } else {
infoLabel->setText(tr("Missing Rig"));
infoLabel->show(); infoLabel->show();
addMotionButton->hide(); addMotionButton->hide();
} }

401
src/posedocument.cpp Normal file
View File

@ -0,0 +1,401 @@
#include <QDebug>
#include "posedocument.h"
#include "rigger.h"
#include "util.h"
constexpr float PoseDocument::m_nodeRadius = 0.01;
constexpr float PoseDocument::m_groundPlaneHalfThickness = m_nodeRadius / 4;
bool PoseDocument::hasPastableNodesInClipboard() const
{
return false;
}
bool PoseDocument::originSettled() const
{
return false;
}
bool PoseDocument::isNodeEditable(QUuid nodeId) const
{
return true;
}
bool PoseDocument::isEdgeEditable(QUuid edgeId) const
{
return true;
}
void PoseDocument::copyNodes(std::set<QUuid> nodeIdSet) const
{
// TODO:
}
void PoseDocument::saveHistoryItem()
{
PoseHistoryItem item;
toParameters(item.parameters);
m_undoItems.push_back(item);
}
bool PoseDocument::undoable() const
{
return m_undoItems.size() >= 2;
}
bool PoseDocument::redoable() const
{
return !m_redoItems.empty();
}
void PoseDocument::undo()
{
if (!undoable())
return;
m_redoItems.push_back(m_undoItems.back());
m_undoItems.pop_back();
const auto &item = m_undoItems.back();
fromParameters(&m_riggerBones, item.parameters);
}
void PoseDocument::redo()
{
if (m_redoItems.empty())
return;
m_undoItems.push_back(m_redoItems.back());
const auto &item = m_redoItems.back();
fromParameters(&m_riggerBones, item.parameters);
m_redoItems.pop_back();
}
void PoseDocument::paste()
{
}
void PoseDocument::updateTurnaround(const QImage &image)
{
turnaround = image;
emit turnaroundChanged();
}
void PoseDocument::reset()
{
nodeMap.clear();
edgeMap.clear();
partMap.clear();
m_boneNameToIdsMap.clear();
m_bonesPartId = QUuid();
m_groundPartId = QUuid();
m_groundEdgeId = QUuid();
emit cleanup();
emit parametersChanged();
}
void PoseDocument::fromParameters(const std::vector<RiggerBone> *rigBones,
const std::map<QString, std::map<QString, QString>> &parameters)
{
if (nullptr == rigBones || rigBones->empty()) {
m_riggerBones.clear();
return;
}
if (&m_riggerBones != rigBones)
m_riggerBones = *rigBones;
QVector3D rootTranslation;
std::vector<RiggerBone> bones = *rigBones;
for (auto &bone: bones) {
const auto findParameterResult = parameters.find(bone.name);
if (findParameterResult == parameters.end())
continue;
const auto &map = findParameterResult->second;
{
auto findXResult = map.find("fromX");
auto findYResult = map.find("fromY");
auto findZResult = map.find("fromZ");
if (findXResult != map.end() ||
findYResult != map.end() ||
findZResult != map.end()) {
bone.headPosition = {
valueOfKeyInMapOrEmpty(map, "fromX").toFloat(),
valueOfKeyInMapOrEmpty(map, "fromY").toFloat(),
valueOfKeyInMapOrEmpty(map, "fromZ").toFloat()
};
}
}
{
auto findXResult = map.find("toX");
auto findYResult = map.find("toY");
auto findZResult = map.find("toZ");
if (findXResult != map.end() ||
findYResult != map.end() ||
findZResult != map.end()) {
bone.tailPosition = {
valueOfKeyInMapOrEmpty(map, "toX").toFloat(),
valueOfKeyInMapOrEmpty(map, "toY").toFloat(),
valueOfKeyInMapOrEmpty(map, "toZ").toFloat()
};
}
}
}
const auto findRoot = parameters.find(Rigger::rootBoneName);
if (findRoot != parameters.end()) {
const auto &map = findRoot->second;
{
auto findXResult = map.find("translateX");
auto findYResult = map.find("translateY");
auto findZResult = map.find("translateZ");
if (findXResult != map.end() ||
findYResult != map.end() ||
findZResult != map.end()) {
rootTranslation = {
valueOfKeyInMapOrEmpty(map, "translateX").toFloat(),
valueOfKeyInMapOrEmpty(map, "translateY").toFloat(),
valueOfKeyInMapOrEmpty(map, "translateZ").toFloat()
};
}
}
}
updateRigBones(&bones, rootTranslation);
}
void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const QVector3D &rootTranslation)
{
reset();
if (nullptr == rigBones || rigBones->empty()) {
return;
}
std::set<QUuid> newAddedNodeIds;
std::set<QUuid> newAddedEdgeIds;
m_bonesPartId = QUuid::createUuid();
auto &bonesPart = partMap[m_bonesPartId];
bonesPart.id = m_bonesPartId;
std::vector<std::pair<int, int>> edgePairs;
for (size_t i = 1; i < rigBones->size(); ++i) {
const auto &bone = (*rigBones)[i];
for (const auto &child: bone.children) {
edgePairs.push_back({i, child});
}
}
std::map<int, QUuid> boneIndexToHeadNodeIdMap;
for (const auto &edgePair: edgePairs) {
QUuid firstNodeId, secondNodeId;
auto findFirst = boneIndexToHeadNodeIdMap.find(edgePair.first);
if (findFirst == boneIndexToHeadNodeIdMap.end()) {
const auto &bone = (*rigBones)[edgePair.first];
SkeletonNode node;
node.partId = m_bonesPartId;
node.id = QUuid::createUuid();
node.setRadius(m_nodeRadius);
node.x = bone.headPosition.x() + 0.5;
node.y = -bone.headPosition.y() + 0.5;
node.z = -bone.headPosition.z() + 1;
nodeMap[node.id] = node;
newAddedNodeIds.insert(node.id);
boneIndexToHeadNodeIdMap[edgePair.first] = node.id;
firstNodeId = node.id;
} else {
firstNodeId = findFirst->second;
}
auto findSecond = boneIndexToHeadNodeIdMap.find(edgePair.second);
if (findSecond == boneIndexToHeadNodeIdMap.end()) {
const auto &bone = (*rigBones)[edgePair.second];
SkeletonNode node;
node.partId = m_bonesPartId;
node.id = QUuid::createUuid();
node.setRadius(m_nodeRadius);
node.x = bone.headPosition.x() + 0.5;
node.y = -bone.headPosition.y() + 0.5;
node.z = -bone.headPosition.z() + 1;
nodeMap[node.id] = node;
newAddedNodeIds.insert(node.id);
boneIndexToHeadNodeIdMap[edgePair.second] = node.id;
secondNodeId = node.id;
} else {
secondNodeId = findSecond->second;
}
SkeletonEdge edge;
edge.partId = m_bonesPartId;
edge.id = QUuid::createUuid();
edge.nodeIds.push_back(firstNodeId);
edge.nodeIds.push_back(secondNodeId);
edgeMap[edge.id] = edge;
newAddedEdgeIds.insert(edge.id);
nodeMap[firstNodeId].edgeIds.push_back(edge.id);
nodeMap[secondNodeId].edgeIds.push_back(edge.id);
}
for (size_t i = 0; i < rigBones->size(); ++i) {
const auto &bone = (*rigBones)[i];
if (bone.children.empty()) {
const QUuid &firstNodeId = boneIndexToHeadNodeIdMap[i];
SkeletonNode node;
node.partId = m_bonesPartId;
node.id = QUuid::createUuid();
node.setRadius(m_nodeRadius / 2);
node.x = bone.tailPosition.x() + 0.5;
node.y = -bone.tailPosition.y() + 0.5;
node.z = -bone.tailPosition.z() + 1;
nodeMap[node.id] = node;
newAddedNodeIds.insert(node.id);
m_boneNameToIdsMap[bone.name] = {firstNodeId, node.id};
SkeletonEdge edge;
edge.partId = m_bonesPartId;
edge.id = QUuid::createUuid();
edge.nodeIds.push_back(firstNodeId);
edge.nodeIds.push_back(node.id);
edgeMap[edge.id] = edge;
newAddedEdgeIds.insert(edge.id);
nodeMap[firstNodeId].edgeIds.push_back(edge.id);
nodeMap[node.id].edgeIds.push_back(edge.id);
continue;
}
for (const auto &child: bone.children) {
m_boneNameToIdsMap[bone.name] = {boneIndexToHeadNodeIdMap[i], boneIndexToHeadNodeIdMap[child]};
}
}
m_groundPartId = QUuid::createUuid();
auto &groundPart = partMap[m_groundPartId];
groundPart.id = m_groundPartId;
float groundY = findGroundY() + rootTranslation.y();
std::pair<QUuid, QUuid> groundNodesPair;
{
SkeletonNode node;
node.partId = m_groundPartId;
node.id = QUuid::createUuid();
node.setRadius(m_groundPlaneHalfThickness);
node.x = -100;
node.y = groundY + m_groundPlaneHalfThickness;
node.z = -100;
nodeMap[node.id] = node;
newAddedNodeIds.insert(node.id);
groundNodesPair.first = node.id;
}
{
SkeletonNode node;
node.partId = m_groundPartId;
node.id = QUuid::createUuid();
node.setRadius(m_groundPlaneHalfThickness);
node.x = 100;
node.y = groundY + m_groundPlaneHalfThickness;
node.z = 100;
nodeMap[node.id] = node;
newAddedNodeIds.insert(node.id);
groundNodesPair.second = node.id;
}
{
SkeletonEdge edge;
edge.partId = m_groundPartId;
edge.id = QUuid::createUuid();
edge.nodeIds.push_back(groundNodesPair.first);
edge.nodeIds.push_back(groundNodesPair.second);
edgeMap[edge.id] = edge;
m_groundEdgeId = edge.id;
newAddedEdgeIds.insert(edge.id);
nodeMap[groundNodesPair.first].edgeIds.push_back(edge.id);
nodeMap[groundNodesPair.second].edgeIds.push_back(edge.id);
}
for (const auto &nodeIt: newAddedNodeIds) {
emit nodeAdded(nodeIt);
}
for (const auto &edgeIt: newAddedEdgeIds) {
emit edgeAdded(edgeIt);
}
emit parametersChanged();
}
void PoseDocument::moveNodeBy(QUuid nodeId, float x, float y, float z)
{
auto it = nodeMap.find(nodeId);
if (it == nodeMap.end()) {
qDebug() << "Find node failed:" << nodeId;
return;
}
it->second.x += x;
it->second.y += y;
it->second.z += z;
emit nodeOriginChanged(it->first);
emit parametersChanged();
}
void PoseDocument::setNodeOrigin(QUuid nodeId, float x, float y, float z)
{
auto it = nodeMap.find(nodeId);
if (it == nodeMap.end()) {
qDebug() << "Find node failed:" << nodeId;
return;
}
it->second.x = x;
it->second.y = y;
it->second.z = z;
auto part = partMap.find(it->second.partId);
if (part != partMap.end())
part->second.dirty = true;
emit nodeOriginChanged(nodeId);
emit parametersChanged();
}
float PoseDocument::findGroundY() const
{
auto maxY = std::numeric_limits<float>::lowest();
for (const auto &nodeIt: nodeMap) {
if (nodeIt.second.partId != m_bonesPartId)
continue;
auto y = nodeIt.second.y + nodeIt.second.radius;
if (y > maxY)
maxY = y;
}
return maxY;
}
void PoseDocument::toParameters(std::map<QString, std::map<QString, QString>> &parameters) const
{
float translateY = 0;
auto findGroundEdge = edgeMap.find(m_groundEdgeId);
if (findGroundEdge != edgeMap.end()) {
const auto &nodeIds = findGroundEdge->second.nodeIds;
if (nodeIds.size() == 2) {
auto findFirstNode = nodeMap.find(nodeIds[0]);
auto findSecondNode = nodeMap.find(nodeIds[1]);
if (findFirstNode != nodeMap.end() && findSecondNode != nodeMap.end()) {
translateY = (findFirstNode->second.y + findSecondNode->second.y) / 2 -
(findGroundY() + m_groundPlaneHalfThickness);
}
}
}
if (!qFuzzyIsNull(translateY)) {
auto &boneParameter = parameters[Rigger::rootBoneName];
boneParameter["translateY"] = QString::number(translateY);
}
for (const auto &item: m_boneNameToIdsMap) {
auto &boneParameter = parameters[item.first];
const auto &boneNodeIdPair = item.second;
auto findFirstNode = nodeMap.find(boneNodeIdPair.first);
if (findFirstNode == nodeMap.end())
continue;
auto findSecondNode = nodeMap.find(boneNodeIdPair.second);
if (findSecondNode == nodeMap.end())
continue;
boneParameter["fromX"] = QString::number(findFirstNode->second.x - 0.5);
boneParameter["fromY"] = QString::number(0.5 - findFirstNode->second.y);
boneParameter["fromZ"] = QString::number(1.0 - findFirstNode->second.z);
boneParameter["toX"] = QString::number(findSecondNode->second.x - 0.5);
boneParameter["toY"] = QString::number(0.5 - findSecondNode->second.y);
boneParameter["toZ"] = QString::number(1.0 - findSecondNode->second.z);
}
}

66
src/posedocument.h Normal file
View File

@ -0,0 +1,66 @@
#ifndef DUST3D_POSE_DOCUMENT_H
#define DUST3D_POSE_DOCUMENT_H
#include <map>
#include <QString>
#include <deque>
#include "skeletondocument.h"
#include "rigger.h"
struct PoseHistoryItem
{
std::map<QString, std::map<QString, QString>> parameters;
};
class PoseDocument : public SkeletonDocument
{
Q_OBJECT
signals:
void turnaroundChanged();
void cleanup();
void nodeAdded(QUuid nodeId);
void edgeAdded(QUuid edgeId);
void nodeOriginChanged(QUuid nodeId);
void parametersChanged();
public:
bool undoable() const override;
bool redoable() const override;
bool hasPastableNodesInClipboard() const override;
bool originSettled() const override;
bool isNodeEditable(QUuid nodeId) const override;
bool isEdgeEditable(QUuid edgeId) const override;
void copyNodes(std::set<QUuid> nodeIdSet) const override;
void updateTurnaround(const QImage &image);
void updateRigBones(const std::vector<RiggerBone> *rigBones, const QVector3D &rootTranslation=QVector3D(0, 0, 0));
void reset();
void toParameters(std::map<QString, std::map<QString, QString>> &parameters) const;
void fromParameters(const std::vector<RiggerBone> *rigBones,
const std::map<QString, std::map<QString, QString>> &parameters);
public slots:
void saveHistoryItem();
void undo() override;
void redo() override;
void paste() override;
void moveNodeBy(QUuid nodeId, float x, float y, float z);
void setNodeOrigin(QUuid nodeId, float x, float y, float z);
float findGroundY() const;
public:
static const float m_nodeRadius;
static const float m_groundPlaneHalfThickness;
private:
std::map<QString, std::pair<QUuid, QUuid>> m_boneNameToIdsMap;
QUuid m_groundPartId;
QUuid m_bonesPartId;
QUuid m_groundEdgeId;
std::deque<PoseHistoryItem> m_undoItems;
std::deque<PoseHistoryItem> m_redoItems;
std::vector<RiggerBone> m_riggerBones;
};
#endif

View File

@ -6,15 +6,21 @@
#include <QWidgetAction> #include <QWidgetAction>
#include <QLineEdit> #include <QLineEdit>
#include <QMessageBox> #include <QMessageBox>
#include <QFileDialog>
#include "theme.h" #include "theme.h"
#include "poseeditwidget.h" #include "poseeditwidget.h"
#include "floatnumberwidget.h" #include "floatnumberwidget.h"
#include "version.h" #include "version.h"
#include "poserconstruct.h" #include "poserconstruct.h"
#include "graphicscontainerwidget.h"
#include "documentwindow.h"
#include "shortcuts.h"
#include "imageforever.h"
PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) : PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
QDialog(parent), QDialog(parent),
m_document(document) m_document(document),
m_poseDocument(new PoseDocument)
{ {
m_posePreviewManager = new PosePreviewManager(); m_posePreviewManager = new PosePreviewManager();
connect(m_posePreviewManager, &PosePreviewManager::renderDone, [=]() { connect(m_posePreviewManager, &PosePreviewManager::renderDone, [=]() {
@ -29,15 +35,53 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
m_previewWidget->updateMesh(m_posePreviewManager->takeResultPreviewMesh()); m_previewWidget->updateMesh(m_posePreviewManager->takeResultPreviewMesh());
}); });
m_previewWidget = new ModelWidget(this); SkeletonGraphicsWidget *graphicsWidget = new SkeletonGraphicsWidget(m_poseDocument);
m_previewWidget->setMinimumSize(128, 128); graphicsWidget->setNodePositionModifyOnly(true);
m_previewWidget->resize(384, 384); m_poseGraphicsWidget = graphicsWidget;
m_previewWidget->move(-64, -64+22);
initShortCuts(this, graphicsWidget);
GraphicsContainerWidget *containerWidget = new GraphicsContainerWidget;
containerWidget->setGraphicsWidget(graphicsWidget);
QGridLayout *containerLayout = new QGridLayout;
containerLayout->setSpacing(0);
containerLayout->setContentsMargins(1, 0, 0, 0);
containerLayout->addWidget(graphicsWidget);
containerWidget->setLayout(containerLayout);
containerWidget->setMinimumSize(400, 400);
m_previewWidget = new ModelWidget(containerWidget);
m_previewWidget->setAttribute(Qt::WA_TransparentForMouseEvents);
m_previewWidget->setMinimumSize(DocumentWindow::m_modelRenderWidgetInitialSize, DocumentWindow::m_modelRenderWidgetInitialSize);
m_previewWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_previewWidget->move(DocumentWindow::m_modelRenderWidgetInitialX, DocumentWindow::m_modelRenderWidgetInitialY);
m_poseGraphicsWidget->setModelWidget(m_previewWidget);
containerWidget->setModelWidget(m_previewWidget);
connect(containerWidget, &GraphicsContainerWidget::containerSizeChanged,
graphicsWidget, &SkeletonGraphicsWidget::canvasResized);
connect(graphicsWidget, &SkeletonGraphicsWidget::moveNodeBy, m_poseDocument, &PoseDocument::moveNodeBy);
connect(graphicsWidget, &SkeletonGraphicsWidget::setNodeOrigin, m_poseDocument, &PoseDocument::setNodeOrigin);
connect(graphicsWidget, &SkeletonGraphicsWidget::groupOperationAdded, m_poseDocument, &PoseDocument::saveHistoryItem);
connect(graphicsWidget, &SkeletonGraphicsWidget::undo, m_poseDocument, &PoseDocument::undo);
connect(graphicsWidget, &SkeletonGraphicsWidget::redo, m_poseDocument, &PoseDocument::redo);
connect(m_poseDocument, &PoseDocument::cleanup, graphicsWidget, &SkeletonGraphicsWidget::removeAllContent);
connect(m_poseDocument, &PoseDocument::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded);
connect(m_poseDocument, &PoseDocument::edgeAdded, graphicsWidget, &SkeletonGraphicsWidget::edgeAdded);
connect(m_poseDocument, &PoseDocument::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged);
connect(m_poseDocument, &PoseDocument::parametersChanged, this, [&]() {
m_parameters.clear();
m_poseDocument->toParameters(m_parameters);
emit parametersAdjusted();
});
QHBoxLayout *paramtersLayout = new QHBoxLayout; QHBoxLayout *paramtersLayout = new QHBoxLayout;
paramtersLayout->setContentsMargins(0, 480, 0, 0); paramtersLayout->addWidget(containerWidget);
paramtersLayout->addStretch();
paramtersLayout->addSpacing(20);
m_nameEdit = new QLineEdit; m_nameEdit = new QLineEdit;
m_nameEdit->setFixedWidth(200); m_nameEdit->setFixedWidth(200);
@ -49,9 +93,17 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
connect(saveButton, &QPushButton::clicked, this, &PoseEditWidget::save); connect(saveButton, &QPushButton::clicked, this, &PoseEditWidget::save);
saveButton->setDefault(true); saveButton->setDefault(true);
QPushButton *changeReferenceSheet = new QPushButton(tr("Change Reference Sheet..."));
connect(changeReferenceSheet, &QPushButton::clicked, this, &PoseEditWidget::changeTurnaround);
connect(m_poseDocument, &PoseDocument::turnaroundChanged,
graphicsWidget, &SkeletonGraphicsWidget::turnaroundChanged);
connect(m_document, &Document::resultRigChanged, this, &PoseEditWidget::updatePoseDocument);
QHBoxLayout *baseInfoLayout = new QHBoxLayout; QHBoxLayout *baseInfoLayout = new QHBoxLayout;
baseInfoLayout->addWidget(new QLabel(tr("Name"))); baseInfoLayout->addWidget(new QLabel(tr("Name")));
baseInfoLayout->addWidget(m_nameEdit); baseInfoLayout->addWidget(m_nameEdit);
baseInfoLayout->addWidget(changeReferenceSheet);
baseInfoLayout->addStretch(); baseInfoLayout->addStretch();
baseInfoLayout->addWidget(saveButton); baseInfoLayout->addWidget(saveButton);
@ -62,7 +114,7 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
setLayout(mainLayout); setLayout(mainLayout);
connect(m_document, &Document::resultRigChanged, this, &PoseEditWidget::updatePreview); connect(m_document, &Document::resultRigChanged, this, &PoseEditWidget::updatePoseDocument);
connect(this, &PoseEditWidget::parametersAdjusted, this, &PoseEditWidget::updatePreview); connect(this, &PoseEditWidget::parametersAdjusted, this, &PoseEditWidget::updatePreview);
connect(this, &PoseEditWidget::parametersAdjusted, [=]() { connect(this, &PoseEditWidget::parametersAdjusted, [=]() {
m_unsaved = true; m_unsaved = true;
@ -71,55 +123,43 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
connect(this, &PoseEditWidget::addPose, m_document, &Document::addPose); connect(this, &PoseEditWidget::addPose, m_document, &Document::addPose);
connect(this, &PoseEditWidget::renamePose, m_document, &Document::renamePose); connect(this, &PoseEditWidget::renamePose, m_document, &Document::renamePose);
connect(this, &PoseEditWidget::setPoseParameters, m_document, &Document::setPoseParameters); connect(this, &PoseEditWidget::setPoseParameters, m_document, &Document::setPoseParameters);
connect(this, &PoseEditWidget::setPoseAttributes, m_document, &Document::setPoseAttributes);
updateButtons(); updatePoseDocument();
updatePreview();
updateTitle(); updateTitle();
m_poseDocument->saveHistoryItem();
} }
void PoseEditWidget::updateButtons() void PoseEditWidget::changeTurnaround()
{ {
delete m_buttonsContainer; QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(),
m_buttonsContainer = new QWidget(this); tr("Image Files (*.png *.jpg *.bmp)")).trimmed();
m_buttonsContainer->resize(600, 500); if (fileName.isEmpty())
m_buttonsContainer->move(256, 0); return;
m_buttonsContainer->show(); QImage image;
if (!image.load(fileName))
return;
m_imageId = ImageForever::add(&image);
m_attributes["canvasImageId"] = m_imageId.toString();
m_poseDocument->updateTurnaround(image);
}
QGridLayout *marksContainerLayout = new QGridLayout; QUuid PoseEditWidget::findImageIdFromAttributes(const std::map<QString, QString> &attributes)
marksContainerLayout->setContentsMargins(0, 0, 0, 0); {
marksContainerLayout->setSpacing(2); auto findImageIdResult = attributes.find("canvasImageId");
if (findImageIdResult == attributes.end())
return QUuid();
return QUuid(findImageIdResult->second);
}
QFont buttonFont; void PoseEditWidget::updatePoseDocument()
buttonFont.setWeight(QFont::Light); {
buttonFont.setPixelSize(7); m_poseDocument->fromParameters(m_document->resultRigBones(), m_parameters);
buttonFont.setBold(false); QUuid imageId = findImageIdFromAttributes(m_attributes);
auto image = ImageForever::get(imageId);
std::map<QString, std::tuple<QPushButton *, PopupWidgetType>> buttons; if (nullptr != image)
const std::vector<RiggerBone> *rigBones = m_document->resultRigBones(); m_poseDocument->updateTurnaround(*image);
if (nullptr != rigBones && !rigBones->empty()) { updatePreview();
for (const auto &bone: *rigBones) {
if (!bone.hasButton)
continue;
QPushButton *buttonWidget = new QPushButton(bone.name);
PopupWidgetType widgetType = bone.buttonParameterType;
buttonWidget->setFont(buttonFont);
buttonWidget->setMaximumWidth(100);
QString boneName = bone.name;
connect(buttonWidget, &QPushButton::clicked, [this, boneName, widgetType]() {
emit showPopupAngleDialog(boneName, widgetType, mapFromGlobal(QCursor::pos()));
});
marksContainerLayout->addWidget(buttonWidget, bone.button.first, bone.button.second);
}
}
marksContainerLayout->setSizeConstraint(QLayout::SizeConstraint::SetMinimumSize);
QVBoxLayout *mainLayouer = new QVBoxLayout;
mainLayouer->addStretch();
mainLayouer->addLayout(marksContainerLayout);
mainLayouer->addStretch();
m_buttonsContainer->setLayout(mainLayouer);
} }
void PoseEditWidget::reject() void PoseEditWidget::reject()
@ -141,10 +181,6 @@ void PoseEditWidget::closeEvent(QCloseEvent *event)
} }
m_closed = true; m_closed = true;
hide(); hide();
if (m_openedMenuCount > 0) {
event->ignore();
return;
}
if (m_posePreviewManager->isRendering()) { if (m_posePreviewManager->isRendering()) {
event->ignore(); event->ignore();
return; return;
@ -160,6 +196,7 @@ QSize PoseEditWidget::sizeHint() const
PoseEditWidget::~PoseEditWidget() PoseEditWidget::~PoseEditWidget()
{ {
delete m_posePreviewManager; delete m_posePreviewManager;
delete m_poseDocument;
} }
void PoseEditWidget::updatePreview() void PoseEditWidget::updatePreview()
@ -214,157 +251,6 @@ void PoseEditWidget::updateTitle()
setWindowTitle(unifiedWindowTitle(pose->name + (m_unsaved ? "*" : ""))); setWindowTitle(unifiedWindowTitle(pose->name + (m_unsaved ? "*" : "")));
} }
void PoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos)
{
QMenu popupMenu;
QWidget *popup = new QWidget;
QVBoxLayout *layout = new QVBoxLayout;
if (PopupWidgetType::PitchYawRoll == popupWidgetType) {
FloatNumberWidget *pitchWidget = new FloatNumberWidget;
pitchWidget->setItemName(tr("Pitch"));
pitchWidget->setRange(-180, 180);
pitchWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "pitch").toFloat());
connect(pitchWidget, &FloatNumberWidget::valueChanged, this, [=](float value) {
m_parameters[boneName]["pitch"] = QString::number(value);
emit parametersAdjusted();
});
QPushButton *pitchEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(pitchEraser);
connect(pitchEraser, &QPushButton::clicked, this, [=]() {
pitchWidget->setValue(0.0);
});
QHBoxLayout *pitchLayout = new QHBoxLayout;
pitchLayout->addWidget(pitchEraser);
pitchLayout->addWidget(pitchWidget);
layout->addLayout(pitchLayout);
FloatNumberWidget *yawWidget = new FloatNumberWidget;
yawWidget->setItemName(tr("Yaw"));
yawWidget->setRange(-180, 180);
yawWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "yaw").toFloat());
connect(yawWidget, &FloatNumberWidget::valueChanged, this, [=](float value) {
m_parameters[boneName]["yaw"] = QString::number(value);
emit parametersAdjusted();
});
QPushButton *yawEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(yawEraser);
connect(yawEraser, &QPushButton::clicked, this, [=]() {
yawWidget->setValue(0.0);
});
QHBoxLayout *yawLayout = new QHBoxLayout;
yawLayout->addWidget(yawEraser);
yawLayout->addWidget(yawWidget);
layout->addLayout(yawLayout);
FloatNumberWidget *rollWidget = new FloatNumberWidget;
rollWidget->setItemName(tr("Roll"));
rollWidget->setRange(-180, 180);
rollWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "roll").toFloat());
connect(rollWidget, &FloatNumberWidget::valueChanged, this, [=](float value) {
m_parameters[boneName]["roll"] = QString::number(value);
emit parametersAdjusted();
});
QPushButton *rollEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(rollEraser);
connect(rollEraser, &QPushButton::clicked, this, [=]() {
rollWidget->setValue(0.0);
});
QHBoxLayout *rollLayout = new QHBoxLayout;
rollLayout->addWidget(rollEraser);
rollLayout->addWidget(rollWidget);
layout->addLayout(rollLayout);
} else if (PopupWidgetType::Intersection == popupWidgetType) {
FloatNumberWidget *intersectionWidget = new FloatNumberWidget;
intersectionWidget->setItemName(tr("Intersection"));
intersectionWidget->setRange(-180, 180);
intersectionWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "intersection").toFloat());
connect(intersectionWidget, &FloatNumberWidget::valueChanged, this, [=](float value) {
m_parameters[boneName]["intersection"] = QString::number(value);
emit parametersAdjusted();
});
QPushButton *intersectionEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(intersectionEraser);
connect(intersectionEraser, &QPushButton::clicked, this, [=]() {
intersectionWidget->setValue(0.0);
});
QHBoxLayout *intersectionLayout = new QHBoxLayout;
intersectionLayout->addWidget(intersectionEraser);
intersectionLayout->addWidget(intersectionWidget);
layout->addLayout(intersectionLayout);
} else if (PopupWidgetType::Translation == popupWidgetType) {
FloatNumberWidget *xWidget = new FloatNumberWidget;
xWidget->setItemName(tr("X"));
xWidget->setRange(-1, 1);
xWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "x").toFloat());
connect(xWidget, &FloatNumberWidget::valueChanged, this, [=](float value) {
m_parameters[boneName]["x"] = QString::number(value);
emit parametersAdjusted();
});
QPushButton *xEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(xEraser);
connect(xEraser, &QPushButton::clicked, this, [=]() {
xWidget->setValue(0.0);
});
QHBoxLayout *xLayout = new QHBoxLayout;
xLayout->addWidget(xEraser);
xLayout->addWidget(xWidget);
layout->addLayout(xLayout);
FloatNumberWidget *yWidget = new FloatNumberWidget;
yWidget->setItemName(tr("Y"));
yWidget->setRange(-1, 1);
yWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "y").toFloat());
connect(yWidget, &FloatNumberWidget::valueChanged, this, [=](float value) {
m_parameters[boneName]["y"] = QString::number(value);
emit parametersAdjusted();
});
QPushButton *yEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(yEraser);
connect(yEraser, &QPushButton::clicked, this, [=]() {
yWidget->setValue(0.0);
});
QHBoxLayout *yLayout = new QHBoxLayout;
yLayout->addWidget(yEraser);
yLayout->addWidget(yWidget);
layout->addLayout(yLayout);
FloatNumberWidget *zWidget = new FloatNumberWidget;
zWidget->setItemName(tr("Z"));
zWidget->setRange(-1, 1);
zWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "z").toFloat());
connect(zWidget, &FloatNumberWidget::valueChanged, this, [=](float value) {
m_parameters[boneName]["z"] = QString::number(value);
emit parametersAdjusted();
});
QPushButton *zEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(zEraser);
connect(zEraser, &QPushButton::clicked, this, [=]() {
zWidget->setValue(0.0);
});
QHBoxLayout *zLayout = new QHBoxLayout;
zLayout->addWidget(zEraser);
zLayout->addWidget(zWidget);
layout->addLayout(zLayout);
}
popup->setLayout(layout);
QWidgetAction action(this);
action.setDefaultWidget(popup);
popupMenu.addAction(&action);
m_openedMenuCount++;
popupMenu.exec(mapToGlobal(pos));
m_openedMenuCount--;
if (m_closed)
close();
}
void PoseEditWidget::setEditPoseName(QString name) void PoseEditWidget::setEditPoseName(QString name)
{ {
m_nameEdit->setText(name); m_nameEdit->setText(name);
@ -374,6 +260,15 @@ void PoseEditWidget::setEditPoseName(QString name)
void PoseEditWidget::setEditParameters(std::map<QString, std::map<QString, QString>> parameters) void PoseEditWidget::setEditParameters(std::map<QString, std::map<QString, QString>> parameters)
{ {
m_parameters = parameters; m_parameters = parameters;
updatePoseDocument();
updatePreview();
m_poseDocument->saveHistoryItem();
}
void PoseEditWidget::setEditAttributes(std::map<QString, QString> attributes)
{
m_attributes = attributes;
updatePoseDocument();
updatePreview(); updatePreview();
} }
@ -390,6 +285,7 @@ void PoseEditWidget::save()
} else if (m_unsaved) { } else if (m_unsaved) {
emit renamePose(m_poseId, m_nameEdit->text()); emit renamePose(m_poseId, m_nameEdit->text());
emit setPoseParameters(m_poseId, m_parameters); emit setPoseParameters(m_poseId, m_parameters);
emit setPoseAttributes(m_poseId, m_attributes);
} }
m_unsaved = false; m_unsaved = false;
close(); close();

View File

@ -8,6 +8,8 @@
#include "document.h" #include "document.h"
#include "modelwidget.h" #include "modelwidget.h"
#include "rigger.h" #include "rigger.h"
#include "skeletongraphicswidget.h"
#include "posedocument.h"
typedef RiggerButtonParameterType PopupWidgetType; typedef RiggerButtonParameterType PopupWidgetType;
@ -18,21 +20,23 @@ signals:
void addPose(QString name, std::map<QString, std::map<QString, QString>> parameters); void addPose(QString name, std::map<QString, std::map<QString, QString>> parameters);
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 setPoseAttributes(QUuid poseId, std::map<QString, QString> attributes);
void renamePose(QUuid poseId, QString name); void renamePose(QUuid poseId, QString name);
void parametersAdjusted(); void parametersAdjusted();
public: public:
PoseEditWidget(const Document *document, QWidget *parent=nullptr); PoseEditWidget(const Document *document, QWidget *parent=nullptr);
~PoseEditWidget(); ~PoseEditWidget();
public slots: public slots:
void updateButtons(); void updatePoseDocument();
void updatePreview(); void updatePreview();
void showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos);
void setEditPoseId(QUuid poseId); void setEditPoseId(QUuid poseId);
void setEditPoseName(QString name); void setEditPoseName(QString name);
void setEditParameters(std::map<QString, std::map<QString, QString>> parameters); void setEditParameters(std::map<QString, std::map<QString, QString>> parameters);
void setEditAttributes(std::map<QString, QString> attributes);
void updateTitle(); void updateTitle();
void save(); void save();
void clearUnsaveState(); void clearUnsaveState();
void changeTurnaround();
protected: protected:
QSize sizeHint() const override; QSize sizeHint() const override;
void closeEvent(QCloseEvent *event) override; void closeEvent(QCloseEvent *event) override;
@ -44,11 +48,14 @@ private:
bool m_isPreviewDirty = false; bool m_isPreviewDirty = false;
bool m_closed = false; bool m_closed = false;
std::map<QString, std::map<QString, QString>> m_parameters; std::map<QString, std::map<QString, QString>> m_parameters;
size_t m_openedMenuCount = 0; std::map<QString, QString> m_attributes;
QUuid m_poseId; QUuid m_poseId;
bool m_unsaved = false; bool m_unsaved = false;
QUuid m_imageId;
QLineEdit *m_nameEdit = nullptr; QLineEdit *m_nameEdit = nullptr;
QWidget *m_buttonsContainer = nullptr; PoseDocument *m_poseDocument = nullptr;
SkeletonGraphicsWidget *m_poseGraphicsWidget = nullptr;
QUuid findImageIdFromAttributes(const std::map<QString, QString> &attributes);
}; };
#endif #endif

View File

@ -22,14 +22,21 @@ PoseManageWidget::PoseManageWidget(const Document *document, QWidget *parent) :
connect(m_poseListWidget, &PoseListWidget::modifyPose, this, &PoseManageWidget::showPoseDialog); connect(m_poseListWidget, &PoseListWidget::modifyPose, this, &PoseManageWidget::showPoseDialog);
InfoLabel *infoLabel = new InfoLabel; InfoLabel *infoLabel = new InfoLabel;
infoLabel->setText(tr("Missing Rig"));
infoLabel->show(); infoLabel->show();
auto refreshInfoLabel = [=]() { auto refreshInfoLabel = [=]() {
if (m_document->currentRigSucceed()) { if (m_document->currentRigSucceed()) {
infoLabel->hide(); if (m_document->rigType == RigType::Tetrapod) {
addPoseButton->show(); infoLabel->setText("");
infoLabel->hide();
addPoseButton->show();
} else {
infoLabel->setText(tr("Pose editor doesn't support this rig type yet: ") + RigTypeToDispName(m_document->rigType));
infoLabel->show();
addPoseButton->hide();
}
} else { } else {
infoLabel->setText(tr("Missing Rig"));
infoLabel->show(); infoLabel->show();
addPoseButton->hide(); addPoseButton->hide();
} }
@ -71,6 +78,7 @@ void PoseManageWidget::showPoseDialog(QUuid poseId)
poseEditWidget->setEditPoseId(poseId); poseEditWidget->setEditPoseId(poseId);
poseEditWidget->setEditPoseName(pose->name); poseEditWidget->setEditPoseName(pose->name);
poseEditWidget->setEditParameters(pose->parameters); poseEditWidget->setEditParameters(pose->parameters);
poseEditWidget->setEditAttributes(pose->attributes);
poseEditWidget->clearUnsaveState(); poseEditWidget->clearUnsaveState();
} }
} }

View File

@ -6,6 +6,7 @@
#include "rigger.h" #include "rigger.h"
size_t Rigger::m_maxCutOffSplitterExpandRound = 3; size_t Rigger::m_maxCutOffSplitterExpandRound = 3;
QString Rigger::rootBoneName = "Body";
Rigger::Rigger(const std::vector<QVector3D> &verticesPositions, Rigger::Rigger(const std::vector<QVector3D> &verticesPositions,
const std::set<MeshSplitterTriangle> &inputTriangles) : const std::set<MeshSplitterTriangle> &inputTriangles) :

View File

@ -125,6 +125,7 @@ public:
const std::vector<QString> &missingMarkNames(); const std::vector<QString> &missingMarkNames();
const std::vector<QString> &errorMarkNames(); const std::vector<QString> &errorMarkNames();
virtual bool rig() = 0; virtual bool rig() = 0;
static QString rootBoneName;
protected: protected:
virtual bool validate() = 0; virtual bool validate() = 0;
virtual bool isCutOffSplitter(BoneMark boneMark) = 0; virtual bool isCutOffSplitter(BoneMark boneMark) = 0;

View File

@ -3,13 +3,13 @@
#include "shortcuts.h" #include "shortcuts.h"
#define defineKey(keyVal, funcName) do { \ #define defineKey(keyVal, funcName) do { \
auto key = new QShortcut(mainWindow); \ auto key = new QShortcut(widget); \
key->setKey(keyVal); \ key->setKey(keyVal); \
QObject::connect(key, &QShortcut::activated, \ QObject::connect(key, &QShortcut::activated, \
graphicsWidget, funcName); \ graphicsWidget, funcName); \
} while (false) } while (false)
void initShortCuts(QMainWindow *mainWindow, SkeletonGraphicsWidget *graphicsWidget) void initShortCuts(QWidget *widget, SkeletonGraphicsWidget *graphicsWidget)
{ {
defineKey(Qt::Key_Delete, &SkeletonGraphicsWidget::shortcutDelete); defineKey(Qt::Key_Delete, &SkeletonGraphicsWidget::shortcutDelete);
defineKey(Qt::Key_Backspace, &SkeletonGraphicsWidget::shortcutDelete); defineKey(Qt::Key_Backspace, &SkeletonGraphicsWidget::shortcutDelete);

View File

@ -3,6 +3,6 @@
#include <QMainWindow> #include <QMainWindow>
#include "skeletongraphicswidget.h" #include "skeletongraphicswidget.h"
void initShortCuts(QMainWindow *mainWindow, SkeletonGraphicsWidget *graphicsWidget); void initShortCuts(QWidget *widget, SkeletonGraphicsWidget *graphicsWidget);
#endif #endif

View File

@ -1,2 +1,73 @@
#include <QDebug>
#include "skeletondocument.h" #include "skeletondocument.h"
const SkeletonNode *SkeletonDocument::findNode(QUuid nodeId) const
{
auto it = nodeMap.find(nodeId);
if (it == nodeMap.end())
return nullptr;
return &it->second;
}
const SkeletonEdge *SkeletonDocument::findEdge(QUuid edgeId) const
{
auto it = edgeMap.find(edgeId);
if (it == edgeMap.end())
return nullptr;
return &it->second;
}
const SkeletonPart *SkeletonDocument::findPart(QUuid partId) const
{
auto it = partMap.find(partId);
if (it == partMap.end())
return nullptr;
return &it->second;
}
const SkeletonEdge *SkeletonDocument::findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const
{
const SkeletonNode *firstNode = nullptr;
firstNode = findNode(firstNodeId);
if (nullptr == firstNode) {
qDebug() << "Find node failed:" << firstNodeId;
return nullptr;
}
for (auto edgeIdIt = firstNode->edgeIds.begin(); edgeIdIt != firstNode->edgeIds.end(); edgeIdIt++) {
auto edgeIt = edgeMap.find(*edgeIdIt);
if (edgeIt == edgeMap.end()) {
qDebug() << "Find edge failed:" << *edgeIdIt;
continue;
}
if (std::find(edgeIt->second.nodeIds.begin(), edgeIt->second.nodeIds.end(), secondNodeId) != edgeIt->second.nodeIds.end())
return &edgeIt->second;
}
return nullptr;
}
void SkeletonDocument::findAllNeighbors(QUuid nodeId, std::set<QUuid> &neighbors) const
{
const auto &node = findNode(nodeId);
if (nullptr == node) {
qDebug() << "findNode:" << nodeId << "failed";
return;
}
for (const auto &edgeId: node->edgeIds) {
const auto &edge = findEdge(edgeId);
if (nullptr == edge) {
qDebug() << "findEdge:" << edgeId << "failed";
continue;
}
const auto &neighborNodeId = edge->neighborOf(nodeId);
if (neighborNodeId.isNull()) {
qDebug() << "neighborOf:" << nodeId << "is null from edge:" << edgeId;
continue;
}
if (neighbors.find(neighborNodeId) != neighbors.end()) {
continue;
}
neighbors.insert(neighborNodeId);
findAllNeighbors(neighborNodeId, neighbors);
}
}

View File

@ -200,18 +200,22 @@ public:
bool zlocked = false; bool zlocked = false;
bool radiusLocked = false; bool radiusLocked = false;
QImage turnaround; QImage turnaround;
std::map<QUuid, SkeletonPart> partMap;
std::map<QUuid, SkeletonNode> nodeMap;
std::map<QUuid, SkeletonEdge> edgeMap;
const SkeletonNode *findNode(QUuid nodeId) const;
const SkeletonEdge *findEdge(QUuid edgeId) const;
const SkeletonPart *findPart(QUuid partId) const;
const SkeletonEdge *findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const;
void findAllNeighbors(QUuid nodeId, std::set<QUuid> &neighbors) const;
virtual const SkeletonNode *findNode(QUuid nodeId) const = 0;
virtual const SkeletonEdge *findEdge(QUuid edgeId) const = 0;
virtual const SkeletonPart *findPart(QUuid partId) const = 0;
virtual const SkeletonEdge *findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const = 0;
virtual bool undoable() const = 0; virtual bool undoable() const = 0;
virtual bool redoable() const = 0; virtual bool redoable() const = 0;
virtual bool hasPastableNodesInClipboard() const = 0; virtual bool hasPastableNodesInClipboard() const = 0;
virtual bool originSettled() const = 0; virtual bool originSettled() const = 0;
virtual bool isNodeEditable(QUuid nodeId) const = 0; virtual bool isNodeEditable(QUuid nodeId) const = 0;
virtual bool isEdgeEditable(QUuid edgeId) const = 0; virtual bool isEdgeEditable(QUuid edgeId) const = 0;
virtual void findAllNeighbors(QUuid nodeId, std::set<QUuid> &neighbors) const = 0;
virtual void copyNodes(std::set<QUuid> nodeIdSet) const = 0; virtual void copyNodes(std::set<QUuid> nodeIdSet) const = 0;
public slots: public slots:

View File

@ -44,7 +44,8 @@ SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document)
m_eventForwardingToModelWidget(false), m_eventForwardingToModelWidget(false),
m_modelWidget(nullptr), m_modelWidget(nullptr),
m_inTempDragMode(false), m_inTempDragMode(false),
m_modeBeforeEnterTempDragMode(SkeletonDocumentEditMode::Select) m_modeBeforeEnterTempDragMode(SkeletonDocumentEditMode::Select),
m_nodePositionModifyOnly(false)
{ {
setRenderHint(QPainter::Antialiasing, false); setRenderHint(QPainter::Antialiasing, false);
setBackgroundBrush(QBrush(QWidget::palette().color(QWidget::backgroundRole()), Qt::SolidPattern)); setBackgroundBrush(QBrush(QWidget::palette().color(QWidget::backgroundRole()), Qt::SolidPattern));
@ -118,10 +119,12 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
QMenu contextMenu(this); QMenu contextMenu(this);
QAction addAction(tr("Add..."), this); QAction addAction(tr("Add..."), this);
connect(&addAction, &QAction::triggered, [=]() { if (!m_nodePositionModifyOnly) {
emit setEditMode(SkeletonDocumentEditMode::Add); connect(&addAction, &QAction::triggered, [=]() {
}); emit setEditMode(SkeletonDocumentEditMode::Add);
contextMenu.addAction(&addAction); });
contextMenu.addAction(&addAction);
}
QAction undoAction(tr("Undo"), this); QAction undoAction(tr("Undo"), this);
if (m_document->undoable()) { if (m_document->undoable()) {
@ -136,67 +139,67 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
} }
QAction deleteAction(tr("Delete"), this); QAction deleteAction(tr("Delete"), this);
if (hasSelection()) { if (!m_nodePositionModifyOnly && hasSelection()) {
connect(&deleteAction, &QAction::triggered, this, &SkeletonGraphicsWidget::deleteSelected); connect(&deleteAction, &QAction::triggered, this, &SkeletonGraphicsWidget::deleteSelected);
contextMenu.addAction(&deleteAction); contextMenu.addAction(&deleteAction);
} }
QAction breakAction(tr("Break"), this); QAction breakAction(tr("Break"), this);
if (hasEdgeSelection()) { if (!m_nodePositionModifyOnly && hasEdgeSelection()) {
connect(&breakAction, &QAction::triggered, this, &SkeletonGraphicsWidget::breakSelected); connect(&breakAction, &QAction::triggered, this, &SkeletonGraphicsWidget::breakSelected);
contextMenu.addAction(&breakAction); contextMenu.addAction(&breakAction);
} }
QAction connectAction(tr("Connect"), this); QAction connectAction(tr("Connect"), this);
if (hasTwoDisconnectedNodesSelection()) { if (!m_nodePositionModifyOnly && hasTwoDisconnectedNodesSelection()) {
connect(&connectAction, &QAction::triggered, this, &SkeletonGraphicsWidget::connectSelected); connect(&connectAction, &QAction::triggered, this, &SkeletonGraphicsWidget::connectSelected);
contextMenu.addAction(&connectAction); contextMenu.addAction(&connectAction);
} }
QAction cutAction(tr("Cut"), this); QAction cutAction(tr("Cut"), this);
if (hasSelection()) { if (!m_nodePositionModifyOnly && hasSelection()) {
connect(&cutAction, &QAction::triggered, this, &SkeletonGraphicsWidget::cut); connect(&cutAction, &QAction::triggered, this, &SkeletonGraphicsWidget::cut);
contextMenu.addAction(&cutAction); contextMenu.addAction(&cutAction);
} }
QAction copyAction(tr("Copy"), this); QAction copyAction(tr("Copy"), this);
if (hasSelection()) { if (!m_nodePositionModifyOnly && hasSelection()) {
connect(&copyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::copy); connect(&copyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::copy);
contextMenu.addAction(&copyAction); contextMenu.addAction(&copyAction);
} }
QAction pasteAction(tr("Paste"), this); QAction pasteAction(tr("Paste"), this);
if (m_document->hasPastableNodesInClipboard()) { if (!m_nodePositionModifyOnly && m_document->hasPastableNodesInClipboard()) {
connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste); connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste);
contextMenu.addAction(&pasteAction); contextMenu.addAction(&pasteAction);
} }
QAction flipHorizontallyAction(tr("H Flip"), this); QAction flipHorizontallyAction(tr("H Flip"), this);
if (hasMultipleSelection()) { if (!m_nodePositionModifyOnly && hasMultipleSelection()) {
connect(&flipHorizontallyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::flipHorizontally); connect(&flipHorizontallyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::flipHorizontally);
contextMenu.addAction(&flipHorizontallyAction); contextMenu.addAction(&flipHorizontallyAction);
} }
QAction flipVerticallyAction(tr("V Flip"), this); QAction flipVerticallyAction(tr("V Flip"), this);
if (hasMultipleSelection()) { if (!m_nodePositionModifyOnly && hasMultipleSelection()) {
connect(&flipVerticallyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::flipVertically); connect(&flipVerticallyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::flipVertically);
contextMenu.addAction(&flipVerticallyAction); contextMenu.addAction(&flipVerticallyAction);
} }
QAction rotateClockwiseAction(tr("Rotate 90D CW"), this); QAction rotateClockwiseAction(tr("Rotate 90D CW"), this);
if (hasMultipleSelection()) { if (!m_nodePositionModifyOnly && hasMultipleSelection()) {
connect(&rotateClockwiseAction, &QAction::triggered, this, &SkeletonGraphicsWidget::rotateClockwise90Degree); connect(&rotateClockwiseAction, &QAction::triggered, this, &SkeletonGraphicsWidget::rotateClockwise90Degree);
contextMenu.addAction(&rotateClockwiseAction); contextMenu.addAction(&rotateClockwiseAction);
} }
QAction rotateCounterclockwiseAction(tr("Rotate 90D CCW"), this); QAction rotateCounterclockwiseAction(tr("Rotate 90D CCW"), this);
if (hasMultipleSelection()) { if (!m_nodePositionModifyOnly && hasMultipleSelection()) {
connect(&rotateCounterclockwiseAction, &QAction::triggered, this, &SkeletonGraphicsWidget::rotateCounterclockwise90Degree); connect(&rotateCounterclockwiseAction, &QAction::triggered, this, &SkeletonGraphicsWidget::rotateCounterclockwise90Degree);
contextMenu.addAction(&rotateCounterclockwiseAction); contextMenu.addAction(&rotateCounterclockwiseAction);
} }
QAction switchXzAction(tr("Switch XZ"), this); QAction switchXzAction(tr("Switch XZ"), this);
if (hasSelection()) { if (!m_nodePositionModifyOnly && hasSelection()) {
connect(&switchXzAction, &QAction::triggered, this, &SkeletonGraphicsWidget::switchSelectedXZ); connect(&switchXzAction, &QAction::triggered, this, &SkeletonGraphicsWidget::switchSelectedXZ);
contextMenu.addAction(&switchXzAction); contextMenu.addAction(&switchXzAction);
} }
@ -207,7 +210,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
QAction alignToGlobalCenterAction(tr("Global Center"), this); QAction alignToGlobalCenterAction(tr("Global Center"), this);
QAction alignToGlobalVerticalCenterAction(tr("Global Vertical Center"), this); QAction alignToGlobalVerticalCenterAction(tr("Global Vertical Center"), this);
QAction alignToGlobalHorizontalCenterAction(tr("Global Horizontal Center"), this); QAction alignToGlobalHorizontalCenterAction(tr("Global Horizontal Center"), this);
if ((hasSelection() && m_document->originSettled()) || hasMultipleSelection()) { if (!m_nodePositionModifyOnly && ((hasSelection() && m_document->originSettled()) || hasMultipleSelection())) {
QMenu *subMenu = contextMenu.addMenu(tr("Align To")); QMenu *subMenu = contextMenu.addMenu(tr("Align To"));
if (hasMultipleSelection()) { if (hasMultipleSelection()) {
@ -238,7 +241,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
for (int i = 0; i < (int)BoneMark::Count - 1; i++) { for (int i = 0; i < (int)BoneMark::Count - 1; i++) {
markAsActions[i] = nullptr; markAsActions[i] = nullptr;
} }
if (hasNodeSelection()) { if (!m_nodePositionModifyOnly && hasNodeSelection()) {
QMenu *subMenu = contextMenu.addMenu(tr("Mark As")); QMenu *subMenu = contextMenu.addMenu(tr("Mark As"));
connect(&markAsNoneAction, &QAction::triggered, [=]() { connect(&markAsNoneAction, &QAction::triggered, [=]() {
@ -265,7 +268,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
} }
QAction selectPartAllAction(tr("Select Part"), this); QAction selectPartAllAction(tr("Select Part"), this);
if (hasItems()) { if (!m_nodePositionModifyOnly && hasItems()) {
connect(&selectPartAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::selectPartAll); connect(&selectPartAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::selectPartAll);
contextMenu.addAction(&selectPartAllAction); contextMenu.addAction(&selectPartAllAction);
} }
@ -2456,3 +2459,8 @@ void SkeletonGraphicsWidget::setSelectedNodesBoneMark(BoneMark boneMark)
emit groupOperationAdded(); emit groupOperationAdded();
} }
} }
void SkeletonGraphicsWidget::setNodePositionModifyOnly(bool nodePositionModifyOnly)
{
m_nodePositionModifyOnly = nodePositionModifyOnly;
}

View File

@ -408,6 +408,7 @@ public:
bool hasNodeSelection(); bool hasNodeSelection();
bool hasTwoDisconnectedNodesSelection(); bool hasTwoDisconnectedNodesSelection();
void setModelWidget(ModelWidget *modelWidget); void setModelWidget(ModelWidget *modelWidget);
void setNodePositionModifyOnly(bool nodePositionModifyOnly);
protected: protected:
void mouseMoveEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override; void wheelEvent(QWheelEvent *event) override;
@ -557,6 +558,7 @@ private: //need initalize
ModelWidget *m_modelWidget; ModelWidget *m_modelWidget;
bool m_inTempDragMode; bool m_inTempDragMode;
SkeletonDocumentEditMode m_modeBeforeEnterTempDragMode; SkeletonDocumentEditMode m_modeBeforeEnterTempDragMode;
bool m_nodePositionModifyOnly;
private: private:
QVector3D m_ikMoveTarget; QVector3D m_ikMoveTarget;
QUuid m_ikMoveEndEffectorId; QUuid m_ikMoveEndEffectorId;

View File

@ -1,3 +1,5 @@
#include <cmath>
#include <QtMath>
#include "tetrapodposer.h" #include "tetrapodposer.h"
#include "util.h" #include "util.h"
@ -6,79 +8,211 @@ TetrapodPoser::TetrapodPoser(const std::vector<RiggerBone> &bones) :
{ {
} }
void TetrapodPoser::commit() void TetrapodPoser::resolveTranslation()
{ {
for (const auto &item: parameters()) { for (const auto &item: parameters()) {
int boneIndex = findBoneIndex(item.first); int boneIndex = findBoneIndex(item.first);
if (-1 == boneIndex) { if (-1 == boneIndex) {
continue; continue;
} }
auto findPitchResult = item.second.find("pitch"); auto findTranslateXResult = item.second.find("translateX");
auto findYawResult = item.second.find("yaw"); auto findTranslateYResult = item.second.find("translateY");
auto findRollResult = item.second.find("roll"); auto findTranslateZResult = item.second.find("translateZ");
if (findPitchResult != item.second.end() || if (findTranslateXResult != item.second.end() ||
findYawResult != item.second.end() || findTranslateYResult != item.second.end() ||
findRollResult != item.second.end()) { findTranslateZResult != item.second.end()) {
float yawAngle = valueOfKeyInMapOrEmpty(item.second, "yaw").toFloat(); float x = valueOfKeyInMapOrEmpty(item.second, "translateX").toFloat();
if (item.first.startsWith("Left")) { float y = valueOfKeyInMapOrEmpty(item.second, "translateY").toFloat();
yawAngle = -yawAngle; float z = valueOfKeyInMapOrEmpty(item.second, "translateZ").toFloat();
}
QQuaternion rotation = eulerAnglesToQuaternion(valueOfKeyInMapOrEmpty(item.second, "pitch").toFloat(),
yawAngle,
valueOfKeyInMapOrEmpty(item.second, "roll").toFloat());
m_jointNodeTree.updateRotation(boneIndex, rotation);
continue;
}
auto findIntersectionResult = item.second.find("intersection");
if (findIntersectionResult != item.second.end()) {
float intersectionAngle = valueOfKeyInMapOrEmpty(item.second, "intersection").toFloat();
const RiggerBone &bone = bones()[boneIndex];
if (bone.name == "LeftHand" || bone.name == "RightHand") {
QVector3D handDirection = bone.tailPosition - bone.headPosition;
QVector3D referenceDirection = bone.name == "RightHand" ? QVector3D(1, 0, 0) : QVector3D(-1, 0, 0);
auto angleWithX = (int)angleBetweenVectors(handDirection, -referenceDirection);
auto angleWithZ = (int)angleBetweenVectors(handDirection, QVector3D(0, 0, -1));
QVector3D rotateAxis = angleWithX < angleWithZ ?
QVector3D::crossProduct(handDirection, referenceDirection).normalized() :
QVector3D::crossProduct(handDirection, QVector3D(0, 0, -1)).normalized();
QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle);
m_jointNodeTree.updateRotation(boneIndex, rotation);
continue;
} else if (bone.name == "LeftLowerArm" || bone.name == "RightLowerArm") {
QVector3D lowerArmDirection = bone.tailPosition - bone.headPosition;
QVector3D rotateAxis = QVector3D::crossProduct(lowerArmDirection, QVector3D(0, 0, 1)).normalized();
QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle);
m_jointNodeTree.updateRotation(boneIndex, rotation);
continue;
} else if (bone.name == "LeftFoot" || bone.name == "RightFoot") {
QVector3D footDirection = bone.tailPosition - bone.headPosition;
QVector3D rotateAxis = QVector3D::crossProduct(footDirection, QVector3D(0, 1, 0)).normalized();
QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle);
m_jointNodeTree.updateRotation(boneIndex, rotation);
continue;
} else if (bone.name == "LeftLowerLeg" || bone.name == "RightLowerLeg") {
QVector3D lowerLegDirection = bone.tailPosition - bone.headPosition;
QVector3D rotateAxis = QVector3D::crossProduct(lowerLegDirection, QVector3D(0, 0, -1)).normalized();
QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle);
m_jointNodeTree.updateRotation(boneIndex, rotation);
continue;
}
continue;
}
auto findXResult = item.second.find("x");
auto findYResult = item.second.find("y");
auto findZResult = item.second.find("z");
if (findXResult != item.second.end() ||
findYResult != item.second.end() ||
findZResult != item.second.end()) {
float x = valueOfKeyInMapOrEmpty(item.second, "x").toFloat();
float y = valueOfKeyInMapOrEmpty(item.second, "y").toFloat();
float z = valueOfKeyInMapOrEmpty(item.second, "z").toFloat();
QVector3D translation = {x, y, z}; QVector3D translation = {x, y, z};
m_jointNodeTree.addTranslation(boneIndex, translation); m_jointNodeTree.addTranslation(boneIndex, translation);
continue; continue;
} }
} }
resolveLimbRotation({QString("LeftUpperArm"), QString("LeftLowerArm"), QString("LeftHand")});
resolveLimbRotation({QString("RightUpperArm"), QString("RightLowerArm"), QString("RightHand")});
resolveLimbRotation({QString("LeftUpperLeg"), QString("LeftLowerLeg"), QString("LeftFoot")});
resolveLimbRotation({QString("RightUpperLeg"), QString("RightLowerLeg"), QString("RightFoot")});
resolveLimbRotation({QString("Spine"), QString("Chest"), QString("Neck"), QString("Head")});
}
std::pair<bool, QVector3D> TetrapodPoser::findQVector3DFromMap(const std::map<QString, QString> &map, const QString &xName, const QString &yName, const QString &zName)
{
auto findXResult = map.find(xName);
auto findYResult = map.find(yName);
auto findZResult = map.find(zName);
if (findXResult == map.end() &&
findYResult == map.end() &&
findZResult == map.end()) {
return {false, QVector3D()};
}
return {true, {
valueOfKeyInMapOrEmpty(map, xName).toFloat(),
valueOfKeyInMapOrEmpty(map, yName).toFloat(),
valueOfKeyInMapOrEmpty(map, zName).toFloat()
}};
}
std::pair<bool, std::pair<QVector3D, QVector3D>> TetrapodPoser::findBonePositionsFromParameters(const std::map<QString, QString> &map)
{
auto findBoneStartResult = findQVector3DFromMap(map, "fromX", "fromY", "fromZ");
auto findBoneStopResult = findQVector3DFromMap(map, "toX", "toY", "toZ");
if (!findBoneStartResult.first || !findBoneStopResult.first)
return {false, {QVector3D(), QVector3D()}};
return {true, {findBoneStartResult.second, findBoneStopResult.second}};
}
void TetrapodPoser::resolveLimbRotation(const std::vector<QString> &limbBoneNames)
{
// We match the poses by the distance and rotation plane
if (limbBoneNames.size() < 3) {
qDebug() << "Cann't resolve limb bones with invalid joints:" << limbBoneNames.size();
return;
}
const auto &beginBoneName = limbBoneNames[0];
const auto &middleBoneName = limbBoneNames[1];
const auto &endBoneName = limbBoneNames[2];
const auto &beginBoneParameters = parameters().find(beginBoneName);
if (beginBoneParameters == parameters().end()) {
qDebug() << beginBoneName << "'s parameters not found";
return;
}
auto matchBeginBonePositions = findBonePositionsFromParameters(beginBoneParameters->second);
if (!matchBeginBonePositions.first) {
qDebug() << beginBoneName << "'s positions not found";
return;
}
const auto &endBoneParameters = parameters().find(endBoneName);
if (endBoneParameters == parameters().end()) {
qDebug() << endBoneName << "'s parameters not found";
return;
}
auto matchEndBonePositions = findBonePositionsFromParameters(endBoneParameters->second);
if (!matchEndBonePositions.first) {
qDebug() << endBoneName << "'s positions not found";
return;
}
float matchLimbLength = (matchBeginBonePositions.second.first - matchBeginBonePositions.second.second).length() +
(matchBeginBonePositions.second.second - matchEndBonePositions.second.first).length();
auto matchDistanceBetweenBeginAndEndBones = (matchBeginBonePositions.second.first - matchEndBonePositions.second.first).length();
auto matchRotatePlaneNormal = QVector3D::crossProduct((matchBeginBonePositions.second.second - matchBeginBonePositions.second.first).normalized(), (matchEndBonePositions.second.first - matchBeginBonePositions.second.second).normalized());
auto matchDirectionBetweenBeginAndEndPones = (matchEndBonePositions.second.first - matchBeginBonePositions.second.first).normalized();
auto matchEndBoneDirection = (matchEndBonePositions.second.second - matchEndBonePositions.second.first).normalized();
int beginBoneIndex = findBoneIndex(beginBoneName);
if (-1 == beginBoneIndex) {
qDebug() << beginBoneName << "not found in rigged bones";
return;
}
const auto &beginBone = bones()[beginBoneIndex];
int middleBoneIndex = findBoneIndex(middleBoneName);
if (-1 == middleBoneIndex) {
qDebug() << middleBoneName << "not found in rigged bones";
return;
}
int endBoneIndex = findBoneIndex(endBoneName);
if (-1 == endBoneIndex) {
qDebug() << endBoneName << "not found in rigged bones";
return;
}
const auto &endBone = bones()[endBoneIndex];
float targetBeginBoneLength = (beginBone.headPosition - beginBone.tailPosition).length();
float targetMiddleBoneLength = (beginBone.tailPosition - endBone.headPosition).length();
float targetLimbLength = targetBeginBoneLength + targetMiddleBoneLength;
float targetDistanceBetweenBeginAndEndBones = matchDistanceBetweenBeginAndEndBones * (targetLimbLength / matchLimbLength);
QVector3D targetEndBoneStartPosition = beginBone.headPosition + matchDirectionBetweenBeginAndEndPones * targetDistanceBetweenBeginAndEndBones;
float angleBetweenDistanceAndMiddleBones = 0;
{
const float &a = targetMiddleBoneLength;
const float &b = targetDistanceBetweenBeginAndEndBones;
const float &c = targetBeginBoneLength;
double cosC = (a*a + b*b - c*c) / (2.0*a*b);
angleBetweenDistanceAndMiddleBones = qRadiansToDegrees(acos(cosC));
}
QVector3D targetMiddleBoneStartPosition;
{
qDebug() << beginBoneName << "Angle:" << angleBetweenDistanceAndMiddleBones;
auto rotation = QQuaternion::fromAxisAndAngle(matchRotatePlaneNormal, angleBetweenDistanceAndMiddleBones);
targetMiddleBoneStartPosition = targetEndBoneStartPosition + rotation.rotatedVector(-matchDirectionBetweenBeginAndEndPones).normalized() * targetMiddleBoneLength;
}
// Now the bones' positions have been resolved, we calculate the rotation
auto oldBeginBoneDirection = (beginBone.tailPosition - beginBone.headPosition).normalized();
auto newBeginBoneDirection = (targetMiddleBoneStartPosition - beginBone.headPosition).normalized();
auto beginBoneRotation = QQuaternion::rotationTo(oldBeginBoneDirection, newBeginBoneDirection);
m_jointNodeTree.updateRotation(beginBoneIndex, beginBoneRotation);
auto oldMiddleBoneDirection = (endBone.headPosition - beginBone.tailPosition).normalized();
auto newMiddleBoneDirection = (targetEndBoneStartPosition - targetMiddleBoneStartPosition).normalized();
oldMiddleBoneDirection = beginBoneRotation.rotatedVector(oldMiddleBoneDirection);
auto middleBoneRotation = QQuaternion::rotationTo(oldMiddleBoneDirection, newMiddleBoneDirection);
m_jointNodeTree.updateRotation(middleBoneIndex, middleBoneRotation);
// Calculate the end effectors' rotation
auto oldEndBoneDirection = (endBone.tailPosition - endBone.headPosition).normalized();
auto newEndBoneDirection = matchEndBoneDirection;
oldEndBoneDirection = beginBoneRotation.rotatedVector(oldEndBoneDirection);
oldEndBoneDirection = middleBoneRotation.rotatedVector(oldEndBoneDirection);
auto endBoneRotation = QQuaternion::rotationTo(oldEndBoneDirection, newEndBoneDirection);
m_jointNodeTree.updateRotation(endBoneIndex, endBoneRotation);
if (limbBoneNames.size() > 3) {
std::vector<QQuaternion> rotations;
rotations.push_back(beginBoneRotation);
rotations.push_back(middleBoneRotation);
rotations.push_back(endBoneRotation);
for (size_t i = 3; i < limbBoneNames.size(); ++i) {
const auto &boneName = limbBoneNames[i];
int boneIndex = findBoneIndex(boneName);
if (-1 == boneIndex) {
qDebug() << "Find bone failed:" << boneName;
continue;
}
const auto &bone = bones()[boneIndex];
const auto &boneParameters = parameters().find(boneName);
if (boneParameters == parameters().end()) {
qDebug() << "Find bone parameters:" << boneName;
continue;
}
auto matchBonePositions = findBonePositionsFromParameters(boneParameters->second);
if (!matchBonePositions.first) {
qDebug() << "Find bone positions failed:" << boneName;
continue;
}
auto matchBoneDirection = (matchBonePositions.second.second - matchBonePositions.second.first).normalized();
auto oldBoneDirection = (bone.tailPosition - bone.headPosition).normalized();
auto newBoneDirection = matchBoneDirection;
for (const auto &rotation: rotations) {
oldBoneDirection = rotation.rotatedVector(oldBoneDirection);
}
auto boneRotation = QQuaternion::rotationTo(oldBoneDirection, newBoneDirection);
m_jointNodeTree.updateRotation(boneIndex, boneRotation);
rotations.push_back(boneRotation);
}
}
}
void TetrapodPoser::commit()
{
resolveTranslation();
Poser::commit(); Poser::commit();
} }

View File

@ -1,5 +1,6 @@
#ifndef DUST3D_TETRAPOD_POSER_H #ifndef DUST3D_TETRAPOD_POSER_H
#define DUST3D_TETRAPOD_POSER_H #define DUST3D_TETRAPOD_POSER_H
#include <vector>
#include "poser.h" #include "poser.h"
class TetrapodPoser : public Poser class TetrapodPoser : public Poser
@ -7,8 +8,13 @@ class TetrapodPoser : public Poser
Q_OBJECT Q_OBJECT
public: public:
TetrapodPoser(const std::vector<RiggerBone> &bones); TetrapodPoser(const std::vector<RiggerBone> &bones);
public:
void commit() override; void commit() override;
private:
void resolveTranslation();
void resolveLimbRotation(const std::vector<QString> &limbBoneNames);
std::pair<bool, QVector3D> findQVector3DFromMap(const std::map<QString, QString> &map, const QString &xName, const QString &yName, const QString &zName);
std::pair<bool, std::pair<QVector3D, QVector3D>> findBonePositionsFromParameters(const std::map<QString, QString> &map);
}; };
#endif #endif

View File

@ -371,7 +371,7 @@ bool TetrapodRigger::rig()
m_resultBones.push_back(RiggerBone()); m_resultBones.push_back(RiggerBone());
RiggerBone &bodyBone = m_resultBones.back(); RiggerBone &bodyBone = m_resultBones.back();
bodyBone.index = m_resultBones.size() - 1; bodyBone.index = m_resultBones.size() - 1;
bodyBone.name = "Body"; bodyBone.name = Rigger::rootBoneName;
bodyBone.headPosition = QVector3D(0, 0, 0); bodyBone.headPosition = QVector3D(0, 0, 0);
bodyBone.tailPosition = bonesOrigin; bodyBone.tailPosition = bonesOrigin;
bodyBone.hasButton = true; bodyBone.hasButton = true;
@ -383,10 +383,10 @@ bool TetrapodRigger::rig()
RiggerBone &leftHipBone = m_resultBones.back(); RiggerBone &leftHipBone = m_resultBones.back();
leftHipBone.index = m_resultBones.size() - 1; leftHipBone.index = m_resultBones.size() - 1;
leftHipBone.name = "LeftHip"; leftHipBone.name = "LeftHip";
leftHipBone.headPosition = m_resultBones[boneIndexMap["Body"]].tailPosition; leftHipBone.headPosition = m_resultBones[boneIndexMap[Rigger::rootBoneName]].tailPosition;
leftHipBone.tailPosition = leftUpperLegBoneStartPosition; leftHipBone.tailPosition = leftUpperLegBoneStartPosition;
boneIndexMap[leftHipBone.name] = leftHipBone.index; boneIndexMap[leftHipBone.name] = leftHipBone.index;
m_resultBones[boneIndexMap["Body"]].children.push_back(leftHipBone.index); m_resultBones[boneIndexMap[Rigger::rootBoneName]].children.push_back(leftHipBone.index);
m_resultBones.push_back(RiggerBone()); m_resultBones.push_back(RiggerBone());
RiggerBone &leftUpperLegBone = m_resultBones.back(); RiggerBone &leftUpperLegBone = m_resultBones.back();
@ -431,10 +431,10 @@ bool TetrapodRigger::rig()
RiggerBone &rightHipBone = m_resultBones.back(); RiggerBone &rightHipBone = m_resultBones.back();
rightHipBone.index = m_resultBones.size() - 1; rightHipBone.index = m_resultBones.size() - 1;
rightHipBone.name = "RightHip"; rightHipBone.name = "RightHip";
rightHipBone.headPosition = m_resultBones[boneIndexMap["Body"]].tailPosition; rightHipBone.headPosition = m_resultBones[boneIndexMap[Rigger::rootBoneName]].tailPosition;
rightHipBone.tailPosition = rightUpperLegBoneStartPosition; rightHipBone.tailPosition = rightUpperLegBoneStartPosition;
boneIndexMap[rightHipBone.name] = rightHipBone.index; boneIndexMap[rightHipBone.name] = rightHipBone.index;
m_resultBones[boneIndexMap["Body"]].children.push_back(rightHipBone.index); m_resultBones[boneIndexMap[Rigger::rootBoneName]].children.push_back(rightHipBone.index);
m_resultBones.push_back(RiggerBone()); m_resultBones.push_back(RiggerBone());
RiggerBone &rightUpperLegBone = m_resultBones.back(); RiggerBone &rightUpperLegBone = m_resultBones.back();
@ -479,14 +479,14 @@ bool TetrapodRigger::rig()
RiggerBone &spineBone = m_resultBones.back(); RiggerBone &spineBone = m_resultBones.back();
spineBone.index = m_resultBones.size() - 1; spineBone.index = m_resultBones.size() - 1;
spineBone.name = "Spine"; spineBone.name = "Spine";
spineBone.headPosition = m_resultBones[boneIndexMap["Body"]].tailPosition; spineBone.headPosition = m_resultBones[boneIndexMap[Rigger::rootBoneName]].tailPosition;
spineBone.tailPosition = chestBoneStartPosition; spineBone.tailPosition = chestBoneStartPosition;
spineBone.color = Qt::white; spineBone.color = Qt::white;
spineBone.hasButton = true; spineBone.hasButton = true;
spineBone.button = {3, 1}; spineBone.button = {3, 1};
spineBone.buttonParameterType = RiggerButtonParameterType::PitchYawRoll; spineBone.buttonParameterType = RiggerButtonParameterType::PitchYawRoll;
boneIndexMap[spineBone.name] = spineBone.index; boneIndexMap[spineBone.name] = spineBone.index;
m_resultBones[boneIndexMap["Body"]].children.push_back(spineBone.index); m_resultBones[boneIndexMap[Rigger::rootBoneName]].children.push_back(spineBone.index);
m_resultBones.push_back(RiggerBone()); m_resultBones.push_back(RiggerBone());
RiggerBone &chestBone = m_resultBones.back(); RiggerBone &chestBone = m_resultBones.back();