Compare commits
37 Commits
c97e80929e
...
master
Author | SHA1 | Date | |
---|---|---|---|
b44bad92e7 | |||
d359075c64 | |||
a59b494758 | |||
228096c1a6 | |||
940ba1be7f | |||
3035ffacb8 | |||
fc4bd15ba6 | |||
908114858d | |||
9aac4d09e6 | |||
810ddaee03 | |||
6cb0dbc21e | |||
a9e0f9612b | |||
dac20b6f38 | |||
8686f6836f | |||
686e8cf397 | |||
be6a1d4f8a | |||
50255ebfbb | |||
831931e8dc | |||
64da1084a0 | |||
61082c8331 | |||
24dabed48c | |||
8177b52927 | |||
1659341370 | |||
068dede832 | |||
a15cb96e2e | |||
36891b7589 | |||
b6b07e3260 | |||
638511ebdb | |||
54470644f1 | |||
6fdca63aee | |||
108ffc25e4 | |||
98d8089893 | |||
7e8749991e | |||
a8f9e1db56 | |||
0d285db809 | |||
1d88c993b9 | |||
1d1f2c22f7 |
@ -6,6 +6,10 @@ find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets)
|
|||||||
add_subdirectory(external/OpenMesh)
|
add_subdirectory(external/OpenMesh)
|
||||||
add_subdirectory(external/Eigen)
|
add_subdirectory(external/Eigen)
|
||||||
|
|
||||||
|
add_subdirectory(external/MeshReconstruction)
|
||||||
|
target_include_directories(MeshReconstruction INTERFACE
|
||||||
|
external/MeshReconstruction/lib)
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME})
|
add_executable(${PROJECT_NAME})
|
||||||
target_sources(${PROJECT_NAME} PRIVATE
|
target_sources(${PROJECT_NAME} PRIVATE
|
||||||
resources.qrc
|
resources.qrc
|
||||||
@ -28,11 +32,18 @@ target_sources(${PROJECT_NAME} PRIVATE
|
|||||||
src/quad_patch.cpp
|
src/quad_patch.cpp
|
||||||
src/quad_patch.h
|
src/quad_patch.h
|
||||||
src/quad_patch_tesselator.cpp
|
src/quad_patch_tesselator.cpp
|
||||||
src/quad_patch_tesselator.h)
|
src/quad_patch_tesselator.h
|
||||||
|
src/hole_filling.cpp
|
||||||
|
src/hole_filling.h
|
||||||
|
src/double_input.cpp
|
||||||
|
src/double_input.h
|
||||||
|
src/noise_removal.cpp
|
||||||
|
src/noise_removal.h)
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||||
Qt5::Core Qt5::Gui Qt5::Widgets
|
Qt5::Core Qt5::Gui Qt5::Widgets
|
||||||
OpenMeshCore
|
OpenMeshCore
|
||||||
eigen)
|
eigen
|
||||||
|
MeshReconstruction)
|
||||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||||
AUTOMOC ON
|
AUTOMOC ON
|
||||||
|
29
LISEZ-MOI.txt
Normal file
29
LISEZ-MOI.txt
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
TP de modélisation géométrique réalisé dans le cadre du M2 GIG
|
||||||
|
par Jérémy André et Cyril Colin
|
||||||
|
|
||||||
|
Ceci comprend les fonctionalités suivantes :
|
||||||
|
- remplissage de trous (dans ‘src/hole_filling.cpp’)
|
||||||
|
- analyse de courbure (dans ‘src/curvature.cpp’)
|
||||||
|
- adoucissement d'un maillage par laplacien uniforme ou cotangent (dans
|
||||||
|
‘src/smoothing.cpp’)
|
||||||
|
- suppression de parasites (dans ‘src/noise_removal.cpp’)
|
||||||
|
|
||||||
|
Des maillages exemples sont inclus, ‘data/gargoyle_trou.obj’ est un
|
||||||
|
bon exemple pour le remplissage, ‘data/bunnyLowPoly-noisy.obj’ pour
|
||||||
|
l'adoucissement et ‘data/bunny.obj’ pour la suppression de parasites.
|
||||||
|
|
||||||
|
|
||||||
|
Compilation
|
||||||
|
‘cmake -Bbuild -DCMAKE_BUILD_TYPE=Release’
|
||||||
|
puis
|
||||||
|
‘cmake --build build’
|
||||||
|
|
||||||
|
|
||||||
|
Exécution
|
||||||
|
‘build/tp [FICHIER OBJ]’
|
||||||
|
|
||||||
|
|
||||||
|
Dépendances
|
||||||
|
- Eigen (téléchargée automatiquement)
|
||||||
|
- Openmesh (téléchargée automatiquement)
|
||||||
|
- MeshReconstruction (inclue dans `external')
|
7509
data/bunnyLowPoly-noisy.obj
Normal file
7509
data/bunnyLowPoly-noisy.obj
Normal file
File diff suppressed because it is too large
Load Diff
68474
data/face_scaled.obj
Normal file
68474
data/face_scaled.obj
Normal file
File diff suppressed because it is too large
Load Diff
103420
data/gargoyle_trou.obj
Normal file
103420
data/gargoyle_trou.obj
Normal file
File diff suppressed because it is too large
Load Diff
3
external/MeshReconstruction/.gitignore
vendored
Normal file
3
external/MeshReconstruction/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
bin
|
||||||
|
html/
|
||||||
|
latex/
|
16
external/MeshReconstruction/CMakeLists.txt
vendored
Normal file
16
external/MeshReconstruction/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
cmake_minimum_required (VERSION 3.0)
|
||||||
|
project (MeshReconstruction)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# Parallel compilation.
|
||||||
|
if(WIN32)
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4250")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_DEBUG_POSTFIX "d")
|
||||||
|
|
||||||
|
add_subdirectory(lib)
|
||||||
|
add_subdirectory(demo)
|
13
external/MeshReconstruction/Readme.md
vendored
Normal file
13
external/MeshReconstruction/Readme.md
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# MeshReconstruction
|
||||||
|
|
||||||
|
This is a small library that can reconstruct a triangle mesh from a <a href="http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm">signed distance function</a> using the <a href="https://en.wikipedia.org/wiki/Marching_cubes">Marching Cubes algorithm</a> and export it to a file in the <a href="https://de.wikipedia.org/wiki/Wavefront_OBJ">obj format</a>.
|
||||||
|
|
||||||
|
The library is self-contained and has no dependencies. The library is fast due to precomputed lookup tables and a narrow-band approach which excludes a lot of marching cubes that are far away from the surface.
|
||||||
|
|
||||||
|
The library requires C++14 and has been tested under Visual Studio 2017 and Windows 10 but should port to other systems without major problems.
|
||||||
|
|
||||||
|
The library can be used under the terms of the MIT License.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="overview.png" width="400" alt="Overview">
|
||||||
|
</p>
|
3
external/MeshReconstruction/demo/CMakeLists.txt
vendored
Normal file
3
external/MeshReconstruction/demo/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
include_directories("." ${PROJECT_SOURCE_DIR}/lib)
|
||||||
|
add_executable(Demo main.cpp)
|
||||||
|
target_link_libraries (Demo MeshReconstruction)
|
22
external/MeshReconstruction/demo/main.cpp
vendored
Normal file
22
external/MeshReconstruction/demo/main.cpp
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#include <MeshReconstruction.h>
|
||||||
|
#include <IO.h>
|
||||||
|
|
||||||
|
using namespace MeshReconstruction;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
auto sphereSdf = [](Vec3 const& pos)
|
||||||
|
{
|
||||||
|
auto const Radius = 1.0;
|
||||||
|
return pos.Norm() - Radius;
|
||||||
|
};
|
||||||
|
|
||||||
|
Rect3 domain;
|
||||||
|
domain.min = { -1.1, -1.1, -1.1 };
|
||||||
|
domain.size = {2.2, 2.2, 2.2};
|
||||||
|
|
||||||
|
Vec3 cubeSize{ 0.05, 0.05, 0.05 };
|
||||||
|
|
||||||
|
auto mesh = MarchCube(sphereSdf, domain, cubeSize);
|
||||||
|
WriteObjFile(mesh, "sphere.obj");
|
||||||
|
}
|
10
external/MeshReconstruction/lib/CMakeLists.txt
vendored
Normal file
10
external/MeshReconstruction/lib/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
add_library(MeshReconstruction MeshReconstruction.h MeshReconstruction.cpp Cube.h Cube.cpp DataStructs.h IO.h IO.cpp Triangulation.h Triangulation.cpp)
|
||||||
|
|
||||||
|
install(TARGETS MeshReconstruction
|
||||||
|
RUNTIME DESTINATION bin
|
||||||
|
LIBRARY DESTINATION lib
|
||||||
|
ARCHIVE DESTINATION lib)
|
||||||
|
|
||||||
|
install(DIRECTORY .
|
||||||
|
DESTINATION .
|
||||||
|
FILES_MATCHING PATTERN "*.h")
|
148
external/MeshReconstruction/lib/Cube.cpp
vendored
Normal file
148
external/MeshReconstruction/lib/Cube.cpp
vendored
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
#include "Cube.h"
|
||||||
|
|
||||||
|
using namespace MeshReconstruction;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Cube has 8 vertices. Each vertex can have positive or negative sign.
|
||||||
|
// 2^8 = 256 possible configurations mapped to intersected edges in each case.
|
||||||
|
// The 12 edges are numbered as 1, 2, 4, ..., 2048 and are stored as a 12-bit bitstring for each configuration.
|
||||||
|
const int signConfigToIntersectedEdges[256] = {
|
||||||
|
0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
|
||||||
|
0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
|
||||||
|
0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
|
||||||
|
0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
|
||||||
|
0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c,
|
||||||
|
0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
|
||||||
|
0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac,
|
||||||
|
0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
|
||||||
|
0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c,
|
||||||
|
0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
|
||||||
|
0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc,
|
||||||
|
0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
|
||||||
|
0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c,
|
||||||
|
0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
|
||||||
|
0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc ,
|
||||||
|
0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
|
||||||
|
0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
|
||||||
|
0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
|
||||||
|
0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
|
||||||
|
0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
|
||||||
|
0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
|
||||||
|
0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
|
||||||
|
0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
|
||||||
|
0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
|
||||||
|
0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
|
||||||
|
0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
|
||||||
|
0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
|
||||||
|
0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
|
||||||
|
0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
|
||||||
|
0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
|
||||||
|
0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
|
||||||
|
0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 };
|
||||||
|
|
||||||
|
struct Edge
|
||||||
|
{
|
||||||
|
unsigned edgeFlag : 12; // flag: 1, 2, 4, ... 2048
|
||||||
|
int vert0; // 0-7
|
||||||
|
int vert1; // 0-7
|
||||||
|
};
|
||||||
|
|
||||||
|
const Edge edges[12] =
|
||||||
|
{
|
||||||
|
{ 1, 0, 1 }, // edge 0
|
||||||
|
{ 2, 1, 2 }, // edge 1
|
||||||
|
{ 4, 2, 3 }, // ...
|
||||||
|
{ 8, 3, 0 },
|
||||||
|
{ 16, 4, 5 },
|
||||||
|
{ 32, 5, 6 },
|
||||||
|
{ 64, 6, 7 },
|
||||||
|
{ 128, 7, 4 },
|
||||||
|
{ 256, 0, 4 },
|
||||||
|
{ 512, 1, 5 },
|
||||||
|
{ 1024, 2, 6 },
|
||||||
|
{ 2048, 3, 7 } // edge 11
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 Cube::LerpVertex(double isoLevel, int i1, int i2) const
|
||||||
|
{
|
||||||
|
auto const Eps = 1e-5;
|
||||||
|
auto const v1 = sdf[i1];
|
||||||
|
auto const v2 = sdf[i2];
|
||||||
|
auto const& p1 = pos[i1];
|
||||||
|
auto const& p2 = pos[i2];
|
||||||
|
|
||||||
|
if (std::abs(isoLevel - v1) < Eps) return p1;
|
||||||
|
if (std::abs(isoLevel - v2) < Eps) return p2;
|
||||||
|
if (std::abs(v1 - v2) < Eps) return p1;
|
||||||
|
|
||||||
|
auto mu = (isoLevel - v1) / (v2 - v1);
|
||||||
|
return p1 + (p2 - p1)*mu;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cube::Cube(Rect3 const& space, Fun3s const& sdf)
|
||||||
|
{
|
||||||
|
auto mx = space.min.x;
|
||||||
|
auto my = space.min.y;
|
||||||
|
auto mz = space.min.z;
|
||||||
|
|
||||||
|
auto sx = space.size.x;
|
||||||
|
auto sy = space.size.y;
|
||||||
|
auto sz = space.size.z;
|
||||||
|
|
||||||
|
pos[0] = space.min;
|
||||||
|
pos[1] = { mx + sx, my, mz };
|
||||||
|
pos[2] = { mx + sx, my, mz + sz };
|
||||||
|
pos[3] = { mx, my, mz + sz };
|
||||||
|
pos[4] = { mx, my + sy, mz };
|
||||||
|
pos[5] = { mx + sx, my + sy, mz };
|
||||||
|
pos[6] = { mx + sx, my + sy, mz + sz };
|
||||||
|
pos[7] = { mx, my + sy, mz + sz };
|
||||||
|
|
||||||
|
for (auto i = 0; i < 8; ++i)
|
||||||
|
{
|
||||||
|
auto sd = sdf(pos[i]);
|
||||||
|
if (sd == 0) sd += 1e-6;
|
||||||
|
this->sdf[i] = sd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Cube::SignConfig(double isoLevel) const
|
||||||
|
{
|
||||||
|
auto edgeIndex = 0;
|
||||||
|
|
||||||
|
for (auto i = 0; i < 8; ++i)
|
||||||
|
{
|
||||||
|
if (sdf[i] < isoLevel)
|
||||||
|
{
|
||||||
|
edgeIndex |= (1 << i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return edgeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntersectInfo Cube::Intersect(double iso) const
|
||||||
|
{
|
||||||
|
// idea:
|
||||||
|
// from signs at 8 corners of cube a sign configuration (256 possible ones) is computed
|
||||||
|
// this configuration can be used to index into a table that tells which of the 12 edges are intersected
|
||||||
|
// find vertices adjacent to edges and interpolate cut vertex and store it in IntersectionInfo object
|
||||||
|
|
||||||
|
IntersectInfo intersect;
|
||||||
|
intersect.signConfig = SignConfig(iso);
|
||||||
|
auto intersectedEdges = signConfigToIntersectedEdges[intersect.signConfig];
|
||||||
|
for (auto e = 0; e<12; ++e)
|
||||||
|
{
|
||||||
|
if (intersectedEdges & edges[e].edgeFlag)
|
||||||
|
{
|
||||||
|
auto v0 = edges[e].vert0;
|
||||||
|
auto v1 = edges[e].vert1;
|
||||||
|
auto vert = LerpVertex(iso, v0, v1);
|
||||||
|
intersect.edgeVertIndices[e] = vert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return intersect;
|
||||||
|
}
|
30
external/MeshReconstruction/lib/Cube.h
vendored
Normal file
30
external/MeshReconstruction/lib/Cube.h
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "DataStructs.h"
|
||||||
|
|
||||||
|
namespace MeshReconstruction
|
||||||
|
{
|
||||||
|
struct IntersectInfo
|
||||||
|
{
|
||||||
|
// 0 - 255
|
||||||
|
int signConfig;
|
||||||
|
|
||||||
|
// If it exists, vertex on edge i is stored at position i.
|
||||||
|
// For edge numbering and location see numberings.png.
|
||||||
|
std::array<Vec3, 12> edgeVertIndices;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Cube
|
||||||
|
{
|
||||||
|
Vec3 pos[8];
|
||||||
|
double sdf[8];
|
||||||
|
|
||||||
|
Vec3 LerpVertex(double isoLevel, int i1, int i2) const;
|
||||||
|
int SignConfig(double isoLevel) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Cube(Rect3 const& space, Fun3s const& sdf);
|
||||||
|
|
||||||
|
// Find the vertices where the surface intersects the cube.
|
||||||
|
IntersectInfo Intersect(double isoLevel = 0) const;
|
||||||
|
};
|
||||||
|
}
|
57
external/MeshReconstruction/lib/DataStructs.h
vendored
Normal file
57
external/MeshReconstruction/lib/DataStructs.h
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
#include <functional>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace MeshReconstruction
|
||||||
|
{
|
||||||
|
struct Vec3
|
||||||
|
{
|
||||||
|
double x, y, z;
|
||||||
|
|
||||||
|
Vec3 operator+(Vec3 const& o) const
|
||||||
|
{
|
||||||
|
return { x + o.x, y + o.y, z + o.z };
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 operator-(Vec3 const& o) const
|
||||||
|
{
|
||||||
|
return { x - o.x, y - o.y, z - o.z };
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 operator*(double c) const
|
||||||
|
{
|
||||||
|
return { c*x, c*y, c*z };
|
||||||
|
}
|
||||||
|
|
||||||
|
double Norm() const
|
||||||
|
{
|
||||||
|
return sqrt(x*x + y*y + z*z);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 Normalized() const
|
||||||
|
{
|
||||||
|
auto n = Norm();
|
||||||
|
return { x / n, y / n, z / n };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Rect3
|
||||||
|
{
|
||||||
|
Vec3 min;
|
||||||
|
Vec3 size;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Triangle = std::array<int, 3>;
|
||||||
|
|
||||||
|
struct Mesh
|
||||||
|
{
|
||||||
|
std::vector<Vec3> vertices;
|
||||||
|
std::vector<Triangle> triangles;
|
||||||
|
std::vector<Vec3> vertexNormals;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Fun3s = std::function<double(Vec3 const&)>;
|
||||||
|
using Fun3v = std::function<Vec3(Vec3 const&)>;
|
||||||
|
}
|
47
external/MeshReconstruction/lib/IO.cpp
vendored
Normal file
47
external/MeshReconstruction/lib/IO.cpp
vendored
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#include "IO.h"
|
||||||
|
#include <stdexcept>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
void MeshReconstruction::WriteObjFile(Mesh const& mesh, string const& fileName)
|
||||||
|
{
|
||||||
|
// FILE faster than streams.
|
||||||
|
|
||||||
|
FILE* file = fopen(fileName.c_str(), "w");
|
||||||
|
if (file == NULL)
|
||||||
|
{
|
||||||
|
throw runtime_error("Could not write obj file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// write stats
|
||||||
|
fprintf(file, "# %d vertices, %d triangles\n\n",
|
||||||
|
static_cast<int>(mesh.vertices.size()),
|
||||||
|
static_cast<int>(mesh.triangles.size()));
|
||||||
|
|
||||||
|
// vertices
|
||||||
|
for (size_t vi = 0; vi < mesh.vertices.size(); ++vi)
|
||||||
|
{
|
||||||
|
auto const& v = mesh.vertices.at(vi);
|
||||||
|
fprintf(file, "v %f %f %f\n", v.x, v.y, v.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// vertex normals
|
||||||
|
fprintf(file, "\n");
|
||||||
|
for (size_t ni = 0; ni < mesh.vertices.size(); ++ni)
|
||||||
|
{
|
||||||
|
auto const& vn = mesh.vertexNormals.at(ni);
|
||||||
|
fprintf(file, "vn %f %f %f\n", vn.x, vn.y, vn.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// triangles (1-based)
|
||||||
|
fprintf(file, "\n");
|
||||||
|
for (size_t ti = 0; ti < mesh.triangles.size(); ++ti)
|
||||||
|
{
|
||||||
|
auto const& t = mesh.triangles.at(ti);
|
||||||
|
fprintf(file, "f %d//%d %d//%d %d//%d\n",
|
||||||
|
t[0] + 1, t[0] + 1,
|
||||||
|
t[1] + 1, t[1] + 1,
|
||||||
|
t[2] + 1, t[2] + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
}
|
9
external/MeshReconstruction/lib/IO.h
vendored
Normal file
9
external/MeshReconstruction/lib/IO.h
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include "DataStructs.h"
|
||||||
|
|
||||||
|
namespace MeshReconstruction
|
||||||
|
{
|
||||||
|
/// Writes a mesh to a file in <a href="https://de.wikipedia.org/wiki/Wavefront_OBJ">obj format</a>.
|
||||||
|
void WriteObjFile(Mesh const& mesh, std::string const& file);
|
||||||
|
}
|
79
external/MeshReconstruction/lib/MeshReconstruction.cpp
vendored
Normal file
79
external/MeshReconstruction/lib/MeshReconstruction.cpp
vendored
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#include "MeshReconstruction.h"
|
||||||
|
#include "Cube.h"
|
||||||
|
#include "Triangulation.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace MeshReconstruction;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// Adapted from here: http://paulbourke.net/geometry/polygonise/
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
Vec3 NumGrad(Fun3s const& f, Vec3 const& p)
|
||||||
|
{
|
||||||
|
auto const Eps = 1e-6;
|
||||||
|
Vec3 epsX{ Eps, 0, 0 }, epsY{ 0, Eps, 0 }, epsZ{ 0, 0, Eps };
|
||||||
|
auto gx = (f(p + epsX) - f(p - epsX)) / 2;
|
||||||
|
auto gy = (f(p + epsY) - f(p - epsY)) / 2;
|
||||||
|
auto gz = (f(p + epsZ) - f(p - epsZ)) / 2;
|
||||||
|
return { gx, gy, gz };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Mesh MeshReconstruction::MarchCube(Fun3s const& sdf, Rect3 const& domain)
|
||||||
|
{
|
||||||
|
auto const NumCubes = 50;
|
||||||
|
auto cubeSize = domain.size * (1.0 / NumCubes);
|
||||||
|
|
||||||
|
return MarchCube(sdf, domain, cubeSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
Mesh MeshReconstruction::MarchCube(
|
||||||
|
Fun3s const& sdf,
|
||||||
|
Rect3 const& domain,
|
||||||
|
Vec3 const& cubeSize,
|
||||||
|
double isoLevel,
|
||||||
|
Fun3v sdfGrad)
|
||||||
|
{
|
||||||
|
// Default value.
|
||||||
|
sdfGrad = sdfGrad == nullptr
|
||||||
|
? [&sdf](Vec3 const& p) { return NumGrad(sdf, p); }
|
||||||
|
: sdfGrad;
|
||||||
|
|
||||||
|
auto const NumX = static_cast<int>(ceil(domain.size.x / cubeSize.x));
|
||||||
|
auto const NumY = static_cast<int>(ceil(domain.size.y / cubeSize.y));
|
||||||
|
auto const NumZ = static_cast<int>(ceil(domain.size.z / cubeSize.z));
|
||||||
|
|
||||||
|
auto const CubeDiag = cubeSize.Norm();
|
||||||
|
// auto const HalfCubeDiag = cubeSize.Norm() * 0.5;
|
||||||
|
// auto const HalfCubeSize = cubeSize * 0.5;
|
||||||
|
|
||||||
|
Mesh mesh;
|
||||||
|
|
||||||
|
for (auto ix = 0; ix < NumX; ++ix)
|
||||||
|
{
|
||||||
|
auto x = domain.min.x + ix * cubeSize.x;
|
||||||
|
for (auto iy = 0; iy < NumY; ++iy)
|
||||||
|
{
|
||||||
|
auto y = domain.min.y + iy * cubeSize.y;
|
||||||
|
for (auto iz = 0; iz < NumZ; ++iz)
|
||||||
|
{
|
||||||
|
auto z = domain.min.z + iz * cubeSize.z;
|
||||||
|
Vec3 min{ x, y, z };
|
||||||
|
|
||||||
|
// Process only if cube lies within narrow band around surface.
|
||||||
|
// auto cubeCenter = min + HalfCubeSize;
|
||||||
|
// double dist = std::abs(sdf(cubeCenter) - isoLevel);
|
||||||
|
double dist = std::abs(sdf(min) - isoLevel);
|
||||||
|
// if (dist > CubeDiag) continue;
|
||||||
|
|
||||||
|
Cube cube({ min, cubeSize }, sdf);
|
||||||
|
auto intersect = cube.Intersect(isoLevel);
|
||||||
|
Triangulate(intersect, sdfGrad, mesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
}
|
27
external/MeshReconstruction/lib/MeshReconstruction.h
vendored
Normal file
27
external/MeshReconstruction/lib/MeshReconstruction.h
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "DataStructs.h"
|
||||||
|
|
||||||
|
namespace MeshReconstruction
|
||||||
|
{
|
||||||
|
/// Reconstructs a triangle mesh from a given signed distance function using <a href="https://en.wikipedia.org/wiki/Marching_cubes">Marching Cubes</a>.
|
||||||
|
/// @param sdf The <a href="http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm">Signed Distance Function</a>.
|
||||||
|
/// @param domain Domain of reconstruction.
|
||||||
|
/// @returns The reconstructed mesh.
|
||||||
|
Mesh MarchCube(
|
||||||
|
Fun3s const& sdf,
|
||||||
|
Rect3 const& domain);
|
||||||
|
|
||||||
|
/// Reconstructs a triangle mesh from a given signed distance function using <a href="https://en.wikipedia.org/wiki/Marching_cubes">Marching Cubes</a>.
|
||||||
|
/// @param sdf The <a href="http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm">Signed Distance Function</a>.
|
||||||
|
/// @param domain Domain of reconstruction.
|
||||||
|
/// @param cubeSize Size of marching cubes. Smaller cubes yields meshes of higher resolution.
|
||||||
|
/// @param isoLevel Level set of the SDF for which triangulation should be done. Changing this value moves the reconstructed surface.
|
||||||
|
/// @param sdfGrad Gradient of the SDF which yields the vertex normals of the reconstructed mesh. If none is provided a numerical approximation is used.
|
||||||
|
/// @returns The reconstructed mesh.
|
||||||
|
Mesh MarchCube(
|
||||||
|
Fun3s const& sdf,
|
||||||
|
Rect3 const& domain,
|
||||||
|
Vec3 const& cubeSize,
|
||||||
|
double isoLevel = 0,
|
||||||
|
Fun3v sdfGrad = nullptr);
|
||||||
|
}
|
301
external/MeshReconstruction/lib/Triangulation.cpp
vendored
Normal file
301
external/MeshReconstruction/lib/Triangulation.cpp
vendored
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
#include "Triangulation.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Indices into vertex buffer (0 - 11).
|
||||||
|
// Three successive entries make up one triangle.
|
||||||
|
// -1 means unused.
|
||||||
|
const int signConfigToTriangles[256][16] =
|
||||||
|
{ { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1 },
|
||||||
|
{ 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1 },
|
||||||
|
{ 3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1 },
|
||||||
|
{ 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1 },
|
||||||
|
{ 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1 },
|
||||||
|
{ 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1 },
|
||||||
|
{ 10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1 },
|
||||||
|
{ 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1 },
|
||||||
|
{ 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1 },
|
||||||
|
{ 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1 },
|
||||||
|
{ 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1 },
|
||||||
|
{ 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1 },
|
||||||
|
{ 11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1 },
|
||||||
|
{ 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1 },
|
||||||
|
{ 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1 },
|
||||||
|
{ 11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1 },
|
||||||
|
{ 2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1 },
|
||||||
|
{ 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1 },
|
||||||
|
{ 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1 },
|
||||||
|
{ 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1 },
|
||||||
|
{ 6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1 },
|
||||||
|
{ 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1 },
|
||||||
|
{ 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1 },
|
||||||
|
{ 3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1 },
|
||||||
|
{ 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1 },
|
||||||
|
{ 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1 },
|
||||||
|
{ 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1 },
|
||||||
|
{ 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1 },
|
||||||
|
{ 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1 },
|
||||||
|
{ 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1 },
|
||||||
|
{ 10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1 },
|
||||||
|
{ 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1 },
|
||||||
|
{ 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1 },
|
||||||
|
{ 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1 },
|
||||||
|
{ 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1 },
|
||||||
|
{ 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1 },
|
||||||
|
{ 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1 },
|
||||||
|
{ 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1 },
|
||||||
|
{ 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1 },
|
||||||
|
{ 10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1 },
|
||||||
|
{ 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1 },
|
||||||
|
{ 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1 },
|
||||||
|
{ 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1 },
|
||||||
|
{ 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1 },
|
||||||
|
{ 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1 },
|
||||||
|
{ 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1 },
|
||||||
|
{ 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1 },
|
||||||
|
{ 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1 },
|
||||||
|
{ 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1 },
|
||||||
|
{ 10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1 },
|
||||||
|
{ 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1 },
|
||||||
|
{ 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1 },
|
||||||
|
{ 6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1 },
|
||||||
|
{ 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1 },
|
||||||
|
{ 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1 },
|
||||||
|
{ 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1 },
|
||||||
|
{ 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1 },
|
||||||
|
{ 10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1 },
|
||||||
|
{ 10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1 },
|
||||||
|
{ 9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1 },
|
||||||
|
{ 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1 },
|
||||||
|
{ 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1 },
|
||||||
|
{ 7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1 },
|
||||||
|
{ 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1 },
|
||||||
|
{ 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1 },
|
||||||
|
{ 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1 },
|
||||||
|
{ 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1 },
|
||||||
|
{ 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1 },
|
||||||
|
{ 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1 },
|
||||||
|
{ 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1 },
|
||||||
|
{ 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1 },
|
||||||
|
{ 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1 },
|
||||||
|
{ 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1 },
|
||||||
|
{ 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1 },
|
||||||
|
{ 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1 },
|
||||||
|
{ 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1 },
|
||||||
|
{ 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1 },
|
||||||
|
{ 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1 },
|
||||||
|
{ 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1 },
|
||||||
|
{ 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1 },
|
||||||
|
{ 11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1 },
|
||||||
|
{ 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1 },
|
||||||
|
{ 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1 },
|
||||||
|
{ 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1 },
|
||||||
|
{ 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1 },
|
||||||
|
{ 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1 },
|
||||||
|
{ 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1 },
|
||||||
|
{ 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1 },
|
||||||
|
{ 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1 },
|
||||||
|
{ 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1 },
|
||||||
|
{ 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1 },
|
||||||
|
{ 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1 },
|
||||||
|
{ 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1 },
|
||||||
|
{ 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1 },
|
||||||
|
{ 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1 },
|
||||||
|
{ 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1 },
|
||||||
|
{ 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1 },
|
||||||
|
{ 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1 },
|
||||||
|
{ 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1 },
|
||||||
|
{ 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1 },
|
||||||
|
{ 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1 },
|
||||||
|
{ 11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1 },
|
||||||
|
{ 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1 },
|
||||||
|
{ 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1 },
|
||||||
|
{ 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1 },
|
||||||
|
{ 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1 },
|
||||||
|
{ 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1 },
|
||||||
|
{ 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1 },
|
||||||
|
{ 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
|
||||||
|
{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 } };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given a grid cube and an isolevel the triangles (5 max)
|
||||||
|
/// required to represent the isosurface in the cube are computed.
|
||||||
|
void MeshReconstruction::Triangulate(
|
||||||
|
IntersectInfo const& intersect,
|
||||||
|
Fun3v const& grad,
|
||||||
|
Mesh& mesh)
|
||||||
|
{
|
||||||
|
// Cube is entirely in/out of the surface. Generate no triangles.
|
||||||
|
if (intersect.signConfig == 0 || intersect.signConfig == 255) return;
|
||||||
|
|
||||||
|
auto const& tri = signConfigToTriangles[intersect.signConfig];
|
||||||
|
|
||||||
|
for (auto i = 0; tri[i] != -1; i += 3)
|
||||||
|
{
|
||||||
|
auto const& v0 = intersect.edgeVertIndices[tri[i]];
|
||||||
|
auto const& v1 = intersect.edgeVertIndices[tri[i + 1]];
|
||||||
|
auto const& v2 = intersect.edgeVertIndices[tri[i + 2]];
|
||||||
|
|
||||||
|
mesh.vertices.push_back(v0);
|
||||||
|
mesh.vertices.push_back(v1);
|
||||||
|
mesh.vertices.push_back(v2);
|
||||||
|
|
||||||
|
auto normal0 = grad(v0).Normalized();
|
||||||
|
auto normal1 = grad(v1).Normalized();
|
||||||
|
auto normal2 = grad(v2).Normalized();
|
||||||
|
|
||||||
|
mesh.vertexNormals.push_back(normal0);
|
||||||
|
mesh.vertexNormals.push_back(normal1);
|
||||||
|
mesh.vertexNormals.push_back(normal2);
|
||||||
|
|
||||||
|
auto last = static_cast<int>(mesh.vertices.size() - 1);
|
||||||
|
|
||||||
|
mesh.triangles.push_back({ last - 2, last - 1, last });
|
||||||
|
}
|
||||||
|
}
|
11
external/MeshReconstruction/lib/Triangulation.h
vendored
Normal file
11
external/MeshReconstruction/lib/Triangulation.h
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Cube.h"
|
||||||
|
#include "DataStructs.h"
|
||||||
|
|
||||||
|
namespace MeshReconstruction
|
||||||
|
{
|
||||||
|
void Triangulate(
|
||||||
|
IntersectInfo const& intersect,
|
||||||
|
Fun3v const& grad,
|
||||||
|
Mesh& mesh);
|
||||||
|
}
|
BIN
external/MeshReconstruction/numberings.png
vendored
Normal file
BIN
external/MeshReconstruction/numberings.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
external/MeshReconstruction/overview.png
vendored
Normal file
BIN
external/MeshReconstruction/overview.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 107 KiB |
5
notes_réparation_bac.txt
Normal file
5
notes_réparation_bac.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Notes du TP de réparation de maillage par surface implicite de Mme Bac
|
||||||
|
|
||||||
|
Fonction implicite f(x) = Σ(1,n)[αi φ(||X-Xi||) + …]
|
||||||
|
|
||||||
|
φ(r) = r²ln(r) : fonction radiale, une par point, =0 au point
|
@ -1,5 +1,6 @@
|
|||||||
#include "curvature.h"
|
#include "curvature.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include <OpenMesh/Core/Utils/PropertyManager.hh>
|
||||||
|
|
||||||
|
|
||||||
void Courbures::normales_locales() {
|
void Courbures::normales_locales() {
|
||||||
@ -9,7 +10,7 @@ void Courbures::normales_locales() {
|
|||||||
MyMesh::Normal normal(0,0,0);
|
MyMesh::Normal normal(0,0,0);
|
||||||
i = 0;
|
i = 0;
|
||||||
for (MyMesh::FaceHandle vf : _mesh.vf_range(vh)) {
|
for (MyMesh::FaceHandle vf : _mesh.vf_range(vh)) {
|
||||||
i++ ;
|
i++;
|
||||||
normal += _mesh.calc_face_normal(vf);
|
normal += _mesh.calc_face_normal(vf);
|
||||||
}
|
}
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
@ -21,44 +22,41 @@ void Courbures::normales_locales() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::vector<MyMesh::VertexHandle> Courbures::get_two_neighborhood(const MyMesh::VertexHandle vh) {
|
void Courbures::get_two_neighborhood(std::vector<MyMesh::VertexHandle> &out,
|
||||||
OpenMesh::VPropHandleT<bool> vprop_flag;
|
const MyMesh::VertexHandle vh) {
|
||||||
_mesh.add_property(vprop_flag, "vprop_flag");
|
auto flag_prop =
|
||||||
|
OpenMesh::getOrMakeProperty<VertexHandle, bool>(_mesh, "vprop_flag");
|
||||||
|
flag_prop.set_range(_mesh.vertices_begin(), _mesh.vertices_end(), false);
|
||||||
|
|
||||||
// Initialisation
|
// Parcours du 1-anneau
|
||||||
for (VertexHandle vh : _mesh.vertices()) {
|
flag_prop[vh] = true;
|
||||||
_mesh.property(vprop_flag, vh) = false;
|
|
||||||
}
|
|
||||||
// Circulateur sur le premier cercle
|
|
||||||
std::vector<MyMesh::VertexHandle> neigh, neigh2;
|
|
||||||
|
|
||||||
_mesh.property(vprop_flag, vh) = true;
|
|
||||||
for(VertexHandle vv : _mesh.vv_range(vh)) {
|
for(VertexHandle vv : _mesh.vv_range(vh)) {
|
||||||
neigh.push_back(vv); // ajout du point à la liste
|
out.push_back(vv); // ajout du point à la liste
|
||||||
_mesh.property(vprop_flag, vv) = true;
|
flag_prop[vv] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parcours du premier cercle et ajout du second cercle par circulateurs
|
if (out.size() >= 5) return;
|
||||||
for (size_t i = 0; i < neigh.size(); i++) {
|
|
||||||
MyMesh::VertexHandle vh = neigh.at(i) ;
|
// Parcours du 1-anneau de chaque sommet du 1-anneau
|
||||||
for (VertexHandle vv : _mesh.vv_range(vh)) {
|
size_t old_size = out.size();
|
||||||
if (!_mesh.property(vprop_flag, vv)) {
|
for (size_t i = 0; i < old_size; i++) {
|
||||||
neigh2.push_back(vv);
|
for (VertexHandle vv : _mesh.vv_range(out[i])) {
|
||||||
|
if (!flag_prop[vv]) {
|
||||||
|
out.push_back(vv);
|
||||||
|
flag_prop[vv] = true;
|
||||||
}
|
}
|
||||||
_mesh.property(vprop_flag, vv) = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concaténation des deux cercles
|
|
||||||
neigh.insert(neigh.end(), neigh2.begin(), neigh2.end());
|
|
||||||
_mesh.remove_property(vprop_flag);
|
|
||||||
return neigh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QuadPatch Courbures::fit_quad(MyMesh::VertexHandle vh) {
|
QuadPatch Courbures::fit_quad(MyMesh::VertexHandle vh) {
|
||||||
std::vector<MyMesh::VertexHandle> neigh = get_two_neighborhood(vh);
|
static std::vector<MyMesh::VertexHandle> neigh;
|
||||||
if (neigh.size() < 5) throw "Quad fitting: not enough neighbors";
|
neigh.clear();
|
||||||
|
get_two_neighborhood(neigh, vh);
|
||||||
|
if (neigh.size() < 5) {
|
||||||
|
throw std::runtime_error("Quad fitting: not enough neighbors");
|
||||||
|
}
|
||||||
|
|
||||||
// Calcul de la matrice de changement de base
|
// Calcul de la matrice de changement de base
|
||||||
Eigen::Vector3d Oz(0,0,1);
|
Eigen::Vector3d Oz(0,0,1);
|
||||||
|
@ -14,14 +14,18 @@ public:
|
|||||||
OpenMesh::VPropHandleT<double> vprop_H;
|
OpenMesh::VPropHandleT<double> vprop_H;
|
||||||
OpenMesh::VPropHandleT<QuadPatch> vprop_quad;
|
OpenMesh::VPropHandleT<QuadPatch> vprop_quad;
|
||||||
|
|
||||||
Courbures(MyMesh &mesh) : _mesh(mesh) {}
|
Courbures(MyMesh &mesh) : _mesh(mesh) {
|
||||||
|
_mesh.request_vertex_normals();
|
||||||
|
_mesh.update_normals();
|
||||||
|
}
|
||||||
|
|
||||||
void set_fixed_colors() ;
|
void set_fixed_colors();
|
||||||
void normales_locales() ;
|
void normales_locales();
|
||||||
std::vector<MyMesh::VertexHandle> get_two_neighborhood(MyMesh::VertexHandle vh);
|
void get_two_neighborhood(std::vector<MyMesh::VertexHandle> &out,
|
||||||
|
MyMesh::VertexHandle vh);
|
||||||
QuadPatch fit_quad(MyMesh::VertexHandle vh);
|
QuadPatch fit_quad(MyMesh::VertexHandle vh);
|
||||||
void compute_KH() ;
|
void compute_KH();
|
||||||
void set_K_colors() ;
|
void set_K_colors();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
59
src/double_input.cpp
Normal file
59
src/double_input.cpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#include "double_input.h"
|
||||||
|
|
||||||
|
|
||||||
|
double DoubleInput::intToDouble(int value) const {
|
||||||
|
return static_cast<double>(value) / _slider_resolution
|
||||||
|
* (_max - _min) + _min;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int DoubleInput::doubleToInt(double value) const {
|
||||||
|
return (value - _min) / (_max - _min) * _slider_resolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DoubleInput::onSpinBoxValueChanged(double value) {
|
||||||
|
emit valueChanged(value);
|
||||||
|
if (_propagate) {
|
||||||
|
_propagate = false;
|
||||||
|
_slider->setValue(doubleToInt(value));
|
||||||
|
} else {
|
||||||
|
_propagate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DoubleInput::onSliderValueChanged(int value) {
|
||||||
|
if (_propagate) {
|
||||||
|
_propagate = false;
|
||||||
|
_spin_box->setValue(intToDouble(value));
|
||||||
|
} else {
|
||||||
|
_propagate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DoubleInput::DoubleInput(QObject *parent, double min, double max,
|
||||||
|
double value,
|
||||||
|
int slider_resolution)
|
||||||
|
:QObject(parent),
|
||||||
|
_min(min),
|
||||||
|
_max(max),
|
||||||
|
_slider_resolution(slider_resolution),
|
||||||
|
_spin_box(new QDoubleSpinBox()),
|
||||||
|
_slider(new QSlider(Qt::Horizontal)) {
|
||||||
|
_spin_box->setRange(_min, _max);
|
||||||
|
_spin_box->setValue(value);
|
||||||
|
_slider->setMaximum(_slider_resolution);
|
||||||
|
_slider->setValue(doubleToInt(value));
|
||||||
|
_slider->setTracking(false);
|
||||||
|
connect(_slider, &QSlider::valueChanged,
|
||||||
|
this, &DoubleInput::onSliderValueChanged);
|
||||||
|
connect(_spin_box, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
|
||||||
|
this, &DoubleInput::onSpinBoxValueChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DoubleInput::setValue(double value) {
|
||||||
|
_spin_box->setValue(value);
|
||||||
|
}
|
41
src/double_input.h
Normal file
41
src/double_input.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#ifndef DOUBLE_INPUT_H
|
||||||
|
#define DOUBLE_INPUT_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QSlider>
|
||||||
|
#include <QDoubleSpinBox>
|
||||||
|
|
||||||
|
|
||||||
|
class DoubleInput : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
const double _min;
|
||||||
|
const double _max;
|
||||||
|
const int _slider_resolution;
|
||||||
|
QDoubleSpinBox *_spin_box;
|
||||||
|
QSlider *_slider;
|
||||||
|
bool _propagate = true;
|
||||||
|
|
||||||
|
double intToDouble(int value) const;
|
||||||
|
int doubleToInt(double value) const;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onSpinBoxValueChanged(double value);
|
||||||
|
void onSliderValueChanged(int value);
|
||||||
|
|
||||||
|
public:
|
||||||
|
DoubleInput(QObject *parent, double min, double max, double value,
|
||||||
|
int slider_resolution=100);
|
||||||
|
QWidget *spinBox() { return _spin_box; }
|
||||||
|
QWidget *slider() { return _slider; }
|
||||||
|
double value() const { return _spin_box->value(); }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void valueChanged(double value);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setValue(double value);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
364
src/hole_filling.cpp
Normal file
364
src/hole_filling.cpp
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
#include "hole_filling.h"
|
||||||
|
#include "IO.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include <MeshReconstruction.h>
|
||||||
|
#include <OpenMesh/Core/Utils/PropertyManager.hh>
|
||||||
|
|
||||||
|
|
||||||
|
static std::vector<std::vector<HalfedgeHandle>> findHoles(MyMesh &mesh) {
|
||||||
|
std::vector<std::vector<HalfedgeHandle>> holes;
|
||||||
|
std::set<HalfedgeHandle> ignore;
|
||||||
|
for (HalfedgeHandle it : mesh.halfedges()) {
|
||||||
|
if (mesh.is_boundary(it)
|
||||||
|
&& ignore.find(it) == ignore.end()) {
|
||||||
|
holes.emplace_back();
|
||||||
|
holes.back().push_back(it);
|
||||||
|
ignore.insert(it);
|
||||||
|
for (HalfedgeHandle it2 : HalfedgeLoopRange<MyMesh>(mesh, it)) {
|
||||||
|
holes.back().push_back(it2);
|
||||||
|
ignore.insert(it2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return holes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void fillHoleDumb(MyMesh &mesh, std::vector<HalfedgeHandle> &hole) {
|
||||||
|
mesh.request_vertex_status();
|
||||||
|
mesh.request_edge_status();
|
||||||
|
mesh.request_face_status();
|
||||||
|
|
||||||
|
Point center(0, 0, 0);
|
||||||
|
size_t length = 0;
|
||||||
|
for (HalfedgeHandle it : hole) {
|
||||||
|
VertexHandle vert = mesh.to_vertex_handle(it);
|
||||||
|
center += mesh.point(vert);
|
||||||
|
length++;
|
||||||
|
}
|
||||||
|
center /= length;
|
||||||
|
VertexHandle center_handle = mesh.new_vertex_dirty(center);
|
||||||
|
mesh.set_color(center_handle, mesh.default_color);
|
||||||
|
|
||||||
|
for (HalfedgeHandle it : hole) {
|
||||||
|
mesh.add_face(mesh.from_vertex_handle(it),
|
||||||
|
mesh.to_vertex_handle(it),
|
||||||
|
center_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void fillHolesDumb(MyMesh &mesh) {
|
||||||
|
mesh.holes = findHoles(mesh);
|
||||||
|
for (auto hole : mesh.holes) {
|
||||||
|
fillHoleDumb(mesh, hole);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
using namespace std ;
|
||||||
|
using namespace MeshReconstruction;
|
||||||
|
|
||||||
|
/* ************** Implicit RBF ************** */
|
||||||
|
|
||||||
|
Implicit_RBF::Implicit_RBF(vector<float> alpha, vector<float> beta, vector<MyMesh::Point> center) : _alpha(alpha), _beta(beta), _center(center)
|
||||||
|
{
|
||||||
|
_n = _alpha.size() ;
|
||||||
|
_d = _beta.size() ;
|
||||||
|
if (_center.size() != _n)
|
||||||
|
throw runtime_error("Inconsistent size of alpha and centers in Implicit_RBF constructor.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computation of the value of the implicit surface at point X
|
||||||
|
float Implicit_RBF::val(MyMesh::Point X) const
|
||||||
|
{
|
||||||
|
float res = 0 ;
|
||||||
|
// Computation of the sum of RBF at centers
|
||||||
|
for(int i=0; i<_n; i++)
|
||||||
|
{
|
||||||
|
res += _alpha.at(i) * myphi((X-_center.at(i)).norm()) ;
|
||||||
|
}
|
||||||
|
// Computation of the polynomial part
|
||||||
|
for(int j=0; j<_d; j++)
|
||||||
|
{
|
||||||
|
res += _beta.at(j) * myp(j, X) ;
|
||||||
|
}
|
||||||
|
return res ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ************** Hole Filling ************** */
|
||||||
|
|
||||||
|
Hole_Filling::Hole_Filling(MyMesh &mesh, float scale, float discr)
|
||||||
|
: _mesh(mesh),
|
||||||
|
_scale(scale),
|
||||||
|
_discr(discr)
|
||||||
|
{
|
||||||
|
_mesh.add_property(_vprop, "vprop_flag");
|
||||||
|
cout << "Starting hole filling ..." << endl ;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***** Computation of boundary and its neighborhood
|
||||||
|
|
||||||
|
MyMesh::HalfedgeHandle Hole_Filling::find_boundary_edge()
|
||||||
|
{
|
||||||
|
MyMesh::HalfedgeIter he_it = _mesh.halfedges_begin();
|
||||||
|
while ( (he_it != _mesh.halfedges_end()) && (!_mesh.is_boundary(*he_it)))
|
||||||
|
{
|
||||||
|
++he_it ;
|
||||||
|
}
|
||||||
|
if (he_it != _mesh.halfedges_end())
|
||||||
|
return *he_it ;
|
||||||
|
else
|
||||||
|
throw std::runtime_error("Boundary HE does not exist") ;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<MyMesh::VertexHandle> Hole_Filling::find_boundary(MyMesh::HalfedgeHandle heh)
|
||||||
|
{
|
||||||
|
MyMesh::HalfedgeHandle heh_ini = heh ;
|
||||||
|
vector<MyMesh::VertexHandle> boundary ;
|
||||||
|
|
||||||
|
// Follow (and memorize) boundary edges
|
||||||
|
do
|
||||||
|
{
|
||||||
|
boundary.push_back(_mesh.to_vertex_handle(heh));
|
||||||
|
heh = _mesh.next_halfedge_handle(heh);
|
||||||
|
} while (heh != heh_ini) ;
|
||||||
|
return boundary ;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Hole_Filling::init_mark_boundary(const vector<MyMesh::VertexHandle> & bnd)
|
||||||
|
{
|
||||||
|
for (MyMesh::VertexIter v_it = _mesh.vertices_begin() ; v_it != _mesh.vertices_end(); ++v_it)
|
||||||
|
_mesh.property(_vprop, *v_it) = false ;
|
||||||
|
for (int i=0; i<bnd.size(); ++i)
|
||||||
|
_mesh.property(_vprop, bnd.at(i)) = true ;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<MyMesh::VertexHandle> Hole_Filling::next_neighbors(const vector<MyMesh::VertexHandle> & bnd)
|
||||||
|
{
|
||||||
|
// Visit bnd vertices to find and mark next circle
|
||||||
|
vector<MyMesh::VertexHandle> next_bnd ;
|
||||||
|
for (int i=0; i<bnd.size(); i++)
|
||||||
|
{
|
||||||
|
for (MyMesh::VertexVertexIter vv_it = _mesh.vv_iter(bnd.at(i));vv_it.is_valid();++vv_it)
|
||||||
|
{
|
||||||
|
if (_mesh.property(_vprop, *vv_it) == false) // new vertex
|
||||||
|
{
|
||||||
|
_mesh.property(_vprop, *vv_it) = true ;
|
||||||
|
next_bnd.push_back(*vv_it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next_bnd ;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***** Computation of RBF
|
||||||
|
|
||||||
|
pair<pair<Eigen::MatrixXd &, Eigen::VectorXd &>, vector<MyMesh::Point> &> Hole_Filling::compute_approx_mat(vector<MyMesh::VertexHandle> vlist, double normal_scale)
|
||||||
|
{
|
||||||
|
const int n(vlist.size()), d(10) ;
|
||||||
|
Eigen::MatrixXd & A = *(new Eigen::MatrixXd(3*n+d,3*n+d)) ;
|
||||||
|
Eigen::MatrixXd Phi(3*n,3*n);
|
||||||
|
Eigen::MatrixXd P(3*n,d);
|
||||||
|
Eigen::VectorXd & B = *(new Eigen::VectorXd(3*n+d));
|
||||||
|
|
||||||
|
vector<MyMesh::Point> & pts_list = *(new vector<MyMesh::Point>);
|
||||||
|
//Append vertices to pts_list
|
||||||
|
for (int i=0; i<n; i++)
|
||||||
|
{
|
||||||
|
pts_list.push_back(_mesh.point(vlist.at(i))) ;
|
||||||
|
}
|
||||||
|
//Append vertices+normals to pts_list
|
||||||
|
for (int i=0; i<n; i++)
|
||||||
|
{
|
||||||
|
pts_list.push_back(_mesh.point(vlist.at(i)) + _mesh.normal(vlist.at(i)) * normal_scale) ;
|
||||||
|
}
|
||||||
|
//Append vertices-normals to pts_list
|
||||||
|
for (int i=0; i<n; i++)
|
||||||
|
{
|
||||||
|
pts_list.push_back(_mesh.point(vlist.at(i)) - _mesh.normal(vlist.at(i)) * normal_scale) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nn = pts_list.size() ;
|
||||||
|
// Compute corresponding B vector (0 / 1 / -1 )
|
||||||
|
B << Eigen::VectorXd::Zero(n), Eigen::VectorXd::Ones(n), -Eigen::VectorXd::Ones(n), Eigen::VectorXd::Zero(d) ;
|
||||||
|
|
||||||
|
// Fill Phi matrix
|
||||||
|
//TODO
|
||||||
|
for (int i = 0; i < 3*n; i++) {
|
||||||
|
for (int j = 0; j <= i; j++) {
|
||||||
|
Phi(i, j) = myphi((pts_list[i] - pts_list[j]).norm());
|
||||||
|
Phi(j, i) = Phi(i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill P matrix
|
||||||
|
for (int i = 0; i < 3*n; i++) {
|
||||||
|
for (int j = 0; j < d; j++) {
|
||||||
|
P(i, j) = myp(j, pts_list[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set final A matrix
|
||||||
|
/* A = Phi | P
|
||||||
|
* P' | 0 */
|
||||||
|
A << Phi, P, P.transpose(), Eigen::MatrixXd::Zero(d, d);
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
cout << "size of pts_list : " << nn << endl ;
|
||||||
|
cout << "End computation of matrices" << endl ;
|
||||||
|
|
||||||
|
return {{A,B},pts_list} ;
|
||||||
|
}
|
||||||
|
|
||||||
|
pair<vector<float>&, vector<float>&> Hole_Filling::solve_approx(const pair<Eigen::MatrixXd &, Eigen::VectorXd &> &p, int n, int d)
|
||||||
|
{
|
||||||
|
Eigen::MatrixXd & A = p.first ;
|
||||||
|
Eigen::VectorXd & B = p.second ;
|
||||||
|
|
||||||
|
Eigen::VectorXd res = A.householderQr().solve(B) ;
|
||||||
|
|
||||||
|
cout << "End of solver" << endl ;
|
||||||
|
cout << "res : " << res.head(10) << endl ;
|
||||||
|
vector<float> & alpha = *(new vector<float>);
|
||||||
|
vector<float> & beta = *(new vector<float>);
|
||||||
|
|
||||||
|
if (res.size() != (n+d))
|
||||||
|
{
|
||||||
|
cout << "taille du res : " << res.size() << endl ;
|
||||||
|
throw std::runtime_error("Error in solve_approx") ;
|
||||||
|
}
|
||||||
|
for (int i=0; i<n; i++)
|
||||||
|
{
|
||||||
|
alpha.push_back(res(i)) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j=0; j<d; j++)
|
||||||
|
{
|
||||||
|
beta.push_back(res(n+j)) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {alpha, beta} ;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***** IO
|
||||||
|
|
||||||
|
void Hole_Filling::colorize_prop()
|
||||||
|
{
|
||||||
|
for (MyMesh::VertexIter v_it = _mesh.vertices_begin(); v_it != _mesh.vertices_end() ; ++v_it)
|
||||||
|
{
|
||||||
|
if(_mesh.property(_vprop, *v_it) == true)
|
||||||
|
_mesh.set_color(*v_it, MyMesh::Color(255, 0, 0)) ;
|
||||||
|
else
|
||||||
|
_mesh.set_color(*v_it, MyMesh::Color(200, 200, 200)) ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Hole_Filling::colorize_verts(const vector<MyMesh::VertexHandle> &vlist)
|
||||||
|
{
|
||||||
|
for (MyMesh::VertexIter v_it = _mesh.vertices_begin(); v_it != _mesh.vertices_end() ; ++v_it)
|
||||||
|
{
|
||||||
|
_mesh.set_color(*v_it, MyMesh::Color(200, 200, 200)) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i=0; i<vlist.size(); i++)
|
||||||
|
{
|
||||||
|
_mesh.set_color(vlist.at(i), MyMesh::Color(255, 0, 0)) ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect3 Hole_Filling::estimate_BB(const vector<MyMesh::VertexHandle> &vlist)
|
||||||
|
{
|
||||||
|
MyMesh::Point minp = _mesh.point(vlist.at(0)), maxp=minp ;
|
||||||
|
for (int i=0; i<vlist.size(); i++)
|
||||||
|
{
|
||||||
|
minp = min(minp, _mesh.point(vlist.at(i))) ;
|
||||||
|
maxp = max(maxp, _mesh.point(vlist.at(i))) ;
|
||||||
|
}
|
||||||
|
MyMesh::Point sizep(maxp-minp), centerp = (minp+maxp)/2 ;
|
||||||
|
minp = centerp - _scale*sizep/2 ;
|
||||||
|
sizep *= _scale ;
|
||||||
|
|
||||||
|
Rect3 domain ;
|
||||||
|
domain.min = {minp[0], minp[1], minp[2]} ;
|
||||||
|
domain.size = {sizep[0], sizep[1], sizep[2]} ;
|
||||||
|
return domain ;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mesh Hole_Filling::poly_n_out(const Implicit_RBF &implicit, Rect3 domain)
|
||||||
|
{
|
||||||
|
auto implicitEq = [&implicit](Vec3 const& pos)
|
||||||
|
{
|
||||||
|
MyMesh::Point X(pos.x, pos.y, pos.z) ;
|
||||||
|
return implicit.val(X) ;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Vec3 cubeSize(domain.size*(_discr)) ;
|
||||||
|
cout << "mind " << domain.min << endl ;
|
||||||
|
cout << "sized" << domain.size << endl ;
|
||||||
|
cout << "cubesize "<< cubeSize << endl ;
|
||||||
|
|
||||||
|
return MarchCube(implicitEq, domain, cubeSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Computes a mesh's bounding box and stores it in a mesh property
|
||||||
|
* named "bounding_box". */
|
||||||
|
static void computeMeshBoundingBox(MyMesh &mesh, Hole_Filling &hf) {
|
||||||
|
try {
|
||||||
|
auto mesh_bb = OpenMesh::getProperty<void, Rect3>
|
||||||
|
(mesh, "bounding_box");
|
||||||
|
} catch (const std::runtime_error &e) {
|
||||||
|
auto mesh_bb = OpenMesh::getOrMakeProperty<void, Rect3>
|
||||||
|
(mesh, "bounding_box");
|
||||||
|
std::vector<VertexHandle> verts;
|
||||||
|
for (VertexHandle vh : mesh.vertices()) {
|
||||||
|
verts.push_back(vh);
|
||||||
|
}
|
||||||
|
*mesh_bb = hf.estimate_BB(verts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MyMesh fillHoleImplicit(MyMesh &mesh, Hole_Filling &hf,
|
||||||
|
std::vector<HalfedgeHandle> &hole) {
|
||||||
|
computeMeshBoundingBox(mesh, hf);
|
||||||
|
Rect3 mesh_bb = *OpenMesh::getProperty<void, Rect3>(mesh, "bounding_box");
|
||||||
|
double diag = mesh_bb.size.Norm();
|
||||||
|
|
||||||
|
std::vector<VertexHandle> verts;
|
||||||
|
for (HalfedgeHandle hh : hole) {
|
||||||
|
verts.push_back(mesh.to_vertex_handle(hh));
|
||||||
|
}
|
||||||
|
auto bb = hf.estimate_BB(verts) ;
|
||||||
|
verts = hf.next_neighbors(verts);
|
||||||
|
auto [system, pts_list] = hf.compute_approx_mat(verts, diag * .1);
|
||||||
|
auto [alpha, beta] = hf.solve_approx(system, pts_list.size(), 10);
|
||||||
|
Implicit_RBF rbf(alpha, beta, pts_list);
|
||||||
|
|
||||||
|
Mesh filling = hf.poly_n_out(rbf, bb);
|
||||||
|
MyMesh ret;
|
||||||
|
for (const Vec3 &v : filling.vertices) {
|
||||||
|
VertexHandle vh = ret.new_vertex({v.x, v.y, v.z});
|
||||||
|
ret.set_color(vh, ret.default_color);
|
||||||
|
}
|
||||||
|
for (const Triangle &t : filling.triangles) {
|
||||||
|
ret.add_face(ret.vertex_handle(t[0]),
|
||||||
|
ret.vertex_handle(t[1]),
|
||||||
|
ret.vertex_handle(t[2]));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<MyMesh> fillHolesImplicit(MyMesh &mesh,
|
||||||
|
float scale, float discr) {
|
||||||
|
mesh.holes = findHoles(mesh);
|
||||||
|
std::vector<MyMesh> fillings;
|
||||||
|
for (auto hole : mesh.holes) {
|
||||||
|
Hole_Filling hf(mesh, scale, discr);
|
||||||
|
fillings.push_back(fillHoleImplicit(mesh, hf, hole));
|
||||||
|
}
|
||||||
|
return fillings;
|
||||||
|
}
|
124
src/hole_filling.h
Normal file
124
src/hole_filling.h
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
#ifndef HOLE_FILLING_H
|
||||||
|
#define HOLE_FILLING_H
|
||||||
|
|
||||||
|
#include "my_mesh.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cmath>
|
||||||
|
#include <Eigen/Core>
|
||||||
|
#include <Eigen/Geometry>
|
||||||
|
#include <Eigen/Dense>
|
||||||
|
#include <MeshReconstruction.h>
|
||||||
|
#include <IO.h>
|
||||||
|
|
||||||
|
|
||||||
|
void fillHoleDumb(MyMesh &mesh, std::vector<HalfedgeHandle> &hole);
|
||||||
|
void fillHolesDumb(MyMesh &mesh);
|
||||||
|
std::vector<MyMesh> fillHolesImplicit(MyMesh &mesh, float scale, float discr);
|
||||||
|
|
||||||
|
using namespace std ;
|
||||||
|
using namespace MeshReconstruction;
|
||||||
|
|
||||||
|
// ******************************
|
||||||
|
// Function computing polynomial P_i (degree 2 polynomials in 3 variables)
|
||||||
|
inline float myp(int i, MyMesh::Point X) {
|
||||||
|
float x = X[0] ;
|
||||||
|
float y = X[1] ;
|
||||||
|
float z = X[2] ;
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
return x*x ;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
return y*y ;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
return z*z ;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
return x*y ;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
return y*z ;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
return x*z ;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
return x ;
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
return y ;
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
return z;
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
return 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Error on indice i : unknown polynomial P.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ******************************
|
||||||
|
// Function computing thin spline RBF
|
||||||
|
inline float myphi(float r) { if (r == 0) return 0 ; else return r*r*log(r) ; }
|
||||||
|
|
||||||
|
// ******************************
|
||||||
|
// Class encoding an implicit function built from the basis of RBF
|
||||||
|
// f(X) = Sum_i=0^n-1 alpha_i * phi(|| X-c_i ||) + Sum_j=0^9 beta_j * P_j(X)
|
||||||
|
class Implicit_RBF {
|
||||||
|
private:
|
||||||
|
int _n ;
|
||||||
|
int _d ;
|
||||||
|
vector<float> _alpha, _beta ;
|
||||||
|
vector<MyMesh::Point> _center ;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Implicit_RBF (vector<float> alpha, vector<float> beta, vector<MyMesh::Point> center) ;
|
||||||
|
float val(MyMesh::Point) const ;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ******************************
|
||||||
|
// Class encoding all the functions required for implicit hole filling
|
||||||
|
class Hole_Filling
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
MyMesh &_mesh ;
|
||||||
|
OpenMesh::VPropHandleT<bool> _vprop ;
|
||||||
|
|
||||||
|
// Constantes servant au rendu
|
||||||
|
// TODO : à mettre dans des boutons ...
|
||||||
|
const float _scale; // Boîte englobante de rendu = _scale * BB des centres
|
||||||
|
const float _discr; // BB discrétisée en 1/_discr voxels
|
||||||
|
public:
|
||||||
|
Hole_Filling(MyMesh &mesh, float scale, float discr);
|
||||||
|
inline ~Hole_Filling() { std::cout << "Ending hole filling" << std::endl ; }
|
||||||
|
|
||||||
|
// Computation of boundary and its neighborhood
|
||||||
|
MyMesh::HalfedgeHandle find_boundary_edge() ;
|
||||||
|
vector<MyMesh::VertexHandle> find_boundary(MyMesh::HalfedgeHandle heh) ;
|
||||||
|
void init_mark_boundary(const vector<MyMesh::VertexHandle> & bnd) ;
|
||||||
|
vector<MyMesh::VertexHandle> next_neighbors(const vector<MyMesh::VertexHandle> & bnd) ;
|
||||||
|
|
||||||
|
// Computation of RBF
|
||||||
|
pair<pair<Eigen::MatrixXd &,Eigen::VectorXd &>,vector<MyMesh::Point> &> compute_approx_mat(vector<MyMesh::VertexHandle> vlist, double normal_scale=1) ;
|
||||||
|
pair<vector<float>&, vector<float>&> solve_approx(const pair<Eigen::MatrixXd &, Eigen::VectorXd &> &p, int n, int d) ;
|
||||||
|
|
||||||
|
// IO
|
||||||
|
void colorize_prop() ;
|
||||||
|
void colorize_verts(const vector<MyMesh::VertexHandle> &vlist) ;
|
||||||
|
Rect3 estimate_BB(const vector<MyMesh::VertexHandle> &vlist) ;
|
||||||
|
Mesh poly_n_out (const Implicit_RBF & implicit, Rect3 domain) ;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Other ....
|
||||||
|
|
||||||
|
inline MyMesh::Point min (MyMesh::Point X, MyMesh::Point Y) { MyMesh::Point P ; for (int i=0; i<3; i++) P[i] = min(X[i], Y[i]) ; return P ;}
|
||||||
|
inline MyMesh::Point max (MyMesh::Point X, MyMesh::Point Y) { MyMesh::Point P ; for (int i=0; i<3; i++) P[i] = max(X[i], Y[i]) ; return P ;}
|
||||||
|
|
||||||
|
inline ostream & operator<< (ostream & out, Vec3 v) { out << v.x << ", " << v.y << ", " << v.z ; return out ;}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
48
src/main.cpp
48
src/main.cpp
@ -5,11 +5,37 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
|
||||||
|
|
||||||
|
static MeshProcessor *create_mesh_processor(const QString &path,
|
||||||
|
MainWindow &main_window) {
|
||||||
|
MeshViewer &mesh_viewer = main_window.mesh_viewer;
|
||||||
|
MeshProcessor *mesh_processor = new MeshProcessor(path, mesh_viewer,
|
||||||
|
main_window.fillHolesImplicitScale(),
|
||||||
|
main_window.fillHolesImplicitDiscr());
|
||||||
|
QObject::connect(&main_window, &MainWindow::fillHolesDumbClicked,
|
||||||
|
mesh_processor, &MeshProcessor::fillHolesDumb);
|
||||||
|
QObject::connect(&main_window, &MainWindow::fillHolesImplicitClicked,
|
||||||
|
mesh_processor, &MeshProcessor::fillHolesImplicit);
|
||||||
|
QObject::connect(&main_window, &MainWindow::smoothUniformClicked,
|
||||||
|
mesh_processor, &MeshProcessor::smoothUniform);
|
||||||
|
QObject::connect(&main_window, &MainWindow::smoothCotangentClicked,
|
||||||
|
mesh_processor, &MeshProcessor::smoothCotangent);
|
||||||
|
QObject::connect(&main_window, &MainWindow::patchViewToggled,
|
||||||
|
mesh_processor, &MeshProcessor::setPatchView);
|
||||||
|
QObject::connect(&main_window, &MainWindow::fillHolesImplicitScaleChanged,
|
||||||
|
mesh_processor, &MeshProcessor::setImplicitHoleFillingScale);
|
||||||
|
QObject::connect(&main_window, &MainWindow::fillHolesImplicitDiscrChanged,
|
||||||
|
mesh_processor, &MeshProcessor::setImplicitHoleFillingDiscr);
|
||||||
|
QObject::connect(&main_window, &MainWindow::filterNoiseClicked,
|
||||||
|
mesh_processor, &MeshProcessor::removeNoise);
|
||||||
|
return mesh_processor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
using namespace std;
|
using namespace std;
|
||||||
QSurfaceFormat format;
|
QSurfaceFormat format;
|
||||||
format.setRenderableType(QSurfaceFormat::OpenGL);
|
format.setRenderableType(QSurfaceFormat::OpenGL);
|
||||||
#ifndef QT_DEBUG
|
#ifdef QT_DEBUG
|
||||||
qDebug("Debug build");
|
qDebug("Debug build");
|
||||||
format.setOption(QSurfaceFormat::DebugContext);
|
format.setOption(QSurfaceFormat::DebugContext);
|
||||||
#endif
|
#endif
|
||||||
@ -17,29 +43,17 @@ int main(int argc, char *argv[]) {
|
|||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
MeshProcessor *mesh_processor = nullptr;
|
MeshProcessor *mesh_processor = nullptr;
|
||||||
MainWindow main_window;
|
MainWindow main_window;
|
||||||
MeshViewer *mesh_viewer = &main_window.mesh_viewer;
|
|
||||||
QObject::connect(mesh_viewer, &MeshViewer::initialized,
|
|
||||||
[&]() {
|
|
||||||
if (mesh_processor) {
|
|
||||||
mesh_viewer->addMesh(mesh_processor->mesh);
|
|
||||||
mesh_viewer->addMesh(mesh_processor->patch);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (argc > 2) {
|
if (argc > 2) {
|
||||||
qWarning("Utilisation : %s [MAILLAGE]", argv[0]);
|
qWarning("Utilisation : %s [MAILLAGE]", argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
} else if (argc == 2) {
|
} else if (argc == 2) {
|
||||||
mesh_processor = new MeshProcessor(argv[1]);
|
mesh_processor = create_mesh_processor(argv[1], main_window);
|
||||||
}
|
}
|
||||||
QObject::connect(&main_window, &MainWindow::open,
|
QObject::connect(&main_window, &MainWindow::open,
|
||||||
[&](const QString &path) {
|
[&](const QString &path) {
|
||||||
if (mesh_processor) {
|
if (mesh_processor) delete mesh_processor;
|
||||||
mesh_viewer->removeMesh(mesh_processor->mesh);
|
mesh_processor = create_mesh_processor
|
||||||
delete mesh_processor;
|
(path, main_window);
|
||||||
}
|
|
||||||
mesh_processor = new MeshProcessor(path);
|
|
||||||
mesh_viewer->addMesh(mesh_processor->mesh);
|
|
||||||
});
|
});
|
||||||
main_window.show();
|
main_window.show();
|
||||||
return app.exec();
|
return app.exec();
|
||||||
|
@ -1,35 +1,132 @@
|
|||||||
#include "main_window.h"
|
#include "main_window.h"
|
||||||
#include "mesh_processor.h"
|
#include "mesh_processor.h"
|
||||||
|
#include "double_input.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
|
#include <QMenuBar>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QSlider>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <qnamespace.h>
|
||||||
|
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget *parent)
|
MainWindow::MainWindow(QWidget *parent)
|
||||||
:QMainWindow(parent),
|
:QMainWindow(parent),
|
||||||
toolbar(this),
|
toolbar(this),
|
||||||
mesh_viewer(this) {
|
mesh_viewer() {
|
||||||
connect(&mesh_viewer, &MeshViewer::initialized, [&]() {
|
|
||||||
open_action->setEnabled(true);
|
|
||||||
});
|
|
||||||
setCentralWidget(&mesh_viewer);
|
setCentralWidget(&mesh_viewer);
|
||||||
addToolBar(Qt::RightToolBarArea, &toolbar);
|
|
||||||
open_action = toolbar.addAction("Ouvrir…", [&](){
|
QMenuBar *menu_bar = new QMenuBar();
|
||||||
|
setMenuBar(menu_bar);
|
||||||
|
|
||||||
|
// File menu
|
||||||
|
QMenu *file_menu = new QMenu("Fichier");
|
||||||
|
open_action = file_menu->addAction("Ouvrir…", [&](){
|
||||||
emit open(QFileDialog::getOpenFileName(this, "Ouvrir un maillage"));
|
emit open(QFileDialog::getOpenFileName(this, "Ouvrir un maillage"));
|
||||||
});
|
});
|
||||||
// toolbar_actions.append(toolbar.addAction("Fractionner", [&](){
|
save_action = file_menu->addAction("Enregistrer sous…", [&]() {
|
||||||
// QVector<QPair<MyMesh::Point, MyMesh>> fragments = shatter(mesh);
|
emit save(QFileDialog::getSaveFileName(this,
|
||||||
// mesh_viewer.removeOpenGLMesh(glm);
|
"Enregistrer un maillage"));
|
||||||
// for (auto &[pos, fragment] : fragments) {
|
});
|
||||||
// fragment.triangulate();
|
menu_bar->addMenu(file_menu);
|
||||||
// QMatrix4x4 mat;
|
if (!mesh_viewer.isInitialized()) {
|
||||||
// float scale = 1.2;
|
open_action->setEnabled(false);
|
||||||
// mat.translate(pos[0] * scale, pos[1] * scale, pos[2] * scale);
|
connect(&mesh_viewer, &MeshViewer::initialized, [&]() {
|
||||||
// mesh_viewer.addOpenGLMeshFromOpenMesh(&fragment, mat);
|
open_action->setEnabled(true);
|
||||||
// }
|
});
|
||||||
// }));
|
|
||||||
open_action->setEnabled(false);
|
|
||||||
for (QAction *a : toolbar_actions) {
|
|
||||||
a->setEnabled(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addToolBar(Qt::RightToolBarArea, &toolbar);
|
||||||
|
|
||||||
|
// Hole filling tools
|
||||||
|
QGroupBox *hole_box = new QGroupBox("Remplissage de trous");
|
||||||
|
QGridLayout *hole_layout = new QGridLayout();
|
||||||
|
hole_box->setLayout(hole_layout);
|
||||||
|
|
||||||
|
QPushButton *fill_holes_dumb = new QPushButton("Remplir bêtement");
|
||||||
|
connect(fill_holes_dumb, &QPushButton::clicked,
|
||||||
|
this, &MainWindow::fillHolesDumbClicked);
|
||||||
|
hole_layout->addWidget(fill_holes_dumb, 0, 0);
|
||||||
|
|
||||||
|
QPushButton *fill_holes_implicit =
|
||||||
|
new QPushButton("Remplir par une surface implicite");
|
||||||
|
connect(fill_holes_implicit, &QPushButton::clicked,
|
||||||
|
this, &MainWindow::fillHolesImplicitClicked);
|
||||||
|
hole_layout->addWidget(fill_holes_implicit, 1, 0);
|
||||||
|
|
||||||
|
QLabel *implicit_scale_text =
|
||||||
|
new QLabel("Échelle du remplissage implicite", this);
|
||||||
|
hole_layout->addWidget(implicit_scale_text, 2, 0);
|
||||||
|
fill_holes_implicit_scale = new DoubleInput(this, 0, 10, 4);
|
||||||
|
connect(fill_holes_implicit_scale, &DoubleInput::valueChanged,
|
||||||
|
this, &MainWindow::fillHolesImplicitScaleChanged);
|
||||||
|
hole_layout->addWidget(fill_holes_implicit_scale->slider(), 3, 0);
|
||||||
|
hole_layout->addWidget(fill_holes_implicit_scale->spinBox(), 3, 1);
|
||||||
|
|
||||||
|
QLabel *implicit_discr_text =
|
||||||
|
new QLabel("Taux de discrétisation du remplissage implicite", this);
|
||||||
|
hole_layout->addWidget(implicit_discr_text, 4, 0);
|
||||||
|
fill_holes_implicit_discr = new DoubleInput(this, .02, .1, 1/40.);
|
||||||
|
connect(fill_holes_implicit_discr, &DoubleInput::valueChanged,
|
||||||
|
this, &MainWindow::fillHolesImplicitDiscrChanged);
|
||||||
|
hole_layout->addWidget(fill_holes_implicit_discr->slider(), 5, 0);
|
||||||
|
hole_layout->addWidget(fill_holes_implicit_discr->spinBox(), 5, 1);
|
||||||
|
|
||||||
|
toolbar.addWidget(hole_box);
|
||||||
|
|
||||||
|
|
||||||
|
// Smoothing tools
|
||||||
|
QGroupBox *smooth_box = new QGroupBox("Adoucissement");
|
||||||
|
QGridLayout *smooth_layout = new QGridLayout();
|
||||||
|
smooth_box->setLayout(smooth_layout);
|
||||||
|
QPushButton *smooth = new QPushButton("Adoucir (uniforme)");
|
||||||
|
connect(smooth, &QPushButton::clicked,
|
||||||
|
this, &MainWindow::smoothUniformClicked);
|
||||||
|
smooth_layout->addWidget(smooth, 1, 0);
|
||||||
|
QPushButton *smooth_cotan = new QPushButton("Adoucir (cotangent)");
|
||||||
|
smooth_cotangent_factor_input =
|
||||||
|
new DoubleInput(this, .00001, .001, .0001);
|
||||||
|
connect(smooth_cotangent_factor_input, &DoubleInput::valueChanged,
|
||||||
|
[&](double value) { smooth_cotangent_factor = value; });
|
||||||
|
connect(smooth_cotan, &QPushButton::clicked,
|
||||||
|
[&]() { emit smoothCotangentClicked(smooth_cotangent_factor); });
|
||||||
|
smooth_layout->addWidget(smooth_cotan, 2, 0);
|
||||||
|
QLabel *smooth_cotan_text =
|
||||||
|
new QLabel("Facteur de l'adoucissement cotangentiel", this);
|
||||||
|
smooth_layout->addWidget(smooth_cotan_text, 3, 0);
|
||||||
|
smooth_layout->addWidget(smooth_cotangent_factor_input->slider(), 4, 0);
|
||||||
|
QDoubleSpinBox *sb = (QDoubleSpinBox *)(smooth_cotangent_factor_input->spinBox());
|
||||||
|
sb->setDecimals(5);
|
||||||
|
smooth_layout->addWidget(smooth_cotangent_factor_input->spinBox(), 4, 1);
|
||||||
|
toolbar.addWidget(smooth_box);
|
||||||
|
|
||||||
|
|
||||||
|
// Curvature tools
|
||||||
|
QGroupBox *curvature_box = new QGroupBox("Analyse de courbure");
|
||||||
|
QLayout *curvature_layout = new QVBoxLayout();
|
||||||
|
curvature_box->setLayout(curvature_layout);
|
||||||
|
QPushButton *patch_mode = new QPushButton(
|
||||||
|
"Afficher le patch de la sélection");
|
||||||
|
patch_mode->setCheckable(true);
|
||||||
|
connect(patch_mode, &QPushButton::toggled,
|
||||||
|
this, &MainWindow::patchViewToggled);
|
||||||
|
curvature_layout->addWidget(patch_mode);
|
||||||
|
toolbar.addWidget(curvature_box);
|
||||||
|
|
||||||
|
|
||||||
|
// Noise cleaning
|
||||||
|
QGroupBox *noise_box = new QGroupBox("Élagage");
|
||||||
|
QLayout *noise_layout = new QVBoxLayout();
|
||||||
|
noise_box->setLayout(noise_layout);
|
||||||
|
QSlider *noise_slider = new QSlider(Qt::Horizontal);
|
||||||
|
QPushButton *noise_button = new QPushButton("Élaguer");
|
||||||
|
connect(noise_button, &QPushButton::clicked,
|
||||||
|
[=]() {
|
||||||
|
emit filterNoiseClicked(noise_slider->value());
|
||||||
|
});
|
||||||
|
noise_layout->addWidget(noise_slider);
|
||||||
|
noise_layout->addWidget(noise_button);
|
||||||
|
toolbar.addWidget(noise_box);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
#ifndef MAIN_WINDOW_H
|
#ifndef MAIN_WINDOW_H
|
||||||
#define MAIN_WINDOW_H
|
#define MAIN_WINDOW_H
|
||||||
|
|
||||||
#include <QMainWindow>
|
|
||||||
#include <QToolBar>
|
|
||||||
|
|
||||||
#include "mesh_viewer.h"
|
#include "mesh_viewer.h"
|
||||||
#include "my_mesh.h"
|
#include "my_mesh.h"
|
||||||
|
#include "double_input.h"
|
||||||
|
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QToolBar>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QDoubleSpinBox>
|
||||||
|
|
||||||
|
|
||||||
class MainWindow : public QMainWindow {
|
class MainWindow : public QMainWindow {
|
||||||
@ -13,15 +16,34 @@ class MainWindow : public QMainWindow {
|
|||||||
|
|
||||||
QToolBar toolbar;
|
QToolBar toolbar;
|
||||||
QAction *open_action;
|
QAction *open_action;
|
||||||
QList<QAction *> toolbar_actions;
|
QAction *save_action;
|
||||||
|
DoubleInput *fill_holes_implicit_scale;
|
||||||
|
DoubleInput *fill_holes_implicit_discr;
|
||||||
|
DoubleInput *smooth_cotangent_factor_input;
|
||||||
|
double smooth_cotangent_factor;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void open(const QString &path);
|
void open(const QString &path);
|
||||||
|
void save(const QString &path);
|
||||||
|
void fillHolesDumbClicked();
|
||||||
|
void fillHolesImplicitClicked();
|
||||||
|
void fillHolesImplicitScaleChanged(float value);
|
||||||
|
void fillHolesImplicitDiscrChanged(float value);
|
||||||
|
void smoothUniformClicked();
|
||||||
|
void smoothCotangentClicked(double factor);
|
||||||
|
void patchViewToggled(bool checked);
|
||||||
|
void filterNoiseClicked(int value);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MeshViewer mesh_viewer;
|
MeshViewer mesh_viewer;
|
||||||
|
|
||||||
MainWindow(QWidget *parent=nullptr);
|
MainWindow(QWidget *parent=nullptr);
|
||||||
|
double fillHolesImplicitScale() const {
|
||||||
|
return fill_holes_implicit_scale->value();
|
||||||
|
}
|
||||||
|
double fillHolesImplicitDiscr() const {
|
||||||
|
return fill_holes_implicit_discr->value();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,75 +1,144 @@
|
|||||||
#include "quad_patch_tesselator.h"
|
#include "quad_patch_tesselator.h"
|
||||||
#include "mesh_processor.h"
|
#include "mesh_processor.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include "hole_filling.h"
|
||||||
#include "smoothing.h"
|
#include "smoothing.h"
|
||||||
#include "curvature.h"
|
#include "curvature.h"
|
||||||
|
#include "noise_removal.h"
|
||||||
#include <QGenericMatrix>
|
#include <QGenericMatrix>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
|
|
||||||
static std::vector<std::vector<HalfedgeHandle>> findHoles(MyMesh &mesh) {
|
MeshProcessor::MeshProcessor(const QString &path, MeshViewer &mesh_viewer,
|
||||||
std::vector<std::vector<HalfedgeHandle>> holes;
|
double implicit_hole_filling_scale,
|
||||||
std::set<HalfedgeHandle> ignore;
|
double implicit_hole_filling_discr)
|
||||||
for (HalfedgeHandle it : mesh.halfedges()) {
|
:mesh_viewer(mesh_viewer),
|
||||||
if (mesh.is_boundary(it)
|
implicit_hole_filling_scale(implicit_hole_filling_scale),
|
||||||
&& ignore.find(it) == ignore.end()) {
|
implicit_hole_filling_discr(implicit_hole_filling_discr) {
|
||||||
holes.emplace_back();
|
|
||||||
holes.back().push_back(it);
|
|
||||||
ignore.insert(it);
|
|
||||||
for (HalfedgeHandle it2 : HalfedgeLoopRange<MyMesh>(mesh, it)) {
|
|
||||||
holes.back().push_back(it2);
|
|
||||||
ignore.insert(it2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return holes;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
MeshProcessor::MeshProcessor(const QString &path) {
|
|
||||||
OpenMesh::IO::Options options;
|
OpenMesh::IO::Options options;
|
||||||
options.set(OpenMesh::IO::Options::VertexColor);
|
options.set(OpenMesh::IO::Options::VertexColor);
|
||||||
if (!OpenMesh::IO::read_mesh(mesh, path.toUtf8().constData(), options)) {
|
if (!OpenMesh::IO::read_mesh(mesh, path.toUtf8().constData(), options)) {
|
||||||
qWarning() << "Failed to read" << path;
|
qWarning() << "Failed to read" << path;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mesh.update_normals();
|
for (VertexHandle vh : mesh.vertices()) {
|
||||||
Courbures courb(mesh);
|
mesh.set_color(vh, MyMesh::Color(.5, .5, .5));
|
||||||
courb.compute_KH();
|
}
|
||||||
courb.set_K_colors();
|
courbure = new Courbures(mesh);
|
||||||
VertexHandle vh = mesh.vertex_handle(16);
|
try {
|
||||||
QuadPatch q = mesh.property(courb.vprop_quad, vh);
|
courbure->compute_KH();
|
||||||
patch = tesselate_quad_patch(q, mesh, vh);
|
courbure->set_K_colors();
|
||||||
// mesh.holes = findHoles(mesh);
|
} catch (std::runtime_error &e) {
|
||||||
// fillHoles();
|
qWarning() << "Curvature computation failed";
|
||||||
// smooth(mesh);
|
}
|
||||||
|
connect(&mesh_viewer, &MeshViewer::clicked, this, &MeshProcessor::click);
|
||||||
|
|
||||||
|
updateView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void fillHoleDumb(MyMesh &mesh, std::vector<HalfedgeHandle> &hole) {
|
MeshProcessor::~MeshProcessor() {
|
||||||
mesh.request_vertex_status();
|
if (mesh_viewer.isInitialized()) {
|
||||||
mesh.request_edge_status();
|
mesh_viewer.removeMesh(mesh);
|
||||||
mesh.request_face_status();
|
|
||||||
|
|
||||||
Point center(0, 0, 0);
|
|
||||||
size_t length = 0;
|
|
||||||
for (HalfedgeHandle it : hole) {
|
|
||||||
VertexHandle vert = mesh.to_vertex_handle(it);
|
|
||||||
center += mesh.point(vert);
|
|
||||||
length++;
|
|
||||||
}
|
}
|
||||||
center /= length;
|
if (courbure) delete courbure;
|
||||||
VertexHandle center_handle = mesh.new_vertex_dirty(center);
|
}
|
||||||
|
|
||||||
for (HalfedgeHandle it : hole) {
|
|
||||||
mesh.add_face(mesh.from_vertex_handle(it),
|
void MeshProcessor::updateView() {
|
||||||
mesh.to_vertex_handle(it),
|
if (mesh_viewer.isInitialized()) {
|
||||||
center_handle);
|
mesh_viewer.removeMesh(mesh);
|
||||||
|
mesh_viewer.addMesh(mesh);
|
||||||
|
mesh_viewer.updateForReal();
|
||||||
|
} else {
|
||||||
|
connect(&mesh_viewer, &MeshViewer::initialized, [&]() {
|
||||||
|
mesh_viewer.addMesh(mesh);
|
||||||
|
mesh_viewer.updateForReal();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshProcessor::fillHoles() {
|
|
||||||
for (auto hole : mesh.holes) {
|
void MeshProcessor::fillHolesDumb() {
|
||||||
fillHoleDumb(mesh, hole);
|
::fillHolesDumb(mesh);
|
||||||
|
updateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MeshProcessor::fillHolesImplicit() {
|
||||||
|
for (MyMesh &filling : fillings) {
|
||||||
|
mesh_viewer.removeMesh(filling);
|
||||||
}
|
}
|
||||||
}
|
std::vector<MyMesh> new_fillings =
|
||||||
|
::fillHolesImplicit(mesh,
|
||||||
|
implicit_hole_filling_scale,
|
||||||
|
implicit_hole_filling_discr);
|
||||||
|
for (MyMesh &filling : new_fillings) {
|
||||||
|
fillings.push_back(filling);
|
||||||
|
mesh_viewer.addMesh(fillings[fillings.size() - 1]);
|
||||||
|
}
|
||||||
|
updateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MeshProcessor::setImplicitHoleFillingScale(float scale) {
|
||||||
|
implicit_hole_filling_scale = scale;
|
||||||
|
if (fillings.size() > 0) {
|
||||||
|
fillHolesImplicit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MeshProcessor::setImplicitHoleFillingDiscr(float discr) {
|
||||||
|
implicit_hole_filling_discr = discr;
|
||||||
|
if (fillings.size() > 0) {
|
||||||
|
fillHolesImplicit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MeshProcessor::smoothCotangent(double factor) {
|
||||||
|
::smooth(mesh, SmoothingMethod::COTANGENT, factor);
|
||||||
|
updateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MeshProcessor::smoothUniform() {
|
||||||
|
::smooth(mesh, SmoothingMethod::UNIFORM);
|
||||||
|
updateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MeshProcessor::setPatchView(bool on) {
|
||||||
|
view_patches = on;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MeshProcessor::click(QVector3D position) {
|
||||||
|
if (!view_patches) return;
|
||||||
|
Eigen::Vector3d pos {position.x(), position.y(), position.z()};
|
||||||
|
MyMesh::VertexIter it = mesh.vertices_begin();
|
||||||
|
VertexHandle min_vert = *it;
|
||||||
|
double min_dist = (mesh.point(*it) - pos).squaredNorm();
|
||||||
|
for (; it != mesh.vertices_end(); ++it) {
|
||||||
|
double dist = (mesh.point(*it) - pos).squaredNorm();
|
||||||
|
if (dist < min_dist) {
|
||||||
|
min_dist = dist;
|
||||||
|
min_vert = *it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QuadPatch q = mesh.property(courbure->vprop_quad, min_vert);
|
||||||
|
patch = tesselate_quad_patch(q, mesh, min_vert);
|
||||||
|
mesh_viewer.removeMesh(patch);
|
||||||
|
mesh_viewer.addMesh(patch);
|
||||||
|
mesh_viewer.updateForReal();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MeshProcessor::removeNoise(int value) {
|
||||||
|
std::cout << value << std::endl;
|
||||||
|
::remove_noise(mesh, value);
|
||||||
|
mesh_viewer.removeMesh(mesh);
|
||||||
|
mesh_viewer.addMesh(mesh);
|
||||||
|
mesh_viewer.updateForReal();
|
||||||
|
}
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
#define MESH_PROCESSING_H
|
#define MESH_PROCESSING_H
|
||||||
|
|
||||||
#include "my_mesh.h"
|
#include "my_mesh.h"
|
||||||
#include "mesh_processor_painter.h"
|
#include "curvature.h"
|
||||||
|
#include "mesh_viewer.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
@ -10,14 +11,34 @@
|
|||||||
class MeshProcessor : public QObject {
|
class MeshProcessor : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
Courbures *courbure = nullptr;
|
||||||
|
MeshViewer &mesh_viewer;
|
||||||
|
bool view_patches = false;
|
||||||
|
double implicit_hole_filling_scale;
|
||||||
|
double implicit_hole_filling_discr;
|
||||||
|
std::vector<MyMesh> fillings;
|
||||||
|
|
||||||
|
void updateView();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MyMesh mesh;
|
MyMesh mesh;
|
||||||
MyMesh patch;
|
MyMesh patch;
|
||||||
|
|
||||||
MeshProcessor(const QString &path);
|
MeshProcessor(const QString &path, MeshViewer &mesh_viewer,
|
||||||
|
double implicit_hole_filling_scale,
|
||||||
|
double implicit_hole_filling_discr);
|
||||||
|
~MeshProcessor();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void fillHoles();
|
void fillHolesDumb();
|
||||||
|
void fillHolesImplicit();
|
||||||
|
void setImplicitHoleFillingScale(float scale);
|
||||||
|
void setImplicitHoleFillingDiscr(float discr);
|
||||||
|
void smoothCotangent(double factor);
|
||||||
|
void smoothUniform();
|
||||||
|
void setPatchView(bool on);
|
||||||
|
void click(QVector3D position);
|
||||||
|
void removeNoise(int value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
#include "mesh_processor_painter.h"
|
|
||||||
|
|
||||||
|
|
||||||
void MeshProcessorPainter::paint() {
|
|
||||||
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
#ifndef MESH_PAINTER_H
|
|
||||||
#define MESH_PAINTER_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
|
|
||||||
class MeshProcessorPainter {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void paint();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
@ -9,6 +9,12 @@
|
|||||||
MeshViewer::MeshViewer(QWidget *parent) : QOpenGLWidget(parent) {
|
MeshViewer::MeshViewer(QWidget *parent) : QOpenGLWidget(parent) {
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
setFocus();
|
setFocus();
|
||||||
|
updateViewMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MeshViewer::updateViewMatrix() {
|
||||||
|
view = zoom * rot * trans;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -59,22 +65,22 @@ void MeshViewer::initializeGL() {
|
|||||||
glf->glEnable(GL_MULTISAMPLE);
|
glf->glEnable(GL_MULTISAMPLE);
|
||||||
|
|
||||||
qDebug("MeshViewer: initialization complete");
|
qDebug("MeshViewer: initialization complete");
|
||||||
|
is_initialized = true;
|
||||||
emit initialized();
|
emit initialized();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MeshViewer::resizeGL(int w, int h) {
|
void MeshViewer::resizeGL(int w, int h) {
|
||||||
QMatrix4x4 projection;
|
proj.setToIdentity();
|
||||||
projection.perspective(FOV, (float) w/h, .01, 100);
|
proj.perspective(FOV, (float) w/h, .01, 100);
|
||||||
program.setUniformValue("proj", projection);
|
program.setUniformValue("proj", proj);
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MeshViewer::paintGL() {
|
void MeshViewer::paintGL() {
|
||||||
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
QMatrix4x4 trans;
|
QMatrix4x4 trans;
|
||||||
trans.translate(0, 0, -cam_dist);
|
|
||||||
QMatrix4x4 view = trans * rot;
|
|
||||||
program.bind();
|
program.bind();
|
||||||
program.setUniformValue("view", view);
|
program.setUniformValue("view", view);
|
||||||
for (MeshView &m : meshes) {
|
for (MeshView &m : meshes) {
|
||||||
@ -83,9 +89,10 @@ void MeshViewer::paintGL() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MeshViewer::addMesh(const MyMesh &mesh) {
|
void MeshViewer::addMesh(MyMesh &mesh) {
|
||||||
Q_ASSERT(isValid());
|
Q_ASSERT(isValid());
|
||||||
makeCurrent();
|
makeCurrent();
|
||||||
|
mesh.viewer_id = meshes.size();
|
||||||
meshes.emplace_back(mesh, program);
|
meshes.emplace_back(mesh, program);
|
||||||
doneCurrent();
|
doneCurrent();
|
||||||
update();
|
update();
|
||||||
@ -94,39 +101,81 @@ void MeshViewer::addMesh(const MyMesh &mesh) {
|
|||||||
|
|
||||||
void MeshViewer::removeMesh(const MyMesh &mesh) {
|
void MeshViewer::removeMesh(const MyMesh &mesh) {
|
||||||
makeCurrent();
|
makeCurrent();
|
||||||
meshes.remove_if([&](const MeshView &mv) {
|
size_t i = 0;
|
||||||
return &mv.mesh == &mesh;
|
for (auto it = meshes.begin(); it != meshes.end(); ++it) {
|
||||||
});
|
if (i == mesh.viewer_id) {
|
||||||
|
meshes.erase(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
doneCurrent();
|
doneCurrent();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MeshViewer::updateForReal() {
|
||||||
|
setEnabled(true);
|
||||||
|
setVisible(true);
|
||||||
|
update();
|
||||||
|
repaint();
|
||||||
|
makeCurrent();
|
||||||
|
paintGL();
|
||||||
|
doneCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void MeshViewer::mousePressEvent(QMouseEvent *e) {
|
void MeshViewer::mousePressEvent(QMouseEvent *e) {
|
||||||
if (e->button() == Qt::LeftButton) {
|
if (e->button() & Qt::MiddleButton || e->button() & Qt::RightButton) {
|
||||||
mouse_pos = e->pos();
|
mouse_pos = e->pos();
|
||||||
|
} else if (e->button() == Qt::LeftButton) {
|
||||||
|
float x = e->x();
|
||||||
|
float y = height() - e->y();
|
||||||
|
float depth;
|
||||||
|
makeCurrent();
|
||||||
|
QOpenGLFunctions *glf = context()->functions();
|
||||||
|
glf->glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth);
|
||||||
|
doneCurrent();
|
||||||
|
QVector3D position {x, y, depth};
|
||||||
|
position = position.unproject(view, proj, rect());
|
||||||
|
emit clicked(QVector3D(position));
|
||||||
|
} else {
|
||||||
|
e->ignore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MeshViewer::mouseReleaseEvent(QMouseEvent *e) {
|
void MeshViewer::mouseReleaseEvent(QMouseEvent *e) {
|
||||||
(void) e;
|
(void) e;
|
||||||
rot_start = rot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MeshViewer::mouseMoveEvent(QMouseEvent *e) {
|
void MeshViewer::mouseMoveEvent(QMouseEvent *e) {
|
||||||
if (e->buttons() & Qt::LeftButton) {
|
QPoint delta = e->pos() - mouse_pos;
|
||||||
QPoint delta = e->pos() - mouse_pos;
|
mouse_pos = e->pos();
|
||||||
rot = rot_start;
|
if (e->buttons() & Qt::MiddleButton) {
|
||||||
rot.rotate(delta.x() / 5., 0, 1, 0);
|
rot.rotate(delta.x() / 5., 0, 1, 0);
|
||||||
rot.rotate(delta.y() / 5., QVector3D(1, 0, 0) * rot);
|
rot.rotate(delta.y() / 5., QVector3D(1, 0, 0) * rot);
|
||||||
|
updateViewMatrix();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
if (e->buttons() & Qt::RightButton) {
|
||||||
|
QMatrix4x4 screen_trans;
|
||||||
|
screen_trans.translate(delta.x() / 1000., -delta.y() / 1000., 0);
|
||||||
|
trans = rot.inverted() * screen_trans * rot * trans;
|
||||||
|
// trans(0, 0) = 1;
|
||||||
|
// trans(0, 1) = 0;
|
||||||
|
// trans(1, 0) = 0;
|
||||||
|
// trans(1, 1) = 1;
|
||||||
|
updateViewMatrix();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void MeshViewer::wheelEvent(QWheelEvent *e) {
|
void MeshViewer::wheelEvent(QWheelEvent *e) {
|
||||||
cam_dist -= e->angleDelta().y() / 1000. * cam_dist;
|
zoom(2, 3) -= e->angleDelta().y() / 1000. * zoom(2, 3);
|
||||||
|
zoom.optimize();
|
||||||
|
updateViewMatrix();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
|
|
||||||
#define WIREFRAME_COLOR 0, 0, 0
|
#define WIREFRAME_COLOR 0, 0, 0
|
||||||
#define FOV 70
|
#define FOV 40
|
||||||
|
|
||||||
|
|
||||||
using namespace OpenMesh;
|
using namespace OpenMesh;
|
||||||
@ -27,9 +27,15 @@ class MeshViewer : public QOpenGLWidget {
|
|||||||
std::list<MeshView> meshes;
|
std::list<MeshView> meshes;
|
||||||
QOpenGLShaderProgram program;
|
QOpenGLShaderProgram program;
|
||||||
QMatrix4x4 proj;
|
QMatrix4x4 proj;
|
||||||
QMatrix4x4 rot, rot_start;
|
QMatrix4x4 zoom = QMatrix4x4(1, 0, 0, 0,
|
||||||
GLfloat cam_dist = 1;
|
0, 1, 0, 0,
|
||||||
|
0, 0, 1, -1,
|
||||||
|
0, 0, 0, 1);
|
||||||
|
QMatrix4x4 rot, trans;
|
||||||
|
QMatrix4x4 view;
|
||||||
QPoint mouse_pos;
|
QPoint mouse_pos;
|
||||||
|
void updateViewMatrix();
|
||||||
|
bool is_initialized = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MeshViewer(QWidget *parent=nullptr);
|
MeshViewer(QWidget *parent=nullptr);
|
||||||
@ -37,10 +43,12 @@ public:
|
|||||||
void initializeGL() override;
|
void initializeGL() override;
|
||||||
void resizeGL(int w, int h) override;
|
void resizeGL(int w, int h) override;
|
||||||
void paintGL() override;
|
void paintGL() override;
|
||||||
|
constexpr bool isInitialized() { return is_initialized; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void addMesh(const MyMesh &mesh);
|
void addMesh(MyMesh &mesh);
|
||||||
void removeMesh(const MyMesh &mesh);
|
void removeMesh(const MyMesh &mesh);
|
||||||
|
void updateForReal();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void mousePressEvent(QMouseEvent *e);
|
virtual void mousePressEvent(QMouseEvent *e);
|
||||||
@ -50,6 +58,7 @@ protected:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void initialized();
|
void initialized();
|
||||||
|
void clicked(QVector3D position);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,17 +14,19 @@ struct MyTraits : public OpenMesh::DefaultTraits {
|
|||||||
using Point = Eigen::Vector3<qreal>;
|
using Point = Eigen::Vector3<qreal>;
|
||||||
using Normal = Eigen::Vector3<qreal>;
|
using Normal = Eigen::Vector3<qreal>;
|
||||||
using Color = Eigen::Vector3<qreal>;
|
using Color = Eigen::Vector3<qreal>;
|
||||||
VertexAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Color);
|
VertexAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Color | OpenMesh::Attributes::Status);
|
||||||
|
EdgeAttributes(OpenMesh::Attributes::Status);
|
||||||
HalfedgeAttributes(OpenMesh::Attributes::PrevHalfedge);
|
HalfedgeAttributes(OpenMesh::Attributes::PrevHalfedge);
|
||||||
FaceAttributes(OpenMesh::Attributes::Normal);
|
FaceAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status);
|
||||||
EdgeAttributes(OpenMesh::Attributes::Color);
|
// EdgeAttributes(OpenMesh::Attributes::Color);
|
||||||
};
|
};
|
||||||
|
|
||||||
class MyMesh : public OpenMesh::TriMesh_ArrayKernelT<MyTraits> {
|
class MyMesh : public OpenMesh::TriMesh_ArrayKernelT<MyTraits> {
|
||||||
public:
|
public:
|
||||||
|
Color default_color {.5, .5, .5};
|
||||||
QMatrix4x4 transform;
|
QMatrix4x4 transform;
|
||||||
|
size_t viewer_id;
|
||||||
std::vector<std::vector<HalfedgeHandle>> holes;
|
std::vector<std::vector<HalfedgeHandle>> holes;
|
||||||
// QColor color = {127, 127, 127};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef MyMesh::FaceHandle FaceHandle;
|
typedef MyMesh::FaceHandle FaceHandle;
|
||||||
|
15
src/noise_removal.cpp
Normal file
15
src/noise_removal.cpp
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#include "noise_removal.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
|
||||||
|
void remove_noise(MyMesh &mesh, unsigned threshold) {
|
||||||
|
auto components = find_connected_components(mesh);
|
||||||
|
for (auto component : components) {
|
||||||
|
if (component.size() < threshold) {
|
||||||
|
for (VertexHandle vh : component) {
|
||||||
|
mesh.delete_vertex(vh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mesh.garbage_collection();
|
||||||
|
}
|
10
src/noise_removal.h
Normal file
10
src/noise_removal.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#ifndef NOISE_REMOVAL_H
|
||||||
|
#define NOISE_REMOVAL_H
|
||||||
|
|
||||||
|
#include "my_mesh.h"
|
||||||
|
|
||||||
|
|
||||||
|
void remove_noise(MyMesh &mesh, unsigned threshold=20);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
@ -2,10 +2,13 @@
|
|||||||
|
|
||||||
|
|
||||||
MyMesh tesselate_quad_patch(QuadPatch q, MyMesh mesh, VertexHandle vh) {
|
MyMesh tesselate_quad_patch(QuadPatch q, MyMesh mesh, VertexHandle vh) {
|
||||||
|
const size_t patch_divs = 8;
|
||||||
MyMesh patch;
|
MyMesh patch;
|
||||||
Eigen::Vector3d point
|
Eigen::Vector3d point
|
||||||
(mesh.point(vh)[0], mesh.point(vh)[1], mesh.point(vh)[2]);
|
(mesh.point(vh)[0], mesh.point(vh)[1], mesh.point(vh)[2]);
|
||||||
point = q.transform() * point;
|
point = q.transform() * point;
|
||||||
|
|
||||||
|
// Recherche des dimensions englobantes du 1-anneau de vh
|
||||||
double xmin = point[0], xmax = point[0];
|
double xmin = point[0], xmax = point[0];
|
||||||
double ymin = point[1], ymax = point[1];
|
double ymin = point[1], ymax = point[1];
|
||||||
for (VertexHandle vi : mesh.vv_range(vh)) {
|
for (VertexHandle vi : mesh.vv_range(vh)) {
|
||||||
@ -17,10 +20,12 @@ MyMesh tesselate_quad_patch(QuadPatch q, MyMesh mesh, VertexHandle vh) {
|
|||||||
ymin = std::min(ymin, point[1]);
|
ymin = std::min(ymin, point[1]);
|
||||||
ymax = std::max(ymax, point[1]);
|
ymax = std::max(ymax, point[1]);
|
||||||
}
|
}
|
||||||
double xstep = (xmax-xmin)/64.;
|
|
||||||
double ystep = (ymax-ymin)/64.;
|
// Générations des sommets
|
||||||
for (size_t y = 0; y < 64; y++) {
|
double xstep = (xmax-xmin)/static_cast<double>(patch_divs);
|
||||||
for (size_t x = 0; x < 64; x++) {
|
double ystep = (ymax-ymin)/static_cast<double>(patch_divs);
|
||||||
|
for (size_t y = 0; y < patch_divs; y++) {
|
||||||
|
for (size_t x = 0; x < patch_divs; x++) {
|
||||||
double dx = xmin + x * xstep;
|
double dx = xmin + x * xstep;
|
||||||
double dy = ymin + y * ystep;
|
double dy = ymin + y * ystep;
|
||||||
Eigen::Vector3d point(dx, dy, -q(dx, dy));
|
Eigen::Vector3d point(dx, dy, -q(dx, dy));
|
||||||
@ -28,17 +33,22 @@ MyMesh tesselate_quad_patch(QuadPatch q, MyMesh mesh, VertexHandle vh) {
|
|||||||
patch.new_vertex(Point(point[0], point[1], point[2]));
|
patch.new_vertex(Point(point[0], point[1], point[2]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Générations des triangles
|
||||||
for (VertexHandle vhi : patch.vertices()) {
|
for (VertexHandle vhi : patch.vertices()) {
|
||||||
patch.set_color(vhi, MyMesh::Color(0, 1, .2));
|
patch.set_color(vhi, MyMesh::Color(0, 1, .2));
|
||||||
size_t i = vhi.idx();
|
size_t i = vhi.idx();
|
||||||
size_t x = i % 64;
|
size_t x = i % patch_divs;
|
||||||
size_t y = i / 64;
|
size_t y = i / patch_divs;
|
||||||
if (x == 63 || y == 63) continue;
|
|
||||||
patch.add_face(vhi, patch.vertex_handle(i + 64),
|
// On ignore la dernière colonne et dernière ligne
|
||||||
|
if (x == patch_divs - 1 || y == patch_divs - 1) continue;
|
||||||
|
|
||||||
|
patch.add_face(vhi, patch.vertex_handle(i + patch_divs),
|
||||||
patch.vertex_handle(i + 1));
|
patch.vertex_handle(i + 1));
|
||||||
patch.add_face(patch.vertex_handle(i + 1),
|
patch.add_face(patch.vertex_handle(i + 1),
|
||||||
patch.vertex_handle(i + 64),
|
patch.vertex_handle(i + patch_divs),
|
||||||
patch.vertex_handle(i + 65));
|
patch.vertex_handle(i + patch_divs + 1));
|
||||||
}
|
}
|
||||||
return patch;
|
return patch;
|
||||||
}
|
}
|
||||||
|
@ -10,92 +10,131 @@
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
vα/--\
|
vα/--\
|
||||||
/ ------\ v
|
/ ------\ vi
|
||||||
/ ---X
|
/ ---X
|
||||||
/ /---- / \
|
/ /---- / \
|
||||||
/ /--- / -\
|
/ /--- / -\
|
||||||
/ /---- / \
|
/ /---- / \
|
||||||
vi --- | \
|
vj --- | \
|
||||||
\-- / -\
|
\-- / -\
|
||||||
\- / /--\
|
\- / /--\
|
||||||
\-- / /-------
|
\-- / /-------
|
||||||
\-+---
|
\-+---
|
||||||
vβ
|
vβ
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Point laplace_beltrami(const MyMesh &mesh, VertexHandle vert) {
|
|
||||||
Point sum {0, 0, 0};
|
/* Poids pour le laplacien uniforme, constant à 1. */
|
||||||
qreal area = 0;
|
double uniform_weight(MyMesh &mesh, HalfedgeHandle vi_vj) {
|
||||||
Point p = mesh.point(vert);
|
(void) mesh;
|
||||||
qreal count = 0;
|
(void) vi_vj;
|
||||||
for (HalfedgeHandle v_vi : mesh.voh_range(vert)) {
|
return 1;
|
||||||
Point pi = mesh.point(mesh.to_vertex_handle(v_vi));
|
}
|
||||||
HalfedgeHandle vi_v = mesh.opposite_halfedge_handle(v_vi);
|
|
||||||
HalfedgeHandle v_va = mesh.next_halfedge_handle(vi_v);
|
|
||||||
Point pa = mesh.point(mesh.to_vertex_handle(v_va));
|
/* Masse pour le laplacien uniforme, 1 / taille(N1(vi)). */
|
||||||
HalfedgeHandle vi_vb = mesh.next_halfedge_handle(v_vi);
|
double uniform_mass(MyMesh &mesh, VertexHandle vi) {
|
||||||
Point pb = mesh.point(mesh.to_vertex_handle(vi_vb));
|
double count = 0;
|
||||||
qreal a = angle_between(pi, pa, p);
|
for (VertexHandle v : mesh.vv_range(vi)) {
|
||||||
qreal b = angle_between(pi, pb, p);
|
(void) v;
|
||||||
sum += (cotan(a) + cotan(b)) * (p - pi);
|
|
||||||
area += triangle_area(p, pi, pb);
|
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
area /= 3.;
|
return 1. / count;
|
||||||
return sum / (2.*count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Eigen::SparseMatrix<qreal> laplacian_matrix(const MyMesh &mesh) {
|
/* Calcule le poids cotangent pour l'arête reliant vi à vj. */
|
||||||
size_t n_verts = mesh.n_vertices();
|
double cotangent_weight(MyMesh &mesh, HalfedgeHandle vi_vj) {
|
||||||
Eigen::SparseMatrix<qreal> mass(n_verts, n_verts);
|
// Voir le graphique en haut de ce fichier.
|
||||||
Eigen::SparseMatrix<qreal> cotangent(n_verts, n_verts);
|
Point pj = mesh.point(mesh.to_vertex_handle(vi_vj));
|
||||||
for (VertexHandle vert : mesh.vertices()) {
|
HalfedgeHandle vj_vb = mesh.next_halfedge_handle(vi_vj);
|
||||||
if (mesh.is_boundary(vert)) continue;
|
Point pb = mesh.point(mesh.to_vertex_handle(vj_vb));
|
||||||
qreal sum = 0;
|
HalfedgeHandle vj_vi = mesh.opposite_halfedge_handle(vi_vj);
|
||||||
qreal area = 0;
|
Point pi = mesh.point(mesh.to_vertex_handle(vj_vi));
|
||||||
qreal count = 0;
|
HalfedgeHandle vi_va = mesh.next_halfedge_handle(vj_vi);
|
||||||
Point p = mesh.point(vert);
|
Point pa = mesh.point(mesh.to_vertex_handle(vi_va));
|
||||||
for (HalfedgeHandle v_vi : mesh.voh_range(vert)) {
|
double a = angle_between(pi, pa, pj);
|
||||||
VertexHandle vi = mesh.to_vertex_handle(v_vi);
|
double b = angle_between(pj, pb, pi);
|
||||||
Point pi = mesh.point(vi);
|
return cotan(a) + cotan(b);
|
||||||
HalfedgeHandle vi_v = mesh.opposite_halfedge_handle(v_vi);
|
}
|
||||||
HalfedgeHandle v_va = mesh.next_halfedge_handle(vi_v);
|
|
||||||
Point pa = mesh.point(mesh.to_vertex_handle(v_va));
|
|
||||||
HalfedgeHandle vi_vb = mesh.next_halfedge_handle(v_vi);
|
/* Calcule l'aire de chaque face et la stoque dans une propriété
|
||||||
Point pb = mesh.point(mesh.to_vertex_handle(vi_vb));
|
* "face_area" du maillage. */
|
||||||
qreal a = angle_between(pi, pa, p);
|
void compute_face_areas(MyMesh &mesh) {
|
||||||
qreal b = angle_between(pi, pb, p);
|
auto area = OpenMesh::getOrMakeProperty<FaceHandle, double>
|
||||||
qreal w = -(cotan(a) + cotan(b)) / 2.;
|
(mesh, "face_area");
|
||||||
sum += w;
|
for (FaceHandle fh : mesh.faces()) {
|
||||||
cotangent.insert(vert.idx(), vi.idx()) = w;
|
MyMesh::FaceVertexIter fv_it = mesh.fv_iter(fh);
|
||||||
area += triangle_area(p, pi, pb);
|
Point pi = mesh.point(*fv_it);
|
||||||
count++;
|
Point pj = mesh.point(*++fv_it);
|
||||||
}
|
Point pk = mesh.point(*++fv_it);
|
||||||
area /= 3.;
|
area[fh] = triangle_area(pi, pj, pk);
|
||||||
cotangent.insert(vert.idx(), vert.idx()) = -sum;
|
|
||||||
mass.insert(vert.idx(), vert.idx()) = 1. / area;
|
|
||||||
}
|
}
|
||||||
return mass * cotangent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void smooth(MyMesh &mesh) {
|
/* Calcule l'aire barycentrique incidente à vert. */
|
||||||
// auto new_pos = OpenMesh::makeTemporaryProperty<VertexHandle, Point>(mesh);
|
double barycentric_vertex_area(MyMesh &mesh, VertexHandle vert) {
|
||||||
// for (VertexHandle v : mesh.vertices()) {
|
auto area = OpenMesh::getProperty<FaceHandle, double>(mesh, "face_area");
|
||||||
// if (!mesh.is_boundary(v)) {
|
double sum = 0;
|
||||||
// new_pos[v] = mesh.point(v) - laplace_beltrami(mesh, v);
|
for (FaceHandle fh : mesh.vf_range(vert)) {
|
||||||
// }
|
sum += area[fh];
|
||||||
// }
|
}
|
||||||
// for (VertexHandle v : mesh.vertices()) {
|
return sum / 3;
|
||||||
// if (!mesh.is_boundary(v)) {
|
}
|
||||||
// mesh.set_point(v, new_pos[v]);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Approche matricielle
|
|
||||||
Eigen::SparseMatrix<qreal> laplacian = laplacian_matrix(mesh);
|
/* Fonction de masse pour le laplacien cotangent. */
|
||||||
|
double cotangent_mass(MyMesh &mesh, VertexHandle vi) {
|
||||||
|
double area = barycentric_vertex_area(mesh, vi);
|
||||||
|
return 1. / (2 * area);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Calcule la matrice laplacienne avec les fonctions de poids d'arête
|
||||||
|
* et de masse de sommet spécifiées par l'utilisatrice. */
|
||||||
|
Eigen::SparseMatrix<qreal> laplacian_matrix(MyMesh &mesh, double (*edge_weight)(MyMesh &, HalfedgeHandle), double (*vertex_mass)(MyMesh &, VertexHandle)) {
|
||||||
|
compute_face_areas(mesh);
|
||||||
|
size_t n_verts = mesh.n_vertices();
|
||||||
|
Eigen::SparseMatrix<double> weight(n_verts, n_verts);
|
||||||
|
Eigen::SparseMatrix<double> mass(n_verts, n_verts);
|
||||||
|
for (VertexHandle vi : mesh.vertices()) {
|
||||||
|
if (mesh.is_boundary(vi)) continue;
|
||||||
|
double sum = 0;
|
||||||
|
for (HalfedgeHandle vi_vj : mesh.voh_range(vi)) {
|
||||||
|
// For each outgoing halfedge
|
||||||
|
VertexHandle vj = mesh.to_vertex_handle(vi_vj);
|
||||||
|
double halfedge_weight = edge_weight(mesh, vi_vj);
|
||||||
|
weight.insert(vi.idx(), vj.idx()) = halfedge_weight;
|
||||||
|
sum -= halfedge_weight;
|
||||||
|
}
|
||||||
|
// weight(i, i) = -Σ(j in N1(i)) weight(i, j)
|
||||||
|
weight.insert(vi.idx(), vi.idx()) = sum;
|
||||||
|
|
||||||
|
mass.insert(vi.idx(), vi.idx()) = vertex_mass(mesh, vi);
|
||||||
|
}
|
||||||
|
return mass * weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Adoucissement du maillage. */
|
||||||
|
void smooth(MyMesh &mesh, SmoothingMethod method, double cotan_factor) {
|
||||||
|
// Calcul de la matrice laplacienne en fonction de la méthode choisie.
|
||||||
|
double factor;
|
||||||
|
Eigen::SparseMatrix<qreal> laplacian;
|
||||||
|
if (method == SmoothingMethod::COTANGENT) {
|
||||||
|
factor = cotan_factor;
|
||||||
|
laplacian = laplacian_matrix(mesh, cotangent_weight, cotangent_mass);
|
||||||
|
} else {
|
||||||
|
factor = 1;
|
||||||
|
laplacian = laplacian_matrix(mesh, uniform_weight, uniform_mass);
|
||||||
|
}
|
||||||
|
|
||||||
|
// laplacian = laplacian * laplacian; // Mise au carré.
|
||||||
|
|
||||||
|
// Transfert des coordonées de chaque point dans une matrice.
|
||||||
size_t n_verts = mesh.n_vertices();
|
size_t n_verts = mesh.n_vertices();
|
||||||
Eigen::VectorX<qreal> X(n_verts), Y(n_verts), Z(n_verts);
|
Eigen::VectorX<qreal> X(n_verts), Y(n_verts), Z(n_verts);
|
||||||
for (VertexHandle vert : mesh.vertices()) {
|
for (VertexHandle vert : mesh.vertices()) {
|
||||||
@ -106,13 +145,17 @@ void smooth(MyMesh &mesh) {
|
|||||||
Y(id) = p[1];
|
Y(id) = p[1];
|
||||||
Z(id) = p[2];
|
Z(id) = p[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Application du laplacien pour calculer les décalages.
|
||||||
X = laplacian * X;
|
X = laplacian * X;
|
||||||
Y = laplacian * Y;
|
Y = laplacian * Y;
|
||||||
Z = laplacian * Z;
|
Z = laplacian * Z;
|
||||||
|
|
||||||
|
// Application des décalages.
|
||||||
for (VertexHandle vert : mesh.vertices()) {
|
for (VertexHandle vert : mesh.vertices()) {
|
||||||
if (mesh.is_boundary(vert)) continue;
|
if (mesh.is_boundary(vert)) continue;
|
||||||
size_t id = vert.idx();
|
size_t id = vert.idx();
|
||||||
Point offset {X(id), Y(id), Z(id)};
|
Point offset {X(id), Y(id), Z(id)};
|
||||||
mesh.set_point(vert, mesh.point(vert) - offset);
|
mesh.set_point(vert, mesh.point(vert) + offset * factor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,12 @@
|
|||||||
#include "my_mesh.h"
|
#include "my_mesh.h"
|
||||||
|
|
||||||
|
|
||||||
Point laplace_beltrami(const MyMesh &mesh, VertexHandle vert);
|
enum class SmoothingMethod {
|
||||||
void smooth(MyMesh &mesh);
|
UNIFORM,
|
||||||
|
COTANGENT,
|
||||||
|
};
|
||||||
|
|
||||||
|
void smooth(MyMesh &mesh, SmoothingMethod method, double cotan_factor=.0001);
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
44
src/util.cpp
44
src/util.cpp
@ -1,5 +1,8 @@
|
|||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <stack>
|
||||||
|
#include <OpenMesh/Core/Utils/PropertyManager.hh>
|
||||||
|
|
||||||
|
|
||||||
QDebug operator<<(QDebug dbg, const Point &p) {
|
QDebug operator<<(QDebug dbg, const Point &p) {
|
||||||
return dbg << p[0] << p[1] << p[2];
|
return dbg << p[0] << p[1] << p[2];
|
||||||
@ -9,3 +12,44 @@ QDebug operator<<(QDebug dbg, const Point &p) {
|
|||||||
qreal cotan(const qreal x) {
|
qreal cotan(const qreal x) {
|
||||||
return 1. / qTan(x);
|
return 1. / qTan(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void traverse_connected(MyMesh &mesh, std::vector<VertexHandle> &out,
|
||||||
|
VertexHandle start) {
|
||||||
|
auto prop =
|
||||||
|
OpenMesh::makeTemporaryProperty<MyMesh::VertexHandle, bool>(mesh);
|
||||||
|
for (VertexHandle vh : mesh.vertices()) {
|
||||||
|
prop[vh] = false;
|
||||||
|
}
|
||||||
|
std::stack<VertexHandle> stack;
|
||||||
|
stack.push(start);
|
||||||
|
prop[start] = true;
|
||||||
|
while (!stack.empty()) {
|
||||||
|
VertexHandle v = stack.top();
|
||||||
|
stack.pop();
|
||||||
|
out.emplace_back(v);
|
||||||
|
for (VertexHandle v2 : mesh.vv_range(v)) {
|
||||||
|
if (!prop[v2]) {
|
||||||
|
stack.push(v2);
|
||||||
|
prop[v2] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<std::vector<VertexHandle>> find_connected_components(
|
||||||
|
MyMesh &mesh) {
|
||||||
|
auto prop =
|
||||||
|
OpenMesh::makeTemporaryProperty<MyMesh::VertexHandle, bool>(mesh);
|
||||||
|
for (VertexHandle vh : mesh.vertices()) {
|
||||||
|
prop[vh] = false;
|
||||||
|
}
|
||||||
|
std::vector<std::vector<VertexHandle>> connected_components;
|
||||||
|
for (VertexHandle vh : mesh.vertices()) {
|
||||||
|
if (prop[vh]) continue;
|
||||||
|
connected_components.push_back({});
|
||||||
|
traverse_connected(mesh, connected_components.back(), vh);
|
||||||
|
}
|
||||||
|
return connected_components;
|
||||||
|
}
|
||||||
|
@ -101,4 +101,8 @@ standard_deviation(const ForwardIt first,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<std::vector<VertexHandle>> find_connected_components(
|
||||||
|
MyMesh &mesh);
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
116
tp.md
Normal file
116
tp.md
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
# TP - Courbures discrètes
|
||||||
|
**Cyril Colin et Jérémy André**
|
||||||
|
|
||||||
|
## Exercice 1
|
||||||
|
Récupération des voisins d'un sommet :
|
||||||
|
Si le nombre de sommets dans le 1-voisinage est < 5, alors on ajoute les points du 2-voisinage pour calculer le patch.
|
||||||
|
```cpp
|
||||||
|
void Courbures::get_neighborhood(std::vector<MyMesh::VertexHandle> &out, const MyMesh::VertexHandle vh) {
|
||||||
|
for(...) {...} // ajout 1N à out
|
||||||
|
if(out.size() >= 5) return;
|
||||||
|
for(...) {...} // ajout 2N à out
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Après avoir récupéré les voisins, la matrice de changement de base calculé pour positionner les sommets dans un repère local (pour avoir Z représentant la distance au plan XY). Puis on construit la matrice et le vecteur permettant à Eigen de calculer les coefficients du patch quadratique avec la méthode des moindres carrés. Le résultat est retourné dans une structure `QuadPatch` comprenant la matrice de changement de repère, son inverse, ainsi que les coefficients.
|
||||||
|
```cpp
|
||||||
|
QuadPatch Courbures::fit_quad(MyMesh::VertexHandle vh) {
|
||||||
|
[...]
|
||||||
|
// Récupération des voisins
|
||||||
|
get_neighborhood(neigh, vh);
|
||||||
|
[...]
|
||||||
|
|
||||||
|
[...] // Calcul de la matrice de changement de base
|
||||||
|
Eigen::Transform<double, 3, Eigen::Affine> ch_base = rot * trans;
|
||||||
|
|
||||||
|
[...]
|
||||||
|
// Calcul de la matrice / vecteur de moindres carrés linéaires (Eigen)
|
||||||
|
for(size_t i = 0; i < neigh.size(); i++) {`
|
||||||
|
MyMesh::Point point = _mesh.point(neigh[i]);
|
||||||
|
|
||||||
|
point = ch_base * point; // Application du changement de base
|
||||||
|
B[i] = -point[2]; // -zi
|
||||||
|
A(i, 0) = point[0] * point[0]; // xi^2
|
||||||
|
A(i, 1) = point[0] * point[1]; // xi*yi
|
||||||
|
A(i, 2) = point[1] * point[1]; // yi^2
|
||||||
|
A(i, 3) = point[0]; // xi
|
||||||
|
A(i, 4) = point[1]; // yi
|
||||||
|
}
|
||||||
|
|
||||||
|
// Résolution aux moindres carrés par SVD
|
||||||
|
Eigen::VectorXd coef(5);
|
||||||
|
coef = A.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(B);
|
||||||
|
return QuadPatch(coef, ch_base);
|
||||||
|
}
|
||||||
|
```
|
||||||
|

|
||||||
|
|
||||||
|
*Visualisation des courbures K*
|
||||||
|
|
||||||
|
## Exercice 2
|
||||||
|
Pour la seconde partie, nous avons choisi d'afficher le patch quadratique.
|
||||||
|
Avec la fonction `fit_quad` chaque sommet possèdes un attribut de type "QuadPatch" qui contient tout ce qu'il faut pour visualiser le patch.
|
||||||
|
|
||||||
|
Pour construire le maillage d'un patch sur un sommet :
|
||||||
|
- On utilise son 1-voisinage pour déterminer la taille du maillage du patch, en cherchant les coordonnées min et max.
|
||||||
|
- Puis selon un niveau de discrétisation `patch_divs`, on créé des sommets en calculant leurs positions Z avec les coefficients du patch et en les multipliant par l'inverse de la matrice de changement de base (pour retrouver sa position par rapport au maillage originel).
|
||||||
|
- Enfin, la dernière étape est d'itérer sur tous ces nouveaux sommets pour ainsi créer des triangles, permettant l'affiche du patch.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
MyMesh tesselate_quad_patch(QuadPatch q, MyMesh mesh, VertexHandle vh) {
|
||||||
|
const size_t patch_divs = 8;
|
||||||
|
|
||||||
|
MyMesh patch;
|
||||||
|
Eigen::Vector3d point(mesh.point(vh)[0], mesh.point(vh)[1], mesh.point(vh)[2]);
|
||||||
|
point = q.transform() * point;
|
||||||
|
|
||||||
|
// Recherche des dimensions englobantes du 1-anneau de vh
|
||||||
|
double xmin = point[0], xmax = point[0];
|
||||||
|
double ymin = point[1], ymax = point[1];
|
||||||
|
|
||||||
|
for (VertexHandle vi : mesh.vv_range(vh)) {
|
||||||
|
Eigen::Vector3d point(mesh.point(vi)[0], mesh.point(vi)[1], mesh.point(vi)[2]);
|
||||||
|
|
||||||
|
point = q.transform() * point;
|
||||||
|
xmin = std::min(xmin, point[0]);
|
||||||
|
xmax = std::max(xmax, point[0]);
|
||||||
|
ymin = std::min(ymin, point[1]);
|
||||||
|
ymax = std::max(ymax, point[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Générations des sommets
|
||||||
|
double xstep = (xmax-xmin)/static_cast<double>(patch_divs);
|
||||||
|
double ystep = (ymax-ymin)/static_cast<double>(patch_divs);
|
||||||
|
|
||||||
|
for (size_t y = 0; y < patch_divs; y++) {
|
||||||
|
for (size_t x = 0; x < patch_divs; x++) {
|
||||||
|
double dx = xmin + x * xstep;
|
||||||
|
double dy = ymin + y * ystep;
|
||||||
|
|
||||||
|
Eigen::Vector3d point(dx, dy, -q(dx, dy));
|
||||||
|
point = q.inverse_transform() * point;
|
||||||
|
patch.new_vertex(Point(point[0], point[1], point[2]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Générations des triangles
|
||||||
|
for (VertexHandle vhi : patch.vertices()) {
|
||||||
|
patch.set_color(vhi, MyMesh::Color(0, 1, .2));
|
||||||
|
size_t i = vhi.idx();
|
||||||
|
size_t x = i % patch_divs;
|
||||||
|
size_t y = i / patch_divs;
|
||||||
|
|
||||||
|
// On ignore la dernière colonne et dernière ligne
|
||||||
|
if (x == patch_divs - 1 || y == patch_divs - 1) continue;
|
||||||
|
|
||||||
|
patch.add_face(vhi, patch.vertex_handle(i + patch_divs), patch.vertex_handle(i + 1));
|
||||||
|
patch.add_face(patch.vertex_handle(i + 1), patch.vertex_handle(i + patch_divs),
|
||||||
|
patch.vertex_handle(i + patch_divs + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return patch;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Visualisation d'un patch*
|
Reference in New Issue
Block a user