Add multiple frames per pose support

Usually one pose consists of one frame, however, sometimes multiple frames per one reference sheet for a serial of action could be very useful, such as a sprite sheet. Multiple frames per pose is different with one motion, one motion could contains multiple poses. Currently, the duration of one frame is fixed to 0.042s, it's based on the 24 frames per second calculation.
master
Jeremy Hu 2018-11-09 11:20:48 +08:00
parent a551995235
commit fff39b0835
18 changed files with 394 additions and 154 deletions

View File

@ -307,14 +307,15 @@ QUuid Document::createNode(float x, float y, float z, float radius, QUuid fromNo
return node.id; return node.id;
} }
void Document::addPose(QString name, std::map<QString, std::map<QString, QString>> parameters) void Document::addPose(QString name, std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> frames, QUuid turnaroundImageId)
{ {
QUuid newPoseId = QUuid::createUuid(); QUuid newPoseId = QUuid::createUuid();
auto &pose = poseMap[newPoseId]; auto &pose = poseMap[newPoseId];
pose.id = newPoseId; pose.id = newPoseId;
pose.name = name; pose.name = name;
pose.parameters = parameters; pose.frames = frames;
pose.turnaroundImageId = turnaroundImageId;
pose.dirty = true; pose.dirty = true;
poseIdList.push_back(newPoseId); poseIdList.push_back(newPoseId);
@ -402,31 +403,32 @@ void Document::removePose(QUuid poseId)
emit optionsChanged(); emit optionsChanged();
} }
void Document::setPoseParameters(QUuid poseId, std::map<QString, std::map<QString, QString>> parameters) void Document::setPoseFrames(QUuid poseId, std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> frames)
{ {
auto findPoseResult = poseMap.find(poseId); auto findPoseResult = poseMap.find(poseId);
if (findPoseResult == poseMap.end()) { if (findPoseResult == poseMap.end()) {
qDebug() << "Find pose failed:" << poseId; qDebug() << "Find pose failed:" << poseId;
return; return;
} }
findPoseResult->second.parameters = parameters; findPoseResult->second.frames = frames;
findPoseResult->second.dirty = true; findPoseResult->second.dirty = true;
emit posesChanged(); emit posesChanged();
emit poseParametersChanged(poseId); emit poseFramesChanged(poseId);
emit optionsChanged(); emit optionsChanged();
} }
void Document::setPoseAttributes(QUuid poseId, std::map<QString, QString> attributes) void Document::setPoseTurnaroundImageId(QUuid poseId, QUuid imageId)
{ {
auto findPoseResult = poseMap.find(poseId); auto findPoseResult = poseMap.find(poseId);
if (findPoseResult == poseMap.end()) { if (findPoseResult == poseMap.end()) {
qDebug() << "Find pose failed:" << poseId; qDebug() << "Find pose failed:" << poseId;
return; return;
} }
findPoseResult->second.attributes = attributes; if (findPoseResult->second.turnaroundImageId == imageId)
return;
findPoseResult->second.turnaroundImageId = imageId;
findPoseResult->second.dirty = true; findPoseResult->second.dirty = true;
emit posesChanged(); emit poseTurnaroundImageIdChanged(poseId);
emit poseAttributesChanged(poseId);
emit optionsChanged(); emit optionsChanged();
} }
@ -971,11 +973,13 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeId
continue; continue;
} }
auto &poseIt = *findPoseResult; auto &poseIt = *findPoseResult;
std::map<QString, QString> pose = poseIt.second.attributes; std::map<QString, QString> pose;
pose["id"] = poseIt.second.id.toString(); pose["id"] = poseIt.second.id.toString();
if (!poseIt.second.name.isEmpty()) if (!poseIt.second.name.isEmpty())
pose["name"] = poseIt.second.name; pose["name"] = poseIt.second.name;
snapshot->poses.push_back(std::make_pair(pose, poseIt.second.parameters)); if (!poseIt.second.turnaroundImageId.isNull())
pose["canvasImageId"] = poseIt.second.turnaroundImageId.toString();
snapshot->poses.push_back(std::make_pair(pose, poseIt.second.frames));
} }
} }
if (DocumentToSnapshotFor::Document == forWhat || if (DocumentToSnapshotFor::Document == forWhat ||
@ -1221,14 +1225,10 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
newPose.id = newPoseId; newPose.id = newPoseId;
const auto &poseAttributes = poseIt.first; const auto &poseAttributes = poseIt.first;
newPose.name = valueOfKeyInMapOrEmpty(poseAttributes, "name"); newPose.name = valueOfKeyInMapOrEmpty(poseAttributes, "name");
for (const auto &attribute: poseAttributes) { auto findCanvasImageId = poseAttributes.find("canvasImageId");
if (attribute.first == "name" || if (findCanvasImageId != poseAttributes.end())
attribute.first == "id") { newPose.turnaroundImageId = QUuid(findCanvasImageId->second);
continue; newPose.frames = poseIt.second;
}
newPose.attributes.insert({attribute.first, attribute.second});
}
newPose.parameters = poseIt.second;
oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(poseAttributes, "id"))] = newPoseId; oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(poseAttributes, "id"))] = newPoseId;
poseIdList.push_back(newPoseId); poseIdList.push_back(newPoseId);
emit poseAdded(newPoseId); emit poseAdded(newPoseId);
@ -2654,7 +2654,7 @@ void Document::generateMotions()
m_motionsGenerator = new MotionsGenerator(rigType, rigBones, rigWeights, currentRiggedOutcome()); m_motionsGenerator = new MotionsGenerator(rigType, rigBones, rigWeights, currentRiggedOutcome());
bool hasDirtyMotion = false; bool hasDirtyMotion = false;
for (const auto &pose: poseMap) { for (const auto &pose: poseMap) {
m_motionsGenerator->addPoseToLibrary(pose.first, pose.second.parameters); m_motionsGenerator->addPoseToLibrary(pose.first, pose.second.frames);
} }
for (auto &motion: motionMap) { for (auto &motion: motionMap) {
if (motion.second.dirty) { if (motion.second.dirty) {
@ -2722,7 +2722,12 @@ void Document::generatePosePreviews()
for (auto &poseIt: poseMap) { for (auto &poseIt: poseMap) {
if (!poseIt.second.dirty) if (!poseIt.second.dirty)
continue; continue;
m_posePreviewsGenerator->addPose(poseIt.first, poseIt.second.parameters); if (poseIt.second.frames.empty())
continue;
int middle = poseIt.second.frames.size() / 2;
if (middle >= (int)poseIt.second.frames.size())
middle = 0;
m_posePreviewsGenerator->addPose({poseIt.first, middle}, poseIt.second.frames[middle].second);
poseIt.second.dirty = false; poseIt.second.dirty = false;
hasDirtyPose = true; hasDirtyPose = true;
} }
@ -2745,12 +2750,12 @@ void Document::generatePosePreviews()
void Document::posePreviewsReady() void Document::posePreviewsReady()
{ {
for (const auto &poseId: m_posePreviewsGenerator->generatedPreviewPoseIds()) { for (const auto &poseIdAndFrame: m_posePreviewsGenerator->generatedPreviewPoseIdAndFrames()) {
auto pose = poseMap.find(poseId); auto pose = poseMap.find(poseIdAndFrame.first);
if (pose != poseMap.end()) { if (pose != poseMap.end()) {
MeshLoader *resultPartPreviewMesh = m_posePreviewsGenerator->takePreview(poseId); MeshLoader *resultPartPreviewMesh = m_posePreviewsGenerator->takePreview(poseIdAndFrame);
pose->second.updatePreviewMesh(resultPartPreviewMesh); pose->second.updatePreviewMesh(resultPartPreviewMesh);
emit posePreviewChanged(poseId); emit posePreviewChanged(poseIdAndFrame.first);
} }
} }

View File

@ -195,8 +195,8 @@ public:
QUuid id; QUuid id;
QString name; QString name;
bool dirty = true; bool dirty = true;
std::map<QString, QString> attributes; QUuid turnaroundImageId;
std::map<QString, std::map<QString, QString>> parameters; std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> frames; // pair<attributes, parameters>
void updatePreviewMesh(MeshLoader *previewMesh) void updatePreviewMesh(MeshLoader *previewMesh)
{ {
delete m_previewMesh; delete m_previewMesh;
@ -290,7 +290,8 @@ public:
{ {
if (m_previewMeshs.empty()) if (m_previewMeshs.empty())
return nullptr; return nullptr;
return new MeshLoader(*m_previewMeshs[0].second); int middle = std::max((int)m_previewMeshs.size() / 2 - 1, (int)0);
return new MeshLoader(*m_previewMeshs[middle].second);
} }
private: private:
Q_DISABLE_COPY(Motion); Q_DISABLE_COPY(Motion);
@ -424,8 +425,8 @@ signals:
void poseRemoved(QUuid); void poseRemoved(QUuid);
void poseListChanged(); void poseListChanged();
void poseNameChanged(QUuid poseId); void poseNameChanged(QUuid poseId);
void poseParametersChanged(QUuid poseId); void poseFramesChanged(QUuid poseId);
void poseAttributesChanged(QUuid poseId); void poseTurnaroundImageIdChanged(QUuid poseId);
void posePreviewChanged(QUuid poseId); void posePreviewChanged(QUuid poseId);
void motionAdded(QUuid motionId); void motionAdded(QUuid motionId);
void motionRemoved(QUuid motionId); void motionRemoved(QUuid motionId);
@ -591,10 +592,11 @@ public slots:
void toggleSmoothNormal(); void toggleSmoothNormal();
void enableWeld(bool enabled); void enableWeld(bool enabled);
void setRigType(RigType toRigType); void setRigType(RigType toRigType);
void addPose(QString name, std::map<QString, std::map<QString, QString>> parameters); void addPose(QString name, std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> frames,
QUuid turnaroundImageId);
void removePose(QUuid poseId); void removePose(QUuid poseId);
void setPoseParameters(QUuid poseId, std::map<QString, std::map<QString, QString>> parameters); void setPoseFrames(QUuid poseId, std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> frames);
void setPoseAttributes(QUuid poseId, std::map<QString, QString> attributes); void setPoseTurnaroundImageId(QUuid poseId, QUuid imageId);
void renamePose(QUuid poseId, QString name); void renamePose(QUuid poseId, QString name);
void addMotion(QString name, std::vector<MotionClip> clips); void addMotion(QString name, std::vector<MotionClip> clips);
void removeMotion(QUuid motionId); void removeMotion(QUuid motionId);

View File

@ -874,7 +874,7 @@ DocumentWindow::DocumentWindow() :
Q_UNUSED(poseId); Q_UNUSED(poseId);
m_document->generatePosePreviews(); m_document->generatePosePreviews();
}); });
connect(m_document, &Document::poseParametersChanged, this, [=](QUuid poseId) { connect(m_document, &Document::poseFramesChanged, this, [=](QUuid poseId) {
Q_UNUSED(poseId); Q_UNUSED(poseId);
m_document->generatePosePreviews(); m_document->generatePosePreviews();
}); });
@ -1112,7 +1112,7 @@ void DocumentWindow::saveTo(const QString &saveAsFilename)
} }
std::set<QUuid> imageIds; std::set<QUuid> imageIds;
for (auto &material: snapshot.materials) { for (const auto &material: snapshot.materials) {
for (auto &layer: material.second) { for (auto &layer: material.second) {
for (auto &mapItem: layer.second) { for (auto &mapItem: layer.second) {
auto findImageIdString = mapItem.find("linkData"); auto findImageIdString = mapItem.find("linkData");

View File

@ -118,7 +118,7 @@ MaterialEditWidget::MaterialEditWidget(const Document *document, QWidget *parent
m_unsaved = true; m_unsaved = true;
updateTitle(); updateTitle();
}); });
QPushButton *saveButton = new QPushButton(tr("Save")); QPushButton *saveButton = new QPushButton(tr("Apply"));
connect(saveButton, &QPushButton::clicked, this, &MaterialEditWidget::save); connect(saveButton, &QPushButton::clicked, this, &MaterialEditWidget::save);
saveButton->setDefault(true); saveButton->setDefault(true);

View File

@ -113,7 +113,7 @@ MotionEditWidget::MotionEditWidget(const Document *document, QWidget *parent) :
m_nameEdit = new QLineEdit; m_nameEdit = new QLineEdit;
m_nameEdit->setFixedWidth(200); m_nameEdit->setFixedWidth(200);
connect(m_nameEdit, &QLineEdit::textChanged, this, &MotionEditWidget::setUnsavedState); connect(m_nameEdit, &QLineEdit::textChanged, this, &MotionEditWidget::setUnsavedState);
QPushButton *saveButton = new QPushButton(tr("Save")); QPushButton *saveButton = new QPushButton(tr("Apply"));
connect(saveButton, &QPushButton::clicked, this, &MotionEditWidget::save); connect(saveButton, &QPushButton::clicked, this, &MotionEditWidget::save);
saveButton->setDefault(true); saveButton->setDefault(true);
@ -250,7 +250,7 @@ void MotionEditWidget::generatePreviews()
m_previewsGenerator = new MotionsGenerator(m_document->rigType, rigBones, rigWeights, m_previewsGenerator = new MotionsGenerator(m_document->rigType, rigBones, rigWeights,
m_document->currentRiggedOutcome()); m_document->currentRiggedOutcome());
for (const auto &pose: m_document->poseMap) for (const auto &pose: m_document->poseMap)
m_previewsGenerator->addPoseToLibrary(pose.first, pose.second.parameters); m_previewsGenerator->addPoseToLibrary(pose.first, pose.second.frames);
for (const auto &motion: m_document->motionMap) for (const auto &motion: m_document->motionMap)
m_previewsGenerator->addMotionToLibrary(motion.first, motion.second.clips); m_previewsGenerator->addMotionToLibrary(motion.first, motion.second.clips);
m_previewsGenerator->addMotionToLibrary(QUuid(), m_timelineWidget->clips()); m_previewsGenerator->addMotionToLibrary(QUuid(), m_timelineWidget->clips());

View File

@ -22,7 +22,7 @@ MotionManageWidget::MotionManageWidget(const Document *document, QWidget *parent
connect(m_motionListWidget, &MotionListWidget::modifyMotion, this, &MotionManageWidget::showMotionDialog); connect(m_motionListWidget, &MotionListWidget::modifyMotion, this, &MotionManageWidget::showMotionDialog);
InfoLabel *infoLabel = new InfoLabel; InfoLabel *infoLabel = new InfoLabel;
infoLabel->show(); infoLabel->hide();
auto refreshInfoLabel = [=]() { auto refreshInfoLabel = [=]() {
if (m_document->currentRigSucceed()) { if (m_document->currentRigSucceed()) {

View File

@ -26,9 +26,9 @@ MotionsGenerator::~MotionsGenerator()
delete m_poser; delete m_poser;
} }
void MotionsGenerator::addPoseToLibrary(const QUuid &poseId, const std::map<QString, std::map<QString, QString>> &parameters) void MotionsGenerator::addPoseToLibrary(const QUuid &poseId, const std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> &frames)
{ {
m_poses[poseId] = parameters; m_poses[poseId] = frames;
} }
void MotionsGenerator::addMotionToLibrary(const QUuid &motionId, const std::vector<MotionClip> &clips) void MotionsGenerator::addMotionToLibrary(const QUuid &motionId, const std::vector<MotionClip> &clips)
@ -60,6 +60,15 @@ std::vector<MotionClip> *MotionsGenerator::findMotionClips(const QUuid &motionId
return &clips; return &clips;
} }
std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> *MotionsGenerator::findPoseFrames(const QUuid &poseId)
{
auto findPoseResult = m_poses.find(poseId);
if (findPoseResult == m_poses.end())
return nullptr;
std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> &frames = findPoseResult->second;
return &frames;
}
void MotionsGenerator::generatePreviewsForOutcomes(const std::vector<std::pair<float, JointNodeTree>> &outcomes, std::vector<std::pair<float, MeshLoader *>> &previews) void MotionsGenerator::generatePreviewsForOutcomes(const std::vector<std::pair<float, JointNodeTree>> &outcomes, std::vector<std::pair<float, MeshLoader *>> &previews)
{ {
for (const auto &item: outcomes) { for (const auto &item: outcomes) {
@ -70,6 +79,18 @@ void MotionsGenerator::generatePreviewsForOutcomes(const std::vector<std::pair<f
} }
} }
float MotionsGenerator::calculatePoseDuration(const QUuid &poseId)
{
const std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> *pose = findPoseFrames(poseId);
if (nullptr == pose)
return 0;
float totalDuration = 0;
for (const auto &frame: *pose) {
totalDuration += valueOfKeyInMapOrEmpty(frame.first, "duration").toFloat();
}
return totalDuration;
}
float MotionsGenerator::calculateMotionDuration(const QUuid &motionId, std::set<QUuid> &visited) float MotionsGenerator::calculateMotionDuration(const QUuid &motionId, std::set<QUuid> &visited)
{ {
const std::vector<MotionClip> *motionClips = findMotionClips(motionId); const std::vector<MotionClip> *motionClips = findMotionClips(motionId);
@ -85,7 +106,7 @@ float MotionsGenerator::calculateMotionDuration(const QUuid &motionId, std::set<
if (clip.clipType == MotionClipType::Interpolation) if (clip.clipType == MotionClipType::Interpolation)
totalDuration += clip.duration; totalDuration += clip.duration;
else if (clip.clipType == MotionClipType::Pose) else if (clip.clipType == MotionClipType::Pose)
totalDuration += clip.duration; totalDuration += calculatePoseDuration(clip.linkToId);
else if (clip.clipType == MotionClipType::Motion) else if (clip.clipType == MotionClipType::Motion)
totalDuration += calculateMotionDuration(clip.linkToId, visited); totalDuration += calculateMotionDuration(clip.linkToId, visited);
} }
@ -111,6 +132,8 @@ void MotionsGenerator::generateMotion(const QUuid &motionId, std::set<QUuid> &vi
if (clip.clipType == MotionClipType::Motion) { if (clip.clipType == MotionClipType::Motion) {
std::set<QUuid> subVisited; std::set<QUuid> subVisited;
clip.duration = calculateMotionDuration(clip.linkToId, subVisited); clip.duration = calculateMotionDuration(clip.linkToId, subVisited);
} else if (clip.clipType == MotionClipType::Pose) {
clip.duration = calculatePoseDuration(clip.linkToId);
} }
timePoints.push_back(totalDuration); timePoints.push_back(totalDuration);
totalDuration += clip.duration; totalDuration += clip.duration;
@ -159,13 +182,15 @@ void MotionsGenerator::generateMotion(const QUuid &motionId, std::set<QUuid> &vi
progress += interval; progress += interval;
continue; continue;
} else if (MotionClipType::Pose == progressClip.clipType) { } else if (MotionClipType::Pose == progressClip.clipType) {
const JointNodeTree *beginJointNodeTree = findClipBeginJointNodeTree((*motionClips)[clipIndex]); const auto &frames = findPoseFrames(progressClip.linkToId);
if (nullptr == beginJointNodeTree) { int frame = clipLocalProgress * frames->size() / std::max((float)0.01, progressClip.duration);
qDebug() << "findClipBeginJointNodeTree failed"; if (frame >= (int)frames->size())
break; frame = frames->size() - 1;
if (frame >= 0 && frame < (int)frames->size()) {
const JointNodeTree jointNodeTree = poseJointNodeTree(progressClip.linkToId, frame);
outcomes.push_back({progress - lastProgress, jointNodeTree});
lastProgress = progress;
} }
outcomes.push_back({progress - lastProgress, *beginJointNodeTree});
lastProgress = progress;
progress += interval; progress += interval;
continue; continue;
} else if (MotionClipType::Motion == progressClip.clipType) { } else if (MotionClipType::Motion == progressClip.clipType) {
@ -182,25 +207,28 @@ JointNodeTree MotionsGenerator::generateInterpolation(InterpolationType interpol
return JointNodeTree::slerp(first, second, calculateInterpolation(interpolationType, progress)); return JointNodeTree::slerp(first, second, calculateInterpolation(interpolationType, progress));
} }
const JointNodeTree &MotionsGenerator::poseJointNodeTree(const QUuid &poseId) const JointNodeTree &MotionsGenerator::poseJointNodeTree(const QUuid &poseId, int frame)
{ {
auto findResult = m_poseJointNodeTreeMap.find(poseId); auto findResult = m_poseJointNodeTreeMap.find({poseId, frame});
if (findResult != m_poseJointNodeTreeMap.end()) if (findResult != m_poseJointNodeTreeMap.end())
return findResult->second; return findResult->second;
const auto &parameters = m_poses[poseId]; const auto &frames = m_poses[poseId];
m_poser->reset(); m_poser->reset();
m_poser->parameters() = parameters; if (frame < (int)frames.size()) {
const auto &parameters = frames[frame].second;
m_poser->parameters() = parameters;
}
m_poser->commit(); m_poser->commit();
auto insertResult = m_poseJointNodeTreeMap.insert({poseId, m_poser->resultJointNodeTree()}); auto insertResult = m_poseJointNodeTreeMap.insert({{poseId, frame}, m_poser->resultJointNodeTree()});
return insertResult.first->second; return insertResult.first->second;
} }
const JointNodeTree *MotionsGenerator::findClipBeginJointNodeTree(const MotionClip &clip) const JointNodeTree *MotionsGenerator::findClipBeginJointNodeTree(const MotionClip &clip)
{ {
if (MotionClipType::Pose == clip.clipType) { if (MotionClipType::Pose == clip.clipType) {
const JointNodeTree &jointNodeTree = poseJointNodeTree(clip.linkToId); const JointNodeTree &jointNodeTree = poseJointNodeTree(clip.linkToId, 0);
return &jointNodeTree; return &jointNodeTree;
} else if (MotionClipType::Motion == clip.clipType) { } else if (MotionClipType::Motion == clip.clipType) {
const std::vector<MotionClip> *motionClips = findMotionClips(clip.linkToId); const std::vector<MotionClip> *motionClips = findMotionClips(clip.linkToId);
@ -215,8 +243,11 @@ const JointNodeTree *MotionsGenerator::findClipBeginJointNodeTree(const MotionCl
const JointNodeTree *MotionsGenerator::findClipEndJointNodeTree(const MotionClip &clip) const JointNodeTree *MotionsGenerator::findClipEndJointNodeTree(const MotionClip &clip)
{ {
if (MotionClipType::Pose == clip.clipType) { if (MotionClipType::Pose == clip.clipType) {
const JointNodeTree &jointNodeTree = poseJointNodeTree(clip.linkToId); const std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> *poseFrames = findPoseFrames(clip.linkToId);
return &jointNodeTree; if (nullptr != poseFrames && !poseFrames->empty()) {
return &poseJointNodeTree(clip.linkToId, poseFrames->size() - 1);
}
return nullptr;
} else if (MotionClipType::Motion == clip.clipType) { } else if (MotionClipType::Motion == clip.clipType) {
const std::vector<MotionClip> *motionClips = findMotionClips(clip.linkToId); const std::vector<MotionClip> *motionClips = findMotionClips(clip.linkToId);
if (nullptr != motionClips && !motionClips->empty()) { if (nullptr != motionClips && !motionClips->empty()) {

View File

@ -19,7 +19,7 @@ public:
const std::map<int, RiggerVertexWeights> *rigWeights, const std::map<int, RiggerVertexWeights> *rigWeights,
const Outcome &outcome); const Outcome &outcome);
~MotionsGenerator(); ~MotionsGenerator();
void addPoseToLibrary(const QUuid &poseId, const std::map<QString, std::map<QString, QString>> &parameters); void addPoseToLibrary(const QUuid &poseId, const std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> &frames);
void addMotionToLibrary(const QUuid &motionId, const std::vector<MotionClip> &clips); void addMotionToLibrary(const QUuid &motionId, const std::vector<MotionClip> &clips);
void addRequirement(const QUuid &motionId); void addRequirement(const QUuid &motionId);
std::vector<std::pair<float, MeshLoader *>> takeResultPreviewMeshs(const QUuid &motionId); std::vector<std::pair<float, MeshLoader *>> takeResultPreviewMeshs(const QUuid &motionId);
@ -35,25 +35,27 @@ public slots:
private: private:
void generateMotion(const QUuid &motionId, std::set<QUuid> &visited, std::vector<std::pair<float, JointNodeTree>> &outcomes); void generateMotion(const QUuid &motionId, std::set<QUuid> &visited, std::vector<std::pair<float, JointNodeTree>> &outcomes);
const JointNodeTree &poseJointNodeTree(const QUuid &poseId); const JointNodeTree &poseJointNodeTree(const QUuid &poseId, int frame);
JointNodeTree generateInterpolation(InterpolationType interpolationType, const JointNodeTree &first, const JointNodeTree &second, float progress); JointNodeTree generateInterpolation(InterpolationType interpolationType, const JointNodeTree &first, const JointNodeTree &second, float progress);
const JointNodeTree *findClipBeginJointNodeTree(const MotionClip &clip); const JointNodeTree *findClipBeginJointNodeTree(const MotionClip &clip);
const JointNodeTree *findClipEndJointNodeTree(const MotionClip &clip); const JointNodeTree *findClipEndJointNodeTree(const MotionClip &clip);
std::vector<MotionClip> *findMotionClips(const QUuid &motionId); std::vector<MotionClip> *findMotionClips(const QUuid &motionId);
std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> *findPoseFrames(const QUuid &poseId);
void generatePreviewsForOutcomes(const std::vector<std::pair<float, JointNodeTree>> &outcomes, std::vector<std::pair<float, MeshLoader *>> &previews); void generatePreviewsForOutcomes(const std::vector<std::pair<float, JointNodeTree>> &outcomes, std::vector<std::pair<float, MeshLoader *>> &previews);
float calculateMotionDuration(const QUuid &motionId, std::set<QUuid> &visited); float calculateMotionDuration(const QUuid &motionId, std::set<QUuid> &visited);
float calculatePoseDuration(const QUuid &poseId);
RigType m_rigType = RigType::None; RigType m_rigType = RigType::None;
std::vector<RiggerBone> m_rigBones; std::vector<RiggerBone> m_rigBones;
std::map<int, RiggerVertexWeights> m_rigWeights; std::map<int, RiggerVertexWeights> m_rigWeights;
Outcome m_outcome; Outcome m_outcome;
std::map<QUuid, std::map<QString, std::map<QString, QString>>> m_poses; std::map<QUuid, std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>>> m_poses;
std::map<QUuid, std::vector<MotionClip>> m_motions; std::map<QUuid, std::vector<MotionClip>> m_motions;
std::set<QUuid> m_requiredMotionIds; std::set<QUuid> m_requiredMotionIds;
std::set<QUuid> m_generatedMotionIds; std::set<QUuid> m_generatedMotionIds;
std::map<QUuid, std::vector<std::pair<float, MeshLoader *>>> m_resultPreviewMeshs; std::map<QUuid, std::vector<std::pair<float, MeshLoader *>>> m_resultPreviewMeshs;
std::map<QUuid, std::vector<std::pair<float, JointNodeTree>>> m_resultJointNodeTrees; std::map<QUuid, std::vector<std::pair<float, JointNodeTree>>> m_resultJointNodeTrees;
std::map<QUuid, JointNodeTree> m_poseJointNodeTreeMap; std::map<std::pair<QUuid, int>, JointNodeTree> m_poseJointNodeTreeMap;
Poser *m_poser = nullptr; Poser *m_poser = nullptr;
int m_fps = 30; int m_fps = 30;
}; };

View File

@ -1,7 +1,14 @@
#include <QDebug> #include <QDebug>
#include <QXmlStreamWriter>
#include <QClipboard>
#include <QApplication>
#include <QMimeData>
#include "posedocument.h" #include "posedocument.h"
#include "rigger.h" #include "rigger.h"
#include "util.h" #include "util.h"
#include "document.h"
#include "snapshot.h"
#include "snapshotxml.h"
const float PoseDocument::m_nodeRadius = 0.01; const float PoseDocument::m_nodeRadius = 0.01;
const float PoseDocument::m_groundPlaneHalfThickness = 0.01 / 4; const float PoseDocument::m_groundPlaneHalfThickness = 0.01 / 4;
@ -10,6 +17,12 @@ const float PoseDocument::m_outcomeScaleFactor = 0.5;
bool PoseDocument::hasPastableNodesInClipboard() const bool PoseDocument::hasPastableNodesInClipboard() const
{ {
const QClipboard *clipboard = QApplication::clipboard();
const QMimeData *mimeData = clipboard->mimeData();
if (mimeData->hasText()) {
if (-1 != mimeData->text().indexOf("<pose ") && -1 != mimeData->text().indexOf("<parameter "))
return true;
}
return false; return false;
} }
@ -20,17 +33,37 @@ bool PoseDocument::originSettled() const
bool PoseDocument::isNodeEditable(QUuid nodeId) const bool PoseDocument::isNodeEditable(QUuid nodeId) const
{ {
Q_UNUSED(nodeId);
return true; return true;
} }
bool PoseDocument::isEdgeEditable(QUuid edgeId) const bool PoseDocument::isEdgeEditable(QUuid edgeId) const
{ {
Q_UNUSED(edgeId);
return true; return true;
} }
void PoseDocument::copyNodes(std::set<QUuid> nodeIdSet) const void PoseDocument::copyNodes(std::set<QUuid> nodeIdSet) const
{ {
// TODO: std::map<QString, std::map<QString, QString>> parameters;
toParameters(parameters, nodeIdSet);
if (parameters.empty())
return;
Document document;
QUuid poseId = QUuid::createUuid();
auto &pose = document.poseMap[poseId];
pose.id = poseId;
pose.frames.push_back({std::map<QString, QString>(), parameters});
document.poseIdList.push_back(poseId);
Snapshot snapshot;
std::set<QUuid> limitPoseIds;
document.toSnapshot(&snapshot, limitPoseIds, DocumentToSnapshotFor::Poses);
QString snapshotXml;
QXmlStreamWriter xmlStreamWriter(&snapshotXml);
saveSkeletonToXmlStream(&snapshot, &xmlStreamWriter);
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(snapshotXml);
} }
void PoseDocument::saveHistoryItem() void PoseDocument::saveHistoryItem()
@ -72,6 +105,21 @@ void PoseDocument::redo()
void PoseDocument::paste() void PoseDocument::paste()
{ {
const QClipboard *clipboard = QApplication::clipboard();
const QMimeData *mimeData = clipboard->mimeData();
if (mimeData->hasText()) {
QXmlStreamReader xmlStreamReader(mimeData->text());
Snapshot snapshot;
loadSkeletonFromXmlStream(&snapshot, xmlStreamReader);
if (snapshot.poses.empty())
return;
const auto &firstPose = *snapshot.poses.begin();
if (firstPose.second.empty())
return;
const auto &firstFrame = *firstPose.second.begin();
fromParameters(&m_riggerBones, firstFrame.second);
saveHistoryItem();
}
} }
void PoseDocument::updateTurnaround(const QImage &image) void PoseDocument::updateTurnaround(const QImage &image)
@ -93,6 +141,12 @@ void PoseDocument::reset()
emit parametersChanged(); emit parametersChanged();
} }
void PoseDocument::clearHistories()
{
m_undoItems.clear();
m_redoItems.clear();
}
void PoseDocument::fromParameters(const std::vector<RiggerBone> *rigBones, void PoseDocument::fromParameters(const std::vector<RiggerBone> *rigBones,
const std::map<QString, std::map<QString, QString>> &parameters) const std::map<QString, std::map<QString, QString>> &parameters)
{ {
@ -390,7 +444,7 @@ float PoseDocument::findGroundY() const
return maxY; return maxY;
} }
void PoseDocument::toParameters(std::map<QString, std::map<QString, QString>> &parameters) const void PoseDocument::toParameters(std::map<QString, std::map<QString, QString>> &parameters, const std::set<QUuid> &limitNodeIds) const
{ {
float translateY = 0; float translateY = 0;
auto findGroundEdge = edgeMap.find(m_groundEdgeId); auto findGroundEdge = edgeMap.find(m_groundEdgeId);
@ -400,6 +454,8 @@ void PoseDocument::toParameters(std::map<QString, std::map<QString, QString>> &p
auto findFirstNode = nodeMap.find(nodeIds[0]); auto findFirstNode = nodeMap.find(nodeIds[0]);
auto findSecondNode = nodeMap.find(nodeIds[1]); auto findSecondNode = nodeMap.find(nodeIds[1]);
if (findFirstNode != nodeMap.end() && findSecondNode != nodeMap.end()) { if (findFirstNode != nodeMap.end() && findSecondNode != nodeMap.end()) {
if (limitNodeIds.empty() || limitNodeIds.find(findFirstNode->first) != limitNodeIds.end() ||
limitNodeIds.find(findSecondNode->first) != limitNodeIds.end())
translateY = (findFirstNode->second.y + findSecondNode->second.y) / 2 - translateY = (findFirstNode->second.y + findSecondNode->second.y) / 2 -
(findGroundY() + m_groundPlaneHalfThickness); (findGroundY() + m_groundPlaneHalfThickness);
} }
@ -417,13 +473,16 @@ void PoseDocument::toParameters(std::map<QString, std::map<QString, QString>> &p
auto findSecondNode = nodeMap.find(boneNodeIdPair.second); auto findSecondNode = nodeMap.find(boneNodeIdPair.second);
if (findSecondNode == nodeMap.end()) if (findSecondNode == nodeMap.end())
continue; continue;
auto &boneParameter = parameters[item.first]; if (limitNodeIds.empty() || limitNodeIds.find(boneNodeIdPair.first) != limitNodeIds.end() ||
boneParameter["fromX"] = QString::number(toOutcomeX(findFirstNode->second.x)); limitNodeIds.find(boneNodeIdPair.second) != limitNodeIds.end()) {
boneParameter["fromY"] = QString::number(toOutcomeY(findFirstNode->second.y)); auto &boneParameter = parameters[item.first];
boneParameter["fromZ"] = QString::number(toOutcomeZ(findFirstNode->second.z)); boneParameter["fromX"] = QString::number(toOutcomeX(findFirstNode->second.x));
boneParameter["toX"] = QString::number(toOutcomeX(findSecondNode->second.x)); boneParameter["fromY"] = QString::number(toOutcomeY(findFirstNode->second.y));
boneParameter["toY"] = QString::number(toOutcomeY(findSecondNode->second.y)); boneParameter["fromZ"] = QString::number(toOutcomeZ(findFirstNode->second.z));
boneParameter["toZ"] = QString::number(toOutcomeZ(findSecondNode->second.z)); boneParameter["toX"] = QString::number(toOutcomeX(findSecondNode->second.x));
boneParameter["toY"] = QString::number(toOutcomeY(findSecondNode->second.y));
boneParameter["toZ"] = QString::number(toOutcomeZ(findSecondNode->second.z));
}
} }
} }

View File

@ -35,12 +35,13 @@ public:
void updateRigBones(const std::vector<RiggerBone> *rigBones, const QVector3D &rootTranslation=QVector3D(0, 0, 0)); void updateRigBones(const std::vector<RiggerBone> *rigBones, const QVector3D &rootTranslation=QVector3D(0, 0, 0));
void reset(); void reset();
void toParameters(std::map<QString, std::map<QString, QString>> &parameters) const; void toParameters(std::map<QString, std::map<QString, QString>> &parameters, const std::set<QUuid> &limitNodeIds=std::set<QUuid>()) const;
void fromParameters(const std::vector<RiggerBone> *rigBones, void fromParameters(const std::vector<RiggerBone> *rigBones,
const std::map<QString, std::map<QString, QString>> &parameters); const std::map<QString, std::map<QString, QString>> &parameters);
public slots: public slots:
void saveHistoryItem(); void saveHistoryItem();
void clearHistories();
void undo() override; void undo() override;
void redo() override; void redo() override;
void paste() override; void paste() override;

View File

@ -7,6 +7,7 @@
#include <QLineEdit> #include <QLineEdit>
#include <QMessageBox> #include <QMessageBox>
#include <QFileDialog> #include <QFileDialog>
#include <QSpinBox>
#include "theme.h" #include "theme.h"
#include "poseeditwidget.h" #include "poseeditwidget.h"
#include "floatnumberwidget.h" #include "floatnumberwidget.h"
@ -17,6 +18,8 @@
#include "shortcuts.h" #include "shortcuts.h"
#include "imageforever.h" #include "imageforever.h"
const float PoseEditWidget::m_frameDuration = 0.042; //(1.0 / 24)
PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) : PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
QDialog(parent), QDialog(parent),
m_document(document), m_document(document),
@ -75,8 +78,9 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
connect(m_poseDocument, &PoseDocument::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged); connect(m_poseDocument, &PoseDocument::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged);
connect(m_poseDocument, &PoseDocument::parametersChanged, this, [&]() { connect(m_poseDocument, &PoseDocument::parametersChanged, this, [&]() {
m_parameters.clear(); m_currentParameters.clear();
m_poseDocument->toParameters(m_parameters); m_poseDocument->toParameters(m_currentParameters);
syncFrameFromCurrent();
emit parametersAdjusted(); emit parametersAdjusted();
}); });
@ -86,10 +90,9 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
m_nameEdit = new QLineEdit; m_nameEdit = new QLineEdit;
m_nameEdit->setFixedWidth(200); m_nameEdit->setFixedWidth(200);
connect(m_nameEdit, &QLineEdit::textChanged, this, [=]() { connect(m_nameEdit, &QLineEdit::textChanged, this, [=]() {
m_unsaved = true; setUnsaveState();
updateTitle();
}); });
QPushButton *saveButton = new QPushButton(tr("Save")); QPushButton *saveButton = new QPushButton(tr("Apply"));
connect(saveButton, &QPushButton::clicked, this, &PoseEditWidget::save); connect(saveButton, &QPushButton::clicked, this, &PoseEditWidget::save);
saveButton->setDefault(true); saveButton->setDefault(true);
@ -98,14 +101,30 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
connect(m_poseDocument, &PoseDocument::turnaroundChanged, connect(m_poseDocument, &PoseDocument::turnaroundChanged,
graphicsWidget, &SkeletonGraphicsWidget::turnaroundChanged); graphicsWidget, &SkeletonGraphicsWidget::turnaroundChanged);
m_framesSettingButton = new QPushButton();
connect(m_framesSettingButton, &QPushButton::clicked, this, [=]() {
showFramesSettingPopup(mapFromGlobal(QCursor::pos()));
});
m_currentFrameSlider = new QSlider(Qt::Horizontal);
m_currentFrameSlider->setRange(0, m_frames.size() - 1);
m_currentFrameSlider->setValue(m_currentFrame);
m_currentFrameSlider->hide();
connect(m_currentFrameSlider, static_cast<void (QSlider::*)(int)>(&QSlider::valueChanged), this, [=](int value) {
setCurrentFrame(value);
});
connect(m_document, &Document::resultRigChanged, this, &PoseEditWidget::updatePoseDocument); connect(m_document, &Document::resultRigChanged, this, &PoseEditWidget::updatePoseDocument);
QHBoxLayout *baseInfoLayout = new QHBoxLayout; QHBoxLayout *baseInfoLayout = new QHBoxLayout;
baseInfoLayout->addWidget(new QLabel(tr("Name"))); baseInfoLayout->addWidget(new QLabel(tr("Name")));
baseInfoLayout->addWidget(m_nameEdit); baseInfoLayout->addWidget(m_nameEdit);
baseInfoLayout->addWidget(changeReferenceSheet); baseInfoLayout->addWidget(changeReferenceSheet);
baseInfoLayout->addWidget(m_framesSettingButton);
baseInfoLayout->addWidget(m_currentFrameSlider);
baseInfoLayout->addStretch(); baseInfoLayout->addStretch();
baseInfoLayout->addWidget(saveButton); baseInfoLayout->addWidget(saveButton);
baseInfoLayout->setStretch(4, 1);
QVBoxLayout *mainLayout = new QVBoxLayout; QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(paramtersLayout); mainLayout->addLayout(paramtersLayout);
@ -114,22 +133,99 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
setLayout(mainLayout); setLayout(mainLayout);
connect(m_document, &Document::resultRigChanged, this, &PoseEditWidget::updatePoseDocument);
connect(this, &PoseEditWidget::parametersAdjusted, this, &PoseEditWidget::updatePreview); connect(this, &PoseEditWidget::parametersAdjusted, this, &PoseEditWidget::updatePreview);
connect(this, &PoseEditWidget::parametersAdjusted, [=]() { connect(this, &PoseEditWidget::parametersAdjusted, [=]() {
m_unsaved = true; setUnsaveState();
updateTitle();
}); });
connect(this, &PoseEditWidget::addPose, m_document, &Document::addPose); connect(this, &PoseEditWidget::addPose, m_document, &Document::addPose);
connect(this, &PoseEditWidget::renamePose, m_document, &Document::renamePose); connect(this, &PoseEditWidget::renamePose, m_document, &Document::renamePose);
connect(this, &PoseEditWidget::setPoseParameters, m_document, &Document::setPoseParameters); connect(this, &PoseEditWidget::setPoseFrames, m_document, &Document::setPoseFrames);
connect(this, &PoseEditWidget::setPoseAttributes, m_document, &Document::setPoseAttributes); connect(this, &PoseEditWidget::setPoseTurnaroundImageId, m_document, &Document::setPoseTurnaroundImageId);
updatePoseDocument(); updatePoseDocument();
updateTitle(); updateTitle();
updateFramesSettingButton();
m_poseDocument->saveHistoryItem(); m_poseDocument->saveHistoryItem();
} }
void PoseEditWidget::showFramesSettingPopup(const QPoint &pos)
{
QMenu popupMenu;
QWidget *popup = new QWidget;
QSpinBox *framesEdit = new QSpinBox();
framesEdit->setMaximum(60);
framesEdit->setMinimum(1);
framesEdit->setSingleStep(1);
framesEdit->setValue(m_frames.size());
connect(framesEdit, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [=](int value) {
setFrameCount(value);
});
QFormLayout *formLayout = new QFormLayout;
formLayout->addRow(tr("Frames:"), framesEdit);
popup->setLayout(formLayout);
QWidgetAction *action = new QWidgetAction(this);
action->setDefaultWidget(popup);
popupMenu.addAction(action);
popupMenu.exec(mapToGlobal(pos));
}
void PoseEditWidget::updateFramesSettingButton()
{
m_currentFrameSlider->setRange(0, m_frames.size() - 1);
m_currentFrameSlider->setVisible(m_frames.size() > 1);
m_framesSettingButton->setText(tr("Frame: %1 / %2").arg(QString::number(m_currentFrame + 1).rightJustified(2, ' ')).arg(QString::number(m_frames.size()).leftJustified(2, ' ')));
}
void PoseEditWidget::ensureEnoughFrames()
{
if (m_currentFrame >= (int)m_frames.size()) {
m_frames.resize(m_currentFrame + 1);
setUnsaveState();
updateFramesSettingButton();
}
}
void PoseEditWidget::syncFrameFromCurrent()
{
ensureEnoughFrames();
m_frames[m_currentFrame] = {m_currentAttributes, m_currentParameters};
m_frames[m_currentFrame].first["duration"] = QString::number(m_frameDuration);
}
void PoseEditWidget::setFrameCount(int count)
{
if (count == (int)m_frames.size())
return;
setUnsaveState();
count = std::max(count, 1);
m_frames.resize(count);
updateFramesSettingButton();
if (m_currentFrame >= count) {
setCurrentFrame(count - 1);
}
}
void PoseEditWidget::setCurrentFrame(int frame)
{
if (m_currentFrame == frame)
return;
m_currentFrame = frame;
ensureEnoughFrames();
updateFramesSettingButton();
m_currentAttributes = m_frames[m_currentFrame].first;
m_currentParameters = m_frames[m_currentFrame].second;
updatePoseDocument();
}
void PoseEditWidget::changeTurnaround() void PoseEditWidget::changeTurnaround()
{ {
QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(), QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(),
@ -139,26 +235,19 @@ void PoseEditWidget::changeTurnaround()
QImage image; QImage image;
if (!image.load(fileName)) if (!image.load(fileName))
return; return;
m_imageId = ImageForever::add(&image); auto newImageId = ImageForever::add(&image);
m_attributes["canvasImageId"] = m_imageId.toString(); if (m_imageId == newImageId)
return;
setUnsaveState();
m_imageId = newImageId;
m_poseDocument->updateTurnaround(image); m_poseDocument->updateTurnaround(image);
} }
QUuid PoseEditWidget::findImageIdFromAttributes(const std::map<QString, QString> &attributes)
{
auto findImageIdResult = attributes.find("canvasImageId");
if (findImageIdResult == attributes.end())
return QUuid();
return QUuid(findImageIdResult->second);
}
void PoseEditWidget::updatePoseDocument() void PoseEditWidget::updatePoseDocument()
{ {
m_poseDocument->fromParameters(m_document->resultRigBones(), m_parameters); m_poseDocument->fromParameters(m_document->resultRigBones(), m_currentParameters);
QUuid imageId = findImageIdFromAttributes(m_attributes); m_poseDocument->clearHistories();
auto image = ImageForever::get(imageId); m_poseDocument->saveHistoryItem();
if (nullptr != image)
m_poseDocument->updateTurnaround(*image);
updatePreview(); updatePreview();
} }
@ -222,10 +311,7 @@ void PoseEditWidget::updatePreview()
if (nullptr == poser) if (nullptr == poser)
return; return;
m_parameters.clear(); poser->parameters() = m_currentParameters;
m_poseDocument->toParameters(m_parameters);
poser->parameters() = m_parameters;
poser->commit(); poser->commit();
m_posePreviewManager->postUpdate(*poser, m_document->currentRiggedOutcome(), *rigWeights); m_posePreviewManager->postUpdate(*poser, m_document->currentRiggedOutcome(), *rigWeights);
delete poser; delete poser;
@ -260,19 +346,28 @@ void PoseEditWidget::setEditPoseName(QString name)
updateTitle(); updateTitle();
} }
void PoseEditWidget::setEditParameters(std::map<QString, std::map<QString, QString>> parameters) void PoseEditWidget::setEditPoseFrames(std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> frames)
{ {
m_parameters = parameters; m_frames = frames;
if (!m_frames.empty()) {
m_currentFrame = 0;
const auto &frame = m_frames[m_currentFrame];
m_currentAttributes = frame.first;
m_currentParameters = frame.second;
}
updatePoseDocument(); updatePoseDocument();
updatePreview(); updatePreview();
updateFramesSettingButton();
m_poseDocument->saveHistoryItem(); m_poseDocument->saveHistoryItem();
} }
void PoseEditWidget::setEditAttributes(std::map<QString, QString> attributes) void PoseEditWidget::setEditPoseTurnaroundImageId(QUuid imageId)
{ {
m_attributes = attributes; m_imageId = imageId;
updatePoseDocument(); const auto &image = ImageForever::get(m_imageId);
updatePreview(); if (nullptr == image)
return;
m_poseDocument->updateTurnaround(*image);
} }
void PoseEditWidget::clearUnsaveState() void PoseEditWidget::clearUnsaveState()
@ -281,14 +376,20 @@ void PoseEditWidget::clearUnsaveState()
updateTitle(); updateTitle();
} }
void PoseEditWidget::setUnsaveState()
{
m_unsaved = true;
updateTitle();
}
void PoseEditWidget::save() void PoseEditWidget::save()
{ {
if (m_poseId.isNull()) { if (m_poseId.isNull()) {
emit addPose(m_nameEdit->text(), m_parameters); emit addPose(m_nameEdit->text(), m_frames, m_imageId);
} else if (m_unsaved) { } else if (m_unsaved) {
emit renamePose(m_poseId, m_nameEdit->text()); emit renamePose(m_poseId, m_nameEdit->text());
emit setPoseParameters(m_poseId, m_parameters); emit setPoseFrames(m_poseId, m_frames);
emit setPoseAttributes(m_poseId, m_attributes); emit setPoseTurnaroundImageId(m_poseId, m_imageId);
} }
m_unsaved = false; m_unsaved = false;
close(); close();

View File

@ -4,56 +4,71 @@
#include <map> #include <map>
#include <QCloseEvent> #include <QCloseEvent>
#include <QLineEdit> #include <QLineEdit>
#include <QSlider>
#include "posepreviewmanager.h" #include "posepreviewmanager.h"
#include "document.h" #include "document.h"
#include "modelwidget.h" #include "modelwidget.h"
#include "rigger.h" #include "rigger.h"
#include "skeletongraphicswidget.h" #include "skeletongraphicswidget.h"
#include "posedocument.h" #include "posedocument.h"
#include "floatnumberwidget.h"
class PoseEditWidget : public QDialog class PoseEditWidget : public QDialog
{ {
Q_OBJECT Q_OBJECT
signals: signals:
void addPose(QString name, std::map<QString, std::map<QString, QString>> parameters); void addPose(QString name, std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> frames, QUuid turnaroundImageId);
void removePose(QUuid poseId); void removePose(QUuid poseId);
void setPoseParameters(QUuid poseId, std::map<QString, std::map<QString, QString>> parameters); void setPoseFrames(QUuid poseId, std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> frames);
void setPoseAttributes(QUuid poseId, std::map<QString, QString> attributes); void setPoseTurnaroundImageId(QUuid poseId, QUuid imageId);
void renamePose(QUuid poseId, QString name); void renamePose(QUuid poseId, QString name);
void parametersAdjusted(); void parametersAdjusted();
public: public:
PoseEditWidget(const Document *document, QWidget *parent=nullptr); PoseEditWidget(const Document *document, QWidget *parent=nullptr);
~PoseEditWidget(); ~PoseEditWidget();
static const float m_frameDuration;
public slots: public slots:
void updatePoseDocument(); void updatePoseDocument();
void updatePreview(); void updatePreview();
void syncFrameFromCurrent();
void setEditPoseId(QUuid poseId); void setEditPoseId(QUuid poseId);
void setEditPoseName(QString name); void setEditPoseName(QString name);
void setEditParameters(std::map<QString, std::map<QString, QString>> parameters); void setEditPoseFrames(std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> frames);
void setEditAttributes(std::map<QString, QString> attributes); void setEditPoseTurnaroundImageId(QUuid imageId);
void setCurrentFrame(int frame);
void setFrameCount(int count);
void updateTitle(); void updateTitle();
void save(); void save();
void clearUnsaveState(); void clearUnsaveState();
void setUnsaveState();
void changeTurnaround(); void changeTurnaround();
private slots:
void updateFramesSettingButton();
void showFramesSettingPopup(const QPoint &pos);
protected: protected:
QSize sizeHint() const override; QSize sizeHint() const override;
void closeEvent(QCloseEvent *event) override; void closeEvent(QCloseEvent *event) override;
void reject() override; void reject() override;
private: private:
void ensureEnoughFrames();
const Document *m_document = nullptr; const Document *m_document = nullptr;
PosePreviewManager *m_posePreviewManager = nullptr; PosePreviewManager *m_posePreviewManager = nullptr;
ModelWidget *m_previewWidget = nullptr; ModelWidget *m_previewWidget = nullptr;
bool m_isPreviewDirty = false; bool m_isPreviewDirty = false;
bool m_closed = false; bool m_closed = false;
std::map<QString, std::map<QString, QString>> m_parameters; std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> m_frames;
std::map<QString, QString> m_attributes; std::map<QString, QString> m_currentAttributes;
std::map<QString, std::map<QString, QString>> m_currentParameters;
int m_currentFrame = 0;
QUuid m_poseId; QUuid m_poseId;
bool m_unsaved = false; bool m_unsaved = false;
QUuid m_imageId; QUuid m_imageId;
QLineEdit *m_nameEdit = nullptr; QLineEdit *m_nameEdit = nullptr;
PoseDocument *m_poseDocument = nullptr; PoseDocument *m_poseDocument = nullptr;
SkeletonGraphicsWidget *m_poseGraphicsWidget = nullptr; SkeletonGraphicsWidget *m_poseGraphicsWidget = nullptr;
QUuid findImageIdFromAttributes(const std::map<QString, QString> &attributes); QPushButton *m_framesSettingButton = nullptr;
QSlider *m_currentFrameSlider = nullptr;
}; };
#endif #endif

View File

@ -22,7 +22,7 @@ PoseManageWidget::PoseManageWidget(const Document *document, QWidget *parent) :
connect(m_poseListWidget, &PoseListWidget::modifyPose, this, &PoseManageWidget::showPoseDialog); connect(m_poseListWidget, &PoseListWidget::modifyPose, this, &PoseManageWidget::showPoseDialog);
InfoLabel *infoLabel = new InfoLabel; InfoLabel *infoLabel = new InfoLabel;
infoLabel->show(); infoLabel->hide();
auto refreshInfoLabel = [=]() { auto refreshInfoLabel = [=]() {
if (m_document->currentRigSucceed()) { if (m_document->currentRigSucceed()) {
@ -77,8 +77,8 @@ void PoseManageWidget::showPoseDialog(QUuid poseId)
if (nullptr != pose) { if (nullptr != pose) {
poseEditWidget->setEditPoseId(poseId); poseEditWidget->setEditPoseId(poseId);
poseEditWidget->setEditPoseName(pose->name); poseEditWidget->setEditPoseName(pose->name);
poseEditWidget->setEditParameters(pose->parameters); poseEditWidget->setEditPoseFrames(pose->frames);
poseEditWidget->setEditAttributes(pose->attributes); poseEditWidget->setEditPoseTurnaroundImageId(pose->turnaroundImageId);
poseEditWidget->clearUnsaveState(); poseEditWidget->clearUnsaveState();
} }
} }

View File

@ -23,20 +23,20 @@ PosePreviewsGenerator::~PosePreviewsGenerator()
delete m_outcome; delete m_outcome;
} }
void PosePreviewsGenerator::addPose(QUuid poseId, const std::map<QString, std::map<QString, QString>> &pose) void PosePreviewsGenerator::addPose(std::pair<QUuid, int> idAndFrame, const std::map<QString, std::map<QString, QString>> &pose)
{ {
m_poses.push_back(std::make_pair(poseId, pose)); m_poses.push_back(std::make_pair(idAndFrame, pose));
} }
const std::set<QUuid> &PosePreviewsGenerator::generatedPreviewPoseIds() const std::set<std::pair<QUuid, int>> &PosePreviewsGenerator::generatedPreviewPoseIdAndFrames()
{ {
return m_generatedPoseIds; return m_generatedPoseIdAndFrames;
} }
MeshLoader *PosePreviewsGenerator::takePreview(QUuid poseId) MeshLoader *PosePreviewsGenerator::takePreview(std::pair<QUuid, int> idAndFrame)
{ {
MeshLoader *resultMesh = m_previews[poseId]; MeshLoader *resultMesh = m_previews[idAndFrame];
m_previews[poseId] = nullptr; m_previews[idAndFrame] = nullptr;
return resultMesh; return resultMesh;
} }
@ -57,7 +57,7 @@ void PosePreviewsGenerator::process()
poser->reset(); poser->reset();
m_generatedPoseIds.insert(pose.first); m_generatedPoseIdAndFrames.insert(pose.first);
} }
delete poser; delete poser;

View File

@ -18,9 +18,9 @@ public:
const std::map<int, RiggerVertexWeights> *rigWeights, const std::map<int, RiggerVertexWeights> *rigWeights,
const Outcome &outcome); const Outcome &outcome);
~PosePreviewsGenerator(); ~PosePreviewsGenerator();
void addPose(QUuid poseId, const std::map<QString, std::map<QString, QString>> &pose); void addPose(std::pair<QUuid, int> idAndFrame, const std::map<QString, std::map<QString, QString>> &pose);
const std::set<QUuid> &generatedPreviewPoseIds(); const std::set<std::pair<QUuid, int>> &generatedPreviewPoseIdAndFrames();
MeshLoader *takePreview(QUuid poseId); MeshLoader *takePreview(std::pair<QUuid, int> idAndFrame);
signals: signals:
void finished(); void finished();
public slots: public slots:
@ -30,9 +30,9 @@ private:
std::vector<RiggerBone> m_rigBones; std::vector<RiggerBone> m_rigBones;
std::map<int, RiggerVertexWeights> m_rigWeights; std::map<int, RiggerVertexWeights> m_rigWeights;
Outcome *m_outcome = nullptr; Outcome *m_outcome = nullptr;
std::vector<std::pair<QUuid, std::map<QString, std::map<QString, QString>>>> m_poses; std::vector<std::pair<std::pair<QUuid, int>, std::map<QString, std::map<QString, QString>>>> m_poses;
std::map<QUuid, MeshLoader *> m_previews; std::map<std::pair<QUuid, int>, MeshLoader *> m_previews;
std::set<QUuid> m_generatedPoseIds; std::set<std::pair<QUuid, int>> m_generatedPoseIdAndFrames;
}; };
#endif #endif

View File

@ -163,13 +163,13 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
} }
QAction copyAction(tr("Copy"), this); QAction copyAction(tr("Copy"), this);
if (!m_nodePositionModifyOnly && hasSelection()) { if (hasNodeSelection()) {
connect(&copyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::copy); connect(&copyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::copy);
contextMenu.addAction(&copyAction); contextMenu.addAction(&copyAction);
} }
QAction pasteAction(tr("Paste"), this); QAction pasteAction(tr("Paste"), this);
if (!m_nodePositionModifyOnly && m_document->hasPastableNodesInClipboard()) { if (m_document->hasPastableNodesInClipboard()) {
connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste); connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste);
contextMenu.addAction(&pasteAction); contextMenu.addAction(&pasteAction);
} }

View File

@ -18,7 +18,7 @@ public:
std::map<QString, std::map<QString, QString>> parts; std::map<QString, std::map<QString, QString>> parts;
std::map<QString, std::map<QString, QString>> components; std::map<QString, std::map<QString, QString>> components;
std::map<QString, QString> rootComponent; std::map<QString, QString> rootComponent;
std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> poses; // std::pair<Pose attributes, Bone attributes> std::vector<std::pair<std::map<QString, QString>, std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>>>> poses; // std::pair<Pose attributes, frames> frame: std::pair<Frame attributes, Frame parameters>
std::vector<std::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>>> motions; // std::pair<Motion attributes, clips> std::vector<std::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>>> motions; // std::pair<Motion attributes, clips>
std::vector<std::pair<std::map<QString, QString>, std::vector<std::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>>>>> materials; // std::pair<Material attributes, layers> layer: std::pair<Layer attributes, maps> std::vector<std::pair<std::map<QString, QString>, std::vector<std::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>>>>> materials; // std::pair<Material attributes, layers> layer: std::pair<Layer attributes, maps>
@ -72,11 +72,17 @@ public:
addQStringToBuffer(subItem.second); addQStringToBuffer(subItem.second);
} }
for (const auto &subItem: item.second) { for (const auto &subItem: item.second) {
addQStringToBuffer(subItem.first); for (const auto &subSubItem: subItem.first) {
for (const auto &subSubItem: subItem.second) {
addQStringToBuffer(subSubItem.first); addQStringToBuffer(subSubItem.first);
addQStringToBuffer(subSubItem.second); addQStringToBuffer(subSubItem.second);
} }
for (const auto &subSubItem: subItem.second) {
addQStringToBuffer(subSubItem.first);
for (const auto &subSubSubItem: subSubItem.second) {
addQStringToBuffer(subSubSubItem.first);
addQStringToBuffer(subSubSubItem.second);
}
}
} }
} }
for (const auto &item: motions) { for (const auto &item: motions) {

View File

@ -127,25 +127,37 @@ void saveSkeletonToXmlStream(Snapshot *snapshot, QXmlStreamWriter *writer)
writer->writeEndElement(); writer->writeEndElement();
writer->writeStartElement("poses"); writer->writeStartElement("poses");
std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>>::iterator poseIterator; //std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>>::iterator poseIterator;
std::vector<std::pair<std::map<QString, QString>, std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>>>>::iterator poseIterator;
for (poseIterator = snapshot->poses.begin(); poseIterator != snapshot->poses.end(); poseIterator++) { for (poseIterator = snapshot->poses.begin(); poseIterator != snapshot->poses.end(); poseIterator++) {
std::map<QString, QString>::iterator poseAttributeIterator; std::map<QString, QString>::iterator poseAttributeIterator;
writer->writeStartElement("pose"); writer->writeStartElement("pose");
for (poseAttributeIterator = poseIterator->first.begin(); poseAttributeIterator != poseIterator->first.end(); poseAttributeIterator++) { for (poseAttributeIterator = poseIterator->first.begin(); poseAttributeIterator != poseIterator->first.end(); poseAttributeIterator++) {
writer->writeAttribute(poseAttributeIterator->first, poseAttributeIterator->second); writer->writeAttribute(poseAttributeIterator->first, poseAttributeIterator->second);
} }
writer->writeStartElement("parameters"); writer->writeStartElement("frames");
std::map<QString, std::map<QString, QString>>::iterator itemsIterator; std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>>::iterator frameIterator;
for (itemsIterator = poseIterator->second.begin(); itemsIterator != poseIterator->second.end(); itemsIterator++) { for (frameIterator = poseIterator->second.begin(); frameIterator != poseIterator->second.end(); frameIterator++) {
std::map<QString, QString>::iterator parametersIterator; std::map<QString, QString>::iterator frameAttributeIterator;
writer->writeStartElement("parameter"); writer->writeStartElement("frame");
writer->writeAttribute("for", itemsIterator->first); for (frameAttributeIterator = frameIterator->first.begin(); frameAttributeIterator != frameIterator->first.end(); frameAttributeIterator++) {
for (parametersIterator = itemsIterator->second.begin(); parametersIterator != itemsIterator->second.end(); writer->writeAttribute(frameAttributeIterator->first, frameAttributeIterator->second);
parametersIterator++) { }
writer->writeAttribute(parametersIterator->first, parametersIterator->second); writer->writeStartElement("parameters");
std::map<QString, std::map<QString, QString>>::iterator itemsIterator;
for (itemsIterator = frameIterator->second.begin(); itemsIterator != frameIterator->second.end(); itemsIterator++) {
std::map<QString, QString>::iterator parametersIterator;
writer->writeStartElement("parameter");
writer->writeAttribute("for", itemsIterator->first);
for (parametersIterator = itemsIterator->second.begin(); parametersIterator != itemsIterator->second.end();
parametersIterator++) {
writer->writeAttribute(parametersIterator->first, parametersIterator->second);
}
writer->writeEndElement();
}
writer->writeEndElement();
writer->writeEndElement();
} }
writer->writeEndElement();
}
writer->writeEndElement(); writer->writeEndElement();
writer->writeEndElement(); writer->writeEndElement();
} }
@ -188,7 +200,8 @@ void loadSkeletonFromXmlStream(Snapshot *snapshot, QXmlStreamReader &reader)
std::vector<QString> elementNameStack; std::vector<QString> elementNameStack;
std::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>> currentMaterialLayer; std::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>> currentMaterialLayer;
std::pair<std::map<QString, QString>, std::vector<std::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>>>> currentMaterial; std::pair<std::map<QString, QString>, std::vector<std::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>>>> currentMaterial;
std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>> currentPose; std::pair<std::map<QString, QString>, std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>>> currentPose;
std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>> currentPoseFrame;
std::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>> currentMotion; std::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>> currentMotion;
while (!reader.atEnd()) { while (!reader.atEnd()) {
reader.readNext(); reader.readNext();
@ -293,15 +306,18 @@ void loadSkeletonFromXmlStream(Snapshot *snapshot, QXmlStreamReader &reader)
foreach(const QXmlStreamAttribute &attr, reader.attributes()) { foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
currentPose.first[attr.name().toString()] = attr.value().toString(); currentPose.first[attr.name().toString()] = attr.value().toString();
} }
} else if (fullName == "canvas.poses.pose.parameter" || } else if (fullName == "canvas.poses.pose.frames.frame") {
fullName == "canvas.poses.pose.parameters.parameter") { foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
currentPoseFrame.first[attr.name().toString()] = attr.value().toString();
}
} else if (fullName == "canvas.poses.pose.frames.frame.parameters.parameter") {
QString forWhat = reader.attributes().value("for").toString(); QString forWhat = reader.attributes().value("for").toString();
if (forWhat.isEmpty()) if (forWhat.isEmpty())
continue; continue;
foreach(const QXmlStreamAttribute &attr, reader.attributes()) { foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
if ("for" == attr.name().toString()) if ("for" == attr.name().toString())
continue; continue;
currentPose.second[forWhat][attr.name().toString()] = attr.value().toString(); currentPoseFrame.second[forWhat][attr.name().toString()] = attr.value().toString();
} }
} else if (fullName == "canvas.motions.motion") { } else if (fullName == "canvas.motions.motion") {
QString motionId = reader.attributes().value("id").toString(); QString motionId = reader.attributes().value("id").toString();
@ -325,6 +341,8 @@ void loadSkeletonFromXmlStream(Snapshot *snapshot, QXmlStreamReader &reader)
currentMaterial.second.push_back(currentMaterialLayer); currentMaterial.second.push_back(currentMaterialLayer);
} else if (fullName == "canvas.materials.material") { } else if (fullName == "canvas.materials.material") {
snapshot->materials.push_back(currentMaterial); snapshot->materials.push_back(currentMaterial);
} else if (fullName == "canvas.poses.pose.frames.frame") {
currentPose.second.push_back(currentPoseFrame);
} else if (fullName == "canvas.poses.pose") { } else if (fullName == "canvas.poses.pose") {
snapshot->poses.push_back(currentPose); snapshot->poses.push_back(currentPose);
} else if (fullName == "canvas.motions.motion") { } else if (fullName == "canvas.motions.motion") {