Add animation clip generator
- Fix skeleton weights - Add rig controller - Add animation clip generator - Implement basic experiment animation(squat)master
parent
b467bcdc76
commit
0829ef818a
15
dust3d.pro
15
dust3d.pro
|
@ -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
|
||||
|
|
|
@ -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> ¶meters) :
|
||||
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();
|
||||
}
|
|
@ -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> ¶meters);
|
||||
~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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ private:
|
|||
QVector3D m_destination;
|
||||
int m_maxRound;
|
||||
float m_distanceThreshold2;
|
||||
float m_distanceCeaseThreshold2;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
133
src/gltffile.cpp
133
src/gltffile.cpp
|
@ -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/
|
||||
|
@ -33,59 +34,33 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
|
|||
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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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]) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
if (m_exportPreviewWidget->isHidden()) {
|
||||
m_document->postProcess();
|
||||
}
|
||||
m_exportPreviewWidget->show();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
Loading…
Reference in New Issue