Add animation clip generator

- Fix skeleton weights
- Add rig controller
- Add animation clip generator
- Implement basic experiment animation(squat)
master
Jeremy Hu 2018-06-11 22:24:25 +08:00
parent b467bcdc76
commit 0829ef818a
25 changed files with 1079 additions and 194 deletions

View File

@ -131,6 +131,21 @@ HEADERS += src/skeletonbonemark.h
SOURCES += src/intermediateboneremover.cpp
HEADERS += src/intermediateboneremover.h
SOURCES += src/animationpanelwidget.cpp
HEADERS += src/animationpanelwidget.h
SOURCES += src/rigcontroller.cpp
HEADERS += src/rigcontroller.h
SOURCES += src/jointnodetree.cpp
HEADERS += src/jointnodetree.h
SOURCES += src/animationclipgenerator.cpp
HEADERS += src/animationclipgenerator.h
SOURCES += src/skinnedmesh.cpp
HEADERS += src/skinnedmesh.h
HEADERS += src/qtlightmapper.h
SOURCES += src/main.cpp

View File

@ -0,0 +1,49 @@
#include <QGuiApplication>
#include "animationclipgenerator.h"
#include "skinnedmesh.h"
AnimationClipGenerator::AnimationClipGenerator(const MeshResultContext &resultContext,
const QString &motionName, const std::map<QString, QString> &parameters) :
m_resultContext(resultContext),
m_motionName(motionName),
m_parameters(parameters)
{
}
AnimationClipGenerator::~AnimationClipGenerator()
{
for (auto &mesh: m_frameMeshes) {
delete mesh.second;
}
}
std::vector<std::pair<int, MeshLoader *>> AnimationClipGenerator::takeFrameMeshes()
{
std::vector<std::pair<int, MeshLoader *>> result = m_frameMeshes;
m_frameMeshes.clear();
return result;
}
void AnimationClipGenerator::generate()
{
SkinnedMesh skinnedMesh(m_resultContext);
skinnedMesh.startRig();
RigController *rigController = skinnedMesh.rigController();
for (float amount = 0.0; amount <= 0.5; amount += 0.05) {
rigController->squat(amount);
RigFrame frame;
rigController->saveFrame(frame);
skinnedMesh.applyRigFrameToMesh(frame);
m_frameMeshes.push_back(std::make_pair(10, skinnedMesh.toMeshLoader()));
}
}
void AnimationClipGenerator::process()
{
generate();
this->moveToThread(QGuiApplication::instance()->thread());
emit finished();
}

View File

@ -0,0 +1,29 @@
#ifndef ANIMATION_CLIP_GENERATOR_H
#define ANIMATION_CLIP_GENERATOR_H
#include <QObject>
#include <map>
#include <QString>
#include "meshresultcontext.h"
#include "meshloader.h"
class AnimationClipGenerator : public QObject
{
Q_OBJECT
signals:
void finished();
public slots:
void process();
public:
AnimationClipGenerator(const MeshResultContext &resultContext,
const QString &motionName, const std::map<QString, QString> &parameters);
~AnimationClipGenerator();
std::vector<std::pair<int, MeshLoader *>> takeFrameMeshes();
void generate();
private:
MeshResultContext m_resultContext;
QString m_motionName;
std::map<QString, QString> m_parameters;
std::vector<std::pair<int, MeshLoader *>> m_frameMeshes;
};
#endif

View File

@ -0,0 +1,166 @@
#include <QHBoxLayout>
#include <QPushButton>
#include "animationpanelwidget.h"
#include "version.h"
AnimationPanelWidget::AnimationPanelWidget(SkeletonDocument *document, QWidget *parent) :
QWidget(parent),
m_document(document),
m_animationClipGenerator(nullptr),
m_lastFrameMesh(nullptr),
m_sourceMeshReady(false)
{
QHBoxLayout *moveControlButtonLayout = new QHBoxLayout;
QHBoxLayout *fightControlButtonLayout = new QHBoxLayout;
QPushButton *resetButton = new QPushButton(tr("Reset"));
connect(resetButton, &QPushButton::clicked, [=] {
m_lastMotionName.clear();
emit panelClosed();
});
QPushButton *walkButton = new QPushButton(tr("Walk"));
connect(walkButton, &QPushButton::clicked, [=] {
generateClip("Walk");
});
QPushButton *runButton = new QPushButton(tr("Run"));
connect(runButton, &QPushButton::clicked, [=] {
generateClip("Run");
});
QPushButton *attackButton = new QPushButton(tr("Attack"));
connect(attackButton, &QPushButton::clicked, [=] {
generateClip("Attack");
});
QPushButton *hurtButton = new QPushButton(tr("Hurt"));
connect(hurtButton, &QPushButton::clicked, [=] {
generateClip("Hurt");
});
QPushButton *dieButton = new QPushButton(tr("Die"));
connect(dieButton, &QPushButton::clicked, [=] {
generateClip("Die");
});
moveControlButtonLayout->addStretch();
moveControlButtonLayout->addWidget(resetButton);
moveControlButtonLayout->addWidget(walkButton);
moveControlButtonLayout->addWidget(runButton);
moveControlButtonLayout->addStretch();
fightControlButtonLayout->addStretch();
fightControlButtonLayout->addWidget(attackButton);
fightControlButtonLayout->addWidget(hurtButton);
fightControlButtonLayout->addWidget(dieButton);
fightControlButtonLayout->addStretch();
QVBoxLayout *controlLayout = new QVBoxLayout;
controlLayout->setSpacing(0);
controlLayout->setContentsMargins(0, 0, 0, 0);
controlLayout->addLayout(moveControlButtonLayout);
controlLayout->addLayout(fightControlButtonLayout);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(controlLayout);
setLayout(mainLayout);
m_countForFrame.start();
setWindowTitle(APP_NAME);
}
AnimationPanelWidget::~AnimationPanelWidget()
{
delete m_lastFrameMesh;
for (auto &it: m_frameMeshes) {
delete it.second;
}
}
void AnimationPanelWidget::sourceMeshChanged()
{
m_sourceMeshReady = true;
if (m_nextMotionName.isEmpty())
return;
generateClip(m_nextMotionName);
}
void AnimationPanelWidget::hideEvent(QHideEvent *event)
{
m_lastMotionName.clear();
emit panelClosed();
}
void AnimationPanelWidget::generateClip(QString motionName)
{
if (nullptr != m_animationClipGenerator || !m_sourceMeshReady) {
m_nextMotionName = motionName;
return;
}
m_lastMotionName = motionName;
m_nextMotionName.clear();
qDebug() << "Animation clip generating..";
QThread *thread = new QThread;
std::map<QString, QString> parameters;
m_animationClipGenerator = new AnimationClipGenerator(m_document->currentPostProcessedResultContext(),
m_nextMotionName, parameters);
m_animationClipGenerator->moveToThread(thread);
connect(thread, &QThread::started, m_animationClipGenerator, &AnimationClipGenerator::process);
connect(m_animationClipGenerator, &AnimationClipGenerator::finished, this, &AnimationPanelWidget::clipReady);
connect(m_animationClipGenerator, &AnimationClipGenerator::finished, thread, &QThread::quit);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
}
void AnimationPanelWidget::clipReady()
{
m_frameMeshes = m_animationClipGenerator->takeFrameMeshes();
delete m_animationClipGenerator;
m_animationClipGenerator = nullptr;
m_countForFrame.restart();
if (!m_frameMeshes.empty())
QTimer::singleShot(m_frameMeshes[0].first, this, &AnimationPanelWidget::frameReadyToShow);
qDebug() << "Animation clip generation done";
if (!m_nextMotionName.isEmpty())
generateClip(m_nextMotionName);
}
MeshLoader *AnimationPanelWidget::takeFrameMesh()
{
if (m_lastMotionName.isEmpty())
return m_document->takeResultMesh();
if (m_frameMeshes.empty()) {
if (nullptr != m_lastFrameMesh)
return new MeshLoader(*m_lastFrameMesh);
return nullptr;
}
int millis = m_frameMeshes[0].first - m_countForFrame.elapsed();
if (millis > 0) {
QTimer::singleShot(millis, this, &AnimationPanelWidget::frameReadyToShow);
if (nullptr != m_lastFrameMesh)
return new MeshLoader(*m_lastFrameMesh);
return nullptr;
}
MeshLoader *mesh = m_frameMeshes[0].second;
m_frameMeshes.erase(m_frameMeshes.begin());
m_countForFrame.restart();
if (!m_frameMeshes.empty()) {
QTimer::singleShot(m_frameMeshes[0].first, this, &AnimationPanelWidget::frameReadyToShow);
}
delete m_lastFrameMesh;
m_lastFrameMesh = new MeshLoader(*mesh);
return mesh;
}

View File

@ -0,0 +1,37 @@
#ifndef ANIMATION_PANEL_WIDGET_H
#define ANIMATION_PANEL_WIDGET_H
#include <QWidget>
#include <QTime>
#include <QTimer>
#include "skeletondocument.h"
#include "animationclipgenerator.h"
class AnimationPanelWidget : public QWidget
{
Q_OBJECT
signals:
void frameReadyToShow();
void panelClosed();
public:
AnimationPanelWidget(SkeletonDocument *document, QWidget *parent=nullptr);
~AnimationPanelWidget();
MeshLoader *takeFrameMesh();
protected:
void hideEvent(QHideEvent *event);
public slots:
void generateClip(QString motionName);
void clipReady();
void sourceMeshChanged();
private:
SkeletonDocument *m_document;
AnimationClipGenerator *m_animationClipGenerator;
MeshLoader *m_lastFrameMesh;
bool m_sourceMeshReady;
private:
std::vector<std::pair<int, MeshLoader *>> m_frameMeshes;
QTime m_countForFrame;
QString m_nextMotionName;
QString m_lastMotionName;
};
#endif

View File

@ -1,11 +1,13 @@
#include <QtGlobal>
#include <QMatrix4x4>
#include <QDebug>
#include <cmath>
#include "ccdikresolver.h"
CCDIKSolver::CCDIKSolver() :
m_maxRound(5),
m_distanceThreshold2(0.001 * 0.001)
m_maxRound(4),
m_distanceThreshold2(0.01 * 0.01),
m_distanceCeaseThreshold2(0.01 * 0.01)
{
}
@ -39,7 +41,7 @@ void CCDIKSolver::solveTo(const QVector3D &position)
qDebug() << "Round:" << i << " distance2:" << distance2;
if (distance2 <= m_distanceThreshold2)
break;
if (lastDistance2 > 0 && distance2 >= lastDistance2)
if (lastDistance2 > 0 && abs(distance2 - lastDistance2) <= m_distanceCeaseThreshold2)
break;
lastDistance2 = distance2;
iterate();
@ -48,7 +50,7 @@ void CCDIKSolver::solveTo(const QVector3D &position)
const QVector3D &CCDIKSolver::getNodeSolvedPosition(int index)
{
Q_ASSERT(index >= 0 && index < m_nodes.size());
Q_ASSERT(index >= 0 && index < (int)m_nodes.size());
return m_nodes[index].position;
}
@ -62,13 +64,13 @@ void CCDIKSolver::iterate()
for (int i = m_nodes.size() - 2; i >= 0; i--) {
const auto &origin = m_nodes[i];
const auto &endEffector = m_nodes[m_nodes.size() - 1];
QVector3D from = endEffector.position - origin.position;
QVector3D to = m_destination - origin.position;
QVector3D from = (endEffector.position - origin.position).normalized();
QVector3D to = (m_destination - origin.position).normalized();
auto quaternion = QQuaternion::rotationTo(from, to);
for (size_t j = i + 1; j <= m_nodes.size() - 1; j++) {
auto &next = m_nodes[j];
const auto offset = next.position - origin.position;
next.position = origin.position + quaternion.rotatedVector(offset).normalized() * offset.length();
next.position = origin.position + quaternion.rotatedVector(offset);
}
}
}

View File

@ -26,6 +26,7 @@ private:
QVector3D m_destination;
int m_maxRound;
float m_distanceThreshold2;
float m_distanceCeaseThreshold2;
};
#endif

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/
@ -32,60 +33,34 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
QFileInfo nameInfo(filename);
QString textureFilenameWithoutPath = nameInfo.completeBaseName() + ".png";
m_textureFilename = nameInfo.path() + QDir::separator() + textureFilenameWithoutPath;
JointInfo rootHandleJoint;
{
rootHandleJoint.jointIndex = m_tracedJoints.size();
QMatrix4x4 localMatrix;
rootHandleJoint.translation = QVector3D(0, - 1, 0);
localMatrix.translate(rootHandleJoint.translation);
rootHandleJoint.worldMatrix = localMatrix;
rootHandleJoint.inverseBindMatrix = rootHandleJoint.worldMatrix.inverted();
m_tracedJoints.push_back(rootHandleJoint);
}
JointInfo rootCenterJoint;
{
rootCenterJoint.jointIndex = m_tracedJoints.size();
QMatrix4x4 localMatrix;
rootCenterJoint.translation = QVector3D(rootNode->origin.x(), rootNode->origin.y() + 1, rootNode->origin.z());
localMatrix.translate(rootCenterJoint.translation);
rootCenterJoint.worldMatrix = rootHandleJoint.worldMatrix * localMatrix;
rootCenterJoint.direction = QVector3D(0, 1, 0);
rootCenterJoint.inverseBindMatrix = rootCenterJoint.worldMatrix.inverted();
m_tracedJoints[rootHandleJoint.jointIndex].children.push_back(rootCenterJoint.jointIndex);
m_tracedJoints.push_back(rootCenterJoint);
}
std::set<std::pair<int, int>> visitedNodes;
std::set<std::pair<std::pair<int, int>, std::pair<int, int>>> connections;
m_tracedNodeToJointIndexMap[std::make_pair(rootNode->bmeshId, rootNode->nodeId)] = rootCenterJoint.jointIndex;
traceBoneFromJoint(resultContext, std::make_pair(rootNode->bmeshId, rootNode->nodeId), visitedNodes, connections, rootCenterJoint.jointIndex);
JointNodeTree jointNodeTree(resultContext);
const std::vector<JointInfo> &tracedJoints = jointNodeTree.joints();
m_json["asset"]["version"] = "2.0";
m_json["asset"]["generator"] = APP_NAME " " APP_HUMAN_VER;
m_json["scenes"][0]["nodes"] = {0};
m_json["nodes"][0]["children"] = {1, 2};
m_json["nodes"][1]["mesh"] = 0;
m_json["nodes"][1]["skin"] = 0;
m_json["nodes"][0]["mesh"] = 0;
m_json["nodes"][0]["skin"] = 0;
m_json["nodes"][0]["children"] = {1};
int skeletonNodeStartIndex = 2;
int skeletonNodeStartIndex = 1;
for (auto i = 0u; i < m_tracedJoints.size(); i++) {
m_json["nodes"][skeletonNodeStartIndex + i]["translation"] = {m_tracedJoints[i].translation.x(),
m_tracedJoints[i].translation.y(),
m_tracedJoints[i].translation.z()
for (auto i = 0u; i < tracedJoints.size(); i++) {
m_json["nodes"][skeletonNodeStartIndex + i]["translation"] = {tracedJoints[i].translation.x(),
tracedJoints[i].translation.y(),
tracedJoints[i].translation.z()
};
m_json["nodes"][skeletonNodeStartIndex + i]["rotation"] = {m_tracedJoints[i].rotation.x(),
m_tracedJoints[i].rotation.y(),
m_tracedJoints[i].rotation.z(),
m_tracedJoints[i].rotation.scalar()
m_json["nodes"][skeletonNodeStartIndex + i]["rotation"] = {tracedJoints[i].rotation.x(),
tracedJoints[i].rotation.y(),
tracedJoints[i].rotation.z(),
tracedJoints[i].rotation.scalar()
};
if (m_tracedJoints[i].children.empty())
if (tracedJoints[i].children.empty())
continue;
m_json["nodes"][skeletonNodeStartIndex + i]["children"] = {};
for (const auto &it: m_tracedJoints[i].children) {
for (const auto &it: tracedJoints[i].children) {
m_json["nodes"][skeletonNodeStartIndex + i]["children"] += skeletonNodeStartIndex + it;
}
}
@ -93,7 +68,7 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
m_json["skins"][0]["inverseBindMatrices"] = 0;
m_json["skins"][0]["skeleton"] = skeletonNodeStartIndex;
m_json["skins"][0]["joints"] = {};
for (auto i = 0u; i < m_tracedJoints.size(); i++) {
for (auto i = 0u; i < tracedJoints.size(); i++) {
m_json["skins"][0]["joints"] += skeletonNodeStartIndex + i;
}
@ -115,13 +90,13 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex;
m_json["accessors"][bufferViewIndex]["byteOffset"] = 0;
m_json["accessors"][bufferViewIndex]["componentType"] = 5126;
m_json["accessors"][bufferViewIndex]["count"] = m_tracedJoints.size();
m_json["accessors"][bufferViewIndex]["count"] = tracedJoints.size();
m_json["accessors"][bufferViewIndex]["type"] = "MAT4";
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = (int)binaries.size();
int bufferViews0FromOffset = (int)binaries.size();
for (auto i = 0u; i < m_tracedJoints.size(); i++) {
const float *floatArray = m_tracedJoints[i].inverseBindMatrix.constData();
for (auto i = 0u; i < tracedJoints.size(); i++) {
const float *floatArray = tracedJoints[i].inverseBindMatrix.constData();
for (auto j = 0u; j < 16; j++) {
stream << (float)floatArray[j];
}
@ -254,7 +229,7 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
for (const auto &it: part.second.weights) {
auto i = 0u;
for (; i < it.size() && i < MAX_WEIGHT_NUM; i++) {
stream << (quint16)m_tracedNodeToJointIndexMap[it[i].sourceNode];
stream << (quint16)jointNodeTree.nodeToJointIndex(it[i].sourceNode.first, it[i].sourceNode.second);
}
for (; i < MAX_WEIGHT_NUM; i++) {
stream << (quint16)0;
@ -277,7 +252,7 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
for (const auto &it: part.second.weights) {
auto i = 0u;
for (; i < it.size() && i < MAX_WEIGHT_NUM; i++) {
stream << (quint16)it[i].weight;
stream << (float)it[i].weight;
}
for (; i < MAX_WEIGHT_NUM; i++) {
stream << (float)0.0;
@ -321,68 +296,6 @@ const QString &GLTFFileWriter::textureFilenameInGltf()
return m_textureFilename;
}
void GLTFFileWriter::traceBoneFromJoint(MeshResultContext &resultContext, std::pair<int, int> node, std::set<std::pair<int, int>> &visitedNodes, std::set<std::pair<std::pair<int, int>, std::pair<int, int>>> &connections, int parentIndex)
{
if (visitedNodes.find(node) != visitedNodes.end())
return;
visitedNodes.insert(node);
const auto &neighbors = resultContext.nodeNeighbors().find(node);
if (neighbors == resultContext.nodeNeighbors().end()) {
return;
}
for (const auto &it: neighbors->second) {
if (connections.find(std::make_pair(std::make_pair(node.first, node.second), std::make_pair(it.first, it.second))) != connections.end())
continue;
connections.insert(std::make_pair(std::make_pair(node.first, node.second), std::make_pair(it.first, it.second)));
connections.insert(std::make_pair(std::make_pair(it.first, it.second), std::make_pair(node.first, node.second)));
const auto &fromNode = resultContext.bmeshNodeMap().find(std::make_pair(node.first, node.second));
if (fromNode == resultContext.bmeshNodeMap().end()) {
qDebug() << "bmeshNodeMap find failed:" << node.first << node.second;
continue;
}
const auto &toNode = resultContext.bmeshNodeMap().find(std::make_pair(it.first, it.second));
if (toNode == resultContext.bmeshNodeMap().end()) {
qDebug() << "bmeshNodeMap find failed:" << it.first << it.second;
continue;
}
QVector3D boneDirect = toNode->second->origin - fromNode->second->origin;
QVector3D normalizedBoneDirect = boneDirect.normalized();
QMatrix4x4 translateMat;
translateMat.translate(boneDirect);
QQuaternion rotation;
QMatrix4x4 rotateMat;
QVector3D cross = QVector3D::crossProduct(normalizedBoneDirect, m_tracedJoints[parentIndex].direction).normalized();
float dot = QVector3D::dotProduct(normalizedBoneDirect, m_tracedJoints[parentIndex].direction);
float angle = acos(dot);
rotation = QQuaternion::fromAxisAndAngle(cross, angle);
rotateMat.rotate(rotation);
QMatrix4x4 localMatrix;
localMatrix = translateMat * rotateMat;
QMatrix4x4 worldMatrix;
worldMatrix = m_tracedJoints[parentIndex].worldMatrix * localMatrix;
JointInfo joint;
joint.position = toNode->second->origin;
joint.direction = normalizedBoneDirect;
joint.translation = boneDirect;
joint.rotation = rotation;
joint.jointIndex = m_tracedJoints.size();
joint.worldMatrix = worldMatrix;
joint.inverseBindMatrix = worldMatrix.inverted();
m_tracedNodeToJointIndexMap[std::make_pair(it.first, it.second)] = joint.jointIndex;
m_tracedJoints.push_back(joint);
m_tracedJoints[parentIndex].children.push_back(joint.jointIndex);
traceBoneFromJoint(resultContext, it, visitedNodes, connections, joint.jointIndex);
}
}
bool GLTFFileWriter::save()
{
QFile file(m_filename);

View File

@ -8,31 +8,15 @@
#include "meshresultcontext.h"
#include "json.hpp"
struct JointInfo
{
int jointIndex;
QVector3D position;
QVector3D direction;
QVector3D translation;
QQuaternion rotation;
QMatrix4x4 worldMatrix;
QMatrix4x4 inverseBindMatrix;
std::vector<int> children;
};
class GLTFFileWriter : public QObject
{
Q_OBJECT
public:
GLTFFileWriter(MeshResultContext &resultContext, const QString &filename);
bool save();
void traceBones(MeshResultContext &resultContext);
void traceBoneFromJoint(MeshResultContext &resultContext, std::pair<int, int> node, std::set<std::pair<int, int>> &visitedNodes, std::set<std::pair<std::pair<int, int>, std::pair<int, int>>> &connections, int parentIndex);
const QString &textureFilenameInGltf();
private:
QByteArray m_data;
std::vector<JointInfo> m_tracedJoints;
std::map<std::pair<int, int>, int> m_tracedNodeToJointIndexMap;
QString getMatrixStringInColumnOrder(const QMatrix4x4 &mat);
QString m_filename;
QString m_textureFilename;

126
src/jointnodetree.cpp Normal file
View File

@ -0,0 +1,126 @@
#include <cmath>
#include "jointnodetree.h"
JointNodeTree::JointNodeTree(MeshResultContext &resultContext)
{
const BmeshNode *rootNode = resultContext.centerBmeshNode();
if (!rootNode) {
qDebug() << "Cannot construct JointNodeTree because lack of root node";
return;
}
JointInfo rootCenterJoint;
{
rootCenterJoint.jointIndex = m_tracedJoints.size();
QMatrix4x4 localMatrix;
rootCenterJoint.partId = rootNode->bmeshId;
rootCenterJoint.nodeId = rootNode->nodeId;
rootCenterJoint.position = rootNode->origin;
rootCenterJoint.boneMark = rootNode->boneMark;
m_tracedJoints.push_back(rootCenterJoint);
}
//qDebug() << "Root node partId:" << rootNode->bmeshId << "nodeId:" << rootNode->nodeId;
std::set<std::pair<int, int>> visitedNodes;
std::set<std::pair<std::pair<int, int>, std::pair<int, int>>> connections;
m_tracedNodeToJointIndexMap[std::make_pair(rootNode->bmeshId, rootNode->nodeId)] = rootCenterJoint.jointIndex;
traceBoneFromJoint(resultContext, std::make_pair(rootNode->bmeshId, rootNode->nodeId), visitedNodes, connections, rootCenterJoint.jointIndex);
calculateMatrices();
}
void JointNodeTree::traceBoneFromJoint(MeshResultContext &resultContext, std::pair<int, int> node, std::set<std::pair<int, int>> &visitedNodes, std::set<std::pair<std::pair<int, int>, std::pair<int, int>>> &connections, int parentIndex)
{
if (visitedNodes.find(node) != visitedNodes.end())
return;
visitedNodes.insert(node);
const auto &neighbors = resultContext.nodeNeighbors().find(node);
if (neighbors == resultContext.nodeNeighbors().end())
return;
for (const auto &it: neighbors->second) {
if (connections.find(std::make_pair(std::make_pair(node.first, node.second), std::make_pair(it.first, it.second))) != connections.end())
continue;
connections.insert(std::make_pair(std::make_pair(node.first, node.second), std::make_pair(it.first, it.second)));
connections.insert(std::make_pair(std::make_pair(it.first, it.second), std::make_pair(node.first, node.second)));
const auto &fromNode = resultContext.bmeshNodeMap().find(std::make_pair(node.first, node.second));
if (fromNode == resultContext.bmeshNodeMap().end()) {
qDebug() << "bmeshNodeMap find failed:" << node.first << node.second;
continue;
}
const auto &toNode = resultContext.bmeshNodeMap().find(std::make_pair(it.first, it.second));
if (toNode == resultContext.bmeshNodeMap().end()) {
qDebug() << "bmeshNodeMap find failed:" << it.first << it.second;
continue;
}
JointInfo joint;
joint.position = toNode->second->origin;
joint.jointIndex = m_tracedJoints.size();
joint.partId = toNode->second->bmeshId;
joint.nodeId = toNode->second->nodeId;
joint.boneMark = toNode->second->boneMark;
m_tracedNodeToJointIndexMap[std::make_pair(it.first, it.second)] = joint.jointIndex;
m_tracedJoints.push_back(joint);
m_tracedJoints[parentIndex].children.push_back(joint.jointIndex);
traceBoneFromJoint(resultContext, it, visitedNodes, connections, joint.jointIndex);
}
}
std::vector<JointInfo> &JointNodeTree::joints()
{
return m_tracedJoints;
}
int JointNodeTree::nodeToJointIndex(int partId, int nodeId)
{
const auto &findIt = m_tracedNodeToJointIndexMap.find(std::make_pair(partId, nodeId));
if (findIt == m_tracedNodeToJointIndexMap.end())
return 0;
return findIt->second;
}
void JointNodeTree::calculateMatrices()
{
if (joints().empty())
return;
calculateMatricesFrom(0, QVector3D(), QVector3D(), QMatrix4x4());
}
void JointNodeTree::calculateMatricesFrom(int jointIndex, const QVector3D &parentPosition, const QVector3D &parentDirection, const QMatrix4x4 &parentMatrix)
{
auto &joint = joints()[jointIndex];
QVector3D translation = joint.position - parentPosition;
QVector3D direction = translation.normalized();
QMatrix4x4 translateMatrix;
translateMatrix.translate(translation);
QMatrix4x4 rotateMatrix;
QVector3D cross = QVector3D::crossProduct(parentDirection, direction).normalized();
float dot = QVector3D::dotProduct(parentDirection, direction);
float angle = acos(dot);
QQuaternion rotation = QQuaternion::fromAxisAndAngle(cross, angle);
rotateMatrix.rotate(QQuaternion::fromAxisAndAngle(cross, angle));
QMatrix4x4 localMatrix = translateMatrix * rotateMatrix;
QMatrix4x4 bindMatrix = parentMatrix * localMatrix;
joint.localMatrix = localMatrix;
joint.translation = translation;
joint.rotation = rotation;
joint.bindMatrix = bindMatrix;
joint.inverseBindMatrix = joint.bindMatrix.inverted();
for (const auto &child: joint.children) {
calculateMatricesFrom(child, joint.position, direction, bindMatrix);
}
}
void JointNodeTree::recalculateMatricesAfterPositionsUpdated()
{
calculateMatrices();
}

39
src/jointnodetree.h Normal file
View File

@ -0,0 +1,39 @@
#ifndef JOINT_NODE_TREE_H
#define JOINT_NODE_TREE_H
#include <QVector3D>
#include <QMatrix4x4>
#include <vector>
#include "meshresultcontext.h"
#include "skeletonbonemark.h"
struct JointInfo
{
int jointIndex = 0;
int partId = 0;
int nodeId = 0;
SkeletonBoneMark boneMark = SkeletonBoneMark::None;
QVector3D position;
QVector3D translation;
QQuaternion rotation;
QMatrix4x4 localMatrix;
QMatrix4x4 bindMatrix;
QMatrix4x4 inverseBindMatrix;
std::vector<int> children;
};
class JointNodeTree
{
public:
JointNodeTree(MeshResultContext &resultContext);
std::vector<JointInfo> &joints();
int nodeToJointIndex(int partId, int nodeId);
void recalculateMatricesAfterPositionsUpdated();
private:
void calculateMatrices();
void calculateMatricesFrom(int jointIndex, const QVector3D &parentPosition, const QVector3D &parentDirection, const QMatrix4x4 &parentMatrix);
std::vector<JointInfo> m_tracedJoints;
std::map<std::pair<int, int>, int> m_tracedNodeToJointIndexMap;
void traceBoneFromJoint(MeshResultContext &resultContext, std::pair<int, int> node, std::set<std::pair<int, int>> &visitedNodes, std::set<std::pair<std::pair<int, int>, std::pair<int, int>>> &connections, int parentIndex);
};
#endif

View File

@ -326,12 +326,15 @@ void MeshGenerator::process()
//qDebug() << "bmeshId[" << bmeshId << "] add lonely node[" << bmeshNodeId << "]" << radius << x << y << z;
bmeshNodeMap[nodeIt.first] = bmeshNodeId;
SkeletonBoneMark boneMark = SkeletonBoneMarkFromString(valueOfKeyInMapOrEmpty(nodeIt.second, "boneMark").toUtf8().constData());
BmeshNode bmeshNode;
bmeshNode.bmeshId = bmeshId;
bmeshNode.origin = QVector3D(x, y, z);
bmeshNode.radius = radius;
bmeshNode.nodeId = bmeshNodeId;
bmeshNode.color = partColorMap[partId];
bmeshNode.boneMark = boneMark;
m_meshResultContext->bmeshNodes.push_back(bmeshNode);
if (partMirrorFlagMap[partId]) {

View File

@ -140,34 +140,44 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColo
delete[] edgeNormals;
}
/*
MeshLoader::MeshLoader(const std::vector<vertex_t> &vertices, const std::vector<int> &indicies, const std::vector<QVector3D> &normals) :
MeshLoader::MeshLoader(const MeshLoader &mesh) :
m_triangleVertices(nullptr),
m_triangleVertexCount(0),
m_edgeVertices(nullptr),
m_edgeVertexCount(0),
m_textureImage(nullptr)
{
m_triangleVertexCount = indicies.size();
m_triangleVertices = new Vertex[indicies.size()];
for (auto i = 0u; i < indicies.size(); i++) {
Vertex *dest = &m_triangleVertices[i];
const vertex_t *srcVert = &vertices[indicies[i]];
const QVector3D *srcNormal = &normals[indicies[i]];
dest->colorR = 0;
dest->colorG = 0;
dest->colorB = 0;
dest->posX = srcVert->p[0];
dest->posY = srcVert->p[1];
dest->posZ = srcVert->p[2];
dest->texU = srcVert->t[0];
dest->texV = srcVert->t[1];
dest->normX = srcNormal->x();
dest->normY = srcNormal->y();
dest->normZ = srcNormal->z();
if (nullptr != mesh.m_triangleVertices &&
mesh.m_triangleVertexCount > 0) {
this->m_triangleVertices = new Vertex[mesh.m_triangleVertexCount];
this->m_triangleVertexCount = mesh.m_triangleVertexCount;
for (int i = 0; i < mesh.m_triangleVertexCount; i++)
this->m_triangleVertices[i] = mesh.m_triangleVertices[i];
}
if (nullptr != mesh.m_edgeVertices &&
mesh.m_edgeVertexCount > 0) {
this->m_edgeVertices = new Vertex[mesh.m_edgeVertexCount];
this->m_edgeVertexCount = mesh.m_edgeVertexCount;
for (int i = 0; i < mesh.m_edgeVertexCount; i++)
this->m_edgeVertices[i] = mesh.m_edgeVertices[i];
}
if (nullptr != mesh.m_textureImage) {
this->m_textureImage = new QImage(*mesh.m_textureImage);
}
this->m_vertices = mesh.m_vertices;
this->m_faces = mesh.m_faces;
this->m_triangulatedVertices = mesh.m_triangulatedVertices;
this->m_triangulatedFaces = mesh.m_triangulatedFaces;
}
MeshLoader::MeshLoader(Vertex *triangleVertices, int vertexNum) :
m_triangleVertices(triangleVertices),
m_triangleVertexCount(vertexNum),
m_edgeVertices(nullptr),
m_edgeVertexCount(0),
m_textureImage(nullptr)
{
}
*/
MeshLoader::MeshLoader(MeshResultContext &resultContext) :
m_triangleVertices(nullptr),
@ -210,6 +220,8 @@ MeshLoader::~MeshLoader()
{
delete[] m_triangleVertices;
m_triangleVertexCount = 0;
delete[] m_edgeVertices;
m_edgeVertexCount = 0;
delete m_textureImage;
}

View File

@ -39,8 +39,9 @@ class MeshLoader
{
public:
MeshLoader(void *meshlite, int meshId, int triangulatedMeshId = -1, QColor modelColor=Theme::white, const std::vector<QColor> *triangleColors=nullptr);
//MeshLoader(const std::vector<vertex_t> &vertices, const std::vector<int> &indicies, const std::vector<QVector3D> &normals);
MeshLoader(MeshResultContext &resultContext);
MeshLoader(Vertex *triangleVertices, int vertexNum);
MeshLoader(const MeshLoader &mesh);
~MeshLoader();
Vertex *triangleVertices();
int triangleVertexCount();

View File

@ -42,12 +42,23 @@ MeshResultContext::MeshResultContext() :
const std::vector<std::pair<int, int>> &MeshResultContext::triangleSourceNodes()
{
if (!m_triangleSourceResolved) {
calculateTriangleSourceNodes(m_triangleSourceNodes);
m_triangleSourceResolved = true;
calculateTriangleSourceNodes(m_triangleSourceNodes, m_vertexSourceMap);
calculateRemainingVertexSourceNodesAfterTriangleSourceNodesSolved(m_vertexSourceMap);
}
return m_triangleSourceNodes;
}
const std::map<int, std::pair<int, int>> &MeshResultContext::vertexSourceMap()
{
if (!m_triangleSourceResolved) {
m_triangleSourceResolved = true;
calculateTriangleSourceNodes(m_triangleSourceNodes, m_vertexSourceMap);
calculateRemainingVertexSourceNodesAfterTriangleSourceNodesSolved(m_vertexSourceMap);
}
return m_vertexSourceMap;
}
const std::vector<QColor> &MeshResultContext::triangleColors()
{
if (!m_triangleColorResolved) {
@ -92,7 +103,7 @@ void MeshResultContext::resolveBmeshConnectivity()
}
}
void MeshResultContext::calculateTriangleSourceNodes(std::vector<std::pair<int, int>> &triangleSourceNodes)
void MeshResultContext::calculateTriangleSourceNodes(std::vector<std::pair<int, int>> &triangleSourceNodes, std::map<int, std::pair<int, int>> &vertexSourceMap)
{
PositionMap<std::pair<int, int>> positionMap;
std::map<std::pair<int, int>, HalfColorEdge> halfColorEdgeMap;
@ -101,14 +112,21 @@ void MeshResultContext::calculateTriangleSourceNodes(std::vector<std::pair<int,
positionMap.addPosition(it.position.x(), it.position.y(), it.position.z(),
std::make_pair(it.bmeshId, it.nodeId));
}
for (auto x = 0u; x < vertices.size(); x++) {
ResultVertex *resultVertex = &vertices[x];
std::pair<int, int> source;
if (positionMap.findPosition(resultVertex->position.x(), resultVertex->position.y(), resultVertex->position.z(), &source)) {
vertexSourceMap[x] = source;
}
}
for (auto x = 0u; x < triangles.size(); x++) {
const auto triangle = &triangles[x];
std::vector<std::pair<std::pair<int, int>, int>> colorTypes;
for (int i = 0; i < 3; i++) {
int index = triangle->indicies[i];
ResultVertex *resultVertex = &vertices[index];
std::pair<int, int> source;
if (positionMap.findPosition(resultVertex->position.x(), resultVertex->position.y(), resultVertex->position.z(), &source)) {
const auto &findResult = vertexSourceMap.find(index);
if (findResult != vertexSourceMap.end()) {
std::pair<int, int> source = findResult->second;
bool colorExisted = false;
for (auto j = 0u; j < colorTypes.size(); j++) {
if (colorTypes[j].first == source) {
@ -228,6 +246,37 @@ void MeshResultContext::calculateTriangleSourceNodes(std::vector<std::pair<int,
}
}
void MeshResultContext::calculateRemainingVertexSourceNodesAfterTriangleSourceNodesSolved(std::map<int, std::pair<int, int>> &vertexSourceMap)
{
std::map<int, std::set<std::pair<int, int>>> remainings;
for (auto x = 0u; x < triangles.size(); x++) {
const auto triangle = &triangles[x];
for (int i = 0; i < 3; i++) {
int index = triangle->indicies[i];
const auto &findResult = vertexSourceMap.find(index);
if (findResult == vertexSourceMap.end()) {
remainings[index].insert(triangleSourceNodes()[x]);
}
}
}
for (const auto &it: remainings) {
float minDist2 = 100000000;
std::pair<int, int> minSource = std::make_pair(0, 0);
for (const auto &source: it.second) {
const auto &vertex = vertices[it.first];
const auto &findNode = bmeshNodeMap().find(source);
if (findNode != bmeshNodeMap().end()) {
float dist2 = (vertex.position - findNode->second->origin).lengthSquared();
if (dist2 < minDist2) {
minSource = source;
minDist2 = dist2;
}
}
}
vertexSourceMap[it.first] = minSource;
}
}
void MeshResultContext::calculateTriangleColors(std::vector<QColor> &triangleColors)
{
std::map<std::pair<int, int>, QColor> nodeColorMap;
@ -373,10 +422,12 @@ struct BmeshNodeDistWithWorldCenter
BmeshNode *MeshResultContext::calculateCenterBmeshNode()
{
// Sort all the nodes by distance with world center.
// Sort all the nodes by distance with world center, excluding leg start nodes
std::vector<BmeshNodeDistWithWorldCenter> nodesOrderByDistWithWorldCenter;
for (auto i = 0u; i < bmeshNodes.size(); i++) {
BmeshNode *bmeshNode = &bmeshNodes[i];
if (SkeletonBoneMarkIsStart(bmeshNode->boneMark))
continue;
BmeshNodeDistWithWorldCenter distNode;
distNode.bmeshNode = bmeshNode;
distNode.dist2 = bmeshNode->origin.lengthSquared();
@ -480,6 +531,7 @@ void MeshResultContext::calculateVertexWeights(std::vector<std::vector<ResultVer
ResultVertexWeight vertexWeight;
vertexWeight.sourceNode = sourceNode;
vertexWeight.count = 1;
vertexWeights[vertexIndex].push_back(vertexWeight);
continue;
}
vertexWeights[vertexIndex][foundSourceNodeIndex].count++;
@ -501,9 +553,12 @@ void MeshResultContext::calculateVertexWeights(std::vector<std::vector<ResultVer
for (auto i = 0u; i < it.size(); i++) {
const auto &findInter = intermediateNodes->find(it[i].sourceNode);
if (findInter != intermediateNodes->end()) {
const auto &interBmeshNode = bmeshNodeMap().find(findInter->first);
const auto &attachedFromBmeshNode = bmeshNodeMap().find(std::make_pair(findInter->second.attachedFromPartId, findInter->second.attachedFromNodeId));
//const auto &interBmeshNode = bmeshNodeMap().find(findInter->first);
//const auto &attachedFromBmeshNode = bmeshNodeMap().find(std::make_pair(findInter->second.attachedFromPartId, findInter->second.attachedFromNodeId));
const auto &attachedToBmeshNode = bmeshNodeMap().find(std::make_pair(findInter->second.attachedToPartId, findInter->second.attachedToNodeId));
if (attachedToBmeshNode != bmeshNodeMap().end())
weights.push_back(std::make_pair(attachedToBmeshNode->first, it[i].weight));
/*
if (interBmeshNode != bmeshNodeMap().end() &&
attachedFromBmeshNode != bmeshNodeMap().end() &&
attachedToBmeshNode != bmeshNodeMap().end()) {
@ -514,7 +569,7 @@ void MeshResultContext::calculateVertexWeights(std::vector<std::vector<ResultVer
weights.push_back(std::make_pair(attachedFromBmeshNode->first, it[i].weight * distWithFrom / distTotal));
weights.push_back(std::make_pair(attachedToBmeshNode->first, it[i].weight * distWithTo / distTotal));
}
}
}*/
} else {
weights.push_back(std::make_pair(it[i].sourceNode, it[i].weight));
}
@ -527,9 +582,11 @@ void MeshResultContext::calculateVertexWeights(std::vector<std::vector<ResultVer
total += weights[i].second;
}
for (auto i = 0u; i < MAX_WEIGHT_NUM && i < weights.size(); i++) {
weights[i].second = weights[i].second / total;
weights[i].second = total > 0 ? weights[i].second / total : 0;
}
for (auto i = 0u; i < MAX_WEIGHT_NUM && i < weights.size(); i++) {
if (i >= it.size())
it.push_back(ResultVertexWeight());
it[i].sourceNode = weights[i].first;
it[i].weight = weights[i].second;
it[i].count = -1; // no use
@ -788,8 +845,10 @@ void MeshResultContext::removeIntermediateBones()
bmeshEdges.clear();
for (const auto &old: oldEdges) {
if (intermediateNodes.find(std::make_pair(old.fromBmeshId, old.fromNodeId)) != intermediateNodes.end() ||
intermediateNodes.find(std::make_pair(old.toBmeshId, old.toNodeId)) != intermediateNodes.end())
intermediateNodes.find(std::make_pair(old.toBmeshId, old.toNodeId)) != intermediateNodes.end()) {
qDebug() << "Intermediate node from:" << old.fromBmeshId << old.fromNodeId << "to:" << old.toBmeshId << old.toNodeId;
continue;
}
bmeshEdges.push_back(old);
}
for (const auto &edge: remover.newEdges()) {
@ -798,6 +857,7 @@ void MeshResultContext::removeIntermediateBones()
newEdge.fromNodeId = std::get<1>(edge);
newEdge.toBmeshId = std::get<2>(edge);
newEdge.toNodeId = std::get<3>(edge);
qDebug() << "New edge from:" << newEdge.fromBmeshId << newEdge.fromNodeId << "to:" << newEdge.toBmeshId << newEdge.toNodeId;
bmeshEdges.push_back(newEdge);
}
calculateBmeshNodeNeighbors(m_nodeNeighbors);

View File

@ -13,27 +13,27 @@
struct BmeshNode
{
int bmeshId;
int nodeId;
int bmeshId = 0;
int nodeId = 0;
QVector3D origin;
float radius;
float radius = 0;
QColor color;
SkeletonBoneMark boneMark;
SkeletonBoneMark boneMark = SkeletonBoneMark::None;
};
struct BmeshVertex
{
QVector3D position;
int bmeshId;
int nodeId;
int bmeshId = 0;
int nodeId = 0;
};
struct BmeshEdge
{
int fromBmeshId;
int fromNodeId;
int toBmeshId;
int toNodeId;
int fromBmeshId = 0;
int fromNodeId = 0;
int toBmeshId = 0;
int toNodeId = 0;
};
struct ResultVertex
@ -43,26 +43,26 @@ struct ResultVertex
struct ResultTriangle
{
int indicies[3];
int indicies[3] = {0, 0, 0};
QVector3D normal;
};
struct ResultVertexWeight
{
std::pair<int, int> sourceNode;
int count;
float weight;
std::pair<int, int> sourceNode = std::make_pair(0, 0);
int count = 0;
float weight = 0;
};
struct ResultTriangleUv
{
float uv[3][2];
bool resolved;
float uv[3][2] = {{0, 0}, {0, 0}, {0, 0}};
bool resolved = false;
};
struct ResultVertexUv
{
float uv[2];
float uv[2] = {0, 0};
};
struct ResultPart
@ -118,6 +118,7 @@ public:
const std::vector<ResultTriangleUv> &triangleUvs();
const std::vector<ResultRearrangedVertex> &rearrangedVertices();
const std::vector<ResultRearrangedTriangle> &rearrangedTriangles();
const std::map<int, std::pair<int, int>> &vertexSourceMap();
void removeIntermediateBones();
private:
bool m_triangleSourceResolved;
@ -145,8 +146,10 @@ private:
std::set<int> m_seamVertices;
std::vector<ResultRearrangedVertex> m_rearrangedVertices;
std::vector<ResultRearrangedTriangle> m_rearrangedTriangles;
std::map<int, std::pair<int, int>> m_vertexSourceMap;
private:
void calculateTriangleSourceNodes(std::vector<std::pair<int, int>> &triangleSourceNodes);
void calculateTriangleSourceNodes(std::vector<std::pair<int, int>> &triangleSourceNodes, std::map<int, std::pair<int, int>> &vertexSourceMap);
void calculateRemainingVertexSourceNodesAfterTriangleSourceNodesSolved(std::map<int, std::pair<int, int>> &vertexSourceMap);
void calculateTriangleColors(std::vector<QColor> &triangleColors);
void calculateTriangleEdgeSourceMap(std::map<std::pair<int, int>, std::pair<int, int>> &triangleEdgeSourceMap);
void calculateBmeshNodeMap(std::map<std::pair<int, int>, BmeshNode *> &bmeshNodeMap);

199
src/rigcontroller.cpp Normal file
View File

@ -0,0 +1,199 @@
#include <cmath>
#include "rigcontroller.h"
#include "ccdikresolver.h"
RigController::RigController(const JointNodeTree &jointNodeTree) :
m_inputJointNodeTree(jointNodeTree),
m_prepared(false),
m_legHeight(0)
{
}
void RigController::saveFrame(RigFrame &frame)
{
frame = m_rigFrame;
}
void RigController::collectLegs()
{
m_legs.clear();
for (const auto &node: m_inputJointNodeTree.joints()) {
if (node.boneMark == SkeletonBoneMark::LegStart && node.children.size() == 1) {
const auto legStart = std::make_pair(node.partId, node.nodeId);
const JointInfo *loopNode = &m_inputJointNodeTree.joints()[node.children[0]];
while (loopNode->boneMark != SkeletonBoneMark::LegEnd &&
loopNode->children.size() == 1) {
loopNode = &m_inputJointNodeTree.joints()[loopNode->children[0]];
}
if (loopNode->boneMark == SkeletonBoneMark::LegEnd) {
const auto legEnd = std::make_pair(loopNode->partId, loopNode->nodeId);
addLeg(legStart, legEnd);
} else {
qDebug() << "Find leg" << node.partId << "'s end failed";
}
}
}
}
int RigController::addLeg(std::pair<int, int> legStart, std::pair<int, int> legEnd)
{
int legIndex = m_legs.size();
m_legs.push_back(std::make_tuple(legStart.first, legStart.second, legEnd.first, legEnd.second));
return legIndex;
}
void RigController::prepare()
{
if (m_prepared)
return;
m_prepared = true;
collectLegs();
m_rigFrame = RigFrame(m_inputJointNodeTree.joints().size());
calculateAverageLegHeight();
}
void RigController::lift(QVector3D offset)
{
if (m_inputJointNodeTree.joints().empty())
return;
m_rigFrame.translations[0] = offset;
}
void RigController::liftLegs(QVector3D offset, QVector3D &effectedOffset)
{
if (m_legs.empty())
return;
QVector3D effectedOffsetSum;
for (auto i = 0u; i < m_legs.size(); i++) {
QVector3D subEffectedOffset;
liftLegEnd(i, offset, subEffectedOffset);
effectedOffsetSum += subEffectedOffset;
}
effectedOffset = effectedOffsetSum / m_legs.size();
}
void RigController::liftLegEnd(int leg, QVector3D offset, QVector3D &effectedOffset)
{
Q_ASSERT(leg >= 0 && leg < (int)m_legs.size());
int legStartPartId = std::get<0>(m_legs[leg]);
int legStartNodeId = std::get<1>(m_legs[leg]);
int legEndPartId = std::get<2>(m_legs[leg]);
int legEndNodeId = std::get<3>(m_legs[leg]);
int legStartIndex = m_inputJointNodeTree.nodeToJointIndex(legStartPartId, legStartNodeId);
int legEndIndex = m_inputJointNodeTree.nodeToJointIndex(legEndPartId, legEndNodeId);
const auto &legStart = m_inputJointNodeTree.joints()[legStartIndex];
const auto &legEnd = m_inputJointNodeTree.joints()[legEndIndex];
auto destPosition = legEnd.position + offset;
qDebug() << "dest move:" << destPosition.distanceToPoint(legEnd.position);
CCDIKSolver ikSolver;
ikSolver.setMaxRound(10);
int loopIndex = legStartIndex;
std::vector<int> ikSolvingIndicies;
for (;;) {
const auto &legJoint = m_inputJointNodeTree.joints()[loopIndex];
ikSolvingIndicies.push_back(loopIndex);
ikSolver.addNodeInOrder(legJoint.position);
if (loopIndex == legEndIndex)
break;
if (legJoint.children.empty())
break;
Q_ASSERT(legStart.children.size() <= 1);
loopIndex = legJoint.children[0];
}
ikSolver.solveTo(destPosition);
int nodeCount = ikSolver.getNodeCount();
Q_ASSERT(nodeCount == (int)ikSolvingIndicies.size());
JointNodeTree outputJointNodeTree = m_inputJointNodeTree;
for (int i = 0; i < nodeCount; i++) {
int jointIndex = ikSolvingIndicies[i];
const QVector3D &newPosition = ikSolver.getNodeSolvedPosition(i);
const QVector3D &oldPosition = outputJointNodeTree.joints()[jointIndex].position;
qDebug() << i << "position moved:" << oldPosition.distanceToPoint(newPosition);
outputJointNodeTree.joints()[jointIndex].position = newPosition;
}
effectedOffset = ikSolver.getNodeSolvedPosition(nodeCount - 1) -
m_inputJointNodeTree.joints()[ikSolvingIndicies[nodeCount - 1]].position;
qDebug() << "end effector offset:" << destPosition.distanceToPoint(ikSolver.getNodeSolvedPosition(nodeCount - 1));
outputJointNodeTree.recalculateMatricesAfterPositionsUpdated();
QMatrix4x4 parentMatrix;
for (int i = 0; i < nodeCount; i++) {
int jointIndex = ikSolvingIndicies[i];
const auto &inputJoint = m_inputJointNodeTree.joints()[jointIndex];
const auto &outputJoint = outputJointNodeTree.joints()[jointIndex];
QMatrix4x4 worldMatrix = outputJoint.bindMatrix * inputJoint.inverseBindMatrix;
QMatrix4x4 trMatrix = worldMatrix * parentMatrix.inverted();
m_rigFrame.rotations[jointIndex] = QQuaternion::fromRotationMatrix(trMatrix.normalMatrix());
m_rigFrame.translations[jointIndex] = QVector3D(trMatrix(0, 3), trMatrix(1, 3), trMatrix(2, 3));
parentMatrix = worldMatrix;
}
}
void RigController::frameToMatrices(const RigFrame &frame, std::vector<QMatrix4x4> &matrices)
{
if (m_inputJointNodeTree.joints().empty())
return;
matrices.clear();
matrices.resize(m_inputJointNodeTree.joints().size());
frameToMatricesAtJoint(frame, matrices, 0, QMatrix4x4());
}
void RigController::frameToMatricesAtJoint(const RigFrame &frame, std::vector<QMatrix4x4> &matrices, int jointIndex, const QMatrix4x4 &parentWorldMatrix)
{
const auto &joint = m_inputJointNodeTree.joints()[jointIndex];
QMatrix4x4 translateMatrix;
translateMatrix.translate(frame.translations[jointIndex]);
QMatrix4x4 rotateMatrix;
rotateMatrix.rotate(frame.rotations[jointIndex]);
QMatrix4x4 worldMatrix = parentWorldMatrix * translateMatrix * rotateMatrix;
matrices[jointIndex] = worldMatrix;
for (const auto &child: joint.children) {
frameToMatricesAtJoint(frame, matrices, child, worldMatrix);
}
}
void RigController::squat(float amount)
{
prepare();
QVector3D wantOffset;
wantOffset.setY(m_legHeight * amount);
QVector3D effectedOffset;
liftLegs(wantOffset, effectedOffset);
lift(-effectedOffset);
}
void RigController::calculateAverageLegHeight()
{
QVector3D averageLegPlaneTop = QVector3D();
QVector3D averageLegPlaneBottom = QVector3D();
if (m_legs.empty())
return;
for (auto leg = 0u; leg < m_legs.size(); leg++) {
int legStartPartId = std::get<0>(m_legs[leg]);
int legStartNodeId = std::get<1>(m_legs[leg]);
int legEndPartId = std::get<2>(m_legs[leg]);
int legEndNodeId = std::get<3>(m_legs[leg]);
int legStartIndex = m_inputJointNodeTree.nodeToJointIndex(legStartPartId, legStartNodeId);
int legEndIndex = m_inputJointNodeTree.nodeToJointIndex(legEndPartId, legEndNodeId);
const auto &legStart = m_inputJointNodeTree.joints()[legStartIndex];
const auto &legEnd = m_inputJointNodeTree.joints()[legEndIndex];
//qDebug() << "leg:" << leg << "legStartPartId:" << legStartPartId << "legEndPartId:" << legEndPartId << legStart.position << legEnd.position;
averageLegPlaneTop += legStart.position;
averageLegPlaneBottom += legEnd.position;
}
averageLegPlaneTop /= m_legs.size();
averageLegPlaneBottom /= m_legs.size();
m_legHeight = abs(averageLegPlaneBottom.y() - averageLegPlaneTop.y());
}

52
src/rigcontroller.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef RIG_CONTROLLER_H
#define RIG_CONTROLLER_H
#include <QVector3D>
#include <vector>
#include <QQuaternion>
#include <QMatrix4x4>
#include "jointnodetree.h"
class RigFrame
{
public:
RigFrame()
{
}
RigFrame(int jointNum)
{
rotations.clear();
rotations.resize(jointNum);
translations.clear();
translations.resize(jointNum);
}
std::vector<QQuaternion> rotations;
std::vector<QVector3D> translations;
};
class RigController
{
public:
RigController(const JointNodeTree &jointNodeTree);
void saveFrame(RigFrame &frame);
void frameToMatrices(const RigFrame &frame, std::vector<QMatrix4x4> &matrices);
void squat(float amount);
private:
void prepare();
void collectLegs();
int addLeg(std::pair<int, int> legStart, std::pair<int, int> legEnd);
void liftLegEnd(int leg, QVector3D offset, QVector3D &effectedOffset);
void liftLegs(QVector3D offset, QVector3D &effectedOffset);
void lift(QVector3D offset);
void calculateAverageLegHeight();
void frameToMatricesAtJoint(const RigFrame &frame, std::vector<QMatrix4x4> &matrices, int jointIndex, const QMatrix4x4 &parentWorldMatrix);
private:
JointNodeTree m_inputJointNodeTree;
bool m_prepared;
float m_legHeight;
private:
std::vector<std::tuple<int, int, int, int>> m_legs;
RigFrame m_rigFrame;
};
#endif

View File

@ -839,8 +839,9 @@ void SkeletonDocument::fromSnapshot(const SkeletonSnapshot &snapshot)
MeshLoader *SkeletonDocument::takeResultMesh()
{
MeshLoader *resultMesh = m_resultMesh;
m_resultMesh = nullptr;
if (nullptr == m_resultMesh)
return nullptr;
MeshLoader *resultMesh = new MeshLoader(*m_resultMesh);
return resultMesh;
}

View File

@ -77,7 +77,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_document(nullptr),
m_firstShow(true),
m_documentSaved(true),
m_exportPreviewWidget(nullptr)
m_exportPreviewWidget(nullptr),
m_animationPanelWidget(nullptr)
{
if (!g_logBrowser) {
g_logBrowser = new LogBrowser;
@ -419,12 +420,19 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_viewMenu->addSeparator();
m_showAnimationPanelAction = new QAction(tr("Show Animation Panel"), this);
connect(m_showAnimationPanelAction, &QAction::triggered, this, &SkeletonDocumentWindow::showAnimationPanel);
//m_viewMenu->addAction(m_showAnimationPanelAction);
m_viewMenu->addSeparator();
m_showDebugDialogAction = new QAction(tr("Show Debug Dialog"), this);
connect(m_showDebugDialogAction, &QAction::triggered, g_logBrowser, &LogBrowser::showDialog);
m_viewMenu->addAction(m_showDebugDialogAction);
connect(m_viewMenu, &QMenu::aboutToShow, [=]() {
m_showPartsListAction->setEnabled(partListDocker->isHidden());
m_showAnimationPanelAction->setEnabled(nullptr == m_animationPanelWidget || m_animationPanelWidget->isHidden());
m_resetModelWidgetPosAction->setEnabled(!modelIsSitInVisibleArea(m_modelRenderWidget));
});
@ -576,7 +584,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
connect(m_document, &SkeletonDocument::skeletonChanged, m_document, &SkeletonDocument::generateMesh);
connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() {
if (m_exportPreviewWidget && m_exportPreviewWidget->isVisible()) {
if ((m_exportPreviewWidget && m_exportPreviewWidget->isVisible()) ||
(m_animationPanelWidget && m_animationPanelWidget->isVisible())) {
m_document->postProcess();
}
});
@ -877,6 +886,26 @@ void SkeletonDocumentWindow::exportModelResult()
QApplication::restoreOverrideCursor();
}
void SkeletonDocumentWindow::showAnimationPanel()
{
if (nullptr == m_animationPanelWidget) {
m_animationPanelWidget = new AnimationPanelWidget(m_document, this);
m_animationPanelWidget->setWindowFlags(Qt::Tool);
connect(m_animationPanelWidget, &AnimationPanelWidget::panelClosed, [=] {
m_modelRenderWidget->updateMesh(m_document->takeResultMesh());
});
connect(m_animationPanelWidget, &AnimationPanelWidget::frameReadyToShow, [=] {
if (m_animationPanelWidget->isVisible())
m_modelRenderWidget->updateMesh(m_animationPanelWidget->takeFrameMesh());
});
connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_animationPanelWidget, &AnimationPanelWidget::sourceMeshChanged);
}
if (m_animationPanelWidget->isHidden()) {
m_document->postProcess();
}
m_animationPanelWidget->show();
}
void SkeletonDocumentWindow::showExportPreview()
{
if (nullptr == m_exportPreviewWidget) {
@ -890,7 +919,9 @@ void SkeletonDocumentWindow::showExportPreview()
connect(m_document, &SkeletonDocument::resultBakedTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
connect(m_document, &SkeletonDocument::resultSkeletonChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateSkeleton);
}
m_document->postProcess();
if (m_exportPreviewWidget->isHidden()) {
m_document->postProcess();
}
m_exportPreviewWidget->show();
}

View File

@ -10,6 +10,7 @@
#include "skeletondocument.h"
#include "modelwidget.h"
#include "exportpreviewwidget.h"
#include "animationpanelwidget.h"
class SkeletonGraphicsWidget;
@ -36,6 +37,7 @@ public slots:
void exportModelResult();
void exportGltfResult();
void showExportPreview();
void showAnimationPanel();
void newWindow();
void newDocument();
void saveAs();
@ -59,6 +61,7 @@ private:
bool m_firstShow;
bool m_documentSaved;
ExportPreviewWidget *m_exportPreviewWidget;
AnimationPanelWidget *m_animationPanelWidget;
private:
QString m_currentFilename;
@ -113,6 +116,7 @@ private:
QAction *m_showPartsListAction;
QAction *m_showDebugDialogAction;
QAction *m_toggleWireframeAction;
QAction *m_showAnimationPanelAction;
QMenu *m_helpMenu;
QAction *m_viewSourceAction;

View File

@ -70,15 +70,15 @@ MeshLoader *SkeletonGenerator::createSkeletonMesh()
return skeletonMesh;
}
struct BmeshNodeDistWithWorldCenter
void SkeletonGenerator::generate()
{
BmeshNode *bmeshNode;
float dist2;
};
Q_ASSERT(nullptr == m_resultSkeletonMesh);
m_resultSkeletonMesh = createSkeletonMesh();
}
void SkeletonGenerator::process()
{
m_resultSkeletonMesh = createSkeletonMesh();
generate();
this->moveToThread(QGuiApplication::instance()->thread());
emit finished();

View File

@ -12,6 +12,7 @@ class SkeletonGenerator : public QObject
public:
SkeletonGenerator(const MeshResultContext &meshResultContext);
~SkeletonGenerator();
void generate();
MeshLoader *takeResultSkeletonMesh();
MeshResultContext *takeResultContext();
signals:
@ -19,7 +20,6 @@ signals:
public slots:
void process();
private:
void combineAllBmeshSkeletons();
MeshLoader *createSkeletonMesh();
private:
MeshResultContext *m_meshResultContext;

107
src/skinnedmesh.cpp Normal file
View File

@ -0,0 +1,107 @@
#include "skinnedmesh.h"
SkinnedMesh::SkinnedMesh(const MeshResultContext &resultContext) :
m_resultContext(resultContext),
m_rigController(nullptr),
m_jointNodeTree(nullptr)
{
}
SkinnedMesh::~SkinnedMesh()
{
delete m_rigController;
delete m_jointNodeTree;
}
RigController *SkinnedMesh::rigController()
{
return m_rigController;
}
void SkinnedMesh::startRig()
{
Q_ASSERT(nullptr == m_rigController);
Q_ASSERT(nullptr == m_jointNodeTree);
m_jointNodeTree = new JointNodeTree(m_resultContext);
m_rigController = new RigController(*m_jointNodeTree);
fromMeshResultContext(m_resultContext);
}
void SkinnedMesh::applyRigFrameToMesh(const RigFrame &frame)
{
std::vector<QMatrix4x4> matrices;
m_rigController->frameToMatrices(frame, matrices);
for (auto &vert: m_vertices) {
QMatrix4x4 matrix;
for (int i = 0; i < MAX_WEIGHT_NUM; i++) {
if (vert.weights[i].amount > 0)
matrix += matrices[vert.weights[i].jointIndex] * vert.weights[i].amount;
}
vert.position = matrix * vert.posPosition;
vert.normal = (matrix * vert.posNormal).normalized();
}
}
void SkinnedMesh::fromMeshResultContext(MeshResultContext &resultContext)
{
m_vertices.clear();
m_triangles.clear();
for (const auto &part: resultContext.parts()) {
std::map<int, int> oldToNewIndexMap;
for (int vertexIndex = 0; vertexIndex < (int)part.second.vertices.size(); vertexIndex++) {
const ResultVertex *srcVert = &part.second.vertices[vertexIndex];
const QVector3D *srcNormal = &part.second.interpolatedVertexNormals[vertexIndex];
SkinnedMeshVertex vertex;
vertex.position = vertex.posPosition = srcVert->position;
vertex.normal = vertex.posNormal = *srcNormal;
const auto &weights = part.second.weights[vertexIndex];
for (int i = 0; i < (int)weights.size() && i < MAX_WEIGHT_NUM; i++) {
vertex.weights[i].amount = weights[i].weight;
vertex.weights[i].jointIndex = m_jointNodeTree->nodeToJointIndex(weights[i].sourceNode.first, weights[i].sourceNode.second);
}
for (int i = weights.size(); i < MAX_WEIGHT_NUM; i++) {
vertex.weights[i].amount = 0;
vertex.weights[i].jointIndex = 0;
}
oldToNewIndexMap[vertexIndex] = m_vertices.size();
m_vertices.push_back(vertex);
}
for (const auto &it: part.second.triangles) {
SkinnedMeshTriangle triangle;
for (auto i = 0; i < 3; i++) {
int vertexIndex = it.indicies[i];
triangle.indicies[i] = oldToNewIndexMap[vertexIndex];
}
triangle.color = part.second.color;
m_triangles.push_back(triangle);
}
}
}
MeshLoader *SkinnedMesh::toMeshLoader()
{
int vertexNum = m_triangles.size() * 3;
Vertex *triangleVertices = new Vertex[vertexNum];
int destIndex = 0;
for (const auto &triangle: m_triangles) {
for (auto i = 0; i < 3; i++) {
int srcVertexIndex = triangle.indicies[i];
const SkinnedMeshVertex *srcVertex = &m_vertices[srcVertexIndex];
Vertex *dest = &triangleVertices[destIndex];
dest->colorR = triangle.color.redF();
dest->colorG = triangle.color.greenF();
dest->colorB = triangle.color.blueF();
dest->texU = 0;
dest->texV = 0;
dest->normX = srcVertex->normal.x();
dest->normY = srcVertex->normal.y();
dest->normZ = srcVertex->normal.z();
dest->posX = srcVertex->position.x();
dest->posY = srcVertex->position.y();
dest->posZ = srcVertex->position.z();
destIndex++;
}
}
return new MeshLoader(triangleVertices, vertexNum);
}

51
src/skinnedmesh.h Normal file
View File

@ -0,0 +1,51 @@
#ifndef SKINNED_MESH_H
#define SKINNED_MESH_H
#include <QVector3D>
#include <vector>
#include "meshresultcontext.h"
#include "rigcontroller.h"
#include "meshloader.h"
struct SkinnedMeshWeight
{
int jointIndex;
float amount;
};
struct SkinnedMeshVertex
{
QVector3D position;
QVector3D normal;
QVector3D posPosition;
QVector3D posNormal;
SkinnedMeshWeight weights[MAX_WEIGHT_NUM];
};
struct SkinnedMeshTriangle
{
int indicies[3];
QColor color;
};
class SkinnedMesh
{
public:
SkinnedMesh(const MeshResultContext &resultContext);
~SkinnedMesh();
void startRig();
RigController *rigController();
void applyRigFrameToMesh(const RigFrame &frame);
MeshLoader *toMeshLoader();
private:
void fromMeshResultContext(MeshResultContext &resultContext);
private:
MeshResultContext m_resultContext;
RigController *m_rigController;
JointNodeTree *m_jointNodeTree;
private:
Q_DISABLE_COPY(SkinnedMesh);
std::vector<SkinnedMeshVertex> m_vertices;
std::vector<SkinnedMeshTriangle> m_triangles;
};
#endif