#include #include #include #include #include #include #include "posedocument.h" #include "rigger.h" #include "util.h" #include "document.h" #include "snapshot.h" #include "snapshotxml.h" const float PoseDocument::m_nodeRadius = 0.015; const float PoseDocument::m_groundPlaneHalfThickness = 0.005 / 4; const bool PoseDocument::m_hideRootAndVirtual = true; const float PoseDocument::m_outcomeScaleFactor = 0.5; bool PoseDocument::hasPastableNodesInClipboard() const { const QClipboard *clipboard = QApplication::clipboard(); const QMimeData *mimeData = clipboard->mimeData(); if (mimeData->hasText()) { if (-1 != mimeData->text().indexOf("text().indexOf(" nodeIdSet) const { std::map> 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(), parameters}); document.poseIdList.push_back(poseId); Snapshot snapshot; std::set 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() { 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() { 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) { turnaround = image; emit turnaroundChanged(); } void PoseDocument::updateOtherFramesParameters(const std::vector>> &otherFramesParameters) { m_otherFramesParameters = otherFramesParameters; } void PoseDocument::reset() { nodeMap.clear(); edgeMap.clear(); partMap.clear(); m_otherIds.clear(); m_boneNameToIdsMap.clear(); m_bonesPartId = QUuid(); m_groundPartId = QUuid(); m_groundEdgeId = QUuid(); emit cleanup(); emit parametersChanged(); } void PoseDocument::clearHistories() { m_undoItems.clear(); m_redoItems.clear(); } void PoseDocument::updateBonesAndHeightAboveGroundLevelFromParameters(std::vector *bones, float *heightAboveGroundLevel, const std::map> ¶meters) { 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()) { QVector3D toPosition = { valueOfKeyInMapOrEmpty(map, "toX").toFloat(), valueOfKeyInMapOrEmpty(map, "toY").toFloat(), valueOfKeyInMapOrEmpty(map, "toZ").toFloat() }; bone.tailPosition = toPosition; for (const auto &child: bone.children) { auto &childBone = (*bones)[child]; childBone.headPosition = toPosition; } } } } const auto findRoot = parameters.find(Rigger::rootBoneName); if (findRoot != parameters.end()) { const auto &map = findRoot->second; { auto findHeightAboveGroundLevel = map.find("heightAboveGroundLevel"); if (findHeightAboveGroundLevel != map.end()) { *heightAboveGroundLevel = valueOfKeyInMapOrEmpty(map, "heightAboveGroundLevel").toFloat(); } } } } void PoseDocument::fromParameters(const std::vector *rigBones, const std::map> ¶meters) { if (nullptr == rigBones || rigBones->empty()) { m_riggerBones.clear(); return; } if (&m_riggerBones != rigBones) m_riggerBones = *rigBones; float heightAboveGroundLevel = 0; std::vector bones = *rigBones; updateBonesAndHeightAboveGroundLevelFromParameters(&bones, &heightAboveGroundLevel, parameters); reset(); for (const auto &otherParameters: m_otherFramesParameters) { float otherHeightAboveGroundLevel = 0; std::vector otherBones = *rigBones; updateBonesAndHeightAboveGroundLevelFromParameters(&otherBones, &otherHeightAboveGroundLevel, otherParameters); std::map> boneNameToIdsMap; QUuid groundPartId; QUuid bonesPartId; QUuid groundEdgeId; parametersToNodes(&otherBones, otherHeightAboveGroundLevel, &boneNameToIdsMap, &groundPartId, &bonesPartId, &groundEdgeId, true); } parametersToNodes(&bones, heightAboveGroundLevel, &m_boneNameToIdsMap, &m_groundPartId, &m_bonesPartId, &m_groundEdgeId, false); emit parametersChanged(); } void PoseDocument::parametersToNodes(const std::vector *rigBones, const float heightAboveGroundLevel, std::map> *boneNameToIdsMap, QUuid *groundPartId, QUuid *bonesPartId, QUuid *groundEdgeId, bool isOther) { if (nullptr == rigBones || rigBones->empty()) { return; } std::set newAddedNodeIds; std::set newAddedEdgeIds; *bonesPartId = QUuid::createUuid(); auto &bonesPart = partMap[*bonesPartId]; bonesPart.id = *bonesPartId; //qDebug() << "rigBones size:" << rigBones->size(); std::vector> edgePairs; for (size_t i = m_hideRootAndVirtual ? 1 : 0; i < rigBones->size(); ++i) { const auto &bone = (*rigBones)[i]; for (const auto &child: bone.children) { //qDebug() << "Add pair:" << bone.name << "->" << (*rigBones)[child].name; edgePairs.push_back({i, child}); } } std::map 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]; if (!bone.name.startsWith("Virtual_") || !m_hideRootAndVirtual) { SkeletonNode node; node.partId = *bonesPartId; node.id = QUuid::createUuid(); node.setRadius(m_nodeRadius); node.x = fromOutcomeX(bone.headPosition.x()); node.y = fromOutcomeY(bone.headPosition.y()); node.z = fromOutcomeZ(bone.headPosition.z()); 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]; if (!bone.name.startsWith("Virtual_") || !m_hideRootAndVirtual) { SkeletonNode node; node.partId = *bonesPartId; node.id = QUuid::createUuid(); node.setRadius(m_nodeRadius); node.x = fromOutcomeX(bone.headPosition.x()); node.y = fromOutcomeY(bone.headPosition.y()); node.z = fromOutcomeZ(bone.headPosition.z()); nodeMap[node.id] = node; newAddedNodeIds.insert(node.id); boneIndexToHeadNodeIdMap[edgePair.second] = node.id; secondNodeId = node.id; } } else { secondNodeId = findSecond->second; } if (firstNodeId.isNull() || secondNodeId.isNull()) continue; SkeletonEdge edge; edge.partId = *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 = m_hideRootAndVirtual ? 1 : 0; i < rigBones->size(); ++i) { const auto &bone = (*rigBones)[i]; if (m_hideRootAndVirtual && bone.name.startsWith("Virtual_")) continue; if (bone.children.empty()) { const QUuid &firstNodeId = boneIndexToHeadNodeIdMap[i]; SkeletonNode node; node.partId = *bonesPartId; node.id = QUuid::createUuid(); node.setRadius(m_nodeRadius / 2); node.x = fromOutcomeX(bone.tailPosition.x()); node.y = fromOutcomeY(bone.tailPosition.y()); node.z = fromOutcomeZ(bone.tailPosition.z()); nodeMap[node.id] = node; newAddedNodeIds.insert(node.id); (*boneNameToIdsMap)[bone.name] = {firstNodeId, node.id}; SkeletonEdge edge; edge.partId = *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); //qDebug() << "Add pair:" << bone.name << "->" << "~"; continue; } for (const auto &child: bone.children) { (*boneNameToIdsMap)[bone.name] = {boneIndexToHeadNodeIdMap[i], boneIndexToHeadNodeIdMap[child]}; } } auto findRootNodeId = boneIndexToHeadNodeIdMap.find(0); if (findRootNodeId != boneIndexToHeadNodeIdMap.end()) { nodeMap[findRootNodeId->second].setRadius(m_nodeRadius * 2); } if (!isOther) { *groundPartId = QUuid::createUuid(); auto &groundPart = partMap[*groundPartId]; groundPart.id = *groundPartId; float footBottomY = findFootBottomY(); float legHeight = findLegHeight(); float myHeightAboveGroundLevel = heightAboveGroundLevel * legHeight; float groundNodeY = footBottomY + m_groundPlaneHalfThickness + myHeightAboveGroundLevel; std::pair groundNodesPair; { SkeletonNode node; node.partId = *groundPartId; node.id = QUuid::createUuid(); node.setRadius(m_groundPlaneHalfThickness); node.x = -100; node.y = groundNodeY; node.z = -100; nodeMap[node.id] = node; newAddedNodeIds.insert(node.id); groundNodesPair.first = node.id; } { SkeletonNode node; node.partId = *groundPartId; node.id = QUuid::createUuid(); node.setRadius(m_groundPlaneHalfThickness); node.x = 100; node.y = groundNodeY; node.z = 100; nodeMap[node.id] = node; newAddedNodeIds.insert(node.id); groundNodesPair.second = node.id; } { SkeletonEdge edge; edge.partId = *groundPartId; edge.id = QUuid::createUuid(); edge.nodeIds.push_back(groundNodesPair.first); edge.nodeIds.push_back(groundNodesPair.second); edgeMap[edge.id] = edge; *groundEdgeId = edge.id; newAddedEdgeIds.insert(edge.id); nodeMap[groundNodesPair.first].edgeIds.push_back(edge.id); nodeMap[groundNodesPair.second].edgeIds.push_back(edge.id); } } if (isOther) { for (const auto &nodeIt: newAddedNodeIds) m_otherIds.insert(nodeIt); for (const auto &edgeIt: newAddedEdgeIds) m_otherIds.insert(edgeIt); } for (const auto &nodeIt: newAddedNodeIds) { emit nodeAdded(nodeIt); } for (const auto &edgeIt: newAddedEdgeIds) { emit edgeAdded(edgeIt); } } 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::findFootBottomY() const { auto maxY = std::numeric_limits::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; } float PoseDocument::findLegHeight() const { float firstSpineY = findFirstSpineY(); float footBottomY = findFootBottomY(); return std::abs(footBottomY - firstSpineY); } float PoseDocument::findFirstSpineY() const { const auto &findFirstSpine = m_boneNameToIdsMap.find(Rigger::firstSpineBoneName); if (findFirstSpine == m_boneNameToIdsMap.end()) { qDebug() << "Find first spine bone failed:" << Rigger::firstSpineBoneName; return 0; } const SkeletonNode *firstSpineNode = findNode(findFirstSpine->second.first); if (nullptr == firstSpineNode) { qDebug() << "Find first spine node failed"; return 0; } return firstSpineNode->y; } void PoseDocument::toParameters(std::map> ¶meters, const std::set &limitNodeIds) const { float heightAboveGroundLevel = 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()) { if (limitNodeIds.empty() || limitNodeIds.find(findFirstNode->first) != limitNodeIds.end() || limitNodeIds.find(findSecondNode->first) != limitNodeIds.end()) { float myHeightAboveGroundLevel = (findFirstNode->second.y + findSecondNode->second.y) / 2 - (findFootBottomY() + m_groundPlaneHalfThickness); float legHeight = findLegHeight(); if (legHeight > 0) heightAboveGroundLevel = myHeightAboveGroundLevel / legHeight; } } } } if (!qFuzzyIsNull(heightAboveGroundLevel)) { auto &boneParameter = parameters[Rigger::rootBoneName]; boneParameter["heightAboveGroundLevel"] = QString::number(heightAboveGroundLevel); } for (const auto &item: m_boneNameToIdsMap) { 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; if (limitNodeIds.empty() || limitNodeIds.find(boneNodeIdPair.first) != limitNodeIds.end() || limitNodeIds.find(boneNodeIdPair.second) != limitNodeIds.end()) { auto &boneParameter = parameters[item.first]; boneParameter["fromX"] = QString::number(toOutcomeX(findFirstNode->second.x)); boneParameter["fromY"] = QString::number(toOutcomeY(findFirstNode->second.y)); boneParameter["fromZ"] = QString::number(toOutcomeZ(findFirstNode->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)); } } } float PoseDocument::fromOutcomeX(float x) { return x * m_outcomeScaleFactor + 0.5; } float PoseDocument::toOutcomeX(float x) { return (x - 0.5) / m_outcomeScaleFactor; } float PoseDocument::fromOutcomeY(float y) { return -y * m_outcomeScaleFactor + 0.5; } float PoseDocument::toOutcomeY(float y) { return (0.5 - y) / m_outcomeScaleFactor; } float PoseDocument::fromOutcomeZ(float z) { return -z * m_outcomeScaleFactor + 1; } float PoseDocument::toOutcomeZ(float z) { return (1.0 - z) / m_outcomeScaleFactor; } QString PoseDocument::findBoneNameByNodeId(const QUuid &nodeId) { for (const auto &item: m_boneNameToIdsMap) { if (nodeId == item.second.first || nodeId == item.second.second) return item.first; } return QString(); } void PoseDocument::switchChainSide(const std::set nodeIds) { QRegularExpression reJoints("^(Left|Right)([a-zA-Z]+\\d*)_(Joint\\d+)$"); std::set baseNames; for (const auto &nodeId: nodeIds) { QString boneName = findBoneNameByNodeId(nodeId); if (boneName.isEmpty()) { //qDebug() << "Find bone name for node failed:" << nodeId; continue; } QRegularExpressionMatch match = reJoints.match(boneName); if (!match.hasMatch()) { //qDebug() << "Match bone name for side failed:" << boneName; continue; } QString baseName = match.captured(2); baseNames.insert(baseName); } auto switchYZ = [=](const QUuid &first, const QUuid &second) { auto findFirstNode = nodeMap.find(first); if (findFirstNode == nodeMap.end()) return; auto findSecondNode = nodeMap.find(second); if (findSecondNode == nodeMap.end()) return; std::swap(findFirstNode->second.y, findSecondNode->second.y); std::swap(findFirstNode->second.z, findSecondNode->second.z); emit nodeOriginChanged(first); emit nodeOriginChanged(second); }; std::set> switchPairs; for (const auto &baseName: baseNames) { for (const auto &item: m_boneNameToIdsMap) { QRegularExpressionMatch match = reJoints.match(item.first); if (!match.hasMatch()) continue; QString itemSide = match.captured(1); QString itemBaseName = match.captured(2); QString itemJointName = match.captured(3); //qDebug() << "itemSide:" << itemSide << "itemBaseName:" << itemBaseName << "itemJointName:" << itemJointName; if (itemBaseName == baseName && "Left" == itemSide) { QString otherSide = "Right"; QString pairedName = otherSide + itemBaseName + "_" + itemJointName; const auto findPaired = m_boneNameToIdsMap.find(pairedName); if (findPaired == m_boneNameToIdsMap.end()) { qDebug() << "Couldn't find paired name:" << pairedName; continue; } //qDebug() << "Switched:" << pairedName; switchPairs.insert({item.second.first, findPaired->second.first}); switchPairs.insert({item.second.second, findPaired->second.second}); } } } for (const auto &pair: switchPairs) { switchYZ(pair.first, pair.second); } //qDebug() << "switchedPairNum:" << switchPairs.size(); if (!switchPairs.empty()) emit parametersChanged(); }