Fix rig skinning weights, add pose configure UI.
parent
8dcabeae72
commit
c64743f2f2
21
dust3d.pro
21
dust3d.pro
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
struct CCDIKNode
|
||||
{
|
||||
QVector3D position;
|
||||
bool hasConstraint = false;
|
||||
};
|
||||
|
||||
class CCDIKSolver
|
||||
|
|
|
@ -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);
|
||||
|
@ -16,11 +18,19 @@ FloatNumberWidget::FloatNumberWidget(QWidget *parent) :
|
|||
updateValueLabel(fvalue);
|
||||
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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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>> ¶meters();
|
||||
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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define RIG_WIDGET_H
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
#include <QPushButton>
|
||||
#include "skeletondocument.h"
|
||||
#include "rigtype.h"
|
||||
#include "modelwidget.h"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 ¤tRiggedResultContext() 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
|
||||
|
|
|
@ -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);
|
||||
|
@ -747,6 +763,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
rigWidget->rigWeightRenderWidget()->update();
|
||||
}
|
||||
});
|
||||
|
||||
//connect(m_document, &SkeletonDocument::resultRigChanged, tetrapodPoseEditWidget, &TetrapodPoseEditWidget::updatePreview);
|
||||
|
||||
connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady);
|
||||
|
||||
|
|
|
@ -129,6 +129,7 @@ private:
|
|||
QAction *m_showPartsListAction;
|
||||
QAction *m_showDebugDialogAction;
|
||||
QAction *m_showRigAction;
|
||||
QAction *m_showAnimationAction;
|
||||
|
||||
QMenu *m_helpMenu;
|
||||
QAction *m_viewSourceAction;
|
||||
|
|
|
@ -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 ¤tVertex = 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);
|
||||
}
|
|
@ -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
|
|
@ -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));
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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
|
Loading…
Reference in New Issue