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
parent
331051879d
commit
3fef6c4463
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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 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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
||||
|
|
12
dust3d.pro
12
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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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),
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<SkeletonHistoryItem> m_undoItems;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}*/
|
||||
}
|
|
@ -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
|
|
@ -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.
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue