Add auto-rig

This is the second version of auto-rig, differing from the first version, the user should pick up a rig type, unless no rig will be generated. Currently, only supports Tetrapod without tail. Export as glTF format to test this feature.

This commit also achive better quality quads than before.
master
Jeremy Hu 2018-09-14 17:45:05 +08:00
parent a9b379a77a
commit 48bc8df5c3
39 changed files with 2631 additions and 233 deletions

View File

@ -140,6 +140,30 @@ HEADERS += src/infolabel.h
SOURCES += src/graphicscontainerwidget.cpp SOURCES += src/graphicscontainerwidget.cpp
HEADERS += src/graphicscontainerwidget.h 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 SOURCES += src/main.cpp
HEADERS += src/version.h HEADERS += src/version.h

847
src/autorigger.cpp Normal file
View File

@ -0,0 +1,847 @@
#include <QDebug>
#include <cmath>
#include "theme.h"
#include "skeletonbonemark.h"
#include "autorigger.h"
AutoRigger::AutoRigger(const std::vector<QVector3D> &verticesPositions,
const std::set<MeshSplitterTriangle> &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<MeshSplitterTriangle> &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<MeshSplitterTriangle> intersection;
std::set_intersection(bodyTriangles.begin(), bodyTriangles.end(),
mark.bigGroup().begin(), mark.bigGroup().end(),
std::insert_iterator<std::set<MeshSplitterTriangle>>(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<MeshSplitterTriangle> &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<std::pair<QtMsgType, QString>> &AutoRigger::messages()
{
return m_messages;
}
void AutoRigger::addTrianglesToVertices(const std::set<MeshSplitterTriangle> &triangles, std::set<int> &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<std::pair<SkeletonBoneMark, SkeletonSide>> 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<int> &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<int> &vertices)
{
QVector3D minX, minY, minZ, maxX, maxY, maxZ;
resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ);
return minX;
}
QVector3D AutoRigger::findMaxX(const std::set<int> &vertices)
{
QVector3D minX, minY, minZ, maxX, maxY, maxZ;
resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ);
return maxX;
}
QVector3D AutoRigger::findMinY(const std::set<int> &vertices)
{
QVector3D minX, minY, minZ, maxX, maxY, maxZ;
resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ);
return minY;
}
QVector3D AutoRigger::findMaxY(const std::set<int> &vertices)
{
QVector3D minX, minY, minZ, maxX, maxY, maxZ;
resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ);
return maxY;
}
QVector3D AutoRigger::findMinZ(const std::set<int> &vertices)
{
QVector3D minX, minY, minZ, maxX, maxY, maxZ;
resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ);
return minZ;
}
QVector3D AutoRigger::findMaxZ(const std::set<int> &vertices)
{
QVector3D minX, minY, minZ, maxX, maxY, maxZ;
resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ);
return maxZ;
}
void AutoRigger::splitVerticesByY(const std::set<int> &vertices, float y, std::set<int> &greaterEqualThanVertices, std::set<int> &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<int> &vertices, float x, std::set<int> &greaterEqualThanVertices, std::set<int> &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<int> &vertices, float z, std::set<int> &greaterEqualThanVertices, std::set<int> &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<AutoRiggerBone> &AutoRigger::resultBones()
{
return m_resultBones;
}
const std::map<int, AutoRiggerVertexWeights> &AutoRigger::resultWeights()
{
return m_resultWeights;
}
void AutoRigger::addVerticesToWeights(const std::set<int> &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<MeshSplitterTriangle> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> bodyVertices;
addTrianglesToVertices(bodyTriangles, bodyVertices);
std::set<int> bodyVerticesAfterShoulder;
std::set<int> neckVertices;
{
std::set<int> 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<int> 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<int> bodyVerticesAfterChest;
std::set<int> 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<int> bodyVerticesBeforeSpine;
std::set<int> 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<int> leftElbowMarkVertices;
addTrianglesToVertices(m_marks[leftElbowIndicies->second[0]].markTriangles, leftElbowMarkVertices);
std::set<int> leftUpperArmVertices;
{
std::set<int> 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<int> leftArmVerticesSinceElbow;
std::set<int> leftLowerArmVertices;
{
std::set<int> 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<int> leftWristMarkVertices;
addTrianglesToVertices(m_marks[leftWristIndicies->second[0]].markTriangles, leftWristMarkVertices);
{
std::set<int> 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<int> leftHandVertices;
{
std::set<int> 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<int> rightElbowMarkVertices;
addTrianglesToVertices(m_marks[rightElbowIndicies->second[0]].markTriangles, rightElbowMarkVertices);
std::set<int> rightUpperArmVertices;
{
std::set<int> 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<int> rightArmVerticesSinceElbow;
std::set<int> rightLowerArmVertices;
{
std::set<int> 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<int> rightWristMarkVertices;
addTrianglesToVertices(m_marks[rightWristIndicies->second[0]].markTriangles, rightWristMarkVertices);
{
std::set<int> 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<int> rightHandVertices;
{
std::set<int> 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<int> leftKneeMarkVertices;
addTrianglesToVertices(m_marks[leftKneeIndicies->second[0]].markTriangles, leftKneeMarkVertices);
std::set<int> leftUpperLegVertices;
{
std::set<int> leftLegVerticesAfterKnee;
QVector3D minY = findMinY(leftKneeMarkVertices);
splitVerticesByY(leftLegVertices, minY.y(), leftUpperLegVertices, leftLegVerticesAfterKnee);
}
// 4.1.2 Collect vertices for left lower leg:
std::set<int> leftLegVerticesSinceKnee;
std::set<int> leftLowerLegVertices;
{
std::set<int> leftLegVerticesBeforeKnee;
QVector3D maxY = findMaxY(leftKneeMarkVertices);
splitVerticesByY(leftLegVertices, maxY.y(), leftLegVerticesBeforeKnee, leftLegVerticesSinceKnee);
}
std::set<int> leftAnkleMarkVertices;
addTrianglesToVertices(m_marks[leftAnkleIndicies->second[0]].markTriangles, leftAnkleMarkVertices);
{
std::set<int> leftLegVerticesAfterAnkle;
QVector3D minY = findMinY(leftAnkleMarkVertices);
splitVerticesByY(leftLegVerticesSinceKnee, minY.y(), leftLowerLegVertices, leftLegVerticesAfterAnkle);
}
// 4.1.3 Collect vertices for left foot:
std::set<int> leftFootVertices;
{
std::set<int> leftLegVerticesBeforeAnkle;
QVector3D maxY = findMaxY(leftAnkleMarkVertices);
splitVerticesByY(leftLegVerticesSinceKnee, maxY.y(), leftLegVerticesBeforeAnkle, leftFootVertices);
}
// 4.2.1 Collect vertices for right upper leg:
std::set<int> rightKneeMarkVertices;
addTrianglesToVertices(m_marks[rightKneeIndicies->second[0]].markTriangles, rightKneeMarkVertices);
std::set<int> rightUpperLegVertices;
{
std::set<int> rightLegVerticesAfterKnee;
QVector3D minY = findMinY(rightKneeMarkVertices);
splitVerticesByY(rightLegVertices, minY.y(), rightUpperLegVertices, rightLegVerticesAfterKnee);
}
// 4.2.2 Collect vertices for right lower leg:
std::set<int> rightLegVerticesSinceKnee;
std::set<int> rightLowerLegVertices;
{
std::set<int> rightLegVerticesBeforeKnee;
QVector3D maxY = findMaxY(rightKneeMarkVertices);
splitVerticesByY(rightLegVertices, maxY.y(), rightLegVerticesBeforeKnee, rightLegVerticesSinceKnee);
}
std::set<int> rightAnkleMarkVertices;
addTrianglesToVertices(m_marks[rightAnkleIndicies->second[0]].markTriangles, rightAnkleMarkVertices);
{
std::set<int> rightLegVerticesAfterAnkle;
QVector3D minY = findMinY(rightAnkleMarkVertices);
splitVerticesByY(rightLegVerticesSinceKnee, minY.y(), rightLowerLegVertices, rightLegVerticesAfterAnkle);
}
// 4.2.3 Collect vertices for right foot:
std::set<int> rightFootVertices;
{
std::set<int> rightLegVerticesBeforeAnkle;
QVector3D maxY = findMaxY(rightAnkleMarkVertices);
splitVerticesByY(rightLegVerticesSinceKnee, maxY.y(), rightLegVerticesBeforeAnkle, rightFootVertices);
}
// 5. Generate bones
std::map<QString, int> 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<QString> &AutoRigger::missingMarkNames()
{
return m_missingMarkNames;
}
const std::vector<QString> &AutoRigger::errorMarkNames()
{
return m_errorMarkNames;
}

126
src/autorigger.h Normal file
View File

@ -0,0 +1,126 @@
#ifndef AUTO_RIGGER_H
#define AUTO_RIGGER_H
#include <QtGlobal>
#include <QVector3D>
#include <QObject>
#include <QColor>
#include "meshsplitter.h"
#include "skeletonbonemark.h"
#include "rigtype.h"
class AutoRiggerMark
{
public:
SkeletonBoneMark boneMark;
SkeletonSide boneSide;
QVector3D bonePosition;
std::set<MeshSplitterTriangle> markTriangles;
const std::set<MeshSplitterTriangle> &bigGroup() const
{
return m_firstGroup.size() > m_secondGroup.size() ?
m_firstGroup :
m_secondGroup;
}
const std::set<MeshSplitterTriangle> &smallGroup() const
{
return m_firstGroup.size() > m_secondGroup.size() ?
m_secondGroup :
m_firstGroup;
}
bool split(const std::set<MeshSplitterTriangle> &input)
{
return MeshSplitter::split(input, markTriangles, m_firstGroup, m_secondGroup);
}
private:
std::set<MeshSplitterTriangle> m_firstGroup;
std::set<MeshSplitterTriangle> m_secondGroup;
};
class AutoRiggerBone
{
public:
QString name;
int index;
QVector3D headPosition;
QVector3D tailPosition;
QColor color;
std::vector<int> 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<int, float> &a, const std::pair<int, float> &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<std::pair<int, float>> m_boneRawWeights;
};
class AutoRigger : public QObject
{
public:
AutoRigger(const std::vector<QVector3D> &verticesPositions,
const std::set<MeshSplitterTriangle> &inputTriangles);
bool addMarkGroup(SkeletonBoneMark boneMark, SkeletonSide boneSide, QVector3D bonePosition,
const std::set<MeshSplitterTriangle> &markTriangles);
const std::vector<std::pair<QtMsgType, QString>> &messages();
bool rig();
const std::vector<AutoRiggerBone> &resultBones();
const std::map<int, AutoRiggerVertexWeights> &resultWeights();
const std::vector<QString> &missingMarkNames();
const std::vector<QString> &errorMarkNames();
private:
bool validate();
void addTrianglesToVertices(const std::set<MeshSplitterTriangle> &triangles, std::set<int> &vertices);
bool calculateBodyTriangles(std::set<MeshSplitterTriangle> &bodyTriangles);
bool isCutOffSplitter(SkeletonBoneMark boneMark);
void resolveBoundingBox(const std::set<int> &vertices, QVector3D &xMin, QVector3D &xMax, QVector3D &yMin, QVector3D &yMax, QVector3D &zMin, QVector3D &zMax);
QVector3D findMinX(const std::set<int> &vertices);
QVector3D findMaxX(const std::set<int> &vertices);
QVector3D findMinY(const std::set<int> &vertices);
QVector3D findMaxY(const std::set<int> &vertices);
QVector3D findMinZ(const std::set<int> &vertices);
QVector3D findMaxZ(const std::set<int> &vertices);
void splitVerticesByY(const std::set<int> &vertices, float y, std::set<int> &greaterEqualThanVertices, std::set<int> &lessThanVertices);
void splitVerticesByX(const std::set<int> &vertices, float x, std::set<int> &greaterEqualThanVertices, std::set<int> &lessThanVertices);
void splitVerticesByZ(const std::set<int> &vertices, float z, std::set<int> &greaterEqualThanVertices, std::set<int> &lessThanVertices);
void addVerticesToWeights(const std::set<int> &vertices, int boneIndex);
std::vector<std::pair<QtMsgType, QString>> m_messages;
std::vector<QVector3D> m_verticesPositions;
std::set<MeshSplitterTriangle> m_inputTriangles;
std::vector<AutoRiggerMark> m_marks;
std::map<std::pair<SkeletonBoneMark, SkeletonSide>, std::vector<int>> m_marksMap;
std::vector<AutoRiggerBone> m_resultBones;
std::map<int, AutoRiggerVertexWeights> m_resultWeights;
std::vector<QString> m_missingMarkNames;
std::vector<QString> m_errorMarkNames;
};
#endif

View File

@ -2,6 +2,7 @@
#include <QSizePolicy> #include <QSizePolicy>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QComboBox>
#include "exportpreviewwidget.h" #include "exportpreviewwidget.h"
#include "aboutwidget.h" #include "aboutwidget.h"
#include "version.h" #include "version.h"
@ -13,27 +14,34 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
m_previewLabel(nullptr), m_previewLabel(nullptr),
m_spinnerWidget(nullptr) m_spinnerWidget(nullptr)
{ {
QVBoxLayout *toolButtonLayout = new QVBoxLayout; QHBoxLayout *toolButtonLayout = new QHBoxLayout;
toolButtonLayout->setSpacing(0); toolButtonLayout->setSpacing(0);
toolButtonLayout->setContentsMargins(5, 10, 4, 0); //toolButtonLayout->setContentsMargins(5, 10, 4, 0);
m_previewLabel = new QLabel; m_previewLabel = new QLabel;
m_previewLabel->setMinimumSize(128, 128); m_previewLabel->setMinimumSize(128, 128);
m_previewLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_previewLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QPushButton *regenerateButton = new QPushButton(QChar(fa::recycle)); //QPushButton *regenerateButton = new QPushButton(QChar(fa::recycle));
initAwesomeButton(regenerateButton); //initAwesomeButton(regenerateButton);
QPushButton *regenerateButton = new QPushButton(tr("Regenerate"));
connect(this, &ExportPreviewWidget::regenerate, this, &ExportPreviewWidget::checkSpinner); connect(this, &ExportPreviewWidget::regenerate, this, &ExportPreviewWidget::checkSpinner);
connect(regenerateButton, &QPushButton::clicked, this, &ExportPreviewWidget::regenerate); connect(regenerateButton, &QPushButton::clicked, this, &ExportPreviewWidget::regenerate);
m_saveButton = new QPushButton(QChar(fa::save)); //m_saveButton = new QPushButton(QChar(fa::save));
initAwesomeButton(m_saveButton); //initAwesomeButton(m_saveButton);
m_saveButton = new QPushButton(tr("Save"));
connect(m_saveButton, &QPushButton::clicked, this, &ExportPreviewWidget::save); connect(m_saveButton, &QPushButton::clicked, this, &ExportPreviewWidget::save);
m_saveButton->hide(); m_saveButton->hide();
QComboBox *exportFormatSelectBox = new QComboBox;
exportFormatSelectBox->addItem(tr("glTF"));
exportFormatSelectBox->setCurrentIndex(0);
toolButtonLayout->addWidget(exportFormatSelectBox);
toolButtonLayout->addWidget(regenerateButton); toolButtonLayout->addWidget(regenerateButton);
toolButtonLayout->addWidget(m_saveButton);
toolButtonLayout->addStretch(); toolButtonLayout->addStretch();
toolButtonLayout->addWidget(m_saveButton);
QGridLayout *containerLayout = new QGridLayout; QGridLayout *containerLayout = new QGridLayout;
containerLayout->setSpacing(0); containerLayout->setSpacing(0);
@ -51,12 +59,22 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
renderLayout->setContentsMargins(20, 0, 20, 0); renderLayout->setContentsMargins(20, 0, 20, 0);
renderLayout->addWidget(m_textureRenderWidget); renderLayout->addWidget(m_textureRenderWidget);
QHBoxLayout *mainLayout = new QHBoxLayout; QWidget *hrLightWidget = new QWidget;
mainLayout->setSpacing(0); hrLightWidget->setFixedHeight(1);
mainLayout->setContentsMargins(0, 0, 0, 0); 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(toolButtonLayout);
mainLayout->addLayout(containerLayout);
mainLayout->addLayout(renderLayout);
setLayout(mainLayout); setLayout(mainLayout);
setMinimumSize(256, 256); setMinimumSize(256, 256);
@ -68,7 +86,7 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
m_spinnerWidget->setNumberOfLines(12); m_spinnerWidget->setNumberOfLines(12);
m_spinnerWidget->hide(); m_spinnerWidget->hide();
setWindowTitle(APP_NAME); setWindowTitle(tr("Export") + tr(" - ") + APP_NAME);
emit updateTexturePreview(); emit updateTexturePreview();
} }

View File

@ -17,9 +17,12 @@
// http://quaternions.online/ // http://quaternions.online/
// https://en.m.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions?wprov=sfla1 // 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<AutoRiggerBone> *resultRigBones,
const std::map<int, AutoRiggerVertexWeights> *resultRigWeights,
const QString &filename) :
m_filename(filename), m_filename(filename),
m_outputNormal(true), m_outputNormal(true),
m_outputAnimation(true), m_outputAnimation(true),
@ -30,12 +33,6 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
QString textureFilenameWithoutPath = nameInfo.completeBaseName() + ".png"; QString textureFilenameWithoutPath = nameInfo.completeBaseName() + ".png";
m_textureFilename = nameInfo.path() + QDir::separator() + textureFilenameWithoutPath; 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; QByteArray binaries;
QDataStream stream(&binaries, QIODevice::WriteOnly); QDataStream stream(&binaries, QIODevice::WriteOnly);
stream.setFloatingPointPrecision(QDataStream::SinglePrecision); stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
@ -50,6 +47,75 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
int bufferViewIndex = 0; int bufferViewIndex = 0;
int bufferViewFromOffset; 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]["sampler"] = 0;
m_json["textures"][0]["source"] = 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); m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["NORMAL"] = bufferViewIndex + (++attributeIndex);
if (m_outputUv) if (m_outputUv)
m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["TEXCOORD_0"] = bufferViewIndex + (++attributeIndex); 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"]["baseColorTexture"]["index"] = 0;
m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["metallicFactor"] = 0.0; m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["metallicFactor"] = 0.0;
m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["roughnessFactor"] = 1.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"; m_json["accessors"][bufferViewIndex]["type"] = "VEC2";
bufferViewIndex++; 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]["uri"] = QString("data:application/octet-stream;base64," + binaries.toBase64()).toUtf8().constData();
m_json["buffers"][0]["byteLength"] = binaries.size(); m_json["buffers"][0]["byteLength"] = binaries.size();
} }
const QString &GLTFFileWriter::textureFilenameInGltf() const QString &GltfFileWriter::textureFilenameInGltf()
{ {
return m_textureFilename; return m_textureFilename;
} }
bool GLTFFileWriter::save() bool GltfFileWriter::save()
{ {
QFile file(m_filename); QFile file(m_filename);
if (!file.open(QIODevice::WriteOnly)) { if (!file.open(QIODevice::WriteOnly)) {
@ -210,3 +358,37 @@ bool GLTFFileWriter::save()
file.write(QString::fromStdString(m_json.dump(4)).toUtf8()); file.write(QString::fromStdString(m_json.dump(4)).toUtf8());
return true; return true;
} }
void GltfFileWriter::calculateMatrices(const std::vector<AutoRiggerBone> *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();
}
}

View File

@ -5,26 +5,42 @@
#include <QByteArray> #include <QByteArray>
#include <QMatrix4x4> #include <QMatrix4x4>
#include <vector> #include <vector>
#include <QQuaternion>
#include "meshresultcontext.h" #include "meshresultcontext.h"
#include "json.hpp" #include "json.hpp"
#include "skeletondocument.h" #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<int> children;
};
class GltfFileWriter : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
GLTFFileWriter(MeshResultContext &resultContext, const QString &filename); GltfFileWriter(MeshResultContext &resultContext,
const std::vector<AutoRiggerBone> *resultRigBones,
const std::map<int, AutoRiggerVertexWeights> *resultRigWeights,
const QString &filename);
bool save(); bool save();
const QString &textureFilenameInGltf(); const QString &textureFilenameInGltf();
private: private:
QByteArray m_data; void calculateMatrices(const std::vector<AutoRiggerBone> *resultRigBones);
QString getMatrixStringInColumnOrder(const QMatrix4x4 &mat);
QString m_filename; QString m_filename;
QString m_textureFilename; QString m_textureFilename;
bool m_outputNormal; bool m_outputNormal;
bool m_outputAnimation; bool m_outputAnimation;
bool m_outputUv; bool m_outputUv;
bool m_testOutputAsWhole; bool m_testOutputAsWhole;
std::vector<GltfNode> m_boneNodes;
private: private:
nlohmann::json m_json; nlohmann::json m_json;
public: public:

View File

@ -10,6 +10,7 @@ InfoLabel::InfoLabel(const QString &text, QWidget *parent) :
Theme::initAwesomeLabel(m_icon); Theme::initAwesomeLabel(m_icon);
m_label = new QLabel(text); m_label = new QLabel(text);
m_label->setWordWrap(true);
QHBoxLayout *mainLayout = new QHBoxLayout; QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(m_icon); mainLayout->addWidget(m_icon);

View File

@ -11,6 +11,7 @@
#include <QTextStream> #include <QTextStream>
#include <QCloseEvent> #include <QCloseEvent>
#include <QKeyEvent> #include <QKeyEvent>
#include "version.h"
LogBrowserDialog::LogBrowserDialog(QWidget *parent) : LogBrowserDialog::LogBrowserDialog(QWidget *parent) :
QDialog(parent) QDialog(parent)
@ -39,6 +40,8 @@ LogBrowserDialog::LogBrowserDialog(QWidget *parent) :
resize(400, 300); resize(400, 300);
setWindowTitle(tr("Debug") + tr(" - ") + APP_NAME);
hide(); hide();
} }

23
src/markiconcreator.cpp Normal file
View File

@ -0,0 +1,23 @@
#include <QPixmap>
#include <QPainter>
#include "markiconcreator.h"
#include "theme.h"
std::map<SkeletonBoneMark, QIcon> 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];
}

16
src/markiconcreator.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef MARK_ICON_CREATOR_H
#define MARK_ICON_CREATOR_H
#include <map>
#include <QIcon>
#include "skeletondocument.h"
class MarkIconCreator
{
public:
static QIcon createIcon(SkeletonBoneMark boneMark);
private:
static std::map<SkeletonBoneMark, QIcon> m_iconMap;
static int m_iconSize;
};
#endif

View File

@ -9,8 +9,10 @@
#include "meshutil.h" #include "meshutil.h"
#include "theme.h" #include "theme.h"
#include "positionmap.h" #include "positionmap.h"
#include "meshquadify.h"
bool MeshGenerator::m_enableDebug = false; bool MeshGenerator::m_enableDebug = false;
PositionMap<int> *MeshGenerator::m_forMakePositionKey = new PositionMap<int>;
GeneratedCacheContext::~GeneratedCacheContext() GeneratedCacheContext::~GeneratedCacheContext()
{ {
@ -107,7 +109,8 @@ MeshResultContext *MeshGenerator::takeMeshResultContext()
return meshResultContext; return meshResultContext;
} }
void MeshGenerator::loadVertexSources(void *meshliteContext, int meshId, QUuid partId, const std::map<int, QUuid> &bmeshToNodeIdMap, std::vector<BmeshVertex> &bmeshVertices) void MeshGenerator::loadVertexSources(void *meshliteContext, int meshId, QUuid partId, const std::map<int, QUuid> &bmeshToNodeIdMap, std::vector<BmeshVertex> &bmeshVertices,
std::vector<std::tuple<PositionMapKey, PositionMapKey, PositionMapKey, PositionMapKey>> &bmeshQuads)
{ {
int vertexCount = meshlite_get_vertex_count(meshliteContext, meshId); int vertexCount = meshlite_get_vertex_count(meshliteContext, meshId);
int positionBufferLen = vertexCount * 3; int positionBufferLen = vertexCount * 3;
@ -116,6 +119,7 @@ void MeshGenerator::loadVertexSources(void *meshliteContext, int meshId, QUuid p
int *sourceBuffer = new int[positionBufferLen]; int *sourceBuffer = new int[positionBufferLen];
int sourceCount = meshlite_get_vertex_source_array(meshliteContext, meshId, sourceBuffer, positionBufferLen); int sourceCount = meshlite_get_vertex_source_array(meshliteContext, meshId, sourceBuffer, positionBufferLen);
Q_ASSERT(positionCount == sourceCount); Q_ASSERT(positionCount == sourceCount);
std::vector<QVector3D> verticesPositions;
for (int i = 0, positionIndex = 0; i < positionCount; i++, positionIndex+=3) { for (int i = 0, positionIndex = 0; i < positionCount; i++, positionIndex+=3) {
BmeshVertex vertex; BmeshVertex vertex;
vertex.partId = partId; vertex.partId = partId;
@ -123,8 +127,34 @@ void MeshGenerator::loadVertexSources(void *meshliteContext, int meshId, QUuid p
if (findNodeId != bmeshToNodeIdMap.end()) if (findNodeId != bmeshToNodeIdMap.end())
vertex.nodeId = findNodeId->second; vertex.nodeId = findNodeId->second;
vertex.position = QVector3D(positionBuffer[positionIndex + 0], positionBuffer[positionIndex + 1], positionBuffer[positionIndex + 2]); vertex.position = QVector3D(positionBuffer[positionIndex + 0], positionBuffer[positionIndex + 1], positionBuffer[positionIndex + 2]);
verticesPositions.push_back(vertex.position);
bmeshVertices.push_back(vertex); 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[] positionBuffer;
delete[] sourceBuffer; delete[] sourceBuffer;
} }
@ -221,19 +251,22 @@ void *MeshGenerator::combinePartMesh(QString partId)
if (MeshGenerator::m_enableDebug) if (MeshGenerator::m_enableDebug)
meshlite_bmesh_enable_debug(m_meshliteContext, bmeshId, 1); meshlite_bmesh_enable_debug(m_meshliteContext, bmeshId, 1);
//QString mirroredPartId; QString mirroredPartId;
//QUuid mirroredPartIdNotAsString; QUuid mirroredPartIdNotAsString;
//if (xMirrored) { if (xMirrored) {
// mirroredPartIdNotAsString = QUuid().createUuid(); mirroredPartIdNotAsString = QUuid().createUuid();
// mirroredPartId = mirroredPartIdNotAsString.toString(); mirroredPartId = mirroredPartIdNotAsString.toString();
//} m_cacheContext->partMirrorIdMap[mirroredPartId] = partId;
}
std::map<QString, int> nodeToBmeshIdMap; std::map<QString, int> nodeToBmeshIdMap;
std::map<int, QUuid> bmeshToNodeIdMap; std::map<int, QUuid> bmeshToNodeIdMap;
auto &cacheBmeshNodes = m_cacheContext->partBmeshNodes[partId]; auto &cacheBmeshNodes = m_cacheContext->partBmeshNodes[partId];
auto &cacheBmeshVertices = m_cacheContext->partBmeshVertices[partId]; auto &cacheBmeshVertices = m_cacheContext->partBmeshVertices[partId];
auto &cacheBmeshQuads = m_cacheContext->partBmeshQuads[partId];
cacheBmeshNodes.clear(); cacheBmeshNodes.clear();
cacheBmeshVertices.clear(); cacheBmeshVertices.clear();
cacheBmeshQuads.clear();
for (const auto &nodeId: m_partNodeIds[partId]) { for (const auto &nodeId: m_partNodeIds[partId]) {
auto findNode = m_snapshot->nodes.find(nodeId); auto findNode = m_snapshot->nodes.find(nodeId);
if (findNode == m_snapshot->nodes.end()) { if (findNode == m_snapshot->nodes.end()) {
@ -247,7 +280,9 @@ void *MeshGenerator::combinePartMesh(QString partId)
float y = (m_mainProfileMiddleY - valueOfKeyInMapOrEmpty(node, "y").toFloat()); float y = (m_mainProfileMiddleY - valueOfKeyInMapOrEmpty(node, "y").toFloat());
float z = (m_sideProfileMiddleX - valueOfKeyInMapOrEmpty(node, "z").toFloat()); float z = (m_sideProfileMiddleX - valueOfKeyInMapOrEmpty(node, "z").toFloat());
int bmeshNodeId = meshlite_bmesh_add_node(m_meshliteContext, bmeshId, x, y, z, radius); int bmeshNodeId = meshlite_bmesh_add_node(m_meshliteContext, bmeshId, x, y, z, radius);
SkeletonBoneMark boneMark = SkeletonBoneMarkFromString(valueOfKeyInMapOrEmpty(node, "boneMark").toUtf8().constData());
nodeToBmeshIdMap[nodeId] = bmeshNodeId; nodeToBmeshIdMap[nodeId] = bmeshNodeId;
bmeshToNodeIdMap[bmeshNodeId] = nodeId; bmeshToNodeIdMap[bmeshNodeId] = nodeId;
@ -257,12 +292,15 @@ void *MeshGenerator::combinePartMesh(QString partId)
bmeshNode.radius = radius; bmeshNode.radius = radius;
bmeshNode.nodeId = QUuid(nodeId); bmeshNode.nodeId = QUuid(nodeId);
bmeshNode.color = partColor; bmeshNode.color = partColor;
bmeshNode.boneMark = boneMark;
//if (SkeletonBoneMark::None != boneMark)
// bmeshNode.color = SkeletonBoneMarkToColor(boneMark);
cacheBmeshNodes.push_back(bmeshNode); cacheBmeshNodes.push_back(bmeshNode);
//if (xMirrored) { if (xMirrored) {
// bmeshNode.partId = mirroredPartId; bmeshNode.partId = mirroredPartId;
// bmeshNode.origin.setX(-x); bmeshNode.origin.setX(-x);
// cacheBmeshNodes.push_back(bmeshNode); cacheBmeshNodes.push_back(bmeshNode);
//} }
} }
for (const auto &edgeId: m_partEdgeIds[partId]) { for (const auto &edgeId: m_partEdgeIds[partId]) {
@ -295,7 +333,7 @@ void *MeshGenerator::combinePartMesh(QString partId)
void *resultMesh = nullptr; void *resultMesh = nullptr;
if (!bmeshToNodeIdMap.empty()) { if (!bmeshToNodeIdMap.empty()) {
meshId = meshlite_bmesh_generate_mesh(m_meshliteContext, bmeshId); 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) if (wrapped)
resultMesh = convertToCombinableConvexHullMesh(m_meshliteContext, meshId); resultMesh = convertToCombinableConvexHullMesh(m_meshliteContext, meshId);
else else
@ -305,7 +343,7 @@ void *MeshGenerator::combinePartMesh(QString partId)
if (nullptr != resultMesh) { if (nullptr != resultMesh) {
if (xMirrored) { if (xMirrored) {
int xMirroredMeshId = meshlite_mirror_in_x(m_meshliteContext, meshId, 0); 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; void *mirroredMesh = nullptr;
if (wrapped) if (wrapped)
mirroredMesh = convertToCombinableConvexHullMesh(m_meshliteContext, xMirroredMeshId); mirroredMesh = convertToCombinableConvexHullMesh(m_meshliteContext, xMirroredMeshId);
@ -554,6 +592,14 @@ void MeshGenerator::process()
} else { } else {
for (auto it = m_cacheContext->partBmeshNodes.begin(); it != m_cacheContext->partBmeshNodes.end(); ) { for (auto it = m_cacheContext->partBmeshNodes.begin(); it != m_cacheContext->partBmeshNodes.end(); ) {
if (m_snapshot->parts.find(it->first) == m_snapshot->parts.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); it = m_cacheContext->partBmeshNodes.erase(it);
continue; continue;
} }
@ -566,6 +612,13 @@ void MeshGenerator::process()
} }
it++; 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(); ) { for (auto it = m_cacheContext->componentCombinableMeshs.begin(); it != m_cacheContext->componentCombinableMeshs.end(); ) {
if (m_snapshot->components.find(it->first) == m_snapshot->components.end()) { if (m_snapshot->components.find(it->first) == m_snapshot->components.end()) {
deleteCombinableMesh(it->second); deleteCombinableMesh(it->second);
@ -617,14 +670,29 @@ void MeshGenerator::process()
bmeshNodes.second.begin(), bmeshNodes.second.end()); bmeshNodes.second.begin(), bmeshNodes.second.end());
} }
if (resultMeshId > 0) { //if (resultMeshId > 0) {
resultMeshId = meshlite_combine_coplanar_faces(m_meshliteContext, resultMeshId); // resultMeshId = meshlite_combine_coplanar_faces(m_meshliteContext, resultMeshId);
if (resultMeshId > 0) // if (resultMeshId > 0)
resultMeshId = meshlite_fix_hole(m_meshliteContext, resultMeshId); // resultMeshId = meshlite_fix_hole(m_meshliteContext, resultMeshId);
//}
int triangulatedFinalMeshId = resultMeshId;
if (triangulatedFinalMeshId > 0) {
std::set<std::pair<PositionMapKey, PositionMapKey>> 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) { if (resultMeshId > 0) {
int triangulatedFinalMeshId = meshlite_triangulate(m_meshliteContext, resultMeshId); //int triangulatedFinalMeshId = meshlite_triangulate(m_meshliteContext, resultMeshId);
//triangulatedFinalMeshId = resultMeshId;
loadGeneratedPositionsToMeshResultContext(m_meshliteContext, triangulatedFinalMeshId); loadGeneratedPositionsToMeshResultContext(m_meshliteContext, triangulatedFinalMeshId);
m_mesh = new MeshLoader(m_meshliteContext, resultMeshId, triangulatedFinalMeshId, Theme::white, &m_meshResultContext->triangleColors(), m_smoothNormal); m_mesh = new MeshLoader(m_meshliteContext, resultMeshId, triangulatedFinalMeshId, Theme::white, &m_meshResultContext->triangleColors(), m_smoothNormal);
} }

View File

@ -19,9 +19,11 @@ public:
~GeneratedCacheContext(); ~GeneratedCacheContext();
std::map<QString, std::vector<BmeshVertex>> partBmeshVertices; std::map<QString, std::vector<BmeshVertex>> partBmeshVertices;
std::map<QString, std::vector<BmeshNode>> partBmeshNodes; std::map<QString, std::vector<BmeshNode>> partBmeshNodes;
std::map<QString, std::vector<std::tuple<PositionMapKey, PositionMapKey, PositionMapKey, PositionMapKey>>> partBmeshQuads;
std::map<QString, void *> componentCombinableMeshs; std::map<QString, void *> componentCombinableMeshs;
std::map<QString, std::vector<QVector3D>> componentPositions; std::map<QString, std::vector<QVector3D>> componentPositions;
std::map<QString, PositionMap<BmeshVertex>> componentVerticesSources; std::map<QString, PositionMap<BmeshVertex>> componentVerticesSources;
std::map<QString, QString> partMirrorIdMap;
void updateComponentCombinableMesh(QString componentId, void *mesh); void updateComponentCombinableMesh(QString componentId, void *mesh);
}; };
@ -63,11 +65,12 @@ private:
std::map<QString, std::set<QString>> m_partEdgeIds; std::map<QString, std::set<QString>> m_partEdgeIds;
std::set<QString> m_dirtyComponentIds; std::set<QString> m_dirtyComponentIds;
std::set<QString> m_dirtyPartIds; std::set<QString> m_dirtyPartIds;
PositionMap<std::pair<QString, int>> m_bmeshPartVerticesIndiciesMap;
private: private:
static bool m_enableDebug; static bool m_enableDebug;
static PositionMap<int> *m_forMakePositionKey;
private: private:
void loadVertexSources(void *meshliteContext, int meshId, QUuid partId, const std::map<int, QUuid> &bmeshToNodeIdMap, std::vector<BmeshVertex> &bmeshVertices); void loadVertexSources(void *meshliteContext, int meshId, QUuid partId, const std::map<int, QUuid> &bmeshToNodeIdMap, std::vector<BmeshVertex> &bmeshVertices,
std::vector<std::tuple<PositionMapKey, PositionMapKey, PositionMapKey, PositionMapKey>> &bmeshQuads);
void loadGeneratedPositionsToMeshResultContext(void *meshliteContext, int triangulatedMeshId); void loadGeneratedPositionsToMeshResultContext(void *meshliteContext, int triangulatedMeshId);
void collectParts(); void collectParts();
void checkDirtyFlags(); void checkDirtyFlags();

96
src/meshquadify.cpp Normal file
View File

@ -0,0 +1,96 @@
#include <QDebug>
#include <unordered_set>
#include "meshquadify.h"
#include "meshlite.h"
int meshQuadify(void *meshlite, int meshId, const std::set<std::pair<PositionMapKey, PositionMapKey>> &sharedQuadEdges,
PositionMap<int> *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<int, PositionMapKey> 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<std::vector<int>> newFaceIndicies;
while (i < filledLength) {
int num = faceVertexNumAndIndices[i++];
assert(num > 0 && num <= MAX_VERTICES_PER_FACE);
if (num < 3) {
i += num;
continue;
}
std::vector<int> 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<int, int>, std::pair<int, int>> 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<int> unionedFaces;
std::vector<std::vector<int>> 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<int> 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<int> 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;
}

10
src/meshquadify.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef MESH_QUADIFY_H
#define MESH_QUADIFY_H
#include <set>
#include "meshutil.h"
#include "positionmap.h"
int meshQuadify(void *meshlite, int meshId, const std::set<std::pair<PositionMapKey, PositionMapKey>> &sharedQuadEdges,
PositionMap<int> *positionMapForMakeKey);
#endif

View File

@ -7,6 +7,7 @@
#include "meshresultcontext.h" #include "meshresultcontext.h"
#include "thekla_atlas.h" #include "thekla_atlas.h"
#include "positionmap.h" #include "positionmap.h"
#include "nvcore/Debug.h"
struct HalfColorEdge struct HalfColorEdge
{ {
@ -334,6 +335,7 @@ void MeshResultContext::calculateResultParts(std::map<QUuid, ResultPart> &parts)
if (isNewVertex || isSeamVertex) { if (isNewVertex || isSeamVertex) {
int newIndex = resultPart.vertices.size(); int newIndex = resultPart.vertices.size();
resultPart.interpolatedVertexNormals.push_back(newTriangle.normal); resultPart.interpolatedVertexNormals.push_back(newTriangle.normal);
resultPart.verticesOldIndicies.push_back(triangle.indicies[i]);
resultPart.vertices.push_back(vertices[triangle.indicies[i]]); resultPart.vertices.push_back(vertices[triangle.indicies[i]]);
ResultVertexUv vertexUv; ResultVertexUv vertexUv;
vertexUv.uv[0] = triangleUvs()[x].uv[i][0]; vertexUv.uv[0] = triangleUvs()[x].uv[i][0];
@ -365,6 +367,18 @@ void MeshResultContext::calculateResultTriangleUvs(std::vector<ResultTriangleUv>
const std::vector<ResultRearrangedTriangle> &choosenTriangles = rearrangedTriangles(); const std::vector<ResultRearrangedTriangle> &choosenTriangles = rearrangedTriangles();
Atlas_Input_Mesh inputMesh; 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_count = choosenVertices.size();
inputMesh.vertex_array = new Atlas_Input_Vertex[inputMesh.vertex_count]; inputMesh.vertex_array = new Atlas_Input_Vertex[inputMesh.vertex_count];

View File

@ -6,6 +6,7 @@
#include <QUuid> #include <QUuid>
#include <QColor> #include <QColor>
#include "positionmap.h" #include "positionmap.h"
#include "skeletonbonemark.h"
#define MAX_WEIGHT_NUM 4 #define MAX_WEIGHT_NUM 4
@ -16,6 +17,7 @@ struct BmeshNode
QVector3D origin; QVector3D origin;
float radius = 0; float radius = 0;
QColor color; QColor color;
SkeletonBoneMark boneMark;
}; };
struct BmeshVertex struct BmeshVertex
@ -51,6 +53,7 @@ struct ResultPart
{ {
QColor color; QColor color;
std::vector<ResultVertex> vertices; std::vector<ResultVertex> vertices;
std::vector<int> verticesOldIndicies;
std::vector<QVector3D> interpolatedVertexNormals; std::vector<QVector3D> interpolatedVertexNormals;
std::vector<ResultTriangle> triangles; std::vector<ResultTriangle> triangles;
std::vector<ResultTriangleUv> uvs; std::vector<ResultTriangleUv> uvs;
@ -107,6 +110,7 @@ private:
std::vector<ResultRearrangedVertex> m_rearrangedVertices; std::vector<ResultRearrangedVertex> m_rearrangedVertices;
std::vector<ResultRearrangedTriangle> m_rearrangedTriangles; std::vector<ResultRearrangedTriangle> m_rearrangedTriangles;
std::map<int, std::pair<QUuid, QUuid>> m_vertexSourceMap; std::map<int, std::pair<QUuid, QUuid>> m_vertexSourceMap;
std::map<int, int> m_rearrangedVerticesToOldIndexMap;
private: private:
void calculateTriangleSourceNodes(std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes, std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap); void calculateTriangleSourceNodes(std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes, std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap);
void calculateRemainingVertexSourceNodesAfterTriangleSourceNodesSolved(std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap); void calculateRemainingVertexSourceNodesAfterTriangleSourceNodesSolved(std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap);

102
src/meshsplitter.cpp Normal file
View File

@ -0,0 +1,102 @@
#include <map>
#include <QDebug>
#include <queue>
#include "meshsplitter.h"
bool MeshSplitter::split(const std::set<MeshSplitterTriangle> &input,
const std::set<MeshSplitterTriangle> &splitter,
std::set<MeshSplitterTriangle> &firstGroup,
std::set<MeshSplitterTriangle> &secondGroup)
{
firstGroup.clear();
secondGroup.clear();
// Make the edge to triangle map, this map will be used to find the neighbor triangles
std::map<std::pair<int, int>, 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<MeshSplitterTriangle> processedTriangles;
for (const auto &triangle: splitter) {
processedTriangles.insert(triangle);
}
std::queue<MeshSplitterTriangle> 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;
}

26
src/meshsplitter.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef MESH_SPLITTER_H
#define MESH_SPLITTER_H
#include <set>
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<MeshSplitterTriangle> &input,
const std::set<MeshSplitterTriangle> &splitter,
std::set<MeshSplitterTriangle> &firstGroup,
std::set<MeshSplitterTriangle> &secondGroup);
};
#endif

View File

@ -4,8 +4,6 @@
#define USE_CGAL 1 #define USE_CGAL 1
#define MAX_VERTICES_PER_FACE 100
#if USE_CGAL == 1 #if USE_CGAL == 1
// Polygon_mesh_processing/corefinement_mesh_union.cpp // 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 // 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<typename Kernel::Point_3> *makeCgalMeshFromMeshlite(
while (i < filledLength) { while (i < filledLength) {
int num = faceVertexNumAndIndices[i++]; int num = faceVertexNumAndIndices[i++];
assert(num > 0 && num <= MAX_VERTICES_PER_FACE); assert(num > 0 && num <= MAX_VERTICES_PER_FACE);
if (num < 3) if (num < 3) {
i += num;
continue; continue;
}
std::vector<typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index> faceVertexIndices; std::vector<typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index> faceVertexIndices;
for (int j = 0; j < num; j++) { for (int j = 0; j < num; j++) {
int index = faceVertexNumAndIndices[i++]; int index = faceVertexNumAndIndices[i++];

View File

@ -4,6 +4,8 @@
#include <set> #include <set>
#include <QVector3D> #include <QVector3D>
#define MAX_VERTICES_PER_FACE 100
int mergeMeshs(void *meshliteContext, const std::vector<int> &meshIds); int mergeMeshs(void *meshliteContext, const std::vector<int> &meshIds);
int unionMeshs(void *meshliteContext, const std::vector<int> &meshIds, const std::set<int> &inverseIds, int *errorCount=0); int unionMeshs(void *meshliteContext, const std::vector<int> &meshIds, const std::set<int> &inverseIds, int *errorCount=0);
int subdivMesh(void *meshliteContext, int meshId, int *errorCount=0); int subdivMesh(void *meshliteContext, int meshId, int *errorCount=0);

View File

@ -6,7 +6,10 @@
ModelOfflineRender::ModelOfflineRender(QOpenGLWidget *sharedContextWidget, QScreen *targetScreen) : ModelOfflineRender::ModelOfflineRender(QOpenGLWidget *sharedContextWidget, QScreen *targetScreen) :
QOffscreenSurface(targetScreen), QOffscreenSurface(targetScreen),
m_context(nullptr), m_context(nullptr),
m_mesh(nullptr) m_mesh(nullptr),
m_xRot(0),
m_yRot(0),
m_zRot(0)
{ {
create(); create();
@ -71,11 +74,9 @@ QImage ModelOfflineRender::toImage(const QSize &size)
m_context->functions()->glViewport(0, 0, size.width(), size.height()); m_context->functions()->glViewport(0, 0, size.width(), size.height());
if (nullptr != m_mesh) { if (nullptr != m_mesh) {
//int xRot = -30 * 16; int xRot = m_xRot;
//int yRot = 45 * 16; int yRot = m_yRot;
int xRot = 0; int zRot = m_zRot;
int yRot = 0;
int zRot = 0;
QMatrix4x4 proj; QMatrix4x4 proj;
QMatrix4x4 camera; QMatrix4x4 camera;
QMatrix4x4 world; QMatrix4x4 world;
@ -132,3 +133,19 @@ QImage ModelOfflineRender::toImage(const QSize &size)
return image; 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;
}

View File

@ -19,9 +19,15 @@ public:
void setRenderThread(QThread *thread); void setRenderThread(QThread *thread);
void updateMesh(MeshLoader *mesh); void updateMesh(MeshLoader *mesh);
QImage toImage(const QSize &size); QImage toImage(const QSize &size);
void setXRotation(int angle);
void setYRotation(int angle);
void setZRotation(int angle);
private: private:
QOpenGLContext *m_context; QOpenGLContext *m_context;
MeshLoader *m_mesh; MeshLoader *m_mesh;
int m_xRot;
int m_yRot;
int m_zRot;
}; };
#endif #endif

177
src/riggenerator.cpp Normal file
View File

@ -0,0 +1,177 @@
#include <QGuiApplication>
#include <QDebug>
#include <QElapsedTimer>
#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<AutoRiggerBone> *RigGenerator::takeResultBones()
{
std::vector<AutoRiggerBone> *resultBones = m_resultBones;
m_resultBones = nullptr;
return resultBones;
}
std::map<int, AutoRiggerVertexWeights> *RigGenerator::takeResultWeights()
{
std::map<int, AutoRiggerVertexWeights> *resultWeights = m_resultWeights;
m_resultWeights = nullptr;
return resultWeights;
}
MeshLoader *RigGenerator::takeResultMesh()
{
MeshLoader *resultMesh = m_resultMesh;
m_resultMesh = nullptr;
return resultMesh;
}
const std::vector<QString> &RigGenerator::missingMarkNames()
{
return m_missingMarkNames;
}
const std::vector<QString> &RigGenerator::errorMarkNames()
{
return m_errorMarkNames;
}
void RigGenerator::process()
{
QElapsedTimer countTimeConsumed;
countTimeConsumed.start();
std::vector<QVector3D> inputVerticesPositions;
std::set<MeshSplitterTriangle> inputTriangles;
for (const auto &vertex: m_meshResultContext->vertices) {
inputVerticesPositions.push_back(vertex.position);
}
std::map<std::pair<SkeletonBoneMark, SkeletonSide>, std::tuple<QVector3D, int, std::set<MeshSplitterTriangle>>> 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<QColor> inputVerticesColors(m_meshResultContext->vertices.size());
if (rigSucceed) {
const auto &resultWeights = m_autoRigger->resultWeights();
const auto &resultBones = m_autoRigger->resultBones();
m_resultWeights = new std::map<int, AutoRiggerVertexWeights>;
*m_resultWeights = resultWeights;
m_resultBones = new std::vector<AutoRiggerBone>;
*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<int, QVector3D> 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 &currentVertex = 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();
}

35
src/riggenerator.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef RIG_GENERATOR_H
#define RIG_GENERATOR_H
#include <QObject>
#include <QThread>
#include <QDebug>
#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<AutoRiggerBone> *takeResultBones();
std::map<int, AutoRiggerVertexWeights> *takeResultWeights();
const std::vector<QString> &missingMarkNames();
const std::vector<QString> &errorMarkNames();
signals:
void finished();
public slots:
void process();
private:
MeshResultContext *m_meshResultContext = nullptr;
MeshLoader *m_resultMesh = nullptr;
AutoRigger *m_autoRigger = nullptr;
std::vector<AutoRiggerBone> *m_resultBones = nullptr;
std::map<int, AutoRiggerVertexWeights> *m_resultWeights = nullptr;
std::vector<QString> m_missingMarkNames;
std::vector<QString> m_errorMarkNames;
};
#endif

6
src/rigtype.cpp Normal file
View File

@ -0,0 +1,6 @@
#include <QObject>
#include "rigtype.h"
IMPL_RigTypeToString
IMPL_RigTypeFromString
IMPL_RigTypeToDispName

47
src/rigtype.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef RIG_TYPE_H
#define RIG_TYPE_H
#include <QString>
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

88
src/rigwidget.cpp Normal file
View File

@ -0,0 +1,88 @@
#include <QFormLayout>
#include <QComboBox>
#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<void (QComboBox::*)(int)>(&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;
}

29
src/rigwidget.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef RIG_WIDGET_H
#define RIG_WIDGET_H
#include <QWidget>
#include <QComboBox>
#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

9
src/skeletonbonemark.cpp Normal file
View File

@ -0,0 +1,9 @@
#include <QObject>
#include "skeletonbonemark.h"
IMPL_SkeletonSideToDispName
IMPL_SkeletonBoneMarkToColor
IMPL_SkeletonBoneMarkToString
IMPL_SkeletonBoneMarkFromString
IMPL_SkeletonBoneMarkToDispName

140
src/skeletonbonemark.h Normal file
View File

@ -0,0 +1,140 @@
#ifndef SKELETON_BONE_MARK_H
#define SKELETON_BONE_MARK_H
#include <QColor>
#include <QString>
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

View File

@ -27,6 +27,7 @@ SkeletonDocument::SkeletonDocument() :
textureBorderImage(nullptr), textureBorderImage(nullptr),
textureAmbientOcclusionImage(nullptr), textureAmbientOcclusionImage(nullptr),
textureColorImage(nullptr), textureColorImage(nullptr),
rigType(RigType::None),
// private // private
m_isResultMeshObsolete(false), m_isResultMeshObsolete(false),
m_meshGenerator(nullptr), m_meshGenerator(nullptr),
@ -44,7 +45,12 @@ SkeletonDocument::SkeletonDocument() :
m_ambientOcclusionBakedImageUpdateVersion(0), m_ambientOcclusionBakedImageUpdateVersion(0),
m_sharedContextWidget(nullptr), m_sharedContextWidget(nullptr),
m_allPositionRelatedLocksEnabled(true), 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 textureBorderImage;
delete textureAmbientOcclusionImage; delete textureAmbientOcclusionImage;
delete m_resultTextureMesh; delete m_resultTextureMesh;
delete m_resultRigWeightMesh;
} }
void SkeletonDocument::uiReady() void SkeletonDocument::uiReady()
@ -555,6 +562,25 @@ void SkeletonDocument::switchNodeXZ(QUuid nodeId)
emit skeletonChanged(); 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) void SkeletonDocument::updateTurnaround(const QImage &image)
{ {
turnaround = image; turnaround = image;
@ -699,6 +725,8 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUu
node["y"] = QString::number(nodeIt.second.y); node["y"] = QString::number(nodeIt.second.y);
node["z"] = QString::number(nodeIt.second.z); node["z"] = QString::number(nodeIt.second.z);
node["partId"] = nodeIt.second.partId.toString(); node["partId"] = nodeIt.second.partId.toString();
if (nodeIt.second.boneMark != SkeletonBoneMark::None)
node["boneMark"] = SkeletonBoneMarkToString(nodeIt.second.boneMark);
if (!nodeIt.second.name.isEmpty()) if (!nodeIt.second.name.isEmpty())
node["name"] = nodeIt.second.name; node["name"] = nodeIt.second.name;
snapshot->nodes[node["id"]] = node; snapshot->nodes[node["id"]] = node;
@ -763,20 +791,31 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUu
canvas["originX"] = QString::number(originX); canvas["originX"] = QString::number(originX);
canvas["originY"] = QString::number(originY); canvas["originY"] = QString::number(originY);
canvas["originZ"] = QString::number(originZ); canvas["originZ"] = QString::number(originZ);
canvas["rigType"] = RigTypeToString(rigType);
snapshot->canvas = canvas; snapshot->canvas = canvas;
} }
void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot) void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste)
{ {
const auto &originXit = snapshot.canvas.find("originX"); bool isOriginChanged = false;
const auto &originYit = snapshot.canvas.find("originY"); bool isRigTypeChanged = false;
const auto &originZit = snapshot.canvas.find("originZ"); if (!fromPaste) {
if (originXit != snapshot.canvas.end() && const auto &originXit = snapshot.canvas.find("originX");
originYit != snapshot.canvas.end() && const auto &originYit = snapshot.canvas.find("originY");
originZit != snapshot.canvas.end()) { const auto &originZit = snapshot.canvas.find("originZ");
originX = originXit->second.toFloat(); if (originXit != snapshot.canvas.end() &&
originY = originYit->second.toFloat(); originYit != snapshot.canvas.end() &&
originZ = originZit->second.toFloat(); 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<QUuid> newAddedNodeIds; std::set<QUuid> newAddedNodeIds;
@ -830,6 +869,7 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot)
node.y = valueOfKeyInMapOrEmpty(nodeKv.second, "y").toFloat(); node.y = valueOfKeyInMapOrEmpty(nodeKv.second, "y").toFloat();
node.z = valueOfKeyInMapOrEmpty(nodeKv.second, "z").toFloat(); node.z = valueOfKeyInMapOrEmpty(nodeKv.second, "z").toFloat();
node.partId = oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(nodeKv.second, "partId"))]; node.partId = oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(nodeKv.second, "partId"))];
node.boneMark = SkeletonBoneMarkFromString(valueOfKeyInMapOrEmpty(nodeKv.second, "boneMark").toUtf8().constData());
nodeMap[node.id] = node; nodeMap[node.id] = node;
newAddedNodeIds.insert(node.id); newAddedNodeIds.insert(node.id);
} }
@ -927,7 +967,10 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot)
} }
emit componentChildrenChanged(QUuid()); emit componentChildrenChanged(QUuid());
emit originChanged(); if (isOriginChanged)
emit originChanged();
if (isRigTypeChanged)
emit rigTypeChanged();
emit skeletonChanged(); emit skeletonChanged();
for (const auto &partIt : newAddedPartIds) { for (const auto &partIt : newAddedPartIds) {
@ -948,6 +991,7 @@ void SkeletonDocument::reset()
originX = 0.0; originX = 0.0;
originY = 0.0; originY = 0.0;
originZ = 0.0; originZ = 0.0;
rigType = RigType::None;
nodeMap.clear(); nodeMap.clear();
edgeMap.clear(); edgeMap.clear();
partMap.clear(); partMap.clear();
@ -960,7 +1004,7 @@ void SkeletonDocument::reset()
void SkeletonDocument::fromSnapshot(const SkeletonSnapshot &snapshot) void SkeletonDocument::fromSnapshot(const SkeletonSnapshot &snapshot)
{ {
reset(); reset();
addFromSnapshot(snapshot); addFromSnapshot(snapshot, false);
emit uncheckAll(); emit uncheckAll();
} }
@ -979,6 +1023,14 @@ MeshLoader *SkeletonDocument::takeResultTextureMesh()
return resultTextureMesh; return resultTextureMesh;
} }
MeshLoader *SkeletonDocument::takeResultRigWeightMesh()
{
if (nullptr == m_resultRigWeightMesh)
return nullptr;
MeshLoader *resultMesh = new MeshLoader(*m_resultRigWeightMesh);
return resultMesh;
}
void SkeletonDocument::meshReady() void SkeletonDocument::meshReady()
{ {
MeshLoader *resultMesh = m_meshGenerator->takeResultMesh(); MeshLoader *resultMesh = m_meshGenerator->takeResultMesh();
@ -1015,6 +1067,7 @@ void SkeletonDocument::meshReady()
qDebug() << "MeshLoader generation done"; qDebug() << "MeshLoader generation done";
m_isPostProcessResultObsolete = true; m_isPostProcessResultObsolete = true;
m_isRigObsolete = true;
emit resultMeshChanged(); emit resultMeshChanged();
@ -1221,8 +1274,6 @@ void SkeletonDocument::postProcess()
return; return;
} }
qDebug() << "Post processing..";
m_isPostProcessResultObsolete = false; m_isPostProcessResultObsolete = false;
if (!m_currentMeshResultContext) { if (!m_currentMeshResultContext) {
@ -1230,6 +1281,8 @@ void SkeletonDocument::postProcess()
return; return;
} }
qDebug() << "Post processing..";
QThread *thread = new QThread; QThread *thread = new QThread;
m_postProcessor = new MeshResultPostProcessor(*m_currentMeshResultContext); m_postProcessor = new MeshResultPostProcessor(*m_currentMeshResultContext);
m_postProcessor->moveToThread(thread); m_postProcessor->moveToThread(thread);
@ -1898,7 +1951,7 @@ void SkeletonDocument::paste()
QXmlStreamReader xmlStreamReader(mimeData->text()); QXmlStreamReader xmlStreamReader(mimeData->text());
SkeletonSnapshot snapshot; SkeletonSnapshot snapshot;
loadSkeletonFromXmlStream(&snapshot, xmlStreamReader); loadSkeletonFromXmlStream(&snapshot, xmlStreamReader);
addFromSnapshot(snapshot); addFromSnapshot(snapshot, true);
} }
} }
@ -1980,9 +2033,11 @@ bool SkeletonDocument::isExportReady() const
if (m_isResultMeshObsolete || if (m_isResultMeshObsolete ||
m_isTextureObsolete || m_isTextureObsolete ||
m_isPostProcessResultObsolete || m_isPostProcessResultObsolete ||
m_isRigObsolete ||
m_meshGenerator || m_meshGenerator ||
m_textureGenerator || m_textureGenerator ||
m_postProcessor) m_postProcessor ||
m_rigGenerator)
return false; return false;
return true; 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<AutoRiggerBone> *SkeletonDocument::resultRigBones()
{
return m_resultRigBones;
}
const std::map<int, AutoRiggerVertexWeights> *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<QString> &SkeletonDocument::resultRigMissingMarkNames() const
{
return m_resultRigMissingMarkNames;
}
const std::vector<QString> &SkeletonDocument::resultRigErrorMarkNames() const
{
return m_resultRigErrorMarkNames;
}

View File

@ -13,11 +13,13 @@
#include "skeletonsnapshot.h" #include "skeletonsnapshot.h"
#include "meshloader.h" #include "meshloader.h"
#include "meshgenerator.h" #include "meshgenerator.h"
#include "skeletongenerator.h"
#include "theme.h" #include "theme.h"
#include "texturegenerator.h" #include "texturegenerator.h"
#include "meshresultpostprocessor.h" #include "meshresultpostprocessor.h"
#include "ambientocclusionbaker.h" #include "ambientocclusionbaker.h"
#include "skeletonbonemark.h"
#include "riggenerator.h"
#include "rigtype.h"
class SkeletonNode class SkeletonNode
{ {
@ -26,7 +28,8 @@ public:
x(0), x(0),
y(0), y(0),
z(0), z(0),
radius(0) radius(0),
boneMark(SkeletonBoneMark::None)
{ {
id = withId.isNull() ? QUuid::createUuid() : withId; id = withId.isNull() ? QUuid::createUuid() : withId;
} }
@ -45,6 +48,7 @@ public:
float y; float y;
float z; float z;
float radius; float radius;
SkeletonBoneMark boneMark;
std::vector<QUuid> edgeIds; std::vector<QUuid> edgeIds;
}; };
@ -342,6 +346,7 @@ signals:
void nodeRemoved(QUuid nodeId); void nodeRemoved(QUuid nodeId);
void edgeRemoved(QUuid edgeId); void edgeRemoved(QUuid edgeId);
void nodeRadiusChanged(QUuid nodeId); void nodeRadiusChanged(QUuid nodeId);
void nodeBoneMarkChanged(QUuid nodeId);
void nodeOriginChanged(QUuid nodeId); void nodeOriginChanged(QUuid nodeId);
void edgeChanged(QUuid edgeId); void edgeChanged(QUuid edgeId);
void partPreviewChanged(QUuid partId); void partPreviewChanged(QUuid partId);
@ -353,6 +358,8 @@ signals:
void resultTextureChanged(); void resultTextureChanged();
void resultBakedTextureChanged(); void resultBakedTextureChanged();
void postProcessedResultChanged(); void postProcessedResultChanged();
void resultRigChanged();
void rigChanged();
void partLockStateChanged(QUuid partId); void partLockStateChanged(QUuid partId);
void partVisibleStateChanged(QUuid partId); void partVisibleStateChanged(QUuid partId);
void partSubdivStateChanged(QUuid partId); void partSubdivStateChanged(QUuid partId);
@ -381,6 +388,7 @@ signals:
void checkNode(QUuid nodeId); void checkNode(QUuid nodeId);
void checkEdge(QUuid edgeId); void checkEdge(QUuid edgeId);
void optionsChanged(); void optionsChanged();
void rigTypeChanged();
public: // need initialize public: // need initialize
float originX; float originX;
float originY; float originY;
@ -395,6 +403,7 @@ public: // need initialize
QImage *textureBorderImage; QImage *textureBorderImage;
QImage *textureAmbientOcclusionImage; QImage *textureAmbientOcclusionImage;
QImage *textureColorImage; QImage *textureColorImage;
RigType rigType;
public: public:
SkeletonDocument(); SkeletonDocument();
~SkeletonDocument(); ~SkeletonDocument();
@ -407,7 +416,7 @@ public:
QImage preview; QImage preview;
void toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>()) const; void toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>()) const;
void fromSnapshot(const SkeletonSnapshot &snapshot); 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 SkeletonNode *findNode(QUuid nodeId) const;
const SkeletonEdge *findEdge(QUuid edgeId) const; const SkeletonEdge *findEdge(QUuid edgeId) const;
const SkeletonPart *findPart(QUuid partId) const; const SkeletonPart *findPart(QUuid partId) const;
@ -417,6 +426,9 @@ public:
QUuid findComponentParentId(QUuid componentId) const; QUuid findComponentParentId(QUuid componentId) const;
MeshLoader *takeResultMesh(); MeshLoader *takeResultMesh();
MeshLoader *takeResultTextureMesh(); MeshLoader *takeResultTextureMesh();
MeshLoader *takeResultRigWeightMesh();
const std::vector<AutoRiggerBone> *resultRigBones();
const std::map<int, AutoRiggerVertexWeights> *resultRigWeights();
void updateTurnaround(const QImage &image); void updateTurnaround(const QImage &image);
void setSharedContextWidget(QOpenGLWidget *sharedContextWidget); void setSharedContextWidget(QOpenGLWidget *sharedContextWidget);
bool hasPastableContentInClipboard() const; bool hasPastableContentInClipboard() const;
@ -431,6 +443,8 @@ public:
void findAllNeighbors(QUuid nodeId, std::set<QUuid> &neighbors) const; void findAllNeighbors(QUuid nodeId, std::set<QUuid> &neighbors) const;
void collectComponentDescendantParts(QUuid componentId, std::vector<QUuid> &partIds) const; void collectComponentDescendantParts(QUuid componentId, std::vector<QUuid> &partIds) const;
void collectComponentDescendantComponents(QUuid componentId, std::vector<QUuid> &componentIds) const; void collectComponentDescendantComponents(QUuid componentId, std::vector<QUuid> &componentIds) const;
const std::vector<QString> &resultRigMissingMarkNames() const;
const std::vector<QString> &resultRigErrorMarkNames() const;
public slots: public slots:
void removeNode(QUuid nodeId); void removeNode(QUuid nodeId);
void removeEdge(QUuid edgeId); void removeEdge(QUuid edgeId);
@ -440,6 +454,7 @@ public slots:
void moveNodeBy(QUuid nodeId, float x, float y, float z); void moveNodeBy(QUuid nodeId, float x, float y, float z);
void setNodeOrigin(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 setNodeRadius(QUuid nodeId, float radius);
void setNodeBoneMark(QUuid nodeId, SkeletonBoneMark mark);
void switchNodeXZ(QUuid nodeId); void switchNodeXZ(QUuid nodeId);
void moveOriginBy(float x, float y, float z); void moveOriginBy(float x, float y, float z);
void addEdge(QUuid fromNodeId, QUuid toNodeId); void addEdge(QUuid fromNodeId, QUuid toNodeId);
@ -454,6 +469,8 @@ public slots:
void postProcessedMeshResultReady(); void postProcessedMeshResultReady();
void bakeAmbientOcclusionTexture(); void bakeAmbientOcclusionTexture();
void ambientOcclusionTextureReady(); void ambientOcclusionTextureReady();
void generateRig();
void rigReady();
void setPartLockState(QUuid partId, bool locked); void setPartLockState(QUuid partId, bool locked);
void setPartVisibleState(QUuid partId, bool visible); void setPartVisibleState(QUuid partId, bool visible);
void setPartSubdivState(QUuid partId, bool subdived); void setPartSubdivState(QUuid partId, bool subdived);
@ -507,6 +524,7 @@ public slots:
void enableAllPositionRelatedLocks(); void enableAllPositionRelatedLocks();
void disableAllPositionRelatedLocks(); void disableAllPositionRelatedLocks();
void toggleSmoothNormal(); void toggleSmoothNormal();
void setRigType(RigType toRigType);
private: private:
void splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId); void splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId);
void joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId=QUuid()); void joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId=QUuid());
@ -521,6 +539,7 @@ private:
void removeComponentRecursively(QUuid componentId); void removeComponentRecursively(QUuid componentId);
void resetDirtyFlags(); void resetDirtyFlags();
void markAllDirty(); void markAllDirty();
void removeRigResults();
private: // need initialize private: // need initialize
bool m_isResultMeshObsolete; bool m_isResultMeshObsolete;
MeshGenerator *m_meshGenerator; MeshGenerator *m_meshGenerator;
@ -540,6 +559,13 @@ private: // need initialize
QUuid m_currentCanvasComponentId; QUuid m_currentCanvasComponentId;
bool m_allPositionRelatedLocksEnabled; bool m_allPositionRelatedLocksEnabled;
bool m_smoothNormal; bool m_smoothNormal;
RigGenerator *m_rigGenerator;
MeshLoader *m_resultRigWeightMesh;
std::vector<AutoRiggerBone> *m_resultRigBones;
std::map<int, AutoRiggerVertexWeights> *m_resultRigWeights;
bool m_isRigObsolete;
std::vector<QString> m_resultRigMissingMarkNames;
std::vector<QString> m_resultRigErrorMarkNames;
private: private:
static unsigned long m_maxSnapshot; static unsigned long m_maxSnapshot;
std::deque<SkeletonHistoryItem> m_undoItems; std::deque<SkeletonHistoryItem> m_undoItems;

View File

@ -28,6 +28,9 @@
#include "gltffile.h" #include "gltffile.h"
#include "graphicscontainerwidget.h" #include "graphicscontainerwidget.h"
#include "skeletonparttreewidget.h" #include "skeletonparttreewidget.h"
#include "rigwidget.h"
#include "modelofflinerender.h"
#include "markiconcreator.h"
int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16; int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16;
int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16; int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16;
@ -197,18 +200,26 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_modelRenderWidget->setGraphicsFunctions(graphicsWidget); m_modelRenderWidget->setGraphicsFunctions(graphicsWidget);
m_document->setSharedContextWidget(m_modelRenderWidget); 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); partTreeWidget->setGraphicsFunctions(graphicsWidget);
partTreeDocker->setWidget(partTreeWidget);
partListDocker->setWidget(partTreeWidget); addDockWidget(Qt::RightDockWidgetArea, partTreeDocker);
addDockWidget(Qt::RightDockWidgetArea, partListDocker); //partTreeDocker->hide();
partListDocker->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; QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->setSpacing(0); mainLayout->setSpacing(0);
@ -251,21 +262,23 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_fileMenu->addSeparator(); m_fileMenu->addSeparator();
m_exportMenu = m_fileMenu->addMenu(tr("Export")); //m_exportMenu = m_fileMenu->addMenu(tr("Export"));
m_fileMenu->addSeparator();
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); 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); //m_exportRenderedAsImageAction = new QAction(tr("Export as PNG..."), this);
connect(m_exportAsObjPlusMaterialsAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportObjPlusMaterialsResult, Qt::QueuedConnection); //connect(m_exportRenderedAsImageAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportRenderedResult, Qt::QueuedConnection);
m_exportMenu->addAction(m_exportAsObjPlusMaterialsAction); //m_fileMenu->addAction(m_exportRenderedAsImageAction);
m_exportAsGltfAction = new QAction(tr("GL Transmission Format (.gltf)..."), this); //m_exportAsObjPlusMaterialsAction = new QAction(tr("Wavefront (.obj + .mtl)..."), this);
connect(m_exportAsGltfAction, &QAction::triggered, this, &SkeletonDocumentWindow::showExportPreview, Qt::QueuedConnection); //connect(m_exportAsObjPlusMaterialsAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportObjPlusMaterialsResult, Qt::QueuedConnection);
m_exportMenu->addAction(m_exportAsGltfAction); //m_exportMenu->addAction(m_exportAsObjPlusMaterialsAction);
m_fileMenu->addSeparator(); m_fileMenu->addSeparator();
@ -277,8 +290,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
connect(m_fileMenu, &QMenu::aboutToShow, [=]() { connect(m_fileMenu, &QMenu::aboutToShow, [=]() {
m_exportAsObjAction->setEnabled(m_graphicsWidget->hasItems()); m_exportAsObjAction->setEnabled(m_graphicsWidget->hasItems());
m_exportAsObjPlusMaterialsAction->setEnabled(m_graphicsWidget->hasItems()); //m_exportAsObjPlusMaterialsAction->setEnabled(m_graphicsWidget->hasItems());
m_exportAsGltfAction->setEnabled(m_graphicsWidget->hasItems()); m_exportAction->setEnabled(m_graphicsWidget->hasItems());
//m_exportRenderedAsImageAction->setEnabled(m_graphicsWidget->hasItems());
}); });
m_editMenu = menuBar()->addMenu(tr("Edit")); m_editMenu = menuBar()->addMenu(tr("Edit"));
@ -374,6 +388,27 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_alignToMenu->addAction(m_alignToGlobalHorizontalCenterAction); m_alignToMenu->addAction(m_alignToGlobalHorizontalCenterAction);
m_editMenu->addMenu(m_alignToMenu); 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); m_selectAllAction = new QAction(tr("Select All"), this);
connect(m_selectAllAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::selectAll); connect(m_selectAllAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::selectAll);
@ -440,24 +475,29 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
}); });
m_viewMenu->addAction(m_toggleSmoothNormalAction); 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, [=]() { connect(m_viewMenu, &QMenu::aboutToShow, [=]() {
m_showPartsListAction->setEnabled(partListDocker->isHidden());
m_resetModelWidgetPosAction->setEnabled(!isModelSitInVisibleArea(m_modelRenderWidget)); 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")); m_helpMenu = menuBar()->addMenu(tr("Help"));
@ -535,8 +575,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_partListDockerVisibleSwitchConnection = connect(m_document, &SkeletonDocument::skeletonChanged, [=]() { m_partListDockerVisibleSwitchConnection = connect(m_document, &SkeletonDocument::skeletonChanged, [=]() {
if (m_graphicsWidget->hasItems()) { if (m_graphicsWidget->hasItems()) {
if (partListDocker->isHidden()) if (partTreeDocker->isHidden())
partListDocker->show(); partTreeDocker->show();
disconnect(m_partListDockerVisibleSwitchConnection); disconnect(m_partListDockerVisibleSwitchConnection);
} }
}); });
@ -549,6 +589,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
connect(graphicsWidget, &SkeletonGraphicsWidget::scaleNodeByAddRadius, m_document, &SkeletonDocument::scaleNodeByAddRadius); connect(graphicsWidget, &SkeletonGraphicsWidget::scaleNodeByAddRadius, m_document, &SkeletonDocument::scaleNodeByAddRadius);
connect(graphicsWidget, &SkeletonGraphicsWidget::moveNodeBy, m_document, &SkeletonDocument::moveNodeBy); connect(graphicsWidget, &SkeletonGraphicsWidget::moveNodeBy, m_document, &SkeletonDocument::moveNodeBy);
connect(graphicsWidget, &SkeletonGraphicsWidget::setNodeOrigin, m_document, &SkeletonDocument::setNodeOrigin); 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::removeNode, m_document, &SkeletonDocument::removeNode);
connect(graphicsWidget, &SkeletonGraphicsWidget::setEditMode, m_document, &SkeletonDocument::setEditMode); connect(graphicsWidget, &SkeletonGraphicsWidget::setEditMode, m_document, &SkeletonDocument::setEditMode);
connect(graphicsWidget, &SkeletonGraphicsWidget::removeEdge, m_document, &SkeletonDocument::removeEdge); 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::edgeAdded, graphicsWidget, &SkeletonGraphicsWidget::edgeAdded);
connect(m_document, &SkeletonDocument::edgeRemoved, graphicsWidget, &SkeletonGraphicsWidget::edgeRemoved); connect(m_document, &SkeletonDocument::edgeRemoved, graphicsWidget, &SkeletonGraphicsWidget::edgeRemoved);
connect(m_document, &SkeletonDocument::nodeRadiusChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeRadiusChanged); 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::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged);
connect(m_document, &SkeletonDocument::partVisibleStateChanged, graphicsWidget, &SkeletonGraphicsWidget::partVisibleStateChanged); connect(m_document, &SkeletonDocument::partVisibleStateChanged, graphicsWidget, &SkeletonGraphicsWidget::partVisibleStateChanged);
connect(m_document, &SkeletonDocument::partDisableStateChanged, graphicsWidget, &SkeletonGraphicsWidget::partVisibleStateChanged); connect(m_document, &SkeletonDocument::partDisableStateChanged, graphicsWidget, &SkeletonGraphicsWidget::partVisibleStateChanged);
@ -660,6 +702,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_document->postProcess(); 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::postProcessedResultChanged, m_document, &SkeletonDocument::generateTexture);
connect(m_document, &SkeletonDocument::resultTextureChanged, m_document, &SkeletonDocument::bakeAmbientOcclusionTexture); 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::skeletonChanged, this, &SkeletonDocumentWindow::documentChanged);
connect(m_document, &SkeletonDocument::turnaroundChanged, this, &SkeletonDocumentWindow::documentChanged); connect(m_document, &SkeletonDocument::turnaroundChanged, this, &SkeletonDocumentWindow::documentChanged);
connect(m_document, &SkeletonDocument::optionsChanged, 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) { connect(m_modelRenderWidget, &ModelWidget::customContextMenuRequested, [=](const QPoint &pos) {
graphicsWidget->showContextMenu(graphicsWidget->mapFromGlobal(m_modelRenderWidget->mapToGlobal(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::ylockStateChanged, this, &SkeletonDocumentWindow::updateYlockButtonState);
connect(m_document, &SkeletonDocument::zlockStateChanged, this, &SkeletonDocumentWindow::updateZlockButtonState); connect(m_document, &SkeletonDocument::zlockStateChanged, this, &SkeletonDocumentWindow::updateZlockButtonState);
connect(m_document, &SkeletonDocument::radiusLockStateChanged, this, &SkeletonDocumentWindow::updateRadiusLockButtonState); 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); connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady);
QTimer *timer = new QTimer(this); QTimer *timer = new QTimer(this);
timer->setInterval(100); timer->setInterval(250);
connect(timer, &QTimer::timeout, [=] { connect(timer, &QTimer::timeout, [=] {
QWidget *focusedWidget = QApplication::focusWidget(); QWidget *focusedWidget = QApplication::focusWidget();
//qDebug() << (focusedWidget ? ("Focused on:" + QString(focusedWidget->metaObject()->className()) + " title:" + focusedWidget->windowTitle()) : "No Focus") << " isActive:" << isActiveWindow(); //qDebug() << (focusedWidget ? ("Focused on:" + QString(focusedWidget->metaObject()->className()) + " title:" + focusedWidget->windowTitle()) : "No Focus") << " isActive:" << isActiveWindow();
@ -977,6 +1037,18 @@ void SkeletonDocumentWindow::exportObjPlusMaterialsResult()
QApplication::restoreOverrideCursor(); 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() void SkeletonDocumentWindow::showExportPreview()
{ {
if (nullptr == m_exportPreviewWidget) { if (nullptr == m_exportPreviewWidget) {
@ -1002,9 +1074,13 @@ void SkeletonDocumentWindow::exportGltfResult()
if (filename.isEmpty()) { if (filename.isEmpty()) {
return; return;
} }
if (!m_document->isExportReady()) {
qDebug() << "Export but document is not export ready";
return;
}
QApplication::setOverrideCursor(Qt::WaitCursor); QApplication::setOverrideCursor(Qt::WaitCursor);
MeshResultContext skeletonResult = m_document->currentPostProcessedResultContext(); MeshResultContext skeletonResult = m_document->currentPostProcessedResultContext();
GLTFFileWriter gltfFileWriter(skeletonResult,filename); GltfFileWriter gltfFileWriter(skeletonResult, m_document->resultRigBones(), m_document->resultRigWeights(), filename);
gltfFileWriter.save(); gltfFileWriter.save();
if (m_document->textureImage) if (m_document->textureImage)
m_document->textureImage->save(gltfFileWriter.textureFilenameInGltf()); m_document->textureImage->save(gltfFileWriter.textureFilenameInGltf());
@ -1052,3 +1128,14 @@ void SkeletonDocumentWindow::updateRadiusLockButtonState()
else else
m_radiusLockButton->setStyleSheet("QPushButton {color: " + Theme::white.name() + "}"); 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);
}

View File

@ -10,6 +10,8 @@
#include "skeletondocument.h" #include "skeletondocument.h"
#include "modelwidget.h" #include "modelwidget.h"
#include "exportpreviewwidget.h" #include "exportpreviewwidget.h"
#include "rigwidget.h"
#include "skeletonbonemark.h"
class SkeletonGraphicsWidget; class SkeletonGraphicsWidget;
@ -37,6 +39,7 @@ public slots:
void exportObjResult(); void exportObjResult();
void exportObjPlusMaterialsResult(); void exportObjPlusMaterialsResult();
void exportGltfResult(); void exportGltfResult();
void exportRenderedResult();
void showExportPreview(); void showExportPreview();
void newWindow(); void newWindow();
void newDocument(); void newDocument();
@ -57,6 +60,7 @@ private:
void initLockButton(QPushButton *button); void initLockButton(QPushButton *button);
void setCurrentFilename(const QString &filename); void setCurrentFilename(const QString &filename);
void updateTitle(); void updateTitle();
void exportRenderedAsImage(const QString &filename);
private: private:
SkeletonDocument *m_document; SkeletonDocument *m_document;
bool m_firstShow; bool m_firstShow;
@ -80,7 +84,8 @@ private:
QAction *m_exportAsObjAction; QAction *m_exportAsObjAction;
QAction *m_exportAsObjPlusMaterialsAction; QAction *m_exportAsObjPlusMaterialsAction;
QAction *m_exportAsGltfAction; QAction *m_exportAction;
QAction *m_exportRenderedAsImageAction;
QMenu *m_editMenu; QMenu *m_editMenu;
QAction *m_addAction; QAction *m_addAction;
@ -110,14 +115,21 @@ private:
QAction *m_selectPartAllAction; QAction *m_selectPartAllAction;
QAction *m_unselectAllAction; QAction *m_unselectAllAction;
QMenu *m_markAsMenu;
QAction *m_markAsNoneAction;
QAction *m_markAsActions[(int)SkeletonBoneMark::Count - 1];
QMenu *m_viewMenu; QMenu *m_viewMenu;
QAction *m_resetModelWidgetPosAction; QAction *m_resetModelWidgetPosAction;
QAction *m_showPartsListAction;
QAction *m_showDebugDialogAction;
QAction *m_toggleWireframeAction; QAction *m_toggleWireframeAction;
QAction *m_toggleSmoothNormalAction; QAction *m_toggleSmoothNormalAction;
QAction *m_showMotionsListAction; QAction *m_showMotionsListAction;
QMenu *m_windowMenu;
QAction *m_showPartsListAction;
QAction *m_showDebugDialogAction;
QAction *m_showRigAction;
QMenu *m_helpMenu; QMenu *m_helpMenu;
QAction *m_viewSourceAction; QAction *m_viewSourceAction;
QAction *m_aboutAction; QAction *m_aboutAction;

View File

@ -1,72 +0,0 @@
#include <set>
#include <QGuiApplication>
#include <QDebug>
#include <vector>
#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();
}

View File

@ -1,29 +0,0 @@
#ifndef SKELETON_GENERATOR_H
#define SKELETON_GENERATOR_H
#include <QObject>
#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

View File

@ -13,6 +13,7 @@
#include "theme.h" #include "theme.h"
#include "dust3dutil.h" #include "dust3dutil.h"
#include "skeletonxml.h" #include "skeletonxml.h"
#include "markiconcreator.h"
SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document) : SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document) :
m_document(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); QAction selectAllAction(tr("Select All"), this);
if (hasItems()) { if (hasItems()) {
connect(&selectAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::selectAll); connect(&selectAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::selectAll);
@ -243,6 +269,10 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
} }
contextMenu.exec(mapToGlobal(pos)); contextMenu.exec(mapToGlobal(pos));
for (int i = 0; i < (int)SkeletonBoneMark::Count - 1; i++) {
delete markAsActions[i];
}
} }
bool SkeletonGraphicsWidget::hasSelection() bool SkeletonGraphicsWidget::hasSelection()
@ -1575,12 +1605,15 @@ void SkeletonGraphicsWidget::nodeAdded(QUuid nodeId)
qDebug() << "New node added but node item already exist:" << nodeId; qDebug() << "New node added but node item already exist:" << nodeId;
return; return;
} }
QColor markColor = SkeletonBoneMarkToColor(node->boneMark);
SkeletonGraphicsNodeItem *mainProfileItem = new SkeletonGraphicsNodeItem(SkeletonProfile::Main); SkeletonGraphicsNodeItem *mainProfileItem = new SkeletonGraphicsNodeItem(SkeletonProfile::Main);
SkeletonGraphicsNodeItem *sideProfileItem = new SkeletonGraphicsNodeItem(SkeletonProfile::Side); SkeletonGraphicsNodeItem *sideProfileItem = new SkeletonGraphicsNodeItem(SkeletonProfile::Side);
mainProfileItem->setOrigin(scenePosFromUnified(QPointF(node->x, node->y))); mainProfileItem->setOrigin(scenePosFromUnified(QPointF(node->x, node->y)));
sideProfileItem->setOrigin(scenePosFromUnified(QPointF(node->z, node->y))); sideProfileItem->setOrigin(scenePosFromUnified(QPointF(node->z, node->y)));
mainProfileItem->setRadius(sceneRadiusFromUnified(node->radius)); mainProfileItem->setRadius(sceneRadiusFromUnified(node->radius));
sideProfileItem->setRadius(sceneRadiusFromUnified(node->radius)); sideProfileItem->setRadius(sceneRadiusFromUnified(node->radius));
mainProfileItem->setMarkColor(markColor);
sideProfileItem->setMarkColor(markColor);
mainProfileItem->setId(nodeId); mainProfileItem->setId(nodeId);
sideProfileItem->setId(nodeId); sideProfileItem->setId(nodeId);
scene()->addItem(mainProfileItem); scene()->addItem(mainProfileItem);
@ -1696,6 +1729,23 @@ void SkeletonGraphicsWidget::nodeRadiusChanged(QUuid nodeId)
it->second.second->setRadius(sceneRadius); 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) void SkeletonGraphicsWidget::nodeOriginChanged(QUuid nodeId)
{ {
const SkeletonNode *node = m_document->findNode(nodeId); const SkeletonNode *node = m_document->findNode(nodeId);
@ -2303,4 +2353,17 @@ void SkeletonGraphicsWidget::ikMove(QUuid endEffectorId, QVector3D target)
thread->start(); thread->start();
} }
void SkeletonGraphicsWidget::setSelectedNodesBoneMark(SkeletonBoneMark boneMark)
{
std::set<QUuid> nodeIdSet;
std::set<QUuid> edgeIdSet;
readSkeletonNodeAndEdgeIdSetFromRangeSelection(&nodeIdSet, &edgeIdSet);
if (!nodeIdSet.empty()) {
emit batchChangeBegin();
for (const auto &id: nodeIdSet) {
emit setNodeBoneMark(id, boneMark);
}
emit batchChangeEnd();
emit groupOperationAdded();
}
}

View File

@ -384,6 +384,7 @@ signals:
void setYlockState(bool locked); void setYlockState(bool locked);
void setZlockState(bool locked); void setZlockState(bool locked);
void setNodeOrigin(QUuid nodeId, float x, float y, float z); void setNodeOrigin(QUuid nodeId, float x, float y, float z);
void setNodeBoneMark(QUuid nodeId, SkeletonBoneMark mark);
void zoomRenderedModelBy(float delta); void zoomRenderedModelBy(float delta);
void switchNodeXZ(QUuid nodeId); void switchNodeXZ(QUuid nodeId);
void enableAllPositionRelatedLocks(); void enableAllPositionRelatedLocks();
@ -423,6 +424,7 @@ public slots:
void nodeRemoved(QUuid nodeId); void nodeRemoved(QUuid nodeId);
void edgeRemoved(QUuid edgeId); void edgeRemoved(QUuid edgeId);
void nodeRadiusChanged(QUuid nodeId); void nodeRadiusChanged(QUuid nodeId);
void nodeBoneMarkChanged(QUuid nodeId);
void nodeOriginChanged(QUuid nodeId); void nodeOriginChanged(QUuid nodeId);
void edgeChanged(QUuid edgeId); void edgeChanged(QUuid edgeId);
void turnaroundChanged(); void turnaroundChanged();
@ -466,6 +468,7 @@ public slots:
void disableBackgroundBlur(); void disableBackgroundBlur();
void ikMove(QUuid endEffectorId, QVector3D target); void ikMove(QUuid endEffectorId, QVector3D target);
void ikMoveReady(); void ikMoveReady();
void setSelectedNodesBoneMark(SkeletonBoneMark boneMark);
void timeToRemoveDeferredNodesAndEdges(); void timeToRemoveDeferredNodesAndEdges();
void switchSelectedXZ(); void switchSelectedXZ();
private slots: private slots:

View File

@ -99,14 +99,27 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write
void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &reader) void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &reader)
{ {
std::stack<QString> componentStack; std::stack<QString> componentStack;
std::vector<QString> elementNameStack;
while (!reader.atEnd()) { while (!reader.atEnd()) {
reader.readNext(); 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.isStartElement()) {
if (reader.name() == "canvas") { if (fullName == "canvas") {
foreach(const QXmlStreamAttribute &attr, reader.attributes()) { foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
snapshot->canvas[attr.name().toString()] = attr.value().toString(); 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(); QString nodeId = reader.attributes().value("id").toString();
if (nodeId.isEmpty()) if (nodeId.isEmpty())
continue; continue;
@ -114,7 +127,7 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
foreach(const QXmlStreamAttribute &attr, reader.attributes()) { foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
(*nodeMap)[attr.name().toString()] = attr.value().toString(); (*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(); QString edgeId = reader.attributes().value("id").toString();
if (edgeId.isEmpty()) if (edgeId.isEmpty())
continue; continue;
@ -122,7 +135,7 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
foreach(const QXmlStreamAttribute &attr, reader.attributes()) { foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
(*edgeMap)[attr.name().toString()] = attr.value().toString(); (*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(); QString partId = reader.attributes().value("id").toString();
if (partId.isEmpty()) if (partId.isEmpty())
continue; continue;
@ -130,7 +143,7 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
foreach(const QXmlStreamAttribute &attr, reader.attributes()) { foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
(*partMap)[attr.name().toString()] = attr.value().toString(); (*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(); QString partId = reader.attributes().value("id").toString();
if (partId.isEmpty()) if (partId.isEmpty())
continue; continue;
@ -143,7 +156,7 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
if (!childrenIds.isEmpty()) if (!childrenIds.isEmpty())
childrenIds += ","; childrenIds += ",";
childrenIds += componentId; childrenIds += componentId;
} else if (reader.name() == "component") { } else if (fullName.startsWith("canvas.components.component")) {
QString componentId = reader.attributes().value("id").toString(); QString componentId = reader.attributes().value("id").toString();
QString parentId; QString parentId;
if (!componentStack.empty()) if (!componentStack.empty())
@ -161,7 +174,7 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
parentChildrenIds += componentId; parentChildrenIds += componentId;
} }
} else if (reader.isEndElement()) { } else if (reader.isEndElement()) {
if (reader.name() == "component") { if (fullName.startsWith("canvas.components.component")) {
componentStack.pop(); componentStack.pop();
} }
} }