improve smoothing
This commit is contained in:
parent
be6a1d4f8a
commit
686e8cf397
7509
data/bunnyLowPoly-noisy.obj
Normal file
7509
data/bunnyLowPoly-noisy.obj
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,5 @@
|
||||
#include "hole_filling.h"
|
||||
#include "IO.h"
|
||||
#include "util.h"
|
||||
#include <MeshReconstruction.h>
|
||||
|
||||
@ -306,10 +307,8 @@ MyMesh fillHoleImplicit(MyMesh &mesh, Hole_Filling &hf,
|
||||
std::vector<VertexHandle> verts;
|
||||
for (HalfedgeHandle hh : hole) {
|
||||
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 [alpha, beta] = hf.solve_approx(system, pts_list.size(), 10);
|
||||
Implicit_RBF rbf(alpha, beta, pts_list);
|
||||
@ -339,4 +338,20 @@ std::vector<MyMesh> fillHolesImplicit(MyMesh &mesh,
|
||||
fillings.push_back(fillHoleImplicit(mesh, hf, hole));
|
||||
}
|
||||
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};
|
||||
}
|
||||
|
@ -15,8 +15,10 @@ static MeshProcessor *create_mesh_processor(const QString &path,
|
||||
mesh_processor, &MeshProcessor::fillHolesDumb);
|
||||
QObject::connect(&main_window, &MainWindow::fillHolesImplicitClicked,
|
||||
mesh_processor, &MeshProcessor::fillHolesImplicit);
|
||||
QObject::connect(&main_window, &MainWindow::smoothClicked,
|
||||
mesh_processor, &MeshProcessor::smooth);
|
||||
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,
|
||||
|
@ -16,22 +16,6 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
toolbar(this),
|
||||
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();
|
||||
setMenuBar(menu_bar);
|
||||
@ -94,12 +78,24 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
|
||||
// Smoothing tools
|
||||
QGroupBox *smooth_box = new QGroupBox("Adoucissement");
|
||||
QLayout *smooth_layout = new QVBoxLayout();
|
||||
QGridLayout *smooth_layout = new QGridLayout();
|
||||
smooth_box->setLayout(smooth_layout);
|
||||
QPushButton *smooth = new QPushButton("Adoucir");
|
||||
QPushButton *smooth = new QPushButton("Adoucir (uniforme)");
|
||||
connect(smooth, &QPushButton::clicked,
|
||||
this, &MainWindow::smoothClicked);
|
||||
smooth_layout->addWidget(smooth);
|
||||
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);
|
||||
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);
|
||||
|
||||
|
||||
|
@ -19,6 +19,8 @@ class MainWindow : public QMainWindow {
|
||||
QAction *save_action;
|
||||
DoubleInput *fill_holes_implicit_scale;
|
||||
DoubleInput *fill_holes_implicit_discr;
|
||||
DoubleInput *smooth_cotangent_factor_input;
|
||||
double smooth_cotangent_factor;
|
||||
|
||||
signals:
|
||||
void open(const QString &path);
|
||||
@ -27,7 +29,8 @@ signals:
|
||||
void fillHolesImplicitClicked();
|
||||
void fillHolesImplicitScaleChanged(float value);
|
||||
void fillHolesImplicitDiscrChanged(float value);
|
||||
void smoothClicked();
|
||||
void smoothUniformClicked();
|
||||
void smoothCotangentClicked(double factor);
|
||||
void patchViewToggled(bool checked);
|
||||
|
||||
public:
|
||||
|
@ -95,8 +95,14 @@ void MeshProcessor::setImplicitHoleFillingDiscr(float discr) {
|
||||
}
|
||||
|
||||
|
||||
void MeshProcessor::smooth() {
|
||||
::smooth(mesh);
|
||||
void MeshProcessor::smoothCotangent(double factor) {
|
||||
::smooth(mesh, SmoothingMethod::COTANGENT, factor);
|
||||
updateView();
|
||||
}
|
||||
|
||||
|
||||
void MeshProcessor::smoothUniform() {
|
||||
::smooth(mesh, SmoothingMethod::UNIFORM);
|
||||
updateView();
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,8 @@ public slots:
|
||||
void fillHolesImplicit();
|
||||
void setImplicitHoleFillingScale(float scale);
|
||||
void setImplicitHoleFillingDiscr(float discr);
|
||||
void smooth();
|
||||
void smoothCotangent(double factor);
|
||||
void smoothUniform();
|
||||
void setPatchView(bool on);
|
||||
void click(QVector3D position);
|
||||
};
|
||||
|
@ -11,12 +11,12 @@
|
||||
|
||||
/*
|
||||
vα/--\
|
||||
/ ------\ v
|
||||
/ ------\ vi
|
||||
/ ---X
|
||||
/ /---- / \
|
||||
/ /--- / -\
|
||||
/ /---- / \
|
||||
vi --- | \
|
||||
vj --- | \
|
||||
\-- / -\
|
||||
\- / /--\
|
||||
\-- / /-------
|
||||
@ -24,98 +24,119 @@
|
||||
vβ
|
||||
*/
|
||||
|
||||
Point laplace_beltrami(const MyMesh &mesh, VertexHandle vert) {
|
||||
Point sum {0, 0, 0};
|
||||
qreal area = 0;
|
||||
Point p = mesh.point(vert);
|
||||
qreal count = 0;
|
||||
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);
|
||||
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);
|
||||
sum += (cotan(a) + cotan(b)) * (p - pi);
|
||||
area += triangle_area(p, pi, pb);
|
||||
|
||||
double uniform_weight(MyMesh &mesh, HalfedgeHandle vi_vj) {
|
||||
(void) mesh;
|
||||
(void) vi_vj;
|
||||
return 1;
|
||||
}
|
||||
|
||||
double uniform_mass(MyMesh &mesh, VertexHandle vi) {
|
||||
double count = 0;
|
||||
for (VertexHandle v : mesh.vv_range(vi)) {
|
||||
(void) v;
|
||||
count++;
|
||||
}
|
||||
// area /= 3.;
|
||||
// return sum / (2.*area);
|
||||
return sum / count;
|
||||
return 1. / 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();
|
||||
Eigen::SparseMatrix<qreal> mass(n_verts, n_verts);
|
||||
Eigen::SparseMatrix<qreal> cotangent(n_verts, n_verts);
|
||||
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)) {
|
||||
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()) {
|
||||
if (mesh.is_boundary(vert)) continue;
|
||||
qreal sum = 0;
|
||||
qreal area = 0;
|
||||
qreal count = 0;
|
||||
size_t id = vert.idx();
|
||||
Point p = mesh.point(vert);
|
||||
for (HalfedgeHandle v_vi : mesh.voh_range(vert)) {
|
||||
VertexHandle vi = mesh.to_vertex_handle(v_vi);
|
||||
Point pi = mesh.point(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));
|
||||
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.;
|
||||
cotangent.insert(vert.idx(), vert.idx()) = -sum;
|
||||
mass.insert(vert.idx(), vert.idx()) = 1. / (4. * area);
|
||||
// mass.insert(vert.idx(), vert.idx()) = 1. / count;
|
||||
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 * factor);
|
||||
}
|
||||
return mass * cotangent;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
// }
|
||||
}
|
||||
|
@ -4,8 +4,12 @@
|
||||
#include "my_mesh.h"
|
||||
|
||||
|
||||
Point laplace_beltrami(const MyMesh &mesh, VertexHandle vert);
|
||||
void smooth(MyMesh &mesh);
|
||||
enum class SmoothingMethod {
|
||||
UNIFORM,
|
||||
COTANGENT,
|
||||
};
|
||||
|
||||
void smooth(MyMesh &mesh, SmoothingMethod method, double cotan_factor=.0001);
|
||||
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user