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
|
SOURCES += src/intermediateboneremover.cpp
|
||||||
HEADERS += src/intermediateboneremover.h
|
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
|
HEADERS += src/qtlightmapper.h
|
||||||
|
|
||||||
SOURCES += src/main.cpp
|
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 <QtGlobal>
|
||||||
#include <QMatrix4x4>
|
#include <QMatrix4x4>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <cmath>
|
||||||
#include "ccdikresolver.h"
|
#include "ccdikresolver.h"
|
||||||
|
|
||||||
CCDIKSolver::CCDIKSolver() :
|
CCDIKSolver::CCDIKSolver() :
|
||||||
m_maxRound(5),
|
m_maxRound(4),
|
||||||
m_distanceThreshold2(0.001 * 0.001)
|
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;
|
qDebug() << "Round:" << i << " distance2:" << distance2;
|
||||||
if (distance2 <= m_distanceThreshold2)
|
if (distance2 <= m_distanceThreshold2)
|
||||||
break;
|
break;
|
||||||
if (lastDistance2 > 0 && distance2 >= lastDistance2)
|
if (lastDistance2 > 0 && abs(distance2 - lastDistance2) <= m_distanceCeaseThreshold2)
|
||||||
break;
|
break;
|
||||||
lastDistance2 = distance2;
|
lastDistance2 = distance2;
|
||||||
iterate();
|
iterate();
|
||||||
|
@ -48,7 +50,7 @@ void CCDIKSolver::solveTo(const QVector3D &position)
|
||||||
|
|
||||||
const QVector3D &CCDIKSolver::getNodeSolvedPosition(int index)
|
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;
|
return m_nodes[index].position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,13 +64,13 @@ void CCDIKSolver::iterate()
|
||||||
for (int i = m_nodes.size() - 2; i >= 0; i--) {
|
for (int i = m_nodes.size() - 2; i >= 0; i--) {
|
||||||
const auto &origin = m_nodes[i];
|
const auto &origin = m_nodes[i];
|
||||||
const auto &endEffector = m_nodes[m_nodes.size() - 1];
|
const auto &endEffector = m_nodes[m_nodes.size() - 1];
|
||||||
QVector3D from = endEffector.position - origin.position;
|
QVector3D from = (endEffector.position - origin.position).normalized();
|
||||||
QVector3D to = m_destination - origin.position;
|
QVector3D to = (m_destination - origin.position).normalized();
|
||||||
auto quaternion = QQuaternion::rotationTo(from, to);
|
auto quaternion = QQuaternion::rotationTo(from, to);
|
||||||
for (size_t j = i + 1; j <= m_nodes.size() - 1; j++) {
|
for (size_t j = i + 1; j <= m_nodes.size() - 1; j++) {
|
||||||
auto &next = m_nodes[j];
|
auto &next = m_nodes[j];
|
||||||
const auto offset = next.position - origin.position;
|
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;
|
QVector3D m_destination;
|
||||||
int m_maxRound;
|
int m_maxRound;
|
||||||
float m_distanceThreshold2;
|
float m_distanceThreshold2;
|
||||||
|
float m_distanceCeaseThreshold2;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
133
src/gltffile.cpp
133
src/gltffile.cpp
|
@ -7,6 +7,7 @@
|
||||||
#include "gltffile.h"
|
#include "gltffile.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "dust3dutil.h"
|
#include "dust3dutil.h"
|
||||||
|
#include "jointnodetree.h"
|
||||||
|
|
||||||
// Play with glTF online:
|
// Play with glTF online:
|
||||||
// https://gltf-viewer.donmccurdy.com/
|
// https://gltf-viewer.donmccurdy.com/
|
||||||
|
@ -33,59 +34,33 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
|
||||||
QString textureFilenameWithoutPath = nameInfo.completeBaseName() + ".png";
|
QString textureFilenameWithoutPath = nameInfo.completeBaseName() + ".png";
|
||||||
m_textureFilename = nameInfo.path() + QDir::separator() + textureFilenameWithoutPath;
|
m_textureFilename = nameInfo.path() + QDir::separator() + textureFilenameWithoutPath;
|
||||||
|
|
||||||
JointInfo rootHandleJoint;
|
JointNodeTree jointNodeTree(resultContext);
|
||||||
{
|
const std::vector<JointInfo> &tracedJoints = jointNodeTree.joints();
|
||||||
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);
|
|
||||||
|
|
||||||
m_json["asset"]["version"] = "2.0";
|
m_json["asset"]["version"] = "2.0";
|
||||||
m_json["asset"]["generator"] = APP_NAME " " APP_HUMAN_VER;
|
m_json["asset"]["generator"] = APP_NAME " " APP_HUMAN_VER;
|
||||||
m_json["scenes"][0]["nodes"] = {0};
|
m_json["scenes"][0]["nodes"] = {0};
|
||||||
m_json["nodes"][0]["children"] = {1, 2};
|
|
||||||
|
|
||||||
m_json["nodes"][1]["mesh"] = 0;
|
m_json["nodes"][0]["mesh"] = 0;
|
||||||
m_json["nodes"][1]["skin"] = 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++) {
|
for (auto i = 0u; i < tracedJoints.size(); i++) {
|
||||||
m_json["nodes"][skeletonNodeStartIndex + i]["translation"] = {m_tracedJoints[i].translation.x(),
|
m_json["nodes"][skeletonNodeStartIndex + i]["translation"] = {tracedJoints[i].translation.x(),
|
||||||
m_tracedJoints[i].translation.y(),
|
tracedJoints[i].translation.y(),
|
||||||
m_tracedJoints[i].translation.z()
|
tracedJoints[i].translation.z()
|
||||||
};
|
};
|
||||||
m_json["nodes"][skeletonNodeStartIndex + i]["rotation"] = {m_tracedJoints[i].rotation.x(),
|
m_json["nodes"][skeletonNodeStartIndex + i]["rotation"] = {tracedJoints[i].rotation.x(),
|
||||||
m_tracedJoints[i].rotation.y(),
|
tracedJoints[i].rotation.y(),
|
||||||
m_tracedJoints[i].rotation.z(),
|
tracedJoints[i].rotation.z(),
|
||||||
m_tracedJoints[i].rotation.scalar()
|
tracedJoints[i].rotation.scalar()
|
||||||
};
|
};
|
||||||
if (m_tracedJoints[i].children.empty())
|
if (tracedJoints[i].children.empty())
|
||||||
continue;
|
continue;
|
||||||
m_json["nodes"][skeletonNodeStartIndex + i]["children"] = {};
|
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;
|
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]["inverseBindMatrices"] = 0;
|
||||||
m_json["skins"][0]["skeleton"] = skeletonNodeStartIndex;
|
m_json["skins"][0]["skeleton"] = skeletonNodeStartIndex;
|
||||||
m_json["skins"][0]["joints"] = {};
|
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;
|
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]["bufferView"] = bufferViewIndex;
|
||||||
m_json["accessors"][bufferViewIndex]["byteOffset"] = 0;
|
m_json["accessors"][bufferViewIndex]["byteOffset"] = 0;
|
||||||
m_json["accessors"][bufferViewIndex]["componentType"] = 5126;
|
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["accessors"][bufferViewIndex]["type"] = "MAT4";
|
||||||
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
|
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
|
||||||
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = (int)binaries.size();
|
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = (int)binaries.size();
|
||||||
int bufferViews0FromOffset = (int)binaries.size();
|
int bufferViews0FromOffset = (int)binaries.size();
|
||||||
for (auto i = 0u; i < m_tracedJoints.size(); i++) {
|
for (auto i = 0u; i < tracedJoints.size(); i++) {
|
||||||
const float *floatArray = m_tracedJoints[i].inverseBindMatrix.constData();
|
const float *floatArray = tracedJoints[i].inverseBindMatrix.constData();
|
||||||
for (auto j = 0u; j < 16; j++) {
|
for (auto j = 0u; j < 16; j++) {
|
||||||
stream << (float)floatArray[j];
|
stream << (float)floatArray[j];
|
||||||
}
|
}
|
||||||
|
@ -254,7 +229,7 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
|
||||||
for (const auto &it: part.second.weights) {
|
for (const auto &it: part.second.weights) {
|
||||||
auto i = 0u;
|
auto i = 0u;
|
||||||
for (; i < it.size() && i < MAX_WEIGHT_NUM; i++) {
|
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++) {
|
for (; i < MAX_WEIGHT_NUM; i++) {
|
||||||
stream << (quint16)0;
|
stream << (quint16)0;
|
||||||
|
@ -277,7 +252,7 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
|
||||||
for (const auto &it: part.second.weights) {
|
for (const auto &it: part.second.weights) {
|
||||||
auto i = 0u;
|
auto i = 0u;
|
||||||
for (; i < it.size() && i < MAX_WEIGHT_NUM; i++) {
|
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++) {
|
for (; i < MAX_WEIGHT_NUM; i++) {
|
||||||
stream << (float)0.0;
|
stream << (float)0.0;
|
||||||
|
@ -321,68 +296,6 @@ const QString &GLTFFileWriter::textureFilenameInGltf()
|
||||||
return m_textureFilename;
|
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()
|
bool GLTFFileWriter::save()
|
||||||
{
|
{
|
||||||
QFile file(m_filename);
|
QFile file(m_filename);
|
||||||
|
|
|
@ -8,31 +8,15 @@
|
||||||
#include "meshresultcontext.h"
|
#include "meshresultcontext.h"
|
||||||
#include "json.hpp"
|
#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
|
class GLTFFileWriter : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
GLTFFileWriter(MeshResultContext &resultContext, const QString &filename);
|
GLTFFileWriter(MeshResultContext &resultContext, const QString &filename);
|
||||||
bool save();
|
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();
|
const QString &textureFilenameInGltf();
|
||||||
private:
|
private:
|
||||||
QByteArray m_data;
|
QByteArray m_data;
|
||||||
std::vector<JointInfo> m_tracedJoints;
|
|
||||||
std::map<std::pair<int, int>, int> m_tracedNodeToJointIndexMap;
|
|
||||||
QString getMatrixStringInColumnOrder(const QMatrix4x4 &mat);
|
QString getMatrixStringInColumnOrder(const QMatrix4x4 &mat);
|
||||||
QString m_filename;
|
QString m_filename;
|
||||||
QString m_textureFilename;
|
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;
|
//qDebug() << "bmeshId[" << bmeshId << "] add lonely node[" << bmeshNodeId << "]" << radius << x << y << z;
|
||||||
bmeshNodeMap[nodeIt.first] = bmeshNodeId;
|
bmeshNodeMap[nodeIt.first] = bmeshNodeId;
|
||||||
|
|
||||||
|
SkeletonBoneMark boneMark = SkeletonBoneMarkFromString(valueOfKeyInMapOrEmpty(nodeIt.second, "boneMark").toUtf8().constData());
|
||||||
|
|
||||||
BmeshNode bmeshNode;
|
BmeshNode bmeshNode;
|
||||||
bmeshNode.bmeshId = bmeshId;
|
bmeshNode.bmeshId = bmeshId;
|
||||||
bmeshNode.origin = QVector3D(x, y, z);
|
bmeshNode.origin = QVector3D(x, y, z);
|
||||||
bmeshNode.radius = radius;
|
bmeshNode.radius = radius;
|
||||||
bmeshNode.nodeId = bmeshNodeId;
|
bmeshNode.nodeId = bmeshNodeId;
|
||||||
bmeshNode.color = partColorMap[partId];
|
bmeshNode.color = partColorMap[partId];
|
||||||
|
bmeshNode.boneMark = boneMark;
|
||||||
m_meshResultContext->bmeshNodes.push_back(bmeshNode);
|
m_meshResultContext->bmeshNodes.push_back(bmeshNode);
|
||||||
|
|
||||||
if (partMirrorFlagMap[partId]) {
|
if (partMirrorFlagMap[partId]) {
|
||||||
|
|
|
@ -140,34 +140,44 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColo
|
||||||
delete[] edgeNormals;
|
delete[] edgeNormals;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
MeshLoader::MeshLoader(const MeshLoader &mesh) :
|
||||||
MeshLoader::MeshLoader(const std::vector<vertex_t> &vertices, const std::vector<int> &indicies, const std::vector<QVector3D> &normals) :
|
|
||||||
m_triangleVertices(nullptr),
|
m_triangleVertices(nullptr),
|
||||||
m_triangleVertexCount(0),
|
m_triangleVertexCount(0),
|
||||||
m_edgeVertices(nullptr),
|
m_edgeVertices(nullptr),
|
||||||
m_edgeVertexCount(0),
|
m_edgeVertexCount(0),
|
||||||
m_textureImage(nullptr)
|
m_textureImage(nullptr)
|
||||||
{
|
{
|
||||||
m_triangleVertexCount = indicies.size();
|
if (nullptr != mesh.m_triangleVertices &&
|
||||||
m_triangleVertices = new Vertex[indicies.size()];
|
mesh.m_triangleVertexCount > 0) {
|
||||||
for (auto i = 0u; i < indicies.size(); i++) {
|
this->m_triangleVertices = new Vertex[mesh.m_triangleVertexCount];
|
||||||
Vertex *dest = &m_triangleVertices[i];
|
this->m_triangleVertexCount = mesh.m_triangleVertexCount;
|
||||||
const vertex_t *srcVert = &vertices[indicies[i]];
|
for (int i = 0; i < mesh.m_triangleVertexCount; i++)
|
||||||
const QVector3D *srcNormal = &normals[indicies[i]];
|
this->m_triangleVertices[i] = mesh.m_triangleVertices[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_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) :
|
MeshLoader::MeshLoader(MeshResultContext &resultContext) :
|
||||||
m_triangleVertices(nullptr),
|
m_triangleVertices(nullptr),
|
||||||
|
@ -210,6 +220,8 @@ MeshLoader::~MeshLoader()
|
||||||
{
|
{
|
||||||
delete[] m_triangleVertices;
|
delete[] m_triangleVertices;
|
||||||
m_triangleVertexCount = 0;
|
m_triangleVertexCount = 0;
|
||||||
|
delete[] m_edgeVertices;
|
||||||
|
m_edgeVertexCount = 0;
|
||||||
delete m_textureImage;
|
delete m_textureImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,8 +39,9 @@ class MeshLoader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MeshLoader(void *meshlite, int meshId, int triangulatedMeshId = -1, QColor modelColor=Theme::white, const std::vector<QColor> *triangleColors=nullptr);
|
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(MeshResultContext &resultContext);
|
||||||
|
MeshLoader(Vertex *triangleVertices, int vertexNum);
|
||||||
|
MeshLoader(const MeshLoader &mesh);
|
||||||
~MeshLoader();
|
~MeshLoader();
|
||||||
Vertex *triangleVertices();
|
Vertex *triangleVertices();
|
||||||
int triangleVertexCount();
|
int triangleVertexCount();
|
||||||
|
|
|
@ -42,12 +42,23 @@ MeshResultContext::MeshResultContext() :
|
||||||
const std::vector<std::pair<int, int>> &MeshResultContext::triangleSourceNodes()
|
const std::vector<std::pair<int, int>> &MeshResultContext::triangleSourceNodes()
|
||||||
{
|
{
|
||||||
if (!m_triangleSourceResolved) {
|
if (!m_triangleSourceResolved) {
|
||||||
calculateTriangleSourceNodes(m_triangleSourceNodes);
|
|
||||||
m_triangleSourceResolved = true;
|
m_triangleSourceResolved = true;
|
||||||
|
calculateTriangleSourceNodes(m_triangleSourceNodes, m_vertexSourceMap);
|
||||||
|
calculateRemainingVertexSourceNodesAfterTriangleSourceNodesSolved(m_vertexSourceMap);
|
||||||
}
|
}
|
||||||
return m_triangleSourceNodes;
|
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()
|
const std::vector<QColor> &MeshResultContext::triangleColors()
|
||||||
{
|
{
|
||||||
if (!m_triangleColorResolved) {
|
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;
|
PositionMap<std::pair<int, int>> positionMap;
|
||||||
std::map<std::pair<int, int>, HalfColorEdge> halfColorEdgeMap;
|
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(),
|
positionMap.addPosition(it.position.x(), it.position.y(), it.position.z(),
|
||||||
std::make_pair(it.bmeshId, it.nodeId));
|
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++) {
|
for (auto x = 0u; x < triangles.size(); x++) {
|
||||||
const auto triangle = &triangles[x];
|
const auto triangle = &triangles[x];
|
||||||
std::vector<std::pair<std::pair<int, int>, int>> colorTypes;
|
std::vector<std::pair<std::pair<int, int>, int>> colorTypes;
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
int index = triangle->indicies[i];
|
int index = triangle->indicies[i];
|
||||||
ResultVertex *resultVertex = &vertices[index];
|
const auto &findResult = vertexSourceMap.find(index);
|
||||||
std::pair<int, int> source;
|
if (findResult != vertexSourceMap.end()) {
|
||||||
if (positionMap.findPosition(resultVertex->position.x(), resultVertex->position.y(), resultVertex->position.z(), &source)) {
|
std::pair<int, int> source = findResult->second;
|
||||||
bool colorExisted = false;
|
bool colorExisted = false;
|
||||||
for (auto j = 0u; j < colorTypes.size(); j++) {
|
for (auto j = 0u; j < colorTypes.size(); j++) {
|
||||||
if (colorTypes[j].first == source) {
|
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)
|
void MeshResultContext::calculateTriangleColors(std::vector<QColor> &triangleColors)
|
||||||
{
|
{
|
||||||
std::map<std::pair<int, int>, QColor> nodeColorMap;
|
std::map<std::pair<int, int>, QColor> nodeColorMap;
|
||||||
|
@ -373,10 +422,12 @@ struct BmeshNodeDistWithWorldCenter
|
||||||
|
|
||||||
BmeshNode *MeshResultContext::calculateCenterBmeshNode()
|
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;
|
std::vector<BmeshNodeDistWithWorldCenter> nodesOrderByDistWithWorldCenter;
|
||||||
for (auto i = 0u; i < bmeshNodes.size(); i++) {
|
for (auto i = 0u; i < bmeshNodes.size(); i++) {
|
||||||
BmeshNode *bmeshNode = &bmeshNodes[i];
|
BmeshNode *bmeshNode = &bmeshNodes[i];
|
||||||
|
if (SkeletonBoneMarkIsStart(bmeshNode->boneMark))
|
||||||
|
continue;
|
||||||
BmeshNodeDistWithWorldCenter distNode;
|
BmeshNodeDistWithWorldCenter distNode;
|
||||||
distNode.bmeshNode = bmeshNode;
|
distNode.bmeshNode = bmeshNode;
|
||||||
distNode.dist2 = bmeshNode->origin.lengthSquared();
|
distNode.dist2 = bmeshNode->origin.lengthSquared();
|
||||||
|
@ -480,6 +531,7 @@ void MeshResultContext::calculateVertexWeights(std::vector<std::vector<ResultVer
|
||||||
ResultVertexWeight vertexWeight;
|
ResultVertexWeight vertexWeight;
|
||||||
vertexWeight.sourceNode = sourceNode;
|
vertexWeight.sourceNode = sourceNode;
|
||||||
vertexWeight.count = 1;
|
vertexWeight.count = 1;
|
||||||
|
vertexWeights[vertexIndex].push_back(vertexWeight);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
vertexWeights[vertexIndex][foundSourceNodeIndex].count++;
|
vertexWeights[vertexIndex][foundSourceNodeIndex].count++;
|
||||||
|
@ -501,9 +553,12 @@ void MeshResultContext::calculateVertexWeights(std::vector<std::vector<ResultVer
|
||||||
for (auto i = 0u; i < it.size(); i++) {
|
for (auto i = 0u; i < it.size(); i++) {
|
||||||
const auto &findInter = intermediateNodes->find(it[i].sourceNode);
|
const auto &findInter = intermediateNodes->find(it[i].sourceNode);
|
||||||
if (findInter != intermediateNodes->end()) {
|
if (findInter != intermediateNodes->end()) {
|
||||||
const auto &interBmeshNode = bmeshNodeMap().find(findInter->first);
|
//const auto &interBmeshNode = bmeshNodeMap().find(findInter->first);
|
||||||
const auto &attachedFromBmeshNode = bmeshNodeMap().find(std::make_pair(findInter->second.attachedFromPartId, findInter->second.attachedFromNodeId));
|
//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));
|
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() &&
|
if (interBmeshNode != bmeshNodeMap().end() &&
|
||||||
attachedFromBmeshNode != bmeshNodeMap().end() &&
|
attachedFromBmeshNode != bmeshNodeMap().end() &&
|
||||||
attachedToBmeshNode != 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(attachedFromBmeshNode->first, it[i].weight * distWithFrom / distTotal));
|
||||||
weights.push_back(std::make_pair(attachedToBmeshNode->first, it[i].weight * distWithTo / distTotal));
|
weights.push_back(std::make_pair(attachedToBmeshNode->first, it[i].weight * distWithTo / distTotal));
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
} else {
|
} else {
|
||||||
weights.push_back(std::make_pair(it[i].sourceNode, it[i].weight));
|
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;
|
total += weights[i].second;
|
||||||
}
|
}
|
||||||
for (auto i = 0u; i < MAX_WEIGHT_NUM && i < weights.size(); i++) {
|
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++) {
|
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].sourceNode = weights[i].first;
|
||||||
it[i].weight = weights[i].second;
|
it[i].weight = weights[i].second;
|
||||||
it[i].count = -1; // no use
|
it[i].count = -1; // no use
|
||||||
|
@ -788,8 +845,10 @@ void MeshResultContext::removeIntermediateBones()
|
||||||
bmeshEdges.clear();
|
bmeshEdges.clear();
|
||||||
for (const auto &old: oldEdges) {
|
for (const auto &old: oldEdges) {
|
||||||
if (intermediateNodes.find(std::make_pair(old.fromBmeshId, old.fromNodeId)) != intermediateNodes.end() ||
|
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;
|
continue;
|
||||||
|
}
|
||||||
bmeshEdges.push_back(old);
|
bmeshEdges.push_back(old);
|
||||||
}
|
}
|
||||||
for (const auto &edge: remover.newEdges()) {
|
for (const auto &edge: remover.newEdges()) {
|
||||||
|
@ -798,6 +857,7 @@ void MeshResultContext::removeIntermediateBones()
|
||||||
newEdge.fromNodeId = std::get<1>(edge);
|
newEdge.fromNodeId = std::get<1>(edge);
|
||||||
newEdge.toBmeshId = std::get<2>(edge);
|
newEdge.toBmeshId = std::get<2>(edge);
|
||||||
newEdge.toNodeId = std::get<3>(edge);
|
newEdge.toNodeId = std::get<3>(edge);
|
||||||
|
qDebug() << "New edge from:" << newEdge.fromBmeshId << newEdge.fromNodeId << "to:" << newEdge.toBmeshId << newEdge.toNodeId;
|
||||||
bmeshEdges.push_back(newEdge);
|
bmeshEdges.push_back(newEdge);
|
||||||
}
|
}
|
||||||
calculateBmeshNodeNeighbors(m_nodeNeighbors);
|
calculateBmeshNodeNeighbors(m_nodeNeighbors);
|
||||||
|
|
|
@ -13,27 +13,27 @@
|
||||||
|
|
||||||
struct BmeshNode
|
struct BmeshNode
|
||||||
{
|
{
|
||||||
int bmeshId;
|
int bmeshId = 0;
|
||||||
int nodeId;
|
int nodeId = 0;
|
||||||
QVector3D origin;
|
QVector3D origin;
|
||||||
float radius;
|
float radius = 0;
|
||||||
QColor color;
|
QColor color;
|
||||||
SkeletonBoneMark boneMark;
|
SkeletonBoneMark boneMark = SkeletonBoneMark::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BmeshVertex
|
struct BmeshVertex
|
||||||
{
|
{
|
||||||
QVector3D position;
|
QVector3D position;
|
||||||
int bmeshId;
|
int bmeshId = 0;
|
||||||
int nodeId;
|
int nodeId = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BmeshEdge
|
struct BmeshEdge
|
||||||
{
|
{
|
||||||
int fromBmeshId;
|
int fromBmeshId = 0;
|
||||||
int fromNodeId;
|
int fromNodeId = 0;
|
||||||
int toBmeshId;
|
int toBmeshId = 0;
|
||||||
int toNodeId;
|
int toNodeId = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ResultVertex
|
struct ResultVertex
|
||||||
|
@ -43,26 +43,26 @@ struct ResultVertex
|
||||||
|
|
||||||
struct ResultTriangle
|
struct ResultTriangle
|
||||||
{
|
{
|
||||||
int indicies[3];
|
int indicies[3] = {0, 0, 0};
|
||||||
QVector3D normal;
|
QVector3D normal;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ResultVertexWeight
|
struct ResultVertexWeight
|
||||||
{
|
{
|
||||||
std::pair<int, int> sourceNode;
|
std::pair<int, int> sourceNode = std::make_pair(0, 0);
|
||||||
int count;
|
int count = 0;
|
||||||
float weight;
|
float weight = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ResultTriangleUv
|
struct ResultTriangleUv
|
||||||
{
|
{
|
||||||
float uv[3][2];
|
float uv[3][2] = {{0, 0}, {0, 0}, {0, 0}};
|
||||||
bool resolved;
|
bool resolved = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ResultVertexUv
|
struct ResultVertexUv
|
||||||
{
|
{
|
||||||
float uv[2];
|
float uv[2] = {0, 0};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ResultPart
|
struct ResultPart
|
||||||
|
@ -118,6 +118,7 @@ public:
|
||||||
const std::vector<ResultTriangleUv> &triangleUvs();
|
const std::vector<ResultTriangleUv> &triangleUvs();
|
||||||
const std::vector<ResultRearrangedVertex> &rearrangedVertices();
|
const std::vector<ResultRearrangedVertex> &rearrangedVertices();
|
||||||
const std::vector<ResultRearrangedTriangle> &rearrangedTriangles();
|
const std::vector<ResultRearrangedTriangle> &rearrangedTriangles();
|
||||||
|
const std::map<int, std::pair<int, int>> &vertexSourceMap();
|
||||||
void removeIntermediateBones();
|
void removeIntermediateBones();
|
||||||
private:
|
private:
|
||||||
bool m_triangleSourceResolved;
|
bool m_triangleSourceResolved;
|
||||||
|
@ -145,8 +146,10 @@ private:
|
||||||
std::set<int> m_seamVertices;
|
std::set<int> m_seamVertices;
|
||||||
std::vector<ResultRearrangedVertex> m_rearrangedVertices;
|
std::vector<ResultRearrangedVertex> m_rearrangedVertices;
|
||||||
std::vector<ResultRearrangedTriangle> m_rearrangedTriangles;
|
std::vector<ResultRearrangedTriangle> m_rearrangedTriangles;
|
||||||
|
std::map<int, std::pair<int, int>> m_vertexSourceMap;
|
||||||
private:
|
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 calculateTriangleColors(std::vector<QColor> &triangleColors);
|
||||||
void calculateTriangleEdgeSourceMap(std::map<std::pair<int, int>, std::pair<int, int>> &triangleEdgeSourceMap);
|
void calculateTriangleEdgeSourceMap(std::map<std::pair<int, int>, std::pair<int, int>> &triangleEdgeSourceMap);
|
||||||
void calculateBmeshNodeMap(std::map<std::pair<int, int>, BmeshNode *> &bmeshNodeMap);
|
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 *SkeletonDocument::takeResultMesh()
|
||||||
{
|
{
|
||||||
MeshLoader *resultMesh = m_resultMesh;
|
if (nullptr == m_resultMesh)
|
||||||
m_resultMesh = nullptr;
|
return nullptr;
|
||||||
|
MeshLoader *resultMesh = new MeshLoader(*m_resultMesh);
|
||||||
return resultMesh;
|
return resultMesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||||
m_document(nullptr),
|
m_document(nullptr),
|
||||||
m_firstShow(true),
|
m_firstShow(true),
|
||||||
m_documentSaved(true),
|
m_documentSaved(true),
|
||||||
m_exportPreviewWidget(nullptr)
|
m_exportPreviewWidget(nullptr),
|
||||||
|
m_animationPanelWidget(nullptr)
|
||||||
{
|
{
|
||||||
if (!g_logBrowser) {
|
if (!g_logBrowser) {
|
||||||
g_logBrowser = new LogBrowser;
|
g_logBrowser = new LogBrowser;
|
||||||
|
@ -419,12 +420,19 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||||
|
|
||||||
m_viewMenu->addSeparator();
|
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);
|
m_showDebugDialogAction = new QAction(tr("Show Debug Dialog"), this);
|
||||||
connect(m_showDebugDialogAction, &QAction::triggered, g_logBrowser, &LogBrowser::showDialog);
|
connect(m_showDebugDialogAction, &QAction::triggered, g_logBrowser, &LogBrowser::showDialog);
|
||||||
m_viewMenu->addAction(m_showDebugDialogAction);
|
m_viewMenu->addAction(m_showDebugDialogAction);
|
||||||
|
|
||||||
connect(m_viewMenu, &QMenu::aboutToShow, [=]() {
|
connect(m_viewMenu, &QMenu::aboutToShow, [=]() {
|
||||||
m_showPartsListAction->setEnabled(partListDocker->isHidden());
|
m_showPartsListAction->setEnabled(partListDocker->isHidden());
|
||||||
|
m_showAnimationPanelAction->setEnabled(nullptr == m_animationPanelWidget || m_animationPanelWidget->isHidden());
|
||||||
m_resetModelWidgetPosAction->setEnabled(!modelIsSitInVisibleArea(m_modelRenderWidget));
|
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::skeletonChanged, m_document, &SkeletonDocument::generateMesh);
|
||||||
connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() {
|
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();
|
m_document->postProcess();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -877,6 +886,26 @@ void SkeletonDocumentWindow::exportModelResult()
|
||||||
QApplication::restoreOverrideCursor();
|
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()
|
void SkeletonDocumentWindow::showExportPreview()
|
||||||
{
|
{
|
||||||
if (nullptr == m_exportPreviewWidget) {
|
if (nullptr == m_exportPreviewWidget) {
|
||||||
|
@ -890,7 +919,9 @@ void SkeletonDocumentWindow::showExportPreview()
|
||||||
connect(m_document, &SkeletonDocument::resultBakedTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
|
connect(m_document, &SkeletonDocument::resultBakedTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
|
||||||
connect(m_document, &SkeletonDocument::resultSkeletonChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateSkeleton);
|
connect(m_document, &SkeletonDocument::resultSkeletonChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateSkeleton);
|
||||||
}
|
}
|
||||||
m_document->postProcess();
|
if (m_exportPreviewWidget->isHidden()) {
|
||||||
|
m_document->postProcess();
|
||||||
|
}
|
||||||
m_exportPreviewWidget->show();
|
m_exportPreviewWidget->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "skeletondocument.h"
|
#include "skeletondocument.h"
|
||||||
#include "modelwidget.h"
|
#include "modelwidget.h"
|
||||||
#include "exportpreviewwidget.h"
|
#include "exportpreviewwidget.h"
|
||||||
|
#include "animationpanelwidget.h"
|
||||||
|
|
||||||
class SkeletonGraphicsWidget;
|
class SkeletonGraphicsWidget;
|
||||||
|
|
||||||
|
@ -36,6 +37,7 @@ public slots:
|
||||||
void exportModelResult();
|
void exportModelResult();
|
||||||
void exportGltfResult();
|
void exportGltfResult();
|
||||||
void showExportPreview();
|
void showExportPreview();
|
||||||
|
void showAnimationPanel();
|
||||||
void newWindow();
|
void newWindow();
|
||||||
void newDocument();
|
void newDocument();
|
||||||
void saveAs();
|
void saveAs();
|
||||||
|
@ -59,6 +61,7 @@ private:
|
||||||
bool m_firstShow;
|
bool m_firstShow;
|
||||||
bool m_documentSaved;
|
bool m_documentSaved;
|
||||||
ExportPreviewWidget *m_exportPreviewWidget;
|
ExportPreviewWidget *m_exportPreviewWidget;
|
||||||
|
AnimationPanelWidget *m_animationPanelWidget;
|
||||||
private:
|
private:
|
||||||
QString m_currentFilename;
|
QString m_currentFilename;
|
||||||
|
|
||||||
|
@ -113,6 +116,7 @@ private:
|
||||||
QAction *m_showPartsListAction;
|
QAction *m_showPartsListAction;
|
||||||
QAction *m_showDebugDialogAction;
|
QAction *m_showDebugDialogAction;
|
||||||
QAction *m_toggleWireframeAction;
|
QAction *m_toggleWireframeAction;
|
||||||
|
QAction *m_showAnimationPanelAction;
|
||||||
|
|
||||||
QMenu *m_helpMenu;
|
QMenu *m_helpMenu;
|
||||||
QAction *m_viewSourceAction;
|
QAction *m_viewSourceAction;
|
||||||
|
|
|
@ -70,15 +70,15 @@ MeshLoader *SkeletonGenerator::createSkeletonMesh()
|
||||||
return skeletonMesh;
|
return skeletonMesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BmeshNodeDistWithWorldCenter
|
void SkeletonGenerator::generate()
|
||||||
{
|
{
|
||||||
BmeshNode *bmeshNode;
|
Q_ASSERT(nullptr == m_resultSkeletonMesh);
|
||||||
float dist2;
|
m_resultSkeletonMesh = createSkeletonMesh();
|
||||||
};
|
}
|
||||||
|
|
||||||
void SkeletonGenerator::process()
|
void SkeletonGenerator::process()
|
||||||
{
|
{
|
||||||
m_resultSkeletonMesh = createSkeletonMesh();
|
generate();
|
||||||
|
|
||||||
this->moveToThread(QGuiApplication::instance()->thread());
|
this->moveToThread(QGuiApplication::instance()->thread());
|
||||||
emit finished();
|
emit finished();
|
||||||
|
|
|
@ -12,6 +12,7 @@ class SkeletonGenerator : public QObject
|
||||||
public:
|
public:
|
||||||
SkeletonGenerator(const MeshResultContext &meshResultContext);
|
SkeletonGenerator(const MeshResultContext &meshResultContext);
|
||||||
~SkeletonGenerator();
|
~SkeletonGenerator();
|
||||||
|
void generate();
|
||||||
MeshLoader *takeResultSkeletonMesh();
|
MeshLoader *takeResultSkeletonMesh();
|
||||||
MeshResultContext *takeResultContext();
|
MeshResultContext *takeResultContext();
|
||||||
signals:
|
signals:
|
||||||
|
@ -19,7 +20,6 @@ signals:
|
||||||
public slots:
|
public slots:
|
||||||
void process();
|
void process();
|
||||||
private:
|
private:
|
||||||
void combineAllBmeshSkeletons();
|
|
||||||
MeshLoader *createSkeletonMesh();
|
MeshLoader *createSkeletonMesh();
|
||||||
private:
|
private:
|
||||||
MeshResultContext *m_meshResultContext;
|
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