Introduce new mesh generator: nodemesh

Remove meshlite, introduce nodemesh: https://github.com/huxingyi/nodemesh
Now, all the codebase move to C++, no rust language dependency.
master
Jeremy Hu 2019-02-18 22:27:18 +09:30
parent 8ace5b43de
commit 4bdbdd496a
62 changed files with 3707 additions and 2264 deletions

View File

@ -54,28 +54,13 @@ install:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install qt59base qt59tools --force-yes; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then source /opt/qt59/bin/qt59-env.sh; fi
# Install Rust
- wget -O installrust.sh https://sh.rustup.rs
- sh installrust.sh -y
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export PATH="$HOME/.cargo/bin:/usr/local/opt/qt/bin:$(brew --prefix)/bin:$PATH"; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export PATH="~/.cargo/bin:$PATH"; fi
script:
# Build Meshlite
- git clone https://github.com/huxingyi/meshlite.git || travis_terminate 1
- cd meshlite/ffi
- cargo build --release
- cd ../../
- cp meshlite/ffi/include/meshlite.h thirdparty/meshlite/meshlite.h || travis_terminate 1
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cp meshlite/ffi/target/release/libmeshlite_ffi.dylib thirdparty/meshlite/libmeshlite_ffi.dylib || travis_terminate 1; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then cp meshlite/ffi/target/release/libmeshlite_ffi.so thirdparty/meshlite/libmeshlite_ffi.so || travis_terminate 1; fi
# Build Dust3D
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then qmake -config release || travis_terminate 1; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then qmake -config release || travis_terminate 1; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then make || travis_terminate 1; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make CXX="g++ -fext-numeric-literals" || travis_terminate 1; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/thirdparty/meshlite || travis_terminate 1; fi
# Prepare tag name
- export TAG="$TRAVIS_TAG"
@ -109,7 +94,7 @@ script:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then cp ./dust3d appdir/usr/bin/dust3d || travis_terminate 1; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then cp ./ci/dust3d.desktop appdir/usr/share/applications/dust3d.desktop || travis_terminate 1; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then chmod a+x ./ci/linuxdeployqt.AppImage || travis_terminate 1; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then unset QTDIR; unset QT_PLUGIN_PATH ; export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/thirdparty/meshlite; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then unset QTDIR; unset QT_PLUGIN_PATH ; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./ci/linuxdeployqt.AppImage appdir/usr/share/applications/dust3d.desktop -bundle-non-qt-libs -verbose=2 || travis_terminate 1; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then rm appdir/AppRun || travis_terminate 1; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then cp ./ci/AppRun-patched-x86_64 appdir/AppRun || travis_terminate 1; fi

View File

@ -1138,3 +1138,8 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode
<pre>
https://en.wikipedia.org/wiki/Eadweard_Muybridge
</pre>
<h1>nodemesh</h1>
<pre>
https://github.com/huxingyi/nodemesh
</pre>

View File

@ -49,7 +49,6 @@ after_test:
- if "%TAG%" == ""
set TAG=unstable
- 7z a dust3d-%TAG%-%PLATFORM%.zip %APPVEYOR_BUILD_FOLDER%\release\dust3d.exe
- 7z a dust3d-%TAG%-%PLATFORM%.zip %APPVEYOR_BUILD_FOLDER%\thirdparty\meshlite\meshlite_unstable_vc14_%PLATFORM%\meshlite_ffi.dll
- 7z a dust3d-%TAG%-%PLATFORM%.zip %APPVEYOR_BUILD_FOLDER%\CGAL-4.11.1\build\bin\Release\CGAL-vc140-mt-4.11.1.dll
- 7z a dust3d-%TAG%-%PLATFORM%.zip %APPVEYOR_BUILD_FOLDER%\CGAL-4.11.1\auxiliary\gmp\lib\libgmp-10.dll
- 7z a dust3d-%TAG%-%PLATFORM%.zip %APPVEYOR_BUILD_FOLDER%\CGAL-4.11.1\auxiliary\gmp\lib\libmpfr-4.dll

View File

@ -3,11 +3,7 @@ Building Dust3D
Overview
==========
The core mesh algorithms of Dust3D written in Rust language, located in meshlite repository,
https://github.com/huxingyi/meshlite
The UI of Dust3D built in Qt5, the only thirdparty dependency which should been compiled separately is the CGAL library, however, CGAL will introduce some new dependencies, such as boost and gmp library.
The UI of Dust3D built in Qt5, the only third party dependency which should be compiled separately is the CGAL library, however, CGAL will introduce some new dependencies, such as boost and gmp library.
Prerequisites
===============
@ -15,10 +11,6 @@ Prerequisites
https://www.cgal.org/
* Rust
https://www.rust-lang.org/en-US/install.html
Building
==========
@ -40,13 +32,6 @@ Here is the snapshot of the command line of one build, you may use different def
.. code-block:: sh
$ cd /Users/jeremy/Desktop
$ git clone https://github.com/huxingyi/meshlite.git
$ cd meshlite/ffi
$ cargo build --release
$ cp include/meshlite.h /Users/jeremy/Repositories/dust3d/thirdparty/meshlite/meshlite.h
$ cp target/release/libmeshlite_ffi.dylib /Users/jeremy/Repositories/dust3d/thirdparty/meshlite/libmeshlite_ffi.dylib
$ cd /Users/jeremy/Repositories/dust3d
$ qmake -spec macx-xcode
Open dust3d.xcodeproj in Xcode and build
@ -55,9 +40,6 @@ Here is the snapshot of the command line of one build, you may use different def
.. code-block:: sh
;Install Rust
$ curl https://sh.rustup.rs -sSf | sh ;Add ~/.cargo/bin to PATH after finishing install
;Install Qt5
$ sudo apt-get install --reinstall qtchooser
$ sudo apt-get install qtbase5-dev
@ -80,17 +62,8 @@ Here is the snapshot of the command line of one build, you may use different def
$ cd ~/Documents
$ git clone https://github.com/huxingyi/dust3d.git
;Compile the internal dependency
$ cd ~/Desktop
$ git clone https://github.com/huxingyi/meshlite.git
$ cd meshlite/ffi
$ cargo build --release
$ cp ~/Desktop/meshlite/ffi/include/meshlite.h ~/Documents/dust3d/thirdparty/meshlite/meshlite.h
$ cp ~/Desktop/meshlite/ffi/target/release/libmeshlite_ffi.so ~/Documents/dust3d/thirdparty/meshlite/libmeshlite_ffi.so
;Compile Dust3D
$ cd ~/Documents/dust3d
$ qmake -qt=5 -makefile
$ make
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/Documents/dust3d/thirdparty/meshlite
$ ./dust3d

View File

@ -101,9 +101,6 @@ HEADERS += src/theme.h
SOURCES += src/meshloader.cpp
HEADERS += src/meshloader.h
SOURCES += src/meshutil.cpp
HEADERS += src/meshutil.h
SOURCES += src/texturegenerator.cpp
HEADERS += src/texturegenerator.h
@ -113,9 +110,6 @@ HEADERS += src/outcome.h
SOURCES += src/meshresultpostprocessor.cpp
HEADERS += src/meshresultpostprocessor.h
SOURCES += src/positionmap.cpp
HEADERS += src/positionmap.h
SOURCES += src/logbrowser.cpp
HEADERS += src/logbrowser.h
@ -167,9 +161,6 @@ HEADERS += src/rigtype.h
SOURCES += src/riggenerator.cpp
HEADERS += src/riggenerator.h
SOURCES += src/meshquadify.cpp
HEADERS += src/meshquadify.h
SOURCES += src/skinnedmeshcreator.cpp
HEADERS += src/skinnedmeshcreator.h
@ -200,9 +191,6 @@ HEADERS += src/posepreviewsgenerator.h
SOURCES += src/posewidget.cpp
HEADERS += src/posewidget.h
SOURCES += src/meshweldseam.cpp
HEADERS += src/meshweldseam.h
SOURCES += src/advancesettingwidget.cpp
HEADERS += src/advancesettingwidget.h
@ -251,9 +239,6 @@ HEADERS += src/material.h
SOURCES += src/fbxfile.cpp
HEADERS += src/fbxfile.h
SOURCES += src/anglesmooth.cpp
HEADERS += src/anglesmooth.h
SOURCES += src/motiontimelinewidget.cpp
HEADERS += src/motiontimelinewidget.h
@ -302,13 +287,41 @@ HEADERS += src/posedocument.h
SOURCES += src/combinemode.cpp
HEADERS += src/combinemode.h
SOURCES += src/meshinflate.cpp
HEADERS += src/meshinflate.h
SOURCES += src/main.cpp
HEADERS += src/version.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/util.cpp
HEADERS += thirdparty/nodemesh/nodemesh/util.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
@ -381,12 +394,6 @@ win32 {
error("No CGAL_DIR define found in enviroment variables")
}
contains(QMAKE_TARGET.arch, x86_64) {
MESHLITE_DIR = thirdparty/meshlite/meshlite_unstable_vc14_x64
} else {
MESHLITE_DIR = thirdparty/meshlite/meshlite_unstable_vc14_x86
}
MESHLITE_LIBNAME = meshlite_ffi.dll
GMP_LIBNAME = libgmp-10
MPFR_LIBNAME = libmpfr-4
CGAL_LIBNAME = CGAL-vc140-mt-4.11.1
@ -400,8 +407,6 @@ win32 {
}
macx {
MESHLITE_DIR = thirdparty/meshlite
MESHLITE_LIBNAME = meshlite_ffi
GMP_LIBNAME = gmp
MPFR_LIBNAME = mpfr
CGAL_LIBNAME = cgal
@ -416,8 +421,6 @@ macx {
}
unix:!macx {
MESHLITE_DIR = thirdparty/meshlite
MESHLITE_LIBNAME = meshlite_ffi
GMP_LIBNAME = gmp
MPFR_LIBNAME = mpfr
CGAL_LIBNAME = CGAL
@ -431,9 +434,6 @@ unix:!macx {
MPFR_LIBDIR = /usr/local/lib
}
INCLUDEPATH += $$MESHLITE_DIR
LIBS += -L$$MESHLITE_DIR -l$$MESHLITE_LIBNAME
INCLUDEPATH += $$BOOST_INCLUDEDIR
INCLUDEPATH += $$GMP_INCLUDEDIR

View File

@ -1,56 +0,0 @@
#include <cmath>
#include <map>
#include "util.h"
#include "anglesmooth.h"
void angleSmooth(const std::vector<QVector3D> &vertices,
const std::vector<std::tuple<size_t, size_t, size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals,
float thresholdAngleDegrees, std::vector<QVector3D> &triangleVertexNormals)
{
std::vector<std::vector<std::pair<size_t, size_t>>> triangleVertexNormalsMapByIndices(vertices.size());
std::vector<QVector3D> angleAreaWeightedNormals;
for (size_t triangleIndex = 0; triangleIndex < triangles.size(); ++triangleIndex) {
const auto &sourceTriangle = triangles[triangleIndex];
size_t indices[] = {std::get<0>(sourceTriangle),
std::get<1>(sourceTriangle),
std::get<2>(sourceTriangle)};
const auto &v1 = vertices[indices[0]];
const auto &v2 = vertices[indices[1]];
const auto &v3 = vertices[indices[2]];
float area = areaOfTriangle(v1, v2, v3);
float angles[] = {radianBetweenVectors(v2-v1, v3-v1),
radianBetweenVectors(v1-v2, v3-v2),
radianBetweenVectors(v1-v3, v2-v3)};
for (int i = 0; i < 3; ++i) {
triangleVertexNormalsMapByIndices[indices[i]].push_back({triangleIndex, angleAreaWeightedNormals.size()});
angleAreaWeightedNormals.push_back(triangleNormals[triangleIndex] * area * angles[i]);
}
}
triangleVertexNormals = angleAreaWeightedNormals;
std::map<std::pair<size_t, size_t>, 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();
}

View File

@ -1,11 +0,0 @@
#ifndef DUST3D_ANGLE_SMOOTH_H
#define DUST3D_ANGLE_SMOOTH_H
#include <QVector3D>
#include <vector>
void angleSmooth(const std::vector<QVector3D> &vertices,
const std::vector<std::tuple<size_t, size_t, size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals,
float thresholdAngleDegrees, std::vector<QVector3D> &triangleVertexNormals);
#endif

View File

@ -519,7 +519,6 @@ bool AnimalRigger::rig()
jointBone.name = namingChain(chainBaseName, chainMark.boneSide, chainGenerateOrder, spineNode.chainMarkIndices.size(), jointGenerateOrder);
jointBone.headPosition = jointPositions[jointGenerateOrder - 1];
jointBone.tailPosition = jointPositions[jointGenerateOrder];
jointBone.baseNormal = jointMark.baseNormal;
jointBone.color = boneColor();
if (jointGenerateOrder == (int)jointMarkIndices.size()) {
addVerticesToWeights(remainingLimbVertices, jointBone.index);

View File

@ -1476,14 +1476,7 @@ void Document::generateMesh()
toSnapshot(snapshot);
resetDirtyFlags();
m_meshGenerator = new MeshGenerator(snapshot);
m_meshGenerator->setSmoothNormal(m_smoothNormal);
m_meshGenerator->setWeldEnabled(weldEnabled);
m_meshGenerator->setGeneratedCacheContext(&m_generatedCacheContext);
if (nullptr != m_sharedContextWidget)
m_meshGenerator->setSharedContextWidget(m_sharedContextWidget);
for (auto &part: partMap) {
m_meshGenerator->addPartPreviewRequirement(part.first);
}
m_meshGenerator->moveToThread(thread);
connect(thread, &QThread::started, m_meshGenerator, &MeshGenerator::process);
connect(m_meshGenerator, &MeshGenerator::finished, this, &Document::meshReady);

View File

@ -57,8 +57,6 @@ void MaterialPreviewsGenerator::generate()
GeneratedCacheContext *cacheContext = new GeneratedCacheContext();
MeshGenerator *meshGenerator = new MeshGenerator(snapshot);
meshGenerator->setSmoothNormal(true);
meshGenerator->setWeldEnabled(false);
meshGenerator->setGeneratedCacheContext(cacheContext);
meshGenerator->generate();

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,49 @@
#ifndef DUST3D_MESH_GENERATOR_H
#define DUST3D_MESH_GENERATOR_H
#include <QObject>
#include <QString>
#include <QImage>
#include <map>
#include <set>
#include <QThread>
#include <QOpenGLWidget>
#include "snapshot.h"
#include "meshloader.h"
#include <QColor>
#include <nodemesh/combiner.h>
#include <nodemesh/positionkey.h>
#include "outcome.h"
#include "positionmap.h"
#include "snapshot.h"
#include "combinemode.h"
#include "meshloader.h"
class GeneratedPart
{
public:
~GeneratedPart()
{
delete mesh;
};
nodemesh::Combiner::Mesh *mesh = nullptr;
std::vector<QVector3D> vertices;
std::vector<std::vector<size_t>> faces;
std::vector<OutcomeNode> outcomeNodes;
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> outcomeNodeVertices;
};
class GeneratedComponent
{
public:
~GeneratedComponent()
{
delete mesh;
};
nodemesh::Combiner::Mesh *mesh = nullptr;
std::set<std::pair<nodemesh::PositionKey, nodemesh::PositionKey>> sharedQuadEdges;
std::set<nodemesh::PositionKey> noneSeamVertices;
std::vector<OutcomeNode> outcomeNodes;
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> outcomeNodeVertices;
};
class GeneratedCacheContext
{
public:
~GeneratedCacheContext();
std::map<QString, std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>>> partBmeshVertices;
std::map<QString, std::vector<OutcomeNode>> partBmeshNodes;
std::map<QString, std::vector<std::tuple<PositionMapKey, PositionMapKey, PositionMapKey, PositionMapKey>>> partBmeshQuads;
std::map<QString, void *> componentCombinableMeshs;
std::map<QString, std::vector<QVector3D>> componentPositions;
std::map<QString, PositionMap<std::pair<QVector3D, std::pair<QUuid, QUuid>>>> componentVerticesSources;
std::map<QString, GeneratedComponent> components;
std::map<QString, GeneratedPart> parts;
std::map<QString, QString> partMirrorIdMap;
std::map<QString, std::vector<std::pair<QVector3D, float>>> componentInflateBalls;
void updateComponentCombinableMesh(QString componentId, void *mesh);
};
class MeshGenerator : public QObject
@ -34,56 +52,51 @@ class MeshGenerator : public QObject
public:
MeshGenerator(Snapshot *snapshot);
~MeshGenerator();
void setSharedContextWidget(QOpenGLWidget *widget);
void addPartPreviewRequirement(const QUuid &partId);
void setGeneratedCacheContext(GeneratedCacheContext *cacheContext);
void setSmoothNormal(bool smoothNormal);
void setWeldEnabled(bool weldEnabled);
bool isSucceed();
MeshLoader *takeResultMesh();
MeshLoader *takePartPreviewMesh(const QUuid &partId);
const std::set<QUuid> &requirePreviewPartIds();
const std::set<QUuid> &generatedPreviewPartIds();
Outcome *takeOutcome();
void generate();
void setGeneratedCacheContext(GeneratedCacheContext *cacheContext);
signals:
void finished();
public slots:
void process();
private:
Snapshot *m_snapshot;
bool m_isSucceed;
MeshLoader *m_mesh;
std::map<QUuid, MeshLoader *> m_partPreviewMeshMap;
std::set<QUuid> m_requirePreviewPartIds;
std::set<QUuid> m_generatedPreviewPartIds;
QThread *m_thread;
Outcome *m_outcome;
QOpenGLWidget *m_sharedContextWidget;
void *m_meshliteContext;
GeneratedCacheContext *m_cacheContext;
bool m_smoothNormal;
bool m_weldEnabled;
float m_mainProfileMiddleX;
float m_sideProfileMiddleX;
float m_mainProfileMiddleY;
std::map<QString, std::set<QString>> m_partNodeIds;
std::map<QString, std::set<QString>> m_partEdgeIds;
QColor m_defaultPartColor = Qt::white;
Snapshot *m_snapshot = nullptr;
GeneratedCacheContext *m_cacheContext = nullptr;
std::set<QString> m_dirtyComponentIds;
std::set<QString> m_dirtyPartIds;
private:
static bool m_enableDebug;
static PositionMap<int> *m_forMakePositionKey;
private:
void loadVertexSources(void *meshliteContext, int meshId, QUuid partId, const std::map<int, QUuid> &bmeshToNodeIdMap, std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> &bmeshVertices,
std::vector<std::tuple<PositionMapKey, PositionMapKey, PositionMapKey, PositionMapKey>> &bmeshQuads);
void loadGeneratedPositionsToOutcome(void *meshliteContext, int triangulatedMeshId);
float m_mainProfileMiddleX = 0;
float m_sideProfileMiddleX = 0;
float m_mainProfileMiddleY = 0;
Outcome *m_outcome = nullptr;
std::map<QString, std::set<QString>> m_partNodeIds;
std::map<QString, std::set<QString>> m_partEdgeIds;
std::set<QUuid> m_generatedPreviewPartIds;
MeshLoader *m_resultMesh = nullptr;
std::map<QUuid, MeshLoader *> m_partPreviewMeshes;
bool m_isSucceed = false;
bool m_cacheEnabled = false;
void collectParts();
bool checkIsComponentDirty(const QString &componentIdString);
bool checkIsPartDirty(const QString &partIdString);
void checkDirtyFlags();
bool checkIsComponentDirty(QString componentId);
bool checkIsPartDirty(QString partId);
void *combineComponentMesh(QString componentId, CombineMode *combineMode, std::vector<std::pair<QVector3D, float>> *inflateBalls);
void *combinePartMesh(QString partId, std::vector<std::pair<QVector3D, float>> *balls=nullptr);
nodemesh::Combiner::Mesh *combinePartMesh(const QString &partIdString);
nodemesh::Combiner::Mesh *combineComponentMesh(const QString &componentIdString, CombineMode *combineMode);
void makeXmirror(const std::vector<QVector3D> &sourceVertices, const std::vector<std::vector<size_t>> &sourceFaces,
std::vector<QVector3D> *destVertices, std::vector<std::vector<size_t>> *destFaces);
void collectSharedQuadEdges(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces,
std::set<std::pair<nodemesh::PositionKey, nodemesh::PositionKey>> *sharedQuadEdges);
nodemesh::Combiner::Mesh *combineTwoMeshes(const nodemesh::Combiner::Mesh &first, const nodemesh::Combiner::Mesh &second,
nodemesh::Combiner::Method method);
void generateSmoothTriangleVertexNormals(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals,
std::vector<std::vector<QVector3D>> *triangleVertexNormals);
};
#endif

View File

@ -1,51 +0,0 @@
#include <QDebug>
#include <set>
#include <cmath>
#include "meshinflate.h"
#include "meshutil.h"
void *meshInflate(void *combinableMesh, const std::vector<std::pair<QVector3D, float>> &inflateBalls, std::vector<std::pair<QVector3D, QVector3D>> &inflatedVertices)
{
std::vector<QVector3D> oldPositions;
std::vector<QVector3D> newPositions;
std::vector<std::vector<int>> faces;
std::set<int> changedVertices;
loadCombinableMeshVerticesPositionsAndFacesIndices(combinableMesh, oldPositions, faces);
newPositions = oldPositions;
std::vector<float> inflateBallRadius2s(inflateBalls.size());
for (size_t i = 0; i < inflateBalls.size(); ++i) {
const auto &radius = inflateBalls[i].second;
inflateBallRadius2s[i] = radius * radius;
}
for (size_t i = 0; i < oldPositions.size(); ++i) {
const auto &oldPos = oldPositions[i];
QVector3D sumOfNewPos;
size_t pushedByBallNum = 0;
for (size_t j = 0; j < inflateBalls.size(); ++j) {
const auto &ball = inflateBalls[j];
const auto &ballPos = ball.first;
const auto &ballRadius = std::max(ball.second, (float)0.00001);
const auto ballToPosVec = oldPos - ballPos;
if (ballToPosVec.lengthSquared() > inflateBallRadius2s[j])
continue;
const auto distance = 1.0 - ballToPosVec.length() / ballRadius;
const auto smoothedDist = 3.0 * std::pow(distance, 2) - 2.0 * std::pow(distance, 3);
const auto newPosPushByBall = oldPos + ballToPosVec * smoothedDist;
sumOfNewPos += newPosPushByBall;
++pushedByBallNum;
}
if (0 == pushedByBallNum)
continue;
newPositions[i] = sumOfNewPos / pushedByBallNum;
changedVertices.insert(i);
}
for (const auto &index: changedVertices) {
inflatedVertices.push_back({oldPositions[index], newPositions[index]});
}
return buildCombinableMeshFromVerticesPositionsAndFacesIndices(newPositions, faces);
}

View File

@ -1,8 +0,0 @@
#ifndef DUST3D_MESH_INFLATE_H
#define DUST3D_MESH_INFLATE_H
#include <vector>
#include <QVector3D>
void *meshInflate(void *combinableMesh, const std::vector<std::pair<QVector3D, float>> &inflateBalls, std::vector<std::pair<QVector3D, QVector3D>> &inflatedVertices);
#endif

View File

@ -3,194 +3,13 @@
#include <QFile>
#include <cmath>
#include "meshloader.h"
#include "meshlite.h"
#include "theme.h"
#include "positionmap.h"
#include "ds3file.h"
#include "anglesmooth.h"
#define MAX_VERTICES_PER_FACE 100
float MeshLoader::m_defaultMetalness = 0.0;
float MeshLoader::m_defaultRoughness = 1.0;
MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColor defaultColor, const std::vector<QColor> *triangleColors, bool smoothNormal) :
m_triangleVertices(nullptr),
m_triangleVertexCount(0),
m_edgeVertices(nullptr),
m_edgeVertexCount(0),
m_textureImage(nullptr)
{
int edgeVertexPositionCount = meshlite_get_vertex_count(meshlite, meshId);
GLfloat *edgeVertexPositions = new GLfloat[edgeVertexPositionCount * 3];
int loadedEdgeVertexPositionItemCount = meshlite_get_vertex_position_array(meshlite, meshId, edgeVertexPositions, edgeVertexPositionCount * 3);
int offset = 0;
while (offset < loadedEdgeVertexPositionItemCount) {
QVector3D position = QVector3D(edgeVertexPositions[offset], edgeVertexPositions[offset + 1], edgeVertexPositions[offset + 2]);
m_vertices.push_back(position);
offset += 3;
}
int faceCount = meshlite_get_face_count(meshlite, meshId);
int *faceVertexNumAndIndices = new int[faceCount * (1 + MAX_VERTICES_PER_FACE)];
int loadedFaceVertexNumAndIndicesItemCount = meshlite_get_face_index_array(meshlite, meshId, faceVertexNumAndIndices, faceCount * (1 + MAX_VERTICES_PER_FACE));
offset = 0;
while (offset < loadedFaceVertexNumAndIndicesItemCount) {
int indicesNum = faceVertexNumAndIndices[offset++];
assert(indicesNum >= 0 && indicesNum <= MAX_VERTICES_PER_FACE);
std::vector<int> face;
for (int i = 0; i < indicesNum && offset < loadedFaceVertexNumAndIndicesItemCount; i++) {
int index = faceVertexNumAndIndices[offset++];
assert(index >= 0 && index < loadedEdgeVertexPositionItemCount);
face.push_back(index);
}
m_faces.push_back(face);
}
delete[] faceVertexNumAndIndices;
faceVertexNumAndIndices = NULL;
int edgeCount = meshlite_get_halfedge_count(meshlite, meshId);
int *edgeIndices = new int[edgeCount * 2];
int loadedEdgeVertexIndexItemCount = meshlite_get_halfedge_index_array(meshlite, meshId, edgeIndices, edgeCount * 2);
GLfloat *edgeNormals = new GLfloat[edgeCount * 3];
int loadedEdgeNormalItemCount = meshlite_get_halfedge_normal_array(meshlite, meshId, edgeNormals, edgeCount * 3);
m_edgeVertexCount = edgeCount * 2;
m_edgeVertices = new Vertex[m_edgeVertexCount * 3];
for (int i = 0; i < edgeCount; i++) {
int firstIndex = i * 2;
for (int j = 0; j < 2; j++) {
assert(firstIndex + j < loadedEdgeVertexIndexItemCount);
int posIndex = edgeIndices[firstIndex + j] * 3;
assert(posIndex < loadedEdgeVertexPositionItemCount);
Vertex *v = &m_edgeVertices[firstIndex + j];
v->posX = edgeVertexPositions[posIndex + 0];
v->posY = edgeVertexPositions[posIndex + 1];
v->posZ = edgeVertexPositions[posIndex + 2];
assert(firstIndex + 2 < loadedEdgeNormalItemCount);
v->normX = edgeNormals[firstIndex + 0];
v->normY = edgeNormals[firstIndex + 1];
v->normZ = edgeNormals[firstIndex + 2];
v->colorR = 0.0;
v->colorG = 0.0;
v->colorB = 0.0;
v->texU = 0.0;
v->texV = 0.0;
v->metalness = 0;
v->roughness = 1.0;
}
}
int triangleMesh = -1 == triangulatedMeshId ? meshlite_triangulate(meshlite, meshId) : triangulatedMeshId;
int triangleVertexPositionCount = meshlite_get_vertex_count(meshlite, triangleMesh);
GLfloat *triangleVertexPositions = new GLfloat[triangleVertexPositionCount * 3];
int loadedTriangleVertexPositionItemCount = meshlite_get_vertex_position_array(meshlite, triangleMesh, triangleVertexPositions, triangleVertexPositionCount * 3);
GLfloat *triangleVertexSmoothNormals = new GLfloat[triangleVertexPositionCount * 3];
if (smoothNormal)
memset(triangleVertexSmoothNormals, 0, sizeof(GLfloat) * triangleVertexPositionCount * 3);
offset = 0;
while (offset < loadedTriangleVertexPositionItemCount) {
QVector3D position = QVector3D(triangleVertexPositions[offset], triangleVertexPositions[offset + 1], triangleVertexPositions[offset + 2]);
m_triangulatedVertices.push_back(position);
offset += 3;
}
int triangleCount = meshlite_get_face_count(meshlite, triangleMesh);
int *triangleIndices = new int[triangleCount * 3];
int loadedTriangleVertexIndexItemCount = meshlite_get_triangle_index_array(meshlite, triangleMesh, triangleIndices, triangleCount * 3);
GLfloat *triangleNormals = new GLfloat[triangleCount * 3];
int loadedTriangleNormalItemCount = meshlite_get_triangle_normal_array(meshlite, triangleMesh, triangleNormals, triangleCount * 3);
float modelR = defaultColor.redF();
float modelG = defaultColor.greenF();
float modelB = defaultColor.blueF();
m_triangleVertexCount = triangleCount * 3;
m_triangleVertices = new Vertex[m_triangleVertexCount * 3];
const std::vector<QVector3D> &inputVerticesForSmoothAngle = m_triangulatedVertices;
std::vector<std::tuple<size_t, size_t, size_t>> inputTrianglesForSmoothAngle;
std::vector<QVector3D> inputNormalsForSmoothAngle;
float thresholdAngleDegrees = 60;
for (int i = 0; i < triangleCount; i++) {
int firstIndex = i * 3;
float useColorR = modelR;
float useColorG = modelG;
float useColorB = modelB;
float useMetalness = m_defaultMetalness;
float useRoughness = m_defaultRoughness;
if (nullptr != triangleColors && i < (int)triangleColors->size()) {
const auto &triangleColor = (*triangleColors)[i];
useColorR = triangleColor.redF();
useColorG = triangleColor.greenF();
useColorB = triangleColor.blueF();
}
TriangulatedFace triangulatedFace;
triangulatedFace.color.setRedF(useColorR);
triangulatedFace.color.setGreenF(useColorG);
triangulatedFace.color.setBlueF(useColorB);
QVector3D positions[3];
if (smoothNormal) {
inputTrianglesForSmoothAngle.push_back(std::make_tuple((size_t)triangleIndices[firstIndex + 0],
(size_t)triangleIndices[firstIndex + 1],
(size_t)triangleIndices[firstIndex + 2]
));
inputNormalsForSmoothAngle.push_back(QVector3D(triangleNormals[firstIndex + 0],
triangleNormals[firstIndex + 1],
triangleNormals[firstIndex + 2]));
}
for (int j = 0; j < 3; j++) {
assert(firstIndex + j < loadedTriangleVertexIndexItemCount);
int posIndex = triangleIndices[firstIndex + j] * 3;
assert(posIndex < loadedTriangleVertexPositionItemCount);
triangulatedFace.indices[j] = triangleIndices[firstIndex + j];
Vertex *v = &m_triangleVertices[firstIndex + j];
v->posX = triangleVertexPositions[posIndex + 0];
v->posY = triangleVertexPositions[posIndex + 1];
v->posZ = triangleVertexPositions[posIndex + 2];
assert(firstIndex + 2 < loadedTriangleNormalItemCount);
v->normX = triangleNormals[firstIndex + 0];
v->normY = triangleNormals[firstIndex + 1];
v->normZ = triangleNormals[firstIndex + 2];
v->colorR = useColorR;
v->colorG = useColorG;
v->colorB = useColorB;
v->metalness = useMetalness;
v->roughness = useRoughness;
}
m_triangulatedFaces.push_back(triangulatedFace);
}
if (smoothNormal) {
std::vector<QVector3D> resultNormals;
angleSmooth(inputVerticesForSmoothAngle, inputTrianglesForSmoothAngle, inputNormalsForSmoothAngle,
thresholdAngleDegrees, resultNormals);
size_t normalIndex = 0;
for (int i = 0; i < triangleCount; i++) {
int firstIndex = i * 3;
for (int j = 0; j < 3; j++) {
assert(firstIndex + j < loadedTriangleVertexIndexItemCount);
Vertex *v = &m_triangleVertices[firstIndex + j];
const auto &normal = resultNormals[normalIndex++];
v->normX = normal.x();
v->normY = normal.y();
v->normZ = normal.z();
}
}
}
delete[] triangleVertexPositions;
delete[] triangleVertexSmoothNormals;
delete[] triangleIndices;
delete[] triangleNormals;
delete[] edgeVertexPositions;
delete[] edgeIndices;
delete[] edgeNormals;
}
MeshLoader::MeshLoader(const MeshLoader &mesh) :
m_triangleVertices(nullptr),
m_triangleVertexCount(0),
@ -239,6 +58,40 @@ MeshLoader::MeshLoader(Vertex *triangleVertices, int vertexNum) :
{
}
MeshLoader::MeshLoader(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles,
const std::vector<std::vector<QVector3D>> &triangleVertexNormals,
const QColor &color)
{
m_triangleVertexCount = triangles.size() * 3;
m_triangleVertices = new Vertex[m_triangleVertexCount];
int destIndex = 0;
for (size_t i = 0; i < triangles.size(); ++i) {
for (auto j = 0; j < 3; j++) {
int vertexIndex = triangles[i][j];
const QVector3D *srcVert = &vertices[vertexIndex];
const QVector3D *srcNormal = &(triangleVertexNormals)[i][j];
Vertex *dest = &m_triangleVertices[destIndex];
dest->colorR = color.redF();
dest->colorG = color.greenF();
dest->colorB = color.blueF();
dest->posX = srcVert->x();
dest->posY = srcVert->y();
dest->posZ = srcVert->z();
dest->texU = 0;
dest->texV = 0;
dest->normX = srcNormal->x();
dest->normY = srcNormal->y();
dest->normZ = srcNormal->z();
dest->metalness = m_defaultMetalness;
dest->roughness = m_defaultRoughness;
dest->tangentX = 0;
dest->tangentY = 0;
dest->tangentZ = 0;
destIndex++;
}
}
}
MeshLoader::MeshLoader(Outcome &outcome) :
m_triangleVertices(nullptr),
m_triangleVertexCount(0),
@ -246,6 +99,9 @@ MeshLoader::MeshLoader(Outcome &outcome) :
m_edgeVertexCount(0),
m_textureImage(nullptr)
{
m_vertices = outcome.vertices;
m_faces = outcome.triangleAndQuads;
m_triangleVertexCount = outcome.triangles.size() * 3;
m_triangleVertices = new Vertex[m_triangleVertexCount];
int destIndex = 0;
@ -315,7 +171,7 @@ const std::vector<QVector3D> &MeshLoader::vertices()
return m_vertices;
}
const std::vector<std::vector<int>> &MeshLoader::faces()
const std::vector<std::vector<size_t>> &MeshLoader::faces()
{
return m_faces;
}
@ -419,9 +275,9 @@ void MeshLoader::exportAsObj(const QString &filename)
for (std::vector<QVector3D>::const_iterator it = vertices().begin() ; it != vertices().end(); ++it) {
stream << "v " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl;
}
for (std::vector<std::vector<int>>::const_iterator it = faces().begin() ; it != faces().end(); ++it) {
for (std::vector<std::vector<size_t>>::const_iterator it = faces().begin() ; it != faces().end(); ++it) {
stream << "f";
for (std::vector<int>::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) {
for (std::vector<size_t>::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) {
stream << " " << (1 + *subIt);
}
stream << endl;

View File

@ -6,8 +6,6 @@
#include <QVector3D>
#include <QColor>
#include <QImage>
#include "positionmap.h"
#include "theme.h"
#include "outcome.h"
#pragma pack(push)
@ -42,7 +40,9 @@ struct TriangulatedFace
class MeshLoader
{
public:
MeshLoader(void *meshlite, int meshId, int triangulatedMeshId=-1, QColor defaultColor=Theme::white, const std::vector<QColor> *triangleColors=nullptr, bool smoothNormal=true);
MeshLoader(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles,
const std::vector<std::vector<QVector3D>> &triangleVertexNormals,
const QColor &color=Qt::white);
MeshLoader(Outcome &outcome);
MeshLoader(Vertex *triangleVertices, int vertexNum);
MeshLoader(const MeshLoader &mesh);
@ -53,7 +53,7 @@ public:
Vertex *edgeVertices();
int edgeVertexCount();
const std::vector<QVector3D> &vertices();
const std::vector<std::vector<int>> &faces();
const std::vector<std::vector<size_t>> &faces();
const std::vector<QVector3D> &triangulatedVertices();
const std::vector<TriangulatedFace> &triangulatedFaces();
void setTextureImage(QImage *textureImage);
@ -77,7 +77,7 @@ private:
Vertex *m_edgeVertices = nullptr;
int m_edgeVertexCount = 0;
std::vector<QVector3D> m_vertices;
std::vector<std::vector<int>> m_faces;
std::vector<std::vector<size_t>> m_faces;
std::vector<QVector3D> m_triangulatedVertices;
std::vector<TriangulatedFace> m_triangulatedFaces;
QImage *m_textureImage = nullptr;

View File

@ -1,97 +0,0 @@
#include <QDebug>
#include <unordered_set>
#include <vector>
#include "meshquadify.h"
#include "meshlite.h"
int meshQuadify(void *meshlite, int meshId, const std::set<std::pair<PositionMapKey, PositionMapKey>> &sharedQuadEdges,
PositionMap<int> *positionMapForMakeKey)
{
int vertexCount = meshlite_get_vertex_count(meshlite, meshId);
float *vertexPositions = new float[vertexCount * 3];
int vertexArrayLen = meshlite_get_vertex_position_array(meshlite, meshId, vertexPositions, vertexCount * 3);
int offset = 0;
Q_ASSERT(vertexArrayLen == vertexCount * 3);
std::vector<PositionMapKey> positionKeyMap;
for (int i = 0; i < vertexCount; i++) {
float x = vertexPositions[offset + 0];
float y = vertexPositions[offset + 1];
float z = vertexPositions[offset + 2];
positionKeyMap.push_back(positionMapForMakeKey->makeKey(x, y, z));
offset += 3;
}
int faceCount = meshlite_get_face_count(meshlite, meshId);
int *faceVertexNumAndIndices = new int[faceCount * MAX_VERTICES_PER_FACE];
int filledLength = meshlite_get_face_index_array(meshlite, meshId, faceVertexNumAndIndices, faceCount * MAX_VERTICES_PER_FACE);
int i = 0;
std::vector<std::vector<int>> newFaceIndices;
while (i < filledLength) {
int num = faceVertexNumAndIndices[i++];
Q_ASSERT(num > 0 && num <= MAX_VERTICES_PER_FACE);
if (num < 3) {
i += num;
continue;
}
std::vector<int> indices;
for (int j = 0; j < num; j++) {
int index = faceVertexNumAndIndices[i++];
Q_ASSERT(index >= 0 && index < vertexCount);
indices.push_back(index);
}
newFaceIndices.push_back(indices);
}
int quadMesh = 0;
std::map<std::pair<int, int>, std::pair<int, int>> triangleEdgeMap;
for (int i = 0; i < (int)newFaceIndices.size(); i++) {
const auto &faceIndices = newFaceIndices[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<int> unionedFaces;
std::vector<std::vector<int>> newUnionedFaceIndices;
for (const auto &edge: triangleEdgeMap) {
if (unionedFaces.find(edge.second.first) != unionedFaces.end())
continue;
auto pair = std::make_pair(positionKeyMap[edge.first.first], positionKeyMap[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<int> 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);
newUnionedFaceIndices.push_back(indices);
}
}
}
}
std::vector<int> newFaceVertexNumAndIndices;
for (int i = 0; i < (int)newFaceIndices.size(); i++) {
if (unionedFaces.find(i) == unionedFaces.end()) {
const auto &faceIndices = newFaceIndices[i];
newFaceVertexNumAndIndices.push_back(faceIndices.size());
for (const auto &index: faceIndices) {
newFaceVertexNumAndIndices.push_back(index);
}
}
}
for (const auto &faceIndices: newUnionedFaceIndices) {
newFaceVertexNumAndIndices.push_back(faceIndices.size());
for (const auto &index: faceIndices) {
newFaceVertexNumAndIndices.push_back(index);
}
}
quadMesh = meshlite_build(meshlite, vertexPositions, vertexCount, newFaceVertexNumAndIndices.data(), newFaceVertexNumAndIndices.size());
delete[] faceVertexNumAndIndices;
delete[] vertexPositions;
return quadMesh;
}

View File

@ -1,10 +0,0 @@
#ifndef DUST3D_MESH_QUADIFY_H
#define DUST3D_MESH_QUADIFY_H
#include <set>
#include "meshutil.h"
#include "positionmap.h"
int meshQuadify(void *meshlite, int meshId, const std::set<std::pair<PositionMapKey, PositionMapKey>> &sharedQuadEdges,
PositionMap<int> *positionMapForMakeKey);
#endif

View File

@ -2,7 +2,6 @@
#include "meshresultpostprocessor.h"
#include "uvunwrap.h"
#include "triangletangentresolve.h"
#include "anglesmooth.h"
MeshResultPostProcessor::MeshResultPostProcessor(const Outcome &outcome)
{
@ -37,37 +36,6 @@ void MeshResultPostProcessor::poseProcess()
triangleTangentResolve(*m_outcome, triangleTangents);
m_outcome->setTriangleTangents(triangleTangents);
}
{
std::vector<QVector3D> inputVerticies;
std::vector<std::tuple<size_t, size_t, size_t>> inputTriangles;
std::vector<QVector3D> inputNormals;
float thresholdAngleDegrees = 60;
for (const auto &vertex: m_outcome->vertices)
inputVerticies.push_back(vertex);
for (size_t i = 0; i < m_outcome->triangles.size(); ++i) {
const auto &triangle = m_outcome->triangles[i];
const auto &triangleNormal = m_outcome->triangleNormals[i];
inputTriangles.push_back(std::make_tuple((size_t)triangle[0], (size_t)triangle[1], (size_t)triangle[2]));
inputNormals.push_back(triangleNormal);
}
std::vector<QVector3D> resultNormals;
angleSmooth(inputVerticies, inputTriangles, inputNormals, thresholdAngleDegrees, resultNormals);
std::vector<std::vector<QVector3D>> triangleVertexNormals;
triangleVertexNormals.resize(m_outcome->triangles.size(), {
QVector3D(), QVector3D(), QVector3D()
});
size_t index = 0;
for (size_t i = 0; i < m_outcome->triangles.size(); ++i) {
auto &normals = triangleVertexNormals[i];
for (size_t j = 0; j < 3; ++j) {
if (index < resultNormals.size())
normals[j] = resultNormals[index];
++index;
}
}
m_outcome->setTriangleVertexNormals(triangleVertexNormals);
}
}
}

View File

@ -1,530 +0,0 @@
#include <QDebug>
#include "meshutil.h"
#include "meshlite.h"
#define USE_CGAL 1
#if USE_CGAL == 1
// Polygon_mesh_processing/corefinement_mesh_union.cpp
// https://doc.cgal.org/latest/Polygon_mesh_processing/Polygon_mesh_processing_2corefinement_mesh_union_8cpp-example.html#a2
// https://doc.cgal.org/latest/Polygon_mesh_processing/Polygon_mesh_processing_2triangulate_faces_example_8cpp-example.html
// https://github.com/CGAL/cgal/issues/2875
// https://doc.cgal.org/latest/Polygon_mesh_processing/Polygon_mesh_processing_2hausdorff_distance_remeshing_example_8cpp-example.html#a3
// https://github.com/CGAL/cgal/blob/master/Subdivision_method_3/examples/Subdivision_method_3/CatmullClark_subdivision.cpp
// https://doc.cgal.org/latest/Polygon_mesh_processing/index.html#title25
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/corefinement.h>
#include <CGAL/Polygon_mesh_processing/triangulate_faces.h>
#include <CGAL/boost/graph/iterator.h>
#include <CGAL/assertions_behaviour.h>
#include <CGAL/Polygon_mesh_processing/remesh.h>
#include <CGAL/subdivision_method_3.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/convex_hull_3.h>
typedef CGAL::Exact_predicates_exact_constructions_kernel ExactKernel;
typedef CGAL::Simple_cartesian<double> SimpleKernel;
typedef CGAL::Surface_mesh<ExactKernel::Point_3> ExactMesh;
typedef CGAL::Surface_mesh<SimpleKernel::Point_3> SimpleMesh;
namespace PMP = CGAL::Polygon_mesh_processing;
void loadMeshVerticesPositions(void *meshliteContext, int meshId, std::vector<QVector3D> &positions)
{
int vertexCount = meshlite_get_vertex_count(meshliteContext, meshId);
float *vertexPositions = new float[vertexCount * 3];
int vertexArrayLen = meshlite_get_vertex_position_array(meshliteContext, meshId, vertexPositions, vertexCount * 3);
int offset = 0;
assert(vertexArrayLen == vertexCount * 3);
for (int i = 0; i < vertexCount; i++) {
float x = vertexPositions[offset + 0];
float y = vertexPositions[offset + 1];
float z = vertexPositions[offset + 2];
if (std::isnan(x) || std::isinf(x))
x = 0;
if (std::isnan(y) || std::isinf(y))
y = 0;
if (std::isnan(z) || std::isinf(z))
z = 0;
positions.push_back(QVector3D(x, y, z));
offset += 3;
}
delete[] vertexPositions;
}
template <class Kernel>
typename CGAL::Surface_mesh<typename Kernel::Point_3> *buildCgalMesh(const std::vector<QVector3D> &positions, const std::vector<std::vector<int>> &indices)
{
typename CGAL::Surface_mesh<typename Kernel::Point_3> *mesh = new typename CGAL::Surface_mesh<typename Kernel::Point_3>;
std::map<int, typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index> oldToNewMap;
for (int i = 0; i < (int)positions.size(); ++i) {
const auto &pos = positions[i];
oldToNewMap[i] = mesh->add_vertex(typename Kernel::Point_3(pos.x(), pos.y(), pos.z()));
}
for (const auto &face: indices) {
std::vector<typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index> faceVertexIndices;
for (const auto &index: face) {
faceVertexIndices.push_back(oldToNewMap[index]);
}
mesh->add_face(faceVertexIndices);
}
return mesh;
}
template <class Kernel>
typename CGAL::Surface_mesh<typename Kernel::Point_3> *makeCgalMeshFromMeshlite(void *meshlite, int meshId)
{
typename CGAL::Surface_mesh<typename Kernel::Point_3> *mesh = new typename CGAL::Surface_mesh<typename Kernel::Point_3>;
int vertexCount = meshlite_get_vertex_count(meshlite, meshId);
float *vertexPositions = new float[vertexCount * 3];
int vertexArrayLen = meshlite_get_vertex_position_array(meshlite, meshId, vertexPositions, vertexCount * 3);
int offset = 0;
assert(vertexArrayLen == vertexCount * 3);
std::vector<QVector3D> oldPositions;
std::map<int, typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index> oldToNewMap;
for (int i = 0; i < vertexCount; i++) {
float x = vertexPositions[offset + 0];
float y = vertexPositions[offset + 1];
float z = vertexPositions[offset + 2];
if (std::isnan(x) || std::isinf(x))
x = 0;
if (std::isnan(y) || std::isinf(y))
y = 0;
if (std::isnan(z) || std::isinf(z))
z = 0;
oldPositions.push_back(QVector3D(x, y, z));
offset += 3;
}
int faceCount = meshlite_get_face_count(meshlite, meshId);
int *faceVertexNumAndIndices = new int[faceCount * MAX_VERTICES_PER_FACE];
int filledLength = meshlite_get_face_index_array(meshlite, meshId, faceVertexNumAndIndices, faceCount * MAX_VERTICES_PER_FACE);
int i = 0;
int addedFaceCount = 0;
while (i < filledLength) {
int num = faceVertexNumAndIndices[i++];
assert(num > 0 && num <= MAX_VERTICES_PER_FACE);
if (num < 3) {
i += num;
continue;
}
std::vector<typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index> faceVertexIndices;
for (int j = 0; j < num; j++) {
int index = faceVertexNumAndIndices[i++];
assert(index >= 0 && index < vertexCount);
if (oldToNewMap.find(index) == oldToNewMap.end()) {
const auto &pos = oldPositions[index];
oldToNewMap[index] = mesh->add_vertex(typename Kernel::Point_3(pos.x(), pos.y(), pos.z()));
}
faceVertexIndices.push_back(oldToNewMap[index]);
}
mesh->add_face(faceVertexIndices);
addedFaceCount++;
}
delete[] faceVertexNumAndIndices;
delete[] vertexPositions;
if (addedFaceCount < 3) {
delete mesh;
mesh = nullptr;
}
return mesh;
}
void loadCombinableMeshVerticesPositions(void *mesh, std::vector<QVector3D> &positions)
{
ExactMesh *exactMesh = (ExactMesh *)mesh;
if (nullptr == exactMesh)
return;
for (auto vertexIt = exactMesh->vertices_begin(); vertexIt != exactMesh->vertices_end(); vertexIt++) {
auto point = exactMesh->point(*vertexIt);
float x = (float)CGAL::to_double(point.x());
float y = (float)CGAL::to_double(point.y());
float z = (float)CGAL::to_double(point.z());
if (std::isnan(x) || std::isinf(x))
x = 0;
if (std::isnan(y) || std::isinf(y))
y = 0;
if (std::isnan(z) || std::isinf(z))
z = 0;
positions.push_back(QVector3D(x, y, z));
}
}
// https://doc.cgal.org/latest/Surface_mesh/index.html#circulators_example
// https://www.cgal.org/FAQ.html
template <class Kernel>
int makeMeshliteMeshFromCgal(void *meshlite, typename CGAL::Surface_mesh<typename Kernel::Point_3> *mesh)
{
typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_range vertexRange = mesh->vertices();
int vertexCount = vertexRange.size();
float *vertexPositions = new float[vertexCount * 3];
int offset = 0;
typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_range::iterator vertexIt;
std::map<typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index, int> vertexIndexMap;
int i = 0;
for (vertexIt = vertexRange.begin(); vertexIt != vertexRange.end(); vertexIt++) {
typename Kernel::Point_3 point = mesh->point(*vertexIt);
float x = (float)CGAL::to_double(point.x());
float y = (float)CGAL::to_double(point.y());
float z = (float)CGAL::to_double(point.z());
if (std::isnan(x) || std::isinf(x))
x = 0;
if (std::isnan(y) || std::isinf(y))
y = 0;
if (std::isnan(z) || std::isinf(z))
z = 0;
vertexPositions[offset++] = x;
vertexPositions[offset++] = y;
vertexPositions[offset++] = z;
//printf("vertexPositions[%d]: %f,%f,%f\n", i, vertexPositions[offset - 3], vertexPositions[offset - 2], vertexPositions[offset - 1]);
vertexIndexMap[*vertexIt] = i;
i++;
}
typename CGAL::Surface_mesh<typename Kernel::Point_3>::Face_range faceRage = mesh->faces();
int faceCount = faceRage.size();
int *faceVertexNumAndIndices = new int[faceCount * MAX_VERTICES_PER_FACE];
typename CGAL::Surface_mesh<typename Kernel::Point_3>::Face_range::iterator faceIt;
offset = 0;
for (faceIt = faceRage.begin(); faceIt != faceRage.end(); faceIt++) {
CGAL::Vertex_around_face_iterator<typename CGAL::Surface_mesh<typename Kernel::Point_3>> vbegin, vend;
std::vector<int> indices;
//printf("face begin\n");
for (boost::tie(vbegin, vend) = CGAL::vertices_around_face(mesh->halfedge(*faceIt), *mesh);
vbegin != vend;
++vbegin){
//printf(" %d", vertexIndexMap[*vbegin]);
indices.push_back(vertexIndexMap[*vbegin]);
}
//printf("\nface end\n");
faceVertexNumAndIndices[offset++] = indices.size();
for (int j = 0; j < (int)indices.size(); j++) {
faceVertexNumAndIndices[offset++] = indices[j];
}
}
int meshId = meshlite_build(meshlite, vertexPositions, vertexCount, faceVertexNumAndIndices, offset);
delete[] vertexPositions;
delete[] faceVertexNumAndIndices;
return meshId;
}
template <class InputIterator, class Kernel>
bool precheckForConvexHull(InputIterator first, InputIterator beyond)
{
typedef typename CGAL::internal::Convex_hull_3::Default_traits_for_Chull_3<typename Kernel::Point_3, typename CGAL::Surface_mesh<typename Kernel::Point_3>>::type Traits;
typedef typename Traits::Point_3 Point_3;
typedef std::list<Point_3> Point_3_list;
typedef typename Point_3_list::iterator P3_iterator;
Traits traits;
Point_3_list points(first, beyond);
if (!(points.size() > 3))
return false;
// at least 4 points
typename Traits::Collinear_3 collinear = traits.collinear_3_object();
typename Traits::Equal_3 equal = traits.equal_3_object();
P3_iterator point1_it = points.begin();
P3_iterator point2_it = points.begin();
point2_it++;
// find three that are not collinear
while (point2_it != points.end() && equal(*point1_it,*point2_it))
++point2_it;
if (!(point2_it != points.end()))
return false;
P3_iterator point3_it = point2_it;
++point3_it;
if (!(point3_it != points.end()))
return false;
while (point3_it != points.end() && collinear(*point1_it,*point2_it,*point3_it))
++point3_it;
if (!(point3_it != points.end()))
return false;
return true;
}
template <class Kernel>
ExactMesh *makeCgalConvexHullMeshFromMeshlite(void *meshlite, int meshId)
{
typename CGAL::Surface_mesh<typename Kernel::Point_3> *mesh = new typename CGAL::Surface_mesh<typename Kernel::Point_3>;
int vertexCount = meshlite_get_vertex_count(meshlite, meshId);
float *vertexPositions = new float[vertexCount * 3];
int vertexArrayLen = meshlite_get_vertex_position_array(meshlite, meshId, vertexPositions, vertexCount * 3);
int offset = 0;
assert(vertexArrayLen == vertexCount * 3);
std::vector<typename Kernel::Point_3> points;
for (int i = 0; i < vertexCount; i++) {
float x = vertexPositions[offset + 0];
float y = vertexPositions[offset + 1];
float z = vertexPositions[offset + 2];
if (std::isnan(x) || std::isinf(x))
x = 0;
if (std::isnan(y) || std::isinf(y))
y = 0;
if (std::isnan(z) || std::isinf(z))
z = 0;
points.push_back(typename Kernel::Point_3(x, y, z));
offset += 3;
}
delete[] vertexPositions;
try {
if (precheckForConvexHull<typename std::vector<typename Kernel::Point_3>::iterator, Kernel>(points.begin(), points.end())) {
CGAL::convex_hull_3(points.begin(), points.end(), *mesh);
} else {
delete mesh;
return nullptr;
}
} catch (...) {
delete mesh;
return nullptr;
}
return mesh;
}
ExactMesh *unionCgalMeshs(ExactMesh *first, ExactMesh *second)
{
ExactMesh *mesh = new ExactMesh;
try {
if (!PMP::corefine_and_compute_union(*first, *second, *mesh)) {
delete mesh;
return nullptr;
}
} catch (...) {
delete mesh;
return nullptr;
}
return mesh;
}
ExactMesh *diffCgalMeshs(ExactMesh *first, ExactMesh *second)
{
ExactMesh *mesh = new ExactMesh;
try {
if (!PMP::corefine_and_compute_difference(*first, *second, *mesh)) {
delete mesh;
return nullptr;
}
} catch (...) {
delete mesh;
return nullptr;
}
return mesh;
}
#endif
// http://cgal-discuss.949826.n4.nabble.com/Polygon-mesh-processing-corefine-and-compute-difference-td4663104.html
int unionMeshs(void *meshliteContext, const std::vector<int> &meshIds, const std::set<int> &inverseIds, int *errorCount)
{
#if USE_CGAL == 1
std::vector<ExactMesh *> externalMeshs;
for (size_t i = 0; i < meshIds.size(); i++) {
int triangledMeshId = meshlite_triangulate(meshliteContext, meshIds[i]);
ExactMesh *mesh = makeCgalMeshFromMeshlite<ExactKernel>(meshliteContext, triangledMeshId);
if (CGAL::Polygon_mesh_processing::does_self_intersect(*mesh)) {
qDebug() << "CGAL::Polygon_mesh_processing::does_self_intersect:" << i;
if (errorCount)
(*errorCount)++;
delete mesh;
continue;
}
externalMeshs.push_back(mesh);
}
if (externalMeshs.size() > 0) {
ExactMesh *mergedExternalMesh = externalMeshs[0];
for (size_t i = 1; i < externalMeshs.size(); i++) {
if (!mergedExternalMesh) {
qDebug() << "Last union failed, break with remains unprocessed:" << (externalMeshs.size() - i);
break;
}
ExactMesh *unionedExternalMesh = NULL;
try {
if (inverseIds.find(meshIds[i]) == inverseIds.end())
unionedExternalMesh = unionCgalMeshs(mergedExternalMesh, externalMeshs[i]);
else
unionedExternalMesh = diffCgalMeshs(mergedExternalMesh, externalMeshs[i]);
} catch (...) {
qDebug() << "unionCgalMeshs throw exception";
if (errorCount)
(*errorCount)++;
}
delete externalMeshs[i];
if (unionedExternalMesh) {
delete mergedExternalMesh;
mergedExternalMesh = unionedExternalMesh;
} else {
if (errorCount)
(*errorCount)++;
qDebug() << "unionCgalMeshs failed";
}
}
if (mergedExternalMesh) {
int mergedMeshId = makeMeshliteMeshFromCgal<ExactKernel>(meshliteContext, mergedExternalMesh);
if (mergedMeshId)
mergedMeshId = fixMeshHoles(meshliteContext, mergedMeshId);
else {
if (errorCount)
(*errorCount)++;
qDebug() << "makeMeshliteMeshFromCgal failed";
}
delete mergedExternalMesh;
return mergedMeshId;
}
}
#else
return mergeMeshs(meshliteContext, meshIds);
#endif
return 0;
}
int mergeMeshs(void *meshliteContext, const std::vector<int> &meshIds)
{
int mergedMeshId = meshIds[0];
for (size_t i = 1; i < meshIds.size(); i++) {
mergedMeshId = meshlite_merge(meshliteContext, mergedMeshId, meshIds[i]);
}
return mergedMeshId;
}
int subdivMesh(void *meshliteContext, int meshId, int *errorCount)
{
int triangulatedMeshId = meshlite_triangulate(meshliteContext, meshId);
if (0 == meshlite_is_triangulated_manifold(meshliteContext, triangulatedMeshId)) {
#if USE_CGAL == 1
int subdiviedMeshId = 0;
SimpleMesh *simpleMesh = nullptr;
try {
simpleMesh = makeCgalMeshFromMeshlite<SimpleKernel>(meshliteContext, triangulatedMeshId);
CGAL::Subdivision_method_3::CatmullClark_subdivision(*simpleMesh, CGAL::Polygon_mesh_processing::parameters::number_of_iterations(1));
subdiviedMeshId = makeMeshliteMeshFromCgal<SimpleKernel>(meshliteContext, simpleMesh);
} catch (...) {
if (errorCount)
(*errorCount)++;
}
if (simpleMesh)
delete simpleMesh;
if (subdiviedMeshId)
subdiviedMeshId = fixMeshHoles(meshliteContext, subdiviedMeshId);
return subdiviedMeshId;
#endif
}
return meshlite_subdivide(meshliteContext, meshId);
}
int fixMeshHoles(void *meshliteContext, int meshId)
{
return meshlite_fix_hole(meshliteContext, meshId);
}
void initMeshUtils()
{
CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION);
}
void *convertToCombinableMesh(void *meshliteContext, int meshId)
{
ExactMesh *mesh = nullptr;
if (0 == meshId)
return nullptr;
mesh = makeCgalMeshFromMeshlite<ExactKernel>(meshliteContext, meshId);
if (nullptr == mesh) {
return mesh;
}
if (CGAL::Polygon_mesh_processing::does_self_intersect(*mesh)) {
qDebug() << "CGAL::Polygon_mesh_processing::does_self_intersect meshId:" << meshId;
delete mesh;
return nullptr;
}
return (void *)mesh;
}
void *unionCombinableMeshs(void *first, void *second)
{
return (void *)unionCgalMeshs((ExactMesh *)first, (ExactMesh *)second);
}
void *diffCombinableMeshs(void *first, void *second)
{
return (void *)diffCgalMeshs((ExactMesh *)first, (ExactMesh *)second);
}
int convertFromCombinableMesh(void *meshliteContext, void *mesh)
{
return makeMeshliteMeshFromCgal<ExactKernel>(meshliteContext, (ExactMesh *)mesh);
}
void deleteCombinableMesh(void *mesh)
{
delete (ExactMesh *)mesh;
}
void *cloneCombinableMesh(void *mesh)
{
if (nullptr == mesh)
return nullptr;
return (void *)new ExactMesh(*(ExactMesh *)mesh);
}
void *convertToCombinableConvexHullMesh(void *meshliteContext, int meshId)
{
ExactMesh *mesh = makeCgalConvexHullMeshFromMeshlite<ExactKernel>(meshliteContext, meshId);
return (void *)mesh;
}
void loadCombinableMeshVerticesPositionsAndFacesIndices(void *mesh, std::vector<QVector3D> &positions, std::vector<std::vector<int>> &indices)
{
ExactMesh *exactMesh = (ExactMesh *)mesh;
if (nullptr == exactMesh)
return;
for (auto vertexIt = exactMesh->vertices_begin(); vertexIt != exactMesh->vertices_end(); vertexIt++) {
auto point = exactMesh->point(*vertexIt);
float x = (float)CGAL::to_double(point.x());
float y = (float)CGAL::to_double(point.y());
float z = (float)CGAL::to_double(point.z());
if (std::isnan(x) || std::isinf(x))
x = 0;
if (std::isnan(y) || std::isinf(y))
y = 0;
if (std::isnan(z) || std::isinf(z))
z = 0;
positions.push_back(QVector3D(x, y, z));
}
typename CGAL::Surface_mesh<ExactKernel::Point_3>::Face_range faceRage = exactMesh->faces();
typename CGAL::Surface_mesh<ExactKernel::Point_3>::Face_range::iterator faceIt;
for (faceIt = faceRage.begin(); faceIt != faceRage.end(); faceIt++) {
CGAL::Vertex_around_face_iterator<typename CGAL::Surface_mesh<ExactKernel::Point_3>> vbegin, vend;
std::vector<int> faceIndices;
for (boost::tie(vbegin, vend) = CGAL::vertices_around_face(exactMesh->halfedge(*faceIt), *exactMesh);
vbegin != vend;
++vbegin){
faceIndices.push_back(*vbegin);
}
indices.push_back(faceIndices);
}
}
void *buildCombinableMeshFromVerticesPositionsAndFacesIndices(const std::vector<QVector3D> &positions, const std::vector<std::vector<int>> &indices)
{
ExactMesh *mesh = nullptr;
if (indices.empty())
return nullptr;
mesh = buildCgalMesh<ExactKernel>(positions, indices);
if (nullptr == mesh) {
return mesh;
}
if (CGAL::Polygon_mesh_processing::does_self_intersect(*mesh)) {
qDebug() << "CGAL::Polygon_mesh_processing::does_self_intersect";
delete mesh;
return nullptr;
}
return (void *)mesh;
}

View File

@ -1,27 +0,0 @@
#ifndef DUST3D_MESH_UTIL_H
#define DUST3D_MESH_UTIL_H
#include <vector>
#include <set>
#include <QVector3D>
#define MAX_VERTICES_PER_FACE 100
int mergeMeshs(void *meshliteContext, const std::vector<int> &meshIds);
int unionMeshs(void *meshliteContext, const std::vector<int> &meshIds, const std::set<int> &inverseIds, int *errorCount=0);
int subdivMesh(void *meshliteContext, int meshId, int *errorCount=0);
int fixMeshHoles(void *meshliteContext, int meshId);
void initMeshUtils();
void *convertToCombinableMesh(void *meshliteContext, int meshId);
void *unionCombinableMeshs(void *first, void *second);
void *diffCombinableMeshs(void *first, void *second);
int convertFromCombinableMesh(void *meshliteContext, void *mesh);
void deleteCombinableMesh(void *mesh);
void *cloneCombinableMesh(void *mesh);
void *convertToCombinableConvexHullMesh(void *meshliteContext, int meshId);
void loadMeshVerticesPositions(void *meshliteContext, int meshId, std::vector<QVector3D> &positions);
void loadCombinableMeshVerticesPositions(void *mesh, std::vector<QVector3D> &positions);
void loadCombinableMeshVerticesPositionsAndFacesIndices(void *mesh, std::vector<QVector3D> &positions, std::vector<std::vector<int>> &indices);
void *buildCombinableMeshFromVerticesPositionsAndFacesIndices(const std::vector<QVector3D> &positions, const std::vector<std::vector<int>> &indices);
#endif

View File

@ -1,164 +0,0 @@
#include <QtGlobal>
#include <QDebug>
#include <map>
#include <vector>
#include <QVector3D>
#include <unordered_map>
#include "meshweldseam.h"
#include "meshutil.h"
int meshWeldSeam(void *meshlite, int meshId, float allowedSmallestDistance, const PositionMap<bool> &excludePositions, int *affectedNum)
{
int vertexCount = meshlite_get_vertex_count(meshlite, meshId);
float *vertexPositions = new float[vertexCount * 3];
int vertexArrayLen = meshlite_get_vertex_position_array(meshlite, meshId, vertexPositions, vertexCount * 3);
int offset = 0;
Q_ASSERT(vertexArrayLen == vertexCount * 3);
std::vector<QVector3D> positions;
std::unordered_set<int> excludeVertices;
for (int i = 0; i < vertexCount; i++) {
float x = vertexPositions[offset + 0];
float y = vertexPositions[offset + 1];
float z = vertexPositions[offset + 2];
auto position = QVector3D(x, y, z);
if (excludePositions.findPosition(x, y, z))
excludeVertices.insert(i);
positions.push_back(position);
offset += 3;
}
int faceCount = meshlite_get_face_count(meshlite, meshId);
int *faceVertexNumAndIndices = new int[faceCount * MAX_VERTICES_PER_FACE];
int filledLength = meshlite_get_face_index_array(meshlite, meshId, faceVertexNumAndIndices, faceCount * MAX_VERTICES_PER_FACE);
int i = 0;
std::vector<std::vector<int>> newFaceIndices;
while (i < filledLength) {
int num = faceVertexNumAndIndices[i++];
Q_ASSERT(num > 0 && num <= MAX_VERTICES_PER_FACE);
if (num < 3) {
i += num;
continue;
}
std::vector<int> indices;
for (int j = 0; j < num; j++) {
int index = faceVertexNumAndIndices[i++];
Q_ASSERT(index >= 0 && index < vertexCount);
indices.push_back(index);
}
newFaceIndices.push_back(indices);
}
float squareOfAllowedSmallestDistance = allowedSmallestDistance * allowedSmallestDistance;
int weldedMesh = 0;
std::map<int, int> weldVertexToMap;
std::unordered_set<int> weldTargetVertices;
std::unordered_set<int> processedFaces;
std::map<std::pair<int, int>, std::pair<int, int>> triangleEdgeMap;
std::unordered_map<int, int> vertexAdjFaceCountMap;
for (int i = 0; i < (int)newFaceIndices.size(); i++) {
const auto &faceIndices = newFaceIndices[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)newFaceIndices.size(); i++) {
if (processedFaces.find(i) != processedFaces.end())
continue;
const auto &faceIndices = newFaceIndices[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<int, int> edge = std::make_pair(faceIndices[j], faceIndices[next]);
int thirdVertexIndex = faceIndices[nextNext];
if ((positions[edge.first] - positions[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 (((positions[edge.first] - positions[thirdVertexIndex]).lengthSquared() <
(positions[edge.second] - positions[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;
}
}
}
}
}
}
std::vector<int> newFaceVertexNumAndIndices;
int weldedCount = 0;
int faceCountAfterWeld = 0;
for (int i = 0; i < (int)newFaceIndices.size(); i++) {
const auto &faceIndices = newFaceIndices[i];
std::vector<int> 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++;
newFaceVertexNumAndIndices.push_back(mappedFaceIndices.size());
for (const auto &index: mappedFaceIndices) {
newFaceVertexNumAndIndices.push_back(index);
}
}
if (affectedNum)
*affectedNum = weldedCount;
qDebug() << "Welded" << weldedCount << "triangles(" << newFaceIndices.size() << " - " << weldedCount << " = " << faceCountAfterWeld << ")";
weldedMesh = meshlite_build(meshlite, vertexPositions, vertexCount, newFaceVertexNumAndIndices.data(), newFaceVertexNumAndIndices.size());
delete[] faceVertexNumAndIndices;
delete[] vertexPositions;
return weldedMesh;
}

View File

@ -1,10 +0,0 @@
#ifndef DUST3D_MESH_WELD_SEAM_H
#define DUST3D_MESH_WELD_SEAM_H
#include "meshlite.h"
#include "positionmap.h"
#include <unordered_set>
int meshWeldSeam(void *meshlite, int meshId, float allowedSmallestDistance,
const PositionMap<bool> &excludePositions, int *affectedNum=nullptr);
#endif

View File

@ -21,7 +21,6 @@ struct OutcomeNode
QUuid mirrorFromPartId;
QUuid mirroredByPartId;
BoneMark boneMark;
QVector3D baseNormal;
};
class Outcome
@ -30,6 +29,7 @@ public:
std::vector<OutcomeNode> nodes;
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> nodeVertices;
std::vector<QVector3D> vertices;
std::vector<std::vector<size_t>> triangleAndQuads;
std::vector<std::vector<size_t>> triangles;
std::vector<QVector3D> triangleNormals;

View File

@ -1,2 +0,0 @@
#include "positionmap.h"

View File

@ -1,78 +0,0 @@
#ifndef DUST3D_POSITION_MAP_H
#define DUST3D_POSITION_MAP_H
#include <map>
struct PositionMapKey
{
int x;
int y;
int z;
bool operator<(const PositionMapKey &other) const {
if (x < other.x) {
return true;
} else if (x > other.x) {
return false;
}
if (y < other.y) {
return true;
} else if (y > other.y) {
return false;
}
return z < other.z;
}
};
template <class T>
class PositionMap
{
public:
PositionMap(float gridSize=0.001) :
m_intGridSize(PositionMap<T>::makeInt(gridSize))
{
}
static int makeInt(float value)
{
return value * 1000;
}
PositionMapKey makeKey(float x, float y, float z) const
{
PositionMapKey key;
key.x = makeInt(x) / m_intGridSize;
key.y = makeInt(y) / m_intGridSize;
key.z = makeInt(z) / m_intGridSize;
return key;
}
void addPosition(float x, float y, float z, T data)
{
m_map[makeKey(x, y, z)] = data;
}
bool findPosition(float x, float y, float z, T *data = nullptr) const
{
const auto &result = m_map.find(makeKey(x, y, z));
if (result == m_map.end())
return false;
if (data)
*data = result->second;
return true;
}
void removePosition(float x, float y, float z)
{
m_map.erase(makeKey(x, y, z));
}
std::map<PositionMapKey, T> &map()
{
return m_map;
}
private:
std::map<PositionMapKey, T> m_map;
int m_intGridSize;
};
#endif

View File

@ -74,7 +74,7 @@ void RigGenerator::generate()
inputVerticesPositions.push_back(vertex);
}
std::map<std::pair<QUuid, QUuid>, std::tuple<BoneMark, SkeletonSide, QVector3D, std::set<MeshSplitterTriangle>, float, QVector3D>> markedNodes;
std::map<std::pair<QUuid, QUuid>, std::tuple<BoneMark, SkeletonSide, QVector3D, std::set<MeshSplitterTriangle>, float>> markedNodes;
for (const auto &bmeshNode: m_outcome->nodes) {
if (bmeshNode.boneMark == BoneMark::None)
continue;
@ -83,7 +83,7 @@ void RigGenerator::generate()
boneSide = bmeshNode.origin.x() > 0 ? SkeletonSide::Left : SkeletonSide::Right;
}
//qDebug() << "Add bone mark:" << BoneMarkToString(bmeshNode.boneMark) << "side:" << SkeletonSideToDispName(boneSide);
markedNodes[std::make_pair(bmeshNode.partId, bmeshNode.nodeId)] = std::make_tuple(bmeshNode.boneMark, boneSide, bmeshNode.origin, std::set<MeshSplitterTriangle>(), bmeshNode.radius, bmeshNode.baseNormal);
markedNodes[std::make_pair(bmeshNode.partId, bmeshNode.nodeId)] = std::make_tuple(bmeshNode.boneMark, boneSide, bmeshNode.origin, std::set<MeshSplitterTriangle>(), bmeshNode.radius);
}
for (size_t triangleIndex = 0; triangleIndex < m_outcome->triangles.size(); triangleIndex++) {
@ -99,19 +99,19 @@ void RigGenerator::generate()
inputTriangles.insert(newTriangle);
}
std::vector<std::tuple<BoneMark, SkeletonSide, QVector3D, std::set<MeshSplitterTriangle>, float, QVector3D>> markedNodesList;
std::vector<std::tuple<BoneMark, SkeletonSide, QVector3D, std::set<MeshSplitterTriangle>, float>> markedNodesList;
for (const auto &markedNode: markedNodes) {
markedNodesList.push_back(markedNode.second);
}
// Combine the overlapped marks
std::vector<std::tuple<BoneMark, SkeletonSide, QVector3D, std::set<MeshSplitterTriangle>, float, QVector3D>> combinedMarkedNodesList;
std::vector<std::tuple<BoneMark, SkeletonSide, QVector3D, std::set<MeshSplitterTriangle>, float>> combinedMarkedNodesList;
std::set<size_t> processedNodes;
for (size_t i = 0; i < markedNodesList.size(); ++i) {
if (processedNodes.find(i) != processedNodes.end())
continue;
const auto &first = markedNodesList[i];
std::tuple<BoneMark, SkeletonSide, QVector3D, std::set<MeshSplitterTriangle>, float, QVector3D> newNodes;
std::tuple<BoneMark, SkeletonSide, QVector3D, std::set<MeshSplitterTriangle>, float> newNodes;
size_t combinedNum = 1;
newNodes = first;
for (size_t j = i + 1; j < markedNodesList.size(); ++j) {
@ -126,7 +126,6 @@ void RigGenerator::generate()
for (const auto &triangle: std::get<3>(second))
std::get<3>(newNodes).insert(triangle);
std::get<4>(newNodes) += std::get<4>(second);
std::get<5>(newNodes) += std::get<5>(second);
++combinedNum;
}
}
@ -134,7 +133,6 @@ void RigGenerator::generate()
if (combinedNum > 1) {
std::get<2>(newNodes) /= combinedNum;
std::get<4>(newNodes) /= combinedNum;
std::get<5>(newNodes).normalize();
qDebug() << "Combined" << combinedNum << "on mark:" << BoneMarkToString(std::get<0>(newNodes)) << "side:" << SkeletonSideToDispName(std::get<1>(newNodes));
}
@ -152,7 +150,6 @@ void RigGenerator::generate()
m_autoRigger->addMarkGroup(std::get<0>(markedNode), std::get<1>(markedNode),
std::get<2>(markedNode),
std::get<4>(markedNode),
std::get<5>(markedNode),
std::get<3>(markedNode));
}
m_isSucceed = m_autoRigger->rig();

View File

@ -71,8 +71,7 @@ bool Rigger::calculateBodyTriangles(std::set<MeshSplitterTriangle> &bodyTriangle
return true;
}
bool Rigger::addMarkGroup(BoneMark boneMark, SkeletonSide boneSide, QVector3D bonePosition, float nodeRadius, QVector3D baseNormal,
const std::set<MeshSplitterTriangle> &markTriangles)
bool Rigger::addMarkGroup(BoneMark boneMark, SkeletonSide boneSide, QVector3D bonePosition, float nodeRadius, const std::set<MeshSplitterTriangle> &markTriangles)
{
m_marks.push_back(RiggerMark());
@ -81,7 +80,6 @@ bool Rigger::addMarkGroup(BoneMark boneMark, SkeletonSide boneSide, QVector3D bo
mark.boneSide = boneSide;
mark.bonePosition = bonePosition;
mark.nodeRadius = nodeRadius;
mark.baseNormal = baseNormal;
mark.markTriangles = markTriangles;
if (isCutOffSplitter(mark.boneMark)) {

View File

@ -18,7 +18,6 @@ public:
SkeletonSide boneSide;
QVector3D bonePosition;
float nodeRadius = 0;
QVector3D baseNormal;
std::set<MeshSplitterTriangle> markTriangles;
const std::set<MeshSplitterTriangle> &bigGroup() const
{
@ -124,8 +123,7 @@ class Rigger : public QObject
public:
Rigger(const std::vector<QVector3D> &verticesPositions,
const std::set<MeshSplitterTriangle> &inputTriangles);
bool addMarkGroup(BoneMark boneMark, SkeletonSide boneSide, QVector3D bonePosition, float nodeRadius, QVector3D baseNormal,
const std::set<MeshSplitterTriangle> &markTriangles);
bool addMarkGroup(BoneMark boneMark, SkeletonSide boneSide, QVector3D bonePosition, float nodeRadius, const std::set<MeshSplitterTriangle> &markTriangles);
const std::vector<std::pair<QtMsgType, QString>> &messages();
const std::vector<RiggerBone> &resultBones();
const std::map<int, RiggerVertexWeights> &resultWeights();

View File

@ -1,5 +1,6 @@
#include <nodemesh/positionkey.h>
#include <map>
#include "trianglesourcenoderesolve.h"
#include "positionmap.h"
struct HalfColorEdge
{
@ -19,18 +20,18 @@ struct CandidateEdge
void triangleSourceNodeResolve(const Outcome &outcome, std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes)
{
std::map<int, std::pair<QUuid, QUuid>> vertexSourceMap;
PositionMap<std::pair<QUuid, QUuid>> positionMap;
std::map<nodemesh::PositionKey, std::pair<QUuid, QUuid>> positionMap;
std::map<std::pair<int, int>, HalfColorEdge> halfColorEdgeMap;
std::set<int> brokenTriangleSet;
for (const auto &it: outcome.nodeVertices) {
positionMap.addPosition(it.first.x(), it.first.y(), it.first.z(), it.second);
positionMap.insert({nodemesh::PositionKey(it.first), it.second});
}
for (auto x = 0u; x < outcome.vertices.size(); x++) {
const QVector3D *resultVertex = &outcome.vertices[x];
std::pair<QUuid, QUuid> source;
if (positionMap.findPosition(resultVertex->x(), resultVertex->y(), resultVertex->z(), &source)) {
vertexSourceMap[x] = source;
}
auto findPosition = positionMap.find(nodemesh::PositionKey(*resultVertex));
if (findPosition != positionMap.end())
vertexSourceMap[x] = findPosition->second;
}
for (auto x = 0u; x < outcome.triangles.size(); x++) {
const auto triangle = outcome.triangles[x];

View File

@ -1,69 +0,0 @@
#ifndef MESHLITE_H
#define MESHLITE_H
#ifdef __cplusplus
extern "C" {
#endif
void *meshlite_create_context(void);
int meshlite_destroy_context(void *context);
int meshlite_merge(void *context, int first_mesh_id, int second_mesh_id);
int meshlite_import(void *context, const char *filename);
int meshlite_export(void *context, int mesh_id, const char *filename);
int meshlite_clone(void *context, int from_mesh_id);
int meshlite_triangulate(void *context, int mesh_id);
int meshlite_is_triangulated_manifold(void *context, int mesh_id);
int meshlite_subdivide(void *context, int mesh_id);
int meshlite_union(void *context, int first_mesh_id, int second_mesh_id);
int meshlite_diff(void *context, int first_mesh_id, int second_mesh_id);
int meshlite_intersect(void *context, int first_mesh_id, int second_mesh_id);
int meshlite_scale(void *context, int mesh_id, float value);
int meshlite_get_vertex_count(void *context, int mesh_id);
int meshlite_get_vertex_position_array(void *context, int mesh_id, float *buffer, int max_buffer_len);
int meshlite_get_vertex_source_array(void *context, int mesh_id, int *buffer, int max_buffer_len);
int meshlite_get_face_count(void *context, int mesh_id);
int meshlite_get_face_index_array(void *context, int mesh_id, int *buffer, int max_buffer_len);
int meshlite_get_triangle_index_array(void *context, int mesh_id, int *buffer, int max_buffer_len);
int meshlite_get_triangle_normal_array(void *context, int mesh_id, float *buffer, int max_buffer_len);
int meshlite_get_edge_count(void *context, int mesh_id);
int meshlite_get_edge_index_array(void *context, int mesh_id, int *buffer, int max_buffer_len);
int meshlite_get_edge_normal_array(void *context, int mesh_id, float *buffer, int max_buffer_len);
int meshlite_get_halfedge_count(void *context, int mesh_id);
int meshlite_get_halfedge_index_array(void *context, int mesh_id, int *buffer, int max_buffer_len);
int meshlite_get_halfedge_normal_array(void *context, int mesh_id, float *buffer, int max_buffer_len);
int meshlite_build(void *context, float *vertex_position_buffer, int vertex_count, int *face_index_buffer, int face_index_buffer_len);
int meshlite_bmesh_create(void *context);
int meshlite_bmesh_set_cut_subdiv_count(void *context, int bmesh_id, int subdiv_count);
int meshlite_bmesh_set_round_way(void *context, int bmesh_id, int round_way);
int meshlite_bmesh_set_deform_thickness(void *context, int bmesh_id, float thickness);
int meshlite_bmesh_set_deform_width(void *context, int bmesh_id, float width);
int meshlite_bmesh_enable_debug(void *context, int bmesh_id, int enable);
int meshlite_bmesh_add_node(void *context, int bmesh_id, float x, float y, float z, float radius);
int meshlite_bmesh_set_node_cut_subdiv_count(void *context, int bmesh_id, int node_id, int subdiv_count);
int meshlite_bmesh_set_node_round_way(void *context, int bmesh_id, int node_id, int round_way);
int meshlite_bmesh_add_edge(void *context, int bmesh_id, int first_node_id, int second_node_id);
int meshlite_bmesh_generate_mesh(void *context, int bmesh_id);
int meshlite_bmesh_get_node_base_norm(void *context, int bmesh_id, int node_id, float *norm_buffer);
int meshlite_bmesh_destroy(void *context, int bmesh_id);
int meshlite_bmesh_error_count(void *context, int bmesh_id);
int meshlite_bmesh_add_seam_requirement(void *context, int bmesh_id);
int meshlite_bmesh_get_seam_count(void *context, int bmesh_id);
int meshlite_bmesh_get_seam_index_array(void *context, int bmesh_id, int *buffer, int max_buffer_len);
int meshlite_combine_adj_faces(void *context, int mesh_id);
int meshlite_combine_coplanar_faces(void *context, int mesh_id);
int meshlite_trim(void *context, int mesh_id, int normalize);
int meshlite_mirror_in_x(void *context, int mesh_id, float center_x);
int meshlite_mirror_in_z(void *context, int mesh_id, float center_z);
int meshlite_fix_hole(void *context, int mesh_id);
int meshlite_skeletonmesh_create(void *context);
int meshlite_skeletonmesh_set_end_radius(void *context, float radius);
int meshlite_skeletonmesh_add_bone(void *context, int sklt_id, float from_x, float from_y, float from_z, float to_x, float to_y, float to_z);
int meshlite_skeletonmesh_generate_mesh(void *context, int sklt_id);
int meshlite_smooth_vertices(void *context, int mesh_id, float factor, int *buffer, int max_buffer_len);
int meshlite_smooth(void *context, int mesh_id, float factor);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,69 +0,0 @@
#ifndef MESHLITE_H
#define MESHLITE_H
#ifdef __cplusplus
extern "C" {
#endif
void *meshlite_create_context(void);
int meshlite_destroy_context(void *context);
int meshlite_merge(void *context, int first_mesh_id, int second_mesh_id);
int meshlite_import(void *context, const char *filename);
int meshlite_export(void *context, int mesh_id, const char *filename);
int meshlite_clone(void *context, int from_mesh_id);
int meshlite_triangulate(void *context, int mesh_id);
int meshlite_is_triangulated_manifold(void *context, int mesh_id);
int meshlite_subdivide(void *context, int mesh_id);
int meshlite_union(void *context, int first_mesh_id, int second_mesh_id);
int meshlite_diff(void *context, int first_mesh_id, int second_mesh_id);
int meshlite_intersect(void *context, int first_mesh_id, int second_mesh_id);
int meshlite_scale(void *context, int mesh_id, float value);
int meshlite_get_vertex_count(void *context, int mesh_id);
int meshlite_get_vertex_position_array(void *context, int mesh_id, float *buffer, int max_buffer_len);
int meshlite_get_vertex_source_array(void *context, int mesh_id, int *buffer, int max_buffer_len);
int meshlite_get_face_count(void *context, int mesh_id);
int meshlite_get_face_index_array(void *context, int mesh_id, int *buffer, int max_buffer_len);
int meshlite_get_triangle_index_array(void *context, int mesh_id, int *buffer, int max_buffer_len);
int meshlite_get_triangle_normal_array(void *context, int mesh_id, float *buffer, int max_buffer_len);
int meshlite_get_edge_count(void *context, int mesh_id);
int meshlite_get_edge_index_array(void *context, int mesh_id, int *buffer, int max_buffer_len);
int meshlite_get_edge_normal_array(void *context, int mesh_id, float *buffer, int max_buffer_len);
int meshlite_get_halfedge_count(void *context, int mesh_id);
int meshlite_get_halfedge_index_array(void *context, int mesh_id, int *buffer, int max_buffer_len);
int meshlite_get_halfedge_normal_array(void *context, int mesh_id, float *buffer, int max_buffer_len);
int meshlite_build(void *context, float *vertex_position_buffer, int vertex_count, int *face_index_buffer, int face_index_buffer_len);
int meshlite_bmesh_create(void *context);
int meshlite_bmesh_set_cut_subdiv_count(void *context, int bmesh_id, int subdiv_count);
int meshlite_bmesh_set_round_way(void *context, int bmesh_id, int round_way);
int meshlite_bmesh_set_deform_thickness(void *context, int bmesh_id, float thickness);
int meshlite_bmesh_set_deform_width(void *context, int bmesh_id, float width);
int meshlite_bmesh_enable_debug(void *context, int bmesh_id, int enable);
int meshlite_bmesh_add_node(void *context, int bmesh_id, float x, float y, float z, float radius);
int meshlite_bmesh_set_node_cut_subdiv_count(void *context, int bmesh_id, int node_id, int subdiv_count);
int meshlite_bmesh_set_node_round_way(void *context, int bmesh_id, int node_id, int round_way);
int meshlite_bmesh_add_edge(void *context, int bmesh_id, int first_node_id, int second_node_id);
int meshlite_bmesh_generate_mesh(void *context, int bmesh_id);
int meshlite_bmesh_get_node_base_norm(void *context, int bmesh_id, int node_id, float *norm_buffer);
int meshlite_bmesh_destroy(void *context, int bmesh_id);
int meshlite_bmesh_error_count(void *context, int bmesh_id);
int meshlite_bmesh_add_seam_requirement(void *context, int bmesh_id);
int meshlite_bmesh_get_seam_count(void *context, int bmesh_id);
int meshlite_bmesh_get_seam_index_array(void *context, int bmesh_id, int *buffer, int max_buffer_len);
int meshlite_combine_adj_faces(void *context, int mesh_id);
int meshlite_combine_coplanar_faces(void *context, int mesh_id);
int meshlite_trim(void *context, int mesh_id, int normalize);
int meshlite_mirror_in_x(void *context, int mesh_id, float center_x);
int meshlite_mirror_in_z(void *context, int mesh_id, float center_z);
int meshlite_fix_hole(void *context, int mesh_id);
int meshlite_skeletonmesh_create(void *context);
int meshlite_skeletonmesh_set_end_radius(void *context, float radius);
int meshlite_skeletonmesh_add_bone(void *context, int sklt_id, float from_x, float from_y, float from_z, float to_x, float to_y, float to_z);
int meshlite_skeletonmesh_generate_mesh(void *context, int sklt_id);
int meshlite_smooth_vertices(void *context, int mesh_id, float factor, int *buffer, int max_buffer_len);
int meshlite_smooth(void *context, int mesh_id, float factor);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,6 +0,0 @@
curl -L https://github.com/huxingyi/meshlite/releases/download/meshlite-unstable/meshlite_unstable_vc14_x86.zip --output meshlite_unstable_vc14_x86.zip
rm -rf meshlite_unstable_vc14_x86/* && unzip meshlite_unstable_vc14_x86.zip -d meshlite_unstable_vc14_x86
rm meshlite_unstable_vc14_x86.zip
curl -L https://github.com/huxingyi/meshlite/releases/download/meshlite-unstable/meshlite_unstable_vc14_x64.zip --output meshlite_unstable_vc14_x64.zip
rm -rf meshlite_unstable_vc14_x64/* && unzip meshlite_unstable_vc14_x64.zip -d meshlite_unstable_vc14_x64
rm meshlite_unstable_vc14_x64.zip

32
thirdparty/nodemesh/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
# 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

21
thirdparty/nodemesh/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
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.

2
thirdparty/nodemesh/README.md vendored Normal file
View File

@ -0,0 +1,2 @@
# nodemesh
Mesh generating experiment for Dust3D

74
thirdparty/nodemesh/nodemesh/box.cpp vendored Normal file
View File

@ -0,0 +1,74 @@
#include <nodemesh/box.h>
#include <nodemesh/util.h>
#include <nodemesh/cgalmesh.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/subdivision_method_3.h>
typedef CGAL::Simple_cartesian<double> SimpleKernel;
typedef CGAL::Surface_mesh<SimpleKernel::Point_3> PolygonMesh;
namespace nodemesh
{
void box(const QVector3D position, float radius, size_t subdivideTimes, std::vector<QVector3D> &vertices, std::vector<std::vector<size_t>> &faces)
{
std::vector<QVector3D> beginPlane = {
{-radius, -radius, radius},
{ radius, -radius, radius},
{ radius, radius, radius},
{-radius, radius, radius},
};
std::vector<QVector3D> 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<size_t> beginLoop = {
0, 1, 2, 3
};
std::vector<size_t> endLoop = {
4, 5, 6, 7
};
std::vector<size_t> 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<std::vector<size_t>> triangles;
triangulate(vertices, faces, triangles);
PolygonMesh *cgalMesh = buildCgalMesh<SimpleKernel>(vertices, triangles);
if (nullptr != cgalMesh) {
CGAL::Subdivision_method_3::CatmullClark_subdivision(*cgalMesh, subdivideTimes);
vertices.clear();
faces.clear();
fetchFromCgalMesh<SimpleKernel>(cgalMesh, vertices, faces);
delete cgalMesh;
}
}
}
}

13
thirdparty/nodemesh/nodemesh/box.h vendored Normal file
View File

@ -0,0 +1,13 @@
#ifndef NODEMESH_BOX_H
#define NODEMESH_BOX_H
#include <QVector3D>
#include <vector>
namespace nodemesh
{
void box(const QVector3D position, float radius, size_t subdivideTimes, std::vector<QVector3D> &vertices, std::vector<std::vector<size_t>> &faces);
}
#endif

633
thirdparty/nodemesh/nodemesh/builder.cpp vendored Normal file
View File

@ -0,0 +1,633 @@
#include <QString>
#include <QDebug>
#include <cmath>
#include <algorithm>
#include <set>
#include <nodemesh/builder.h>
#include <nodemesh/stitcher.h>
#include <nodemesh/box.h>
#include <nodemesh/combiner.h>
#include <nodemesh/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<QVector2D> &cutTemplate)
{
size_t nodeIndex = m_nodes.size();
Node node;
node.position = position;
node.radius = radius;
node.cutTemplate = cutTemplate;
m_nodes.push_back(node);
m_sortedNodeIndices.push_back(nodeIndex);
//qDebug() << "addNode" << position << radius;
return nodeIndex;
}
size_t Builder::addEdge(size_t firstNodeIndex, size_t secondNodeIndex)
{
size_t edgeIndex = m_edges.size();
m_edges.push_back(Edge {
.nodes = {firstNodeIndex, secondNodeIndex}
});
m_nodes[firstNodeIndex].edges.push_back(edgeIndex);
m_nodes[secondNodeIndex].edges.push_back(edgeIndex);
//qDebug() << "addEdge" << firstNodeIndex << secondNodeIndex;
return edgeIndex;
}
const std::vector<QVector3D> &Builder::generatedVertices()
{
return m_generatedVertices;
}
const std::vector<std::vector<size_t>> &Builder::generatedFaces()
{
return m_generatedFaces;
}
const std::vector<size_t> &Builder::generatedVerticesSourceNodeIndices()
{
return m_generatedVerticesSourceNodeIndices;
}
void Builder::sortNodeIndices()
{
std::sort(m_sortedNodeIndices.begin(), m_sortedNodeIndices.end(), [&](const size_t &firstIndex,
const size_t &secondIndex) {
const Node &firstNode = m_nodes[firstIndex];
const Node &secondNode = m_nodes[secondIndex];
if (firstNode.edges.size() > secondNode.edges.size())
return true;
if (firstNode.edges.size() < secondNode.edges.size())
return false;
if (firstNode.radius > secondNode.radius)
return true;
if (firstNode.radius < secondNode.radius)
return false;
if (firstNode.position.y() > secondNode.position.y())
return true;
if (firstNode.position.y() < secondNode.position.y())
return false;
if (firstNode.position.z() > secondNode.position.z())
return true;
if (firstNode.position.z() < secondNode.position.z())
return false;
if (firstNode.position.x() > secondNode.position.x())
return true;
if (firstNode.position.x() < secondNode.position.x())
return false;
return false;
});
}
void Builder::prepareNode(size_t nodeIndex)
{
auto &node = m_nodes[nodeIndex];
node.raysToNeibors.resize(node.edges.size());
std::vector<QVector3D> neighborPositions(node.edges.size());
std::vector<float> neighborRadius(node.edges.size());
for (size_t i = 0; i < node.edges.size(); ++i) {
size_t neighborIndex = m_edges[node.edges[i]].neiborOf(nodeIndex);
const auto &neighbor = m_nodes[neighborIndex];
node.raysToNeibors[i] = (neighbor.position - node.position).normalized();
neighborPositions[i] = neighbor.position;
neighborRadius[i] = neighbor.radius;
}
auto baseNormalResult = calculateBaseNormal(node.raysToNeibors,
neighborPositions,
neighborRadius);
node.initialBaseNormal = baseNormalResult.first;
node.hasInitialBaseNormal = baseNormalResult.second;
}
void Builder::resolveBaseNormalRecursively(size_t nodeIndex)
{
auto &node = m_nodes[nodeIndex];
if (node.baseNormalResolved)
return;
if (node.hasInitialBaseNormal) {
resolveBaseNormalForLeavesRecursively(nodeIndex, node.initialBaseNormal);
} else {
node.baseNormalSearched = true;
auto searchResult = searchBaseNormalFromNeighborsRecursively(nodeIndex);
if (searchResult.second) {
resolveBaseNormalForLeavesRecursively(nodeIndex, searchResult.first);
} else {
resolveBaseNormalForLeavesRecursively(nodeIndex, QVector3D {0, 0, 1});
}
}
}
void Builder::resolveBaseNormalForLeavesRecursively(size_t nodeIndex, const QVector3D &baseNormal)
{
auto &node = m_nodes[nodeIndex];
if (node.baseNormalResolved)
return;
node.baseNormalResolved = true;
node.baseNormal = baseNormal;
for (size_t i = 0; i < node.edges.size(); ++i) {
size_t neighborIndex = m_edges[node.edges[i]].neiborOf(nodeIndex);
const auto &neighbor = m_nodes[neighborIndex];
switch (neighbor.edges.size()) {
case 1:
resolveBaseNormalForLeavesRecursively(neighborIndex, baseNormal);
break;
case 2:
neighbor.hasInitialBaseNormal ?
resolveBaseNormalForLeavesRecursively(neighborIndex, neighbor.initialBaseNormal) :
resolveBaseNormalForLeavesRecursively(neighborIndex, baseNormal);
break;
default:
// void
break;
}
}
}
std::pair<QVector3D, bool> Builder::searchBaseNormalFromNeighborsRecursively(size_t nodeIndex)
{
auto &node = m_nodes[nodeIndex];
node.baseNormalSearched = true;
for (size_t i = 0; i < node.edges.size(); ++i) {
size_t neighborIndex = m_edges[node.edges[i]].neiborOf(nodeIndex);
const auto &neighbor = m_nodes[neighborIndex];
if (neighbor.baseNormalResolved)
return {neighbor.baseNormal, true};
}
for (size_t i = 0; i < node.edges.size(); ++i) {
size_t neighborIndex = m_edges[node.edges[i]].neiborOf(nodeIndex);
const auto &neighbor = m_nodes[neighborIndex];
if (neighbor.hasInitialBaseNormal)
return {neighbor.initialBaseNormal, true};
}
for (size_t i = 0; i < node.edges.size(); ++i) {
size_t neighborIndex = m_edges[node.edges[i]].neiborOf(nodeIndex);
const auto &neighbor = m_nodes[neighborIndex];
if (neighbor.baseNormalSearched)
continue;
auto searchResult = searchBaseNormalFromNeighborsRecursively(neighborIndex);
if (searchResult.second)
return searchResult;
}
return {{}, false};
}
bool Builder::build()
{
bool succeed = true;
sortNodeIndices();
if (m_sortedNodeIndices.size() < 2) {
if (m_sortedNodeIndices.size() == 1) {
const Node &node = m_nodes[0];
int subdivideTimes = (node.cutTemplate.size() / 4) - 1;
if (subdivideTimes < 0)
subdivideTimes = 0;
box(node.position, node.radius, subdivideTimes, m_generatedVertices, m_generatedFaces);
m_generatedVerticesSourceNodeIndices.resize(m_generatedVertices.size(), 0);
}
return true;
}
for (const auto &nodeIndex: m_sortedNodeIndices) {
prepareNode(nodeIndex);
}
for (const auto &nodeIndex: m_sortedNodeIndices) {
resolveBaseNormalRecursively(nodeIndex);
}
for (const auto &nodeIndex: m_sortedNodeIndices) {
if (!generateCutsForNode(nodeIndex))
succeed = false;
}
stitchEdgeCuts();
applyWeld();
applyDeform();
return succeed;
}
bool Builder::validateNormal(const QVector3D &normal)
{
if (normal.isNull()) {
return false;
}
if (isnan(normal.x()) || isnan(normal.y()) || isnan(normal.z())) {
return false;
}
return true;
}
std::pair<QVector3D, bool> Builder::calculateBaseNormal(const std::vector<QVector3D> &directs,
const std::vector<QVector3D> &positions,
const std::vector<float> &weights)
{
auto calculateThreePointsNormal = [&](size_t i0, size_t i1, size_t i2) -> std::pair<QVector3D, bool> {
auto normal = QVector3D::normal(positions[i0], positions[i1], positions[i2]);
if (validateNormal(normal)) {
return {normal, true};
}
// >=15 degrees && <= 165 degrees
if (abs(QVector3D::dotProduct(directs[i0], directs[i1])) < 0.966) {
return {QVector3D::crossProduct(directs[i0], directs[i1]).normalized(), true};
} else if (abs(QVector3D::dotProduct(directs[i1], directs[i2])) < 0.966) {
return {QVector3D::crossProduct(directs[i1], directs[i2]).normalized(), true};
} else if (abs(QVector3D::dotProduct(directs[i2], directs[i0])) < 0.966) {
return {QVector3D::crossProduct(directs[i2], directs[i0]).normalized(), true};
} else {
return {{}, false};
}
};
if (directs.size() <= 1) {
return {{}, false};
} else if (directs.size() <= 2) {
// >=15 degrees && <= 165 degrees
if (abs(QVector3D::dotProduct(directs[0], directs[1])) < 0.966) {
return {QVector3D::crossProduct(directs[0], directs[1]).normalized(), true};
}
return {{}, false};
} else if (directs.size() <= 3) {
return calculateThreePointsNormal(0, 1, 2);
} else {
std::vector<std::pair<size_t, float>> weightedIndices;
for (size_t i = 0; i < weights.size(); ++i) {
weightedIndices.push_back({i, weights[i]});
}
std::sort(weightedIndices.begin(), weightedIndices.end(), [](const std::pair<size_t, size_t> &first, const std::pair<size_t, size_t> &second) {
return first.second > second.second;
});
return calculateThreePointsNormal(weightedIndices[0].first,
weightedIndices[1].first,
weightedIndices[2].first);
}
}
void Builder::insertCutVertices(const std::vector<QVector3D> &cut, std::vector<size_t> &vertices, size_t nodeIndex, const QVector3D &cutDirect)
{
for (const auto &position: cut) {
size_t vertexIndex = m_generatedVertices.size();
m_generatedVertices.push_back(position);
m_generatedVerticesSourceNodeIndices.push_back(nodeIndex);
m_generatedVerticesCutDirects.push_back(cutDirect);
vertices.push_back(vertexIndex);
}
}
bool Builder::generateCutsForNode(size_t nodeIndex)
{
if (m_swallowedNodes.find(nodeIndex) != m_swallowedNodes.end()) {
qDebug() << "node" << nodeIndex << "ignore cuts generating because of been swallowed";
return true;
}
auto &node = m_nodes[nodeIndex];
size_t neighborsCount = node.edges.size();
//qDebug() << "Generate cuts for node" << nodeIndex << "with neighbor count" << neighborsCount;
if (1 == neighborsCount) {
QVector3D cutNormal = node.raysToNeibors[0];
std::vector<QVector3D> cut;
makeCut(node.position, node.radius, node.cutTemplate, node.baseNormal, cutNormal, cut);
std::vector<size_t> vertices;
insertCutVertices(cut, vertices, nodeIndex, cutNormal);
m_generatedFaces.push_back(vertices);
m_edges[node.edges[0]].cuts.push_back({vertices, -cutNormal});
} else if (2 == neighborsCount) {
QVector3D cutNormal = (node.raysToNeibors[0].normalized() - node.raysToNeibors[1].normalized()) / 2;
std::vector<QVector3D> cut;
makeCut(node.position, node.radius, node.cutTemplate, node.baseNormal, cutNormal, cut);
std::vector<size_t> vertices;
insertCutVertices(cut, vertices, nodeIndex, cutNormal);
std::vector<size_t> verticesReversed;
verticesReversed = vertices;
std::reverse(verticesReversed.begin(), verticesReversed.end());
m_edges[node.edges[0]].cuts.push_back({vertices, -cutNormal});
m_edges[node.edges[1]].cuts.push_back({verticesReversed, cutNormal});
} else if (neighborsCount >= 3) {
std::vector<float> offsets(node.edges.size(), 0.0);
bool offsetChanged = false;
size_t tries = 0;
do {
offsetChanged = false;
qDebug() << "Try wrap #" << tries;
if (tryWrapMultipleBranchesForNode(nodeIndex, offsets, offsetChanged)) {
qDebug() << "Wrap succeed";
return true;
}
++tries;
} while (offsetChanged);
return false;
}
return true;
}
bool Builder::tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector<float> &offsets, bool &offsetsChanged)
{
auto backupVertices = m_generatedVertices;
auto backupFaces = m_generatedFaces;
auto backupSourceIndices = m_generatedVerticesSourceNodeIndices;
auto backupVerticesCutDirects = m_generatedVerticesCutDirects;
auto &node = m_nodes[nodeIndex];
std::vector<std::pair<std::vector<size_t>, QVector3D>> cutsForWrapping;
std::vector<std::pair<std::vector<size_t>, QVector3D>> cutsForEdges;
bool directSwallowed = false;
for (size_t i = 0; i < node.edges.size(); ++i) {
QVector3D cutNormal = node.raysToNeibors[i];
size_t edgeIndex = node.edges[i];
size_t neighborIndex = m_edges[edgeIndex].neiborOf(nodeIndex);
const auto &neighbor = m_nodes[neighborIndex];
float distance = (neighbor.position - node.position).length();
if (qFuzzyIsNull(distance))
distance = 0.0001f;
float radiusFactor = 1.0 - (node.radius / distance);
float radius = node.radius * radiusFactor + neighbor.radius * (1.0 - radiusFactor);
std::vector<QVector3D> cut;
float offsetDistance = 0;
offsetDistance = offsets[i] * (distance - node.radius - neighbor.radius);
if (offsetDistance < 0)
offsetDistance = 0;
float finalDistance = node.radius + offsetDistance;
if (finalDistance >= distance) {
if (swallowEdgeForNode(nodeIndex, i)) {
qDebug() << "Neighbor too near to wrap, swallow it";
offsets[i] = 0;
offsetsChanged = true;
directSwallowed = true;
continue;
}
}
makeCut(node.position + cutNormal * finalDistance, radius, node.cutTemplate, node.baseNormal, cutNormal, cut);
std::vector<size_t> vertices;
insertCutVertices(cut, vertices, nodeIndex, cutNormal);
cutsForEdges.push_back({vertices, -cutNormal});
std::vector<size_t> verticesReversed;
verticesReversed = vertices;
std::reverse(verticesReversed.begin(), verticesReversed.end());
cutsForWrapping.push_back({verticesReversed, cutNormal});
}
if (directSwallowed) {
m_generatedVertices = backupVertices;
m_generatedFaces = backupFaces;
m_generatedVerticesSourceNodeIndices = backupSourceIndices;
m_generatedVerticesCutDirects = backupVerticesCutDirects;
return false;
}
Stitcher stitcher;
stitcher.setVertices(&m_generatedVertices);
std::vector<size_t> failedEdgeLoops;
bool stitchSucceed = stitcher.stitch(cutsForWrapping);
std::vector<std::vector<size_t>> testFaces = stitcher.newlyGeneratedFaces();
for (const auto &cuts: cutsForWrapping) {
testFaces.push_back(cuts.first);
}
if (stitchSucceed) {
stitchSucceed = nodemesh::isManifold(testFaces);
if (!stitchSucceed) {
qDebug() << "Mesh stitch but not manifold";
}
}
if (stitchSucceed) {
nodemesh::Combiner::Mesh mesh(m_generatedVertices, testFaces, false);
if (mesh.isNull()) {
qDebug() << "Mesh stitched but not not pass test";
//nodemesh::exportMeshAsObj(m_generatedVertices, testFaces, "/Users/jeremy/Desktop/test.obj");
stitchSucceed = false;
for (size_t i = 0; i < node.edges.size(); ++i) {
failedEdgeLoops.push_back(i);
}
}
} else {
//nodemesh::exportMeshAsObj(m_generatedVertices, testFaces, "/Users/jeremy/Desktop/test.obj");
stitcher.getFailedEdgeLoops(failedEdgeLoops);
}
if (!stitchSucceed) {
for (const auto &edgeLoop: failedEdgeLoops) {
if (offsets[edgeLoop] + WRAP_STEP_BACK_FACTOR < 1.0) {
offsets[edgeLoop] += WRAP_STEP_BACK_FACTOR;
offsetsChanged = true;
}
}
if (!offsetsChanged) {
for (const auto &edgeLoop: failedEdgeLoops) {
if (offsets[edgeLoop] + WRAP_STEP_BACK_FACTOR >= 1.0) {
if (swallowEdgeForNode(nodeIndex, edgeLoop)) {
qDebug() << "No offset to step back, swallow neighbor instead";
offsets[edgeLoop] = 0;
offsetsChanged = true;
break;
}
}
}
}
m_generatedVertices = backupVertices;
m_generatedFaces = backupFaces;
m_generatedVerticesSourceNodeIndices = backupSourceIndices;
m_generatedVerticesCutDirects = backupVerticesCutDirects;
return false;
}
// Weld nearby vertices
float weldThreshold = node.radius * WRAP_WELD_FACTOR;
float allowedMinDist2 = weldThreshold * weldThreshold;
for (size_t i = 0; i < node.edges.size(); ++i) {
for (size_t j = i + 1; j < node.edges.size(); ++j) {
const auto &first = cutsForEdges[i];
const auto &second = cutsForEdges[j];
for (const auto &u: first.first) {
for (const auto &v: second.first) {
if ((m_generatedVertices[u] - m_generatedVertices[v]).lengthSquared() < allowedMinDist2) {
qDebug() << "Weld" << v << "to" << u;
m_weldMap.insert({v, u});
}
}
}
}
}
for (const auto &face: stitcher.newlyGeneratedFaces()) {
m_generatedFaces.push_back(face);
}
for (size_t i = 0; i < node.edges.size(); ++i) {
m_edges[node.edges[i]].cuts.push_back(cutsForEdges[i]);
}
return true;
}
bool Builder::swallowEdgeForNode(size_t nodeIndex, size_t edgeOrder)
{
auto &node = m_nodes[nodeIndex];
size_t edgeIndex = node.edges[edgeOrder];
if (m_swallowedEdges.find(edgeIndex) != m_swallowedEdges.end()) {
qDebug() << "No more edge to swallow";
return false;
}
size_t neighborIndex = m_edges[edgeIndex].neiborOf(nodeIndex);
const auto &neighbor = m_nodes[neighborIndex];
if (neighbor.edges.size() != 2) {
qDebug() << "Neighbor is not a simple two edges node to swallow, edges:" << neighbor.edges.size() << "neighbor:" << neighborIndex << "node" << nodeIndex;
return false;
}
size_t anotherEdgeIndex = neighbor.anotherEdge(edgeIndex);
if (m_swallowedEdges.find(anotherEdgeIndex) != m_swallowedEdges.end()) {
qDebug() << "Desired edge already been swallowed";
return false;
}
node.edges[edgeOrder] = anotherEdgeIndex;
//qDebug() << "Nodes of edge" << anotherEdgeIndex << "before update:";
//for (const auto &it: m_edges[anotherEdgeIndex].nodes)
// qDebug() << it;
m_edges[anotherEdgeIndex].updateNodeIndex(neighborIndex, nodeIndex);
//qDebug() << "Nodes of edge" << anotherEdgeIndex << "after update:";
//for (const auto &it: m_edges[anotherEdgeIndex].nodes)
// qDebug() << it;
m_swallowedEdges.insert(edgeIndex);
m_swallowedNodes.insert(neighborIndex);
qDebug() << "Swallow edge" << edgeIndex << "for node" << nodeIndex << "neighbor" << neighborIndex << "got eliminated, choosen edge" << anotherEdgeIndex;
return true;
}
void Builder::makeCut(const QVector3D &position,
float radius,
const std::vector<QVector2D> &cutTemplate,
QVector3D &baseNormal,
const QVector3D &cutNormal,
std::vector<QVector3D> &resultCut)
{
QVector3D orientedBaseNormal = QVector3D::dotProduct(cutNormal, baseNormal) > 0 ?
baseNormal : -baseNormal;
// 0.966: < 15 degress
if (QVector3D::dotProduct(cutNormal, orientedBaseNormal) > 0.966) {
if (QVector3D::dotProduct(cutNormal, QVector3D(1, 0, 0)) > 0.966) {
orientedBaseNormal = QVector3D(0, 1, 0);
} else {
orientedBaseNormal = QVector3D(1, 0, 0);
}
}
baseNormal = orientedBaseNormal.normalized();
QVector3D u = QVector3D::crossProduct(cutNormal, orientedBaseNormal).normalized();
QVector3D v = QVector3D::crossProduct(u, cutNormal).normalized();
auto uFactor = u * radius;
auto vFactor = v * radius;
for (const auto &t: cutTemplate) {
resultCut.push_back(position + uFactor * t.x() + vFactor * t.y());
}
}
void Builder::stitchEdgeCuts()
{
for (size_t edgeIndex = 0; edgeIndex < m_edges.size(); ++edgeIndex) {
auto &edge = m_edges[edgeIndex];
if (2 == edge.cuts.size()) {
Stitcher stitcher;
stitcher.setVertices(&m_generatedVertices);
stitcher.stitch(edge.cuts);
for (const auto &face: stitcher.newlyGeneratedFaces()) {
m_generatedFaces.push_back(face);
}
}
}
}
void Builder::applyWeld()
{
if (m_weldMap.empty())
return;
std::vector<QVector3D> newVertices;
std::vector<size_t> newSourceIndices;
std::vector<std::vector<size_t>> newFaces;
std::vector<QVector3D> newVerticesCutDirects;
std::map<size_t, size_t> oldVertexToNewMap;
for (const auto &face: m_generatedFaces) {
std::vector<size_t> newIndices;
std::set<size_t> inserted;
for (const auto &oldVertex: face) {
size_t useOldVertex = oldVertex;
size_t newIndex = 0;
auto findWeld = m_weldMap.find(useOldVertex);
if (findWeld != m_weldMap.end()) {
useOldVertex = findWeld->second;
}
auto findResult = oldVertexToNewMap.find(useOldVertex);
if (findResult == oldVertexToNewMap.end()) {
newIndex = newVertices.size();
oldVertexToNewMap.insert({useOldVertex, newIndex});
newVertices.push_back(m_generatedVertices[useOldVertex]);
newSourceIndices.push_back(m_generatedVerticesSourceNodeIndices[useOldVertex]);
newVerticesCutDirects.push_back(m_generatedVerticesCutDirects[useOldVertex]);
} else {
newIndex = findResult->second;
}
if (inserted.find(newIndex) != inserted.end())
continue;
inserted.insert(newIndex);
newIndices.push_back(newIndex);
}
if (newIndices.size() < 3) {
qDebug() << "Face been welded";
continue;
}
newFaces.push_back(newIndices);
}
m_generatedVertices = newVertices;
m_generatedFaces = newFaces;
m_generatedVerticesSourceNodeIndices = newSourceIndices;
m_generatedVerticesCutDirects = newVerticesCutDirects;
}
void Builder::setDeformThickness(float thickness)
{
m_deformThickness = thickness;
}
void Builder::setDeformWidth(float width)
{
m_deformWidth = width;
}
QVector3D Builder::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());
auto scaledProjct = projectRayOnRevisedNormal * deformFactor;
return vertexPosition + (scaledProjct - projectRayOnRevisedNormal);
}
void Builder::applyDeform()
{
for (size_t i = 0; i < m_generatedVertices.size(); ++i) {
auto &position = m_generatedVertices[i];
const auto &node = m_nodes[m_generatedVerticesSourceNodeIndices[i]];
const auto &cutDirect = m_generatedVerticesCutDirects[i];
auto ray = position - node.position;
QVector3D sum;
size_t count = 0;
if (!qFuzzyCompare(m_deformThickness, (float)1.0)) {
auto deformedPosition = calculateDeformPosition(position, ray, node.baseNormal, m_deformThickness);
sum += deformedPosition;
++count;
}
if (!qFuzzyCompare(m_deformWidth, (float)1.0)) {
auto deformedPosition = calculateDeformPosition(position, ray, QVector3D::crossProduct(node.baseNormal, cutDirect), m_deformWidth);
sum += deformedPosition;
++count;
}
if (count > 0)
position = sum / count;
}
}
}

117
thirdparty/nodemesh/nodemesh/builder.h vendored Normal file
View File

@ -0,0 +1,117 @@
#ifndef NODEMESH_BUILDER_H
#define NODEMESH_BUILDER_H
#include <QVector3D>
#include <QVector2D>
#include <vector>
#include <map>
#include <set>
namespace nodemesh
{
class Builder
{
public:
size_t addNode(const QVector3D &position, float radius, const std::vector<QVector2D> &cutTemplate);
size_t addEdge(size_t firstNodeIndex, size_t secondNodeIndex);
void setDeformThickness(float thickness);
void setDeformWidth(float width);
const std::vector<QVector3D> &generatedVertices();
const std::vector<std::vector<size_t>> &generatedFaces();
const std::vector<size_t> &generatedVerticesSourceNodeIndices();
void exportAsObj(const QString &filename);
bool build();
private:
struct Edge;
struct Node
{
float radius;
QVector3D position;
std::vector<size_t> edges;
std::vector<QVector2D> cutTemplate;
std::vector<QVector3D> raysToNeibors;
QVector3D growthDirection;
QVector3D initialBaseNormal;
QVector3D baseNormal;
bool hasInitialBaseNormal = false;
bool baseNormalResolved = false;
bool baseNormalSearched = false;
size_t anotherEdge(size_t edgeIndex) const
{
if (edges.size() != 2)
return edgeIndex;
const auto &otherIndex = edges[0];
if (otherIndex == edgeIndex)
return edges[1];
return otherIndex;
}
};
struct Edge
{
std::vector<size_t> nodes;
std::vector<std::pair<std::vector<size_t>, QVector3D>> cuts;
size_t neiborOf(size_t nodeIndex)
{
const auto &otherIndex = nodes[0];
if (otherIndex == nodeIndex)
return nodes[1];
return otherIndex;
}
void updateNodeIndex(size_t fromNodeIndex, size_t toNodeIndex)
{
if (nodes[0] == fromNodeIndex) {
nodes[0] = toNodeIndex;
return;
}
nodes[1] = toNodeIndex;
}
};
std::vector<Node> m_nodes;
std::vector<Edge> m_edges;
std::vector<QVector3D> m_generatedVertices;
std::vector<QVector3D> m_generatedVerticesCutDirects;
std::vector<size_t> m_generatedVerticesSourceNodeIndices;
std::vector<std::vector<size_t>> m_generatedFaces;
std::vector<size_t> m_sortedNodeIndices;
std::map<size_t, size_t> m_weldMap;
std::set<size_t> m_swallowedEdges;
std::set<size_t> m_swallowedNodes;
float m_deformThickness = 1.0;
float m_deformWidth = 1.0;
void sortNodeIndices();
void prepareNode(size_t nodeIndex);
std::pair<QVector3D, bool> calculateBaseNormal(const std::vector<QVector3D> &directs,
const std::vector<QVector3D> &positions,
const std::vector<float> &weights);
bool validateNormal(const QVector3D &normal);
void resolveBaseNormalRecursively(size_t nodeIndex);
void resolveBaseNormalForLeavesRecursively(size_t nodeIndex, const QVector3D &baseNormal);
std::pair<QVector3D, bool> searchBaseNormalFromNeighborsRecursively(size_t nodeIndex);
bool generateCutsForNode(size_t nodeIndex);
bool tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector<float> &offsets, bool &offsetsChanged);
void makeCut(const QVector3D &position,
float radius,
const std::vector<QVector2D> &cutTemplate,
QVector3D &baseNormal,
const QVector3D &cutNormal,
std::vector<QVector3D> &resultCut);
void insertCutVertices(const std::vector<QVector3D> &cut, std::vector<size_t> &vertices, size_t nodeIndex, const QVector3D &cutDirect);
void stitchEdgeCuts();
void applyWeld();
void applyDeform();
QVector3D calculateDeformPosition(const QVector3D &vertexPosition, const QVector3D &ray, const QVector3D &deformNormal, float deformFactor);
bool swallowEdgeForNode(size_t nodeIndex, size_t edgeOrder);
};
}
#endif

View File

@ -0,0 +1,2 @@
#include <nodemesh/cgalmesh.h>

62
thirdparty/nodemesh/nodemesh/cgalmesh.h vendored Normal file
View File

@ -0,0 +1,62 @@
#ifndef NODEMESH_CGAL_MESH_H
#define NODEMESH_CGAL_MESH_H
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <QVector3D>
#include <vector>
typedef CGAL::Exact_predicates_exact_constructions_kernel ExactKernel;
typedef CGAL::Surface_mesh<ExactKernel::Point_3> ExactMesh;
template <class Kernel>
typename CGAL::Surface_mesh<typename Kernel::Point_3> *buildCgalMesh(const std::vector<QVector3D> &positions, const std::vector<std::vector<size_t>> &indices)
{
typename CGAL::Surface_mesh<typename Kernel::Point_3> *mesh = new typename CGAL::Surface_mesh<typename Kernel::Point_3>;
std::map<size_t, typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index> vertexIndices;
for (const auto &face: indices) {
std::vector<typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index> faceVertexIndices;
for (const auto &index: face) {
auto findIndex = vertexIndices.find(index);
if (findIndex != vertexIndices.end()) {
faceVertexIndices.push_back(findIndex->second);
} else {
const auto &pos = positions[index];
auto newIndex = mesh->add_vertex(typename Kernel::Point_3(pos.x(), pos.y(), pos.z()));
vertexIndices.insert({index, newIndex});
faceVertexIndices.push_back(newIndex);
}
}
mesh->add_face(faceVertexIndices);
}
return mesh;
}
template <class Kernel>
void fetchFromCgalMesh(typename CGAL::Surface_mesh<typename Kernel::Point_3> *mesh, std::vector<QVector3D> &vertices, std::vector<std::vector<size_t>> &faces)
{
std::map<typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index, size_t> vertexIndicesMap;
for (auto vertexIt = mesh->vertices_begin(); vertexIt != mesh->vertices_end(); vertexIt++) {
auto point = mesh->point(*vertexIt);
float x = (float)CGAL::to_double(point.x());
float y = (float)CGAL::to_double(point.y());
float z = (float)CGAL::to_double(point.z());
vertexIndicesMap[*vertexIt] = vertices.size();
vertices.push_back(QVector3D(x, y, z));
}
typename CGAL::Surface_mesh<typename Kernel::Point_3>::Face_range faceRage = mesh->faces();
typename CGAL::Surface_mesh<typename Kernel::Point_3>::Face_range::iterator faceIt;
for (faceIt = faceRage.begin(); faceIt != faceRage.end(); faceIt++) {
CGAL::Vertex_around_face_iterator<typename CGAL::Surface_mesh<typename Kernel::Point_3>> vbegin, vend;
std::vector<size_t> faceIndices;
for (boost::tie(vbegin, vend) = CGAL::vertices_around_face(mesh->halfedge(*faceIt), *mesh);
vbegin != vend;
++vbegin){
faceIndices.push_back(vertexIndicesMap[*vbegin]);
}
faces.push_back(faceIndices);
}
}
#endif

View File

@ -0,0 +1,157 @@
#include <nodemesh/combiner.h>
#include <nodemesh/util.h>
#include <nodemesh/positionkey.h>
#include <nodemesh/cgalmesh.h>
#include <CGAL/Polygon_mesh_processing/corefinement.h>
#include <CGAL/Polygon_mesh_processing/repair.h>
#include <QDebug>
#include <map>
typedef CGAL::Exact_predicates_exact_constructions_kernel ExactKernel;
typedef CGAL::Surface_mesh<ExactKernel::Point_3> ExactMesh;
namespace nodemesh
{
Combiner::Mesh::Mesh(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, bool removeSelfIntersects)
{
ExactMesh *cgalMesh = nullptr;
if (!faces.empty()) {
std::vector<std::vector<size_t>> triangles;
triangulate(vertices, faces, triangles);
cgalMesh = buildCgalMesh<ExactKernel>(vertices, triangles);
if (!CGAL::is_valid_polygon_mesh(*cgalMesh)) {
qDebug() << "Mesh is not valid polygon";
delete cgalMesh;
cgalMesh = nullptr;
} else if (CGAL::Polygon_mesh_processing::does_self_intersect(*cgalMesh)) {
//nodemesh::exportMeshAsObj(vertices, triangles, "/Users/jeremy/Desktop/test.obj");
m_isSelfIntersected = true;
if (removeSelfIntersects) {
if (!CGAL::Polygon_mesh_processing::remove_self_intersections(*cgalMesh)) {
qDebug() << "Mesh does self intersect and cann't remove intersections";
delete cgalMesh;
cgalMesh = nullptr;
} else {
qDebug() << "Mesh does self intersect but intersections got removed";
}
} else {
delete cgalMesh;
cgalMesh = nullptr;
qDebug() << "Mesh does self intersect";
}
}
}
m_privateData = cgalMesh;
}
Combiner::Mesh::Mesh(const Mesh &other)
{
if (other.m_privateData) {
m_privateData = new ExactMesh(*(ExactMesh *)other.m_privateData);
}
}
Combiner::Mesh::~Mesh()
{
ExactMesh *cgalMesh = (ExactMesh *)m_privateData;
delete cgalMesh;
}
void Combiner::Mesh::fetch(std::vector<QVector3D> &vertices, std::vector<std::vector<size_t>> &faces) const
{
ExactMesh *exactMesh = (ExactMesh *)m_privateData;
if (nullptr == exactMesh)
return;
fetchFromCgalMesh<ExactKernel>(exactMesh, vertices, faces);
}
bool Combiner::Mesh::isNull() const
{
return nullptr == m_privateData;
}
bool Combiner::Mesh::isSelfIntersected() const
{
return m_isSelfIntersected;
}
Combiner::Mesh *Combiner::combine(const Mesh &firstMesh, const Mesh &secondMesh, Method method,
std::vector<std::pair<Source, size_t>> *combinedVerticesComeFrom)
{
ExactMesh *resultCgalMesh = nullptr;
ExactMesh *firstCgalMesh = (ExactMesh *)firstMesh.m_privateData;
ExactMesh *secondCgalMesh = (ExactMesh *)secondMesh.m_privateData;
std::map<PositionKey, std::pair<Source, size_t>> verticesSourceMap;
auto addToSourceMap = [&](ExactMesh *mesh, Source source) {
size_t vertexIndex = 0;
for (auto vertexIt = mesh->vertices_begin(); vertexIt != mesh->vertices_end(); vertexIt++) {
auto point = mesh->point(*vertexIt);
float x = (float)CGAL::to_double(point.x());
float y = (float)CGAL::to_double(point.y());
float z = (float)CGAL::to_double(point.z());
auto insertResult = verticesSourceMap.insert({{x, y, z}, {source, vertexIndex}});
if (!insertResult.second) {
qDebug() << "Position key conflict:" << QVector3D {x, y, z} << "with:" << insertResult.first->first.position();
}
++vertexIndex;
}
};
if (nullptr != combinedVerticesComeFrom) {
addToSourceMap(firstCgalMesh, Source::First);
addToSourceMap(secondCgalMesh, Source::Second);
}
if (Method::Union == method) {
resultCgalMesh = new ExactMesh;
try {
if (!CGAL::Polygon_mesh_processing::corefine_and_compute_union(*firstCgalMesh, *secondCgalMesh, *resultCgalMesh)) {
delete resultCgalMesh;
resultCgalMesh = nullptr;
}
} catch (...) {
delete resultCgalMesh;
resultCgalMesh = nullptr;
}
} else if (Method::Diff == method) {
resultCgalMesh = new ExactMesh;
try {
if (!CGAL::Polygon_mesh_processing::corefine_and_compute_difference(*firstCgalMesh, *secondCgalMesh, *resultCgalMesh)) {
delete resultCgalMesh;
resultCgalMesh = nullptr;
}
} catch (...) {
delete resultCgalMesh;
resultCgalMesh = nullptr;
}
}
if (nullptr != combinedVerticesComeFrom) {
combinedVerticesComeFrom->clear();
if (nullptr != resultCgalMesh) {
for (auto vertexIt = resultCgalMesh->vertices_begin(); vertexIt != resultCgalMesh->vertices_end(); vertexIt++) {
auto point = resultCgalMesh->point(*vertexIt);
float x = (float)CGAL::to_double(point.x());
float y = (float)CGAL::to_double(point.y());
float z = (float)CGAL::to_double(point.z());
auto findSource = verticesSourceMap.find(PositionKey(x, y, z));
if (findSource == verticesSourceMap.end()) {
combinedVerticesComeFrom->push_back({Source::None, 0});
} else {
combinedVerticesComeFrom->push_back(findSource->second);
}
}
}
}
if (nullptr == resultCgalMesh)
return nullptr;
Mesh *mesh = new Mesh;
mesh->m_privateData = resultCgalMesh;
return mesh;
}
}

49
thirdparty/nodemesh/nodemesh/combiner.h vendored Normal file
View File

@ -0,0 +1,49 @@
#ifndef NODEMESH_COMBINER_H
#define NODEMESH_COMBINER_H
#include <QVector3D>
#include <vector>
namespace nodemesh
{
class Combiner
{
public:
enum class Method
{
Union,
Diff
};
enum class Source
{
None,
First,
Second
};
class Mesh
{
public:
Mesh() = default;
Mesh(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, bool removeSelfIntersects=true);
Mesh(const Mesh &other);
~Mesh();
void fetch(std::vector<QVector3D> &vertices, std::vector<std::vector<size_t>> &faces) const;
bool isNull() const;
bool isSelfIntersected() const;
friend Combiner;
private:
void *m_privateData = nullptr;
bool m_isSelfIntersected = false;
};
static Mesh *combine(const Mesh &firstMesh, const Mesh &secondMesh, Method method,
std::vector<std::pair<Source, size_t>> *combinedVerticesComeFrom=nullptr);
};
}
#endif

View File

@ -0,0 +1,146 @@
#include <nodemesh/modifier.h>
#include <QVector2D>
#include <QDebug>
namespace nodemesh
{
size_t Modifier::addNode(const QVector3D &position, float radius, const std::vector<QVector2D> &cutTemplate)
{
size_t nodeIndex = m_nodes.size();
Node node;
node.position = position;
node.radius = radius;
node.cutTemplate = cutTemplate;
node.originNodeIndex = nodeIndex;
m_nodes.push_back(node);
return nodeIndex;
}
size_t Modifier::addEdge(size_t firstNodeIndex, size_t secondNodeIndex)
{
size_t edgeIndex = m_edges.size();
Edge edge;
edge.firstNodeIndex = firstNodeIndex;
edge.secondNodeIndex = secondNodeIndex;
m_edges.push_back(edge);
return edgeIndex;
}
void Modifier::createIntermediateNode(const Node &firstNode, const Node &secondNode, float factor, Node *resultNode)
{
float firstFactor = 1.0 - factor;
resultNode->position = firstNode.position * firstFactor + secondNode.position * factor;
resultNode->radius = firstNode.radius * firstFactor + secondNode.radius * factor;
resultNode->cutTemplate = firstNode.cutTemplate;
for (size_t i = 0; i < secondNode.cutTemplate.size(); ++i) {
if (i >= resultNode->cutTemplate.size())
break;
resultNode->cutTemplate[i] = resultNode->cutTemplate[i] * firstFactor + secondNode.cutTemplate[i] * factor;
}
}
void Modifier::subdivide()
{
for (auto &node: m_nodes) {
auto oldCutTemplate = node.cutTemplate;
node.cutTemplate.clear();
for (size_t i = 0; i < oldCutTemplate.size(); ++i) {
size_t j = (i + 1) % oldCutTemplate.size();
QVector2D direct = (oldCutTemplate[i] + oldCutTemplate[j]).normalized();
float length = (oldCutTemplate[i].length() + oldCutTemplate[j].length()) * 0.4; // 0.4 = 0.5 * 0.8
node.cutTemplate.push_back(oldCutTemplate[i] * 0.8);
node.cutTemplate.push_back(direct * length);
}
}
}
float Modifier::averageCutTemplateEdgeLength(const std::vector<QVector2D> &cutTemplate)
{
if (cutTemplate.empty())
return 0;
float sum = 0;
for (size_t i = 0; i < cutTemplate.size(); ++i) {
size_t j = (i + 1) % cutTemplate.size();
sum += (cutTemplate[i] - cutTemplate[j]).length();
}
return sum / cutTemplate.size();
}
void Modifier::roundEnd()
{
std::map<size_t, std::vector<size_t>> neighbors;
for (const auto &edge: m_edges) {
neighbors[edge.firstNodeIndex].push_back(edge.secondNodeIndex);
neighbors[edge.secondNodeIndex].push_back(edge.firstNodeIndex);
}
for (const auto &it: neighbors) {
if (1 == it.second.size()) {
const Node &currentNode = m_nodes[it.first];
const Node &neighborNode = m_nodes[it.second[0]];
Node endNode;
endNode.radius = currentNode.radius * 0.5;
endNode.position = currentNode.position + (currentNode.position - neighborNode.position).normalized() * endNode.radius;
endNode.cutTemplate = currentNode.cutTemplate;
endNode.originNodeIndex = currentNode.originNodeIndex;
size_t endNodeIndex = m_nodes.size();
m_nodes.push_back(endNode);
addEdge(endNode.originNodeIndex, endNodeIndex);
}
}
}
void Modifier::finalize()
{
auto oldEdges = m_edges;
m_edges.clear();
for (const auto &edge: oldEdges) {
const Node &firstNode = m_nodes[edge.firstNodeIndex];
const Node &secondNode = m_nodes[edge.secondNodeIndex];
float targetEdgeLength = (averageCutTemplateEdgeLength(firstNode.cutTemplate) * firstNode.radius +
averageCutTemplateEdgeLength(secondNode.cutTemplate) * secondNode.radius) * 0.5;
float currentEdgeLength = (firstNode.position - secondNode.position).length();
if (targetEdgeLength >= currentEdgeLength) {
addEdge(edge.firstNodeIndex, edge.secondNodeIndex);
continue;
}
size_t newInsertNum = currentEdgeLength / targetEdgeLength;
if (newInsertNum < 1)
newInsertNum = 1;
float stepFactor = 1.0 / (newInsertNum + 1);
std::vector<size_t> nodeIndices;
nodeIndices.push_back(edge.firstNodeIndex);
float factor = stepFactor;
for (size_t i = 0; i < newInsertNum && factor < 1.0; factor += stepFactor, ++i) {
Node intermediateNode;
const Node &firstNode = m_nodes[edge.firstNodeIndex];
const Node &secondNode = m_nodes[edge.secondNodeIndex];
createIntermediateNode(firstNode, secondNode, factor, &intermediateNode);
intermediateNode.originNodeIndex = factor <= 0.5 ? firstNode.originNodeIndex : secondNode.originNodeIndex;
size_t intermedidateNodeIndex = m_nodes.size();
nodeIndices.push_back(intermedidateNodeIndex);
m_nodes.push_back(intermediateNode);
}
nodeIndices.push_back(edge.secondNodeIndex);
for (size_t i = 1; i < nodeIndices.size(); ++i) {
addEdge(nodeIndices[i - 1], nodeIndices[i]);
}
}
}
const std::vector<Modifier::Node> &Modifier::nodes()
{
return m_nodes;
}
const std::vector<Modifier::Edge> &Modifier::edges()
{
return m_edges;
}
}

45
thirdparty/nodemesh/nodemesh/modifier.h vendored Normal file
View File

@ -0,0 +1,45 @@
#ifndef NODEMESH_MODIFIER_H
#define NODEMESH_MODIFIER_H
#include <QVector3D>
#include <vector>
namespace nodemesh
{
class Modifier
{
public:
struct Node
{
QVector3D position;
float radius;
std::vector<QVector2D> cutTemplate;
size_t originNodeIndex;
};
struct Edge
{
size_t firstNodeIndex;
size_t secondNodeIndex;
};
size_t addNode(const QVector3D &position, float radius, const std::vector<QVector2D> &cutTemplate);
size_t addEdge(size_t firstNodeIndex, size_t secondNodeIndex);
void subdivide();
void roundEnd();
const std::vector<Node> &nodes();
const std::vector<Edge> &edges();
void finalize();
private:
std::vector<Node> m_nodes;
std::vector<Edge> m_edges;
void createIntermediateNode(const Node &firstNode, const Node &secondNode, float factor, Node *resultNode);
float averageCutTemplateEdgeLength(const std::vector<QVector2D> &cutTemplate);
};
}
#endif

View File

@ -0,0 +1,53 @@
#include <nodemesh/positionkey.h>
namespace nodemesh
{
long PositionKey::m_toIntFactor = 100000;
PositionKey::PositionKey(const QVector3D &v) :
PositionKey(v.x(), v.y(), v.z())
{
}
PositionKey::PositionKey(float x, float y, float z)
{
m_position.setX(x);
m_position.setY(y);
m_position.setZ(z);
m_intX = x * m_toIntFactor;
m_intY = y * m_toIntFactor;
m_intZ = z * m_toIntFactor;
}
const QVector3D &PositionKey::position() const
{
return m_position;
}
bool PositionKey::operator <(const PositionKey &right) const
{
if (m_intX < right.m_intX)
return true;
if (m_intX > right.m_intX)
return false;
if (m_intY < right.m_intY)
return true;
if (m_intY > right.m_intY)
return false;
if (m_intZ < right.m_intZ)
return true;
if (m_intZ > right.m_intZ)
return false;
return false;
}
bool PositionKey::operator ==(const PositionKey &right) const
{
return m_intX == right.m_intX &&
m_intY == right.m_intY &&
m_intZ == right.m_intZ;
}
}

View File

@ -0,0 +1,28 @@
#ifndef NODEMESH_POSITION_KEY_H
#define NODEMESH_POSITION_KEY_H
#include <QVector3D>
namespace nodemesh
{
class PositionKey
{
public:
PositionKey(const QVector3D &v);
PositionKey(float x, float y, float z);
const QVector3D &position() const;
bool operator <(const PositionKey &right) const;
bool operator ==(const PositionKey &right) const;
private:
long m_intX = 0;
long m_intY = 0;
long m_intZ = 0;
QVector3D m_position;
static long m_toIntFactor;
};
};
#endif

View File

@ -0,0 +1,449 @@
#include <nodemesh/recombiner.h>
#include <nodemesh/positionkey.h>
#include <nodemesh/util.h>
#include <nodemesh/wrapper.h>
#include <set>
#include <QDebug>
#include <cmath>
#include <queue>
#define MAX_EDGE_LOOP_LENGTH 1000
namespace nodemesh
{
void Recombiner::setVertices(const std::vector<QVector3D> *vertices,
const std::vector<std::pair<nodemesh::Combiner::Source, size_t>> *verticesSourceIndices)
{
m_vertices = vertices;
m_verticesSourceIndices = verticesSourceIndices;
}
void Recombiner::setFaces(const std::vector<std::vector<size_t>> *faces)
{
m_faces = faces;
}
bool Recombiner::convertHalfEdgesToEdgeLoops(const std::vector<std::pair<size_t, size_t>> &halfEdges,
std::vector<std::vector<size_t>> *edgeLoops)
{
std::map<size_t, size_t> vertexLinkMap;
for (const auto &halfEdge: halfEdges) {
auto inserResult = vertexLinkMap.insert(halfEdge);
if (!inserResult.second) {
qDebug() << "Create edge loop from half edge failed, found repeated vertex link" << halfEdge.first << "->" << halfEdge.second << "exist:" << inserResult.first->first << "->" << inserResult.first->second;
return false;
}
}
while (!vertexLinkMap.empty()) {
std::vector<size_t> edgeLoop;
size_t vertex = vertexLinkMap.begin()->first;
size_t head = vertex;
bool loopBack = false;
size_t limitLoop = MAX_EDGE_LOOP_LENGTH;
while ((limitLoop--) > 0) {
edgeLoop.push_back(vertex);
auto findNext = vertexLinkMap.find(vertex);
if (findNext == vertexLinkMap.end())
break;
vertex = findNext->second;
if (vertex == head) {
loopBack = true;
break;
}
}
if (!loopBack) {
qDebug() << "Create edge loop from half edge failed, edge doesn't loop back";
return false;
}
if (edgeLoop.size() < 3) {
qDebug() << "Create edge loop from half edge failed, edge loop size invalid:" << edgeLoop.size();
return false;
}
for (const auto &vertex: edgeLoop) {
vertexLinkMap.erase(vertex);
}
edgeLoops->push_back(edgeLoop);
}
return true;
}
size_t Recombiner::splitSeamVerticesToIslands(const std::map<size_t, std::vector<size_t>> &seamEdges,
std::map<size_t, size_t> *vertexToIslandMap)
{
std::set<size_t> visited;
size_t nextIslandId = 0;
for (const auto &it: seamEdges) {
std::queue<size_t> vertices;
vertices.push(it.first);
bool hasVertexJoin = false;
while (!vertices.empty()) {
auto v = vertices.front();
vertices.pop();
if (visited.find(v) != visited.end())
continue;
visited.insert(v);
vertexToIslandMap->insert({v, nextIslandId});
hasVertexJoin = true;
const auto findNeighbors = seamEdges.find(v);
if (findNeighbors != seamEdges.end()) {
for (const auto &neighbor: findNeighbors->second) {
vertices.push(neighbor);
}
}
}
if (hasVertexJoin)
++nextIslandId;
}
return nextIslandId;
}
bool Recombiner::buildHalfEdgeToFaceMap(std::map<std::pair<size_t, size_t>, size_t> &halfEdgeToFaceMap)
{
bool succeed = true;
for (size_t faceIndex = 0; faceIndex < m_faces->size(); ++faceIndex) {
const auto &face = (*m_faces)[faceIndex];
for (size_t i = 0; i < face.size(); ++i) {
size_t j = (i + 1) % face.size();
const auto insertResult = halfEdgeToFaceMap.insert({{face[i], face[j]}, faceIndex});
if (!insertResult.second) {
qDebug() << "Non manifold edge found:" << face[i] << face[j];
succeed = false;
}
}
}
return succeed;
}
bool Recombiner::recombine()
{
buildHalfEdgeToFaceMap(m_halfEdgeToFaceMap);
std::map<size_t, std::vector<size_t>> seamLink;
for (const auto &face: *m_faces) {
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) {
auto next = face[(i + 1) % face.size()];
auto nextSource = (*m_verticesSourceIndices)[next];
if (Combiner::Source::None == nextSource.first) {
seamLink[index].push_back(next);
}
}
}
}
std::map<size_t, size_t> seamVertexToIslandMap;
size_t islands = splitSeamVerticesToIslands(seamLink, &seamVertexToIslandMap);
qDebug() << "Seam islands:" << islands;
std::map<std::pair<size_t, size_t>, std::pair<size_t, bool>> edgesInSeamArea;
for (size_t faceIndex = 0; faceIndex < (*m_faces).size(); ++faceIndex) {
const auto &face = (*m_faces)[faceIndex];
bool containsSeamVertex = false;
bool inFirstGroup = false;
size_t island = 0;
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) {
const auto &findIsland = seamVertexToIslandMap.find(index);
if (findIsland != seamVertexToIslandMap.end()) {
containsSeamVertex = true;
island = findIsland->second;
}
} else if (Combiner::Source::First == source.first) {
inFirstGroup = true;
}
}
if (containsSeamVertex) {
m_facesInSeamArea.insert({faceIndex, island});
for (size_t i = 0; i < face.size(); ++i) {
const auto &index = face[i];
const auto &next = face[(i + 1) % face.size()];
std::pair<size_t, size_t> edge = {index, next};
edgesInSeamArea.insert({edge, {island, inFirstGroup}});
}
}
}
struct IslandData
{
std::vector<std::pair<size_t, size_t>> halfedges[2];
std::vector<std::vector<size_t>> edgeLoops[2];
};
std::map<size_t, IslandData> islandsMap;
for (const auto &edge: edgesInSeamArea) {
if (edgesInSeamArea.find({edge.first.second, edge.first.first}) == edgesInSeamArea.end()) {
islandsMap[edge.second.first].halfedges[edge.second.second ? 0 : 1].push_back(edge.first);
}
}
for (auto &it: islandsMap) {
for (size_t side = 0; side < 2; ++side) {
if (!convertHalfEdgesToEdgeLoops(it.second.halfedges[side], &it.second.edgeLoops[side])) {
qDebug() << "Convert half edges to edge loops for island" << it.first << "side" << side << "failed";
it.second.edgeLoops[side].clear();
}
}
}
for (auto &it: islandsMap) {
for (size_t side = 0; side < 2; ++side) {
for (size_t i = 0; i < it.second.edgeLoops[side].size(); ++i) {
auto &edgeLoop = it.second.edgeLoops[side][i];
size_t totalAdjustedTriangles = 0;
size_t adjustedTriangles = 0;
while ((adjustedTriangles=adjustTrianglesFromSeam(edgeLoop, it.first)) > 0) {
totalAdjustedTriangles += adjustedTriangles;
}
qDebug() << "Island" << it.first << "side" << side << "edge loop" << i << "adjusted" << totalAdjustedTriangles << "triangles";
}
}
}
for (auto &it: islandsMap) {
if (1 == it.second.edgeLoops[0].size() &&
it.second.edgeLoops[0].size() == it.second.edgeLoops[1].size()) {
if (bridge(it.second.edgeLoops[0][0], it.second.edgeLoops[1][0])) {
m_goodSeams.insert(it.first);
}
}
}
//for (auto &it: islandsMap) {
// m_goodSeams.insert(it.first);
//}
copyNonSeamFacesAsRegenerated();
removeReluctantVertices();
qDebug() << "Optimized" << m_goodSeams.size() << "seams";
return true;
}
size_t Recombiner::adjustTrianglesFromSeam(std::vector<size_t> &edgeLoop, size_t seamIndex)
{
if (edgeLoop.size() <= 3)
return 0;
std::vector<size_t> halfEdgeToFaces;
for (size_t i = 0; i < edgeLoop.size(); ++i) {
size_t j = (i + 1) % edgeLoop.size();
auto findFace = m_halfEdgeToFaceMap.find({edgeLoop[j], edgeLoop[i]});
if (findFace == m_halfEdgeToFaceMap.end()) {
qDebug() << "Find face for half edge failed:" << edgeLoop[j] << edgeLoop[i];
return 0;
}
halfEdgeToFaces.push_back(findFace->second);
}
std::vector<size_t> removedFaceIndices;
std::set<size_t> ignored;
for (size_t i = 0; i < edgeLoop.size(); ++i) {
size_t j = (i + 1) % edgeLoop.size();
if (halfEdgeToFaces[i] == halfEdgeToFaces[j]) {
removedFaceIndices.push_back(halfEdgeToFaces[i]);
ignored.insert(edgeLoop[j]);
++i;
continue;
}
}
if (!ignored.empty()) {
std::vector<size_t> newEdgeLoop;
for (const auto &v: edgeLoop) {
if (ignored.find(v) != ignored.end())
continue;
newEdgeLoop.push_back(v);
}
if (newEdgeLoop.size() < 3)
return 0;
edgeLoop = newEdgeLoop;
for (const auto &faceIndex: removedFaceIndices)
m_facesInSeamArea.insert({faceIndex, seamIndex});
}
return ignored.size();
}
size_t Recombiner::otherVertexOfTriangle(const std::vector<size_t> &face, const std::vector<size_t> &indices)
{
for (const auto &v: face) {
for (const auto &u: indices) {
if (u != v)
return u;
}
}
return face[0];
}
void Recombiner::copyNonSeamFacesAsRegenerated()
{
for (size_t faceIndex = 0; faceIndex < m_faces->size(); ++faceIndex) {
const auto &findFaceInSeam = m_facesInSeamArea.find(faceIndex);
if (findFaceInSeam != m_facesInSeamArea.end() &&
m_goodSeams.find(findFaceInSeam->second) != m_goodSeams.end())
continue;
m_regeneratedFaces.push_back((*m_faces)[faceIndex]);
}
}
const std::vector<QVector3D> &Recombiner::regeneratedVertices()
{
return m_regeneratedVertices;
}
const std::vector<std::pair<nodemesh::Combiner::Source, size_t>> &Recombiner::regeneratedVerticesSourceIndices()
{
return m_regeneratedVerticesSourceIndices;
}
const std::vector<std::vector<size_t>> &Recombiner::regeneratedFaces()
{
return m_regeneratedFaces;
}
size_t Recombiner::nearestIndex(const QVector3D &position, const std::vector<size_t> &edgeLoop)
{
float minDist2 = std::numeric_limits<float>::max();
size_t choosenIndex = 0;
for (size_t i = 0; i < edgeLoop.size(); ++i) {
float dist2 = ((*m_vertices)[edgeLoop[i]] - position).lengthSquared();
if (dist2 < minDist2) {
minDist2 = dist2;
choosenIndex = i;
}
}
return choosenIndex;
}
bool Recombiner::bridge(const std::vector<size_t> &first, const std::vector<size_t> &second)
{
const std::vector<size_t> *large = &first;
const std::vector<size_t> *small = &second;
if (large->size() < small->size())
std::swap(large, small);
std::vector<std::pair<size_t, size_t>> matchedPairs;
std::map<size_t, size_t> nearestIndicesFromLargeToSmall;
for (size_t i = 0; i < small->size(); ++i) {
const auto &positionOnSmall = (*m_vertices)[(*small)[i]];
size_t nearestIndexOnLarge = nearestIndex(positionOnSmall, *large);
auto matchResult = nearestIndicesFromLargeToSmall.find(nearestIndexOnLarge);
size_t nearestIndexOnSmall;
if (matchResult == nearestIndicesFromLargeToSmall.end()) {
const auto &positionOnLarge = (*m_vertices)[(*large)[nearestIndexOnLarge]];
nearestIndexOnSmall = nearestIndex(positionOnLarge, *small);
nearestIndicesFromLargeToSmall.insert({nearestIndexOnLarge, nearestIndexOnSmall});
} else {
nearestIndexOnSmall = matchResult->second;
}
if (nearestIndexOnSmall == i) {
matchedPairs.push_back({nearestIndexOnSmall, nearestIndexOnLarge});
}
}
qDebug() << "small:" << small->size() << "large:" << large->size() << "matches:" << matchedPairs.size();
if (matchedPairs.empty())
return false;
for (size_t i = 0; i < matchedPairs.size(); ++i) {
size_t j = (i + 1) % matchedPairs.size();
std::vector<size_t> smallSide;
std::vector<size_t> largeSide;
for (size_t indexOnSmall = matchedPairs[i].first;
;
indexOnSmall = (indexOnSmall + 1) % small->size()) {
smallSide.push_back((*small)[indexOnSmall]);
if (indexOnSmall == matchedPairs[j].first)
break;
}
for (size_t indexOnLarge = matchedPairs[j].second;
;
indexOnLarge = (indexOnLarge + 1) % large->size()) {
largeSide.push_back((*large)[indexOnLarge]);
if (indexOnLarge == matchedPairs[i].second)
break;
}
std::reverse(largeSide.begin(), largeSide.end());
fillPairs(smallSide, largeSide);
}
return true;
}
void Recombiner::fillPairs(const std::vector<size_t> &small, const std::vector<size_t> &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]],
(*m_vertices)[small[smallIndex + 1]] - (*m_vertices)[small[smallIndex]]);
float angleOnLargeEdgeLoop = angleBetween((*m_vertices)[small[smallIndex]] - (*m_vertices)[large[largeIndex]],
(*m_vertices)[large[largeIndex + 1]] - (*m_vertices)[large[largeIndex]]);
if (angleOnSmallEdgeLoop < angleOnLargeEdgeLoop) {
m_regeneratedFaces.push_back({
small[smallIndex],
small[smallIndex + 1],
large[largeIndex]
});
++smallIndex;
continue;
}
m_regeneratedFaces.push_back({
large[largeIndex + 1],
large[largeIndex],
small[smallIndex]
});
++largeIndex;
continue;
}
if (smallIndex + 1 >= small.size()) {
m_regeneratedFaces.push_back({
large[largeIndex + 1],
large[largeIndex],
small[smallIndex]
});
++largeIndex;
continue;
}
if (largeIndex + 1 >= large.size()) {
m_regeneratedFaces.push_back({
small[smallIndex],
small[smallIndex + 1],
large[largeIndex]
});
++smallIndex;
continue;
}
break;
}
}
void Recombiner::removeReluctantVertices()
{
std::vector<std::vector<size_t>> rearrangedFaces;
std::map<size_t, size_t> oldToNewIndexMap;
for (const auto &face: m_regeneratedFaces) {
std::vector<size_t> newFace;
for (const auto &index: face) {
const auto &findIndex = oldToNewIndexMap.find(index);
if (findIndex != oldToNewIndexMap.end()) {
newFace.push_back(findIndex->second);
} else {
size_t newIndex = m_regeneratedVertices.size();
m_regeneratedVertices.push_back((*m_vertices)[index]);
m_regeneratedVerticesSourceIndices.push_back((*m_verticesSourceIndices)[index]);
oldToNewIndexMap.insert({index, newIndex});
newFace.push_back(newIndex);
}
}
rearrangedFaces.push_back(newFace);
}
m_regeneratedFaces = rearrangedFaces;
}
}

View File

@ -0,0 +1,52 @@
#ifndef NODEMESH_RECOMBINER_H
#define NODEMESH_RECOMBINER_H
#include <QVector3D>
#include <vector>
#include <set>
#include <map>
#include <nodemesh/combiner.h>
namespace nodemesh
{
class Recombiner
{
public:
void setVertices(const std::vector<QVector3D> *vertices,
const std::vector<std::pair<nodemesh::Combiner::Source, size_t>> *verticesSourceIndices);
void setFaces(const std::vector<std::vector<size_t>> *faces);
const std::vector<QVector3D> &regeneratedVertices();
const std::vector<std::pair<nodemesh::Combiner::Source, size_t>> &regeneratedVerticesSourceIndices();
const std::vector<std::vector<size_t>> &regeneratedFaces();
bool recombine();
private:
const std::vector<QVector3D> *m_vertices = nullptr;
const std::vector<std::pair<nodemesh::Combiner::Source, size_t>> *m_verticesSourceIndices = nullptr;
const std::vector<std::vector<size_t>> *m_faces = nullptr;
std::vector<QVector3D> m_regeneratedVertices;
std::vector<std::pair<nodemesh::Combiner::Source, size_t>> m_regeneratedVerticesSourceIndices;
std::vector<std::vector<size_t>> m_regeneratedFaces;
std::map<std::pair<size_t, size_t>, size_t> m_halfEdgeToFaceMap;
std::map<size_t, size_t> m_facesInSeamArea;
std::set<size_t> m_goodSeams;
bool addFaceToHalfEdgeToFaceMap(size_t faceIndex,
std::map<std::pair<size_t, size_t>, size_t> &halfEdgeToFaceMap);
bool buildHalfEdgeToFaceMap(std::map<std::pair<size_t, size_t>, size_t> &halfEdgeToFaceMap);
bool convertHalfEdgesToEdgeLoops(const std::vector<std::pair<size_t, size_t>> &halfEdges,
std::vector<std::vector<size_t>> *edgeLoops);
size_t splitSeamVerticesToIslands(const std::map<size_t, std::vector<size_t>> &seamEdges,
std::map<size_t, size_t> *vertexToIslandMap);
void copyNonSeamFacesAsRegenerated();
size_t adjustTrianglesFromSeam(std::vector<size_t> &edgeLoop, size_t seamIndex);
size_t otherVertexOfTriangle(const std::vector<size_t> &face, const std::vector<size_t> &indices);
bool bridge(const std::vector<size_t> &first, const std::vector<size_t> &second);
size_t nearestIndex(const QVector3D &position, const std::vector<size_t> &edgeLoop);
void removeReluctantVertices();
void fillPairs(const std::vector<size_t> &small, const std::vector<size_t> &large);
};
}
#endif

View File

@ -0,0 +1,86 @@
#include <nodemesh/stitcher.h>
#include <nodemesh/util.h>
#include <memory>
#include <QDebug>
namespace nodemesh
{
Stitcher::~Stitcher()
{
delete m_wrapper;
}
void Stitcher::setVertices(const std::vector<QVector3D> *vertices)
{
m_positions = vertices;
}
const std::vector<std::vector<size_t>> &Stitcher::newlyGeneratedFaces()
{
if (m_wrapper)
return m_wrapper->newlyGeneratedFaces();
return m_newlyGeneratedFaces;
}
void Stitcher::getFailedEdgeLoops(std::vector<size_t> &failedEdgeLoops)
{
if (m_wrapper)
m_wrapper->getFailedEdgeLoops(failedEdgeLoops);
}
bool Stitcher::stitchByQuads(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops)
{
if (2 != edgeLoops.size())
return false;
const auto &firstEdgeLoop = edgeLoops[0].first;
const auto &secondEdgeLoop = edgeLoops[1].first;
if (firstEdgeLoop.size() < 3 || firstEdgeLoop.size() != secondEdgeLoop.size())
return false;
float minDist2 = std::numeric_limits<float>::max();
size_t choosenStartIndex = 0;
for (size_t k = 0; k < secondEdgeLoop.size(); ++k) {
float sumOfDist2 = 0;
std::vector<QVector3D> edges;
for (size_t i = 0, j = k; i < firstEdgeLoop.size(); ++i, --j) {
const auto &positionOnFirstEdgeLoop = (*m_positions)[firstEdgeLoop[i % firstEdgeLoop.size()]];
const auto &positionOnSecondEdgeLoop = (*m_positions)[secondEdgeLoop[(j + secondEdgeLoop.size()) % secondEdgeLoop.size()]];
auto edge = positionOnSecondEdgeLoop - positionOnFirstEdgeLoop;
sumOfDist2 += edge.lengthSquared();
}
if (sumOfDist2 < minDist2) {
minDist2 = sumOfDist2;
choosenStartIndex = k;
}
}
for (size_t i = 0, j = choosenStartIndex; i < firstEdgeLoop.size(); ++i, --j) {
m_newlyGeneratedFaces.push_back({
secondEdgeLoop[(j + secondEdgeLoop.size()) % secondEdgeLoop.size()],
secondEdgeLoop[(j + secondEdgeLoop.size() - 1) % secondEdgeLoop.size()],
firstEdgeLoop[(i + 1) % firstEdgeLoop.size()],
firstEdgeLoop[i % firstEdgeLoop.size()],
});
}
return true;
}
bool Stitcher::stitch(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops)
{
if (edgeLoops.size() == 2 &&
edgeLoops[0].first.size() == edgeLoops[1].first.size())
return stitchByQuads(edgeLoops);
m_wrapper = new Wrapper;
m_wrapper->setVertices(m_positions);
m_wrapper->wrap(edgeLoops);
if (!m_wrapper->finished()) {
qDebug() << "Wrapping failed";
return false;
}
return true;
}
}

31
thirdparty/nodemesh/nodemesh/stitcher.h vendored Normal file
View File

@ -0,0 +1,31 @@
#ifndef NODEMESH_STITCHER_H
#define NODEMESH_STITCHER_H
#include <QVector3D>
#include <vector>
#include <nodemesh/wrapper.h>
namespace nodemesh
{
class Stitcher
{
public:
~Stitcher();
void setVertices(const std::vector<QVector3D> *vertices);
bool stitch(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops);
const std::vector<std::vector<size_t>> &newlyGeneratedFaces();
void getFailedEdgeLoops(std::vector<size_t> &failedEdgeLoops);
private:
const std::vector<QVector3D> *m_positions;
std::vector<std::vector<size_t>> m_newlyGeneratedFaces;
Wrapper *m_wrapper = nullptr;
bool stitchByQuads(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops);
};
}
#endif

492
thirdparty/nodemesh/nodemesh/util.cpp vendored Normal file
View File

@ -0,0 +1,492 @@
#include <nodemesh/util.h>
#include <cmath>
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <unordered_set>
#include <unordered_map>
namespace nodemesh
{
float radianToDegree(float r)
{
return r * 180.0 / M_PI;
}
float angleBetween(const QVector3D &v1, const QVector3D &v2)
{
return atan2(QVector3D::crossProduct(v1, v2).length(), QVector3D::dotProduct(v1, v2));
}
float degreeBetween(const QVector3D &v1, const QVector3D &v2)
{
return radianToDegree(angleBetween(v1, v2));
}
float 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 += 180;
}
return angle;
}
QVector3D polygonNormal(const std::vector<QVector3D> &vertices, const std::vector<size_t> &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 triangulate(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, std::vector<std::vector<size_t>> &triangles)
{
std::vector<std::vector<size_t>> 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<size_t> 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<size_t> 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<size_t> newFace = {
fillRing[0],
fillRing[1],
fillRing[2],
};
triangles.push_back(newFace);
} else {
qDebug() << "Triangulate failed, ring size:" << fillRing.size();
}
}
}
void exportMeshAsObj(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, const QString &filename, const std::set<size_t> *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<QVector3D>::const_iterator it = vertices.begin() ; it != vertices.end(); ++it) {
stream << "v " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl;
}
for (std::vector<std::vector<size_t>>::const_iterator it = faces.begin() ; it != faces.end(); ++it) {
bool excluded = false;
for (std::vector<size_t>::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<size_t>::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) {
stream << " " << (1 + *subIt);
}
stream << endl;
}
}
}
void exportMeshAsObjWithNormals(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, const QString &filename,
const std::vector<QVector3D> &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<QVector3D>::const_iterator it = vertices.begin() ; it != vertices.end(); ++it) {
stream << "v " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl;
}
for (std::vector<QVector3D>::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<std::vector<size_t>>::const_iterator it = faces.begin() ; it != faces.end(); ++it) {
stream << "f";
for (std::vector<size_t>::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) {
++normalIndex;
stream << " " << QString::number(1 + *subIt) + "//" + QString::number(normalIndex);
}
stream << endl;
}
}
}
float 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 angleSmooth(const std::vector<QVector3D> &vertices,
const std::vector<std::vector<size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals,
float thresholdAngleDegrees,
std::vector<QVector3D> &triangleVertexNormals)
{
std::vector<std::vector<std::pair<size_t, size_t>>> triangleVertexNormalsMapByIndices(vertices.size());
std::vector<QVector3D> 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<std::pair<size_t, size_t>, 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 recoverQuads(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles, const std::set<std::pair<PositionKey, PositionKey>> &sharedQuadEdges, std::vector<std::vector<size_t>> &triangleAndQuads)
{
std::vector<PositionKey> verticesPositionKeys;
for (const auto &position: vertices) {
verticesPositionKeys.push_back(PositionKey(position));
}
std::map<std::pair<size_t, size_t>, std::pair<size_t, size_t>> 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<size_t> unionedFaces;
std::vector<std::vector<size_t>> 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<size_t> 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<QVector3D> &sourceVertices, const std::vector<std::vector<size_t>> &sourceTriangles,
float allowedSmallestDistance, const std::set<PositionKey> &excludePositions,
std::vector<QVector3D> &destVertices, std::vector<std::vector<size_t>> &destTriangles)
{
std::unordered_set<int> 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<int, int> weldVertexToMap;
std::unordered_set<int> weldTargetVertices;
std::unordered_set<int> processedFaces;
std::map<std::pair<int, int>, std::pair<int, int>> triangleEdgeMap;
std::unordered_map<int, int> 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<int, int> 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<int, int> oldToNewVerticesMap;
for (int i = 0; i < (int)sourceTriangles.size(); i++) {
const auto &faceIndices = sourceTriangles[i];
std::vector<int> 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<size_t> 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<std::vector<size_t>> &faces)
{
std::set<std::pair<size_t, size_t>> 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<QVector3D> *vertices, bool normalize)
{
float xLow = std::numeric_limits<float>::max();
float xHigh = std::numeric_limits<float>::lowest();
float yLow = std::numeric_limits<float>::max();
float yHigh = std::numeric_limits<float>::lowest();
float zLow = std::numeric_limits<float>::max();
float zHigh = std::numeric_limits<float>::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));
}
}
}
}

38
thirdparty/nodemesh/nodemesh/util.h vendored Normal file
View File

@ -0,0 +1,38 @@
#ifndef NODEMESH_UTIL_H
#define NODEMESH_UTIL_H
#include <QVector3D>
#include <vector>
#include <set>
#include <QString>
#include <nodemesh/positionkey.h>
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<QVector3D> &vertices, const std::vector<size_t> &polygon);
void triangulate(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, std::vector<std::vector<size_t>> &triangles);
void exportMeshAsObj(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, const QString &filename, const std::set<size_t> *excludeFacesOfVertices=nullptr);
void exportMeshAsObjWithNormals(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, const QString &filename,
const std::vector<QVector3D> &triangleVertexNormals);
float areaOfTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c);
void angleSmooth(const std::vector<QVector3D> &vertices,
const std::vector<std::vector<size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals,
float thresholdAngleDegrees,
std::vector<QVector3D> &triangleVertexNormals);
void recoverQuads(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles, const std::set<std::pair<PositionKey, PositionKey>> &sharedQuadEdges, std::vector<std::vector<size_t>> &triangleAndQuads);
size_t weldSeam(const std::vector<QVector3D> &sourceVertices, const std::vector<std::vector<size_t>> &sourceTriangles,
float allowedSmallestDistance, const std::set<PositionKey> &excludePositions,
std::vector<QVector3D> &destVertices, std::vector<std::vector<size_t>> &destTriangles);
bool isManifold(const std::vector<std::vector<size_t>> &faces);
void trim(std::vector<QVector3D> *vertices, bool normalize=false);
}
#endif

315
thirdparty/nodemesh/nodemesh/wrapper.cpp vendored Normal file
View File

@ -0,0 +1,315 @@
#include <nodemesh/wrapper.h>
#include <nodemesh/util.h>
#include <cmath>
#include <set>
namespace nodemesh
{
void Wrapper::setVertices(const std::vector<QVector3D> *vertices)
{
m_positions = vertices;
}
void Wrapper::wrap(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops)
{
size_t nextPlaneId = 1;
for (const auto &it: edgeLoops) {
addCandidateVertices(it.first, it.second, nextPlaneId++);
}
generate();
finalize();
}
const std::vector<std::vector<size_t>> &Wrapper::newlyGeneratedFaces()
{
return m_newlyGeneratedfaces;
}
bool Wrapper::finished()
{
if (!m_finalizeFinished)
return false;
if (m_candidates.empty())
return true;
for (const auto &it: m_candidates) {
if (!isVertexClosed(it))
return false;
}
return true;
}
void Wrapper::getFailedEdgeLoops(std::vector<size_t> &failedEdgeLoops)
{
std::set<size_t> edgeLoopIndices;
for (const auto &it: m_candidates) {
if (isVertexClosed(it))
continue;
edgeLoopIndices.insert(m_sourceVertices[it].sourcePlane - 1);
}
for (const auto &index: edgeLoopIndices) {
failedEdgeLoops.push_back(index);
}
}
void Wrapper::addCandidateVertices(const std::vector<size_t> &vertices, const QVector3D &planeNormal, size_t planeId)
{
std::map<size_t, size_t> verticesIndexSet;
for (const auto &oldVertId: vertices) {
if (verticesIndexSet.find(oldVertId) == verticesIndexSet.end()) {
size_t index = addSourceVertex((*m_positions)[oldVertId], planeId, oldVertId);
verticesIndexSet.insert({oldVertId, index});
}
}
for (size_t i = 0; i < vertices.size(); ++i) {
const auto &oldVertId = vertices[i];
const auto &oldNextVertId = vertices[(i + 1) % vertices.size()];
auto vertexIndex = verticesIndexSet[oldVertId];
auto nextVertexIndex = verticesIndexSet[oldNextVertId];
addStartup(nextVertexIndex, vertexIndex, planeNormal);
}
}
size_t Wrapper::addSourceVertex(const QVector3D &position, size_t sourcePlane, size_t tag)
{
auto addedIndex = m_sourceVertices.size();
SourceVertex sourceVertex;
sourceVertex.position = position;
sourceVertex.sourcePlane = sourcePlane;
sourceVertex.tag = tag;
sourceVertex.index = addedIndex;
m_sourceVertices.push_back(sourceVertex);
m_candidates.push_back(addedIndex);
return addedIndex;
}
void Wrapper::addStartup(size_t p1, size_t p2, const QVector3D &baseNormal)
{
if (m_items.empty())
addItem(p1, p2, baseNormal);
m_generatedFaceEdgesMap.insert({WrapItemKey {.p1 = p2, .p2 = p1}, {0, false}});
}
QVector3D Wrapper::calculateFaceVector(size_t p1, size_t p2, const QVector3D &baseNormal)
{
const auto &v1 = m_sourceVertices[p1];
const auto &v2 = m_sourceVertices[p2];
auto seg = v2.position - v1.position;
return QVector3D::crossProduct(seg, baseNormal);
}
void Wrapper::addItem(size_t p1, size_t p2, const QVector3D &baseNormal)
{
const auto &v1 = m_sourceVertices[p1];
const auto &v2 = m_sourceVertices[p2];
if (!m_items.empty() && v1.sourcePlane == v2.sourcePlane)
return;
if (findItem(p1, p2).second || findItem(p2, p1).second)
return;
if (isEdgeGenerated(p1, p2) || isEdgeGenerated(p2, p1))
return;
auto index = m_items.size();
m_items.push_back(WrapItem {.p3 = 0, .p1 = p1, .p2 = p2, .baseNormal = baseNormal, .processed = false});
m_itemsMap.insert({WrapItemKey {.p1 = p1, .p2 = p2}, index});
m_itemsList.push_front(index);
}
std::pair<size_t, bool> Wrapper::findItem(size_t p1, size_t p2)
{
auto key = WrapItemKey {.p1 = p1, .p2 = p2};
auto findResult = m_itemsMap.find(key);
if (findResult == m_itemsMap.end()) {
return {0, false};
}
return {findResult->second, true};
}
bool Wrapper::isEdgeGenerated(size_t p1, size_t p2)
{
auto key = WrapItemKey {.p1 = p1, .p2 = p2};
if (m_generatedFaceEdgesMap.find(key) == m_generatedFaceEdgesMap.end())
return false;
return true;
}
float Wrapper::angleOfBaseFaceAndPoint(size_t itemIndex, size_t vertexIndex)
{
const auto &item = m_items[itemIndex];
if (item.p1 == vertexIndex || item.p2 == vertexIndex)
return 0.0;
const auto &v1 = m_sourceVertices[item.p1];
const auto &v2 = m_sourceVertices[item.p2];
const auto &vp = m_sourceVertices[vertexIndex];
if (v1.sourcePlane == v2.sourcePlane && v1.sourcePlane == vp.sourcePlane)
return 0.0;
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));
}
std::pair<size_t, bool> Wrapper::findBestVertexOnTheLeft(size_t itemIndex)
{
auto p1 = m_items[itemIndex].p1;
auto p2 = m_items[itemIndex].p2;
auto maxAngle = 0.0;
std::pair<size_t, bool> result = {0, false};
for (auto it = m_candidates.begin(); it != m_candidates.end(); /*void*/) {
auto cand = *it;
if (isVertexClosed(cand)) {
it = m_candidates.erase(it);
continue;
}
++it;
if (isEdgeClosed(p1, cand) || isEdgeClosed(p2, cand))
continue;
auto angle = angleOfBaseFaceAndPoint(itemIndex, cand);
if (angle > maxAngle) {
maxAngle = angle;
result = {cand, true};
}
}
return result;
}
std::pair<size_t, bool> Wrapper::peekItem()
{
for (const auto &itemIndex : m_itemsList) {
if (!m_items[itemIndex].processed) {
return {itemIndex, true};
}
}
return {0, false};
}
bool Wrapper::isEdgeClosed(size_t p1, size_t p2)
{
return m_generatedFaceEdgesMap.find(WrapItemKey {.p1 = p1, .p2 = p2}) != m_generatedFaceEdgesMap.end() &&
m_generatedFaceEdgesMap.find(WrapItemKey {.p1 = p2, .p2 = p1}) != m_generatedFaceEdgesMap.end();
}
bool Wrapper::isVertexClosed(size_t vertexIndex)
{
auto findResult = m_generatedVertexEdgesMap.find(vertexIndex);
if (findResult == m_generatedVertexEdgesMap.end())
return false;
for (const auto &otherIndex: findResult->second) {
if (!isEdgeClosed(vertexIndex, otherIndex))
return false;
}
return true;
}
void Wrapper::generate()
{
for (;;) {
auto findItem = peekItem();
if (!findItem.second)
break;
auto itemIndex = findItem.first;
m_items[itemIndex].processed = true;
auto p1 = m_items[itemIndex].p1;
auto p2 = m_items[itemIndex].p2;
if (isEdgeClosed(p1, p2))
continue;
auto findP3 = findBestVertexOnTheLeft(itemIndex);
if (findP3.second) {
auto p3 = findP3.first;
m_items[itemIndex].p3 = p3;
auto baseNormal = QVector3D::normal(m_sourceVertices[p1].position,
m_sourceVertices[p2].position,
m_sourceVertices[p3].position);
auto faceIndex = m_generatedFaces.size();
m_generatedFaces.push_back(Face3 {.p1 = p1, .p2 = p2, .p3 = p3, .normal = baseNormal, .index = faceIndex});
addItem(p3, p2, baseNormal);
addItem(p1, p3, baseNormal);
m_generatedFaceEdgesMap.insert({WrapItemKey {.p1 = p1, .p2 = p2}, {faceIndex, true}});
m_generatedFaceEdgesMap.insert({WrapItemKey {.p1 = p2, .p2 = p3}, {faceIndex, true}});
m_generatedFaceEdgesMap.insert({WrapItemKey {.p1 = p3, .p2 = p1}, {faceIndex, true}});
m_generatedVertexEdgesMap[p1].push_back(p2);
m_generatedVertexEdgesMap[p1].push_back(p3);
m_generatedVertexEdgesMap[p2].push_back(p3);
m_generatedVertexEdgesMap[p2].push_back(p1);
m_generatedVertexEdgesMap[p3].push_back(p1);
m_generatedVertexEdgesMap[p3].push_back(p2);
}
}
}
size_t Wrapper::anotherVertexIndexOfFace3(const Face3 &f, size_t p1, size_t p2)
{
std::vector<size_t> indices = {f.p1, f.p2, f.p3};
for (const auto &index : indices) {
if (index != p1 && index != p2)
return index;
}
return 0;
}
std::pair<size_t, bool> Wrapper::findPairFace3(const Face3 &f, std::map<size_t, bool> &usedIds, std::vector<Face4> &q)
{
std::vector<size_t> indices = {f.p1, f.p2, f.p3};
for (size_t i = 0; i < indices.size(); ++i) {
auto j = (i + 1) % indices.size();
auto k = (i + 2) % indices.size();
auto findPairedFace3Id = m_generatedFaceEdgesMap.find(WrapItemKey {.p1 = indices[j], .p2 = indices[i]});
if (findPairedFace3Id != m_generatedFaceEdgesMap.end() && findPairedFace3Id->second.second) {
auto pairedFace3Id = findPairedFace3Id->second.first;
if (usedIds.find(pairedFace3Id) != usedIds.end())
continue;
const auto &pairedFace3 = m_generatedFaces[pairedFace3Id];
if (!almostEqual(pairedFace3.normal, f.normal))
continue;
auto anotherIndex = anotherVertexIndexOfFace3(pairedFace3, indices[j], indices[i]);
auto mergedF = Face4 {.p1 = indices[i], .p2 = anotherIndex, .p3 = indices[j], .p4 = indices[k]};
q.push_back(mergedF);
return {pairedFace3Id, true};
}
}
return {0, false};
}
bool Wrapper::almostEqual(const QVector3D &v1, const QVector3D &v2)
{
return abs(v1.x() - v2.x()) <= 0.01 &&
abs(v1.y() - v2.y()) <= 0.01 &&
abs(v1.z() - v2.z()) <= 0.01;
}
void Wrapper::finalize()
{
std::vector<Face4> quads;
std::map<size_t, bool> usedIds;
m_finalizeFinished = true;
for (const auto &f: m_generatedFaces) {
if (usedIds.find(f.index) != usedIds.end())
continue;
usedIds.insert({f.index, true});
auto paired = findPairFace3(f, usedIds, quads);
if (paired.second) {
usedIds.insert({paired.first, true});
continue;
}
std::vector<size_t> addedVertices = {
m_sourceVertices[f.p1].tag,
m_sourceVertices[f.p2].tag,
m_sourceVertices[f.p3].tag,
};
m_newlyGeneratedfaces.push_back(addedVertices);
}
for (const auto &f: quads) {
std::vector<size_t> addedVertices = {
m_sourceVertices[f.p1].tag,
m_sourceVertices[f.p2].tag,
m_sourceVertices[f.p3].tag,
m_sourceVertices[f.p4].tag,
};
m_newlyGeneratedfaces.push_back(addedVertices);
}
}
}

107
thirdparty/nodemesh/nodemesh/wrapper.h vendored Normal file
View File

@ -0,0 +1,107 @@
#ifndef NODEMESH_WRAPPER_H
#define NODEMESH_WRAPPER_H
#include <QVector3D>
#include <vector>
#include <map>
#include <deque>
namespace nodemesh
{
class Wrapper
{
public:
void setVertices(const std::vector<QVector3D> *vertices);
void wrap(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops);
const std::vector<std::vector<size_t>> &newlyGeneratedFaces();
bool finished();
void getFailedEdgeLoops(std::vector<size_t> &failedEdgeLoops);
private:
struct WrapItemKey
{
size_t p1;
size_t p2;
bool operator <(const WrapItemKey &right) const
{
if (p1 < right.p1)
return true;
if (p1 > right.p1)
return false;
if (p2 < right.p2)
return true;
if (p2 > right.p2)
return false;
return false;
}
};
struct WrapItem
{
QVector3D baseNormal;
size_t p1;
size_t p2;
size_t p3;
bool processed;
};
struct Face3
{
size_t p1;
size_t p2;
size_t p3;
QVector3D normal;
size_t index;
};
struct Face4
{
size_t p1;
size_t p2;
size_t p3;
size_t p4;
};
struct SourceVertex
{
QVector3D position;
size_t sourcePlane;
size_t index;
size_t tag;
};
std::vector<WrapItem> m_items;
std::map<WrapItemKey, size_t> m_itemsMap;
std::deque<size_t> m_itemsList;
const std::vector<QVector3D> *m_positions;
std::vector<size_t> m_candidates;
std::vector<SourceVertex> m_sourceVertices;
std::vector<Face3> m_generatedFaces;
std::map<WrapItemKey, std::pair<size_t, bool>> m_generatedFaceEdgesMap;
std::map<size_t, std::vector<size_t>> m_generatedVertexEdgesMap;
bool m_finalizeFinished = false;
std::vector<std::vector<size_t>> m_newlyGeneratedfaces;
void addCandidateVertices(const std::vector<size_t> &vertices, const QVector3D &planeNormal, size_t planeId);
size_t addSourceVertex(const QVector3D &position, size_t sourcePlane, size_t tag);
void addStartup(size_t p1, size_t p2, const QVector3D &baseNormal);
QVector3D calculateFaceVector(size_t p1, size_t p2, const QVector3D &baseNormal);
void addItem(size_t p1, size_t p2, const QVector3D &baseNormal);
std::pair<size_t, bool> findItem(size_t p1, size_t p2);
bool isEdgeGenerated(size_t p1, size_t p2);
float angleOfBaseFaceAndPoint(size_t itemIndex, size_t vertexIndex);
std::pair<size_t, bool> findBestVertexOnTheLeft(size_t itemIndex);
std::pair<size_t, bool> peekItem();
bool isEdgeClosed(size_t p1, size_t p2);
bool isVertexClosed(size_t vertexIndex);
void generate();
size_t anotherVertexIndexOfFace3(const Face3 &f, size_t p1, size_t p2);
std::pair<size_t, bool> findPairFace3(const Face3 &f, std::map<size_t, bool> &usedIds, std::vector<Face4> &q);
void finalize();
bool almostEqual(const QVector3D &v1, const QVector3D &v2);
};
}
#endif