From 48bc8df5c39bd9d5c7854ce7a7aee5f8d38f1169 Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Fri, 14 Sep 2018 17:45:05 +0800 Subject: [PATCH] Add auto-rig This is the second version of auto-rig, differing from the first version, the user should pick up a rig type, unless no rig will be generated. Currently, only supports Tetrapod without tail. Export as glTF format to test this feature. This commit also achive better quality quads than before. --- dust3d.pro | 24 + src/autorigger.cpp | 847 +++++++++++++++++++++++++++++++++ src/autorigger.h | 126 +++++ src/exportpreviewwidget.cpp | 44 +- src/gltffile.cpp | 202 +++++++- src/gltffile.h | 24 +- src/infolabel.cpp | 1 + src/logbrowserdialog.cpp | 3 + src/markiconcreator.cpp | 23 + src/markiconcreator.h | 16 + src/meshgenerator.cpp | 108 ++++- src/meshgenerator.h | 7 +- src/meshquadify.cpp | 96 ++++ src/meshquadify.h | 10 + src/meshresultcontext.cpp | 14 + src/meshresultcontext.h | 4 + src/meshsplitter.cpp | 102 ++++ src/meshsplitter.h | 26 + src/meshutil.cpp | 6 +- src/meshutil.h | 2 + src/modelofflinerender.cpp | 29 +- src/modelofflinerender.h | 6 + src/riggenerator.cpp | 177 +++++++ src/riggenerator.h | 35 ++ src/rigtype.cpp | 6 + src/rigtype.h | 47 ++ src/rigwidget.cpp | 88 ++++ src/rigwidget.h | 29 ++ src/skeletonbonemark.cpp | 9 + src/skeletonbonemark.h | 140 ++++++ src/skeletondocument.cpp | 194 +++++++- src/skeletondocument.h | 32 +- src/skeletondocumentwindow.cpp | 173 +++++-- src/skeletondocumentwindow.h | 18 +- src/skeletongenerator.cpp | 72 --- src/skeletongenerator.h | 29 -- src/skeletongraphicswidget.cpp | 65 ++- src/skeletongraphicswidget.h | 3 + src/skeletonxml.cpp | 27 +- 39 files changed, 2631 insertions(+), 233 deletions(-) create mode 100644 src/autorigger.cpp create mode 100644 src/autorigger.h create mode 100644 src/markiconcreator.cpp create mode 100644 src/markiconcreator.h create mode 100644 src/meshquadify.cpp create mode 100644 src/meshquadify.h create mode 100644 src/meshsplitter.cpp create mode 100644 src/meshsplitter.h create mode 100644 src/riggenerator.cpp create mode 100644 src/riggenerator.h create mode 100644 src/rigtype.cpp create mode 100644 src/rigtype.h create mode 100644 src/rigwidget.cpp create mode 100644 src/rigwidget.h create mode 100644 src/skeletonbonemark.cpp create mode 100644 src/skeletonbonemark.h delete mode 100644 src/skeletongenerator.cpp delete mode 100644 src/skeletongenerator.h diff --git a/dust3d.pro b/dust3d.pro index f8a40a4f..83c617d2 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -140,6 +140,30 @@ HEADERS += src/infolabel.h SOURCES += src/graphicscontainerwidget.cpp HEADERS += src/graphicscontainerwidget.h +SOURCES += src/rigwidget.cpp +HEADERS += src/rigwidget.h + +SOURCES += src/markiconcreator.cpp +HEADERS += src/markiconcreator.h + +SOURCES += src/skeletonbonemark.cpp +HEADERS += src/skeletonbonemark.h + +SOURCES += src/meshsplitter.cpp +HEADERS += src/meshsplitter.h + +SOURCES += src/autorigger.cpp +HEADERS += src/autorigger.h + +SOURCES += src/rigtype.cpp +HEADERS += src/rigtype.h + +SOURCES += src/riggenerator.cpp +HEADERS += src/riggenerator.h + +SOURCES += src/meshquadify.cpp +HEADERS += src/meshquadify.h + SOURCES += src/main.cpp HEADERS += src/version.h diff --git a/src/autorigger.cpp b/src/autorigger.cpp new file mode 100644 index 00000000..8c644bc0 --- /dev/null +++ b/src/autorigger.cpp @@ -0,0 +1,847 @@ +#include +#include +#include "theme.h" +#include "skeletonbonemark.h" +#include "autorigger.h" + +AutoRigger::AutoRigger(const std::vector &verticesPositions, + const std::set &inputTriangles) : + m_verticesPositions(verticesPositions), + m_inputTriangles(inputTriangles) +{ +} + +bool AutoRigger::isCutOffSplitter(SkeletonBoneMark boneMark) +{ + return boneMark == SkeletonBoneMark::Neck || + boneMark == SkeletonBoneMark::Shoulder || + boneMark == SkeletonBoneMark::Hip; +} + +bool AutoRigger::calculateBodyTriangles(std::set &bodyTriangles) +{ + bodyTriangles = m_inputTriangles; + for (const auto &marksMapIt: m_marksMap) { + if (isCutOffSplitter(marksMapIt.first.first)) { + for (const auto index: marksMapIt.second) { + auto &mark = m_marks[index]; + std::set intersection; + std::set_intersection(bodyTriangles.begin(), bodyTriangles.end(), + mark.bigGroup().begin(), mark.bigGroup().end(), + std::insert_iterator>(intersection, intersection.begin())); + bodyTriangles = intersection; + } + } + } + if (bodyTriangles.empty()) { + m_messages.push_back(std::make_pair(QtCriticalMsg, + tr("Calculate body from marks failed"))); + return false; + } + return true; +} + +bool AutoRigger::addMarkGroup(SkeletonBoneMark boneMark, SkeletonSide boneSide, QVector3D bonePosition, + const std::set &markTriangles) +{ + m_marks.push_back(AutoRiggerMark()); + + AutoRiggerMark &mark = m_marks.back(); + mark.boneMark = boneMark; + mark.boneSide = boneSide; + mark.bonePosition = bonePosition; + mark.markTriangles = markTriangles; + + if (isCutOffSplitter(mark.boneMark)) { + if (!mark.split(m_inputTriangles)) { + m_marksMap[std::make_pair(mark.boneMark, mark.boneSide)].push_back(m_marks.size() - 1); + m_errorMarkNames.push_back(SkeletonSideToDispName(mark.boneSide) + " " + SkeletonBoneMarkToDispName(mark.boneMark)); + m_messages.push_back(std::make_pair(QtCriticalMsg, + tr("Mark \"%1 %2\" couldn't cut off the mesh").arg(SkeletonSideToDispName(mark.boneSide)).arg(SkeletonBoneMarkToString(mark.boneMark)))); + return false; + } + } + + m_marksMap[std::make_pair(mark.boneMark, mark.boneSide)].push_back(m_marks.size() - 1); + + return true; +} + +const std::vector> &AutoRigger::messages() +{ + return m_messages; +} + +void AutoRigger::addTrianglesToVertices(const std::set &triangles, std::set &vertices) +{ + for (const auto &triangle: triangles) { + for (int i = 0; i < 3; i++) { + vertices.insert(triangle.indicies[i]); + } + } +} + +bool AutoRigger::validate() +{ + bool foundError = false; + + std::vector> mustPresentedMarks; + + mustPresentedMarks.push_back(std::make_pair(SkeletonBoneMark::Neck, SkeletonSide::None)); + mustPresentedMarks.push_back(std::make_pair(SkeletonBoneMark::Shoulder, SkeletonSide::Left)); + mustPresentedMarks.push_back(std::make_pair(SkeletonBoneMark::Elbow, SkeletonSide::Left)); + mustPresentedMarks.push_back(std::make_pair(SkeletonBoneMark::Wrist, SkeletonSide::Left)); + mustPresentedMarks.push_back(std::make_pair(SkeletonBoneMark::Shoulder, SkeletonSide::Right)); + mustPresentedMarks.push_back(std::make_pair(SkeletonBoneMark::Elbow, SkeletonSide::Right)); + mustPresentedMarks.push_back(std::make_pair(SkeletonBoneMark::Wrist, SkeletonSide::Right)); + mustPresentedMarks.push_back(std::make_pair(SkeletonBoneMark::Hip, SkeletonSide::Left)); + mustPresentedMarks.push_back(std::make_pair(SkeletonBoneMark::Knee, SkeletonSide::Left)); + mustPresentedMarks.push_back(std::make_pair(SkeletonBoneMark::Ankle, SkeletonSide::Left)); + mustPresentedMarks.push_back(std::make_pair(SkeletonBoneMark::Hip, SkeletonSide::Right)); + mustPresentedMarks.push_back(std::make_pair(SkeletonBoneMark::Knee, SkeletonSide::Right)); + mustPresentedMarks.push_back(std::make_pair(SkeletonBoneMark::Ankle, SkeletonSide::Right)); + + for (const auto &pair: mustPresentedMarks) { + if (m_marksMap.find(pair) == m_marksMap.end()) { + foundError = true; + QString markDispName = SkeletonSideToDispName(pair.second) + " " + SkeletonBoneMarkToDispName(pair.first); + m_missingMarkNames.push_back(markDispName); + m_messages.push_back(std::make_pair(QtCriticalMsg, + tr("Couldn't find valid \"%1\" mark").arg(markDispName))); + } + } + + if (foundError) + return false; + + if (!m_errorMarkNames.empty() || !m_missingMarkNames.empty()) + return false; + + return true; +} + +void AutoRigger::resolveBoundingBox(const std::set &vertices, QVector3D &xMin, QVector3D &xMax, QVector3D &yMin, QVector3D &yMax, QVector3D &zMin, QVector3D &zMax) +{ + bool leftFirstTime = true; + bool rightFirstTime = true; + bool topFirstTime = true; + bool bottomFirstTime = true; + bool zLeftFirstTime = true; + bool zRightFirstTime = true; + for (const auto &index: vertices) { + const auto &position = m_verticesPositions[index]; + const float &x = position.x(); + const float &y = position.y(); + const float &z = position.z(); + if (leftFirstTime || x < xMin.x()) { + xMin = position; + leftFirstTime = false; + } + if (topFirstTime || y < yMin.y()) { + yMin = position; + topFirstTime = false; + } + if (rightFirstTime || x > xMax.x()) { + xMax = position; + rightFirstTime = false; + } + if (bottomFirstTime || y > yMax.y()) { + yMax = position; + bottomFirstTime = false; + } + if (zLeftFirstTime || z < zMin.z()) { + zMin = position; + zLeftFirstTime = false; + } + if (zRightFirstTime || z > zMax.z()) { + zMax = position; + zRightFirstTime = false; + } + } +} + +QVector3D AutoRigger::findMinX(const std::set &vertices) +{ + QVector3D minX, minY, minZ, maxX, maxY, maxZ; + resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ); + return minX; +} + +QVector3D AutoRigger::findMaxX(const std::set &vertices) +{ + QVector3D minX, minY, minZ, maxX, maxY, maxZ; + resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ); + return maxX; +} + +QVector3D AutoRigger::findMinY(const std::set &vertices) +{ + QVector3D minX, minY, minZ, maxX, maxY, maxZ; + resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ); + return minY; +} + +QVector3D AutoRigger::findMaxY(const std::set &vertices) +{ + QVector3D minX, minY, minZ, maxX, maxY, maxZ; + resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ); + return maxY; +} + +QVector3D AutoRigger::findMinZ(const std::set &vertices) +{ + QVector3D minX, minY, minZ, maxX, maxY, maxZ; + resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ); + return minZ; +} + +QVector3D AutoRigger::findMaxZ(const std::set &vertices) +{ + QVector3D minX, minY, minZ, maxX, maxY, maxZ; + resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ); + return maxZ; +} + +void AutoRigger::splitVerticesByY(const std::set &vertices, float y, std::set &greaterEqualThanVertices, std::set &lessThanVertices) +{ + for (const auto &index: vertices) { + const auto &position = m_verticesPositions[index]; + if (position.y() >= y) + greaterEqualThanVertices.insert(index); + else + lessThanVertices.insert(index); + } +} + +void AutoRigger::splitVerticesByX(const std::set &vertices, float x, std::set &greaterEqualThanVertices, std::set &lessThanVertices) +{ + for (const auto &index: vertices) { + const auto &position = m_verticesPositions[index]; + if (position.x() >= x) + greaterEqualThanVertices.insert(index); + else + lessThanVertices.insert(index); + } +} + +void AutoRigger::splitVerticesByZ(const std::set &vertices, float z, std::set &greaterEqualThanVertices, std::set &lessThanVertices) +{ + for (const auto &index: vertices) { + const auto &position = m_verticesPositions[index]; + if (position.z() >= z) + greaterEqualThanVertices.insert(index); + else + lessThanVertices.insert(index); + } +} + +const std::vector &AutoRigger::resultBones() +{ + return m_resultBones; +} + +const std::map &AutoRigger::resultWeights() +{ + return m_resultWeights; +} + +void AutoRigger::addVerticesToWeights(const std::set &vertices, int boneIndex) +{ + for (const auto &vertexIndex: vertices) { + auto &weights = m_resultWeights[vertexIndex]; + float distance = m_verticesPositions[vertexIndex].distanceToPoint(m_resultBones[boneIndex].headPosition); + weights.addBone(boneIndex, distance); + } +} + +bool AutoRigger::rig() +{ + if (!validate()) + return false; + + std::set bodyTriangles; + if (!calculateBodyTriangles(bodyTriangles)) + return false; + + auto neckIndicies = m_marksMap.find(std::make_pair(SkeletonBoneMark::Neck, SkeletonSide::None)); + auto leftShoulderIndicies = m_marksMap.find(std::make_pair(SkeletonBoneMark::Shoulder, SkeletonSide::Left)); + auto leftElbowIndicies = m_marksMap.find(std::make_pair(SkeletonBoneMark::Elbow, SkeletonSide::Left)); + auto leftWristIndicies = m_marksMap.find(std::make_pair(SkeletonBoneMark::Wrist, SkeletonSide::Left)); + auto rightShoulderIndicies = m_marksMap.find(std::make_pair(SkeletonBoneMark::Shoulder, SkeletonSide::Right)); + auto rightElbowIndicies = m_marksMap.find(std::make_pair(SkeletonBoneMark::Elbow, SkeletonSide::Right)); + auto rightWristIndicies = m_marksMap.find(std::make_pair(SkeletonBoneMark::Wrist, SkeletonSide::Right)); + auto leftHipIndicies = m_marksMap.find(std::make_pair(SkeletonBoneMark::Hip, SkeletonSide::Left)); + auto leftKneeIndicies = m_marksMap.find(std::make_pair(SkeletonBoneMark::Knee, SkeletonSide::Left)); + auto leftAnkleIndicies = m_marksMap.find(std::make_pair(SkeletonBoneMark::Ankle, SkeletonSide::Left)); + auto rightHipIndicies = m_marksMap.find(std::make_pair(SkeletonBoneMark::Hip, SkeletonSide::Right)); + auto rightKneeIndicies = m_marksMap.find(std::make_pair(SkeletonBoneMark::Knee, SkeletonSide::Right)); + auto rightAnkleIndicies = m_marksMap.find(std::make_pair(SkeletonBoneMark::Ankle, SkeletonSide::Right)); + + // 1. Prepare all bones start and stop positions: + + QVector3D shouldersCenter = (m_marks[leftShoulderIndicies->second[0]].bonePosition + + m_marks[rightShoulderIndicies->second[0]].bonePosition) / 2; + QVector3D hipsCenter = (m_marks[leftHipIndicies->second[0]].bonePosition + + m_marks[rightHipIndicies->second[0]].bonePosition) / 2; + + QVector3D bonesOrigin = hipsCenter; + QVector3D chestBoneStartPosition = (shouldersCenter + hipsCenter) / 2; + QVector3D neckBoneStartPosition = shouldersCenter; + QVector3D headBoneStartPosition = m_marks[neckIndicies->second[0]].bonePosition; + QVector3D leftUpperArmBoneStartPosition = m_marks[leftShoulderIndicies->second[0]].bonePosition; + QVector3D leftLowerArmBoneStartPosition = m_marks[leftElbowIndicies->second[0]].bonePosition; + QVector3D leftHandBoneStartPosition = m_marks[leftWristIndicies->second[0]].bonePosition; + QVector3D rightUpperArmBoneStartPosition = m_marks[rightShoulderIndicies->second[0]].bonePosition; + QVector3D rightLowerArmBoneStartPosition = m_marks[rightElbowIndicies->second[0]].bonePosition; + QVector3D rightHandBoneStartPosition = m_marks[rightWristIndicies->second[0]].bonePosition; + QVector3D leftUpperLegBoneStartPosition = m_marks[leftHipIndicies->second[0]].bonePosition; + QVector3D leftLowerLegBoneStartPosition = m_marks[leftKneeIndicies->second[0]].bonePosition; + QVector3D leftFootBoneStartPosition = m_marks[leftAnkleIndicies->second[0]].bonePosition; + QVector3D rightUpperLegBoneStartPosition = m_marks[rightHipIndicies->second[0]].bonePosition; + QVector3D rightLowerLegBoneStartPosition = m_marks[rightKneeIndicies->second[0]].bonePosition; + QVector3D rightFootBoneStartPosition = m_marks[rightAnkleIndicies->second[0]].bonePosition; + + bool isMainBodyVerticalAligned = fabs(shouldersCenter.y() - hipsCenter.y()) > fabs(shouldersCenter.z() - hipsCenter.z()); + bool isLeftArmVerticalAligned = fabs(leftUpperArmBoneStartPosition.y() - leftHandBoneStartPosition.y()) > fabs(leftUpperArmBoneStartPosition.z() - leftHandBoneStartPosition.z()); + bool isRightArmVerticalAligned = fabs(rightUpperArmBoneStartPosition.y() - rightHandBoneStartPosition.y()) > fabs(rightUpperArmBoneStartPosition.z() - rightHandBoneStartPosition.z()); + + std::set headVertices; + addTrianglesToVertices(m_marks[neckIndicies->second[0]].smallGroup(), headVertices); + QVector3D headBoneStopPosition; + if (isMainBodyVerticalAligned) { + QVector3D maxY = findMaxY(headVertices); + headBoneStopPosition = QVector3D(headBoneStartPosition.x(), + maxY.y(), + maxY.z()); + } else { + QVector3D maxZ = findMaxZ(headVertices); + headBoneStopPosition = QVector3D(headBoneStartPosition.x(), + maxZ.y(), + maxZ.z()); + } + + std::set leftArmVertices; + addTrianglesToVertices(m_marks[leftShoulderIndicies->second[0]].smallGroup(), leftArmVertices); + QVector3D leftHandBoneStopPosition; + if (isLeftArmVerticalAligned) { + QVector3D minY = findMinY(leftArmVertices); + leftHandBoneStopPosition = QVector3D(leftHandBoneStartPosition.x(), + minY.y(), + minY.z()); + } else { + QVector3D maxX = findMaxZ(leftArmVertices); + leftHandBoneStopPosition = QVector3D(maxX.x(), + maxX.y(), + leftHandBoneStartPosition.z()); + } + addTrianglesToVertices(m_marks[leftShoulderIndicies->second[0]].markTriangles, leftArmVertices); + + std::set rightArmVertices; + addTrianglesToVertices(m_marks[rightShoulderIndicies->second[0]].smallGroup(), rightArmVertices); + QVector3D rightHandBoneStopPosition; + if (isRightArmVerticalAligned) { + QVector3D minY = findMinY(rightArmVertices); + rightHandBoneStopPosition = QVector3D(rightHandBoneStartPosition.x(), + minY.y(), + minY.z()); + } else { + QVector3D minX = findMinX(rightArmVertices); + rightHandBoneStopPosition = QVector3D(minX.x(), + minX.y(), + rightHandBoneStartPosition.z()); + } + addTrianglesToVertices(m_marks[rightShoulderIndicies->second[0]].markTriangles, rightArmVertices); + + std::set leftLegVertices; + QVector3D leftFootBoneStopPosition; + addTrianglesToVertices(m_marks[leftHipIndicies->second[0]].smallGroup(), leftLegVertices); + { + QVector3D maxZ = findMaxZ(leftLegVertices); + leftFootBoneStopPosition = QVector3D(leftFootBoneStartPosition.x(), + maxZ.y(), + maxZ.z()); + } + addTrianglesToVertices(m_marks[leftHipIndicies->second[0]].markTriangles, leftLegVertices); + + std::set rightLegVertices; + QVector3D rightFootBoneStopPosition; + addTrianglesToVertices(m_marks[rightHipIndicies->second[0]].smallGroup(), rightLegVertices); + { + QVector3D maxZ = findMaxZ(rightLegVertices); + rightFootBoneStopPosition = QVector3D(rightFootBoneStartPosition.x(), + maxZ.y(), + maxZ.z()); + } + addTrianglesToVertices(m_marks[rightHipIndicies->second[0]].markTriangles, rightLegVertices); + + // 2. Collect vertices for each bone: + + // 2.1 Collect vertices for neck bone: + std::set bodyVertices; + addTrianglesToVertices(bodyTriangles, bodyVertices); + std::set bodyVerticesAfterShoulder; + std::set neckVertices; + { + std::set shoulderMarkVertices; + addTrianglesToVertices(m_marks[leftShoulderIndicies->second[0]].markTriangles, shoulderMarkVertices); + addTrianglesToVertices(m_marks[rightShoulderIndicies->second[0]].markTriangles, shoulderMarkVertices); + if (isMainBodyVerticalAligned) { + QVector3D maxY = findMaxY(shoulderMarkVertices); + splitVerticesByY(bodyVertices, maxY.y(), neckVertices, bodyVerticesAfterShoulder); + } else { + QVector3D maxZ = findMaxZ(shoulderMarkVertices); + splitVerticesByZ(bodyVertices, maxZ.z(), neckVertices, bodyVerticesAfterShoulder); + } + } + addTrianglesToVertices(m_marks[neckIndicies->second[0]].markTriangles, neckVertices); + + // 2.2 Collect vertices for chest bone: + + // Calculate neck's radius + float neckRadius = 0; + std::set neckMarkVertices; + addTrianglesToVertices(m_marks[neckIndicies->second[0]].markTriangles, neckMarkVertices); + { + QVector3D minX, minY, minZ, maxX, maxY, maxZ; + resolveBoundingBox(neckMarkVertices, minX, maxX, minY, maxY, minZ, maxZ); + neckRadius = fabs(minX.x() - maxX.x()); + } + + std::set bodyVerticesAfterChest; + std::set chestVertices; + if (isMainBodyVerticalAligned) { + splitVerticesByY(bodyVerticesAfterShoulder, chestBoneStartPosition.y() - neckRadius, chestVertices, bodyVerticesAfterChest); + } else { + splitVerticesByZ(bodyVerticesAfterShoulder, chestBoneStartPosition.z() - neckRadius, chestVertices, bodyVerticesAfterChest); + } + + // 2.3 Collect vertices for spine bone: + std::set bodyVerticesBeforeSpine; + std::set spineVertices; + if (isMainBodyVerticalAligned) { + splitVerticesByY(bodyVerticesAfterShoulder, chestBoneStartPosition.y() + neckRadius, bodyVerticesBeforeSpine, spineVertices); + } else { + splitVerticesByZ(bodyVerticesAfterShoulder, chestBoneStartPosition.z() + neckRadius, bodyVerticesBeforeSpine, spineVertices); + } + + // 3. Collect vertices for arms: + + // 3.1.1 Collect vertices for left upper arm: + std::set leftElbowMarkVertices; + addTrianglesToVertices(m_marks[leftElbowIndicies->second[0]].markTriangles, leftElbowMarkVertices); + std::set leftUpperArmVertices; + { + std::set leftArmVerticesAfterElbow; + if (isLeftArmVerticalAligned) { + QVector3D minY = findMinY(leftElbowMarkVertices); + splitVerticesByY(leftArmVertices, minY.y(), leftUpperArmVertices, leftArmVerticesAfterElbow); + } else { + QVector3D maxX = findMaxX(leftElbowMarkVertices); + splitVerticesByX(leftArmVertices, maxX.x(), leftArmVerticesAfterElbow, leftUpperArmVertices); + } + } + + // 3.1.2 Collect vertices for left lower arm: + std::set leftArmVerticesSinceElbow; + std::set leftLowerArmVertices; + { + std::set leftArmVerticesBeforeElbow; + if (isLeftArmVerticalAligned) { + QVector3D maxY = findMaxY(leftElbowMarkVertices); + splitVerticesByY(leftArmVertices, maxY.y(), leftArmVerticesBeforeElbow, leftArmVerticesSinceElbow); + } else { + QVector3D minX = findMinX(leftElbowMarkVertices); + splitVerticesByX(leftArmVertices, minX.x(), leftArmVerticesSinceElbow, leftArmVerticesBeforeElbow); + } + } + std::set leftWristMarkVertices; + addTrianglesToVertices(m_marks[leftWristIndicies->second[0]].markTriangles, leftWristMarkVertices); + { + std::set leftArmVerticesAfterWrist; + if (isLeftArmVerticalAligned) { + QVector3D minY = findMinY(leftWristMarkVertices); + splitVerticesByY(leftArmVerticesSinceElbow, minY.y(), leftLowerArmVertices, leftArmVerticesAfterWrist); + } else { + QVector3D maxX = findMaxX(leftWristMarkVertices); + splitVerticesByX(leftArmVerticesSinceElbow, maxX.x(), leftArmVerticesAfterWrist, leftLowerArmVertices); + } + } + + // 3.1.3 Collect vertices for left hand: + std::set leftHandVertices; + { + std::set leftArmVerticesBeforeWrist; + if (isLeftArmVerticalAligned) { + QVector3D maxY = findMaxY(leftWristMarkVertices); + splitVerticesByY(leftArmVerticesSinceElbow, maxY.y(), leftArmVerticesBeforeWrist, leftHandVertices); + } else { + QVector3D minX = findMinX(leftWristMarkVertices); + splitVerticesByX(leftArmVerticesSinceElbow, minX.x(), leftHandVertices, leftArmVerticesBeforeWrist); + } + } + + // 3.2.1 Collect vertices for right upper arm: + std::set rightElbowMarkVertices; + addTrianglesToVertices(m_marks[rightElbowIndicies->second[0]].markTriangles, rightElbowMarkVertices); + std::set rightUpperArmVertices; + { + std::set rightArmVerticesAfterElbow; + if (isRightArmVerticalAligned) { + QVector3D minY = findMinY(rightElbowMarkVertices); + splitVerticesByY(rightArmVertices, minY.y(), rightUpperArmVertices, rightArmVerticesAfterElbow); + } else { + QVector3D minX = findMinX(rightElbowMarkVertices); + splitVerticesByX(rightArmVertices, minX.x(), rightUpperArmVertices, rightArmVerticesAfterElbow); + } + } + + // 3.2.2 Collect vertices for right lower arm: + std::set rightArmVerticesSinceElbow; + std::set rightLowerArmVertices; + { + std::set rightArmVerticesBeforeElbow; + if (isRightArmVerticalAligned) { + QVector3D maxY = findMaxY(rightElbowMarkVertices); + splitVerticesByY(rightArmVertices, maxY.y(), rightArmVerticesBeforeElbow, rightArmVerticesSinceElbow); + } else { + QVector3D maxX = findMaxX(rightElbowMarkVertices); + splitVerticesByX(rightArmVertices, maxX.x(), rightArmVerticesBeforeElbow, rightArmVerticesSinceElbow); + } + } + std::set rightWristMarkVertices; + addTrianglesToVertices(m_marks[rightWristIndicies->second[0]].markTriangles, rightWristMarkVertices); + { + std::set rightArmVerticesAfterWrist; + if (isRightArmVerticalAligned) { + QVector3D minY = findMinY(rightWristMarkVertices); + splitVerticesByY(rightArmVerticesSinceElbow, minY.y(), rightLowerArmVertices, rightArmVerticesAfterWrist); + } else { + QVector3D minX = findMinX(rightWristMarkVertices); + splitVerticesByX(rightArmVerticesSinceElbow, minX.x(), rightLowerArmVertices, rightArmVerticesAfterWrist); + } + } + + // 3.2.3 Collect vertices for right hand: + std::set rightHandVertices; + { + std::set rightArmVerticesBeforeWrist; + if (isRightArmVerticalAligned) { + QVector3D maxY = findMaxY(rightWristMarkVertices); + splitVerticesByY(rightArmVerticesSinceElbow, maxY.y(), rightArmVerticesBeforeWrist, rightHandVertices); + } else { + QVector3D maxX = findMaxX(rightWristMarkVertices); + splitVerticesByX(rightArmVerticesSinceElbow, maxX.x(), rightArmVerticesBeforeWrist, rightHandVertices); + } + } + + // 4. Collect vertices for legs: + + // 4.1.1 Collect vertices for left upper leg: + std::set leftKneeMarkVertices; + addTrianglesToVertices(m_marks[leftKneeIndicies->second[0]].markTriangles, leftKneeMarkVertices); + std::set leftUpperLegVertices; + { + std::set leftLegVerticesAfterKnee; + QVector3D minY = findMinY(leftKneeMarkVertices); + splitVerticesByY(leftLegVertices, minY.y(), leftUpperLegVertices, leftLegVerticesAfterKnee); + } + + // 4.1.2 Collect vertices for left lower leg: + std::set leftLegVerticesSinceKnee; + std::set leftLowerLegVertices; + { + std::set leftLegVerticesBeforeKnee; + QVector3D maxY = findMaxY(leftKneeMarkVertices); + splitVerticesByY(leftLegVertices, maxY.y(), leftLegVerticesBeforeKnee, leftLegVerticesSinceKnee); + } + std::set leftAnkleMarkVertices; + addTrianglesToVertices(m_marks[leftAnkleIndicies->second[0]].markTriangles, leftAnkleMarkVertices); + { + std::set leftLegVerticesAfterAnkle; + QVector3D minY = findMinY(leftAnkleMarkVertices); + splitVerticesByY(leftLegVerticesSinceKnee, minY.y(), leftLowerLegVertices, leftLegVerticesAfterAnkle); + } + + // 4.1.3 Collect vertices for left foot: + std::set leftFootVertices; + { + std::set leftLegVerticesBeforeAnkle; + QVector3D maxY = findMaxY(leftAnkleMarkVertices); + splitVerticesByY(leftLegVerticesSinceKnee, maxY.y(), leftLegVerticesBeforeAnkle, leftFootVertices); + } + + // 4.2.1 Collect vertices for right upper leg: + std::set rightKneeMarkVertices; + addTrianglesToVertices(m_marks[rightKneeIndicies->second[0]].markTriangles, rightKneeMarkVertices); + std::set rightUpperLegVertices; + { + std::set rightLegVerticesAfterKnee; + QVector3D minY = findMinY(rightKneeMarkVertices); + splitVerticesByY(rightLegVertices, minY.y(), rightUpperLegVertices, rightLegVerticesAfterKnee); + } + + // 4.2.2 Collect vertices for right lower leg: + std::set rightLegVerticesSinceKnee; + std::set rightLowerLegVertices; + { + std::set rightLegVerticesBeforeKnee; + QVector3D maxY = findMaxY(rightKneeMarkVertices); + splitVerticesByY(rightLegVertices, maxY.y(), rightLegVerticesBeforeKnee, rightLegVerticesSinceKnee); + } + std::set rightAnkleMarkVertices; + addTrianglesToVertices(m_marks[rightAnkleIndicies->second[0]].markTriangles, rightAnkleMarkVertices); + { + std::set rightLegVerticesAfterAnkle; + QVector3D minY = findMinY(rightAnkleMarkVertices); + splitVerticesByY(rightLegVerticesSinceKnee, minY.y(), rightLowerLegVertices, rightLegVerticesAfterAnkle); + } + + // 4.2.3 Collect vertices for right foot: + std::set rightFootVertices; + { + std::set rightLegVerticesBeforeAnkle; + QVector3D maxY = findMaxY(rightAnkleMarkVertices); + splitVerticesByY(rightLegVerticesSinceKnee, maxY.y(), rightLegVerticesBeforeAnkle, rightFootVertices); + } + + // 5. Generate bones + std::map boneIndexMap; + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &bodyBone = m_resultBones.back(); + bodyBone.index = m_resultBones.size() - 1; + bodyBone.name = "Body"; + bodyBone.headPosition = QVector3D(0, 0, 0); + bodyBone.tailPosition = bonesOrigin; + boneIndexMap[bodyBone.name] = bodyBone.index; + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &leftHipBone = m_resultBones.back(); + leftHipBone.index = m_resultBones.size() - 1; + leftHipBone.name = "LeftHip"; + leftHipBone.headPosition = m_resultBones[boneIndexMap["Body"]].tailPosition; + leftHipBone.tailPosition = leftUpperLegBoneStartPosition; + boneIndexMap[leftHipBone.name] = leftHipBone.index; + m_resultBones[boneIndexMap["Body"]].children.push_back(leftHipBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &leftUpperLegBone = m_resultBones.back(); + leftUpperLegBone.index = m_resultBones.size() - 1; + leftUpperLegBone.name = "LeftUpperLeg"; + leftUpperLegBone.headPosition = m_resultBones[boneIndexMap["LeftHip"]].tailPosition; + leftUpperLegBone.tailPosition = leftLowerLegBoneStartPosition; + leftUpperLegBone.color = SkeletonBoneMarkToColor(SkeletonBoneMark::Hip); + boneIndexMap[leftUpperLegBone.name] = leftUpperLegBone.index; + m_resultBones[boneIndexMap["LeftHip"]].children.push_back(leftUpperLegBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &leftLowerLegBone = m_resultBones.back(); + leftLowerLegBone.index = m_resultBones.size() - 1; + leftLowerLegBone.name = "LeftLowerLeg"; + leftLowerLegBone.headPosition = m_resultBones[boneIndexMap["LeftUpperLeg"]].tailPosition; + leftLowerLegBone.tailPosition = leftFootBoneStartPosition; + leftLowerLegBone.color = SkeletonBoneMarkToColor(SkeletonBoneMark::Knee); + boneIndexMap[leftLowerLegBone.name] = leftLowerLegBone.index; + m_resultBones[boneIndexMap["LeftUpperLeg"]].children.push_back(leftLowerLegBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &leftFootBone = m_resultBones.back(); + leftFootBone.index = m_resultBones.size() - 1; + leftFootBone.name = "LeftFoot"; + leftFootBone.headPosition = m_resultBones[boneIndexMap["LeftLowerLeg"]].tailPosition; + leftFootBone.tailPosition = leftFootBoneStopPosition; + leftFootBone.color = SkeletonBoneMarkToColor(SkeletonBoneMark::Ankle); + boneIndexMap[leftFootBone.name] = leftFootBone.index; + m_resultBones[boneIndexMap["LeftLowerLeg"]].children.push_back(leftFootBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &rightHipBone = m_resultBones.back(); + rightHipBone.index = m_resultBones.size() - 1; + rightHipBone.name = "RightHip"; + rightHipBone.headPosition = m_resultBones[boneIndexMap["Body"]].tailPosition; + rightHipBone.tailPosition = rightUpperLegBoneStartPosition; + boneIndexMap[rightHipBone.name] = rightHipBone.index; + m_resultBones[boneIndexMap["Body"]].children.push_back(rightHipBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &rightUpperLegBone = m_resultBones.back(); + rightUpperLegBone.index = m_resultBones.size() - 1; + rightUpperLegBone.name = "RightUpperLeg"; + rightUpperLegBone.headPosition = m_resultBones[boneIndexMap["RightHip"]].tailPosition; + rightUpperLegBone.tailPosition = rightLowerLegBoneStartPosition; + rightUpperLegBone.color = SkeletonBoneMarkToColor(SkeletonBoneMark::Hip); + boneIndexMap[rightUpperLegBone.name] = rightUpperLegBone.index; + m_resultBones[boneIndexMap["RightHip"]].children.push_back(rightUpperLegBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &rightLowerLegBone = m_resultBones.back(); + rightLowerLegBone.index = m_resultBones.size() - 1; + rightLowerLegBone.name = "RightLowerLeg"; + rightLowerLegBone.headPosition = m_resultBones[boneIndexMap["RightUpperLeg"]].tailPosition; + rightLowerLegBone.tailPosition = rightFootBoneStartPosition; + rightLowerLegBone.color = SkeletonBoneMarkToColor(SkeletonBoneMark::Knee); + boneIndexMap[rightLowerLegBone.name] = rightLowerLegBone.index; + m_resultBones[boneIndexMap["RightUpperLeg"]].children.push_back(rightLowerLegBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &rightFootBone = m_resultBones.back(); + rightFootBone.index = m_resultBones.size() - 1; + rightFootBone.name = "RightFoot"; + rightFootBone.headPosition = m_resultBones[boneIndexMap["RightLowerLeg"]].tailPosition; + rightFootBone.tailPosition = rightFootBoneStopPosition; + rightFootBone.color = SkeletonBoneMarkToColor(SkeletonBoneMark::Ankle); + boneIndexMap[rightFootBone.name] = rightFootBone.index; + m_resultBones[boneIndexMap["RightLowerLeg"]].children.push_back(rightFootBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &spineBone = m_resultBones.back(); + spineBone.index = m_resultBones.size() - 1; + spineBone.name = "Spine"; + spineBone.headPosition = m_resultBones[boneIndexMap["Body"]].tailPosition; + spineBone.tailPosition = chestBoneStartPosition; + spineBone.color = Qt::white; + boneIndexMap[spineBone.name] = spineBone.index; + m_resultBones[boneIndexMap["Body"]].children.push_back(spineBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &chestBone = m_resultBones.back(); + chestBone.index = m_resultBones.size() - 1; + chestBone.name = "Chest"; + chestBone.headPosition = m_resultBones[boneIndexMap["Spine"]].tailPosition; + chestBone.tailPosition = neckBoneStartPosition; + chestBone.color = QColor(0x57, 0x43, 0x98); + boneIndexMap[chestBone.name] = chestBone.index; + m_resultBones[boneIndexMap["Spine"]].children.push_back(chestBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &leftShoulderBone = m_resultBones.back(); + leftShoulderBone.index = m_resultBones.size() - 1; + leftShoulderBone.name = "LeftShoulder"; + leftShoulderBone.headPosition = m_resultBones[boneIndexMap["Chest"]].tailPosition; + leftShoulderBone.tailPosition = leftUpperArmBoneStartPosition; + boneIndexMap[leftShoulderBone.name] = leftShoulderBone.index; + m_resultBones[boneIndexMap["Chest"]].children.push_back(leftShoulderBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &leftUpperArmBone = m_resultBones.back(); + leftUpperArmBone.index = m_resultBones.size() - 1; + leftUpperArmBone.name = "LeftUpperArm"; + leftUpperArmBone.headPosition = m_resultBones[boneIndexMap["LeftShoulder"]].tailPosition; + leftUpperArmBone.tailPosition = leftLowerArmBoneStartPosition; + leftUpperArmBone.color = SkeletonBoneMarkToColor(SkeletonBoneMark::Shoulder); + boneIndexMap[leftUpperArmBone.name] = leftUpperArmBone.index; + m_resultBones[boneIndexMap["LeftShoulder"]].children.push_back(leftUpperArmBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &leftLowerArmBone = m_resultBones.back(); + leftLowerArmBone.index = m_resultBones.size() - 1; + leftLowerArmBone.name = "LeftLowerArm"; + leftLowerArmBone.headPosition = m_resultBones[boneIndexMap["LeftUpperArm"]].tailPosition; + leftLowerArmBone.tailPosition = leftHandBoneStartPosition; + leftLowerArmBone.color = SkeletonBoneMarkToColor(SkeletonBoneMark::Elbow); + boneIndexMap[leftLowerArmBone.name] = leftLowerArmBone.index; + m_resultBones[boneIndexMap["LeftUpperArm"]].children.push_back(leftLowerArmBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &leftHandBone = m_resultBones.back(); + leftHandBone.index = m_resultBones.size() - 1; + leftHandBone.name = "LeftHand"; + leftHandBone.headPosition = m_resultBones[boneIndexMap["LeftLowerArm"]].tailPosition; + leftHandBone.tailPosition = leftHandBoneStopPosition; + leftHandBone.color = SkeletonBoneMarkToColor(SkeletonBoneMark::Wrist); + boneIndexMap[leftHandBone.name] = leftHandBone.index; + m_resultBones[boneIndexMap["LeftLowerArm"]].children.push_back(leftHandBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &rightShoulderBone = m_resultBones.back(); + rightShoulderBone.index = m_resultBones.size() - 1; + rightShoulderBone.name = "RightShoulder"; + rightShoulderBone.headPosition = m_resultBones[boneIndexMap["Chest"]].tailPosition; + rightShoulderBone.tailPosition = rightUpperArmBoneStartPosition; + boneIndexMap[rightShoulderBone.name] = rightShoulderBone.index; + m_resultBones[boneIndexMap["Chest"]].children.push_back(rightShoulderBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &rightUpperArmBone = m_resultBones.back(); + rightUpperArmBone.index = m_resultBones.size() - 1; + rightUpperArmBone.name = "RightUpperArm"; + rightUpperArmBone.headPosition = m_resultBones[boneIndexMap["RightShoulder"]].tailPosition; + rightUpperArmBone.tailPosition = rightLowerArmBoneStartPosition; + rightUpperArmBone.color = SkeletonBoneMarkToColor(SkeletonBoneMark::Shoulder); + boneIndexMap[rightUpperArmBone.name] = rightUpperArmBone.index; + m_resultBones[boneIndexMap["RightShoulder"]].children.push_back(rightUpperArmBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &rightLowerArmBone = m_resultBones.back(); + rightLowerArmBone.index = m_resultBones.size() - 1; + rightLowerArmBone.name = "RightLowerArm"; + rightLowerArmBone.headPosition = m_resultBones[boneIndexMap["RightUpperArm"]].tailPosition; + rightLowerArmBone.tailPosition = rightHandBoneStartPosition; + rightLowerArmBone.color = SkeletonBoneMarkToColor(SkeletonBoneMark::Elbow); + boneIndexMap[rightLowerArmBone.name] = rightLowerArmBone.index; + m_resultBones[boneIndexMap["RightUpperArm"]].children.push_back(rightLowerArmBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &rightHandBone = m_resultBones.back(); + rightHandBone.index = m_resultBones.size() - 1; + rightHandBone.name = "RightHand"; + rightHandBone.headPosition = m_resultBones[boneIndexMap["RightLowerArm"]].tailPosition; + rightHandBone.tailPosition = rightHandBoneStopPosition; + rightHandBone.color = SkeletonBoneMarkToColor(SkeletonBoneMark::Wrist); + boneIndexMap[rightHandBone.name] = rightHandBone.index; + m_resultBones[boneIndexMap["RightLowerArm"]].children.push_back(rightHandBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &neckBone = m_resultBones.back(); + neckBone.index = m_resultBones.size() - 1; + neckBone.name = "Neck"; + neckBone.headPosition = m_resultBones[boneIndexMap["Chest"]].tailPosition; + neckBone.tailPosition = headBoneStartPosition; + neckBone.color = SkeletonBoneMarkToColor(SkeletonBoneMark::Neck); + boneIndexMap[neckBone.name] = neckBone.index; + m_resultBones[boneIndexMap["Chest"]].children.push_back(neckBone.index); + + m_resultBones.push_back(AutoRiggerBone()); + AutoRiggerBone &headBone = m_resultBones.back(); + headBone.index = m_resultBones.size() - 1; + headBone.name = "Head"; + headBone.headPosition = m_resultBones[boneIndexMap["Neck"]].tailPosition; + headBone.tailPosition = headBoneStopPosition; + headBone.color = QColor(0xfb, 0xef, 0x8b); + boneIndexMap[headBone.name] = headBone.index; + m_resultBones[boneIndexMap["Neck"]].children.push_back(headBone.index); + + // 6. Calculate weights for vertices + addVerticesToWeights(headVertices, boneIndexMap["Head"]); + addVerticesToWeights(neckVertices, boneIndexMap["Neck"]); + addVerticesToWeights(chestVertices, boneIndexMap["Chest"]); + addVerticesToWeights(spineVertices, boneIndexMap["Spine"]); + addVerticesToWeights(leftUpperArmVertices, boneIndexMap["LeftUpperArm"]); + addVerticesToWeights(leftLowerArmVertices, boneIndexMap["LeftLowerArm"]); + addVerticesToWeights(leftHandVertices, boneIndexMap["LeftHand"]); + addVerticesToWeights(rightUpperArmVertices, boneIndexMap["RightUpperArm"]); + addVerticesToWeights(rightLowerArmVertices, boneIndexMap["RightLowerArm"]); + addVerticesToWeights(rightHandVertices, boneIndexMap["RightHand"]); + addVerticesToWeights(leftUpperLegVertices, boneIndexMap["LeftUpperLeg"]); + addVerticesToWeights(leftLowerLegVertices, boneIndexMap["LeftLowerLeg"]); + addVerticesToWeights(leftFootVertices, boneIndexMap["LeftFoot"]); + addVerticesToWeights(rightUpperLegVertices, boneIndexMap["RightUpperLeg"]); + addVerticesToWeights(rightLowerLegVertices, boneIndexMap["RightLowerLeg"]); + addVerticesToWeights(rightFootVertices, boneIndexMap["RightFoot"]); + + for (auto &weights: m_resultWeights) { + weights.second.finalizeWeights(); + } + + return true; +} + +const std::vector &AutoRigger::missingMarkNames() +{ + return m_missingMarkNames; +} + +const std::vector &AutoRigger::errorMarkNames() +{ + return m_errorMarkNames; +} diff --git a/src/autorigger.h b/src/autorigger.h new file mode 100644 index 00000000..1b9991fc --- /dev/null +++ b/src/autorigger.h @@ -0,0 +1,126 @@ +#ifndef AUTO_RIGGER_H +#define AUTO_RIGGER_H +#include +#include +#include +#include +#include "meshsplitter.h" +#include "skeletonbonemark.h" +#include "rigtype.h" + +class AutoRiggerMark +{ +public: + SkeletonBoneMark boneMark; + SkeletonSide boneSide; + QVector3D bonePosition; + std::set markTriangles; + const std::set &bigGroup() const + { + return m_firstGroup.size() > m_secondGroup.size() ? + m_firstGroup : + m_secondGroup; + } + const std::set &smallGroup() const + { + return m_firstGroup.size() > m_secondGroup.size() ? + m_secondGroup : + m_firstGroup; + } + bool split(const std::set &input) + { + return MeshSplitter::split(input, markTriangles, m_firstGroup, m_secondGroup); + } +private: + std::set m_firstGroup; + std::set m_secondGroup; +}; + +class AutoRiggerBone +{ +public: + QString name; + int index; + QVector3D headPosition; + QVector3D tailPosition; + QColor color; + std::vector children; +}; + +class AutoRiggerVertexWeights +{ +public: + int boneIndicies[4] = {0, 0, 0, 0}; + float boneWeights[4] = {0, 0, 0, 0}; + void addBone(int boneIndex, float distance) + { + if (qFuzzyIsNull(distance)) + distance = 0.01; + m_boneRawWeights.push_back(std::make_pair(boneIndex, 1.0 / distance)); + } + void finalizeWeights() + { + std::sort(m_boneRawWeights.begin(), m_boneRawWeights.end(), + [](const std::pair &a, const std::pair &b) { + return a.second > b.second; + }); + float totalDistance = 0; + for (size_t i = 0; i < m_boneRawWeights.size() && i < 4; i++) { + const auto &item = m_boneRawWeights[i]; + totalDistance += item.second; + } + if (totalDistance > 0) { + for (size_t i = 0; i < m_boneRawWeights.size() && i < 4; i++) { + const auto &item = m_boneRawWeights[i]; + boneIndicies[i] = item.first; + boneWeights[i] = item.second / totalDistance; + } + } else { + qDebug() << "totalDistance:" << totalDistance; + } + } +private: + std::vector> m_boneRawWeights; +}; + +class AutoRigger : public QObject +{ +public: + AutoRigger(const std::vector &verticesPositions, + const std::set &inputTriangles); + bool addMarkGroup(SkeletonBoneMark boneMark, SkeletonSide boneSide, QVector3D bonePosition, + const std::set &markTriangles); + const std::vector> &messages(); + bool rig(); + const std::vector &resultBones(); + const std::map &resultWeights(); + const std::vector &missingMarkNames(); + const std::vector &errorMarkNames(); +private: + bool validate(); + void addTrianglesToVertices(const std::set &triangles, std::set &vertices); + bool calculateBodyTriangles(std::set &bodyTriangles); + bool isCutOffSplitter(SkeletonBoneMark boneMark); + void resolveBoundingBox(const std::set &vertices, QVector3D &xMin, QVector3D &xMax, QVector3D &yMin, QVector3D &yMax, QVector3D &zMin, QVector3D &zMax); + QVector3D findMinX(const std::set &vertices); + QVector3D findMaxX(const std::set &vertices); + QVector3D findMinY(const std::set &vertices); + QVector3D findMaxY(const std::set &vertices); + QVector3D findMinZ(const std::set &vertices); + QVector3D findMaxZ(const std::set &vertices); + void splitVerticesByY(const std::set &vertices, float y, std::set &greaterEqualThanVertices, std::set &lessThanVertices); + void splitVerticesByX(const std::set &vertices, float x, std::set &greaterEqualThanVertices, std::set &lessThanVertices); + void splitVerticesByZ(const std::set &vertices, float z, std::set &greaterEqualThanVertices, std::set &lessThanVertices); + void addVerticesToWeights(const std::set &vertices, int boneIndex); + std::vector> m_messages; + std::vector m_verticesPositions; + std::set m_inputTriangles; + std::vector m_marks; + std::map, std::vector> m_marksMap; + std::vector m_resultBones; + std::map m_resultWeights; + std::vector m_missingMarkNames; + std::vector m_errorMarkNames; +}; + +#endif diff --git a/src/exportpreviewwidget.cpp b/src/exportpreviewwidget.cpp index cb5827de..8bf369d8 100644 --- a/src/exportpreviewwidget.cpp +++ b/src/exportpreviewwidget.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "exportpreviewwidget.h" #include "aboutwidget.h" #include "version.h" @@ -13,27 +14,34 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa m_previewLabel(nullptr), m_spinnerWidget(nullptr) { - QVBoxLayout *toolButtonLayout = new QVBoxLayout; + QHBoxLayout *toolButtonLayout = new QHBoxLayout; toolButtonLayout->setSpacing(0); - toolButtonLayout->setContentsMargins(5, 10, 4, 0); + //toolButtonLayout->setContentsMargins(5, 10, 4, 0); m_previewLabel = new QLabel; m_previewLabel->setMinimumSize(128, 128); m_previewLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - QPushButton *regenerateButton = new QPushButton(QChar(fa::recycle)); - initAwesomeButton(regenerateButton); + //QPushButton *regenerateButton = new QPushButton(QChar(fa::recycle)); + //initAwesomeButton(regenerateButton); + QPushButton *regenerateButton = new QPushButton(tr("Regenerate")); connect(this, &ExportPreviewWidget::regenerate, this, &ExportPreviewWidget::checkSpinner); connect(regenerateButton, &QPushButton::clicked, this, &ExportPreviewWidget::regenerate); - m_saveButton = new QPushButton(QChar(fa::save)); - initAwesomeButton(m_saveButton); + //m_saveButton = new QPushButton(QChar(fa::save)); + //initAwesomeButton(m_saveButton); + m_saveButton = new QPushButton(tr("Save")); connect(m_saveButton, &QPushButton::clicked, this, &ExportPreviewWidget::save); m_saveButton->hide(); + QComboBox *exportFormatSelectBox = new QComboBox; + exportFormatSelectBox->addItem(tr("glTF")); + exportFormatSelectBox->setCurrentIndex(0); + + toolButtonLayout->addWidget(exportFormatSelectBox); toolButtonLayout->addWidget(regenerateButton); - toolButtonLayout->addWidget(m_saveButton); toolButtonLayout->addStretch(); + toolButtonLayout->addWidget(m_saveButton); QGridLayout *containerLayout = new QGridLayout; containerLayout->setSpacing(0); @@ -51,12 +59,22 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa renderLayout->setContentsMargins(20, 0, 20, 0); renderLayout->addWidget(m_textureRenderWidget); - QHBoxLayout *mainLayout = new QHBoxLayout; - mainLayout->setSpacing(0); - mainLayout->setContentsMargins(0, 0, 0, 0); + QWidget *hrLightWidget = new QWidget; + hrLightWidget->setFixedHeight(1); + hrLightWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + hrLightWidget->setStyleSheet(QString("background-color: #565656;")); + hrLightWidget->setContentsMargins(0, 0, 0, 0); + + QHBoxLayout *topLayout = new QHBoxLayout; + topLayout->setSpacing(0); + topLayout->setContentsMargins(0, 0, 0, 0); + topLayout->addLayout(containerLayout); + topLayout->addLayout(renderLayout); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(topLayout); + mainLayout->addWidget(hrLightWidget); mainLayout->addLayout(toolButtonLayout); - mainLayout->addLayout(containerLayout); - mainLayout->addLayout(renderLayout); setLayout(mainLayout); setMinimumSize(256, 256); @@ -68,7 +86,7 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa m_spinnerWidget->setNumberOfLines(12); m_spinnerWidget->hide(); - setWindowTitle(APP_NAME); + setWindowTitle(tr("Export") + tr(" - ") + APP_NAME); emit updateTexturePreview(); } diff --git a/src/gltffile.cpp b/src/gltffile.cpp index 6397d82a..55d94e4a 100644 --- a/src/gltffile.cpp +++ b/src/gltffile.cpp @@ -17,9 +17,12 @@ // http://quaternions.online/ // https://en.m.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions?wprov=sfla1 -bool GLTFFileWriter::m_enableComment = false; +bool GltfFileWriter::m_enableComment = true; -GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &filename) : +GltfFileWriter::GltfFileWriter(MeshResultContext &resultContext, + const std::vector *resultRigBones, + const std::map *resultRigWeights, + const QString &filename) : m_filename(filename), m_outputNormal(true), m_outputAnimation(true), @@ -30,12 +33,6 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & QString textureFilenameWithoutPath = nameInfo.completeBaseName() + ".png"; m_textureFilename = nameInfo.path() + QDir::separator() + textureFilenameWithoutPath; - m_json["asset"]["version"] = "2.0"; - m_json["asset"]["generator"] = APP_NAME " " APP_HUMAN_VER; - m_json["scenes"][0]["nodes"] = {0}; - - m_json["nodes"][0]["mesh"] = 0; - QByteArray binaries; QDataStream stream(&binaries, QIODevice::WriteOnly); stream.setFloatingPointPrecision(QDataStream::SinglePrecision); @@ -50,6 +47,75 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & int bufferViewIndex = 0; int bufferViewFromOffset; + m_json["asset"]["version"] = "2.0"; + m_json["asset"]["generator"] = APP_NAME " " APP_HUMAN_VER; + m_json["scenes"][0]["nodes"] = {0}; + + if (resultRigBones && resultRigWeights && !resultRigBones->empty()) { + + calculateMatrices(resultRigBones); + + constexpr int skeletonNodeStartIndex = 2; + + m_json["nodes"][0]["children"] = { + 1, + skeletonNodeStartIndex + }; + + m_json["nodes"][1]["mesh"] = 0; + m_json["nodes"][1]["skin"] = 0; + + m_json["skins"][0]["joints"] = {}; + for (size_t i = 0; i < m_boneNodes.size(); i++) { + m_json["skins"][0]["joints"] += skeletonNodeStartIndex + i; + + m_json["nodes"][skeletonNodeStartIndex + i]["name"] = m_boneNodes[i].name.toUtf8().constData(); + m_json["nodes"][skeletonNodeStartIndex + i]["translation"] = { + m_boneNodes[i].translation.x(), + m_boneNodes[i].translation.y(), + m_boneNodes[i].translation.z() + }; + m_json["nodes"][skeletonNodeStartIndex + i]["rotation"] = { + m_boneNodes[i].rotation.x(), + m_boneNodes[i].rotation.y(), + m_boneNodes[i].rotation.z(), + m_boneNodes[i].rotation.scalar() + }; + + if (!m_boneNodes[i].children.empty()) { + m_json["nodes"][skeletonNodeStartIndex + i]["children"] = {}; + for (const auto &it: m_boneNodes[i].children) { + m_json["nodes"][skeletonNodeStartIndex + i]["children"] += skeletonNodeStartIndex + it; + } + } + } + + m_json["skins"][0]["skeleton"] = skeletonNodeStartIndex; + m_json["skins"][0]["inverseBindMatrices"] = bufferViewIndex; + bufferViewFromOffset = (int)binaries.size(); + m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; + m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; + for (auto i = 0u; i < m_boneNodes.size(); i++) { + const float *floatArray = m_boneNodes[i].inverseBindMatrix.constData(); + for (auto j = 0u; j < 16; j++) { + stream << (float)floatArray[j]; + } + } + m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset; + Q_ASSERT((int)m_boneNodes.size() * 16 * sizeof(float) == binaries.size() - bufferViewFromOffset); + alignBinaries(); + if (m_enableComment) + m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: mat").arg(QString::number(bufferViewIndex)).toUtf8().constData(); + m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex; + m_json["accessors"][bufferViewIndex]["byteOffset"] = 0; + m_json["accessors"][bufferViewIndex]["componentType"] = 5126; + m_json["accessors"][bufferViewIndex]["count"] = m_boneNodes.size(); + m_json["accessors"][bufferViewIndex]["type"] = "MAT4"; + bufferViewIndex++; + } else { + m_json["nodes"][0]["mesh"] = 0; + } + m_json["textures"][0]["sampler"] = 0; m_json["textures"][0]["source"] = 0; @@ -84,6 +150,10 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["NORMAL"] = bufferViewIndex + (++attributeIndex); if (m_outputUv) m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["TEXCOORD_0"] = bufferViewIndex + (++attributeIndex); + if (resultRigWeights && !resultRigWeights->empty()) { + m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["JOINTS_0"] = bufferViewIndex + (++attributeIndex); + m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["WEIGHTS_0"] = bufferViewIndex + (++attributeIndex); + } m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["baseColorTexture"]["index"] = 0; m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["metallicFactor"] = 0.0; m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["roughnessFactor"] = 1.0; @@ -190,18 +260,96 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString & m_json["accessors"][bufferViewIndex]["type"] = "VEC2"; bufferViewIndex++; } + + if (resultRigWeights && !resultRigWeights->empty()) { + bufferViewFromOffset = (int)binaries.size(); + m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; + m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; + QStringList boneList; + int weightItIndex = 0; + for (const auto &oldIndex: part.second.verticesOldIndicies) { + auto i = 0u; + if (m_enableComment) + boneList.append(QString("%1:<").arg(QString::number(weightItIndex))); + auto findWeight = resultRigWeights->find(oldIndex); + if (findWeight != resultRigWeights->end()) { + for (; i < MAX_WEIGHT_NUM; i++) { + quint16 nodeIndex = (quint16)findWeight->second.boneIndicies[i]; + stream << (quint16)nodeIndex; + if (m_enableComment) + boneList.append(QString("%1").arg(nodeIndex)); + } + } + for (; i < MAX_WEIGHT_NUM; i++) { + stream << (quint16)0; + if (m_enableComment) + boneList.append(QString("%1").arg(0)); + } + if (m_enableComment) + boneList.append(QString(">")); + weightItIndex++; + } + m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset; + alignBinaries(); + if (m_enableComment) + m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: bone indicies %2").arg(QString::number(bufferViewIndex)).arg(boneList.join(" ")).toUtf8().constData(); + m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex; + m_json["accessors"][bufferViewIndex]["byteOffset"] = 0; + m_json["accessors"][bufferViewIndex]["componentType"] = 5123; + m_json["accessors"][bufferViewIndex]["count"] = part.second.verticesOldIndicies.size(); + m_json["accessors"][bufferViewIndex]["type"] = "VEC4"; + bufferViewIndex++; + + bufferViewFromOffset = (int)binaries.size(); + m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; + m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; + QStringList weightList; + weightItIndex = 0; + for (const auto &oldIndex: part.second.verticesOldIndicies) { + auto i = 0u; + if (m_enableComment) + weightList.append(QString("%1:<").arg(QString::number(weightItIndex))); + auto findWeight = resultRigWeights->find(oldIndex); + if (findWeight != resultRigWeights->end()) { + for (; i < MAX_WEIGHT_NUM; i++) { + float weight = (quint16)findWeight->second.boneIndicies[i]; + stream << (float)weight; + if (m_enableComment) + weightList.append(QString("%1").arg(QString::number((float)weight))); + } + } + for (; i < MAX_WEIGHT_NUM; i++) { + stream << (float)0.0; + if (m_enableComment) + weightList.append(QString("%1").arg(QString::number(0.0))); + } + if (m_enableComment) + weightList.append(QString(">")); + weightItIndex++; + } + m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset; + alignBinaries(); + if (m_enableComment) + m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: bone weights %2").arg(QString::number(bufferViewIndex)).arg(weightList.join(" ")).toUtf8().constData(); + m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex; + m_json["accessors"][bufferViewIndex]["byteOffset"] = 0; + m_json["accessors"][bufferViewIndex]["componentType"] = 5126; + m_json["accessors"][bufferViewIndex]["count"] = part.second.verticesOldIndicies.size(); + m_json["accessors"][bufferViewIndex]["type"] = "VEC4"; + bufferViewIndex++; + } } m_json["buffers"][0]["uri"] = QString("data:application/octet-stream;base64," + binaries.toBase64()).toUtf8().constData(); m_json["buffers"][0]["byteLength"] = binaries.size(); } -const QString &GLTFFileWriter::textureFilenameInGltf() +const QString &GltfFileWriter::textureFilenameInGltf() { return m_textureFilename; } -bool GLTFFileWriter::save() +bool GltfFileWriter::save() { QFile file(m_filename); if (!file.open(QIODevice::WriteOnly)) { @@ -210,3 +358,37 @@ bool GLTFFileWriter::save() file.write(QString::fromStdString(m_json.dump(4)).toUtf8()); return true; } + +void GltfFileWriter::calculateMatrices(const std::vector *resultRigBones) +{ + if (nullptr == resultRigBones) + return; + + m_boneNodes.resize(resultRigBones->size()); + + m_boneNodes[0].parentIndex = -1; + for (decltype(resultRigBones->size()) i = 0; i < resultRigBones->size(); i++) { + const auto &bone = (*resultRigBones)[i]; + auto &node = m_boneNodes[i]; + node.name = bone.name; + node.position = bone.tailPosition; + node.children = bone.children; + for (const auto &childIndex: bone.children) + m_boneNodes[childIndex].parentIndex = i; + } + + for (decltype(resultRigBones->size()) i = 0; i < resultRigBones->size(); i++) { + const auto &bone = (*resultRigBones)[i]; + QMatrix4x4 parentBindMatrix; + auto &node = m_boneNodes[i]; + node.translation = bone.tailPosition - bone.headPosition; + if (node.parentIndex != -1) { + const auto &parent = m_boneNodes[node.parentIndex]; + parentBindMatrix = parent.bindMatrix; + } + QMatrix4x4 translateMatrix; + translateMatrix.translate(node.translation); + node.bindMatrix = parentBindMatrix * translateMatrix; + node.inverseBindMatrix = node.bindMatrix.inverted(); + } +} diff --git a/src/gltffile.h b/src/gltffile.h index 5ae88597..9286781a 100644 --- a/src/gltffile.h +++ b/src/gltffile.h @@ -5,26 +5,42 @@ #include #include #include +#include #include "meshresultcontext.h" #include "json.hpp" #include "skeletondocument.h" -class GLTFFileWriter : public QObject +struct GltfNode +{ + int parentIndex; + QString name; + QVector3D position; + QVector3D translation; + QQuaternion rotation; + QMatrix4x4 bindMatrix; + QMatrix4x4 inverseBindMatrix; + std::vector children; +}; + +class GltfFileWriter : public QObject { Q_OBJECT public: - GLTFFileWriter(MeshResultContext &resultContext, const QString &filename); + GltfFileWriter(MeshResultContext &resultContext, + const std::vector *resultRigBones, + const std::map *resultRigWeights, + const QString &filename); bool save(); const QString &textureFilenameInGltf(); private: - QByteArray m_data; - QString getMatrixStringInColumnOrder(const QMatrix4x4 &mat); + void calculateMatrices(const std::vector *resultRigBones); QString m_filename; QString m_textureFilename; bool m_outputNormal; bool m_outputAnimation; bool m_outputUv; bool m_testOutputAsWhole; + std::vector m_boneNodes; private: nlohmann::json m_json; public: diff --git a/src/infolabel.cpp b/src/infolabel.cpp index 1febd9b7..782b7c20 100644 --- a/src/infolabel.cpp +++ b/src/infolabel.cpp @@ -10,6 +10,7 @@ InfoLabel::InfoLabel(const QString &text, QWidget *parent) : Theme::initAwesomeLabel(m_icon); m_label = new QLabel(text); + m_label->setWordWrap(true); QHBoxLayout *mainLayout = new QHBoxLayout; mainLayout->addWidget(m_icon); diff --git a/src/logbrowserdialog.cpp b/src/logbrowserdialog.cpp index 221e2bc0..4559c502 100644 --- a/src/logbrowserdialog.cpp +++ b/src/logbrowserdialog.cpp @@ -11,6 +11,7 @@ #include #include #include +#include "version.h" LogBrowserDialog::LogBrowserDialog(QWidget *parent) : QDialog(parent) @@ -39,6 +40,8 @@ LogBrowserDialog::LogBrowserDialog(QWidget *parent) : resize(400, 300); + setWindowTitle(tr("Debug") + tr(" - ") + APP_NAME); + hide(); } diff --git a/src/markiconcreator.cpp b/src/markiconcreator.cpp new file mode 100644 index 00000000..6da92af4 --- /dev/null +++ b/src/markiconcreator.cpp @@ -0,0 +1,23 @@ +#include +#include +#include "markiconcreator.h" +#include "theme.h" + +std::map MarkIconCreator::m_iconMap; +int MarkIconCreator::m_iconSize = 40; + +QIcon MarkIconCreator::createIcon(SkeletonBoneMark boneMark) +{ + if (m_iconMap.find(boneMark) == m_iconMap.end()) { + QPixmap pixmap(MarkIconCreator::m_iconSize, MarkIconCreator::m_iconSize); + pixmap.fill(Qt::transparent); + QColor color = SkeletonBoneMarkToColor(boneMark); + QPainter painter(&pixmap); + painter.setBrush(QBrush(color)); + painter.setPen(Qt::NoPen); + painter.drawEllipse(0, 0, MarkIconCreator::m_iconSize, MarkIconCreator::m_iconSize); + QIcon icon(pixmap); + m_iconMap[boneMark] = icon; + } + return m_iconMap[boneMark]; +} diff --git a/src/markiconcreator.h b/src/markiconcreator.h new file mode 100644 index 00000000..b7921e18 --- /dev/null +++ b/src/markiconcreator.h @@ -0,0 +1,16 @@ +#ifndef MARK_ICON_CREATOR_H +#define MARK_ICON_CREATOR_H +#include +#include +#include "skeletondocument.h" + +class MarkIconCreator +{ +public: + static QIcon createIcon(SkeletonBoneMark boneMark); +private: + static std::map m_iconMap; + static int m_iconSize; +}; + +#endif diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index c8ca3ab6..1823aa9c 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -9,8 +9,10 @@ #include "meshutil.h" #include "theme.h" #include "positionmap.h" +#include "meshquadify.h" bool MeshGenerator::m_enableDebug = false; +PositionMap *MeshGenerator::m_forMakePositionKey = new PositionMap; GeneratedCacheContext::~GeneratedCacheContext() { @@ -107,7 +109,8 @@ MeshResultContext *MeshGenerator::takeMeshResultContext() return meshResultContext; } -void MeshGenerator::loadVertexSources(void *meshliteContext, int meshId, QUuid partId, const std::map &bmeshToNodeIdMap, std::vector &bmeshVertices) +void MeshGenerator::loadVertexSources(void *meshliteContext, int meshId, QUuid partId, const std::map &bmeshToNodeIdMap, std::vector &bmeshVertices, + std::vector> &bmeshQuads) { int vertexCount = meshlite_get_vertex_count(meshliteContext, meshId); int positionBufferLen = vertexCount * 3; @@ -116,6 +119,7 @@ void MeshGenerator::loadVertexSources(void *meshliteContext, int meshId, QUuid p int *sourceBuffer = new int[positionBufferLen]; int sourceCount = meshlite_get_vertex_source_array(meshliteContext, meshId, sourceBuffer, positionBufferLen); Q_ASSERT(positionCount == sourceCount); + std::vector verticesPositions; for (int i = 0, positionIndex = 0; i < positionCount; i++, positionIndex+=3) { BmeshVertex vertex; vertex.partId = partId; @@ -123,8 +127,34 @@ void MeshGenerator::loadVertexSources(void *meshliteContext, int meshId, QUuid p if (findNodeId != bmeshToNodeIdMap.end()) vertex.nodeId = findNodeId->second; vertex.position = QVector3D(positionBuffer[positionIndex + 0], positionBuffer[positionIndex + 1], positionBuffer[positionIndex + 2]); + verticesPositions.push_back(vertex.position); bmeshVertices.push_back(vertex); } + int faceCount = meshlite_get_face_count(meshliteContext, meshId); + int *faceVertexNumAndIndices = new int[faceCount * MAX_VERTICES_PER_FACE]; + int filledLength = meshlite_get_face_index_array(meshliteContext, meshId, faceVertexNumAndIndices, faceCount * MAX_VERTICES_PER_FACE); + int i = 0; + while (i < filledLength) { + int num = faceVertexNumAndIndices[i++]; + assert(num > 0 && num <= MAX_VERTICES_PER_FACE); + if (4 != num) { + i += num; + continue; + } + int i0 = faceVertexNumAndIndices[i++]; + int i1 = faceVertexNumAndIndices[i++]; + int i2 = faceVertexNumAndIndices[i++]; + int i3 = faceVertexNumAndIndices[i++]; + const auto &v0 = verticesPositions[i0]; + const auto &v1 = verticesPositions[i1]; + const auto &v2 = verticesPositions[i2]; + const auto &v3 = verticesPositions[i3]; + bmeshQuads.push_back(std::make_tuple(m_forMakePositionKey->makeKey(v0.x(), v0.y(), v0.z()), + m_forMakePositionKey->makeKey(v1.x(), v1.y(), v1.z()), + m_forMakePositionKey->makeKey(v2.x(), v2.y(), v2.z()), + m_forMakePositionKey->makeKey(v3.x(), v3.y(), v3.z()))); + } + delete[] faceVertexNumAndIndices; delete[] positionBuffer; delete[] sourceBuffer; } @@ -221,19 +251,22 @@ void *MeshGenerator::combinePartMesh(QString partId) if (MeshGenerator::m_enableDebug) meshlite_bmesh_enable_debug(m_meshliteContext, bmeshId, 1); - //QString mirroredPartId; - //QUuid mirroredPartIdNotAsString; - //if (xMirrored) { - // mirroredPartIdNotAsString = QUuid().createUuid(); - // mirroredPartId = mirroredPartIdNotAsString.toString(); - //} + QString mirroredPartId; + QUuid mirroredPartIdNotAsString; + if (xMirrored) { + mirroredPartIdNotAsString = QUuid().createUuid(); + mirroredPartId = mirroredPartIdNotAsString.toString(); + m_cacheContext->partMirrorIdMap[mirroredPartId] = partId; + } std::map nodeToBmeshIdMap; std::map bmeshToNodeIdMap; auto &cacheBmeshNodes = m_cacheContext->partBmeshNodes[partId]; auto &cacheBmeshVertices = m_cacheContext->partBmeshVertices[partId]; + auto &cacheBmeshQuads = m_cacheContext->partBmeshQuads[partId]; cacheBmeshNodes.clear(); cacheBmeshVertices.clear(); + cacheBmeshQuads.clear(); for (const auto &nodeId: m_partNodeIds[partId]) { auto findNode = m_snapshot->nodes.find(nodeId); if (findNode == m_snapshot->nodes.end()) { @@ -247,7 +280,9 @@ void *MeshGenerator::combinePartMesh(QString partId) float y = (m_mainProfileMiddleY - valueOfKeyInMapOrEmpty(node, "y").toFloat()); float z = (m_sideProfileMiddleX - valueOfKeyInMapOrEmpty(node, "z").toFloat()); int bmeshNodeId = meshlite_bmesh_add_node(m_meshliteContext, bmeshId, x, y, z, radius); - + + SkeletonBoneMark boneMark = SkeletonBoneMarkFromString(valueOfKeyInMapOrEmpty(node, "boneMark").toUtf8().constData()); + nodeToBmeshIdMap[nodeId] = bmeshNodeId; bmeshToNodeIdMap[bmeshNodeId] = nodeId; @@ -257,12 +292,15 @@ void *MeshGenerator::combinePartMesh(QString partId) bmeshNode.radius = radius; bmeshNode.nodeId = QUuid(nodeId); bmeshNode.color = partColor; + bmeshNode.boneMark = boneMark; + //if (SkeletonBoneMark::None != boneMark) + // bmeshNode.color = SkeletonBoneMarkToColor(boneMark); cacheBmeshNodes.push_back(bmeshNode); - //if (xMirrored) { - // bmeshNode.partId = mirroredPartId; - // bmeshNode.origin.setX(-x); - // cacheBmeshNodes.push_back(bmeshNode); - //} + if (xMirrored) { + bmeshNode.partId = mirroredPartId; + bmeshNode.origin.setX(-x); + cacheBmeshNodes.push_back(bmeshNode); + } } for (const auto &edgeId: m_partEdgeIds[partId]) { @@ -295,7 +333,7 @@ void *MeshGenerator::combinePartMesh(QString partId) void *resultMesh = nullptr; if (!bmeshToNodeIdMap.empty()) { meshId = meshlite_bmesh_generate_mesh(m_meshliteContext, bmeshId); - loadVertexSources(m_meshliteContext, meshId, partIdNotAsString, bmeshToNodeIdMap, cacheBmeshVertices); + loadVertexSources(m_meshliteContext, meshId, partIdNotAsString, bmeshToNodeIdMap, cacheBmeshVertices, cacheBmeshQuads); if (wrapped) resultMesh = convertToCombinableConvexHullMesh(m_meshliteContext, meshId); else @@ -305,7 +343,7 @@ void *MeshGenerator::combinePartMesh(QString partId) if (nullptr != resultMesh) { if (xMirrored) { int xMirroredMeshId = meshlite_mirror_in_x(m_meshliteContext, meshId, 0); - loadVertexSources(m_meshliteContext, xMirroredMeshId, partIdNotAsString, bmeshToNodeIdMap, cacheBmeshVertices); + loadVertexSources(m_meshliteContext, xMirroredMeshId, mirroredPartIdNotAsString, bmeshToNodeIdMap, cacheBmeshVertices, cacheBmeshQuads); void *mirroredMesh = nullptr; if (wrapped) mirroredMesh = convertToCombinableConvexHullMesh(m_meshliteContext, xMirroredMeshId); @@ -554,6 +592,14 @@ void MeshGenerator::process() } else { for (auto it = m_cacheContext->partBmeshNodes.begin(); it != m_cacheContext->partBmeshNodes.end(); ) { if (m_snapshot->parts.find(it->first) == m_snapshot->parts.end()) { + auto mirrorFrom = m_cacheContext->partMirrorIdMap.find(it->first); + if (mirrorFrom != m_cacheContext->partMirrorIdMap.end()) { + if (m_snapshot->parts.find(mirrorFrom->second) != m_snapshot->parts.end()) { + it++; + continue; + } + m_cacheContext->partMirrorIdMap.erase(mirrorFrom); + } it = m_cacheContext->partBmeshNodes.erase(it); continue; } @@ -566,6 +612,13 @@ void MeshGenerator::process() } it++; } + for (auto it = m_cacheContext->partBmeshQuads.begin(); it != m_cacheContext->partBmeshQuads.end(); ) { + if (m_snapshot->parts.find(it->first) == m_snapshot->parts.end()) { + it = m_cacheContext->partBmeshQuads.erase(it); + continue; + } + it++; + } for (auto it = m_cacheContext->componentCombinableMeshs.begin(); it != m_cacheContext->componentCombinableMeshs.end(); ) { if (m_snapshot->components.find(it->first) == m_snapshot->components.end()) { deleteCombinableMesh(it->second); @@ -617,14 +670,29 @@ void MeshGenerator::process() bmeshNodes.second.begin(), bmeshNodes.second.end()); } - if (resultMeshId > 0) { - resultMeshId = meshlite_combine_coplanar_faces(m_meshliteContext, resultMeshId); - if (resultMeshId > 0) - resultMeshId = meshlite_fix_hole(m_meshliteContext, resultMeshId); + //if (resultMeshId > 0) { + // resultMeshId = meshlite_combine_coplanar_faces(m_meshliteContext, resultMeshId); + // if (resultMeshId > 0) + // resultMeshId = meshlite_fix_hole(m_meshliteContext, resultMeshId); + //} + + int triangulatedFinalMeshId = resultMeshId; + if (triangulatedFinalMeshId > 0) { + std::set> sharedQuadEdges; + for (const auto &bmeshQuads: m_cacheContext->partBmeshQuads) { + for (const auto &quad: bmeshQuads.second) { + sharedQuadEdges.insert(std::make_pair(std::get<0>(quad), std::get<2>(quad))); + sharedQuadEdges.insert(std::make_pair(std::get<1>(quad), std::get<3>(quad))); + } + } + if (!sharedQuadEdges.empty()) { + resultMeshId = meshQuadify(m_meshliteContext, triangulatedFinalMeshId, sharedQuadEdges, m_forMakePositionKey); + } } if (resultMeshId > 0) { - int triangulatedFinalMeshId = meshlite_triangulate(m_meshliteContext, resultMeshId); + //int triangulatedFinalMeshId = meshlite_triangulate(m_meshliteContext, resultMeshId); + //triangulatedFinalMeshId = resultMeshId; loadGeneratedPositionsToMeshResultContext(m_meshliteContext, triangulatedFinalMeshId); m_mesh = new MeshLoader(m_meshliteContext, resultMeshId, triangulatedFinalMeshId, Theme::white, &m_meshResultContext->triangleColors(), m_smoothNormal); } diff --git a/src/meshgenerator.h b/src/meshgenerator.h index 84b989e8..2ca963c1 100644 --- a/src/meshgenerator.h +++ b/src/meshgenerator.h @@ -19,9 +19,11 @@ public: ~GeneratedCacheContext(); std::map> partBmeshVertices; std::map> partBmeshNodes; + std::map>> partBmeshQuads; std::map componentCombinableMeshs; std::map> componentPositions; std::map> componentVerticesSources; + std::map partMirrorIdMap; void updateComponentCombinableMesh(QString componentId, void *mesh); }; @@ -63,11 +65,12 @@ private: std::map> m_partEdgeIds; std::set m_dirtyComponentIds; std::set m_dirtyPartIds; - PositionMap> m_bmeshPartVerticesIndiciesMap; private: static bool m_enableDebug; + static PositionMap *m_forMakePositionKey; private: - void loadVertexSources(void *meshliteContext, int meshId, QUuid partId, const std::map &bmeshToNodeIdMap, std::vector &bmeshVertices); + void loadVertexSources(void *meshliteContext, int meshId, QUuid partId, const std::map &bmeshToNodeIdMap, std::vector &bmeshVertices, + std::vector> &bmeshQuads); void loadGeneratedPositionsToMeshResultContext(void *meshliteContext, int triangulatedMeshId); void collectParts(); void checkDirtyFlags(); diff --git a/src/meshquadify.cpp b/src/meshquadify.cpp new file mode 100644 index 00000000..aa7da991 --- /dev/null +++ b/src/meshquadify.cpp @@ -0,0 +1,96 @@ +#include +#include +#include "meshquadify.h" +#include "meshlite.h" + +int meshQuadify(void *meshlite, int meshId, const std::set> &sharedQuadEdges, + PositionMap *positionMapForMakeKey) +{ + int vertexCount = meshlite_get_vertex_count(meshlite, meshId); + float *vertexPositions = new float[vertexCount * 3]; + int vertexArrayLen = meshlite_get_vertex_position_array(meshlite, meshId, vertexPositions, vertexCount * 3); + int offset = 0; + assert(vertexArrayLen == vertexCount * 3); + std::map positionKeyMap; + for (int i = 0; i < vertexCount; i++) { + float x = vertexPositions[offset + 0]; + float y = vertexPositions[offset + 1]; + float z = vertexPositions[offset + 2]; + positionKeyMap[i] = positionMapForMakeKey->makeKey(x, y, z); + offset += 3; + } + int faceCount = meshlite_get_face_count(meshlite, meshId); + int *faceVertexNumAndIndices = new int[faceCount * MAX_VERTICES_PER_FACE]; + int filledLength = meshlite_get_face_index_array(meshlite, meshId, faceVertexNumAndIndices, faceCount * MAX_VERTICES_PER_FACE); + int i = 0; + std::vector> newFaceIndicies; + while (i < filledLength) { + int num = faceVertexNumAndIndices[i++]; + assert(num > 0 && num <= MAX_VERTICES_PER_FACE); + if (num < 3) { + i += num; + continue; + } + std::vector indices; + for (int j = 0; j < num; j++) { + int index = faceVertexNumAndIndices[i++]; + assert(index >= 0 && index < vertexCount); + indices.push_back(index); + } + newFaceIndicies.push_back(indices); + } + int quadMesh = 0; + std::map, std::pair> triangleEdgeMap; + for (int i = 0; i < (int)newFaceIndicies.size(); i++) { + const auto &faceIndicies = newFaceIndicies[i]; + if (faceIndicies.size() == 3) { + triangleEdgeMap[std::make_pair(faceIndicies[0], faceIndicies[1])] = std::make_pair(i, faceIndicies[2]); + triangleEdgeMap[std::make_pair(faceIndicies[1], faceIndicies[2])] = std::make_pair(i, faceIndicies[0]); + triangleEdgeMap[std::make_pair(faceIndicies[2], faceIndicies[0])] = std::make_pair(i, faceIndicies[1]); + } + } + std::unordered_set unionedFaces; + std::vector> newUnionedFaceIndicies; + for (const auto &edge: triangleEdgeMap) { + if (unionedFaces.find(edge.second.first) != unionedFaces.end()) + continue; + auto pair = std::make_pair(positionKeyMap[edge.first.first], positionKeyMap[edge.first.second]); + if (sharedQuadEdges.find(pair) != sharedQuadEdges.end()) { + auto oppositeEdge = triangleEdgeMap.find(std::make_pair(edge.first.second, edge.first.first)); + if (oppositeEdge == triangleEdgeMap.end()) { + qDebug() << "Find opposite edge failed"; + } else { + if (unionedFaces.find(oppositeEdge->second.first) == unionedFaces.end()) { + unionedFaces.insert(edge.second.first); + unionedFaces.insert(oppositeEdge->second.first); + std::vector indices; + indices.push_back(edge.second.second); + indices.push_back(edge.first.first); + indices.push_back(oppositeEdge->second.second); + indices.push_back(edge.first.second); + newUnionedFaceIndicies.push_back(indices); + } + } + } + } + std::vector newFaceVertexNumAndIndices; + for (int i = 0; i < (int)newFaceIndicies.size(); i++) { + if (unionedFaces.find(i) == unionedFaces.end()) { + const auto &faceIndicies = newFaceIndicies[i]; + newFaceVertexNumAndIndices.push_back(faceIndicies.size()); + for (const auto &index: faceIndicies) { + newFaceVertexNumAndIndices.push_back(index); + } + } + } + for (const auto &faceIndicies: newUnionedFaceIndicies) { + newFaceVertexNumAndIndices.push_back(faceIndicies.size()); + for (const auto &index: faceIndicies) { + newFaceVertexNumAndIndices.push_back(index); + } + } + quadMesh = meshlite_build(meshlite, vertexPositions, vertexCount, newFaceVertexNumAndIndices.data(), newFaceVertexNumAndIndices.size()); + delete[] faceVertexNumAndIndices; + delete[] vertexPositions; + return quadMesh; +} diff --git a/src/meshquadify.h b/src/meshquadify.h new file mode 100644 index 00000000..b123f909 --- /dev/null +++ b/src/meshquadify.h @@ -0,0 +1,10 @@ +#ifndef MESH_QUADIFY_H +#define MESH_QUADIFY_H +#include +#include "meshutil.h" +#include "positionmap.h" + +int meshQuadify(void *meshlite, int meshId, const std::set> &sharedQuadEdges, + PositionMap *positionMapForMakeKey); + +#endif diff --git a/src/meshresultcontext.cpp b/src/meshresultcontext.cpp index e757186f..68e2db82 100644 --- a/src/meshresultcontext.cpp +++ b/src/meshresultcontext.cpp @@ -7,6 +7,7 @@ #include "meshresultcontext.h" #include "thekla_atlas.h" #include "positionmap.h" +#include "nvcore/Debug.h" struct HalfColorEdge { @@ -334,6 +335,7 @@ void MeshResultContext::calculateResultParts(std::map &parts) if (isNewVertex || isSeamVertex) { int newIndex = resultPart.vertices.size(); resultPart.interpolatedVertexNormals.push_back(newTriangle.normal); + resultPart.verticesOldIndicies.push_back(triangle.indicies[i]); resultPart.vertices.push_back(vertices[triangle.indicies[i]]); ResultVertexUv vertexUv; vertexUv.uv[0] = triangleUvs()[x].uv[i][0]; @@ -365,6 +367,18 @@ void MeshResultContext::calculateResultTriangleUvs(std::vector const std::vector &choosenTriangles = rearrangedTriangles(); Atlas_Input_Mesh inputMesh; + + using namespace nv; + + class NvAssertHandler : public nv::AssertHandler { + virtual int assertion(const char *exp, const char *file, int line, const char *func, const char *msg, va_list arg) + { + qDebug() << "Something bad happended inside nvMesh:" << exp << "file:" << file << "line:" << line << "msg:" << msg; + return NV_ABORT_IGNORE; + }; + }; + NvAssertHandler assertHandler; + nv::debug::setAssertHandler(&assertHandler); inputMesh.vertex_count = choosenVertices.size(); inputMesh.vertex_array = new Atlas_Input_Vertex[inputMesh.vertex_count]; diff --git a/src/meshresultcontext.h b/src/meshresultcontext.h index 873af4ba..5a60f3dc 100644 --- a/src/meshresultcontext.h +++ b/src/meshresultcontext.h @@ -6,6 +6,7 @@ #include #include #include "positionmap.h" +#include "skeletonbonemark.h" #define MAX_WEIGHT_NUM 4 @@ -16,6 +17,7 @@ struct BmeshNode QVector3D origin; float radius = 0; QColor color; + SkeletonBoneMark boneMark; }; struct BmeshVertex @@ -51,6 +53,7 @@ struct ResultPart { QColor color; std::vector vertices; + std::vector verticesOldIndicies; std::vector interpolatedVertexNormals; std::vector triangles; std::vector uvs; @@ -107,6 +110,7 @@ private: std::vector m_rearrangedVertices; std::vector m_rearrangedTriangles; std::map> m_vertexSourceMap; + std::map m_rearrangedVerticesToOldIndexMap; private: void calculateTriangleSourceNodes(std::vector> &triangleSourceNodes, std::map> &vertexSourceMap); void calculateRemainingVertexSourceNodesAfterTriangleSourceNodesSolved(std::map> &vertexSourceMap); diff --git a/src/meshsplitter.cpp b/src/meshsplitter.cpp new file mode 100644 index 00000000..6aaabd49 --- /dev/null +++ b/src/meshsplitter.cpp @@ -0,0 +1,102 @@ +#include +#include +#include +#include "meshsplitter.h" + +bool MeshSplitter::split(const std::set &input, + const std::set &splitter, + std::set &firstGroup, + std::set &secondGroup) +{ + firstGroup.clear(); + secondGroup.clear(); + + // Make the edge to triangle map, this map will be used to find the neighbor triangles + std::map, MeshSplitterTriangle> edgeToTriangleMap; + for (const auto &triangle: input) { + for (int i = 0; i < 3; i++) { + int next = (i + 1) % 3; + edgeToTriangleMap[std::make_pair(triangle.indicies[i], triangle.indicies[next])] = triangle; + } + } + + /* + size_t noClosingEdges = 0; + for (const auto &triangle: input) { + for (int i = 0; i < 3; i++) { + int next = (i + 1) % 3; + if (edgeToTriangleMap.find(std::make_pair(triangle.indicies[next], triangle.indicies[i])) == edgeToTriangleMap.end()) { + qDebug() << "Edge is not closing:" << triangle.indicies[next] << triangle.indicies[i]; + noClosingEdges++; + } + } + } + qDebug() << "noClosingEdges:" << noClosingEdges; + */ + + // Find one triangle wich is direct neighbor of one splitter + MeshSplitterTriangle startTriangle; + bool foundStartTriangle = false; + for (const auto &triangle: splitter) { + for (int i = 0; i < 3; i++) { + int next = (i + 1) % 3; + auto oppositeEdge = std::make_pair(triangle.indicies[next], triangle.indicies[i]); + auto oppositeTriangle = edgeToTriangleMap.find(oppositeEdge); + if (oppositeTriangle == edgeToTriangleMap.end()) { + qDebug() << "Find opposite edge failed:" << oppositeEdge.first << oppositeEdge.second; + return false; + } + if (splitter.find(oppositeTriangle->second) == splitter.end()) { + foundStartTriangle = true; + startTriangle = oppositeTriangle->second; + break; + } + } + } + if (!foundStartTriangle) { + qDebug() << "Find start triangle for splitter failed"; + return false; + } + + // Recursively join all the neighbors of the first found triangle to the first group + std::set processedTriangles; + for (const auto &triangle: splitter) { + processedTriangles.insert(triangle); + } + std::queue waitQueue; + waitQueue.push(startTriangle); + while (!waitQueue.empty()) { + MeshSplitterTriangle triangle = waitQueue.front(); + waitQueue.pop(); + firstGroup.insert(triangle); + if (!processedTriangles.insert(triangle).second) + continue; + for (int i = 0; i < 3; i++) { + int next = (i + 1) % 3; + auto oppositeEdge = std::make_pair(triangle.indicies[next], triangle.indicies[i]); + auto oppositeTriangle = edgeToTriangleMap.find(oppositeEdge); + if (oppositeTriangle == edgeToTriangleMap.end()) { + qDebug() << "Find opposite edge failed:" << oppositeEdge.first << oppositeEdge.second; + return false; + } + if (processedTriangles.find(oppositeTriangle->second) == processedTriangles.end()) { + waitQueue.push(oppositeTriangle->second); + } + } + } + + // Now, the remains should be put in the second group + for (const auto &triangle: input) { + if (processedTriangles.find(triangle) != processedTriangles.end()) + continue; + secondGroup.insert(triangle); + } + + // Any of these two groups is empty means split failed + if (firstGroup.empty() || secondGroup.empty()) { + qDebug() << "At lease one group is empty"; + return false; + } + + return true; +} diff --git a/src/meshsplitter.h b/src/meshsplitter.h new file mode 100644 index 00000000..44157375 --- /dev/null +++ b/src/meshsplitter.h @@ -0,0 +1,26 @@ +#ifndef MESH_SPLITTER_H +#define MESH_SPLITTER_H +#include + +class MeshSplitterTriangle +{ +public: + int indicies[3] = {0, 0, 0}; + + bool operator<(const MeshSplitterTriangle &other) const + { + return std::make_tuple(indicies[0], indicies[1], indicies[2]) < + std::make_tuple(other.indicies[0], other.indicies[1], other.indicies[2]); + } +}; + +class MeshSplitter +{ +public: + static bool split(const std::set &input, + const std::set &splitter, + std::set &firstGroup, + std::set &secondGroup); +}; + +#endif diff --git a/src/meshutil.cpp b/src/meshutil.cpp index b04d85d0..83194d28 100644 --- a/src/meshutil.cpp +++ b/src/meshutil.cpp @@ -4,8 +4,6 @@ #define USE_CGAL 1 -#define MAX_VERTICES_PER_FACE 100 - #if USE_CGAL == 1 // Polygon_mesh_processing/corefinement_mesh_union.cpp // https://doc.cgal.org/latest/Polygon_mesh_processing/Polygon_mesh_processing_2corefinement_mesh_union_8cpp-example.html#a2 @@ -86,8 +84,10 @@ typename CGAL::Surface_mesh *makeCgalMeshFromMeshlite( while (i < filledLength) { int num = faceVertexNumAndIndices[i++]; assert(num > 0 && num <= MAX_VERTICES_PER_FACE); - if (num < 3) + if (num < 3) { + i += num; continue; + } std::vector::Vertex_index> faceVertexIndices; for (int j = 0; j < num; j++) { int index = faceVertexNumAndIndices[i++]; diff --git a/src/meshutil.h b/src/meshutil.h index a767efe3..df3b1b6d 100644 --- a/src/meshutil.h +++ b/src/meshutil.h @@ -4,6 +4,8 @@ #include #include +#define MAX_VERTICES_PER_FACE 100 + int mergeMeshs(void *meshliteContext, const std::vector &meshIds); int unionMeshs(void *meshliteContext, const std::vector &meshIds, const std::set &inverseIds, int *errorCount=0); int subdivMesh(void *meshliteContext, int meshId, int *errorCount=0); diff --git a/src/modelofflinerender.cpp b/src/modelofflinerender.cpp index 4b7bc224..52d2c20a 100644 --- a/src/modelofflinerender.cpp +++ b/src/modelofflinerender.cpp @@ -6,7 +6,10 @@ ModelOfflineRender::ModelOfflineRender(QOpenGLWidget *sharedContextWidget, QScreen *targetScreen) : QOffscreenSurface(targetScreen), m_context(nullptr), - m_mesh(nullptr) + m_mesh(nullptr), + m_xRot(0), + m_yRot(0), + m_zRot(0) { create(); @@ -71,11 +74,9 @@ QImage ModelOfflineRender::toImage(const QSize &size) m_context->functions()->glViewport(0, 0, size.width(), size.height()); if (nullptr != m_mesh) { - //int xRot = -30 * 16; - //int yRot = 45 * 16; - int xRot = 0; - int yRot = 0; - int zRot = 0; + int xRot = m_xRot; + int yRot = m_yRot; + int zRot = m_zRot; QMatrix4x4 proj; QMatrix4x4 camera; QMatrix4x4 world; @@ -132,3 +133,19 @@ QImage ModelOfflineRender::toImage(const QSize &size) return image; } + +void ModelOfflineRender::setXRotation(int angle) +{ + m_xRot = angle; +} + +void ModelOfflineRender::setYRotation(int angle) +{ + m_yRot = angle; +} + +void ModelOfflineRender::setZRotation(int angle) +{ + m_zRot = angle; +} + diff --git a/src/modelofflinerender.h b/src/modelofflinerender.h index 12f440f7..b1fc64bf 100644 --- a/src/modelofflinerender.h +++ b/src/modelofflinerender.h @@ -19,9 +19,15 @@ public: void setRenderThread(QThread *thread); void updateMesh(MeshLoader *mesh); QImage toImage(const QSize &size); + void setXRotation(int angle); + void setYRotation(int angle); + void setZRotation(int angle); private: QOpenGLContext *m_context; MeshLoader *m_mesh; + int m_xRot; + int m_yRot; + int m_zRot; }; #endif diff --git a/src/riggenerator.cpp b/src/riggenerator.cpp new file mode 100644 index 00000000..af9cf34c --- /dev/null +++ b/src/riggenerator.cpp @@ -0,0 +1,177 @@ +#include +#include +#include +#include "riggenerator.h" +#include "autorigger.h" + +RigGenerator::RigGenerator(const MeshResultContext &meshResultContext) : + m_meshResultContext(new MeshResultContext(meshResultContext)) +{ +} + +RigGenerator::~RigGenerator() +{ + delete m_meshResultContext; + delete m_resultMesh; + delete m_autoRigger; + delete m_resultBones; + delete m_resultWeights; +} + +std::vector *RigGenerator::takeResultBones() +{ + std::vector *resultBones = m_resultBones; + m_resultBones = nullptr; + return resultBones; +} + +std::map *RigGenerator::takeResultWeights() +{ + std::map *resultWeights = m_resultWeights; + m_resultWeights = nullptr; + return resultWeights; +} + +MeshLoader *RigGenerator::takeResultMesh() +{ + MeshLoader *resultMesh = m_resultMesh; + m_resultMesh = nullptr; + return resultMesh; +} + +const std::vector &RigGenerator::missingMarkNames() +{ + return m_missingMarkNames; +} + +const std::vector &RigGenerator::errorMarkNames() +{ + return m_errorMarkNames; +} + +void RigGenerator::process() +{ + QElapsedTimer countTimeConsumed; + countTimeConsumed.start(); + + std::vector inputVerticesPositions; + std::set inputTriangles; + + for (const auto &vertex: m_meshResultContext->vertices) { + inputVerticesPositions.push_back(vertex.position); + } + std::map, std::tuple>> marksMap; + for (size_t triangleIndex = 0; triangleIndex < m_meshResultContext->triangles.size(); triangleIndex++) { + const auto &sourceTriangle = m_meshResultContext->triangles[triangleIndex]; + MeshSplitterTriangle newTriangle; + for (int i = 0; i < 3; i++) + newTriangle.indicies[i] = sourceTriangle.indicies[i]; + auto findBmeshNodeResult = m_meshResultContext->bmeshNodeMap().find(m_meshResultContext->triangleSourceNodes()[triangleIndex]); + if (findBmeshNodeResult != m_meshResultContext->bmeshNodeMap().end()) { + const auto &bmeshNode = *findBmeshNodeResult->second; + if (bmeshNode.boneMark != SkeletonBoneMark::None) { + SkeletonSide boneSide = SkeletonSide::None; + if (SkeletonBoneMarkHasSide(bmeshNode.boneMark)) { + boneSide = bmeshNode.origin.x() > 0 ? SkeletonSide::Left : SkeletonSide::Right; + } + auto &marks = marksMap[std::make_pair(bmeshNode.boneMark, boneSide)]; + std::get<0>(marks) += bmeshNode.origin; + std::get<1>(marks) += 1; + std::get<2>(marks).insert(newTriangle); + } + } + inputTriangles.insert(newTriangle); + } + m_autoRigger = new AutoRigger(inputVerticesPositions, inputTriangles); + for (const auto &marks: marksMap) { + m_autoRigger->addMarkGroup(marks.first.first, marks.first.second, + std::get<0>(marks.second) / std::get<1>(marks.second), + std::get<2>(marks.second)); + } + bool rigSucceed = m_autoRigger->rig(); + + if (rigSucceed) { + qDebug() << "Rig succeed"; + } else { + qDebug() << "Rig failed"; + m_missingMarkNames = m_autoRigger->missingMarkNames(); + m_errorMarkNames = m_autoRigger->errorMarkNames(); + for (const auto &message: m_autoRigger->messages()) { + qDebug() << "errorType:" << message.first << "Message:" << message.second; + } + } + + // Blend vertices colors according to bone weights + + std::vector inputVerticesColors(m_meshResultContext->vertices.size()); + if (rigSucceed) { + const auto &resultWeights = m_autoRigger->resultWeights(); + const auto &resultBones = m_autoRigger->resultBones(); + + m_resultWeights = new std::map; + *m_resultWeights = resultWeights; + + m_resultBones = new std::vector; + *m_resultBones = resultBones; + + for (size_t vertexIndex = 0; vertexIndex < inputVerticesColors.size(); vertexIndex++) { + auto findResult = resultWeights.find((int)vertexIndex); + int blendR = 0, blendG = 0, blendB = 0; + if (findResult != resultWeights.end()) { + for (int i = 0; i < 4; i++) { + int boneIndex = findResult->second.boneIndicies[i]; + if (boneIndex > 0) { + const auto &bone = resultBones[boneIndex]; + blendR += bone.color.red() * findResult->second.boneWeights[i]; + blendG += bone.color.green() * findResult->second.boneWeights[i]; + blendB += bone.color.blue() * findResult->second.boneWeights[i]; + } + } + } + QColor blendColor = QColor(blendR, blendG, blendB, 255); + inputVerticesColors[vertexIndex] = blendColor; + } + } + + // Smooth normals + + std::map vertexNormalMap; + for (size_t triangleIndex = 0; triangleIndex < m_meshResultContext->triangles.size(); triangleIndex++) { + const auto &sourceTriangle = m_meshResultContext->triangles[triangleIndex]; + for (int i = 0; i < 3; i++) + vertexNormalMap[sourceTriangle.indicies[i]] += sourceTriangle.normal; + } + for (auto &item: vertexNormalMap) + item.second.normalize(); + + // Create mesh for demo + + Vertex *triangleVertices = new Vertex[m_meshResultContext->triangles.size() * 3]; + int triangleVerticesNum = 0; + for (size_t triangleIndex = 0; triangleIndex < m_meshResultContext->triangles.size(); triangleIndex++) { + const auto &sourceTriangle = m_meshResultContext->triangles[triangleIndex]; + for (int i = 0; i < 3; i++) { + Vertex ¤tVertex = triangleVertices[triangleVerticesNum++]; + const auto &sourcePosition = inputVerticesPositions[sourceTriangle.indicies[i]]; + const auto &sourceColor = inputVerticesColors[sourceTriangle.indicies[i]]; + const auto &sourceNormal = vertexNormalMap[sourceTriangle.indicies[i]]; + currentVertex.posX = sourcePosition.x(); + currentVertex.posY = sourcePosition.y(); + currentVertex.posZ = sourcePosition.z(); + currentVertex.texU = 0; + currentVertex.texV = 0; + currentVertex.colorR = sourceColor.redF(); + currentVertex.colorG = sourceColor.greenF(); + currentVertex.colorB = sourceColor.blueF(); + currentVertex.normX = sourceNormal.x(); + currentVertex.normY = sourceNormal.y(); + currentVertex.normZ = sourceNormal.z(); + } + } + m_resultMesh = new MeshLoader(triangleVertices, triangleVerticesNum); + + qDebug() << "The rig generation took" << countTimeConsumed.elapsed() << "milliseconds"; + + this->moveToThread(QGuiApplication::instance()->thread()); + emit finished(); +} diff --git a/src/riggenerator.h b/src/riggenerator.h new file mode 100644 index 00000000..15eda13a --- /dev/null +++ b/src/riggenerator.h @@ -0,0 +1,35 @@ +#ifndef RIG_GENERATOR_H +#define RIG_GENERATOR_H +#include +#include +#include +#include "meshresultcontext.h" +#include "meshloader.h" +#include "autorigger.h" + +class RigGenerator : public QObject +{ + Q_OBJECT +public: + RigGenerator(const MeshResultContext &meshResultContext); + ~RigGenerator(); + MeshLoader *takeResultMesh(); + std::vector *takeResultBones(); + std::map *takeResultWeights(); + const std::vector &missingMarkNames(); + const std::vector &errorMarkNames(); +signals: + void finished(); +public slots: + void process(); +private: + MeshResultContext *m_meshResultContext = nullptr; + MeshLoader *m_resultMesh = nullptr; + AutoRigger *m_autoRigger = nullptr; + std::vector *m_resultBones = nullptr; + std::map *m_resultWeights = nullptr; + std::vector m_missingMarkNames; + std::vector m_errorMarkNames; +}; + +#endif diff --git a/src/rigtype.cpp b/src/rigtype.cpp new file mode 100644 index 00000000..5881524a --- /dev/null +++ b/src/rigtype.cpp @@ -0,0 +1,6 @@ +#include +#include "rigtype.h" + +IMPL_RigTypeToString +IMPL_RigTypeFromString +IMPL_RigTypeToDispName diff --git a/src/rigtype.h b/src/rigtype.h new file mode 100644 index 00000000..13b70282 --- /dev/null +++ b/src/rigtype.h @@ -0,0 +1,47 @@ +#ifndef RIG_TYPE_H +#define RIG_TYPE_H +#include + +enum class RigType +{ + None = 0, + Tetrapod, + Count +}; +RigType RigTypeFromString(const char *typeString); +#define IMPL_RigTypeFromString \ +RigType RigTypeFromString(const char *typeString) \ +{ \ + QString type = typeString; \ + if (type == "Tetrapod") \ + return RigType::Tetrapod; \ + return RigType::None; \ +} +const char *RigTypeToString(RigType type); +#define IMPL_RigTypeToString \ +const char *RigTypeToString(RigType type) \ +{ \ + switch (type) { \ + case RigType::Tetrapod: \ + return "Tetrapod"; \ + case RigType::None: \ + return "None"; \ + default: \ + return "None"; \ + } \ +} +QString RigTypeToDispName(RigType type); +#define IMPL_RigTypeToDispName \ +QString RigTypeToDispName(RigType type) \ +{ \ + switch (type) { \ + case RigType::Tetrapod: \ + return QObject::tr("Tetrapod"); \ + case RigType::None: \ + return QObject::tr("None"); \ + default: \ + return ""; \ + } \ +} + +#endif diff --git a/src/rigwidget.cpp b/src/rigwidget.cpp new file mode 100644 index 00000000..9f0a389d --- /dev/null +++ b/src/rigwidget.cpp @@ -0,0 +1,88 @@ +#include +#include +#include "rigwidget.h" +#include "rigtype.h" +#include "infolabel.h" + +RigWidget::RigWidget(const SkeletonDocument *document, QWidget *parent) : + QWidget(parent), + m_document(document) +{ + QFormLayout *formLayout = new QFormLayout; + m_rigTypeBox = new QComboBox; + m_rigTypeBox->setEditable(false); + + for (int i = 0; i < (int)RigType::Count; i++) { + RigType rigType = (RigType)(i); + m_rigTypeBox->addItem(RigTypeToDispName(rigType)); + } + + formLayout->addRow(tr("Type"), m_rigTypeBox); + + m_rigTypeBox->setCurrentIndex((int)m_document->rigType); + + connect(m_rigTypeBox, static_cast(&QComboBox::currentIndexChanged), [=](int index) { + RigType currentRigType = (RigType)index; + emit setRigType(currentRigType); + }); + + m_rigWeightRenderWidget = new ModelWidget(this); + m_rigWeightRenderWidget->setMinimumSize(128, 128); + m_rigWeightRenderWidget->setXRotation(0); + m_rigWeightRenderWidget->setYRotation(0); + m_rigWeightRenderWidget->setZRotation(0); + + m_missingMarksInfoLabel = new InfoLabel; + m_missingMarksInfoLabel->hide(); + + m_errorMarksInfoLabel = new InfoLabel; + m_errorMarksInfoLabel->hide(); + + QVBoxLayout *layout = new QVBoxLayout; + layout->addLayout(formLayout); + layout->addWidget(m_rigWeightRenderWidget); + layout->addWidget(m_missingMarksInfoLabel); + layout->addWidget(m_errorMarksInfoLabel); + layout->addStretch(); + + setLayout(layout); +} + +void RigWidget::rigTypeChanged() +{ + m_rigTypeBox->setCurrentIndex((int)m_document->rigType); +} + +void RigWidget::updateResultInfo() +{ + QStringList missingMarkNames; + for (const auto &markName: m_document->resultRigMissingMarkNames()) { + missingMarkNames.append(markName); + } + QString missingNamesString = missingMarkNames.join(tr(", ")); + if (missingNamesString.isEmpty()) { + m_missingMarksInfoLabel->hide(); + } else { + m_missingMarksInfoLabel->setText(tr("Missing marks: ") + missingNamesString); + m_missingMarksInfoLabel->setMaximumWidth(width() * 0.8); + m_missingMarksInfoLabel->show(); + } + + QStringList errorMarkNames; + for (const auto &markName: m_document->resultRigErrorMarkNames()) { + errorMarkNames.append(markName); + } + QString errorNamesString = errorMarkNames.join(tr(",")); + if (errorNamesString.isEmpty()) { + m_errorMarksInfoLabel->hide(); + } else { + m_errorMarksInfoLabel->setText(tr("Error marks: ") + errorNamesString); + m_errorMarksInfoLabel->setMaximumWidth(width() * 0.8); + m_errorMarksInfoLabel->show(); + } +} + +ModelWidget *RigWidget::rigWeightRenderWidget() +{ + return m_rigWeightRenderWidget; +} diff --git a/src/rigwidget.h b/src/rigwidget.h new file mode 100644 index 00000000..0ce5fca1 --- /dev/null +++ b/src/rigwidget.h @@ -0,0 +1,29 @@ +#ifndef RIG_WIDGET_H +#define RIG_WIDGET_H +#include +#include +#include "skeletondocument.h" +#include "rigtype.h" +#include "modelwidget.h" +#include "infolabel.h" + +class RigWidget : public QWidget +{ + Q_OBJECT +signals: + void setRigType(RigType rigType); +public slots: + void rigTypeChanged(); + void updateResultInfo(); +public: + RigWidget(const SkeletonDocument *document, QWidget *parent=nullptr); + ModelWidget *rigWeightRenderWidget(); +private: + const SkeletonDocument *m_document = nullptr; + QComboBox *m_rigTypeBox = nullptr; + ModelWidget *m_rigWeightRenderWidget = nullptr; + InfoLabel *m_missingMarksInfoLabel = nullptr; + InfoLabel *m_errorMarksInfoLabel = nullptr; +}; + +#endif diff --git a/src/skeletonbonemark.cpp b/src/skeletonbonemark.cpp new file mode 100644 index 00000000..3b395e71 --- /dev/null +++ b/src/skeletonbonemark.cpp @@ -0,0 +1,9 @@ +#include +#include "skeletonbonemark.h" + +IMPL_SkeletonSideToDispName + +IMPL_SkeletonBoneMarkToColor +IMPL_SkeletonBoneMarkToString +IMPL_SkeletonBoneMarkFromString +IMPL_SkeletonBoneMarkToDispName diff --git a/src/skeletonbonemark.h b/src/skeletonbonemark.h new file mode 100644 index 00000000..1fd38283 --- /dev/null +++ b/src/skeletonbonemark.h @@ -0,0 +1,140 @@ +#ifndef SKELETON_BONE_MARK_H +#define SKELETON_BONE_MARK_H +#include +#include + +enum class SkeletonSide +{ + None = 0, + Left, + Right +}; + +QString SkeletonSideToDispName(SkeletonSide side); +#define IMPL_SkeletonSideToDispName \ +QString SkeletonSideToDispName(SkeletonSide side) \ +{ \ + switch (side) { \ + case SkeletonSide::Left: \ + return QObject::tr("Left"); \ + case SkeletonSide::Right: \ + return QObject::tr("Right"); \ + case SkeletonSide::None: \ + return ""; \ + default: \ + return ""; \ + } \ +} + +enum class SkeletonBoneMark +{ + None = 0, + Neck, + Shoulder, + Elbow, + Wrist, + Hip, + Knee, + Ankle, + Count +}; +#define SkeletonBoneMarkHasSide(mark) ((mark) != SkeletonBoneMark::Neck) +QColor SkeletonBoneMarkToColor(SkeletonBoneMark mark); +#define IMPL_SkeletonBoneMarkToColor \ +QColor SkeletonBoneMarkToColor(SkeletonBoneMark mark) \ +{ \ + switch (mark) { \ + case SkeletonBoneMark::Neck: \ + return QColor(0xfc, 0x0d, 0x1b); \ + case SkeletonBoneMark::Shoulder: \ + return QColor(0xfd, 0x80, 0x23); \ + case SkeletonBoneMark::Elbow: \ + return QColor(0x29, 0xfd, 0x2f); \ + case SkeletonBoneMark::Wrist: \ + return QColor(0xff, 0xfd, 0x38); \ + case SkeletonBoneMark::Hip: \ + return QColor(0x2c, 0xff, 0xfe); \ + case SkeletonBoneMark::Knee: \ + return QColor(0x0b, 0x24, 0xfb); \ + case SkeletonBoneMark::Ankle: \ + return QColor(0xfc, 0x28, 0xfc); \ + case SkeletonBoneMark::None: \ + return Qt::transparent; \ + default: \ + return Qt::transparent; \ + } \ +} +const char *SkeletonBoneMarkToString(SkeletonBoneMark mark); +#define IMPL_SkeletonBoneMarkToString \ +const char *SkeletonBoneMarkToString(SkeletonBoneMark mark) \ +{ \ + switch (mark) { \ + case SkeletonBoneMark::Neck: \ + return "Neck"; \ + case SkeletonBoneMark::Shoulder: \ + return "Shoulder"; \ + case SkeletonBoneMark::Elbow: \ + return "Elbow"; \ + case SkeletonBoneMark::Wrist: \ + return "Wrist"; \ + case SkeletonBoneMark::Hip: \ + return "Hip"; \ + case SkeletonBoneMark::Knee: \ + return "Knee"; \ + case SkeletonBoneMark::Ankle: \ + return "Ankle"; \ + case SkeletonBoneMark::None: \ + return "None"; \ + default: \ + return "None"; \ + } \ +} +SkeletonBoneMark SkeletonBoneMarkFromString(const char *markString); +#define IMPL_SkeletonBoneMarkFromString \ +SkeletonBoneMark SkeletonBoneMarkFromString(const char *markString) \ +{ \ + QString mark = markString; \ + if (mark == "Neck") \ + return SkeletonBoneMark::Neck; \ + if (mark == "Shoulder") \ + return SkeletonBoneMark::Shoulder; \ + if (mark == "Elbow") \ + return SkeletonBoneMark::Elbow; \ + if (mark == "Wrist") \ + return SkeletonBoneMark::Wrist; \ + if (mark == "Hip") \ + return SkeletonBoneMark::Hip; \ + if (mark == "Knee") \ + return SkeletonBoneMark::Knee; \ + if (mark == "Ankle") \ + return SkeletonBoneMark::Ankle; \ + return SkeletonBoneMark::None; \ +} +QString SkeletonBoneMarkToDispName(SkeletonBoneMark mark); +#define IMPL_SkeletonBoneMarkToDispName \ +QString SkeletonBoneMarkToDispName(SkeletonBoneMark mark) \ +{ \ + switch (mark) { \ + case SkeletonBoneMark::Neck: \ + return QObject::tr("Neck"); \ + case SkeletonBoneMark::Shoulder: \ + return QObject::tr("Shoulder (Arm Start)"); \ + case SkeletonBoneMark::Elbow: \ + return QObject::tr("Elbow"); \ + case SkeletonBoneMark::Wrist: \ + return QObject::tr("Wrist"); \ + case SkeletonBoneMark::Hip: \ + return QObject::tr("Hip (Leg Start)"); \ + case SkeletonBoneMark::Knee: \ + return QObject::tr("Knee"); \ + case SkeletonBoneMark::Ankle: \ + return QObject::tr("Ankle"); \ + case SkeletonBoneMark::None: \ + return QObject::tr("None"); \ + default: \ + return ""; \ + } \ +} + +#endif + diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp index 211caea1..d205046c 100644 --- a/src/skeletondocument.cpp +++ b/src/skeletondocument.cpp @@ -27,6 +27,7 @@ SkeletonDocument::SkeletonDocument() : textureBorderImage(nullptr), textureAmbientOcclusionImage(nullptr), textureColorImage(nullptr), + rigType(RigType::None), // private m_isResultMeshObsolete(false), m_meshGenerator(nullptr), @@ -44,7 +45,12 @@ SkeletonDocument::SkeletonDocument() : m_ambientOcclusionBakedImageUpdateVersion(0), m_sharedContextWidget(nullptr), m_allPositionRelatedLocksEnabled(true), - m_smoothNormal(true) + m_smoothNormal(true), + m_rigGenerator(nullptr), + m_resultRigWeightMesh(nullptr), + m_resultRigBones(nullptr), + m_resultRigWeights(nullptr), + m_isRigObsolete(false) { } @@ -57,6 +63,7 @@ SkeletonDocument::~SkeletonDocument() delete textureBorderImage; delete textureAmbientOcclusionImage; delete m_resultTextureMesh; + delete m_resultRigWeightMesh; } void SkeletonDocument::uiReady() @@ -555,6 +562,25 @@ void SkeletonDocument::switchNodeXZ(QUuid nodeId) emit skeletonChanged(); } +void SkeletonDocument::setNodeBoneMark(QUuid nodeId, SkeletonBoneMark mark) +{ + auto it = nodeMap.find(nodeId); + if (it == nodeMap.end()) { + qDebug() << "Find node failed:" << nodeId; + return; + } + if (isPartReadonly(it->second.partId)) + return; + if (it->second.boneMark == mark) + return; + it->second.boneMark = mark; + auto part = partMap.find(it->second.partId); + if (part != partMap.end()) + part->second.dirty = true; + emit nodeBoneMarkChanged(nodeId); + emit skeletonChanged(); +} + void SkeletonDocument::updateTurnaround(const QImage &image) { turnaround = image; @@ -699,6 +725,8 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::setnodes[node["id"]] = node; @@ -763,20 +791,31 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::setcanvas = canvas; } -void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot) +void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste) { - const auto &originXit = snapshot.canvas.find("originX"); - const auto &originYit = snapshot.canvas.find("originY"); - const auto &originZit = snapshot.canvas.find("originZ"); - if (originXit != snapshot.canvas.end() && - originYit != snapshot.canvas.end() && - originZit != snapshot.canvas.end()) { - originX = originXit->second.toFloat(); - originY = originYit->second.toFloat(); - originZ = originZit->second.toFloat(); + bool isOriginChanged = false; + bool isRigTypeChanged = false; + if (!fromPaste) { + const auto &originXit = snapshot.canvas.find("originX"); + const auto &originYit = snapshot.canvas.find("originY"); + const auto &originZit = snapshot.canvas.find("originZ"); + if (originXit != snapshot.canvas.end() && + originYit != snapshot.canvas.end() && + originZit != snapshot.canvas.end()) { + originX = originXit->second.toFloat(); + originY = originYit->second.toFloat(); + originZ = originZit->second.toFloat(); + isOriginChanged = true; + } + const auto &rigTypeIt = snapshot.canvas.find("rigType"); + if (rigTypeIt != snapshot.canvas.end()) { + rigType = RigTypeFromString(rigTypeIt->second.toUtf8().constData()); + } + isRigTypeChanged = true; } std::set newAddedNodeIds; @@ -830,6 +869,7 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot) node.y = valueOfKeyInMapOrEmpty(nodeKv.second, "y").toFloat(); node.z = valueOfKeyInMapOrEmpty(nodeKv.second, "z").toFloat(); node.partId = oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(nodeKv.second, "partId"))]; + node.boneMark = SkeletonBoneMarkFromString(valueOfKeyInMapOrEmpty(nodeKv.second, "boneMark").toUtf8().constData()); nodeMap[node.id] = node; newAddedNodeIds.insert(node.id); } @@ -927,7 +967,10 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot) } emit componentChildrenChanged(QUuid()); - emit originChanged(); + if (isOriginChanged) + emit originChanged(); + if (isRigTypeChanged) + emit rigTypeChanged(); emit skeletonChanged(); for (const auto &partIt : newAddedPartIds) { @@ -948,6 +991,7 @@ void SkeletonDocument::reset() originX = 0.0; originY = 0.0; originZ = 0.0; + rigType = RigType::None; nodeMap.clear(); edgeMap.clear(); partMap.clear(); @@ -960,7 +1004,7 @@ void SkeletonDocument::reset() void SkeletonDocument::fromSnapshot(const SkeletonSnapshot &snapshot) { reset(); - addFromSnapshot(snapshot); + addFromSnapshot(snapshot, false); emit uncheckAll(); } @@ -979,6 +1023,14 @@ MeshLoader *SkeletonDocument::takeResultTextureMesh() return resultTextureMesh; } +MeshLoader *SkeletonDocument::takeResultRigWeightMesh() +{ + if (nullptr == m_resultRigWeightMesh) + return nullptr; + MeshLoader *resultMesh = new MeshLoader(*m_resultRigWeightMesh); + return resultMesh; +} + void SkeletonDocument::meshReady() { MeshLoader *resultMesh = m_meshGenerator->takeResultMesh(); @@ -1015,6 +1067,7 @@ void SkeletonDocument::meshReady() qDebug() << "MeshLoader generation done"; m_isPostProcessResultObsolete = true; + m_isRigObsolete = true; emit resultMeshChanged(); @@ -1221,8 +1274,6 @@ void SkeletonDocument::postProcess() return; } - qDebug() << "Post processing.."; - m_isPostProcessResultObsolete = false; if (!m_currentMeshResultContext) { @@ -1230,6 +1281,8 @@ void SkeletonDocument::postProcess() return; } + qDebug() << "Post processing.."; + QThread *thread = new QThread; m_postProcessor = new MeshResultPostProcessor(*m_currentMeshResultContext); m_postProcessor->moveToThread(thread); @@ -1898,7 +1951,7 @@ void SkeletonDocument::paste() QXmlStreamReader xmlStreamReader(mimeData->text()); SkeletonSnapshot snapshot; loadSkeletonFromXmlStream(&snapshot, xmlStreamReader); - addFromSnapshot(snapshot); + addFromSnapshot(snapshot, true); } } @@ -1980,9 +2033,11 @@ bool SkeletonDocument::isExportReady() const if (m_isResultMeshObsolete || m_isTextureObsolete || m_isPostProcessResultObsolete || + m_isRigObsolete || m_meshGenerator || m_textureGenerator || - m_postProcessor) + m_postProcessor || + m_rigGenerator) return false; return true; } @@ -2168,3 +2223,108 @@ void SkeletonDocument::unlockDescendantComponents(QUuid componentId) } } +void SkeletonDocument::generateRig() +{ + if (nullptr != m_rigGenerator) { + m_isRigObsolete = true; + return; + } + + m_isRigObsolete = false; + + if (RigType::None == rigType || nullptr == m_currentMeshResultContext) { + removeRigResults(); + return; + } + + qDebug() << "Rig generating.."; + + QThread *thread = new QThread; + m_rigGenerator = new RigGenerator(*m_currentMeshResultContext); + m_rigGenerator->moveToThread(thread); + connect(thread, &QThread::started, m_rigGenerator, &RigGenerator::process); + connect(m_rigGenerator, &RigGenerator::finished, this, &SkeletonDocument::rigReady); + connect(m_rigGenerator, &RigGenerator::finished, thread, &QThread::quit); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); +} + +void SkeletonDocument::rigReady() +{ + delete m_resultRigWeightMesh; + m_resultRigWeightMesh = m_rigGenerator->takeResultMesh(); + + delete m_resultRigBones; + m_resultRigBones = m_rigGenerator->takeResultBones(); + + delete m_resultRigWeights; + m_resultRigWeights = m_rigGenerator->takeResultWeights(); + + m_resultRigMissingMarkNames = m_rigGenerator->missingMarkNames(); + m_resultRigErrorMarkNames = m_rigGenerator->errorMarkNames(); + + delete m_rigGenerator; + m_rigGenerator = nullptr; + + qDebug() << "Rig generation done"; + + emit resultRigChanged(); + + if (m_isRigObsolete) { + generateRig(); + } else { + checkExportReadyState(); + } +} + +const std::vector *SkeletonDocument::resultRigBones() +{ + return m_resultRigBones; +} + +const std::map *SkeletonDocument::resultRigWeights() +{ + return m_resultRigWeights; +} + +void SkeletonDocument::removeRigResults() +{ + delete m_resultRigBones; + m_resultRigBones = nullptr; + + delete m_resultRigWeights; + m_resultRigWeights = nullptr; + + delete m_resultRigWeightMesh; + m_resultRigWeightMesh = nullptr; + + m_resultRigErrorMarkNames.clear(); + m_resultRigMissingMarkNames.clear(); + + emit resultRigChanged(); +} + +void SkeletonDocument::setRigType(RigType toRigType) +{ + if (rigType == toRigType) + return; + + rigType = toRigType; + + m_isRigObsolete = true; + + removeRigResults(); + + emit rigTypeChanged(); + emit rigChanged(); +} + +const std::vector &SkeletonDocument::resultRigMissingMarkNames() const +{ + return m_resultRigMissingMarkNames; +} + +const std::vector &SkeletonDocument::resultRigErrorMarkNames() const +{ + return m_resultRigErrorMarkNames; +} diff --git a/src/skeletondocument.h b/src/skeletondocument.h index 96a8c8e7..fd3ed727 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -13,11 +13,13 @@ #include "skeletonsnapshot.h" #include "meshloader.h" #include "meshgenerator.h" -#include "skeletongenerator.h" #include "theme.h" #include "texturegenerator.h" #include "meshresultpostprocessor.h" #include "ambientocclusionbaker.h" +#include "skeletonbonemark.h" +#include "riggenerator.h" +#include "rigtype.h" class SkeletonNode { @@ -26,7 +28,8 @@ public: x(0), y(0), z(0), - radius(0) + radius(0), + boneMark(SkeletonBoneMark::None) { id = withId.isNull() ? QUuid::createUuid() : withId; } @@ -45,6 +48,7 @@ public: float y; float z; float radius; + SkeletonBoneMark boneMark; std::vector edgeIds; }; @@ -342,6 +346,7 @@ signals: void nodeRemoved(QUuid nodeId); void edgeRemoved(QUuid edgeId); void nodeRadiusChanged(QUuid nodeId); + void nodeBoneMarkChanged(QUuid nodeId); void nodeOriginChanged(QUuid nodeId); void edgeChanged(QUuid edgeId); void partPreviewChanged(QUuid partId); @@ -353,6 +358,8 @@ signals: void resultTextureChanged(); void resultBakedTextureChanged(); void postProcessedResultChanged(); + void resultRigChanged(); + void rigChanged(); void partLockStateChanged(QUuid partId); void partVisibleStateChanged(QUuid partId); void partSubdivStateChanged(QUuid partId); @@ -381,6 +388,7 @@ signals: void checkNode(QUuid nodeId); void checkEdge(QUuid edgeId); void optionsChanged(); + void rigTypeChanged(); public: // need initialize float originX; float originY; @@ -395,6 +403,7 @@ public: // need initialize QImage *textureBorderImage; QImage *textureAmbientOcclusionImage; QImage *textureColorImage; + RigType rigType; public: SkeletonDocument(); ~SkeletonDocument(); @@ -407,7 +416,7 @@ public: QImage preview; void toSnapshot(SkeletonSnapshot *snapshot, const std::set &limitNodeIds=std::set()) const; void fromSnapshot(const SkeletonSnapshot &snapshot); - void addFromSnapshot(const SkeletonSnapshot &snapshot); + void addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste=true); const SkeletonNode *findNode(QUuid nodeId) const; const SkeletonEdge *findEdge(QUuid edgeId) const; const SkeletonPart *findPart(QUuid partId) const; @@ -417,6 +426,9 @@ public: QUuid findComponentParentId(QUuid componentId) const; MeshLoader *takeResultMesh(); MeshLoader *takeResultTextureMesh(); + MeshLoader *takeResultRigWeightMesh(); + const std::vector *resultRigBones(); + const std::map *resultRigWeights(); void updateTurnaround(const QImage &image); void setSharedContextWidget(QOpenGLWidget *sharedContextWidget); bool hasPastableContentInClipboard() const; @@ -431,6 +443,8 @@ public: void findAllNeighbors(QUuid nodeId, std::set &neighbors) const; void collectComponentDescendantParts(QUuid componentId, std::vector &partIds) const; void collectComponentDescendantComponents(QUuid componentId, std::vector &componentIds) const; + const std::vector &resultRigMissingMarkNames() const; + const std::vector &resultRigErrorMarkNames() const; public slots: void removeNode(QUuid nodeId); void removeEdge(QUuid edgeId); @@ -440,6 +454,7 @@ public slots: void moveNodeBy(QUuid nodeId, float x, float y, float z); void setNodeOrigin(QUuid nodeId, float x, float y, float z); void setNodeRadius(QUuid nodeId, float radius); + void setNodeBoneMark(QUuid nodeId, SkeletonBoneMark mark); void switchNodeXZ(QUuid nodeId); void moveOriginBy(float x, float y, float z); void addEdge(QUuid fromNodeId, QUuid toNodeId); @@ -454,6 +469,8 @@ public slots: void postProcessedMeshResultReady(); void bakeAmbientOcclusionTexture(); void ambientOcclusionTextureReady(); + void generateRig(); + void rigReady(); void setPartLockState(QUuid partId, bool locked); void setPartVisibleState(QUuid partId, bool visible); void setPartSubdivState(QUuid partId, bool subdived); @@ -507,6 +524,7 @@ public slots: void enableAllPositionRelatedLocks(); void disableAllPositionRelatedLocks(); void toggleSmoothNormal(); + void setRigType(RigType toRigType); private: void splitPartByNode(std::vector> *groups, QUuid nodeId); void joinNodeAndNeiborsToGroup(std::vector *group, QUuid nodeId, std::set *visitMap, QUuid noUseEdgeId=QUuid()); @@ -521,6 +539,7 @@ private: void removeComponentRecursively(QUuid componentId); void resetDirtyFlags(); void markAllDirty(); + void removeRigResults(); private: // need initialize bool m_isResultMeshObsolete; MeshGenerator *m_meshGenerator; @@ -540,6 +559,13 @@ private: // need initialize QUuid m_currentCanvasComponentId; bool m_allPositionRelatedLocksEnabled; bool m_smoothNormal; + RigGenerator *m_rigGenerator; + MeshLoader *m_resultRigWeightMesh; + std::vector *m_resultRigBones; + std::map *m_resultRigWeights; + bool m_isRigObsolete; + std::vector m_resultRigMissingMarkNames; + std::vector m_resultRigErrorMarkNames; private: static unsigned long m_maxSnapshot; std::deque m_undoItems; diff --git a/src/skeletondocumentwindow.cpp b/src/skeletondocumentwindow.cpp index 9fe0c5f5..8ca86e28 100644 --- a/src/skeletondocumentwindow.cpp +++ b/src/skeletondocumentwindow.cpp @@ -28,6 +28,9 @@ #include "gltffile.h" #include "graphicscontainerwidget.h" #include "skeletonparttreewidget.h" +#include "rigwidget.h" +#include "modelofflinerender.h" +#include "markiconcreator.h" int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16; int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16; @@ -197,18 +200,26 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_modelRenderWidget->setGraphicsFunctions(graphicsWidget); m_document->setSharedContextWidget(m_modelRenderWidget); - - QDockWidget *partListDocker = new QDockWidget(QString(), this); - partListDocker->setAllowedAreas(Qt::RightDockWidgetArea); - - SkeletonPartTreeWidget *partTreeWidget = new SkeletonPartTreeWidget(m_document, partListDocker); + setTabPosition(Qt::RightDockWidgetArea, QTabWidget::East); + + QDockWidget *partTreeDocker = new QDockWidget(tr("Parts"), this); + partTreeDocker->setAllowedAreas(Qt::RightDockWidgetArea); + SkeletonPartTreeWidget *partTreeWidget = new SkeletonPartTreeWidget(m_document, partTreeDocker); partTreeWidget->setGraphicsFunctions(graphicsWidget); - - partListDocker->setWidget(partTreeWidget); - addDockWidget(Qt::RightDockWidgetArea, partListDocker); - - partListDocker->hide(); + partTreeDocker->setWidget(partTreeWidget); + addDockWidget(Qt::RightDockWidgetArea, partTreeDocker); + //partTreeDocker->hide(); + + QDockWidget *rigDocker = new QDockWidget(tr("Rig"), this); + rigDocker->setAllowedAreas(Qt::RightDockWidgetArea); + RigWidget *rigWidget = new RigWidget(m_document, rigDocker); + rigDocker->setWidget(rigWidget); + addDockWidget(Qt::RightDockWidgetArea, rigDocker); + + tabifyDockWidget(partTreeDocker, rigDocker); + + partTreeDocker->raise(); QHBoxLayout *mainLayout = new QHBoxLayout; mainLayout->setSpacing(0); @@ -251,21 +262,23 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_fileMenu->addSeparator(); - m_exportMenu = m_fileMenu->addMenu(tr("Export")); - - m_fileMenu->addSeparator(); + //m_exportMenu = m_fileMenu->addMenu(tr("Export")); - m_exportAsObjAction = new QAction(tr("Wavefront (.obj)..."), this); + m_exportAction = new QAction(tr("Export..."), this); + connect(m_exportAction, &QAction::triggered, this, &SkeletonDocumentWindow::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); - m_exportMenu->addAction(m_exportAsObjAction); + m_fileMenu->addAction(m_exportAsObjAction); - m_exportAsObjPlusMaterialsAction = new QAction(tr("Wavefront (.obj + .mtl)..."), this); - connect(m_exportAsObjPlusMaterialsAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportObjPlusMaterialsResult, Qt::QueuedConnection); - m_exportMenu->addAction(m_exportAsObjPlusMaterialsAction); - - m_exportAsGltfAction = new QAction(tr("GL Transmission Format (.gltf)..."), this); - connect(m_exportAsGltfAction, &QAction::triggered, this, &SkeletonDocumentWindow::showExportPreview, Qt::QueuedConnection); - m_exportMenu->addAction(m_exportAsGltfAction); + //m_exportRenderedAsImageAction = new QAction(tr("Export as PNG..."), this); + //connect(m_exportRenderedAsImageAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportRenderedResult, Qt::QueuedConnection); + //m_fileMenu->addAction(m_exportRenderedAsImageAction); + + //m_exportAsObjPlusMaterialsAction = new QAction(tr("Wavefront (.obj + .mtl)..."), this); + //connect(m_exportAsObjPlusMaterialsAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportObjPlusMaterialsResult, Qt::QueuedConnection); + //m_exportMenu->addAction(m_exportAsObjPlusMaterialsAction); m_fileMenu->addSeparator(); @@ -277,8 +290,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(m_fileMenu, &QMenu::aboutToShow, [=]() { m_exportAsObjAction->setEnabled(m_graphicsWidget->hasItems()); - m_exportAsObjPlusMaterialsAction->setEnabled(m_graphicsWidget->hasItems()); - m_exportAsGltfAction->setEnabled(m_graphicsWidget->hasItems()); + //m_exportAsObjPlusMaterialsAction->setEnabled(m_graphicsWidget->hasItems()); + m_exportAction->setEnabled(m_graphicsWidget->hasItems()); + //m_exportRenderedAsImageAction->setEnabled(m_graphicsWidget->hasItems()); }); m_editMenu = menuBar()->addMenu(tr("Edit")); @@ -374,6 +388,27 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_alignToMenu->addAction(m_alignToGlobalHorizontalCenterAction); m_editMenu->addMenu(m_alignToMenu); + + m_markAsMenu = new QMenu(tr("Mark As")); + + m_markAsNoneAction = new QAction(tr("None"), this); + connect(m_markAsNoneAction, &QAction::triggered, [=]() { + m_graphicsWidget->setSelectedNodesBoneMark(SkeletonBoneMark::None); + }); + m_markAsMenu->addAction(m_markAsNoneAction); + + m_markAsMenu->addSeparator(); + + for (int i = 0; i < (int)SkeletonBoneMark::Count - 1; i++) { + SkeletonBoneMark boneMark = (SkeletonBoneMark)(i + 1); + m_markAsActions[i] = new QAction(MarkIconCreator::createIcon(boneMark), SkeletonBoneMarkToDispName(boneMark), this); + connect(m_markAsActions[i], &QAction::triggered, [=]() { + m_graphicsWidget->setSelectedNodesBoneMark(boneMark); + }); + m_markAsMenu->addAction(m_markAsActions[i]); + } + + m_editMenu->addMenu(m_markAsMenu); m_selectAllAction = new QAction(tr("Select All"), this); connect(m_selectAllAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::selectAll); @@ -440,24 +475,29 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : }); m_viewMenu->addAction(m_toggleSmoothNormalAction); - m_viewMenu->addSeparator(); - - m_showPartsListAction = new QAction(tr("Show Parts List"), this); - connect(m_showPartsListAction, &QAction::triggered, [=]() { - partListDocker->show(); - }); - m_viewMenu->addAction(m_showPartsListAction); - - m_viewMenu->addSeparator(); - - m_showDebugDialogAction = new QAction(tr("Show Debug Dialog"), this); - connect(m_showDebugDialogAction, &QAction::triggered, g_logBrowser, &LogBrowser::showDialog); - m_viewMenu->addAction(m_showDebugDialogAction); - connect(m_viewMenu, &QMenu::aboutToShow, [=]() { - m_showPartsListAction->setEnabled(partListDocker->isHidden()); m_resetModelWidgetPosAction->setEnabled(!isModelSitInVisibleArea(m_modelRenderWidget)); }); + + m_windowMenu = menuBar()->addMenu(tr("Window")); + + m_showPartsListAction = new QAction(tr("Parts"), this); + connect(m_showPartsListAction, &QAction::triggered, [=]() { + partTreeDocker->show(); + partTreeDocker->raise(); + }); + m_windowMenu->addAction(m_showPartsListAction); + + m_showRigAction = new QAction(tr("Rig"), this); + connect(m_showRigAction, &QAction::triggered, [=]() { + rigDocker->show(); + rigDocker->raise(); + }); + m_windowMenu->addAction(m_showRigAction); + + m_showDebugDialogAction = new QAction(tr("Debug"), this); + connect(m_showDebugDialogAction, &QAction::triggered, g_logBrowser, &LogBrowser::showDialog); + m_windowMenu->addAction(m_showDebugDialogAction); m_helpMenu = menuBar()->addMenu(tr("Help")); @@ -535,8 +575,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_partListDockerVisibleSwitchConnection = connect(m_document, &SkeletonDocument::skeletonChanged, [=]() { if (m_graphicsWidget->hasItems()) { - if (partListDocker->isHidden()) - partListDocker->show(); + if (partTreeDocker->isHidden()) + partTreeDocker->show(); disconnect(m_partListDockerVisibleSwitchConnection); } }); @@ -549,6 +589,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(graphicsWidget, &SkeletonGraphicsWidget::scaleNodeByAddRadius, m_document, &SkeletonDocument::scaleNodeByAddRadius); connect(graphicsWidget, &SkeletonGraphicsWidget::moveNodeBy, m_document, &SkeletonDocument::moveNodeBy); connect(graphicsWidget, &SkeletonGraphicsWidget::setNodeOrigin, m_document, &SkeletonDocument::setNodeOrigin); + connect(graphicsWidget, &SkeletonGraphicsWidget::setNodeBoneMark, m_document, &SkeletonDocument::setNodeBoneMark); connect(graphicsWidget, &SkeletonGraphicsWidget::removeNode, m_document, &SkeletonDocument::removeNode); connect(graphicsWidget, &SkeletonGraphicsWidget::setEditMode, m_document, &SkeletonDocument::setEditMode); connect(graphicsWidget, &SkeletonGraphicsWidget::removeEdge, m_document, &SkeletonDocument::removeEdge); @@ -589,6 +630,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(m_document, &SkeletonDocument::edgeAdded, graphicsWidget, &SkeletonGraphicsWidget::edgeAdded); connect(m_document, &SkeletonDocument::edgeRemoved, graphicsWidget, &SkeletonGraphicsWidget::edgeRemoved); connect(m_document, &SkeletonDocument::nodeRadiusChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeRadiusChanged); + connect(m_document, &SkeletonDocument::nodeBoneMarkChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeBoneMarkChanged); connect(m_document, &SkeletonDocument::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged); connect(m_document, &SkeletonDocument::partVisibleStateChanged, graphicsWidget, &SkeletonGraphicsWidget::partVisibleStateChanged); connect(m_document, &SkeletonDocument::partDisableStateChanged, graphicsWidget, &SkeletonGraphicsWidget::partVisibleStateChanged); @@ -660,6 +702,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_document->postProcess(); } }); + connect(m_document, &SkeletonDocument::resultMeshChanged, m_document, &SkeletonDocument::generateRig); + connect(m_document, &SkeletonDocument::rigChanged, m_document, &SkeletonDocument::generateRig); connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_document, &SkeletonDocument::generateTexture); connect(m_document, &SkeletonDocument::resultTextureChanged, m_document, &SkeletonDocument::bakeAmbientOcclusionTexture); @@ -678,6 +722,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(m_document, &SkeletonDocument::skeletonChanged, this, &SkeletonDocumentWindow::documentChanged); connect(m_document, &SkeletonDocument::turnaroundChanged, this, &SkeletonDocumentWindow::documentChanged); connect(m_document, &SkeletonDocument::optionsChanged, this, &SkeletonDocumentWindow::documentChanged); + connect(m_document, &SkeletonDocument::rigChanged, this, &SkeletonDocumentWindow::documentChanged); connect(m_modelRenderWidget, &ModelWidget::customContextMenuRequested, [=](const QPoint &pos) { graphicsWidget->showContextMenu(graphicsWidget->mapFromGlobal(m_modelRenderWidget->mapToGlobal(pos))); @@ -687,11 +732,26 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(m_document, &SkeletonDocument::ylockStateChanged, this, &SkeletonDocumentWindow::updateYlockButtonState); connect(m_document, &SkeletonDocument::zlockStateChanged, this, &SkeletonDocumentWindow::updateZlockButtonState); connect(m_document, &SkeletonDocument::radiusLockStateChanged, this, &SkeletonDocumentWindow::updateRadiusLockButtonState); + + connect(rigWidget, &RigWidget::setRigType, m_document, &SkeletonDocument::setRigType); + + connect(m_document, &SkeletonDocument::rigTypeChanged, rigWidget, &RigWidget::rigTypeChanged); + connect(m_document, &SkeletonDocument::resultRigChanged, rigWidget, &RigWidget::updateResultInfo); + connect(m_document, &SkeletonDocument::resultRigChanged, [=]() { + MeshLoader *resultRigWeightMesh = m_document->takeResultRigWeightMesh(); + if (nullptr == resultRigWeightMesh) { + rigWidget->rigWeightRenderWidget()->hide(); + } else { + rigWidget->rigWeightRenderWidget()->updateMesh(resultRigWeightMesh); + rigWidget->rigWeightRenderWidget()->show(); + rigWidget->rigWeightRenderWidget()->update(); + } + }); connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady); QTimer *timer = new QTimer(this); - timer->setInterval(100); + timer->setInterval(250); connect(timer, &QTimer::timeout, [=] { QWidget *focusedWidget = QApplication::focusWidget(); //qDebug() << (focusedWidget ? ("Focused on:" + QString(focusedWidget->metaObject()->className()) + " title:" + focusedWidget->windowTitle()) : "No Focus") << " isActive:" << isActiveWindow(); @@ -977,6 +1037,18 @@ void SkeletonDocumentWindow::exportObjPlusMaterialsResult() QApplication::restoreOverrideCursor(); } +void SkeletonDocumentWindow::exportRenderedResult() +{ + QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), + tr("Image (*.png)")); + if (filename.isEmpty()) { + return; + } + QApplication::setOverrideCursor(Qt::WaitCursor); + exportRenderedAsImage(filename); + QApplication::restoreOverrideCursor(); +} + void SkeletonDocumentWindow::showExportPreview() { if (nullptr == m_exportPreviewWidget) { @@ -1002,9 +1074,13 @@ void SkeletonDocumentWindow::exportGltfResult() if (filename.isEmpty()) { return; } + if (!m_document->isExportReady()) { + qDebug() << "Export but document is not export ready"; + return; + } QApplication::setOverrideCursor(Qt::WaitCursor); MeshResultContext skeletonResult = m_document->currentPostProcessedResultContext(); - GLTFFileWriter gltfFileWriter(skeletonResult,filename); + GltfFileWriter gltfFileWriter(skeletonResult, m_document->resultRigBones(), m_document->resultRigWeights(), filename); gltfFileWriter.save(); if (m_document->textureImage) m_document->textureImage->save(gltfFileWriter.textureFilenameInGltf()); @@ -1052,3 +1128,14 @@ void SkeletonDocumentWindow::updateRadiusLockButtonState() else m_radiusLockButton->setStyleSheet("QPushButton {color: " + Theme::white.name() + "}"); } + +void SkeletonDocumentWindow::exportRenderedAsImage(const QString &filename) +{ + ModelOfflineRender offlineRender(m_modelRenderWidget); + offlineRender.setXRotation(m_modelRenderWidget->xRot()); + offlineRender.setYRotation(m_modelRenderWidget->yRot()); + offlineRender.setZRotation(m_modelRenderWidget->zRot()); + offlineRender.updateMesh(m_document->takeResultMesh()); + QImage renderedImage = offlineRender.toImage(QSize(1024, 1024)); + renderedImage.save(filename); +} diff --git a/src/skeletondocumentwindow.h b/src/skeletondocumentwindow.h index 789aaf96..38c5d5ee 100644 --- a/src/skeletondocumentwindow.h +++ b/src/skeletondocumentwindow.h @@ -10,6 +10,8 @@ #include "skeletondocument.h" #include "modelwidget.h" #include "exportpreviewwidget.h" +#include "rigwidget.h" +#include "skeletonbonemark.h" class SkeletonGraphicsWidget; @@ -37,6 +39,7 @@ public slots: void exportObjResult(); void exportObjPlusMaterialsResult(); void exportGltfResult(); + void exportRenderedResult(); void showExportPreview(); void newWindow(); void newDocument(); @@ -57,6 +60,7 @@ private: void initLockButton(QPushButton *button); void setCurrentFilename(const QString &filename); void updateTitle(); + void exportRenderedAsImage(const QString &filename); private: SkeletonDocument *m_document; bool m_firstShow; @@ -80,7 +84,8 @@ private: QAction *m_exportAsObjAction; QAction *m_exportAsObjPlusMaterialsAction; - QAction *m_exportAsGltfAction; + QAction *m_exportAction; + QAction *m_exportRenderedAsImageAction; QMenu *m_editMenu; QAction *m_addAction; @@ -110,14 +115,21 @@ private: QAction *m_selectPartAllAction; QAction *m_unselectAllAction; + QMenu *m_markAsMenu; + QAction *m_markAsNoneAction; + QAction *m_markAsActions[(int)SkeletonBoneMark::Count - 1]; + QMenu *m_viewMenu; QAction *m_resetModelWidgetPosAction; - QAction *m_showPartsListAction; - QAction *m_showDebugDialogAction; QAction *m_toggleWireframeAction; QAction *m_toggleSmoothNormalAction; QAction *m_showMotionsListAction; + QMenu *m_windowMenu; + QAction *m_showPartsListAction; + QAction *m_showDebugDialogAction; + QAction *m_showRigAction; + QMenu *m_helpMenu; QAction *m_viewSourceAction; QAction *m_aboutAction; diff --git a/src/skeletongenerator.cpp b/src/skeletongenerator.cpp deleted file mode 100644 index 3aed5023..00000000 --- a/src/skeletongenerator.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include -#include -#include -#include -#include "skeletongenerator.h" -#include "positionmap.h" -#include "meshlite.h" -#include "jointnodetree.h" - -SkeletonGenerator::SkeletonGenerator(const MeshResultContext &meshResultContext) : - m_resultSkeletonMesh(nullptr) -{ - m_meshResultContext = new MeshResultContext; - *m_meshResultContext = meshResultContext; -} - -SkeletonGenerator::~SkeletonGenerator() -{ - delete m_resultSkeletonMesh; - delete m_meshResultContext; -} - -MeshLoader *SkeletonGenerator::takeResultSkeletonMesh() -{ - MeshLoader *resultSkeletonMesh = m_resultSkeletonMesh; - m_resultSkeletonMesh = nullptr; - return resultSkeletonMesh; -} - -MeshResultContext *SkeletonGenerator::takeResultContext() -{ - MeshResultContext *resultContext = m_meshResultContext; - m_meshResultContext = nullptr; - return resultContext; -} - -MeshLoader *SkeletonGenerator::createSkeletonMesh() -{ - void *meshliteContext = meshlite_create_context(); - int sklt = meshlite_skeletonmesh_create(meshliteContext); - - JointNodeTree jointNodeTree(*m_meshResultContext); - for (const auto &joint: jointNodeTree.joints()) { - for (const auto &childIndex: joint.children) { - const auto &child = jointNodeTree.joints()[childIndex]; - meshlite_skeletonmesh_add_bone(meshliteContext, sklt, - joint.position.x(), joint.position.y(), joint.position.z(), - child.position.x(), child.position.y(), child.position.z()); - } - } - - int meshId = meshlite_skeletonmesh_generate_mesh(meshliteContext, sklt); - MeshLoader *skeletonMesh = new MeshLoader(meshliteContext, meshId, -1, Theme::green); - - meshlite_destroy_context(meshliteContext); - - return skeletonMesh; -} - -void SkeletonGenerator::generate() -{ - Q_ASSERT(nullptr == m_resultSkeletonMesh); - m_resultSkeletonMesh = createSkeletonMesh(); -} - -void SkeletonGenerator::process() -{ - generate(); - - this->moveToThread(QGuiApplication::instance()->thread()); - emit finished(); -} diff --git a/src/skeletongenerator.h b/src/skeletongenerator.h deleted file mode 100644 index 21928805..00000000 --- a/src/skeletongenerator.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef SKELETON_GENERATOR_H -#define SKELETON_GENERATOR_H -#include -#include "meshresultcontext.h" -#include "meshloader.h" - -// https://raw.githubusercontent.com/KhronosGroup/glTF/master/specification/2.0/figures/gltfOverview-2.0.0a.png - -class SkeletonGenerator : public QObject -{ - Q_OBJECT -public: - SkeletonGenerator(const MeshResultContext &meshResultContext); - ~SkeletonGenerator(); - void generate(); - MeshLoader *takeResultSkeletonMesh(); - MeshResultContext *takeResultContext(); -signals: - void finished(); -public slots: - void process(); -private: - MeshLoader *createSkeletonMesh(); -private: - MeshResultContext *m_meshResultContext; - MeshLoader *m_resultSkeletonMesh; -}; - -#endif diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index 2facb5e6..5293a2e4 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -13,6 +13,7 @@ #include "theme.h" #include "dust3dutil.h" #include "skeletonxml.h" +#include "markiconcreator.h" SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document) : m_document(document), @@ -224,6 +225,31 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos) } } + QAction markAsNoneAction(tr("None"), this); + QAction *markAsActions[(int)SkeletonBoneMark::Count - 1]; + for (int i = 0; i < (int)SkeletonBoneMark::Count - 1; i++) { + markAsActions[i] = nullptr; + } + if (hasNodeSelection()) { + QMenu *subMenu = contextMenu.addMenu(tr("Mark As")); + + connect(&markAsNoneAction, &QAction::triggered, [=]() { + setSelectedNodesBoneMark(SkeletonBoneMark::None); + }); + subMenu->addAction(&markAsNoneAction); + + subMenu->addSeparator(); + + for (int i = 0; i < (int)SkeletonBoneMark::Count - 1; i++) { + SkeletonBoneMark boneMark = (SkeletonBoneMark)(i + 1); + markAsActions[i] = new QAction(MarkIconCreator::createIcon(boneMark), SkeletonBoneMarkToDispName(boneMark), this); + connect(markAsActions[i], &QAction::triggered, [=]() { + setSelectedNodesBoneMark(boneMark); + }); + subMenu->addAction(markAsActions[i]); + } + } + QAction selectAllAction(tr("Select All"), this); if (hasItems()) { connect(&selectAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::selectAll); @@ -243,6 +269,10 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos) } contextMenu.exec(mapToGlobal(pos)); + + for (int i = 0; i < (int)SkeletonBoneMark::Count - 1; i++) { + delete markAsActions[i]; + } } bool SkeletonGraphicsWidget::hasSelection() @@ -1575,12 +1605,15 @@ void SkeletonGraphicsWidget::nodeAdded(QUuid nodeId) qDebug() << "New node added but node item already exist:" << nodeId; return; } + QColor markColor = SkeletonBoneMarkToColor(node->boneMark); SkeletonGraphicsNodeItem *mainProfileItem = new SkeletonGraphicsNodeItem(SkeletonProfile::Main); SkeletonGraphicsNodeItem *sideProfileItem = new SkeletonGraphicsNodeItem(SkeletonProfile::Side); mainProfileItem->setOrigin(scenePosFromUnified(QPointF(node->x, node->y))); sideProfileItem->setOrigin(scenePosFromUnified(QPointF(node->z, node->y))); mainProfileItem->setRadius(sceneRadiusFromUnified(node->radius)); sideProfileItem->setRadius(sceneRadiusFromUnified(node->radius)); + mainProfileItem->setMarkColor(markColor); + sideProfileItem->setMarkColor(markColor); mainProfileItem->setId(nodeId); sideProfileItem->setId(nodeId); scene()->addItem(mainProfileItem); @@ -1696,6 +1729,23 @@ void SkeletonGraphicsWidget::nodeRadiusChanged(QUuid nodeId) it->second.second->setRadius(sceneRadius); } +void SkeletonGraphicsWidget::nodeBoneMarkChanged(QUuid nodeId) +{ + const SkeletonNode *node = m_document->findNode(nodeId); + if (nullptr == node) { + qDebug() << "Node changed but node id not exist:" << nodeId; + return; + } + auto it = nodeItemMap.find(nodeId); + if (it == nodeItemMap.end()) { + qDebug() << "Node not found:" << nodeId; + return; + } + QColor markColor = SkeletonBoneMarkToColor(node->boneMark); + it->second.first->setMarkColor(markColor); + it->second.second->setMarkColor(markColor); +} + void SkeletonGraphicsWidget::nodeOriginChanged(QUuid nodeId) { const SkeletonNode *node = m_document->findNode(nodeId); @@ -2303,4 +2353,17 @@ void SkeletonGraphicsWidget::ikMove(QUuid endEffectorId, QVector3D target) thread->start(); } - +void SkeletonGraphicsWidget::setSelectedNodesBoneMark(SkeletonBoneMark boneMark) +{ + std::set nodeIdSet; + std::set edgeIdSet; + readSkeletonNodeAndEdgeIdSetFromRangeSelection(&nodeIdSet, &edgeIdSet); + if (!nodeIdSet.empty()) { + emit batchChangeBegin(); + for (const auto &id: nodeIdSet) { + emit setNodeBoneMark(id, boneMark); + } + emit batchChangeEnd(); + emit groupOperationAdded(); + } +} diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h index 78dcba48..541cefd4 100644 --- a/src/skeletongraphicswidget.h +++ b/src/skeletongraphicswidget.h @@ -384,6 +384,7 @@ signals: void setYlockState(bool locked); void setZlockState(bool locked); void setNodeOrigin(QUuid nodeId, float x, float y, float z); + void setNodeBoneMark(QUuid nodeId, SkeletonBoneMark mark); void zoomRenderedModelBy(float delta); void switchNodeXZ(QUuid nodeId); void enableAllPositionRelatedLocks(); @@ -423,6 +424,7 @@ public slots: void nodeRemoved(QUuid nodeId); void edgeRemoved(QUuid edgeId); void nodeRadiusChanged(QUuid nodeId); + void nodeBoneMarkChanged(QUuid nodeId); void nodeOriginChanged(QUuid nodeId); void edgeChanged(QUuid edgeId); void turnaroundChanged(); @@ -466,6 +468,7 @@ public slots: void disableBackgroundBlur(); void ikMove(QUuid endEffectorId, QVector3D target); void ikMoveReady(); + void setSelectedNodesBoneMark(SkeletonBoneMark boneMark); void timeToRemoveDeferredNodesAndEdges(); void switchSelectedXZ(); private slots: diff --git a/src/skeletonxml.cpp b/src/skeletonxml.cpp index 1512435e..db2a0f60 100644 --- a/src/skeletonxml.cpp +++ b/src/skeletonxml.cpp @@ -99,14 +99,27 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &reader) { std::stack componentStack; + std::vector elementNameStack; while (!reader.atEnd()) { reader.readNext(); + if (!reader.isStartElement() && !reader.isEndElement()) + continue; + QString baseName = reader.name().toString(); + if (reader.isStartElement()) + elementNameStack.push_back(baseName); + QStringList nameItems; + for (const auto &nameItem: elementNameStack) { + nameItems.append(nameItem); + } + QString fullName = nameItems.join("."); + if (reader.isEndElement()) + elementNameStack.pop_back(); if (reader.isStartElement()) { - if (reader.name() == "canvas") { + if (fullName == "canvas") { foreach(const QXmlStreamAttribute &attr, reader.attributes()) { snapshot->canvas[attr.name().toString()] = attr.value().toString(); } - } else if (reader.name() == "node") { + } else if (fullName == "canvas.nodes.node") { QString nodeId = reader.attributes().value("id").toString(); if (nodeId.isEmpty()) continue; @@ -114,7 +127,7 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea foreach(const QXmlStreamAttribute &attr, reader.attributes()) { (*nodeMap)[attr.name().toString()] = attr.value().toString(); } - } else if (reader.name() == "edge") { + } else if (fullName == "canvas.edges.edge") { QString edgeId = reader.attributes().value("id").toString(); if (edgeId.isEmpty()) continue; @@ -122,7 +135,7 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea foreach(const QXmlStreamAttribute &attr, reader.attributes()) { (*edgeMap)[attr.name().toString()] = attr.value().toString(); } - } else if (reader.name() == "part") { + } else if (fullName == "canvas.parts.part") { QString partId = reader.attributes().value("id").toString(); if (partId.isEmpty()) continue; @@ -130,7 +143,7 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea foreach(const QXmlStreamAttribute &attr, reader.attributes()) { (*partMap)[attr.name().toString()] = attr.value().toString(); } - } else if (reader.name() == "partId") { + } else if (fullName == "canvas.partIdList.partId") { QString partId = reader.attributes().value("id").toString(); if (partId.isEmpty()) continue; @@ -143,7 +156,7 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea if (!childrenIds.isEmpty()) childrenIds += ","; childrenIds += componentId; - } else if (reader.name() == "component") { + } else if (fullName.startsWith("canvas.components.component")) { QString componentId = reader.attributes().value("id").toString(); QString parentId; if (!componentStack.empty()) @@ -161,7 +174,7 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea parentChildrenIds += componentId; } } else if (reader.isEndElement()) { - if (reader.name() == "component") { + if (fullName.startsWith("canvas.components.component")) { componentStack.pop(); } }