Add auto-generated skeleton(skinned mesh) and glTF exporter.

And added a CONTRIBUTORS file to list the users who have done great work to help Dust3D become better, thank you!

@Alistair401
@hdu
@shadowndacorner
@Skullfurious
@RSDuck
@xtvjxk123456
@Zireael07
@glasyalabolas
@dlpatri

PS. please feel free to make a pull request or email me to change the ID or contact info listed in the CONTRIBUTORS file, and if you are mistakenly been missed in the file, please MUST edit this file and make a PR or email me.
master
Jeremy Hu 2018-04-30 19:31:09 +08:00
parent 331051879d
commit 3fef6c4463
31 changed files with 18505 additions and 156 deletions

View File

@ -421,3 +421,28 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode
<pre>
https://rosettacode.org/wiki/Catmull%E2%80%93Clark_subdivision_surface/C
</pre>
<h1>JSON for Modern C++</h1>
<pre>
MIT License
Copyright (c) 2013-2018 Niels Lohmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</pre>

11
CONTRIBUTORS Normal file
View File

@ -0,0 +1,11 @@
# Contributors ordered by first contribution.
Alistair Miles <https://github.com/Alistair401>
hdu <https://www.reddit.com/user/hdu>
Ian Diaz <https://github.com/shadowndacorner>
Skullfurious <https://www.reddit.com/user/Skullfurious>
Game From Scratch <http://www.gamefromscratch.com/>
RSDuck <https://github.com/RSDuck>
xtvjxk123456 <https://github.com/xtvjxk123456>
Zireael07 <https://github.com/Zireael07>
glasyalabolas <https://github.com/glasyalabolas>
David Patrick <https://github.com/dlpatri>

View File

@ -7,7 +7,7 @@ Dust3D is a brand new 3D modeling software. It helps you create a 3D watertight
[Download Dust3D](https://dust3d.readthedocs.io/en/latest/install.html)
[Online Dust3D Reference Guide](https://dust3d.readthedocs.io/en/latest/index.html)
Examples:
Examples
----------------------
[Make a 3D model from scratch using Dust3D](https://youtu.be/wQerDObDjOs)
@ -28,12 +28,17 @@ Examples:
<a href="https://dust3d.readthedocs.io/en/latest/examples/modeling-horse/index.html">
<image src="https://raw.githubusercontent.com/huxingyi/dust3d/master/docs/examples/modeling-horse/modeling-horse-dust3d-screenshot.png" width="320" height="192"></a>
Tutorials:
Tutorials
-------------
[Dust3D -- Free Open Source 3D Modelling Application](https://www.youtube.com/watch?v=YBnEQk_5D70)
<a href="https://www.youtube.com/watch?v=YBnEQk_5D70" target="_blank"><image src="https://raw.githubusercontent.com/huxingyi/dust3d/master/docs/images/dust3d-free-open-source-3d-modelling-application-video-thumbnail.png" width="480" height="270"></a>
Contributing
---------------
Any contributions are welcome, including fix bug, correct typo, test functionality, propose new features, make post to introduce and let more people know Dust3D. If you have done any of these, and cannot find your name in CONTRIBUTORS, please feel free to make a pull request to add your name, or email me.
If you have done programming code changes, including the example code listed in the docs folder, please dont forget to add your name to AUTHORS in your pull request.
License
-----------
Dust3D software binaries and source code use MIT License, which means you can use it freely no matter for personal or for commercial purpose. However, Dust3D's UI built on Qt5, the Mesh Union Algorithm based on CGAL library, and there are other algorithms such as Gift Wrapping, Bmesh, and so on which implemented in the meshlite repository, all these, may have other license restrictions.

View File

@ -30,7 +30,10 @@ Save all openned window.
Export...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Export current generated model mesh as Wavefront file format(.obj). You can choose to export the bare model or plus materials in (.mtl).
If you want mesh only result, export as Wavefront (.obj) format.
Choose GL Transmission Format (.gltf) to export all results in one gltf file, the results including autogenerated Meshes, Skeleton, and Materials.
Because .gltf is a very new format, not every 3D software officially supports it, and there are some plugins for blender to support gltf export and import, but mostly are buggy at current stage.
You can use Don McCurdy's online website, glTF Viewer https://gltf-viewer.donmccurdy.com/ to check your exported result.
Change Turnaround...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -104,7 +107,12 @@ View
Show Model
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Usually, you will no need to use this, because the Rendered Model always show. But if you can not find the Rendered Model and you are sure the generation is done, then maybe it goes to some wierd position, you can use this menu item to reset it's position.
Usually, you will no need to use this, because the Rendered Model always show. But if you can not find the Rendered Model and you are sure the generation is done, then maybe it goes to some weird position, you can use this menu item to reset it's position.
Toggle Bones
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The skeleton would also been autogenerated after meshes got generated.
Toggle this item to show or hide the Rendered Skeleton from canvas.
Show Parts List
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -132,5 +140,3 @@ Dust3D is a totally free and opensourced project, this bring you to the project
Report Issues
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you encounter any problem, or have any suggestion, thoughts, on Dust3D please drop it here, thanks.

View File

@ -74,6 +74,9 @@ HEADERS += src/skeletonxml.h
SOURCES += src/ds3file.cpp
HEADERS += src/ds3file.h
SOURCES += src/gltffile.cpp
HEADERS += src/gltffile.h
SOURCES += src/theme.cpp
HEADERS += src/theme.h
@ -86,8 +89,8 @@ HEADERS += src/meshutil.h
SOURCES += src/uvunwrapper.cpp
HEADERS += src/uvunwrapper.h
SOURCES += src/vertexskingenerator.cpp
HEADERS += src/vertexskingenerator.h
SOURCES += src/skeletongenerator.cpp
HEADERS += src/skeletongenerator.h
SOURCES += src/meshresultcontext.cpp
HEADERS += src/meshresultcontext.h
@ -104,9 +107,6 @@ HEADERS += src/logbrowserdialog.h
SOURCES += src/floatnumberwidget.cpp
HEADERS += src/floatnumberwidget.h
SOURCES += src/boneexportwidget.cpp
HEADERS += src/boneexportwidget.h
SOURCES += src/main.cpp
HEADERS += src/version.h
@ -179,6 +179,8 @@ unix:!macx {
MPFR_LIBDIR = /usr/local/lib
}
INCLUDEPATH += thirdparty/json
INCLUDEPATH += $$MESHLITE_DIR
LIBS += -L$$MESHLITE_DIR -l$$MESHLITE_LIBNAME

View File

@ -1,9 +0,0 @@
#include "boneexportwidget.h"
BoneExportWidget::BoneExportWidget() :
m_boneModelWidget(nullptr)
{
m_boneModelWidget = new ModelWidget(this);
m_boneModelWidget->setMinimumSize(128, 128);
m_boneModelWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
}

View File

@ -1,15 +0,0 @@
#ifndef BONE_EXPORT_WIDGET_H
#define BONE_EXPORT_WIDGET_H
#include <QWidget>
#include "modelwidget.h"
class BoneExportWidget : public QWidget
{
Q_OBJECT
public:
BoneExportWidget();
private:
ModelWidget *m_boneModelWidget;
};
#endif

314
src/gltffile.cpp Normal file
View File

@ -0,0 +1,314 @@
#include <QFile>
#include <QQuaternion>
#include <QByteArray>
#include <QDataStream>
#include "gltffile.h"
#include "version.h"
#include "util.h"
// Play with glTF online:
// https://gltf-viewer.donmccurdy.com/
// https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_020_Skins.md
// https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_004_ScenesNodes.md#global-transforms-of-nodes
// http://quaternions.online/
// https://en.m.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions?wprov=sfla1
GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext)
{
const BmeshNode *rootNode = resultContext.centerBmeshNode();
if (!rootNode) {
qDebug() << "Cannot export gltf because lack of root node";
return;
}
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);
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;
int skeletonNodeStartIndex = 2;
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()
};
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()
};
if (m_tracedJoints[i].children.empty())
continue;
m_json["nodes"][skeletonNodeStartIndex + i]["children"] = {};
for (const auto &it: m_tracedJoints[i].children) {
m_json["nodes"][skeletonNodeStartIndex + i]["children"] += skeletonNodeStartIndex + it;
}
}
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++) {
m_json["skins"][0]["joints"] += skeletonNodeStartIndex + i;
}
QByteArray binaries;
QDataStream stream(&binaries, QIODevice::WriteOnly);
stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
stream.setByteOrder(QDataStream::LittleEndian);
auto alignBinaries = [&binaries, &stream] {
while (0 != binaries.size() % 4) {
stream << (quint8)0;
}
};
int bufferViewIndex = 0;
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]["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 j = 0u; j < 16; j++) {
stream << (float)floatArray[j];
}
}
m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViews0FromOffset;
alignBinaries();
bufferViewIndex++;
int primitiveIndex = 0;
for (const auto &part: resultContext.resultParts()) {
int bufferViewFromOffset;
m_json["meshes"][0]["primitives"][primitiveIndex]["indices"] = bufferViewIndex;
m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["POSITION"] = bufferViewIndex + 1;
m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["JOINTS_0"] = bufferViewIndex + 2;
m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["WEIGHTS_0"] = bufferViewIndex + 3;
m_json["meshes"][0]["primitives"][primitiveIndex]["material"] = primitiveIndex;
m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["baseColorFactor"] = {
part.second.color.redF(),
part.second.color.greenF(),
part.second.color.blueF(),
1.0
};
m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["metallicFactor"] = 0.0;
m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["roughnessFactor"] = 1.0;
primitiveIndex++;
bufferViewFromOffset = (int)binaries.size();
for (const auto &it: part.second.triangles) {
stream << (quint16)it.indicies[0] << (quint16)it.indicies[1] << (quint16)it.indicies[2];
}
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset;
m_json["bufferViews"][bufferViewIndex]["byteLength"] = (int)part.second.triangles.size() * 3 * sizeof(quint16);
m_json["bufferViews"][bufferViewIndex]["target"] = 34963;
Q_ASSERT(part.second.triangles.size() * 3 * sizeof(quint16) == binaries.size() - bufferViewFromOffset);
alignBinaries();
m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex;
m_json["accessors"][bufferViewIndex]["byteOffset"] = 0;
m_json["accessors"][bufferViewIndex]["componentType"] = 5123;
m_json["accessors"][bufferViewIndex]["count"] = part.second.triangles.size() * 3;
m_json["accessors"][bufferViewIndex]["type"] = "SCALAR";
bufferViewIndex++;
bufferViewFromOffset = (int)binaries.size();
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset;
float minX = 100;
float maxX = -100;
float minY = 100;
float maxY = -100;
float minZ = 100;
float maxZ = -100;
for (const auto &it: part.second.vertices) {
if (it.position.x() < minX)
minX = it.position.x();
if (it.position.x() > maxX)
maxX = it.position.x();
if (it.position.y() < minY)
minY = it.position.y();
if (it.position.y() > maxY)
maxY = it.position.y();
if (it.position.z() < minZ)
minZ = it.position.z();
if (it.position.z() > maxZ)
maxZ = it.position.z();
stream << (float)it.position.x() << (float)it.position.y() << (float)it.position.z();
}
Q_ASSERT( part.second.vertices.size() * 3 * sizeof(float) == binaries.size() - bufferViewFromOffset);
m_json["bufferViews"][bufferViewIndex]["byteLength"] = part.second.vertices.size() * 3 * sizeof(float);
m_json["bufferViews"][bufferViewIndex]["target"] = 34962;
alignBinaries();
m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex;
m_json["accessors"][bufferViewIndex]["byteOffset"] = 0;
m_json["accessors"][bufferViewIndex]["componentType"] = 5126;
m_json["accessors"][bufferViewIndex]["count"] = part.second.vertices.size();
m_json["accessors"][bufferViewIndex]["type"] = "VEC3";
m_json["accessors"][bufferViewIndex]["max"] = {maxX, maxY, maxZ};
m_json["accessors"][bufferViewIndex]["min"] = {minX, minY, minZ};
bufferViewIndex++;
bufferViewFromOffset = (int)binaries.size();
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset;
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];
}
for (; i < MAX_WEIGHT_NUM; i++) {
stream << (quint16)0;
}
}
m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset;
alignBinaries();
m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex;
m_json["accessors"][bufferViewIndex]["byteOffset"] = 0;
m_json["accessors"][bufferViewIndex]["componentType"] = 5123;
m_json["accessors"][bufferViewIndex]["count"] = part.second.weights.size();
m_json["accessors"][bufferViewIndex]["type"] = "VEC4";
bufferViewIndex++;
bufferViewFromOffset = (int)binaries.size();
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset;
for (const auto &it: part.second.weights) {
auto i = 0u;
for (; i < it.size() && i < MAX_WEIGHT_NUM; i++) {
stream << (quint16)it[i].weight;
}
for (; i < MAX_WEIGHT_NUM; i++) {
stream << (float)0.0;
}
}
m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset;
alignBinaries();
m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex;
m_json["accessors"][bufferViewIndex]["byteOffset"] = 0;
m_json["accessors"][bufferViewIndex]["componentType"] = 5126;
m_json["accessors"][bufferViewIndex]["count"] = part.second.weights.size();
m_json["accessors"][bufferViewIndex]["type"] = "VEC4";
bufferViewIndex++;
}
m_json["buffers"][0]["uri"] = QString("data:application/octet-stream;base64," + binaries.toBase64()).toUtf8().constData();
m_json["buffers"][0]["byteLength"] = binaries.size();
}
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(const QString &filename)
{
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) {
return false;
}
file.write(QString::fromStdString(m_json.dump(4)).toUtf8());
return true;
}

40
src/gltffile.h Normal file
View File

@ -0,0 +1,40 @@
#ifndef GLTF_FILE_H
#define GLTF_FILE_H
#include <QObject>
#include <QString>
#include <QByteArray>
#include <QMatrix4x4>
#include <vector>
#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);
bool save(const QString &filename);
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);
private:
QByteArray m_data;
std::vector<JointInfo> m_tracedJoints;
std::map<std::pair<int, int>, int> m_tracedNodeToJointIndexMap;
QString getMatrixStringInColumnOrder(const QMatrix4x4 &mat);
private:
nlohmann::json m_json;
};
#endif

View File

@ -6,7 +6,7 @@
#define MAX_VERTICES_PER_FACE 100
Mesh::Mesh(void *meshlite, int meshId, int triangulatedMeshId, QColor modelColor, std::vector<QColor> *triangleColors) :
Mesh::Mesh(void *meshlite, int meshId, int triangulatedMeshId, QColor modelColor, const std::vector<QColor> *triangleColors) :
m_triangleVertices(NULL),
m_triangleVertexCount(0),
m_edgeVertices(NULL),

View File

@ -33,7 +33,7 @@ struct TriangulatedFace
class Mesh
{
public:
Mesh(void *meshlite, int meshId, int triangulatedMeshId = -1, QColor modelColor=Theme::white, std::vector<QColor> *triangleColors=nullptr);
Mesh(void *meshlite, int meshId, int triangulatedMeshId = -1, QColor modelColor=Theme::white, const std::vector<QColor> *triangleColors=nullptr);
~Mesh();
Vertex *triangleVertices();
int triangleVertexCount();

View File

@ -180,6 +180,7 @@ void MeshGenerator::process()
}
std::map<QString, QColor> partColorMap;
std::map<QString, bool> partMirrorFlagMap;
for (const auto &partIdIt: m_snapshot->partIdList) {
const auto &part = m_snapshot->parts.find(partIdIt);
@ -195,6 +196,7 @@ void MeshGenerator::process()
meshlite_bmesh_set_cut_subdiv_count(meshliteContext, bmeshId, 1);
if (isTrueValueString(valueOfKeyInMapOrEmpty(part->second, "rounded")))
meshlite_bmesh_set_round_way(meshliteContext, bmeshId, 1);
partMirrorFlagMap[partIdIt] = isTrueValueString(valueOfKeyInMapOrEmpty(part->second, "xMirrored"));
QString colorString = valueOfKeyInMapOrEmpty(part->second, "color");
QColor partColor = colorString.isEmpty() ? Theme::white : QColor(colorString);
partColorMap[partIdIt] = partColor;
@ -241,6 +243,12 @@ void MeshGenerator::process()
bmeshNode.nodeId = bmeshFromNodeId;
bmeshNode.color = partColorMap[partId];
m_meshResultContext->bmeshNodes.push_back(bmeshNode);
if (partMirrorFlagMap[partId]) {
bmeshNode.bmeshId = -bmeshId;
bmeshNode.origin.setX(-x);
m_meshResultContext->bmeshNodes.push_back(bmeshNode);
}
} else {
bmeshFromNodeId = bmeshFromIt->second;
//qDebug() << "bmeshId[" << bmeshId << "] use existed node[" << bmeshFromNodeId << "]";
@ -264,6 +272,12 @@ void MeshGenerator::process()
bmeshNode.nodeId = bmeshToNodeId;
bmeshNode.color = partColorMap[partId];
m_meshResultContext->bmeshNodes.push_back(bmeshNode);
if (partMirrorFlagMap[partId]) {
bmeshNode.bmeshId = -bmeshId;
bmeshNode.origin.setX(-x);
m_meshResultContext->bmeshNodes.push_back(bmeshNode);
}
} else {
bmeshToNodeId = bmeshToIt->second;
//qDebug() << "bmeshId[" << bmeshId << "] use existed node[" << bmeshToNodeId << "]";
@ -272,10 +286,20 @@ void MeshGenerator::process()
meshlite_bmesh_add_edge(meshliteContext, bmeshId, bmeshFromNodeId, bmeshToNodeId);
BmeshEdge bmeshEdge;
bmeshEdge.bmeshId = bmeshId;
bmeshEdge.fromBmeshId = bmeshId;
bmeshEdge.fromNodeId = bmeshFromNodeId;
bmeshEdge.toBmeshId = bmeshId;
bmeshEdge.toNodeId = bmeshToNodeId;
m_meshResultContext->bmeshEdges.push_back(bmeshEdge);
if (partMirrorFlagMap[partId]) {
BmeshEdge bmeshEdge;
bmeshEdge.fromBmeshId = -bmeshId;
bmeshEdge.fromNodeId = bmeshFromNodeId;
bmeshEdge.toBmeshId = -bmeshId;
bmeshEdge.toNodeId = bmeshToNodeId;
m_meshResultContext->bmeshEdges.push_back(bmeshEdge);
}
}
for (const auto &nodeIt: m_snapshot->nodes) {
@ -302,6 +326,12 @@ void MeshGenerator::process()
bmeshNode.nodeId = bmeshNodeId;
bmeshNode.color = partColorMap[partId];
m_meshResultContext->bmeshNodes.push_back(bmeshNode);
if (partMirrorFlagMap[partId]) {
bmeshNode.bmeshId = -bmeshId;
bmeshNode.origin.setX(-x);
m_meshResultContext->bmeshNodes.push_back(bmeshNode);
}
}
bool broken = false;
@ -321,19 +351,13 @@ void MeshGenerator::process()
if (meshlite_bmesh_error_count(meshliteContext, bmeshId) != 0)
broken = true;
bool xMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(part->second, "xMirrored"));
bool zMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(part->second, "zMirrored"));
loadVertexSourcesToMeshResultContext(meshliteContext, meshId, bmeshId);
QColor modelColor = partColorMap[partIdIt];
int xMirroredMeshId = 0;
int zMirroredMeshId = 0;
if (xMirrored || zMirrored) {
if (xMirrored) {
if (xMirrored) {
xMirroredMeshId = meshlite_mirror_in_x(meshliteContext, meshId, 0);
loadVertexSourcesToMeshResultContext(meshliteContext, xMirroredMeshId, bmeshId);
}
if (zMirrored) {
zMirroredMeshId = meshlite_mirror_in_z(meshliteContext, meshId, 0);
loadVertexSourcesToMeshResultContext(meshliteContext, zMirroredMeshId, bmeshId);
loadVertexSourcesToMeshResultContext(meshliteContext, xMirroredMeshId, -bmeshId);
}
}
if (m_requirePartPreviewMap.find(partIdIt) != m_requirePartPreviewMap.end()) {
@ -346,8 +370,6 @@ void MeshGenerator::process()
meshIds.push_back(meshId);
if (xMirroredMeshId)
meshIds.push_back(xMirroredMeshId);
if (zMirroredMeshId)
meshIds.push_back(zMirroredMeshId);
}
if (!subdivMeshIds.empty()) {
@ -404,9 +426,7 @@ void MeshGenerator::process()
loadGeneratedPositionsToMeshResultContext(meshliteContext, triangulatedFinalMeshId);
//PositionMap<QColor> positionColorMap;
//m_meshResultContext->calculatePositionColorMap(positionColorMap);
std::vector<QColor> triangleColors;
m_meshResultContext->calculateTriangleColors(triangleColors);
m_mesh = new Mesh(meshliteContext, finalMeshId, triangulatedFinalMeshId, broken ? Theme::broken : Theme::white, &triangleColors);
m_mesh = new Mesh(meshliteContext, finalMeshId, triangulatedFinalMeshId, broken ? Theme::broken : Theme::white, &m_meshResultContext->triangleColors());
}
if (m_previewRender) {

View File

@ -8,65 +8,129 @@
struct HalfColorEdge
{
int cornVertexIndex;
QColor color;
std::pair<int, int> source;
};
struct CandidateEdge
{
QColor color;
std::pair<int, int> source;
int fromVertexIndex;
int toVertexIndex;
float dot;
float length;
};
void MeshResultContext::calculateTriangleColors(std::vector<QColor> &triangleColors)
MeshResultContext::MeshResultContext() :
m_triangleSourceResolved(false),
m_triangleColorResolved(false),
m_bmeshConnectivityResolved(false),
m_triangleEdgeSourceMapResolved(false),
m_bmeshNodeMapResolved(false),
m_centerBmeshNodeResolved(false),
m_bmeshEdgeDirectionsResolved(false),
m_bmeshNodeNeighborsResolved(false),
m_vertexWeightResolved(false),
m_centerBmeshNode(nullptr),
m_resultPartsResolved(false)
{
PositionMap<QColor> positionColorMap;
std::map<std::pair<int, int>, QColor> nodeColorMap;
for (const auto &it: bmeshNodes) {
nodeColorMap[std::make_pair(it.bmeshId, it.nodeId)] = it.color;
}
const std::vector<std::pair<int, int>> &MeshResultContext::triangleSourceNodes()
{
if (!m_triangleSourceResolved) {
calculateTriangleSourceNodes(m_triangleSourceNodes);
m_triangleSourceResolved = true;
}
for (const auto &it: bmeshVertices) {
positionColorMap.addPosition(it.position.x(), it.position.y(), it.position.z(),
nodeColorMap[std::make_pair(it.bmeshId, it.nodeId)]);
return m_triangleSourceNodes;
}
const std::vector<QColor> &MeshResultContext::triangleColors()
{
if (!m_triangleColorResolved) {
calculateTriangleColors(m_triangleColors);
m_triangleColorResolved = true;
}
return m_triangleColors;
}
const std::map<std::pair<int, int>, std::pair<int, int>> &MeshResultContext::triangleEdgeSourceMap()
{
if (!m_triangleEdgeSourceMapResolved) {
calculateTriangleEdgeSourceMap(m_triangleEdgeSourceMap);
m_triangleEdgeSourceMapResolved = true;
}
return m_triangleEdgeSourceMap;
}
const std::map<std::pair<int, int>, BmeshNode *> &MeshResultContext::bmeshNodeMap()
{
if (!m_bmeshNodeMapResolved) {
calculateBmeshNodeMap(m_bmeshNodeMap);
m_bmeshNodeMapResolved = true;
}
return m_bmeshNodeMap;
}
const BmeshNode *MeshResultContext::centerBmeshNode()
{
if (!m_centerBmeshNodeResolved) {
m_centerBmeshNode = calculateCenterBmeshNode();
m_centerBmeshNodeResolved = true;
}
return m_centerBmeshNode;
}
void MeshResultContext::resolveBmeshConnectivity()
{
if (!m_bmeshConnectivityResolved) {
calculateBmeshConnectivity();
m_bmeshConnectivityResolved = true;
}
}
void MeshResultContext::calculateTriangleSourceNodes(std::vector<std::pair<int, int>> &triangleSourceNodes)
{
PositionMap<std::pair<int, int>> positionMap;
std::map<std::pair<int, int>, HalfColorEdge> halfColorEdgeMap;
std::set<int> brokenTriangleSet;
for (const auto &it: bmeshVertices) {
positionMap.addPosition(it.position.x(), it.position.y(), it.position.z(),
std::make_pair(it.bmeshId, it.nodeId));
}
for (auto x = 0u; x < resultTriangles.size(); x++) {
const auto triangle = &resultTriangles[x];
std::vector<std::pair<QColor, int>> colorTypes;
std::vector<std::pair<std::pair<int, int>, int>> colorTypes;
for (int i = 0; i < 3; i++) {
int index = triangle->indicies[i];
ResultVertex *resultVertex = &resultVertices[index];
QColor color;
if (positionColorMap.findPosition(resultVertex->position.x(), resultVertex->position.y(), resultVertex->position.z(), &color)) {
std::pair<int, int> source;
if (positionMap.findPosition(resultVertex->position.x(), resultVertex->position.y(), resultVertex->position.z(), &source)) {
bool colorExisted = false;
for (auto j = 0u; j < colorTypes.size(); j++) {
if (colorTypes[j].first == color) {
if (colorTypes[j].first == source) {
colorTypes[j].second++;
colorExisted = true;
break;
}
}
if (!colorExisted) {
colorTypes.push_back(std::make_pair(color, 1));
colorTypes.push_back(std::make_pair(source, 1));
}
}
}
if (colorTypes.empty()) {
//qDebug() << "All vertices of a triangle can't find a color";
triangleColors.push_back(Theme::white);
triangleSourceNodes.push_back(std::make_pair(0, 0));
brokenTriangleSet.insert(x);
continue;
}
if (colorTypes.size() != 1 || 3 == colorTypes[0].second) {
std::sort(colorTypes.begin(), colorTypes.end(), [](const std::pair<QColor, int> &a, const std::pair<QColor, int> &b) -> bool {
std::sort(colorTypes.begin(), colorTypes.end(), [](const std::pair<std::pair<int, int>, int> &a, const std::pair<std::pair<int, int>, int> &b) -> bool {
return a.second > b.second;
});
}
QColor choosenColor = colorTypes[0].first;
triangleColors.push_back(choosenColor);
std::pair<int, int> choosenColor = colorTypes[0].first;
triangleSourceNodes.push_back(choosenColor);
for (int i = 0; i < 3; i++) {
int oppositeStartIndex = triangle->indicies[(i + 1) % 3];
int oppositeStopIndex = triangle->indicies[i];
@ -78,7 +142,7 @@ void MeshResultContext::calculateTriangleColors(std::vector<QColor> &triangleCol
auto selfPair = std::make_pair(oppositeStopIndex, oppositeStartIndex);
HalfColorEdge edge;
edge.cornVertexIndex = triangle->indicies[(i + 2) % 3];
edge.color = choosenColor;
edge.source = choosenColor;
halfColorEdgeMap[selfPair] = edge;
}
}
@ -119,7 +183,7 @@ void MeshResultContext::calculateTriangleColors(std::vector<QColor> &triangleCol
candidate.length = length;
candidate.fromVertexIndex = triangle->indicies[i];
candidate.toVertexIndex = triangle->indicies[(i + 1) % 3];
candidate.color = findOpposite->second.color;
candidate.source = findOpposite->second.source;
candidateEdges.push_back(candidate);
}
}
@ -147,7 +211,7 @@ void MeshResultContext::calculateTriangleColors(std::vector<QColor> &triangleCol
if (brokenTriangleSet.find(x) == brokenTriangleSet.end())
continue;
brokenTriangleSet.erase(x);
triangleColors[x] = candidate.color;
triangleSourceNodes[x] = candidate.source;
//qDebug() << "resolved triangle:" << x;
const auto triangle = &resultTriangles[x];
for (int i = 0; i < 3; i++) {
@ -159,3 +223,265 @@ void MeshResultContext::calculateTriangleColors(std::vector<QColor> &triangleCol
}
}
}
void MeshResultContext::calculateTriangleColors(std::vector<QColor> &triangleColors)
{
PositionMap<QColor> positionColorMap;
std::map<std::pair<int, int>, QColor> nodeColorMap;
for (const auto &it: bmeshNodes) {
nodeColorMap[std::make_pair(it.bmeshId, it.nodeId)] = it.color;
}
const auto sourceNodes = triangleSourceNodes();
for (const auto &it: sourceNodes) {
triangleColors.push_back(nodeColorMap[it]);
}
}
struct BmeshConnectionPossible
{
BmeshEdge bmeshEdge;
float score;
};
void MeshResultContext::calculateBmeshConnectivity()
{
const std::map<std::pair<int, int>, std::pair<int, int>> edgeSourceMap = triangleEdgeSourceMap();
std::set<std::pair<int, int>> processedSet;
qDebug() << "start calculateBmeshConnectivity";
std::vector<BmeshConnectionPossible> possibles;
for (const auto &it: edgeSourceMap) {
if (processedSet.find(it.first) != processedSet.end())
continue;
const auto pairKey = std::make_pair(it.first.second, it.first.first);
const auto &paired = edgeSourceMap.find(pairKey);
if (paired == edgeSourceMap.end())
continue;
processedSet.insert(pairKey);
if (it.second == paired->second)
continue;
BmeshConnectionPossible possible;
possible.bmeshEdge.fromBmeshId = it.second.first;
possible.bmeshEdge.fromNodeId = it.second.second;
possible.bmeshEdge.toBmeshId = paired->second.first;
possible.bmeshEdge.toNodeId = paired->second.second;
possible.score = 100000000; // just as bigger than all the lengthSquared
const auto &fromBmeshNode = bmeshNodeMap().find(std::make_pair(possible.bmeshEdge.fromBmeshId, possible.bmeshEdge.fromNodeId));
const auto &toBmeshNode = bmeshNodeMap().find(std::make_pair(possible.bmeshEdge.toBmeshId, possible.bmeshEdge.toNodeId));
if (fromBmeshNode == bmeshNodeMap().end())
qDebug() << "There is noexists node:" << possible.bmeshEdge.fromBmeshId << possible.bmeshEdge.fromNodeId;
else if (toBmeshNode == bmeshNodeMap().end())
qDebug() << "There is noexists node:" << possible.bmeshEdge.toBmeshId << possible.bmeshEdge.toNodeId;
else
possible.score = (fromBmeshNode->second->origin - toBmeshNode->second->origin).lengthSquared();
possibles.push_back(possible);
}
std::sort(possibles.begin(), possibles.end(), [](const BmeshConnectionPossible &a, const BmeshConnectionPossible &b) -> bool {
return a.score < b.score;
});
std::set<std::pair<int, int>> connectedBmeshSet;
for (auto i = 0u; i < possibles.size(); i++) {
BmeshConnectionPossible *possible = &possibles[i];
int fromBmeshId = possible->bmeshEdge.fromBmeshId;
int toBmeshId = possible->bmeshEdge.toBmeshId;
if (connectedBmeshSet.find(std::make_pair(fromBmeshId, toBmeshId)) != connectedBmeshSet.end())
continue;
if (possible->bmeshEdge.fromBmeshId != -possible->bmeshEdge.toBmeshId) {
// Don't connect each other mirrored parts
bmeshEdges.push_back(possible->bmeshEdge);
}
connectedBmeshSet.insert(std::make_pair(fromBmeshId, toBmeshId));
connectedBmeshSet.insert(std::make_pair(toBmeshId, fromBmeshId));
}
qDebug() << "finish calculateBmeshConnectivity";
}
void MeshResultContext::calculateTriangleEdgeSourceMap(std::map<std::pair<int, int>, std::pair<int, int>> &triangleEdgeSourceMap)
{
const std::vector<std::pair<int, int>> sourceNodes = triangleSourceNodes();
for (auto x = 0u; x < resultTriangles.size(); x++) {
const auto triangle = &resultTriangles[x];
for (int i = 0; i < 3; i++) {
int startIndex = triangle->indicies[i];
int stopIndex = triangle->indicies[(i + 1) % 3];
triangleEdgeSourceMap[std::make_pair(startIndex, stopIndex)] = sourceNodes[x];
}
}
}
void MeshResultContext::calculateBmeshNodeMap(std::map<std::pair<int, int>, BmeshNode *> &bmeshNodeMap) {
for (auto i = 0u; i < bmeshNodes.size(); i++) {
BmeshNode *bmeshNode = &bmeshNodes[i];
bmeshNodeMap[std::make_pair(bmeshNode->bmeshId, bmeshNode->nodeId)] = bmeshNode;
}
}
struct BmeshNodeDistWithWorldCenter
{
BmeshNode *bmeshNode;
float dist2;
};
BmeshNode *MeshResultContext::calculateCenterBmeshNode()
{
// Sort all the nodes by distance with world center.
std::vector<BmeshNodeDistWithWorldCenter> nodesOrderByDistWithWorldCenter;
for (auto i = 0u; i < bmeshNodes.size(); i++) {
BmeshNode *bmeshNode = &bmeshNodes[i];
BmeshNodeDistWithWorldCenter distNode;
distNode.bmeshNode = bmeshNode;
distNode.dist2 = bmeshNode->origin.lengthSquared();
nodesOrderByDistWithWorldCenter.push_back(distNode);
}
if (nodesOrderByDistWithWorldCenter.empty())
return nullptr;
std::sort(nodesOrderByDistWithWorldCenter.begin(), nodesOrderByDistWithWorldCenter.end(), [](const BmeshNodeDistWithWorldCenter &a, const BmeshNodeDistWithWorldCenter &b) -> bool {
return a.dist2 < b.dist2;
});
return nodesOrderByDistWithWorldCenter[0].bmeshNode;
}
const std::map<std::pair<int, int>, std::vector<std::pair<int, int>>> &MeshResultContext::nodeNeighbors()
{
if (!m_bmeshNodeNeighborsResolved) {
calculateBmeshNodeNeighbors(m_nodeNeighbors);
m_bmeshNodeNeighborsResolved = true;
}
return m_nodeNeighbors;
}
void MeshResultContext::calculateBmeshNodeNeighbors(std::map<std::pair<int, int>, std::vector<std::pair<int, int>>> &nodeNeighbors)
{
for (const auto &it: bmeshEdges) {
nodeNeighbors[std::make_pair(it.fromBmeshId, it.fromNodeId)].push_back(std::make_pair(it.toBmeshId, it.toNodeId));
nodeNeighbors[std::make_pair(it.toBmeshId, it.toNodeId)].push_back(std::make_pair(it.fromBmeshId, it.fromNodeId));
}
}
void MeshResultContext::calculateBmeshEdgeDirectionsFromNode(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, std::vector<BmeshEdge> &rearrangedEdges)
{
if (visitedNodes.find(node) != visitedNodes.end())
return;
visitedNodes.insert(node);
const auto &neighbors = nodeNeighbors().find(node);
if (neighbors == 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;
BmeshEdge bmeshEdge;
bmeshEdge.fromBmeshId = node.first;
bmeshEdge.fromNodeId = node.second;
bmeshEdge.toBmeshId = it.first;
bmeshEdge.toNodeId = it.second;
rearrangedEdges.push_back(bmeshEdge);
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)));
calculateBmeshEdgeDirectionsFromNode(it, visitedNodes, connections, rearrangedEdges);
}
}
void MeshResultContext::resolveBmeshEdgeDirections()
{
if (!m_bmeshEdgeDirectionsResolved) {
calculateBmeshEdgeDirections();
m_bmeshEdgeDirectionsResolved = true;
}
}
void MeshResultContext::calculateBmeshEdgeDirections()
{
const BmeshNode *rootNode = centerBmeshNode();
if (!rootNode)
return;
std::set<std::pair<int, int>> visitedNodes;
std::vector<BmeshEdge> rearrangedEdges;
std::set<std::pair<std::pair<int, int>, std::pair<int, int>>> connections;
calculateBmeshEdgeDirectionsFromNode(std::make_pair(rootNode->bmeshId, rootNode->nodeId), visitedNodes, connections, rearrangedEdges);
bmeshEdges = rearrangedEdges;
}
const std::vector<std::vector<ResultVertexWeight>> &MeshResultContext::resultVertexWeights()
{
if (!m_vertexWeightResolved) {
calculateVertexWeights(m_resultVertexWeights);
m_vertexWeightResolved = true;
}
return m_resultVertexWeights;
}
void MeshResultContext::calculateVertexWeights(std::vector<std::vector<ResultVertexWeight>> &vertexWeights)
{
vertexWeights.clear();
vertexWeights.resize(resultVertices.size());
for (auto i = 0u; i < resultTriangles.size(); i++) {
std::pair<int, int> sourceNode = triangleSourceNodes()[i];
for (int j = 0; j < 3; j++) {
int vertexIndex = resultTriangles[i].indicies[j];
Q_ASSERT(vertexIndex < (int)vertexWeights.size());
int foundSourceNodeIndex = -1;
for (auto k = 0u; k < vertexWeights[vertexIndex].size(); k++) {
if (vertexWeights[vertexIndex][k].sourceNode == sourceNode) {
foundSourceNodeIndex = k;
break;
}
}
if (-1 == foundSourceNodeIndex) {
ResultVertexWeight vertexWeight;
vertexWeight.sourceNode = sourceNode;
vertexWeight.count = 1;
continue;
}
vertexWeights[vertexIndex][foundSourceNodeIndex].count++;
}
}
for (auto &it: vertexWeights) {
int total = 0;
for (auto i = 0u; i < MAX_WEIGHT_NUM && i < it.size(); i++) {
total += it[i].count;
}
for (auto i = 0u; i < MAX_WEIGHT_NUM && i < it.size(); i++) {
it[i].weight = (float)it[i].count / total;
}
}
}
const std::map<int, ResultPart> &MeshResultContext::resultParts()
{
if (!m_resultPartsResolved) {
calculateResultParts(m_resultParts);
m_resultPartsResolved = true;
}
return m_resultParts;
}
void MeshResultContext::calculateResultParts(std::map<int, ResultPart> &parts)
{
std::map<std::pair<int, int>, int> oldVertexToNewMap;
for (auto x = 0u; x < resultTriangles.size(); x++) {
const auto &triangle = resultTriangles[x];
const auto &sourceNode = triangleSourceNodes()[x];
auto it = parts.find(sourceNode.first);
if (it == parts.end()) {
ResultPart newPart;
newPart.color = triangleColors()[x];
parts.insert(std::make_pair(sourceNode.first, newPart));
}
auto &resultPart = parts[sourceNode.first];
ResultTriangle newTriangle;
newTriangle.normal = triangle.normal;
for (auto i = 0u; i < 3; i++) {
auto key = std::make_pair(sourceNode.first, triangle.indicies[i]);
const auto &it = oldVertexToNewMap.find(key);
if (it == oldVertexToNewMap.end()) {
int newIndex = resultPart.vertices.size();
resultPart.vertices.push_back(resultVertices[triangle.indicies[i]]);
resultPart.weights.push_back(resultVertexWeights()[triangle.indicies[i]]);
oldVertexToNewMap.insert(std::make_pair(key, newIndex));
newTriangle.indicies[i] = newIndex;
} else {
newTriangle.indicies[i] = it->second;
}
}
resultPart.triangles.push_back(newTriangle);
}
}

View File

@ -1,11 +1,14 @@
#ifndef MESH_RESULT_CONTEXT_H
#define MESH_RESULT_CONTEXT_H
#include <vector>
#include <set>
#include <QVector3D>
#include <QUuid>
#include <QColor>
#include "positionmap.h"
#define MAX_WEIGHT_NUM 4
struct BmeshNode
{
int bmeshId;
@ -24,8 +27,9 @@ struct BmeshVertex
struct BmeshEdge
{
int bmeshId;
int fromBmeshId;
int fromNodeId;
int toBmeshId;
int toNodeId;
};
@ -40,6 +44,21 @@ struct ResultTriangle
QVector3D normal;
};
struct ResultVertexWeight
{
std::pair<int, int> sourceNode;
int count;
float weight;
};
struct ResultPart
{
QColor color;
std::vector<ResultVertex> vertices;
std::vector<std::vector<ResultVertexWeight>> weights;
std::vector<ResultTriangle> triangles;
};
class MeshResultContext
{
public:
@ -48,8 +67,51 @@ public:
std::vector<BmeshEdge> bmeshEdges;
std::vector<ResultVertex> resultVertices;
std::vector<ResultTriangle> resultTriangles;
MeshResultContext();
public:
const std::vector<std::pair<int, int>> &triangleSourceNodes();
const std::vector<QColor> &triangleColors();
const std::map<std::pair<int, int>, std::pair<int, int>> &triangleEdgeSourceMap();
const std::map<std::pair<int, int>, BmeshNode *> &bmeshNodeMap();
const BmeshNode *centerBmeshNode();
void resolveBmeshConnectivity();
void resolveBmeshEdgeDirections();
const std::map<std::pair<int, int>, std::vector<std::pair<int, int>>> &nodeNeighbors();
const std::vector<std::vector<ResultVertexWeight>> &resultVertexWeights();
const std::map<int, ResultPart> &resultParts();
private:
bool m_triangleSourceResolved;
bool m_triangleColorResolved;
bool m_bmeshConnectivityResolved;
bool m_triangleEdgeSourceMapResolved;
bool m_bmeshNodeMapResolved;
bool m_centerBmeshNodeResolved;
bool m_bmeshEdgeDirectionsResolved;
bool m_bmeshNodeNeighborsResolved;
bool m_vertexWeightResolved;
BmeshNode *m_centerBmeshNode;
bool m_resultPartsResolved;
private:
std::vector<std::pair<int, int>> m_triangleSourceNodes;
std::vector<QColor> m_triangleColors;
std::map<std::pair<int, int>, std::pair<int, int>> m_triangleEdgeSourceMap;
std::map<std::pair<int, int>, BmeshNode *> m_bmeshNodeMap;
std::map<std::pair<int, int>, std::vector<std::pair<int, int>>> m_nodeNeighbors;
std::vector<std::vector<ResultVertexWeight>> m_resultVertexWeights;
std::map<int, ResultPart> m_resultParts;
private:
void calculateTriangleSourceNodes(std::vector<std::pair<int, int>> &triangleSourceNodes);
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);
BmeshNode *calculateCenterBmeshNode();
void calculateBmeshConnectivity();
void calculateBmeshEdgeDirections();
void calculateBmeshNodeNeighbors();
void calculateBmeshEdgeDirectionsFromNode(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, std::vector<BmeshEdge> &rearrangedEdges);
void calculateBmeshNodeNeighbors(std::map<std::pair<int, int>, std::vector<std::pair<int, int>>> &nodeNeighbors);
void calculateVertexWeights(std::vector<std::vector<ResultVertexWeight>> &vertexWeights);
void calculateResultParts(std::map<int, ResultPart> &parts);
};
#endif

View File

@ -25,13 +25,20 @@ SkeletonDocument::SkeletonDocument() :
m_resultMeshIsObsolete(false),
m_meshGenerator(nullptr),
m_resultMesh(nullptr),
m_batchChangeRefCount(0)
m_batchChangeRefCount(0),
m_currentMeshResultContext(nullptr),
m_resultSkeletonIsObsolete(false),
m_skeletonGenerator(nullptr),
m_resultSkeletonMesh(nullptr),
m_currentSkeletonResultContext(new MeshResultContext)
{
}
SkeletonDocument::~SkeletonDocument()
{
delete m_resultMesh;
delete m_resultSkeletonMesh;
delete m_currentSkeletonResultContext;
}
void SkeletonDocument::uiReady()
@ -765,9 +772,17 @@ Mesh *SkeletonDocument::takeResultMesh()
return resultMesh;
}
Mesh *SkeletonDocument::takeResultSkeletonMesh()
{
Mesh *resultSkeletonMesh = m_resultSkeletonMesh;
m_resultSkeletonMesh = nullptr;
return resultSkeletonMesh;
}
void SkeletonDocument::meshReady()
{
Mesh *resultMesh = m_meshGenerator->takeResultMesh();
MeshResultContext *meshResultContext = m_meshGenerator->takeMeshResultContext();
QImage *resultPreview = m_meshGenerator->takePreview();
if (resultPreview) {
@ -787,6 +802,9 @@ void SkeletonDocument::meshReady()
delete m_resultMesh;
m_resultMesh = resultMesh;
delete m_currentMeshResultContext;
m_currentMeshResultContext = meshResultContext;
if (nullptr == m_resultMesh) {
qDebug() << "Result mesh is null";
}
@ -845,6 +863,61 @@ void SkeletonDocument::generateMesh()
thread->start();
}
void SkeletonDocument::generateSkeleton()
{
if (nullptr != m_skeletonGenerator) {
m_resultSkeletonIsObsolete = true;
return;
}
qDebug() << "Skeleton generating..";
m_resultSkeletonIsObsolete = false;
if (!m_currentMeshResultContext) {
qDebug() << "Skeleton is null";
return;
}
QThread *thread = new QThread;
m_skeletonGenerator = new SkeletonGenerator(*m_currentMeshResultContext);
m_skeletonGenerator->moveToThread(thread);
connect(thread, &QThread::started, m_skeletonGenerator, &SkeletonGenerator::process);
connect(m_skeletonGenerator, &SkeletonGenerator::finished, this, &SkeletonDocument::skeletonReady);
connect(m_skeletonGenerator, &SkeletonGenerator::finished, thread, &QThread::quit);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
}
void SkeletonDocument::skeletonReady()
{
Mesh *resultSkeletonMesh = m_skeletonGenerator->takeResultSkeletonMesh();
delete m_resultSkeletonMesh;
m_resultSkeletonMesh = resultSkeletonMesh;
MeshResultContext *resultContext = m_skeletonGenerator->takeResultContext();
delete m_currentSkeletonResultContext;
m_currentSkeletonResultContext = resultContext;
delete m_skeletonGenerator;
m_skeletonGenerator = nullptr;
qDebug() << "Skeleton generation done";
emit resultSkeletonChanged();
if (m_resultSkeletonIsObsolete) {
generateSkeleton();
}
}
MeshResultContext &SkeletonDocument::currentSkeletonResultContext()
{
return *m_currentSkeletonResultContext;
}
void SkeletonDocument::setPartLockState(QUuid partId, bool locked)
{
auto part = partMap.find(partId);

View File

@ -11,6 +11,7 @@
#include "skeletonsnapshot.h"
#include "mesh.h"
#include "meshgenerator.h"
#include "skeletongenerator.h"
#include "theme.h"
class SkeletonNode
@ -184,6 +185,7 @@ signals:
void turnaroundChanged();
void editModeChanged();
void skeletonChanged();
void resultSkeletonChanged();
void partLockStateChanged(QUuid partId);
void partVisibleStateChanged(QUuid partId);
void partSubdivStateChanged(QUuid partId);
@ -229,6 +231,7 @@ public:
const SkeletonPart *findPart(QUuid partId) const;
const SkeletonEdge *findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const;
Mesh *takeResultMesh();
Mesh *takeResultSkeletonMesh();
void updateTurnaround(const QImage &image);
bool hasPastableContentInClipboard() const;
bool undoable() const;
@ -236,6 +239,7 @@ public:
bool isNodeEditable(QUuid nodeId) const;
bool isEdgeEditable(QUuid edgeId) const;
bool originSettled() const;
MeshResultContext &currentSkeletonResultContext();
public slots:
void removeNode(QUuid nodeId);
void removeEdge(QUuid edgeId);
@ -251,6 +255,8 @@ public slots:
void uiReady();
void generateMesh();
void meshReady();
void generateSkeleton();
void skeletonReady();
void setPartLockState(QUuid partId, bool locked);
void setPartVisibleState(QUuid partId, bool visible);
void setPartSubdivState(QUuid partId, bool subdived);
@ -284,6 +290,11 @@ private: // need initialize
MeshGenerator *m_meshGenerator;
Mesh *m_resultMesh;
int m_batchChangeRefCount;
MeshResultContext *m_currentMeshResultContext;
bool m_resultSkeletonIsObsolete;
SkeletonGenerator *m_skeletonGenerator;
Mesh *m_resultSkeletonMesh;
MeshResultContext *m_currentSkeletonResultContext;
private:
static unsigned long m_maxSnapshot;
std::deque<SkeletonHistoryItem> m_undoItems;

View File

@ -25,6 +25,14 @@
#include "util.h"
#include "aboutwidget.h"
#include "version.h"
#include "gltffile.h"
int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16;
int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16;
int SkeletonDocumentWindow::m_modelRenderWidgetInitialSize = 128;
int SkeletonDocumentWindow::m_skeletonRenderWidgetInitialX = SkeletonDocumentWindow::m_modelRenderWidgetInitialX + SkeletonDocumentWindow::m_modelRenderWidgetInitialSize + 16;
int SkeletonDocumentWindow::m_skeletonRenderWidgetInitialY = SkeletonDocumentWindow::m_modelRenderWidgetInitialY;
int SkeletonDocumentWindow::m_skeletonRenderWidgetInitialSize = SkeletonDocumentWindow::m_modelRenderWidgetInitialSize;
QPointer<LogBrowser> g_logBrowser;
std::set<SkeletonDocumentWindow *> g_documentWindows;
@ -66,8 +74,7 @@ void SkeletonDocumentWindow::SkeletonDocumentWindow::showAbout()
SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_document(nullptr),
m_firstShow(true),
m_documentSaved(true),
m_boneExportWidget(nullptr)
m_documentSaved(true)
{
if (!g_logBrowser) {
g_logBrowser = new LogBrowser;
@ -153,11 +160,18 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
containerWidget->setLayout(containerLayout);
containerWidget->setMinimumSize(400, 400);
m_modelWidget = new ModelWidget(containerWidget);
m_modelWidget->setMinimumSize(128, 128);
m_modelWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_modelWidget->move(16, 16);
m_modelWidget->setGraphicsFunctions(graphicsWidget);
m_modelRenderWidget = new ModelWidget(containerWidget);
m_modelRenderWidget->setMinimumSize(SkeletonDocumentWindow::m_modelRenderWidgetInitialSize, SkeletonDocumentWindow::m_modelRenderWidgetInitialSize);
m_modelRenderWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_modelRenderWidget->move(SkeletonDocumentWindow::m_modelRenderWidgetInitialX, SkeletonDocumentWindow::m_modelRenderWidgetInitialY);
m_modelRenderWidget->setGraphicsFunctions(graphicsWidget);
m_skeletonRenderWidget = new ModelWidget(containerWidget);
m_skeletonRenderWidget->setMinimumSize(SkeletonDocumentWindow::m_skeletonRenderWidgetInitialSize, SkeletonDocumentWindow::m_skeletonRenderWidgetInitialSize);
m_skeletonRenderWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_skeletonRenderWidget->move(SkeletonDocumentWindow::m_skeletonRenderWidgetInitialX, SkeletonDocumentWindow::m_skeletonRenderWidgetInitialY);
m_skeletonRenderWidget->setGraphicsFunctions(graphicsWidget);
m_skeletonRenderWidget->hide();
SkeletonPartListWidget *partListWidget = new SkeletonPartListWidget(m_document, this);
partListWidget->setWindowFlags(Qt::Tool);
@ -213,17 +227,17 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_exportMenu = m_fileMenu->addMenu(tr("Export"));
m_exportModelAction = new QAction(tr("Bare Model (.obj)..."), this);
m_exportModelAction = new QAction(tr("Wavefront (.obj)..."), this);
connect(m_exportModelAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportModelResult, Qt::QueuedConnection);
m_exportMenu->addAction(m_exportModelAction);
m_exportModelAndMaterialsAction = new QAction(tr("Model and Materials (.obj)..."), this);
m_exportModelAndMaterialsAction = new QAction(tr("Model and Materials(.obj)..."), this);
connect(m_exportModelAndMaterialsAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportModelAndMaterialResult, Qt::QueuedConnection);
m_exportMenu->addAction(m_exportModelAndMaterialsAction);
//m_exportMenu->addAction(m_exportModelAndMaterialsAction);
m_exportSkeletonAction = new QAction(tr("Skinned Model (.gltf)..."), this);
m_exportSkeletonAction = new QAction(tr("GL Transmission Format (.gltf)..."), this);
connect(m_exportSkeletonAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportSkeletonResult, Qt::QueuedConnection);
//m_exportMenu->addAction(m_exportSkeletonAction);
m_exportMenu->addAction(m_exportSkeletonAction);
m_changeTurnaroundAction = new QAction(tr("Change Turnaround..."), this);
connect(m_changeTurnaroundAction, &QAction::triggered, this, &SkeletonDocumentWindow::changeTurnaround, Qt::QueuedConnection);
@ -320,13 +334,27 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_resetModelWidgetPosAction = new QAction(tr("Show Model"), this);
connect(m_resetModelWidgetPosAction, &QAction::triggered, [=]() {
QRect parentRect = QRect(QPoint(0, 0), m_modelWidget->parentWidget()->size());
if (!parentRect.contains(m_modelWidget->geometry().center())) {
m_modelWidget->move(16, 16);
QRect parentRect = QRect(QPoint(0, 0), m_modelRenderWidget->parentWidget()->size());
if (!parentRect.contains(m_modelRenderWidget->geometry().center())) {
m_modelRenderWidget->move(SkeletonDocumentWindow::m_modelRenderWidgetInitialX, SkeletonDocumentWindow::m_modelRenderWidgetInitialY);
}
});
m_viewMenu->addAction(m_resetModelWidgetPosAction);
m_toggleSkeletonWidgetAction = new QAction(tr("Toggle Bones"), this);
connect(m_toggleSkeletonWidgetAction, &QAction::triggered, [=]() {
if (m_skeletonRenderWidget->isVisible()) {
m_skeletonRenderWidget->hide();
} else {
QRect parentRect = QRect(QPoint(0, 0), m_skeletonRenderWidget->parentWidget()->size());
if (!parentRect.contains(m_skeletonRenderWidget->geometry().center())) {
m_skeletonRenderWidget->move(SkeletonDocumentWindow::m_skeletonRenderWidgetInitialX, SkeletonDocumentWindow::m_skeletonRenderWidgetInitialY);
}
m_skeletonRenderWidget->show();
}
});
m_viewMenu->addAction(m_toggleSkeletonWidgetAction);
m_showPartsListAction = new QAction(tr("Show Parts List"), this);
connect(m_showPartsListAction, &QAction::triggered, [=]() {
partListWidget->show();
@ -335,7 +363,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_toggleWireframeAction = new QAction(tr("Toggle Wireframe"), this);
connect(m_toggleWireframeAction, &QAction::triggered, [=]() {
m_modelWidget->toggleWireframe();
m_modelRenderWidget->toggleWireframe();
});
m_viewMenu->addAction(m_toggleWireframeAction);
@ -474,20 +502,25 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
connect(m_document, &SkeletonDocument::partUnchecked, partListWidget, &SkeletonPartListWidget::partUnchecked);
connect(m_document, &SkeletonDocument::skeletonChanged, m_document, &SkeletonDocument::generateMesh);
connect(m_document, &SkeletonDocument::resultMeshChanged, m_document, &SkeletonDocument::generateSkeleton);
connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() {
m_modelWidget->updateMesh(m_document->takeResultMesh());
m_modelRenderWidget->updateMesh(m_document->takeResultMesh());
});
connect(m_document, &SkeletonDocument::resultSkeletonChanged, [=]() {
m_skeletonRenderWidget->updateMesh(m_document->takeResultSkeletonMesh());
});
connect(graphicsWidget, &SkeletonGraphicsWidget::cursorChanged, [=]() {
m_modelWidget->setCursor(graphicsWidget->cursor());
m_modelRenderWidget->setCursor(graphicsWidget->cursor());
m_skeletonRenderWidget->setCursor(graphicsWidget->cursor());
});
connect(m_document, &SkeletonDocument::skeletonChanged, this, &SkeletonDocumentWindow::documentChanged);
connect(m_document, &SkeletonDocument::turnaroundChanged, this, &SkeletonDocumentWindow::documentChanged);
connect(m_modelWidget, &ModelWidget::customContextMenuRequested, [=](const QPoint &pos) {
graphicsWidget->showContextMenu(graphicsWidget->mapFromGlobal(m_modelWidget->mapToGlobal(pos)));
connect(m_modelRenderWidget, &ModelWidget::customContextMenuRequested, [=](const QPoint &pos) {
graphicsWidget->showContextMenu(graphicsWidget->mapFromGlobal(m_modelRenderWidget->mapToGlobal(pos)));
});
connect(m_document, &SkeletonDocument::xlockStateChanged, this, &SkeletonDocumentWindow::updateXlockButtonState);
@ -759,7 +792,7 @@ void SkeletonDocumentWindow::exportModelResult()
return;
}
QApplication::setOverrideCursor(Qt::WaitCursor);
m_modelWidget->exportMeshAsObj(filename);
m_modelRenderWidget->exportMeshAsObj(filename);
QApplication::restoreOverrideCursor();
}
@ -771,18 +804,22 @@ void SkeletonDocumentWindow::exportModelAndMaterialResult()
return;
}
QApplication::setOverrideCursor(Qt::WaitCursor);
m_modelWidget->exportMeshAsObjPlusMaterials(filename);
m_modelRenderWidget->exportMeshAsObjPlusMaterials(filename);
QApplication::restoreOverrideCursor();
}
void SkeletonDocumentWindow::exportSkeletonResult()
{
if (nullptr == m_boneExportWidget) {
m_boneExportWidget = new BoneExportWidget;
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
tr("glTF (*.gltf)"));
if (filename.isEmpty()) {
return;
}
m_boneExportWidget->show();
m_boneExportWidget->activateWindow();
m_boneExportWidget->raise();
QApplication::setOverrideCursor(Qt::WaitCursor);
MeshResultContext skeletonResult = m_document->currentSkeletonResultContext();
GLTFFileWriter gltfFileWriter(skeletonResult);
gltfFileWriter.save(filename);
QApplication::restoreOverrideCursor();
}
void SkeletonDocumentWindow::updateXlockButtonState()

View File

@ -9,7 +9,6 @@
#include <QTextBrowser>
#include "skeletondocument.h"
#include "modelwidget.h"
#include "boneexportwidget.h"
class SkeletonGraphicsWidget;
@ -58,11 +57,11 @@ private:
SkeletonDocument *m_document;
bool m_firstShow;
bool m_documentSaved;
BoneExportWidget *m_boneExportWidget;
private:
QString m_currentFilename;
ModelWidget *m_modelWidget;
ModelWidget *m_modelRenderWidget;
ModelWidget *m_skeletonRenderWidget;
SkeletonGraphicsWidget *m_graphicsWidget;
QMenu *m_fileMenu;
@ -98,6 +97,7 @@ private:
QMenu *m_viewMenu;
QAction *m_resetModelWidgetPosAction;
QAction *m_toggleSkeletonWidgetAction;
QAction *m_showPartsListAction;
QAction *m_showDebugDialogAction;
QAction *m_toggleWireframeAction;
@ -112,6 +112,14 @@ private:
QPushButton *m_xlockButton;
QPushButton *m_ylockButton;
QPushButton *m_zlockButton;
public:
static int m_modelRenderWidgetInitialX;
static int m_modelRenderWidgetInitialY;
static int m_modelRenderWidgetInitialSize;
static int m_skeletonRenderWidgetInitialX;
static int m_skeletonRenderWidgetInitialY;
static int m_skeletonRenderWidgetInitialSize;
};
#endif

95
src/skeletongenerator.cpp Normal file
View File

@ -0,0 +1,95 @@
#include <set>
#include <QGuiApplication>
#include <QDebug>
#include <vector>
#include "skeletongenerator.h"
#include "positionmap.h"
#include "meshlite.h"
SkeletonGenerator::SkeletonGenerator(const MeshResultContext &meshResultContext) :
m_resultSkeletonMesh(nullptr)
{
m_meshResultContext = new MeshResultContext;
*m_meshResultContext = meshResultContext;
}
SkeletonGenerator::~SkeletonGenerator()
{
delete m_resultSkeletonMesh;
delete m_meshResultContext;
}
Mesh *SkeletonGenerator::takeResultSkeletonMesh()
{
Mesh *resultSkeletonMesh = m_resultSkeletonMesh;
m_resultSkeletonMesh = nullptr;
return resultSkeletonMesh;
}
MeshResultContext *SkeletonGenerator::takeResultContext()
{
MeshResultContext *resultContext = m_meshResultContext;
m_meshResultContext = nullptr;
return resultContext;
}
Mesh *SkeletonGenerator::createSkeletonMesh()
{
void *meshliteContext = meshlite_create_context();
int sklt = meshlite_skeletonmesh_create(meshliteContext);
std::map<std::pair<int, int>, BmeshNode *> nodeIndexMap;
for (auto i = 0u; i < m_meshResultContext->bmeshNodes.size(); i++) {
BmeshNode *bmeshNode = &m_meshResultContext->bmeshNodes[i];
nodeIndexMap[std::make_pair(bmeshNode->bmeshId, bmeshNode->nodeId)] = bmeshNode;
}
for (const auto &it: m_meshResultContext->bmeshEdges) {
const auto &from = nodeIndexMap.find(std::make_pair(it.fromBmeshId, it.fromNodeId));
const auto &to = nodeIndexMap.find(std::make_pair(it.toBmeshId, it.toNodeId));
if (from == nodeIndexMap.end()) {
qDebug() << "fromNodeId not found in nodeIndexMap:" << it.fromBmeshId << it.fromNodeId;
continue;
}
if (to == nodeIndexMap.end()) {
qDebug() << "toNodeId not found in nodeIndexMap:" << it.toBmeshId << it.toNodeId;
continue;
}
BmeshNode *fromNode = from->second;
BmeshNode *toNode = to->second;
meshlite_skeletonmesh_add_bone(meshliteContext, sklt,
fromNode->origin.x(), fromNode->origin.y(), fromNode->origin.z(),
toNode->origin.x(), toNode->origin.y(), toNode->origin.z());
}
int meshId = meshlite_skeletonmesh_generate_mesh(meshliteContext, sklt);
Mesh *skeletonMesh = new Mesh(meshliteContext, meshId, -1, Theme::green);
meshlite_destroy_context(meshliteContext);
return skeletonMesh;
}
struct BmeshNodeDistWithWorldCenter
{
BmeshNode *bmeshNode;
float dist2;
};
void SkeletonGenerator::combineAllBmeshSkeletons()
{
m_meshResultContext->resolveBmeshConnectivity();
m_meshResultContext->resolveBmeshEdgeDirections();
m_meshResultContext->resultParts();
}
void SkeletonGenerator::process()
{
if (!m_meshResultContext->bmeshNodes.empty())
combineAllBmeshSkeletons();
m_resultSkeletonMesh = createSkeletonMesh();
this->moveToThread(QGuiApplication::instance()->thread());
emit finished();
}

29
src/skeletongenerator.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef SKELETON_GENERATOR_H
#define SKELETON_GENERATOR_H
#include <QObject>
#include "meshresultcontext.h"
#include "mesh.h"
// https://raw.githubusercontent.com/KhronosGroup/glTF/master/specification/2.0/figures/gltfOverview-2.0.0a.png
class SkeletonGenerator : public QObject
{
Q_OBJECT
public:
SkeletonGenerator(const MeshResultContext &meshResultContext);
~SkeletonGenerator();
Mesh *takeResultSkeletonMesh();
MeshResultContext *takeResultContext();
signals:
void finished();
public slots:
void process();
private:
void combineAllBmeshSkeletons();
Mesh *createSkeletonMesh();
private:
MeshResultContext *m_meshResultContext;
Mesh *m_resultSkeletonMesh;
};
#endif

View File

@ -3,6 +3,7 @@
#include <QString>
#include <map>
#include <cmath>
#include <QVector3D>
#ifndef M_PI
#define M_PI 3.14159265358979323846
@ -13,5 +14,4 @@ bool isTrueValueString(const QString &str);
bool isFloatEqual(float a, float b);
void qNormalizeAngle(int &angle);
#endif

View File

@ -1,29 +0,0 @@
#include <set>
#include "vertexskingenerator.h"
#include "positionmap.h"
float VertexSkinGenerator::m_positionMapGridSize = 0.01;
VertexSkinGenerator::VertexSkinGenerator(MeshResultContext *meshResultContext) :
m_meshResultContext(meshResultContext)
{
}
void VertexSkinGenerator::process()
{
/*
PositionMap resultPositionMap(m_positionMapGridSize);
for (auto i = 0u; i < m_meshResultContext->resultVertices.size(); i++) {
ResultVertex *resultVertex = &m_meshResultContext->resultVertices[i];
resultPositionMap.addPosition(resultVertex->position.x(), resultVertex->position.y(), resultVertex->position.z(), i);
}
*/
/*
std::set<std::pair<int, int>> validNodes;
for (const auto &it: m_meshResultContext->bmeshVertices) {
if (!resultPositionMap.find(it.position.x(), it.position.y(), it.position.z()))
continue;
validNodes.insert(std::make_pair(it.bmeshId, it.nodeId));
}*/
}

View File

@ -1,21 +0,0 @@
#ifndef VERTEX_SKIN_GENERATOR_H
#define VERTEX_SKIN_GENERATOR_H
#include "meshresultcontext.h"
// https://raw.githubusercontent.com/KhronosGroup/glTF/master/specification/2.0/figures/gltfOverview-2.0.0a.png
class VertexSkinGenerator
{
public:
VertexSkinGenerator(MeshResultContext *meshResultContext);
signals:
void finished();
public slots:
void process();
private:
MeshResultContext *m_meshResultContext;
public:
static float m_positionMapGridSize;
};
#endif

21
thirdparty/json/LICENSE.MIT vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2013-2018 Niels Lohmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

17330
thirdparty/json/json.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -52,6 +52,10 @@ int meshlite_trim(void *context, int mesh_id, int normalize);
int meshlite_mirror_in_x(void *context, int mesh_id, float center_x);
int meshlite_mirror_in_z(void *context, int mesh_id, float center_z);
int meshlite_fix_hole(void *context, int mesh_id);
int meshlite_skeletonmesh_create(void *context);
int meshlite_skeletonmesh_set_end_radius(void *context, float radius);
int meshlite_skeletonmesh_add_bone(void *context, int sklt_id, float from_x, float from_y, float from_z, float to_x, float to_y, float to_z);
int meshlite_skeletonmesh_generate_mesh(void *context, int sklt_id);
#ifdef __cplusplus
}

View File

@ -52,6 +52,10 @@ int meshlite_trim(void *context, int mesh_id, int normalize);
int meshlite_mirror_in_x(void *context, int mesh_id, float center_x);
int meshlite_mirror_in_z(void *context, int mesh_id, float center_z);
int meshlite_fix_hole(void *context, int mesh_id);
int meshlite_skeletonmesh_create(void *context);
int meshlite_skeletonmesh_set_end_radius(void *context, float radius);
int meshlite_skeletonmesh_add_bone(void *context, int sklt_id, float from_x, float from_y, float from_z, float to_x, float to_y, float to_z);
int meshlite_skeletonmesh_generate_mesh(void *context, int sklt_id);
#ifdef __cplusplus
}