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
parent
a4aefdd308
commit
70f075a36e
|
@ -1133,3 +1133,8 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode
|
|||
<pre>
|
||||
http://bediyap.com/programming/convert-quaternion-to-euler-rotations/
|
||||
</pre>
|
||||
|
||||
<h1>Eadweard Muybridge</h1>
|
||||
<pre>
|
||||
https://en.wikipedia.org/wiki/Eadweard_Muybridge
|
||||
</pre>
|
||||
|
|
|
@ -294,6 +294,9 @@ HEADERS += src/poserconstruct.h
|
|||
SOURCES += src/skeletondocument.cpp
|
||||
HEADERS += src/skeletondocument.h
|
||||
|
||||
SOURCES += src/posedocument.cpp
|
||||
HEADERS += src/posedocument.h
|
||||
|
||||
SOURCES += src/main.cpp
|
||||
|
||||
HEADERS += src/version.h
|
||||
|
|
|
@ -416,6 +416,20 @@ void Document::setPoseParameters(QUuid poseId, std::map<QString, std::map<QStrin
|
|||
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)
|
||||
{
|
||||
auto findPoseResult = poseMap.find(poseId);
|
||||
|
@ -431,26 +445,6 @@ void Document::renamePose(QUuid poseId, QString name)
|
|||
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
|
||||
{
|
||||
return !qFuzzyIsNull(originX) && !qFuzzyIsNull(originY) && !qFuzzyIsNull(originZ);
|
||||
|
@ -537,30 +531,6 @@ void Document::addEdge(QUuid fromNodeId, QUuid toNodeId)
|
|||
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
|
||||
{
|
||||
if (componentId.isNull())
|
||||
|
@ -1001,7 +971,7 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeId
|
|||
continue;
|
||||
}
|
||||
auto &poseIt = *findPoseResult;
|
||||
std::map<QString, QString> pose;
|
||||
std::map<QString, QString> pose = poseIt.second.attributes;
|
||||
pose["id"] = poseIt.second.id.toString();
|
||||
if (!poseIt.second.name.isEmpty())
|
||||
pose["name"] = poseIt.second.name;
|
||||
|
@ -1251,6 +1221,13 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
|
|||
newPose.id = newPoseId;
|
||||
const auto &poseAttributes = poseIt.first;
|
||||
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;
|
||||
oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(poseAttributes, "id"))] = newPoseId;
|
||||
poseIdList.push_back(newPoseId);
|
||||
|
@ -2394,32 +2371,6 @@ void Document::checkExportReadyState()
|
|||
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)
|
||||
{
|
||||
m_sharedContextWidget = sharedContextWidget;
|
||||
|
|
|
@ -195,6 +195,7 @@ public:
|
|||
QUuid id;
|
||||
QString name;
|
||||
bool dirty = true;
|
||||
std::map<QString, QString> attributes;
|
||||
std::map<QString, std::map<QString, QString>> parameters;
|
||||
void updatePreviewMesh(MeshLoader *previewMesh)
|
||||
{
|
||||
|
@ -424,6 +425,7 @@ signals:
|
|||
void poseListChanged();
|
||||
void poseNameChanged(QUuid poseId);
|
||||
void poseParametersChanged(QUuid poseId);
|
||||
void poseAttributesChanged(QUuid poseId);
|
||||
void posePreviewChanged(QUuid poseId);
|
||||
void motionAdded(QUuid motionId);
|
||||
void motionRemoved(QUuid motionId);
|
||||
|
@ -453,9 +455,6 @@ public: // need initialize
|
|||
public:
|
||||
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, Material> materialMap;
|
||||
std::vector<QUuid> materialIdList;
|
||||
|
@ -465,17 +464,12 @@ public:
|
|||
std::vector<QUuid> motionIdList;
|
||||
Component rootComponent;
|
||||
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 redoable() const override;
|
||||
bool hasPastableNodesInClipboard() const override;
|
||||
bool originSettled() const override;
|
||||
bool isNodeEditable(QUuid nodeId) 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 toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>(),
|
||||
DocumentToSnapshotFor forWhat=DocumentToSnapshotFor::Document,
|
||||
|
@ -601,6 +595,7 @@ public slots:
|
|||
void addPose(QString name, std::map<QString, std::map<QString, QString>> parameters);
|
||||
void removePose(QUuid poseId);
|
||||
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 addMotion(QString name, std::vector<MotionClip> clips);
|
||||
void removeMotion(QUuid motionId);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -37,15 +37,15 @@
|
|||
#include "fbxfile.h"
|
||||
#include "shortcuts.h"
|
||||
|
||||
int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16;
|
||||
int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16;
|
||||
int SkeletonDocumentWindow::m_modelRenderWidgetInitialSize = 128;
|
||||
int SkeletonDocumentWindow::m_skeletonRenderWidgetInitialX = SkeletonDocumentWindow::m_modelRenderWidgetInitialX + SkeletonDocumentWindow::m_modelRenderWidgetInitialSize + 16;
|
||||
int SkeletonDocumentWindow::m_skeletonRenderWidgetInitialY = SkeletonDocumentWindow::m_modelRenderWidgetInitialY;
|
||||
int SkeletonDocumentWindow::m_skeletonRenderWidgetInitialSize = SkeletonDocumentWindow::m_modelRenderWidgetInitialSize;
|
||||
int DocumentWindow::m_modelRenderWidgetInitialX = 16;
|
||||
int DocumentWindow::m_modelRenderWidgetInitialY = 16;
|
||||
int DocumentWindow::m_modelRenderWidgetInitialSize = 128;
|
||||
int DocumentWindow::m_skeletonRenderWidgetInitialX = DocumentWindow::m_modelRenderWidgetInitialX + DocumentWindow::m_modelRenderWidgetInitialSize + 16;
|
||||
int DocumentWindow::m_skeletonRenderWidgetInitialY = DocumentWindow::m_modelRenderWidgetInitialY;
|
||||
int DocumentWindow::m_skeletonRenderWidgetInitialSize = DocumentWindow::m_modelRenderWidgetInitialSize;
|
||||
|
||||
LogBrowser *g_logBrowser = nullptr;
|
||||
std::set<SkeletonDocumentWindow *> g_documentWindows;
|
||||
std::set<DocumentWindow *> g_documentWindows;
|
||||
QTextBrowser *g_acknowlegementsWidget = nullptr;
|
||||
AboutWidget *g_aboutWidget = 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);
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::showAcknowlegements()
|
||||
void DocumentWindow::showAcknowlegements()
|
||||
{
|
||||
if (!g_acknowlegementsWidget) {
|
||||
g_acknowlegementsWidget = new QTextBrowser;
|
||||
|
@ -72,7 +72,7 @@ void SkeletonDocumentWindow::showAcknowlegements()
|
|||
g_acknowlegementsWidget->raise();
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::showContributors()
|
||||
void DocumentWindow::showContributors()
|
||||
{
|
||||
if (!g_contributorsWidget) {
|
||||
g_contributorsWidget = new QTextBrowser;
|
||||
|
@ -89,7 +89,7 @@ void SkeletonDocumentWindow::showContributors()
|
|||
g_contributorsWidget->raise();
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::showAbout()
|
||||
void DocumentWindow::showAbout()
|
||||
{
|
||||
if (!g_aboutWidget) {
|
||||
g_aboutWidget = new AboutWidget;
|
||||
|
@ -99,7 +99,7 @@ void SkeletonDocumentWindow::showAbout()
|
|||
g_aboutWidget->raise();
|
||||
}
|
||||
|
||||
SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||
DocumentWindow::DocumentWindow() :
|
||||
m_document(nullptr),
|
||||
m_firstShow(true),
|
||||
m_documentSaved(true),
|
||||
|
@ -220,9 +220,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
|
||||
m_modelRenderWidget = new ModelWidget(containerWidget);
|
||||
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->move(SkeletonDocumentWindow::m_modelRenderWidgetInitialX, SkeletonDocumentWindow::m_modelRenderWidgetInitialY);
|
||||
m_modelRenderWidget->move(DocumentWindow::m_modelRenderWidgetInitialX, DocumentWindow::m_modelRenderWidgetInitialY);
|
||||
|
||||
m_graphicsWidget->setModelWidget(m_modelRenderWidget);
|
||||
containerWidget->setModelWidget(m_modelRenderWidget);
|
||||
|
@ -246,8 +246,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
materialDocker->setAllowedAreas(Qt::RightDockWidgetArea);
|
||||
MaterialManageWidget *materialManageWidget = new MaterialManageWidget(m_document, materialDocker);
|
||||
materialDocker->setWidget(materialManageWidget);
|
||||
connect(materialManageWidget, &MaterialManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog);
|
||||
connect(materialManageWidget, &MaterialManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog);
|
||||
connect(materialManageWidget, &MaterialManageWidget::registerDialog, this, &DocumentWindow::registerDialog);
|
||||
connect(materialManageWidget, &MaterialManageWidget::unregisterDialog, this, &DocumentWindow::unregisterDialog);
|
||||
addDockWidget(Qt::RightDockWidgetArea, materialDocker);
|
||||
connect(materialDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) {
|
||||
Q_UNUSED(topLevel);
|
||||
|
@ -269,8 +269,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
poseDocker->setAllowedAreas(Qt::RightDockWidgetArea);
|
||||
PoseManageWidget *poseManageWidget = new PoseManageWidget(m_document, poseDocker);
|
||||
poseDocker->setWidget(poseManageWidget);
|
||||
connect(poseManageWidget, &PoseManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog);
|
||||
connect(poseManageWidget, &PoseManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog);
|
||||
connect(poseManageWidget, &PoseManageWidget::registerDialog, this, &DocumentWindow::registerDialog);
|
||||
connect(poseManageWidget, &PoseManageWidget::unregisterDialog, this, &DocumentWindow::unregisterDialog);
|
||||
addDockWidget(Qt::RightDockWidgetArea, poseDocker);
|
||||
connect(poseDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) {
|
||||
Q_UNUSED(topLevel);
|
||||
|
@ -282,8 +282,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
motionDocker->setAllowedAreas(Qt::RightDockWidgetArea);
|
||||
MotionManageWidget *motionManageWidget = new MotionManageWidget(m_document, motionDocker);
|
||||
motionDocker->setWidget(motionManageWidget);
|
||||
connect(motionManageWidget, &MotionManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog);
|
||||
connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog);
|
||||
connect(motionManageWidget, &MotionManageWidget::registerDialog, this, &DocumentWindow::registerDialog);
|
||||
connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &DocumentWindow::unregisterDialog);
|
||||
addDockWidget(Qt::RightDockWidgetArea, motionDocker);
|
||||
|
||||
tabifyDockWidget(partTreeDocker, materialDocker);
|
||||
|
@ -309,27 +309,27 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
m_fileMenu = menuBar()->addMenu(tr("File"));
|
||||
|
||||
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_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_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_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_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_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->addSeparator();
|
||||
|
@ -337,11 +337,11 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
//m_exportMenu = m_fileMenu->addMenu(tr("Export"));
|
||||
|
||||
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_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_exportRenderedAsImageAction = new QAction(tr("Export as PNG..."), this);
|
||||
|
@ -355,7 +355,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
m_fileMenu->addSeparator();
|
||||
|
||||
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->addSeparator();
|
||||
|
@ -530,7 +530,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
m_resetModelWidgetPosAction = new QAction(tr("Show Model"), this);
|
||||
connect(m_resetModelWidgetPosAction, &QAction::triggered, [=]() {
|
||||
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);
|
||||
|
@ -610,7 +610,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
m_windowMenu->addAction(m_showDebugDialogAction);
|
||||
|
||||
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
|
||||
m_windowMenu->addAction(m_showAdvanceSettingAction);
|
||||
#endif
|
||||
|
@ -618,33 +618,33 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
m_helpMenu = menuBar()->addMenu(tr("Help"));
|
||||
|
||||
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->addSeparator();
|
||||
|
||||
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->addSeparator();
|
||||
|
||||
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_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->addSeparator();
|
||||
|
||||
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_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);
|
||||
|
||||
connect(containerWidget, &GraphicsContainerWidget::containerSizeChanged,
|
||||
|
@ -737,9 +737,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
connect(graphicsWidget, &SkeletonGraphicsWidget::enableAllPositionRelatedLocks, m_document, &Document::enableAllPositionRelatedLocks);
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::disableAllPositionRelatedLocks, m_document, &Document::disableAllPositionRelatedLocks);
|
||||
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::changeTurnaround, this, &SkeletonDocumentWindow::changeTurnaround);
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::save, this, &SkeletonDocumentWindow::save);
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::open, this, &SkeletonDocumentWindow::open);
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::changeTurnaround, this, &DocumentWindow::changeTurnaround);
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::save, this, &DocumentWindow::save);
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::open, this, &DocumentWindow::open);
|
||||
|
||||
connect(m_document, &Document::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded);
|
||||
connect(m_document, &Document::nodeRemoved, graphicsWidget, &SkeletonGraphicsWidget::nodeRemoved);
|
||||
|
@ -848,25 +848,25 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
//m_skeletonRenderWidget->setCursor(graphicsWidget->cursor());
|
||||
});
|
||||
|
||||
connect(m_document, &Document::skeletonChanged, this, &SkeletonDocumentWindow::documentChanged);
|
||||
connect(m_document, &Document::turnaroundChanged, this, &SkeletonDocumentWindow::documentChanged);
|
||||
connect(m_document, &Document::optionsChanged, this, &SkeletonDocumentWindow::documentChanged);
|
||||
connect(m_document, &Document::rigChanged, this, &SkeletonDocumentWindow::documentChanged);
|
||||
connect(m_document, &Document::skeletonChanged, this, &DocumentWindow::documentChanged);
|
||||
connect(m_document, &Document::turnaroundChanged, this, &DocumentWindow::documentChanged);
|
||||
connect(m_document, &Document::optionsChanged, this, &DocumentWindow::documentChanged);
|
||||
connect(m_document, &Document::rigChanged, this, &DocumentWindow::documentChanged);
|
||||
|
||||
connect(m_modelRenderWidget, &ModelWidget::customContextMenuRequested, [=](const QPoint &pos) {
|
||||
graphicsWidget->showContextMenu(graphicsWidget->mapFromGlobal(m_modelRenderWidget->mapToGlobal(pos)));
|
||||
});
|
||||
|
||||
connect(m_document, &Document::xlockStateChanged, this, &SkeletonDocumentWindow::updateXlockButtonState);
|
||||
connect(m_document, &Document::ylockStateChanged, this, &SkeletonDocumentWindow::updateYlockButtonState);
|
||||
connect(m_document, &Document::zlockStateChanged, this, &SkeletonDocumentWindow::updateZlockButtonState);
|
||||
connect(m_document, &Document::radiusLockStateChanged, this, &SkeletonDocumentWindow::updateRadiusLockButtonState);
|
||||
connect(m_document, &Document::xlockStateChanged, this, &DocumentWindow::updateXlockButtonState);
|
||||
connect(m_document, &Document::ylockStateChanged, this, &DocumentWindow::updateYlockButtonState);
|
||||
connect(m_document, &Document::zlockStateChanged, this, &DocumentWindow::updateZlockButtonState);
|
||||
connect(m_document, &Document::radiusLockStateChanged, this, &DocumentWindow::updateRadiusLockButtonState);
|
||||
|
||||
connect(m_rigWidget, &RigWidget::setRigType, m_document, &Document::setRigType);
|
||||
|
||||
connect(m_document, &Document::rigTypeChanged, m_rigWidget, &RigWidget::rigTypeChanged);
|
||||
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);
|
||||
|
||||
|
@ -893,7 +893,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
|
||||
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);
|
||||
timer->setInterval(250);
|
||||
|
@ -906,15 +906,15 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
timer->start();
|
||||
}
|
||||
|
||||
SkeletonDocumentWindow *SkeletonDocumentWindow::createDocumentWindow()
|
||||
DocumentWindow *DocumentWindow::createDocumentWindow()
|
||||
{
|
||||
SkeletonDocumentWindow *documentWindow = new SkeletonDocumentWindow();
|
||||
DocumentWindow *documentWindow = new DocumentWindow();
|
||||
documentWindow->setAttribute(Qt::WA_DeleteOnClose);
|
||||
documentWindow->showMaximized();
|
||||
return documentWindow;
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::closeEvent(QCloseEvent *event)
|
||||
void DocumentWindow::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
if (m_documentSaved) {
|
||||
event->accept();
|
||||
|
@ -931,21 +931,21 @@ void SkeletonDocumentWindow::closeEvent(QCloseEvent *event)
|
|||
event->ignore();
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::setCurrentFilename(const QString &filename)
|
||||
void DocumentWindow::setCurrentFilename(const QString &filename)
|
||||
{
|
||||
m_currentFilename = filename;
|
||||
m_documentSaved = true;
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::updateTitle()
|
||||
void DocumentWindow::updateTitle()
|
||||
{
|
||||
QString appName = APP_NAME;
|
||||
QString appVer = APP_HUMAN_VER;
|
||||
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) {
|
||||
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) {
|
||||
QMessageBox::StandardButton answer = QMessageBox::question(this,
|
||||
|
@ -973,7 +973,7 @@ void SkeletonDocumentWindow::newDocument()
|
|||
m_document->saveSnapshot();
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::saveAs()
|
||||
void DocumentWindow::saveAs()
|
||||
{
|
||||
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
|
||||
tr("Dust3D Document (*.ds3)"));
|
||||
|
@ -983,50 +983,50 @@ void SkeletonDocumentWindow::saveAs()
|
|||
saveTo(filename);
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::saveAll()
|
||||
void DocumentWindow::saveAll()
|
||||
{
|
||||
for (auto &window: g_documentWindows) {
|
||||
window->save();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::viewSource()
|
||||
void DocumentWindow::viewSource()
|
||||
{
|
||||
QString url = APP_REPOSITORY_URL;
|
||||
qDebug() << "viewSource:" << 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;
|
||||
qDebug() << "reportIssues:" << url;
|
||||
QDesktopServices::openUrl(QUrl(url));
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::seeReferenceGuide()
|
||||
void DocumentWindow::seeReferenceGuide()
|
||||
{
|
||||
QString url = APP_REFERENCE_GUIDE_URL;
|
||||
qDebug() << "referenceGuide:" << 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;
|
||||
font.setWeight(QFont::Light);
|
||||
|
@ -1039,12 +1039,12 @@ void SkeletonDocumentWindow::initLockButton(QPushButton *button)
|
|||
button->setFocusPolicy(Qt::NoFocus);
|
||||
}
|
||||
|
||||
SkeletonDocumentWindow::~SkeletonDocumentWindow()
|
||||
DocumentWindow::~DocumentWindow()
|
||||
{
|
||||
g_documentWindows.erase(this);
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::showEvent(QShowEvent *event)
|
||||
void DocumentWindow::showEvent(QShowEvent *event)
|
||||
{
|
||||
QMainWindow::showEvent(event);
|
||||
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);
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::changeTurnaround()
|
||||
void DocumentWindow::changeTurnaround()
|
||||
{
|
||||
QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(),
|
||||
tr("Image Files (*.png *.jpg *.bmp)")).trimmed();
|
||||
|
@ -1073,12 +1073,12 @@ void SkeletonDocumentWindow::changeTurnaround()
|
|||
m_document->updateTurnaround(image);
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::save()
|
||||
void DocumentWindow::save()
|
||||
{
|
||||
saveTo(m_currentFilename);
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename)
|
||||
void DocumentWindow::saveTo(const QString &saveAsFilename)
|
||||
{
|
||||
QString filename = saveAsFilename;
|
||||
|
||||
|
@ -1111,6 +1111,7 @@ void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename)
|
|||
ds3Writer.add("canvas.png", "asset", &imageByteArray);
|
||||
}
|
||||
|
||||
std::set<QUuid> imageIds;
|
||||
for (auto &material: snapshot.materials) {
|
||||
for (auto &layer: material.second) {
|
||||
for (auto &mapItem: layer.second) {
|
||||
|
@ -1118,6 +1119,20 @@ void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename)
|
|||
if (findImageIdString == mapItem.end())
|
||||
continue;
|
||||
QUuid imageId = QUuid(findImageIdString->second);
|
||||
imageIds.insert(imageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -1128,8 +1143,6 @@ void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename)
|
|||
if (imageByteArray.size() > 0)
|
||||
ds3Writer.add("images/" + imageId.toString() + ".png", "asset", &imageByteArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ds3Writer.save(filename)) {
|
||||
setCurrentFilename(filename);
|
||||
|
@ -1138,7 +1151,7 @@ void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename)
|
|||
QApplication::restoreOverrideCursor();
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::open()
|
||||
void DocumentWindow::open()
|
||||
{
|
||||
if (!m_documentSaved) {
|
||||
QMessageBox::StandardButton answer = QMessageBox::question(this,
|
||||
|
@ -1202,7 +1215,7 @@ void SkeletonDocumentWindow::open()
|
|||
setCurrentFilename(filename);
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::showAdvanceSetting()
|
||||
void DocumentWindow::showAdvanceSetting()
|
||||
{
|
||||
if (nullptr == m_advanceSettingWidget) {
|
||||
m_advanceSettingWidget = new AdvanceSettingWidget(m_document, this);
|
||||
|
@ -1211,7 +1224,7 @@ void SkeletonDocumentWindow::showAdvanceSetting()
|
|||
m_advanceSettingWidget->raise();
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::exportObjResult()
|
||||
void DocumentWindow::exportObjResult()
|
||||
{
|
||||
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
|
||||
tr("Wavefront (*.obj)"));
|
||||
|
@ -1227,13 +1240,13 @@ void SkeletonDocumentWindow::exportObjResult()
|
|||
QApplication::restoreOverrideCursor();
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::showExportPreview()
|
||||
void DocumentWindow::showExportPreview()
|
||||
{
|
||||
if (nullptr == m_exportPreviewWidget) {
|
||||
m_exportPreviewWidget = new ExportPreviewWidget(m_document, this);
|
||||
connect(m_exportPreviewWidget, &ExportPreviewWidget::regenerate, m_document, &Document::regenerateMesh);
|
||||
connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsGlb, this, &SkeletonDocumentWindow::exportGlbResult);
|
||||
connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsFbx, this, &SkeletonDocumentWindow::exportFbxResult);
|
||||
connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsGlb, this, &DocumentWindow::exportGlbResult);
|
||||
connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsFbx, this, &DocumentWindow::exportFbxResult);
|
||||
connect(m_document, &Document::resultMeshChanged, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner);
|
||||
connect(m_document, &Document::exportReady, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner);
|
||||
connect(m_document, &Document::resultTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
|
||||
|
@ -1244,7 +1257,7 @@ void SkeletonDocumentWindow::showExportPreview()
|
|||
m_exportPreviewWidget->raise();
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::exportFbxResult()
|
||||
void DocumentWindow::exportFbxResult()
|
||||
{
|
||||
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
|
||||
tr("Autodesk FBX (.fbx)"));
|
||||
|
@ -1270,7 +1283,7 @@ void SkeletonDocumentWindow::exportFbxResult()
|
|||
QApplication::restoreOverrideCursor();
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::exportGlbResult()
|
||||
void DocumentWindow::exportGlbResult()
|
||||
{
|
||||
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
|
||||
tr("glTF Binary Format (.glb)"));
|
||||
|
@ -1296,7 +1309,7 @@ void SkeletonDocumentWindow::exportGlbResult()
|
|||
QApplication::restoreOverrideCursor();
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::updateXlockButtonState()
|
||||
void DocumentWindow::updateXlockButtonState()
|
||||
{
|
||||
if (m_document->xlocked)
|
||||
m_xlockButton->setStyleSheet("QPushButton {color: #252525}");
|
||||
|
@ -1304,7 +1317,7 @@ void SkeletonDocumentWindow::updateXlockButtonState()
|
|||
m_xlockButton->setStyleSheet("QPushButton {color: #fc6621}");
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::updateYlockButtonState()
|
||||
void DocumentWindow::updateYlockButtonState()
|
||||
{
|
||||
if (m_document->ylocked)
|
||||
m_ylockButton->setStyleSheet("QPushButton {color: #252525}");
|
||||
|
@ -1312,7 +1325,7 @@ void SkeletonDocumentWindow::updateYlockButtonState()
|
|||
m_ylockButton->setStyleSheet("QPushButton {color: #2a5aac}");
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::updateZlockButtonState()
|
||||
void DocumentWindow::updateZlockButtonState()
|
||||
{
|
||||
if (m_document->zlocked)
|
||||
m_zlockButton->setStyleSheet("QPushButton {color: #252525}");
|
||||
|
@ -1320,7 +1333,7 @@ void SkeletonDocumentWindow::updateZlockButtonState()
|
|||
m_zlockButton->setStyleSheet("QPushButton {color: #aaebc4}");
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::updateRadiusLockButtonState()
|
||||
void DocumentWindow::updateRadiusLockButtonState()
|
||||
{
|
||||
if (m_document->radiusLocked)
|
||||
m_radiusLockButton->setStyleSheet("QPushButton {color: #252525}");
|
||||
|
@ -1328,7 +1341,7 @@ void SkeletonDocumentWindow::updateRadiusLockButtonState()
|
|||
m_radiusLockButton->setStyleSheet("QPushButton {color: " + Theme::white.name() + "}");
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::updateRigWeightRenderWidget()
|
||||
void DocumentWindow::updateRigWeightRenderWidget()
|
||||
{
|
||||
MeshLoader *resultRigWeightMesh = m_document->takeResultRigWeightMesh();
|
||||
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);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
|
|
@ -17,15 +17,15 @@
|
|||
|
||||
class SkeletonGraphicsWidget;
|
||||
|
||||
class SkeletonDocumentWindow : public QMainWindow
|
||||
class DocumentWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void initialized();
|
||||
public:
|
||||
SkeletonDocumentWindow();
|
||||
~SkeletonDocumentWindow();
|
||||
static SkeletonDocumentWindow *createDocumentWindow();
|
||||
DocumentWindow();
|
||||
~DocumentWindow();
|
||||
static DocumentWindow *createDocumentWindow();
|
||||
static void showAcknowlegements();
|
||||
static void showContributors();
|
||||
static void showAbout();
|
||||
|
|
|
@ -8,50 +8,7 @@ GenericPoser::GenericPoser(const std::vector<RiggerBone> &bones) :
|
|||
|
||||
void GenericPoser::commit()
|
||||
{
|
||||
for (const auto &item: parameters()) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
// TODO:
|
||||
|
||||
Poser::commit();
|
||||
}
|
||||
|
|
|
@ -302,7 +302,7 @@ bool GenericRigger::rig()
|
|||
m_resultBones.push_back(RiggerBone());
|
||||
RiggerBone &bodyBone = m_resultBones.back();
|
||||
bodyBone.index = m_resultBones.size() - 1;
|
||||
bodyBone.name = "Body";
|
||||
bodyBone.name = Rigger::rootBoneName;
|
||||
bodyBone.headPosition = QVector3D(0, 0, 0);
|
||||
bodyBone.hasButton = true;
|
||||
bodyBone.button = {spineNodes.size(), 0};
|
||||
|
@ -378,8 +378,8 @@ bool GenericRigger::rig()
|
|||
//qDebug() << spineBone.name << "head:" << spineBone.headPosition << "tail:" << spineBone.tailPosition;
|
||||
|
||||
if (1 == spineGenerateOrder) {
|
||||
m_resultBones[boneIndexMap["Body"]].tailPosition = spineBone.headPosition;
|
||||
m_resultBones[boneIndexMap["Body"]].children.push_back(spineBone.index);
|
||||
m_resultBones[boneIndexMap[Rigger::rootBoneName]].tailPosition = spineBone.headPosition;
|
||||
m_resultBones[boneIndexMap[Rigger::rootBoneName]].children.push_back(spineBone.index);
|
||||
} else {
|
||||
m_resultBones[boneIndexMap["Spine" + QString::number(spineGenerateOrder - 1)]].tailPosition = spineBone.headPosition;
|
||||
m_resultBones[boneIndexMap["Spine" + QString::number(spineGenerateOrder - 1)]].children.push_back(spineBone.index);
|
||||
|
@ -396,7 +396,7 @@ bool GenericRigger::rig()
|
|||
ribBone.headPosition = spineBoneHeadPosition;
|
||||
boneIndexMap[ribBone.name] = ribBone.index;
|
||||
if (1 == spineGenerateOrder) {
|
||||
m_resultBones[boneIndexMap["Body"]].children.push_back(ribBone.index);
|
||||
m_resultBones[boneIndexMap[Rigger::rootBoneName]].children.push_back(ribBone.index);
|
||||
} else {
|
||||
m_resultBones[boneIndexMap["Spine" + QString::number(spineGenerateOrder)]].children.push_back(ribBone.index);
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ int main(int argc, char ** argv)
|
|||
font.setBold(false);
|
||||
QApplication::setFont(font);
|
||||
|
||||
SkeletonDocumentWindow::createDocumentWindow();
|
||||
DocumentWindow::createDocumentWindow();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
|
|
@ -22,14 +22,21 @@ MotionManageWidget::MotionManageWidget(const Document *document, QWidget *parent
|
|||
connect(m_motionListWidget, &MotionListWidget::modifyMotion, this, &MotionManageWidget::showMotionDialog);
|
||||
|
||||
InfoLabel *infoLabel = new InfoLabel;
|
||||
infoLabel->setText(tr("Missing Rig"));
|
||||
infoLabel->show();
|
||||
|
||||
auto refreshInfoLabel = [=]() {
|
||||
if (m_document->currentRigSucceed()) {
|
||||
if (m_document->rigType == RigType::Tetrapod) {
|
||||
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 {
|
||||
infoLabel->setText(tr("Missing Rig"));
|
||||
infoLabel->show();
|
||||
addMotionButton->hide();
|
||||
}
|
||||
|
|
|
@ -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>> ¶meters)
|
||||
{
|
||||
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>> ¶meters) 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);
|
||||
}
|
||||
}
|
|
@ -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>> ¶meters) const;
|
||||
void fromParameters(const std::vector<RiggerBone> *rigBones,
|
||||
const std::map<QString, std::map<QString, QString>> ¶meters);
|
||||
|
||||
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
|
|
@ -6,15 +6,21 @@
|
|||
#include <QWidgetAction>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
#include "theme.h"
|
||||
#include "poseeditwidget.h"
|
||||
#include "floatnumberwidget.h"
|
||||
#include "version.h"
|
||||
#include "poserconstruct.h"
|
||||
#include "graphicscontainerwidget.h"
|
||||
#include "documentwindow.h"
|
||||
#include "shortcuts.h"
|
||||
#include "imageforever.h"
|
||||
|
||||
PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
m_document(document)
|
||||
m_document(document),
|
||||
m_poseDocument(new PoseDocument)
|
||||
{
|
||||
m_posePreviewManager = new PosePreviewManager();
|
||||
connect(m_posePreviewManager, &PosePreviewManager::renderDone, [=]() {
|
||||
|
@ -29,15 +35,53 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
|
|||
m_previewWidget->updateMesh(m_posePreviewManager->takeResultPreviewMesh());
|
||||
});
|
||||
|
||||
m_previewWidget = new ModelWidget(this);
|
||||
m_previewWidget->setMinimumSize(128, 128);
|
||||
m_previewWidget->resize(384, 384);
|
||||
m_previewWidget->move(-64, -64+22);
|
||||
SkeletonGraphicsWidget *graphicsWidget = new SkeletonGraphicsWidget(m_poseDocument);
|
||||
graphicsWidget->setNodePositionModifyOnly(true);
|
||||
m_poseGraphicsWidget = graphicsWidget;
|
||||
|
||||
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;
|
||||
paramtersLayout->setContentsMargins(0, 480, 0, 0);
|
||||
paramtersLayout->addStretch();
|
||||
paramtersLayout->addSpacing(20);
|
||||
paramtersLayout->addWidget(containerWidget);
|
||||
|
||||
m_nameEdit = new QLineEdit;
|
||||
m_nameEdit->setFixedWidth(200);
|
||||
|
@ -49,9 +93,17 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
|
|||
connect(saveButton, &QPushButton::clicked, this, &PoseEditWidget::save);
|
||||
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;
|
||||
baseInfoLayout->addWidget(new QLabel(tr("Name")));
|
||||
baseInfoLayout->addWidget(m_nameEdit);
|
||||
baseInfoLayout->addWidget(changeReferenceSheet);
|
||||
baseInfoLayout->addStretch();
|
||||
baseInfoLayout->addWidget(saveButton);
|
||||
|
||||
|
@ -62,7 +114,7 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
|
|||
|
||||
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, [=]() {
|
||||
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::renamePose, m_document, &Document::renamePose);
|
||||
connect(this, &PoseEditWidget::setPoseParameters, m_document, &Document::setPoseParameters);
|
||||
connect(this, &PoseEditWidget::setPoseAttributes, m_document, &Document::setPoseAttributes);
|
||||
|
||||
updateButtons();
|
||||
updatePreview();
|
||||
updatePoseDocument();
|
||||
updateTitle();
|
||||
m_poseDocument->saveHistoryItem();
|
||||
}
|
||||
|
||||
void PoseEditWidget::updateButtons()
|
||||
void PoseEditWidget::changeTurnaround()
|
||||
{
|
||||
delete m_buttonsContainer;
|
||||
m_buttonsContainer = new QWidget(this);
|
||||
m_buttonsContainer->resize(600, 500);
|
||||
m_buttonsContainer->move(256, 0);
|
||||
m_buttonsContainer->show();
|
||||
|
||||
QGridLayout *marksContainerLayout = new QGridLayout;
|
||||
marksContainerLayout->setContentsMargins(0, 0, 0, 0);
|
||||
marksContainerLayout->setSpacing(2);
|
||||
|
||||
QFont buttonFont;
|
||||
buttonFont.setWeight(QFont::Light);
|
||||
buttonFont.setPixelSize(7);
|
||||
buttonFont.setBold(false);
|
||||
|
||||
std::map<QString, std::tuple<QPushButton *, PopupWidgetType>> buttons;
|
||||
const std::vector<RiggerBone> *rigBones = m_document->resultRigBones();
|
||||
if (nullptr != rigBones && !rigBones->empty()) {
|
||||
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);
|
||||
}
|
||||
QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(),
|
||||
tr("Image Files (*.png *.jpg *.bmp)")).trimmed();
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
QImage image;
|
||||
if (!image.load(fileName))
|
||||
return;
|
||||
m_imageId = ImageForever::add(&image);
|
||||
m_attributes["canvasImageId"] = m_imageId.toString();
|
||||
m_poseDocument->updateTurnaround(image);
|
||||
}
|
||||
|
||||
marksContainerLayout->setSizeConstraint(QLayout::SizeConstraint::SetMinimumSize);
|
||||
QUuid PoseEditWidget::findImageIdFromAttributes(const std::map<QString, QString> &attributes)
|
||||
{
|
||||
auto findImageIdResult = attributes.find("canvasImageId");
|
||||
if (findImageIdResult == attributes.end())
|
||||
return QUuid();
|
||||
return QUuid(findImageIdResult->second);
|
||||
}
|
||||
|
||||
QVBoxLayout *mainLayouer = new QVBoxLayout;
|
||||
mainLayouer->addStretch();
|
||||
mainLayouer->addLayout(marksContainerLayout);
|
||||
mainLayouer->addStretch();
|
||||
|
||||
m_buttonsContainer->setLayout(mainLayouer);
|
||||
void PoseEditWidget::updatePoseDocument()
|
||||
{
|
||||
m_poseDocument->fromParameters(m_document->resultRigBones(), m_parameters);
|
||||
QUuid imageId = findImageIdFromAttributes(m_attributes);
|
||||
auto image = ImageForever::get(imageId);
|
||||
if (nullptr != image)
|
||||
m_poseDocument->updateTurnaround(*image);
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
void PoseEditWidget::reject()
|
||||
|
@ -141,10 +181,6 @@ void PoseEditWidget::closeEvent(QCloseEvent *event)
|
|||
}
|
||||
m_closed = true;
|
||||
hide();
|
||||
if (m_openedMenuCount > 0) {
|
||||
event->ignore();
|
||||
return;
|
||||
}
|
||||
if (m_posePreviewManager->isRendering()) {
|
||||
event->ignore();
|
||||
return;
|
||||
|
@ -160,6 +196,7 @@ QSize PoseEditWidget::sizeHint() const
|
|||
PoseEditWidget::~PoseEditWidget()
|
||||
{
|
||||
delete m_posePreviewManager;
|
||||
delete m_poseDocument;
|
||||
}
|
||||
|
||||
void PoseEditWidget::updatePreview()
|
||||
|
@ -214,157 +251,6 @@ void PoseEditWidget::updateTitle()
|
|||
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)
|
||||
{
|
||||
m_nameEdit->setText(name);
|
||||
|
@ -374,6 +260,15 @@ void PoseEditWidget::setEditPoseName(QString name)
|
|||
void PoseEditWidget::setEditParameters(std::map<QString, std::map<QString, QString>> parameters)
|
||||
{
|
||||
m_parameters = parameters;
|
||||
updatePoseDocument();
|
||||
updatePreview();
|
||||
m_poseDocument->saveHistoryItem();
|
||||
}
|
||||
|
||||
void PoseEditWidget::setEditAttributes(std::map<QString, QString> attributes)
|
||||
{
|
||||
m_attributes = attributes;
|
||||
updatePoseDocument();
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
|
@ -390,6 +285,7 @@ void PoseEditWidget::save()
|
|||
} else if (m_unsaved) {
|
||||
emit renamePose(m_poseId, m_nameEdit->text());
|
||||
emit setPoseParameters(m_poseId, m_parameters);
|
||||
emit setPoseAttributes(m_poseId, m_attributes);
|
||||
}
|
||||
m_unsaved = false;
|
||||
close();
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include "document.h"
|
||||
#include "modelwidget.h"
|
||||
#include "rigger.h"
|
||||
#include "skeletongraphicswidget.h"
|
||||
#include "posedocument.h"
|
||||
|
||||
typedef RiggerButtonParameterType PopupWidgetType;
|
||||
|
||||
|
@ -18,21 +20,23 @@ signals:
|
|||
void addPose(QString name, std::map<QString, std::map<QString, QString>> parameters);
|
||||
void removePose(QUuid poseId);
|
||||
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 parametersAdjusted();
|
||||
public:
|
||||
PoseEditWidget(const Document *document, QWidget *parent=nullptr);
|
||||
~PoseEditWidget();
|
||||
public slots:
|
||||
void updateButtons();
|
||||
void updatePoseDocument();
|
||||
void updatePreview();
|
||||
void showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos);
|
||||
void setEditPoseId(QUuid poseId);
|
||||
void setEditPoseName(QString name);
|
||||
void setEditParameters(std::map<QString, std::map<QString, QString>> parameters);
|
||||
void setEditAttributes(std::map<QString, QString> attributes);
|
||||
void updateTitle();
|
||||
void save();
|
||||
void clearUnsaveState();
|
||||
void changeTurnaround();
|
||||
protected:
|
||||
QSize sizeHint() const override;
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
|
@ -44,11 +48,14 @@ private:
|
|||
bool m_isPreviewDirty = false;
|
||||
bool m_closed = false;
|
||||
std::map<QString, std::map<QString, QString>> m_parameters;
|
||||
size_t m_openedMenuCount = 0;
|
||||
std::map<QString, QString> m_attributes;
|
||||
QUuid m_poseId;
|
||||
bool m_unsaved = false;
|
||||
QUuid m_imageId;
|
||||
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
|
||||
|
|
|
@ -22,14 +22,21 @@ PoseManageWidget::PoseManageWidget(const Document *document, QWidget *parent) :
|
|||
connect(m_poseListWidget, &PoseListWidget::modifyPose, this, &PoseManageWidget::showPoseDialog);
|
||||
|
||||
InfoLabel *infoLabel = new InfoLabel;
|
||||
infoLabel->setText(tr("Missing Rig"));
|
||||
infoLabel->show();
|
||||
|
||||
auto refreshInfoLabel = [=]() {
|
||||
if (m_document->currentRigSucceed()) {
|
||||
if (m_document->rigType == RigType::Tetrapod) {
|
||||
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 {
|
||||
infoLabel->setText(tr("Missing Rig"));
|
||||
infoLabel->show();
|
||||
addPoseButton->hide();
|
||||
}
|
||||
|
@ -71,6 +78,7 @@ void PoseManageWidget::showPoseDialog(QUuid poseId)
|
|||
poseEditWidget->setEditPoseId(poseId);
|
||||
poseEditWidget->setEditPoseName(pose->name);
|
||||
poseEditWidget->setEditParameters(pose->parameters);
|
||||
poseEditWidget->setEditAttributes(pose->attributes);
|
||||
poseEditWidget->clearUnsaveState();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "rigger.h"
|
||||
|
||||
size_t Rigger::m_maxCutOffSplitterExpandRound = 3;
|
||||
QString Rigger::rootBoneName = "Body";
|
||||
|
||||
Rigger::Rigger(const std::vector<QVector3D> &verticesPositions,
|
||||
const std::set<MeshSplitterTriangle> &inputTriangles) :
|
||||
|
|
|
@ -125,6 +125,7 @@ public:
|
|||
const std::vector<QString> &missingMarkNames();
|
||||
const std::vector<QString> &errorMarkNames();
|
||||
virtual bool rig() = 0;
|
||||
static QString rootBoneName;
|
||||
protected:
|
||||
virtual bool validate() = 0;
|
||||
virtual bool isCutOffSplitter(BoneMark boneMark) = 0;
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
#include "shortcuts.h"
|
||||
|
||||
#define defineKey(keyVal, funcName) do { \
|
||||
auto key = new QShortcut(mainWindow); \
|
||||
auto key = new QShortcut(widget); \
|
||||
key->setKey(keyVal); \
|
||||
QObject::connect(key, &QShortcut::activated, \
|
||||
graphicsWidget, funcName); \
|
||||
} while (false)
|
||||
|
||||
void initShortCuts(QMainWindow *mainWindow, SkeletonGraphicsWidget *graphicsWidget)
|
||||
void initShortCuts(QWidget *widget, SkeletonGraphicsWidget *graphicsWidget)
|
||||
{
|
||||
defineKey(Qt::Key_Delete, &SkeletonGraphicsWidget::shortcutDelete);
|
||||
defineKey(Qt::Key_Backspace, &SkeletonGraphicsWidget::shortcutDelete);
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
#include <QMainWindow>
|
||||
#include "skeletongraphicswidget.h"
|
||||
|
||||
void initShortCuts(QMainWindow *mainWindow, SkeletonGraphicsWidget *graphicsWidget);
|
||||
void initShortCuts(QWidget *widget, SkeletonGraphicsWidget *graphicsWidget);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,2 +1,73 @@
|
|||
#include <QDebug>
|
||||
#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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -200,18 +200,22 @@ public:
|
|||
bool zlocked = false;
|
||||
bool radiusLocked = false;
|
||||
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 redoable() const = 0;
|
||||
virtual bool hasPastableNodesInClipboard() const = 0;
|
||||
virtual bool originSettled() const = 0;
|
||||
virtual bool isNodeEditable(QUuid nodeId) 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;
|
||||
|
||||
public slots:
|
||||
|
|
|
@ -44,7 +44,8 @@ SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document)
|
|||
m_eventForwardingToModelWidget(false),
|
||||
m_modelWidget(nullptr),
|
||||
m_inTempDragMode(false),
|
||||
m_modeBeforeEnterTempDragMode(SkeletonDocumentEditMode::Select)
|
||||
m_modeBeforeEnterTempDragMode(SkeletonDocumentEditMode::Select),
|
||||
m_nodePositionModifyOnly(false)
|
||||
{
|
||||
setRenderHint(QPainter::Antialiasing, false);
|
||||
setBackgroundBrush(QBrush(QWidget::palette().color(QWidget::backgroundRole()), Qt::SolidPattern));
|
||||
|
@ -118,10 +119,12 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
|||
QMenu contextMenu(this);
|
||||
|
||||
QAction addAction(tr("Add..."), this);
|
||||
if (!m_nodePositionModifyOnly) {
|
||||
connect(&addAction, &QAction::triggered, [=]() {
|
||||
emit setEditMode(SkeletonDocumentEditMode::Add);
|
||||
});
|
||||
contextMenu.addAction(&addAction);
|
||||
}
|
||||
|
||||
QAction undoAction(tr("Undo"), this);
|
||||
if (m_document->undoable()) {
|
||||
|
@ -136,67 +139,67 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
|||
}
|
||||
|
||||
QAction deleteAction(tr("Delete"), this);
|
||||
if (hasSelection()) {
|
||||
if (!m_nodePositionModifyOnly && hasSelection()) {
|
||||
connect(&deleteAction, &QAction::triggered, this, &SkeletonGraphicsWidget::deleteSelected);
|
||||
contextMenu.addAction(&deleteAction);
|
||||
}
|
||||
|
||||
QAction breakAction(tr("Break"), this);
|
||||
if (hasEdgeSelection()) {
|
||||
if (!m_nodePositionModifyOnly && hasEdgeSelection()) {
|
||||
connect(&breakAction, &QAction::triggered, this, &SkeletonGraphicsWidget::breakSelected);
|
||||
contextMenu.addAction(&breakAction);
|
||||
}
|
||||
|
||||
QAction connectAction(tr("Connect"), this);
|
||||
if (hasTwoDisconnectedNodesSelection()) {
|
||||
if (!m_nodePositionModifyOnly && hasTwoDisconnectedNodesSelection()) {
|
||||
connect(&connectAction, &QAction::triggered, this, &SkeletonGraphicsWidget::connectSelected);
|
||||
contextMenu.addAction(&connectAction);
|
||||
}
|
||||
|
||||
QAction cutAction(tr("Cut"), this);
|
||||
if (hasSelection()) {
|
||||
if (!m_nodePositionModifyOnly && hasSelection()) {
|
||||
connect(&cutAction, &QAction::triggered, this, &SkeletonGraphicsWidget::cut);
|
||||
contextMenu.addAction(&cutAction);
|
||||
}
|
||||
|
||||
QAction copyAction(tr("Copy"), this);
|
||||
if (hasSelection()) {
|
||||
if (!m_nodePositionModifyOnly && hasSelection()) {
|
||||
connect(©Action, &QAction::triggered, this, &SkeletonGraphicsWidget::copy);
|
||||
contextMenu.addAction(©Action);
|
||||
}
|
||||
|
||||
QAction pasteAction(tr("Paste"), this);
|
||||
if (m_document->hasPastableNodesInClipboard()) {
|
||||
if (!m_nodePositionModifyOnly && m_document->hasPastableNodesInClipboard()) {
|
||||
connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste);
|
||||
contextMenu.addAction(&pasteAction);
|
||||
}
|
||||
|
||||
QAction flipHorizontallyAction(tr("H Flip"), this);
|
||||
if (hasMultipleSelection()) {
|
||||
if (!m_nodePositionModifyOnly && hasMultipleSelection()) {
|
||||
connect(&flipHorizontallyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::flipHorizontally);
|
||||
contextMenu.addAction(&flipHorizontallyAction);
|
||||
}
|
||||
|
||||
QAction flipVerticallyAction(tr("V Flip"), this);
|
||||
if (hasMultipleSelection()) {
|
||||
if (!m_nodePositionModifyOnly && hasMultipleSelection()) {
|
||||
connect(&flipVerticallyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::flipVertically);
|
||||
contextMenu.addAction(&flipVerticallyAction);
|
||||
}
|
||||
|
||||
QAction rotateClockwiseAction(tr("Rotate 90D CW"), this);
|
||||
if (hasMultipleSelection()) {
|
||||
if (!m_nodePositionModifyOnly && hasMultipleSelection()) {
|
||||
connect(&rotateClockwiseAction, &QAction::triggered, this, &SkeletonGraphicsWidget::rotateClockwise90Degree);
|
||||
contextMenu.addAction(&rotateClockwiseAction);
|
||||
}
|
||||
|
||||
QAction rotateCounterclockwiseAction(tr("Rotate 90D CCW"), this);
|
||||
if (hasMultipleSelection()) {
|
||||
if (!m_nodePositionModifyOnly && hasMultipleSelection()) {
|
||||
connect(&rotateCounterclockwiseAction, &QAction::triggered, this, &SkeletonGraphicsWidget::rotateCounterclockwise90Degree);
|
||||
contextMenu.addAction(&rotateCounterclockwiseAction);
|
||||
}
|
||||
|
||||
QAction switchXzAction(tr("Switch XZ"), this);
|
||||
if (hasSelection()) {
|
||||
if (!m_nodePositionModifyOnly && hasSelection()) {
|
||||
connect(&switchXzAction, &QAction::triggered, this, &SkeletonGraphicsWidget::switchSelectedXZ);
|
||||
contextMenu.addAction(&switchXzAction);
|
||||
}
|
||||
|
@ -207,7 +210,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
|||
QAction alignToGlobalCenterAction(tr("Global Center"), this);
|
||||
QAction alignToGlobalVerticalCenterAction(tr("Global Vertical 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"));
|
||||
|
||||
if (hasMultipleSelection()) {
|
||||
|
@ -238,7 +241,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
|||
for (int i = 0; i < (int)BoneMark::Count - 1; i++) {
|
||||
markAsActions[i] = nullptr;
|
||||
}
|
||||
if (hasNodeSelection()) {
|
||||
if (!m_nodePositionModifyOnly && hasNodeSelection()) {
|
||||
QMenu *subMenu = contextMenu.addMenu(tr("Mark As"));
|
||||
|
||||
connect(&markAsNoneAction, &QAction::triggered, [=]() {
|
||||
|
@ -265,7 +268,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
|||
}
|
||||
|
||||
QAction selectPartAllAction(tr("Select Part"), this);
|
||||
if (hasItems()) {
|
||||
if (!m_nodePositionModifyOnly && hasItems()) {
|
||||
connect(&selectPartAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::selectPartAll);
|
||||
contextMenu.addAction(&selectPartAllAction);
|
||||
}
|
||||
|
@ -2456,3 +2459,8 @@ void SkeletonGraphicsWidget::setSelectedNodesBoneMark(BoneMark boneMark)
|
|||
emit groupOperationAdded();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonGraphicsWidget::setNodePositionModifyOnly(bool nodePositionModifyOnly)
|
||||
{
|
||||
m_nodePositionModifyOnly = nodePositionModifyOnly;
|
||||
}
|
||||
|
|
|
@ -408,6 +408,7 @@ public:
|
|||
bool hasNodeSelection();
|
||||
bool hasTwoDisconnectedNodesSelection();
|
||||
void setModelWidget(ModelWidget *modelWidget);
|
||||
void setNodePositionModifyOnly(bool nodePositionModifyOnly);
|
||||
protected:
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void wheelEvent(QWheelEvent *event) override;
|
||||
|
@ -557,6 +558,7 @@ private: //need initalize
|
|||
ModelWidget *m_modelWidget;
|
||||
bool m_inTempDragMode;
|
||||
SkeletonDocumentEditMode m_modeBeforeEnterTempDragMode;
|
||||
bool m_nodePositionModifyOnly;
|
||||
private:
|
||||
QVector3D m_ikMoveTarget;
|
||||
QUuid m_ikMoveEndEffectorId;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#include <cmath>
|
||||
#include <QtMath>
|
||||
#include "tetrapodposer.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()) {
|
||||
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];
|
||||
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();
|
||||
auto findTranslateXResult = item.second.find("translateX");
|
||||
auto findTranslateYResult = item.second.find("translateY");
|
||||
auto findTranslateZResult = item.second.find("translateZ");
|
||||
if (findTranslateXResult != item.second.end() ||
|
||||
findTranslateYResult != item.second.end() ||
|
||||
findTranslateZResult != item.second.end()) {
|
||||
float x = valueOfKeyInMapOrEmpty(item.second, "translateX").toFloat();
|
||||
float y = valueOfKeyInMapOrEmpty(item.second, "translateY").toFloat();
|
||||
float z = valueOfKeyInMapOrEmpty(item.second, "translateZ").toFloat();
|
||||
QVector3D translation = {x, y, z};
|
||||
m_jointNodeTree.addTranslation(boneIndex, translation);
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#ifndef DUST3D_TETRAPOD_POSER_H
|
||||
#define DUST3D_TETRAPOD_POSER_H
|
||||
#include <vector>
|
||||
#include "poser.h"
|
||||
|
||||
class TetrapodPoser : public Poser
|
||||
|
@ -7,8 +8,13 @@ class TetrapodPoser : public Poser
|
|||
Q_OBJECT
|
||||
public:
|
||||
TetrapodPoser(const std::vector<RiggerBone> &bones);
|
||||
public:
|
||||
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
|
||||
|
|
|
@ -371,7 +371,7 @@ bool TetrapodRigger::rig()
|
|||
m_resultBones.push_back(RiggerBone());
|
||||
RiggerBone &bodyBone = m_resultBones.back();
|
||||
bodyBone.index = m_resultBones.size() - 1;
|
||||
bodyBone.name = "Body";
|
||||
bodyBone.name = Rigger::rootBoneName;
|
||||
bodyBone.headPosition = QVector3D(0, 0, 0);
|
||||
bodyBone.tailPosition = bonesOrigin;
|
||||
bodyBone.hasButton = true;
|
||||
|
@ -383,10 +383,10 @@ bool TetrapodRigger::rig()
|
|||
RiggerBone &leftHipBone = m_resultBones.back();
|
||||
leftHipBone.index = m_resultBones.size() - 1;
|
||||
leftHipBone.name = "LeftHip";
|
||||
leftHipBone.headPosition = m_resultBones[boneIndexMap["Body"]].tailPosition;
|
||||
leftHipBone.headPosition = m_resultBones[boneIndexMap[Rigger::rootBoneName]].tailPosition;
|
||||
leftHipBone.tailPosition = leftUpperLegBoneStartPosition;
|
||||
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());
|
||||
RiggerBone &leftUpperLegBone = m_resultBones.back();
|
||||
|
@ -431,10 +431,10 @@ bool TetrapodRigger::rig()
|
|||
RiggerBone &rightHipBone = m_resultBones.back();
|
||||
rightHipBone.index = m_resultBones.size() - 1;
|
||||
rightHipBone.name = "RightHip";
|
||||
rightHipBone.headPosition = m_resultBones[boneIndexMap["Body"]].tailPosition;
|
||||
rightHipBone.headPosition = m_resultBones[boneIndexMap[Rigger::rootBoneName]].tailPosition;
|
||||
rightHipBone.tailPosition = rightUpperLegBoneStartPosition;
|
||||
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());
|
||||
RiggerBone &rightUpperLegBone = m_resultBones.back();
|
||||
|
@ -479,14 +479,14 @@ bool TetrapodRigger::rig()
|
|||
RiggerBone &spineBone = m_resultBones.back();
|
||||
spineBone.index = m_resultBones.size() - 1;
|
||||
spineBone.name = "Spine";
|
||||
spineBone.headPosition = m_resultBones[boneIndexMap["Body"]].tailPosition;
|
||||
spineBone.headPosition = m_resultBones[boneIndexMap[Rigger::rootBoneName]].tailPosition;
|
||||
spineBone.tailPosition = chestBoneStartPosition;
|
||||
spineBone.color = Qt::white;
|
||||
spineBone.hasButton = true;
|
||||
spineBone.button = {3, 1};
|
||||
spineBone.buttonParameterType = RiggerButtonParameterType::PitchYawRoll;
|
||||
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());
|
||||
RiggerBone &chestBone = m_resultBones.back();
|
||||
|
|
Loading…
Reference in New Issue