From 22373b1ed7895c0b6871b872bade3834704a4702 Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Sat, 14 Dec 2019 22:58:14 +0930 Subject: [PATCH] Implement grid mesh builder --- ACKNOWLEDGEMENTS.html | 16 +- dust3d.pro | 73 +- languages/dust3d_zh_CN.ts | 12 +- resources/model-mosquito.ds3 | 205 +-- src/booleanmesh.cpp | 1 + .../nodemesh/cgalmesh.h => src/booleanmesh.h | 39 +- src/boxmesh.cpp | 125 ++ src/boxmesh.h | 8 + src/cyclefinder.cpp | 206 +++ src/cyclefinder.h | 34 + src/document.cpp | 250 ++- src/document.h | 9 +- src/documentwindow.cpp | 27 +- src/documentwindow.h | 6 + src/gridmeshbuilder.cpp | 444 +++++ src/gridmeshbuilder.h | 62 + src/logbrowser.cpp | 4 +- .../combiner.cpp => src/meshcombiner.cpp | 28 +- .../nodemesh/combiner.h => src/meshcombiner.h | 13 +- src/meshgenerator.cpp | 431 +++-- src/meshgenerator.h | 34 +- .../recombiner.cpp => src/meshrecombiner.cpp | 59 +- .../recombiner.h => src/meshrecombiner.h | 21 +- .../stitcher.cpp => src/meshstitcher.cpp | 23 +- .../nodemesh/stitcher.h => src/meshstitcher.h | 18 +- .../wrapper.cpp => src/meshwrapper.cpp | 56 +- .../nodemesh/wrapper.h => src/meshwrapper.h | 11 +- src/modelwidget.cpp | 10 +- src/outcome.h | 1 + src/partwidget.cpp | 1 - .../nodemesh/nodemesh => src}/positionkey.cpp | 7 +- .../nodemesh/nodemesh => src}/positionkey.h | 9 +- src/preferences.cpp | 23 +- src/preferences.h | 4 - src/preferenceswidget.cpp | 8 - src/regionfiller.cpp | 1496 +++++++++++++++++ src/regionfiller.h | 74 + src/shortestpath.cpp | 65 + src/shortestpath.h | 46 + src/skeletondocument.cpp | 7 +- src/skeletondocument.h | 5 +- src/skeletongraphicswidget.cpp | 18 + src/skeletongraphicswidget.h | 2 + .../builder.cpp => src/strokemeshbuilder.cpp | 129 +- .../builder.h => src/strokemeshbuilder.h | 12 +- .../modifier.cpp => src/strokemodifier.cpp | 30 +- .../modifier.h => src/strokemodifier.h | 11 +- src/texturegenerator.cpp | 3 +- src/triangleislandslink.cpp | 2 +- src/trianglesourcenoderesolve.cpp | 17 +- src/trianglesourcenoderesolve.h | 5 +- src/triangulate.cpp | 86 + src/triangulate.h | 8 + src/util.cpp | 360 ++++ src/util.h | 18 + thirdparty/nodemesh/.gitignore | 32 - thirdparty/nodemesh/LICENSE | 21 - thirdparty/nodemesh/README.md | 2 - thirdparty/nodemesh/nodemesh/box.cpp | 74 - thirdparty/nodemesh/nodemesh/box.h | 13 - thirdparty/nodemesh/nodemesh/cgalmesh.cpp | 2 - thirdparty/nodemesh/nodemesh/misc.cpp | 554 ------ thirdparty/nodemesh/nodemesh/misc.h | 42 - 63 files changed, 4042 insertions(+), 1370 deletions(-) create mode 100644 src/booleanmesh.cpp rename thirdparty/nodemesh/nodemesh/cgalmesh.h => src/booleanmesh.h (82%) create mode 100644 src/boxmesh.cpp create mode 100644 src/boxmesh.h create mode 100644 src/cyclefinder.cpp create mode 100644 src/cyclefinder.h create mode 100644 src/gridmeshbuilder.cpp create mode 100644 src/gridmeshbuilder.h rename thirdparty/nodemesh/nodemesh/combiner.cpp => src/meshcombiner.cpp (89%) rename thirdparty/nodemesh/nodemesh/combiner.h => src/meshcombiner.h (88%) rename thirdparty/nodemesh/nodemesh/recombiner.cpp => src/meshrecombiner.cpp (87%) rename thirdparty/nodemesh/nodemesh/recombiner.h => src/meshrecombiner.h (76%) rename thirdparty/nodemesh/nodemesh/stitcher.cpp => src/meshstitcher.cpp (79%) rename thirdparty/nodemesh/nodemesh/stitcher.h => src/meshstitcher.h (76%) rename thirdparty/nodemesh/nodemesh/wrapper.cpp => src/meshwrapper.cpp (84%) rename thirdparty/nodemesh/nodemesh/wrapper.h => src/meshwrapper.h (96%) rename {thirdparty/nodemesh/nodemesh => src}/positionkey.cpp (94%) rename {thirdparty/nodemesh/nodemesh => src}/positionkey.h (82%) create mode 100644 src/regionfiller.cpp create mode 100644 src/regionfiller.h create mode 100644 src/shortestpath.cpp create mode 100644 src/shortestpath.h rename thirdparty/nodemesh/nodemesh/builder.cpp => src/strokemeshbuilder.cpp (90%) rename thirdparty/nodemesh/nodemesh/builder.h => src/strokemeshbuilder.h (98%) rename thirdparty/nodemesh/nodemesh/modifier.cpp => src/strokemodifier.cpp (87%) rename thirdparty/nodemesh/nodemesh/modifier.h => src/strokemodifier.h (93%) create mode 100644 src/triangulate.cpp create mode 100644 src/triangulate.h delete mode 100644 thirdparty/nodemesh/.gitignore delete mode 100644 thirdparty/nodemesh/LICENSE delete mode 100644 thirdparty/nodemesh/README.md delete mode 100644 thirdparty/nodemesh/nodemesh/box.cpp delete mode 100644 thirdparty/nodemesh/nodemesh/box.h delete mode 100644 thirdparty/nodemesh/nodemesh/cgalmesh.cpp delete mode 100644 thirdparty/nodemesh/nodemesh/misc.cpp delete mode 100644 thirdparty/nodemesh/nodemesh/misc.h diff --git a/ACKNOWLEDGEMENTS.html b/ACKNOWLEDGEMENTS.html index 7baa1cc3..563696b3 100644 --- a/ACKNOWLEDGEMENTS.html +++ b/ACKNOWLEDGEMENTS.html @@ -1139,11 +1139,6 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode https://en.wikipedia.org/wiki/Eadweard_Muybridge -

nodemesh

-
-    https://github.com/huxingyi/nodemesh
-
-

QuickJS

     #
@@ -1212,4 +1207,15 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode
     1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
     2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
     3. This notice may not be removed or altered from any source distribution.
+
+ +

A. Nasri1∗, M. Sabin2 and Z. Yasseen1

+
+    Filling N-Sided Regions by Quad Meshes for Subdivision Surfaces
+    https://doi.org/10.1111/j.1467-8659.2009.01417.x
+
+ +

Coons patch

+
+    https://en.wikipedia.org/wiki/Coons_patch
 
\ No newline at end of file diff --git a/dust3d.pro b/dust3d.pro index f8ef30ce..da6da3e9 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -435,6 +435,48 @@ HEADERS += src/triangleislandsresolve.h SOURCES += src/triangleislandslink.cpp HEADERS += src/triangleislandslink.h +SOURCES += src/gridmeshbuilder.cpp +HEADERS += src/gridmeshbuilder.h + +SOURCES += src/regionfiller.cpp +HEADERS += src/regionfiller.h + +SOURCES += src/cyclefinder.cpp +HEADERS += src/cyclefinder.h + +SOURCES += src/shortestpath.cpp +HEADERS += src/shortestpath.h + +SOURCES += src/meshwrapper.cpp +HEADERS += src/meshwrapper.h + +SOURCES += src/meshstitcher.cpp +HEADERS += src/meshstitcher.h + +SOURCES += src/strokemeshbuilder.cpp +HEADERS += src/strokemeshbuilder.h + +SOURCES += src/meshcombiner.cpp +HEADERS += src/meshcombiner.h + +SOURCES += src/positionkey.cpp +HEADERS += src/positionkey.h + +SOURCES += src/strokemodifier.cpp +HEADERS += src/strokemodifier.h + +SOURCES += src/boxmesh.cpp +HEADERS += src/boxmesh.h + +SOURCES += src/meshrecombiner.cpp +HEADERS += src/meshrecombiner.h + +SOURCES += src/triangulate.cpp +HEADERS += src/triangulate.h + +SOURCES += src/booleanmesh.cpp +HEADERS += src/booleanmesh.h + SOURCES += src/main.cpp HEADERS += src/version.h @@ -666,37 +708,6 @@ HEADERS += thirdparty/quickjs/quickjs-2019-07-09-dust3d/libunicode.h SOURCES += thirdparty/quickjs/quickjs-2019-07-09-dust3d/libregexp.c HEADERS += thirdparty/quickjs/quickjs-2019-07-09-dust3d/libregexp.h -INCLUDEPATH += thirdparty/nodemesh - -SOURCES += thirdparty/nodemesh/nodemesh/wrapper.cpp -HEADERS += thirdparty/nodemesh/nodemesh/wrapper.h - -SOURCES += thirdparty/nodemesh/nodemesh/stitcher.cpp -HEADERS += thirdparty/nodemesh/nodemesh/stitcher.h - -SOURCES += thirdparty/nodemesh/nodemesh/builder.cpp -HEADERS += thirdparty/nodemesh/nodemesh/builder.h - -SOURCES += thirdparty/nodemesh/nodemesh/combiner.cpp -HEADERS += thirdparty/nodemesh/nodemesh/combiner.h - -SOURCES += thirdparty/nodemesh/nodemesh/misc.cpp -HEADERS += thirdparty/nodemesh/nodemesh/misc.h - -SOURCES += thirdparty/nodemesh/nodemesh/positionkey.cpp -HEADERS += thirdparty/nodemesh/nodemesh/positionkey.h - -SOURCES += thirdparty/nodemesh/nodemesh/modifier.cpp -HEADERS += thirdparty/nodemesh/nodemesh/modifier.h - -SOURCES += thirdparty/nodemesh/nodemesh/box.cpp -HEADERS += thirdparty/nodemesh/nodemesh/box.h - -SOURCES += thirdparty/nodemesh/nodemesh/recombiner.cpp -HEADERS += thirdparty/nodemesh/nodemesh/recombiner.h - -HEADERS += thirdparty/nodemesh/nodemesh/cgalmesh.h - INCLUDEPATH += thirdparty/crc64 SOURCES += thirdparty/crc64/crc64.c diff --git a/languages/dust3d_zh_CN.ts b/languages/dust3d_zh_CN.ts index 31c7fba5..050df67c 100644 --- a/languages/dust3d_zh_CN.ts +++ b/languages/dust3d_zh_CN.ts @@ -382,6 +382,10 @@ Tips: Auto Color 自动着色 + + Create Wrap Parts + 创建包裹部件 + ExportPreviewWidget @@ -923,10 +927,6 @@ Tips: Flat shading: 未平滑: - - Three nodes branch: - 三结点分枝: - Reset 重置 @@ -1195,6 +1195,10 @@ Tips: Colorize 着色 + + Create Wrap Parts + 创建包裹部件 + UpdatesCheckWidget diff --git a/resources/model-mosquito.ds3 b/resources/model-mosquito.ds3 index 8f7dbf72..66975273 100644 --- a/resources/model-mosquito.ds3 +++ b/resources/model-mosquito.ds3 @@ -1,117 +1,120 @@ DUST3D 1.0 xml 0000000193 - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + - - - - - + + + + + + diff --git a/src/booleanmesh.cpp b/src/booleanmesh.cpp new file mode 100644 index 00000000..dbe0a697 --- /dev/null +++ b/src/booleanmesh.cpp @@ -0,0 +1 @@ +#include "booleanmesh.h" diff --git a/thirdparty/nodemesh/nodemesh/cgalmesh.h b/src/booleanmesh.h similarity index 82% rename from thirdparty/nodemesh/nodemesh/cgalmesh.h rename to src/booleanmesh.h index 7a4d555a..ef699d0a 100644 --- a/thirdparty/nodemesh/nodemesh/cgalmesh.h +++ b/src/booleanmesh.h @@ -1,34 +1,50 @@ -#ifndef NODEMESH_CGAL_MESH_H -#define NODEMESH_CGAL_MESH_H +#ifndef DUST3D_CGAL_MESH_H +#define DUST3D_CGAL_MESH_H #include #include #include #include #include -#include -#include +#include "positionkey.h" typedef CGAL::Exact_predicates_inexact_constructions_kernel CgalKernel; typedef CGAL::Surface_mesh CgalMesh; +inline bool validatePosition(const QVector3D &position) +{ + if (std::isnan(position.x())) + return false; + if (std::isnan(position.y())) + return false; + if (std::isnan(position.z())) + return false; + if (std::isinf(position.x())) + return false; + if (std::isinf(position.y())) + return false; + if (std::isinf(position.z())) + return false; + return true; +} + template typename CGAL::Surface_mesh *buildCgalMesh(const std::vector &positions, const std::vector> &indices) { typename CGAL::Surface_mesh *mesh = new typename CGAL::Surface_mesh; - std::map::Vertex_index> vertexIndices; + std::map::Vertex_index> vertexIndices; for (const auto &face: indices) { std::vector::Vertex_index> faceVertexIndices; bool faceValid = true; - std::vector positionKeys; + std::vector positionKeys; std::vector positionsInKeys; - std::set existedKeys; + std::set existedKeys; for (const auto &index: face) { const auto &position = positions[index]; - if (!nodemesh::validatePosition(position)) { + if (!validatePosition(position)) { faceValid = false; break; } - auto positionKey = nodemesh::PositionKey(position); + auto positionKey = PositionKey(position); if (existedKeys.find(positionKey) != existedKeys.end()) { continue; } @@ -69,7 +85,7 @@ void fetchFromCgalMesh(typename CGAL::Surface_mesh *me vertexIndicesMap[*vertexIt] = vertices.size(); vertices.push_back(QVector3D(x, y, z)); } - + typename CGAL::Surface_mesh::Face_range faceRage = mesh->faces(); typename CGAL::Surface_mesh::Face_range::iterator faceIt; for (faceIt = faceRage.begin(); faceIt != faceRage.end(); faceIt++) { @@ -91,5 +107,4 @@ bool isNullCgalMesh(typename CGAL::Surface_mesh *mesh) return faceRage.begin() == faceRage.end(); } -#endif - +#endif \ No newline at end of file diff --git a/src/boxmesh.cpp b/src/boxmesh.cpp new file mode 100644 index 00000000..de1fa56f --- /dev/null +++ b/src/boxmesh.cpp @@ -0,0 +1,125 @@ +#include "boxmesh.h" +#include "strokemeshbuilder.h" +#include "triangulate.h" + +static const std::vector subdivedBoxObjVertices = { + {-0.025357, -0.025357, 0.025357}, + {-0.025357, 0.025357, 0.025357}, + {-0.025357, 0.025357, -0.025357}, + {-0.025357, -0.025357, -0.025357}, + {0.025357, -0.025357, 0.025357}, + {0.025357, -0.025357, -0.025357}, + {0.025357, 0.025357, 0.025357}, + {0.025357, 0.025357, -0.025357}, + {-0.030913, -0.030913, -0.000000}, + {-0.030913, -0.000000, 0.030913}, + {-0.030913, 0.030913, 0.000000}, + {-0.030913, 0.000000, -0.030913}, + {0.030913, -0.030913, -0.000000}, + {0.000000, -0.030913, 0.030913}, + {-0.000000, -0.030913, -0.030913}, + {0.030913, -0.000000, 0.030913}, + {-0.000000, 0.030913, 0.030913}, + {0.030913, 0.030913, 0.000000}, + {0.030913, 0.000000, -0.030913}, + {-0.000000, 0.030913, -0.030913}, + {-0.042574, 0.000000, -0.000000}, + {-0.000000, -0.042574, -0.000000}, + {0.000000, -0.000000, 0.042574}, + {0.042574, -0.000000, -0.000000}, + {-0.000000, 0.000000, -0.042574}, + {0.000000, 0.042574, 0.000000}, +}; + +static const std::vector> subdivedBoxObjFaces = { + {1, 10, 21, 9}, + {10, 2, 11, 21}, + {21, 11, 3, 12}, + {9, 21, 12, 4}, + {5, 14, 22, 13}, + {14, 1, 9, 22}, + {22, 9, 4, 15}, + {13, 22, 15, 6}, + {1, 14, 23, 10}, + {14, 5, 16, 23}, + {23, 16, 7, 17}, + {10, 23, 17, 2}, + {7, 16, 24, 18}, + {16, 5, 13, 24}, + {24, 13, 6, 19}, + {18, 24, 19, 8}, + {4, 12, 25, 15}, + {12, 3, 20, 25}, + {25, 20, 8, 19}, + {15, 25, 19, 6}, + {2, 17, 26, 11}, + {17, 7, 18, 26}, + {26, 18, 8, 20}, + {11, 26, 20, 3}, +}; + +void boxmesh(const QVector3D &position, float radius, size_t subdivideTimes, std::vector &vertices, std::vector> &faces) +{ + if (subdivideTimes > 0) { + vertices.reserve(subdivedBoxObjVertices.size()); + auto ratio = 24 * radius; + for (const auto &vertex: subdivedBoxObjVertices) { + auto newVertex = vertex * ratio + position; + vertices.push_back(newVertex); + } + faces.reserve(subdivedBoxObjFaces.size()); + for (const auto &face: subdivedBoxObjFaces) { + auto newFace = {face[0] - 1, + face[1] - 1, + face[2] - 1, + face[3] - 1, + }; + faces.push_back(newFace); + } + return; + } + std::vector beginPlane = { + {-radius, -radius, radius}, + { radius, -radius, radius}, + { radius, radius, radius}, + {-radius, radius, radius}, + }; + std::vector endPlane = { + beginPlane[0], + beginPlane[3], + beginPlane[2], + beginPlane[1] + }; + for (auto &vertex: endPlane) { + vertex.setZ(vertex.z() - radius - radius); + } + for (const auto &vertex: beginPlane) { + vertices.push_back(vertex); + } + for (const auto &vertex: endPlane) { + vertices.push_back(vertex); + } + std::vector beginLoop = { + 0, 1, 2, 3 + }; + std::vector endLoop = { + 4, 5, 6, 7 + }; + std::vector alignedEndLoop = { + 4, 7, 6, 5 + }; + faces.push_back(beginLoop); + faces.push_back(endLoop); + for (size_t i = 0; i < beginLoop.size(); ++i) { + size_t j = (i + 1) % beginLoop.size(); + faces.push_back({ + beginLoop[j], + beginLoop[i], + alignedEndLoop[i], + alignedEndLoop[j] + }); + } + for (auto &vertex: vertices) { + vertex += position; + } +} diff --git a/src/boxmesh.h b/src/boxmesh.h new file mode 100644 index 00000000..08e381dd --- /dev/null +++ b/src/boxmesh.h @@ -0,0 +1,8 @@ +#ifndef DUST3D_BOX_H +#define DUST3D_BOX_H +#include +#include + +void boxmesh(const QVector3D &position, float radius, size_t subdivideTimes, std::vector &vertices, std::vector> &faces); + +#endif diff --git a/src/cyclefinder.cpp b/src/cyclefinder.cpp new file mode 100644 index 00000000..15785438 --- /dev/null +++ b/src/cyclefinder.cpp @@ -0,0 +1,206 @@ +#include +#include +#include +#include +#include +#include "cyclefinder.h" +#include "shortestpath.h" + +// +// The cycle finder implement the following method: +// 1. Remove edge +// 2. Find shortest alternative path between the removed edge +// https://www.geeksforgeeks.org/find-minimum-weight-cycle-undirected-graph/ +// + +CycleFinder::CycleFinder(const std::vector &nodePositions, + const std::vector> &edges) : + m_nodeNum(nodePositions.size()), + m_nodePositions(nodePositions), + m_edges(edges) +{ +} + +void CycleFinder::prepareWeights() +{ + m_edgeLengths.resize(m_edges.size(), 1); + for (size_t i = 0; i < m_edges.size(); ++i) { + const auto &edge = m_edges[i]; + auto length = m_nodePositions[edge.first].distanceToPoint( + m_nodePositions[edge.second]) * 10000; + m_edgeLengths[i] = length; + m_edgeLengthMap.insert({std::make_pair(edge.first, edge.second), length}); + m_edgeLengthMap.insert({std::make_pair(edge.second, edge.first), length}); + } +} + +bool CycleFinder::validateCycleByFlatness(const std::vector &cycle) +{ + // Validate cycle by mesaure the flatness of the face + // Flatness = Average variation of corner normals + if (cycle.empty()) + return false; + std::vector normals; + for (size_t i = 0; i < cycle.size(); ++i) { + size_t h = (i + cycle.size() - 1) % cycle.size(); + size_t j = (i + 1) % cycle.size(); + QVector3D vh = m_nodePositions[cycle[h]]; + QVector3D vi = m_nodePositions[cycle[i]]; + QVector3D vj = m_nodePositions[cycle[j]]; + if (QVector3D::dotProduct((vj - vi).normalized(), + (vi - vh).normalized()) <= 0.966) { // 15 degrees + auto vertexNormal = QVector3D::normal(vj - vi, vh - vi); + normals.push_back(vertexNormal); + } + } + if (normals.empty()) + return false; + float sumOfDistance = 0; + for (size_t i = 0; i < normals.size(); ++i) { + size_t j = (i + 1) % normals.size(); + sumOfDistance += (normals[i] - normals[j]).length(); + } + float flatness = sumOfDistance / normals.size(); + if (flatness >= m_invalidFlatness) { + return false; + } + return true; +} + +int CycleFinder::calculateCycleLength(const std::vector &cycle) +{ + float cycleLength = 0; + for (size_t i = 0; i < cycle.size(); ++i) { + size_t j = (i + 1) % cycle.size(); + auto edge = std::make_pair(cycle[i], cycle[j]); + auto findEdgeLength = m_edgeLengthMap.find(edge); + if (findEdgeLength == m_edgeLengthMap.end()) + continue; + cycleLength += findEdgeLength->second; + } + return cycleLength; +} + +void CycleFinder::find() +{ + prepareWeights(); + + if (m_edges.empty()) + return; + + std::queue> waitEdges; + std::set> visited; + std::map, size_t> halfEdgeToCycleMap; + + auto isPathValid = [&](const std::vector &path) { + for (size_t i = 0; i < path.size(); ++i) { + size_t j = (i + 1) % path.size(); + auto edge = std::make_pair(path[i], path[j]); + if (m_halfEdges.find(edge) != m_halfEdges.end()) { + return false; + } + } + std::map oppositeCycleEdgeLengths; + for (size_t i = 0; i < path.size(); ++i) { + size_t j = (i + 1) % path.size(); + auto oppositeEdge = std::make_pair(path[j], path[i]); + auto findOpposite = halfEdgeToCycleMap.find(oppositeEdge); + if (findOpposite == halfEdgeToCycleMap.end()) + continue; + oppositeCycleEdgeLengths[findOpposite->second] += m_edgeLengthMap[oppositeEdge]; + } + for (const auto &it: oppositeCycleEdgeLengths) { + if (it.first >= m_cycleLengths.size()) { + qDebug() << "Find cycle length failed, should not happen"; + return false; + } + const auto &fullLength = m_cycleLengths[it.first]; + if (fullLength <= 0) { + qDebug() << "Cycle length invalid, should not happen"; + return false; + } + if ((float)it.second / fullLength >= 0.5) { + // Half of the edges (measured by sum of length) have been used by opposite face + return false; + } + } + if (!validateCycleByFlatness(path)) + return false; + return true; + }; + + std::map, size_t> edgeIndexMap; + for (size_t i = 0; i < m_edges.size(); ++i) { + const auto &edge = m_edges[i]; + edgeIndexMap.insert({edge, i}); + edgeIndexMap.insert({std::make_pair(edge.second, edge.first), i}); + } + + waitEdges.push(m_edges[0]); + while (!waitEdges.empty()) { + auto currentEdge = waitEdges.front(); + waitEdges.pop(); + if (visited.find(currentEdge) != visited.end()) + continue; + visited.insert(currentEdge); + + auto edges = m_edges; + auto weights = m_edgeLengths; + std::vector path; + removeEdgeFrom(currentEdge, &edges, &weights); + if (!shortestPath(m_nodeNum, edges, weights, currentEdge.first, currentEdge.second, &path)) + continue; + + bool isValid = isPathValid(path); + + if (!isValid) + continue; + + for (size_t i = 0; i < path.size(); ++i) { + size_t j = (i + 1) % path.size(); + auto edge = std::make_pair(path[i], path[j]); + m_halfEdges.insert(edge); + halfEdgeToCycleMap.insert({edge, m_cycles.size()}); + } + + m_cycles.push_back(path); + m_cycleLengths.push_back(calculateCycleLength(path)); + + // Update weights + for (size_t i = 0; i < path.size(); ++i) { + size_t j = (i + 1) % path.size(); + auto edge = std::make_pair(path[i], path[j]); + auto index = edgeIndexMap[edge]; + m_edgeLengths[index] += 100000; + } + + // Add opposite edges to wait queue + for (size_t i = 0; i < path.size(); ++i) { + size_t j = (i + 1) % path.size(); + auto oppositeEdge = std::make_pair(path[j], path[i]); + if (visited.find(oppositeEdge) != visited.end()) + continue; + waitEdges.push(oppositeEdge); + } + } +} + +const std::vector> &CycleFinder::getCycles() +{ + return m_cycles; +} + +void CycleFinder::removeEdgeFrom(const std::pair &edge, + std::vector> *edges, + std::vector *edgeLengths) +{ + auto oppositeEdge = std::make_pair(edge.second, edge.first); + for (auto it = edges->begin(); it != edges->end(); ++it) { + if (*it == edge || *it == oppositeEdge) { + auto index = it - edges->begin(); + edgeLengths->erase(edgeLengths->begin() + index); + edges->erase(it); + break; + } + } +} diff --git a/src/cyclefinder.h b/src/cyclefinder.h new file mode 100644 index 00000000..606a34a4 --- /dev/null +++ b/src/cyclefinder.h @@ -0,0 +1,34 @@ +#ifndef DUST3D_CYCLE_FINDER +#define DUST3D_CYCLE_FINDER +#include +#include +#include + +class CycleFinder +{ +public: + CycleFinder(const std::vector &nodePositions, + const std::vector> &edges); + void find(); + const std::vector> &getCycles(); +private: + size_t m_nodeNum = 0; + std::vector m_nodePositions; + std::vector> m_edges; + std::vector m_edgeLengths; + std::map, int> m_edgeLengthMap; + std::vector> m_cycles; + std::vector m_cycleLengths; + std::set> m_cycleEdges; + std::set> m_halfEdges; + float m_invalidFlatness = 1.0; + void removeEdgeFrom(const std::pair &edge, + std::vector> *edges, + std::vector *edgeLengths); + void prepareWeights(); + bool validateCycleByFlatness(const std::vector &cycle); + int calculateCycleLength(const std::vector &cycle); +}; + +#endif + diff --git a/src/document.cpp b/src/document.cpp index bffd13e7..23ba69d9 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "document.h" #include "util.h" #include "snapshotxml.h" @@ -76,9 +77,6 @@ Document::Document() : { connect(&Preferences::instance(), &Preferences::partColorChanged, this, &Document::applyPreferencePartColorChange); connect(&Preferences::instance(), &Preferences::flatShadingChanged, this, &Document::applyPreferenceFlatShadingChange); - connect(&Preferences::instance(), &Preferences::threeNodesBranchEnableStateChanged, this, [&]() { - threeNodesBranchEnabled = Preferences::instance().threeNodesBranchEnabled(); - }); } void Document::applyPreferencePartColorChange() @@ -181,6 +179,7 @@ void Document::removeEdge(QUuid edgeId) std::vector> groups; splitPartByEdge(&groups, edgeId); std::vector> newPartNodeNumMap; + std::vector newPartIds; for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) { const auto newUuid = QUuid::createUuid(); SkeletonPart &part = partMap[newUuid]; @@ -206,6 +205,7 @@ void Document::removeEdge(QUuid edgeId) } addPartToComponent(part.id, findComponentParentId(part.componentId)); newPartNodeNumMap.push_back({part.id, part.nodeIds.size()}); + newPartIds.push_back(part.id); emit partAdded(part.id); } for (auto nodeIdIt = edge->nodeIds.begin(); nodeIdIt != edge->nodeIds.end(); nodeIdIt++) { @@ -229,6 +229,10 @@ void Document::removeEdge(QUuid edgeId) updateLinkedPart(oldPartId, newPartNodeNumMap[0].first); } + for (const auto &partId: newPartIds) { + checkPartGrid(partId); + } + emit skeletonChanged(); } @@ -251,6 +255,7 @@ void Document::removeNode(QUuid nodeId) std::vector> groups; splitPartByNode(&groups, nodeId); std::vector> newPartNodeNumMap; + std::vector newPartIds; for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) { const auto newUuid = QUuid::createUuid(); SkeletonPart &part = partMap[newUuid]; @@ -276,6 +281,7 @@ void Document::removeNode(QUuid nodeId) } addPartToComponent(part.id, findComponentParentId(part.componentId)); newPartNodeNumMap.push_back({part.id, part.nodeIds.size()}); + newPartIds.push_back(part.id); emit partAdded(part.id); } for (auto edgeIdIt = node->edgeIds.begin(); edgeIdIt != node->edgeIds.end(); edgeIdIt++) { @@ -306,6 +312,10 @@ void Document::removeNode(QUuid nodeId) updateLinkedPart(oldPartId, newPartNodeNumMap[0].first); } + for (const auto &partId: newPartIds) { + checkPartGrid(partId); + } + emit skeletonChanged(); } @@ -371,6 +381,7 @@ QUuid Document::createNode(QUuid nodeId, float x, float y, float z, float radius if (newPartAdded) addPartToComponent(partId, m_currentCanvasComponentId); + checkPartGrid(partId); emit skeletonChanged(); return node.id; @@ -616,9 +627,33 @@ void Document::addEdge(QUuid fromNodeId, QUuid toNodeId) removePart(toPartId); } + checkPartGrid(fromNode->partId); + emit skeletonChanged(); } +void Document::checkPartGrid(QUuid partId) +{ + SkeletonPart *part = (SkeletonPart *)findPart(partId); + if (nullptr == part) + return; + bool isGrid = false; + for (const auto &nodeId: part->nodeIds) { + const SkeletonNode *node = findNode(nodeId); + if (nullptr == node) + continue; + if (node->edgeIds.size() >= 3) { + isGrid = true; + break; + } + } + if (part->gridded == isGrid) + return; + part->gridded = isGrid; + part->dirty = true; + emit partGridStateChanged(partId); +} + void Document::updateLinkedPart(QUuid oldPartId, QUuid newPartId) { for (auto &partIt: partMap) { @@ -1083,6 +1118,8 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set &limitNodeId part["materialId"] = partIt.second.materialId.toString(); if (partIt.second.countershaded) part["countershaded"] = "true"; + if (partIt.second.gridded) + part["gridded"] = "true"; snapshot->parts[part["id"]] = part; } for (const auto &nodeIt: nodeMap) { @@ -1259,6 +1296,154 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set &limitNodeId } } +void Document::createSinglePartFromEdges(const std::vector &nodes, + const std::vector> &edges) +{ + std::vector newAddedNodeIds; + std::vector newAddedEdgeIds; + std::vector newAddedPartIds; + std::map nodeIndexToIdMap; + + QUuid partId = QUuid::createUuid(); + + Component component(QUuid(), partId.toString(), "partId"); + component.combineMode = CombineMode::Normal; + componentMap[component.id] = component; + rootComponent.addChild(component.id); + + auto &newPart = partMap[partId]; + newPart.id = partId; + newPart.componentId = component.id; + newAddedPartIds.push_back(newPart.id); + + auto nodeToId = [&](size_t nodeIndex) { + auto findId = nodeIndexToIdMap.find(nodeIndex); + if (findId != nodeIndexToIdMap.end()) + return findId->second; + const auto &position = nodes[nodeIndex]; + SkeletonNode newNode; + newNode.partId = newPart.id; + newNode.setX(getOriginX() + position.x()); + newNode.setY(getOriginY() - position.y()); + newNode.setZ(getOriginZ() - position.z()); + newNode.setRadius(0); + nodeMap[newNode.id] = newNode; + newPart.nodeIds.push_back(newNode.id); + newAddedNodeIds.push_back(newNode.id); + nodeIndexToIdMap.insert({nodeIndex, newNode.id}); + return newNode.id; + }; + + for (const auto &edge: edges) { + QUuid firstNodeId = nodeToId(edge.first); + QUuid secondNodeId = nodeToId(edge.second); + + SkeletonEdge newEdge; + newEdge.nodeIds.push_back(firstNodeId); + newEdge.nodeIds.push_back(secondNodeId); + newEdge.partId = newPart.id; + + nodeMap[firstNodeId].edgeIds.push_back(newEdge.id); + nodeMap[secondNodeId].edgeIds.push_back(newEdge.id); + + newAddedEdgeIds.push_back(newEdge.id); + + edgeMap[newEdge.id] = newEdge; + } + + for (const auto &nodeIt: newAddedNodeIds) { + qDebug() << "new node:" << nodeIt; + emit nodeAdded(nodeIt); + } + for (const auto &edgeIt: newAddedEdgeIds) { + qDebug() << "new edge:" << edgeIt; + emit edgeAdded(edgeIt); + } + for (const auto &partIt: newAddedPartIds) { + qDebug() << "new part:" << partIt; + emit partAdded(partIt); + } + + for (const auto &partIt : newAddedPartIds) { + checkPartGrid(partIt); + emit partVisibleStateChanged(partIt); + } + + emit uncheckAll(); + for (const auto &nodeIt: newAddedNodeIds) { + emit checkNode(nodeIt); + } + for (const auto &edgeIt: newAddedEdgeIds) { + emit checkEdge(edgeIt); + } + + emit componentChildrenChanged(QUuid()); + emit skeletonChanged(); +} + +void Document::createFromNodesAndEdges(const std::vector &nodes, + const std::vector> &edges) +{ + std::map> edgeLinks; + for (const auto &it: edges) { + edgeLinks[it.first].push_back(it.second); + edgeLinks[it.second].push_back(it.first); + } + if (edgeLinks.empty()) + return; + std::vector> islands; + std::set visited; + for (size_t i = 0; i < nodes.size(); ++i) { + std::set island; + std::queue waitVertices; + waitVertices.push(i); + while (!waitVertices.empty()) { + size_t vertexIndex = waitVertices.front(); + waitVertices.pop(); + if (visited.find(vertexIndex) == visited.end()) { + visited.insert(vertexIndex); + island.insert(vertexIndex); + } + auto findLink = edgeLinks.find(vertexIndex); + if (findLink == edgeLinks.end()) { + continue; + } + for (const auto &it: findLink->second) { + if (visited.find(it) == visited.end()) + waitVertices.push(it); + } + } + if (!island.empty()) + islands.push_back(island); + } + + std::map vertexIslandMap; + for (size_t islandIndex = 0; islandIndex < islands.size(); ++islandIndex) { + const auto &island = islands[islandIndex]; + for (const auto &it: island) + vertexIslandMap.insert({it, islandIndex}); + } + + std::vector>> edgesGroupByIsland(islands.size()); + for (const auto &it: edges) { + auto findFirstVertex = vertexIslandMap.find(it.first); + if (findFirstVertex != vertexIslandMap.end()) { + edgesGroupByIsland[findFirstVertex->second].push_back(it); + continue; + } + auto findSecondVertex = vertexIslandMap.find(it.second); + if (findSecondVertex != vertexIslandMap.end()) { + edgesGroupByIsland[findSecondVertex->second].push_back(it); + continue; + } + } + + for (size_t islandIndex = 0; islandIndex < islands.size(); ++islandIndex) { + const auto &islandEdges = edgesGroupByIsland[islandIndex]; + createSinglePartFromEdges(nodes, islandEdges); + } +} + void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste) { bool isOriginChanged = false; @@ -1389,6 +1574,7 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste) if (materialIdIt != partKv.second.end()) part.materialId = oldNewIdMap[QUuid(materialIdIt->second)]; part.countershaded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "countershaded")); + part.gridded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "gridded"));; newAddedPartIds.insert(part.id); } for (const auto &it: cutFaceLinkedIdModifyMap) { @@ -1589,6 +1775,7 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste) emit skeletonChanged(); for (const auto &partIt : newAddedPartIds) { + checkPartGrid(partIt); emit partVisibleStateChanged(partIt); } @@ -3761,3 +3948,60 @@ void Document::setMousePickMaskNodeIds(const std::set &nodeIds) { m_mousePickMaskNodeIds = nodeIds; } + +void Document::createGriddedPartsFromNodes(const std::set &nodeIds) +{ + if (nullptr == m_currentOutcome) + return; + + const auto &vertices = m_currentOutcome->vertices; + const auto &vertexSourceNodes = m_currentOutcome->vertexSourceNodes; + std::set selectedVertices; + for (size_t i = 0; i < vertexSourceNodes.size(); ++i) { + if (nodeIds.find(vertexSourceNodes[i].second) == nodeIds.end()) + continue; + selectedVertices.insert(i); + } + + std::vector newVertices; + std::map oldToNewMap; + std::vector> newEdges; + + auto oldToNew = [&](size_t oldIndex) { + auto findNewIndex = oldToNewMap.find(oldIndex); + if (findNewIndex != oldToNewMap.end()) + return findNewIndex->second; + size_t newIndex = newVertices.size(); + newVertices.push_back(vertices[oldIndex]); + oldToNewMap.insert({oldIndex, newIndex}); + return newIndex; + }; + + std::set> visitedOldEdges; + const auto &faces = m_currentOutcome->triangleAndQuads; + for (const auto &face: faces) { + bool isFaceSelected = false; + for (size_t i = 0; i < face.size(); ++i) { + if (selectedVertices.find(face[i]) != selectedVertices.end()) { + isFaceSelected = true; + break; + } + } + if (!isFaceSelected) + continue; + for (size_t i = 0; i < face.size(); ++i) { + size_t j = (i + 1) % face.size(); + auto oldEdge = std::make_pair(face[i], face[j]); + if (visitedOldEdges.find(oldEdge) != visitedOldEdges.end()) + continue; + visitedOldEdges.insert(oldEdge); + visitedOldEdges.insert(std::make_pair(oldEdge.second, oldEdge.first)); + newEdges.push_back({ + oldToNew(oldEdge.first), + oldToNew(oldEdge.second) + }); + } + } + + createFromNodesAndEdges(newVertices, newEdges); +} diff --git a/src/document.h b/src/document.h index d9d0f4c7..e1c140a0 100644 --- a/src/document.h +++ b/src/document.h @@ -435,6 +435,7 @@ signals: void partColorSolubilityChanged(QUuid partId); void partHollowThicknessChanged(QUuid partId); void partCountershadeStateChanged(QUuid partId); + void partGridStateChanged(QUuid partId); void componentCombineModeChanged(QUuid componentId); void cleanup(); void cleanupScript(); @@ -583,6 +584,11 @@ public slots: void setEditMode(SkeletonDocumentEditMode mode); void setPaintMode(PaintMode mode); void setMousePickRadius(float radius); + void createGriddedPartsFromNodes(const std::set &nodeIds); + void createFromNodesAndEdges(const std::vector &nodes, + const std::vector> &edges); + void createSinglePartFromEdges(const std::vector &nodes, + const std::vector> &edges); void uiReady(); void generateMesh(); void regenerateMesh(); @@ -715,11 +721,12 @@ private: void updateLinkedPart(QUuid oldPartId, QUuid newPartId); //void addToolToMesh(MeshLoader *mesh); bool updateDefaultVariables(const std::map> &defaultVariables); + void checkPartGrid(QUuid partId); private: // need initialize bool m_isResultMeshObsolete; MeshGenerator *m_meshGenerator; MeshLoader *m_resultMesh; - std::map *m_resultMeshCutFaceTransforms; + std::map *m_resultMeshCutFaceTransforms; std::map> *m_resultMeshNodesCutFaces; bool m_isMeshGenerationSucceed; int m_batchChangeRefCount; diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index 592d88bf..43bb2085 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "documentwindow.h" #include "skeletongraphicswidget.h" #include "theme.h" @@ -27,7 +28,6 @@ #include "aboutwidget.h" #include "version.h" #include "glbfile.h" -#include "graphicscontainerwidget.h" #include "parttreewidget.h" #include "rigwidget.h" #include "markiconcreator.h" @@ -283,6 +283,17 @@ DocumentWindow::DocumentWindow() : containerLayout->addWidget(graphicsWidget); containerWidget->setLayout(containerLayout); containerWidget->setMinimumSize(400, 400); + + m_graphicsContainerWidget = containerWidget; + + //m_infoWidget = new QLabel(containerWidget); + //m_infoWidget->setAttribute(Qt::WA_TransparentForMouseEvents); + //QGraphicsOpacityEffect *graphicsOpacityEffect = new QGraphicsOpacityEffect(m_infoWidget); + //graphicsOpacityEffect->setOpacity(0.5); + //m_infoWidget->setGraphicsEffect(graphicsOpacityEffect); + //updateInfoWidgetPosition(); + + //connect(containerWidget, &GraphicsContainerWidget::containerSizeChanged, this, &DocumentWindow::updateInfoWidgetPosition); m_modelRenderWidget = new ModelWidget(containerWidget); m_modelRenderWidget->setAttribute(Qt::WA_TransparentForMouseEvents); @@ -574,6 +585,12 @@ DocumentWindow::DocumentWindow() : m_graphicsWidget->clearSelectedCutFace(); }); m_editMenu->addAction(m_clearCutFaceAction); + + m_createWrapPartsAction = new QAction(tr("Create Wrap Parts"), this); + connect(m_createWrapPartsAction, &QAction::triggered, [=] { + m_graphicsWidget->createWrapParts(); + }); + m_editMenu->addAction(m_createWrapPartsAction); m_alignToMenu = new QMenu(tr("Align To")); @@ -668,6 +685,7 @@ DocumentWindow::DocumentWindow() : m_switchXzAction->setEnabled(m_graphicsWidget->hasSelection()); m_setCutFaceAction->setEnabled(m_graphicsWidget->hasSelection()); m_clearCutFaceAction->setEnabled(m_graphicsWidget->hasCutFaceAdjustedNodesSelection()); + m_createWrapPartsAction->setEnabled(m_graphicsWidget->hasSelection()); m_colorizeAsBlankAction->setEnabled(m_graphicsWidget->hasSelection()); m_colorizeAsAutoAction->setEnabled(m_graphicsWidget->hasSelection()); m_alignToGlobalCenterAction->setEnabled(m_graphicsWidget->hasSelection() && m_document->originSettled()); @@ -923,6 +941,7 @@ DocumentWindow::DocumentWindow() : connect(graphicsWidget, &SkeletonGraphicsWidget::setPartXmirrorState, m_document, &Document::setPartXmirrorState); connect(graphicsWidget, &SkeletonGraphicsWidget::setPartRoundState, m_document, &Document::setPartRoundState); connect(graphicsWidget, &SkeletonGraphicsWidget::setPartWrapState, m_document, &Document::setPartCutRotation); + connect(graphicsWidget, &SkeletonGraphicsWidget::createGriddedPartsFromNodes, m_document, &Document::createGriddedPartsFromNodes); connect(graphicsWidget, &SkeletonGraphicsWidget::setXlockState, m_document, &Document::setXlockState); connect(graphicsWidget, &SkeletonGraphicsWidget::setYlockState, m_document, &Document::setYlockState); @@ -1852,3 +1871,9 @@ void DocumentWindow::checkExportWaitingList() } } } + +//void DocumentWindow::updateInfoWidgetPosition() +//{ +// m_infoWidget->move(0, m_graphicsContainerWidget->height() - m_infoWidget->height() - 5); +//} + diff --git a/src/documentwindow.h b/src/documentwindow.h index dd0379c6..6073f4a6 100644 --- a/src/documentwindow.h +++ b/src/documentwindow.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "document.h" #include "modelwidget.h" #include "exportpreviewwidget.h" @@ -16,6 +17,7 @@ #include "bonemark.h" #include "posemanagewidget.h" #include "preferenceswidget.h" +#include "graphicscontainerwidget.h" class SkeletonGraphicsWidget; @@ -80,6 +82,7 @@ public slots: void exportFbxToFilename(const QString &filename); void exportGlbToFilename(const QString &filename); void toggleRotation(); + //void updateInfoWidgetPosition(); private: void initLockButton(QPushButton *button); void setCurrentFilename(const QString &filename); @@ -100,6 +103,8 @@ private: ModelWidget *m_modelRenderWidget; SkeletonGraphicsWidget *m_graphicsWidget; RigWidget *m_rigWidget; + //QLabel *m_infoWidget; + GraphicsContainerWidget *m_graphicsContainerWidget; QMenu *m_fileMenu; QAction *m_newWindowAction; @@ -136,6 +141,7 @@ private: QAction *m_switchXzAction; QAction *m_setCutFaceAction; QAction *m_clearCutFaceAction; + QAction *m_createWrapPartsAction; QMenu *m_alignToMenu; QAction *m_alignToGlobalCenterAction; diff --git a/src/gridmeshbuilder.cpp b/src/gridmeshbuilder.cpp new file mode 100644 index 00000000..be97eeb7 --- /dev/null +++ b/src/gridmeshbuilder.cpp @@ -0,0 +1,444 @@ +#include +#include "gridmeshbuilder.h" +#include "cyclefinder.h" +#include "regionfiller.h" +#include "util.h" + +size_t GridMeshBuilder::addNode(const QVector3D &position, float radius) +{ + size_t newNodeIndex = m_nodes.size(); + + Node node; + node.position = position; + node.radius = radius; + node.source = newNodeIndex; + m_nodes.push_back(node); + + return newNodeIndex; +} + +size_t GridMeshBuilder::addEdge(size_t firstNodeIndex, size_t secondNodeIndex) +{ + size_t newEdgeIndex = m_edges.size(); + + Edge edge; + edge.firstNodeIndex = firstNodeIndex; + edge.secondNodeIndex = secondNodeIndex; + m_edges.push_back(edge); + + m_nodes[firstNodeIndex].neighborIndices.push_back(secondNodeIndex); + m_nodes[secondNodeIndex].neighborIndices.push_back(firstNodeIndex); + + return newEdgeIndex; +} + +const std::vector &GridMeshBuilder::getGeneratedPositions() +{ + return m_generatedPositions; +} + +const std::vector &GridMeshBuilder::getGeneratedSources() +{ + return m_generatedSources; +} + +const std::vector> &GridMeshBuilder::getGeneratedFaces() +{ + return m_generatedFaces; +} + +void GridMeshBuilder::splitCycleToPolylines(const std::vector &cycle, + std::vector> *polylines) +{ + if (cycle.size() < 3) { + qDebug() << "Invalid cycle size:" << cycle.size(); + return; + } + //qDebug() << "Cycle:" << cycle; + std::vector cornerIndices; + for (size_t i = 0; i < cycle.size(); ++i) { + size_t h = (i + cycle.size() - 1) % cycle.size(); + size_t j = (i + 1) % cycle.size(); + QVector3D hi = m_nodeVertices[cycle[i]].position - m_nodeVertices[cycle[h]].position; + QVector3D ij = m_nodeVertices[cycle[j]].position - m_nodeVertices[cycle[i]].position; + auto angle = angleBetweenVectors(hi, ij); + //qDebug() << "angle[" << i << "]:" << angle; + if (angle >= m_polylineAngleChangeThreshold) + cornerIndices.push_back(i); + } + if (cornerIndices.size() < 3) { + qDebug() << "Invalid corners:" << cornerIndices.size(); + return; + } + for (size_t m = 0; m < cornerIndices.size(); ++m) { + size_t n = (m + 1) % cornerIndices.size(); + std::vector polyline; + size_t i = cornerIndices[m]; + size_t j = cornerIndices[n]; + for (size_t p = i; p != j; p = (p + 1) % cycle.size()) + polyline.push_back(cycle[p]); + polyline.push_back(cycle[j]); + //qDebug() << "Polyline[m" << m << "n" << n << "i" << i << "j" << j << "]:" << polyline; + polylines->push_back(polyline); + } +} + +void GridMeshBuilder::prepareNodeVertices() +{ + m_nodeVertices.resize(m_nodes.size()); + m_nodePositions.resize(m_nodes.size()); + for (size_t i = 0; i < m_nodes.size(); ++i) { + RegionFiller::Node node; + node.position = m_nodes[i].position; + node.radius = m_nodes[i].radius; + node.source = i; + m_nodeVertices[i] = node; + m_nodePositions[i] = node.position; + } +} + +void GridMeshBuilder::generateFaces() +{ + auto createEdgeKey = [](size_t v1, size_t v2) { + if (v1 > v2) + std::swap(v1, v2); + return std::make_pair(v1, v2); + }; + + std::map, std::vector> edgeToCandidateFaceMap; + m_generatedVertices = m_nodeVertices; + std::vector> candidateFaces; + std::vector candidateFaceSourceCycles; + m_generatedFaces.clear(); + + auto addCandidateFaces = [&](const std::vector> &newFaces, size_t sourceCycle) { + for (const auto &face: newFaces) { + size_t candidateFaceIndex = candidateFaces.size(); + candidateFaces.push_back(face); + candidateFaceSourceCycles.push_back(sourceCycle); + for (size_t i = 0; i < face.size(); ++i) { + size_t j = (i + 1) % face.size(); + auto edgeKey = createEdgeKey(face[i], face[j]); + edgeToCandidateFaceMap[edgeKey].push_back(candidateFaceIndex); + } + } + }; + + for (size_t i = 0; i < m_cycles.size(); ++i) { + const auto &it = m_cycles[i]; + std::vector> polylines; + splitCycleToPolylines(it, &polylines); + if (polylines.size() < 3) { + std::vector> faces; + faces.push_back(it); + addCandidateFaces(faces, i); + continue; + } + RegionFiller regionFiller(&m_generatedVertices, &polylines); + if (regionFiller.fill()) { + m_generatedVertices = regionFiller.getOldAndNewVertices(); + } else { + regionFiller.fillWithoutPartition(); + } + auto newFaces = regionFiller.getNewFaces(); + addCandidateFaces(newFaces, i); + } + + if (candidateFaces.empty()) + return; + + std::set visitedFaceIndices; + std::queue waitFaceIndices; + waitFaceIndices.push(0); + while (!waitFaceIndices.empty()) { + auto faceIndex = waitFaceIndices.front(); + waitFaceIndices.pop(); + if (visitedFaceIndices.find(faceIndex) != visitedFaceIndices.end()) + continue; + visitedFaceIndices.insert(faceIndex); + const auto &face = candidateFaces[faceIndex]; + if (face.size() < 3) { + qDebug() << "Invalid face, edges:" << face.size(); + continue; + } + bool shouldReverse = false; + size_t checkIndex = 0; + for (size_t i = 0; i < face.size(); ++i) { + size_t j = (i + 1) % face.size(); + if (m_halfEdgeMap.find(std::make_pair(face[i], face[j])) != m_halfEdgeMap.end() || + m_halfEdgeMap.find(std::make_pair(face[j], face[i])) != m_halfEdgeMap.end()) { + checkIndex = i; + break; + } + } + size_t nextOfCheckIndex = (checkIndex + 1) % face.size(); + std::pair edge = std::make_pair(face[checkIndex], face[nextOfCheckIndex]); + if (m_halfEdgeMap.find(edge) != m_halfEdgeMap.end()) { + std::pair oppositeEdge = std::make_pair(face[nextOfCheckIndex], face[checkIndex]); + if (m_halfEdgeMap.find(oppositeEdge) != m_halfEdgeMap.end()) { + qDebug() << "Too many face share one edge, should not happen"; + continue; + } + shouldReverse = true; + } + auto finalFace = face; + if (shouldReverse) { + std::reverse(finalFace.begin(), finalFace.end()); + } + size_t finalFaceIndex = m_generatedFaces.size(); + m_generatedFaces.push_back(finalFace); + m_generatedFaceSourceCycles.push_back(candidateFaceSourceCycles[faceIndex]); + for (size_t i = 0; i < finalFace.size(); ++i) { + size_t j = (i + 1) % finalFace.size(); + auto insertResult = m_halfEdgeMap.insert({std::make_pair(finalFace[i], finalFace[j]), finalFaceIndex}); + if (!insertResult.second) { + qDebug() << "Should not happend, half edge conflicts"; + } + auto edgeKey = createEdgeKey(finalFace[i], finalFace[j]); + auto findCandidates = edgeToCandidateFaceMap.find(edgeKey); + if (findCandidates == edgeToCandidateFaceMap.end()) + continue; + for (const auto &candidateFaceIndex: findCandidates->second) { + if (visitedFaceIndices.find(candidateFaceIndex) != visitedFaceIndices.end()) + continue; + waitFaceIndices.push(candidateFaceIndex); + } + } + } +} + +void GridMeshBuilder::calculateNormals() +{ + std::vector> vertexNormals(m_generatedVertices.size()); + for (const auto &face: m_generatedFaces) { + QVector3D sumOfNormals; + for (size_t i = 0; i < face.size(); ++i) { + size_t h = (i + face.size() - 1) % face.size(); + size_t j = (i + 1) % face.size(); + QVector3D vh = m_generatedVertices[face[h]].position; + QVector3D vi = m_generatedVertices[face[i]].position; + QVector3D vj = m_generatedVertices[face[j]].position; + sumOfNormals += QVector3D::normal(vj - vi, vh - vi); + } + QVector3D faceNormal = sumOfNormals.normalized(); + for (size_t i = 0; i < face.size(); ++i) { + vertexNormals[face[i]].push_back(faceNormal); + } + } + m_nodeNormals.resize(m_generatedVertices.size()); + for (size_t i = 0; i < m_nodeNormals.size(); ++i) { + const auto &normals = vertexNormals[i]; + if (normals.empty()) + continue; + m_nodeNormals[i] = std::accumulate(normals.begin(), normals.end(), QVector3D()).normalized(); + } +} + +void GridMeshBuilder::removeBigRingFaces() +{ + if (m_generatedFaces.size() != m_generatedFaceSourceCycles.size()) { + qDebug() << "Generated source cycles invalid"; + return; + } + auto maxBigRingSize = m_maxBigRingSize; + if (m_subdived) + maxBigRingSize *= 2; + std::set invalidCycles; + for (size_t faceIndex = 0; faceIndex < m_generatedFaces.size(); ++faceIndex) { + const auto &face = m_generatedFaces[faceIndex]; + size_t sourceCycle = m_generatedFaceSourceCycles[faceIndex]; + size_t oppositeCycles = 0; + for (size_t i = 0; i < face.size(); ++i) { + size_t j = (i + 1) % face.size(); + auto oppositeEdge = std::make_pair(face[j], face[i]); + auto findOpposite = m_halfEdgeMap.find(oppositeEdge); + if (findOpposite == m_halfEdgeMap.end()) + continue; + size_t oppositeFaceIndex = findOpposite->second; + size_t oppositeFaceSourceCycle = m_generatedFaceSourceCycles[oppositeFaceIndex]; + if (sourceCycle == oppositeFaceSourceCycle) + continue; + ++oppositeCycles; + } + if (oppositeCycles > maxBigRingSize) + invalidCycles.insert(sourceCycle); + } + + if (invalidCycles.empty()) + return; + + auto oldFaces = m_generatedFaces; + m_halfEdgeMap.clear(); + m_generatedFaces.clear(); + for (size_t faceIndex = 0; faceIndex < oldFaces.size(); ++faceIndex) { + size_t sourceCycle = m_generatedFaceSourceCycles[faceIndex]; + if (invalidCycles.find(sourceCycle) != invalidCycles.end()) + continue; + const auto &face = oldFaces[faceIndex]; + for (size_t i = 0; i < face.size(); ++i) { + size_t j = (i + 1) % face.size(); + auto edge = std::make_pair(face[i], face[j]); + m_halfEdgeMap.insert({edge, m_generatedFaces.size()}); + } + m_generatedFaces.push_back(face); + } +} + +void GridMeshBuilder::extrude() +{ + removeBigRingFaces(); + calculateNormals(); + + bool hasHalfEdge = false; + for (const auto &halfEdge: m_halfEdgeMap) { + auto oppositeHalfEdge = std::make_pair(halfEdge.first.second, halfEdge.first.first); + if (m_halfEdgeMap.find(oppositeHalfEdge) != m_halfEdgeMap.end()) + continue; + hasHalfEdge = true; + break; + } + + if (m_generatedVertices.empty()) + return; + + m_generatedPositions.resize(m_generatedVertices.size() * 2); + m_generatedSources.resize(m_generatedPositions.size(), 0); + for (size_t i = 0; i < m_generatedVertices.size(); ++i) { + const auto &vertex = m_generatedVertices[i]; + const auto &normal = m_nodeNormals[i]; + m_generatedPositions[i] = vertex.position; + m_generatedPositions[i] += normal * vertex.radius; + m_generatedSources[i] = m_nodes[vertex.source].source; + size_t j = m_generatedVertices.size() + i; + m_generatedPositions[j] = vertex.position; + m_generatedPositions[j] -= normal * vertex.radius; + m_generatedSources[j] = m_generatedSources[i]; + } + + bool pickSecondMesh = false; + // The outter faces should have longer edges + float sumOfFirstMeshEdgeLength = 0; + float sumOfSecondMeshEdgeLength = 0; + for (size_t i = 0; i < m_generatedFaces.size(); ++i) { + const auto &face = m_generatedFaces[i]; + for (size_t m = 0; m < face.size(); ++m) { + size_t n = (m + 1) % face.size(); + sumOfFirstMeshEdgeLength += (m_generatedPositions[face[m]] - m_generatedPositions[face[n]]).length(); + sumOfSecondMeshEdgeLength += (m_generatedPositions[m_generatedVertices.size() + face[m]] - m_generatedPositions[m_generatedVertices.size() + face[n]]).length(); + } + } + if (sumOfFirstMeshEdgeLength < sumOfSecondMeshEdgeLength) + pickSecondMesh = true; + + size_t faceNumPerLayer = m_generatedFaces.size(); + if (hasHalfEdge) { + m_generatedFaces.resize(faceNumPerLayer * 2); + for (size_t i = faceNumPerLayer; i < m_generatedFaces.size(); ++i) { + auto &face = m_generatedFaces[i]; + face = m_generatedFaces[i - faceNumPerLayer]; + for (auto &it: face) + it += m_generatedVertices.size(); + std::reverse(face.begin(), face.end()); + } + for (const auto &halfEdge: m_halfEdgeMap) { + auto oppositeHalfEdge = std::make_pair(halfEdge.first.second, halfEdge.first.first); + if (m_halfEdgeMap.find(oppositeHalfEdge) != m_halfEdgeMap.end()) + continue; + std::vector face = { + oppositeHalfEdge.first, + oppositeHalfEdge.second, + halfEdge.first.first + m_generatedVertices.size(), + halfEdge.first.second + m_generatedVertices.size() + }; + m_generatedFaces.push_back(face); + } + } else { + if (pickSecondMesh) { + for (auto &face: m_generatedFaces) { + for (auto &it: face) + it += m_generatedVertices.size(); + std::reverse(face.begin(), face.end()); + } + } + } +} + +void GridMeshBuilder::applyModifiers() +{ + std::vector oldNodes = m_nodes; + std::vector oldEdges = m_edges; + + m_edges.clear(); + + float distance2Threshold = m_meshTargetEdgeSize * m_meshTargetEdgeSize; + + std::set> visitedEdges; + for (const auto &oldEdge: oldEdges) { + auto key = std::make_pair(oldEdge.firstNodeIndex, oldEdge.secondNodeIndex); + if (visitedEdges.find(key) != visitedEdges.end()) + continue; + visitedEdges.insert(std::make_pair(oldEdge.firstNodeIndex, oldEdge.secondNodeIndex)); + visitedEdges.insert(std::make_pair(oldEdge.secondNodeIndex, oldEdge.firstNodeIndex)); + const auto &oldStartNode = oldNodes[oldEdge.firstNodeIndex]; + const auto &oldStopNode = oldNodes[oldEdge.secondNodeIndex]; + if ((oldStartNode.position - oldStopNode.position).lengthSquared() <= distance2Threshold) { + m_edges.push_back(oldEdge); + continue; + } + auto oldEdgeLength = (oldStartNode.position - oldStopNode.position).length(); + size_t newInsertNum = oldEdgeLength / m_meshTargetEdgeSize; + if (newInsertNum < 1) + newInsertNum = 1; + if (newInsertNum > 100) + continue; + float stepFactor = 1.0 / (newInsertNum + 1); + float factor = stepFactor; + std::vector edgeNodeIndices; + edgeNodeIndices.push_back(oldEdge.firstNodeIndex); + for (size_t i = 0; i < newInsertNum && factor < 1.0; factor += stepFactor, ++i) { + float firstFactor = 1.0 - factor; + Node newNode; + newNode.position = oldStartNode.position * firstFactor + oldStopNode.position * factor; + newNode.radius = oldStartNode.radius * firstFactor + oldStopNode.radius * factor; + if (firstFactor >= 0.5) { + newNode.source = oldEdge.firstNodeIndex; + } else { + newNode.source = oldEdge.secondNodeIndex; + } + edgeNodeIndices.push_back(m_nodes.size()); + m_nodes.push_back(newNode); + } + edgeNodeIndices.push_back(oldEdge.secondNodeIndex); + for (size_t i = 1; i < edgeNodeIndices.size(); ++i) { + size_t h = i - 1; + m_edges.push_back(Edge {edgeNodeIndices[h], edgeNodeIndices[i]}); + } + } +} + +void GridMeshBuilder::setSubdived(bool subdived) +{ + m_subdived = subdived; +} + +void GridMeshBuilder::build() +{ + if (m_subdived) + applyModifiers(); + prepareNodeVertices(); + findCycles(); + generateFaces(); + extrude(); +} + +void GridMeshBuilder::findCycles() +{ + std::vector> edges(m_edges.size()); + for (size_t i = 0; i < m_edges.size(); ++i) { + const auto &source = m_edges[i]; + edges[i] = std::make_pair(source.firstNodeIndex, source.secondNodeIndex); + } + CycleFinder cycleFinder(m_nodePositions, edges); + cycleFinder.find(); + m_cycles = cycleFinder.getCycles(); +} diff --git a/src/gridmeshbuilder.h b/src/gridmeshbuilder.h new file mode 100644 index 00000000..19073743 --- /dev/null +++ b/src/gridmeshbuilder.h @@ -0,0 +1,62 @@ +#ifndef DUST3D_GRID_MESH_BUILDER_H +#define DUST3D_GRID_MESH_BUILDER_H +#include +#include +#include +#include "regionfiller.h" + +class GridMeshBuilder +{ +public: + struct Node + { + QVector3D position; + float radius; + size_t source; + std::vector neighborIndices; + }; + + struct Edge + { + size_t firstNodeIndex; + size_t secondNodeIndex; + }; + + size_t addNode(const QVector3D &position, float radius); + size_t addEdge(size_t firstNodeIndex, size_t secondNodeIndex); + void setSubdived(bool subdived); + void build(); + const std::vector &getGeneratedPositions(); + const std::vector &getGeneratedSources(); + const std::vector> &getGeneratedFaces(); + +private: + std::vector m_nodes; + std::vector m_edges; + std::vector m_generatedVertices; + std::vector m_generatedPositions; + std::vector m_generatedSources; + std::vector> m_generatedFaces; + std::vector m_generatedFaceSourceCycles; + std::vector m_nodeVertices; + std::vector m_nodePositions; + std::vector> m_cycles; + std::map, size_t> m_halfEdgeMap; + std::vector m_nodeNormals; + float m_polylineAngleChangeThreshold = 35; + float m_meshTargetEdgeSize = 0.04; + size_t m_maxBigRingSize = 8; + bool m_subdived = false; + void applyModifiers(); + void prepareNodeVertices(); + void findCycles(); + void splitCycleToPolylines(const std::vector &cycle, + std::vector> *polylines); + void generateFaces(); + void extrude(); + void calculateNormals(); + void removeBigRingFaces(); +}; + +#endif + diff --git a/src/logbrowser.cpp b/src/logbrowser.cpp index 5f15a015..4c8ae425 100644 --- a/src/logbrowser.cpp +++ b/src/logbrowser.cpp @@ -35,6 +35,8 @@ bool LogBrowser::isDialogVisible() void LogBrowser::outputMessage(QtMsgType type, const QString &msg, const QString &source, int line) { - //printf("%s\n", msg.toUtf8().constData()); +#ifdef IN_DEVELOPMENT + printf("%s\n", msg.toUtf8().constData()); +#endif emit sendMessage(type, msg, source, line); } diff --git a/thirdparty/nodemesh/nodemesh/combiner.cpp b/src/meshcombiner.cpp similarity index 89% rename from thirdparty/nodemesh/nodemesh/combiner.cpp rename to src/meshcombiner.cpp index 357d371d..43179071 100644 --- a/thirdparty/nodemesh/nodemesh/combiner.cpp +++ b/src/meshcombiner.cpp @@ -1,20 +1,16 @@ -#include -#include -#include -#include #include #include #include #include #include +#include "meshcombiner.h" +#include "positionkey.h" +#include "booleanmesh.h" typedef CGAL::Exact_predicates_inexact_constructions_kernel CgalKernel; typedef CGAL::Surface_mesh CgalMesh; -namespace nodemesh -{ - -Combiner::Mesh::Mesh(const std::vector &vertices, const std::vector> &faces, bool removeSelfIntersects) +MeshCombiner::Mesh::Mesh(const std::vector &vertices, const std::vector> &faces, bool removeSelfIntersects) { CgalMesh *cgalMesh = nullptr; if (!faces.empty()) { @@ -50,7 +46,7 @@ Combiner::Mesh::Mesh(const std::vector &vertices, const std::vector &vertices, std::vector> &faces) const +void MeshCombiner::Mesh::fetch(std::vector &vertices, std::vector> &faces) const { CgalMesh *exactMesh = (CgalMesh *)m_privateData; if (nullptr == exactMesh) @@ -73,17 +69,17 @@ void Combiner::Mesh::fetch(std::vector &vertices, std::vector(exactMesh, vertices, faces); } -bool Combiner::Mesh::isNull() const +bool MeshCombiner::Mesh::isNull() const { return nullptr == m_privateData; } -bool Combiner::Mesh::isSelfIntersected() const +bool MeshCombiner::Mesh::isSelfIntersected() const { return m_isSelfIntersected; } -Combiner::Mesh *Combiner::combine(const Mesh &firstMesh, const Mesh &secondMesh, Method method, +MeshCombiner::Mesh *MeshCombiner::combine(const Mesh &firstMesh, const Mesh &secondMesh, Method method, std::vector> *combinedVerticesComeFrom) { CgalMesh *resultCgalMesh = nullptr; @@ -160,7 +156,7 @@ Combiner::Mesh *Combiner::combine(const Mesh &firstMesh, const Mesh &secondMesh, return mesh; } -void Combiner::Mesh::validate() +void MeshCombiner::Mesh::validate() { if (nullptr == m_privateData) return; @@ -171,5 +167,3 @@ void Combiner::Mesh::validate() m_privateData = nullptr; } } - -} diff --git a/thirdparty/nodemesh/nodemesh/combiner.h b/src/meshcombiner.h similarity index 88% rename from thirdparty/nodemesh/nodemesh/combiner.h rename to src/meshcombiner.h index 36e91bcd..90ce7337 100644 --- a/thirdparty/nodemesh/nodemesh/combiner.h +++ b/src/meshcombiner.h @@ -1,12 +1,9 @@ -#ifndef NODEMESH_COMBINER_H -#define NODEMESH_COMBINER_H +#ifndef DUST3D_COMBINER_H +#define DUST3D_COMBINER_H #include #include -namespace nodemesh -{ - -class Combiner +class MeshCombiner { public: enum class Method @@ -33,7 +30,7 @@ public: bool isNull() const; bool isSelfIntersected() const; - friend Combiner; + friend MeshCombiner; private: void *m_privateData = nullptr; @@ -46,6 +43,4 @@ public: std::vector> *combinedVerticesComeFrom=nullptr); }; -} - #endif diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index 9faf1cef..83e68745 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -3,10 +3,9 @@ #include #include #include -#include -#include -#include -#include +#include "strokemeshbuilder.h" +#include "strokemodifier.h" +#include "meshrecombiner.h" #include "meshgenerator.h" #include "util.h" #include "trianglesourcenoderesolve.h" @@ -15,6 +14,8 @@ #include "theme.h" #include "partbase.h" #include "imageforever.h" +#include "gridmeshbuilder.h" +#include "triangulate.h" MeshGenerator::MeshGenerator(Snapshot *snapshot) : m_snapshot(snapshot) @@ -73,7 +74,7 @@ Outcome *MeshGenerator::takeOutcome() return outcome; } -std::map *MeshGenerator::takeCutFaceTransforms() +std::map *MeshGenerator::takeCutFaceTransforms() { auto cutFaceTransforms = m_cutFaceTransforms; m_cutFaceTransforms = nullptr; @@ -318,7 +319,28 @@ void MeshGenerator::cutFaceStringToCutTemplate(const QString &cutFaceString, std } } -nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString, bool *hasError, bool addIntermediateNodes) +#ifdef IN_DEVELOPMENT +#include +#include +static void exportAsObj(const std::vector &positions, + const std::vector> &faces, + QTextStream *textStream) +{ + auto &stream = *textStream; + for (std::vector::const_iterator it = positions.begin() ; it != positions.end(); ++it) { + stream << "v " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl; + } + for (std::vector>::const_iterator it = faces.begin() ; it != faces.end(); ++it) { + stream << "f"; + for (std::vector::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) { + stream << " " << (1 + *subIt); + } + stream << endl; + } +} +#endif + +MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString, bool *hasError, bool addIntermediateNodes) { auto findPart = m_snapshot->parts.find(partIdString); if (findPart == m_snapshot->parts.end()) { @@ -342,12 +364,13 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt float hollowThickness = 0.0; auto target = PartTargetFromString(valueOfKeyInMapOrEmpty(part, "target").toUtf8().constData()); auto base = PartBaseFromString(valueOfKeyInMapOrEmpty(part, "base").toUtf8().constData()); - + bool gridded = isTrueValueString(valueOfKeyInMapOrEmpty(part, "gridded")); + QString cutFaceString = valueOfKeyInMapOrEmpty(part, "cutFace"); std::vector cutTemplate; cutFaceStringToCutTemplate(cutFaceString, cutTemplate); if (chamfered) - nodemesh::chamferFace2D(&cutTemplate); + chamferFace2D(&cutTemplate); QString cutRotationString = valueOfKeyInMapOrEmpty(part, "cutRotation"); if (!cutRotationString.isEmpty()) { @@ -483,13 +506,12 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt edges.insert({fromNodeIdString, toNodeIdString}); } + bool buildSucceed = false; std::map nodeIdStringToIndexMap; std::map nodeIndexToIdStringMap; - - nodemesh::Modifier *modifier = new nodemesh::Modifier; - - if (addIntermediateNodes) - modifier->enableIntermediateAddition(); + StrokeModifier *nodeMeshModifier = nullptr; + StrokeMeshBuilder *nodeMeshBuilder = nullptr; + GridMeshBuilder *gridMeshBuilder = nullptr; QString mirroredPartIdString; QUuid mirroredPartId; @@ -499,22 +521,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt m_cacheContext->partMirrorIdMap[mirroredPartIdString] = partIdString; } - for (const auto &nodeIt: nodeInfos) { - const auto &nodeIdString = nodeIt.first; - const auto &nodeInfo = nodeIt.second; - size_t nodeIndex = 0; - if (nodeInfo.hasCutFaceSettings) { - std::vector nodeCutTemplate; - cutFaceStringToCutTemplate(nodeInfo.cutFace, nodeCutTemplate); - if (chamfered) - nodemesh::chamferFace2D(&nodeCutTemplate); - nodeIndex = modifier->addNode(nodeInfo.position, nodeInfo.radius, nodeCutTemplate, nodeInfo.cutRotation); - } else { - nodeIndex = modifier->addNode(nodeInfo.position, nodeInfo.radius, cutTemplate, cutRotation); - } - nodeIdStringToIndexMap[nodeIdString] = nodeIndex; - nodeIndexToIdStringMap[nodeIndex] = nodeIdString; - + auto addNode = [&](const QString &nodeIdString, const NodeInfo &nodeInfo) { OutcomeNode outcomeNode; outcomeNode.partId = QUuid(partIdString); outcomeNode.nodeId = QUuid(nodeIdString); @@ -534,99 +541,174 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt outcomeNode.origin.setX(-nodeInfo.position.x()); partCache.outcomeNodes.push_back(outcomeNode); } - } + }; - for (const auto &edgeIt: edges) { - const QString &fromNodeIdString = edgeIt.first; - const QString &toNodeIdString = edgeIt.second; + if (gridded) { + gridMeshBuilder = new GridMeshBuilder; - auto findFromNodeIndex = nodeIdStringToIndexMap.find(fromNodeIdString); - if (findFromNodeIndex == nodeIdStringToIndexMap.end()) { - qDebug() << "Find from-node failed:" << fromNodeIdString; - continue; + for (const auto &nodeIt: nodeInfos) { + const auto &nodeIdString = nodeIt.first; + const auto &nodeInfo = nodeIt.second; + size_t nodeIndex = 0; + + nodeIndex = gridMeshBuilder->addNode(nodeInfo.position, nodeInfo.radius); + + nodeIdStringToIndexMap[nodeIdString] = nodeIndex; + nodeIndexToIdStringMap[nodeIndex] = nodeIdString; + + addNode(nodeIdString, nodeInfo); } - auto findToNodeIndex = nodeIdStringToIndexMap.find(toNodeIdString); - if (findToNodeIndex == nodeIdStringToIndexMap.end()) { - qDebug() << "Find to-node failed:" << toNodeIdString; - continue; + for (const auto &edgeIt: edges) { + const QString &fromNodeIdString = edgeIt.first; + const QString &toNodeIdString = edgeIt.second; + + auto findFromNodeIndex = nodeIdStringToIndexMap.find(fromNodeIdString); + if (findFromNodeIndex == nodeIdStringToIndexMap.end()) { + qDebug() << "Find from-node failed:" << fromNodeIdString; + continue; + } + + auto findToNodeIndex = nodeIdStringToIndexMap.find(toNodeIdString); + if (findToNodeIndex == nodeIdStringToIndexMap.end()) { + qDebug() << "Find to-node failed:" << toNodeIdString; + continue; + } + + gridMeshBuilder->addEdge(findFromNodeIndex->second, findToNodeIndex->second); } - modifier->addEdge(findFromNodeIndex->second, findToNodeIndex->second); - } - - if (subdived) - modifier->subdivide(); - - if (rounded) - modifier->roundEnd(); - - modifier->finalize(); - - nodemesh::Builder *builder = new nodemesh::Builder; - builder->setDeformThickness(deformThickness); - builder->setDeformWidth(deformWidth); - builder->setDeformMapScale(deformMapScale); - builder->setHollowThickness(hollowThickness); - if (nullptr != deformImage) - builder->setDeformMapImage(deformImage); - if (PartBase::YZ == base) { - builder->enableBaseNormalOnX(false); - } else if (PartBase::Average == base) { - builder->enableBaseNormalAverage(true); - } else if (PartBase::XY == base) { - builder->enableBaseNormalOnZ(false); - } else if (PartBase::ZX == base) { - builder->enableBaseNormalOnY(false); - } - - std::vector builderNodeIndices; - for (const auto &node: modifier->nodes()) { - auto nodeIndex = builder->addNode(node.position, node.radius, node.cutTemplate, node.cutRotation); - builder->setNodeOriginInfo(nodeIndex, node.nearOriginNodeIndex, node.farOriginNodeIndex); - builderNodeIndices.push_back(nodeIndex); + if (subdived) + gridMeshBuilder->setSubdived(true); + gridMeshBuilder->build(); + buildSucceed = true; - const auto &originNodeIdString = nodeIndexToIdStringMap[node.originNodeIndex]; + partCache.vertices = gridMeshBuilder->getGeneratedPositions(); + partCache.faces = gridMeshBuilder->getGeneratedFaces(); - OutcomePaintNode paintNode; - paintNode.originNodeIndex = node.originNodeIndex; - paintNode.originNodeId = QUuid(originNodeIdString); - paintNode.radius = node.radius; - paintNode.origin = node.position; + for (size_t i = 0; i < partCache.vertices.size(); ++i) { + const auto &position = partCache.vertices[i]; + const auto &nodeIndex = gridMeshBuilder->getGeneratedSources()[i]; + const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex]; + partCache.outcomeNodeVertices.push_back({position, {partIdString, nodeIdString}}); + } + } else { + nodeMeshModifier = new StrokeModifier; - partCache.outcomePaintMap.paintNodes.push_back(paintNode); - } - for (const auto &edge: modifier->edges()) - builder->addEdge(edge.firstNodeIndex, edge.secondNodeIndex); - bool buildSucceed = builder->build(); - - partCache.vertices = builder->generatedVertices(); - partCache.faces = builder->generatedFaces(); - for (size_t i = 0; i < partCache.vertices.size(); ++i) { - const auto &position = partCache.vertices[i]; - const auto &source = builder->generatedVerticesSourceNodeIndices()[i]; - size_t nodeIndex = modifier->nodes()[source].originNodeIndex; - const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex]; - partCache.outcomeNodeVertices.push_back({position, {partIdString, nodeIdString}}); + if (addIntermediateNodes) + nodeMeshModifier->enableIntermediateAddition(); - auto &paintNode = partCache.outcomePaintMap.paintNodes[source]; - paintNode.vertices.push_back(position); - } - - for (size_t i = 0; i < partCache.outcomePaintMap.paintNodes.size(); ++i) { - auto &paintNode = partCache.outcomePaintMap.paintNodes[i]; - paintNode.baseNormal = builder->nodeBaseNormal(i); - paintNode.direction = builder->nodeTraverseDirection(i); - paintNode.order = builder->nodeTraverseOrder(i); + for (const auto &nodeIt: nodeInfos) { + const auto &nodeIdString = nodeIt.first; + const auto &nodeInfo = nodeIt.second; + size_t nodeIndex = 0; + if (nodeInfo.hasCutFaceSettings) { + std::vector nodeCutTemplate; + cutFaceStringToCutTemplate(nodeInfo.cutFace, nodeCutTemplate); + if (chamfered) + chamferFace2D(&nodeCutTemplate); + nodeIndex = nodeMeshModifier->addNode(nodeInfo.position, nodeInfo.radius, nodeCutTemplate, nodeInfo.cutRotation); + } else { + nodeIndex = nodeMeshModifier->addNode(nodeInfo.position, nodeInfo.radius, cutTemplate, cutRotation); + } + nodeIdStringToIndexMap[nodeIdString] = nodeIndex; + nodeIndexToIdStringMap[nodeIndex] = nodeIdString; + + addNode(nodeIdString, nodeInfo); + } - partCache.outcomeNodes[paintNode.originNodeIndex].direction = paintNode.direction; + for (const auto &edgeIt: edges) { + const QString &fromNodeIdString = edgeIt.first; + const QString &toNodeIdString = edgeIt.second; + + auto findFromNodeIndex = nodeIdStringToIndexMap.find(fromNodeIdString); + if (findFromNodeIndex == nodeIdStringToIndexMap.end()) { + qDebug() << "Find from-node failed:" << fromNodeIdString; + continue; + } + + auto findToNodeIndex = nodeIdStringToIndexMap.find(toNodeIdString); + if (findToNodeIndex == nodeIdStringToIndexMap.end()) { + qDebug() << "Find to-node failed:" << toNodeIdString; + continue; + } + + nodeMeshModifier->addEdge(findFromNodeIndex->second, findToNodeIndex->second); + } + + if (subdived) + nodeMeshModifier->subdivide(); + + if (rounded) + nodeMeshModifier->roundEnd(); + + nodeMeshModifier->finalize(); + + nodeMeshBuilder = new StrokeMeshBuilder; + nodeMeshBuilder->setDeformThickness(deformThickness); + nodeMeshBuilder->setDeformWidth(deformWidth); + nodeMeshBuilder->setDeformMapScale(deformMapScale); + nodeMeshBuilder->setHollowThickness(hollowThickness); + if (nullptr != deformImage) + nodeMeshBuilder->setDeformMapImage(deformImage); + if (PartBase::YZ == base) { + nodeMeshBuilder->enableBaseNormalOnX(false); + } else if (PartBase::Average == base) { + nodeMeshBuilder->enableBaseNormalAverage(true); + } else if (PartBase::XY == base) { + nodeMeshBuilder->enableBaseNormalOnZ(false); + } else if (PartBase::ZX == base) { + nodeMeshBuilder->enableBaseNormalOnY(false); + } + + std::vector builderNodeIndices; + for (const auto &node: nodeMeshModifier->nodes()) { + auto nodeIndex = nodeMeshBuilder->addNode(node.position, node.radius, node.cutTemplate, node.cutRotation); + nodeMeshBuilder->setNodeOriginInfo(nodeIndex, node.nearOriginNodeIndex, node.farOriginNodeIndex); + builderNodeIndices.push_back(nodeIndex); + + const auto &originNodeIdString = nodeIndexToIdStringMap[node.originNodeIndex]; + + OutcomePaintNode paintNode; + paintNode.originNodeIndex = node.originNodeIndex; + paintNode.originNodeId = QUuid(originNodeIdString); + paintNode.radius = node.radius; + paintNode.origin = node.position; + + partCache.outcomePaintMap.paintNodes.push_back(paintNode); + } + for (const auto &edge: nodeMeshModifier->edges()) + nodeMeshBuilder->addEdge(edge.firstNodeIndex, edge.secondNodeIndex); + buildSucceed = nodeMeshBuilder->build(); + + partCache.vertices = nodeMeshBuilder->generatedVertices(); + partCache.faces = nodeMeshBuilder->generatedFaces(); + for (size_t i = 0; i < partCache.vertices.size(); ++i) { + const auto &position = partCache.vertices[i]; + const auto &source = nodeMeshBuilder->generatedVerticesSourceNodeIndices()[i]; + size_t nodeIndex = nodeMeshModifier->nodes()[source].originNodeIndex; + const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex]; + partCache.outcomeNodeVertices.push_back({position, {partIdString, nodeIdString}}); + + auto &paintNode = partCache.outcomePaintMap.paintNodes[source]; + paintNode.vertices.push_back(position); + } + + for (size_t i = 0; i < partCache.outcomePaintMap.paintNodes.size(); ++i) { + auto &paintNode = partCache.outcomePaintMap.paintNodes[i]; + paintNode.baseNormal = nodeMeshBuilder->nodeBaseNormal(i); + paintNode.direction = nodeMeshBuilder->nodeTraverseDirection(i); + paintNode.order = nodeMeshBuilder->nodeTraverseOrder(i); + + partCache.outcomeNodes[paintNode.originNodeIndex].direction = paintNode.direction; + } } bool hasMeshError = false; - nodemesh::Combiner::Mesh *mesh = nullptr; + MeshCombiner::Mesh *mesh = nullptr; if (buildSucceed) { - mesh = new nodemesh::Combiner::Mesh(partCache.vertices, partCache.faces, false); + mesh = new MeshCombiner::Mesh(partCache.vertices, partCache.faces, false); if (!mesh->isNull()) { if (xMirrored) { std::vector xMirroredVertices; @@ -634,8 +716,13 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt makeXmirror(partCache.vertices, partCache.faces, &xMirroredVertices, &xMirroredFaces); for (size_t i = 0; i < xMirroredVertices.size(); ++i) { const auto &position = xMirroredVertices[i]; - const auto &source = builder->generatedVerticesSourceNodeIndices()[i]; - size_t nodeIndex = modifier->nodes()[source].originNodeIndex; + size_t nodeIndex = 0; + if (gridded) { + nodeIndex = gridMeshBuilder->getGeneratedSources()[i]; + } else { + const auto &source = nodeMeshBuilder->generatedVerticesSourceNodeIndices()[i]; + nodeIndex = nodeMeshModifier->nodes()[source].originNodeIndex; + } const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex]; partCache.outcomeNodeVertices.push_back({position, {mirroredPartIdString, nodeIdString}}); } @@ -648,9 +735,9 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt it += xMirrorStart; partCache.faces.push_back(newFace); } - nodemesh::Combiner::Mesh *xMirroredMesh = new nodemesh::Combiner::Mesh(xMirroredVertices, xMirroredFaces); - nodemesh::Combiner::Mesh *newMesh = combineTwoMeshes(*mesh, - *xMirroredMesh, nodemesh::Combiner::Method::Union); + MeshCombiner::Mesh *xMirroredMesh = new MeshCombiner::Mesh(xMirroredVertices, xMirroredFaces); + MeshCombiner::Mesh *newMesh = combineTwoMeshes(*mesh, + *xMirroredMesh, MeshCombiner::Method::Union); delete xMirroredMesh; if (newMesh && !newMesh->isNull()) { delete mesh; @@ -677,18 +764,29 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt std::vector partPreviewVertices; QColor partPreviewColor = partColor; if (nullptr != mesh) { - partCache.mesh = new nodemesh::Combiner::Mesh(*mesh); + partCache.mesh = new MeshCombiner::Mesh(*mesh); mesh->fetch(partPreviewVertices, partCache.previewTriangles); partCache.isSucceed = true; } if (partCache.previewTriangles.empty()) { partPreviewVertices = partCache.vertices; - nodemesh::triangulate(partPreviewVertices, partCache.faces, partCache.previewTriangles); + triangulate(partPreviewVertices, partCache.faces, partCache.previewTriangles); +#ifdef IN_DEVELOPMENT + { + QFile file("/Users/jeremy/Desktop/dust3d_debug.obj"); + if (file.open(QIODevice::WriteOnly)) { + QTextStream stream(&file); + exportAsObj(partPreviewVertices, + partCache.previewTriangles, + &stream); + } + } +#endif partPreviewColor = Qt::red; partCache.isSucceed = false; } - nodemesh::trim(&partPreviewVertices, true); + trim(&partPreviewVertices, true); for (auto &it: partPreviewVertices) { it *= 2.0; } @@ -714,8 +812,10 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt partPreviewColor); } - delete builder; - delete modifier; + delete nodeMeshBuilder; + delete nodeMeshModifier; + + delete gridMeshBuilder; if (mesh && mesh->isNull()) { delete mesh; @@ -791,9 +891,9 @@ QString MeshGenerator::componentColorName(const std::map *comp return QString(); } -nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &componentIdString, CombineMode *combineMode) +MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &componentIdString, CombineMode *combineMode) { - nodemesh::Combiner::Mesh *mesh = nullptr; + MeshCombiner::Mesh *mesh = nullptr; QUuid componentId; const std::map *component = &m_snapshot->rootComponent; @@ -814,7 +914,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com if (m_cacheEnabled) { if (m_dirtyComponentIds.find(componentIdString) == m_dirtyComponentIds.end()) { if (nullptr != componentCache.mesh) - return new nodemesh::Combiner::Mesh(*componentCache.mesh); + return new MeshCombiner::Mesh(*componentCache.mesh); } } @@ -878,7 +978,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com combineGroups[currentGroupIndex].second.push_back({childIdString, colorName}); } // Secondly, sub group by color - std::vector> groupMeshes; + std::vector> groupMeshes; for (const auto &group: combineGroups) { std::set used; std::vector> componentIdStrings; @@ -914,13 +1014,13 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com componentIdStrings[currentSubGroupIndex].push_back(group.second[j].first); } } - std::vector> multipleMeshes; + std::vector> multipleMeshes; QStringList subGroupMeshIdStringList; for (const auto &it: componentIdStrings) { QStringList componentChildGroupIdStringList; for (const auto &componentChildGroupIdString: it) componentChildGroupIdStringList += componentChildGroupIdString; - nodemesh::Combiner::Mesh *childMesh = combineComponentChildGroupMesh(it, componentCache); + MeshCombiner::Mesh *childMesh = combineComponentChildGroupMesh(it, componentCache); if (nullptr == childMesh) continue; if (childMesh->isNull()) { @@ -931,7 +1031,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com subGroupMeshIdStringList += componentChildGroupIdStringListString; multipleMeshes.push_back(std::make_tuple(childMesh, CombineMode::Normal, componentChildGroupIdStringListString)); } - nodemesh::Combiner::Mesh *subGroupMesh = combineMultipleMeshes(multipleMeshes, foundColorSolubilitySetting); + MeshCombiner::Mesh *subGroupMesh = combineMultipleMeshes(multipleMeshes, foundColorSolubilitySetting); if (nullptr == subGroupMesh) continue; groupMeshes.push_back(std::make_tuple(subGroupMesh, group.first, subGroupMeshIdStringList.join("&"))); @@ -940,7 +1040,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com } if (nullptr != mesh) - componentCache.mesh = new nodemesh::Combiner::Mesh(*mesh); + componentCache.mesh = new MeshCombiner::Mesh(*mesh); if (nullptr != mesh && mesh->isNull()) { delete mesh; @@ -950,13 +1050,13 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com return mesh; } -nodemesh::Combiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector> &multipleMeshes, bool recombine) +MeshCombiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector> &multipleMeshes, bool recombine) { - nodemesh::Combiner::Mesh *mesh = nullptr; + MeshCombiner::Mesh *mesh = nullptr; QString meshIdStrings; for (const auto &it: multipleMeshes) { const auto &childCombineMode = std::get<1>(it); - nodemesh::Combiner::Mesh *subMesh = std::get<0>(it); + MeshCombiner::Mesh *subMesh = std::get<0>(it); const QString &subMeshIdString = std::get<2>(it); //qDebug() << "Combine mode:" << CombineModeToString(childCombineMode); if (nullptr == subMesh) { @@ -973,18 +1073,18 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector meshIdStrings = subMeshIdString; } else { auto combinerMethod = childCombineMode == CombineMode::Inversion ? - nodemesh::Combiner::Method::Diff : nodemesh::Combiner::Method::Union; - auto combinerMethodString = combinerMethod == nodemesh::Combiner::Method::Union ? + MeshCombiner::Method::Diff : MeshCombiner::Method::Union; + auto combinerMethodString = combinerMethod == MeshCombiner::Method::Union ? "+" : "-"; meshIdStrings += combinerMethodString + subMeshIdString; if (recombine) meshIdStrings += "!"; - nodemesh::Combiner::Mesh *newMesh = nullptr; + MeshCombiner::Mesh *newMesh = nullptr; auto findCached = m_cacheContext->cachedCombination.find(meshIdStrings); if (findCached != m_cacheContext->cachedCombination.end()) { if (nullptr != findCached->second) { //qDebug() << "Use cached combination:" << meshIdStrings; - newMesh = new nodemesh::Combiner::Mesh(*findCached->second); + newMesh = new MeshCombiner::Mesh(*findCached->second); } } else { newMesh = combineTwoMeshes(*mesh, @@ -993,7 +1093,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector recombine); delete subMesh; if (nullptr != newMesh) - m_cacheContext->cachedCombination.insert({meshIdStrings, new nodemesh::Combiner::Mesh(*newMesh)}); + m_cacheContext->cachedCombination.insert({meshIdStrings, new MeshCombiner::Mesh(*newMesh)}); else m_cacheContext->cachedCombination.insert({meshIdStrings, nullptr}); //qDebug() << "Add cached combination:" << meshIdStrings; @@ -1015,12 +1115,12 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector return mesh; } -nodemesh::Combiner::Mesh *MeshGenerator::combineComponentChildGroupMesh(const std::vector &componentIdStrings, GeneratedComponent &componentCache) +MeshCombiner::Mesh *MeshGenerator::combineComponentChildGroupMesh(const std::vector &componentIdStrings, GeneratedComponent &componentCache) { - std::vector> multipleMeshes; + std::vector> multipleMeshes; for (const auto &childIdString: componentIdStrings) { CombineMode childCombineMode = CombineMode::Normal; - nodemesh::Combiner::Mesh *subMesh = combineComponentMesh(childIdString, &childCombineMode); + MeshCombiner::Mesh *subMesh = combineComponentMesh(childIdString, &childCombineMode); if (CombineMode::Uncombined == childCombineMode) { delete subMesh; @@ -1049,29 +1149,29 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentChildGroupMesh(const st return combineMultipleMeshes(multipleMeshes); } -nodemesh::Combiner::Mesh *MeshGenerator::combineTwoMeshes(const nodemesh::Combiner::Mesh &first, const nodemesh::Combiner::Mesh &second, - nodemesh::Combiner::Method method, +MeshCombiner::Mesh *MeshGenerator::combineTwoMeshes(const MeshCombiner::Mesh &first, const MeshCombiner::Mesh &second, + MeshCombiner::Method method, bool recombine) { if (first.isNull() || second.isNull()) return nullptr; - std::vector> combinedVerticesSources; - nodemesh::Combiner::Mesh *newMesh = nodemesh::Combiner::combine(first, + std::vector> combinedVerticesSources; + MeshCombiner::Mesh *newMesh = MeshCombiner::combine(first, second, method, &combinedVerticesSources); if (nullptr == newMesh) return nullptr; if (!newMesh->isNull() && recombine) { - nodemesh::Recombiner recombiner; + MeshRecombiner recombiner; std::vector combinedVertices; std::vector> combinedFaces; newMesh->fetch(combinedVertices, combinedFaces); recombiner.setVertices(&combinedVertices, &combinedVerticesSources); recombiner.setFaces(&combinedFaces); if (recombiner.recombine()) { - if (nodemesh::isManifold(recombiner.regeneratedFaces())) { - nodemesh::Combiner::Mesh *reMesh = new nodemesh::Combiner::Mesh(recombiner.regeneratedVertices(), recombiner.regeneratedFaces(), false); + if (isManifold(recombiner.regeneratedFaces())) { + MeshCombiner::Mesh *reMesh = new MeshCombiner::Mesh(recombiner.regeneratedVertices(), recombiner.regeneratedFaces(), false); if (!reMesh->isNull() && !reMesh->isSelfIntersected()) { delete newMesh; newMesh = reMesh; @@ -1099,18 +1199,18 @@ void MeshGenerator::makeXmirror(const std::vector &sourceVertices, co } void MeshGenerator::collectSharedQuadEdges(const std::vector &vertices, const std::vector> &faces, - std::set> *sharedQuadEdges) + std::set> *sharedQuadEdges) { for (const auto &face: faces) { if (face.size() != 4) continue; sharedQuadEdges->insert({ - nodemesh::PositionKey(vertices[face[0]]), - nodemesh::PositionKey(vertices[face[2]]) + PositionKey(vertices[face[0]]), + PositionKey(vertices[face[2]]) }); sharedQuadEdges->insert({ - nodemesh::PositionKey(vertices[face[1]]), - nodemesh::PositionKey(vertices[face[3]]) + PositionKey(vertices[face[1]]), + PositionKey(vertices[face[3]]) }); } } @@ -1223,7 +1323,7 @@ void MeshGenerator::generate() do { std::vector weldedVertices; std::vector> weldedFaces; - affectedNum = nodemesh::weldSeam(combinedVertices, combinedFaces, + affectedNum = weldSeam(combinedVertices, combinedFaces, 0.025, componentCache.noneSeamVertices, weldedVertices, weldedFaces); combinedVertices = weldedVertices; @@ -1234,6 +1334,18 @@ void MeshGenerator::generate() recoverQuads(combinedVertices, combinedFaces, componentCache.sharedQuadEdges, m_outcome->triangleAndQuads); +#ifdef IN_DEVELOPMENT + { + QFile file("/Users/jeremy/Desktop/dust3d_debug.obj"); + if (file.open(QIODevice::WriteOnly)) { + QTextStream stream(&file); + exportAsObj(combinedVertices, + m_outcome->triangleAndQuads, + &stream); + } + } +#endif + m_outcome->nodes = componentCache.outcomeNodes; m_outcome->nodeVertices = componentCache.outcomeNodeVertices; m_outcome->vertices = combinedVertices; @@ -1244,6 +1356,25 @@ void MeshGenerator::generate() // Recursively check uncombined components collectUncombinedComponent(QUuid().toString()); + // Collect errored parts + for (const auto &it: m_cacheContext->parts) { + if (!it.second.isSucceed) { + auto vertexStartIndex = m_outcome->vertices.size(); + auto updateVertexIndices = [=](std::vector> &faces) { + for (auto &it: faces) { + for (auto &subIt: it) + subIt += vertexStartIndex; + } + }; + + auto errorTriangleAndQuads = it.second.faces; + updateVertexIndices(errorTriangleAndQuads); + + m_outcome->vertices.insert(m_outcome->vertices.end(), it.second.vertices.begin(), it.second.vertices.end()); + m_outcome->triangleAndQuads.insert(m_outcome->triangleAndQuads.end(), errorTriangleAndQuads.begin(), errorTriangleAndQuads.end()); + } + } + auto postprocessOutcome = [this](Outcome *outcome) { std::vector combinedFacesNormals; for (const auto &face: outcome->triangles) { @@ -1257,7 +1388,7 @@ void MeshGenerator::generate() outcome->triangleNormals = combinedFacesNormals; std::vector> sourceNodes; - triangleSourceNodeResolve(*outcome, sourceNodes); + triangleSourceNodeResolve(*outcome, sourceNodes, &outcome->vertexSourceNodes); outcome->setTriangleSourceNodes(sourceNodes); std::map, QColor> sourceNodeToColorMap; @@ -1343,7 +1474,7 @@ void MeshGenerator::generateSmoothTriangleVertexNormals(const std::vector> *triangleVertexNormals) { std::vector smoothNormals; - nodemesh::angleSmooth(vertices, + angleSmooth(vertices, triangles, triangleNormals, m_smoothShadingThresholdAngleDegrees, diff --git a/src/meshgenerator.h b/src/meshgenerator.h index 15660b09..2ba64ff0 100644 --- a/src/meshgenerator.h +++ b/src/meshgenerator.h @@ -3,10 +3,10 @@ #include #include #include -#include -#include -#include #include +#include "meshcombiner.h" +#include "positionkey.h" +#include "strokemeshbuilder.h" #include "outcome.h" #include "snapshot.h" #include "combinemode.h" @@ -19,7 +19,7 @@ public: { delete mesh; }; - nodemesh::Combiner::Mesh *mesh = nullptr; + MeshCombiner::Mesh *mesh = nullptr; std::vector vertices; std::vector> faces; std::vector outcomeNodes; @@ -36,9 +36,9 @@ public: { delete mesh; }; - nodemesh::Combiner::Mesh *mesh = nullptr; - std::set> sharedQuadEdges; - std::set noneSeamVertices; + MeshCombiner::Mesh *mesh = nullptr; + std::set> sharedQuadEdges; + std::set noneSeamVertices; std::vector outcomeNodes; std::vector>> outcomeNodeVertices; std::vector outcomePaintMaps; @@ -50,7 +50,7 @@ public: std::map components; std::map parts; std::map partMirrorIdMap; - std::map cachedCombination; + std::map cachedCombination; }; class MeshGenerator : public QObject @@ -64,7 +64,7 @@ public: MeshLoader *takePartPreviewMesh(const QUuid &partId); const std::set &generatedPreviewPartIds(); Outcome *takeOutcome(); - std::map *takeCutFaceTransforms(); + std::map *takeCutFaceTransforms(); std::map> *takeNodesCutFaces(); void generate(); void setGeneratedCacheContext(GeneratedCacheContext *cacheContext); @@ -95,7 +95,7 @@ private: bool m_isSucceed = false; bool m_cacheEnabled = false; float m_smoothShadingThresholdAngleDegrees = 60; - std::map *m_cutFaceTransforms = nullptr; + std::map *m_cutFaceTransforms = nullptr; std::map> *m_nodesCutFaces = nullptr; quint64 m_id = 0; @@ -104,23 +104,23 @@ private: bool checkIsPartDirty(const QString &partIdString); bool checkIsPartDependencyDirty(const QString &partIdString); void checkDirtyFlags(); - nodemesh::Combiner::Mesh *combinePartMesh(const QString &partIdString, bool *hasError, bool addIntermediateNodes=true); - nodemesh::Combiner::Mesh *combineComponentMesh(const QString &componentIdString, CombineMode *combineMode); + MeshCombiner::Mesh *combinePartMesh(const QString &partIdString, bool *hasError, bool addIntermediateNodes=true); + MeshCombiner::Mesh *combineComponentMesh(const QString &componentIdString, CombineMode *combineMode); void makeXmirror(const std::vector &sourceVertices, const std::vector> &sourceFaces, std::vector *destVertices, std::vector> *destFaces); void collectSharedQuadEdges(const std::vector &vertices, const std::vector> &faces, - std::set> *sharedQuadEdges); - nodemesh::Combiner::Mesh *combineTwoMeshes(const nodemesh::Combiner::Mesh &first, const nodemesh::Combiner::Mesh &second, - nodemesh::Combiner::Method method, + std::set> *sharedQuadEdges); + MeshCombiner::Mesh *combineTwoMeshes(const MeshCombiner::Mesh &first, const MeshCombiner::Mesh &second, + MeshCombiner::Method method, bool recombine=true); void generateSmoothTriangleVertexNormals(const std::vector &vertices, const std::vector> &triangles, const std::vector &triangleNormals, std::vector> *triangleVertexNormals); const std::map *findComponent(const QString &componentIdString); CombineMode componentCombineMode(const std::map *component); - nodemesh::Combiner::Mesh *combineComponentChildGroupMesh(const std::vector &componentIdStrings, + MeshCombiner::Mesh *combineComponentChildGroupMesh(const std::vector &componentIdStrings, GeneratedComponent &componentCache); - nodemesh::Combiner::Mesh *combineMultipleMeshes(const std::vector> &multipleMeshes, bool recombine=true); + MeshCombiner::Mesh *combineMultipleMeshes(const std::vector> &multipleMeshes, bool recombine=true); QString componentColorName(const std::map *component); void collectUncombinedComponent(const QString &componentIdString); void cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector &cutTemplate); diff --git a/thirdparty/nodemesh/nodemesh/recombiner.cpp b/src/meshrecombiner.cpp similarity index 87% rename from thirdparty/nodemesh/nodemesh/recombiner.cpp rename to src/meshrecombiner.cpp index 12d6569e..7e2118f1 100644 --- a/thirdparty/nodemesh/nodemesh/recombiner.cpp +++ b/src/meshrecombiner.cpp @@ -1,30 +1,27 @@ -#include -#include -#include -#include #include #include #include #include +#include "meshrecombiner.h" +#include "positionkey.h" +#include "meshwrapper.h" +#include "util.h" #define MAX_EDGE_LOOP_LENGTH 1000 -namespace nodemesh -{ - -void Recombiner::setVertices(const std::vector *vertices, - const std::vector> *verticesSourceIndices) +void MeshRecombiner::setVertices(const std::vector *vertices, + const std::vector> *verticesSourceIndices) { m_vertices = vertices; m_verticesSourceIndices = verticesSourceIndices; } -void Recombiner::setFaces(const std::vector> *faces) +void MeshRecombiner::setFaces(const std::vector> *faces) { m_faces = faces; } -bool Recombiner::convertHalfEdgesToEdgeLoops(const std::vector> &halfEdges, +bool MeshRecombiner::convertHalfEdgesToEdgeLoops(const std::vector> &halfEdges, std::vector> *edgeLoops) { std::map vertexLinkMap; @@ -68,7 +65,7 @@ bool Recombiner::convertHalfEdgesToEdgeLoops(const std::vector> &seamEdges, +size_t MeshRecombiner::splitSeamVerticesToIslands(const std::map> &seamEdges, std::map *vertexToIslandMap) { std::set visited; @@ -98,7 +95,7 @@ size_t Recombiner::splitSeamVerticesToIslands(const std::map, size_t> &halfEdgeToFaceMap) +bool MeshRecombiner::buildHalfEdgeToFaceMap(std::map, size_t> &halfEdgeToFaceMap) { bool succeed = true; for (size_t faceIndex = 0; faceIndex < m_faces->size(); ++faceIndex) { @@ -115,7 +112,7 @@ bool Recombiner::buildHalfEdgeToFaceMap(std::map, size return succeed; } -bool Recombiner::recombine() +bool MeshRecombiner::recombine() { buildHalfEdgeToFaceMap(m_halfEdgeToFaceMap); @@ -124,10 +121,10 @@ bool Recombiner::recombine() for (size_t i = 0; i < face.size(); ++i) { const auto &index = face[i]; auto source = (*m_verticesSourceIndices)[index]; - if (Combiner::Source::None == source.first) { + if (MeshCombiner::Source::None == source.first) { auto next = face[(i + 1) % face.size()]; auto nextSource = (*m_verticesSourceIndices)[next]; - if (Combiner::Source::None == nextSource.first) { + if (MeshCombiner::Source::None == nextSource.first) { seamLink[index].push_back(next); } } @@ -146,13 +143,13 @@ bool Recombiner::recombine() for (size_t i = 0; i < face.size(); ++i) { const auto &index = face[i]; auto source = (*m_verticesSourceIndices)[index]; - if (Combiner::Source::None == source.first) { + if (MeshCombiner::Source::None == source.first) { const auto &findIsland = seamVertexToIslandMap.find(index); if (findIsland != seamVertexToIslandMap.end()) { containsSeamVertex = true; island = findIsland->second; } - } else if (Combiner::Source::First == source.first) { + } else if (MeshCombiner::Source::First == source.first) { inFirstGroup = true; } } @@ -222,7 +219,7 @@ bool Recombiner::recombine() return true; } -size_t Recombiner::adjustTrianglesFromSeam(std::vector &edgeLoop, size_t seamIndex) +size_t MeshRecombiner::adjustTrianglesFromSeam(std::vector &edgeLoop, size_t seamIndex) { if (edgeLoop.size() <= 3) return 0; @@ -267,7 +264,7 @@ size_t Recombiner::adjustTrianglesFromSeam(std::vector &edgeLoop, size_t return ignored.size(); } -size_t Recombiner::otherVertexOfTriangle(const std::vector &face, const std::vector &indices) +size_t MeshRecombiner::otherVertexOfTriangle(const std::vector &face, const std::vector &indices) { for (const auto &v: face) { for (const auto &u: indices) { @@ -278,7 +275,7 @@ size_t Recombiner::otherVertexOfTriangle(const std::vector &face, const return face[0]; } -void Recombiner::copyNonSeamFacesAsRegenerated() +void MeshRecombiner::copyNonSeamFacesAsRegenerated() { for (size_t faceIndex = 0; faceIndex < m_faces->size(); ++faceIndex) { const auto &findFaceInSeam = m_facesInSeamArea.find(faceIndex); @@ -289,22 +286,22 @@ void Recombiner::copyNonSeamFacesAsRegenerated() } } -const std::vector &Recombiner::regeneratedVertices() +const std::vector &MeshRecombiner::regeneratedVertices() { return m_regeneratedVertices; } -const std::vector> &Recombiner::regeneratedVerticesSourceIndices() +const std::vector> &MeshRecombiner::regeneratedVerticesSourceIndices() { return m_regeneratedVerticesSourceIndices; } -const std::vector> &Recombiner::regeneratedFaces() +const std::vector> &MeshRecombiner::regeneratedFaces() { return m_regeneratedFaces; } -size_t Recombiner::nearestIndex(const QVector3D &position, const std::vector &edgeLoop) +size_t MeshRecombiner::nearestIndex(const QVector3D &position, const std::vector &edgeLoop) { float minDist2 = std::numeric_limits::max(); size_t choosenIndex = 0; @@ -318,7 +315,7 @@ size_t Recombiner::nearestIndex(const QVector3D &position, const std::vector &first, const std::vector &second) +bool MeshRecombiner::bridge(const std::vector &first, const std::vector &second) { const std::vector *large = &first; const std::vector *small = &second; @@ -373,16 +370,16 @@ bool Recombiner::bridge(const std::vector &first, const std::vector &small, const std::vector &large) +void MeshRecombiner::fillPairs(const std::vector &small, const std::vector &large) { size_t smallIndex = 0; size_t largeIndex = 0; while (smallIndex + 1 < small.size() || largeIndex + 1 < large.size()) { if (smallIndex + 1 < small.size() && largeIndex + 1 < large.size()) { - float angleOnSmallEdgeLoop = angleBetween((*m_vertices)[large[largeIndex]] - (*m_vertices)[small[smallIndex]], + float angleOnSmallEdgeLoop = radianBetweenVectors((*m_vertices)[large[largeIndex]] - (*m_vertices)[small[smallIndex]], (*m_vertices)[small[smallIndex + 1]] - (*m_vertices)[small[smallIndex]]); - float angleOnLargeEdgeLoop = angleBetween((*m_vertices)[small[smallIndex]] - (*m_vertices)[large[largeIndex]], + float angleOnLargeEdgeLoop = radianBetweenVectors((*m_vertices)[small[smallIndex]] - (*m_vertices)[large[largeIndex]], (*m_vertices)[large[largeIndex + 1]] - (*m_vertices)[large[largeIndex]]); if (angleOnSmallEdgeLoop < angleOnLargeEdgeLoop) { m_regeneratedFaces.push_back({ @@ -423,7 +420,7 @@ void Recombiner::fillPairs(const std::vector &small, const std::vector> rearrangedFaces; std::map oldToNewIndexMap; @@ -445,5 +442,3 @@ void Recombiner::removeReluctantVertices() } m_regeneratedFaces = rearrangedFaces; } - -} diff --git a/thirdparty/nodemesh/nodemesh/recombiner.h b/src/meshrecombiner.h similarity index 76% rename from thirdparty/nodemesh/nodemesh/recombiner.h rename to src/meshrecombiner.h index 5933ab8b..5f8fb697 100644 --- a/thirdparty/nodemesh/nodemesh/recombiner.h +++ b/src/meshrecombiner.h @@ -1,31 +1,28 @@ -#ifndef NODEMESH_RECOMBINER_H -#define NODEMESH_RECOMBINER_H +#ifndef DUST3D_RECOMBINER_H +#define DUST3D_RECOMBINER_H #include #include #include #include -#include +#include "meshcombiner.h" -namespace nodemesh -{ - -class Recombiner +class MeshRecombiner { public: void setVertices(const std::vector *vertices, - const std::vector> *verticesSourceIndices); + const std::vector> *verticesSourceIndices); void setFaces(const std::vector> *faces); const std::vector ®eneratedVertices(); - const std::vector> ®eneratedVerticesSourceIndices(); + const std::vector> ®eneratedVerticesSourceIndices(); const std::vector> ®eneratedFaces(); bool recombine(); private: const std::vector *m_vertices = nullptr; - const std::vector> *m_verticesSourceIndices = nullptr; + const std::vector> *m_verticesSourceIndices = nullptr; const std::vector> *m_faces = nullptr; std::vector m_regeneratedVertices; - std::vector> m_regeneratedVerticesSourceIndices; + std::vector> m_regeneratedVerticesSourceIndices; std::vector> m_regeneratedFaces; std::map, size_t> m_halfEdgeToFaceMap; std::map m_facesInSeamArea; @@ -47,6 +44,4 @@ private: void fillPairs(const std::vector &small, const std::vector &large); }; -} - #endif diff --git a/thirdparty/nodemesh/nodemesh/stitcher.cpp b/src/meshstitcher.cpp similarity index 79% rename from thirdparty/nodemesh/nodemesh/stitcher.cpp rename to src/meshstitcher.cpp index 6870f734..d86336d8 100644 --- a/thirdparty/nodemesh/nodemesh/stitcher.cpp +++ b/src/meshstitcher.cpp @@ -1,22 +1,18 @@ -#include -#include #include #include +#include "meshstitcher.h" -namespace nodemesh -{ - -Stitcher::~Stitcher() +MeshStitcher::~MeshStitcher() { delete m_wrapper; } -void Stitcher::setVertices(const std::vector *vertices) +void MeshStitcher::setVertices(const std::vector *vertices) { m_positions = vertices; } -const std::vector> &Stitcher::newlyGeneratedFaces() +const std::vector> &MeshStitcher::newlyGeneratedFaces() { if (m_wrapper) return m_wrapper->newlyGeneratedFaces(); @@ -24,13 +20,13 @@ const std::vector> &Stitcher::newlyGeneratedFaces() return m_newlyGeneratedFaces; } -void Stitcher::getFailedEdgeLoops(std::vector &failedEdgeLoops) +void MeshStitcher::getFailedEdgeLoops(std::vector &failedEdgeLoops) { if (m_wrapper) m_wrapper->getFailedEdgeLoops(failedEdgeLoops); } -bool Stitcher::stitchByQuads(const std::vector, QVector3D>> &edgeLoops) +bool MeshStitcher::stitchByQuads(const std::vector, QVector3D>> &edgeLoops) { if (2 != edgeLoops.size()) return false; @@ -65,13 +61,13 @@ bool Stitcher::stitchByQuads(const std::vector, QV return true; } -bool Stitcher::stitch(const std::vector, QVector3D>> &edgeLoops) +bool MeshStitcher::stitch(const std::vector, QVector3D>> &edgeLoops) { if (edgeLoops.size() == 2 && edgeLoops[0].first.size() == edgeLoops[1].first.size()) return stitchByQuads(edgeLoops); - m_wrapper = new Wrapper; + m_wrapper = new MeshWrapper; m_wrapper->setVertices(m_positions); m_wrapper->wrap(edgeLoops); if (!m_wrapper->finished()) { @@ -81,6 +77,3 @@ bool Stitcher::stitch(const std::vector, QVector3D return true; } -} - - diff --git a/thirdparty/nodemesh/nodemesh/stitcher.h b/src/meshstitcher.h similarity index 76% rename from thirdparty/nodemesh/nodemesh/stitcher.h rename to src/meshstitcher.h index 3666c688..2aa5a28b 100644 --- a/thirdparty/nodemesh/nodemesh/stitcher.h +++ b/src/meshstitcher.h @@ -1,16 +1,13 @@ -#ifndef NODEMESH_STITCHER_H -#define NODEMESH_STITCHER_H +#ifndef DUST3D_STITCHER_H +#define DUST3D_STITCHER_H #include #include -#include +#include "meshwrapper.h" -namespace nodemesh -{ - -class Stitcher +class MeshStitcher { public: - ~Stitcher(); + ~MeshStitcher(); void setVertices(const std::vector *vertices); bool stitch(const std::vector, QVector3D>> &edgeLoops); const std::vector> &newlyGeneratedFaces(); @@ -19,13 +16,10 @@ public: private: const std::vector *m_positions; std::vector> m_newlyGeneratedFaces; - Wrapper *m_wrapper = nullptr; + MeshWrapper *m_wrapper = nullptr; bool stitchByQuads(const std::vector, QVector3D>> &edgeLoops); }; -} - - #endif diff --git a/thirdparty/nodemesh/nodemesh/wrapper.cpp b/src/meshwrapper.cpp similarity index 84% rename from thirdparty/nodemesh/nodemesh/wrapper.cpp rename to src/meshwrapper.cpp index 0a8d9a58..7edf8c24 100644 --- a/thirdparty/nodemesh/nodemesh/wrapper.cpp +++ b/src/meshwrapper.cpp @@ -1,17 +1,14 @@ -#include -#include #include #include +#include "meshwrapper.h" +#include "util.h" -namespace nodemesh -{ - -void Wrapper::setVertices(const std::vector *vertices) +void MeshWrapper::setVertices(const std::vector *vertices) { m_positions = vertices; } -void Wrapper::wrap(const std::vector, QVector3D>> &edgeLoops) +void MeshWrapper::wrap(const std::vector, QVector3D>> &edgeLoops) { size_t nextPlaneId = 1; for (const auto &it: edgeLoops) { @@ -21,12 +18,12 @@ void Wrapper::wrap(const std::vector, QVector3D>> finalize(); } -const std::vector> &Wrapper::newlyGeneratedFaces() +const std::vector> &MeshWrapper::newlyGeneratedFaces() { return m_newlyGeneratedfaces; } -bool Wrapper::finished() +bool MeshWrapper::finished() { if (!m_finalizeFinished) return false; @@ -39,7 +36,7 @@ bool Wrapper::finished() return true; } -void Wrapper::getFailedEdgeLoops(std::vector &failedEdgeLoops) +void MeshWrapper::getFailedEdgeLoops(std::vector &failedEdgeLoops) { std::set edgeLoopIndices; for (const auto &it: m_candidates) { @@ -52,7 +49,7 @@ void Wrapper::getFailedEdgeLoops(std::vector &failedEdgeLoops) } } -void Wrapper::addCandidateVertices(const std::vector &vertices, const QVector3D &planeNormal, size_t planeId) +void MeshWrapper::addCandidateVertices(const std::vector &vertices, const QVector3D &planeNormal, size_t planeId) { std::map verticesIndexSet; for (const auto &oldVertId: vertices) { @@ -70,7 +67,7 @@ void Wrapper::addCandidateVertices(const std::vector &vertices, const QV } } -size_t Wrapper::addSourceVertex(const QVector3D &position, size_t sourcePlane, size_t tag) +size_t MeshWrapper::addSourceVertex(const QVector3D &position, size_t sourcePlane, size_t tag) { auto addedIndex = m_sourceVertices.size(); @@ -86,14 +83,14 @@ size_t Wrapper::addSourceVertex(const QVector3D &position, size_t sourcePlane, s return addedIndex; } -void Wrapper::addStartup(size_t p1, size_t p2, const QVector3D &baseNormal) +void MeshWrapper::addStartup(size_t p1, size_t p2, const QVector3D &baseNormal) { if (m_items.empty()) addItem(p1, p2, baseNormal); m_generatedFaceEdgesMap.insert({WrapItemKey {p2, p1}, {0, false}}); } -QVector3D Wrapper::calculateFaceVector(size_t p1, size_t p2, const QVector3D &baseNormal) +QVector3D MeshWrapper::calculateFaceVector(size_t p1, size_t p2, const QVector3D &baseNormal) { const auto &v1 = m_sourceVertices[p1]; const auto &v2 = m_sourceVertices[p2]; @@ -101,7 +98,7 @@ QVector3D Wrapper::calculateFaceVector(size_t p1, size_t p2, const QVector3D &ba return QVector3D::crossProduct(seg, baseNormal); } -void Wrapper::addItem(size_t p1, size_t p2, const QVector3D &baseNormal) +void MeshWrapper::addItem(size_t p1, size_t p2, const QVector3D &baseNormal) { const auto &v1 = m_sourceVertices[p1]; const auto &v2 = m_sourceVertices[p2]; @@ -123,7 +120,7 @@ void Wrapper::addItem(size_t p1, size_t p2, const QVector3D &baseNormal) m_itemsList.push_front(index); } -std::pair Wrapper::findItem(size_t p1, size_t p2) +std::pair MeshWrapper::findItem(size_t p1, size_t p2) { auto key = WrapItemKey {p1, p2}; auto findResult = m_itemsMap.find(key); @@ -133,7 +130,7 @@ std::pair Wrapper::findItem(size_t p1, size_t p2) return {findResult->second, true}; } -bool Wrapper::isEdgeGenerated(size_t p1, size_t p2) +bool MeshWrapper::isEdgeGenerated(size_t p1, size_t p2) { auto key = WrapItemKey {p1, p2}; if (m_generatedFaceEdgesMap.find(key) == m_generatedFaceEdgesMap.end()) @@ -141,7 +138,7 @@ bool Wrapper::isEdgeGenerated(size_t p1, size_t p2) return true; } -float Wrapper::angleOfBaseFaceAndPoint(size_t itemIndex, size_t vertexIndex) +float MeshWrapper::angleOfBaseFaceAndPoint(size_t itemIndex, size_t vertexIndex) { const auto &item = m_items[itemIndex]; if (item.p1 == vertexIndex || item.p2 == vertexIndex) @@ -154,10 +151,10 @@ float Wrapper::angleOfBaseFaceAndPoint(size_t itemIndex, size_t vertexIndex) auto vd1 = calculateFaceVector(item.p1, item.p2, item.baseNormal); auto normal = QVector3D::normal(v2.position, v1.position, vp.position); auto vd2 = calculateFaceVector(item.p1, item.p2, normal); - return radianToDegree(angleBetween(vd2, vd1)); + return angleBetweenVectors(vd2, vd1); } -std::pair Wrapper::findBestVertexOnTheLeft(size_t itemIndex) +std::pair MeshWrapper::findBestVertexOnTheLeft(size_t itemIndex) { auto p1 = m_items[itemIndex].p1; auto p2 = m_items[itemIndex].p2; @@ -181,7 +178,7 @@ std::pair Wrapper::findBestVertexOnTheLeft(size_t itemIndex) return result; } -std::pair Wrapper::peekItem() +std::pair MeshWrapper::peekItem() { for (const auto &itemIndex : m_itemsList) { if (!m_items[itemIndex].processed) { @@ -191,13 +188,13 @@ std::pair Wrapper::peekItem() return {0, false}; } -bool Wrapper::isEdgeClosed(size_t p1, size_t p2) +bool MeshWrapper::isEdgeClosed(size_t p1, size_t p2) { return m_generatedFaceEdgesMap.find(WrapItemKey {p1, p2}) != m_generatedFaceEdgesMap.end() && m_generatedFaceEdgesMap.find(WrapItemKey {p2, p1}) != m_generatedFaceEdgesMap.end(); } -bool Wrapper::isVertexClosed(size_t vertexIndex) +bool MeshWrapper::isVertexClosed(size_t vertexIndex) { auto findResult = m_generatedVertexEdgesMap.find(vertexIndex); if (findResult == m_generatedVertexEdgesMap.end()) @@ -209,7 +206,7 @@ bool Wrapper::isVertexClosed(size_t vertexIndex) return true; } -void Wrapper::generate() +void MeshWrapper::generate() { for (;;) { auto findItem = peekItem(); @@ -245,7 +242,7 @@ void Wrapper::generate() } } -size_t Wrapper::anotherVertexIndexOfFace3(const Face3 &f, size_t p1, size_t p2) +size_t MeshWrapper::anotherVertexIndexOfFace3(const Face3 &f, size_t p1, size_t p2) { std::vector indices = {f.p1, f.p2, f.p3}; for (const auto &index : indices) { @@ -255,7 +252,7 @@ size_t Wrapper::anotherVertexIndexOfFace3(const Face3 &f, size_t p1, size_t p2) return 0; } -std::pair Wrapper::findPairFace3(const Face3 &f, std::map &usedIds, std::vector &q) +std::pair MeshWrapper::findPairFace3(const Face3 &f, std::map &usedIds, std::vector &q) { std::vector indices = {f.p1, f.p2, f.p3}; for (size_t i = 0; i < indices.size(); ++i) { @@ -278,14 +275,14 @@ std::pair Wrapper::findPairFace3(const Face3 &f, std::map quads; std::map usedIds; @@ -316,6 +313,3 @@ void Wrapper::finalize() m_newlyGeneratedfaces.push_back(addedVertices); } } - - -} diff --git a/thirdparty/nodemesh/nodemesh/wrapper.h b/src/meshwrapper.h similarity index 96% rename from thirdparty/nodemesh/nodemesh/wrapper.h rename to src/meshwrapper.h index cc917ec2..4806f52b 100644 --- a/thirdparty/nodemesh/nodemesh/wrapper.h +++ b/src/meshwrapper.h @@ -1,14 +1,11 @@ -#ifndef NODEMESH_WRAPPER_H -#define NODEMESH_WRAPPER_H +#ifndef DUST3D_WRAPPER_H +#define DUST3D_WRAPPER_H #include #include #include #include -namespace nodemesh -{ - -class Wrapper +class MeshWrapper { public: void setVertices(const std::vector *vertices); @@ -102,6 +99,4 @@ private: bool almostEqual(const QVector3D &v1, const QVector3D &v2); }; -} - #endif diff --git a/src/modelwidget.cpp b/src/modelwidget.cpp index e1d47f9b..145387e8 100644 --- a/src/modelwidget.cpp +++ b/src/modelwidget.cpp @@ -12,7 +12,7 @@ bool ModelWidget::m_transparent = true; const QVector3D ModelWidget::m_cameraPosition = QVector3D(0, 0, -4.0); float ModelWidget::m_minZoomRatio = 5.0; -float ModelWidget::m_maxZoomRatio = 90.0; +float ModelWidget::m_maxZoomRatio = 80.0; ModelWidget::ModelWidget(QWidget *parent) : QOpenGLWidget(parent), @@ -314,10 +314,12 @@ bool ModelWidget::inputWheelEventFromOtherWidget(QWheelEvent *event) if (!m_zoomEnabled) return false; - qreal delta = event->delta() / 5; - if (fabs(delta) < 1) - delta = delta < 0 ? -1.0 : 1.0; + + qreal delta = geometry().height() * 0.1f; + if (event->delta() < 0) + delta = -delta; zoom(delta); + return true; } diff --git a/src/outcome.h b/src/outcome.h index 062ac8d7..2119eccc 100644 --- a/src/outcome.h +++ b/src/outcome.h @@ -56,6 +56,7 @@ public: std::vector nodes; std::vector>> nodeVertices; std::vector vertices; + std::vector> vertexSourceNodes; std::vector> triangleAndQuads; std::vector> triangles; std::vector triangleNormals; diff --git a/src/partwidget.cpp b/src/partwidget.cpp index 068e4d4d..618454e3 100644 --- a/src/partwidget.cpp +++ b/src/partwidget.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include "partwidget.h" #include "theme.h" #include "floatnumberwidget.h" diff --git a/thirdparty/nodemesh/nodemesh/positionkey.cpp b/src/positionkey.cpp similarity index 94% rename from thirdparty/nodemesh/nodemesh/positionkey.cpp rename to src/positionkey.cpp index 2c365605..2dbb13a6 100644 --- a/thirdparty/nodemesh/nodemesh/positionkey.cpp +++ b/src/positionkey.cpp @@ -1,7 +1,4 @@ -#include - -namespace nodemesh -{ +#include "positionkey.h" long PositionKey::m_toIntFactor = 100000; @@ -49,5 +46,3 @@ bool PositionKey::operator ==(const PositionKey &right) const m_intY == right.m_intY && m_intZ == right.m_intZ; } - -} diff --git a/thirdparty/nodemesh/nodemesh/positionkey.h b/src/positionkey.h similarity index 82% rename from thirdparty/nodemesh/nodemesh/positionkey.h rename to src/positionkey.h index c581e169..9392a748 100644 --- a/thirdparty/nodemesh/nodemesh/positionkey.h +++ b/src/positionkey.h @@ -1,10 +1,7 @@ -#ifndef NODEMESH_POSITION_KEY_H -#define NODEMESH_POSITION_KEY_H +#ifndef DUST3D_POSITION_KEY_H +#define DUST3D_POSITION_KEY_H #include -namespace nodemesh -{ - class PositionKey { public: @@ -23,6 +20,4 @@ private: static long m_toIntFactor; }; -}; - #endif diff --git a/src/preferences.cpp b/src/preferences.cpp index 88253c65..d67b93f4 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -15,7 +15,6 @@ void Preferences::loadDefault() m_componentCombineMode = CombineMode::Normal; m_partColor = Qt::white; m_flatShading = true; - m_threeNodesBranchEnabled = false; } Preferences::Preferences() @@ -34,15 +33,10 @@ Preferences::Preferences() { QString value = m_settings.value("flatShading").toString(); if (value.isEmpty()) - m_flatShading = true; + m_flatShading = false; else m_flatShading = isTrueValueString(value); } - { - QString value = m_settings.value("threeNodesBranchEnabled").toString(); - if (!value.isEmpty()) - m_threeNodesBranchEnabled = isTrueValueString(value); - } } CombineMode Preferences::componentCombineMode() const @@ -60,11 +54,6 @@ bool Preferences::flatShading() const return m_flatShading; } -bool Preferences::threeNodesBranchEnabled() const -{ - return m_threeNodesBranchEnabled; -} - void Preferences::setComponentCombineMode(CombineMode mode) { if (m_componentCombineMode == mode) @@ -92,15 +81,6 @@ void Preferences::setFlatShading(bool flatShading) emit flatShadingChanged(); } -void Preferences::setThreeNodesBranchEnableState(bool enabled) -{ - if (m_threeNodesBranchEnabled == enabled) - return; - m_threeNodesBranchEnabled = enabled; - m_settings.setValue("threeNodesBranchEnabled", enabled ? "true" : "false"); - emit threeNodesBranchEnableStateChanged(); -} - void Preferences::reset() { m_settings.clear(); @@ -108,5 +88,4 @@ void Preferences::reset() emit componentCombineModeChanged(); emit partColorChanged(); emit flatShadingChanged(); - emit threeNodesBranchEnableStateChanged(); } diff --git a/src/preferences.h b/src/preferences.h index ef930a45..4d5120c5 100644 --- a/src/preferences.h +++ b/src/preferences.h @@ -13,24 +13,20 @@ public: CombineMode componentCombineMode() const; const QColor &partColor() const; bool flatShading() const; - bool threeNodesBranchEnabled() const; signals: void componentCombineModeChanged(); void partColorChanged(); void flatShadingChanged(); - void threeNodesBranchEnableStateChanged(); public slots: void setComponentCombineMode(CombineMode mode); void setPartColor(const QColor &color); void setFlatShading(bool flatShading); - void setThreeNodesBranchEnableState(bool enabled); void reset(); private: CombineMode m_componentCombineMode; QColor m_partColor; bool m_flatShading; QSettings m_settings; - bool m_threeNodesBranchEnabled; private: void loadDefault(); }; diff --git a/src/preferenceswidget.cpp b/src/preferenceswidget.cpp index ef023731..f96516cf 100644 --- a/src/preferenceswidget.cpp +++ b/src/preferenceswidget.cpp @@ -63,23 +63,15 @@ PreferencesWidget::PreferencesWidget(const Document *document, QWidget *parent) Preferences::instance().setFlatShading(flatShadingBox->isChecked()); }); - QCheckBox *threeNodesBranchEnabledBox = new QCheckBox(); - Theme::initCheckbox(threeNodesBranchEnabledBox); - connect(threeNodesBranchEnabledBox, &QCheckBox::stateChanged, this, [=]() { - Preferences::instance().setThreeNodesBranchEnableState(threeNodesBranchEnabledBox->isChecked()); - }); - QFormLayout *formLayout = new QFormLayout; formLayout->addRow(tr("Part color:"), colorLayout); formLayout->addRow(tr("Combine mode:"), combineModeSelectBox); formLayout->addRow(tr("Flat shading:"), flatShadingBox); - formLayout->addRow(tr("Three nodes branch:"), threeNodesBranchEnabledBox); auto loadFromPreferences = [=]() { updatePickButtonColor(); combineModeSelectBox->setCurrentIndex((int)Preferences::instance().componentCombineMode()); flatShadingBox->setChecked(Preferences::instance().flatShading()); - threeNodesBranchEnabledBox->setChecked(Preferences::instance().threeNodesBranchEnabled()); }; loadFromPreferences(); diff --git a/src/regionfiller.cpp b/src/regionfiller.cpp new file mode 100644 index 00000000..e93b33a9 --- /dev/null +++ b/src/regionfiller.cpp @@ -0,0 +1,1496 @@ +#include +#include +#include +#include +#include "regionfiller.h" + +// +// This is an implementation of the following paper +// +// +// A. Nasri1∗, M. Sabin2 and Z. Yasseen1 +// + +#define isEven(n) (0 == (n) % 2) +#define isOdd(n) (!isEven(n)) + +RegionFiller::RegionFiller(const std::vector *vertices, + const std::vector> *polylines) : + m_sourceVertices(vertices), + m_sourcePolylines(new std::vector>(*polylines)) +{ +} + +RegionFiller::~RegionFiller() +{ + delete m_sourcePolylines; +} + +bool RegionFiller::resolveEvenSidedEvenSumOfSegmentsAndBothL2L2AreEven() +{ + return resolveOddSidedEvenSumOfSegments(m_sideNum / 2); +} + +std::vector RegionFiller::createPointsToMapBetween(size_t fromIndex, + size_t toIndex, + size_t segments, + std::map, std::vector> *map) +{ + size_t a = fromIndex; + size_t b = toIndex; + bool needReverse = false; + if (a > b) { + std::swap(a, b); + needReverse = true; + } + auto key = std::make_pair(a, b); + auto findPoints = map->find(key); + if (findPoints != map->end()) { + return needReverse ? reversed(findPoints->second) : findPoints->second; + } + std::vector newPointIndices; + createPointsBetween(a, b, segments, &newPointIndices); + map->insert({key, newPointIndices}); + return needReverse ? reversed(newPointIndices) : newPointIndices; +} + +void RegionFiller::createPointsBetween(size_t fromIndex, + size_t toIndex, + size_t segments, + std::vector *newPointIndices) +{ + if (fromIndex == toIndex || 0 == segments) + return; + auto fromVertex = m_oldAndNewVertices[fromIndex]; + auto toVertex = m_oldAndNewVertices[toIndex]; + float stepFactor = 1.0 / segments; + newPointIndices->push_back(fromIndex); + for (size_t i = 1; i <= segments - 1; ++i) { + float factor = stepFactor * i; + RegionFiller::Node node; + node.position = fromVertex.position * (1.0 - factor) + toVertex.position * factor; + node.radius = fromVertex.radius * (1.0 - factor) + toVertex.radius * factor; + if (m_centerSources.find(toVertex.source) != m_centerSources.end()) { + node.source = fromVertex.source; + } else if (m_centerSources.find(fromVertex.source) != m_centerSources.end()) { + node.source = toVertex.source; + } else { + if (factor > 0.5) { + node.source = toVertex.source; + } else { + node.source = fromVertex.source; + } + } + size_t newIndex = m_oldAndNewVertices.size(); + m_oldAndNewVertices.push_back(node); + newPointIndices->push_back(newIndex); + } + newPointIndices->push_back(toIndex); +} + +std::vector RegionFiller::createPointsBetween(size_t fromIndex, + size_t toIndex, + size_t segments) +{ + std::vector newPointIndices; + createPointsBetween(fromIndex, toIndex, segments, &newPointIndices); + return newPointIndices; +} + +void RegionFiller::collectEdgePoints(size_t polyline, int startPos, int stopPos, + std::vector *pointIndices) +{ + const auto &edgeIndices = (*m_sourcePolylines)[polyline]; + int step = startPos < stopPos ? 1 : -1; + int totalSteps = std::abs(startPos - stopPos) + 1; + int current = startPos; + for (int i = 0; i < totalSteps; ++i) { + pointIndices->push_back(edgeIndices[current]); + current += step; + } +} + +std::vector RegionFiller::collectEdgePoints(size_t polyline, int startPos, int stopPos) +{ + std::vector pointIndices; + collectEdgePoints(polyline, startPos, stopPos, &pointIndices); + return pointIndices; +} + +std::vector RegionFiller::reversed(const std::vector &pointIndices) +{ + std::vector newPointIndices = pointIndices; + std::reverse(newPointIndices.begin(), newPointIndices.end()); + return newPointIndices; +} + +float RegionFiller::averageRadius(size_t *maxNodeIndex) +{ + float sumOfRadius = 0; + size_t num = 0; + float maxRadius = 0; + for (const auto &polyline: *m_sourcePolylines) { + for (const auto &it: polyline) { + const auto &vertex = (*m_sourceVertices)[it]; + if (vertex.radius >= maxRadius) { + maxRadius = vertex.radius; + if (nullptr != maxNodeIndex) + *maxNodeIndex = it; + } + sumOfRadius += vertex.radius; + ++num; + } + } + if (0 == num) + return 0; + auto radius = sumOfRadius / num; + return radius; +} + +bool RegionFiller::resolveOddSidedEvenSumOfSegments(int siUpperBound) +{ + auto si = [&](int i) { + int sum = 0; + for (int j = 1; j <= siUpperBound; ++j) { + sum += std::pow(-1, j + 1) * + m_sideSegmentNums[(i + 2 * j - 1) % m_sideSegmentNums.size()]; + } + return sum; + }; + auto di = [&](int i) { + return std::ceil(si(i) * 0.5); + }; + + std::vector gRaySegmentNums(m_sideNum); + for (int i = 0; i < m_sideNum; ++i) { + gRaySegmentNums[i] = di(i); + if (gRaySegmentNums[i] <= 0) { + qDebug() << "resolveOddSidedEvenSumOfSegments failed, di:" << gRaySegmentNums[i]; + return false; + } + } + + std::vector eIndices(m_sideNum); + for (int i = 0; i < m_sideNum; ++i) { + auto length = gRaySegmentNums[(i + 1) % gRaySegmentNums.size()]; + eIndices[i] = m_sideSegmentNums[i] - length; + } + + std::set cornerVertices; + std::vector> edgeSegments; // tuple + for (int i = 0; i < m_sideNum; ++i) { + if (gRaySegmentNums[i] <= 0) { + edgeSegments.push_back(std::make_tuple(0, (*m_sourcePolylines)[i].size() - 1, i)); + continue; + } + const auto &pointEdgeIndex = eIndices[i]; + cornerVertices.insert((*m_sourcePolylines)[i][pointEdgeIndex]); + if (pointEdgeIndex > 0) + edgeSegments.push_back(std::make_tuple(0, pointEdgeIndex, i)); + if (pointEdgeIndex < (int)(*m_sourcePolylines)[i].size() - 1) + edgeSegments.push_back(std::make_tuple(pointEdgeIndex, (*m_sourcePolylines)[i].size() - 1, i)); + } + + std::vector> gPositionAndAreas; + for (size_t i = 0; i < edgeSegments.size(); ++i) { + size_t j = (i + 1) % edgeSegments.size(); + size_t m = (j + 2) % edgeSegments.size(); + const auto &segmentI = edgeSegments[i]; + const auto &segmentJ = edgeSegments[j]; + const auto &segmentM = edgeSegments[m]; + std::vector firstSegmentPoints; + std::vector secondSegmentPoints; + std::vector forthSegmentPoints; + collectEdgePoints(std::get<2>(segmentI), std::get<0>(segmentI), std::get<1>(segmentI), + &firstSegmentPoints); + collectEdgePoints(std::get<2>(segmentJ), std::get<0>(segmentJ), std::get<1>(segmentJ), + &secondSegmentPoints); + collectEdgePoints(std::get<2>(segmentM), std::get<0>(segmentM), std::get<1>(segmentM), + &forthSegmentPoints); + if (cornerVertices.find((*m_sourcePolylines)[std::get<2>(segmentI)][std::get<0>(segmentI)]) != cornerVertices.end() && + cornerVertices.find((*m_sourcePolylines)[std::get<2>(segmentI)][std::get<1>(segmentI)]) != cornerVertices.end()) { + const auto &p1 = (*m_sourceVertices)[(*m_sourcePolylines)[std::get<2>(segmentI)][std::get<0>(segmentI)]]; + const auto &p2 = (*m_sourceVertices)[(*m_sourcePolylines)[std::get<2>(segmentI)][std::get<1>(segmentI)]]; + gPositionAndAreas.push_back(std::make_pair((p1.position + p2.position) * 0.5, + (firstSegmentPoints.size() - 1) * (secondSegmentPoints.size() - 1) * 0.5)); + continue; + } + if (cornerVertices.find((*m_sourcePolylines)[std::get<2>(segmentI)][std::get<0>(segmentI)]) != cornerVertices.end() && + cornerVertices.find((*m_sourcePolylines)[std::get<2>(segmentJ)][std::get<1>(segmentJ)]) != cornerVertices.end()) { + ++i; + const auto &p1 = (*m_sourceVertices)[(*m_sourcePolylines)[std::get<2>(segmentI)][std::get<0>(segmentI)]]; + const auto &p2 = (*m_sourceVertices)[(*m_sourcePolylines)[std::get<2>(segmentI)][std::get<1>(segmentI)]]; + const auto &p3 = (*m_sourceVertices)[(*m_sourcePolylines)[std::get<2>(segmentJ)][std::get<1>(segmentJ)]]; + gPositionAndAreas.push_back(std::make_pair(p1.position + p2.position - p3.position, + (firstSegmentPoints.size() - 1) * (secondSegmentPoints.size() - 1))); + } + } + + QVector3D gTop; + float gBottom = 0; + for (const auto &it: gPositionAndAreas) { + gTop += it.first / it.second; + gBottom += 1.0 / it.second; + } + auto gPosition = gTop / gBottom; + size_t gIndex = m_oldAndNewVertices.size(); + Node node; + node.position = gPosition; + node.radius = averageRadius(&node.source); + m_centerSources.insert(node.source); + m_oldAndNewVertices.push_back(node); + + std::map, std::vector> map; + for (size_t i = 0; i < edgeSegments.size(); ++i) { + size_t j = (i + 1) % edgeSegments.size(); + size_t m = (j + 2) % edgeSegments.size(); + const auto &segmentI = edgeSegments[i]; + const auto &segmentJ = edgeSegments[j]; + const auto &segmentM = edgeSegments[m]; + std::vector firstSegmentPoints; + std::vector secondSegmentPoints; + std::vector forthSegmentPoints; + collectEdgePoints(std::get<2>(segmentI), std::get<0>(segmentI), std::get<1>(segmentI), + &firstSegmentPoints); + collectEdgePoints(std::get<2>(segmentJ), std::get<0>(segmentJ), std::get<1>(segmentJ), + &secondSegmentPoints); + collectEdgePoints(std::get<2>(segmentM), std::get<0>(segmentM), std::get<1>(segmentM), + &forthSegmentPoints); + if (cornerVertices.find((*m_sourcePolylines)[std::get<2>(segmentI)][std::get<0>(segmentI)]) != cornerVertices.end() && + cornerVertices.find((*m_sourcePolylines)[std::get<2>(segmentI)][std::get<1>(segmentI)]) != cornerVertices.end()) { + std::vector> region = { + firstSegmentPoints, + createPointsToMapBetween((*m_sourcePolylines)[std::get<2>(segmentI)][std::get<1>(segmentI)], + gIndex, forthSegmentPoints.size() - 1, &map), + createPointsToMapBetween(gIndex, + (*m_sourcePolylines)[std::get<2>(segmentI)][std::get<0>(segmentI)], secondSegmentPoints.size() - 1, &map) + }; + m_newRegions.push_back(region); + continue; + } + if (cornerVertices.find((*m_sourcePolylines)[std::get<2>(segmentI)][std::get<0>(segmentI)]) != cornerVertices.end() && + cornerVertices.find((*m_sourcePolylines)[std::get<2>(segmentJ)][std::get<1>(segmentJ)]) != cornerVertices.end()) { + ++i; + std::vector> region = { + firstSegmentPoints, + secondSegmentPoints, + createPointsToMapBetween((*m_sourcePolylines)[std::get<2>(segmentJ)][std::get<1>(segmentJ)], + gIndex, firstSegmentPoints.size() - 1, &map), + createPointsToMapBetween(gIndex, + (*m_sourcePolylines)[std::get<2>(segmentI)][std::get<0>(segmentI)], secondSegmentPoints.size() - 1, &map) + }; + m_newRegions.push_back(region); + } + } + + return true; +} + +bool RegionFiller::resolveEvenSidedEvenSumOfSegmentsAndBothL2L2AreOdd() +{ + return resolveOddSidedOddSumOfSegments(m_sideNum / 2); +} + +bool RegionFiller::resolveOddSidedOddSumOfSegments(int siUpperBound) +{ + auto si = [&](int i) { + int sum = 0; + for (int j = 1; j <= siUpperBound; ++j) { + sum += std::pow(-1, j + 1) * + (m_sideSegmentNums[(i + 2 * j - 1) % m_sideSegmentNums.size()] - 1); + } + return sum; + }; + auto di = [&](int i) { + return std::round(si(i) * 0.5); + }; + std::vector gRaySegmentNums(m_sideNum); + for (int i = 0; i < m_sideNum; ++i) { + gRaySegmentNums[i] = di(i); + if (gRaySegmentNums[i] <= 0) { + qDebug() << "resolveOddSidedOddSumOfSegments failed, di:" << gRaySegmentNums[i]; + return false; + } + } + + std::vector areas(m_sideNum); + for (int i = 0; i < m_sideNum; ++i) { + areas[i] = gRaySegmentNums[i] * gRaySegmentNums[(i + 1) % m_sideNum]; + } + + std::vector> eIndices(m_sideNum); + for (int i = 0; i < m_sideNum; ++i) { + auto length = gRaySegmentNums[(i + 1) % gRaySegmentNums.size()]; + eIndices[i] = std::vector{(int)m_sideSegmentNums[i] - length - 1, + (int)m_sideSegmentNums[i] - length + }; + if (eIndices[i][0] < 0 || eIndices[i][1] < 0) + return false; + } + std::vector eEdgeLengths(m_sideNum); + for (int i = 0; i < m_sideNum; ++i) { + eEdgeLengths[i] = ((*m_sourceVertices)[(*m_sourcePolylines)[i][eIndices[i][0]]].position - + (*m_sourceVertices)[(*m_sourcePolylines)[i][eIndices[i][1]]].position).length(); + } + float averageDislocationEdgeLength = std::accumulate(eEdgeLengths.begin(), eEdgeLengths.end(), 0.0) / eEdgeLengths.size(); + + auto gi = [&](int i) { + int j = (i + m_sideNum - 1) % m_sideNum; + return (*m_sourceVertices)[(*m_sourcePolylines)[i][eIndices[i][0]]].position + + (*m_sourceVertices)[(*m_sourcePolylines)[j][eIndices[j][1]]].position - + (*m_sourceVertices)[(*m_sourcePolylines)[i][0]].position; + }; + auto gOffsetTargets = [&](int i) { + int h = (i + m_sideNum - 1) % m_sideNum; + return ((*m_sourceVertices)[(*m_sourcePolylines)[i][eIndices[i][0]]].position + + (*m_sourceVertices)[(*m_sourcePolylines)[h][eIndices[h][1]]].position) * 0.5; + }; + + QVector3D gTop; + float gBottom = 0; + for (int i = 0; i < m_sideNum; ++i) { + gTop += gi(i) / areas[i]; + gBottom += 1.0 / areas[i]; + } + + auto gPosition = gTop / gBottom; + size_t gSource = 0; + auto gRadius = averageRadius(&gSource); + m_centerSources.insert(gSource); + + std::vector>> segmentsWithG(m_sideNum); + for (int i = 0; i < m_sideNum; ++i) { + segmentsWithG[i].resize(2); + } + std::vector gFace(m_sideNum); + for (int i = 0; i < m_sideNum; ++i) { + int j = (i + 1) % m_sideNum; + auto offsetTarget = gOffsetTargets(j); + auto gPositionPerEdge = gPosition + (offsetTarget - gPosition).normalized() * averageDislocationEdgeLength * 0.7; + size_t gIndex = m_oldAndNewVertices.size(); + gFace[j] = gIndex; + Node node; + node.position = gPositionPerEdge; + node.radius = gRadius; + node.source = gSource; + m_oldAndNewVertices.push_back(node); + createPointsBetween(gIndex, (*m_sourcePolylines)[i][eIndices[i][1]], gRaySegmentNums[i], &segmentsWithG[i][1]); + createPointsBetween(gIndex, (*m_sourcePolylines)[j][eIndices[j][0]], gRaySegmentNums[j], &segmentsWithG[j][0]); + } + m_newFaces.push_back(gFace); + + std::vector>> reversedSegmentsWithG(m_sideNum); + reversedSegmentsWithG = segmentsWithG; + for (int i = 0; i < m_sideNum; ++i) { + for (int x = 0; x < 2; ++x) + std::reverse(reversedSegmentsWithG[i][x].begin(), reversedSegmentsWithG[i][x].end()); + } + + for (int i = 0; i < m_sideNum; ++i) { + int j = (i + 1) % m_sideNum; + std::vector e0c1; + collectEdgePoints(i, eIndices[i][1], (*m_sourcePolylines)[i].size() - 1, &e0c1); + std::vector c1e1; + collectEdgePoints(j, 0, eIndices[j][0], &c1e1); + std::vector> region = { + e0c1, + c1e1, + reversedSegmentsWithG[j][0], + segmentsWithG[i][1] + }; + m_newRegions.push_back(region); + } + + // rectangle slices + for (int i = 0; i < m_sideNum; ++i) { + int j = (i + 1) % m_sideNum; + std::vector e1e2 = { + (*m_sourcePolylines)[i][eIndices[i][0]], + (*m_sourcePolylines)[i][eIndices[i][1]] + }; + std::vector gCoreEdge = { + gFace[j], + gFace[i] + }; + std::vector> region = { + e1e2, + reversedSegmentsWithG[i][1], + gCoreEdge, + segmentsWithG[i][0], + }; + m_newRegions.push_back(region); + } + + return true; +} + +bool RegionFiller::resolveEvenSideEvenSumOfSegmentsAndL1IsOddL2IsEven() +{ + std::rotate(m_sourcePolylines->begin(), m_sourcePolylines->begin() + 1, m_sourcePolylines->end()); + prepare(); + return resolveEvenSideEvenSumOfSegmentsAndL1IsEvenL2IsOdd(); +} + +bool RegionFiller::resolveEvenSideEvenSumOfSegmentsAndL1IsEvenL2IsOdd() +{ + auto modifiedSideSegmentNums = m_sideSegmentNums; + for (int i = 0; i < m_sideNum; i += 2) { + modifiedSideSegmentNums[i] -= 1; + } + + auto si = [&](int i) { + int sum = 0; + int halfSideNum = m_sideNum / 2; + for (int j = 1; j <= halfSideNum; ++j) { + sum += std::pow(-1, j + 1) * + (modifiedSideSegmentNums[(i + 2 * j - 1) % m_sideSegmentNums.size()]); + } + return sum; + }; + auto di = [&](int i) { + return std::round(si(i) * 0.5); + }; + std::vector gRaySegmentNums(m_sideNum); + for (int i = 0; i < m_sideNum; ++i) { + gRaySegmentNums[i] = di(i); + if (gRaySegmentNums[i] <= 0) { + qDebug() << "resolveEvenSideEvenSumOfSegmentsAndL1IsEvenL2IsOdd failed, di:" << gRaySegmentNums[i]; + return false; + } + } + + std::vector areas(m_sideNum); + for (int i = 0; i < m_sideNum; ++i) { + areas[i] = gRaySegmentNums[i] * gRaySegmentNums[(i + 1) % m_sideNum]; + } + + std::vector> eIndices(m_sideNum); + for (int i = 0; i < m_sideNum; ++i) { + auto length = gRaySegmentNums[(i + gRaySegmentNums.size() - 1) % gRaySegmentNums.size()]; + if (0 == i % 2) { + eIndices[i] = std::vector{(int)length, + (int)length + 1 + }; + } else { + eIndices[i] = std::vector{(int)length, + (int)length + }; + } + if (eIndices[i][0] < 0 || eIndices[i][1] < 0) { + qDebug() << "resolveEvenSideEvenSumOfSegmentsAndL1IsEvenL2IsOdd failed"; + return false; + } + } + std::vector eEdgeLengths(m_sideNum, (float)0.0); + int validEdgeNum = 0; + for (int i = 0; i < m_sideNum; ++i) { + const auto &firstIndex = eIndices[i][0]; + const auto &secondIndex = eIndices[i][1]; + if (firstIndex == secondIndex) + continue; + validEdgeNum++; + eEdgeLengths[i] = ((*m_sourceVertices)[(*m_sourcePolylines)[i][firstIndex]].position - + (*m_sourceVertices)[(*m_sourcePolylines)[i][secondIndex]].position).length(); + } + float averageDislocationEdgeLength = std::accumulate(eEdgeLengths.begin(), eEdgeLengths.end(), 0.0) / validEdgeNum; + + auto gi = [&](int i) { + int h = (i + m_sideNum - 1) % m_sideNum; + return (*m_sourceVertices)[(*m_sourcePolylines)[i][eIndices[i][0]]].position + + (*m_sourceVertices)[(*m_sourcePolylines)[h][eIndices[h][1]]].position - + (*m_sourceVertices)[(*m_sourcePolylines)[i][0]].position; + }; + auto gOffsetTargets = [&](int i) { + int h = (i + m_sideNum - 1) % m_sideNum; + return ((*m_sourceVertices)[(*m_sourcePolylines)[i][eIndices[i][0]]].position + + (*m_sourceVertices)[(*m_sourcePolylines)[h][eIndices[h][1]]].position) * 0.5; + }; + + QVector3D gTop; + float gBottom = 0; + for (int i = 0; i < m_sideNum; ++i) { + gTop += gi(i) / areas[i]; + gBottom += 1.0 / areas[i]; + } + + auto gPosition = gTop / gBottom; + size_t gSource = 0; + auto gRadius = averageRadius(&gSource); + + std::vector gFace(m_sideNum / 2); + for (size_t k = 0; k < gFace.size(); ++k) { + int i = k * 2; + int h = (i + m_sideNum - 1) % m_sideNum; + auto offsetTarget = gOffsetTargets(i) * 0.5 + gOffsetTargets(h) * 0.5; + size_t gIndex = m_oldAndNewVertices.size(); + auto gPositionPerEdge = gPosition + (offsetTarget - gPosition).normalized() * averageDislocationEdgeLength * 0.7; + Node node; + node.position = gPositionPerEdge; + node.radius = gRadius; + node.source = gSource; + m_centerSources.insert(node.source); + m_oldAndNewVertices.push_back(node); + gFace[k] = gIndex; + } + m_newFaces.push_back(gFace); + + std::vector>> segmentsWithG(m_sideNum); + for (int i = 0; i < m_sideNum; ++i) { + segmentsWithG[i].resize(2); + } + for (int i = 0; i < m_sideNum; ++i) { + int k = i / 2; + const auto &g0Index = gFace[k % gFace.size()]; + const auto &g1Index = gFace[(k + 1) % gFace.size()]; + if (eIndices[i][0] == eIndices[i][1]) { + createPointsBetween(g1Index, (*m_sourcePolylines)[i][eIndices[i][0]], gRaySegmentNums[i], + &segmentsWithG[i][0]); + segmentsWithG[i][1] = segmentsWithG[i][0]; + } else { + createPointsBetween(g0Index, (*m_sourcePolylines)[i][eIndices[i][0]], gRaySegmentNums[i], + &segmentsWithG[i][0]); + createPointsBetween(g1Index, (*m_sourcePolylines)[i][eIndices[i][1]], gRaySegmentNums[i], + &segmentsWithG[i][1]); + } + } + + std::vector>> reversedSegmentsWithG(m_sideNum); + reversedSegmentsWithG = segmentsWithG; + for (int i = 0; i < m_sideNum; ++i) { + for (int x = 0; x < 2; ++x) + std::reverse(reversedSegmentsWithG[i][x].begin(), reversedSegmentsWithG[i][x].end()); + } + + for (int i = 0; i < m_sideNum; ++i) { + int j = (i + 1) % m_sideNum; + std::vector e0c1; + collectEdgePoints(i, eIndices[i][1], (*m_sourcePolylines)[i].size() - 1, &e0c1); + std::vector c1e1; + collectEdgePoints(j, 0, eIndices[j][0], &c1e1); + std::vector> region = { + e0c1, c1e1, reversedSegmentsWithG[j][0], segmentsWithG[i][1] + }; + m_newRegions.push_back(region); + } + + // Rectangle slices + for (size_t k = 0; k < gFace.size(); ++k) { + int i = k * 2; + std::vector e1e2 = { + (*m_sourcePolylines)[i][eIndices[i][0]], + (*m_sourcePolylines)[i][eIndices[i][1]] + }; + std::vector g1g0 = { + gFace[(k + 1) % gFace.size()], + gFace[k % gFace.size()] + }; + std::vector> region = { + g1g0, segmentsWithG[i][0], e1e2, reversedSegmentsWithG[i][1] + }; + m_newRegions.push_back(region); + } + + return true; +} + +bool RegionFiller::resolveQuadrilateralRegionMismatchSimilarCase(int m, int n, int p, int q, bool pqSwapped) +{ + return resolveQuadrilateralRegionWithIntegerSolution(m, n, p, q, pqSwapped); +} + +bool RegionFiller::resolveQuadrilateralRegionWithNonIntegerSolution(int m, int n, int p, int q, bool pqSwapped) +{ + float aPlusB = m_sideSegmentNums[m] - m_sideSegmentNums[n]; + float aFloat = ((m_sideSegmentNums[m] - m_sideSegmentNums[n]) + (m_sideSegmentNums[p] - m_sideSegmentNums[q])) / 2.0; + float bFloat = (m_sideSegmentNums[m] - m_sideSegmentNums[n]) - aFloat; + float a = std::ceil(aFloat); + float b = std::ceil(bFloat); + + int c0EdgeIndex = pqSwapped ? (*m_sourcePolylines)[m].size() - 1 : 0; + int c1EdgeIndex = pqSwapped ? (*m_sourcePolylines)[p].size() - 1 : 0; + int c2EdgeIndex = pqSwapped ? (*m_sourcePolylines)[n].size() - 1 : 0; + int c3EdgeIndex = pqSwapped ? (*m_sourcePolylines)[q].size() - 1 : 0; + + int c0AlternativeEdgeIndex = pqSwapped ? 0 : (*m_sourcePolylines)[q].size() - 1; + int c1AlternativeEdgeIndex = pqSwapped ? 0 : (*m_sourcePolylines)[m].size() - 1; + int c2AlternativeEdgeIndex = pqSwapped ? 0 : (*m_sourcePolylines)[p].size() - 1; + int c3AlternativeEdgeIndex = pqSwapped ? 0 : (*m_sourcePolylines)[n].size() - 1; + + const auto &c0Index = (*m_sourcePolylines)[m][c0EdgeIndex]; + const auto &c1Index = (*m_sourcePolylines)[p][c1EdgeIndex]; + + const auto &c0 = (*m_sourceVertices)[c0Index].position; + const auto &c1 = (*m_sourceVertices)[c1Index].position; + + int f0 = m_sideSegmentNums[n] / 2; + int f1 = m_sideSegmentNums[n] - f0; + int f2 = std::ceil((m_sideSegmentNums[p] - a) / 2.0); + int f3 = m_sideSegmentNums[p] - a - f2; + + if (f3 < 0) { + qDebug() << "resolveQuadrilateralRegionWithNonIntegerSolution failed, f3:" << f3; + return false; + } + + int e0EdgeIndex = f0; + if (pqSwapped) + e0EdgeIndex = (*m_sourcePolylines)[m].size() - 1 - e0EdgeIndex; + const auto &e0Index = (*m_sourcePolylines)[m][e0EdgeIndex]; + + int e2EdgeIndex = f0 + aPlusB; + if (pqSwapped) + e2EdgeIndex = (*m_sourcePolylines)[m].size() - 1 - e2EdgeIndex; + const auto &e2Index = (*m_sourcePolylines)[m][e2EdgeIndex]; + + int e3EdgeIndex = f2; + if (pqSwapped) + e3EdgeIndex = (*m_sourcePolylines)[p].size() - 1 - e3EdgeIndex; + const auto &e3Index = (*m_sourcePolylines)[p][e3EdgeIndex]; + + int e4EdgeIndex = (*m_sourcePolylines)[p].size() - 1 - f3; + if (pqSwapped) + e4EdgeIndex = (*m_sourcePolylines)[p].size() - 1 - e4EdgeIndex; + const auto &e4Index = (*m_sourcePolylines)[p][e4EdgeIndex]; + + int e5EdgeIndex = f3; + if (pqSwapped) + e5EdgeIndex = (*m_sourcePolylines)[q].size() - 1 - e5EdgeIndex; + const auto &e5Index = (*m_sourcePolylines)[q][e5EdgeIndex]; + + int e6EdgeIndex = (*m_sourcePolylines)[q].size() - 1 - f2; + if (pqSwapped) + e6EdgeIndex = (*m_sourcePolylines)[q].size() - 1 - e6EdgeIndex; + const auto &e6Index = (*m_sourcePolylines)[q][e6EdgeIndex]; + + int e7EdgeIndex = f1; + if (pqSwapped) + e7EdgeIndex = (*m_sourcePolylines)[n].size() - 1 - e7EdgeIndex; + const auto &e7Index = (*m_sourcePolylines)[n][e7EdgeIndex]; + + const auto &e0 = (*m_sourceVertices)[e0Index].position; + const auto &e2 = (*m_sourceVertices)[e2Index].position; + const auto &e3 = (*m_sourceVertices)[e3Index].position; + const auto &e4 = (*m_sourceVertices)[e4Index].position; + const auto &e5 = (*m_sourceVertices)[e5Index].position; + const auto &e6 = (*m_sourceVertices)[e6Index].position; + const auto &e7 = (*m_sourceVertices)[e7Index].position; + + auto g1InitialPosition = e0 + e6 - c0; + auto g2InitialPosition = e2 + e3 - c1; + auto gpInitialPosition1 = (e5 + e4) * 0.5; + auto gpInitialPosition2 = gpInitialPosition1; + auto g1a = f0 * f2; + auto g2a = f1 * f2; + auto gp1a = f0 * f3 + f0 * b; + auto gp2a = f1 * f3 + f1 * a; + + float weightG1 = 1.0 / g1a; + float weightG2 = 1.0 / g2a; + float weightGp1 = 1.0 / gp1a; + float weightGp2 = 1.0 / gp2a; + + size_t gSource = 0; + auto gRadius = averageRadius(&gSource); + + auto gPosition = (g1InitialPosition * weightG1 + + g2InitialPosition * weightG2 + + gpInitialPosition1 * weightGp1 + + gpInitialPosition2 * weightGp2) / + (weightG1 + weightG2 + weightGp1 + weightGp2); + + auto averageOffsetDistance = e0.distanceToPoint(e2); + + auto g1Position = gPosition + (((e0 + e6) * 0.5) - gPosition).normalized() * averageOffsetDistance * 0.7; + auto g2Position = gPosition + (((e2 + e3) * 0.5) - gPosition).normalized() * averageOffsetDistance * 0.7; + auto gpPosition = gPosition + (e7 - gPosition).normalized() * averageOffsetDistance * 0.7; + + auto g1Index = m_oldAndNewVertices.size(); + { + Node node; + node.position = g1Position; + node.radius = gRadius; + node.source = gSource; + m_centerSources.insert(node.source); + m_oldAndNewVertices.push_back(node); + } + + auto g2Index = m_oldAndNewVertices.size(); + { + Node node; + node.position = g2Position; + node.radius = gRadius; + node.source = gSource; + m_centerSources.insert(node.source); + m_oldAndNewVertices.push_back(node); + } + + auto gpIndex = m_oldAndNewVertices.size(); + if (0 != f3) { + Node node; + node.position = gpPosition; + node.radius = gRadius; + node.source = gSource; + m_centerSources.insert(node.source); + m_oldAndNewVertices.push_back(node); + } else { + gpIndex = e7Index; + } + + std::map, std::vector> map; + + { + std::vector> region = { + collectEdgePoints(m, c0EdgeIndex, e0EdgeIndex), + createPointsToMapBetween(e0Index, g1Index, f2, &map), + createPointsToMapBetween(g1Index, e6Index, f0, &map), + collectEdgePoints(q, e6EdgeIndex, c0AlternativeEdgeIndex) + }; + m_newRegions.push_back(region); + } + + { + std::vector> region = { + collectEdgePoints(m, e0EdgeIndex, e2EdgeIndex), + createPointsToMapBetween(e2Index, g2Index, f2, &map), + createPointsToMapBetween(g2Index, g1Index, aPlusB, &map), + createPointsToMapBetween(g1Index, e0Index, f2, &map) + }; + m_newRegions.push_back(region); + } + + { + std::vector> region = { + collectEdgePoints(m, e2EdgeIndex, c1AlternativeEdgeIndex), + collectEdgePoints(p, c1EdgeIndex, e3EdgeIndex), + createPointsToMapBetween(e3Index, g2Index, f1, &map), + createPointsToMapBetween(g2Index, e2Index, f2, &map) + }; + m_newRegions.push_back(region); + } + + if (gpIndex != e7Index) { + std::vector> region = { + createPointsToMapBetween(g2Index, e3Index, f1, &map), + collectEdgePoints(p, e3EdgeIndex, e4EdgeIndex), + createPointsToMapBetween(e4Index, gpIndex, f1, &map), + createPointsToMapBetween(gpIndex, g2Index, a, &map) + }; + m_newRegions.push_back(region); + } else { + std::vector> region = { + createPointsToMapBetween(g2Index, e3Index, f1, &map), + collectEdgePoints(p, e3EdgeIndex, c2AlternativeEdgeIndex), + collectEdgePoints(n, c2EdgeIndex, e7EdgeIndex), + createPointsToMapBetween(gpIndex, g2Index, a, &map) + }; + m_newRegions.push_back(region); + } + + if (gpIndex != e7Index) { + std::vector> region = { + collectEdgePoints(p, e4EdgeIndex, c2AlternativeEdgeIndex), + collectEdgePoints(n, c2EdgeIndex, e7EdgeIndex), + createPointsToMapBetween(e7Index, gpIndex, f3, &map), + createPointsToMapBetween(gpIndex, e4Index, f1, &map) + }; + m_newRegions.push_back(region); + } + + if (gpIndex != e7Index) { + std::vector> region = { + createPointsToMapBetween(gpIndex, e7Index, f3, &map), + collectEdgePoints(n, e7EdgeIndex, c3AlternativeEdgeIndex), + collectEdgePoints(q, c3EdgeIndex, e5EdgeIndex), + createPointsToMapBetween(e5Index, gpIndex, f0, &map) + }; + m_newRegions.push_back(region); + } + + if (gpIndex != e7Index) { + std::vector> region = { + createPointsToMapBetween(g1Index, gpIndex, b, &map), + createPointsToMapBetween(gpIndex, e5Index, f0, &map), + collectEdgePoints(q, e5EdgeIndex, e6EdgeIndex), + createPointsToMapBetween(e6Index, g1Index, f0, &map) + }; + m_newRegions.push_back(region); + } else { + std::vector> region = { + createPointsToMapBetween(g1Index, gpIndex, b, &map), + collectEdgePoints(n, e7EdgeIndex, c3AlternativeEdgeIndex), + collectEdgePoints(q, c3EdgeIndex, e6EdgeIndex), + createPointsToMapBetween(e6Index, g1Index, f0, &map) + }; + m_newRegions.push_back(region); + } + { + std::vector> region = { + createPointsToMapBetween(g1Index, g2Index, aPlusB, &map), + createPointsToMapBetween(g2Index, gpIndex, a, &map), + createPointsToMapBetween(gpIndex, g1Index, b, &map) + }; + m_newRegions.push_back(region); + } + return true; +} + +bool RegionFiller::resolveQuadrilateralRegionWithIntegerSolution(int m, int n, int p, int q, bool pqSwapped) +{ + // a + b = m − n + // a − b = p − q + // (a + b) + (a - b) = (m - n) + (p - q) + // a = ((m - n) + (p - q)) / 2 + + int a = ((m_sideSegmentNums[m] - m_sideSegmentNums[n]) + (m_sideSegmentNums[p] - m_sideSegmentNums[q])) / 2; + int b = (m_sideSegmentNums[m] - m_sideSegmentNums[n]) - a; + + bool isMismatchSimilar = 0 == b; + + int f0 = m_sideSegmentNums[n] / 2; + int f1 = m_sideSegmentNums[n] - f0; + int f2 = (m_sideSegmentNums[q] - b) / 2; + int f3 = m_sideSegmentNums[q] - b - f2; + + if (f3 < 0) { + qDebug() << "resolveQuadrilateralRegionWithIntegerSolution failed, f3:" << f3; + return false; + } + + float a1 = f0 * f2; + float a2 = f0 * b; + float a3 = f0 * f3; + float a4 = f1 * f2; + float a5 = f1 * a; + float a6 = f1 * f3; + float a7 = a * f2; + float a8 = b * f2; + float a9 = a * b; + + int c0EdgeIndex = pqSwapped ? (*m_sourcePolylines)[m].size() - 1 : 0; + int c1EdgeIndex = pqSwapped ? (*m_sourcePolylines)[p].size() - 1 : 0; + int c2EdgeIndex = pqSwapped ? (*m_sourcePolylines)[n].size() - 1 : 0; + int c3EdgeIndex = pqSwapped ? (*m_sourcePolylines)[q].size() - 1 : 0; + + int c0AlternativeEdgeIndex = pqSwapped ? 0 : (*m_sourcePolylines)[q].size() - 1; + int c1AlternativeEdgeIndex = pqSwapped ? 0 : (*m_sourcePolylines)[m].size() - 1; + int c2AlternativeEdgeIndex = pqSwapped ? 0 : (*m_sourcePolylines)[p].size() - 1; + int c3AlternativeEdgeIndex = pqSwapped ? 0 : (*m_sourcePolylines)[n].size() - 1; + + const auto &c0Index = (*m_sourcePolylines)[m][c0EdgeIndex]; + const auto &c1Index = (*m_sourcePolylines)[p][c1EdgeIndex]; + const auto &c2Index = (*m_sourcePolylines)[n][c2EdgeIndex]; + const auto &c3Index = (*m_sourcePolylines)[q][c3EdgeIndex]; + + const auto &c0 = (*m_sourceVertices)[c0Index].position; + const auto &c1 = (*m_sourceVertices)[c1Index].position; + const auto &c2 = (*m_sourceVertices)[c2Index].position; + const auto &c3 = (*m_sourceVertices)[c3Index].position; + + int e0EdgeIndex = f0; + if (pqSwapped) + e0EdgeIndex = (*m_sourcePolylines)[m].size() - 1 - e0EdgeIndex; + const auto &e0Index = (*m_sourcePolylines)[m][e0EdgeIndex]; + + int e1EdgeIndex = f0 + a; + if (pqSwapped) + e1EdgeIndex = (*m_sourcePolylines)[m].size() - 1 - e1EdgeIndex; + const auto &e1Index = (*m_sourcePolylines)[m][e1EdgeIndex]; + + int e2EdgeIndex = f0 + a + b; + if (pqSwapped) + e2EdgeIndex = (*m_sourcePolylines)[m].size() - 1 - e2EdgeIndex; + const auto &e2Index = (*m_sourcePolylines)[m][e2EdgeIndex]; + + int e3EdgeIndex = f2; + if (pqSwapped) + e3EdgeIndex = (*m_sourcePolylines)[p].size() - 1 - e3EdgeIndex; + const auto &e3Index = (*m_sourcePolylines)[p][e3EdgeIndex]; + + int e4EdgeIndex = f2 + a; + if (pqSwapped) + e4EdgeIndex = (*m_sourcePolylines)[p].size() - 1 - e4EdgeIndex; + const auto &e4Index = (*m_sourcePolylines)[p][e4EdgeIndex]; + + int e5EdgeIndex = f3; + if (pqSwapped) + e5EdgeIndex = (*m_sourcePolylines)[q].size() - 1 - e5EdgeIndex; + const auto &e5Index = (*m_sourcePolylines)[q][e5EdgeIndex]; + + int e6EdgeIndex = f3 + b; + if (pqSwapped) + e6EdgeIndex = (*m_sourcePolylines)[q].size() - 1 - e6EdgeIndex; + const auto &e6Index = (*m_sourcePolylines)[q][e6EdgeIndex]; + + int e7EdgeIndex = f1; + if (pqSwapped) + e7EdgeIndex = (*m_sourcePolylines)[n].size() - 1 - e7EdgeIndex; + const auto &e7Index = (*m_sourcePolylines)[n][e7EdgeIndex]; + + const auto &e0 = (*m_sourceVertices)[e0Index].position; + const auto &e1 = (*m_sourceVertices)[e1Index].position; + const auto &e2 = (*m_sourceVertices)[e2Index].position; + const auto &e3 = (*m_sourceVertices)[e3Index].position; + const auto &e4 = (*m_sourceVertices)[e4Index].position; + const auto &e5 = (*m_sourceVertices)[e5Index].position; + const auto &e6 = (*m_sourceVertices)[e6Index].position; + const auto &e7 = (*m_sourceVertices)[e7Index].position; + + auto g1 = e1 + e5 - c0; + auto gn = e1 + e4 - c1; + auto g2 = e4 + e7 - c2; + auto gp = e5 + e7 - c3; + + auto weight1 = 1.0 / (a1 + a2 + a7 + a9); + auto weightn = 1.0 / (a4 + a5 + a8 + a9); + auto weight2 = 1.0 / (a6); + auto weightp = 1.0 / (a3); + + auto gPosition = (g1 * weight1 + gn * weightn + g2 * weight2 + gp * weightp) / + (weight1 + weightn + weight2 + weightp); + + size_t gSource = 0; + auto gRadius = averageRadius(&gSource); + + auto averageOffsetDistance = e0.distanceToPoint(e2); + if (!isMismatchSimilar) + averageOffsetDistance *= 0.5; + auto g1Position = gPosition + (e6 - gPosition).normalized() * averageOffsetDistance * 0.7; + auto gnPosition = gPosition + (e1 - gPosition).normalized() * averageOffsetDistance * 0.7; + auto g2Position = gPosition + (e3 - gPosition).normalized() * averageOffsetDistance * 0.7; + auto gpPosition = gPosition + (e7 - gPosition).normalized() * averageOffsetDistance * 0.7; + + if (isMismatchSimilar) { + gpPosition = ((*m_sourceVertices)[e0Index].position + + (*m_sourceVertices)[e5Index].position + + (*m_sourceVertices)[e7Index].position + + (*m_sourceVertices)[e4Index].position) / 4.0; + gnPosition = ((*m_sourceVertices)[e0Index].position + + gpPosition + + (*m_sourceVertices)[e3Index].position + + (*m_sourceVertices)[c1Index].position) / 4.0; + } + + size_t g1Index = m_oldAndNewVertices.size(); + if (!isMismatchSimilar) { + Node node; + node.position = g1Position; + node.radius = gRadius; + node.source = gSource; + m_centerSources.insert(node.source); + m_oldAndNewVertices.push_back(node); + } + + size_t gnIndex = m_oldAndNewVertices.size(); + { + Node node; + node.position = gnPosition; + node.radius = gRadius; + node.source = gSource; + m_centerSources.insert(node.source); + m_oldAndNewVertices.push_back(node); + } + + size_t g2Index = m_oldAndNewVertices.size(); + if (!isMismatchSimilar) { + Node node; + node.position = g2Position; + node.radius = gRadius; + node.source = gSource; + m_centerSources.insert(node.source); + m_oldAndNewVertices.push_back(node); + } + + size_t gpIndex = m_oldAndNewVertices.size(); + { + Node node; + node.position = gpPosition; + node.radius = gRadius; + node.source = gSource; + m_centerSources.insert(node.source); + m_oldAndNewVertices.push_back(node); + } + + if (isMismatchSimilar) { + g1Index = gpIndex; + g2Index = gnIndex; + } + + std::map, std::vector> map; + + std::vector> region1 = { + collectEdgePoints(m, c0EdgeIndex, e0EdgeIndex), + createPointsToMapBetween(e0Index, g1Index, f2, &map), + createPointsToMapBetween(g1Index, e6Index, f0, &map), + collectEdgePoints(q, e6EdgeIndex, c0AlternativeEdgeIndex) + }; + m_newRegions.push_back(region1); + + if (e5Index != e6Index) { + std::vector> region2 = { + createPointsToMapBetween(e6Index, g1Index, f0, &map), + createPointsToMapBetween(g1Index, gpIndex, b, &map), + createPointsToMapBetween(gpIndex, e5Index, f0, &map), + collectEdgePoints(q, e5EdgeIndex, e6EdgeIndex) + }; + m_newRegions.push_back(region2); + } + + std::vector> region3 = { + createPointsToMapBetween(e5Index, gpIndex, f0, &map), + createPointsToMapBetween(gpIndex, e7Index, f3, &map), + collectEdgePoints(n, e7EdgeIndex, c3AlternativeEdgeIndex), + collectEdgePoints(q, c3EdgeIndex, e5EdgeIndex) + }; + m_newRegions.push_back(region3); + + std::vector> region4 = { + collectEdgePoints(m, e2EdgeIndex, c1AlternativeEdgeIndex), + collectEdgePoints(p, c1EdgeIndex, e3EdgeIndex), + createPointsToMapBetween(e3Index, g2Index, f1, &map), + createPointsToMapBetween(g2Index, e2Index, f2, &map) + }; + m_newRegions.push_back(region4); + + if (e3Index != e4Index) { + std::vector> region5 = { + createPointsToMapBetween(g2Index, e3Index, f1, &map), + collectEdgePoints(p, e3EdgeIndex, e4EdgeIndex), + createPointsToMapBetween(e4Index, gpIndex, f1, &map), + createPointsToMapBetween(gpIndex, g2Index, a, &map) + }; + m_newRegions.push_back(region5); + } + + std::vector> region6 = { + createPointsToMapBetween(gpIndex, e4Index, f1, &map), + collectEdgePoints(p, e4EdgeIndex, c2AlternativeEdgeIndex), + collectEdgePoints(n, c2EdgeIndex, e7EdgeIndex), + createPointsToMapBetween(e7Index, gpIndex, f3, &map) + }; + m_newRegions.push_back(region6); + + if (g1Index != gnIndex && e0Index != e1Index) { + std::vector> region7 = { + collectEdgePoints(m, e0EdgeIndex, e1EdgeIndex), + createPointsToMapBetween(e1Index, gnIndex, f2, &map), + createPointsToMapBetween(gnIndex, g1Index, a, &map), + createPointsToMapBetween(g1Index, e0Index, f2, &map) + }; + m_newRegions.push_back(region7); + } + + if (g2Index != gnIndex && e1Index != e2Index) { + std::vector> region8 = { + collectEdgePoints(m, e1EdgeIndex, e2EdgeIndex), + createPointsToMapBetween(e2Index, g2Index, f2, &map), + createPointsToMapBetween(g2Index, gnIndex, b, &map), + createPointsToMapBetween(gnIndex, e1Index, f2, &map) + }; + m_newRegions.push_back(region8); + } + + if (!isMismatchSimilar) { + std::vector> region9 = { + createPointsToMapBetween(g1Index, gnIndex, a, &map), + createPointsToMapBetween(gnIndex, g2Index, b, &map), + createPointsToMapBetween(g2Index, gpIndex, a, &map), + createPointsToMapBetween(gpIndex, g1Index, b, &map) + }; + m_newRegions.push_back(region9); + } + + return true; +} + +bool RegionFiller::resolveQuadrilateralRegion() +{ + int diff02 = std::abs(m_sideSegmentNums[0] - m_sideSegmentNums[2]); + int diff13 = std::abs(m_sideSegmentNums[1] - m_sideSegmentNums[3]); + int m, n, p, q; + if (diff02 >= diff13) { + if (m_sideSegmentNums[0] > m_sideSegmentNums[2]) { + m = 0; + n = 2; + p = 1; + q = 3; + } else { + m = 2; + n = 0; + p = 3; + q = 1; + } + } else { + if (m_sideSegmentNums[1] > m_sideSegmentNums[3]) { + m = 1; + n = 3; + p = 2; + q = 0; + } else { + m = 3; + n = 1; + p = 0; + q = 2; + } + } + bool pqSwapped = false; + if (m_sideSegmentNums[p] < m_sideSegmentNums[q]) { + std::swap(p, q); + pqSwapped = true; + } + if (diff02 != diff13) { + if (isEven(m_sumOfSegments)) { + return resolveQuadrilateralRegionWithIntegerSolution(m, n, p, q, pqSwapped); + } else { + return resolveQuadrilateralRegionWithNonIntegerSolution(m, n, p, q, pqSwapped); + } + } else { + return resolveQuadrilateralRegionMismatchSimilarCase(m, n, p, q, pqSwapped); + } +} + +const std::vector &RegionFiller::getOldAndNewVertices() +{ + return m_oldAndNewVertices; +} + +const std::vector> &RegionFiller::getNewFaces() +{ + return m_newFaces; +} + +void RegionFiller::prepare() +{ + m_sideNum = m_sourcePolylines->size(); + m_sideSegmentNums.clear(); + for (const auto &it: *m_sourcePolylines) { + auto l = (int)it.size() - 1; + m_sideSegmentNums.push_back(l); + } + m_sumOfSegments = std::accumulate(m_sideSegmentNums.begin(), m_sideSegmentNums.end(), 0); +} + +bool RegionFiller::resolveQuadrilateralRegionDirectCase() +{ + std::vector> region = { + collectEdgePoints(0, 0, (*m_sourcePolylines)[0].size() - 1), + collectEdgePoints(1, 0, (*m_sourcePolylines)[1].size() - 1), + collectEdgePoints(2, 0, (*m_sourcePolylines)[2].size() - 1), + collectEdgePoints(3, 0, (*m_sourcePolylines)[3].size() - 1) + }; + m_newRegions.push_back(region); + return true; +} + +void RegionFiller::fillWithoutPartition() +{ + m_oldAndNewVertices = *m_sourceVertices; + m_newFaces.clear(); + convertPolylinesToFaces(); +} + +bool RegionFiller::fill() +{ + m_oldAndNewVertices = *m_sourceVertices; + + prepare(); + + if (4 == m_sideNum) { + if (m_sideSegmentNums[0] == m_sideSegmentNums[2] && + m_sideSegmentNums[1] == m_sideSegmentNums[3]) { + if (!resolveQuadrilateralRegionDirectCase()) { + qDebug() << "resolveQuadrilateralRegionDirectCase failed"; + return false; + } + } else { + if (!resolveQuadrilateralRegion()) { + qDebug() << "resolveQuadrilateralRegion failed"; + return false; + } + } + } else { + if (isOdd(m_sideNum)) { + if (isEven(m_sumOfSegments)) { + if (!resolveOddSidedEvenSumOfSegments(m_sideNum)) { + qDebug() << "resolveOddSidedEvenSumOfSegments failed, m_sideNum:" << m_sideNum; + return false; + } + } else { + if (!resolveOddSidedOddSumOfSegments(m_sideNum)) { + qDebug() << "resolveOddSidedOddSumOfSegments failed, m_sideNum:" << m_sideNum; + return false; + } + } + } else { + int l1 = 0; + int l2 = 0; + + int halfSideNum = m_sideNum / 2; + + for (int i = 1; i <= halfSideNum; ++i) { + l1 += m_sideSegmentNums[(i * 2 - 1) % m_sideNum]; + l2 += m_sideSegmentNums[(i * 2 - 1 - 1) % m_sideNum]; + } + + if (isEven(l1) && isEven(l2)) { + if (!resolveEvenSidedEvenSumOfSegmentsAndBothL2L2AreEven()) { + qDebug() << "resolveEvenSidedEvenSumOfSegmentsAndBothL2L2AreEven failed"; + return false; + } + } else if (isOdd(l1) && isOdd(l2)) { + if (!resolveEvenSidedEvenSumOfSegmentsAndBothL2L2AreOdd()) { + qDebug() << "resolveEvenSidedEvenSumOfSegmentsAndBothL2L2AreOdd failed"; + return false; + } + } else if (isEven(l1) && isOdd(l2)) { + if (!resolveEvenSideEvenSumOfSegmentsAndL1IsEvenL2IsOdd()) { + qDebug() << "resolveEvenSideEvenSumOfSegmentsAndL1IsEvenL2IsOdd failed"; + return false; + } + } else { + if (!resolveEvenSideEvenSumOfSegmentsAndL1IsOddL2IsEven()) { + qDebug() << "resolveEvenSideEvenSumOfSegmentsAndL1IsOddL2IsEven failed"; + return false; + } + } + } + } + + if (!convertRegionsToPatches()) { + qDebug() << "convertRegionsToPatches failed"; + return false; + } + + return true; +} + +void RegionFiller::convertRegionsToFaces() +{ + if (m_newRegions.empty()) { + qDebug() << "No region, should not happen"; + return; + } + + for (const auto ®ion: m_newRegions) { + std::vector face; + for (const auto &line: region) { + for (size_t i = 1; i < line.size(); ++i) { + face.push_back(line[i]); + } + } + if (face.size() < 3) { + qDebug() << "Face length invalid:" << face.size(); + continue; + } + m_newFaces.push_back(face); + } +} + +void RegionFiller::convertPolylinesToFaces() +{ + if (m_sourcePolylines->empty()) + return; + + std::vector face; + for (const auto &line: *m_sourcePolylines) { + for (size_t i = 1; i < line.size(); ++i) { + face.push_back(line[i]); + } + } + if (face.size() < 3) { + return; + } + m_newFaces.push_back(face); +} + +bool RegionFiller::createCoonsPatchFrom(const std::vector &c0, + const std::vector &c1, + const std::vector &d0, + const std::vector &d1, + bool fillLastTriangle) +{ + auto Lc_Position = [&](int s, int t) { + float factor = (float)t / d0.size(); + return (1.0 - factor) * m_oldAndNewVertices[c0[s]].position + factor * m_oldAndNewVertices[c1[s]].position; + }; + auto Ld_Position = [&](int s, int t) { + float factor = (float)s / c0.size(); + return (1.0 - factor) * m_oldAndNewVertices[d0[t]].position + factor * m_oldAndNewVertices[d1[t]].position; + }; + auto B_Position = [&](int s, int t) { + float tFactor = (float)t / d0.size(); + float sFactor = (float)s / c0.size(); + return m_oldAndNewVertices[c0[0]].position * (1.0 - sFactor) * (1.0 - tFactor) + + m_oldAndNewVertices[c0[c0.size() - 1]].position * sFactor * (1.0 - tFactor) + + m_oldAndNewVertices[c1[0]].position * (1.0 - sFactor) * tFactor + + m_oldAndNewVertices[c1[c1.size() - 1]].position * sFactor * tFactor; + }; + auto C_Position = [&](int s, int t) { + return Lc_Position(s, t) + Ld_Position(s, t) - B_Position(s, t); + }; + + auto Lc_Radius = [&](int s, int t) { + float factor = (float)t / d0.size(); + return (1.0 - factor) * m_oldAndNewVertices[c0[s]].radius + factor * m_oldAndNewVertices[c1[s]].radius; + }; + auto Ld_Radius = [&](int s, int t) { + float factor = (float)s / c0.size(); + return (1.0 - factor) * m_oldAndNewVertices[d0[t]].radius + factor * m_oldAndNewVertices[d1[t]].radius; + }; + auto B_Radius = [&](int s, int t) { + float tFactor = (float)t / d0.size(); + float sFactor = (float)s / c0.size(); + return m_oldAndNewVertices[c0[0]].radius * (1.0 - sFactor) * (1.0 - tFactor) + + m_oldAndNewVertices[c0[c0.size() - 1]].radius * sFactor * (1.0 - tFactor) + + m_oldAndNewVertices[c1[0]].radius * (1.0 - sFactor) * tFactor + + m_oldAndNewVertices[c1[c1.size() - 1]].radius * sFactor * tFactor; + }; + auto C_Radius = [&](int s, int t) { + return Lc_Radius(s, t) + Ld_Radius(s, t) - B_Radius(s, t); + }; + + auto getSource = [&](size_t i, size_t j, float factor) { + if (m_centerSources.find(m_oldAndNewVertices[j].source) != m_centerSources.end()) { + return m_oldAndNewVertices[i].source; + } else if (m_centerSources.find(m_oldAndNewVertices[i].source) != m_centerSources.end()) { + return m_oldAndNewVertices[j].source; + } else { + if (factor > 0.5) { + return m_oldAndNewVertices[j].source; + } + return m_oldAndNewVertices[i].source; + } + }; + + auto Lc_Source = [&](int s, int t) { + float factor = (float)t / d0.size(); + return getSource(c0[s], c1[s], factor); + }; + auto C_Source = [&](int s, int t) { + return Lc_Source(s, t); + }; + + std::vector> grid(c0.size()); + for (int s = 1; s < (int)c0.size() - 1; ++s) { + grid[s].resize(d0.size()); + for (int t = 1; t < (int)d0.size() - 1; ++t) { + Node node; + node.position = C_Position(s, t); + node.radius = C_Radius(s, t); + node.source = C_Source(s, t); + grid[s][t] = m_oldAndNewVertices.size(); + m_oldAndNewVertices.push_back(node); + } + } + grid[0].resize(d0.size()); + grid[c0.size() - 1].resize(d0.size()); + for (size_t i = 0; i < c0.size(); ++i) { + grid[i][0] = c0[i]; + grid[i][d0.size() - 1] = c1[i]; + } + for (size_t i = 0; i < d0.size(); ++i) { + grid[0][i] = d0[i]; + grid[c0.size() - 1][i] = d1[i]; + } + for (int s = 1; s < (int)c0.size(); ++s) { + for (int t = 1; t < (int)d0.size(); ++t) { + std::vector face = { + grid[s - 1][t - 1], + grid[s - 1][t], + grid[s][t], + grid[s][t - 1] + }; + m_newFaces.push_back(face); + } + } + if (fillLastTriangle) { + std::vector face = { + grid[c0.size() - 1][d0.size() - 2], + grid[c0.size() - 2][d0.size() - 2], + grid[c0.size() - 2][d0.size() - 1] + }; + m_newFaces.push_back(face); + } + return true; +} + +bool RegionFiller::createCoonsPatch(const std::vector> ®ion) +{ + // https://en.wikipedia.org/wiki/Coons_patch + if (region.size() != 4) { + if (region.size() == 3) { + createCoonsPatchThreeSidedRegion(region); + return true; + } + qDebug() << "Invalid region edges:" << region.size(); + return false; + } + const auto &c0 = region[0]; + auto c1 = region[2]; + std::reverse(c1.begin(), c1.end()); + const auto &d1 = region[1]; + auto d0 = region[3]; + std::reverse(d0.begin(), d0.end()); + if (c0.empty() || + c0.size() != c1.size() || + d0.empty() || + d0.size() != d1.size()) { + qDebug() << "Invalid region size:" << c0.size() << c1.size() << d0.size() << d1.size(); + return false; + } + return createCoonsPatchFrom(c0, c1, d0, d1, false); +} + +bool RegionFiller::createCoonsPatchThreeSidedRegion(const std::vector> ®ion) +{ + if (region.size() != 3) { + qDebug() << "Not three sided region"; + return false; + } + + int longest = 0; + size_t longestLength = region[longest].size(); + for (int i = 1; i < 3; ++i) { + if (region[i].size() > longestLength) { + longest = i; + longestLength = region[i].size(); + } + } + + int c = longest; + int a = (c + 1) % 3; + int b = (a + 1) % 3; + + auto c0 = region[a]; + std::reverse(c0.begin(), c0.end()); + auto c1 = std::vector(region[c].begin(), region[c].begin() + c0.size()); + + auto d0 = region[b]; + auto d1 = std::vector(region[c].begin() + region[c].size() - d0.size(), region[c].begin() + region[c].size()); + std::reverse(d1.begin(), d1.end()); + + bool fillTriangle = region[c].size() != region[a].size() && + region[a].size() != region[b].size(); + if (c0.size() < d0.size()) { + if (!createCoonsPatchFrom(d0, d1, c0, c1, fillTriangle)) + return false; + } else { + if (!createCoonsPatchFrom(c0, c1, d0, d1, fillTriangle)) + return false; + } + + return true; +} + +bool RegionFiller::convertRegionsToPatches() +{ + if (m_newRegions.empty()) { + qDebug() << "No region, should not happen"; + return false; + } + for (const auto ®ion: m_newRegions) { + if (!createCoonsPatch(region)) + return false; + } + + return true; +} diff --git a/src/regionfiller.h b/src/regionfiller.h new file mode 100644 index 00000000..0ab01473 --- /dev/null +++ b/src/regionfiller.h @@ -0,0 +1,74 @@ +#ifndef DUST3D_REGION_FILLER_H +#define DUST3D_REGION_FILLER_H +#include +#include +#include + +class RegionFiller +{ +public: + struct Node + { + QVector3D position; + float radius = 0.0; + size_t source = 0; + }; + RegionFiller(const std::vector *vertices, + const std::vector> *polylines); + ~RegionFiller(); + bool fill(); + void fillWithoutPartition(); + const std::vector &getOldAndNewVertices(); + const std::vector> &getNewFaces(); +private: + const std::vector *m_sourceVertices = nullptr; + std::vector> *m_sourcePolylines = nullptr; + int m_sideNum = 0; + int m_sumOfSegments = 0; + std::vector m_sideSegmentNums; + std::vector m_oldAndNewVertices; + std::vector> m_newFaces; + std::vector>> m_newRegions; + std::set m_centerSources; + float averageRadius(size_t *maxNodeIndex=nullptr); + void createPointsBetween(size_t fromIndex, + size_t toIndex, + size_t segments, + std::vector *newPointIndices); + std::vector createPointsBetween(size_t fromIndex, + size_t toIndex, + size_t segments); + void collectEdgePoints(size_t polyline, int startPos, int stopPos, + std::vector *pointIndices); + std::vector collectEdgePoints(size_t polyline, int startPos, int stopPos); + std::vector reversed(const std::vector &pointIndices); + std::vector createPointsToMapBetween(size_t fromIndex, + size_t toIndex, + size_t segments, + std::map, std::vector> *map); + bool resolveOddSidedEvenSumOfSegments(int siUpperBound); + bool resolveOddSidedOddSumOfSegments(int siUpperBound); + bool resolveEvenSidedEvenSumOfSegmentsAndBothL2L2AreEven(); + bool resolveEvenSidedEvenSumOfSegmentsAndBothL2L2AreOdd(); + bool resolveEvenSideEvenSumOfSegmentsAndL1IsEvenL2IsOdd(); + bool resolveEvenSideEvenSumOfSegmentsAndL1IsOddL2IsEven(); + bool resolveQuadrilateralRegion(); + bool resolveQuadrilateralRegionWithIntegerSolution(int m, int n, int p, int q, bool pqSwapped); + bool resolveQuadrilateralRegionMismatchSimilarCase(int m, int n, int p, int q, bool pqSwapped); + bool resolveQuadrilateralRegionWithNonIntegerSolution(int m, int n, int p, int q, bool pqSwapped); + bool resolveQuadrilateralRegionDirectCase(); + void convertRegionsToFaces(); + void prepare(); + bool createCoonsPatch(const std::vector> ®ion); + bool convertRegionsToPatches(); + bool createCoonsPatchFrom(const std::vector &c0, + const std::vector &c1, + const std::vector &d0, + const std::vector &d1, + bool fillLastTriangle=false); + bool createCoonsPatchThreeSidedRegion(const std::vector> ®ion); + void convertPolylinesToFaces(); +}; + +#endif + diff --git a/src/shortestpath.cpp b/src/shortestpath.cpp new file mode 100644 index 00000000..19a0df37 --- /dev/null +++ b/src/shortestpath.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include +#include +#include "shortestpath.h" + +using namespace boost; + +bool shortestPath(size_t nodeNum, + const std::vector> &edges, + const std::vector &weights, + size_t start, + size_t stop, + std::vector *path) +{ + typedef adjacency_list < listS, vecS, undirectedS, + no_property, property < edge_weight_t, int > > graph_t; + typedef graph_traits < graph_t >::vertex_descriptor vertex_descriptor; + typedef graph_traits < graph_t >::edge_descriptor edge_descriptor; + typedef std::pair Edge; + + size_t edgeNum = edges.size(); +#if defined(BOOST_MSVC) && BOOST_MSVC <= 1300 + graph_t g(nodeNum); + property_map::type weightmap = get(edge_weight, g); + for (std::size_t j = 0; j < edgeNum; ++j) { + edge_descriptor e; bool inserted; + tie(e, inserted) = add_edge(edges[j].first, edges[j].second, g); + weightmap[e] = weights[j]; + } +#else + graph_t g(edges.data(), edges.data() + edgeNum, weights.data(), nodeNum); + property_map::type weightmap = get(edge_weight, g); +#endif + + std::vector p(num_vertices(g)); + std::vector d(num_vertices(g)); + vertex_descriptor s = vertex(start, g); + +#if defined(BOOST_MSVC) && BOOST_MSVC <= 1300 + // VC++ has trouble with the named parameters mechanism + property_map::type indexmap = get(vertex_index, g); + dijkstra_shortest_paths(g, s, &p[0], &d[0], weightmap, indexmap, + std::less(), closed_plus(), + (std::numeric_limits::max)(), 0, + default_dijkstra_visitor()); +#else + dijkstra_shortest_paths(g, s, predecessor_map(&p[0]).distance_map(&d[0])); +#endif + + auto current = stop; + while (current != start) { + path->push_back(current); + size_t next = p[current]; + if (next == current) + return false; + current = next; + } + path->push_back(current); + + return true; +} diff --git a/src/shortestpath.h b/src/shortestpath.h new file mode 100644 index 00000000..aa1ccce4 --- /dev/null +++ b/src/shortestpath.h @@ -0,0 +1,46 @@ +#ifndef DUST3D_SHORTEST_PATH +#define DUST3D_SHORTEST_PATH +#include +#include + +/* + +Example code: + + std::vector nodeNames = { + "A", //0 + "B", //1 + "C", //2 + "D", //3 + "E", //4 + "F" //5 + }; + std::vector> edges = { + {1,0}, + {1,3}, + {1,4}, + {3,4}, + {3,5}, + {4,5}, + {5,2}, + {0,2} + }; + std::vector weights(edges.size(), 1); + weights[7] = 10; + weights[4] = 10; + std::vector path; + shortestPath(nodeNames.size(), edges, weights, 0, 5, &path); + for (const auto &it: path) { + qDebug() << nodeNames[it]; + } + +*/ + +bool shortestPath(size_t nodeNum, + const std::vector> &edges, + const std::vector &weights, + size_t start, + size_t stop, + std::vector *path); + +#endif diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp index 6b3931ac..29ce06bd 100644 --- a/src/skeletondocument.cpp +++ b/src/skeletondocument.cpp @@ -73,10 +73,5 @@ void SkeletonDocument::findAllNeighbors(QUuid nodeId, std::set &neighbors bool SkeletonDocument::isNodeConnectable(QUuid nodeId) const { - if (threeNodesBranchEnabled) - return true; - const SkeletonNode *node = findNode(nodeId); - if (nullptr == node) - return false; - return node->edgeIds.size() < 2; + return true; } diff --git a/src/skeletondocument.h b/src/skeletondocument.h index c5322c20..1f709742 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -179,6 +179,7 @@ public: QUuid deformMapImageId; float hollowThickness; bool countershaded; + bool gridded; SkeletonPart(const QUuid &withId=QUuid()) : visible(true), locked(false), @@ -200,7 +201,8 @@ public: colorSolubility(0.0), deformMapScale(1.0), hollowThickness(0.0), - countershaded(false) + countershaded(false), + gridded(false) { id = withId.isNull() ? QUuid::createUuid() : withId; } @@ -357,7 +359,6 @@ public: bool ylocked = false; bool zlocked = false; bool radiusLocked = false; - bool threeNodesBranchEnabled = Preferences::instance().threeNodesBranchEnabled(); QImage turnaround; QByteArray turnaroundPngByteArray; std::map partMap; diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index 62b11863..6fcb7acb 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -259,6 +259,14 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos) contextMenu.addAction(&clearCutFaceAction); } + QAction createWrapPartsAction(tr("Create Wrap Parts"), this); + if (!m_nodePositionModifyOnly && hasSelection()) { + connect(&createWrapPartsAction, &QAction::triggered, this, [&]() { + createWrapParts(); + }); + contextMenu.addAction(&createWrapPartsAction); + } + QAction alignToLocalCenterAction(tr("Local Center"), this); QAction alignToLocalVerticalCenterAction(tr("Local Vertical Center"), this); QAction alignToLocalHorizontalCenterAction(tr("Local Horizontal Center"), this); @@ -2894,4 +2902,14 @@ void SkeletonGraphicsWidget::setMainProfileOnly(bool mainProfileOnly) m_mainProfileOnly = mainProfileOnly; } +void SkeletonGraphicsWidget::createWrapParts() +{ + std::set nodeItemSet; + readMergedSkeletonNodeSetFromRangeSelection(&nodeItemSet); + std::set nodeIds; + for (const auto &it: nodeItemSet) { + nodeIds.insert(it->id()); + } + emit createGriddedPartsFromNodes(nodeIds); +} diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h index 7d03f5fc..9cd9fc3b 100644 --- a/src/skeletongraphicswidget.h +++ b/src/skeletongraphicswidget.h @@ -440,6 +440,7 @@ signals: void showOrHideAllComponents(); void shortcutToggleFlatShading(); void shortcutToggleRotation(); + void createGriddedPartsFromNodes(const std::set &nodeIds); public: SkeletonGraphicsWidget(const SkeletonDocument *document); std::map> nodeItemMap; @@ -540,6 +541,7 @@ public slots: void showSelectedCutFaceSettingPopup(const QPoint &pos); void clearSelectedCutFace(); void setRotated(bool rotated); + void createWrapParts(); void shortcutDelete(); void shortcutAddMode(); void shortcutUndo(); diff --git a/thirdparty/nodemesh/nodemesh/builder.cpp b/src/strokemeshbuilder.cpp similarity index 90% rename from thirdparty/nodemesh/nodemesh/builder.cpp rename to src/strokemeshbuilder.cpp index 7e24f7df..b6457619 100644 --- a/thirdparty/nodemesh/nodemesh/builder.cpp +++ b/src/strokemeshbuilder.cpp @@ -3,22 +3,24 @@ #include #include #include -#include -#include -#include -#include -#include #include #include #include +#include +#include +#include +#include +#include +#include "strokemeshbuilder.h" +#include "meshstitcher.h" +#include "boxmesh.h" +#include "meshcombiner.h" +#include "util.h" #define WRAP_STEP_BACK_FACTOR 0.1 // 0.1 ~ 0.9 #define WRAP_WELD_FACTOR 0.01 // Allowed distance: WELD_FACTOR * radius -namespace nodemesh -{ - -size_t Builder::addNode(const QVector3D &position, float radius, const std::vector &cutTemplate, float cutRotation) +size_t StrokeMeshBuilder::addNode(const QVector3D &position, float radius, const std::vector &cutTemplate, float cutRotation) { size_t nodeIndex = m_nodes.size(); Node node; @@ -32,7 +34,7 @@ size_t Builder::addNode(const QVector3D &position, float radius, const std::vect return nodeIndex; } -size_t Builder::addEdge(size_t firstNodeIndex, size_t secondNodeIndex) +size_t StrokeMeshBuilder::addEdge(size_t firstNodeIndex, size_t secondNodeIndex) { size_t edgeIndex = m_edges.size(); Edge edge; @@ -44,22 +46,22 @@ size_t Builder::addEdge(size_t firstNodeIndex, size_t secondNodeIndex) return edgeIndex; } -const std::vector &Builder::generatedVertices() +const std::vector &StrokeMeshBuilder::generatedVertices() { return m_generatedVertices; } -const std::vector> &Builder::generatedFaces() +const std::vector> &StrokeMeshBuilder::generatedFaces() { return m_generatedFaces; } -const std::vector &Builder::generatedVerticesSourceNodeIndices() +const std::vector &StrokeMeshBuilder::generatedVerticesSourceNodeIndices() { return m_generatedVerticesSourceNodeIndices; } -void Builder::layoutNodes() +void StrokeMeshBuilder::layoutNodes() { std::unordered_set processedNodes; std::queue waitNodes; @@ -172,12 +174,12 @@ void Builder::layoutNodes() m_sortedNodeIndices.insert(m_sortedNodeIndices.begin(), threeBranchNodes.begin(), threeBranchNodes.end()); } -void Builder::sortNodeIndices() +void StrokeMeshBuilder::sortNodeIndices() { layoutNodes(); } -void Builder::prepareNode(size_t nodeIndex) +void StrokeMeshBuilder::prepareNode(size_t nodeIndex) { auto &node = m_nodes[nodeIndex]; node.raysToNeibors.resize(node.edges.size()); @@ -204,14 +206,14 @@ void Builder::prepareNode(size_t nodeIndex) node.initialBaseNormal = revisedBaseNormalAcordingToCutNormal(node.initialBaseNormal, node.traverseDirection); } -void Builder::setNodeOriginInfo(size_t nodeIndex, int nearOriginNodeIndex, int farOriginNodeIndex) +void StrokeMeshBuilder::setNodeOriginInfo(size_t nodeIndex, int nearOriginNodeIndex, int farOriginNodeIndex) { auto &node = m_nodes[nodeIndex]; node.nearOriginNodeIndex = nearOriginNodeIndex; node.farOriginNodeIndex = farOriginNodeIndex; } -QVector3D Builder::calculateBaseNormalFromTraverseDirection(const QVector3D &traverseDirection) +QVector3D StrokeMeshBuilder::calculateBaseNormalFromTraverseDirection(const QVector3D &traverseDirection) { const std::vector axisList = { QVector3D {1, 0, 0}, @@ -238,7 +240,7 @@ QVector3D Builder::calculateBaseNormalFromTraverseDirection(const QVector3D &tra return reversed ? -baseNormal : baseNormal; } -void Builder::resolveBaseNormalRecursively(size_t nodeIndex) +void StrokeMeshBuilder::resolveBaseNormalRecursively(size_t nodeIndex) { auto &node = m_nodes[nodeIndex]; if (node.baseNormalResolved) @@ -258,7 +260,7 @@ void Builder::resolveBaseNormalRecursively(size_t nodeIndex) } } -void Builder::resolveBaseNormalForLeavesRecursively(size_t nodeIndex, const QVector3D &baseNormal) +void StrokeMeshBuilder::resolveBaseNormalForLeavesRecursively(size_t nodeIndex, const QVector3D &baseNormal) { auto &node = m_nodes[nodeIndex]; if (node.baseNormalResolved) @@ -284,7 +286,7 @@ void Builder::resolveBaseNormalForLeavesRecursively(size_t nodeIndex, const QVec } } -void Builder::resolveInitialTraverseDirectionRecursively(size_t nodeIndex, const QVector3D *from, std::set *visited) +void StrokeMeshBuilder::resolveInitialTraverseDirectionRecursively(size_t nodeIndex, const QVector3D *from, std::set *visited) { if (visited->find(nodeIndex) != visited->end()) return; @@ -301,7 +303,7 @@ void Builder::resolveInitialTraverseDirectionRecursively(size_t nodeIndex, const } } -void Builder::resolveTraverseDirection(size_t nodeIndex) +void StrokeMeshBuilder::resolveTraverseDirection(size_t nodeIndex) { auto &node = m_nodes[nodeIndex]; if (!node.hasInitialTraverseDirection) { @@ -324,7 +326,7 @@ void Builder::resolveTraverseDirection(size_t nodeIndex) } } -std::pair Builder::searchBaseNormalFromNeighborsRecursively(size_t nodeIndex) +std::pair StrokeMeshBuilder::searchBaseNormalFromNeighborsRecursively(size_t nodeIndex) { auto &node = m_nodes[nodeIndex]; node.baseNormalSearched = true; @@ -353,7 +355,7 @@ std::pair Builder::searchBaseNormalFromNeighborsRecursively(siz return {{}, false}; } -bool Builder::build() +bool StrokeMeshBuilder::build() { bool succeed = true; @@ -365,7 +367,7 @@ bool Builder::build() int subdivideTimes = (node.cutTemplate.size() / 4) - 1; if (subdivideTimes < 0) subdivideTimes = 0; - box(node.position, node.radius, subdivideTimes, m_generatedVertices, m_generatedFaces); + boxmesh(node.position, node.radius, subdivideTimes, m_generatedVertices, m_generatedFaces); m_generatedVerticesSourceNodeIndices.resize(m_generatedVertices.size(), 0); } return true; @@ -428,7 +430,7 @@ bool Builder::build() return succeed; } -void Builder::localAverageBaseNormals() +void StrokeMeshBuilder::localAverageBaseNormals() { std::vector localAverageNormals; for (size_t nodeIndex = 0; nodeIndex < m_nodes.size(); ++nodeIndex) { @@ -447,38 +449,35 @@ void Builder::localAverageBaseNormals() } } -bool Builder::validateNormal(const QVector3D &normal) +bool StrokeMeshBuilder::validateNormal(const QVector3D &normal) { if (normal.isNull()) { return false; } - if (!validatePosition(normal)) { - return false; - } return true; } -void Builder::enableBaseNormalOnX(bool enabled) +void StrokeMeshBuilder::enableBaseNormalOnX(bool enabled) { m_baseNormalOnX = enabled; } -void Builder::enableBaseNormalOnY(bool enabled) +void StrokeMeshBuilder::enableBaseNormalOnY(bool enabled) { m_baseNormalOnY = enabled; } -void Builder::enableBaseNormalOnZ(bool enabled) +void StrokeMeshBuilder::enableBaseNormalOnZ(bool enabled) { m_baseNormalOnZ = enabled; } -void Builder::enableBaseNormalAverage(bool enabled) +void StrokeMeshBuilder::enableBaseNormalAverage(bool enabled) { m_baseNormalAverageEnabled = enabled; } -std::pair Builder::calculateBaseNormal(const std::vector &inputDirects, +std::pair StrokeMeshBuilder::calculateBaseNormal(const std::vector &inputDirects, const std::vector &inputPositions, const std::vector &weights) { @@ -559,7 +558,7 @@ std::pair Builder::calculateBaseNormal(const std::vector &cut, +void StrokeMeshBuilder::insertCutVertices(const std::vector &cut, std::vector &vertices, size_t nodeIndex, const QVector3D &cutDirect, @@ -583,7 +582,7 @@ void Builder::insertCutVertices(const std::vector &cut, } } -const Builder::CutFaceTransform *Builder::nodeAdjustableCutFaceTransform(size_t nodeIndex) +const StrokeMeshBuilder::CutFaceTransform *StrokeMeshBuilder::nodeAdjustableCutFaceTransform(size_t nodeIndex) { if (nodeIndex >= m_nodes.size()) return nullptr; @@ -593,7 +592,7 @@ const Builder::CutFaceTransform *Builder::nodeAdjustableCutFaceTransform(size_t return &node.cutFaceTransform; } -bool Builder::generateCutsForNode(size_t nodeIndex) +bool StrokeMeshBuilder::generateCutsForNode(size_t nodeIndex) { if (m_swallowedNodes.find(nodeIndex) != m_swallowedNodes.end()) { //qDebug() << "node" << nodeIndex << "ignore cuts generating because of been swallowed"; @@ -676,7 +675,7 @@ bool Builder::generateCutsForNode(size_t nodeIndex) return true; } -bool Builder::tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector &offsets, bool &offsetsChanged) +bool StrokeMeshBuilder::tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector &offsets, bool &offsetsChanged) { auto backupVertices = m_generatedVertices; auto backupFaces = m_generatedFaces; @@ -739,7 +738,7 @@ bool Builder::tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector failedEdgeLoops; bool stitchSucceed = stitcher.stitch(cutsForWrapping); @@ -748,23 +747,20 @@ bool Builder::tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector nodeIndices(m_nodes.size()); for (size_t i = 0; i < m_nodes.size(); ++i) { @@ -872,7 +868,7 @@ void Builder::unifyBaseNormals() } } -QVector3D Builder::revisedBaseNormalAcordingToCutNormal(const QVector3D &baseNormal, const QVector3D &cutNormal) +QVector3D StrokeMeshBuilder::revisedBaseNormalAcordingToCutNormal(const QVector3D &baseNormal, const QVector3D &cutNormal) { QVector3D orientedBaseNormal = QVector3D::dotProduct(cutNormal, baseNormal) > 0 ? baseNormal : -baseNormal; @@ -883,7 +879,7 @@ QVector3D Builder::revisedBaseNormalAcordingToCutNormal(const QVector3D &baseNor return orientedBaseNormal.normalized(); } -void Builder::makeCut(const QVector3D &position, +void StrokeMeshBuilder::makeCut(const QVector3D &position, float radius, const std::vector &cutTemplate, float cutRotation, @@ -939,12 +935,12 @@ void Builder::makeCut(const QVector3D &position, } } -void Builder::stitchEdgeCuts() +void StrokeMeshBuilder::stitchEdgeCuts() { for (size_t edgeIndex = 0; edgeIndex < m_edges.size(); ++edgeIndex) { auto &edge = m_edges[edgeIndex]; if (2 == edge.cuts.size()) { - Stitcher stitcher; + MeshStitcher stitcher; stitcher.setVertices(&m_generatedVertices); stitcher.stitch(edge.cuts); for (const auto &face: stitcher.newlyGeneratedFaces()) { @@ -954,7 +950,7 @@ void Builder::stitchEdgeCuts() } } -void Builder::applyWeld() +void StrokeMeshBuilder::applyWeld() { if (m_weldMap.empty()) return; @@ -1005,32 +1001,32 @@ void Builder::applyWeld() m_generatedVerticesInfos = newVerticesInfos; } -void Builder::setDeformThickness(float thickness) +void StrokeMeshBuilder::setDeformThickness(float thickness) { m_deformThickness = thickness; } -void Builder::setDeformWidth(float width) +void StrokeMeshBuilder::setDeformWidth(float width) { m_deformWidth = width; } -void Builder::setDeformMapImage(const QImage *image) +void StrokeMeshBuilder::setDeformMapImage(const QImage *image) { m_deformMapImage = image; } -void Builder::setHollowThickness(float hollowThickness) +void StrokeMeshBuilder::setHollowThickness(float hollowThickness) { m_hollowThickness = hollowThickness; } -void Builder::setDeformMapScale(float scale) +void StrokeMeshBuilder::setDeformMapScale(float scale) { m_deformMapScale = scale; } -QVector3D Builder::calculateDeformPosition(const QVector3D &vertexPosition, const QVector3D &ray, const QVector3D &deformNormal, float deformFactor) +QVector3D StrokeMeshBuilder::calculateDeformPosition(const QVector3D &vertexPosition, const QVector3D &ray, const QVector3D &deformNormal, float deformFactor) { QVector3D revisedNormal = QVector3D::dotProduct(ray, deformNormal) < 0.0 ? -deformNormal : deformNormal; QVector3D projectRayOnRevisedNormal = revisedNormal * (QVector3D::dotProduct(ray, revisedNormal) / revisedNormal.lengthSquared()); @@ -1038,7 +1034,7 @@ QVector3D Builder::calculateDeformPosition(const QVector3D &vertexPosition, cons return vertexPosition + (scaledProjct - projectRayOnRevisedNormal); } -void Builder::finalizeHollow() +void StrokeMeshBuilder::finalizeHollow() { if (qFuzzyIsNull(m_hollowThickness)) return; @@ -1078,7 +1074,7 @@ void Builder::finalizeHollow() } } -void Builder::applyDeform() +void StrokeMeshBuilder::applyDeform() { for (size_t i = 0; i < m_generatedVertices.size(); ++i) { auto &position = m_generatedVertices[i]; @@ -1086,7 +1082,7 @@ void Builder::applyDeform() const auto &cutDirect = m_generatedVerticesCutDirects[i]; auto ray = position - node.position; if (nullptr != m_deformMapImage) { - float degrees = degreeBetweenIn360(node.baseNormal, ray.normalized(), node.traverseDirection); + float degrees = angleInRangle360BetweenTwoVectors(node.baseNormal, ray.normalized(), node.traverseDirection); int x = node.reversedTraverseOrder * m_deformMapImage->width() / m_nodes.size(); int y = degrees * m_deformMapImage->height() / 360.0; if (y >= m_deformMapImage->height()) @@ -1112,19 +1108,24 @@ void Builder::applyDeform() } } -const QVector3D &Builder::nodeTraverseDirection(size_t nodeIndex) const +const QVector3D &StrokeMeshBuilder::nodeTraverseDirection(size_t nodeIndex) const { return m_nodes[nodeIndex].traverseDirection; } -const QVector3D &Builder::nodeBaseNormal(size_t nodeIndex) const +const QVector3D &StrokeMeshBuilder::nodeBaseNormal(size_t nodeIndex) const { return m_nodes[nodeIndex].baseNormal; } -size_t Builder::nodeTraverseOrder(size_t nodeIndex) const +size_t StrokeMeshBuilder::nodeTraverseOrder(size_t nodeIndex) const { return m_nodes[nodeIndex].reversedTraverseOrder; } +float radianToDegree(float r) +{ + return r * 180.0 / M_PI; } + + diff --git a/thirdparty/nodemesh/nodemesh/builder.h b/src/strokemeshbuilder.h similarity index 98% rename from thirdparty/nodemesh/nodemesh/builder.h rename to src/strokemeshbuilder.h index 1f63c258..4a5f285a 100644 --- a/thirdparty/nodemesh/nodemesh/builder.h +++ b/src/strokemeshbuilder.h @@ -1,5 +1,5 @@ -#ifndef NODEMESH_BUILDER_H -#define NODEMESH_BUILDER_H +#ifndef DUST3D_BUILDER_H +#define DUST3D_BUILDER_H #include #include #include @@ -7,11 +7,9 @@ #include #include #include +#include "positionkey.h" -namespace nodemesh -{ - -class Builder +class StrokeMeshBuilder { public: struct CutFaceTransform @@ -177,7 +175,5 @@ private: static QVector3D calculateBaseNormalFromTraverseDirection(const QVector3D &traverseDirection); void layoutNodes(); }; - -} #endif diff --git a/thirdparty/nodemesh/nodemesh/modifier.cpp b/src/strokemodifier.cpp similarity index 87% rename from thirdparty/nodemesh/nodemesh/modifier.cpp rename to src/strokemodifier.cpp index de791a3a..a69aeb00 100644 --- a/thirdparty/nodemesh/nodemesh/modifier.cpp +++ b/src/strokemodifier.cpp @@ -1,17 +1,14 @@ -#include -#include #include #include +#include "strokemodifier.h" +#include "util.h" -namespace nodemesh -{ - -void Modifier::enableIntermediateAddition() +void StrokeModifier::enableIntermediateAddition() { m_intermediateAdditionEnabled = true; } -size_t Modifier::addNode(const QVector3D &position, float radius, const std::vector &cutTemplate, float cutRotation) +size_t StrokeModifier::addNode(const QVector3D &position, float radius, const std::vector &cutTemplate, float cutRotation) { size_t nodeIndex = m_nodes.size(); @@ -27,7 +24,7 @@ size_t Modifier::addNode(const QVector3D &position, float radius, const std::vec return nodeIndex; } -size_t Modifier::addEdge(size_t firstNodeIndex, size_t secondNodeIndex) +size_t StrokeModifier::addEdge(size_t firstNodeIndex, size_t secondNodeIndex) { size_t edgeIndex = m_edges.size(); @@ -39,7 +36,7 @@ size_t Modifier::addEdge(size_t firstNodeIndex, size_t secondNodeIndex) return edgeIndex; } -void Modifier::createIntermediateNode(const Node &firstNode, const Node &secondNode, float factor, Node *resultNode) +void StrokeModifier::createIntermediateNode(const Node &firstNode, const Node &secondNode, float factor, Node *resultNode) { float firstFactor = 1.0 - factor; resultNode->position = firstNode.position * firstFactor + secondNode.position * factor; @@ -59,14 +56,14 @@ void Modifier::createIntermediateNode(const Node &firstNode, const Node &secondN } } -void Modifier::subdivide() +void StrokeModifier::subdivide() { for (auto &node: m_nodes) { subdivideFace2D(&node.cutTemplate); } } -float Modifier::averageCutTemplateEdgeLength(const std::vector &cutTemplate) +float StrokeModifier::averageCutTemplateEdgeLength(const std::vector &cutTemplate) { if (cutTemplate.empty()) return 0; @@ -79,7 +76,7 @@ float Modifier::averageCutTemplateEdgeLength(const std::vector &cutTe return sum / cutTemplate.size(); } -void Modifier::roundEnd() +void StrokeModifier::roundEnd() { std::map> neighbors; for (const auto &edge: m_edges) { @@ -103,7 +100,7 @@ void Modifier::roundEnd() } } -void Modifier::createIntermediateCutTemplateEdges(std::vector &cutTemplate, float averageCutTemplateLength) +void StrokeModifier::createIntermediateCutTemplateEdges(std::vector &cutTemplate, float averageCutTemplateLength) { std::vector newCutTemplate; auto pointCount = cutTemplate.size(); @@ -129,7 +126,7 @@ void Modifier::createIntermediateCutTemplateEdges(std::vector &cutTem cutTemplate = newCutTemplate; } -void Modifier::finalize() +void StrokeModifier::finalize() { if (!m_intermediateAdditionEnabled) return; @@ -180,14 +177,13 @@ void Modifier::finalize() } } -const std::vector &Modifier::nodes() +const std::vector &StrokeModifier::nodes() { return m_nodes; } -const std::vector &Modifier::edges() +const std::vector &StrokeModifier::edges() { return m_edges; } -} diff --git a/thirdparty/nodemesh/nodemesh/modifier.h b/src/strokemodifier.h similarity index 93% rename from thirdparty/nodemesh/nodemesh/modifier.h rename to src/strokemodifier.h index b5dc1cde..c73c1d72 100644 --- a/thirdparty/nodemesh/nodemesh/modifier.h +++ b/src/strokemodifier.h @@ -1,12 +1,9 @@ -#ifndef NODEMESH_MODIFIER_H -#define NODEMESH_MODIFIER_H +#ifndef DUST3D_MODIFIER_H +#define DUST3D_MODIFIER_H #include #include -namespace nodemesh -{ - -class Modifier +class StrokeModifier { public: struct Node @@ -48,6 +45,4 @@ private: void createIntermediateCutTemplateEdges(std::vector &cutTemplate, float averageCutTemplateLength); }; -} - #endif diff --git a/src/texturegenerator.cpp b/src/texturegenerator.cpp index 8c79de3d..112cf3d9 100644 --- a/src/texturegenerator.cpp +++ b/src/texturegenerator.cpp @@ -212,6 +212,8 @@ void TextureGenerator::prepare() void TextureGenerator::generate() { + m_resultMesh = new MeshLoader(*m_outcome); + if (nullptr == m_outcome->triangleVertexUvs()) return; if (nullptr == m_outcome->triangleSourceNodes()) @@ -698,7 +700,6 @@ void TextureGenerator::generate() } auto createResultBeginTime = countTimeConsumed.elapsed(); - m_resultMesh = new MeshLoader(*m_outcome); m_resultMesh->setTextureImage(new QImage(*m_resultTextureImage)); if (nullptr != m_resultTextureNormalImage) m_resultMesh->setNormalMapImage(new QImage(*m_resultTextureNormalImage)); diff --git a/src/triangleislandslink.cpp b/src/triangleislandslink.cpp index 781988b2..4b5a66d8 100644 --- a/src/triangleislandslink.cpp +++ b/src/triangleislandslink.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -9,6 +8,7 @@ #include #include "triangleislandslink.h" #include "triangleislandsresolve.h" +#include "booleanmesh.h" template bool precheckForConvexHull(InputIterator first, InputIterator beyond) diff --git a/src/trianglesourcenoderesolve.cpp b/src/trianglesourcenoderesolve.cpp index 89f0db92..c4a51cb7 100644 --- a/src/trianglesourcenoderesolve.cpp +++ b/src/trianglesourcenoderesolve.cpp @@ -1,6 +1,6 @@ -#include #include #include "trianglesourcenoderesolve.h" +#include "positionkey.h" struct HalfColorEdge { @@ -17,21 +17,26 @@ struct CandidateEdge float length; }; -void triangleSourceNodeResolve(const Outcome &outcome, std::vector> &triangleSourceNodes) +void triangleSourceNodeResolve(const Outcome &outcome, std::vector> &triangleSourceNodes, + std::vector> *vertexSourceNodes) { std::map> vertexSourceMap; - std::map> positionMap; + std::map> positionMap; std::map, HalfColorEdge> halfColorEdgeMap; std::set brokenTriangleSet; for (const auto &it: outcome.nodeVertices) { - positionMap.insert({nodemesh::PositionKey(it.first), it.second}); + positionMap.insert({PositionKey(it.first), it.second}); } + if (nullptr != vertexSourceNodes) + vertexSourceNodes->resize(outcome.vertices.size()); for (auto x = 0u; x < outcome.vertices.size(); x++) { const QVector3D *resultVertex = &outcome.vertices[x]; std::pair source; - auto findPosition = positionMap.find(nodemesh::PositionKey(*resultVertex)); - if (findPosition != positionMap.end()) + auto findPosition = positionMap.find(PositionKey(*resultVertex)); + if (findPosition != positionMap.end()) { + (*vertexSourceNodes)[x] = findPosition->second; vertexSourceMap[x] = findPosition->second; + } } for (auto x = 0u; x < outcome.triangles.size(); x++) { const auto triangle = outcome.triangles[x]; diff --git a/src/trianglesourcenoderesolve.h b/src/trianglesourcenoderesolve.h index dd835a6d..515bf423 100644 --- a/src/trianglesourcenoderesolve.h +++ b/src/trianglesourcenoderesolve.h @@ -2,6 +2,7 @@ #define DUST3D_TRIANGLE_SOURCE_NODE_RESOLVE_H #include "outcome.h" -void triangleSourceNodeResolve(const Outcome &outcome, std::vector> &triangleSourceNodes); +void triangleSourceNodeResolve(const Outcome &outcome, std::vector> &triangleSourceNodes, + std::vector> *vertexSourceNodes=nullptr); -#endif \ No newline at end of file +#endif diff --git a/src/triangulate.cpp b/src/triangulate.cpp new file mode 100644 index 00000000..02e61b49 --- /dev/null +++ b/src/triangulate.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include "booleanmesh.h" +#include "triangulate.h" +#include "util.h" + +typedef CGAL::Exact_predicates_inexact_constructions_kernel InexactKernel; +typedef CGAL::Surface_mesh InexactMesh; + +bool triangulate(std::vector &vertices, const std::vector> &faces, std::vector> &triangles) +{ + auto cgalMesh = buildCgalMesh(vertices, faces); + bool isSucceed = CGAL::Polygon_mesh_processing::triangulate_faces(*cgalMesh); + if (isSucceed) { + vertices.clear(); + fetchFromCgalMesh(cgalMesh, vertices, triangles); + delete cgalMesh; + return true; + } + delete cgalMesh; + + // fallback to our own imeplementation + + isSucceed = true; + std::vector> rings; + for (const auto &face: faces) { + if (face.size() > 3) { + rings.push_back(face); + } else { + triangles.push_back(face); + } + } + for (const auto &ring: rings) { + std::vector fillRing = ring; + QVector3D direct = polygonNormal(vertices, fillRing); + while (fillRing.size() > 3) { + bool newFaceGenerated = false; + for (decltype(fillRing.size()) i = 0; i < fillRing.size(); ++i) { + auto j = (i + 1) % fillRing.size(); + auto k = (i + 2) % fillRing.size(); + const auto &enter = vertices[fillRing[i]]; + const auto &cone = vertices[fillRing[j]]; + const auto &leave = vertices[fillRing[k]]; + auto angle = angleInRangle360BetweenTwoVectors(cone - enter, leave - cone, direct); + if (angle >= 1.0 && angle <= 179.0) { + bool isEar = true; + for (size_t x = 0; x < fillRing.size() - 3; ++x) { + auto fourth = vertices[(i + 3 + k) % fillRing.size()]; + if (pointInTriangle(enter, cone, leave, fourth)) { + isEar = false; + break; + } + } + if (isEar) { + std::vector newFace = { + fillRing[i], + fillRing[j], + fillRing[k], + }; + triangles.push_back(newFace); + fillRing.erase(fillRing.begin() + j); + newFaceGenerated = true; + break; + } + } + } + if (!newFaceGenerated) + break; + } + if (fillRing.size() == 3) { + std::vector newFace = { + fillRing[0], + fillRing[1], + fillRing[2], + }; + triangles.push_back(newFace); + } else { + qDebug() << "Triangulate failed, ring size:" << fillRing.size(); + isSucceed = false; + } + } + return isSucceed; +} diff --git a/src/triangulate.h b/src/triangulate.h new file mode 100644 index 00000000..bfcbfbe0 --- /dev/null +++ b/src/triangulate.h @@ -0,0 +1,8 @@ +#ifndef DUST3D_TRIANGULATE_H +#define DUST3D_TRIANGULATE_H +#include +#include + +bool triangulate(std::vector &vertices, const std::vector> &faces, std::vector> &triangles); + +#endif diff --git a/src/util.cpp b/src/util.cpp index 279cecfa..8fc3b42b 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include "util.h" #include "version.h" @@ -104,3 +106,361 @@ void quaternionToEulerAngles(const QQuaternion &q, double *pitch, double *yaw, d *yaw = eulerAngles.y(); *roll = eulerAngles.z(); } + +QVector3D polygonNormal(const std::vector &vertices, const std::vector &polygon) +{ + QVector3D normal; + for (size_t i = 0; i < polygon.size(); ++i) { + auto j = (i + 1) % polygon.size(); + auto k = (i + 2) % polygon.size(); + const auto &enter = vertices[polygon[i]]; + const auto &cone = vertices[polygon[j]]; + const auto &leave = vertices[polygon[k]]; + normal += QVector3D::normal(enter, cone, leave); + } + return normal.normalized(); +} + +bool pointInTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &p) +{ + auto u = b - a; + auto v = c - a; + auto w = p - a; + auto vXw = QVector3D::crossProduct(v, w); + auto vXu = QVector3D::crossProduct(v, u); + if (QVector3D::dotProduct(vXw, vXu) < 0.0) { + return false; + } + auto uXw = QVector3D::crossProduct(u, w); + auto uXv = QVector3D::crossProduct(u, v); + if (QVector3D::dotProduct(uXw, uXv) < 0.0) { + return false; + } + auto denom = uXv.length(); + auto r = vXw.length() / denom; + auto t = uXw.length() / denom; + return r + t <= 1.0; +} + +void angleSmooth(const std::vector &vertices, + const std::vector> &triangles, + const std::vector &triangleNormals, + float thresholdAngleDegrees, + std::vector &triangleVertexNormals) +{ + std::vector>> triangleVertexNormalsMapByIndices(vertices.size()); + std::vector angleAreaWeightedNormals; + for (size_t triangleIndex = 0; triangleIndex < triangles.size(); ++triangleIndex) { + const auto &sourceTriangle = triangles[triangleIndex]; + if (sourceTriangle.size() != 3) { + qDebug() << "Encounter non triangle"; + continue; + } + const auto &v1 = vertices[sourceTriangle[0]]; + const auto &v2 = vertices[sourceTriangle[1]]; + const auto &v3 = vertices[sourceTriangle[2]]; + float area = areaOfTriangle(v1, v2, v3); + float angles[] = {angleBetweenVectors(v2-v1, v3-v1), + angleBetweenVectors(v1-v2, v3-v2), + angleBetweenVectors(v1-v3, v2-v3)}; + for (int i = 0; i < 3; ++i) { + if (sourceTriangle[i] >= vertices.size()) { + qDebug() << "Invalid vertex index" << sourceTriangle[i] << "vertices size" << vertices.size(); + continue; + } + triangleVertexNormalsMapByIndices[sourceTriangle[i]].push_back({triangleIndex, angleAreaWeightedNormals.size()}); + angleAreaWeightedNormals.push_back(triangleNormals[triangleIndex] * area * angles[i]); + } + } + triangleVertexNormals = angleAreaWeightedNormals; + std::map, float> degreesBetweenFacesMap; + for (size_t vertexIndex = 0; vertexIndex < vertices.size(); ++vertexIndex) { + const auto &triangleVertices = triangleVertexNormalsMapByIndices[vertexIndex]; + for (const auto &triangleVertex: triangleVertices) { + for (const auto &otherTriangleVertex: triangleVertices) { + if (triangleVertex.first == otherTriangleVertex.first) + continue; + float degrees = 0; + auto findDegreesResult = degreesBetweenFacesMap.find({triangleVertex.first, otherTriangleVertex.first}); + if (findDegreesResult == degreesBetweenFacesMap.end()) { + degrees = angleBetweenVectors(triangleNormals[triangleVertex.first], triangleNormals[otherTriangleVertex.first]); + degreesBetweenFacesMap.insert({{triangleVertex.first, otherTriangleVertex.first}, degrees}); + degreesBetweenFacesMap.insert({{otherTriangleVertex.first, triangleVertex.first}, degrees}); + } else { + degrees = findDegreesResult->second; + } + if (degrees > thresholdAngleDegrees) { + continue; + } + triangleVertexNormals[triangleVertex.second] += angleAreaWeightedNormals[otherTriangleVertex.second]; + } + } + } + for (auto &item: triangleVertexNormals) + item.normalize(); +} + +void recoverQuads(const std::vector &vertices, const std::vector> &triangles, const std::set> &sharedQuadEdges, std::vector> &triangleAndQuads) +{ + std::vector verticesPositionKeys; + for (const auto &position: vertices) { + verticesPositionKeys.push_back(PositionKey(position)); + } + std::map, std::pair> triangleEdgeMap; + for (size_t i = 0; i < triangles.size(); i++) { + const auto &faceIndices = triangles[i]; + if (faceIndices.size() == 3) { + triangleEdgeMap[std::make_pair(faceIndices[0], faceIndices[1])] = std::make_pair(i, faceIndices[2]); + triangleEdgeMap[std::make_pair(faceIndices[1], faceIndices[2])] = std::make_pair(i, faceIndices[0]); + triangleEdgeMap[std::make_pair(faceIndices[2], faceIndices[0])] = std::make_pair(i, faceIndices[1]); + } + } + std::unordered_set unionedFaces; + std::vector> newUnionedFaceIndices; + for (const auto &edge: triangleEdgeMap) { + if (unionedFaces.find(edge.second.first) != unionedFaces.end()) + continue; + auto pair = std::make_pair(verticesPositionKeys[edge.first.first], verticesPositionKeys[edge.first.second]); + if (sharedQuadEdges.find(pair) != sharedQuadEdges.end()) { + auto oppositeEdge = triangleEdgeMap.find(std::make_pair(edge.first.second, edge.first.first)); + if (oppositeEdge == triangleEdgeMap.end()) { + qDebug() << "Find opposite edge failed"; + } else { + if (unionedFaces.find(oppositeEdge->second.first) == unionedFaces.end()) { + unionedFaces.insert(edge.second.first); + unionedFaces.insert(oppositeEdge->second.first); + std::vector indices; + indices.push_back(edge.second.second); + indices.push_back(edge.first.first); + indices.push_back(oppositeEdge->second.second); + indices.push_back(edge.first.second); + triangleAndQuads.push_back(indices); + } + } + } + } + for (size_t i = 0; i < triangles.size(); i++) { + if (unionedFaces.find(i) == unionedFaces.end()) { + triangleAndQuads.push_back(triangles[i]); + } + } +} + +size_t weldSeam(const std::vector &sourceVertices, const std::vector> &sourceTriangles, + float allowedSmallestDistance, const std::set &excludePositions, + std::vector &destVertices, std::vector> &destTriangles) +{ + std::unordered_set excludeVertices; + for (size_t i = 0; i < sourceVertices.size(); ++i) { + if (excludePositions.find(sourceVertices[i]) != excludePositions.end()) + excludeVertices.insert(i); + } + float squareOfAllowedSmallestDistance = allowedSmallestDistance * allowedSmallestDistance; + std::map weldVertexToMap; + std::unordered_set weldTargetVertices; + std::unordered_set processedFaces; + std::map, std::pair> triangleEdgeMap; + std::unordered_map vertexAdjFaceCountMap; + for (int i = 0; i < (int)sourceTriangles.size(); i++) { + const auto &faceIndices = sourceTriangles[i]; + if (faceIndices.size() == 3) { + vertexAdjFaceCountMap[faceIndices[0]]++; + vertexAdjFaceCountMap[faceIndices[1]]++; + vertexAdjFaceCountMap[faceIndices[2]]++; + triangleEdgeMap[std::make_pair(faceIndices[0], faceIndices[1])] = std::make_pair(i, faceIndices[2]); + triangleEdgeMap[std::make_pair(faceIndices[1], faceIndices[2])] = std::make_pair(i, faceIndices[0]); + triangleEdgeMap[std::make_pair(faceIndices[2], faceIndices[0])] = std::make_pair(i, faceIndices[1]); + } + } + for (int i = 0; i < (int)sourceTriangles.size(); i++) { + if (processedFaces.find(i) != processedFaces.end()) + continue; + const auto &faceIndices = sourceTriangles[i]; + if (faceIndices.size() == 3) { + bool indicesSeamCheck[3] = { + excludeVertices.find(faceIndices[0]) == excludeVertices.end(), + excludeVertices.find(faceIndices[1]) == excludeVertices.end(), + excludeVertices.find(faceIndices[2]) == excludeVertices.end() + }; + for (int j = 0; j < 3; j++) { + int next = (j + 1) % 3; + int nextNext = (j + 2) % 3; + if (indicesSeamCheck[j] && indicesSeamCheck[next]) { + std::pair edge = std::make_pair(faceIndices[j], faceIndices[next]); + int thirdVertexIndex = faceIndices[nextNext]; + if ((sourceVertices[edge.first] - sourceVertices[edge.second]).lengthSquared() < squareOfAllowedSmallestDistance) { + auto oppositeEdge = std::make_pair(edge.second, edge.first); + auto findOppositeFace = triangleEdgeMap.find(oppositeEdge); + if (findOppositeFace == triangleEdgeMap.end()) { + qDebug() << "Find opposite edge failed"; + continue; + } + int oppositeFaceIndex = findOppositeFace->second.first; + if (((sourceVertices[edge.first] - sourceVertices[thirdVertexIndex]).lengthSquared() < + (sourceVertices[edge.second] - sourceVertices[thirdVertexIndex]).lengthSquared()) && + vertexAdjFaceCountMap[edge.second] <= 4 && + weldVertexToMap.find(edge.second) == weldVertexToMap.end()) { + weldVertexToMap[edge.second] = edge.first; + weldTargetVertices.insert(edge.first); + processedFaces.insert(i); + processedFaces.insert(oppositeFaceIndex); + break; + } else if (vertexAdjFaceCountMap[edge.first] <= 4 && + weldVertexToMap.find(edge.first) == weldVertexToMap.end()) { + weldVertexToMap[edge.first] = edge.second; + weldTargetVertices.insert(edge.second); + processedFaces.insert(i); + processedFaces.insert(oppositeFaceIndex); + break; + } + } + } + } + } + } + int weldedCount = 0; + int faceCountAfterWeld = 0; + std::map oldToNewVerticesMap; + for (int i = 0; i < (int)sourceTriangles.size(); i++) { + const auto &faceIndices = sourceTriangles[i]; + std::vector mappedFaceIndices; + bool errored = false; + for (const auto &index: faceIndices) { + int finalIndex = index; + int mapTimes = 0; + while (mapTimes < 500) { + auto findMapResult = weldVertexToMap.find(finalIndex); + if (findMapResult == weldVertexToMap.end()) + break; + finalIndex = findMapResult->second; + mapTimes++; + } + if (mapTimes >= 500) { + qDebug() << "Map too much times"; + errored = true; + break; + } + mappedFaceIndices.push_back(finalIndex); + } + if (errored || mappedFaceIndices.size() < 3) + continue; + bool welded = false; + for (decltype(mappedFaceIndices.size()) j = 0; j < mappedFaceIndices.size(); j++) { + int next = (j + 1) % 3; + if (mappedFaceIndices[j] == mappedFaceIndices[next]) { + welded = true; + break; + } + } + if (welded) { + weldedCount++; + continue; + } + faceCountAfterWeld++; + std::vector newFace; + for (const auto &index: mappedFaceIndices) { + auto findMap = oldToNewVerticesMap.find(index); + if (findMap == oldToNewVerticesMap.end()) { + size_t newIndex = destVertices.size(); + newFace.push_back(newIndex); + destVertices.push_back(sourceVertices[index]); + oldToNewVerticesMap.insert({index, newIndex}); + } else { + newFace.push_back(findMap->second); + } + } + destTriangles.push_back(newFace); + } + return weldedCount; +} + +bool isManifold(const std::vector> &faces) +{ + std::set> halfEdges; + for (const auto &face: faces) { + for (size_t i = 0; i < face.size(); ++i) { + size_t j = (i + 1) % face.size(); + auto insertResult = halfEdges.insert({face[i], face[j]}); + if (!insertResult.second) + return false; + } + } + for (const auto &it: halfEdges) { + if (halfEdges.find({it.second, it.first}) == halfEdges.end()) + return false; + } + return true; +} + +void trim(std::vector *vertices, bool normalize) +{ + float xLow = std::numeric_limits::max(); + float xHigh = std::numeric_limits::lowest(); + float yLow = std::numeric_limits::max(); + float yHigh = std::numeric_limits::lowest(); + float zLow = std::numeric_limits::max(); + float zHigh = std::numeric_limits::lowest(); + for (const auto &position: *vertices) { + if (position.x() < xLow) + xLow = position.x(); + else if (position.x() > xHigh) + xHigh = position.x(); + if (position.y() < yLow) + yLow = position.y(); + else if (position.y() > yHigh) + yHigh = position.y(); + if (position.z() < zLow) + zLow = position.z(); + else if (position.z() > zHigh) + zHigh = position.z(); + } + float xMiddle = (xHigh + xLow) * 0.5; + float yMiddle = (yHigh + yLow) * 0.5; + float zMiddle = (zHigh + zLow) * 0.5; + if (normalize) { + float xSize = xHigh - xLow; + float ySize = yHigh - yLow; + float zSize = zHigh - zLow; + float longSize = ySize; + if (xSize > longSize) + longSize = xSize; + if (zSize > longSize) + longSize = zSize; + if (qFuzzyIsNull(longSize)) + longSize = 0.000001; + for (auto &position: *vertices) { + position.setX((position.x() - xMiddle) / longSize); + position.setY((position.y() - yMiddle) / longSize); + position.setZ((position.z() - zMiddle) / longSize); + } + } else { + for (auto &position: *vertices) { + position.setX((position.x() - xMiddle)); + position.setY((position.y() - yMiddle)); + position.setZ((position.z() - zMiddle)); + } + } +} + +void chamferFace2D(std::vector *face) +{ + auto oldFace = *face; + face->clear(); + for (size_t i = 0; i < oldFace.size(); ++i) { + size_t j = (i + 1) % oldFace.size(); + face->push_back(oldFace[i] * 0.8 + oldFace[j] * 0.2); + face->push_back(oldFace[i] * 0.2 + oldFace[j] * 0.8); + } +} + +void subdivideFace2D(std::vector *face) +{ + auto oldFace = *face; + face->resize(oldFace.size() * 2); + for (size_t i = 0, n = 0; i < oldFace.size(); ++i) { + size_t h = (i + oldFace.size() - 1) % oldFace.size(); + size_t j = (i + 1) % oldFace.size(); + (*face)[n++] = oldFace[h] * 0.125 + oldFace[i] * 0.75 + oldFace[j] * 0.125; + (*face)[n++] = (oldFace[i] + oldFace[j]) * 0.5; + } +} diff --git a/src/util.h b/src/util.h index 871caa1c..36c98fed 100644 --- a/src/util.h +++ b/src/util.h @@ -4,7 +4,10 @@ #include #include #include +#include #include +#include +#include "positionkey.h" #ifndef M_PI #define M_PI 3.14159265358979323846 @@ -25,5 +28,20 @@ float areaOfTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c) QQuaternion eulerAnglesToQuaternion(double pitch, double yaw, double roll); void quaternionToEulerAngles(const QQuaternion &q, double *pitch, double *yaw, double *roll); void quaternionToEulerAnglesXYZ(const QQuaternion &q, double *pitch, double *yaw, double *roll); +bool pointInTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &p); +QVector3D polygonNormal(const std::vector &vertices, const std::vector &polygon); +void angleSmooth(const std::vector &vertices, + const std::vector> &triangles, + const std::vector &triangleNormals, + float thresholdAngleDegrees, + std::vector &triangleVertexNormals); +void recoverQuads(const std::vector &vertices, const std::vector> &triangles, const std::set> &sharedQuadEdges, std::vector> &triangleAndQuads); +size_t weldSeam(const std::vector &sourceVertices, const std::vector> &sourceTriangles, + float allowedSmallestDistance, const std::set &excludePositions, + std::vector &destVertices, std::vector> &destTriangles); +bool isManifold(const std::vector> &faces); +void trim(std::vector *vertices, bool normalize=false); +void chamferFace2D(std::vector *face); +void subdivideFace2D(std::vector *face); #endif diff --git a/thirdparty/nodemesh/.gitignore b/thirdparty/nodemesh/.gitignore deleted file mode 100644 index 259148fa..00000000 --- a/thirdparty/nodemesh/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app diff --git a/thirdparty/nodemesh/LICENSE b/thirdparty/nodemesh/LICENSE deleted file mode 100644 index 0fcae81c..00000000 --- a/thirdparty/nodemesh/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Jeremy HU - -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/thirdparty/nodemesh/README.md b/thirdparty/nodemesh/README.md deleted file mode 100644 index de5a1c3d..00000000 --- a/thirdparty/nodemesh/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# nodemesh -Mesh generating experiment for Dust3D diff --git a/thirdparty/nodemesh/nodemesh/box.cpp b/thirdparty/nodemesh/nodemesh/box.cpp deleted file mode 100644 index 2b9a5265..00000000 --- a/thirdparty/nodemesh/nodemesh/box.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include -#include -#include -#include - -typedef CGAL::Simple_cartesian SimpleKernel; -typedef CGAL::Surface_mesh PolygonMesh; - -namespace nodemesh -{ - -void box(const QVector3D position, float radius, size_t subdivideTimes, std::vector &vertices, std::vector> &faces) -{ - std::vector beginPlane = { - {-radius, -radius, radius}, - { radius, -radius, radius}, - { radius, radius, radius}, - {-radius, radius, radius}, - }; - std::vector endPlane = { - beginPlane[0], - beginPlane[3], - beginPlane[2], - beginPlane[1] - }; - for (auto &vertex: endPlane) { - vertex.setZ(vertex.z() - radius - radius); - } - for (const auto &vertex: beginPlane) { - vertices.push_back(vertex); - } - for (const auto &vertex: endPlane) { - vertices.push_back(vertex); - } - std::vector beginLoop = { - 0, 1, 2, 3 - }; - std::vector endLoop = { - 4, 5, 6, 7 - }; - std::vector alignedEndLoop = { - 4, 7, 6, 5 - }; - faces.push_back(beginLoop); - faces.push_back(endLoop); - for (size_t i = 0; i < beginLoop.size(); ++i) { - size_t j = (i + 1) % beginLoop.size(); - faces.push_back({ - beginLoop[j], - beginLoop[i], - alignedEndLoop[i], - alignedEndLoop[j] - }); - } - for (auto &vertex: vertices) { - vertex += position; - } - - if (subdivideTimes > 0) { - std::vector> triangles; - triangulate(vertices, faces, triangles); - PolygonMesh *cgalMesh = buildCgalMesh(vertices, triangles); - if (nullptr != cgalMesh) { - CGAL::Subdivision_method_3::CatmullClark_subdivision(*cgalMesh, subdivideTimes); - vertices.clear(); - faces.clear(); - fetchFromCgalMesh(cgalMesh, vertices, faces); - delete cgalMesh; - } - } -} - -} diff --git a/thirdparty/nodemesh/nodemesh/box.h b/thirdparty/nodemesh/nodemesh/box.h deleted file mode 100644 index 83c11574..00000000 --- a/thirdparty/nodemesh/nodemesh/box.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef NODEMESH_BOX_H -#define NODEMESH_BOX_H -#include -#include - -namespace nodemesh -{ - -void box(const QVector3D position, float radius, size_t subdivideTimes, std::vector &vertices, std::vector> &faces); - -} - -#endif diff --git a/thirdparty/nodemesh/nodemesh/cgalmesh.cpp b/thirdparty/nodemesh/nodemesh/cgalmesh.cpp deleted file mode 100644 index 4e5f33c5..00000000 --- a/thirdparty/nodemesh/nodemesh/cgalmesh.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include - diff --git a/thirdparty/nodemesh/nodemesh/misc.cpp b/thirdparty/nodemesh/nodemesh/misc.cpp deleted file mode 100644 index 7a3000d1..00000000 --- a/thirdparty/nodemesh/nodemesh/misc.cpp +++ /dev/null @@ -1,554 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -typedef CGAL::Exact_predicates_inexact_constructions_kernel InexactKernel; -typedef CGAL::Surface_mesh InexactMesh; - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - -using namespace nodemesh; - -float nodemesh::radianToDegree(float r) -{ - return r * 180.0 / M_PI; -} - -float nodemesh::angleBetween(const QVector3D &v1, const QVector3D &v2) -{ - return atan2(QVector3D::crossProduct(v1, v2).length(), QVector3D::dotProduct(v1, v2)); -} - -float nodemesh::degreeBetween(const QVector3D &v1, const QVector3D &v2) -{ - return radianToDegree(angleBetween(v1, v2)); -} - -float nodemesh::degreeBetweenIn360(const QVector3D &a, const QVector3D &b, const QVector3D &direct) -{ - auto angle = radianToDegree(angleBetween(a, b)); - auto c = QVector3D::crossProduct(a, b); - if (QVector3D::dotProduct(c, direct) < 0) { - angle = 360 - angle; - } - return angle; -} - -QVector3D nodemesh::polygonNormal(const std::vector &vertices, const std::vector &polygon) -{ - QVector3D normal; - for (size_t i = 0; i < polygon.size(); ++i) { - auto j = (i + 1) % polygon.size(); - auto k = (i + 2) % polygon.size(); - const auto &enter = vertices[polygon[i]]; - const auto &cone = vertices[polygon[j]]; - const auto &leave = vertices[polygon[k]]; - normal += QVector3D::normal(enter, cone, leave); - } - return normal.normalized(); -} - -bool nodemesh::pointInTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &p) -{ - auto u = b - a; - auto v = c - a; - auto w = p - a; - auto vXw = QVector3D::crossProduct(v, w); - auto vXu = QVector3D::crossProduct(v, u); - if (QVector3D::dotProduct(vXw, vXu) < 0.0) { - return false; - } - auto uXw = QVector3D::crossProduct(u, w); - auto uXv = QVector3D::crossProduct(u, v); - if (QVector3D::dotProduct(uXw, uXv) < 0.0) { - return false; - } - auto denom = uXv.length(); - auto r = vXw.length() / denom; - auto t = uXw.length() / denom; - return r + t <= 1.0; -} - -bool nodemesh::validatePosition(const QVector3D &position) -{ - if (std::isnan(position.x())) - return false; - if (std::isnan(position.y())) - return false; - if (std::isnan(position.z())) - return false; - if (std::isinf(position.x())) - return false; - if (std::isinf(position.y())) - return false; - if (std::isinf(position.z())) - return false; - return true; -} - -bool nodemesh::triangulate(std::vector &vertices, const std::vector> &faces, std::vector> &triangles) -{ - auto cgalMesh = buildCgalMesh(vertices, faces); - bool isSucceed = CGAL::Polygon_mesh_processing::triangulate_faces(*cgalMesh); - if (isSucceed) { - vertices.clear(); - fetchFromCgalMesh(cgalMesh, vertices, triangles); - delete cgalMesh; - return true; - } - delete cgalMesh; - - // fallback to our own imeplementation - - isSucceed = true; - std::vector> rings; - for (const auto &face: faces) { - if (face.size() > 3) { - rings.push_back(face); - } else { - triangles.push_back(face); - } - } - for (const auto &ring: rings) { - std::vector fillRing = ring; - QVector3D direct = polygonNormal(vertices, fillRing); - while (fillRing.size() > 3) { - bool newFaceGenerated = false; - for (decltype(fillRing.size()) i = 0; i < fillRing.size(); ++i) { - auto j = (i + 1) % fillRing.size(); - auto k = (i + 2) % fillRing.size(); - const auto &enter = vertices[fillRing[i]]; - const auto &cone = vertices[fillRing[j]]; - const auto &leave = vertices[fillRing[k]]; - auto angle = degreeBetweenIn360(cone - enter, leave - cone, direct); - if (angle >= 1.0 && angle <= 179.0) { - bool isEar = true; - for (size_t x = 0; x < fillRing.size() - 3; ++x) { - auto fourth = vertices[(i + 3 + k) % fillRing.size()]; - if (pointInTriangle(enter, cone, leave, fourth)) { - isEar = false; - break; - } - } - if (isEar) { - std::vector newFace = { - fillRing[i], - fillRing[j], - fillRing[k], - }; - triangles.push_back(newFace); - fillRing.erase(fillRing.begin() + j); - newFaceGenerated = true; - break; - } - } - } - if (!newFaceGenerated) - break; - } - if (fillRing.size() == 3) { - std::vector newFace = { - fillRing[0], - fillRing[1], - fillRing[2], - }; - triangles.push_back(newFace); - } else { - qDebug() << "Triangulate failed, ring size:" << fillRing.size(); - isSucceed = false; - } - } - return isSucceed; -} - -void nodemesh::exportMeshAsObj(const std::vector &vertices, const std::vector> &faces, const QString &filename, const std::set *excludeFacesOfVertices) -{ - QFile file(filename); - if (file.open(QIODevice::WriteOnly)) { - QTextStream stream(&file); - stream << "# Generated by nodemesh, a library from Dust3D https://dust3d.org" << endl; - for (std::vector::const_iterator it = vertices.begin() ; it != vertices.end(); ++it) { - stream << "v " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl; - } - for (std::vector>::const_iterator it = faces.begin() ; it != faces.end(); ++it) { - bool excluded = false; - for (std::vector::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) { - if (excludeFacesOfVertices && excludeFacesOfVertices->find(*subIt) != excludeFacesOfVertices->end()) { - excluded = true; - break; - } - } - if (excluded) - continue; - stream << "f"; - for (std::vector::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) { - stream << " " << (1 + *subIt); - } - stream << endl; - } - } -} - -void nodemesh::exportMeshAsObjWithNormals(const std::vector &vertices, const std::vector> &faces, const QString &filename, - const std::vector &triangleVertexNormals) -{ - QFile file(filename); - if (file.open(QIODevice::WriteOnly)) { - QTextStream stream(&file); - stream << "# Generated by nodemesh, a library from Dust3D https://dust3d.org" << endl; - for (std::vector::const_iterator it = vertices.begin() ; it != vertices.end(); ++it) { - stream << "v " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl; - } - for (std::vector::const_iterator it = triangleVertexNormals.begin() ; it != triangleVertexNormals.end(); ++it) { - stream << "vn " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl; - } - size_t normalIndex = 0; - for (std::vector>::const_iterator it = faces.begin() ; it != faces.end(); ++it) { - stream << "f"; - for (std::vector::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) { - ++normalIndex; - stream << " " << QString::number(1 + *subIt) + "//" + QString::number(normalIndex); - } - stream << endl; - } - } -} - -float nodemesh::areaOfTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c) -{ - auto ab = b - a; - auto ac = c - a; - return 0.5 * QVector3D::crossProduct(ab, ac).length(); -} - -void nodemesh::angleSmooth(const std::vector &vertices, - const std::vector> &triangles, - const std::vector &triangleNormals, - float thresholdAngleDegrees, - std::vector &triangleVertexNormals) -{ - std::vector>> triangleVertexNormalsMapByIndices(vertices.size()); - std::vector angleAreaWeightedNormals; - for (size_t triangleIndex = 0; triangleIndex < triangles.size(); ++triangleIndex) { - const auto &sourceTriangle = triangles[triangleIndex]; - if (sourceTriangle.size() != 3) { - qDebug() << "Encounter non triangle"; - continue; - } - const auto &v1 = vertices[sourceTriangle[0]]; - const auto &v2 = vertices[sourceTriangle[1]]; - const auto &v3 = vertices[sourceTriangle[2]]; - float area = areaOfTriangle(v1, v2, v3); - float angles[] = {radianToDegree(angleBetween(v2-v1, v3-v1)), - radianToDegree(angleBetween(v1-v2, v3-v2)), - radianToDegree(angleBetween(v1-v3, v2-v3))}; - for (int i = 0; i < 3; ++i) { - if (sourceTriangle[i] >= vertices.size()) { - qDebug() << "Invalid vertex index" << sourceTriangle[i] << "vertices size" << vertices.size(); - continue; - } - triangleVertexNormalsMapByIndices[sourceTriangle[i]].push_back({triangleIndex, angleAreaWeightedNormals.size()}); - angleAreaWeightedNormals.push_back(triangleNormals[triangleIndex] * area * angles[i]); - } - } - triangleVertexNormals = angleAreaWeightedNormals; - std::map, float> degreesBetweenFacesMap; - for (size_t vertexIndex = 0; vertexIndex < vertices.size(); ++vertexIndex) { - const auto &triangleVertices = triangleVertexNormalsMapByIndices[vertexIndex]; - for (const auto &triangleVertex: triangleVertices) { - for (const auto &otherTriangleVertex: triangleVertices) { - if (triangleVertex.first == otherTriangleVertex.first) - continue; - float degrees = 0; - auto findDegreesResult = degreesBetweenFacesMap.find({triangleVertex.first, otherTriangleVertex.first}); - if (findDegreesResult == degreesBetweenFacesMap.end()) { - degrees = angleBetween(triangleNormals[triangleVertex.first], triangleNormals[otherTriangleVertex.first]); - degreesBetweenFacesMap.insert({{triangleVertex.first, otherTriangleVertex.first}, degrees}); - degreesBetweenFacesMap.insert({{otherTriangleVertex.first, triangleVertex.first}, degrees}); - } else { - degrees = findDegreesResult->second; - } - if (degrees > thresholdAngleDegrees) { - continue; - } - triangleVertexNormals[triangleVertex.second] += angleAreaWeightedNormals[otherTriangleVertex.second]; - } - } - } - for (auto &item: triangleVertexNormals) - item.normalize(); -} - -void nodemesh::recoverQuads(const std::vector &vertices, const std::vector> &triangles, const std::set> &sharedQuadEdges, std::vector> &triangleAndQuads) -{ - std::vector verticesPositionKeys; - for (const auto &position: vertices) { - verticesPositionKeys.push_back(PositionKey(position)); - } - std::map, std::pair> triangleEdgeMap; - for (size_t i = 0; i < triangles.size(); i++) { - const auto &faceIndices = triangles[i]; - if (faceIndices.size() == 3) { - triangleEdgeMap[std::make_pair(faceIndices[0], faceIndices[1])] = std::make_pair(i, faceIndices[2]); - triangleEdgeMap[std::make_pair(faceIndices[1], faceIndices[2])] = std::make_pair(i, faceIndices[0]); - triangleEdgeMap[std::make_pair(faceIndices[2], faceIndices[0])] = std::make_pair(i, faceIndices[1]); - } - } - std::unordered_set unionedFaces; - std::vector> newUnionedFaceIndices; - for (const auto &edge: triangleEdgeMap) { - if (unionedFaces.find(edge.second.first) != unionedFaces.end()) - continue; - auto pair = std::make_pair(verticesPositionKeys[edge.first.first], verticesPositionKeys[edge.first.second]); - if (sharedQuadEdges.find(pair) != sharedQuadEdges.end()) { - auto oppositeEdge = triangleEdgeMap.find(std::make_pair(edge.first.second, edge.first.first)); - if (oppositeEdge == triangleEdgeMap.end()) { - qDebug() << "Find opposite edge failed"; - } else { - if (unionedFaces.find(oppositeEdge->second.first) == unionedFaces.end()) { - unionedFaces.insert(edge.second.first); - unionedFaces.insert(oppositeEdge->second.first); - std::vector indices; - indices.push_back(edge.second.second); - indices.push_back(edge.first.first); - indices.push_back(oppositeEdge->second.second); - indices.push_back(edge.first.second); - triangleAndQuads.push_back(indices); - } - } - } - } - for (size_t i = 0; i < triangles.size(); i++) { - if (unionedFaces.find(i) == unionedFaces.end()) { - triangleAndQuads.push_back(triangles[i]); - } - } -} - -size_t nodemesh::weldSeam(const std::vector &sourceVertices, const std::vector> &sourceTriangles, - float allowedSmallestDistance, const std::set &excludePositions, - std::vector &destVertices, std::vector> &destTriangles) -{ - std::unordered_set excludeVertices; - for (size_t i = 0; i < sourceVertices.size(); ++i) { - if (excludePositions.find(sourceVertices[i]) != excludePositions.end()) - excludeVertices.insert(i); - } - float squareOfAllowedSmallestDistance = allowedSmallestDistance * allowedSmallestDistance; - std::map weldVertexToMap; - std::unordered_set weldTargetVertices; - std::unordered_set processedFaces; - std::map, std::pair> triangleEdgeMap; - std::unordered_map vertexAdjFaceCountMap; - for (int i = 0; i < (int)sourceTriangles.size(); i++) { - const auto &faceIndices = sourceTriangles[i]; - if (faceIndices.size() == 3) { - vertexAdjFaceCountMap[faceIndices[0]]++; - vertexAdjFaceCountMap[faceIndices[1]]++; - vertexAdjFaceCountMap[faceIndices[2]]++; - triangleEdgeMap[std::make_pair(faceIndices[0], faceIndices[1])] = std::make_pair(i, faceIndices[2]); - triangleEdgeMap[std::make_pair(faceIndices[1], faceIndices[2])] = std::make_pair(i, faceIndices[0]); - triangleEdgeMap[std::make_pair(faceIndices[2], faceIndices[0])] = std::make_pair(i, faceIndices[1]); - } - } - for (int i = 0; i < (int)sourceTriangles.size(); i++) { - if (processedFaces.find(i) != processedFaces.end()) - continue; - const auto &faceIndices = sourceTriangles[i]; - if (faceIndices.size() == 3) { - bool indicesSeamCheck[3] = { - excludeVertices.find(faceIndices[0]) == excludeVertices.end(), - excludeVertices.find(faceIndices[1]) == excludeVertices.end(), - excludeVertices.find(faceIndices[2]) == excludeVertices.end() - }; - for (int j = 0; j < 3; j++) { - int next = (j + 1) % 3; - int nextNext = (j + 2) % 3; - if (indicesSeamCheck[j] && indicesSeamCheck[next]) { - std::pair edge = std::make_pair(faceIndices[j], faceIndices[next]); - int thirdVertexIndex = faceIndices[nextNext]; - if ((sourceVertices[edge.first] - sourceVertices[edge.second]).lengthSquared() < squareOfAllowedSmallestDistance) { - auto oppositeEdge = std::make_pair(edge.second, edge.first); - auto findOppositeFace = triangleEdgeMap.find(oppositeEdge); - if (findOppositeFace == triangleEdgeMap.end()) { - qDebug() << "Find opposite edge failed"; - continue; - } - int oppositeFaceIndex = findOppositeFace->second.first; - if (((sourceVertices[edge.first] - sourceVertices[thirdVertexIndex]).lengthSquared() < - (sourceVertices[edge.second] - sourceVertices[thirdVertexIndex]).lengthSquared()) && - vertexAdjFaceCountMap[edge.second] <= 4 && - weldVertexToMap.find(edge.second) == weldVertexToMap.end()) { - weldVertexToMap[edge.second] = edge.first; - weldTargetVertices.insert(edge.first); - processedFaces.insert(i); - processedFaces.insert(oppositeFaceIndex); - break; - } else if (vertexAdjFaceCountMap[edge.first] <= 4 && - weldVertexToMap.find(edge.first) == weldVertexToMap.end()) { - weldVertexToMap[edge.first] = edge.second; - weldTargetVertices.insert(edge.second); - processedFaces.insert(i); - processedFaces.insert(oppositeFaceIndex); - break; - } - } - } - } - } - } - int weldedCount = 0; - int faceCountAfterWeld = 0; - std::map oldToNewVerticesMap; - for (int i = 0; i < (int)sourceTriangles.size(); i++) { - const auto &faceIndices = sourceTriangles[i]; - std::vector mappedFaceIndices; - bool errored = false; - for (const auto &index: faceIndices) { - int finalIndex = index; - int mapTimes = 0; - while (mapTimes < 500) { - auto findMapResult = weldVertexToMap.find(finalIndex); - if (findMapResult == weldVertexToMap.end()) - break; - finalIndex = findMapResult->second; - mapTimes++; - } - if (mapTimes >= 500) { - qDebug() << "Map too much times"; - errored = true; - break; - } - mappedFaceIndices.push_back(finalIndex); - } - if (errored || mappedFaceIndices.size() < 3) - continue; - bool welded = false; - for (decltype(mappedFaceIndices.size()) j = 0; j < mappedFaceIndices.size(); j++) { - int next = (j + 1) % 3; - if (mappedFaceIndices[j] == mappedFaceIndices[next]) { - welded = true; - break; - } - } - if (welded) { - weldedCount++; - continue; - } - faceCountAfterWeld++; - std::vector newFace; - for (const auto &index: mappedFaceIndices) { - auto findMap = oldToNewVerticesMap.find(index); - if (findMap == oldToNewVerticesMap.end()) { - size_t newIndex = destVertices.size(); - newFace.push_back(newIndex); - destVertices.push_back(sourceVertices[index]); - oldToNewVerticesMap.insert({index, newIndex}); - } else { - newFace.push_back(findMap->second); - } - } - destTriangles.push_back(newFace); - } - return weldedCount; -} - -bool nodemesh::isManifold(const std::vector> &faces) -{ - std::set> halfEdges; - for (const auto &face: faces) { - for (size_t i = 0; i < face.size(); ++i) { - size_t j = (i + 1) % face.size(); - auto insertResult = halfEdges.insert({face[i], face[j]}); - if (!insertResult.second) - return false; - } - } - for (const auto &it: halfEdges) { - if (halfEdges.find({it.second, it.first}) == halfEdges.end()) - return false; - } - return true; -} - -void nodemesh::trim(std::vector *vertices, bool normalize) -{ - float xLow = std::numeric_limits::max(); - float xHigh = std::numeric_limits::lowest(); - float yLow = std::numeric_limits::max(); - float yHigh = std::numeric_limits::lowest(); - float zLow = std::numeric_limits::max(); - float zHigh = std::numeric_limits::lowest(); - for (const auto &position: *vertices) { - if (position.x() < xLow) - xLow = position.x(); - else if (position.x() > xHigh) - xHigh = position.x(); - if (position.y() < yLow) - yLow = position.y(); - else if (position.y() > yHigh) - yHigh = position.y(); - if (position.z() < zLow) - zLow = position.z(); - else if (position.z() > zHigh) - zHigh = position.z(); - } - float xMiddle = (xHigh + xLow) * 0.5; - float yMiddle = (yHigh + yLow) * 0.5; - float zMiddle = (zHigh + zLow) * 0.5; - if (normalize) { - float xSize = xHigh - xLow; - float ySize = yHigh - yLow; - float zSize = zHigh - zLow; - float longSize = ySize; - if (xSize > longSize) - longSize = xSize; - if (zSize > longSize) - longSize = zSize; - if (qFuzzyIsNull(longSize)) - longSize = 0.000001; - for (auto &position: *vertices) { - position.setX((position.x() - xMiddle) / longSize); - position.setY((position.y() - yMiddle) / longSize); - position.setZ((position.z() - zMiddle) / longSize); - } - } else { - for (auto &position: *vertices) { - position.setX((position.x() - xMiddle)); - position.setY((position.y() - yMiddle)); - position.setZ((position.z() - zMiddle)); - } - } -} - -void nodemesh::subdivideFace2D(std::vector *face) -{ - auto oldFace = *face; - face->resize(oldFace.size() * 2); - for (size_t i = 0, n = 0; i < oldFace.size(); ++i) { - size_t h = (i + oldFace.size() - 1) % oldFace.size(); - size_t j = (i + 1) % oldFace.size(); - (*face)[n++] = oldFace[h] * 0.125 + oldFace[i] * 0.75 + oldFace[j] * 0.125; - (*face)[n++] = (oldFace[i] + oldFace[j]) * 0.5; - } -} - -void nodemesh::chamferFace2D(std::vector *face) -{ - auto oldFace = *face; - face->clear(); - for (size_t i = 0; i < oldFace.size(); ++i) { - size_t j = (i + 1) % oldFace.size(); - face->push_back(oldFace[i] * 0.8 + oldFace[j] * 0.2); - face->push_back(oldFace[i] * 0.2 + oldFace[j] * 0.8); - } -} diff --git a/thirdparty/nodemesh/nodemesh/misc.h b/thirdparty/nodemesh/nodemesh/misc.h deleted file mode 100644 index 0bcbc022..00000000 --- a/thirdparty/nodemesh/nodemesh/misc.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef NODEMESH_MISC_H -#define NODEMESH_MISC_H -#include -#include -#include -#include -#include -#include - -namespace nodemesh -{ - -float angleBetween(const QVector3D &v1, const QVector3D &v2); -float radianToDegree(float r); -float degreeBetween(const QVector3D &v1, const QVector3D &v2); -float degreeBetweenIn360(const QVector3D &a, const QVector3D &b, const QVector3D &direct); -bool pointInTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &p); -QVector3D polygonNormal(const std::vector &vertices, const std::vector &polygon); -bool triangulate(std::vector &vertices, const std::vector> &faces, std::vector> &triangles); -void exportMeshAsObj(const std::vector &vertices, const std::vector> &faces, const QString &filename, const std::set *excludeFacesOfVertices=nullptr); -void exportMeshAsObjWithNormals(const std::vector &vertices, const std::vector> &faces, const QString &filename, - const std::vector &triangleVertexNormals); -float areaOfTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c); -void angleSmooth(const std::vector &vertices, - const std::vector> &triangles, - const std::vector &triangleNormals, - float thresholdAngleDegrees, - std::vector &triangleVertexNormals); -void recoverQuads(const std::vector &vertices, const std::vector> &triangles, const std::set> &sharedQuadEdges, std::vector> &triangleAndQuads); -size_t weldSeam(const std::vector &sourceVertices, const std::vector> &sourceTriangles, - float allowedSmallestDistance, const std::set &excludePositions, - std::vector &destVertices, std::vector> &destTriangles); -bool isManifold(const std::vector> &faces); -void trim(std::vector *vertices, bool normalize=false); -void subdivideFace2D(std::vector *face); -void chamferFace2D(std::vector *face); -bool validatePosition(const QVector3D &position); - -} - -#endif -