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(); } }