diff --git a/ACKNOWLEDGEMENTS.html b/ACKNOWLEDGEMENTS.html
index c5873c9a..7e36530a 100644
--- a/ACKNOWLEDGEMENTS.html
+++ b/ACKNOWLEDGEMENTS.html
@@ -421,3 +421,28 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode
https://rosettacode.org/wiki/Catmull%E2%80%93Clark_subdivision_surface/C
+
+JSON for Modern C++
+
+ 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.
+
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
new file mode 100644
index 00000000..9907149b
--- /dev/null
+++ b/CONTRIBUTORS
@@ -0,0 +1,11 @@
+# Contributors ordered by first contribution.
+Alistair Miles
+hdu
+Ian Diaz
+Skullfurious
+Game From Scratch
+RSDuck
+xtvjxk123456
+Zireael07
+glasyalabolas
+David Patrick
diff --git a/README.md b/README.md
index 1053898e..dd7243b5 100644
--- a/README.md
+++ b/README.md
@@ -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:
-Tutorials:
+Tutorials
-------------
[Dust3D -- Free Open Source 3D Modelling Application](https://www.youtube.com/watch?v=YBnEQk_5D70)
+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 don’t 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.
diff --git a/docs/interface/menubar.rst b/docs/interface/menubar.rst
index 476a461a..aa9a948d 100644
--- a/docs/interface/menubar.rst
+++ b/docs/interface/menubar.rst
@@ -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.
-
-
diff --git a/dust3d.pro b/dust3d.pro
index 30bb7b6a..86aa3a80 100644
--- a/dust3d.pro
+++ b/dust3d.pro
@@ -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
diff --git a/src/boneexportwidget.cpp b/src/boneexportwidget.cpp
deleted file mode 100644
index 2ac4a0c7..00000000
--- a/src/boneexportwidget.cpp
+++ /dev/null
@@ -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);
-}
diff --git a/src/boneexportwidget.h b/src/boneexportwidget.h
deleted file mode 100644
index 6d111108..00000000
--- a/src/boneexportwidget.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#ifndef BONE_EXPORT_WIDGET_H
-#define BONE_EXPORT_WIDGET_H
-#include
-#include "modelwidget.h"
-
-class BoneExportWidget : public QWidget
-{
- Q_OBJECT
-public:
- BoneExportWidget();
-private:
- ModelWidget *m_boneModelWidget;
-};
-
-#endif
diff --git a/src/gltffile.cpp b/src/gltffile.cpp
new file mode 100644
index 00000000..3a858505
--- /dev/null
+++ b/src/gltffile.cpp
@@ -0,0 +1,314 @@
+#include
+#include
+#include
+#include
+#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> visitedNodes;
+ std::set, std::pair>> 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 node, std::set> &visitedNodes, std::set, std::pair>> &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;
+}
diff --git a/src/gltffile.h b/src/gltffile.h
new file mode 100644
index 00000000..cfa6fd2c
--- /dev/null
+++ b/src/gltffile.h
@@ -0,0 +1,40 @@
+#ifndef GLTF_FILE_H
+#define GLTF_FILE_H
+#include
+#include
+#include
+#include
+#include
+#include "meshresultcontext.h"
+#include "json.hpp"
+
+struct JointInfo
+{
+ int jointIndex;
+ QVector3D position;
+ QVector3D direction;
+ QVector3D translation;
+ QQuaternion rotation;
+ QMatrix4x4 worldMatrix;
+ QMatrix4x4 inverseBindMatrix;
+ std::vector 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 node, std::set> &visitedNodes, std::set, std::pair>> &connections, int parentIndex);
+private:
+ QByteArray m_data;
+ std::vector m_tracedJoints;
+ std::map, int> m_tracedNodeToJointIndexMap;
+ QString getMatrixStringInColumnOrder(const QMatrix4x4 &mat);
+private:
+ nlohmann::json m_json;
+};
+
+#endif
diff --git a/src/mesh.cpp b/src/mesh.cpp
index 591b3e24..43ab79c4 100644
--- a/src/mesh.cpp
+++ b/src/mesh.cpp
@@ -6,7 +6,7 @@
#define MAX_VERTICES_PER_FACE 100
-Mesh::Mesh(void *meshlite, int meshId, int triangulatedMeshId, QColor modelColor, std::vector *triangleColors) :
+Mesh::Mesh(void *meshlite, int meshId, int triangulatedMeshId, QColor modelColor, const std::vector *triangleColors) :
m_triangleVertices(NULL),
m_triangleVertexCount(0),
m_edgeVertices(NULL),
diff --git a/src/mesh.h b/src/mesh.h
index dcac5d39..1a48b4a2 100644
--- a/src/mesh.h
+++ b/src/mesh.h
@@ -33,7 +33,7 @@ struct TriangulatedFace
class Mesh
{
public:
- Mesh(void *meshlite, int meshId, int triangulatedMeshId = -1, QColor modelColor=Theme::white, std::vector *triangleColors=nullptr);
+ Mesh(void *meshlite, int meshId, int triangulatedMeshId = -1, QColor modelColor=Theme::white, const std::vector *triangleColors=nullptr);
~Mesh();
Vertex *triangleVertices();
int triangleVertexCount();
diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp
index 9109da9a..426ca66f 100644
--- a/src/meshgenerator.cpp
+++ b/src/meshgenerator.cpp
@@ -180,6 +180,7 @@ void MeshGenerator::process()
}
std::map partColorMap;
+ std::map 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 positionColorMap;
//m_meshResultContext->calculatePositionColorMap(positionColorMap);
- std::vector 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) {
diff --git a/src/meshresultcontext.cpp b/src/meshresultcontext.cpp
index 150ecfe4..6655905f 100644
--- a/src/meshresultcontext.cpp
+++ b/src/meshresultcontext.cpp
@@ -8,65 +8,129 @@
struct HalfColorEdge
{
int cornVertexIndex;
- QColor color;
+ std::pair source;
};
struct CandidateEdge
{
- QColor color;
+ std::pair source;
int fromVertexIndex;
int toVertexIndex;
float dot;
float length;
};
-void MeshResultContext::calculateTriangleColors(std::vector &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 positionColorMap;
- std::map, QColor> nodeColorMap;
- for (const auto &it: bmeshNodes) {
- nodeColorMap[std::make_pair(it.bmeshId, it.nodeId)] = it.color;
+}
+
+const std::vector> &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 &MeshResultContext::triangleColors()
+{
+ if (!m_triangleColorResolved) {
+ calculateTriangleColors(m_triangleColors);
+ m_triangleColorResolved = true;
}
+ return m_triangleColors;
+}
+
+const std::map, std::pair> &MeshResultContext::triangleEdgeSourceMap()
+{
+ if (!m_triangleEdgeSourceMapResolved) {
+ calculateTriangleEdgeSourceMap(m_triangleEdgeSourceMap);
+ m_triangleEdgeSourceMapResolved = true;
+ }
+ return m_triangleEdgeSourceMap;
+}
+
+const std::map, 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> &triangleSourceNodes)
+{
+ PositionMap> positionMap;
std::map, HalfColorEdge> halfColorEdgeMap;
std::set 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> colorTypes;
+ std::vector, 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 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 &a, const std::pair &b) -> bool {
+ std::sort(colorTypes.begin(), colorTypes.end(), [](const std::pair, int> &a, const std::pair, int> &b) -> bool {
return a.second > b.second;
});
}
- QColor choosenColor = colorTypes[0].first;
- triangleColors.push_back(choosenColor);
+ std::pair 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 &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 &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 &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 &triangleCol
}
}
}
+
+void MeshResultContext::calculateTriangleColors(std::vector &triangleColors)
+{
+ PositionMap positionColorMap;
+ std::map, 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> edgeSourceMap = triangleEdgeSourceMap();
+ std::set> processedSet;
+ qDebug() << "start calculateBmeshConnectivity";
+ std::vector 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> 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> &triangleEdgeSourceMap)
+{
+ const std::vector> 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, 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 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::vector>> &MeshResultContext::nodeNeighbors()
+{
+ if (!m_bmeshNodeNeighborsResolved) {
+ calculateBmeshNodeNeighbors(m_nodeNeighbors);
+ m_bmeshNodeNeighborsResolved = true;
+ }
+ return m_nodeNeighbors;
+}
+
+void MeshResultContext::calculateBmeshNodeNeighbors(std::map, std::vector>> &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 node, std::set> &visitedNodes, std::set, std::pair>> &connections, std::vector &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> visitedNodes;
+ std::vector rearrangedEdges;
+ std::set, std::pair>> connections;
+ calculateBmeshEdgeDirectionsFromNode(std::make_pair(rootNode->bmeshId, rootNode->nodeId), visitedNodes, connections, rearrangedEdges);
+ bmeshEdges = rearrangedEdges;
+}
+
+const std::vector> &MeshResultContext::resultVertexWeights()
+{
+ if (!m_vertexWeightResolved) {
+ calculateVertexWeights(m_resultVertexWeights);
+ m_vertexWeightResolved = true;
+ }
+ return m_resultVertexWeights;
+}
+
+void MeshResultContext::calculateVertexWeights(std::vector> &vertexWeights)
+{
+ vertexWeights.clear();
+ vertexWeights.resize(resultVertices.size());
+ for (auto i = 0u; i < resultTriangles.size(); i++) {
+ std::pair 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 &MeshResultContext::resultParts()
+{
+ if (!m_resultPartsResolved) {
+ calculateResultParts(m_resultParts);
+ m_resultPartsResolved = true;
+ }
+ return m_resultParts;
+}
+
+void MeshResultContext::calculateResultParts(std::map &parts)
+{
+ std::map, 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);
+ }
+}
diff --git a/src/meshresultcontext.h b/src/meshresultcontext.h
index bbd77dbd..58a04444 100644
--- a/src/meshresultcontext.h
+++ b/src/meshresultcontext.h
@@ -1,11 +1,14 @@
#ifndef MESH_RESULT_CONTEXT_H
#define MESH_RESULT_CONTEXT_H
#include
+#include
#include
#include
#include
#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 sourceNode;
+ int count;
+ float weight;
+};
+
+struct ResultPart
+{
+ QColor color;
+ std::vector vertices;
+ std::vector> weights;
+ std::vector triangles;
+};
+
class MeshResultContext
{
public:
@@ -48,8 +67,51 @@ public:
std::vector bmeshEdges;
std::vector resultVertices;
std::vector resultTriangles;
+ MeshResultContext();
public:
+ const std::vector> &triangleSourceNodes();
+ const std::vector &triangleColors();
+ const std::map, std::pair> &triangleEdgeSourceMap();
+ const std::map, BmeshNode *> &bmeshNodeMap();
+ const BmeshNode *centerBmeshNode();
+ void resolveBmeshConnectivity();
+ void resolveBmeshEdgeDirections();
+ const std::map, std::vector>> &nodeNeighbors();
+ const std::vector> &resultVertexWeights();
+ const std::map &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> m_triangleSourceNodes;
+ std::vector m_triangleColors;
+ std::map, std::pair> m_triangleEdgeSourceMap;
+ std::map, BmeshNode *> m_bmeshNodeMap;
+ std::map, std::vector>> m_nodeNeighbors;
+ std::vector> m_resultVertexWeights;
+ std::map m_resultParts;
+private:
+ void calculateTriangleSourceNodes(std::vector> &triangleSourceNodes);
void calculateTriangleColors(std::vector &triangleColors);
+ void calculateTriangleEdgeSourceMap(std::map, std::pair> &triangleEdgeSourceMap);
+ void calculateBmeshNodeMap(std::map, BmeshNode *> &bmeshNodeMap);
+ BmeshNode *calculateCenterBmeshNode();
+ void calculateBmeshConnectivity();
+ void calculateBmeshEdgeDirections();
+ void calculateBmeshNodeNeighbors();
+ void calculateBmeshEdgeDirectionsFromNode(std::pair node, std::set> &visitedNodes, std::set, std::pair>> &connections, std::vector &rearrangedEdges);
+ void calculateBmeshNodeNeighbors(std::map, std::vector>> &nodeNeighbors);
+ void calculateVertexWeights(std::vector> &vertexWeights);
+ void calculateResultParts(std::map &parts);
};
#endif
diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp
index db3487ce..5730a351 100644
--- a/src/skeletondocument.cpp
+++ b/src/skeletondocument.cpp
@@ -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);
diff --git a/src/skeletondocument.h b/src/skeletondocument.h
index 5fff07d6..ae084822 100644
--- a/src/skeletondocument.h
+++ b/src/skeletondocument.h
@@ -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 ¤tSkeletonResultContext();
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 m_undoItems;
diff --git a/src/skeletondocumentwindow.cpp b/src/skeletondocumentwindow.cpp
index dab527ab..74797476 100644
--- a/src/skeletondocumentwindow.cpp
+++ b/src/skeletondocumentwindow.cpp
@@ -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 g_logBrowser;
std::set 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()
diff --git a/src/skeletondocumentwindow.h b/src/skeletondocumentwindow.h
index 5f634739..a394fcd1 100644
--- a/src/skeletondocumentwindow.h
+++ b/src/skeletondocumentwindow.h
@@ -9,7 +9,6 @@
#include
#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
diff --git a/src/skeletongenerator.cpp b/src/skeletongenerator.cpp
new file mode 100644
index 00000000..c24d5ac0
--- /dev/null
+++ b/src/skeletongenerator.cpp
@@ -0,0 +1,95 @@
+#include
+#include
+#include
+#include
+#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, 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();
+}
diff --git a/src/skeletongenerator.h b/src/skeletongenerator.h
new file mode 100644
index 00000000..d20e9885
--- /dev/null
+++ b/src/skeletongenerator.h
@@ -0,0 +1,29 @@
+#ifndef SKELETON_GENERATOR_H
+#define SKELETON_GENERATOR_H
+#include
+#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
diff --git a/src/util.h b/src/util.h
index f7e0dde1..1c871ec9 100644
--- a/src/util.h
+++ b/src/util.h
@@ -3,6 +3,7 @@
#include
#include