improve smoothing

This commit is contained in:
papush! 2021-11-28 16:55:10 +01:00
parent be6a1d4f8a
commit 686e8cf397
9 changed files with 7674 additions and 117 deletions

7509
data/bunnyLowPoly-noisy.obj Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
#include "hole_filling.h" #include "hole_filling.h"
#include "IO.h"
#include "util.h" #include "util.h"
#include <MeshReconstruction.h> #include <MeshReconstruction.h>
@ -306,10 +307,8 @@ MyMesh fillHoleImplicit(MyMesh &mesh, Hole_Filling &hf,
std::vector<VertexHandle> verts; std::vector<VertexHandle> verts;
for (HalfedgeHandle hh : hole) { for (HalfedgeHandle hh : hole) {
verts.push_back(mesh.to_vertex_handle(hh)); verts.push_back(mesh.to_vertex_handle(hh));
verts.push_back(mesh.to_vertex_handle(
mesh.next_halfedge_handle(
mesh.opposite_halfedge_handle(hh))));
} }
verts = hf.next_neighbors(verts);
auto [system, pts_list] = hf.compute_approx_mat(verts); auto [system, pts_list] = hf.compute_approx_mat(verts);
auto [alpha, beta] = hf.solve_approx(system, pts_list.size(), 10); auto [alpha, beta] = hf.solve_approx(system, pts_list.size(), 10);
Implicit_RBF rbf(alpha, beta, pts_list); Implicit_RBF rbf(alpha, beta, pts_list);
@ -339,4 +338,20 @@ std::vector<MyMesh> fillHolesImplicit(MyMesh &mesh,
fillings.push_back(fillHoleImplicit(mesh, hf, hole)); fillings.push_back(fillHoleImplicit(mesh, hf, hole));
} }
return fillings; return fillings;
// auto sdf = [&](Vec3 const& v) { return v.Norm() - 10.; };
// Rect3 domain {{-10, -10, -10}, {20, 20, 20}};
// auto filling = MarchCube(sdf, domain);
// WriteObjFile(filling, "out.obj");
// 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};
} }

View File

@ -15,8 +15,10 @@ static MeshProcessor *create_mesh_processor(const QString &path,
mesh_processor, &MeshProcessor::fillHolesDumb); mesh_processor, &MeshProcessor::fillHolesDumb);
QObject::connect(&main_window, &MainWindow::fillHolesImplicitClicked, QObject::connect(&main_window, &MainWindow::fillHolesImplicitClicked,
mesh_processor, &MeshProcessor::fillHolesImplicit); mesh_processor, &MeshProcessor::fillHolesImplicit);
QObject::connect(&main_window, &MainWindow::smoothClicked, QObject::connect(&main_window, &MainWindow::smoothUniformClicked,
mesh_processor, &MeshProcessor::smooth); mesh_processor, &MeshProcessor::smoothUniform);
QObject::connect(&main_window, &MainWindow::smoothCotangentClicked,
mesh_processor, &MeshProcessor::smoothCotangent);
QObject::connect(&main_window, &MainWindow::patchViewToggled, QObject::connect(&main_window, &MainWindow::patchViewToggled,
mesh_processor, &MeshProcessor::setPatchView); mesh_processor, &MeshProcessor::setPatchView);
QObject::connect(&main_window, &MainWindow::fillHolesImplicitScaleChanged, QObject::connect(&main_window, &MainWindow::fillHolesImplicitScaleChanged,

View File

@ -16,22 +16,6 @@ MainWindow::MainWindow(QWidget *parent)
toolbar(this), toolbar(this),
mesh_viewer() { mesh_viewer() {
setCentralWidget(&mesh_viewer); setCentralWidget(&mesh_viewer);
// addToolBar(Qt::RightToolBarArea, &toolbar);
// open_action = toolbar.addAction("Ouvrir…", [&](){
// emit open(QFileDialog::getOpenFileName(this, "Ouvrir un maillage"));
// });
// toolbar_actions.append(toolbar.addAction("Fractionner", [&](){
// QVector<QPair<MyMesh::Point, MyMesh>> fragments = shatter(mesh);
// mesh_viewer.removeOpenGLMesh(glm);
// for (auto &[pos, fragment] : fragments) {
// fragment.triangulate();
// QMatrix4x4 mat;
// float scale = 1.2;
// mat.translate(pos[0] * scale, pos[1] * scale, pos[2] * scale);
// mesh_viewer.addOpenGLMeshFromOpenMesh(&fragment, mat);
// }
// }));
QMenuBar *menu_bar = new QMenuBar(); QMenuBar *menu_bar = new QMenuBar();
setMenuBar(menu_bar); setMenuBar(menu_bar);
@ -94,12 +78,24 @@ MainWindow::MainWindow(QWidget *parent)
// Smoothing tools // Smoothing tools
QGroupBox *smooth_box = new QGroupBox("Adoucissement"); QGroupBox *smooth_box = new QGroupBox("Adoucissement");
QLayout *smooth_layout = new QVBoxLayout(); QGridLayout *smooth_layout = new QGridLayout();
smooth_box->setLayout(smooth_layout); smooth_box->setLayout(smooth_layout);
QPushButton *smooth = new QPushButton("Adoucir"); QPushButton *smooth = new QPushButton("Adoucir (uniforme)");
connect(smooth, &QPushButton::clicked, connect(smooth, &QPushButton::clicked,
this, &MainWindow::smoothClicked); this, &MainWindow::smoothUniformClicked);
smooth_layout->addWidget(smooth); 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);
smooth_layout->addWidget(smooth_cotangent_factor_input->slider(), 3, 0);
QDoubleSpinBox *sb = (QDoubleSpinBox *)(smooth_cotangent_factor_input->spinBox());
sb->setDecimals(5);
smooth_layout->addWidget(smooth_cotangent_factor_input->spinBox(), 3, 1);
toolbar.addWidget(smooth_box); toolbar.addWidget(smooth_box);

View File

@ -19,6 +19,8 @@ class MainWindow : public QMainWindow {
QAction *save_action; QAction *save_action;
DoubleInput *fill_holes_implicit_scale; DoubleInput *fill_holes_implicit_scale;
DoubleInput *fill_holes_implicit_discr; 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);
@ -27,7 +29,8 @@ signals:
void fillHolesImplicitClicked(); void fillHolesImplicitClicked();
void fillHolesImplicitScaleChanged(float value); void fillHolesImplicitScaleChanged(float value);
void fillHolesImplicitDiscrChanged(float value); void fillHolesImplicitDiscrChanged(float value);
void smoothClicked(); void smoothUniformClicked();
void smoothCotangentClicked(double factor);
void patchViewToggled(bool checked); void patchViewToggled(bool checked);
public: public:

View File

@ -95,8 +95,14 @@ void MeshProcessor::setImplicitHoleFillingDiscr(float discr) {
} }
void MeshProcessor::smooth() { void MeshProcessor::smoothCotangent(double factor) {
::smooth(mesh); ::smooth(mesh, SmoothingMethod::COTANGENT, factor);
updateView();
}
void MeshProcessor::smoothUniform() {
::smooth(mesh, SmoothingMethod::UNIFORM);
updateView(); updateView();
} }

View File

@ -34,7 +34,8 @@ public slots:
void fillHolesImplicit(); void fillHolesImplicit();
void setImplicitHoleFillingScale(float scale); void setImplicitHoleFillingScale(float scale);
void setImplicitHoleFillingDiscr(float discr); void setImplicitHoleFillingDiscr(float discr);
void smooth(); void smoothCotangent(double factor);
void smoothUniform();
void setPatchView(bool on); void setPatchView(bool on);
void click(QVector3D position); void click(QVector3D position);
}; };

View File

@ -11,12 +11,12 @@
/* /*
vα/--\ vα/--\
/ ------\ v / ------\ vi
/ ---X / ---X
/ /---- / \ / /---- / \
/ /--- / -\ / /--- / -\
/ /---- / \ / /---- / \
vi --- | \ vj --- | \
\-- / -\ \-- / -\
\- / /--\ \- / /--\
\-- / /------- \-- / /-------
@ -24,98 +24,119 @@
*/ */
Point laplace_beltrami(const MyMesh &mesh, VertexHandle vert) {
Point sum {0, 0, 0}; double uniform_weight(MyMesh &mesh, HalfedgeHandle vi_vj) {
qreal area = 0; (void) mesh;
Point p = mesh.point(vert); (void) vi_vj;
qreal count = 0; return 1;
for (HalfedgeHandle v_vi : mesh.voh_range(vert)) { }
Point pi = mesh.point(mesh.to_vertex_handle(v_vi));
HalfedgeHandle vi_v = mesh.opposite_halfedge_handle(v_vi); double uniform_mass(MyMesh &mesh, VertexHandle vi) {
HalfedgeHandle v_va = mesh.next_halfedge_handle(vi_v); double count = 0;
Point pa = mesh.point(mesh.to_vertex_handle(v_va)); for (VertexHandle v : mesh.vv_range(vi)) {
HalfedgeHandle vi_vb = mesh.next_halfedge_handle(v_vi); (void) v;
Point pb = mesh.point(mesh.to_vertex_handle(vi_vb));
qreal a = angle_between(pi, pa, p);
qreal b = angle_between(pi, pb, p);
sum += (cotan(a) + cotan(b)) * (p - pi);
area += triangle_area(p, pi, pb);
count++; count++;
} }
// area /= 3.; return 1. / count;
// return sum / (2.*area);
return sum / count;
} }
Eigen::SparseMatrix<qreal> laplacian_matrix(const MyMesh &mesh) { /* Calcule le poids cotangent pour l'arête reliant vi à vj. */
double cotangent_weight(MyMesh &mesh, HalfedgeHandle vi_vj) {
Point pj = mesh.point(mesh.to_vertex_handle(vi_vj));
HalfedgeHandle vj_vb = mesh.next_halfedge_handle(vi_vj);
Point pb = mesh.point(mesh.to_vertex_handle(vj_vb));
HalfedgeHandle vj_vi = mesh.opposite_halfedge_handle(vi_vj);
Point pi = mesh.point(mesh.to_vertex_handle(vj_vi));
HalfedgeHandle vi_va = mesh.next_halfedge_handle(vj_vi);
Point pa = mesh.point(mesh.to_vertex_handle(vi_va));
double a = angle_between(pi, pa, pj);
double b = angle_between(pj, pb, pi);
return cotan(a) + cotan(b);
}
/* Calcule l'aire de chaque face et la stoque dans une propriété
* "face_area" du maillage. */
void compute_face_areas(MyMesh &mesh) {
auto area = OpenMesh::getOrMakeProperty<FaceHandle, double>
(mesh, "face_area");
for (FaceHandle fh : mesh.faces()) {
MyMesh::FaceVertexIter fv_it = mesh.fv_iter(fh);
Point pi = mesh.point(*fv_it);
Point pj = mesh.point(*++fv_it);
Point pk = mesh.point(*++fv_it);
area[fh] = triangle_area(pi, pj, pk);
}
}
/* Calcule l'aire barycentrique incidente à vert. */
double barycentric_vertex_area(MyMesh &mesh, VertexHandle vert) {
auto area = OpenMesh::getProperty<FaceHandle, double>(mesh, "face_area");
double sum = 0;
for (FaceHandle fh : mesh.vf_range(vert)) {
sum += area[fh];
}
return sum / 3;
}
double cotangent_mass(MyMesh &mesh, VertexHandle vi) {
double area = barycentric_vertex_area(mesh, vi);
return 1. / (2 * area);
}
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(); size_t n_verts = mesh.n_vertices();
Eigen::SparseMatrix<qreal> mass(n_verts, n_verts); Eigen::SparseMatrix<double> weight(n_verts, n_verts);
Eigen::SparseMatrix<qreal> cotangent(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)) {
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.insert(vi.idx(), vi.idx()) = sum;
mass.insert(vi.idx(), vi.idx()) = vertex_mass(mesh, vi);
}
return mass * weight;
}
void smooth(MyMesh &mesh, SmoothingMethod method, double cotan_factor) {
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;
size_t n_verts = mesh.n_vertices();
Eigen::VectorX<qreal> X(n_verts), Y(n_verts), Z(n_verts);
for (VertexHandle vert : mesh.vertices()) { for (VertexHandle vert : mesh.vertices()) {
if (mesh.is_boundary(vert)) continue; if (mesh.is_boundary(vert)) continue;
qreal sum = 0; size_t id = vert.idx();
qreal area = 0;
qreal count = 0;
Point p = mesh.point(vert); Point p = mesh.point(vert);
for (HalfedgeHandle v_vi : mesh.voh_range(vert)) { X(id) = p[0];
VertexHandle vi = mesh.to_vertex_handle(v_vi); Y(id) = p[1];
Point pi = mesh.point(vi); Z(id) = p[2];
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);
Point pb = mesh.point(mesh.to_vertex_handle(vi_vb));
qreal a = angle_between(pi, pa, p);
qreal b = angle_between(pi, pb, p);
qreal w = -(cotan(a) + cotan(b)) / 2.;
sum += w;
cotangent.insert(vert.idx(), vi.idx()) = w;
area += triangle_area(p, pi, pb);
count++;
} }
// area /= 3.; X = laplacian * X;
cotangent.insert(vert.idx(), vert.idx()) = -sum; Y = laplacian * Y;
mass.insert(vert.idx(), vert.idx()) = 1. / (4. * area); Z = laplacian * Z;
// mass.insert(vert.idx(), vert.idx()) = 1. / count; for (VertexHandle vert : mesh.vertices()) {
} if (mesh.is_boundary(vert)) continue;
return mass * cotangent; size_t id = vert.idx();
} Point offset {X(id), Y(id), Z(id)};
mesh.set_point(vert, mesh.point(vert) + offset * factor);
void smooth(MyMesh &mesh) {
auto new_pos = OpenMesh::makeTemporaryProperty<VertexHandle, Point>(mesh);
for (VertexHandle v : mesh.vertices()) {
if (!mesh.is_boundary(v)) {
new_pos[v] = mesh.point(v) - laplace_beltrami(mesh, v);
} }
} }
for (VertexHandle v : mesh.vertices()) {
if (!mesh.is_boundary(v)) {
mesh.set_point(v, new_pos[v]);
}
}
// // Approche matricielle
// Eigen::SparseMatrix<qreal> laplacian = laplacian_matrix(mesh);
// // laplacian = laplacian * laplacian;
// size_t n_verts = mesh.n_vertices();
// Eigen::VectorX<qreal> X(n_verts), Y(n_verts), Z(n_verts);
// for (VertexHandle vert : mesh.vertices()) {
// if (mesh.is_boundary(vert)) continue;
// size_t id = vert.idx();
// Point p = mesh.point(vert);
// X(id) = p[0];
// Y(id) = p[1];
// Z(id) = p[2];
// }
// X = laplacian * X;
// Y = laplacian * Y;
// Z = laplacian * Z;
// for (VertexHandle vert : mesh.vertices()) {
// if (mesh.is_boundary(vert)) continue;
// size_t id = vert.idx();
// Point offset {X(id), Y(id), Z(id)};
// mesh.set_point(vert, mesh.point(vert) - offset);
// }
}

View File

@ -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