Fix rig skinning weights, add pose configure UI.

master
Jeremy Hu 2018-09-18 11:17:35 +08:00
parent 8dcabeae72
commit c64743f2f2
32 changed files with 920 additions and 207 deletions

View File

@ -164,6 +164,27 @@ HEADERS += src/riggenerator.h
SOURCES += src/meshquadify.cpp
HEADERS += src/meshquadify.h
SOURCES += src/skinnedmeshcreator.cpp
HEADERS += src/skinnedmeshcreator.h
SOURCES += src/jointnodetree.cpp
HEADERS += src/jointnodetree.h
SOURCES += src/tetrapodposer.cpp
HEADERS += src/tetrapodposer.h
SOURCES += src/poser.cpp
HEADERS += src/poser.h
SOURCES += src/posemeshcreator.cpp
HEADERS += src/posemeshcreator.h
SOURCES += src/posepreviewmanager.cpp
HEADERS += src/posepreviewmanager.h
SOURCES += src/tetrapodposeeditwidget.cpp
HEADERS += src/tetrapodposeeditwidget.h
SOURCES += src/main.cpp
HEADERS += src/version.h

View File

@ -1,59 +0,0 @@
#include "animationclipplayer.h"
AnimationClipPlayer::~AnimationClipPlayer()
{
clear();
}
void AnimationClipPlayer::updateFrameMeshes(std::vector<std::pair<float, MeshLoader *>> &frameMeshes)
{
clear();
m_frameMeshes = frameMeshes;
frameMeshes.clear();
m_currentPlayIndex = 0;
m_countForFrame.restart();
if (!m_frameMeshes.empty())
m_timerForFrame.singleShot(0, this, &AnimationClipPlayer::frameReadyToShow);
}
void AnimationClipPlayer::clear()
{
freeFrames();
delete m_lastFrameMesh;
m_lastFrameMesh = nullptr;
}
void AnimationClipPlayer::freeFrames()
{
for (auto &it: m_frameMeshes) {
delete it.second;
}
m_frameMeshes.clear();
}
MeshLoader *AnimationClipPlayer::takeFrameMesh()
{
if (m_currentPlayIndex >= (int)m_frameMeshes.size()) {
if (nullptr != m_lastFrameMesh)
return new MeshLoader(*m_lastFrameMesh);
return nullptr;
}
int millis = m_frameMeshes[m_currentPlayIndex].first * 1000 - m_countForFrame.elapsed();
if (millis > 0) {
m_timerForFrame.singleShot(millis, this, &AnimationClipPlayer::frameReadyToShow);
if (nullptr != m_lastFrameMesh)
return new MeshLoader(*m_lastFrameMesh);
return nullptr;
}
m_currentPlayIndex = (m_currentPlayIndex + 1) % m_frameMeshes.size();
m_countForFrame.restart();
MeshLoader *mesh = new MeshLoader(*m_frameMeshes[m_currentPlayIndex].second);
m_timerForFrame.singleShot(m_frameMeshes[m_currentPlayIndex].first * 1000, this, &AnimationClipPlayer::frameReadyToShow);
delete m_lastFrameMesh;
m_lastFrameMesh = new MeshLoader(*mesh);
return mesh;
}

View File

@ -1,29 +0,0 @@
#ifndef ANIMATION_PLAYER_H
#define ANIMATION_PLAYER_H
#include <QObject>
#include <QTimer>
#include <QTime>
#include "meshloader.h"
class AnimationClipPlayer : public QObject
{
Q_OBJECT
signals:
void frameReadyToShow();
public:
~AnimationClipPlayer();
MeshLoader *takeFrameMesh();
void updateFrameMeshes(std::vector<std::pair<float, MeshLoader *>> &frameMeshes);
void clear();
private:
void freeFrames();
private:
MeshLoader *m_lastFrameMesh = nullptr;
int m_currentPlayIndex = 0;
private:
std::vector<std::pair<float, MeshLoader *>> m_frameMeshes;
QTime m_countForFrame;
QTimer m_timerForFrame;
};
#endif

View File

@ -249,7 +249,8 @@ void AutoRigger::addVerticesToWeights(const std::set<int> &vertices, int boneInd
{
for (const auto &vertexIndex: vertices) {
auto &weights = m_resultWeights[vertexIndex];
float distance = m_verticesPositions[vertexIndex].distanceToPoint(m_resultBones[boneIndex].headPosition);
auto strongestPoint = (m_resultBones[boneIndex].headPosition * 3 + m_resultBones[boneIndex].tailPosition) / 4;
float distance = m_verticesPositions[vertexIndex].distanceToPoint(strongestPoint);
weights.addBone(boneIndex, distance);
}
}
@ -307,6 +308,7 @@ bool AutoRigger::rig()
std::set<int> headVertices;
addTrianglesToVertices(m_marks[neckIndicies->second[0]].smallGroup(), headVertices);
addTrianglesToVertices(m_marks[neckIndicies->second[0]].markTriangles, headVertices);
QVector3D headBoneStopPosition;
if (isMainBodyVerticalAligned) {
QVector3D maxY = findMaxY(headVertices);
@ -379,6 +381,9 @@ bool AutoRigger::rig()
// 2.1 Collect vertices for neck bone:
std::set<int> bodyVertices;
addTrianglesToVertices(bodyTriangles, bodyVertices);
addTrianglesToVertices(m_marks[leftShoulderIndicies->second[0]].markTriangles, bodyVertices);
addTrianglesToVertices(m_marks[rightShoulderIndicies->second[0]].markTriangles, bodyVertices);
std::set<int> bodyVerticesAfterShoulder;
std::set<int> neckVertices;
{
@ -447,11 +452,9 @@ bool AutoRigger::rig()
{
std::set<int> leftArmVerticesBeforeElbow;
if (isLeftArmVerticalAligned) {
QVector3D maxY = findMaxY(leftElbowMarkVertices);
splitVerticesByY(leftArmVertices, maxY.y(), leftArmVerticesBeforeElbow, leftArmVerticesSinceElbow);
splitVerticesByY(leftArmVertices, m_marks[leftElbowIndicies->second[0]].bonePosition.y(), leftArmVerticesBeforeElbow, leftArmVerticesSinceElbow);
} else {
QVector3D minX = findMinX(leftElbowMarkVertices);
splitVerticesByX(leftArmVertices, minX.x(), leftArmVerticesSinceElbow, leftArmVerticesBeforeElbow);
splitVerticesByX(leftArmVertices, m_marks[leftElbowIndicies->second[0]].bonePosition.x(), leftArmVerticesSinceElbow, leftArmVerticesBeforeElbow);
}
}
std::set<int> leftWristMarkVertices;
@ -472,11 +475,9 @@ bool AutoRigger::rig()
{
std::set<int> leftArmVerticesBeforeWrist;
if (isLeftArmVerticalAligned) {
QVector3D maxY = findMaxY(leftWristMarkVertices);
splitVerticesByY(leftArmVerticesSinceElbow, maxY.y(), leftArmVerticesBeforeWrist, leftHandVertices);
splitVerticesByY(leftArmVerticesSinceElbow, m_marks[leftWristIndicies->second[0]].bonePosition.y(), leftArmVerticesBeforeWrist, leftHandVertices);
} else {
QVector3D minX = findMinX(leftWristMarkVertices);
splitVerticesByX(leftArmVerticesSinceElbow, minX.x(), leftHandVertices, leftArmVerticesBeforeWrist);
splitVerticesByX(leftArmVerticesSinceElbow, m_marks[leftWristIndicies->second[0]].bonePosition.x(), leftHandVertices, leftArmVerticesBeforeWrist);
}
}
@ -501,11 +502,9 @@ bool AutoRigger::rig()
{
std::set<int> rightArmVerticesBeforeElbow;
if (isRightArmVerticalAligned) {
QVector3D maxY = findMaxY(rightElbowMarkVertices);
splitVerticesByY(rightArmVertices, maxY.y(), rightArmVerticesBeforeElbow, rightArmVerticesSinceElbow);
splitVerticesByY(rightArmVertices, m_marks[rightElbowIndicies->second[0]].bonePosition.y(), rightArmVerticesBeforeElbow, rightArmVerticesSinceElbow);
} else {
QVector3D maxX = findMaxX(rightElbowMarkVertices);
splitVerticesByX(rightArmVertices, maxX.x(), rightArmVerticesBeforeElbow, rightArmVerticesSinceElbow);
splitVerticesByX(rightArmVertices, m_marks[rightElbowIndicies->second[0]].bonePosition.x(), rightArmVerticesBeforeElbow, rightArmVerticesSinceElbow);
}
}
std::set<int> rightWristMarkVertices;
@ -526,11 +525,9 @@ bool AutoRigger::rig()
{
std::set<int> rightArmVerticesBeforeWrist;
if (isRightArmVerticalAligned) {
QVector3D maxY = findMaxY(rightWristMarkVertices);
splitVerticesByY(rightArmVerticesSinceElbow, maxY.y(), rightArmVerticesBeforeWrist, rightHandVertices);
splitVerticesByY(rightArmVerticesSinceElbow, m_marks[rightWristIndicies->second[0]].bonePosition.y(), rightArmVerticesBeforeWrist, rightHandVertices);
} else {
QVector3D maxX = findMaxX(rightWristMarkVertices);
splitVerticesByX(rightArmVerticesSinceElbow, maxX.x(), rightArmVerticesBeforeWrist, rightHandVertices);
splitVerticesByX(rightArmVerticesSinceElbow, m_marks[rightWristIndicies->second[0]].bonePosition.x(), rightArmVerticesBeforeWrist, rightHandVertices);
}
}
@ -566,8 +563,7 @@ bool AutoRigger::rig()
std::set<int> leftFootVertices;
{
std::set<int> leftLegVerticesBeforeAnkle;
QVector3D maxY = findMaxY(leftAnkleMarkVertices);
splitVerticesByY(leftLegVerticesSinceKnee, maxY.y(), leftLegVerticesBeforeAnkle, leftFootVertices);
splitVerticesByY(leftLegVerticesSinceKnee, m_marks[leftAnkleIndicies->second[0]].bonePosition.y(), leftLegVerticesBeforeAnkle, leftFootVertices);
}
// 4.2.1 Collect vertices for right upper leg:
@ -600,8 +596,7 @@ bool AutoRigger::rig()
std::set<int> rightFootVertices;
{
std::set<int> rightLegVerticesBeforeAnkle;
QVector3D maxY = findMaxY(rightAnkleMarkVertices);
splitVerticesByY(rightLegVerticesSinceKnee, maxY.y(), rightLegVerticesBeforeAnkle, rightFootVertices);
splitVerticesByY(rightLegVerticesSinceKnee, m_marks[rightAnkleIndicies->second[0]].bonePosition.y(), rightLegVerticesBeforeAnkle, rightFootVertices);
}
// 5. Generate bones

View File

@ -4,6 +4,7 @@
#include <QVector3D>
#include <QObject>
#include <QColor>
#include <QDebug>
#include "meshsplitter.h"
#include "skeletonbonemark.h"
#include "rigtype.h"
@ -55,7 +56,7 @@ public:
void addBone(int boneIndex, float distance)
{
if (qFuzzyIsNull(distance))
distance = 0.01;
distance = 0.0001;
m_boneRawWeights.push_back(std::make_pair(boneIndex, 1.0 / distance));
}
void finalizeWeights()

View File

@ -7,7 +7,6 @@
struct CCDIKNode
{
QVector3D position;
bool hasConstraint = false;
};
class CCDIKSolver

View File

@ -1,7 +1,9 @@
#include <QtWidgets>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include "floatnumberwidget.h"
FloatNumberWidget::FloatNumberWidget(QWidget *parent) :
FloatNumberWidget::FloatNumberWidget(QWidget *parent, bool singleLine) :
QWidget(parent)
{
m_slider = new QSlider(Qt::Horizontal, this);
@ -17,10 +19,18 @@ FloatNumberWidget::FloatNumberWidget(QWidget *parent) :
emit valueChanged(fvalue);
});
QBoxLayout *popupLayout = new QHBoxLayout(this);
popupLayout->setMargin(2);
popupLayout->addWidget(m_slider);
popupLayout->addWidget(m_label);
QBoxLayout *layout = nullptr;
if (singleLine) {
layout = new QHBoxLayout(this);
layout->setMargin(2);
layout->addWidget(m_slider);
layout->addWidget(m_label);
} else {
layout = new QVBoxLayout(this);
layout->setMargin(2);
layout->addWidget(m_label);
layout->addWidget(m_slider);
}
}
void FloatNumberWidget::updateValueLabel(float value)

View File

@ -9,7 +9,7 @@ class FloatNumberWidget : public QWidget
{
Q_OBJECT
public:
explicit FloatNumberWidget(QWidget *parent = nullptr);
explicit FloatNumberWidget(QWidget *parent = nullptr, bool singleLine=true);
void setRange(float min, float max);
float value() const;
void setItemName(const QString &name);

View File

@ -7,6 +7,7 @@
#include "gltffile.h"
#include "version.h"
#include "dust3dutil.h"
#include "jointnodetree.h"
// Play with glTF online:
// https://gltf-viewer.donmccurdy.com/
@ -47,14 +48,15 @@ GltfFileWriter::GltfFileWriter(MeshResultContext &resultContext,
int bufferViewIndex = 0;
int bufferViewFromOffset;
JointNodeTree tailNodeTree(resultRigBones);
const auto &boneNodes = tailNodeTree.nodes();
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"] = {
@ -66,25 +68,25 @@ GltfFileWriter::GltfFileWriter(MeshResultContext &resultContext,
m_json["nodes"][1]["skin"] = 0;
m_json["skins"][0]["joints"] = {};
for (size_t i = 0; i < m_boneNodes.size(); i++) {
for (size_t i = 0; i < 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]["name"] = 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()
boneNodes[i].translation.x(),
boneNodes[i].translation.y(),
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()
boneNodes[i].rotation.x(),
boneNodes[i].rotation.y(),
boneNodes[i].rotation.z(),
boneNodes[i].rotation.scalar()
};
if (!m_boneNodes[i].children.empty()) {
if (!boneNodes[i].children.empty()) {
m_json["nodes"][skeletonNodeStartIndex + i]["children"] = {};
for (const auto &it: m_boneNodes[i].children) {
for (const auto &it: boneNodes[i].children) {
m_json["nodes"][skeletonNodeStartIndex + i]["children"] += skeletonNodeStartIndex + it;
}
}
@ -95,21 +97,21 @@ GltfFileWriter::GltfFileWriter(MeshResultContext &resultContext,
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 i = 0u; i < boneNodes.size(); i++) {
const float *floatArray = 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);
Q_ASSERT((int)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]["count"] = boneNodes.size();
m_json["accessors"][bufferViewIndex]["type"] = "MAT4";
bufferViewIndex++;
} else {
@ -312,7 +314,7 @@ GltfFileWriter::GltfFileWriter(MeshResultContext &resultContext,
auto findWeight = resultRigWeights->find(oldIndex);
if (findWeight != resultRigWeights->end()) {
for (; i < MAX_WEIGHT_NUM; i++) {
float weight = (quint16)findWeight->second.boneIndicies[i];
float weight = (float)findWeight->second.boneWeights[i];
stream << (float)weight;
if (m_enableComment)
weightList.append(QString("%1").arg(QString::number((float)weight)));
@ -358,37 +360,3 @@ bool GltfFileWriter::save()
file.write(QString::fromStdString(m_json.dump(4)).toUtf8());
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

@ -10,18 +10,6 @@
#include "json.hpp"
#include "skeletondocument.h"
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
@ -33,14 +21,12 @@ public:
bool save();
const QString &textureFilenameInGltf();
private:
void calculateMatrices(const std::vector<AutoRiggerBone> *resultRigBones);
QString m_filename;
QString m_textureFilename;
bool m_outputNormal;
bool m_outputAnimation;
bool m_outputUv;
bool m_testOutputAsWhole;
std::vector<GltfNode> m_boneNodes;
private:
nlohmann::json m_json;
public:

74
src/jointnodetree.cpp Normal file
View File

@ -0,0 +1,74 @@
#include "jointnodetree.h"
const std::vector<JointNode> &JointNodeTree::nodes() const
{
return m_boneNodes;
}
void JointNodeTree::updateRotation(int index, QQuaternion rotation)
{
m_boneNodes[index].rotation = rotation;
}
void JointNodeTree::reset()
{
for (auto &node: m_boneNodes) {
node.rotation = QQuaternion();
}
}
void JointNodeTree::recalculateTransformMatrices()
{
for (decltype(m_boneNodes.size()) i = 0; i < m_boneNodes.size(); i++) {
QMatrix4x4 parentTransformMatrix;
auto &node = m_boneNodes[i];
if (node.parentIndex != -1) {
const auto &parent = m_boneNodes[node.parentIndex];
parentTransformMatrix = parent.transformMatrix;
}
QMatrix4x4 translateMatrix;
translateMatrix.translate(node.translation);
QMatrix4x4 rotationMatrix;
rotationMatrix.rotate(node.rotation);
node.transformMatrix = parentTransformMatrix * translateMatrix * rotationMatrix;
}
for (decltype(m_boneNodes.size()) i = 0; i < m_boneNodes.size(); i++) {
auto &node = m_boneNodes[i];
node.transformMatrix *= node.inverseBindMatrix;
}
}
JointNodeTree::JointNodeTree(const std::vector<AutoRiggerBone> *resultRigBones)
{
if (nullptr == resultRigBones || resultRigBones->empty())
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.headPosition;
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++) {
QMatrix4x4 parentTransformMatrix;
auto &node = m_boneNodes[i];
if (node.parentIndex != -1) {
const auto &parent = m_boneNodes[node.parentIndex];
parentTransformMatrix = parent.transformMatrix;
node.translation = node.position - parent.position;
} else {
node.translation = node.position;
}
QMatrix4x4 translateMatrix;
translateMatrix.translate(node.translation);
node.transformMatrix = parentTransformMatrix * translateMatrix;
node.inverseBindMatrix = node.transformMatrix.inverted();
}
}

32
src/jointnodetree.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef JOINT_NODE_TREE_H
#define JOINT_NODE_TREE_H
#include <QMatrix4x4>
#include <vector>
#include <QQuaternion>
#include "autorigger.h"
struct JointNode
{
int parentIndex;
QString name;
QVector3D position;
QVector3D translation;
QQuaternion rotation;
QMatrix4x4 transformMatrix;
QMatrix4x4 inverseBindMatrix;
std::vector<int> children;
};
class JointNodeTree
{
public:
const std::vector<JointNode> &nodes() const;
JointNodeTree(const std::vector<AutoRiggerBone> *resultRigBones);
void updateRotation(int index, QQuaternion rotation);
void reset();
void recalculateTransformMatrices();
private:
std::vector<JointNode> m_boneNodes;
};
#endif

47
src/posemeshcreator.cpp Normal file
View File

@ -0,0 +1,47 @@
#include <QGuiApplication>
#include "posemeshcreator.h"
#include "skinnedmeshcreator.h"
PoseMeshCreator::PoseMeshCreator(const Poser &poser,
const MeshResultContext &meshResultContext,
const std::map<int, AutoRiggerVertexWeights> &resultWeights) :
m_resultNodes(poser.resultNodes()),
m_meshResultContext(meshResultContext),
m_resultWeights(resultWeights)
{
}
PoseMeshCreator::~PoseMeshCreator()
{
delete m_resultMesh;
}
MeshLoader *PoseMeshCreator::takeResultMesh()
{
MeshLoader *resultMesh = m_resultMesh;
m_resultMesh = nullptr;
return resultMesh;
}
void PoseMeshCreator::createMesh()
{
SkinnedMeshCreator skinnedMeshCreator(m_meshResultContext, m_resultWeights);
std::vector<QMatrix4x4> matricies;
matricies.resize(m_resultNodes.size());
for (decltype(m_resultNodes.size()) i = 0; i < m_resultNodes.size(); i++) {
const auto &node = m_resultNodes[i];
matricies[i] = node.transformMatrix;
}
delete m_resultMesh;
m_resultMesh = skinnedMeshCreator.createMeshFromTransform(matricies);
}
void PoseMeshCreator::process()
{
createMesh();
this->moveToThread(QGuiApplication::instance()->thread());
emit finished();
}

30
src/posemeshcreator.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef POSE_MESH_CREATOR_H
#define POSE_MESH_CREATOR_H
#include <QObject>
#include "poser.h"
#include "meshloader.h"
#include "jointnodetree.h"
#include "meshresultcontext.h"
class PoseMeshCreator : public QObject
{
Q_OBJECT
signals:
void finished();
public:
PoseMeshCreator(const Poser &poser,
const MeshResultContext &meshResultContext,
const std::map<int, AutoRiggerVertexWeights> &resultWeights);
~PoseMeshCreator();
void createMesh();
MeshLoader *takeResultMesh();
public slots:
void process();
private:
std::vector<JointNode> m_resultNodes;
MeshResultContext m_meshResultContext;
std::map<int, AutoRiggerVertexWeights> m_resultWeights;
MeshLoader *m_resultMesh = nullptr;
};
#endif

View File

@ -0,0 +1,60 @@
#include <QThread>
#include <QGridLayout>
#include "posepreviewmanager.h"
PosePreviewManager::PosePreviewManager()
{
}
PosePreviewManager::~PosePreviewManager()
{
delete m_previewMesh;
}
bool PosePreviewManager::isRendering()
{
return nullptr != m_poseMeshCreator;
}
bool PosePreviewManager::postUpdate(const Poser &poser,
const MeshResultContext &meshResultContext,
const std::map<int, AutoRiggerVertexWeights> &resultWeights)
{
if (nullptr != m_poseMeshCreator)
return false;
qDebug() << "Pose mesh generating..";
QThread *thread = new QThread;
m_poseMeshCreator = new PoseMeshCreator(poser, meshResultContext, resultWeights);
m_poseMeshCreator->moveToThread(thread);
connect(thread, &QThread::started, m_poseMeshCreator, &PoseMeshCreator::process);
connect(m_poseMeshCreator, &PoseMeshCreator::finished, this, &PosePreviewManager::poseMeshReady);
connect(m_poseMeshCreator, &PoseMeshCreator::finished, thread, &QThread::quit);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
return true;
}
MeshLoader *PosePreviewManager::takeResultPreviewMesh()
{
if (nullptr == m_previewMesh)
return nullptr;
return new MeshLoader(*m_previewMesh);
}
void PosePreviewManager::poseMeshReady()
{
delete m_previewMesh;
m_previewMesh = m_poseMeshCreator->takeResultMesh();
emit resultPreviewMeshChanged();
qDebug() << "Pose mesh generation done";
delete m_poseMeshCreator;
m_poseMeshCreator = nullptr;
emit renderDone();
}

30
src/posepreviewmanager.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef POSE_PREVIEW_MANAGER_H
#define POSE_PREVIEW_MANAGER_H
#include <QWidget>
#include "skeletondocument.h"
#include "poser.h"
#include "posemeshcreator.h"
#include "meshloader.h"
class PosePreviewManager : public QObject
{
Q_OBJECT
public:
PosePreviewManager();
~PosePreviewManager();
bool isRendering();
bool postUpdate(const Poser &poser,
const MeshResultContext &meshResultContext,
const std::map<int, AutoRiggerVertexWeights> &resultWeights);
MeshLoader *takeResultPreviewMesh();
private slots:
void poseMeshReady();
signals:
void resultPreviewMeshChanged();
void renderDone();
private:
PoseMeshCreator *m_poseMeshCreator = nullptr;
MeshLoader *m_previewMesh = nullptr;
};
#endif

56
src/poser.cpp Normal file
View File

@ -0,0 +1,56 @@
#include <QQuaternion>
#include "poser.h"
Poser::Poser(const std::vector<AutoRiggerBone> &bones) :
m_bones(bones),
m_jointNodeTree(&bones)
{
for (decltype(m_bones.size()) i = 0; i < m_bones.size(); i++) {
m_boneNameToIndexMap[m_bones[i].name] = i;
}
}
Poser::~Poser()
{
}
int Poser::findBoneIndex(const QString &name)
{
auto findResult = m_boneNameToIndexMap.find(name);
if (findResult == m_boneNameToIndexMap.end())
return -1;
return findResult->second;
}
const AutoRiggerBone *Poser::findBone(const QString &name)
{
auto findResult = m_boneNameToIndexMap.find(name);
if (findResult == m_boneNameToIndexMap.end())
return nullptr;
return &m_bones[findResult->second];
}
const std::vector<AutoRiggerBone> &Poser::bones() const
{
return m_bones;
}
const std::vector<JointNode> &Poser::resultNodes() const
{
return m_jointNodeTree.nodes();
}
void Poser::commit()
{
m_jointNodeTree.recalculateTransformMatrices();
}
void Poser::reset()
{
m_jointNodeTree.reset();
}
std::map<QString, std::map<QString, QString>> &Poser::parameters()
{
return m_parameters;
}

28
src/poser.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef POSER_H
#define POSER_H
#include <QObject>
#include "autorigger.h"
#include "jointnodetree.h"
#include "dust3dutil.h"
class Poser : public QObject
{
Q_OBJECT
public:
Poser(const std::vector<AutoRiggerBone> &bones);
~Poser();
const AutoRiggerBone *findBone(const QString &name);
int findBoneIndex(const QString &name);
const std::vector<AutoRiggerBone> &bones() const;
const std::vector<JointNode> &resultNodes() const;
std::map<QString, std::map<QString, QString>> &parameters();
void commit();
void reset();
protected:
std::vector<AutoRiggerBone> m_bones;
std::map<QString, int> m_boneNameToIndexMap;
JointNodeTree m_jointNodeTree;
std::map<QString, std::map<QString, QString>> m_parameters;
};
#endif

View File

@ -18,6 +18,13 @@ RigGenerator::~RigGenerator()
delete m_resultWeights;
}
MeshResultContext *RigGenerator::takeMeshResultContext()
{
MeshResultContext *resultContext = m_meshResultContext;
m_meshResultContext = nullptr;
return resultContext;
}
std::vector<AutoRiggerBone> *RigGenerator::takeResultBones()
{
std::vector<AutoRiggerBone> *resultBones = m_resultBones;
@ -114,18 +121,17 @@ void RigGenerator::process()
m_resultBones = new std::vector<AutoRiggerBone>;
*m_resultBones = resultBones;
for (size_t vertexIndex = 0; vertexIndex < inputVerticesColors.size(); vertexIndex++) {
auto findResult = resultWeights.find((int)vertexIndex);
for (const auto &weightItem: resultWeights) {
size_t vertexIndex = weightItem.first;
const auto &weight = weightItem.second;
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];
}
for (int i = 0; i < 4; i++) {
int boneIndex = weight.boneIndicies[i];
if (boneIndex > 0) {
const auto &bone = resultBones[boneIndex];
blendR += bone.color.red() * weight.boneWeights[i];
blendG += bone.color.green() * weight.boneWeights[i];
blendB += bone.color.blue() * weight.boneWeights[i];
}
}
QColor blendColor = QColor(blendR, blendG, blendB, 255);
@ -135,14 +141,15 @@ void RigGenerator::process()
// Smooth normals
std::map<int, QVector3D> vertexNormalMap;
std::vector<QVector3D> vertexNormalMap;
vertexNormalMap.resize(inputVerticesPositions.size());
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();
item.normalize();
// Create mesh for demo

View File

@ -18,6 +18,7 @@ public:
std::map<int, AutoRiggerVertexWeights> *takeResultWeights();
const std::vector<QString> &missingMarkNames();
const std::vector<QString> &errorMarkNames();
MeshResultContext *takeMeshResultContext();
signals:
void finished();
public slots:

View File

@ -1,8 +1,10 @@
#include <QFormLayout>
#include <QComboBox>
#include <QHBoxLayout>
#include "rigwidget.h"
#include "rigtype.h"
#include "infolabel.h"
#include "theme.h"
RigWidget::RigWidget(const SkeletonDocument *document, QWidget *parent) :
QWidget(parent),
@ -17,7 +19,15 @@ RigWidget::RigWidget(const SkeletonDocument *document, QWidget *parent) :
m_rigTypeBox->addItem(RigTypeToDispName(rigType));
}
formLayout->addRow(tr("Type"), m_rigTypeBox);
//m_poseCheckButton = new QPushButton(QChar(fa::child));
//Theme::initAwesomeButton(m_poseCheckButton);
//m_poseCheckButton->hide();
QHBoxLayout *controlsLayout = new QHBoxLayout;
controlsLayout->addWidget(m_rigTypeBox);
//controlsLayout->addWidget(m_poseCheckButton);
formLayout->addRow(tr("Type"), controlsLayout);
m_rigTypeBox->setCurrentIndex((int)m_document->rigType);
@ -28,6 +38,8 @@ RigWidget::RigWidget(const SkeletonDocument *document, QWidget *parent) :
m_rigWeightRenderWidget = new ModelWidget(this);
m_rigWeightRenderWidget->setMinimumSize(128, 128);
//m_rigWeightRenderWidget->resize(256, 256);
//m_rigWeightRenderWidget->move(-64, 0);
m_rigWeightRenderWidget->setXRotation(0);
m_rigWeightRenderWidget->setYRotation(0);
m_rigWeightRenderWidget->setZRotation(0);
@ -50,6 +62,7 @@ RigWidget::RigWidget(const SkeletonDocument *document, QWidget *parent) :
void RigWidget::rigTypeChanged()
{
//m_poseCheckButton->setVisible(RigType::None != m_document->rigType);
m_rigTypeBox->setCurrentIndex((int)m_document->rigType);
}

View File

@ -2,6 +2,7 @@
#define RIG_WIDGET_H
#include <QWidget>
#include <QComboBox>
#include <QPushButton>
#include "skeletondocument.h"
#include "rigtype.h"
#include "modelwidget.h"

View File

@ -50,7 +50,8 @@ SkeletonDocument::SkeletonDocument() :
m_resultRigWeightMesh(nullptr),
m_resultRigBones(nullptr),
m_resultRigWeights(nullptr),
m_isRigObsolete(false)
m_isRigObsolete(false),
m_riggedResultContext(new MeshResultContext)
{
}
@ -2263,6 +2264,11 @@ void SkeletonDocument::rigReady()
m_resultRigMissingMarkNames = m_rigGenerator->missingMarkNames();
m_resultRigErrorMarkNames = m_rigGenerator->errorMarkNames();
delete m_riggedResultContext;
m_riggedResultContext = m_rigGenerator->takeMeshResultContext();
if (nullptr == m_riggedResultContext)
m_riggedResultContext = new MeshResultContext;
delete m_rigGenerator;
m_rigGenerator = nullptr;
@ -2277,12 +2283,12 @@ void SkeletonDocument::rigReady()
}
}
const std::vector<AutoRiggerBone> *SkeletonDocument::resultRigBones()
const std::vector<AutoRiggerBone> *SkeletonDocument::resultRigBones() const
{
return m_resultRigBones;
}
const std::map<int, AutoRiggerVertexWeights> *SkeletonDocument::resultRigWeights()
const std::map<int, AutoRiggerVertexWeights> *SkeletonDocument::resultRigWeights() const
{
return m_resultRigWeights;
}
@ -2328,3 +2334,8 @@ const std::vector<QString> &SkeletonDocument::resultRigErrorMarkNames() const
{
return m_resultRigErrorMarkNames;
}
const MeshResultContext &SkeletonDocument::currentRiggedResultContext() const
{
return *m_riggedResultContext;
}

View File

@ -427,8 +427,8 @@ public:
MeshLoader *takeResultMesh();
MeshLoader *takeResultTextureMesh();
MeshLoader *takeResultRigWeightMesh();
const std::vector<AutoRiggerBone> *resultRigBones();
const std::map<int, AutoRiggerVertexWeights> *resultRigWeights();
const std::vector<AutoRiggerBone> *resultRigBones() const;
const std::map<int, AutoRiggerVertexWeights> *resultRigWeights() const;
void updateTurnaround(const QImage &image);
void setSharedContextWidget(QOpenGLWidget *sharedContextWidget);
bool hasPastableContentInClipboard() const;
@ -445,6 +445,7 @@ public:
void collectComponentDescendantComponents(QUuid componentId, std::vector<QUuid> &componentIds) const;
const std::vector<QString> &resultRigMissingMarkNames() const;
const std::vector<QString> &resultRigErrorMarkNames() const;
const MeshResultContext &currentRiggedResultContext() const;
public slots:
void removeNode(QUuid nodeId);
void removeEdge(QUuid edgeId);
@ -563,14 +564,15 @@ private: // need initialize
MeshLoader *m_resultRigWeightMesh;
std::vector<AutoRiggerBone> *m_resultRigBones;
std::map<int, AutoRiggerVertexWeights> *m_resultRigWeights;
MeshResultContext *m_riggedResultContext;
bool m_isRigObsolete;
std::vector<QString> m_resultRigMissingMarkNames;
std::vector<QString> m_resultRigErrorMarkNames;
private:
static unsigned long m_maxSnapshot;
std::deque<SkeletonHistoryItem> m_undoItems;
std::deque<SkeletonHistoryItem> m_redoItems;
GeneratedCacheContext m_generatedCacheContext;
std::vector<QString> m_resultRigMissingMarkNames;
std::vector<QString> m_resultRigErrorMarkNames;
};
#endif

View File

@ -31,6 +31,7 @@
#include "rigwidget.h"
#include "modelofflinerender.h"
#include "markiconcreator.h"
#include "tetrapodposeeditwidget.h"
int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16;
int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16;
@ -209,7 +210,6 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
partTreeWidget->setGraphicsFunctions(graphicsWidget);
partTreeDocker->setWidget(partTreeWidget);
addDockWidget(Qt::RightDockWidgetArea, partTreeDocker);
//partTreeDocker->hide();
QDockWidget *rigDocker = new QDockWidget(tr("Rig"), this);
rigDocker->setAllowedAreas(Qt::RightDockWidgetArea);
@ -217,7 +217,16 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
rigDocker->setWidget(rigWidget);
addDockWidget(Qt::RightDockWidgetArea, rigDocker);
//QDockWidget *animationDocker = new QDockWidget(tr("Animation"), this);
//animationDocker->setAllowedAreas(Qt::RightDockWidgetArea);
//TetrapodPoseEditWidget *tetrapodPoseEditWidget = new TetrapodPoseEditWidget(m_document, animationDocker);
//animationDocker->setWidget(tetrapodPoseEditWidget);
//AnimationListWidget *animationListWidget = new AnimationListWidget(m_document, animationDocker);
//animationDocker->setWidget(animationListWidget);
//addDockWidget(Qt::RightDockWidgetArea, animationDocker);
tabifyDockWidget(partTreeDocker, rigDocker);
//tabifyDockWidget(rigDocker, animationDocker);
partTreeDocker->raise();
@ -495,6 +504,13 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
});
m_windowMenu->addAction(m_showRigAction);
//m_showAnimationAction = new QAction(tr("Animation"), this);
//connect(m_showAnimationAction, &QAction::triggered, [=]() {
// animationDocker->show();
// animationDocker->raise();
//});
//m_windowMenu->addAction(m_showAnimationAction);
m_showDebugDialogAction = new QAction(tr("Debug"), this);
connect(m_showDebugDialogAction, &QAction::triggered, g_logBrowser, &LogBrowser::showDialog);
m_windowMenu->addAction(m_showDebugDialogAction);
@ -748,6 +764,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
}
});
//connect(m_document, &SkeletonDocument::resultRigChanged, tetrapodPoseEditWidget, &TetrapodPoseEditWidget::updatePreview);
connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady);
QTimer *timer = new QTimer(this);

View File

@ -129,6 +129,7 @@ private:
QAction *m_showPartsListAction;
QAction *m_showDebugDialogAction;
QAction *m_showRigAction;
QAction *m_showAnimationAction;
QMenu *m_helpMenu;
QAction *m_viewSourceAction;

View File

@ -0,0 +1,69 @@
#include "skinnedmeshcreator.h"
SkinnedMeshCreator::SkinnedMeshCreator(const MeshResultContext &meshResultContext,
const std::map<int, AutoRiggerVertexWeights> &resultWeights) :
m_meshResultContext(meshResultContext),
m_resultWeights(resultWeights)
{
for (const auto &vertex: m_meshResultContext.vertices) {
m_verticesBindPositions.push_back(vertex.position);
}
m_verticesBindNormals.resize(m_meshResultContext.vertices.size());
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++)
m_verticesBindNormals[sourceTriangle.indicies[i]] += sourceTriangle.normal;
}
for (auto &item: m_verticesBindNormals)
item.normalize();
}
MeshLoader *SkinnedMeshCreator::createMeshFromTransform(const std::vector<QMatrix4x4> &matricies)
{
std::vector<QVector3D> transformedPositions = m_verticesBindPositions;
std::vector<QVector3D> transformedPoseNormals = m_verticesBindNormals;
if (!matricies.empty()) {
for (const auto &weightItem: m_resultWeights) {
size_t vertexIndex = weightItem.first;
const auto &weight = weightItem.second;
QMatrix4x4 mixedMatrix;
transformedPositions[vertexIndex] = QVector3D();
transformedPoseNormals[vertexIndex] = QVector3D();
for (int i = 0; i < 4; i++) {
float factor = weight.boneWeights[i];
if (factor > 0) {
transformedPositions[vertexIndex] += matricies[weight.boneIndicies[i]] * m_verticesBindPositions[vertexIndex] * factor;
transformedPoseNormals[vertexIndex] += matricies[weight.boneIndicies[i]] * m_verticesBindNormals[vertexIndex] * factor;
}
}
}
}
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];
QColor triangleColor = m_meshResultContext.triangleColors()[triangleIndex];
for (int i = 0; i < 3; i++) {
Vertex &currentVertex = triangleVertices[triangleVerticesNum++];
const auto &sourcePosition = transformedPositions[sourceTriangle.indicies[i]];
const auto &sourceColor = triangleColor;
const auto &sourceNormal = transformedPoseNormals[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();
}
}
return new MeshLoader(triangleVertices, triangleVerticesNum);
}

23
src/skinnedmeshcreator.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef SKINNED_MESH_CREATOR_H
#define SKINNED_MESH_CREATOR_H
#include <QMatrix4x4>
#include <vector>
#include <QVector3D>
#include "meshloader.h"
#include "meshresultcontext.h"
#include "jointnodetree.h"
class SkinnedMeshCreator
{
public:
SkinnedMeshCreator(const MeshResultContext &meshResultContext,
const std::map<int, AutoRiggerVertexWeights> &resultWeights);
MeshLoader *createMeshFromTransform(const std::vector<QMatrix4x4> &matricies);
private:
MeshResultContext m_meshResultContext;
std::map<int, AutoRiggerVertexWeights> m_resultWeights;
std::vector<QVector3D> m_verticesBindPositions;
std::vector<QVector3D> m_verticesBindNormals;
};
#endif

View File

@ -0,0 +1,205 @@
#include <QVBoxLayout>
#include <QFormLayout>
#include <QGridLayout>
#include <QMenu>
#include <QWidgetAction>
#include "theme.h"
#include "tetrapodposeeditwidget.h"
#include "floatnumberwidget.h"
TetrapodPoseEditWidget::TetrapodPoseEditWidget(const SkeletonDocument *document, QWidget *parent) :
QWidget(parent),
m_document(document)
{
m_posePreviewManager = new PosePreviewManager();
connect(m_posePreviewManager, &PosePreviewManager::renderDone, [=]() {
if (m_isPreviewDirty)
updatePreview();
});
std::map<QString, std::tuple<QPushButton *, PopupWidgetType>> buttons;
buttons["Head"] = std::make_tuple(new QPushButton(tr("Head")), PopupWidgetType::PitchYawRoll);
buttons["Neck"] = std::make_tuple(new QPushButton(tr("Neck")), PopupWidgetType::PitchYawRoll);
buttons["RightUpperArm"] = std::make_tuple(new QPushButton(tr("UpperArm")), PopupWidgetType::PitchYawRoll);
buttons["RightLowerArm"] = std::make_tuple(new QPushButton(tr("LowerArm")), PopupWidgetType::Intersection);
buttons["RightHand"] = std::make_tuple(new QPushButton(tr("Hand")), PopupWidgetType::Intersection);
buttons["LeftUpperArm"] = std::make_tuple(new QPushButton(tr("UpperArm")), PopupWidgetType::PitchYawRoll);
buttons["LeftLowerArm"] = std::make_tuple(new QPushButton(tr("LowerArm")), PopupWidgetType::Intersection);
buttons["LeftHand"] = std::make_tuple(new QPushButton(tr("Hand")), PopupWidgetType::Intersection);
buttons["Chest"] = std::make_tuple(new QPushButton(tr("Chest")), PopupWidgetType::PitchYawRoll);
buttons["Spine"] = std::make_tuple(new QPushButton(tr("Spine")), PopupWidgetType::PitchYawRoll);
buttons["RightUpperLeg"] = std::make_tuple(new QPushButton(tr("UpperLeg")), PopupWidgetType::PitchYawRoll);
buttons["RightLowerLeg"] = std::make_tuple(new QPushButton(tr("LowerLeg")), PopupWidgetType::Intersection);
buttons["RightFoot"] = std::make_tuple(new QPushButton(tr("Foot")), PopupWidgetType::Intersection);
buttons["LeftUpperLeg"] = std::make_tuple(new QPushButton(tr("UpperLeg")), PopupWidgetType::PitchYawRoll);
buttons["LeftLowerLeg"] = std::make_tuple(new QPushButton(tr("LowerLeg")), PopupWidgetType::Intersection);
buttons["LeftFoot"] = std::make_tuple(new QPushButton(tr("Foot")), PopupWidgetType::Intersection);
QGridLayout *marksContainerLayout = new QGridLayout;
marksContainerLayout->setContentsMargins(0, 0, 0, 0);
marksContainerLayout->setSpacing(0);
marksContainerLayout->addWidget(std::get<0>(buttons["Head"]), 0, 1);
marksContainerLayout->addWidget(std::get<0>(buttons["Neck"]), 1, 1);
marksContainerLayout->addWidget(std::get<0>(buttons["RightUpperArm"]), 1, 0);
marksContainerLayout->addWidget(std::get<0>(buttons["RightLowerArm"]), 2, 0);
marksContainerLayout->addWidget(std::get<0>(buttons["RightHand"]), 3, 0);
marksContainerLayout->addWidget(std::get<0>(buttons["LeftUpperArm"]), 1, 2);
marksContainerLayout->addWidget(std::get<0>(buttons["LeftLowerArm"]), 2, 2);
marksContainerLayout->addWidget(std::get<0>(buttons["LeftHand"]), 3, 2);
marksContainerLayout->addWidget(std::get<0>(buttons["Chest"]), 2, 1);
marksContainerLayout->addWidget(std::get<0>(buttons["Spine"]), 3, 1);
QGridLayout *lowerMarksContainerLayout = new QGridLayout;
lowerMarksContainerLayout->setContentsMargins(0, 0, 0, 0);
lowerMarksContainerLayout->setSpacing(0);
lowerMarksContainerLayout->addWidget(std::get<0>(buttons["RightUpperLeg"]), 0, 1);
lowerMarksContainerLayout->addWidget(std::get<0>(buttons["RightLowerLeg"]), 1, 1);
lowerMarksContainerLayout->addWidget(std::get<0>(buttons["RightFoot"]), 2, 0);
lowerMarksContainerLayout->addWidget(std::get<0>(buttons["LeftUpperLeg"]), 0, 2);
lowerMarksContainerLayout->addWidget(std::get<0>(buttons["LeftLowerLeg"]), 1, 2);
lowerMarksContainerLayout->addWidget(std::get<0>(buttons["LeftFoot"]), 2, 3);
QFont buttonFont;
buttonFont.setWeight(QFont::Light);
buttonFont.setPixelSize(7);
buttonFont.setBold(false);
for (const auto &item: buttons) {
QString boneName = item.first;
QPushButton *buttonWidget = std::get<0>(item.second);
PopupWidgetType widgetType = std::get<1>(item.second);
buttonWidget->setFont(buttonFont);
buttonWidget->setMaximumWidth(45);
connect(buttonWidget, &QPushButton::clicked, [this, boneName, widgetType]() {
emit showPopupAngleDialog(boneName, widgetType, mapFromGlobal(QCursor::pos()));
});
}
QVBoxLayout *layout = new QVBoxLayout;
layout->addLayout(marksContainerLayout);
layout->addLayout(lowerMarksContainerLayout);
layout->addStretch();
setLayout(layout);
connect(m_document, &SkeletonDocument::resultRigChanged, this, &TetrapodPoseEditWidget::updatePreview);
}
TetrapodPoseEditWidget::~TetrapodPoseEditWidget()
{
delete m_posePreviewManager;
}
void TetrapodPoseEditWidget::updatePreview()
{
if (m_posePreviewManager->isRendering()) {
m_isPreviewDirty = true;
return;
}
const std::vector<AutoRiggerBone> *rigBones = m_document->resultRigBones();
const std::map<int, AutoRiggerVertexWeights> *rigWeights = m_document->resultRigWeights();
m_isPreviewDirty = false;
if (nullptr == rigBones || nullptr == rigWeights) {
return;
}
delete m_poser;
m_poser = new TetrapodPoser(*rigBones);
m_poser->parameters() = m_parameters;
m_poser->commit();
m_posePreviewManager->postUpdate(*m_poser, m_document->currentRiggedResultContext(), *rigWeights);
}
void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos)
{
QMenu popupMenu;
QWidget *popup = new QWidget;
QVBoxLayout *layout = new QVBoxLayout;
if (PopupWidgetType::PitchYawRoll == popupWidgetType) {
FloatNumberWidget *pitchWidget = new FloatNumberWidget;
pitchWidget->setItemName(tr("Pitch"));
pitchWidget->setRange(-180, 180);
pitchWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "pitch").toFloat());
connect(pitchWidget, &FloatNumberWidget::valueChanged, [=](float value) {
m_parameters[boneName]["pitch"] = QString::number(value);
updatePreview();
});
QPushButton *pitchEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(pitchEraser);
connect(pitchEraser, &QPushButton::clicked, [=]() {
pitchWidget->setValue(0.0);
});
QHBoxLayout *pitchLayout = new QHBoxLayout;
pitchLayout->addWidget(pitchEraser);
pitchLayout->addWidget(pitchWidget);
layout->addLayout(pitchLayout);
FloatNumberWidget *yawWidget = new FloatNumberWidget;
yawWidget->setItemName(tr("Yaw"));
yawWidget->setRange(-180, 180);
yawWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "yaw").toFloat());
connect(yawWidget, &FloatNumberWidget::valueChanged, [=](float value) {
m_parameters[boneName]["yaw"] = QString::number(value);
updatePreview();
});
QPushButton *yawEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(yawEraser);
connect(yawEraser, &QPushButton::clicked, [=]() {
yawWidget->setValue(0.0);
});
QHBoxLayout *yawLayout = new QHBoxLayout;
yawLayout->addWidget(yawEraser);
yawLayout->addWidget(yawWidget);
layout->addLayout(yawLayout);
FloatNumberWidget *rollWidget = new FloatNumberWidget;
rollWidget->setItemName(tr("Roll"));
rollWidget->setRange(-180, 180);
rollWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "roll").toFloat());
connect(rollWidget, &FloatNumberWidget::valueChanged, [=](float value) {
m_parameters[boneName]["roll"] = QString::number(value);
updatePreview();
});
QPushButton *rollEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(rollEraser);
connect(rollEraser, &QPushButton::clicked, [=]() {
rollWidget->setValue(0.0);
});
QHBoxLayout *rollLayout = new QHBoxLayout;
rollLayout->addWidget(rollEraser);
rollLayout->addWidget(rollWidget);
layout->addLayout(rollLayout);
} else {
FloatNumberWidget *intersectionWidget = new FloatNumberWidget;
intersectionWidget->setItemName(tr("Intersection"));
intersectionWidget->setRange(-180, 180);
intersectionWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "intersection").toFloat());
connect(intersectionWidget, &FloatNumberWidget::valueChanged, [=](float value) {
m_parameters[boneName]["intersection"] = QString::number(value);
updatePreview();
});
QPushButton *intersectionEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(intersectionEraser);
connect(intersectionEraser, &QPushButton::clicked, [=]() {
intersectionWidget->setValue(0.0);
});
QHBoxLayout *intersectionLayout = new QHBoxLayout;
intersectionLayout->addWidget(intersectionEraser);
intersectionLayout->addWidget(intersectionWidget);
layout->addLayout(intersectionLayout);
}
popup->setLayout(layout);
QWidgetAction action(this);
action.setDefaultWidget(popup);
popupMenu.addAction(&action);
popupMenu.exec(mapToGlobal(pos));
}

View File

@ -0,0 +1,33 @@
#ifndef TETRAPOD_POSE_EDIT_WIDGET_H
#define TETRAPOD_POSE_EDIT_WIDGET_H
#include <QWidget>
#include <map>
#include <QPointF>
#include "posepreviewmanager.h"
#include "tetrapodposer.h"
#include "skeletondocument.h"
enum class PopupWidgetType
{
PitchYawRoll,
Intersection
};
class TetrapodPoseEditWidget : public QWidget
{
Q_OBJECT
public:
TetrapodPoseEditWidget(const SkeletonDocument *document, QWidget *parent=nullptr);
~TetrapodPoseEditWidget();
public slots:
void updatePreview();
void showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos);
private:
const SkeletonDocument *m_document = nullptr;
PosePreviewManager *m_posePreviewManager = nullptr;
TetrapodPoser *m_poser = nullptr;
bool m_isPreviewDirty = false;
std::map<QString, std::map<QString, QString>> m_parameters;
};
#endif

66
src/tetrapodposer.cpp Normal file
View File

@ -0,0 +1,66 @@
#include "tetrapodposer.h"
TetrapodPoser::TetrapodPoser(const std::vector<AutoRiggerBone> &bones) :
Poser(bones)
{
}
void TetrapodPoser::commit()
{
for (const auto &item: parameters()) {
int boneIndex = findBoneIndex(item.first);
if (-1 == boneIndex) {
continue;
}
auto findPitchResult = item.second.find("pitch");
auto findYawResult = item.second.find("yaw");
auto findRollResult = item.second.find("roll");
if (findPitchResult != item.second.end() ||
findYawResult != item.second.end() ||
findRollResult != item.second.end()) {
float yawAngle = valueOfKeyInMapOrEmpty(item.second, "yaw").toFloat();
if (item.first.startsWith("Left")) {
yawAngle = -yawAngle;
}
QQuaternion rotation = QQuaternion::fromEulerAngles(valueOfKeyInMapOrEmpty(item.second, "pitch").toFloat(),
yawAngle,
valueOfKeyInMapOrEmpty(item.second, "roll").toFloat());
m_jointNodeTree.updateRotation(boneIndex, rotation);
continue;
}
auto findIntersectionResult = item.second.find("intersection");
if (findIntersectionResult != item.second.end()) {
float intersectionAngle = valueOfKeyInMapOrEmpty(item.second, "intersection").toFloat();
const AutoRiggerBone &bone = bones()[boneIndex];
if (bone.name == "LeftHand" || bone.name == "RightHand") {
QVector3D handDirection = bone.tailPosition - bone.headPosition;
QVector3D rotateAxis = QVector3D::crossProduct(handDirection, bone.name == "RightHand" ? QVector3D(1, 0, 0) : QVector3D(-1, 0, 0)).normalized();
QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle);
m_jointNodeTree.updateRotation(boneIndex, rotation);
continue;
} else if (bone.name == "LeftLowerArm" || bone.name == "RightLowerArm") {
QVector3D lowerArmDirection = bone.tailPosition - bone.headPosition;
QVector3D rotateAxis = QVector3D::crossProduct(lowerArmDirection, QVector3D(0, 0, 1)).normalized();
QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle);
m_jointNodeTree.updateRotation(boneIndex, rotation);
continue;
} else if (bone.name == "LeftFoot" || bone.name == "RightFoot") {
QVector3D footDirection = bone.tailPosition - bone.headPosition;
QVector3D rotateAxis = QVector3D::crossProduct(footDirection, QVector3D(0, 1, 0)).normalized();
QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle);
m_jointNodeTree.updateRotation(boneIndex, rotation);
continue;
} else if (bone.name == "LeftLowerLeg" || bone.name == "RightLowerLeg") {
QVector3D lowerLegDirection = bone.tailPosition - bone.headPosition;
QVector3D rotateAxis = QVector3D::crossProduct(lowerLegDirection, QVector3D(0, 0, -1)).normalized();
QQuaternion rotation = QQuaternion::fromAxisAndAngle(rotateAxis, intersectionAngle);
m_jointNodeTree.updateRotation(boneIndex, rotation);
continue;
}
continue;
}
}
Poser::commit();
}

14
src/tetrapodposer.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef TETRAPOD_POSER_H
#define TETRAPOD_POSER_H
#include "poser.h"
class TetrapodPoser : public Poser
{
Q_OBJECT
public:
TetrapodPoser(const std::vector<AutoRiggerBone> &bones);
public:
void commit();
};
#endif