From 3230628abfb6e80aeb4c4175e51ea0a0af2616ec Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 19 Jul 2023 14:57:59 +0300 Subject: [PATCH 01/56] ACVD initial cluster expansion --- .../Polygon_mesh_processing/CMakeLists.txt | 2 + .../Polygon_mesh_processing/acvd_example.cpp | 38 ++++ .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 197 ++++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp create mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index c755ecf63e8..114cea5422d 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -51,6 +51,8 @@ create_single_source_cgal_program("match_faces.cpp") create_single_source_cgal_program("cc_compatible_orientations.cpp") create_single_source_cgal_program("hausdorff_distance_remeshing_example.cpp") create_single_source_cgal_program("hausdorff_bounded_error_distance_example.cpp") +create_single_source_cgal_program("acvd_example.cpp") + find_package(Eigen3 3.2.0 QUIET) #(requires 3.2.0 or greater) include(CGAL_Eigen3_support) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp new file mode 100644 index 00000000000..d998c447999 --- /dev/null +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include + +#include + +#include + +namespace PMP = CGAL::Polygon_mesh_processing; + +typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic_kernel; +typedef CGAL::Surface_mesh Surface_Mesh; +typedef boost::graph_traits::vertex_descriptor vertex_descriptor; +typedef boost::property_map >::type VertexColorMap; + + +int main(int argc, char* argv[]) +{ + Surface_Mesh smesh; + const std::string filename = (argc > 1) ? + argv[1] : + CGAL::data_file_path("meshes/sphere.off"); + + if (!CGAL::IO::read_polygon_mesh(filename, smesh)) + { + std::cerr << "Invalid input file." << std::endl; + return EXIT_FAILURE; + } + + auto vcm = PMP::acvd_isotropic_simplification(smesh, 70); + + // Output the simplified mesh, use write_OFF() + CGAL::IO::write_OFF("sphere_clustered_0.off", smesh, CGAL::parameters::stream_precision(17).vertex_color_map(vcm)); + +} + diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h new file mode 100644 index 00000000000..3c034706be8 --- /dev/null +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -0,0 +1,197 @@ +// Copyright (c) 2023 GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// +// Author(s) : Hossam Saeed +// + +// #ifndef CGAL_POLYGON_MESH_PROCESSING_<> +// #define CGAL_POLYGON_MESH_PROCESSING_<> + +// #include > + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace CGAL { + +namespace Polygon_mesh_processing { + +namespace internal { + +template +struct IsotropicMetricCluster { + typename GT::Vector_3 site_sum; + typename GT::FT weight; + typename GT::FT energy; + + IsotropicMetricCluster() : site_sum(0, 0, 0), weight(0), energy(0) {} + + void add_vertex(const typename GT::Vector_3& vertex_position, const typename GT::FT& weight = 1) + { + site_sum += vertex_position * weight; + this->weight += weight; + } + + void remove_vertex(const typename GT::Vector_3& vertex_position, const typename GT::FT& weight = 1) + { + site_sum -= vertex_position * weight; + this->weight -= weight; + } +}; + +template +typename boost::property_map >::type + acvd_simplification( + PolygonMesh& pmesh, + const int& nb_vertices, + const NamedParameters& np = parameters::default_values() + // seed_randomization can be a named parameter +) +{ + typedef typename GetGeomTraits::type GT; + typedef typename GetVertexPointMap::const_type Vertex_position_map; + typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename boost::property_map >::type VertexColorMap; + typedef typename boost::property_map >::type VertexClusterMap; + + + using parameters::choose_parameter; + using parameters::get_parameter; + using parameters::is_default_parameter; + + Vertex_position_map vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), + get_property_map(CGAL::vertex_point, pmesh)); + + // initial random clusters + // property map from vertex_descriptor to cluster index + boost::property_map>::type + vertex_clusters_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); + VertexClusterMap clusters = get(CGAL::dynamic_vertex_property_t(), pmesh); + std::vector> clusters_sites(nb_vertices); + std::queue clusters_edges; + + srand(time(NULL)); + for(int ci = 0; ci < nb_vertices; ci++) + { + // random index + int vi = rand() % num_vertices(pmesh); + Vertex_descriptor vd = *(vertices(pmesh).begin() + vi); + // TODO: check for cluster conflict at the same vertex + put(vertex_clusters_pmap, vd, ci + 1); // TODO: should be ci but for now we start from 1 (can't set null value to -1) + typename GT::Point_3 vp = get(vpm, vd); + typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); + clusters_sites[ci].add_vertex(vpv); + for (Halfedge_descriptor hd : halfedges_around_source(halfedge(vd, pmesh), pmesh)) + clusters_edges.push(hd); + } + + // minimize the energy + int nb_modifications = 0; + int prev_nb_modifications = 0; + + while (nb_modifications != prev_nb_modifications) { + Halfedge_descriptor hi = clusters_edges.front(); + clusters_edges.pop(); + + Vertex_descriptor v1 = source(hi, pmesh); + Vertex_descriptor v2 = target(hi, pmesh); + + int c1 = get(vertex_clusters_pmap, v1); + int c2 = get(vertex_clusters_pmap, v2); + + prev_nb_modifications = nb_modifications; + + if (c1 == 0) + { + // expand cluster c2 (add v1 to c2) + put(vertex_clusters_pmap, v1, c2); + typename GT::Point_3 vp = get(vpm, v1); + typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); + clusters_sites[c2].add_vertex(vpv); + + // add all halfedges around v1 except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v1, pmesh), pmesh)) + if (hd != hi) + clusters_edges.push(hd); + nb_modifications++; + + } + else if (c2 == 0) + { + // expand cluster c1 (add v2 to c1) + put(vertex_clusters_pmap, v2, c1); + typename GT::Point_3 vp = get(vpm, v2); + typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); + clusters_sites[c1].add_vertex(vpv); + // add all halfedges around v2 except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v2, pmesh), pmesh)) + if (hd != hi) + clusters_edges.push(hd); + nb_modifications++; + } + else if (c1 == c2) + continue; // no modification + else + { + // compare the energy of the 3 cases + continue; + } + } + + VertexColorMap vcm = get(CGAL::dynamic_vertex_property_t(), pmesh); + + for (Vertex_descriptor vd : vertices(pmesh)) + { + int c = get(vertex_clusters_pmap, vd); + if (!c) c = 0; + std::cout << c << std::endl; + CGAL::IO::Color color(255 - (c * 255 / nb_vertices), (c % 10) * 255 / 10, (c % 50) * 255 / 50); + put(vcm, vd, color); + } + + return vcm; +} + + +} // namespace internal + +template + typename boost::property_map >::type + acvd_isotropic_simplification( + PolygonMesh& pmesh, + const int& nb_vertices, + const NamedParameters& np = parameters::default_values() +) +{ + return internal::acvd_simplification( + pmesh, + nb_vertices, + np + ); +} + +} // namespace Polygon_mesh_processing + +} // namespace CGAL From 6625e100b5e9ff32d6cfa66252e3a8c724bc3ecb Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 20 Jul 2023 15:27:39 +0300 Subject: [PATCH 02/56] energy minimzaition (not stable yet) --- .../Polygon_mesh_processing/acvd_example.cpp | 8 +- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 285 +++++++++++------- 2 files changed, 189 insertions(+), 104 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp index d998c447999..d4b125e243b 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -21,7 +21,7 @@ int main(int argc, char* argv[]) Surface_Mesh smesh; const std::string filename = (argc > 1) ? argv[1] : - CGAL::data_file_path("meshes/sphere.off"); + CGAL::data_file_path("meshes/eight.off"); if (!CGAL::IO::read_polygon_mesh(filename, smesh)) { @@ -29,10 +29,12 @@ int main(int argc, char* argv[]) return EXIT_FAILURE; } - auto vcm = PMP::acvd_isotropic_simplification(smesh, 70); + PMP::acvd_isotropic_simplification(smesh, 70); + std::cout << "kak3" << std::endl; + return 0; // Output the simplified mesh, use write_OFF() - CGAL::IO::write_OFF("sphere_clustered_0.off", smesh, CGAL::parameters::stream_precision(17).vertex_color_map(vcm)); + //CGAL::IO::write_OFF("sphere966_clustered_0.off", smesh, CGAL::parameters::stream_precision(17).vertex_color_map(vcm)); } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 3c034706be8..d1f2d0d807d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -24,6 +24,8 @@ #include #include #include +#include + #include #include @@ -38,157 +40,238 @@ namespace Polygon_mesh_processing { namespace internal { template -struct IsotropicMetricCluster { +struct ClusterData { typename GT::Vector_3 site_sum; - typename GT::FT weight; + typename GT::FT weight_sum; typename GT::FT energy; - IsotropicMetricCluster() : site_sum(0, 0, 0), weight(0), energy(0) {} + ClusterData() : site_sum(0, 0, 0), weight_sum(0), energy(0) {} - void add_vertex(const typename GT::Vector_3& vertex_position, const typename GT::FT& weight = 1) + void add_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight = 1) { - site_sum += vertex_position * weight; - this->weight += weight; + this->site_sum += vertex_position * weight; + this->weight_sum += weight; } - void remove_vertex(const typename GT::Vector_3& vertex_position, const typename GT::FT& weight = 1) + void remove_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight = 1) { - site_sum -= vertex_position * weight; - this->weight -= weight; + this->site_sum -= vertex_position * weight; + this->weight_sum -= weight; + } + + typename GT::FT compute_energy(const typename GT::Vector_3 vertex_position, const typename GT::FT weight = 1) + { + this->energy = - (this->site_sum).squared_length() / this->weight_sum; + return this->energy; } }; template -typename boost::property_map >::type - acvd_simplification( + typename NamedParameters = parameters::Default_named_parameters> +void acvd_simplification( PolygonMesh& pmesh, - const int& nb_vertices, + const int& nb_clusters, const NamedParameters& np = parameters::default_values() // seed_randomization can be a named parameter -) + ) { - typedef typename GetGeomTraits::type GT; - typedef typename GetVertexPointMap::const_type Vertex_position_map; - typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; - typedef typename boost::property_map >::type VertexColorMap; - typedef typename boost::property_map >::type VertexClusterMap; + typedef typename GetGeomTraits::type GT; + typedef typename GetVertexPointMap::const_type Vertex_position_map; + typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename boost::property_map >::type VertexColorMap; + typedef typename boost::property_map >::type VertexClusterMap; + + using parameters::choose_parameter; + using parameters::get_parameter; + using parameters::is_default_parameter; + + Vertex_position_map vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), + get_property_map(CGAL::vertex_point, pmesh)); + + // initial random clusters + // property map from vertex_descriptor to cluster index + VertexClusterMap vertex_clusters_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); + std::vector> clusters(nb_clusters); + std::queue clusters_edges_active; + std::queue clusters_edges_new; + + int nb_vertices = num_vertices(pmesh); + + srand(time(NULL)); + for (int ci = 0; ci < nb_clusters; ci++) + { + // random index + int vi = rand() % num_vertices(pmesh); + Vertex_descriptor vd = *(vertices(pmesh).begin() + vi); + /*int vi; + Vertex_descriptor vd; + do { + vi = ci * nb_vertices / nb_clusters; + vd = *(vertices(pmesh).begin() + vi); + } while (get(vertex_clusters_pmap, vd));*/ + + // TODO: check for cluster conflict at the same vertex + put(vertex_clusters_pmap, vd, ci + 1); // TODO: should be ci but for now we start from 1 (can't set null value to -1) + typename GT::Point_3 vp = get(vpm, vd); + typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); + clusters[ci].add_vertex(vpv); + + for (Halfedge_descriptor hd : halfedges_around_source(halfedge(vd, pmesh), pmesh)) + clusters_edges_active.push(hd); + } + + int nb_modifications; + + do + { + nb_modifications = 0; + + while (clusters_edges_active.empty() == false) { + Halfedge_descriptor hi = clusters_edges_active.front(); + clusters_edges_active.pop(); + + Vertex_descriptor v1 = source(hi, pmesh); + Vertex_descriptor v2 = target(hi, pmesh); + + int c1 = get(vertex_clusters_pmap, v1); + int c2 = get(vertex_clusters_pmap, v2); + + if (c1 == 0) + { + // expand cluster c2 (add v1 to c2) + put(vertex_clusters_pmap, v1, c2); + typename GT::Point_3 vp1 = get(vpm, v1); + typename GT::Vector_3 vpv(vp1.x(), vp1.y(), vp1.z()); + clusters[c2].add_vertex(vpv); + clusters[c1].remove_vertex(vpv); - using parameters::choose_parameter; - using parameters::get_parameter; - using parameters::is_default_parameter; + // add all halfedges around v1 except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v1, pmesh), pmesh)) + if (hd != hi) + clusters_edges_new.push(hd); + nb_modifications++; - Vertex_position_map vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), - get_property_map(CGAL::vertex_point, pmesh)); + } + else if (c2 == 0) + { + // expand cluster c1 (add v2 to c1) + put(vertex_clusters_pmap, v2, c1); + typename GT::Point_3 vp2 = get(vpm, v2); + typename GT::Vector_3 vpv(vp2.x(), vp2.y(), vp2.z()); + clusters[c1].add_vertex(vpv); + clusters[c2].remove_vertex(vpv); - // initial random clusters - // property map from vertex_descriptor to cluster index - boost::property_map>::type - vertex_clusters_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); - VertexClusterMap clusters = get(CGAL::dynamic_vertex_property_t(), pmesh); - std::vector> clusters_sites(nb_vertices); - std::queue clusters_edges; - srand(time(NULL)); - for(int ci = 0; ci < nb_vertices; ci++) - { - // random index - int vi = rand() % num_vertices(pmesh); - Vertex_descriptor vd = *(vertices(pmesh).begin() + vi); - // TODO: check for cluster conflict at the same vertex - put(vertex_clusters_pmap, vd, ci + 1); // TODO: should be ci but for now we start from 1 (can't set null value to -1) - typename GT::Point_3 vp = get(vpm, vd); - typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); - clusters_sites[ci].add_vertex(vpv); - for (Halfedge_descriptor hd : halfedges_around_source(halfedge(vd, pmesh), pmesh)) - clusters_edges.push(hd); - } + // add all halfedges around v2 except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v2, pmesh), pmesh)) + if (hd != hi) + clusters_edges_new.push(hd); + nb_modifications++; + } + else if (c1 == c2) + continue; // no modification + else + { + // compare the energy of the 3 cases + typename GT::Point_3 vp1 = get(vpm, v1); + typename GT::Vector_3 vpv1(vp1.x(), vp1.y(), vp1.z()); + typename GT::Point_3 vp2 = get(vpm, v2); + typename GT::Vector_3 vpv2(vp2.x(), vp2.y(), vp2.z()); - // minimize the energy - int nb_modifications = 0; - int prev_nb_modifications = 0; + typename GT::FT e_no_change = clusters[c1].compute_energy(vpv1) + clusters[c2].compute_energy(vpv2); - while (nb_modifications != prev_nb_modifications) { - Halfedge_descriptor hi = clusters_edges.front(); - clusters_edges.pop(); + clusters[c1].remove_vertex(vpv1); + clusters[c2].add_vertex(vpv1); - Vertex_descriptor v1 = source(hi, pmesh); - Vertex_descriptor v2 = target(hi, pmesh); + typename GT::FT e_v1_to_c2 = clusters[c1].compute_energy(vpv1) + clusters[c2].compute_energy(vpv2); - int c1 = get(vertex_clusters_pmap, v1); - int c2 = get(vertex_clusters_pmap, v2); + // reset to no change + clusters[c1].add_vertex(vpv1); + clusters[c2].remove_vertex(vpv1); - prev_nb_modifications = nb_modifications; + // The effect of the following should always be reversed after the comparison + clusters[c2].remove_vertex(vpv2); + clusters[c1].add_vertex(vpv2); - if (c1 == 0) + typename GT::FT e_v2_to_c1 = clusters[c1].compute_energy(vpv1) + clusters[c2].compute_energy(vpv2); + + if (e_v2_to_c1 < e_no_change && e_v2_to_c1 < e_v1_to_c2) { - // expand cluster c2 (add v1 to c2) - put(vertex_clusters_pmap, v1, c2); - typename GT::Point_3 vp = get(vpm, v1); - typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); - clusters_sites[c2].add_vertex(vpv); + // move v2 to c1 + put(vertex_clusters_pmap, v2, c1); - // add all halfedges around v1 except hi to the queue - for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v1, pmesh), pmesh)) - if (hd != hi) - clusters_edges.push(hd); - nb_modifications++; + // cluster data is already updated + // add all halfedges around v2 except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v2, pmesh), pmesh)) + if (hd != hi) + clusters_edges_new.push(hd); + nb_modifications++; } - else if (c2 == 0) + else if (e_v1_to_c2 < e_no_change) { - // expand cluster c1 (add v2 to c1) - put(vertex_clusters_pmap, v2, c1); - typename GT::Point_3 vp = get(vpm, v2); - typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); - clusters_sites[c1].add_vertex(vpv); - // add all halfedges around v2 except hi to the queue - for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v2, pmesh), pmesh)) - if (hd != hi) - clusters_edges.push(hd); - nb_modifications++; + // move v1 to c2 + put(vertex_clusters_pmap, v1, c2); + + // need to reset cluster data and then update + clusters[c2].add_vertex(vpv2); + clusters[c1].remove_vertex(vpv2); + + clusters[c1].remove_vertex(vpv1); + clusters[c2].add_vertex(vpv1); + + // add all halfedges around v1 except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v1, pmesh), pmesh)) + if (hd != hi) + clusters_edges_new.push(hd); + nb_modifications++; } - else if (c1 == c2) - continue; // no modification else { - // compare the energy of the 3 cases - continue; + // no change but need to reset cluster data + clusters[c2].add_vertex(vpv2); + clusters[c1].remove_vertex(vpv2); } + + continue; + } } + clusters_edges_active.swap(clusters_edges_new); + } while (nb_modifications > 0); - VertexColorMap vcm = get(CGAL::dynamic_vertex_property_t(), pmesh); - for (Vertex_descriptor vd : vertices(pmesh)) - { - int c = get(vertex_clusters_pmap, vd); - if (!c) c = 0; - std::cout << c << std::endl; - CGAL::IO::Color color(255 - (c * 255 / nb_vertices), (c % 10) * 255 / 10, (c % 50) * 255 / 50); - put(vcm, vd, color); - } + VertexColorMap vcm = get(CGAL::dynamic_vertex_property_t(), pmesh); + + for (Vertex_descriptor vd : vertices(pmesh)) + { + int c = get(vertex_clusters_pmap, vd); + CGAL::IO::Color color(255 - (c * 255 / nb_clusters), (c * c % 7) * 255 / 7, (c * c * c % 31) * 255 / 31); + std::cout << vd.idx() << " " << c << " " << color << std::endl; + put(vcm, vd, color); + } + std::cout << "kak1" << std::endl; + CGAL::IO::write_OFF("eight_clustered_0.off", pmesh, CGAL::parameters::vertex_color_map(vcm)); + std::cout << "kak2" << std::endl; - return vcm; } } // namespace internal template - typename boost::property_map >::type - acvd_isotropic_simplification( + typename NamedParameters = parameters::Default_named_parameters> +void acvd_isotropic_simplification( PolygonMesh& pmesh, const int& nb_vertices, const NamedParameters& np = parameters::default_values() -) + ) { - return internal::acvd_simplification( - pmesh, - nb_vertices, - np + internal::acvd_simplification( + pmesh, + nb_vertices, + np ); } From b7512e7ade12e265e8c926545917d9cfc5304fce Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 20 Jul 2023 15:34:22 +0300 Subject: [PATCH 03/56] temp fix --- .../include/CGAL/Polygon_mesh_processing/acvd/acvd.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index d1f2d0d807d..ef36132b05a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -92,7 +92,7 @@ void acvd_simplification( // initial random clusters // property map from vertex_descriptor to cluster index VertexClusterMap vertex_clusters_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); - std::vector> clusters(nb_clusters); + std::vector> clusters(nb_clusters + 1); std::queue clusters_edges_active; std::queue clusters_edges_new; From c9e4db0d63eced1f73886a977d2ea5d1a586d20b Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 22 Jul 2023 14:38:46 +0300 Subject: [PATCH 04/56] weight (dual area computation) --- .../Polygon_mesh_processing/acvd_example.cpp | 2 +- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 71 +++++++++++-------- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp index d4b125e243b..77769b14206 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -21,7 +21,7 @@ int main(int argc, char* argv[]) Surface_Mesh smesh; const std::string filename = (argc > 1) ? argv[1] : - CGAL::data_file_path("meshes/eight.off"); + CGAL::data_file_path("meshes/S52k.stl"); if (!CGAL::IO::read_polygon_mesh(filename, smesh)) { diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index ef36132b05a..0338864db8c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -47,19 +47,19 @@ struct ClusterData { ClusterData() : site_sum(0, 0, 0), weight_sum(0), energy(0) {} - void add_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight = 1) + void add_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight) { - this->site_sum += vertex_position * weight; + this->site_sum += vertex_position; this->weight_sum += weight; } - void remove_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight = 1) + void remove_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight) { - this->site_sum -= vertex_position * weight; + this->site_sum -= vertex_position; this->weight_sum -= weight; } - typename GT::FT compute_energy(const typename GT::Vector_3 vertex_position, const typename GT::FT weight = 1) + typename GT::FT compute_energy() { this->energy = - (this->site_sum).squared_length() / this->weight_sum; return this->energy; @@ -68,7 +68,7 @@ struct ClusterData { template -void acvd_simplification( +void acvd_simplification( PolygonMesh& pmesh, const int& nb_clusters, const NamedParameters& np = parameters::default_values() @@ -79,8 +79,10 @@ void acvd_simplification( typedef typename GetVertexPointMap::const_type Vertex_position_map; typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename boost::graph_traits::face_descriptor Face_descriptor; typedef typename boost::property_map >::type VertexColorMap; typedef typename boost::property_map >::type VertexClusterMap; + typedef typename boost::property_map >::type VertexWeightMap; using parameters::choose_parameter; using parameters::get_parameter; @@ -92,13 +94,24 @@ void acvd_simplification( // initial random clusters // property map from vertex_descriptor to cluster index VertexClusterMap vertex_clusters_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); + VertexWeightMap vertex_weight_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); std::vector> clusters(nb_clusters + 1); std::queue clusters_edges_active; std::queue clusters_edges_new; int nb_vertices = num_vertices(pmesh); - srand(time(NULL)); + // compute vertex weights (dual area) + for (Face_descriptor fd : faces(pmesh)) + { + typename GT::FT weight = CGAL::Polygon_mesh_processing::face_area(fd, pmesh) / 3; + for (Vertex_descriptor vd : vertices_around_face(halfedge(fd, pmesh), pmesh)) + { + put(vertex_weight_pmap, vd, weight); + } + } + + srand(3); for (int ci = 0; ci < nb_clusters; ci++) { // random index @@ -115,7 +128,7 @@ void acvd_simplification( put(vertex_clusters_pmap, vd, ci + 1); // TODO: should be ci but for now we start from 1 (can't set null value to -1) typename GT::Point_3 vp = get(vpm, vd); typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); - clusters[ci].add_vertex(vpv); + clusters[ci].add_vertex(vpv, get(vertex_weight_pmap, vd)); for (Halfedge_descriptor hd : halfedges_around_source(halfedge(vd, pmesh), pmesh)) clusters_edges_active.push(hd); @@ -143,8 +156,8 @@ void acvd_simplification( put(vertex_clusters_pmap, v1, c2); typename GT::Point_3 vp1 = get(vpm, v1); typename GT::Vector_3 vpv(vp1.x(), vp1.y(), vp1.z()); - clusters[c2].add_vertex(vpv); - clusters[c1].remove_vertex(vpv); + clusters[c2].add_vertex(vpv, get(vertex_weight_pmap, v1)); + clusters[c1].remove_vertex(vpv, get(vertex_weight_pmap, v1)); // add all halfedges around v1 except hi to the queue @@ -160,8 +173,8 @@ void acvd_simplification( put(vertex_clusters_pmap, v2, c1); typename GT::Point_3 vp2 = get(vpm, v2); typename GT::Vector_3 vpv(vp2.x(), vp2.y(), vp2.z()); - clusters[c1].add_vertex(vpv); - clusters[c2].remove_vertex(vpv); + clusters[c1].add_vertex(vpv, get(vertex_weight_pmap, v2)); + clusters[c2].remove_vertex(vpv, get(vertex_weight_pmap, v2)); // add all halfedges around v2 except hi to the queue @@ -171,7 +184,9 @@ void acvd_simplification( nb_modifications++; } else if (c1 == c2) + { continue; // no modification + } else { // compare the energy of the 3 cases @@ -180,22 +195,22 @@ void acvd_simplification( typename GT::Point_3 vp2 = get(vpm, v2); typename GT::Vector_3 vpv2(vp2.x(), vp2.y(), vp2.z()); - typename GT::FT e_no_change = clusters[c1].compute_energy(vpv1) + clusters[c2].compute_energy(vpv2); + typename GT::FT e_no_change = clusters[c1].compute_energy() + clusters[c2].compute_energy(); - clusters[c1].remove_vertex(vpv1); - clusters[c2].add_vertex(vpv1); + clusters[c1].remove_vertex(vpv1, get(vertex_weight_pmap, v1)); + clusters[c2].add_vertex(vpv1, get(vertex_weight_pmap, v1)); - typename GT::FT e_v1_to_c2 = clusters[c1].compute_energy(vpv1) + clusters[c2].compute_energy(vpv2); + typename GT::FT e_v1_to_c2 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); // reset to no change - clusters[c1].add_vertex(vpv1); - clusters[c2].remove_vertex(vpv1); + clusters[c1].add_vertex(vpv1, get(vertex_weight_pmap, v1)); + clusters[c2].remove_vertex(vpv1, get(vertex_weight_pmap, v1)); // The effect of the following should always be reversed after the comparison - clusters[c2].remove_vertex(vpv2); - clusters[c1].add_vertex(vpv2); + clusters[c2].remove_vertex(vpv2, get(vertex_weight_pmap, v2)); + clusters[c1].add_vertex(vpv2, get(vertex_weight_pmap, v2)); - typename GT::FT e_v2_to_c1 = clusters[c1].compute_energy(vpv1) + clusters[c2].compute_energy(vpv2); + typename GT::FT e_v2_to_c1 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); if (e_v2_to_c1 < e_no_change && e_v2_to_c1 < e_v1_to_c2) { @@ -216,11 +231,11 @@ void acvd_simplification( put(vertex_clusters_pmap, v1, c2); // need to reset cluster data and then update - clusters[c2].add_vertex(vpv2); - clusters[c1].remove_vertex(vpv2); + clusters[c2].add_vertex(vpv2, get(vertex_weight_pmap, v2)); + clusters[c1].remove_vertex(vpv2, get(vertex_weight_pmap, v2)); - clusters[c1].remove_vertex(vpv1); - clusters[c2].add_vertex(vpv1); + clusters[c1].remove_vertex(vpv1, get(vertex_weight_pmap, v1)); + clusters[c2].add_vertex(vpv1, get(vertex_weight_pmap, v1)); // add all halfedges around v1 except hi to the queue for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v1, pmesh), pmesh)) @@ -231,8 +246,8 @@ void acvd_simplification( else { // no change but need to reset cluster data - clusters[c2].add_vertex(vpv2); - clusters[c1].remove_vertex(vpv2); + clusters[c2].add_vertex(vpv2, get(vertex_weight_pmap, v2)); + clusters[c1].remove_vertex(vpv2, get(vertex_weight_pmap, v2)); } continue; @@ -252,7 +267,7 @@ void acvd_simplification( put(vcm, vd, color); } std::cout << "kak1" << std::endl; - CGAL::IO::write_OFF("eight_clustered_0.off", pmesh, CGAL::parameters::vertex_color_map(vcm)); + CGAL::IO::write_OFF("S52k_clustered_0.off", pmesh, CGAL::parameters::vertex_color_map(vcm)); std::cout << "kak2" << std::endl; } From 809e22f5637029fa10630767d23c0f353b12d7d6 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 22 Jul 2023 14:39:00 +0300 Subject: [PATCH 05/56] pushing same edge if no update --- .../include/CGAL/Polygon_mesh_processing/acvd/acvd.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 0338864db8c..137e428dd7e 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -185,6 +185,7 @@ void acvd_simplification( } else if (c1 == c2) { + clusters_edges_new.push(hi); continue; // no modification } else @@ -248,6 +249,8 @@ void acvd_simplification( // no change but need to reset cluster data clusters[c2].add_vertex(vpv2, get(vertex_weight_pmap, v2)); clusters[c1].remove_vertex(vpv2, get(vertex_weight_pmap, v2)); + + clusters_edges_new.push(hi); } continue; From 190ac09dc396aff747c1e119a04e99be61dea034 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 1 Aug 2023 03:12:41 +0300 Subject: [PATCH 06/56] some fixes (now clusters look very clean) need to clean up code --- .../Polygon_mesh_processing/acvd_example.cpp | 8 +- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 122 +++++++++++------- 2 files changed, 77 insertions(+), 53 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp index 77769b14206..d6c2212acac 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -20,8 +20,10 @@ int main(int argc, char* argv[]) { Surface_Mesh smesh; const std::string filename = (argc > 1) ? - argv[1] : - CGAL::data_file_path("meshes/S52k.stl"); + CGAL::data_file_path(argv[1]) : + CGAL::data_file_path("meshes/bear.off"); + + const int nb_clusters = (argc > 2) ? atoi(argv[2]) : 50; if (!CGAL::IO::read_polygon_mesh(filename, smesh)) { @@ -29,7 +31,7 @@ int main(int argc, char* argv[]) return EXIT_FAILURE; } - PMP::acvd_isotropic_simplification(smesh, 70); + PMP::acvd_isotropic_simplification(smesh, nb_clusters); std::cout << "kak3" << std::endl; return 0; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 137e428dd7e..c0c52d75ecf 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -50,13 +50,13 @@ struct ClusterData { void add_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight) { this->site_sum += vertex_position; - this->weight_sum += weight; + this->weight_sum += 1; } void remove_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight) { this->site_sum -= vertex_position; - this->weight_sum -= weight; + this->weight_sum -= 1; } typename GT::FT compute_energy() @@ -93,7 +93,7 @@ void acvd_simplification( // initial random clusters // property map from vertex_descriptor to cluster index - VertexClusterMap vertex_clusters_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); + VertexClusterMap vertex_cluster_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); VertexWeightMap vertex_weight_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); std::vector> clusters(nb_clusters + 1); std::queue clusters_edges_active; @@ -101,41 +101,57 @@ void acvd_simplification( int nb_vertices = num_vertices(pmesh); + typename GT::FT max_area = 0; + typename GT::FT min_area = std::numeric_limits::max(); + // compute vertex weights (dual area) for (Face_descriptor fd : faces(pmesh)) { typename GT::FT weight = CGAL::Polygon_mesh_processing::face_area(fd, pmesh) / 3; + + max_area = std::max(max_area, weight * 8.0); + min_area = std::min(min_area, weight * 3.0); + for (Vertex_descriptor vd : vertices_around_face(halfedge(fd, pmesh), pmesh)) { - put(vertex_weight_pmap, vd, weight); + typename GT::FT vertex_weight = get(vertex_weight_pmap, vd); + vertex_weight += weight; + put(vertex_weight_pmap, vd, vertex_weight); } } srand(3); for (int ci = 0; ci < nb_clusters; ci++) { - // random index - int vi = rand() % num_vertices(pmesh); - Vertex_descriptor vd = *(vertices(pmesh).begin() + vi); - /*int vi; + //// random index + //int vi = rand() % num_vertices(pmesh); + //Vertex_descriptor vd = *(vertices(pmesh).begin() + vi); + int vi; Vertex_descriptor vd; do { - vi = ci * nb_vertices / nb_clusters; + vi = rand() % num_vertices(pmesh); vd = *(vertices(pmesh).begin() + vi); - } while (get(vertex_clusters_pmap, vd));*/ + } while (get(vertex_cluster_pmap, vd)); // TODO: check for cluster conflict at the same vertex - put(vertex_clusters_pmap, vd, ci + 1); // TODO: should be ci but for now we start from 1 (can't set null value to -1) + put(vertex_cluster_pmap, vd, ci + 1); // TODO: should be ci but for now we start from 1 (can't set null value to -1) typename GT::Point_3 vp = get(vpm, vd); typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); clusters[ci].add_vertex(vpv, get(vertex_weight_pmap, vd)); - for (Halfedge_descriptor hd : halfedges_around_source(halfedge(vd, pmesh), pmesh)) + for (Halfedge_descriptor hd : halfedges_around_source(vd, pmesh)) clusters_edges_active.push(hd); } int nb_modifications; + int nb_mod1, nb_mod2, nb_mod3, nb_mod4, nb_mod5; + nb_mod1 = 0; + nb_mod2 = 0; + nb_mod3 = 0; + nb_mod4 = 0; + nb_mod5 = 0; + do { nb_modifications = 0; @@ -147,46 +163,42 @@ void acvd_simplification( Vertex_descriptor v1 = source(hi, pmesh); Vertex_descriptor v2 = target(hi, pmesh); - int c1 = get(vertex_clusters_pmap, v1); - int c2 = get(vertex_clusters_pmap, v2); + int c1 = get(vertex_cluster_pmap, v1); + int c2 = get(vertex_cluster_pmap, v2); - if (c1 == 0) + if (!(c1 > 0)) { // expand cluster c2 (add v1 to c2) - put(vertex_clusters_pmap, v1, c2); + put(vertex_cluster_pmap, v1, c2); typename GT::Point_3 vp1 = get(vpm, v1); typename GT::Vector_3 vpv(vp1.x(), vp1.y(), vp1.z()); clusters[c2].add_vertex(vpv, get(vertex_weight_pmap, v1)); - clusters[c1].remove_vertex(vpv, get(vertex_weight_pmap, v1)); - // add all halfedges around v1 except hi to the queue - for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v1, pmesh), pmesh)) - if (hd != hi) + for (Halfedge_descriptor hd : halfedges_around_source(v1, pmesh)) + //if (hd != hi && hd != opposite(hi, pmesh)) clusters_edges_new.push(hd); nb_modifications++; - + nb_mod1++; } - else if (c2 == 0) + else if (!(c2 > 0)) { // expand cluster c1 (add v2 to c1) - put(vertex_clusters_pmap, v2, c1); + put(vertex_cluster_pmap, v2, c1); typename GT::Point_3 vp2 = get(vpm, v2); typename GT::Vector_3 vpv(vp2.x(), vp2.y(), vp2.z()); clusters[c1].add_vertex(vpv, get(vertex_weight_pmap, v2)); - clusters[c2].remove_vertex(vpv, get(vertex_weight_pmap, v2)); - // add all halfedges around v2 except hi to the queue - for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v2, pmesh), pmesh)) - if (hd != hi) + for (Halfedge_descriptor hd : halfedges_around_source(v2, pmesh)) + //if (hd != hi && hd != opposite(hi, pmesh)) clusters_edges_new.push(hd); nb_modifications++; + nb_mod2++; } else if (c1 == c2) { clusters_edges_new.push(hi); - continue; // no modification } else { @@ -195,82 +207,92 @@ void acvd_simplification( typename GT::Vector_3 vpv1(vp1.x(), vp1.y(), vp1.z()); typename GT::Point_3 vp2 = get(vpm, v2); typename GT::Vector_3 vpv2(vp2.x(), vp2.y(), vp2.z()); + typename GT::FT v1_weight = get(vertex_weight_pmap, v1); + typename GT::FT v2_weight = get(vertex_weight_pmap, v2); typename GT::FT e_no_change = clusters[c1].compute_energy() + clusters[c2].compute_energy(); - clusters[c1].remove_vertex(vpv1, get(vertex_weight_pmap, v1)); - clusters[c2].add_vertex(vpv1, get(vertex_weight_pmap, v1)); + clusters[c1].remove_vertex(vpv1, v1_weight); + clusters[c2].add_vertex(vpv1, v1_weight); typename GT::FT e_v1_to_c2 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); // reset to no change - clusters[c1].add_vertex(vpv1, get(vertex_weight_pmap, v1)); - clusters[c2].remove_vertex(vpv1, get(vertex_weight_pmap, v1)); + clusters[c1].add_vertex(vpv1, v1_weight); + clusters[c2].remove_vertex(vpv1, v1_weight); // The effect of the following should always be reversed after the comparison - clusters[c2].remove_vertex(vpv2, get(vertex_weight_pmap, v2)); - clusters[c1].add_vertex(vpv2, get(vertex_weight_pmap, v2)); + clusters[c2].remove_vertex(vpv2, v2_weight); + clusters[c1].add_vertex(vpv2, v2_weight); typename GT::FT e_v2_to_c1 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); if (e_v2_to_c1 < e_no_change && e_v2_to_c1 < e_v1_to_c2) { // move v2 to c1 - put(vertex_clusters_pmap, v2, c1); + put(vertex_cluster_pmap, v2, c1); // cluster data is already updated // add all halfedges around v2 except hi to the queue - for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v2, pmesh), pmesh)) - if (hd != hi) + for (Halfedge_descriptor hd : halfedges_around_source(v2, pmesh)) + //if (hd != hi && hd != opposite(hi, pmesh)) clusters_edges_new.push(hd); nb_modifications++; + nb_mod3++; } else if (e_v1_to_c2 < e_no_change) { // move v1 to c2 - put(vertex_clusters_pmap, v1, c2); + put(vertex_cluster_pmap, v1, c2); // need to reset cluster data and then update - clusters[c2].add_vertex(vpv2, get(vertex_weight_pmap, v2)); - clusters[c1].remove_vertex(vpv2, get(vertex_weight_pmap, v2)); + clusters[c2].add_vertex(vpv2, v2_weight); + clusters[c1].remove_vertex(vpv2, v2_weight); - clusters[c1].remove_vertex(vpv1, get(vertex_weight_pmap, v1)); - clusters[c2].add_vertex(vpv1, get(vertex_weight_pmap, v1)); + clusters[c1].remove_vertex(vpv1, v1_weight); + clusters[c2].add_vertex(vpv1, v1_weight); // add all halfedges around v1 except hi to the queue for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v1, pmesh), pmesh)) - if (hd != hi) + //if (hd != hi && hd != opposite(hi, pmesh)) clusters_edges_new.push(hd); nb_modifications++; + nb_mod4++; } else { // no change but need to reset cluster data - clusters[c2].add_vertex(vpv2, get(vertex_weight_pmap, v2)); - clusters[c1].remove_vertex(vpv2, get(vertex_weight_pmap, v2)); + clusters[c2].add_vertex(vpv2, v2_weight); + clusters[c1].remove_vertex(vpv2, v2_weight); clusters_edges_new.push(hi); + nb_mod5++; } - - continue; } } clusters_edges_active.swap(clusters_edges_new); } while (nb_modifications > 0); + std::cout << nb_mod1 << std::endl; + std::cout << nb_mod2 << std::endl; + std::cout << nb_mod3 << std::endl; + std::cout << nb_mod4 << std::endl; + std::cout << nb_mod5 << std::endl; VertexColorMap vcm = get(CGAL::dynamic_vertex_property_t(), pmesh); for (Vertex_descriptor vd : vertices(pmesh)) { - int c = get(vertex_clusters_pmap, vd); + int c = get(vertex_cluster_pmap, vd); + //CGAL::IO::Color color((c - min_area) * 255 / (max_area - min_area), 0, 0); CGAL::IO::Color color(255 - (c * 255 / nb_clusters), (c * c % 7) * 255 / 7, (c * c * c % 31) * 255 / 31); - std::cout << vd.idx() << " " << c << " " << color << std::endl; + //std::cout << vd.idx() << " " << c << " " << color << std::endl; put(vcm, vd, color); } std::cout << "kak1" << std::endl; - CGAL::IO::write_OFF("S52k_clustered_0.off", pmesh, CGAL::parameters::vertex_color_map(vcm)); + std::string name = std::to_string(nb_mod4) + ".off"; + CGAL::IO::write_OFF(name, pmesh, CGAL::parameters::vertex_color_map(vcm)); std::cout << "kak2" << std::endl; } From 8817c27142e5dd39b0247abaa3e2443d99d0d761 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 1 Aug 2023 11:51:18 +0300 Subject: [PATCH 07/56] constructing mesh --- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 128 +++++++++++++++--- 1 file changed, 108 insertions(+), 20 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index c0c52d75ecf..1be0575e95c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -25,7 +25,10 @@ #include #include #include +#include +#include +#include #include #include @@ -64,6 +67,14 @@ struct ClusterData { this->energy = - (this->site_sum).squared_length() / this->weight_sum; return this->energy; } + + typename GT::Vector_3 compute_centroid() + { + if (this->weight_sum > 0) + return (this->site_sum) / this->weight_sum; + else + return typename GT::Vector_3 (-1, -1, -1); // Change this + } }; template cluster_frequency (nb_clusters + 1, 0); - int nb_mod1, nb_mod2, nb_mod3, nb_mod4, nb_mod5; - nb_mod1 = 0; - nb_mod2 = 0; - nb_mod3 = 0; - nb_mod4 = 0; - nb_mod5 = 0; + for (Vertex_descriptor vd : vertices(pmesh)) + { + int c = get(vertex_cluster_pmap, vd); + cluster_frequency[c]++; + } + + int nb_empty = 0; + for (int i = 0; i < nb_clusters + 1; i++) + { + if (cluster_frequency[i] == 0) + { + nb_empty++; + } + } + + std::cout << "nb_empty before: " << nb_empty << std::endl; + + int nb_modifications = 0; do { @@ -179,7 +203,6 @@ void acvd_simplification( //if (hd != hi && hd != opposite(hi, pmesh)) clusters_edges_new.push(hd); nb_modifications++; - nb_mod1++; } else if (!(c2 > 0)) { @@ -194,7 +217,6 @@ void acvd_simplification( //if (hd != hi && hd != opposite(hi, pmesh)) clusters_edges_new.push(hd); nb_modifications++; - nb_mod2++; } else if (c1 == c2) { @@ -217,6 +239,8 @@ void acvd_simplification( typename GT::FT e_v1_to_c2 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); + typename GT::FT c1_weight_threshold = clusters[c1].weight_sum; + // reset to no change clusters[c1].add_vertex(vpv1, v1_weight); clusters[c2].remove_vertex(vpv1, v1_weight); @@ -227,7 +251,10 @@ void acvd_simplification( typename GT::FT e_v2_to_c1 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); - if (e_v2_to_c1 < e_no_change && e_v2_to_c1 < e_v1_to_c2) + typename GT::FT c2_weight_threshold = clusters[c2].weight_sum; + + + if (e_v2_to_c1 < e_no_change && e_v2_to_c1 < e_v1_to_c2 /*&& c2_weight_threshold > 0*/) { // move v2 to c1 put(vertex_cluster_pmap, v2, c1); @@ -239,7 +266,6 @@ void acvd_simplification( //if (hd != hi && hd != opposite(hi, pmesh)) clusters_edges_new.push(hd); nb_modifications++; - nb_mod3++; } else if (e_v1_to_c2 < e_no_change) { @@ -258,7 +284,6 @@ void acvd_simplification( //if (hd != hi && hd != opposite(hi, pmesh)) clusters_edges_new.push(hd); nb_modifications++; - nb_mod4++; } else { @@ -267,34 +292,97 @@ void acvd_simplification( clusters[c1].remove_vertex(vpv2, v2_weight); clusters_edges_new.push(hi); - nb_mod5++; } } } clusters_edges_active.swap(clusters_edges_new); } while (nb_modifications > 0); - std::cout << nb_mod1 << std::endl; - std::cout << nb_mod2 << std::endl; - std::cout << nb_mod3 << std::endl; - std::cout << nb_mod4 << std::endl; - std::cout << nb_mod5 << std::endl; - VertexColorMap vcm = get(CGAL::dynamic_vertex_property_t(), pmesh); + // frequency of each cluster + cluster_frequency = std::vector(nb_clusters + 1, 0); + for (Vertex_descriptor vd : vertices(pmesh)) { int c = get(vertex_cluster_pmap, vd); + cluster_frequency[c]++; //CGAL::IO::Color color((c - min_area) * 255 / (max_area - min_area), 0, 0); CGAL::IO::Color color(255 - (c * 255 / nb_clusters), (c * c % 7) * 255 / 7, (c * c * c % 31) * 255 / 31); //std::cout << vd.idx() << " " << c << " " << color << std::endl; put(vcm, vd, color); } + + nb_empty = 0; + for (int i = 0; i < nb_clusters + 1; i++) + { + if (cluster_frequency[i] == 0) + { + nb_empty++; + } + } + + std::cout << "nb_empty: " << nb_empty << std::endl; + std::cout << "kak1" << std::endl; - std::string name = std::to_string(nb_mod4) + ".off"; + std::string name = std::to_string(nb_clusters) + ".off"; CGAL::IO::write_OFF(name, pmesh, CGAL::parameters::vertex_color_map(vcm)); std::cout << "kak2" << std::endl; + /// Construct new Mesh + std::vector valid_cluster_map(nb_clusters + 1, -1); + std::vector points; + Point_set_3 point_set; + + std::vector > polygons; + PolygonMesh simplified_mesh; + + for (int i = 0; i < nb_clusters + 1; i++) //should i =1 ? + { + if (clusters[i].weight_sum > 0) + { + valid_cluster_map[i] = points.size(); + typename GT::Vector_3 center_v = clusters[i].compute_centroid(); + typename GT::Point_3 center_p(center_v.x(), center_v.y(), center_v.z()); + points.push_back(center_p); + point_set.insert(center_p); + } + } + + name = std::to_string(nb_clusters) + "_points.off"; + CGAL::IO::write_point_set(name, point_set); + + for (Face_descriptor fd : faces(pmesh)) + { + Halfedge_descriptor hd1 = halfedge(fd, pmesh); + Vertex_descriptor v1 = source(hd1, pmesh); + Halfedge_descriptor hd2 = next(hd1, pmesh); + Vertex_descriptor v2 = source(hd2, pmesh); + Halfedge_descriptor hd3 = next(hd2, pmesh); + Vertex_descriptor v3 = source(hd3, pmesh); + + int c1 = get(vertex_cluster_pmap, v1); + int c2 = get(vertex_cluster_pmap, v2); + int c3 = get(vertex_cluster_pmap, v3); + + if (c1 != c2 && c1 != c3 && c2 != c3) + { + int c1_mapped = valid_cluster_map[c1], c2_mapped = valid_cluster_map[c2], c3_mapped = valid_cluster_map[c3]; + if (c1_mapped != -1 && c2_mapped != -1 && c3_mapped != -1) + { + std::vector polygon = {c1_mapped, c2_mapped, c3_mapped}; + polygons.push_back(polygon); + } + } + } + + std::cout << "are polygons a valid mesh ? : " << is_polygon_soup_a_polygon_mesh(polygons) << std::endl; + polygon_soup_to_polygon_mesh(points, polygons, simplified_mesh); + + name = std::to_string(nb_clusters) + "_simped.off"; + CGAL::IO::write_OFF(name, simplified_mesh); + std::cout << "kak3" << std::endl; + } From 4907b3b75c955b358fdd69b77d3d587f71ebfd6d Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:28:53 +0300 Subject: [PATCH 08/56] Minor initializtions and fixes --- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 1be0575e95c..2f383c39d28 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -73,7 +73,7 @@ struct ClusterData { if (this->weight_sum > 0) return (this->site_sum) / this->weight_sum; else - return typename GT::Vector_3 (-1, -1, -1); // Change this + return typename GT::Vector_3 (-1, -1, -1); // TODO: Change this } }; @@ -106,22 +106,29 @@ void acvd_simplification( // property map from vertex_descriptor to cluster index VertexClusterMap vertex_cluster_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); VertexWeightMap vertex_weight_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); - std::vector> clusters(nb_clusters + 1); + std::vector> clusters(nb_clusters); std::queue clusters_edges_active; std::queue clusters_edges_new; int nb_vertices = num_vertices(pmesh); - typename GT::FT max_area = 0; - typename GT::FT min_area = std::numeric_limits::max(); + // initialize vertex weights and clusters + for (Vertex_descriptor vd : vertices(pmesh)) + { + put(vertex_weight_pmap, vd, 0); + put(vertex_cluster_pmap, vd, -1); + } + + //typename GT::FT max_area = 0; + //typename GT::FT min_area = std::numeric_limits::max(); // compute vertex weights (dual area) for (Face_descriptor fd : faces(pmesh)) { typename GT::FT weight = CGAL::Polygon_mesh_processing::face_area(fd, pmesh) / 3; - max_area = std::max(max_area, weight * 8.0); - min_area = std::min(min_area, weight * 3.0); + //max_area = std::max(max_area, weight * 8.0); + //min_area = std::min(min_area, weight * 3.0); for (Vertex_descriptor vd : vertices_around_face(halfedge(fd, pmesh), pmesh)) { @@ -131,7 +138,8 @@ void acvd_simplification( } } - srand(3); + // srand(3); + srand(time(NULL)); for (int ci = 0; ci < nb_clusters; ci++) { //// random index @@ -142,10 +150,9 @@ void acvd_simplification( do { vi = rand() % num_vertices(pmesh); vd = *(vertices(pmesh).begin() + vi); - } while (get(vertex_cluster_pmap, vd)); + } while (get(vertex_cluster_pmap, vd) != -1); - // TODO: check for cluster conflict at the same vertex - put(vertex_cluster_pmap, vd, ci + 1); // TODO: should be ci but for now we start from 1 (can't set null value to -1) + put(vertex_cluster_pmap, vd, ci); typename GT::Point_3 vp = get(vpm, vd); typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); clusters[ci].add_vertex(vpv, get(vertex_weight_pmap, vd)); @@ -155,16 +162,16 @@ void acvd_simplification( } // frequency of each cluster - std::vector cluster_frequency (nb_clusters + 1, 0); + std::vector cluster_frequency (nb_clusters, 0); for (Vertex_descriptor vd : vertices(pmesh)) { int c = get(vertex_cluster_pmap, vd); - cluster_frequency[c]++; + if (c != -1) cluster_frequency[c]++; } int nb_empty = 0; - for (int i = 0; i < nb_clusters + 1; i++) + for (int i = 0; i < nb_clusters; i++) { if (cluster_frequency[i] == 0) { @@ -190,7 +197,7 @@ void acvd_simplification( int c1 = get(vertex_cluster_pmap, v1); int c2 = get(vertex_cluster_pmap, v2); - if (!(c1 > 0)) + if (c1 == -1) { // expand cluster c2 (add v1 to c2) put(vertex_cluster_pmap, v1, c2); @@ -204,7 +211,7 @@ void acvd_simplification( clusters_edges_new.push(hd); nb_modifications++; } - else if (!(c2 > 0)) + else if (c2 == -1) { // expand cluster c1 (add v2 to c1) put(vertex_cluster_pmap, v2, c1); @@ -254,7 +261,7 @@ void acvd_simplification( typename GT::FT c2_weight_threshold = clusters[c2].weight_sum; - if (e_v2_to_c1 < e_no_change && e_v2_to_c1 < e_v1_to_c2 /*&& c2_weight_threshold > 0*/) + if (e_v2_to_c1 < e_no_change && e_v2_to_c1 < e_v1_to_c2 && c2_weight_threshold > 0) { // move v2 to c1 put(vertex_cluster_pmap, v2, c1); @@ -267,7 +274,7 @@ void acvd_simplification( clusters_edges_new.push(hd); nb_modifications++; } - else if (e_v1_to_c2 < e_no_change) + else if (e_v1_to_c2 < e_no_change && c1_weight_threshold > 0) { // move v1 to c2 put(vertex_cluster_pmap, v1, c2); @@ -301,7 +308,7 @@ void acvd_simplification( VertexColorMap vcm = get(CGAL::dynamic_vertex_property_t(), pmesh); // frequency of each cluster - cluster_frequency = std::vector(nb_clusters + 1, 0); + cluster_frequency = std::vector(nb_clusters, 0); for (Vertex_descriptor vd : vertices(pmesh)) { @@ -314,7 +321,7 @@ void acvd_simplification( } nb_empty = 0; - for (int i = 0; i < nb_clusters + 1; i++) + for (int i = 0; i < nb_clusters; i++) { if (cluster_frequency[i] == 0) { @@ -330,14 +337,14 @@ void acvd_simplification( std::cout << "kak2" << std::endl; /// Construct new Mesh - std::vector valid_cluster_map(nb_clusters + 1, -1); + std::vector valid_cluster_map(nb_clusters, -1); std::vector points; Point_set_3 point_set; std::vector > polygons; PolygonMesh simplified_mesh; - for (int i = 0; i < nb_clusters + 1; i++) //should i =1 ? + for (int i = 0; i < nb_clusters; i++) //should i =1 ? { if (clusters[i].weight_sum > 0) { From 7d4d0c94e219b39b3b75923646da611e89046b02 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 19 Aug 2023 20:02:00 +0300 Subject: [PATCH 09/56] fixing random initialization and weighting --- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 92 +++++++++++++++++-- 1 file changed, 85 insertions(+), 7 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 2f383c39d28..9c4305ca3a6 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -52,14 +52,14 @@ struct ClusterData { void add_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight) { - this->site_sum += vertex_position; - this->weight_sum += 1; + this->site_sum += vertex_position * weight; + this->weight_sum += weight; } void remove_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight) { - this->site_sum -= vertex_position; - this->weight_sum -= 1; + this->site_sum -= vertex_position * weight; + this->weight_sum -= weight; } typename GT::FT compute_energy() @@ -94,6 +94,7 @@ void acvd_simplification( typedef typename boost::property_map >::type VertexColorMap; typedef typename boost::property_map >::type VertexClusterMap; typedef typename boost::property_map >::type VertexWeightMap; + typedef typename boost::property_map >::type HalfedgeVisitedMap; using parameters::choose_parameter; using parameters::get_parameter; @@ -125,7 +126,7 @@ void acvd_simplification( // compute vertex weights (dual area) for (Face_descriptor fd : faces(pmesh)) { - typename GT::FT weight = CGAL::Polygon_mesh_processing::face_area(fd, pmesh) / 3; + typename GT::FT weight = abs(CGAL::Polygon_mesh_processing::face_area(fd, pmesh)) / 3; //max_area = std::max(max_area, weight * 8.0); //min_area = std::min(min_area, weight * 3.0); @@ -139,7 +140,8 @@ void acvd_simplification( } // srand(3); - srand(time(NULL)); + //srand(time(NULL)); + double avg_rand = 0; for (int ci = 0; ci < nb_clusters; ci++) { //// random index @@ -148,7 +150,8 @@ void acvd_simplification( int vi; Vertex_descriptor vd; do { - vi = rand() % num_vertices(pmesh); + vi = CGAL::get_default_random().get_int(0, num_vertices(pmesh)); + avg_rand += vi; vd = *(vertices(pmesh).begin() + vi); } while (get(vertex_cluster_pmap, vd) != -1); @@ -159,7 +162,13 @@ void acvd_simplification( for (Halfedge_descriptor hd : halfedges_around_source(vd, pmesh)) clusters_edges_active.push(hd); + + if (ci % (nb_clusters / 5) == 0) + std::cout << "rand ci" << ci << " " << vi << "\n"; } + avg_rand = avg_rand / nb_clusters; + + std::cout << "avg_rand: " << avg_rand << " nVertices: " << num_vertices(pmesh) << "\n"; // frequency of each cluster std::vector cluster_frequency (nb_clusters, 0); @@ -356,6 +365,75 @@ void acvd_simplification( } } + + // extract boundary cycles + // loop over boundary loops + // for each vertex in the boundary loop, we create a new vertex + //HalfedgeVisitedMap halfedge_visited_map = get(CGAL::dynamic_halfedge_property_t(), pmesh); + //for (Halfedge_descriptor hd : halfedges(pmesh)) + //{ + // put(halfedge_visited_map, hd, false); + //} + + //for (Halfedge_descriptor hd : halfedges(pmesh)) + //{ + // if (get(halfedge_visited_map, hd) == true) + // continue; + // put(halfedge_visited_map, hd, true); + // if (is_border(hd, pmesh)) + // { + // Halfedge_descriptor hd1 = hd; + + // int cb_first = -1; + + // do + // { + // // 1- get the target and source vertices vt, vs + // // 2- if the target and source vertices are in different clusters, create a new vertex vb between them vb = (vt + vs) / 2 + // // it is also added to the point_set + // // 3- make a new face with the new vertex vb and the centers of the clusters of vt and vs + // // 4- also make a new face with vb, the next vb, and the center of the cluster of vt + + // Vertex_descriptor vt = target(hd1, pmesh); + // Vertex_descriptor vs = source(hd1, pmesh); + + // int ct = get(vertex_cluster_pmap, vt); + // int cs = get(vertex_cluster_pmap, vs); + + // if (ct != cs) + // { + // typename GT::Point_3 vt_p = get(vpm, vt); + // typename GT::Point_3 vs_p = get(vpm, vs); + // typename GT::Vector_3 vt_v(vt_p.x(), vt_p.y(), vt_p.z()); + // typename GT::Vector_3 vs_v(vs_p.x(), vs_p.y(), vs_p.z()); + + // typename GT::Vector_3 vb_v = (vt_v + vs_v) / 2; + // typename GT::Point_3 vb_p(vb_v.x(), vb_v.y(), vb_v.z()); + + // points.push_back(vb_p); + // point_set.insert(vb_p); + + // int cb = points.size() - 1; + + // int ct_mapped = valid_cluster_map[ct], cs_mapped = valid_cluster_map[cs]; + + // if (ct_mapped != -1 && cs_mapped != -1) + // { + // std::vector polygon = {ct_mapped, cb, cs_mapped}; + // polygons.push_back(polygon); + + // // after the loop, the last cb+1 should be modified to the first cb + // polygon = {ct, cb + 1, cb}; + // polygons.push_back(polygon); + // } + // } + // hd1 = next(hd1, pmesh); + // } while (hd1 != hd); + // } + //} + + + name = std::to_string(nb_clusters) + "_points.off"; CGAL::IO::write_point_set(name, point_set); From e1320217a1ec5d3ca9719472e9743cf75f429560 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 24 Aug 2023 01:39:37 +0300 Subject: [PATCH 10/56] boundary ring --- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 105 ++++++++---------- 1 file changed, 49 insertions(+), 56 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 9c4305ca3a6..63ea60c2cc5 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -163,8 +164,8 @@ void acvd_simplification( for (Halfedge_descriptor hd : halfedges_around_source(vd, pmesh)) clusters_edges_active.push(hd); - if (ci % (nb_clusters / 5) == 0) - std::cout << "rand ci" << ci << " " << vi << "\n"; + /*if (ci % (nb_clusters / 5) == 0) + std::cout << "rand ci" << ci << " " << vi << "\n";*/ } avg_rand = avg_rand / nb_clusters; @@ -364,75 +365,67 @@ void acvd_simplification( point_set.insert(center_p); } } - // extract boundary cycles + std::vector border_hedges; + extract_boundary_cycles(pmesh, std::back_inserter(border_hedges)); + // loop over boundary loops - // for each vertex in the boundary loop, we create a new vertex - //HalfedgeVisitedMap halfedge_visited_map = get(CGAL::dynamic_halfedge_property_t(), pmesh); - //for (Halfedge_descriptor hd : halfedges(pmesh)) - //{ - // put(halfedge_visited_map, hd, false); - //} + for (Halfedge_descriptor hd : border_hedges) + { + Halfedge_descriptor hd1 = hd; - //for (Halfedge_descriptor hd : halfedges(pmesh)) - //{ - // if (get(halfedge_visited_map, hd) == true) - // continue; - // put(halfedge_visited_map, hd, true); - // if (is_border(hd, pmesh)) - // { - // Halfedge_descriptor hd1 = hd; + int cb_first = -1; - // int cb_first = -1; + do + { + // 1- get the target and source vertices vt, vs + // 2- if the target and source vertices are in different clusters, create a new vertex vb between them vb = (vt + vs) / 2 + // it is also added to the point_set + // 3- make a new face with the new vertex vb and the centers of the clusters of vt and vs + // 4- also make a new face with vb, the next vb, and the center of the cluster of vt - // do - // { - // // 1- get the target and source vertices vt, vs - // // 2- if the target and source vertices are in different clusters, create a new vertex vb between them vb = (vt + vs) / 2 - // // it is also added to the point_set - // // 3- make a new face with the new vertex vb and the centers of the clusters of vt and vs - // // 4- also make a new face with vb, the next vb, and the center of the cluster of vt + Vertex_descriptor vt = target(hd1, pmesh); + Vertex_descriptor vs = source(hd1, pmesh); - // Vertex_descriptor vt = target(hd1, pmesh); - // Vertex_descriptor vs = source(hd1, pmesh); + int ct = get(vertex_cluster_pmap, vt); + int cs = get(vertex_cluster_pmap, vs); - // int ct = get(vertex_cluster_pmap, vt); - // int cs = get(vertex_cluster_pmap, vs); + if (ct != cs) + { + typename GT::Point_3 vt_p = get(vpm, vt); + typename GT::Point_3 vs_p = get(vpm, vs); + typename GT::Vector_3 vt_v(vt_p.x(), vt_p.y(), vt_p.z()); + typename GT::Vector_3 vs_v(vs_p.x(), vs_p.y(), vs_p.z()); - // if (ct != cs) - // { - // typename GT::Point_3 vt_p = get(vpm, vt); - // typename GT::Point_3 vs_p = get(vpm, vs); - // typename GT::Vector_3 vt_v(vt_p.x(), vt_p.y(), vt_p.z()); - // typename GT::Vector_3 vs_v(vs_p.x(), vs_p.y(), vs_p.z()); + typename GT::Vector_3 vb_v = (vt_v + vs_v) / 2; + typename GT::Point_3 vb_p(vb_v.x(), vb_v.y(), vb_v.z()); - // typename GT::Vector_3 vb_v = (vt_v + vs_v) / 2; - // typename GT::Point_3 vb_p(vb_v.x(), vb_v.y(), vb_v.z()); + points.push_back(vb_p); + point_set.insert(vb_p); - // points.push_back(vb_p); - // point_set.insert(vb_p); + int cb = points.size() - 1; - // int cb = points.size() - 1; + if (cb_first == -1) + cb_first = cb; - // int ct_mapped = valid_cluster_map[ct], cs_mapped = valid_cluster_map[cs]; - - // if (ct_mapped != -1 && cs_mapped != -1) - // { - // std::vector polygon = {ct_mapped, cb, cs_mapped}; - // polygons.push_back(polygon); - - // // after the loop, the last cb+1 should be modified to the first cb - // polygon = {ct, cb + 1, cb}; - // polygons.push_back(polygon); - // } - // } - // hd1 = next(hd1, pmesh); - // } while (hd1 != hd); - // } - //} + int ct_mapped = valid_cluster_map[ct], cs_mapped = valid_cluster_map[cs]; + if (ct_mapped != -1 && cs_mapped != -1) + { + std::vector + polygon = {ct_mapped, cb, cs_mapped}; + polygons.push_back(polygon); + // after the loop, the last cb+1 should be modified to the first cb + polygon = {cb, ct_mapped, cb + 1}; + polygons.push_back(polygon); + } + } + hd1 = next(hd1, pmesh); + } while (hd1 != hd); + polygons[polygons.size() - 1][2] = cb_first; + } name = std::to_string(nb_clusters) + "_points.off"; CGAL::IO::write_point_set(name, point_set); From 234487124617bc5e5497fda1bb10e607786be160 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:34:11 +0300 Subject: [PATCH 11/56] upsample subdivision --- .../subdivision_masks_3.h | 93 +++++++++++++++++++ .../subdivision_methods_3.h | 50 ++++++++++ 2 files changed, 143 insertions(+) diff --git a/Subdivision_method_3/include/CGAL/Subdivision_method_3/subdivision_masks_3.h b/Subdivision_method_3/include/CGAL/Subdivision_method_3/subdivision_masks_3.h index af9edb11968..256be04f095 100644 --- a/Subdivision_method_3/include/CGAL/Subdivision_method_3/subdivision_masks_3.h +++ b/Subdivision_method_3/include/CGAL/Subdivision_method_3/subdivision_masks_3.h @@ -373,6 +373,99 @@ public: /// @} }; +// ====================================================================== +/*! +\ingroup PkgSurfaceSubdivisionMethod3Ref + +TODO: need to find the correct name for this scheme, if it exists. And also complete the documentation. + +The geometry mask of Upsample subdivision. This does not change the geometry of the mesh. +In other words, the original vertices are not moved, and the new vertices are +the average of the incident vertices. This is useful for increasing the resolution +of a mesh without changing its geometry. + +A stencil determines a source neighborhood +whose points contribute to the position of a refined point. +The geometry mask of a stencil specifies +the computation on the nodes of the stencil. +`Upsample_mask_3` implements the geometry masks of +Upsample subdivision on a triangulated model of `MutableFaceGraph`, +such as `Polyhedron_3` and `Surface_mesh`. + +\tparam PolygonMesh must be a model of the concept `MutableFaceGraph`. Additionally all faces must be triangles. +\tparam VertexPointMap must be a model of `WritablePropertyMap` with value type `Point_3` + +\cgalModels `PTQMask_3` + +\sa `CGAL::Subdivision_method_3` + +*/ +template ::type > +class Upsample_mask_3 : public PQQ_stencil_3 { + typedef PQQ_stencil_3 Base; +public: + typedef PolygonMesh Mesh; + +#ifndef DOXYGEN_RUNNING + typedef typename Base::vertex_descriptor vertex_descriptor; + typedef typename Base::halfedge_descriptor halfedge_descriptor; + + typedef typename Base::Kernel Kernel; + + typedef typename Base::FT FT; + typedef typename Base::Point Point; + typedef typename boost::property_traits::reference Point_ref; +#endif + + typedef Halfedge_around_target_circulator Halfedge_around_vertex_circulator; + +public: +/// \name Creation +/// @{ + + /// Constructor. + /// The default vertex point property map, `get(vertex_point, pmesh)`, is used. + Upsample_mask_3(Mesh* pmesh) + : Base(pmesh, get(vertex_point, pmesh)) + { } + + /// Constructor with a custom vertex point property map. + Upsample_mask_3(Mesh* pmesh, VertexPointMap vpmap) + : Base(pmesh, vpmap) + { } + +/// @} + +/// \name Stencil functions +/// @{ + + /// computes the Upsample edge-point `pt` of the edge `edge`, simply the midpoint of the edge. + void edge_node(halfedge_descriptor edge, Point& pt) { + Point_ref p1 = get(this->vpmap,target(edge, *(this->pmesh))); + Point_ref p2 = get(this->vpmap,target(opposite(edge, *(this->pmesh)), *(this->pmesh))); + + pt = Point(0.5 * (p1[0]+p2[0]), + 0.5 * (p1[1]+p2[1]), + 0.5 * (p1[2]+p2[2]) ); + } + + /// returns Upsample vertex-point `pt` of the vertex `vertex`, simply the original vertex. + void vertex_node(vertex_descriptor vertex, Point& pt) { + pt = get(this->vpmap,vertex); + } + + /// computes the Upsample edge-point `ept` and the Upsample vertex-point `vpt` of the border edge `edge`. + void border_node(halfedge_descriptor edge, Point& ept, Point& vpt) { + edge_node(edge, ept); + + Halfedge_around_vertex_circulator vcir(edge, *(this->pmesh)); + vpt = get(this->vpmap,target(*vcir, *(this->pmesh))); + } + +/// @} +}; //========================================================================== /// The stencil of the Dual-Quadrilateral-Quadrisection diff --git a/Subdivision_method_3/include/CGAL/Subdivision_method_3/subdivision_methods_3.h b/Subdivision_method_3/include/CGAL/Subdivision_method_3/subdivision_methods_3.h index 4aebef94d49..d035bf3cf0c 100644 --- a/Subdivision_method_3/include/CGAL/Subdivision_method_3/subdivision_methods_3.h +++ b/Subdivision_method_3/include/CGAL/Subdivision_method_3/subdivision_methods_3.h @@ -199,6 +199,56 @@ void Loop_subdivision(PolygonMesh& pmesh, const NamedParameters& np = parameters } // ----------------------------------------------------------------------------- +/*! + * + * applies Upsample subdivision several times on the control mesh `pmesh`. + * The geometry of the refined mesh is computed by the geometry policy mask `Upsample_mask_3`. + * Which is similar to Loop subdivision but does not change the shape of the mesh (only the connectivity). + * The new vertices are trivially computed as the average of the incident vertices (midpoint of edge). + * This function overwrites the control mesh `pmesh` with the subdivided mesh. + + * @tparam PolygonMesh a model of `MutableFaceGraph` + * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" + * + * @param pmesh a polygon mesh + * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below + * + * \cgalNamedParamsBegin + * \cgalParamNBegin{vertex_point_map} + * \cgalParamDescription{a property map associating points to the vertices of `pmesh`} + * \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits::%vertex_descriptor` + * as key type and `%Point_3` as value type} + * \cgalParamDefault{`boost::get(CGAL::vertex_point, pmesh)`} + * \cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` + * should be available for the vertices of `pmesh`.} + * \cgalParamNEnd + * + * \cgalParamNBegin{number_of_iterations} + * \cgalParamDescription{the number of subdivision steps} + * \cgalParamType{unsigned int} + * \cgalParamDefault{`1`} + * \cgalParamNEnd + * \cgalNamedParamsEnd + * + * \pre `pmesh` must be a triangle mesh. + **/ +template +void Upsample_subdivision(PolygonMesh& pmesh, const NamedParameters& np = parameters::default_values()) { + using parameters::choose_parameter; + using parameters::get_parameter; + + typedef typename CGAL::GetVertexPointMap::type Vpm; + Vpm vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), + get_property_map(CGAL::vertex_point, pmesh)); + + unsigned int step = choose_parameter(get_parameter(np, internal_np::number_of_iterations), 1); + Upsample_mask_3 mask(&pmesh, vpm); + + for(unsigned int i = 0; i < step; i++) + internal::PTQ_1step(pmesh, vpm, mask); +} +// ----------------------------------------------------------------------------- + #ifndef DOXYGEN_RUNNING // backward compatibility #ifndef CGAL_NO_DEPRECATED_CODE From d4cb18f1234b6d89e506e6e23d09fb0e02efcb9d Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:34:33 +0300 Subject: [PATCH 12/56] disconnection check + using subdivision --- .../Polygon_mesh_processing/acvd_example.cpp | 6 +- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 142 +++++++++++++++--- 2 files changed, 123 insertions(+), 25 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp index d6c2212acac..1319e505fa5 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -21,9 +21,9 @@ int main(int argc, char* argv[]) Surface_Mesh smesh; const std::string filename = (argc > 1) ? CGAL::data_file_path(argv[1]) : - CGAL::data_file_path("meshes/bear.off"); + CGAL::data_file_path("meshes/better_dragon.obj"); - const int nb_clusters = (argc > 2) ? atoi(argv[2]) : 50; + const int nb_clusters = (argc > 2) ? atoi(argv[2]) : 98; if (!CGAL::IO::read_polygon_mesh(filename, smesh)) { @@ -32,7 +32,7 @@ int main(int argc, char* argv[]) } PMP::acvd_isotropic_simplification(smesh, nb_clusters); - std::cout << "kak3" << std::endl; + // std::cout << "kak3" << std::endl; return 0; // Output the simplified mesh, use write_OFF() diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 63ea60c2cc5..055370afc09 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,8 @@ #include #include +#define CGAL_CLUSTERS_TO_VERTICES_THRESHOLD 0.1 + namespace CGAL { namespace Polygon_mesh_processing { @@ -104,6 +107,28 @@ void acvd_simplification( Vertex_position_map vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), get_property_map(CGAL::vertex_point, pmesh)); + // TODO: handle cases where the mesh is not a triangle mesh + CGAL_precondition(CGAL::is_triangle_mesh(pmesh)); + + int nb_vertices = num_vertices(pmesh); + + // To provide the functionality remeshing (not just simplification), we might need to + // subdivide the mesh before clustering + // in either case, nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD + double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); + int subdivide_steps = max((int)ceil(log(curr_factor) / log(4)), 0); + + std::cout << "subdivide_steps: " << subdivide_steps << std::endl; + + if (subdivide_steps > 0) + { + Subdivision_method_3::Upsample_subdivision( + pmesh, + CGAL::parameters::number_of_iterations(subdivide_steps).vertex_point_map(vpm) + ); + vpm = get_property_map(CGAL::vertex_point, pmesh); + } + // initial random clusters // property map from vertex_descriptor to cluster index VertexClusterMap vertex_cluster_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); @@ -112,8 +137,6 @@ void acvd_simplification( std::queue clusters_edges_active; std::queue clusters_edges_new; - int nb_vertices = num_vertices(pmesh); - // initialize vertex weights and clusters for (Vertex_descriptor vd : vertices(pmesh)) { @@ -121,17 +144,11 @@ void acvd_simplification( put(vertex_cluster_pmap, vd, -1); } - //typename GT::FT max_area = 0; - //typename GT::FT min_area = std::numeric_limits::max(); - // compute vertex weights (dual area) for (Face_descriptor fd : faces(pmesh)) { typename GT::FT weight = abs(CGAL::Polygon_mesh_processing::face_area(fd, pmesh)) / 3; - //max_area = std::max(max_area, weight * 8.0); - //min_area = std::min(min_area, weight * 3.0); - for (Vertex_descriptor vd : vertices_around_face(halfedge(fd, pmesh), pmesh)) { typename GT::FT vertex_weight = get(vertex_weight_pmap, vd); @@ -140,19 +157,12 @@ void acvd_simplification( } } - // srand(3); - //srand(time(NULL)); - double avg_rand = 0; for (int ci = 0; ci < nb_clusters; ci++) { - //// random index - //int vi = rand() % num_vertices(pmesh); - //Vertex_descriptor vd = *(vertices(pmesh).begin() + vi); int vi; Vertex_descriptor vd; do { vi = CGAL::get_default_random().get_int(0, num_vertices(pmesh)); - avg_rand += vi; vd = *(vertices(pmesh).begin() + vi); } while (get(vertex_cluster_pmap, vd) != -1); @@ -164,12 +174,7 @@ void acvd_simplification( for (Halfedge_descriptor hd : halfedges_around_source(vd, pmesh)) clusters_edges_active.push(hd); - /*if (ci % (nb_clusters / 5) == 0) - std::cout << "rand ci" << ci << " " << vi << "\n";*/ } - avg_rand = avg_rand / nb_clusters; - - std::cout << "avg_rand: " << avg_rand << " nVertices: " << num_vertices(pmesh) << "\n"; // frequency of each cluster std::vector cluster_frequency (nb_clusters, 0); @@ -192,10 +197,12 @@ void acvd_simplification( std::cout << "nb_empty before: " << nb_empty << std::endl; int nb_modifications = 0; + int nb_disconnected = 0; do { nb_modifications = 0; + nb_disconnected = 0; while (clusters_edges_active.empty() == false) { Halfedge_descriptor hi = clusters_edges_active.front(); @@ -312,8 +319,100 @@ void acvd_simplification( } } } + // clean clusters here + // the goal is to delete clusters with multiple connected components + // for each cluster, do a BFS from a vertex in the cluster + // we need to keep the largest connected component for each cluster + // and set the other connected components to -1 (empty cluster), would also need to update clusters_edges_new + + std::vector visited(num_vertices(pmesh), false); + // std::vector visited_clusters(nb_clusters, false); + // [cluster][component_index][vertex_index] + std::vector>> cluster_components(nb_clusters, std::vector>()); + + std::queue q; + + // loop over vertices + for (Vertex_descriptor vd : vertices(pmesh)) + { + if (visited[vd]) continue; + int c = get(vertex_cluster_pmap, vd); + if (c != -1) + { + // first component of this cluster + if (cluster_components[c].size() == 0) + cluster_components[c].push_back(std::vector()); + + int component_i = cluster_components[c].size() - 1; + + // visited_clusters[c] = true; + q.push(vd); + visited[vd] = true; + while (q.empty() == false) + { + Vertex_descriptor v = q.front(); + q.pop(); + cluster_components[c][component_i].push_back(v); + + for (Halfedge_descriptor hd : halfedges_around_source(v, pmesh)) + { + Vertex_descriptor v2 = target(hd, pmesh); + int c2 = get(vertex_cluster_pmap, v2); + if (c2 == c && visited[v2] == false) + { + q.push(v2); + visited[v2] = true; + } + } + } + } + } + + // loop over clusters + for (int c = 0; c < nb_clusters; c++) + { + if (cluster_components[c].size() <= 1) continue; // only one component, no need to do anything + int max_component_size = 0; + int max_component_index = -1; + for (int component_i = 0; component_i < cluster_components[c].size(); component_i++) + { + if (cluster_components[c][component_i].size() > max_component_size) + { + max_component_size = cluster_components[c][component_i].size(); + max_component_index = component_i; + } + } + // set cluster to -1 for all components except the largest one + for (int component_i = 0; component_i < cluster_components[c].size(); component_i++) + { + if (component_i != max_component_index) + { + nb_disconnected++; + for (Vertex_descriptor vd : cluster_components[c][component_i]) + { + put(vertex_cluster_pmap, vd, -1); + // remove vd from cluster c + typename GT::Point_3 vp = get(vpm, vd); + typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); + clusters[c].remove_vertex(vpv, get(vertex_weight_pmap, vd)); + // add all halfedges around v except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(vd, pmesh)) + { + // add hd to the queue if its target is not in the same cluster + Vertex_descriptor v2 = target(hd, pmesh); + int c2 = get(vertex_cluster_pmap, v2); + if (c2 != c) + clusters_edges_new.push(hd); + } + } + } + } + } + + std::cout << "nb_disconnected: " << nb_disconnected << "\n"; + clusters_edges_active.swap(clusters_edges_new); - } while (nb_modifications > 0); + } while (nb_modifications > 0 || nb_disconnected > 0); VertexColorMap vcm = get(CGAL::dynamic_vertex_property_t(), pmesh); @@ -324,7 +423,6 @@ void acvd_simplification( { int c = get(vertex_cluster_pmap, vd); cluster_frequency[c]++; - //CGAL::IO::Color color((c - min_area) * 255 / (max_area - min_area), 0, 0); CGAL::IO::Color color(255 - (c * 255 / nb_clusters), (c * c % 7) * 255 / 7, (c * c * c % 31) * 255 / 31); //std::cout << vd.idx() << " " << c << " " << color << std::endl; put(vcm, vd, color); From b98477e4bc017f8e31cf113e6f78ad2260004805 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:57:34 +0300 Subject: [PATCH 13/56] guarantee with subdivision steps for remeshing --- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 055370afc09..4a2a27759a2 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -115,20 +115,29 @@ void acvd_simplification( // To provide the functionality remeshing (not just simplification), we might need to // subdivide the mesh before clustering // in either case, nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD - double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); - int subdivide_steps = max((int)ceil(log(curr_factor) / log(4)), 0); - std::cout << "subdivide_steps: " << subdivide_steps << std::endl; - - if (subdivide_steps > 0) + // do the following while nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD + // That is, because the subdivision steps heuristic is not 100% guaranteed to produce + // the desired number of vertices. + while (nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) { - Subdivision_method_3::Upsample_subdivision( - pmesh, - CGAL::parameters::number_of_iterations(subdivide_steps).vertex_point_map(vpm) - ); - vpm = get_property_map(CGAL::vertex_point, pmesh); + double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); + int subdivide_steps = max((int)ceil(log(curr_factor) / log(4)), 0); + + std::cout << "subdivide_steps: " << subdivide_steps << std::endl; + + if (subdivide_steps > 0) + { + Subdivision_method_3::Upsample_subdivision( + pmesh, + CGAL::parameters::number_of_iterations(subdivide_steps).vertex_point_map(vpm) + ); + vpm = get_property_map(CGAL::vertex_point, pmesh); + } } + + // initial random clusters // property map from vertex_descriptor to cluster index VertexClusterMap vertex_cluster_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); From 793959b4b06863f8625e8346a5f258d4be2a6a79 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 31 Aug 2023 16:04:21 +0300 Subject: [PATCH 14/56] minor fix --- .../include/CGAL/Polygon_mesh_processing/acvd/acvd.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 4a2a27759a2..c424ed80df1 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -133,6 +133,7 @@ void acvd_simplification( CGAL::parameters::number_of_iterations(subdivide_steps).vertex_point_map(vpm) ); vpm = get_property_map(CGAL::vertex_point, pmesh); + nb_vertices = num_vertices(pmesh); } } From 9838426faf079286bf9134febec709b9bf524e03 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 1 Sep 2023 14:19:35 +0300 Subject: [PATCH 15/56] orient polygon soup --- .../include/CGAL/Polygon_mesh_processing/acvd/acvd.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index c424ed80df1..9645ede915a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -112,6 +112,8 @@ void acvd_simplification( int nb_vertices = num_vertices(pmesh); + // TODO: copy mesh before subdivision (copy constructor or copy face graph if with properties) + // To provide the functionality remeshing (not just simplification), we might need to // subdivide the mesh before clustering // in either case, nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD @@ -424,6 +426,8 @@ void acvd_simplification( clusters_edges_active.swap(clusters_edges_new); } while (nb_modifications > 0 || nb_disconnected > 0); + // TODO: Move out the disconnected clustering check (& cleaning) + VertexColorMap vcm = get(CGAL::dynamic_vertex_property_t(), pmesh); // frequency of each cluster @@ -563,6 +567,7 @@ void acvd_simplification( } std::cout << "are polygons a valid mesh ? : " << is_polygon_soup_a_polygon_mesh(polygons) << std::endl; + orient_polygon_soup(points, polygons); polygon_soup_to_polygon_mesh(points, polygons, simplified_mesh); name = std::to_string(nb_clusters) + "_simped.off"; From 21effb4277ec90f94e964d7123b1167011b3972e Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 8 Sep 2023 02:34:27 +0300 Subject: [PATCH 16/56] added upsample subdivision to demo --- .../Subdivision_methods_plugin.cpp | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Subdivision_methods/Subdivision_methods_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Subdivision_methods/Subdivision_methods_plugin.cpp index 87bb50fd96f..66c512f9d60 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Subdivision_methods/Subdivision_methods_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Subdivision_methods/Subdivision_methods_plugin.cpp @@ -37,14 +37,17 @@ public: messages = m; scene = scene_interface; QAction *actionLoop = new QAction("Loop", mw); + QAction *actionUpsample = new QAction("Upsample", mw); QAction *actionCatmullClark = new QAction("Catmull Clark", mw); QAction *actionSqrt3 = new QAction("Sqrt3", mw); QAction *actionDooSabin = new QAction("Doo Sabin", mw); actionLoop->setObjectName("actionLoop"); + actionUpsample->setObjectName("actionUpsample"); actionCatmullClark->setObjectName("actionCatmullClark"); actionSqrt3->setObjectName("actionSqrt3"); actionDooSabin->setObjectName("actionDooSabin"); _actions << actionLoop + << actionUpsample << actionCatmullClark << actionSqrt3 << actionDooSabin; @@ -60,6 +63,7 @@ public: public Q_SLOTS: void on_actionLoop_triggered(); + void on_actionUpsample_triggered(); void on_actionCatmullClark_triggered(); void on_actionSqrt3_triggered(); void on_actionDooSabin_triggered(); @@ -69,6 +73,8 @@ private : template void apply_loop(FaceGraphItem* item, int nb_steps); template + void apply_upsample(FaceGraphItem* item, int nb_steps); + template void apply_catmullclark(FaceGraphItem* item, int nb_steps); template void apply_sqrt3(FaceGraphItem* item, int nb_steps); @@ -92,6 +98,22 @@ void Polyhedron_demo_subdivision_methods_plugin::apply_loop(FaceGraphItem* item, scene->itemChanged(item); } +template +void Polyhedron_demo_subdivision_methods_plugin::apply_upsample(FaceGraphItem* item, int nb_steps) +{ + typename FaceGraphItem::Face_graph* graph = item->face_graph(); + if(!graph) return; + QElapsedTimer time; + time.start(); + CGAL::Three::Three::information("Upsample subdivision..."); + QApplication::setOverrideCursor(Qt::WaitCursor); + CGAL::Subdivision_method_3::Upsample_subdivision(*graph, params::number_of_iterations(nb_steps)); + CGAL::Three::Three::information(QString("ok (%1 ms)").arg(time.elapsed())); + QApplication::restoreOverrideCursor(); + item->invalidateOpenGLBuffers(); + scene->itemChanged(item); +} + void Polyhedron_demo_subdivision_methods_plugin::on_actionLoop_triggered() { CGAL::Three::Scene_interface::Item_id index = scene->mainSelectionIndex(); @@ -111,6 +133,25 @@ void Polyhedron_demo_subdivision_methods_plugin::on_actionLoop_triggered() apply_loop(sm_item, nb_steps); } +void Polyhedron_demo_subdivision_methods_plugin::on_actionUpsample_triggered() +{ + CGAL::Three::Scene_interface::Item_id index = scene->mainSelectionIndex(); + Scene_surface_mesh_item* sm_item = qobject_cast(scene->item(index)); + if(!sm_item) + return; + + bool ok = true; + int nb_steps = QInputDialog::getInt(mw, + QString("Number of Iterations"), + QString("Choose number of iterations"), + 1 /* value */, 1 /* min */, (std::numeric_limits::max)() /* max */, 1 /*step*/, + &ok); + if(!ok) + return; + + apply_upsample(sm_item, nb_steps); +} + template void Polyhedron_demo_subdivision_methods_plugin::apply_catmullclark(FaceGraphItem* item, int nb_steps) { From d2e4f36c8ff1b2e6c536185a34077a0a8a2318eb Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 15 Sep 2023 11:14:11 +0300 Subject: [PATCH 17/56] Polyhedron demo (incomplete) --- .../Polygon_mesh_processing/acvd_example.cpp | 4 +- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 291 ++++++++++-------- .../PMP/ACVD_simplification_plugin.cpp | 122 ++++++++ .../Plugins/PMP/ACVD_simplification_widget.ui | 93 ++++++ .../Polyhedron/Plugins/PMP/CMakeLists.txt | 4 + 5 files changed, 378 insertions(+), 136 deletions(-) create mode 100644 Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_plugin.cpp create mode 100644 Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_widget.ui diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp index 1319e505fa5..62f3f156df1 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -21,9 +21,9 @@ int main(int argc, char* argv[]) Surface_Mesh smesh; const std::string filename = (argc > 1) ? CGAL::data_file_path(argv[1]) : - CGAL::data_file_path("meshes/better_dragon.obj"); + CGAL::data_file_path("meshes/cactus.off"); - const int nb_clusters = (argc > 2) ? atoi(argv[2]) : 98; + const int nb_clusters = (argc > 2) ? atoi(argv[2]) : 20; if (!CGAL::IO::read_polygon_mesh(filename, smesh)) { diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 9645ede915a..90d8debd83c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -81,11 +81,50 @@ struct ClusterData { } }; + +// To provide the functionality remeshing (not just simplification), we might need to +// subdivide the mesh before clustering +// in either case, nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD + +// do the following while nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD +// That is, because the subdivision steps heuristic is not 100% guaranteed to produce +// the desired number of vertices. +template +void acvd_subdivide_if_needed( + PolygonMesh& pmesh, + const int nb_clusters, + const NamedParameters& np = parameters::default_values() +) +{ + int nb_vertices = num_vertices(pmesh); + if (nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) + return; + + while (nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) + { + double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); + int subdivide_steps = max((int)ceil(log(curr_factor) / log(4)), 0); + + std::cout << "subdivide_steps: " << subdivide_steps << std::endl; + + if (subdivide_steps > 0) + { + Subdivision_method_3::Upsample_subdivision( + pmesh, + np.number_of_iterations(subdivide_steps) + ); + } + } + return; +} + + template void acvd_simplification( PolygonMesh& pmesh, - const int& nb_clusters, + const int nb_clusters, const NamedParameters& np = parameters::default_values() // seed_randomization can be a named parameter ) @@ -109,37 +148,9 @@ void acvd_simplification( // TODO: handle cases where the mesh is not a triangle mesh CGAL_precondition(CGAL::is_triangle_mesh(pmesh)); - - int nb_vertices = num_vertices(pmesh); - - // TODO: copy mesh before subdivision (copy constructor or copy face graph if with properties) - - // To provide the functionality remeshing (not just simplification), we might need to - // subdivide the mesh before clustering - // in either case, nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD - - // do the following while nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD - // That is, because the subdivision steps heuristic is not 100% guaranteed to produce - // the desired number of vertices. - while (nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) - { - double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); - int subdivide_steps = max((int)ceil(log(curr_factor) / log(4)), 0); - - std::cout << "subdivide_steps: " << subdivide_steps << std::endl; - - if (subdivide_steps > 0) - { - Subdivision_method_3::Upsample_subdivision( - pmesh, - CGAL::parameters::number_of_iterations(subdivide_steps).vertex_point_map(vpm) - ); - vpm = get_property_map(CGAL::vertex_point, pmesh); - nb_vertices = num_vertices(pmesh); - } - } - - + + PolygonMesh pmesh_subdivided = pmesh; + acvd_subdivide_if_needed(pmesh_subdivided, nb_clusters, np); // initial random clusters // property map from vertex_descriptor to cluster index @@ -185,7 +196,6 @@ void acvd_simplification( for (Halfedge_descriptor hd : halfedges_around_source(vd, pmesh)) clusters_edges_active.push(hd); - } // frequency of each cluster @@ -213,129 +223,135 @@ void acvd_simplification( do { - nb_modifications = 0; nb_disconnected = 0; + do + { + nb_modifications = 0; - while (clusters_edges_active.empty() == false) { - Halfedge_descriptor hi = clusters_edges_active.front(); - clusters_edges_active.pop(); + while (!clusters_edges_active.empty()) { + Halfedge_descriptor hi = clusters_edges_active.front(); + clusters_edges_active.pop(); - Vertex_descriptor v1 = source(hi, pmesh); - Vertex_descriptor v2 = target(hi, pmesh); + Vertex_descriptor v1 = source(hi, pmesh); + Vertex_descriptor v2 = target(hi, pmesh); - int c1 = get(vertex_cluster_pmap, v1); - int c2 = get(vertex_cluster_pmap, v2); + int c1 = get(vertex_cluster_pmap, v1); + int c2 = get(vertex_cluster_pmap, v2); - if (c1 == -1) - { - // expand cluster c2 (add v1 to c2) - put(vertex_cluster_pmap, v1, c2); - typename GT::Point_3 vp1 = get(vpm, v1); - typename GT::Vector_3 vpv(vp1.x(), vp1.y(), vp1.z()); - clusters[c2].add_vertex(vpv, get(vertex_weight_pmap, v1)); - - // add all halfedges around v1 except hi to the queue - for (Halfedge_descriptor hd : halfedges_around_source(v1, pmesh)) - //if (hd != hi && hd != opposite(hi, pmesh)) - clusters_edges_new.push(hd); - nb_modifications++; - } - else if (c2 == -1) - { - // expand cluster c1 (add v2 to c1) - put(vertex_cluster_pmap, v2, c1); - typename GT::Point_3 vp2 = get(vpm, v2); - typename GT::Vector_3 vpv(vp2.x(), vp2.y(), vp2.z()); - clusters[c1].add_vertex(vpv, get(vertex_weight_pmap, v2)); - - // add all halfedges around v2 except hi to the queue - for (Halfedge_descriptor hd : halfedges_around_source(v2, pmesh)) - //if (hd != hi && hd != opposite(hi, pmesh)) - clusters_edges_new.push(hd); - nb_modifications++; - } - else if (c1 == c2) - { - clusters_edges_new.push(hi); - } - else - { - // compare the energy of the 3 cases - typename GT::Point_3 vp1 = get(vpm, v1); - typename GT::Vector_3 vpv1(vp1.x(), vp1.y(), vp1.z()); - typename GT::Point_3 vp2 = get(vpm, v2); - typename GT::Vector_3 vpv2(vp2.x(), vp2.y(), vp2.z()); - typename GT::FT v1_weight = get(vertex_weight_pmap, v1); - typename GT::FT v2_weight = get(vertex_weight_pmap, v2); - - typename GT::FT e_no_change = clusters[c1].compute_energy() + clusters[c2].compute_energy(); - - clusters[c1].remove_vertex(vpv1, v1_weight); - clusters[c2].add_vertex(vpv1, v1_weight); - - typename GT::FT e_v1_to_c2 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); - - typename GT::FT c1_weight_threshold = clusters[c1].weight_sum; - - // reset to no change - clusters[c1].add_vertex(vpv1, v1_weight); - clusters[c2].remove_vertex(vpv1, v1_weight); - - // The effect of the following should always be reversed after the comparison - clusters[c2].remove_vertex(vpv2, v2_weight); - clusters[c1].add_vertex(vpv2, v2_weight); - - typename GT::FT e_v2_to_c1 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); - - typename GT::FT c2_weight_threshold = clusters[c2].weight_sum; - - - if (e_v2_to_c1 < e_no_change && e_v2_to_c1 < e_v1_to_c2 && c2_weight_threshold > 0) + if (c1 == -1) { - // move v2 to c1 - put(vertex_cluster_pmap, v2, c1); + // expand cluster c2 (add v1 to c2) + put(vertex_cluster_pmap, v1, c2); + typename GT::Point_3 vp1 = get(vpm, v1); + typename GT::Vector_3 vpv(vp1.x(), vp1.y(), vp1.z()); + clusters[c2].add_vertex(vpv, get(vertex_weight_pmap, v1)); - // cluster data is already updated + // add all halfedges around v1 except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(v1, pmesh)) + //if (hd != hi && hd != opposite(hi, pmesh)) + clusters_edges_new.push(hd); + nb_modifications++; + } + else if (c2 == -1) + { + // expand cluster c1 (add v2 to c1) + put(vertex_cluster_pmap, v2, c1); + typename GT::Point_3 vp2 = get(vpm, v2); + typename GT::Vector_3 vpv(vp2.x(), vp2.y(), vp2.z()); + clusters[c1].add_vertex(vpv, get(vertex_weight_pmap, v2)); // add all halfedges around v2 except hi to the queue for (Halfedge_descriptor hd : halfedges_around_source(v2, pmesh)) //if (hd != hi && hd != opposite(hi, pmesh)) - clusters_edges_new.push(hd); + clusters_edges_new.push(hd); nb_modifications++; } - else if (e_v1_to_c2 < e_no_change && c1_weight_threshold > 0) + else if (c1 == c2) { - // move v1 to c2 - put(vertex_cluster_pmap, v1, c2); + clusters_edges_new.push(hi); + } + else + { + // compare the energy of the 3 cases + typename GT::Point_3 vp1 = get(vpm, v1); + typename GT::Vector_3 vpv1(vp1.x(), vp1.y(), vp1.z()); + typename GT::Point_3 vp2 = get(vpm, v2); + typename GT::Vector_3 vpv2(vp2.x(), vp2.y(), vp2.z()); + typename GT::FT v1_weight = get(vertex_weight_pmap, v1); + typename GT::FT v2_weight = get(vertex_weight_pmap, v2); - // need to reset cluster data and then update - clusters[c2].add_vertex(vpv2, v2_weight); - clusters[c1].remove_vertex(vpv2, v2_weight); + typename GT::FT e_no_change = clusters[c1].compute_energy() + clusters[c2].compute_energy(); clusters[c1].remove_vertex(vpv1, v1_weight); clusters[c2].add_vertex(vpv1, v1_weight); - // add all halfedges around v1 except hi to the queue - for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v1, pmesh), pmesh)) - //if (hd != hi && hd != opposite(hi, pmesh)) + typename GT::FT e_v1_to_c2 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); + + typename GT::FT c1_weight_threshold = clusters[c1].weight_sum; + + // reset to no change + clusters[c1].add_vertex(vpv1, v1_weight); + clusters[c2].remove_vertex(vpv1, v1_weight); + + // The effect of the following should always be reversed after the comparison + clusters[c2].remove_vertex(vpv2, v2_weight); + clusters[c1].add_vertex(vpv2, v2_weight); + + typename GT::FT e_v2_to_c1 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); + + typename GT::FT c2_weight_threshold = clusters[c2].weight_sum; + + + if (e_v2_to_c1 < e_no_change && e_v2_to_c1 < e_v1_to_c2 && c2_weight_threshold > 0) + { + // move v2 to c1 + put(vertex_cluster_pmap, v2, c1); + + // cluster data is already updated + + // add all halfedges around v2 except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(v2, pmesh)) + //if (hd != hi && hd != opposite(hi, pmesh)) clusters_edges_new.push(hd); - nb_modifications++; - } - else - { + nb_modifications++; + } + else if (e_v1_to_c2 < e_no_change && c1_weight_threshold > 0) + { + // move v1 to c2 + put(vertex_cluster_pmap, v1, c2); + + // need to reset cluster data and then update + clusters[c2].add_vertex(vpv2, v2_weight); + clusters[c1].remove_vertex(vpv2, v2_weight); + + clusters[c1].remove_vertex(vpv1, v1_weight); + clusters[c2].add_vertex(vpv1, v1_weight); + + // add all halfedges around v1 except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v1, pmesh), pmesh)) + //if (hd != hi && hd != opposite(hi, pmesh)) + clusters_edges_new.push(hd); + nb_modifications++; + } + else + { // no change but need to reset cluster data clusters[c2].add_vertex(vpv2, v2_weight); clusters[c1].remove_vertex(vpv2, v2_weight); clusters_edges_new.push(hi); + } } } - } + + clusters_edges_active.swap(clusters_edges_new); + } while (nb_modifications > 0); + // clean clusters here - // the goal is to delete clusters with multiple connected components - // for each cluster, do a BFS from a vertex in the cluster - // we need to keep the largest connected component for each cluster - // and set the other connected components to -1 (empty cluster), would also need to update clusters_edges_new + // the goal is to delete clusters with multiple connected components + // for each cluster, do a BFS from a vertex in the cluster + // we need to keep the largest connected component for each cluster + // and set the other connected components to -1 (empty cluster), would also need to update clusters_edges_new std::vector visited(num_vertices(pmesh), false); // std::vector visited_clusters(nb_clusters, false); @@ -360,7 +376,7 @@ void acvd_simplification( // visited_clusters[c] = true; q.push(vd); visited[vd] = true; - while (q.empty() == false) + while (!q.empty()) { Vertex_descriptor v = q.front(); q.pop(); @@ -370,7 +386,7 @@ void acvd_simplification( { Vertex_descriptor v2 = target(hd, pmesh); int c2 = get(vertex_cluster_pmap, v2); - if (c2 == c && visited[v2] == false) + if (c2 == c && !visited[v2]) { q.push(v2); visited[v2] = true; @@ -380,10 +396,20 @@ void acvd_simplification( } } + for (int c = 0; c < nb_clusters; c++) + { + std::cout << "cluster " << c << " has " << cluster_components[c].size() << " components\n"; + std::cout << "sizes: "; + for (int i = 0; i < cluster_components[c].size(); i++) + std::cout << cluster_components[c][i].size() << " / " << num_vertices(pmesh) << " "; + std::cout << "\n"; + } + // loop over clusters for (int c = 0; c < nb_clusters; c++) { if (cluster_components[c].size() <= 1) continue; // only one component, no need to do anything + nb_disconnected++; int max_component_size = 0; int max_component_index = -1; for (int component_i = 0; component_i < cluster_components[c].size(); component_i++) @@ -399,7 +425,6 @@ void acvd_simplification( { if (component_i != max_component_index) { - nb_disconnected++; for (Vertex_descriptor vd : cluster_components[c][component_i]) { put(vertex_cluster_pmap, vd, -1); @@ -422,9 +447,7 @@ void acvd_simplification( } std::cout << "nb_disconnected: " << nb_disconnected << "\n"; - - clusters_edges_active.swap(clusters_edges_new); - } while (nb_modifications > 0 || nb_disconnected > 0); + } while (nb_disconnected > 0); // TODO: Move out the disconnected clustering check (& cleaning) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_plugin.cpp new file mode 100644 index 00000000000..f430f805d73 --- /dev/null +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_plugin.cpp @@ -0,0 +1,122 @@ +#undef NDEBUG +#include + +#include "Messages_interface.h" +#include +#include +#include "Scene_polyhedron_selection_item.h" +#include "ui_ACVD_simplification_widget.h" + +#include "SMesh_type.h" +typedef Scene_surface_mesh_item Scene_facegraph_item; + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +typedef Scene_facegraph_item::Face_graph FaceGraph; + +using namespace CGAL::Three; +class Polyhedron_demo_acvd_simplification_plugin : + public QObject, + public Polyhedron_demo_plugin_helper +{ + Q_OBJECT + Q_INTERFACES(CGAL::Three::Polyhedron_demo_plugin_interface) + Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.PluginInterface/1.0" FILE "acvd_simplification_plugin.json") +public: + bool applicable(QAction*) const { + return qobject_cast(scene->item(scene->mainSelectionIndex())); + } + void print_message(QString message) { CGAL::Three::Three::information(message);} + QList actions() const { return QList() << actionACVD; } + + + void init(QMainWindow* mainWindow, CGAL::Three::Scene_interface* scene_interface, Messages_interface* m) { + mw = mainWindow; + scene = scene_interface; + messages = m; + actionACVD = new QAction(tr( + "ACVD Simplification" + ), mw); + //actionACVD->setProperty("subMenuName", ""); + if (actionACVD) + connect(actionACVD, SIGNAL(triggered()), this, SLOT(acvd_action())); + + dock_widget = new QDockWidget( + "ACVD Simplification", mw); + + dock_widget->setVisible(false); + + ui_widget.setupUi(dock_widget); + ui_widget.nb_clusters_spin_box->setMinimum(10); + ui_widget.nb_clusters_spin_box->setMaximum(1000000); + // get number of vertices of the mesh + Scene_facegraph_item* item = getSelectedItem(); + if(!item) { return; } + // set default number of clusters to 5% of the number of vertices + ui_widget.nb_clusters_spin_box->setValue(max(item->face_graph()->number_of_faces() / 20, (unsigned int)10)); + + addDockWidget(dock_widget); + dock_widget->setWindowTitle(tr( + "ACVD Simplification" + )); + + connect(ui_widget.ACVD_button, SIGNAL(clicked()), this, SLOT(on_ACVD_button_clicked())); + } + virtual void closure() + { + dock_widget->hide(); + } + +public Q_SLOTS: + void acvd_action() { + dock_widget->show(); + dock_widget->raise(); + } + + void on_ACVD_button_clicked() { + /*typename FaceGraphItem::Face_graph* graph = item->face_graph(); + if(!graph) return; + QElapsedTimer time; + time.start(); + CGAL::Three::Three::information("Upsample subdivision..."); + QApplication::setOverrideCursor(Qt::WaitCursor); + CGAL::Subdivision_method_3::Upsample_subdivision( + *graph, + CGAL::params::number_of_iterations(ui_widget.nb_clusters_spin_box->value()/10) + ); + + CGAL::Three::Three::information(QString("ok (%1 ms)").arg(time.elapsed())); + QApplication::restoreOverrideCursor(); + item->invalidateOpenGLBuffers(); + scene->itemChanged(item);*/ + } + +private: + Messages_interface* messages; + QAction* actionACVD; + + QDockWidget* dock_widget; + Ui::ACVD_Simplification ui_widget; + +}; // end Polyhedron_demo_acvd_simplification_plugin + +// Q_EXPORT_PLUGIN2(Polyhedron_demo_acvd_simplification_plugin, Polyhedron_demo_acvd_simplification_plugin) + +#include "ACVD_simplification_plugin.moc" diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_widget.ui b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_widget.ui new file mode 100644 index 00000000000..476c8227c5b --- /dev/null +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_widget.ui @@ -0,0 +1,93 @@ + + + ACVD_Simplification + + + + 0 + 0 + 342 + 274 + + + + ACVD Simplification + + + + + + + ACVD-Simplification + + + + + + Cluster Metric: + + + + + + + Isotropic Clustering + + + + + + + QEM Clustering + + + + + + + Anisotropic Clustering + + + + + + + + + Target number of Vertices (Clusters): + + + + + + + + 100 + + + + + + + + + ACVD Simplification + + + + + + + + + + + + DoubleEdit + QLineEdit +
CGAL_double_edit.h
+
+
+ + +
diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt b/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt index 152d84952ae..9a8c36df74f 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt @@ -13,6 +13,10 @@ polyhedron_demo_plugin(extrude_plugin Extrude_plugin KEYWORDS PMP) target_link_libraries(extrude_plugin PUBLIC scene_surface_mesh_item scene_selection_item) +qt5_wrap_ui( acvd_simplificationUI_FILES ACVD_simplification_widget.ui) +polyhedron_demo_plugin(acvd_simplification_plugin ACVD_simplification_plugin ${acvd_simplificationUI_FILES} KEYWORDS PMP) +target_link_libraries(acvd_simplification_plugin PUBLIC scene_surface_mesh_item scene_points_with_normal_item scene_polygon_soup_item CGAL::Eigen3_support) + if(TARGET CGAL::Eigen3_support) if("${EIGEN3_VERSION}" VERSION_GREATER "3.1.90") qt5_wrap_ui( hole_fillingUI_FILES Hole_filling_widget.ui) From fa30609b6dc59e5d83aa1b692b2dc4da24bd89c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 15 Sep 2023 11:30:00 +0200 Subject: [PATCH 18/56] use a dialog instead of the widget --- .../Plugins/PMP/ACVD_simplification_dialog.ui | 120 ++++++++++++++++++ .../PMP/ACVD_simplification_plugin.cpp | 60 ++++----- .../Plugins/PMP/ACVD_simplification_widget.ui | 93 -------------- .../Polyhedron/Plugins/PMP/CMakeLists.txt | 2 +- 4 files changed, 144 insertions(+), 131 deletions(-) create mode 100644 Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_dialog.ui delete mode 100644 Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_widget.ui diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_dialog.ui b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_dialog.ui new file mode 100644 index 00000000000..a8e67668ef1 --- /dev/null +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_dialog.ui @@ -0,0 +1,120 @@ + + + ACVDDialog + + + + 0 + 0 + 400 + 300 + + + + ACVD Simplification + + + + + + Target number of Vertices (Clusters): + + + + + + + 100 + + + + + + + Cluster Metric: + + + + + + + Isotropic Clustering + + + + + + + QEM Clustering + + + + + + + Anisotropic Clustering + + + + + + + ACVD Simplification + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + DoubleEdit + QLineEdit +
CGAL_double_edit.h
+
+
+ + + + buttonBox + accepted() + ACVDDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ACVDDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_plugin.cpp index f430f805d73..22be740f4ec 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_plugin.cpp @@ -5,7 +5,7 @@ #include #include #include "Scene_polyhedron_selection_item.h" -#include "ui_ACVD_simplification_widget.h" +#include "ui_ACVD_simplification_dialog.h" #include "SMesh_type.h" typedef Scene_surface_mesh_item Scene_facegraph_item; @@ -47,50 +47,40 @@ public: QList actions() const { return QList() << actionACVD; } - void init(QMainWindow* mainWindow, CGAL::Three::Scene_interface* scene_interface, Messages_interface* m) { + void init(QMainWindow* mainWindow, CGAL::Three::Scene_interface* scene_interface, Messages_interface*) { mw = mainWindow; scene = scene_interface; - messages = m; actionACVD = new QAction(tr( "ACVD Simplification" ), mw); //actionACVD->setProperty("subMenuName", ""); if (actionACVD) connect(actionACVD, SIGNAL(triggered()), this, SLOT(acvd_action())); - - dock_widget = new QDockWidget( - "ACVD Simplification", mw); - - dock_widget->setVisible(false); - - ui_widget.setupUi(dock_widget); - ui_widget.nb_clusters_spin_box->setMinimum(10); - ui_widget.nb_clusters_spin_box->setMaximum(1000000); - // get number of vertices of the mesh - Scene_facegraph_item* item = getSelectedItem(); - if(!item) { return; } - // set default number of clusters to 5% of the number of vertices - ui_widget.nb_clusters_spin_box->setValue(max(item->face_graph()->number_of_faces() / 20, (unsigned int)10)); - - addDockWidget(dock_widget); - dock_widget->setWindowTitle(tr( - "ACVD Simplification" - )); - - connect(ui_widget.ACVD_button, SIGNAL(clicked()), this, SLOT(on_ACVD_button_clicked())); - } - virtual void closure() - { - dock_widget->hide(); } public Q_SLOTS: void acvd_action() { - dock_widget->show(); - dock_widget->raise(); - } - void on_ACVD_button_clicked() { + // Create dialog box + QDialog dialog(mw); + ui.setupUi(&dialog); + + ui.nb_clusters_spin_box->setMinimum(10); + ui.nb_clusters_spin_box->setMaximum(1000000); + // get number of vertices of the mesh + Scene_facegraph_item* item = getSelectedItem(); + if(!item) { return; } + // set default number of clusters to 5% of the number of vertices + ui.nb_clusters_spin_box->setValue(max(item->face_graph()->number_of_faces() / 20, (unsigned int)10)); + + int i = dialog.exec(); + if (i == QDialog::Rejected) + { + std::cout << "Remeshing aborted" << std::endl; + return; + } + + /*typename FaceGraphItem::Face_graph* graph = item->face_graph(); if(!graph) return; QElapsedTimer time; @@ -109,12 +99,8 @@ public Q_SLOTS: } private: - Messages_interface* messages; QAction* actionACVD; - - QDockWidget* dock_widget; - Ui::ACVD_Simplification ui_widget; - + Ui::ACVDDialog ui; }; // end Polyhedron_demo_acvd_simplification_plugin // Q_EXPORT_PLUGIN2(Polyhedron_demo_acvd_simplification_plugin, Polyhedron_demo_acvd_simplification_plugin) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_widget.ui b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_widget.ui deleted file mode 100644 index 476c8227c5b..00000000000 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_widget.ui +++ /dev/null @@ -1,93 +0,0 @@ - - - ACVD_Simplification - - - - 0 - 0 - 342 - 274 - - - - ACVD Simplification - - - - - - - ACVD-Simplification - - - - - - Cluster Metric: - - - - - - - Isotropic Clustering - - - - - - - QEM Clustering - - - - - - - Anisotropic Clustering - - - - - - - - - Target number of Vertices (Clusters): - - - - - - - - 100 - - - - - - - - - ACVD Simplification - - - - - - - - - - - - DoubleEdit - QLineEdit -
CGAL_double_edit.h
-
-
- - -
diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt b/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt index 9a8c36df74f..2b159b7de13 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt @@ -13,7 +13,7 @@ polyhedron_demo_plugin(extrude_plugin Extrude_plugin KEYWORDS PMP) target_link_libraries(extrude_plugin PUBLIC scene_surface_mesh_item scene_selection_item) -qt5_wrap_ui( acvd_simplificationUI_FILES ACVD_simplification_widget.ui) +qt5_wrap_ui( acvd_simplificationUI_FILES ACVD_simplification_dialog.ui) polyhedron_demo_plugin(acvd_simplification_plugin ACVD_simplification_plugin ${acvd_simplificationUI_FILES} KEYWORDS PMP) target_link_libraries(acvd_simplification_plugin PUBLIC scene_surface_mesh_item scene_points_with_normal_item scene_polygon_soup_item CGAL::Eigen3_support) From cbd4c0c318819b79e599d1b706b7ee5967171c0c Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 22 Sep 2023 10:50:02 +0300 Subject: [PATCH 19/56] demo plugin action --- .../PMP/ACVD_simplification_plugin.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_plugin.cpp index 22be740f4ec..9fb797f63bd 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_plugin.cpp @@ -81,21 +81,26 @@ public Q_SLOTS: } - /*typename FaceGraphItem::Face_graph* graph = item->face_graph(); + Face_graph* graph = item->face_graph(); if(!graph) return; QElapsedTimer time; time.start(); - CGAL::Three::Three::information("Upsample subdivision..."); + CGAL::Three::Three::information("ACVD Simplification..."); QApplication::setOverrideCursor(Qt::WaitCursor); - CGAL::Subdivision_method_3::Upsample_subdivision( - *graph, - CGAL::params::number_of_iterations(ui_widget.nb_clusters_spin_box->value()/10) - ); + FaceGraph simplified_graph = CGAL::Polygon_mesh_processing::acvd_isotropic_simplification(*graph, ui.nb_clusters_spin_box->value()); + // update the scene CGAL::Three::Three::information(QString("ok (%1 ms)").arg(time.elapsed())); QApplication::restoreOverrideCursor(); item->invalidateOpenGLBuffers(); - scene->itemChanged(item);*/ + + // add the simplified mesh to the scene + Scene_facegraph_item* simplified_item = new Scene_facegraph_item(simplified_graph); + simplified_item->setName(QString(item->name() + "_simplified")); + scene->addItem(simplified_item); + simplified_item->invalidateOpenGLBuffers(); + scene->itemChanged(simplified_item); + } private: From dbda656cd773baf4f372759e8576a7e568d0e511 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 22 Sep 2023 10:50:11 +0300 Subject: [PATCH 20/56] minor changes --- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 57 ++++++++++++++----- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 90d8debd83c..5f9952f3fdd 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -122,7 +122,7 @@ void acvd_subdivide_if_needed( template -void acvd_simplification( +PolygonMesh acvd_simplification( PolygonMesh& pmesh, const int nb_clusters, const NamedParameters& np = parameters::default_values() @@ -149,8 +149,33 @@ void acvd_simplification( // TODO: handle cases where the mesh is not a triangle mesh CGAL_precondition(CGAL::is_triangle_mesh(pmesh)); - PolygonMesh pmesh_subdivided = pmesh; - acvd_subdivide_if_needed(pmesh_subdivided, nb_clusters, np); + // TODO: copy the mesh in order to not modify the original mesh + int nb_vertices = num_vertices(pmesh); + + // To provide the functionality remeshing (not just simplification), we might need to + // subdivide the mesh before clustering + // in either case, nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD + + // do the following while nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD + // That is, because the subdivision steps heuristic is not 100% guaranteed to produce + // the desired number of vertices. + while (nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) + { + double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); + int subdivide_steps = max((int)ceil(log(curr_factor) / log(4)), 0); + + std::cout << "subdivide_steps: " << subdivide_steps << std::endl; + + if (subdivide_steps > 0) + { + Subdivision_method_3::Upsample_subdivision( + pmesh, + CGAL::parameters::number_of_iterations(subdivide_steps).vertex_point_map(vpm) + ); + vpm = get_property_map(CGAL::vertex_point, pmesh); + nb_vertices = num_vertices(pmesh); + } + } // initial random clusters // property map from vertex_descriptor to cluster index @@ -396,14 +421,14 @@ void acvd_simplification( } } - for (int c = 0; c < nb_clusters; c++) - { - std::cout << "cluster " << c << " has " << cluster_components[c].size() << " components\n"; - std::cout << "sizes: "; - for (int i = 0; i < cluster_components[c].size(); i++) - std::cout << cluster_components[c][i].size() << " / " << num_vertices(pmesh) << " "; - std::cout << "\n"; - } + // for (int c = 0; c < nb_clusters; c++) + // { + // std::cout << "cluster " << c << " has " << cluster_components[c].size() << " components\n"; + // std::cout << "sizes: "; + // for (int i = 0; i < cluster_components[c].size(); i++) + // std::cout << cluster_components[c][i].size() << " / " << num_vertices(pmesh) << " "; + // std::cout << "\n"; + // } // loop over clusters for (int c = 0; c < nb_clusters; c++) @@ -593,10 +618,12 @@ void acvd_simplification( orient_polygon_soup(points, polygons); polygon_soup_to_polygon_mesh(points, polygons, simplified_mesh); - name = std::to_string(nb_clusters) + "_simped.off"; - CGAL::IO::write_OFF(name, simplified_mesh); + // name = std::to_string(nb_clusters) + "_simped.off"; + // CGAL::IO::write_OFF(name, simplified_mesh); std::cout << "kak3" << std::endl; + return simplified_mesh; + } @@ -604,13 +631,13 @@ void acvd_simplification( template -void acvd_isotropic_simplification( +PolygonMesh acvd_isotropic_simplification( PolygonMesh& pmesh, const int& nb_vertices, const NamedParameters& np = parameters::default_values() ) { - internal::acvd_simplification( + return internal::acvd_simplification( pmesh, nb_vertices, np From 4f99fddb39270d60cc9f10ddd3693d7908864a73 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 22 Sep 2023 11:46:46 +0300 Subject: [PATCH 21/56] Func Documentation --- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 5f9952f3fdd..cddc7c7644c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -629,6 +629,43 @@ PolygonMesh acvd_simplification( } // namespace internal +/** +* \ingroup ACVD_grp +* +* Performs uniform (isotropic) centroidal voronoi diagram simplification on a polygon mesh. +* This can also be used for remeshing by setting the number of clusters to the desired number of vertices. +* The number of clusters is the number of vertices in the output mesh. +* +* @tparam PolygonMesh a model of `FaceListGraph` +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". +* +* @param pmesh input polygon mesh +* @param nb_vertices number of target vertices in the output mesh +* @param np optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below +* `GT` stands for the type of the object provided to the named parameter `geom_traits()`. +* +* \cgalNamedParamsBegin +* \cgalParamNBegin{vertex_point_map} +* \cgalParamDescription{a property map associating points to the vertices of `pmesh`.} +* \cgalParamType{a class model of `ReadablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` +* as key type and `GT::Point_3` as value type.} +* \cgalParamDefault{`boost::get(CGAL::vertex_point, pmesh)`.} +* \cgalParamExtra{If this parameter is omitted, an internal property map for +* `CGAL::vertex_point_t` must be available in `PolygonMesh`.} +* \cgalParamNEnd +* +* \cgalParamNBegin{geom_traits} +* \cgalParamDescription{an instance of a geometric traits class.} +* \cgalParamType{a class model of `Kernel`} +* \cgalParamDefault{a \cgal Kernel deduced from the point type, using `CGAL::Kernel_traits`.} +* \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.} +* \cgalParamNEnd +* +* \cgalNamedParamsEnd +* +*/ + template PolygonMesh acvd_isotropic_simplification( From bbc5a345b8dcb01215c3474b3c723aa23e1e485d Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 5 Oct 2023 13:08:42 +0300 Subject: [PATCH 22/56] adaptive clustering first version --- .../Polygon_mesh_processing/acvd_example.cpp | 26 ++++++++++++++- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 33 +++++++++++++++++-- .../internal/parameters_interface.h | 9 +++++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp index 62f3f156df1..193362237cd 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -25,13 +26,36 @@ int main(int argc, char* argv[]) const int nb_clusters = (argc > 2) ? atoi(argv[2]) : 20; + const double gradation_factor = (argc > 3) ? atof(argv[3]) : 0; + + if (!CGAL::IO::read_polygon_mesh(filename, smesh)) { std::cerr << "Invalid input file." << std::endl; return EXIT_FAILURE; } - PMP::acvd_isotropic_simplification(smesh, nb_clusters); + bool created = false; + Surface_Mesh::Property_map> + principal_curvatures_and_directions_map; + + boost::tie(principal_curvatures_and_directions_map, created) = + smesh.add_property_map> + ("v:principal_curvatures_and_directions_map", { 0, 0, + Epic_kernel::Vector_3(0,0,0), + Epic_kernel::Vector_3(0,0,0) }); + assert(created); + + + PMP::interpolated_corrected_principal_curvatures_and_directions(smesh, principal_curvatures_and_directions_map); + + + PMP::acvd_isotropic_simplification( + smesh, + nb_clusters, + CGAL::parameters::vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) + .gradation_factor(gradation_factor) + ); // std::cout << "kak3" << std::endl; return 0; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index cddc7c7644c..0d46ca802b4 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -120,6 +121,9 @@ void acvd_subdivide_if_needed( } +// provide a property map for principal curvatures as a named parameter for adaptive clustering +// provide a gradation factor as a named parameter for adaptive clustering + template PolygonMesh acvd_simplification( @@ -138,6 +142,10 @@ PolygonMesh acvd_simplification( typedef typename boost::property_map >::type VertexClusterMap; typedef typename boost::property_map >::type VertexWeightMap; typedef typename boost::property_map >::type HalfedgeVisitedMap; + typedef Constant_property_map> Default_principal_map; + typedef typename internal_np::Lookup_named_param_def::type Vertex_principal_curvatures_and_directions_map; using parameters::choose_parameter; using parameters::get_parameter; @@ -146,6 +154,17 @@ PolygonMesh acvd_simplification( Vertex_position_map vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), get_property_map(CGAL::vertex_point, pmesh)); + // get curvature related parameters + const typename GT::FT gradation_factor = choose_parameter(get_parameter(np, internal_np::gradation_factor), 0); + const typename Vertex_principal_curvatures_and_directions_map vpcd_map = + choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions_map), + Default_principal_map()); + + if (gradation_factor > 0 && + is_default_parameter::value) + interpolated_corrected_principal_curvatures_and_directions(pmesh, vpcd_map); + + // TODO: handle cases where the mesh is not a triangle mesh CGAL_precondition(CGAL::is_triangle_mesh(pmesh)); @@ -200,7 +219,15 @@ PolygonMesh acvd_simplification( for (Vertex_descriptor vd : vertices_around_face(halfedge(fd, pmesh), pmesh)) { typename GT::FT vertex_weight = get(vertex_weight_pmap, vd); - vertex_weight += weight; + if (gradation_factor == 0) // no adaptive clustering + vertex_weight += weight; + else // adaptive clustering + { + typename GT::FT k1 = get(vpcd_map, vd).min_curvature; + typename GT::FT k2 = get(vpcd_map, vd).max_curvature; + typename GT::FT k_sq = (k1 * k1 + k2 * k2); + vertex_weight += weight * pow(k_sq, gradation_factor / 2.0); // /2.0 because k_sq is squared + } put(vertex_weight_pmap, vd, vertex_weight); } } @@ -618,8 +645,8 @@ PolygonMesh acvd_simplification( orient_polygon_soup(points, polygons); polygon_soup_to_polygon_mesh(points, polygons, simplified_mesh); - // name = std::to_string(nb_clusters) + "_simped.off"; - // CGAL::IO::write_OFF(name, simplified_mesh); + name = std::to_string(nb_clusters) + "_simped.off"; + CGAL::IO::write_OFF(name, simplified_mesh); std::cout << "kak3" << std::endl; return simplified_mesh; diff --git a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h index 7792ad5cdb6..2734b8d3648 100644 --- a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h +++ b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h @@ -93,6 +93,14 @@ CGAL_add_named_parameter(number_of_points_per_edge_t, number_of_points_per_edge, CGAL_add_named_parameter(number_of_points_on_edges_t, number_of_points_on_edges, number_of_points_on_edges) CGAL_add_named_parameter(nb_points_per_area_unit_t, nb_points_per_area_unit, number_of_points_per_area_unit) CGAL_add_named_parameter(nb_points_per_distance_unit_t, nb_points_per_distance_unit, number_of_points_per_distance_unit) +CGAL_add_named_parameter(vertex_mean_curvature_map_t, vertex_mean_curvature_map, vertex_mean_curvature_map) +CGAL_add_named_parameter(vertex_Gaussian_curvature_map_t, vertex_Gaussian_curvature_map, vertex_Gaussian_curvature_map) +CGAL_add_named_parameter(vertex_principal_curvatures_and_directions_map_t, vertex_principal_curvatures_and_directions_map, vertex_principal_curvatures_and_directions_map) +CGAL_add_named_parameter(vertex_mean_curvature_t, vertex_mean_curvature, vertex_mean_curvature) +CGAL_add_named_parameter(vertex_Gaussian_curvature_t, vertex_Gaussian_curvature, vertex_Gaussian_curvature) +CGAL_add_named_parameter(vertex_principal_curvatures_and_directions_t, vertex_principal_curvatures_and_directions, vertex_principal_curvatures_and_directions) +CGAL_add_named_parameter(ball_radius_t, ball_radius, ball_radius) +CGAL_add_named_parameter(gradation_factor_t, gradation_factor, gradation_factor) CGAL_add_named_parameter(outward_orientation_t, outward_orientation, outward_orientation) CGAL_add_named_parameter(overlap_test_t, overlap_test, do_overlap_test_of_bounded_sides) CGAL_add_named_parameter(preserve_genus_t, preserve_genus, preserve_genus) @@ -155,6 +163,7 @@ CGAL_add_named_parameter(patch_normal_map_t, patch_normal_map, patch_normal_map) CGAL_add_named_parameter(region_primitive_map_t, region_primitive_map, region_primitive_map) CGAL_add_named_parameter(postprocess_regions_t, postprocess_regions, postprocess_regions) + // List of named parameters that we use in the package 'Surface Mesh Simplification' CGAL_add_named_parameter(get_cost_policy_t, get_cost_policy, get_cost) CGAL_add_named_parameter(get_placement_policy_t, get_placement_policy, get_placement) From 0a4a4f43404298827244a00cff11f7e08a7cbc0e Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:16:05 +0300 Subject: [PATCH 23/56] curvature weight --- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 0d46ca802b4..8c292378f0d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -187,10 +187,17 @@ PolygonMesh acvd_simplification( if (subdivide_steps > 0) { - Subdivision_method_3::Upsample_subdivision( - pmesh, - CGAL::parameters::number_of_iterations(subdivide_steps).vertex_point_map(vpm) - ); + if (gradation_factor == 0) // no adaptive clustering + Subdivision_method_3::Upsample_subdivision( + pmesh, + CGAL::parameters::number_of_iterations(subdivide_steps) + ); + else // adaptive clustering + // TODO: need to interpolate curvature values + Subdivision_method_3::Upsample_subdivision( + pmesh, + CGAL::parameters::number_of_iterations(subdivide_steps).vertex_principal_curvatures_and_directions_map(vpcd_map) + ); vpm = get_property_map(CGAL::vertex_point, pmesh); nb_vertices = num_vertices(pmesh); } @@ -226,7 +233,7 @@ PolygonMesh acvd_simplification( typename GT::FT k1 = get(vpcd_map, vd).min_curvature; typename GT::FT k2 = get(vpcd_map, vd).max_curvature; typename GT::FT k_sq = (k1 * k1 + k2 * k2); - vertex_weight += weight * pow(k_sq, gradation_factor / 2.0); // /2.0 because k_sq is squared + vertex_weight += weight * (1 + pow(k_sq, gradation_factor / 2.0)); // /2.0 because k_sq is squared } put(vertex_weight_pmap, vd, vertex_weight); } From 0868dcc009227badbf86ce91e55770cb05f4e3b3 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 5 Oct 2023 15:57:25 +0300 Subject: [PATCH 24/56] minor changes --- .../examples/Polygon_mesh_processing/acvd_example.cpp | 2 -- .../include/CGAL/Polygon_mesh_processing/acvd/acvd.h | 7 ++++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp index 193362237cd..32fb17342e4 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -46,10 +46,8 @@ int main(int argc, char* argv[]) Epic_kernel::Vector_3(0,0,0) }); assert(created); - PMP::interpolated_corrected_principal_curvatures_and_directions(smesh, principal_curvatures_and_directions_map); - PMP::acvd_isotropic_simplification( smesh, nb_clusters, diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 8c292378f0d..d525f2e9789 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -279,6 +279,7 @@ PolygonMesh acvd_simplification( int nb_modifications = 0; int nb_disconnected = 0; + int iteration = 0; do { @@ -403,8 +404,11 @@ PolygonMesh acvd_simplification( } } + iteration++; + std::cout << "iteration: " << iteration << ", # Modifications: " << nb_modifications << "\n"; + clusters_edges_active.swap(clusters_edges_new); - } while (nb_modifications > 0); + } while (nb_modifications > 0/* && iteration < 100*/); // clean clusters here // the goal is to delete clusters with multiple connected components @@ -412,6 +416,7 @@ PolygonMesh acvd_simplification( // we need to keep the largest connected component for each cluster // and set the other connected components to -1 (empty cluster), would also need to update clusters_edges_new + // TODO: This visited array should be a property map for generalization std::vector visited(num_vertices(pmesh), false); // std::vector visited_clusters(nb_clusters, false); // [cluster][component_index][vertex_index] From 1bf969316095b94bc02caaf46ba87359b2e6a502 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 20 Oct 2023 12:31:39 +0300 Subject: [PATCH 25/56] curvature interpolation and other fixes --- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 133 +++++++++++++++--- 1 file changed, 110 insertions(+), 23 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index d525f2e9789..7b070f7e66f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -82,6 +82,82 @@ struct ClusterData { } }; +template +void upsample_subdivision_property(PolygonMesh& pmesh, const NamedParameters& np = parameters::default_values()) { + typedef typename GetGeomTraits::type GT; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; + typedef Constant_property_map> Default_principal_map; + typedef typename internal_np::Lookup_named_param_def::type VPCDM; + + using parameters::choose_parameter; + using parameters::get_parameter; + using parameters::is_default_parameter; + + typedef typename CGAL::GetVertexPointMap::type VPM; + VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), + get_property_map(CGAL::vertex_point, pmesh)); + + // get curvature related parameters + const typename VPCDM vpcd_map = + choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions_map), + Default_principal_map()); + + // unordered_set of old vertices + std::unordered_set old_vertices; + +// // make a copy of the property map +// typename VPCDM vpcd_map_new = get(CGAL::dynamic_vertex_property_t>(), pmesh); + + + bool curvatures_available = !is_default_parameter::value; + + unsigned int step = choose_parameter(get_parameter(np, internal_np::number_of_iterations), 1); + Upsample_mask_3 mask(&pmesh, vpm); + + for (unsigned int i = 0; i < step; i++){ + for (Vertex_descriptor vd : vertices(pmesh)) + old_vertices.insert(vd); + + Subdivision_method_3::internal::PTQ_1step(pmesh, vpm, mask); + // interpolate curvature values + if (curvatures_available) + { + for (Vertex_descriptor vd : vertices(pmesh)) + { + if (old_vertices.find(vd) == old_vertices.end()) + { + Principal_curvatures_and_directions pcd; + pcd.min_curvature = 0; + pcd.max_curvature = 0; + pcd.min_direction = GT::Vector_3(0, 0, 0); + pcd.max_direction = GT::Vector_3(0, 0, 0); + for (Halfedge_descriptor hd : halfedges_around_target(vd, pmesh)) + { + Vertex_descriptor v1 = source(hd, pmesh); + if (old_vertices.find(v1) != old_vertices.end()) + { + Principal_curvatures_and_directions pcd1 = get(vpcd_map, v1); + pcd.min_curvature += pcd1.min_curvature; + pcd.max_curvature += pcd1.max_curvature; + pcd.min_direction += pcd1.min_direction; + pcd.max_direction += pcd1.max_direction; + } + } + pcd.min_curvature = pcd.min_curvature / 2; + pcd.max_curvature = pcd.max_curvature / 2; + pcd.min_direction = pcd.min_direction / sqrt(pcd.min_direction.squared_length()); + pcd.max_direction = pcd.max_direction / sqrt(pcd.max_direction.squared_length()); + put(vpcd_map, vd, pcd); + } + } + } + } +} + + // To provide the functionality remeshing (not just simplification), we might need to // subdivide the mesh before clustering @@ -111,7 +187,7 @@ void acvd_subdivide_if_needed( if (subdivide_steps > 0) { - Subdivision_method_3::Upsample_subdivision( + upsample_subdivision_property( pmesh, np.number_of_iterations(subdivide_steps) ); @@ -140,9 +216,9 @@ PolygonMesh acvd_simplification( typedef typename boost::graph_traits::face_descriptor Face_descriptor; typedef typename boost::property_map >::type VertexColorMap; typedef typename boost::property_map >::type VertexClusterMap; + typedef typename boost::property_map >::type VertexVisitedMap; typedef typename boost::property_map >::type VertexWeightMap; - typedef typename boost::property_map >::type HalfedgeVisitedMap; - typedef Constant_property_map> Default_principal_map; + typedef Constant_property_map> Default_principal_map; typedef typename internal_np::Lookup_named_param_def::type Vertex_principal_curvatures_and_directions_map; @@ -167,8 +243,9 @@ PolygonMesh acvd_simplification( // TODO: handle cases where the mesh is not a triangle mesh CGAL_precondition(CGAL::is_triangle_mesh(pmesh)); - + // TODO: copy the mesh in order to not modify the original mesh + //PolygonMesh pmesh = pmesh_org; int nb_vertices = num_vertices(pmesh); // To provide the functionality remeshing (not just simplification), we might need to @@ -194,7 +271,7 @@ PolygonMesh acvd_simplification( ); else // adaptive clustering // TODO: need to interpolate curvature values - Subdivision_method_3::Upsample_subdivision( + upsample_subdivision_property( pmesh, CGAL::parameters::number_of_iterations(subdivide_steps).vertex_principal_curvatures_and_directions_map(vpcd_map) ); @@ -206,6 +283,7 @@ PolygonMesh acvd_simplification( // initial random clusters // property map from vertex_descriptor to cluster index VertexClusterMap vertex_cluster_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); + VertexVisitedMap vertex_visited_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); VertexWeightMap vertex_weight_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); std::vector> clusters(nb_clusters); std::queue clusters_edges_active; @@ -215,6 +293,7 @@ PolygonMesh acvd_simplification( for (Vertex_descriptor vd : vertices(pmesh)) { put(vertex_weight_pmap, vd, 0); + put(vertex_visited_pmap, vd, false); put(vertex_cluster_pmap, vd, -1); } @@ -233,11 +312,12 @@ PolygonMesh acvd_simplification( typename GT::FT k1 = get(vpcd_map, vd).min_curvature; typename GT::FT k2 = get(vpcd_map, vd).max_curvature; typename GT::FT k_sq = (k1 * k1 + k2 * k2); - vertex_weight += weight * (1 + pow(k_sq, gradation_factor / 2.0)); // /2.0 because k_sq is squared + vertex_weight += weight * (/*`eps * avg_curvature` instead of */ 1 + pow(k_sq, gradation_factor / 2.0)); // /2.0 because k_sq is squared } put(vertex_weight_pmap, vd, vertex_weight); } } + // TODO: clamp the weights up and below by a ratio (like 10,000) * avg_weights for (int ci = 0; ci < nb_clusters; ci++) { @@ -279,11 +359,11 @@ PolygonMesh acvd_simplification( int nb_modifications = 0; int nb_disconnected = 0; - int iteration = 0; do { nb_disconnected = 0; + int iteration = 0; do { nb_modifications = 0; @@ -408,7 +488,21 @@ PolygonMesh acvd_simplification( std::cout << "iteration: " << iteration << ", # Modifications: " << nb_modifications << "\n"; clusters_edges_active.swap(clusters_edges_new); - } while (nb_modifications > 0/* && iteration < 100*/); + } while (nb_modifications > 0 && (iteration < 50 || nb_modifications > 0.001 * nb_vertices)); + + // to test the following: make all clusters belonging to c2 to belong to c1 instead + + //for (Vertex_descriptor vd : vertices(pmesh)) + //{ + // int c = get(vertex_cluster_pmap, vd); + // if (c == 2) { + // put(vertex_cluster_pmap, vd, 1); + // typename GT::Point_3 vp = get(vpm, vd); + // typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); + // clusters[1].add_vertex(vpv, get(vertex_weight_pmap, vd)); + // clusters[2].remove_vertex(vpv, get(vertex_weight_pmap, vd)); + // } + //} // clean clusters here // the goal is to delete clusters with multiple connected components @@ -416,30 +510,25 @@ PolygonMesh acvd_simplification( // we need to keep the largest connected component for each cluster // and set the other connected components to -1 (empty cluster), would also need to update clusters_edges_new - // TODO: This visited array should be a property map for generalization - std::vector visited(num_vertices(pmesh), false); - // std::vector visited_clusters(nb_clusters, false); - // [cluster][component_index][vertex_index] std::vector>> cluster_components(nb_clusters, std::vector>()); std::queue q; // loop over vertices + int cou = 0; for (Vertex_descriptor vd : vertices(pmesh)) { - if (visited[vd]) continue; + if (get(vertex_visited_pmap, vd)) continue; int c = get(vertex_cluster_pmap, vd); if (c != -1) { // first component of this cluster - if (cluster_components[c].size() == 0) - cluster_components[c].push_back(std::vector()); - + cluster_components[c].push_back(std::vector()); int component_i = cluster_components[c].size() - 1; // visited_clusters[c] = true; q.push(vd); - visited[vd] = true; + put(vertex_visited_pmap, vd, true); while (!q.empty()) { Vertex_descriptor v = q.front(); @@ -450,16 +539,15 @@ PolygonMesh acvd_simplification( { Vertex_descriptor v2 = target(hd, pmesh); int c2 = get(vertex_cluster_pmap, v2); - if (c2 == c && !visited[v2]) + if (c2 == c && !get(vertex_visited_pmap, v2)) { q.push(v2); - visited[v2] = true; + put(vertex_visited_pmap, v2, true); } } } } } - // for (int c = 0; c < nb_clusters; c++) // { // std::cout << "cluster " << c << " has " << cluster_components[c].size() << " components\n"; @@ -513,7 +601,6 @@ PolygonMesh acvd_simplification( std::cout << "nb_disconnected: " << nb_disconnected << "\n"; } while (nb_disconnected > 0); - // TODO: Move out the disconnected clustering check (& cleaning) VertexColorMap vcm = get(CGAL::dynamic_vertex_property_t(), pmesh); @@ -545,7 +632,7 @@ PolygonMesh acvd_simplification( CGAL::IO::write_OFF(name, pmesh, CGAL::parameters::vertex_color_map(vcm)); std::cout << "kak2" << std::endl; - /// Construct new Mesh + /// Construct new Mesh std::vector valid_cluster_map(nb_clusters, -1); std::vector points; Point_set_3 point_set; @@ -564,7 +651,7 @@ PolygonMesh acvd_simplification( point_set.insert(center_p); } } - + // extract boundary cycles std::vector border_hedges; extract_boundary_cycles(pmesh, std::back_inserter(border_hedges)); From 6f07f4b0c7c90fa71c88b3a373d45e29f302cac9 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 31 Oct 2023 10:13:51 +0300 Subject: [PATCH 26/56] qem init --- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 137 +++++++++++++----- 1 file changed, 102 insertions(+), 35 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 7b070f7e66f..1d5ea0b222d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -40,6 +40,7 @@ #include #define CGAL_CLUSTERS_TO_VERTICES_THRESHOLD 0.1 +#define CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD 10000 namespace CGAL { @@ -48,12 +49,53 @@ namespace Polygon_mesh_processing { namespace internal { template -struct ClusterData { +struct IsotropicClusterData { typename GT::Vector_3 site_sum; typename GT::FT weight_sum; typename GT::FT energy; - ClusterData() : site_sum(0, 0, 0), weight_sum(0), energy(0) {} + IsotropicClusterData() : site_sum(0, 0, 0), weight_sum(0), energy(0) {} + + void add_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight) + { + this->site_sum += vertex_position * weight; + this->weight_sum += weight; + } + + void remove_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight) + { + this->site_sum -= vertex_position * weight; + this->weight_sum -= weight; + } + + typename GT::FT compute_energy() + { + this->energy = - (this->site_sum).squared_length() / this->weight_sum; + return this->energy; + } + + typename GT::Vector_3 compute_centroid() + { + if (this->weight_sum > 0) + return (this->site_sum) / this->weight_sum; + else + return typename GT::Vector_3 (-1, -1, -1); // TODO: Change this + } +}; + +template +struct QEMClusterData { + typename GT::Vector_3 site_sum; + typename GT::Vector_3 q_centroid; + typename GT::FT weight_sum; + typename GT::FT energy; + typename GT::FT qem[9]; + char rank_deficiency; + + QEMClusterData() : site_sum(0, 0, 0), weight_sum(0), energy(0), rank_deficiency(0), q_centroid(0, 0, 0) { + for (int i = 0; i < 9; i++) + qem[i] = 0; + } void add_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight) { @@ -166,43 +208,42 @@ void upsample_subdivision_property(PolygonMesh& pmesh, const NamedParameters& np // do the following while nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD // That is, because the subdivision steps heuristic is not 100% guaranteed to produce // the desired number of vertices. -template -void acvd_subdivide_if_needed( - PolygonMesh& pmesh, - const int nb_clusters, - const NamedParameters& np = parameters::default_values() -) -{ - int nb_vertices = num_vertices(pmesh); - if (nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) - return; +// template +// void acvd_subdivide_if_needed( +// PolygonMesh& pmesh, +// const int nb_clusters, +// const NamedParameters& np = parameters::default_values() +// ) +// { +// int nb_vertices = num_vertices(pmesh); +// if (nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) +// return; - while (nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) - { - double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); - int subdivide_steps = max((int)ceil(log(curr_factor) / log(4)), 0); +// while (nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) +// { +// double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); +// int subdivide_steps = max((int)ceil(log(curr_factor) / log(4)), 0); - std::cout << "subdivide_steps: " << subdivide_steps << std::endl; +// std::cout << "subdivide_steps: " << subdivide_steps << std::endl; - if (subdivide_steps > 0) - { - upsample_subdivision_property( - pmesh, - np.number_of_iterations(subdivide_steps) - ); - } - } - return; -} +// if (subdivide_steps > 0) +// { +// upsample_subdivision_property( +// pmesh, +// np.number_of_iterations(subdivide_steps) +// ); +// } +// } +// return; +// } // provide a property map for principal curvatures as a named parameter for adaptive clustering // provide a gradation factor as a named parameter for adaptive clustering - template -PolygonMesh acvd_simplification( +PolygonMesh acvd_isotropic( PolygonMesh& pmesh, const int nb_clusters, const NamedParameters& np = parameters::default_values() @@ -270,7 +311,6 @@ PolygonMesh acvd_simplification( CGAL::parameters::number_of_iterations(subdivide_steps) ); else // adaptive clustering - // TODO: need to interpolate curvature values upsample_subdivision_property( pmesh, CGAL::parameters::number_of_iterations(subdivide_steps).vertex_principal_curvatures_and_directions_map(vpcd_map) @@ -285,7 +325,7 @@ PolygonMesh acvd_simplification( VertexClusterMap vertex_cluster_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); VertexVisitedMap vertex_visited_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); VertexWeightMap vertex_weight_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); - std::vector> clusters(nb_clusters); + std::vector> clusters(nb_clusters); std::queue clusters_edges_active; std::queue clusters_edges_new; @@ -298,6 +338,7 @@ PolygonMesh acvd_simplification( } // compute vertex weights (dual area) + typename GT::FT weight_avg = 0; for (Face_descriptor fd : faces(pmesh)) { typename GT::FT weight = abs(CGAL::Polygon_mesh_processing::face_area(fd, pmesh)) / 3; @@ -312,12 +353,23 @@ PolygonMesh acvd_simplification( typename GT::FT k1 = get(vpcd_map, vd).min_curvature; typename GT::FT k2 = get(vpcd_map, vd).max_curvature; typename GT::FT k_sq = (k1 * k1 + k2 * k2); - vertex_weight += weight * (/*`eps * avg_curvature` instead of */ 1 + pow(k_sq, gradation_factor / 2.0)); // /2.0 because k_sq is squared + vertex_weight += weight * (/*`eps * avg_curvature` instead of 1 +*/ pow(k_sq, gradation_factor / 2.0)); // /2.0 because k_sq is squared } + weight_avg += vertex_weight; put(vertex_weight_pmap, vd, vertex_weight); } } - // TODO: clamp the weights up and below by a ratio (like 10,000) * avg_weights + weight_avg /= nb_vertices; + + // clamp the weights up and below by a ratio (like 10,000) * avg_weights + for (Vertex_descriptor vd : vertices(pmesh)) + { + typename GT::FT vertex_weight = get(vertex_weight_pmap, vd); + if (vertex_weight > CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg) + put(vertex_weight_pmap, vd, CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg); + else if (vertex_weight < 1.0 / CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg) + put(vertex_weight_pmap, vd, 1.0 / CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg); + } for (int ci = 0; ci < nb_clusters; ci++) { @@ -800,7 +852,22 @@ PolygonMesh acvd_isotropic_simplification( const NamedParameters& np = parameters::default_values() ) { - return internal::acvd_simplification( + return internal::acvd_isotropic( + pmesh, + nb_vertices, + np + ); +} + +template +PolygonMesh acvd_q acve_qem_simplification( + PolygonMesh& pmesh, + const int& nb_vertices, + const NamedParameters& np = parameters::default_values() + ) +{ + return internal::acve_qem( pmesh, nb_vertices, np From 99d8741367a2a5041eb9ad71eb1fa80b7180e204 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 31 Oct 2023 14:34:11 +0300 Subject: [PATCH 27/56] commenting out incomplete part --- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 1d5ea0b222d..ce53e2b4006 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -11,10 +11,10 @@ // Author(s) : Hossam Saeed // -// #ifndef CGAL_POLYGON_MESH_PROCESSING_<> -// #define CGAL_POLYGON_MESH_PROCESSING_<> - -// #include > +/// TODO: +// #ifndef CGAL_<> +// #define CGAL_<> +// #include > #include #include @@ -859,20 +859,20 @@ PolygonMesh acvd_isotropic_simplification( ); } -template -PolygonMesh acvd_q acve_qem_simplification( - PolygonMesh& pmesh, - const int& nb_vertices, - const NamedParameters& np = parameters::default_values() - ) -{ - return internal::acve_qem( - pmesh, - nb_vertices, - np - ); -} +// template +// PolygonMesh acvd_qem_simplification( +// PolygonMesh& pmesh, +// const int& nb_vertices, +// const NamedParameters& np = parameters::default_values() +// ) +// { +// return internal::acve_qem( +// pmesh, +// nb_vertices, +// np +// ); +// } } // namespace Polygon_mesh_processing From 613ca2e6ddcdc6ba9e5404bc2ff6937304333359 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 7 Nov 2023 04:56:51 +0300 Subject: [PATCH 28/56] adding qem file from another gsoc proj --- .../metrics.h | 272 ++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 Variational_shape_reconstruction/include/CGAL/Variational_shape_reconstruction/metrics.h diff --git a/Variational_shape_reconstruction/include/CGAL/Variational_shape_reconstruction/metrics.h b/Variational_shape_reconstruction/include/CGAL/Variational_shape_reconstruction/metrics.h new file mode 100644 index 00000000000..34a772f474a --- /dev/null +++ b/Variational_shape_reconstruction/include/CGAL/Variational_shape_reconstruction/metrics.h @@ -0,0 +1,272 @@ +#ifndef QEM_METRIC_H +#define QEM_METRIC_H + +#include +#include +#include +#include "types.h" + +typedef CGAL::Aff_transformation_3 Aff_transformation; +//template +class QEM_metric +{ +public: + typedef Eigen::Matrix EVec10d; + typedef Eigen::VectorXd VectorXd; + typedef Eigen::MatrixXd MatrixXd; + typedef Eigen::Matrix Matrix3d; + typedef Eigen::Matrix Matrix4d; + +private: // members + + EVec10d m_tensor; + + /* + n n^T | -nnq^T + -nnq | (nq)^2 + + aa ab ac ad + ab bb bc bd + ac bc cc cd + ad bd cd dd + + m_tensor[0]: aa, m_tensor[1]: ab, m_tensor[2]: ac, m_tensor[3]: ad + m_tensor[4]: bb, m_tensor[5]: bc, m_tensor[6]: bd, m_tensor[7]: cc + m_tensor[8]: cd, m_tensor[9]: dd + + m_tensor[0] m_tensor[1] m_tensor[2] m_tensor[3] + m_tensor[1] m_tensor[4] m_tensor[5] m_tensor[6] + m_tensor[2] m_tensor[5] m_tensor[7] m_tensor[8] + m_tensor[3] m_tensor[6] m_tensor[8] m_tensor[9] + */ + +public: // functions + + QEM_metric(): m_tensor(EVec10d::Zero()) {} + + EVec10d& tensors() { return m_tensor; } + const EVec10d& tensors() const { return m_tensor; } + + /*MatrixXd 4x4_matrix() + { + MatrixXd qem(4, 4); + + qem << m_tensor[0], m_tensor[1], m_tensor[2], m_tensor[3], + m_tensor[1], m_tensor[4], m_tensor[5], m_tensor[6], + m_tensor[2], m_tensor[5], m_tensor[7], m_tensor[8], + m_tensor[3], m_tensor[6], m_tensor[8], m_tensor[9]; + + return qem; + }*/ + + Matrix4d get_4x4_svd_matrix() + { + MatrixXd qem(4, 4); + + qem << m_tensor[0], m_tensor[1], m_tensor[2], m_tensor[3], + m_tensor[1], m_tensor[4], m_tensor[5], m_tensor[6], + m_tensor[2], m_tensor[5], m_tensor[7], m_tensor[8], + 0. , 0. , 0. , 1.; + + return qem; + } + + Matrix4d get_4x4_matrix() + { + MatrixXd qem(4, 4); + + qem << m_tensor[0], m_tensor[1], m_tensor[2], m_tensor[3], + m_tensor[1], m_tensor[4], m_tensor[5], m_tensor[6], + m_tensor[2], m_tensor[5], m_tensor[7], m_tensor[8], + m_tensor[3], m_tensor[6], m_tensor[8], m_tensor[9]; + + return qem; + } + + Matrix3d get_3x3_matrix() + { + MatrixXd qem(3, 3); + + qem << m_tensor[0], m_tensor[1], m_tensor[2], + m_tensor[1], m_tensor[4], m_tensor[5], + m_tensor[2], m_tensor[5], m_tensor[7]; + + return qem; + } + /// @brief Compute the tensor using a point and its normal and weighted by the area + /// @param area + /// @param query + /// @param normal + void init_qem_metrics_face(const double& area, const Point& query, const Vector& normal) + { + double a = normal.x(); + double b = normal.y(); + double c = normal.z(); + double d = -1. * CGAL::scalar_product(normal, (query - CGAL::ORIGIN)); + + m_tensor[0] = a * a; + m_tensor[1] = a * b; + m_tensor[2] = a * c; + m_tensor[3] = a * d; + m_tensor[4] = b * b; + m_tensor[5] = b * c; + m_tensor[6] = b * d; + m_tensor[7] = c * c; + m_tensor[8] = c * d; + m_tensor[9] = d * d; + + m_tensor = m_tensor * area; + } + /// @brief Compute the tensor (probabilistic qem) using a point and its normal and weighted by the area + /// @param area + /// @param mean_query + /// @param std_query + /// @param mean_normal + /// @param std_normal + void init_proba_qem_metrics_face(const double area, + const Point& mean_query, + const double std_query, + const Vector& mean_normal, + const double std_normal) + { + double sn2 = std_normal * std_normal; + double sq2 = std_query * std_query; + double dot_nq = CGAL::scalar_product(mean_normal, (mean_query - CGAL::ORIGIN)); + + m_tensor[0] = mean_normal.x() * mean_normal.x() + sn2; + m_tensor[1] = mean_normal.x() * mean_normal.y(); + m_tensor[2] = mean_normal.x() * mean_normal.z(); + m_tensor[4] = mean_normal.y() * mean_normal.y() + sn2; + m_tensor[5] = mean_normal.y() * mean_normal.z(); + m_tensor[7] = mean_normal.z() * mean_normal.z() + sn2; + + Vector vec_nnq = mean_normal * dot_nq + (mean_query - CGAL::ORIGIN) * sn2; + m_tensor[3] = -vec_nnq.x(); + m_tensor[6] = -vec_nnq.y(); + m_tensor[8] = -vec_nnq.z(); + + m_tensor[9] = dot_nq * dot_nq + sn2 * CGAL::scalar_product(mean_query - CGAL::ORIGIN, mean_query - CGAL::ORIGIN) + + sq2 * CGAL::scalar_product(mean_normal, mean_normal) + 3 * sq2 * sn2; + + m_tensor = m_tensor * area; + } + /// @brief Compute the qem tensor for a query point and a list of areas and normals + /// @param query + /// @param areas + /// @param normals + void init_qem_metrics_vertex(Point& query, + const std::vector& areas, + const std::vector& normals) + { + assert(areas.size() == normals.size()); + + for(int i = 0; i < areas.size(); i++) + { + Vector normal = normals[i]; + double area = areas[i]; + + double a = normal.x(); + double b = normal.y(); + double c = normal.z(); + double d = -1. * CGAL::scalar_product(normal, (query - CGAL::ORIGIN)); + + m_tensor[0] += area * a * a; + m_tensor[1] += area * a * b; + m_tensor[2] += area * a * c; + m_tensor[3] += area * a * d; + m_tensor[4] += area * b * b; + m_tensor[5] += area * b * c; + m_tensor[6] += area * b * d; + m_tensor[7] += area * c * c; + m_tensor[8] += area * c * d; + m_tensor[9] += area * d * d; + } + } + /// @brief Compute the affine transformation on a sphere to display the qem error as en ellipsoid + /// @return the affine transformation + Aff_transformation aff_transform_sphere() + { + + MatrixXd A(3, 3); + A << m_tensor[0], m_tensor[1], m_tensor[2], + m_tensor[1], m_tensor[4], m_tensor[5], + m_tensor[2], m_tensor[5], m_tensor[7]; + + Eigen::EigenSolver es(A); + // eigenvalues + VectorXd D = es.eigenvalues().real(); + D = D.cwiseMax(1e-5).cwiseInverse(); // take inverse + MatrixXd D_inv = MatrixXd::Zero(3, 3); + D_inv.diagonal() = D / D.sum(); // normalization + // eigenvectors + MatrixXd V = es.eigenvectors().real(); + // new matrix + MatrixXd new_trans = V * D_inv * V.transpose(); + Aff_transformation aff( new_trans(0, 0), new_trans(0, 1), new_trans(0, 2), + new_trans(1, 0), new_trans(1, 1), new_trans(1, 2), + new_trans(2, 0), new_trans(2, 1), new_trans(2, 2)); + + return aff; + } + + QEM_metric& operator+(const QEM_metric& other) + { + this->tensors() = this->tensors() + other.tensors(); + return *this; + } + + QEM_metric& operator-(const QEM_metric& other) + { + this->tensors() = this->tensors() - other.tensors(); + return *this; + } + + QEM_metric& operator*(const double& scale) + { + this->tensors() = this->tensors() * scale; + return *this; + } + /// @brief Compute optimal point using either SVD or the direct inverse + /// @param cluster_qem + /// @param cluster_pole + /// @return the optimal point + Point compute_optimal_point(QEM_metric& cluster_qem, Point& cluster_pole) + { + // solve Qx = b + Eigen::MatrixXd qem_mat = cluster_qem.get_4x4_svd_matrix(); + Eigen::VectorXd qem_vec = qem_mat.row(3); // 0., 0., 0., 1. + Eigen::VectorXd optim(4); + + // check rank + Eigen::FullPivLU lu_decomp(qem_mat); + lu_decomp.setThreshold(1e-5); + + // full rank -> direct inverse + if(lu_decomp.isInvertible()) + { + optim = lu_decomp.inverse() * qem_vec; + } + else + { // low rank -> svd pseudo-inverse + Eigen::JacobiSVD svd_decomp(qem_mat, Eigen::ComputeThinU | Eigen::ComputeThinV); + svd_decomp.setThreshold(1e-5); + + optim(0) = cluster_pole.x(); + optim(1) = cluster_pole.y(); + optim(2) = cluster_pole.z(); + optim(3) = 1.; + + optim = optim + svd_decomp.solve(qem_vec - qem_mat * optim); + } + + Point optim_point(optim(0), optim(1), optim(2)); + + return optim_point; + } + +}; + + + + +#endif \ No newline at end of file From c4d02d1996e1a34290e54875898fd86ffbabd7d0 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 7 Nov 2023 05:20:52 +0300 Subject: [PATCH 29/56] update example file --- .../Polygon_mesh_processing/acvd_example.cpp | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp index 32fb17342e4..00c8d1e8131 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -24,10 +24,7 @@ int main(int argc, char* argv[]) CGAL::data_file_path(argv[1]) : CGAL::data_file_path("meshes/cactus.off"); - const int nb_clusters = (argc > 2) ? atoi(argv[2]) : 20; - - const double gradation_factor = (argc > 3) ? atof(argv[3]) : 0; - + const int nb_clusters = (argc > 2) ? atoi(argv[2]) : 100; if (!CGAL::IO::read_polygon_mesh(filename, smesh)) { @@ -35,6 +32,13 @@ int main(int argc, char* argv[]) return EXIT_FAILURE; } + /// Uniform Isotropic ACVD Remeshing + + auto acvd_mesh = PMP::acvd_isotropic_simplification(smesh, nb_clusters); + CGAL::IO::write_OFF("acvd_mesh.off", acvd_mesh); + + /// Adaptive Isotropic ACVD Remeshing + bool created = false; Surface_Mesh::Property_map> principal_curvatures_and_directions_map; @@ -48,17 +52,18 @@ int main(int argc, char* argv[]) PMP::interpolated_corrected_principal_curvatures_and_directions(smesh, principal_curvatures_and_directions_map); - PMP::acvd_isotropic_simplification( - smesh, - nb_clusters, - CGAL::parameters::vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) - .gradation_factor(gradation_factor) - ); - // std::cout << "kak3" << std::endl; + const double gradation_factor = (argc > 3) ? atof(argv[3]) : 0; + + auto adaptive_acvd_mesh = + PMP::acvd_isotropic_simplification( + smesh, + nb_clusters, + CGAL::parameters::vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) + .gradation_factor(gradation_factor) + ); + + CGAL::IO::write_OFF("acvd_mesh_adaptive.off", adaptive_acvd_mesh); + return 0; - - // Output the simplified mesh, use write_OFF() - //CGAL::IO::write_OFF("sphere966_clustered_0.off", smesh, CGAL::parameters::stream_precision(17).vertex_color_map(vcm)); - } From 54a1d2d76653ad40f3a3d7f6f66143ce178f16e1 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 7 Nov 2023 05:21:09 +0300 Subject: [PATCH 30/56] qem metric moved --- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 52 ++++++------------ .../acvd/qem_metrics.h | 0 .../CGAL/Polygon_mesh_processing/acvd/types.h | 54 +++++++++++++++++++ 3 files changed, 70 insertions(+), 36 deletions(-) rename Variational_shape_reconstruction/include/CGAL/Variational_shape_reconstruction/metrics.h => Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/qem_metrics.h (100%) create mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/types.h diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index ce53e2b4006..95c15836074 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -18,21 +18,26 @@ #include #include -#include -#include #include #include #include -#include + +#include +#include #include #include #include + #include +#include + #include #include #include +#include + #include #include #include @@ -238,11 +243,10 @@ void upsample_subdivision_property(PolygonMesh& pmesh, const NamedParameters& np // return; // } - // provide a property map for principal curvatures as a named parameter for adaptive clustering // provide a gradation factor as a named parameter for adaptive clustering -template +template PolygonMesh acvd_isotropic( PolygonMesh& pmesh, const int nb_clusters, @@ -542,25 +546,11 @@ PolygonMesh acvd_isotropic( clusters_edges_active.swap(clusters_edges_new); } while (nb_modifications > 0 && (iteration < 50 || nb_modifications > 0.001 * nb_vertices)); - // to test the following: make all clusters belonging to c2 to belong to c1 instead - - //for (Vertex_descriptor vd : vertices(pmesh)) - //{ - // int c = get(vertex_cluster_pmap, vd); - // if (c == 2) { - // put(vertex_cluster_pmap, vd, 1); - // typename GT::Point_3 vp = get(vpm, vd); - // typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); - // clusters[1].add_vertex(vpv, get(vertex_weight_pmap, vd)); - // clusters[2].remove_vertex(vpv, get(vertex_weight_pmap, vd)); - // } - //} - // clean clusters here - // the goal is to delete clusters with multiple connected components - // for each cluster, do a BFS from a vertex in the cluster - // we need to keep the largest connected component for each cluster - // and set the other connected components to -1 (empty cluster), would also need to update clusters_edges_new + // the goal is to delete clusters with multiple connected components + // for each cluster, do a BFS from a vertex in the cluster + // we need to keep the largest connected component for each cluster + // and set the other connected components to -1 (empty cluster), would also need to update clusters_edges_new std::vector>> cluster_components(nb_clusters, std::vector>()); @@ -600,14 +590,6 @@ PolygonMesh acvd_isotropic( } } } - // for (int c = 0; c < nb_clusters; c++) - // { - // std::cout << "cluster " << c << " has " << cluster_components[c].size() << " components\n"; - // std::cout << "sizes: "; - // for (int i = 0; i < cluster_components[c].size(); i++) - // std::cout << cluster_components[c][i].size() << " / " << num_vertices(pmesh) << " "; - // std::cout << "\n"; - // } // loop over clusters for (int c = 0; c < nb_clusters; c++) @@ -664,7 +646,6 @@ PolygonMesh acvd_isotropic( int c = get(vertex_cluster_pmap, vd); cluster_frequency[c]++; CGAL::IO::Color color(255 - (c * 255 / nb_clusters), (c * c % 7) * 255 / 7, (c * c * c % 31) * 255 / 31); - //std::cout << vd.idx() << " " << c << " " << color << std::endl; put(vcm, vd, color); } @@ -801,7 +782,6 @@ PolygonMesh acvd_isotropic( std::cout << "kak3" << std::endl; return simplified_mesh; - } @@ -852,11 +832,11 @@ PolygonMesh acvd_isotropic_simplification( const NamedParameters& np = parameters::default_values() ) { - return internal::acvd_isotropic( + return internal::acvd_isotropic( pmesh, nb_vertices, np - ); + ); } // template +#include + +#include +#include +#include + +typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; + + +typedef Kernel::FT FT; +typedef Kernel::Point_3 Point; +typedef Kernel::Vector_3 Vector; +typedef Kernel::Triangle_3 Triangle; +typedef Kernel::Plane_3 Plane; + +typedef Kernel::Point_2 Point_2; +typedef Kernel::Segment_2 Segment_2; + +typedef CGAL::First_of_pair_property_map> Point_map; +typedef CGAL::Second_of_pair_property_map> Normal_map; +typedef CGAL::Point_set_3< Point, Vector > Pointset; + +// triangle fit +typedef CGAL::Polyhedron_3 Polyhedron; +typedef std::vector PointList; +typedef CGAL::Bbox_3 Bbox; + + +/// @brief + +typedef std::vector DataList; +typedef std::vector IntList; +typedef std::vector DoubleList; +typedef std::vector BoolList; +typedef std::vector VecList; + +typedef std::set IntSet; + +typedef std::pair IntPair; + + +#ifndef TYPE_H +#define TYPE_H + +namespace qem +{ + enum class INIT_QEM_GENERATOR {RANDOM,KMEANS_FARTHEST,KMEANS_PLUSPLUS}; + enum class VERBOSE_LEVEL {LOW,MEDIUM,HIGH}; +} + +#endif /* TYPE_H */ \ No newline at end of file From 7aa5b66450c286d56cad3f3376985f8804d1c635 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 7 Nov 2023 09:59:13 +0300 Subject: [PATCH 31/56] fixed isolated vertices + clean up --- .../Polygon_mesh_processing/acvd_example.cpp | 6 +- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 197 ++++++++---------- 2 files changed, 86 insertions(+), 117 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp index 00c8d1e8131..7ac4da56831 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -24,7 +24,7 @@ int main(int argc, char* argv[]) CGAL::data_file_path(argv[1]) : CGAL::data_file_path("meshes/cactus.off"); - const int nb_clusters = (argc > 2) ? atoi(argv[2]) : 100; + const int nb_clusters = (argc > 2) ? atoi(argv[2]) : 400; if (!CGAL::IO::read_polygon_mesh(filename, smesh)) { @@ -39,6 +39,8 @@ int main(int argc, char* argv[]) /// Adaptive Isotropic ACVD Remeshing + const double gradation_factor = (argc > 3) ? atof(argv[3]) : 1; + bool created = false; Surface_Mesh::Property_map> principal_curvatures_and_directions_map; @@ -52,8 +54,6 @@ int main(int argc, char* argv[]) PMP::interpolated_corrected_principal_curvatures_and_directions(smesh, principal_curvatures_and_directions_map); - const double gradation_factor = (argc > 3) ? atof(argv[3]) : 0; - auto adaptive_acvd_mesh = PMP::acvd_isotropic_simplification( smesh, diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 95c15836074..ff27413c5a1 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -58,19 +58,22 @@ struct IsotropicClusterData { typename GT::Vector_3 site_sum; typename GT::FT weight_sum; typename GT::FT energy; + size_t nb_vertices; - IsotropicClusterData() : site_sum(0, 0, 0), weight_sum(0), energy(0) {} + IsotropicClusterData() : site_sum(0, 0, 0), weight_sum(0), energy(0), nb_vertices(0) {} void add_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight) { this->site_sum += vertex_position * weight; this->weight_sum += weight; + this->nb_vertices++; } void remove_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight) { this->site_sum -= vertex_position * weight; this->weight_sum -= weight; + this->nb_vertices--; } typename GT::FT compute_energy() @@ -204,45 +207,6 @@ void upsample_subdivision_property(PolygonMesh& pmesh, const NamedParameters& np } } - - -// To provide the functionality remeshing (not just simplification), we might need to -// subdivide the mesh before clustering -// in either case, nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD - -// do the following while nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD -// That is, because the subdivision steps heuristic is not 100% guaranteed to produce -// the desired number of vertices. -// template -// void acvd_subdivide_if_needed( -// PolygonMesh& pmesh, -// const int nb_clusters, -// const NamedParameters& np = parameters::default_values() -// ) -// { -// int nb_vertices = num_vertices(pmesh); -// if (nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) -// return; - -// while (nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) -// { -// double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); -// int subdivide_steps = max((int)ceil(log(curr_factor) / log(4)), 0); - -// std::cout << "subdivide_steps: " << subdivide_steps << std::endl; - -// if (subdivide_steps > 0) -// { -// upsample_subdivision_property( -// pmesh, -// np.number_of_iterations(subdivide_steps) -// ); -// } -// } -// return; -// } - // provide a property map for principal curvatures as a named parameter for adaptive clustering // provide a gradation factor as a named parameter for adaptive clustering template 0) { @@ -393,33 +357,12 @@ PolygonMesh acvd_isotropic( clusters_edges_active.push(hd); } - // frequency of each cluster - std::vector cluster_frequency (nb_clusters, 0); - - for (Vertex_descriptor vd : vertices(pmesh)) - { - int c = get(vertex_cluster_pmap, vd); - if (c != -1) cluster_frequency[c]++; - } - - int nb_empty = 0; - for (int i = 0; i < nb_clusters; i++) - { - if (cluster_frequency[i] == 0) - { - nb_empty++; - } - } - - std::cout << "nb_empty before: " << nb_empty << std::endl; - int nb_modifications = 0; int nb_disconnected = 0; do { nb_disconnected = 0; - int iteration = 0; do { nb_modifications = 0; @@ -483,8 +426,6 @@ PolygonMesh acvd_isotropic( typename GT::FT e_v1_to_c2 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); - typename GT::FT c1_weight_threshold = clusters[c1].weight_sum; - // reset to no change clusters[c1].add_vertex(vpv1, v1_weight); clusters[c2].remove_vertex(vpv1, v1_weight); @@ -495,10 +436,7 @@ PolygonMesh acvd_isotropic( typename GT::FT e_v2_to_c1 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); - typename GT::FT c2_weight_threshold = clusters[c2].weight_sum; - - - if (e_v2_to_c1 < e_no_change && e_v2_to_c1 < e_v1_to_c2 && c2_weight_threshold > 0) + if (e_v2_to_c1 < e_no_change && e_v2_to_c1 < e_v1_to_c2 && clusters[c2].nb_vertices > 0) // > 0 as 1 vertex was removed from c2 { // move v2 to c1 put(vertex_cluster_pmap, v2, c1); @@ -511,7 +449,7 @@ PolygonMesh acvd_isotropic( clusters_edges_new.push(hd); nb_modifications++; } - else if (e_v1_to_c2 < e_no_change && c1_weight_threshold > 0) + else if (e_v1_to_c2 < e_no_change && clusters[c1].nb_vertices > 2) // > 2 as 1 vertex was added to c1 { // move v1 to c2 put(vertex_cluster_pmap, v1, c2); @@ -539,12 +477,10 @@ PolygonMesh acvd_isotropic( } } } - - iteration++; - std::cout << "iteration: " << iteration << ", # Modifications: " << nb_modifications << "\n"; + // std::cout << "# Modifications: " << nb_modifications << "\n"; clusters_edges_active.swap(clusters_edges_new); - } while (nb_modifications > 0 && (iteration < 50 || nb_modifications > 0.001 * nb_vertices)); + } while (nb_modifications > 0); // clean clusters here // the goal is to delete clusters with multiple connected components @@ -554,10 +490,9 @@ PolygonMesh acvd_isotropic( std::vector>> cluster_components(nb_clusters, std::vector>()); - std::queue q; + std::queue vertex_queue; // loop over vertices - int cou = 0; for (Vertex_descriptor vd : vertices(pmesh)) { if (get(vertex_visited_pmap, vd)) continue; @@ -569,12 +504,12 @@ PolygonMesh acvd_isotropic( int component_i = cluster_components[c].size() - 1; // visited_clusters[c] = true; - q.push(vd); + vertex_queue.push(vd); put(vertex_visited_pmap, vd, true); - while (!q.empty()) + while (!vertex_queue.empty()) { - Vertex_descriptor v = q.front(); - q.pop(); + Vertex_descriptor v = vertex_queue.front(); + vertex_queue.pop(); cluster_components[c][component_i].push_back(v); for (Halfedge_descriptor hd : halfedges_around_source(v, pmesh)) @@ -583,7 +518,7 @@ PolygonMesh acvd_isotropic( int c2 = get(vertex_cluster_pmap, v2); if (c2 == c && !get(vertex_visited_pmap, v2)) { - q.push(v2); + vertex_queue.push(v2); put(vertex_visited_pmap, v2, true); } } @@ -632,38 +567,24 @@ PolygonMesh acvd_isotropic( } } - std::cout << "nb_disconnected: " << nb_disconnected << "\n"; } while (nb_disconnected > 0); VertexColorMap vcm = get(CGAL::dynamic_vertex_property_t(), pmesh); - // frequency of each cluster - cluster_frequency = std::vector(nb_clusters, 0); + // // frequency of each cluster + // cluster_frequency = std::vector(nb_clusters, 0); - for (Vertex_descriptor vd : vertices(pmesh)) - { - int c = get(vertex_cluster_pmap, vd); - cluster_frequency[c]++; - CGAL::IO::Color color(255 - (c * 255 / nb_clusters), (c * c % 7) * 255 / 7, (c * c * c % 31) * 255 / 31); - put(vcm, vd, color); - } + // for (Vertex_descriptor vd : vertices(pmesh)) + // { + // int c = get(vertex_cluster_pmap, vd); + // cluster_frequency[c]++; + // CGAL::IO::Color color(255 - (c * 255 / nb_clusters), (c * c % 7) * 255 / 7, (c * c * c % 31) * 255 / 31); + // put(vcm, vd, color); + // } - nb_empty = 0; - for (int i = 0; i < nb_clusters; i++) - { - if (cluster_frequency[i] == 0) - { - nb_empty++; - } - } - - std::cout << "nb_empty: " << nb_empty << std::endl; - - std::cout << "kak1" << std::endl; - std::string name = std::to_string(nb_clusters) + ".off"; - CGAL::IO::write_OFF(name, pmesh, CGAL::parameters::vertex_color_map(vcm)); - std::cout << "kak2" << std::endl; + // std::string name = std::to_string(nb_clusters) + ".off"; + // CGAL::IO::write_OFF(name, pmesh, CGAL::parameters::vertex_color_map(vcm)); /// Construct new Mesh std::vector valid_cluster_map(nb_clusters, -1); @@ -746,9 +667,6 @@ PolygonMesh acvd_isotropic( polygons[polygons.size() - 1][2] = cb_first; } - name = std::to_string(nb_clusters) + "_points.off"; - CGAL::IO::write_point_set(name, point_set); - for (Face_descriptor fd : faces(pmesh)) { Halfedge_descriptor hd1 = halfedge(fd, pmesh); @@ -773,14 +691,9 @@ PolygonMesh acvd_isotropic( } } - std::cout << "are polygons a valid mesh ? : " << is_polygon_soup_a_polygon_mesh(polygons) << std::endl; orient_polygon_soup(points, polygons); polygon_soup_to_polygon_mesh(points, polygons, simplified_mesh); - name = std::to_string(nb_clusters) + "_simped.off"; - CGAL::IO::write_OFF(name, simplified_mesh); - std::cout << "kak3" << std::endl; - return simplified_mesh; } @@ -826,7 +739,7 @@ PolygonMesh acvd_isotropic( template -PolygonMesh acvd_isotropic_simplification( +PolygonMesh acvd_isotropic_simplification_polygon_soup( PolygonMesh& pmesh, const int& nb_vertices, const NamedParameters& np = parameters::default_values() @@ -839,6 +752,62 @@ PolygonMesh acvd_isotropic_simplification( ); } +/** +* \ingroup ACVD_grp +* +* Performs uniform (isotropic) centroidal voronoi diagram simplification on a polygon mesh. +* This can also be used for remeshing by setting the number of clusters to the desired number of vertices. +* The number of clusters is the number of vertices in the output mesh. +* +* @tparam PolygonMesh a model of `FaceListGraph` +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". +* +* @param pmesh input polygon mesh +* @param nb_vertices number of target vertices in the output mesh +* @param np optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below +* `GT` stands for the type of the object provided to the named parameter `geom_traits()`. +* +* \cgalNamedParamsBegin +* \cgalParamNBegin{vertex_point_map} +* \cgalParamDescription{a property map associating points to the vertices of `pmesh`.} +* \cgalParamType{a class model of `ReadablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` +* as key type and `GT::Point_3` as value type.} +* \cgalParamDefault{`boost::get(CGAL::vertex_point, pmesh)`.} +* \cgalParamExtra{If this parameter is omitted, an internal property map for +* `CGAL::vertex_point_t` must be available in `PolygonMesh`.} +* \cgalParamNEnd +* +* \cgalParamNBegin{geom_traits} +* \cgalParamDescription{an instance of a geometric traits class.} +* \cgalParamType{a class model of `Kernel`} +* \cgalParamDefault{a \cgal Kernel deduced from the point type, using `CGAL::Kernel_traits`.} +* \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.} +* \cgalParamNEnd +* +* \cgalNamedParamsEnd +* +*/ + +template +PolygonMesh acvd_isotropic_simplification( + PolygonMesh& pmesh, + const int& nb_vertices, + const NamedParameters& np = parameters::default_values() + ) +{ + auto ps = acvd_isotropic_simplification_polygon_soup( + pmesh, + nb_vertices, + np + ); + + //PolygonMesh simplified_mesh; + //polygon_soup_to_polygon_mesh(points, polygons, simplified_mesh); + return ps; +} + // template // PolygonMesh acvd_qem_simplification( From 3ea551c1b2ca5a049f3cd482bb37f3214d5cc9e1 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 7 Nov 2023 10:06:56 +0300 Subject: [PATCH 32/56] bibliography --- Documentation/doc/biblio/cgal_manual.bib | 42 ++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Documentation/doc/biblio/cgal_manual.bib b/Documentation/doc/biblio/cgal_manual.bib index ce72340b8f5..491f031e5dd 100644 --- a/Documentation/doc/biblio/cgal_manual.bib +++ b/Documentation/doc/biblio/cgal_manual.bib @@ -2358,6 +2358,48 @@ location = {Salt Lake City, Utah, USA} ,update = "98.01 kettner" } +@article{cgal:vc-acvdupmc-04, + author = {Valette, Sébastien and Chassery, Jean-Marc}, + doi = {10.1111/j.1467-8659.2004.00769.x}, + hal_id = {hal-00534535}, + hal_version = {v1}, + journal = {Computer Graphics Forum}, + note = {Eurographics 2004 proceedings}, + number = {3}, + pages = {381-389}, + pdf = {https://hal.archives-ouvertes.fr/hal-00534535/file/valette.pdf}, + publisher = {Wiley}, + title = {Approximated Centroidal Voronoi Diagrams for Uniform Polygonal Mesh Coarsening}, + url = {https://hal.archives-ouvertes.fr/hal-00534535}, + volume = {23}, + year = {2004} +} + +@inproceedings{cgal:vkc-apmsdcvd-05, + address = {Tozeur, Tunisia}, + author = {Valette, Sébastien and Kompatsiaris, I. and Chassery, J.M.}, + booktitle = {Proceedings of 2nd International Conference on Machine Intelligence ICMI 2005}, + month = {November}, + pages = {655-662}, + title = {Adaptive Polygonal Mesh Simplification With Discrete Centroidal Voronoi Diagrams}, + year = {2005} +} + +@article{cgal:vcp-grtmmdvd-08, + author = {Valette, Sébastien and Chassery, Jean-Marc and Prost, Rémy}, + doi = {10.1109/TVCG.2007.70430}, + hal_id = {hal-00537025}, + hal_version = {v1}, + journal = {IEEE Transactions on Visualization and Computer Graphics}, + number = {2}, + pages = {369--381}, + pdf = {https://hal.archives-ouvertes.fr/hal-00537025/file/VCP08TVCG.pdf}, + publisher = {Institute of Electrical and Electronics Engineers}, + title = {Generic remeshing of 3D triangular meshes with metric-dependent discrete Voronoi Diagrams}, + url = {https://hal.archives-ouvertes.fr/hal-00537025}, + volume = {14}, + year = {2008} +} @techreport{cgal:vla-lod-15, title={LOD Generation for Urban Scenes}, From 41b726e12c01b79ec7072d5b0aae94bf0f7e2ef9 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 7 Nov 2023 10:29:41 +0300 Subject: [PATCH 33/56] function returning a triangle soup --- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index ff27413c5a1..ff7e94f12c7 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -211,7 +211,10 @@ void upsample_subdivision_property(PolygonMesh& pmesh, const NamedParameters& np // provide a gradation factor as a named parameter for adaptive clustering template -PolygonMesh acvd_isotropic( +std::pair< + std::vector::type::Point_3>, + std::vector> +> acvd_isotropic( PolygonMesh& pmesh, const int nb_clusters, const NamedParameters& np = parameters::default_values() @@ -591,7 +594,7 @@ PolygonMesh acvd_isotropic( std::vector points; Point_set_3 point_set; - std::vector > polygons; + std::vector> polygons; PolygonMesh simplified_mesh; for (int i = 0; i < nb_clusters; i++) //should i =1 ? @@ -692,9 +695,8 @@ PolygonMesh acvd_isotropic( } orient_polygon_soup(points, polygons); - polygon_soup_to_polygon_mesh(points, polygons, simplified_mesh); - return simplified_mesh; + return std::make_pair(points, polygons); } @@ -739,7 +741,10 @@ PolygonMesh acvd_isotropic( template -PolygonMesh acvd_isotropic_simplification_polygon_soup( +std::pair< + std::vector::type::Point_3>, + std::vector> +> acvd_isotropic_simplification_polygon_soup( PolygonMesh& pmesh, const int& nb_vertices, const NamedParameters& np = parameters::default_values() @@ -803,9 +808,9 @@ PolygonMesh acvd_isotropic_simplification( np ); - //PolygonMesh simplified_mesh; - //polygon_soup_to_polygon_mesh(points, polygons, simplified_mesh); - return ps; + PolygonMesh simplified_mesh; + polygon_soup_to_polygon_mesh(ps.first, ps.second, simplified_mesh); + return simplified_mesh; } // template Date: Tue, 7 Nov 2023 11:06:50 +0300 Subject: [PATCH 34/56] reference documentation --- .../include/CGAL/license/gpl_package_list.txt | 1 + .../PackageDescription.txt | 10 +++- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 46 ++++++++++++++++--- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/Installation/include/CGAL/license/gpl_package_list.txt b/Installation/include/CGAL/license/gpl_package_list.txt index cd274cf329b..492ce5de149 100644 --- a/Installation/include/CGAL/license/gpl_package_list.txt +++ b/Installation/include/CGAL/license/gpl_package_list.txt @@ -60,6 +60,7 @@ Polygon_mesh_processing/geometric_repair Polygon Mesh Processing - Geometric Rep Polygon_mesh_processing/miscellaneous Polygon Mesh Processing - Miscellaneous Polygon_mesh_processing/detect_features Polygon Mesh Processing - Feature Detection Polygon_mesh_processing/collision_detection Polygon Mesh Processing - Collision Detection +Polygon_mesh_processing/remeshing Polygon Mesh Processing - ACVD Simplification and Remeshing Polyhedron 3D Polyhedral Surface Polyline_simplification_2 2D Polyline Simplification Polytope_distance_d Optimal Distances diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index b17c41399d0..caf2068db41 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -12,6 +12,10 @@ /// Functions to fill holes given as a range of halfedges or as range of points. /// \ingroup PkgPolygonMeshProcessingRef +/// \defgroup PMP_acvd_grp ACVD Simplification +/// Functions to simplify and remesh triangle meshes based on Approximated Centroidal Voronoi Diagrams (ACVD) with a variety of metrics. +/// \ingroup PkgPolygonMeshProcessingRef + /// \defgroup PMP_meshing_grp Meshing /// Functions to triangulate faces, and to refine and fair regions of a polygon mesh. /// \ingroup PkgPolygonMeshProcessingRef @@ -75,7 +79,7 @@ \cgalPkgPicture{hole_filling_ico.png} \cgalPkgSummaryBegin -\cgalPkgAuthors{Sébastien Loriot, Mael Rouxel-Labbé, Jane Tournois, and Ilker %O. Yaz} +\cgalPkgAuthors{Sébastien Loriot, Mael Rouxel-Labbé, Hossam Saeed, Jane Tournois, Sébastien Valette, and Ilker %O. Yaz} \cgalPkgDesc{This package provides a collection of methods and classes for polygon mesh processing, ranging from basic operations on simplices, to complex geometry processing algorithms such as Boolean operations, remeshing, repairing, collision and intersection detection, and more.} @@ -130,6 +134,10 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage. - `CGAL::Polygon_mesh_processing::smooth_shape()` - `CGAL::Polygon_mesh_processing::random_perturbation()` +\cgalCRPSection{ACVD Simplification Functions} +- \link PMP_acvd_grp `CGAL::Polygon_mesh_processing::acvd_isotropic_simplification()` \endlink +- \link PMP_acvd_grp `CGAL::Polygon_mesh_processing::acvd_isotropic_simplification_polygon_soup()` \endlink + \cgalCRPSection{Orientation Functions} - `CGAL::Polygon_mesh_processing::orient_polygon_soup()` - `CGAL::Polygon_mesh_processing::orient()` diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index ff7e94f12c7..fbd72c3ede2 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -158,10 +158,6 @@ void upsample_subdivision_property(PolygonMesh& pmesh, const NamedParameters& np // unordered_set of old vertices std::unordered_set old_vertices; -// // make a copy of the property map -// typename VPCDM vpcd_map_new = get(CGAL::dynamic_vertex_property_t>(), pmesh); - - bool curvatures_available = !is_default_parameter::value; unsigned int step = choose_parameter(get_parameter(np, internal_np::number_of_iterations), 1); @@ -703,7 +699,7 @@ std::pair< } // namespace internal /** -* \ingroup ACVD_grp +* \ingroup PMP_acvd_grp * * Performs uniform (isotropic) centroidal voronoi diagram simplification on a polygon mesh. * This can also be used for remeshing by setting the number of clusters to the desired number of vertices. @@ -718,6 +714,23 @@ std::pair< * `GT` stands for the type of the object provided to the named parameter `geom_traits()`. * * \cgalNamedParamsBegin +* +* \cgalParamNBegin{vertex_principal_curvatures_and_directions_map} +* \cgalParamDescription{a property map associating principal curvatures and directions to the vertices of `pmesh`, used for adaptive clustering.} +* \cgalParamType{a class model of `ReadWritePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` +* as key type and `Principal_curvatures_and_directions` as value type.} +* \cgalParamExtra{If this parameter is omitted, but `gradation_factor` is not (and is > 0), an internal property map +* will be created and curvature values will be computed.} +* \cgalParamNEnd +* +* \cgalParamNBegin{gradation_factor} +* \cgalParamDescription{a factor used to gradate the weights of the vertices based on their curvature values.} +* \cgalParamType{`GT::FT`} +* \cgalParamDefault{0} +* \cgalParamExtra{If this parameter is omitted, no adaptive clustering will be performed.} +* \cgalParamNEnd +* * \cgalParamNBegin{vertex_point_map} * \cgalParamDescription{a property map associating points to the vertices of `pmesh`.} * \cgalParamType{a class model of `ReadablePropertyMap` with @@ -737,6 +750,8 @@ std::pair< * * \cgalNamedParamsEnd * +* @pre only triangle meshes are supported for now +* @return a pair of vectors of points and polygons representing the simplified mesh as a polygon soup */ template ::%vertex_descriptor` +* as key type and `Principal_curvatures_and_directions` as value type.} +* \cgalParamExtra{If this parameter is omitted, but `gradation_factor` is not (and is > 0), an internal property map +* will be created and curvature values will be computed.} +* \cgalParamNEnd +* +* \cgalParamNBegin{gradation_factor} +* \cgalParamDescription{a factor used to gradate the weights of the vertices based on their curvature values.} +* \cgalParamType{`GT::FT`} +* \cgalParamDefault{0} +* \cgalParamExtra{If this parameter is omitted, no adaptive clustering will be performed.} +* \cgalParamNEnd +* * \cgalParamNBegin{vertex_point_map} * \cgalParamDescription{a property map associating points to the vertices of `pmesh`.} * \cgalParamType{a class model of `ReadablePropertyMap` with @@ -792,6 +824,8 @@ std::pair< * * \cgalNamedParamsEnd * +* @pre only triangle meshes are supported for now +* @return the simplified mesh as a PolygonMesh */ template Date: Tue, 7 Nov 2023 11:24:45 +0300 Subject: [PATCH 35/56] user doc (imp history, example file ...) --- Installation/include/CGAL/license/gpl_package_list.txt | 2 +- .../Polygon_mesh_processing/Polygon_mesh_processing.txt | 9 ++++++++- .../doc/Polygon_mesh_processing/examples.txt | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Installation/include/CGAL/license/gpl_package_list.txt b/Installation/include/CGAL/license/gpl_package_list.txt index 492ce5de149..a775d7c4f87 100644 --- a/Installation/include/CGAL/license/gpl_package_list.txt +++ b/Installation/include/CGAL/license/gpl_package_list.txt @@ -60,7 +60,7 @@ Polygon_mesh_processing/geometric_repair Polygon Mesh Processing - Geometric Rep Polygon_mesh_processing/miscellaneous Polygon Mesh Processing - Miscellaneous Polygon_mesh_processing/detect_features Polygon Mesh Processing - Feature Detection Polygon_mesh_processing/collision_detection Polygon Mesh Processing - Collision Detection -Polygon_mesh_processing/remeshing Polygon Mesh Processing - ACVD Simplification and Remeshing +Polygon_mesh_processing/remeshing Polygon Mesh Processing - ACVD Simplification Polyhedron 3D Polyhedral Surface Polyline_simplification_2 2D Polyline Simplification Polytope_distance_d Optimal Distances diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt index 00ccfd382f6..f4b1acbe0e9 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt @@ -4,7 +4,7 @@ namespace CGAL { \anchor Chapter_PolygonMeshProcessing \cgalAutoToc -\authors Sébastien Loriot, Mael Rouxel-Labbé, Jane Tournois, Ilker %O. Yaz +\authors Sébastien Loriot, Mael Rouxel-Labbé, Hossam Saeed, Jane Tournois, Sébastien Valette, Ilker %O. Yaz \image html neptun_head.jpg \image latex neptun_head.jpg @@ -52,6 +52,8 @@ mesh, which includes point location and self intersection tests. - \ref PMPSlicer : functor able to compute the intersections of a polygon mesh with arbitrary planes (slicer). - \ref PMPConnectedComponents : methods to deal with connected components of a polygon mesh (extraction, marks, removal, ...). +- \ref PMPacvd : methods to simplify or remesh a polygon mesh using approximated centroidal Voronoi diagrams + as described in \cgalCite{cgal:vcp-grtmmdvd-08} and preceeding work. \subsection PMPIO Reading and Writing Polygon Meshes @@ -1143,6 +1145,11 @@ available on 7th of October 2020. It only uses the high level algorithm of chec is covered by a set of prisms, where each prism is an offset for an input triangle. That is, the implementation in \cgal does not use indirect predicates. +The ACVD Simplification and Remeshing methods were implemented during GSoC 2023. This was implemented by Hossam Saeed and under +supervision of Sébastien Valette and Sébastien Loriot. The implementation is based on \cgalCite{cgal:vcp-grtmmdvd-08}. and +preceeding work. ACVD's implementation was also +used as a reference during the project. + */ } /* namespace CGAL */ diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index 146e1261ff4..41fbfe61936 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -43,4 +43,5 @@ \example Polygon_mesh_processing/cc_compatible_orientations.cpp \example Polygon_mesh_processing/remesh_planar_patches.cpp \example Polygon_mesh_processing/remesh_almost_planar_patches.cpp +\example Polygon_mesh_processing/acvd_example.cpp */ From 0ecab9b935c9f1a9af29c469ba9d85beaf2e91ed Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:42:03 +0300 Subject: [PATCH 36/56] WIP: user doc --- .../Polygon_mesh_processing.txt | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt index f4b1acbe0e9..b11c498b69f 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt @@ -52,7 +52,7 @@ mesh, which includes point location and self intersection tests. - \ref PMPSlicer : functor able to compute the intersections of a polygon mesh with arbitrary planes (slicer). - \ref PMPConnectedComponents : methods to deal with connected components of a polygon mesh (extraction, marks, removal, ...). -- \ref PMPacvd : methods to simplify or remesh a polygon mesh using approximated centroidal Voronoi diagrams +- \ref PMPACVD : methods to simplify or remesh a polygon mesh using approximated centroidal Voronoi diagrams as described in \cgalCite{cgal:vcp-grtmmdvd-08} and preceeding work. \subsection PMPIO Reading and Writing Polygon Meshes @@ -1022,6 +1022,31 @@ which enables to treat one or several connected components as a face graph. \cgalExample{Polygon_mesh_processing/face_filtered_graph_example.cpp} +\section PMPACVD ACVD Simplification and Remeshing + +The Approximated Centroidal Voronoi Diagram (ACVD) package is a set of vertex-clustering-based tools to simplify +and remesh a polygon mesh. It is based on the method introduced in \cgalCite{cgal:vc-acvdupmc-04} and extended +in \cgalCite{cgal:vkc-apmsdcvd-05} and \cgalCite{cgal:vcp-grtmmdvd-08}. + +\subsection ACVDBackground Brief Background + +WIP + +\subsection ACVDAPI API + +WIP + +\subsection ACVDResults Results + +WIP + +\subsection ACVDExample Usage Example + +The example below shows the usage of the `CGAL::Polygon_mesh_processing::acvd_isotropic_simplification()` function +and how the extra named parameters can be passed. + +\cgalExample{Polygon_mesh_processing/acvd_example.cpp} + \section PMPDistance Hausdorff Distance This package provides methods to compute (approximate) distances between meshes and point sets. @@ -1150,6 +1175,5 @@ supervision of Sébastien Valette and Sébastien Loriot. The implementation is b preceeding work. ACVD's implementation was also used as a reference during the project. - */ } /* namespace CGAL */ From 8ea673dec97f43e956059c14cf431ab91e56427b Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:34:52 +0300 Subject: [PATCH 37/56] linux build fixes --- .../examples/Polygon_mesh_processing/CMakeLists.txt | 2 +- .../include/CGAL/Polygon_mesh_processing/acvd/acvd.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index 114cea5422d..72ecf051ffe 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -51,12 +51,12 @@ create_single_source_cgal_program("match_faces.cpp") create_single_source_cgal_program("cc_compatible_orientations.cpp") create_single_source_cgal_program("hausdorff_distance_remeshing_example.cpp") create_single_source_cgal_program("hausdorff_bounded_error_distance_example.cpp") -create_single_source_cgal_program("acvd_example.cpp") find_package(Eigen3 3.2.0 QUIET) #(requires 3.2.0 or greater) include(CGAL_Eigen3_support) if(TARGET CGAL::Eigen3_support) + create_single_source_cgal_program("acvd_example.cpp") create_single_source_cgal_program("hole_filling_example.cpp") target_link_libraries(hole_filling_example PUBLIC CGAL::Eigen3_support) create_single_source_cgal_program("hole_filling_example_SM.cpp") diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index fbd72c3ede2..88a94979c31 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -151,7 +151,7 @@ void upsample_subdivision_property(PolygonMesh& pmesh, const NamedParameters& np get_property_map(CGAL::vertex_point, pmesh)); // get curvature related parameters - const typename VPCDM vpcd_map = + const VPCDM vpcd_map = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions_map), Default_principal_map()); @@ -178,8 +178,8 @@ void upsample_subdivision_property(PolygonMesh& pmesh, const NamedParameters& np Principal_curvatures_and_directions pcd; pcd.min_curvature = 0; pcd.max_curvature = 0; - pcd.min_direction = GT::Vector_3(0, 0, 0); - pcd.max_direction = GT::Vector_3(0, 0, 0); + pcd.min_direction = typename GT::Vector_3(0, 0, 0); + pcd.max_direction = typename GT::Vector_3(0, 0, 0); for (Halfedge_descriptor hd : halfedges_around_target(vd, pmesh)) { Vertex_descriptor v1 = source(hd, pmesh); @@ -240,7 +240,7 @@ std::pair< // get curvature related parameters const typename GT::FT gradation_factor = choose_parameter(get_parameter(np, internal_np::gradation_factor), 0); - const typename Vertex_principal_curvatures_and_directions_map vpcd_map = + const Vertex_principal_curvatures_and_directions_map vpcd_map = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions_map), Default_principal_map()); From 8d15e0cb8aac7336dc317c06cc7fd5f1ee66eb07 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 8 Nov 2023 17:30:23 +0300 Subject: [PATCH 38/56] wip: qem incomplete --- .../Polygon_mesh_processing/acvd_example.cpp | 15 +- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 657 +++++++++++++++++- 2 files changed, 635 insertions(+), 37 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp index 7ac4da56831..e0341b18940 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -22,7 +22,7 @@ int main(int argc, char* argv[]) Surface_Mesh smesh; const std::string filename = (argc > 1) ? CGAL::data_file_path(argv[1]) : - CGAL::data_file_path("meshes/cactus.off"); + CGAL::data_file_path("meshes/S52k.stl"); const int nb_clusters = (argc > 2) ? atoi(argv[2]) : 400; @@ -32,12 +32,19 @@ int main(int argc, char* argv[]) return EXIT_FAILURE; } - /// Uniform Isotropic ACVD Remeshing + /// Uniform Isotropic ACVD + + std::cout << "Uniform Isotropic ACVD ...." << std::endl; auto acvd_mesh = PMP::acvd_isotropic_simplification(smesh, nb_clusters); CGAL::IO::write_OFF("acvd_mesh.off", acvd_mesh); - /// Adaptive Isotropic ACVD Remeshing + std::cout << "Completed" << std::endl; + + + /// Adaptive Isotropic ACVD + + std::cout << "Adaptive Isotropic ACVD ...." << std::endl; const double gradation_factor = (argc > 3) ? atof(argv[3]) : 1; @@ -64,6 +71,8 @@ int main(int argc, char* argv[]) CGAL::IO::write_OFF("acvd_mesh_adaptive.off", adaptive_acvd_mesh); + std::cout << "Completed" << std::endl; + return 0; } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 88a94979c31..edb22d10e10 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -29,13 +29,13 @@ #include #include -#include #include #include #include +#include #include #include @@ -87,34 +87,48 @@ struct IsotropicClusterData { if (this->weight_sum > 0) return (this->site_sum) / this->weight_sum; else - return typename GT::Vector_3 (-1, -1, -1); // TODO: Change this + return typename GT::Vector_3 (0, 0, 0); } }; +// Not Fully used yet template struct QEMClusterData { typename GT::Vector_3 site_sum; typename GT::Vector_3 q_centroid; typename GT::FT weight_sum; typename GT::FT energy; - typename GT::FT qem[9]; + std::vector qem; + size_t nb_vertices; char rank_deficiency; - QEMClusterData() : site_sum(0, 0, 0), weight_sum(0), energy(0), rank_deficiency(0), q_centroid(0, 0, 0) { - for (int i = 0; i < 9; i++) - qem[i] = 0; + QEMClusterData() + : site_sum(0, 0, 0), + weight_sum(0), + energy(0), + rank_deficiency(0), + q_centroid(0, 0, 0), + nb_vertices(0) + { + qem = std::vector(10, 0); } - void add_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight) + void add_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight, std::vector qem) { this->site_sum += vertex_position * weight; this->weight_sum += weight; + this->nb_vertices++; + for (int i = 0; i < 10; i++) + this->qem[i] += qem[i]; } - void remove_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight) + void remove_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight, std::vector qem) { this->site_sum -= vertex_position * weight; this->weight_sum -= weight; + this->nb_vertices--; + for (int i = 0; i < 10; i++) + this->qem[i] -= qem[i]; } typename GT::FT compute_energy() @@ -126,9 +140,12 @@ struct QEMClusterData { typename GT::Vector_3 compute_centroid() { if (this->weight_sum > 0) - return (this->site_sum) / this->weight_sum; + { + typename GT::Vector_3 centroid = (this->site_sum) / this->weight_sum; + centroid += compute_qem_point_displacement(this->qem, centroid); + } else - return typename GT::Vector_3 (-1, -1, -1); // TODO: Change this + return typename GT::Vector_3 (0, 0, 0); } }; @@ -203,8 +220,75 @@ void upsample_subdivision_property(PolygonMesh& pmesh, const NamedParameters& np } } -// provide a property map for principal curvatures as a named parameter for adaptive clustering -// provide a gradation factor as a named parameter for adaptive clustering + +// [0 1 2 3] +// [1 4 5 6] +// [2 5 7 8] +// [3 6 8 9] +template +void compute_triangle_QEM(typename GT::Vector_3 points[3], std::vector& qem) +{ + typename GT::FT determinantABC = CGAL::determinant(points[0].x(), points[0].y(), points[0].z(), + points[1].x(), points[1].y(), points[1].z(), + points[2].x(), points[2].y(), points[2].z()); + + typename GT::Vector_3 crossX1X2 = CGAL::cross_product(points[0], points[1]); + typename GT::Vector_3 crossX2X3 = CGAL::cross_product(points[1], points[2]); + typename GT::Vector_3 crossX3X1 = CGAL::cross_product(points[2], points[0]); + + typename GT::FT n[4]; + n[0] = crossX1X2.x() + crossX2X3.x() + crossX3X1.x(); + n[1] = crossX1X2.y() + crossX2X3.y() + crossX3X1.y(); + n[2] = crossX1X2.z() + crossX2X3.z() + crossX3X1.z(); + n[3] = -determinantABC; + + // 0 1 2 3 + // 4 5 6 + // 7 8 + // 9 + qem[0] += n[0] * n[0]; + qem[1] += n[0] * n[1]; + qem[2] += n[0] * n[2]; + qem[3] += n[0] * n[3]; + qem[4] += n[1] * n[1]; + qem[5] += n[1] * n[2]; + qem[6] += n[1] * n[3]; + qem[7] += n[2] * n[2]; + qem[8] += n[2] * n[3]; + qem[9] += n[3] * n[3]; +} + +template +typename GT::Vector_3 compute_qem_point_displacement(std::vector qem, typename GT::Vector_3 point) +{ + Eigen::Matrix A; + A << qem[0], qem[1], qem[2], qem[3], + qem[1], qem[4], qem[5], qem[6], + qem[2], qem[5], qem[7], qem[8], + qem[3], qem[6], qem[8], qem[9]; + + Eigen::EigenSolver> es(A); + Eigen::Matrix eigenvalues = es.eigenvalues().real(); + Eigen::Matrix eigenvectors = es.eigenvectors().real(); + + // find the smallest eigenvalue + int min_eigenvalue_index = 0; + typename GT::FT min_eigenvalue = eigenvalues(0); + for (int i = 1; i < 4; i++) + { + if (eigenvalues(i) < min_eigenvalue) + { + min_eigenvalue = eigenvalues(i); + min_eigenvalue_index = i; + } + } + + // find the corresponding eigenvector + Eigen::Matrix eigenvector = eigenvectors.col(min_eigenvalue_index); + + return typename GT::Vector_3(eigenvector(0), eigenvector(1), eigenvector(2)); +} + template std::pair< @@ -214,7 +298,6 @@ std::pair< PolygonMesh& pmesh, const int nb_clusters, const NamedParameters& np = parameters::default_values() - // seed_randomization can be a named parameter ) { typedef typename GetGeomTraits::type GT; @@ -244,6 +327,7 @@ std::pair< choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions_map), Default_principal_map()); + // if adaptive clustering if (gradation_factor > 0 && is_default_parameter::value) interpolated_corrected_principal_curvatures_and_directions(pmesh, vpcd_map); @@ -259,7 +343,6 @@ std::pair< // To provide the functionality remeshing (not just simplification), we might need to // subdivide the mesh before clustering // in either case, nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD - // do the following while nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD // That is, because the subdivision steps heuristic is not 100% guaranteed to produce // the desired number of vertices. @@ -268,8 +351,6 @@ std::pair< double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); int subdivide_steps = max((int)ceil(log(curr_factor) / log(4)), 0); - //std::cout << "subdivide_steps: " << subdivide_steps << std::endl; - if (subdivide_steps > 0) { if (gradation_factor == 0) // no adaptive clustering @@ -287,8 +368,7 @@ std::pair< } } - // initial random clusters - // property map from vertex_descriptor to cluster index + // creating needed property maps VertexClusterMap vertex_cluster_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); VertexVisitedMap vertex_visited_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); VertexWeightMap vertex_weight_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); @@ -320,7 +400,7 @@ std::pair< typename GT::FT k1 = get(vpcd_map, vd).min_curvature; typename GT::FT k2 = get(vpcd_map, vd).max_curvature; typename GT::FT k_sq = (k1 * k1 + k2 * k2); - vertex_weight += weight * (/*`eps * avg_curvature` instead of 1 +*/ pow(k_sq, gradation_factor / 2.0)); // /2.0 because k_sq is squared + vertex_weight += weight * pow(k_sq, gradation_factor / 2.0); // /2.0 because k_sq is squared } weight_avg += vertex_weight; put(vertex_weight_pmap, vd, vertex_weight); @@ -593,7 +673,7 @@ std::pair< std::vector> polygons; PolygonMesh simplified_mesh; - for (int i = 0; i < nb_clusters; i++) //should i =1 ? + for (int i = 0; i < nb_clusters; i++) { if (clusters[i].weight_sum > 0) { @@ -696,6 +776,493 @@ std::pair< } +template +std::pair< + std::vector::type::Point_3>, + std::vector> +> acvd_qem( + PolygonMesh& pmesh, + const int nb_clusters, + const NamedParameters& np = parameters::default_values() + ) +{ + typedef typename GetGeomTraits::type GT; + typedef typename GetVertexPointMap::const_type Vertex_position_map; + typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename boost::graph_traits::face_descriptor Face_descriptor; + typedef typename boost::property_map >::type VertexClusterMap; + typedef typename boost::property_map >::type VertexVisitedMap; + typedef typename boost::property_map >::type VertexWeightMap; + typedef typename boost::property_map> >::type VertexQEMMap; + typedef Constant_property_map> Default_principal_map; + typedef typename internal_np::Lookup_named_param_def::type Vertex_principal_curvatures_and_directions_map; + + using parameters::choose_parameter; + using parameters::get_parameter; + using parameters::is_default_parameter; + + Vertex_position_map vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), + get_property_map(CGAL::vertex_point, pmesh)); + + // get curvature related parameters + const typename GT::FT gradation_factor = choose_parameter(get_parameter(np, internal_np::gradation_factor), 0); + const Vertex_principal_curvatures_and_directions_map vpcd_map = + choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions_map), + Default_principal_map()); + + // if adaptive clustering + if (gradation_factor > 0 && + is_default_parameter::value) + interpolated_corrected_principal_curvatures_and_directions(pmesh, vpcd_map); + + + // TODO: handle cases where the mesh is not a triangle mesh + CGAL_precondition(CGAL::is_triangle_mesh(pmesh)); + + // TODO: copy the mesh in order to not modify the original mesh + //PolygonMesh pmesh = pmesh_org; + int nb_vertices = num_vertices(pmesh); + + // To provide the functionality remeshing (not just simplification), we might need to + // subdivide the mesh before clustering + // in either case, nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD + // do the following while nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD + // That is, because the subdivision steps heuristic is not 100% guaranteed to produce + // the desired number of vertices. + while (nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) + { + double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); + int subdivide_steps = max((int)ceil(log(curr_factor) / log(4)), 0); + + if (subdivide_steps > 0) + { + if (gradation_factor == 0) // no adaptive clustering + Subdivision_method_3::Upsample_subdivision( + pmesh, + CGAL::parameters::number_of_iterations(subdivide_steps) + ); + else // adaptive clustering + upsample_subdivision_property( + pmesh, + CGAL::parameters::number_of_iterations(subdivide_steps).vertex_principal_curvatures_and_directions_map(vpcd_map) + ); + vpm = get_property_map(CGAL::vertex_point, pmesh); + nb_vertices = num_vertices(pmesh); + } + } + + // creating needed property maps + VertexClusterMap vertex_cluster_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); + VertexVisitedMap vertex_visited_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); + VertexWeightMap vertex_weight_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); + VertexQEMMap vertex_qem_pmap = get(CGAL::dynamic_vertex_property_t>(), pmesh); + std::vector> clusters(nb_clusters); + std::queue clusters_edges_active; + std::queue clusters_edges_new; + + // initialize vertex weights, clusters, QEMs + std::vector temp_qem(10, 0); + for (Vertex_descriptor vd : vertices(pmesh)) + { + put(vertex_weight_pmap, vd, 0); + put(vertex_visited_pmap, vd, false); + put(vertex_cluster_pmap, vd, -1); + put(vertex_qem_pmap, vd, temp_qem); + } + + // compute vertex weights (dual area) and QEMs + typename GT::FT weight_avg = 0; + std::vector qem(10, 0); + for (Face_descriptor fd : faces(pmesh)) + { + typename GT::FT weight = abs(CGAL::Polygon_mesh_processing::face_area(fd, pmesh)) / 3; + + // for positions of the 3 vertices of the face + typename GT::Vector_3 vpv[3]; + int f_vi = 0; + for (Vertex_descriptor vd : vertices_around_face(halfedge(fd, pmesh), pmesh)) + { + typename GT::Point_3 vp = get(vpm, vd); + vpv[f_vi++] = typename GT::Vector_3(vp.x(), vp.y(), vp.z()); + } + + // compute QEM for the face + compute_triangle_QEM(vpv, qem); + + for (Vertex_descriptor vd : vertices_around_face(halfedge(fd, pmesh), pmesh)) + { + typename GT::FT vertex_weight = get(vertex_weight_pmap, vd); + std::vector vertex_qem = get(vertex_qem_pmap, vd); + + for (int i = 0; i < 10; i++) + vertex_qem[i] += qem[i]; + + if (gradation_factor == 0) // no adaptive clustering + vertex_weight += weight; + else // adaptive clustering + { + typename GT::FT k1 = get(vpcd_map, vd).min_curvature; + typename GT::FT k2 = get(vpcd_map, vd).max_curvature; + typename GT::FT k_sq = (k1 * k1 + k2 * k2); + vertex_weight += weight * pow(k_sq, gradation_factor / 2.0); // /2.0 because k_sq is squared + } + weight_avg += vertex_weight; + put(vertex_weight_pmap, vd, vertex_weight); + put(vertex_qem_pmap, vd, vertex_qem); + } + + } + weight_avg /= nb_vertices; + + // clamp the weights up and below by a ratio (like 10,000) * avg_weights + for (Vertex_descriptor vd : vertices(pmesh)) + { + typename GT::FT vertex_weight = get(vertex_weight_pmap, vd); + if (vertex_weight > CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg) + put(vertex_weight_pmap, vd, CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg); + else if (vertex_weight < 1.0 / CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg) + put(vertex_weight_pmap, vd, 1.0 / CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg); + } + + for (int ci = 0; ci < nb_clusters; ci++) + { + int vi; + Vertex_descriptor vd; + do { + vi = CGAL::get_default_random().get_int(0, num_vertices(pmesh)); + vd = *(vertices(pmesh).begin() + vi); + } while (get(vertex_cluster_pmap, vd) != -1); + + put(vertex_cluster_pmap, vd, ci); + typename GT::Point_3 vp = get(vpm, vd); + typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); + clusters[ci].add_vertex(vpv, get(vertex_weight_pmap, vd), get(vertex_qem_pmap, vd)); + + for (Halfedge_descriptor hd : halfedges_around_source(vd, pmesh)) + clusters_edges_active.push(hd); + } + + int nb_modifications = 0; + int nb_disconnected = 0; + + do + { + nb_disconnected = 0; + do + { + nb_modifications = 0; + + while (!clusters_edges_active.empty()) { + Halfedge_descriptor hi = clusters_edges_active.front(); + clusters_edges_active.pop(); + + Vertex_descriptor v1 = source(hi, pmesh); + Vertex_descriptor v2 = target(hi, pmesh); + + int c1 = get(vertex_cluster_pmap, v1); + int c2 = get(vertex_cluster_pmap, v2); + + if (c1 == -1) + { + // expand cluster c2 (add v1 to c2) + put(vertex_cluster_pmap, v1, c2); + typename GT::Point_3 vp1 = get(vpm, v1); + typename GT::Vector_3 vpv(vp1.x(), vp1.y(), vp1.z()); + clusters[c2].add_vertex(vpv, get(vertex_weight_pmap, v1), get(vertex_qem_pmap, v1)); + + // add all halfedges around v1 + for (Halfedge_descriptor hd : halfedges_around_source(v1, pmesh)) + clusters_edges_new.push(hd); + nb_modifications++; + } + else if (c2 == -1) + { + // expand cluster c1 (add v2 to c1) + put(vertex_cluster_pmap, v2, c1); + typename GT::Point_3 vp2 = get(vpm, v2); + typename GT::Vector_3 vpv(vp2.x(), vp2.y(), vp2.z()); + clusters[c1].add_vertex(vpv, get(vertex_weight_pmap, v2), get(vertex_qem_pmap, v2)); + + // add all halfedges around v2 + for (Halfedge_descriptor hd : halfedges_around_source(v2, pmesh)) + clusters_edges_new.push(hd); + nb_modifications++; + } + else if (c1 == c2) + { + clusters_edges_new.push(hi); + } + else + { + // compare the energy of the 3 cases + typename GT::Point_3 vp1 = get(vpm, v1); + typename GT::Vector_3 vpv1(vp1.x(), vp1.y(), vp1.z()); + typename GT::Point_3 vp2 = get(vpm, v2); + typename GT::Vector_3 vpv2(vp2.x(), vp2.y(), vp2.z()); + typename GT::FT v1_weight = get(vertex_weight_pmap, v1); + typename GT::FT v2_weight = get(vertex_weight_pmap, v2); + std::vector v1_qem = get(vertex_qem_pmap, v1); + std::vector v2_qem = get(vertex_qem_pmap, v2); + + typename GT::FT e_no_change = clusters[c1].compute_energy() + clusters[c2].compute_energy(); + + clusters[c1].remove_vertex(vpv1, v1_weight, v1_qem); + clusters[c2].add_vertex(vpv1, v1_weight, v1_qem); + + typename GT::FT e_v1_to_c2 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); + + // reset to no change + clusters[c1].add_vertex(vpv1, v1_weight, v1_qem); + clusters[c2].remove_vertex(vpv1, v1_weight, v1_qem); + + // The effect of the following should always be reversed after the comparison + clusters[c2].remove_vertex(vpv2, v2_weight, v2_qem); + clusters[c1].add_vertex(vpv2, v2_weight, v2_qem); + + typename GT::FT e_v2_to_c1 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); + + if (e_v2_to_c1 < e_no_change && e_v2_to_c1 < e_v1_to_c2 && clusters[c2].nb_vertices > 0) // > 0 as 1 vertex was removed from c2 + { + // move v2 to c1 + put(vertex_cluster_pmap, v2, c1); + + // cluster data is already updated + + // add all halfedges around v2 + for (Halfedge_descriptor hd : halfedges_around_source(v2, pmesh)) + clusters_edges_new.push(hd); + nb_modifications++; + } + else if (e_v1_to_c2 < e_no_change && clusters[c1].nb_vertices > 2) // > 2 as 1 vertex was added to c1 + { + // move v1 to c2 + put(vertex_cluster_pmap, v1, c2); + + // need to reset cluster data and then update + clusters[c2].add_vertex(vpv2, v2_weight, v2_qem); + clusters[c1].remove_vertex(vpv2, v2_weight, v2_qem); + + clusters[c1].remove_vertex(vpv1, v1_weight, v1_qem); + clusters[c2].add_vertex(vpv1, v1_weight, v1_qem); + + // add all halfedges around v1 + for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v1, pmesh), pmesh)) + clusters_edges_new.push(hd); + nb_modifications++; + } + else + { + // no change but need to reset cluster data + clusters[c2].add_vertex(vpv2, v2_weight, v2_qem); + clusters[c1].remove_vertex(vpv2, v2_weight, v2_qem); + + clusters_edges_new.push(hi); + } + } + } + // std::cout << "# Modifications: " << nb_modifications << "\n"; + + clusters_edges_active.swap(clusters_edges_new); + } while (nb_modifications > 0); + + // clean clusters here + // the goal is to delete clusters with multiple connected components + // for each cluster, do a BFS from a vertex in the cluster + // we need to keep the largest connected component for each cluster + // and set the other connected components to -1 (empty cluster), would also need to update clusters_edges_new + + std::vector>> cluster_components(nb_clusters, std::vector>()); + + std::queue vertex_queue; + + // loop over vertices + for (Vertex_descriptor vd : vertices(pmesh)) + { + if (get(vertex_visited_pmap, vd)) continue; + int c = get(vertex_cluster_pmap, vd); + if (c != -1) + { + // first component of this cluster + cluster_components[c].push_back(std::vector()); + int component_i = cluster_components[c].size() - 1; + + // visited_clusters[c] = true; + vertex_queue.push(vd); + put(vertex_visited_pmap, vd, true); + while (!vertex_queue.empty()) + { + Vertex_descriptor v = vertex_queue.front(); + vertex_queue.pop(); + cluster_components[c][component_i].push_back(v); + + for (Halfedge_descriptor hd : halfedges_around_source(v, pmesh)) + { + Vertex_descriptor v2 = target(hd, pmesh); + int c2 = get(vertex_cluster_pmap, v2); + if (c2 == c && !get(vertex_visited_pmap, v2)) + { + vertex_queue.push(v2); + put(vertex_visited_pmap, v2, true); + } + } + } + } + } + + // loop over clusters + for (int c = 0; c < nb_clusters; c++) + { + if (cluster_components[c].size() <= 1) continue; // only one component, no need to do anything + nb_disconnected++; + int max_component_size = 0; + int max_component_index = -1; + for (int component_i = 0; component_i < cluster_components[c].size(); component_i++) + { + if (cluster_components[c][component_i].size() > max_component_size) + { + max_component_size = cluster_components[c][component_i].size(); + max_component_index = component_i; + } + } + // set cluster to -1 for all components except the largest one + for (int component_i = 0; component_i < cluster_components[c].size(); component_i++) + { + if (component_i != max_component_index) + { + for (Vertex_descriptor vd : cluster_components[c][component_i]) + { + put(vertex_cluster_pmap, vd, -1); + // remove vd from cluster c + typename GT::Point_3 vp = get(vpm, vd); + typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); + clusters[c].remove_vertex(vpv, get(vertex_weight_pmap, vd), get(vertex_qem_pmap, vd)); + // add all halfedges around v except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(vd, pmesh)) + { + // add hd to the queue if its target is not in the same cluster + Vertex_descriptor v2 = target(hd, pmesh); + int c2 = get(vertex_cluster_pmap, v2); + if (c2 != c) + clusters_edges_new.push(hd); + } + } + } + } + } + + } while (nb_disconnected > 0); + + /// Construct new Mesh + std::vector valid_cluster_map(nb_clusters, -1); + std::vector points; + + std::vector> polygons; + PolygonMesh simplified_mesh; + + for (int i = 0; i < nb_clusters; i++) + { + if (clusters[i].weight_sum > 0) + { + valid_cluster_map[i] = points.size(); + typename GT::Vector_3 center_v = clusters[i].compute_centroid(); + typename GT::Point_3 center_p(center_v.x(), center_v.y(), center_v.z()); + points.push_back(center_p); + } + } + + // extract boundary cycles + std::vector border_hedges; + extract_boundary_cycles(pmesh, std::back_inserter(border_hedges)); + + // loop over boundary loops + for (Halfedge_descriptor hd : border_hedges) + { + Halfedge_descriptor hd1 = hd; + + int cb_first = -1; + + do + { + // 1- get the target and source vertices vt, vs + // 2- if the target and source vertices are in different clusters, create a new vertex vb between them vb = (vt + vs) / 2 + // 3- make a new face with the new vertex vb and the centers of the clusters of vt and vs + // 4- also make a new face with vb, the next vb, and the center of the cluster of vt + + Vertex_descriptor vt = target(hd1, pmesh); + Vertex_descriptor vs = source(hd1, pmesh); + + int ct = get(vertex_cluster_pmap, vt); + int cs = get(vertex_cluster_pmap, vs); + + if (ct != cs) + { + typename GT::Point_3 vt_p = get(vpm, vt); + typename GT::Point_3 vs_p = get(vpm, vs); + typename GT::Vector_3 vt_v(vt_p.x(), vt_p.y(), vt_p.z()); + typename GT::Vector_3 vs_v(vs_p.x(), vs_p.y(), vs_p.z()); + + typename GT::Vector_3 vb_v = (vt_v + vs_v) / 2; + typename GT::Point_3 vb_p(vb_v.x(), vb_v.y(), vb_v.z()); + + points.push_back(vb_p); + + int cb = points.size() - 1; + + if (cb_first == -1) + cb_first = cb; + + int ct_mapped = valid_cluster_map[ct], cs_mapped = valid_cluster_map[cs]; + + if (ct_mapped != -1 && cs_mapped != -1) + { + std::vector + polygon = {ct_mapped, cb, cs_mapped}; + polygons.push_back(polygon); + + // after the loop, the last cb+1 should be modified to the first cb + polygon = {cb, ct_mapped, cb + 1}; + polygons.push_back(polygon); + } + } + hd1 = next(hd1, pmesh); + } while (hd1 != hd); + polygons[polygons.size() - 1][2] = cb_first; + } + + for (Face_descriptor fd : faces(pmesh)) + { + Halfedge_descriptor hd1 = halfedge(fd, pmesh); + Vertex_descriptor v1 = source(hd1, pmesh); + Halfedge_descriptor hd2 = next(hd1, pmesh); + Vertex_descriptor v2 = source(hd2, pmesh); + Halfedge_descriptor hd3 = next(hd2, pmesh); + Vertex_descriptor v3 = source(hd3, pmesh); + + int c1 = get(vertex_cluster_pmap, v1); + int c2 = get(vertex_cluster_pmap, v2); + int c3 = get(vertex_cluster_pmap, v3); + + if (c1 != c2 && c1 != c3 && c2 != c3) + { + int c1_mapped = valid_cluster_map[c1], c2_mapped = valid_cluster_map[c2], c3_mapped = valid_cluster_map[c3]; + if (c1_mapped != -1 && c2_mapped != -1 && c3_mapped != -1) + { + std::vector polygon = {c1_mapped, c2_mapped, c3_mapped}; + polygons.push_back(polygon); + } + } + } + + orient_polygon_soup(points, polygons); + + return std::make_pair(points, polygons); +} + + } // namespace internal /** @@ -847,20 +1414,42 @@ PolygonMesh acvd_isotropic_simplification( return simplified_mesh; } -// template -// PolygonMesh acvd_qem_simplification( -// PolygonMesh& pmesh, -// const int& nb_vertices, -// const NamedParameters& np = parameters::default_values() -// ) -// { -// return internal::acve_qem( -// pmesh, -// nb_vertices, -// np -// ); -// } +template + std::pair< + std::vector::type::Point_3>, + std::vector> + > acvd_qem_simplification_polygon_soup( + PolygonMesh& pmesh, + const int& nb_vertices, + const NamedParameters& np = parameters::default_values() + ) +{ + return internal::acvd_qem( + pmesh, + nb_vertices, + np + ); +} + +template + PolygonMesh acvd_qem_simplification( + PolygonMesh& pmesh, + const int& nb_vertices, + const NamedParameters& np = parameters::default_values() + ) +{ + auto ps = acvd_qem_simplification_polygon_soup( + pmesh, + nb_vertices, + np + ); + + PolygonMesh simplified_mesh; + polygon_soup_to_polygon_mesh(ps.first, ps.second, simplified_mesh); + return simplified_mesh; +} } // namespace Polygon_mesh_processing From 1c114c8d75b05449008cfcd16680414489031063 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 8 Nov 2023 17:32:39 +0300 Subject: [PATCH 39/56] minor fix (std::max) --- .../include/CGAL/Polygon_mesh_processing/acvd/acvd.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index edb22d10e10..41f9dbe5fc6 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -349,7 +349,7 @@ std::pair< while (nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) { double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); - int subdivide_steps = max((int)ceil(log(curr_factor) / log(4)), 0); + int subdivide_steps = (std::max)((int)ceil(log(curr_factor) / log(4)), 0); if (subdivide_steps > 0) { @@ -836,7 +836,7 @@ std::pair< while (nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) { double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); - int subdivide_steps = max((int)ceil(log(curr_factor) / log(4)), 0); + int subdivide_steps = (std::max)((int)ceil(log(curr_factor) / log(4)), 0); if (subdivide_steps > 0) { From 603cac2fd68ef163ca1d4e0ae936e5a9599b7d24 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 8 Nov 2023 17:49:56 +0300 Subject: [PATCH 40/56] some cleanup and comments --- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 76 +++++++------------ 1 file changed, 29 insertions(+), 47 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 41f9dbe5fc6..ac0dce8a1af 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -32,9 +32,6 @@ #include -#include -#include - #include #include @@ -332,20 +329,15 @@ std::pair< is_default_parameter::value) interpolated_corrected_principal_curvatures_and_directions(pmesh, vpcd_map); - - // TODO: handle cases where the mesh is not a triangle mesh CGAL_precondition(CGAL::is_triangle_mesh(pmesh)); // TODO: copy the mesh in order to not modify the original mesh //PolygonMesh pmesh = pmesh_org; int nb_vertices = num_vertices(pmesh); - // To provide the functionality remeshing (not just simplification), we might need to - // subdivide the mesh before clustering - // in either case, nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD - // do the following while nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD - // That is, because the subdivision steps heuristic is not 100% guaranteed to produce - // the desired number of vertices. + // For remeshing, we might need to subdivide the mesh before clustering + // This shoould always hold: nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD + // So do the following till the condition is met while (nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) { double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); @@ -418,6 +410,7 @@ std::pair< put(vertex_weight_pmap, vd, 1.0 / CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg); } + // randomly initialize clusters for (int ci = 0; ci < nb_clusters; ci++) { int vi; @@ -436,9 +429,9 @@ std::pair< clusters_edges_active.push(hd); } + // the energy minimization loop (clustering loop) int nb_modifications = 0; int nb_disconnected = 0; - do { nb_disconnected = 0; @@ -561,28 +554,23 @@ std::pair< clusters_edges_active.swap(clusters_edges_new); } while (nb_modifications > 0); - // clean clusters here - // the goal is to delete clusters with multiple connected components - // for each cluster, do a BFS from a vertex in the cluster - // we need to keep the largest connected component for each cluster - // and set the other connected components to -1 (empty cluster), would also need to update clusters_edges_new + // Disconnected clusters handling + // the goal is to delete clusters with multiple connected components and only keep the largest connected component of each cluster + // For each cluster, do a BFS from a vertex in the cluster std::vector>> cluster_components(nb_clusters, std::vector>()); - std::queue vertex_queue; - // loop over vertices + // loop over vertices to compute cluster components for (Vertex_descriptor vd : vertices(pmesh)) { if (get(vertex_visited_pmap, vd)) continue; int c = get(vertex_cluster_pmap, vd); if (c != -1) { - // first component of this cluster cluster_components[c].push_back(std::vector()); int component_i = cluster_components[c].size() - 1; - // visited_clusters[c] = true; vertex_queue.push(vd); put(vertex_visited_pmap, vd, true); while (!vertex_queue.empty()) @@ -605,7 +593,7 @@ std::pair< } } - // loop over clusters + // loop over clusters to delete disconnected components except the largest one for (int c = 0; c < nb_clusters; c++) { if (cluster_components[c].size() <= 1) continue; // only one component, no need to do anything @@ -631,7 +619,7 @@ std::pair< // remove vd from cluster c typename GT::Point_3 vp = get(vpm, vd); typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); - clusters[c].remove_vertex(vpv, get(vertex_weight_pmap, vd)); + clusters[c].remove_vertex(vpv, get(vertex_weight_pmap, vd), get(vertex_qem_pmap, vd)); // add all halfedges around v except hi to the queue for (Halfedge_descriptor hd : halfedges_around_source(vd, pmesh)) { @@ -668,11 +656,11 @@ std::pair< /// Construct new Mesh std::vector valid_cluster_map(nb_clusters, -1); std::vector points; - Point_set_3 point_set; std::vector> polygons; PolygonMesh simplified_mesh; + // create a point for each cluster for (int i = 0; i < nb_clusters; i++) { if (clusters[i].weight_sum > 0) @@ -681,7 +669,6 @@ std::pair< typename GT::Vector_3 center_v = clusters[i].compute_centroid(); typename GT::Point_3 center_p(center_v.x(), center_v.y(), center_v.z()); points.push_back(center_p); - point_set.insert(center_p); } } @@ -700,7 +687,6 @@ std::pair< { // 1- get the target and source vertices vt, vs // 2- if the target and source vertices are in different clusters, create a new vertex vb between them vb = (vt + vs) / 2 - // it is also added to the point_set // 3- make a new face with the new vertex vb and the centers of the clusters of vt and vs // 4- also make a new face with vb, the next vb, and the center of the cluster of vt @@ -721,7 +707,6 @@ std::pair< typename GT::Point_3 vb_p(vb_v.x(), vb_v.y(), vb_v.z()); points.push_back(vb_p); - point_set.insert(vb_p); int cb = points.size() - 1; @@ -746,6 +731,7 @@ std::pair< polygons[polygons.size() - 1][2] = cb_first; } + // create a triangle for each face with all vertices in 3 different clusters for (Face_descriptor fd : faces(pmesh)) { Halfedge_descriptor hd1 = halfedge(fd, pmesh); @@ -775,7 +761,8 @@ std::pair< return std::make_pair(points, polygons); } - +/// QEM is WIP (TODO) +/// Also, TODO, need to break down both functions for modularity and DRY template std::pair< @@ -819,20 +806,16 @@ std::pair< is_default_parameter::value) interpolated_corrected_principal_curvatures_and_directions(pmesh, vpcd_map); - - // TODO: handle cases where the mesh is not a triangle mesh CGAL_precondition(CGAL::is_triangle_mesh(pmesh)); // TODO: copy the mesh in order to not modify the original mesh //PolygonMesh pmesh = pmesh_org; + int nb_vertices = num_vertices(pmesh); - // To provide the functionality remeshing (not just simplification), we might need to - // subdivide the mesh before clustering - // in either case, nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD - // do the following while nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD - // That is, because the subdivision steps heuristic is not 100% guaranteed to produce - // the desired number of vertices. + // For remeshing, we might need to subdivide the mesh before clustering + // This shoould always hold: nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD + // So do the following till the condition is met while (nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) { double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); @@ -928,6 +911,7 @@ std::pair< put(vertex_weight_pmap, vd, 1.0 / CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg); } + // randomly initialize clusters for (int ci = 0; ci < nb_clusters; ci++) { int vi; @@ -946,9 +930,9 @@ std::pair< clusters_edges_active.push(hd); } + // the energy minimization loop (clustering loop) int nb_modifications = 0; int nb_disconnected = 0; - do { nb_disconnected = 0; @@ -1069,28 +1053,23 @@ std::pair< clusters_edges_active.swap(clusters_edges_new); } while (nb_modifications > 0); - // clean clusters here - // the goal is to delete clusters with multiple connected components - // for each cluster, do a BFS from a vertex in the cluster - // we need to keep the largest connected component for each cluster - // and set the other connected components to -1 (empty cluster), would also need to update clusters_edges_new + // Disconnected clusters handling + // the goal is to delete clusters with multiple connected components and only keep the largest connected component of each cluster + // For each cluster, do a BFS from a vertex in the cluster std::vector>> cluster_components(nb_clusters, std::vector>()); - std::queue vertex_queue; - // loop over vertices + // loop over vertices to compute cluster components for (Vertex_descriptor vd : vertices(pmesh)) { if (get(vertex_visited_pmap, vd)) continue; int c = get(vertex_cluster_pmap, vd); if (c != -1) { - // first component of this cluster cluster_components[c].push_back(std::vector()); int component_i = cluster_components[c].size() - 1; - // visited_clusters[c] = true; vertex_queue.push(vd); put(vertex_visited_pmap, vd, true); while (!vertex_queue.empty()) @@ -1113,7 +1092,7 @@ std::pair< } } - // loop over clusters + // loop over clusters to delete disconnected components except the largest one for (int c = 0; c < nb_clusters; c++) { if (cluster_components[c].size() <= 1) continue; // only one component, no need to do anything @@ -1163,6 +1142,7 @@ std::pair< std::vector> polygons; PolygonMesh simplified_mesh; + // create a point for each cluster for (int i = 0; i < nb_clusters; i++) { if (clusters[i].weight_sum > 0) @@ -1233,6 +1213,7 @@ std::pair< polygons[polygons.size() - 1][2] = cb_first; } + // create a triangle for each face with all vertices in 3 different clusters for (Face_descriptor fd : faces(pmesh)) { Halfedge_descriptor hd1 = halfedge(fd, pmesh); @@ -1414,6 +1395,7 @@ PolygonMesh acvd_isotropic_simplification( return simplified_mesh; } +/// QEM is WIP (TODO) template std::pair< From 9ed6d1afe07823fc585a09cd6bc32303876e3608 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 8 Nov 2023 17:52:41 +0300 Subject: [PATCH 41/56] typo fix --- .../include/CGAL/Polygon_mesh_processing/acvd/acvd.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index ac0dce8a1af..4acfde3f3e7 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -619,7 +619,7 @@ std::pair< // remove vd from cluster c typename GT::Point_3 vp = get(vpm, vd); typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); - clusters[c].remove_vertex(vpv, get(vertex_weight_pmap, vd), get(vertex_qem_pmap, vd)); + clusters[c].remove_vertex(vpv, get(vertex_weight_pmap, vd)); // add all halfedges around v except hi to the queue for (Halfedge_descriptor hd : halfedges_around_source(vd, pmesh)) { From 5e5b97ab3b238df2aeeaa472cafb81cb17bda8bc Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 8 Nov 2023 21:26:24 +0300 Subject: [PATCH 42/56] user manual API doc --- .../Polygon_mesh_processing.txt | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt index b11c498b69f..e3a3c06f276 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt @@ -1034,7 +1034,24 @@ WIP \subsection ACVDAPI API -WIP +The implementation is generic in terms of mesh data structure. It can be used on `Surface_mesh`, `Polyhedron_3` and +other triangle mesh structures based on the concept `FaceGraph`. + +The main function is `CGAL::Polygon_mesh_processing::acvd_isotropic_simplification()`. It takes as input a triangle mesh and +a target number of vertices and returns a remeshed version of the input mesh with the target number of vertices. + +The returned mesh can be triangle mesh in the same data structure as the input mesh representing a `FaceGraph`. +Or a polygon (triangle) soup (as a `pair, std::vector>>)`. For the latter, +use the function `CGAL::Polygon_mesh_processing::acvd_isotropic_simplification_polygon_soup()` instead. The reason for +having a polygon soup version is that there are no guarantees that the output mesh is manifold. + +To enable Adaptive Remeshing, the named parameter `gradation_factor` can be used. It controls the sensitivity of the +clustering algorithm to curvature. If a value of 0 is used, the algorithm is not adaptive. Moreover, the named parameter +`vertex_principal_curvatures_and_directions_map` can be used to provide a user-defined principal curvature map. + +For Clustering based on QEM, the function `CGAL::Polygon_mesh_processing::acvd_qem_simplification()` and +`CGAL::Polygon_mesh_processing::acvd_qem_simplification_polygon_soup()` can be used. THIS IS NOT 100% WORKING YET. + \subsection ACVDResults Results From 8b649551be15ef31487bbdd85f765fc2420ee0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 25 Mar 2024 16:30:50 +0100 Subject: [PATCH 43/56] fix compilation issues mostly due to change of function name --- .../Polygon_mesh_processing/CMakeLists.txt | 1 + .../Polygon_mesh_processing/acvd_example.cpp | 2 +- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 66 +++++++++---------- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index d9c35e0d000..ec542b7c245 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -61,6 +61,7 @@ find_package(Eigen3 3.2.0 QUIET) #(requires 3.2.0 or greater) include(CGAL_Eigen3_support) if(TARGET CGAL::Eigen3_support) create_single_source_cgal_program("acvd_example.cpp") + target_link_libraries(acvd_example PUBLIC CGAL::Eigen3_support) create_single_source_cgal_program("hole_filling_example.cpp") target_link_libraries(hole_filling_example PUBLIC CGAL::Eigen3_support) create_single_source_cgal_program("hole_filling_example_SM.cpp") diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp index e0341b18940..c1d7223b80e 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -59,7 +59,7 @@ int main(int argc, char* argv[]) Epic_kernel::Vector_3(0,0,0) }); assert(created); - PMP::interpolated_corrected_principal_curvatures_and_directions(smesh, principal_curvatures_and_directions_map); + PMP::interpolated_corrected_curvatures(smesh, CGAL::parameters::vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map)); auto adaptive_acvd_mesh = PMP::acvd_isotropic_simplification( diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 4acfde3f3e7..36c6f9c7224 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -88,6 +88,37 @@ struct IsotropicClusterData { } }; +template +typename GT::Vector_3 compute_qem_point_displacement(std::vector qem, typename GT::Vector_3 point) +{ + Eigen::Matrix A; + A << qem[0], qem[1], qem[2], qem[3], + qem[1], qem[4], qem[5], qem[6], + qem[2], qem[5], qem[7], qem[8], + qem[3], qem[6], qem[8], qem[9]; + + Eigen::EigenSolver> es(A); + Eigen::Matrix eigenvalues = es.eigenvalues().real(); + Eigen::Matrix eigenvectors = es.eigenvectors().real(); + + // find the smallest eigenvalue + int min_eigenvalue_index = 0; + typename GT::FT min_eigenvalue = eigenvalues(0); + for (int i = 1; i < 4; i++) + { + if (eigenvalues(i) < min_eigenvalue) + { + min_eigenvalue = eigenvalues(i); + min_eigenvalue_index = i; + } + } + + // find the corresponding eigenvector + Eigen::Matrix eigenvector = eigenvectors.col(min_eigenvalue_index); + + return typename GT::Vector_3(eigenvector(0), eigenvector(1), eigenvector(2)); +} + // Not Fully used yet template struct QEMClusterData { @@ -255,37 +286,6 @@ void compute_triangle_QEM(typename GT::Vector_3 points[3], std::vector -typename GT::Vector_3 compute_qem_point_displacement(std::vector qem, typename GT::Vector_3 point) -{ - Eigen::Matrix A; - A << qem[0], qem[1], qem[2], qem[3], - qem[1], qem[4], qem[5], qem[6], - qem[2], qem[5], qem[7], qem[8], - qem[3], qem[6], qem[8], qem[9]; - - Eigen::EigenSolver> es(A); - Eigen::Matrix eigenvalues = es.eigenvalues().real(); - Eigen::Matrix eigenvectors = es.eigenvectors().real(); - - // find the smallest eigenvalue - int min_eigenvalue_index = 0; - typename GT::FT min_eigenvalue = eigenvalues(0); - for (int i = 1; i < 4; i++) - { - if (eigenvalues(i) < min_eigenvalue) - { - min_eigenvalue = eigenvalues(i); - min_eigenvalue_index = i; - } - } - - // find the corresponding eigenvector - Eigen::Matrix eigenvector = eigenvectors.col(min_eigenvalue_index); - - return typename GT::Vector_3(eigenvector(0), eigenvector(1), eigenvector(2)); -} - template std::pair< @@ -327,7 +327,7 @@ std::pair< // if adaptive clustering if (gradation_factor > 0 && is_default_parameter::value) - interpolated_corrected_principal_curvatures_and_directions(pmesh, vpcd_map); + interpolated_corrected_curvatures(pmesh, parameters::vertex_principal_curvatures_and_directions_map(vpcd_map)); CGAL_precondition(CGAL::is_triangle_mesh(pmesh)); @@ -804,7 +804,7 @@ std::pair< // if adaptive clustering if (gradation_factor > 0 && is_default_parameter::value) - interpolated_corrected_principal_curvatures_and_directions(pmesh, vpcd_map); + interpolated_corrected_curvatures(pmesh, parameters::vertex_principal_curvatures_and_directions_map(vpcd_map)); CGAL_precondition(CGAL::is_triangle_mesh(pmesh)); From 37b16ab65632d0352186a92ab79eee61b47b9e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 25 Mar 2024 16:32:44 +0100 Subject: [PATCH 44/56] fix warnings --- .../include/CGAL/Polygon_mesh_processing/acvd/acvd.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 36c6f9c7224..b1860c0a4e5 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -598,9 +598,9 @@ std::pair< { if (cluster_components[c].size() <= 1) continue; // only one component, no need to do anything nb_disconnected++; - int max_component_size = 0; - int max_component_index = -1; - for (int component_i = 0; component_i < cluster_components[c].size(); component_i++) + std::size_t max_component_size = 0; + std::size_t max_component_index = -1; + for (std::size_t component_i = 0; component_i < cluster_components[c].size(); component_i++) { if (cluster_components[c][component_i].size() > max_component_size) { @@ -609,7 +609,7 @@ std::pair< } } // set cluster to -1 for all components except the largest one - for (int component_i = 0; component_i < cluster_components[c].size(); component_i++) + for (std::size_t component_i = 0; component_i < cluster_components[c].size(); component_i++) { if (component_i != max_component_index) { From 9c6aa8c04f4326d8108f62c3f9a034d491ca3b40 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:15:47 +0200 Subject: [PATCH 45/56] Optimizing Cluster Rep-Points with Qem (PostProcessing) --- .../Polygon_mesh_processing/acvd_example.cpp | 46 +- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 815 ++++-------------- 2 files changed, 192 insertions(+), 669 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp index c1d7223b80e..41da1260f24 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -22,9 +22,9 @@ int main(int argc, char* argv[]) Surface_Mesh smesh; const std::string filename = (argc > 1) ? CGAL::data_file_path(argv[1]) : - CGAL::data_file_path("meshes/S52k.stl"); + CGAL::data_file_path("meshes/fandisk.off"); - const int nb_clusters = (argc > 2) ? atoi(argv[2]) : 400; + const int nb_clusters = (argc > 2) ? atoi(argv[2]) : 3000; if (!CGAL::IO::read_polygon_mesh(filename, smesh)) { @@ -37,39 +37,39 @@ int main(int argc, char* argv[]) std::cout << "Uniform Isotropic ACVD ...." << std::endl; auto acvd_mesh = PMP::acvd_isotropic_simplification(smesh, nb_clusters); - CGAL::IO::write_OFF("acvd_mesh.off", acvd_mesh); + CGAL::IO::write_OFF("fandisk_qem-pp3000.off", acvd_mesh); std::cout << "Completed" << std::endl; /// Adaptive Isotropic ACVD - std::cout << "Adaptive Isotropic ACVD ...." << std::endl; + //std::cout << "Adaptive Isotropic ACVD ...." << std::endl; - const double gradation_factor = (argc > 3) ? atof(argv[3]) : 1; + //const double gradation_factor = (argc > 3) ? atof(argv[3]) : 1; - bool created = false; - Surface_Mesh::Property_map> - principal_curvatures_and_directions_map; + //bool created = false; + //Surface_Mesh::Property_map> + // principal_curvatures_and_directions_map; - boost::tie(principal_curvatures_and_directions_map, created) = - smesh.add_property_map> - ("v:principal_curvatures_and_directions_map", { 0, 0, - Epic_kernel::Vector_3(0,0,0), - Epic_kernel::Vector_3(0,0,0) }); - assert(created); + //boost::tie(principal_curvatures_and_directions_map, created) = + // smesh.add_property_map> + // ("v:principal_curvatures_and_directions_map", { 0, 0, + // Epic_kernel::Vector_3(0,0,0), + // Epic_kernel::Vector_3(0,0,0) }); + //assert(created); - PMP::interpolated_corrected_curvatures(smesh, CGAL::parameters::vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map)); + //PMP::interpolated_corrected_curvatures(smesh, CGAL::parameters::vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map)); - auto adaptive_acvd_mesh = - PMP::acvd_isotropic_simplification( - smesh, - nb_clusters, - CGAL::parameters::vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) - .gradation_factor(gradation_factor) - ); + //auto adaptive_acvd_mesh = + // PMP::acvd_isotropic_simplification( + // smesh, + // nb_clusters, + // CGAL::parameters::vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) + // .gradation_factor(gradation_factor) + // ); - CGAL::IO::write_OFF("acvd_mesh_adaptive.off", adaptive_acvd_mesh); + //CGAL::IO::write_OFF("acvd_mesh_adaptive.off", adaptive_acvd_mesh); std::cout << "Completed" << std::endl; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index b1860c0a4e5..5b0b8479525 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -50,6 +50,138 @@ namespace Polygon_mesh_processing { namespace internal { +template +void compute_qem_face(const typename GT::Vector_3& p1, const typename GT::Vector_3& p2, const typename GT::Vector_3& p3, Eigen::Matrix& quadric) +{ + typename GT::Vector_3 crossX1X2 = CGAL::cross_product(p1, p2); + typename GT::Vector_3 crossX2X3 = CGAL::cross_product(p2, p3); + typename GT::Vector_3 crossX3X1 = CGAL::cross_product(p3, p1); + double determinantABC = CGAL::determinant(p1, p2, p3); + + typename GT::FT n[4] = { + crossX1X2.x() + crossX2X3.x() + crossX3X1.x(), + crossX1X2.y() + crossX2X3.y() + crossX3X1.y(), + crossX1X2.z() + crossX2X3.z() + crossX3X1.z(), + -determinantABC + }; + + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + quadric(i, j) = n[i] * n[j]; +} + +template +void compute_qem_vertex(std::vector> cluster_tris, Eigen::Matrix& quadric, typename GT::Vector_3& centroid) +{ + quadric.setZero(); + centroid = typename GT::Vector_3(0, 0, 0); + for (int i = 0; i < cluster_tris.size(); i++) + { + centroid = centroid + (cluster_tris[i][0] + cluster_tris[i][1] + cluster_tris[i][2]); + } + centroid = centroid / 3; + + for (int i = 0; i < cluster_tris.size(); i++) + { + Eigen::Matrix q; + compute_qem_face(cluster_tris[i][0], cluster_tris[i][1], cluster_tris[i][2], q); + quadric = quadric + q; + } +} + +template +typename GT::Vector_3 compute_displacement(const Eigen::Matrix& quadric, const typename GT::Vector_3& p, int& RankDeficiency) +{ + typedef Eigen::Matrix Matrix4d; + typedef Eigen::Matrix Matrix3d; + + + int MaxNumberOfUsedSingularValues = 3; + Matrix3d A; + A(0, 0) = quadric(0, 0); + A(0, 1) = A(1, 0) = quadric(0, 1); + A(0, 2) = A(2, 0) = quadric(0, 2); + A(1, 1) = quadric(1, 1); + A(1, 2) = A(2, 1) = quadric(1, 2); + A(2, 2) = quadric(2, 2); + + Matrix3d U, VT; + typename GT::Vector_3 w; + //CGAL::linear_algebra_qr(A, U, w, VT); + Eigen::JacobiSVD svd(A, Eigen::ComputeFullU | Eigen::ComputeFullV); + U = svd.matrixU(); + VT = svd.matrixV(); + auto w_temp = svd.singularValues(); + w = typename GT::Vector_3(w_temp(0), w_temp(1), w_temp(2)); + + // compute all eigen values absolute values + double AbsolutesEigenValues[3]; + double maxW = -1.0; + for (int j = 0; j < 3; j++) + { + double AbsoluteEigenValue = fabs(w[j]); + AbsolutesEigenValues[j] = AbsoluteEigenValue; + if (AbsoluteEigenValue > maxW) + maxW = AbsoluteEigenValue; + } + double invmaxW = 1.0 / maxW; + + Matrix3d tempMatrix, tempMatrix2; + + for (int i = 0; i < 3; i++) + { + double LocalMaxW = -1; + int IndexMax = -1; + + // find the remaining eigenvalue with highest absolute value + for (int j = 0; j < 3; j++) + { + if (LocalMaxW < AbsolutesEigenValues[j]) + { + LocalMaxW = AbsolutesEigenValues[j]; + IndexMax = j; + } + } + + if ((AbsolutesEigenValues[IndexMax] * invmaxW > 1e-4) + && (MaxNumberOfUsedSingularValues > 0)) + { + // If this is true, then w[i] != 0, so this division is ok. + double Inv = 1.0 / w[IndexMax]; + tempMatrix(IndexMax, 0) = U(0, IndexMax) * Inv; + tempMatrix(IndexMax, 1) = U(1, IndexMax) * Inv; + tempMatrix(IndexMax, 2) = U(2, IndexMax) * Inv; + } + else + { + tempMatrix(IndexMax, 0) = 0; + tempMatrix(IndexMax, 1) = 0; + tempMatrix(IndexMax, 2) = 0; + RankDeficiency++; + } + + // set the eigenvalu to -2 to remove it from subsequent tests + AbsolutesEigenValues[IndexMax] = -2; + MaxNumberOfUsedSingularValues--; + } + + tempMatrix2 = VT * tempMatrix; + Eigen::Matrix p_temp, b; + b << -quadric(0, 3), -quadric(1, 3), -quadric(2, 3); + p_temp << p.x(), p.y(), p.z(); + Eigen::Matrix displacement_temp = tempMatrix2 * (b - A * p_temp); + typename GT::Vector_3 displacement = { displacement_temp(0), displacement_temp(1), displacement_temp(2) }; + return displacement; +} + +template +void compute_representative_point(const Eigen::Matrix& quadric, typename GT::Vector_3& p, int& RankDeficiency) +{ + // average point + typename GT::Vector_3 displacement = compute_displacement(quadric, p, RankDeficiency); + p = p + displacement; +} + template struct IsotropicClusterData { typename GT::Vector_3 site_sum; @@ -88,95 +220,6 @@ struct IsotropicClusterData { } }; -template -typename GT::Vector_3 compute_qem_point_displacement(std::vector qem, typename GT::Vector_3 point) -{ - Eigen::Matrix A; - A << qem[0], qem[1], qem[2], qem[3], - qem[1], qem[4], qem[5], qem[6], - qem[2], qem[5], qem[7], qem[8], - qem[3], qem[6], qem[8], qem[9]; - - Eigen::EigenSolver> es(A); - Eigen::Matrix eigenvalues = es.eigenvalues().real(); - Eigen::Matrix eigenvectors = es.eigenvectors().real(); - - // find the smallest eigenvalue - int min_eigenvalue_index = 0; - typename GT::FT min_eigenvalue = eigenvalues(0); - for (int i = 1; i < 4; i++) - { - if (eigenvalues(i) < min_eigenvalue) - { - min_eigenvalue = eigenvalues(i); - min_eigenvalue_index = i; - } - } - - // find the corresponding eigenvector - Eigen::Matrix eigenvector = eigenvectors.col(min_eigenvalue_index); - - return typename GT::Vector_3(eigenvector(0), eigenvector(1), eigenvector(2)); -} - -// Not Fully used yet -template -struct QEMClusterData { - typename GT::Vector_3 site_sum; - typename GT::Vector_3 q_centroid; - typename GT::FT weight_sum; - typename GT::FT energy; - std::vector qem; - size_t nb_vertices; - char rank_deficiency; - - QEMClusterData() - : site_sum(0, 0, 0), - weight_sum(0), - energy(0), - rank_deficiency(0), - q_centroid(0, 0, 0), - nb_vertices(0) - { - qem = std::vector(10, 0); - } - - void add_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight, std::vector qem) - { - this->site_sum += vertex_position * weight; - this->weight_sum += weight; - this->nb_vertices++; - for (int i = 0; i < 10; i++) - this->qem[i] += qem[i]; - } - - void remove_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight, std::vector qem) - { - this->site_sum -= vertex_position * weight; - this->weight_sum -= weight; - this->nb_vertices--; - for (int i = 0; i < 10; i++) - this->qem[i] -= qem[i]; - } - - typename GT::FT compute_energy() - { - this->energy = - (this->site_sum).squared_length() / this->weight_sum; - return this->energy; - } - - typename GT::Vector_3 compute_centroid() - { - if (this->weight_sum > 0) - { - typename GT::Vector_3 centroid = (this->site_sum) / this->weight_sum; - centroid += compute_qem_point_displacement(this->qem, centroid); - } - else - return typename GT::Vector_3 (0, 0, 0); - } -}; - template void upsample_subdivision_property(PolygonMesh& pmesh, const NamedParameters& np = parameters::default_values()) { typedef typename GetGeomTraits::type GT; @@ -248,44 +291,6 @@ void upsample_subdivision_property(PolygonMesh& pmesh, const NamedParameters& np } } - -// [0 1 2 3] -// [1 4 5 6] -// [2 5 7 8] -// [3 6 8 9] -template -void compute_triangle_QEM(typename GT::Vector_3 points[3], std::vector& qem) -{ - typename GT::FT determinantABC = CGAL::determinant(points[0].x(), points[0].y(), points[0].z(), - points[1].x(), points[1].y(), points[1].z(), - points[2].x(), points[2].y(), points[2].z()); - - typename GT::Vector_3 crossX1X2 = CGAL::cross_product(points[0], points[1]); - typename GT::Vector_3 crossX2X3 = CGAL::cross_product(points[1], points[2]); - typename GT::Vector_3 crossX3X1 = CGAL::cross_product(points[2], points[0]); - - typename GT::FT n[4]; - n[0] = crossX1X2.x() + crossX2X3.x() + crossX3X1.x(); - n[1] = crossX1X2.y() + crossX2X3.y() + crossX3X1.y(); - n[2] = crossX1X2.z() + crossX2X3.z() + crossX3X1.z(); - n[3] = -determinantABC; - - // 0 1 2 3 - // 4 5 6 - // 7 8 - // 9 - qem[0] += n[0] * n[0]; - qem[1] += n[0] * n[1]; - qem[2] += n[0] * n[2]; - qem[3] += n[0] * n[3]; - qem[4] += n[1] * n[1]; - qem[5] += n[1] * n[2]; - qem[6] += n[1] * n[3]; - qem[7] += n[2] * n[2]; - qem[8] += n[2] * n[3]; - qem[9] += n[3] * n[3]; -} - template std::pair< @@ -549,7 +554,7 @@ std::pair< } } } - // std::cout << "# Modifications: " << nb_modifications << "\n"; + std::cout << "# Modifications: " << nb_modifications << "\n"; clusters_edges_active.swap(clusters_edges_new); } while (nb_modifications > 0); @@ -661,496 +666,51 @@ std::pair< PolygonMesh simplified_mesh; // create a point for each cluster + std::vector> cluster_quadrics(clusters.size()); + + // initialize quadrics + for (int i = 0; i < nb_clusters; i++) + cluster_quadrics[i].setZero(); + + for (Face_descriptor fd : faces(pmesh)) { + // get Vs for fd + // compute qem from Vs->"vector_3"s + // add to the 3 indices of the cluster + typename GT::Point_3 p_i = get(vpm, target(halfedge(fd, pmesh), pmesh)); + typename GT::Vector_3 vec_1 = typename GT::Vector_3(p_i.x(), p_i.y(), p_i.z()); + p_i = get(vpm, target(next(halfedge(fd, pmesh), pmesh), pmesh)); + typename GT::Vector_3 vec_2 = typename GT::Vector_3(p_i.x(), p_i.y(), p_i.z()); + p_i = get(vpm, target(next(next(halfedge(fd, pmesh), pmesh), pmesh), pmesh)); + typename GT::Vector_3 vec_3 = typename GT::Vector_3(p_i.x(), p_i.y(), p_i.z()); + + int c_1 = get(vertex_cluster_pmap, target(halfedge(fd, pmesh), pmesh)); + int c_2 = get(vertex_cluster_pmap, target(next(halfedge(fd, pmesh), pmesh), pmesh)); + int c_3 = get(vertex_cluster_pmap, target(next(next(halfedge(fd, pmesh), pmesh), pmesh), pmesh)); + + if (c_1 != -1 && c_2 != -1 && c_3 != -1) + { + Eigen::Matrix q; + compute_qem_face(vec_1, vec_2, vec_3, q); + cluster_quadrics[c_1] += q; + cluster_quadrics[c_2] += q; + cluster_quadrics[c_3] += q; + } + } + for (int i = 0; i < nb_clusters; i++) { if (clusters[i].weight_sum > 0) { valid_cluster_map[i] = points.size(); - typename GT::Vector_3 center_v = clusters[i].compute_centroid(); - typename GT::Point_3 center_p(center_v.x(), center_v.y(), center_v.z()); - points.push_back(center_p); - } - } + typename GT::Vector_3 cluster_representative = clusters[i].compute_centroid(); - // extract boundary cycles - std::vector border_hedges; - extract_boundary_cycles(pmesh, std::back_inserter(border_hedges)); + int RankDeficiency = 0; + compute_representative_point(cluster_quadrics[i], cluster_representative, RankDeficiency); - // loop over boundary loops - for (Halfedge_descriptor hd : border_hedges) - { - Halfedge_descriptor hd1 = hd; - int cb_first = -1; + typename GT::Point_3 cluster_representative_p(cluster_representative.x(), cluster_representative.y(), cluster_representative.z()); + points.push_back(cluster_representative_p); - do - { - // 1- get the target and source vertices vt, vs - // 2- if the target and source vertices are in different clusters, create a new vertex vb between them vb = (vt + vs) / 2 - // 3- make a new face with the new vertex vb and the centers of the clusters of vt and vs - // 4- also make a new face with vb, the next vb, and the center of the cluster of vt - - Vertex_descriptor vt = target(hd1, pmesh); - Vertex_descriptor vs = source(hd1, pmesh); - - int ct = get(vertex_cluster_pmap, vt); - int cs = get(vertex_cluster_pmap, vs); - - if (ct != cs) - { - typename GT::Point_3 vt_p = get(vpm, vt); - typename GT::Point_3 vs_p = get(vpm, vs); - typename GT::Vector_3 vt_v(vt_p.x(), vt_p.y(), vt_p.z()); - typename GT::Vector_3 vs_v(vs_p.x(), vs_p.y(), vs_p.z()); - - typename GT::Vector_3 vb_v = (vt_v + vs_v) / 2; - typename GT::Point_3 vb_p(vb_v.x(), vb_v.y(), vb_v.z()); - - points.push_back(vb_p); - - int cb = points.size() - 1; - - if (cb_first == -1) - cb_first = cb; - - int ct_mapped = valid_cluster_map[ct], cs_mapped = valid_cluster_map[cs]; - - if (ct_mapped != -1 && cs_mapped != -1) - { - std::vector - polygon = {ct_mapped, cb, cs_mapped}; - polygons.push_back(polygon); - - // after the loop, the last cb+1 should be modified to the first cb - polygon = {cb, ct_mapped, cb + 1}; - polygons.push_back(polygon); - } - } - hd1 = next(hd1, pmesh); - } while (hd1 != hd); - polygons[polygons.size() - 1][2] = cb_first; - } - - // create a triangle for each face with all vertices in 3 different clusters - for (Face_descriptor fd : faces(pmesh)) - { - Halfedge_descriptor hd1 = halfedge(fd, pmesh); - Vertex_descriptor v1 = source(hd1, pmesh); - Halfedge_descriptor hd2 = next(hd1, pmesh); - Vertex_descriptor v2 = source(hd2, pmesh); - Halfedge_descriptor hd3 = next(hd2, pmesh); - Vertex_descriptor v3 = source(hd3, pmesh); - - int c1 = get(vertex_cluster_pmap, v1); - int c2 = get(vertex_cluster_pmap, v2); - int c3 = get(vertex_cluster_pmap, v3); - - if (c1 != c2 && c1 != c3 && c2 != c3) - { - int c1_mapped = valid_cluster_map[c1], c2_mapped = valid_cluster_map[c2], c3_mapped = valid_cluster_map[c3]; - if (c1_mapped != -1 && c2_mapped != -1 && c3_mapped != -1) - { - std::vector polygon = {c1_mapped, c2_mapped, c3_mapped}; - polygons.push_back(polygon); - } - } - } - - orient_polygon_soup(points, polygons); - - return std::make_pair(points, polygons); -} - -/// QEM is WIP (TODO) -/// Also, TODO, need to break down both functions for modularity and DRY -template -std::pair< - std::vector::type::Point_3>, - std::vector> -> acvd_qem( - PolygonMesh& pmesh, - const int nb_clusters, - const NamedParameters& np = parameters::default_values() - ) -{ - typedef typename GetGeomTraits::type GT; - typedef typename GetVertexPointMap::const_type Vertex_position_map; - typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; - typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::property_map >::type VertexClusterMap; - typedef typename boost::property_map >::type VertexVisitedMap; - typedef typename boost::property_map >::type VertexWeightMap; - typedef typename boost::property_map> >::type VertexQEMMap; - typedef Constant_property_map> Default_principal_map; - typedef typename internal_np::Lookup_named_param_def::type Vertex_principal_curvatures_and_directions_map; - - using parameters::choose_parameter; - using parameters::get_parameter; - using parameters::is_default_parameter; - - Vertex_position_map vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), - get_property_map(CGAL::vertex_point, pmesh)); - - // get curvature related parameters - const typename GT::FT gradation_factor = choose_parameter(get_parameter(np, internal_np::gradation_factor), 0); - const Vertex_principal_curvatures_and_directions_map vpcd_map = - choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions_map), - Default_principal_map()); - - // if adaptive clustering - if (gradation_factor > 0 && - is_default_parameter::value) - interpolated_corrected_curvatures(pmesh, parameters::vertex_principal_curvatures_and_directions_map(vpcd_map)); - - CGAL_precondition(CGAL::is_triangle_mesh(pmesh)); - - // TODO: copy the mesh in order to not modify the original mesh - //PolygonMesh pmesh = pmesh_org; - - int nb_vertices = num_vertices(pmesh); - - // For remeshing, we might need to subdivide the mesh before clustering - // This shoould always hold: nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD - // So do the following till the condition is met - while (nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) - { - double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); - int subdivide_steps = (std::max)((int)ceil(log(curr_factor) / log(4)), 0); - - if (subdivide_steps > 0) - { - if (gradation_factor == 0) // no adaptive clustering - Subdivision_method_3::Upsample_subdivision( - pmesh, - CGAL::parameters::number_of_iterations(subdivide_steps) - ); - else // adaptive clustering - upsample_subdivision_property( - pmesh, - CGAL::parameters::number_of_iterations(subdivide_steps).vertex_principal_curvatures_and_directions_map(vpcd_map) - ); - vpm = get_property_map(CGAL::vertex_point, pmesh); - nb_vertices = num_vertices(pmesh); - } - } - - // creating needed property maps - VertexClusterMap vertex_cluster_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); - VertexVisitedMap vertex_visited_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); - VertexWeightMap vertex_weight_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); - VertexQEMMap vertex_qem_pmap = get(CGAL::dynamic_vertex_property_t>(), pmesh); - std::vector> clusters(nb_clusters); - std::queue clusters_edges_active; - std::queue clusters_edges_new; - - // initialize vertex weights, clusters, QEMs - std::vector temp_qem(10, 0); - for (Vertex_descriptor vd : vertices(pmesh)) - { - put(vertex_weight_pmap, vd, 0); - put(vertex_visited_pmap, vd, false); - put(vertex_cluster_pmap, vd, -1); - put(vertex_qem_pmap, vd, temp_qem); - } - - // compute vertex weights (dual area) and QEMs - typename GT::FT weight_avg = 0; - std::vector qem(10, 0); - for (Face_descriptor fd : faces(pmesh)) - { - typename GT::FT weight = abs(CGAL::Polygon_mesh_processing::face_area(fd, pmesh)) / 3; - - // for positions of the 3 vertices of the face - typename GT::Vector_3 vpv[3]; - int f_vi = 0; - for (Vertex_descriptor vd : vertices_around_face(halfedge(fd, pmesh), pmesh)) - { - typename GT::Point_3 vp = get(vpm, vd); - vpv[f_vi++] = typename GT::Vector_3(vp.x(), vp.y(), vp.z()); - } - - // compute QEM for the face - compute_triangle_QEM(vpv, qem); - - for (Vertex_descriptor vd : vertices_around_face(halfedge(fd, pmesh), pmesh)) - { - typename GT::FT vertex_weight = get(vertex_weight_pmap, vd); - std::vector vertex_qem = get(vertex_qem_pmap, vd); - - for (int i = 0; i < 10; i++) - vertex_qem[i] += qem[i]; - - if (gradation_factor == 0) // no adaptive clustering - vertex_weight += weight; - else // adaptive clustering - { - typename GT::FT k1 = get(vpcd_map, vd).min_curvature; - typename GT::FT k2 = get(vpcd_map, vd).max_curvature; - typename GT::FT k_sq = (k1 * k1 + k2 * k2); - vertex_weight += weight * pow(k_sq, gradation_factor / 2.0); // /2.0 because k_sq is squared - } - weight_avg += vertex_weight; - put(vertex_weight_pmap, vd, vertex_weight); - put(vertex_qem_pmap, vd, vertex_qem); - } - - } - weight_avg /= nb_vertices; - - // clamp the weights up and below by a ratio (like 10,000) * avg_weights - for (Vertex_descriptor vd : vertices(pmesh)) - { - typename GT::FT vertex_weight = get(vertex_weight_pmap, vd); - if (vertex_weight > CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg) - put(vertex_weight_pmap, vd, CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg); - else if (vertex_weight < 1.0 / CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg) - put(vertex_weight_pmap, vd, 1.0 / CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg); - } - - // randomly initialize clusters - for (int ci = 0; ci < nb_clusters; ci++) - { - int vi; - Vertex_descriptor vd; - do { - vi = CGAL::get_default_random().get_int(0, num_vertices(pmesh)); - vd = *(vertices(pmesh).begin() + vi); - } while (get(vertex_cluster_pmap, vd) != -1); - - put(vertex_cluster_pmap, vd, ci); - typename GT::Point_3 vp = get(vpm, vd); - typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); - clusters[ci].add_vertex(vpv, get(vertex_weight_pmap, vd), get(vertex_qem_pmap, vd)); - - for (Halfedge_descriptor hd : halfedges_around_source(vd, pmesh)) - clusters_edges_active.push(hd); - } - - // the energy minimization loop (clustering loop) - int nb_modifications = 0; - int nb_disconnected = 0; - do - { - nb_disconnected = 0; - do - { - nb_modifications = 0; - - while (!clusters_edges_active.empty()) { - Halfedge_descriptor hi = clusters_edges_active.front(); - clusters_edges_active.pop(); - - Vertex_descriptor v1 = source(hi, pmesh); - Vertex_descriptor v2 = target(hi, pmesh); - - int c1 = get(vertex_cluster_pmap, v1); - int c2 = get(vertex_cluster_pmap, v2); - - if (c1 == -1) - { - // expand cluster c2 (add v1 to c2) - put(vertex_cluster_pmap, v1, c2); - typename GT::Point_3 vp1 = get(vpm, v1); - typename GT::Vector_3 vpv(vp1.x(), vp1.y(), vp1.z()); - clusters[c2].add_vertex(vpv, get(vertex_weight_pmap, v1), get(vertex_qem_pmap, v1)); - - // add all halfedges around v1 - for (Halfedge_descriptor hd : halfedges_around_source(v1, pmesh)) - clusters_edges_new.push(hd); - nb_modifications++; - } - else if (c2 == -1) - { - // expand cluster c1 (add v2 to c1) - put(vertex_cluster_pmap, v2, c1); - typename GT::Point_3 vp2 = get(vpm, v2); - typename GT::Vector_3 vpv(vp2.x(), vp2.y(), vp2.z()); - clusters[c1].add_vertex(vpv, get(vertex_weight_pmap, v2), get(vertex_qem_pmap, v2)); - - // add all halfedges around v2 - for (Halfedge_descriptor hd : halfedges_around_source(v2, pmesh)) - clusters_edges_new.push(hd); - nb_modifications++; - } - else if (c1 == c2) - { - clusters_edges_new.push(hi); - } - else - { - // compare the energy of the 3 cases - typename GT::Point_3 vp1 = get(vpm, v1); - typename GT::Vector_3 vpv1(vp1.x(), vp1.y(), vp1.z()); - typename GT::Point_3 vp2 = get(vpm, v2); - typename GT::Vector_3 vpv2(vp2.x(), vp2.y(), vp2.z()); - typename GT::FT v1_weight = get(vertex_weight_pmap, v1); - typename GT::FT v2_weight = get(vertex_weight_pmap, v2); - std::vector v1_qem = get(vertex_qem_pmap, v1); - std::vector v2_qem = get(vertex_qem_pmap, v2); - - typename GT::FT e_no_change = clusters[c1].compute_energy() + clusters[c2].compute_energy(); - - clusters[c1].remove_vertex(vpv1, v1_weight, v1_qem); - clusters[c2].add_vertex(vpv1, v1_weight, v1_qem); - - typename GT::FT e_v1_to_c2 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); - - // reset to no change - clusters[c1].add_vertex(vpv1, v1_weight, v1_qem); - clusters[c2].remove_vertex(vpv1, v1_weight, v1_qem); - - // The effect of the following should always be reversed after the comparison - clusters[c2].remove_vertex(vpv2, v2_weight, v2_qem); - clusters[c1].add_vertex(vpv2, v2_weight, v2_qem); - - typename GT::FT e_v2_to_c1 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); - - if (e_v2_to_c1 < e_no_change && e_v2_to_c1 < e_v1_to_c2 && clusters[c2].nb_vertices > 0) // > 0 as 1 vertex was removed from c2 - { - // move v2 to c1 - put(vertex_cluster_pmap, v2, c1); - - // cluster data is already updated - - // add all halfedges around v2 - for (Halfedge_descriptor hd : halfedges_around_source(v2, pmesh)) - clusters_edges_new.push(hd); - nb_modifications++; - } - else if (e_v1_to_c2 < e_no_change && clusters[c1].nb_vertices > 2) // > 2 as 1 vertex was added to c1 - { - // move v1 to c2 - put(vertex_cluster_pmap, v1, c2); - - // need to reset cluster data and then update - clusters[c2].add_vertex(vpv2, v2_weight, v2_qem); - clusters[c1].remove_vertex(vpv2, v2_weight, v2_qem); - - clusters[c1].remove_vertex(vpv1, v1_weight, v1_qem); - clusters[c2].add_vertex(vpv1, v1_weight, v1_qem); - - // add all halfedges around v1 - for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v1, pmesh), pmesh)) - clusters_edges_new.push(hd); - nb_modifications++; - } - else - { - // no change but need to reset cluster data - clusters[c2].add_vertex(vpv2, v2_weight, v2_qem); - clusters[c1].remove_vertex(vpv2, v2_weight, v2_qem); - - clusters_edges_new.push(hi); - } - } - } - // std::cout << "# Modifications: " << nb_modifications << "\n"; - - clusters_edges_active.swap(clusters_edges_new); - } while (nb_modifications > 0); - - // Disconnected clusters handling - // the goal is to delete clusters with multiple connected components and only keep the largest connected component of each cluster - // For each cluster, do a BFS from a vertex in the cluster - - std::vector>> cluster_components(nb_clusters, std::vector>()); - std::queue vertex_queue; - - // loop over vertices to compute cluster components - for (Vertex_descriptor vd : vertices(pmesh)) - { - if (get(vertex_visited_pmap, vd)) continue; - int c = get(vertex_cluster_pmap, vd); - if (c != -1) - { - cluster_components[c].push_back(std::vector()); - int component_i = cluster_components[c].size() - 1; - - vertex_queue.push(vd); - put(vertex_visited_pmap, vd, true); - while (!vertex_queue.empty()) - { - Vertex_descriptor v = vertex_queue.front(); - vertex_queue.pop(); - cluster_components[c][component_i].push_back(v); - - for (Halfedge_descriptor hd : halfedges_around_source(v, pmesh)) - { - Vertex_descriptor v2 = target(hd, pmesh); - int c2 = get(vertex_cluster_pmap, v2); - if (c2 == c && !get(vertex_visited_pmap, v2)) - { - vertex_queue.push(v2); - put(vertex_visited_pmap, v2, true); - } - } - } - } - } - - // loop over clusters to delete disconnected components except the largest one - for (int c = 0; c < nb_clusters; c++) - { - if (cluster_components[c].size() <= 1) continue; // only one component, no need to do anything - nb_disconnected++; - int max_component_size = 0; - int max_component_index = -1; - for (int component_i = 0; component_i < cluster_components[c].size(); component_i++) - { - if (cluster_components[c][component_i].size() > max_component_size) - { - max_component_size = cluster_components[c][component_i].size(); - max_component_index = component_i; - } - } - // set cluster to -1 for all components except the largest one - for (int component_i = 0; component_i < cluster_components[c].size(); component_i++) - { - if (component_i != max_component_index) - { - for (Vertex_descriptor vd : cluster_components[c][component_i]) - { - put(vertex_cluster_pmap, vd, -1); - // remove vd from cluster c - typename GT::Point_3 vp = get(vpm, vd); - typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); - clusters[c].remove_vertex(vpv, get(vertex_weight_pmap, vd), get(vertex_qem_pmap, vd)); - // add all halfedges around v except hi to the queue - for (Halfedge_descriptor hd : halfedges_around_source(vd, pmesh)) - { - // add hd to the queue if its target is not in the same cluster - Vertex_descriptor v2 = target(hd, pmesh); - int c2 = get(vertex_cluster_pmap, v2); - if (c2 != c) - clusters_edges_new.push(hd); - } - } - } - } - } - - } while (nb_disconnected > 0); - - /// Construct new Mesh - std::vector valid_cluster_map(nb_clusters, -1); - std::vector points; - - std::vector> polygons; - PolygonMesh simplified_mesh; - - // create a point for each cluster - for (int i = 0; i < nb_clusters; i++) - { - if (clusters[i].weight_sum > 0) - { - valid_cluster_map[i] = points.size(); - typename GT::Vector_3 center_v = clusters[i].compute_centroid(); - typename GT::Point_3 center_p(center_v.x(), center_v.y(), center_v.z()); - points.push_back(center_p); } } @@ -1395,43 +955,6 @@ PolygonMesh acvd_isotropic_simplification( return simplified_mesh; } -/// QEM is WIP (TODO) -template - std::pair< - std::vector::type::Point_3>, - std::vector> - > acvd_qem_simplification_polygon_soup( - PolygonMesh& pmesh, - const int& nb_vertices, - const NamedParameters& np = parameters::default_values() - ) -{ - return internal::acvd_qem( - pmesh, - nb_vertices, - np - ); -} - -template - PolygonMesh acvd_qem_simplification( - PolygonMesh& pmesh, - const int& nb_vertices, - const NamedParameters& np = parameters::default_values() - ) -{ - auto ps = acvd_qem_simplification_polygon_soup( - pmesh, - nb_vertices, - np - ); - - PolygonMesh simplified_mesh; - polygon_soup_to_polygon_mesh(ps.first, ps.second, simplified_mesh); - return simplified_mesh; -} } // namespace Polygon_mesh_processing From 6e145b23d6cff85668b196fa1a8bdb9864ea2284 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:33:28 +0200 Subject: [PATCH 46/56] double -> typename GT::FT --- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 5b0b8479525..28fd27ff250 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -51,12 +51,12 @@ namespace Polygon_mesh_processing { namespace internal { template -void compute_qem_face(const typename GT::Vector_3& p1, const typename GT::Vector_3& p2, const typename GT::Vector_3& p3, Eigen::Matrix& quadric) +void compute_qem_face(const typename GT::Vector_3& p1, const typename GT::Vector_3& p2, const typename GT::Vector_3& p3, Eigen::Matrix& quadric) { typename GT::Vector_3 crossX1X2 = CGAL::cross_product(p1, p2); typename GT::Vector_3 crossX2X3 = CGAL::cross_product(p2, p3); typename GT::Vector_3 crossX3X1 = CGAL::cross_product(p3, p1); - double determinantABC = CGAL::determinant(p1, p2, p3); + typename GT::FT determinantABC = CGAL::determinant(p1, p2, p3); typename GT::FT n[4] = { crossX1X2.x() + crossX2X3.x() + crossX3X1.x(), @@ -71,29 +71,23 @@ void compute_qem_face(const typename GT::Vector_3& p1, const typename GT::Vector } template -void compute_qem_vertex(std::vector> cluster_tris, Eigen::Matrix& quadric, typename GT::Vector_3& centroid) +void compute_qem_vertex(std::vector> cluster_tris, Eigen::Matrix& quadric) { quadric.setZero(); - centroid = typename GT::Vector_3(0, 0, 0); - for (int i = 0; i < cluster_tris.size(); i++) - { - centroid = centroid + (cluster_tris[i][0] + cluster_tris[i][1] + cluster_tris[i][2]); - } - centroid = centroid / 3; for (int i = 0; i < cluster_tris.size(); i++) { - Eigen::Matrix q; + Eigen::Matrix q; compute_qem_face(cluster_tris[i][0], cluster_tris[i][1], cluster_tris[i][2], q); quadric = quadric + q; } } template -typename GT::Vector_3 compute_displacement(const Eigen::Matrix& quadric, const typename GT::Vector_3& p, int& RankDeficiency) +typename GT::Vector_3 compute_displacement(const Eigen::Matrix quadric, const typename GT::Vector_3& p, int& RankDeficiency) { - typedef Eigen::Matrix Matrix4d; - typedef Eigen::Matrix Matrix3d; + typedef Eigen::Matrix Matrix4d; + typedef Eigen::Matrix Matrix3d; int MaxNumberOfUsedSingularValues = 3; @@ -115,22 +109,22 @@ typename GT::Vector_3 compute_displacement(const Eigen::Matrix& qu w = typename GT::Vector_3(w_temp(0), w_temp(1), w_temp(2)); // compute all eigen values absolute values - double AbsolutesEigenValues[3]; - double maxW = -1.0; + typename GT::FT AbsolutesEigenValues[3]; + typename GT::FT maxW = -1.0; for (int j = 0; j < 3; j++) { - double AbsoluteEigenValue = fabs(w[j]); + typename GT::FT AbsoluteEigenValue = fabs(w[j]); AbsolutesEigenValues[j] = AbsoluteEigenValue; if (AbsoluteEigenValue > maxW) maxW = AbsoluteEigenValue; } - double invmaxW = 1.0 / maxW; + typename GT::FT invmaxW = 1.0 / maxW; Matrix3d tempMatrix, tempMatrix2; for (int i = 0; i < 3; i++) { - double LocalMaxW = -1; + typename GT::FT LocalMaxW = -1; int IndexMax = -1; // find the remaining eigenvalue with highest absolute value @@ -147,7 +141,7 @@ typename GT::Vector_3 compute_displacement(const Eigen::Matrix& qu && (MaxNumberOfUsedSingularValues > 0)) { // If this is true, then w[i] != 0, so this division is ok. - double Inv = 1.0 / w[IndexMax]; + typename GT::FT Inv = 1.0 / w[IndexMax]; tempMatrix(IndexMax, 0) = U(0, IndexMax) * Inv; tempMatrix(IndexMax, 1) = U(1, IndexMax) * Inv; tempMatrix(IndexMax, 2) = U(2, IndexMax) * Inv; @@ -166,16 +160,16 @@ typename GT::Vector_3 compute_displacement(const Eigen::Matrix& qu } tempMatrix2 = VT * tempMatrix; - Eigen::Matrix p_temp, b; + Eigen::Matrix p_temp, b; b << -quadric(0, 3), -quadric(1, 3), -quadric(2, 3); p_temp << p.x(), p.y(), p.z(); - Eigen::Matrix displacement_temp = tempMatrix2 * (b - A * p_temp); + Eigen::Matrix displacement_temp = tempMatrix2 * (b - A * p_temp); typename GT::Vector_3 displacement = { displacement_temp(0), displacement_temp(1), displacement_temp(2) }; return displacement; } template -void compute_representative_point(const Eigen::Matrix& quadric, typename GT::Vector_3& p, int& RankDeficiency) +void compute_representative_point(const Eigen::Matrix& quadric, typename GT::Vector_3& p, int& RankDeficiency) { // average point typename GT::Vector_3 displacement = compute_displacement(quadric, p, RankDeficiency); @@ -345,7 +339,7 @@ std::pair< // So do the following till the condition is met while (nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) { - double curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); + typename GT::FT curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); int subdivide_steps = (std::max)((int)ceil(log(curr_factor) / log(4)), 0); if (subdivide_steps > 0) @@ -666,7 +660,7 @@ std::pair< PolygonMesh simplified_mesh; // create a point for each cluster - std::vector> cluster_quadrics(clusters.size()); + std::vector> cluster_quadrics(clusters.size()); // initialize quadrics for (int i = 0; i < nb_clusters; i++) @@ -689,7 +683,7 @@ std::pair< if (c_1 != -1 && c_2 != -1 && c_3 != -1) { - Eigen::Matrix q; + Eigen::Matrix q; compute_qem_face(vec_1, vec_2, vec_3, q); cluster_quadrics[c_1] += q; cluster_quadrics[c_2] += q; @@ -705,7 +699,7 @@ std::pair< typename GT::Vector_3 cluster_representative = clusters[i].compute_centroid(); int RankDeficiency = 0; - compute_representative_point(cluster_quadrics[i], cluster_representative, RankDeficiency); + //compute_representative_point(cluster_quadrics[i], cluster_representative, RankDeficiency); typename GT::Point_3 cluster_representative_p(cluster_representative.x(), cluster_representative.y(), cluster_representative.z()); From e417a32a9f3b0784311ecdb7912241545b51ba8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 27 Mar 2024 18:36:34 +0100 Subject: [PATCH 47/56] doc improvements --- .../PackageDescription.txt | 3 +- .../Polygon_mesh_processing.txt | 24 ++- .../Polygon_mesh_processing/acvd_example.cpp | 4 +- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 162 +++++++----------- 4 files changed, 73 insertions(+), 120 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 6fcc71621c5..4d50b86cfb8 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -139,8 +139,7 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage. - `CGAL::Polygon_mesh_processing::random_perturbation()` \cgalCRPSection{ACVD Simplification Functions} -- \link PMP_acvd_grp `CGAL::Polygon_mesh_processing::acvd_isotropic_simplification()` \endlink -- \link PMP_acvd_grp `CGAL::Polygon_mesh_processing::acvd_isotropic_simplification_polygon_soup()` \endlink +- \link PMP_acvd_grp `CGAL::Polygon_mesh_processing::acvd_isotropic_remeshing()` \endlink \cgalCRPSection{Sizing Fields} - `CGAL::Polygon_mesh_processing::Uniform_sizing_field` diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt index af6cd3bbb62..76212ba5b67 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt @@ -53,7 +53,7 @@ mesh, which includes point location and self intersection tests. - \ref PMPSlicer : functor able to compute the intersections of a polygon mesh with arbitrary planes (slicer). - \ref PMPConnectedComponents : methods to deal with connected components of a polygon mesh (extraction, marks, removal, ...). -- \ref PMPACVD : methods to simplify or remesh a polygon mesh using approximated centroidal Voronoi diagrams +- \ref PMPACVD : methods to remesh a polygon mesh using approximated centroidal Voronoi diagrams as described in \cgalCite{cgal:vcp-grtmmdvd-08} and preceeding work. \subsection PMPIO Reading and Writing Polygon Meshes @@ -1313,10 +1313,10 @@ which enables to treat one or several connected components as a face graph. \cgalExample{Polygon_mesh_processing/face_filtered_graph_example.cpp} -\section PMPACVD ACVD Simplification and Remeshing +\section PMPACVD ACVD Remeshing -The Approximated Centroidal Voronoi Diagram (ACVD) package is a set of vertex-clustering-based tools to simplify -and remesh a polygon mesh. It is based on the method introduced in \cgalCite{cgal:vc-acvdupmc-04} and extended +The Approximated Centroidal Voronoi Diagram (ACVD) package is a set of vertex-clustering-based tools to +remesh a triangle mesh. It is based on the method introduced in \cgalCite{cgal:vc-acvdupmc-04} and extended in \cgalCite{cgal:vkc-apmsdcvd-05} and \cgalCite{cgal:vcp-grtmmdvd-08}. \subsection ACVDBackground Brief Background @@ -1328,16 +1328,14 @@ WIP The implementation is generic in terms of mesh data structure. It can be used on `Surface_mesh`, `Polyhedron_3` and other triangle mesh structures based on the concept `FaceGraph`. -The main function is `CGAL::Polygon_mesh_processing::acvd_isotropic_simplification()`. It takes as input a triangle mesh and -a target number of vertices and returns a remeshed version of the input mesh with the target number of vertices. - -The returned mesh can be triangle mesh in the same data structure as the input mesh representing a `FaceGraph`. -Or a polygon (triangle) soup (as a `pair, std::vector>>)`. For the latter, -use the function `CGAL::Polygon_mesh_processing::acvd_isotropic_simplification_polygon_soup()` instead. The reason for -having a polygon soup version is that there are no guarantees that the output mesh is manifold. +The main function is `CGAL::Polygon_mesh_processing::acvd_isotropic_remeshing()`. It takes as input a triangle mesh and +a lower bound on the target number of vertices and returns a remeshed version. The number of vertices in the output mesh +might be larger than the input parameters if the input is not closed or if the budget of points provided is too low +to generate a manifold output mesh. Note that providing a initial low number of vertices will affect the uniformity +of the output triangles in case some extra points are added to make the output manifold. To enable Adaptive Remeshing, the named parameter `gradation_factor` can be used. It controls the sensitivity of the -clustering algorithm to curvature. If a value of 0 is used, the algorithm is not adaptive. Moreover, the named parameter +clustering algorithm to curvature. Moreover, the named parameter `vertex_principal_curvatures_and_directions_map` can be used to provide a user-defined principal curvature map. For Clustering based on QEM, the function `CGAL::Polygon_mesh_processing::acvd_qem_simplification()` and @@ -1350,7 +1348,7 @@ WIP \subsection ACVDExample Usage Example -The example below shows the usage of the `CGAL::Polygon_mesh_processing::acvd_isotropic_simplification()` function +The example below shows the usage of the `CGAL::Polygon_mesh_processing::acvd_isotropic_remeshing()` function and how the extra named parameters can be passed. \cgalExample{Polygon_mesh_processing/acvd_example.cpp} diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp index 41da1260f24..c6a65b3b414 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -36,7 +36,7 @@ int main(int argc, char* argv[]) std::cout << "Uniform Isotropic ACVD ...." << std::endl; - auto acvd_mesh = PMP::acvd_isotropic_simplification(smesh, nb_clusters); + auto acvd_mesh = PMP::acvd_isotropic_remeshing(smesh, nb_clusters); CGAL::IO::write_OFF("fandisk_qem-pp3000.off", acvd_mesh); std::cout << "Completed" << std::endl; @@ -62,7 +62,7 @@ int main(int argc, char* argv[]) //PMP::interpolated_corrected_curvatures(smesh, CGAL::parameters::vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map)); //auto adaptive_acvd_mesh = - // PMP::acvd_isotropic_simplification( + // PMP::acvd_isotropic_remeshing( // smesh, // nb_clusters, // CGAL::parameters::vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 5b0b8479525..dbdb7ef8402 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -220,11 +220,11 @@ struct IsotropicClusterData { } }; -template -void upsample_subdivision_property(PolygonMesh& pmesh, const NamedParameters& np = parameters::default_values()) { - typedef typename GetGeomTraits::type GT; - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; - typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; +template +void upsample_subdivision_property(TriangleMesh& pmesh, const NamedParameters& np = parameters::default_values()) { + typedef typename GetGeomTraits::type GT; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; typedef Constant_property_map> Default_principal_map; typedef typename internal_np::Lookup_named_param_def::type VPM; + typedef typename CGAL::GetVertexPointMap::type VPM; VPM vpm = choose_parameter(get_parameter(np, internal_np::vertex_point), get_property_map(CGAL::vertex_point, pmesh)); @@ -249,7 +249,7 @@ void upsample_subdivision_property(PolygonMesh& pmesh, const NamedParameters& np bool curvatures_available = !is_default_parameter::value; unsigned int step = choose_parameter(get_parameter(np, internal_np::number_of_iterations), 1); - Upsample_mask_3 mask(&pmesh, vpm); + Upsample_mask_3 mask(&pmesh, vpm); for (unsigned int i = 0; i < step; i++){ for (Vertex_descriptor vd : vertices(pmesh)) @@ -291,26 +291,26 @@ void upsample_subdivision_property(PolygonMesh& pmesh, const NamedParameters& np } } -template std::pair< - std::vector::type::Point_3>, + std::vector::type::Point_3>, std::vector> > acvd_isotropic( - PolygonMesh& pmesh, + TriangleMesh& pmesh, const int nb_clusters, const NamedParameters& np = parameters::default_values() ) { - typedef typename GetGeomTraits::type GT; - typedef typename GetVertexPointMap::const_type Vertex_position_map; - typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; - typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::property_map >::type VertexColorMap; - typedef typename boost::property_map >::type VertexClusterMap; - typedef typename boost::property_map >::type VertexVisitedMap; - typedef typename boost::property_map >::type VertexWeightMap; + typedef typename GetGeomTraits::type GT; + typedef typename GetVertexPointMap::const_type Vertex_position_map; + typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename boost::graph_traits::face_descriptor Face_descriptor; + typedef typename boost::property_map >::type VertexColorMap; + typedef typename boost::property_map >::type VertexClusterMap; + typedef typename boost::property_map >::type VertexVisitedMap; + typedef typename boost::property_map >::type VertexWeightMap; typedef Constant_property_map> Default_principal_map; typedef typename internal_np::Lookup_named_param_def points; std::vector> polygons; - PolygonMesh simplified_mesh; + TriangleMesh simplified_mesh; // create a point for each cluster std::vector> cluster_quadrics(clusters.size()); @@ -806,104 +806,51 @@ std::pair< } // namespace internal -/** -* \ingroup PMP_acvd_grp -* -* Performs uniform (isotropic) centroidal voronoi diagram simplification on a polygon mesh. -* This can also be used for remeshing by setting the number of clusters to the desired number of vertices. -* The number of clusters is the number of vertices in the output mesh. -* -* @tparam PolygonMesh a model of `FaceListGraph` -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". -* -* @param pmesh input polygon mesh -* @param nb_vertices number of target vertices in the output mesh -* @param np optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below -* `GT` stands for the type of the object provided to the named parameter `geom_traits()`. -* -* \cgalNamedParamsBegin -* -* \cgalParamNBegin{vertex_principal_curvatures_and_directions_map} -* \cgalParamDescription{a property map associating principal curvatures and directions to the vertices of `pmesh`, used for adaptive clustering.} -* \cgalParamType{a class model of `ReadWritePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` -* as key type and `Principal_curvatures_and_directions` as value type.} -* \cgalParamExtra{If this parameter is omitted, but `gradation_factor` is not (and is > 0), an internal property map -* will be created and curvature values will be computed.} -* \cgalParamNEnd -* -* \cgalParamNBegin{gradation_factor} -* \cgalParamDescription{a factor used to gradate the weights of the vertices based on their curvature values.} -* \cgalParamType{`GT::FT`} -* \cgalParamDefault{0} -* \cgalParamExtra{If this parameter is omitted, no adaptive clustering will be performed.} -* \cgalParamNEnd -* -* \cgalParamNBegin{vertex_point_map} -* \cgalParamDescription{a property map associating points to the vertices of `pmesh`.} -* \cgalParamType{a class model of `ReadablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` -* as key type and `GT::Point_3` as value type.} -* \cgalParamDefault{`boost::get(CGAL::vertex_point, pmesh)`.} -* \cgalParamExtra{If this parameter is omitted, an internal property map for -* `CGAL::vertex_point_t` must be available in `PolygonMesh`.} -* \cgalParamNEnd -* -* \cgalParamNBegin{geom_traits} -* \cgalParamDescription{an instance of a geometric traits class.} -* \cgalParamType{a class model of `Kernel`} -* \cgalParamDefault{a \cgal Kernel deduced from the point type, using `CGAL::Kernel_traits`.} -* \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.} -* \cgalParamNEnd -* -* \cgalNamedParamsEnd -* -* @pre only triangle meshes are supported for now -* @return a pair of vectors of points and polygons representing the simplified mesh as a polygon soup -*/ - -template std::pair< - std::vector::type::Point_3>, + std::vector::type::Point_3>, std::vector> > acvd_isotropic_simplification_polygon_soup( - PolygonMesh& pmesh, + TriangleMesh& tmesh, const int& nb_vertices, const NamedParameters& np = parameters::default_values() ) { - return internal::acvd_isotropic( - pmesh, + return internal::acvd_isotropic( + tmesh, nb_vertices, np ); } +#endif /** * \ingroup PMP_acvd_grp * -* Performs uniform (isotropic) centroidal voronoi diagram simplification on a polygon mesh. -* This can also be used for remeshing by setting the number of clusters to the desired number of vertices. -* The number of clusters is the number of vertices in the output mesh. +* performs isotropic centroidal voronoi diagram remeshing on a triangle mesh. The remeshing is either uniform or adaptative. * -* @tparam PolygonMesh a model of `FaceListGraph` +* @tparam TriangleMesh a model of `FaceListGraph` * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * -* @param pmesh input polygon mesh -* @param nb_vertices number of target vertices in the output mesh +* @param tmesh input triangle mesh +* @param nb_vertices lower bound on the number of target vertices in the output mesh. +* In the case the mesh is not closed or if the number of points is too low +* and no manifold mesh could be produced with that budget of points, extra points +* are added to get a manifold output. * @param np optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * `GT` stands for the type of the object provided to the named parameter `geom_traits()`. * * \cgalNamedParamsBegin * * \cgalParamNBegin{vertex_principal_curvatures_and_directions_map} -* \cgalParamDescription{a property map associating principal curvatures and directions to the vertices of `pmesh`, used for adaptive clustering.} +* \cgalParamDescription{a property map associating principal curvatures and directions to the vertices of `tmesh`, used for adaptive clustering.} * \cgalParamType{a class model of `ReadWritePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` +* `boost::graph_traits::%vertex_descriptor` * as key type and `Principal_curvatures_and_directions` as value type.} -* \cgalParamExtra{If this parameter is omitted, but `gradation_factor` is not (and is > 0), an internal property map -* will be created and curvature values will be computed.} +* \cgalParamExtra{If this parameter is omitted, but `gradation_factor` is provided, an internal property map +* will be created and curvature values will be computed using the function `interpolated_corrected_curvatures()` will be called to initialize it.} * \cgalParamNEnd * * \cgalParamNBegin{gradation_factor} @@ -914,13 +861,13 @@ std::pair< * \cgalParamNEnd * * \cgalParamNBegin{vertex_point_map} -* \cgalParamDescription{a property map associating points to the vertices of `pmesh`.} +* \cgalParamDescription{a property map associating points to the vertices of `tmesh`.} * \cgalParamType{a class model of `ReadablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` +* `boost::graph_traits::%vertex_descriptor` * as key type and `GT::Point_3` as value type.} -* \cgalParamDefault{`boost::get(CGAL::vertex_point, pmesh)`.} +* \cgalParamDefault{`boost::get(CGAL::vertex_point, tmesh)`.} * \cgalParamExtra{If this parameter is omitted, an internal property map for -* `CGAL::vertex_point_t` must be available in `PolygonMesh`.} +* `CGAL::vertex_point_t` must be available in `TriangleMesh`.} * \cgalParamNEnd * * \cgalParamNBegin{geom_traits} @@ -933,24 +880,30 @@ std::pair< * \cgalNamedParamsEnd * * @pre only triangle meshes are supported for now -* @return the simplified mesh as a PolygonMesh +* @return the simplified mesh as a TriangleMesh +* +* @todo how is uniform affected by input mesh ? (check area based sampling?) +* @todo implement manifold version +* @todo how to handle output vertex point map */ -template -PolygonMesh acvd_isotropic_simplification( - PolygonMesh& pmesh, +template +TriangleMesh acvd_isotropic_remeshing( + TriangleMesh& tmesh, const int& nb_vertices, const NamedParameters& np = parameters::default_values() ) { auto ps = acvd_isotropic_simplification_polygon_soup( - pmesh, + tmesh, nb_vertices, np ); - PolygonMesh simplified_mesh; + CGAL_assertion(is_polygon_soup_a_polygon_mesh(ps.second)); + + TriangleMesh simplified_mesh; polygon_soup_to_polygon_mesh(ps.first, ps.second, simplified_mesh); return simplified_mesh; } @@ -959,3 +912,6 @@ PolygonMesh acvd_isotropic_simplification( } // namespace Polygon_mesh_processing } // namespace CGAL + +#undef CGAL_CLUSTERS_TO_VERTICES_THRESHOLD +#undef CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD \ No newline at end of file From 97102e32f79f4b7bcf4d5cce6c0ca3450c34c2d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 27 Mar 2024 18:47:18 +0100 Subject: [PATCH 48/56] add license file --- .../license/Polygon_mesh_processing/acvd.h | 54 +++++++++++++++++++ .../include/CGAL/license/gpl_package_list.txt | 2 +- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 12 +++-- 3 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 Installation/include/CGAL/license/Polygon_mesh_processing/acvd.h diff --git a/Installation/include/CGAL/license/Polygon_mesh_processing/acvd.h b/Installation/include/CGAL/license/Polygon_mesh_processing/acvd.h new file mode 100644 index 00000000000..eb65e39c3b8 --- /dev/null +++ b/Installation/include/CGAL/license/Polygon_mesh_processing/acvd.h @@ -0,0 +1,54 @@ +// Copyright (c) 2016 GeometryFactory SARL (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org) +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Andreas Fabri +// +// Warning: this file is generated, see include/CGAL/license/README.md + +#ifndef CGAL_LICENSE_POLYGON_MESH_PROCESSING_ACVD_H +#define CGAL_LICENSE_POLYGON_MESH_PROCESSING_ACVD_H + +#include +#include + +#ifdef CGAL_POLYGON_MESH_PROCESSING_ACVD_COMMERCIAL_LICENSE + +# if CGAL_POLYGON_MESH_PROCESSING_ACVD_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE + +# if defined(CGAL_LICENSE_WARNING) + + CGAL_pragma_warning("Your commercial license for CGAL does not cover " + "this release of the Polygon Mesh Processing - ACVD remeshing package.") +# endif + +# ifdef CGAL_LICENSE_ERROR +# error "Your commercial license for CGAL does not cover this release \ + of the Polygon Mesh Processing - ACVD remeshing package. \ + You get this error, as you defined CGAL_LICENSE_ERROR." +# endif // CGAL_LICENSE_ERROR + +# endif // CGAL_POLYGON_MESH_PROCESSING_ACVD_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE + +#else // no CGAL_POLYGON_MESH_PROCESSING_ACVD_COMMERCIAL_LICENSE + +# if defined(CGAL_LICENSE_WARNING) + CGAL_pragma_warning("\nThe macro CGAL_POLYGON_MESH_PROCESSING_ACVD_COMMERCIAL_LICENSE is not defined." + "\nYou use the CGAL Polygon Mesh Processing - ACVD remeshing package under " + "the terms of the GPLv3+.") +# endif // CGAL_LICENSE_WARNING + +# ifdef CGAL_LICENSE_ERROR +# error "The macro CGAL_POLYGON_MESH_PROCESSING_ACVD_COMMERCIAL_LICENSE is not defined.\ + You use the CGAL Polygon Mesh Processing - ACVD remeshing package under the terms of \ + the GPLv3+. You get this error, as you defined CGAL_LICENSE_ERROR." +# endif // CGAL_LICENSE_ERROR + +#endif // no CGAL_POLYGON_MESH_PROCESSING_ACVD_COMMERCIAL_LICENSE + +#endif // CGAL_LICENSE_POLYGON_MESH_PROCESSING_ACVD_H diff --git a/Installation/include/CGAL/license/gpl_package_list.txt b/Installation/include/CGAL/license/gpl_package_list.txt index bacaaaf7111..44dfd382635 100644 --- a/Installation/include/CGAL/license/gpl_package_list.txt +++ b/Installation/include/CGAL/license/gpl_package_list.txt @@ -65,8 +65,8 @@ Polygon_mesh_processing/geometric_repair Polygon Mesh Processing - Geometric Rep Polygon_mesh_processing/miscellaneous Polygon Mesh Processing - Miscellaneous Polygon_mesh_processing/detect_features Polygon Mesh Processing - Feature Detection Polygon_mesh_processing/collision_detection Polygon Mesh Processing - Collision Detection -Polygon_mesh_processing/remeshing Polygon Mesh Processing - ACVD Simplification Polygon_mesh_processing/autorefinement Polygon Mesh Processing - Autorefinement +Polygon_mesh_processing/acvd Polygon Mesh Processing - ACVD remeshing Polyhedron 3D Polyhedral Surface Polyline_simplification_2 2D Polyline Simplification Polytope_distance_d Optimal Distances diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index dbdb7ef8402..05c7e087f6d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -11,10 +11,10 @@ // Author(s) : Hossam Saeed // -/// TODO: -// #ifndef CGAL_<> -// #define CGAL_<> -// #include > +#ifndef CGAL_PMP_ACVD_REMESHING_H +#define CGAL_PMP_ACVD_REMESHING_H + +#include #include #include @@ -914,4 +914,6 @@ TriangleMesh acvd_isotropic_remeshing( } // namespace CGAL #undef CGAL_CLUSTERS_TO_VERTICES_THRESHOLD -#undef CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD \ No newline at end of file +#undef CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD + +#endif // CGAL_PMP_ACVD_REMESHING_H From 83f06b54612dab5b4820227377f7a6a3eed823e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 27 Mar 2024 18:54:33 +0100 Subject: [PATCH 49/56] remove unused file --- .../acvd/qem_metrics.h | 272 ------------------ .../CGAL/Polygon_mesh_processing/acvd/types.h | 54 ---- 2 files changed, 326 deletions(-) delete mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/qem_metrics.h delete mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/types.h diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/qem_metrics.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/qem_metrics.h deleted file mode 100644 index 34a772f474a..00000000000 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/qem_metrics.h +++ /dev/null @@ -1,272 +0,0 @@ -#ifndef QEM_METRIC_H -#define QEM_METRIC_H - -#include -#include -#include -#include "types.h" - -typedef CGAL::Aff_transformation_3 Aff_transformation; -//template -class QEM_metric -{ -public: - typedef Eigen::Matrix EVec10d; - typedef Eigen::VectorXd VectorXd; - typedef Eigen::MatrixXd MatrixXd; - typedef Eigen::Matrix Matrix3d; - typedef Eigen::Matrix Matrix4d; - -private: // members - - EVec10d m_tensor; - - /* - n n^T | -nnq^T - -nnq | (nq)^2 - - aa ab ac ad - ab bb bc bd - ac bc cc cd - ad bd cd dd - - m_tensor[0]: aa, m_tensor[1]: ab, m_tensor[2]: ac, m_tensor[3]: ad - m_tensor[4]: bb, m_tensor[5]: bc, m_tensor[6]: bd, m_tensor[7]: cc - m_tensor[8]: cd, m_tensor[9]: dd - - m_tensor[0] m_tensor[1] m_tensor[2] m_tensor[3] - m_tensor[1] m_tensor[4] m_tensor[5] m_tensor[6] - m_tensor[2] m_tensor[5] m_tensor[7] m_tensor[8] - m_tensor[3] m_tensor[6] m_tensor[8] m_tensor[9] - */ - -public: // functions - - QEM_metric(): m_tensor(EVec10d::Zero()) {} - - EVec10d& tensors() { return m_tensor; } - const EVec10d& tensors() const { return m_tensor; } - - /*MatrixXd 4x4_matrix() - { - MatrixXd qem(4, 4); - - qem << m_tensor[0], m_tensor[1], m_tensor[2], m_tensor[3], - m_tensor[1], m_tensor[4], m_tensor[5], m_tensor[6], - m_tensor[2], m_tensor[5], m_tensor[7], m_tensor[8], - m_tensor[3], m_tensor[6], m_tensor[8], m_tensor[9]; - - return qem; - }*/ - - Matrix4d get_4x4_svd_matrix() - { - MatrixXd qem(4, 4); - - qem << m_tensor[0], m_tensor[1], m_tensor[2], m_tensor[3], - m_tensor[1], m_tensor[4], m_tensor[5], m_tensor[6], - m_tensor[2], m_tensor[5], m_tensor[7], m_tensor[8], - 0. , 0. , 0. , 1.; - - return qem; - } - - Matrix4d get_4x4_matrix() - { - MatrixXd qem(4, 4); - - qem << m_tensor[0], m_tensor[1], m_tensor[2], m_tensor[3], - m_tensor[1], m_tensor[4], m_tensor[5], m_tensor[6], - m_tensor[2], m_tensor[5], m_tensor[7], m_tensor[8], - m_tensor[3], m_tensor[6], m_tensor[8], m_tensor[9]; - - return qem; - } - - Matrix3d get_3x3_matrix() - { - MatrixXd qem(3, 3); - - qem << m_tensor[0], m_tensor[1], m_tensor[2], - m_tensor[1], m_tensor[4], m_tensor[5], - m_tensor[2], m_tensor[5], m_tensor[7]; - - return qem; - } - /// @brief Compute the tensor using a point and its normal and weighted by the area - /// @param area - /// @param query - /// @param normal - void init_qem_metrics_face(const double& area, const Point& query, const Vector& normal) - { - double a = normal.x(); - double b = normal.y(); - double c = normal.z(); - double d = -1. * CGAL::scalar_product(normal, (query - CGAL::ORIGIN)); - - m_tensor[0] = a * a; - m_tensor[1] = a * b; - m_tensor[2] = a * c; - m_tensor[3] = a * d; - m_tensor[4] = b * b; - m_tensor[5] = b * c; - m_tensor[6] = b * d; - m_tensor[7] = c * c; - m_tensor[8] = c * d; - m_tensor[9] = d * d; - - m_tensor = m_tensor * area; - } - /// @brief Compute the tensor (probabilistic qem) using a point and its normal and weighted by the area - /// @param area - /// @param mean_query - /// @param std_query - /// @param mean_normal - /// @param std_normal - void init_proba_qem_metrics_face(const double area, - const Point& mean_query, - const double std_query, - const Vector& mean_normal, - const double std_normal) - { - double sn2 = std_normal * std_normal; - double sq2 = std_query * std_query; - double dot_nq = CGAL::scalar_product(mean_normal, (mean_query - CGAL::ORIGIN)); - - m_tensor[0] = mean_normal.x() * mean_normal.x() + sn2; - m_tensor[1] = mean_normal.x() * mean_normal.y(); - m_tensor[2] = mean_normal.x() * mean_normal.z(); - m_tensor[4] = mean_normal.y() * mean_normal.y() + sn2; - m_tensor[5] = mean_normal.y() * mean_normal.z(); - m_tensor[7] = mean_normal.z() * mean_normal.z() + sn2; - - Vector vec_nnq = mean_normal * dot_nq + (mean_query - CGAL::ORIGIN) * sn2; - m_tensor[3] = -vec_nnq.x(); - m_tensor[6] = -vec_nnq.y(); - m_tensor[8] = -vec_nnq.z(); - - m_tensor[9] = dot_nq * dot_nq + sn2 * CGAL::scalar_product(mean_query - CGAL::ORIGIN, mean_query - CGAL::ORIGIN) + - sq2 * CGAL::scalar_product(mean_normal, mean_normal) + 3 * sq2 * sn2; - - m_tensor = m_tensor * area; - } - /// @brief Compute the qem tensor for a query point and a list of areas and normals - /// @param query - /// @param areas - /// @param normals - void init_qem_metrics_vertex(Point& query, - const std::vector& areas, - const std::vector& normals) - { - assert(areas.size() == normals.size()); - - for(int i = 0; i < areas.size(); i++) - { - Vector normal = normals[i]; - double area = areas[i]; - - double a = normal.x(); - double b = normal.y(); - double c = normal.z(); - double d = -1. * CGAL::scalar_product(normal, (query - CGAL::ORIGIN)); - - m_tensor[0] += area * a * a; - m_tensor[1] += area * a * b; - m_tensor[2] += area * a * c; - m_tensor[3] += area * a * d; - m_tensor[4] += area * b * b; - m_tensor[5] += area * b * c; - m_tensor[6] += area * b * d; - m_tensor[7] += area * c * c; - m_tensor[8] += area * c * d; - m_tensor[9] += area * d * d; - } - } - /// @brief Compute the affine transformation on a sphere to display the qem error as en ellipsoid - /// @return the affine transformation - Aff_transformation aff_transform_sphere() - { - - MatrixXd A(3, 3); - A << m_tensor[0], m_tensor[1], m_tensor[2], - m_tensor[1], m_tensor[4], m_tensor[5], - m_tensor[2], m_tensor[5], m_tensor[7]; - - Eigen::EigenSolver es(A); - // eigenvalues - VectorXd D = es.eigenvalues().real(); - D = D.cwiseMax(1e-5).cwiseInverse(); // take inverse - MatrixXd D_inv = MatrixXd::Zero(3, 3); - D_inv.diagonal() = D / D.sum(); // normalization - // eigenvectors - MatrixXd V = es.eigenvectors().real(); - // new matrix - MatrixXd new_trans = V * D_inv * V.transpose(); - Aff_transformation aff( new_trans(0, 0), new_trans(0, 1), new_trans(0, 2), - new_trans(1, 0), new_trans(1, 1), new_trans(1, 2), - new_trans(2, 0), new_trans(2, 1), new_trans(2, 2)); - - return aff; - } - - QEM_metric& operator+(const QEM_metric& other) - { - this->tensors() = this->tensors() + other.tensors(); - return *this; - } - - QEM_metric& operator-(const QEM_metric& other) - { - this->tensors() = this->tensors() - other.tensors(); - return *this; - } - - QEM_metric& operator*(const double& scale) - { - this->tensors() = this->tensors() * scale; - return *this; - } - /// @brief Compute optimal point using either SVD or the direct inverse - /// @param cluster_qem - /// @param cluster_pole - /// @return the optimal point - Point compute_optimal_point(QEM_metric& cluster_qem, Point& cluster_pole) - { - // solve Qx = b - Eigen::MatrixXd qem_mat = cluster_qem.get_4x4_svd_matrix(); - Eigen::VectorXd qem_vec = qem_mat.row(3); // 0., 0., 0., 1. - Eigen::VectorXd optim(4); - - // check rank - Eigen::FullPivLU lu_decomp(qem_mat); - lu_decomp.setThreshold(1e-5); - - // full rank -> direct inverse - if(lu_decomp.isInvertible()) - { - optim = lu_decomp.inverse() * qem_vec; - } - else - { // low rank -> svd pseudo-inverse - Eigen::JacobiSVD svd_decomp(qem_mat, Eigen::ComputeThinU | Eigen::ComputeThinV); - svd_decomp.setThreshold(1e-5); - - optim(0) = cluster_pole.x(); - optim(1) = cluster_pole.y(); - optim(2) = cluster_pole.z(); - optim(3) = 1.; - - optim = optim + svd_decomp.solve(qem_vec - qem_mat * optim); - } - - Point optim_point(optim(0), optim(1), optim(2)); - - return optim_point; - } - -}; - - - - -#endif \ No newline at end of file diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/types.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/types.h deleted file mode 100644 index 6b4992e8074..00000000000 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/types.h +++ /dev/null @@ -1,54 +0,0 @@ - -// CGAL -#include -#include - -#include -#include -#include - -typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; - - -typedef Kernel::FT FT; -typedef Kernel::Point_3 Point; -typedef Kernel::Vector_3 Vector; -typedef Kernel::Triangle_3 Triangle; -typedef Kernel::Plane_3 Plane; - -typedef Kernel::Point_2 Point_2; -typedef Kernel::Segment_2 Segment_2; - -typedef CGAL::First_of_pair_property_map> Point_map; -typedef CGAL::Second_of_pair_property_map> Normal_map; -typedef CGAL::Point_set_3< Point, Vector > Pointset; - -// triangle fit -typedef CGAL::Polyhedron_3 Polyhedron; -typedef std::vector PointList; -typedef CGAL::Bbox_3 Bbox; - - -/// @brief - -typedef std::vector DataList; -typedef std::vector IntList; -typedef std::vector DoubleList; -typedef std::vector BoolList; -typedef std::vector VecList; - -typedef std::set IntSet; - -typedef std::pair IntPair; - - -#ifndef TYPE_H -#define TYPE_H - -namespace qem -{ - enum class INIT_QEM_GENERATOR {RANDOM,KMEANS_FARTHEST,KMEANS_PLUSPLUS}; - enum class VERBOSE_LEVEL {LOW,MEDIUM,HIGH}; -} - -#endif /* TYPE_H */ \ No newline at end of file From ad926fe662e53fd08ccbb17d3d91f12bca152eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 27 Mar 2024 19:00:12 +0100 Subject: [PATCH 50/56] remove tabs --- .../include/CGAL/Polygon_mesh_processing/acvd/acvd.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 05c7e087f6d..9a9ebcc6144 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -146,7 +146,7 @@ typename GT::Vector_3 compute_displacement(const Eigen::Matrix& qu if ((AbsolutesEigenValues[IndexMax] * invmaxW > 1e-4) && (MaxNumberOfUsedSingularValues > 0)) { - // If this is true, then w[i] != 0, so this division is ok. + // If this is true, then w[i] != 0, so this division is ok. double Inv = 1.0 / w[IndexMax]; tempMatrix(IndexMax, 0) = U(0, IndexMax) * Inv; tempMatrix(IndexMax, 1) = U(1, IndexMax) * Inv; From 97917c31c498c0295cfa170de93043d3473c02df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 27 Mar 2024 19:12:52 +0100 Subject: [PATCH 51/56] qt6 --- Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt b/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt index 44d9f8ff81b..89dfa68ea65 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt @@ -27,7 +27,7 @@ polyhedron_demo_plugin(extrude_plugin Extrude_plugin KEYWORDS PMP) target_link_libraries(extrude_plugin PUBLIC scene_surface_mesh_item scene_selection_item) -qt5_wrap_ui( acvd_simplificationUI_FILES ACVD_simplification_dialog.ui) +qt6_wrap_ui( acvd_simplificationUI_FILES ACVD_simplification_dialog.ui) polyhedron_demo_plugin(acvd_simplification_plugin ACVD_simplification_plugin ${acvd_simplificationUI_FILES} KEYWORDS PMP) target_link_libraries(acvd_simplification_plugin PUBLIC scene_surface_mesh_item scene_points_with_normal_item scene_polygon_soup_item CGAL::Eigen3_support) From 916e5f55c5a68572cc913f077a52ee2bb3b36ad9 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 28 Mar 2024 11:45:44 +0200 Subject: [PATCH 52/56] Making PostProcessing QEM Optional (np) --- .../Polygon_mesh_processing/acvd_example.cpp | 10 +- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 100 +++++++++++------- .../internal/parameters_interface.h | 1 + 3 files changed, 73 insertions(+), 38 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp index c6a65b3b414..31c0aa848e5 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -37,10 +37,18 @@ int main(int argc, char* argv[]) std::cout << "Uniform Isotropic ACVD ...." << std::endl; auto acvd_mesh = PMP::acvd_isotropic_remeshing(smesh, nb_clusters); - CGAL::IO::write_OFF("fandisk_qem-pp3000.off", acvd_mesh); + CGAL::IO::write_OFF("fandisk_acvd.off", acvd_mesh); std::cout << "Completed" << std::endl; + // With Post-Processing QEM Optimization + + std::cout << "Uniform Isotropic ACVD with QEM optimization ...." << std::endl; + + auto acvd_mesh_qem = PMP::acvd_isotropic_remeshing(smesh, nb_clusters, CGAL::parameters::post_processing_qem(true)); + CGAL::IO::write_OFF("fandisk_acvd_qem-pp.off", acvd_mesh_qem); + + std::cout << "Completed" << std::endl; /// Adaptive Isotropic ACVD diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 6bbd2273009..d7642589ca8 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -84,7 +84,7 @@ void compute_qem_vertex(std::vector> cluster_ } template -typename GT::Vector_3 compute_displacement(const Eigen::Matrix quadric, const typename GT::Vector_3& p, int& RankDeficiency) +typename GT::Vector_3 compute_displacement(const Eigen::Matrix quadric, const typename GT::Point_3& p, int& RankDeficiency) { typedef Eigen::Matrix Matrix4d; typedef Eigen::Matrix Matrix3d; @@ -169,7 +169,7 @@ typename GT::Vector_3 compute_displacement(const Eigen::Matrix -void compute_representative_point(const Eigen::Matrix& quadric, typename GT::Vector_3& p, int& RankDeficiency) +void compute_representative_point(const Eigen::Matrix& quadric, typename GT::Point_3& p, int& RankDeficiency) { // average point typename GT::Vector_3 displacement = compute_displacement(quadric, p, RankDeficiency); @@ -323,6 +323,9 @@ std::pair< choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions_map), Default_principal_map()); + // get parameter for turning on post-processing qem point optimization + const bool post_processing_qem = choose_parameter(get_parameter(np, internal_np::post_processing_qem), false); + // if adaptive clustering if (gradation_factor > 0 && is_default_parameter::value) @@ -659,37 +662,6 @@ std::pair< std::vector> polygons; TriangleMesh simplified_mesh; - // create a point for each cluster - std::vector> cluster_quadrics(clusters.size()); - - // initialize quadrics - for (int i = 0; i < nb_clusters; i++) - cluster_quadrics[i].setZero(); - - for (Face_descriptor fd : faces(pmesh)) { - // get Vs for fd - // compute qem from Vs->"vector_3"s - // add to the 3 indices of the cluster - typename GT::Point_3 p_i = get(vpm, target(halfedge(fd, pmesh), pmesh)); - typename GT::Vector_3 vec_1 = typename GT::Vector_3(p_i.x(), p_i.y(), p_i.z()); - p_i = get(vpm, target(next(halfedge(fd, pmesh), pmesh), pmesh)); - typename GT::Vector_3 vec_2 = typename GT::Vector_3(p_i.x(), p_i.y(), p_i.z()); - p_i = get(vpm, target(next(next(halfedge(fd, pmesh), pmesh), pmesh), pmesh)); - typename GT::Vector_3 vec_3 = typename GT::Vector_3(p_i.x(), p_i.y(), p_i.z()); - - int c_1 = get(vertex_cluster_pmap, target(halfedge(fd, pmesh), pmesh)); - int c_2 = get(vertex_cluster_pmap, target(next(halfedge(fd, pmesh), pmesh), pmesh)); - int c_3 = get(vertex_cluster_pmap, target(next(next(halfedge(fd, pmesh), pmesh), pmesh), pmesh)); - - if (c_1 != -1 && c_2 != -1 && c_3 != -1) - { - Eigen::Matrix q; - compute_qem_face(vec_1, vec_2, vec_3, q); - cluster_quadrics[c_1] += q; - cluster_quadrics[c_2] += q; - cluster_quadrics[c_3] += q; - } - } for (int i = 0; i < nb_clusters; i++) { @@ -698,16 +670,70 @@ std::pair< valid_cluster_map[i] = points.size(); typename GT::Vector_3 cluster_representative = clusters[i].compute_centroid(); - int RankDeficiency = 0; - //compute_representative_point(cluster_quadrics[i], cluster_representative, RankDeficiency); - - typename GT::Point_3 cluster_representative_p(cluster_representative.x(), cluster_representative.y(), cluster_representative.z()); points.push_back(cluster_representative_p); } } + if (post_processing_qem){ + // create a point for each cluster + std::vector> cluster_quadrics(clusters.size()); + + // initialize quadrics + for (int i = 0; i < nb_clusters; i++) + cluster_quadrics[i].setZero(); + + // for storing the vertex_descriptor of each face + std::vector face_vertices; + + for (Face_descriptor fd : faces(pmesh)) { + // get Vs for fd + // compute qem from Vs->"vector_3"s + // add to the 3 indices of the cluster + Halfedge_descriptor hd = halfedge(fd, pmesh); + do { + Vertex_descriptor vd = target(hd, pmesh); + face_vertices.push_back(vd); + hd = next(hd, pmesh); + } while (hd != halfedge(fd, pmesh)); + + auto p_i = get(vpm, face_vertices[0]); + typename GT::Vector_3 vec_1 = typename GT::Vector_3(p_i.x(), p_i.y(), p_i.z()); + p_i = get(vpm, face_vertices[1]); + typename GT::Vector_3 vec_2 = typename GT::Vector_3(p_i.x(), p_i.y(), p_i.z()); + p_i = get(vpm, face_vertices[2]); + typename GT::Vector_3 vec_3 = typename GT::Vector_3(p_i.x(), p_i.y(), p_i.z()); + + int c_1 = get(vertex_cluster_pmap, face_vertices[0]); + int c_2 = get(vertex_cluster_pmap, face_vertices[1]); + int c_3 = get(vertex_cluster_pmap, face_vertices[2]); + + if (c_1 != -1 && c_2 != -1 && c_3 != -1) + { + Eigen::Matrix q; + compute_qem_face(vec_1, vec_2, vec_3, q); + cluster_quadrics[c_1] += q; + cluster_quadrics[c_2] += q; + cluster_quadrics[c_3] += q; + } + + face_vertices.clear(); + } + + int valid_index = 0; + + for (int i = 0; i < nb_clusters; i++) + { + if (clusters[i].weight_sum > 0) + { + int RankDeficiency = 0; + compute_representative_point(cluster_quadrics[i], points[valid_index], RankDeficiency); + valid_index++; + } + } + } + // extract boundary cycles std::vector border_hedges; extract_boundary_cycles(pmesh, std::back_inserter(border_hedges)); diff --git a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h index 220d08b79b0..aad511fa1ed 100644 --- a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h +++ b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h @@ -102,6 +102,7 @@ CGAL_add_named_parameter(vertex_Gaussian_curvature_t, vertex_Gaussian_curvature, CGAL_add_named_parameter(vertex_principal_curvatures_and_directions_t, vertex_principal_curvatures_and_directions, vertex_principal_curvatures_and_directions) CGAL_add_named_parameter(ball_radius_t, ball_radius, ball_radius) CGAL_add_named_parameter(gradation_factor_t, gradation_factor, gradation_factor) +CGAL_add_named_parameter(post_processing_qem_t, post_processing_qem, post_processing_qem) CGAL_add_named_parameter(outward_orientation_t, outward_orientation, outward_orientation) CGAL_add_named_parameter(overlap_test_t, overlap_test, do_overlap_test_of_bounded_sides) CGAL_add_named_parameter(preserve_genus_t, preserve_genus, preserve_genus) From 4130a354626b3d3d263da0aae578aa5680019adf Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 28 Mar 2024 11:51:57 +0200 Subject: [PATCH 53/56] renaming a variable --- .../include/CGAL/Polygon_mesh_processing/acvd/acvd.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index d7642589ca8..151ab61a54d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -99,12 +99,11 @@ typename GT::Vector_3 compute_displacement(const Eigen::Matrix svd(A, Eigen::ComputeFullU | Eigen::ComputeFullV); U = svd.matrixU(); - VT = svd.matrixV(); + V = svd.matrixV(); auto w_temp = svd.singularValues(); w = typename GT::Vector_3(w_temp(0), w_temp(1), w_temp(2)); @@ -159,7 +158,7 @@ typename GT::Vector_3 compute_displacement(const Eigen::Matrix p_temp, b; b << -quadric(0, 3), -quadric(1, 3), -quadric(2, 3); p_temp << p.x(), p.y(), p.z(); From 349c526f8ec915b1be0976333dcadf759dce228a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 28 Mar 2024 16:03:27 +0100 Subject: [PATCH 54/56] update plugin --- ...VD_simplification_dialog.ui => ACVD_remeshing_dialog.ui} | 0 ..._simplification_plugin.cpp => ACVD_remeshing_plugin.cpp} | 6 +++--- Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename Polyhedron/demo/Polyhedron/Plugins/PMP/{ACVD_simplification_dialog.ui => ACVD_remeshing_dialog.ui} (100%) rename Polyhedron/demo/Polyhedron/Plugins/PMP/{ACVD_simplification_plugin.cpp => ACVD_remeshing_plugin.cpp} (95%) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_dialog.ui b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_remeshing_dialog.ui similarity index 100% rename from Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_dialog.ui rename to Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_remeshing_dialog.ui diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_remeshing_plugin.cpp similarity index 95% rename from Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_plugin.cpp rename to Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_remeshing_plugin.cpp index 9fb797f63bd..8f9f5eed0d1 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_simplification_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_remeshing_plugin.cpp @@ -1,4 +1,3 @@ -#undef NDEBUG #include #include "Messages_interface.h" @@ -51,7 +50,7 @@ public: mw = mainWindow; scene = scene_interface; actionACVD = new QAction(tr( - "ACVD Simplification" + "ACVD Remeshing" ), mw); //actionACVD->setProperty("subMenuName", ""); if (actionACVD) @@ -87,7 +86,7 @@ public Q_SLOTS: time.start(); CGAL::Three::Three::information("ACVD Simplification..."); QApplication::setOverrideCursor(Qt::WaitCursor); - FaceGraph simplified_graph = CGAL::Polygon_mesh_processing::acvd_isotropic_simplification(*graph, ui.nb_clusters_spin_box->value()); + FaceGraph simplified_graph = CGAL::Polygon_mesh_processing::acvd_isotropic_remeshing(*graph, ui.nb_clusters_spin_box->value()); // update the scene CGAL::Three::Three::information(QString("ok (%1 ms)").arg(time.elapsed())); @@ -100,6 +99,7 @@ public Q_SLOTS: scene->addItem(simplified_item); simplified_item->invalidateOpenGLBuffers(); scene->itemChanged(simplified_item); + item->setVisible(false); } diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt b/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt index 89dfa68ea65..e98333d9471 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt @@ -27,9 +27,9 @@ polyhedron_demo_plugin(extrude_plugin Extrude_plugin KEYWORDS PMP) target_link_libraries(extrude_plugin PUBLIC scene_surface_mesh_item scene_selection_item) -qt6_wrap_ui( acvd_simplificationUI_FILES ACVD_simplification_dialog.ui) -polyhedron_demo_plugin(acvd_simplification_plugin ACVD_simplification_plugin ${acvd_simplificationUI_FILES} KEYWORDS PMP) -target_link_libraries(acvd_simplification_plugin PUBLIC scene_surface_mesh_item scene_points_with_normal_item scene_polygon_soup_item CGAL::Eigen3_support) +qt6_wrap_ui( acvd_remeshingUI_FILES ACVD_remeshing_dialog.ui) +polyhedron_demo_plugin(acvd_remeshing_plugin ACVD_remeshing_plugin ${acvd_remeshingUI_FILES} KEYWORDS PMP) +target_link_libraries(acvd_remeshing_plugin PUBLIC scene_surface_mesh_item scene_points_with_normal_item scene_polygon_soup_item CGAL::Eigen3_support) if(TARGET CGAL::Eigen3_support) if("${EIGEN3_VERSION}" VERSION_GREATER "3.1.90") From 3bd0a7c92ff83a4664d5832e9d8b87b31468699c Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:55:12 +0200 Subject: [PATCH 55/56] QEM Minimization (still a bit buggy) --- .../Polygon_mesh_processing/acvd_example.cpp | 68 +- .../CGAL/Polygon_mesh_processing/acvd/acvd.h | 674 +++++++++++++++++- 2 files changed, 683 insertions(+), 59 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp index 31c0aa848e5..4e5e537ce12 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/acvd_example.cpp @@ -32,54 +32,62 @@ int main(int argc, char* argv[]) return EXIT_FAILURE; } - /// Uniform Isotropic ACVD + ///// Uniform Isotropic ACVD - std::cout << "Uniform Isotropic ACVD ...." << std::endl; + //std::cout << "Uniform Isotropic ACVD ...." << std::endl; + //auto acvd_mesh = PMP::acvd_isotropic_remeshing(smesh, nb_clusters); + //CGAL::IO::write_OFF("fandisk_acvd_3000.off", acvd_mesh); - auto acvd_mesh = PMP::acvd_isotropic_remeshing(smesh, nb_clusters); - CGAL::IO::write_OFF("fandisk_acvd.off", acvd_mesh); + //std::cout << "Completed" << std::endl; - std::cout << "Completed" << std::endl; + //// With Post-Processing QEM Optimization - // With Post-Processing QEM Optimization + //std::cout << "Uniform Isotropic ACVD with QEM optimization ...." << std::endl; - std::cout << "Uniform Isotropic ACVD with QEM optimization ...." << std::endl; + //auto acvd_mesh_qem_pp = PMP::acvd_isotropic_remeshing(smesh, nb_clusters, CGAL::parameters::post_processing_qem(true)); + //CGAL::IO::write_OFF("fandisk_acvd_qem-pp_3000.off", acvd_mesh_qem_pp); - auto acvd_mesh_qem = PMP::acvd_isotropic_remeshing(smesh, nb_clusters, CGAL::parameters::post_processing_qem(true)); - CGAL::IO::write_OFF("fandisk_acvd_qem-pp.off", acvd_mesh_qem); + //std::cout << "Completed" << std::endl; + + // With QEM Energy Minimization + + std::cout << "Uniform QEM ACVD ...." << std::endl; + + auto acvd_mesh_qem = PMP::acvd_qem_remeshing(smesh, nb_clusters); + CGAL::IO::write_OFF("fandisk_acvd_qem_3000.off", acvd_mesh_qem); std::cout << "Completed" << std::endl; /// Adaptive Isotropic ACVD - //std::cout << "Adaptive Isotropic ACVD ...." << std::endl; + /*std::cout << "Adaptive Isotropic ACVD ...." << std::endl; - //const double gradation_factor = (argc > 3) ? atof(argv[3]) : 1; + const double gradation_factor = (argc > 3) ? atof(argv[3]) : 2; - //bool created = false; - //Surface_Mesh::Property_map> - // principal_curvatures_and_directions_map; + bool created = false; + Surface_Mesh::Property_map> + principal_curvatures_and_directions_map; - //boost::tie(principal_curvatures_and_directions_map, created) = - // smesh.add_property_map> - // ("v:principal_curvatures_and_directions_map", { 0, 0, - // Epic_kernel::Vector_3(0,0,0), - // Epic_kernel::Vector_3(0,0,0) }); - //assert(created); + boost::tie(principal_curvatures_and_directions_map, created) = + smesh.add_property_map> + ("v:principal_curvatures_and_directions_map", { 0, 0, + Epic_kernel::Vector_3(0,0,0), + Epic_kernel::Vector_3(0,0,0) }); + assert(created); - //PMP::interpolated_corrected_curvatures(smesh, CGAL::parameters::vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map)); + PMP::interpolated_corrected_curvatures(smesh, CGAL::parameters::vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map)); - //auto adaptive_acvd_mesh = - // PMP::acvd_isotropic_remeshing( - // smesh, - // nb_clusters, - // CGAL::parameters::vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) - // .gradation_factor(gradation_factor) - // ); + auto adaptive_acvd_mesh = + PMP::acvd_isotropic_remeshing( + smesh, + nb_clusters, + CGAL::parameters::vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) + .gradation_factor(gradation_factor) + ); - //CGAL::IO::write_OFF("acvd_mesh_adaptive.off", adaptive_acvd_mesh); + CGAL::IO::write_OFF("fandisk_acvd_adaptive_3000.off", adaptive_acvd_mesh); - std::cout << "Completed" << std::endl; + std::cout << "Completed" << std::endl;*/ return 0; } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h index 151ab61a54d..a7e899773b7 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/acvd/acvd.h @@ -42,6 +42,7 @@ #include #define CGAL_CLUSTERS_TO_VERTICES_THRESHOLD 0.1 +#define CGAL_TO_QEM_MODIFICATION_THRESHOLD 1e-3 #define CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD 10000 namespace CGAL { @@ -84,7 +85,7 @@ void compute_qem_vertex(std::vector> cluster_ } template -typename GT::Vector_3 compute_displacement(const Eigen::Matrix quadric, const typename GT::Point_3& p, int& RankDeficiency) +typename GT::Vector_3 compute_displacement(const Eigen::Matrix quadric, const typename GT::Point_3& p, int& rank_deficiency) { typedef Eigen::Matrix Matrix4d; typedef Eigen::Matrix Matrix3d; @@ -136,7 +137,7 @@ typename GT::Vector_3 compute_displacement(const Eigen::Matrix 1e-4) + if ((AbsolutesEigenValues[IndexMax] * invmaxW > 1e-3) && (MaxNumberOfUsedSingularValues > 0)) { // If this is true, then w[i] != 0, so this division is ok. @@ -150,7 +151,7 @@ typename GT::Vector_3 compute_displacement(const Eigen::Matrix -void compute_representative_point(const Eigen::Matrix& quadric, typename GT::Point_3& p, int& RankDeficiency) +void compute_representative_point(const Eigen::Matrix& quadric, typename GT::Point_3& p, int& rank_deficiency) { // average point - typename GT::Vector_3 displacement = compute_displacement(quadric, p, RankDeficiency); + typename GT::Vector_3 displacement = compute_displacement(quadric, p, rank_deficiency); p = p + displacement; } + +template +struct QEMClusterData { + typename GT::Vector_3 site_sum; + typename GT::FT weight_sum; + Eigen::Matrix quadric_sum; + typename GT::Vector_3 representative_point; + typename GT::FT energy; + + size_t nb_vertices; + + QEMClusterData() : + site_sum(0, 0, 0), + weight_sum(0), + representative_point(0, 0, 0), + energy(0), + nb_vertices(0) + { + quadric_sum.setZero(); + } + + void add_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight, const Eigen::Matrix& quadric) + { + this->site_sum += vertex_position * weight; + this->weight_sum += weight; + this->quadric_sum += quadric; + this->nb_vertices++; + } + + void remove_vertex(const typename GT::Vector_3 vertex_position, const typename GT::FT weight, const Eigen::Matrix& quadric) + { + this->site_sum -= vertex_position * weight; + this->weight_sum -= weight; + this->quadric_sum -= quadric; + this->nb_vertices--; + } + + typename GT::FT compute_energy() + { + auto dot_product = GT().compute_scalar_product_3_object(); + + this->energy = (this->representative_point).squared_length() * this->weight_sum + - 2 * dot_product(this->representative_point, this->site_sum); + return this->energy; + } + + typename GT::Vector_3 compute_representative(bool qem) + { + if (this->weight_sum > 0) + { + if (qem) + { + int rank_deficiency = 0; + typename GT::Point_3 point = { this->representative_point.x(), this->representative_point.y(), this->representative_point.z() }; + compute_representative_point(this->quadric_sum, point, rank_deficiency); + this->representative_point = { point.x(), point.y(), point.z() }; + } + else + this->representative_point = (this->site_sum) / this->weight_sum; + + return this->representative_point; + } + else + return typename GT::Vector_3(0, 0, 0); + } +}; + template struct IsotropicClusterData { typename GT::Vector_3 site_sum; @@ -300,7 +368,6 @@ std::pair< typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::property_map >::type VertexColorMap; typedef typename boost::property_map >::type VertexClusterMap; typedef typename boost::property_map >::type VertexVisitedMap; typedef typename boost::property_map >::type VertexWeightMap; @@ -460,7 +527,7 @@ std::pair< // add all halfedges around v1 except hi to the queue for (Halfedge_descriptor hd : halfedges_around_source(v1, pmesh)) - //if (hd != hi && hd != opposite(hi, pmesh)) + //TODO: if (hd != hi && hd != opposite(hi, pmesh)) clusters_edges_new.push(hd); nb_modifications++; } @@ -474,7 +541,7 @@ std::pair< // add all halfedges around v2 except hi to the queue for (Halfedge_descriptor hd : halfedges_around_source(v2, pmesh)) - //if (hd != hi && hd != opposite(hi, pmesh)) + //TODO: if (hd != hi && hd != opposite(hi, pmesh)) clusters_edges_new.push(hd); nb_modifications++; } @@ -518,7 +585,7 @@ std::pair< // add all halfedges around v2 except hi to the queue for (Halfedge_descriptor hd : halfedges_around_source(v2, pmesh)) - //if (hd != hi && hd != opposite(hi, pmesh)) + //TODO: if (hd != hi && hd != opposite(hi, pmesh)) clusters_edges_new.push(hd); nb_modifications++; } @@ -536,7 +603,7 @@ std::pair< // add all halfedges around v1 except hi to the queue for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v1, pmesh), pmesh)) - //if (hd != hi && hd != opposite(hi, pmesh)) + //TODO: if (hd != hi && hd != opposite(hi, pmesh)) clusters_edges_new.push(hd); nb_modifications++; } @@ -634,26 +701,10 @@ std::pair< } } } + std::cout << "# nb_disconnected: " << nb_disconnected << "\n"; } while (nb_disconnected > 0); - - VertexColorMap vcm = get(CGAL::dynamic_vertex_property_t(), pmesh); - - // // frequency of each cluster - // cluster_frequency = std::vector(nb_clusters, 0); - - // for (Vertex_descriptor vd : vertices(pmesh)) - // { - // int c = get(vertex_cluster_pmap, vd); - // cluster_frequency[c]++; - // CGAL::IO::Color color(255 - (c * 255 / nb_clusters), (c * c % 7) * 255 / 7, (c * c * c % 31) * 255 / 31); - // put(vcm, vd, color); - // } - - // std::string name = std::to_string(nb_clusters) + ".off"; - // CGAL::IO::write_OFF(name, pmesh, CGAL::parameters::vertex_color_map(vcm)); - /// Construct new Mesh std::vector valid_cluster_map(nb_clusters, -1); std::vector points; @@ -726,8 +777,8 @@ std::pair< { if (clusters[i].weight_sum > 0) { - int RankDeficiency = 0; - compute_representative_point(cluster_quadrics[i], points[valid_index], RankDeficiency); + int rank_deficiency = 0; + compute_representative_point(cluster_quadrics[i], points[valid_index], rank_deficiency); valid_index++; } } @@ -823,6 +874,531 @@ std::pair< } +template +std::pair< + std::vector::type::Point_3>, + std::vector> +> acvd_qem( + TriangleMesh& pmesh, + const int nb_clusters, + const NamedParameters& np = parameters::default_values() + ) +{ + typedef typename GetGeomTraits::type GT; + typedef typename Eigen::Matrix Matrix4x4; + typedef typename GetVertexPointMap::const_type Vertex_position_map; + typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename boost::graph_traits::face_descriptor Face_descriptor; + typedef typename boost::property_map >::type VertexClusterMap; + typedef typename boost::property_map >::type VertexVisitedMap; + typedef typename boost::property_map >::type VertexWeightMap; + typedef typename boost::property_map >::type VertexQuadricMap; + typedef Constant_property_map> Default_principal_map; + typedef typename internal_np::Lookup_named_param_def::type Vertex_principal_curvatures_and_directions_map; + + using parameters::choose_parameter; + using parameters::get_parameter; + using parameters::is_default_parameter; + + Vertex_position_map vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), + get_property_map(CGAL::vertex_point, pmesh)); + + // get curvature related parameters + const typename GT::FT gradation_factor = choose_parameter(get_parameter(np, internal_np::gradation_factor), 0); + const Vertex_principal_curvatures_and_directions_map vpcd_map = + choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions_map), + Default_principal_map()); + + // if adaptive clustering + if (gradation_factor > 0 && + is_default_parameter::value) + interpolated_corrected_curvatures(pmesh, parameters::vertex_principal_curvatures_and_directions_map(vpcd_map)); + + CGAL_precondition(CGAL::is_triangle_mesh(pmesh)); + + // TODO: copy the mesh in order to not modify the original mesh + //TriangleMesh pmesh = pmesh_org; + int nb_vertices = num_vertices(pmesh); + + + // For remeshing, we might need to subdivide the mesh before clustering + // This shoould always hold: nb_clusters <= nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD + // So do the following till the condition is met + while (nb_clusters > nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD) + { + typename GT::FT curr_factor = nb_clusters / (nb_vertices * CGAL_CLUSTERS_TO_VERTICES_THRESHOLD); + int subdivide_steps = (std::max)((int)ceil(log(curr_factor) / log(4)), 0); + + if (subdivide_steps > 0) + { + if (gradation_factor == 0) // no adaptive clustering + Subdivision_method_3::Upsample_subdivision( + pmesh, + CGAL::parameters::number_of_iterations(subdivide_steps) + ); + else // adaptive clustering + upsample_subdivision_property( + pmesh, + CGAL::parameters::number_of_iterations(subdivide_steps).vertex_principal_curvatures_and_directions_map(vpcd_map) + ); + vpm = get_property_map(CGAL::vertex_point, pmesh); + nb_vertices = num_vertices(pmesh); + } + } + + // creating needed property maps + VertexClusterMap vertex_cluster_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); + VertexVisitedMap vertex_visited_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); + VertexWeightMap vertex_weight_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); + VertexQuadricMap vertex_quadric_pmap = get(CGAL::dynamic_vertex_property_t(), pmesh); + + std::vector> clusters(nb_clusters); + std::queue clusters_edges_active; + std::queue clusters_edges_new; + + // initialize vertex weights and clusters + for (Vertex_descriptor vd : vertices(pmesh)) + { + put(vertex_weight_pmap, vd, 0); + put(vertex_visited_pmap, vd, false); + put(vertex_cluster_pmap, vd, -1); + + Matrix4x4 q; q.setZero(); + put(vertex_quadric_pmap, vd, q); + } + + // compute vertex weights (dual area), and quadrics + typename GT::FT weight_avg = 0; + for (Face_descriptor fd : faces(pmesh)) + { + typename GT::FT weight = abs(CGAL::Polygon_mesh_processing::face_area(fd, pmesh)) / 3; + + // get points of the face + Halfedge_descriptor hd = halfedge(fd, pmesh); + typename GT::Point_3 pi = get(vpm, source(hd, pmesh)); + typename GT::Vector_3 vp1(pi.x(), pi.y(), pi.z()); + hd = next(hd, pmesh); + typename GT::Point_3 pj = get(vpm, source(hd, pmesh)); + typename GT::Vector_3 vp2(pj.x(), pj.y(), pj.z()); + hd = next(hd, pmesh); + typename GT::Point_3 pk = get(vpm, source(hd, pmesh)); + typename GT::Vector_3 vp3(pk.x(), pk.y(), pk.z()); + + // compute quadric for the face + Matrix4x4 face_quadric; + compute_qem_face(vp1, vp2, vp3, face_quadric); + + for (Vertex_descriptor vd : vertices_around_face(halfedge(fd, pmesh), pmesh)) + { + typename GT::FT vertex_weight = get(vertex_weight_pmap, vd); + Matrix4x4 vertex_quadric = get(vertex_quadric_pmap, vd); + + if (gradation_factor == 0) // no adaptive clustering + vertex_weight += weight; + else // adaptive clustering + { + typename GT::FT k1 = get(vpcd_map, vd).min_curvature; + typename GT::FT k2 = get(vpcd_map, vd).max_curvature; + typename GT::FT k_sq = (k1 * k1 + k2 * k2); + vertex_weight += weight * pow(k_sq, gradation_factor / 2.0); // /2.0 because k_sq is squared + } + + weight_avg += vertex_weight; + put(vertex_weight_pmap, vd, vertex_weight); + + vertex_quadric += face_quadric; + put(vertex_quadric_pmap, vd, vertex_quadric); + } + } + weight_avg /= nb_vertices; + + // clamp the weights up and below by a ratio (like 10,000) * avg_weights + for (Vertex_descriptor vd : vertices(pmesh)) + { + typename GT::FT vertex_weight = get(vertex_weight_pmap, vd); + if (vertex_weight > CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg) + put(vertex_weight_pmap, vd, CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg); + else if (vertex_weight < 1.0 / CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg) + put(vertex_weight_pmap, vd, 1.0 / CGAL_WEIGHT_CLAMP_RATIO_THRESHOLD * weight_avg); + } + + // randomly initialize clusters + for (int ci = 0; ci < nb_clusters; ci++) + { + int vi; + Vertex_descriptor vd; + do { + vi = CGAL::get_default_random().get_int(0, num_vertices(pmesh)); + vd = *(vertices(pmesh).begin() + vi); + } while (get(vertex_cluster_pmap, vd) != -1); + + put(vertex_cluster_pmap, vd, ci); + typename GT::Point_3 vp = get(vpm, vd); + typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); + clusters[ci].add_vertex(vpv, get(vertex_weight_pmap, vd), get(vertex_quadric_pmap, vd)); + + for (Halfedge_descriptor hd : halfedges_around_source(vd, pmesh)) + clusters_edges_active.push(hd); + } + + // the energy minimization loop (clustering loop) + int nb_modifications = 0; + int nb_disconnected = 0; + int nb_qem_iters = 0; + // Turned on once nb_modifications < nb_vertices * CGAL_TO_QEM_MODIFICATION_THRESHOLD + bool qem_energy_minimization = false; + bool just_switched_to_qem = false; + do + { + nb_disconnected = 0; + do + { + nb_modifications = 0; + + while (!clusters_edges_active.empty()) { + Halfedge_descriptor hi = clusters_edges_active.front(); + clusters_edges_active.pop(); + + Vertex_descriptor v1 = source(hi, pmesh); + Vertex_descriptor v2 = target(hi, pmesh); + + int c1 = get(vertex_cluster_pmap, v1); + int c2 = get(vertex_cluster_pmap, v2); + + if (c1 == -1) + { + // expand cluster c2 (add v1 to c2) + put(vertex_cluster_pmap, v1, c2); + typename GT::Point_3 vp1 = get(vpm, v1); + typename GT::Vector_3 vpv(vp1.x(), vp1.y(), vp1.z()); + clusters[c2].add_vertex(vpv, get(vertex_weight_pmap, v1), get(vertex_quadric_pmap, v1)); + + // add all halfedges around v1 except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(v1, pmesh)) + //TODO: if (hd != hi && hd != opposite(hi, pmesh)) + clusters_edges_new.push(hd); + nb_modifications++; + } + else if (c2 == -1) + { + // expand cluster c1 (add v2 to c1) + put(vertex_cluster_pmap, v2, c1); + typename GT::Point_3 vp2 = get(vpm, v2); + typename GT::Vector_3 vpv(vp2.x(), vp2.y(), vp2.z()); + clusters[c1].add_vertex(vpv, get(vertex_weight_pmap, v2), get(vertex_quadric_pmap, v2)); + + // add all halfedges around v2 except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(v2, pmesh)) + //TODO: if (hd != hi && hd != opposite(hi, pmesh)) + clusters_edges_new.push(hd); + nb_modifications++; + } + else if (c1 == c2) + { + clusters_edges_new.push(hi); + } + else + { + // compare the energy of the 3 cases + typename GT::Point_3 vp1 = get(vpm, v1); + typename GT::Vector_3 vpv1(vp1.x(), vp1.y(), vp1.z()); + typename GT::Point_3 vp2 = get(vpm, v2); + typename GT::Vector_3 vpv2(vp2.x(), vp2.y(), vp2.z()); + typename GT::FT v1_weight = get(vertex_weight_pmap, v1); + typename GT::FT v2_weight = get(vertex_weight_pmap, v2); + Matrix4x4 v1_qem = get (vertex_quadric_pmap, v1); + Matrix4x4 v2_qem = get (vertex_quadric_pmap, v2); + + clusters[c1].compute_representative(qem_energy_minimization); + clusters[c2].compute_representative(qem_energy_minimization); + typename GT::FT e_no_change = clusters[c1].compute_energy() + clusters[c2].compute_energy(); + + clusters[c1].remove_vertex(vpv1, v1_weight, v1_qem); + clusters[c2].add_vertex(vpv1, v1_weight, v1_qem); + + clusters[c1].compute_representative(qem_energy_minimization); + clusters[c2].compute_representative(qem_energy_minimization); + typename GT::FT e_v1_to_c2 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); + + // reset to no change + clusters[c1].add_vertex(vpv1, v1_weight, v1_qem); + clusters[c2].remove_vertex(vpv1, v1_weight, v1_qem); + + // The effect of the following should always be reversed after the comparison + clusters[c2].remove_vertex(vpv2, v2_weight, v2_qem); + clusters[c1].add_vertex(vpv2, v2_weight, v2_qem); + + clusters[c1].compute_representative(qem_energy_minimization); + clusters[c2].compute_representative(qem_energy_minimization); + typename GT::FT e_v2_to_c1 = clusters[c1].compute_energy() + clusters[c2].compute_energy(); + + if (e_v2_to_c1 < e_no_change && e_v2_to_c1 < e_v1_to_c2 && clusters[c2].nb_vertices > 0) // > 0 as 1 vertex was removed from c2 + { + // move v2 to c1 + put(vertex_cluster_pmap, v2, c1); + + // cluster data is already updated + + // add all halfedges around v2 except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(v2, pmesh)) + //TODO: if (hd != hi && hd != opposite(hi, pmesh)) + clusters_edges_new.push(hd); + nb_modifications++; + + clusters[c1].compute_representative(qem_energy_minimization); + clusters[c2].compute_representative(qem_energy_minimization); + } + else if (e_v1_to_c2 < e_no_change && clusters[c1].nb_vertices > 2) // > 2 as 1 vertex was added to c1 + { + + // move v1 to c2 + put(vertex_cluster_pmap, v1, c2); + + // need to reset cluster data and then update + clusters[c2].add_vertex(vpv2, v2_weight, v2_qem); + clusters[c1].remove_vertex(vpv2, v2_weight, v2_qem); + + clusters[c1].remove_vertex(vpv1, v1_weight, v1_qem); + clusters[c2].add_vertex(vpv1, v1_weight, v1_qem); + + // add all halfedges around v1 except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(halfedge(v1, pmesh), pmesh)) + //TODO: if (hd != hi && hd != opposite(hi, pmesh)) + clusters_edges_new.push(hd); + nb_modifications++; + + clusters[c1].compute_representative(qem_energy_minimization); + clusters[c2].compute_representative(qem_energy_minimization); + } + else + { + // no change but need to reset cluster data + clusters[c2].add_vertex(vpv2, v2_weight, v2_qem); + clusters[c1].remove_vertex(vpv2, v2_weight, v2_qem); + + clusters_edges_new.push(hi); + + clusters[c1].compute_representative(qem_energy_minimization); + clusters[c2].compute_representative(qem_energy_minimization); + } + } + } + std::cout << "# Modifications: " << nb_modifications << "\n"; + + //if(qem_energy_minimization) + // just_switched_to_qem = false; + if (nb_qem_iters == 10) + break; + + if (nb_modifications < nb_vertices * CGAL_TO_QEM_MODIFICATION_THRESHOLD) + { + qem_energy_minimization = true; + //just_switched_to_qem = true; + } + + if (qem_energy_minimization) + nb_qem_iters++; + clusters_edges_active.swap(clusters_edges_new); + } while (nb_modifications > 0 /*&& !just_switched_to_qem*/); + + // Disconnected clusters handling + // the goal is to delete clusters with multiple connected components and only keep the largest connected component of each cluster + // For each cluster, do a BFS from a vertex in the cluster + + std::vector>> cluster_components(nb_clusters, std::vector>()); + std::queue vertex_queue; + + // loop over vertices to compute cluster components + for (Vertex_descriptor vd : vertices(pmesh)) + { + if (get(vertex_visited_pmap, vd)) continue; + int c = get(vertex_cluster_pmap, vd); + if (c != -1) + { + cluster_components[c].push_back(std::vector()); + int component_i = cluster_components[c].size() - 1; + + vertex_queue.push(vd); + put(vertex_visited_pmap, vd, true); + while (!vertex_queue.empty()) + { + Vertex_descriptor v = vertex_queue.front(); + vertex_queue.pop(); + cluster_components[c][component_i].push_back(v); + + for (Halfedge_descriptor hd : halfedges_around_source(v, pmesh)) + { + Vertex_descriptor v2 = target(hd, pmesh); + int c2 = get(vertex_cluster_pmap, v2); + if (c2 == c && !get(vertex_visited_pmap, v2)) + { + vertex_queue.push(v2); + put(vertex_visited_pmap, v2, true); + } + } + } + } + } + + // loop over clusters to delete disconnected components except the largest one + for (int c = 0; c < nb_clusters; c++) + { + if (cluster_components[c].size() <= 1) continue; // only one component, no need to do anything + nb_disconnected++; + std::size_t max_component_size = 0; + std::size_t max_component_index = -1; + for (std::size_t component_i = 0; component_i < cluster_components[c].size(); component_i++) + { + if (cluster_components[c][component_i].size() > max_component_size) + { + max_component_size = cluster_components[c][component_i].size(); + max_component_index = component_i; + } + } + // set cluster to -1 for all components except the largest one + for (std::size_t component_i = 0; component_i < cluster_components[c].size(); component_i++) + { + if (component_i != max_component_index) + { + for (Vertex_descriptor vd : cluster_components[c][component_i]) + { + put(vertex_cluster_pmap, vd, -1); + // remove vd from cluster c + typename GT::Point_3 vp = get(vpm, vd); + typename GT::Vector_3 vpv(vp.x(), vp.y(), vp.z()); + clusters[c].remove_vertex(vpv, get(vertex_weight_pmap, vd), get(vertex_quadric_pmap, vd)); + // add all halfedges around v except hi to the queue + for (Halfedge_descriptor hd : halfedges_around_source(vd, pmesh)) + { + // add hd to the queue if its target is not in the same cluster + Vertex_descriptor v2 = target(hd, pmesh); + int c2 = get(vertex_cluster_pmap, v2); + if (c2 != c) + clusters_edges_new.push(hd); + } + } + } + } + } + + std::cout << "# nb_disconnected: " << nb_disconnected << "\n"; + } while (nb_disconnected > 0); + + /// Construct new Mesh + std::vector valid_cluster_map(nb_clusters, -1); + std::vector points; + + std::vector> polygons; + TriangleMesh simplified_mesh; + + + for (int i = 0; i < nb_clusters; i++) + { + if (clusters[i].weight_sum > 0) + { + valid_cluster_map[i] = points.size(); + typename GT::Vector_3 cluster_representative = clusters[i].representative_point; + + typename GT::Point_3 cluster_representative_p(cluster_representative.x(), cluster_representative.y(), cluster_representative.z()); + points.push_back(cluster_representative_p); + + } + } + + // extract boundary cycles + std::vector border_hedges; + extract_boundary_cycles(pmesh, std::back_inserter(border_hedges)); + + // loop over boundary loops + for (Halfedge_descriptor hd : border_hedges) + { + Halfedge_descriptor hd1 = hd; + + int cb_first = -1; + + do + { + // 1- get the target and source vertices vt, vs + // 2- if the target and source vertices are in different clusters, create a new vertex vb between them vb = (vt + vs) / 2 + // 3- make a new face with the new vertex vb and the centers of the clusters of vt and vs + // 4- also make a new face with vb, the next vb, and the center of the cluster of vt + + Vertex_descriptor vt = target(hd1, pmesh); + Vertex_descriptor vs = source(hd1, pmesh); + + int ct = get(vertex_cluster_pmap, vt); + int cs = get(vertex_cluster_pmap, vs); + + if (ct != cs) + { + typename GT::Point_3 vt_p = get(vpm, vt); + typename GT::Point_3 vs_p = get(vpm, vs); + typename GT::Vector_3 vt_v(vt_p.x(), vt_p.y(), vt_p.z()); + typename GT::Vector_3 vs_v(vs_p.x(), vs_p.y(), vs_p.z()); + + typename GT::Vector_3 vb_v = (vt_v + vs_v) / 2; + typename GT::Point_3 vb_p(vb_v.x(), vb_v.y(), vb_v.z()); + + points.push_back(vb_p); + + int cb = points.size() - 1; + + if (cb_first == -1) + cb_first = cb; + + int ct_mapped = valid_cluster_map[ct], cs_mapped = valid_cluster_map[cs]; + + if (ct_mapped != -1 && cs_mapped != -1) + { + std::vector + polygon = {ct_mapped, cb, cs_mapped}; + polygons.push_back(polygon); + + // after the loop, the last cb+1 should be modified to the first cb + polygon = {cb, ct_mapped, cb + 1}; + polygons.push_back(polygon); + } + } + hd1 = next(hd1, pmesh); + } while (hd1 != hd); + polygons[polygons.size() - 1][2] = cb_first; + } + + // create a triangle for each face with all vertices in 3 different clusters + for (Face_descriptor fd : faces(pmesh)) + { + Halfedge_descriptor hd1 = halfedge(fd, pmesh); + Vertex_descriptor v1 = source(hd1, pmesh); + Halfedge_descriptor hd2 = next(hd1, pmesh); + Vertex_descriptor v2 = source(hd2, pmesh); + Halfedge_descriptor hd3 = next(hd2, pmesh); + Vertex_descriptor v3 = source(hd3, pmesh); + + int c1 = get(vertex_cluster_pmap, v1); + int c2 = get(vertex_cluster_pmap, v2); + int c3 = get(vertex_cluster_pmap, v3); + + if (c1 != c2 && c1 != c3 && c2 != c3) + { + int c1_mapped = valid_cluster_map[c1], c2_mapped = valid_cluster_map[c2], c3_mapped = valid_cluster_map[c3]; + if (c1_mapped != -1 && c2_mapped != -1 && c3_mapped != -1) + { + std::vector polygon = {c1_mapped, c2_mapped, c3_mapped}; + polygons.push_back(polygon); + } + } + } + + orient_polygon_soup(points, polygons); + + return std::make_pair(points, polygons); +} + + } // namespace internal #ifndef DOXYGEN_RUNNING @@ -843,6 +1419,24 @@ std::pair< np ); } + +template +std::pair< + std::vector::type::Point_3>, + std::vector> +> acvd_qem_simplification_polygon_soup( + TriangleMesh& tmesh, + const int& nb_vertices, + const NamedParameters& np = parameters::default_values() + ) +{ + return internal::acvd_qem( + tmesh, + nb_vertices, + np + ); +} #endif /** @@ -928,6 +1522,28 @@ TriangleMesh acvd_isotropic_remeshing( } +template +TriangleMesh acvd_qem_remeshing( + TriangleMesh& tmesh, + const int& nb_vertices, + const NamedParameters& np = parameters::default_values() + ) +{ + auto ps = acvd_qem_simplification_polygon_soup( + tmesh, + nb_vertices, + np + ); + + CGAL_assertion(is_polygon_soup_a_polygon_mesh(ps.second)); + + TriangleMesh simplified_mesh; + polygon_soup_to_polygon_mesh(ps.first, ps.second, simplified_mesh); + return simplified_mesh; +} + + } // namespace Polygon_mesh_processing } // namespace CGAL From 410ecb90ab736e8185f4c16d00795f762763fd70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 29 Mar 2024 00:25:11 +0100 Subject: [PATCH 56/56] update plugin with different options --- .../Plugins/PMP/ACVD_remeshing_dialog.ui | 26 +++++++----- .../Plugins/PMP/ACVD_remeshing_plugin.cpp | 40 ++++++++++++------- .../Polyhedron/Plugins/PMP/CMakeLists.txt | 9 +++-- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_remeshing_dialog.ui b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_remeshing_dialog.ui index a8e67668ef1..8c119c36ba6 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_remeshing_dialog.ui +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_remeshing_dialog.ui @@ -7,11 +7,11 @@ 0 0 400 - 300 + 241 - ACVD Simplification + ACVD Remeshing @@ -40,6 +40,12 @@ Isotropic Clustering + + true + + + buttonGroup + @@ -47,6 +53,9 @@ QEM Clustering + + buttonGroup + @@ -54,13 +63,9 @@ Anisotropic Clustering - - - - - - ACVD Simplification - + + buttonGroup + @@ -117,4 +122,7 @@ + + + diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_remeshing_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_remeshing_plugin.cpp index 8f9f5eed0d1..a288024489d 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_remeshing_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/ACVD_remeshing_plugin.cpp @@ -4,7 +4,7 @@ #include #include #include "Scene_polyhedron_selection_item.h" -#include "ui_ACVD_simplification_dialog.h" +#include "ui_ACVD_remeshing_dialog.h" #include "SMesh_type.h" typedef Scene_surface_mesh_item Scene_facegraph_item; @@ -31,13 +31,15 @@ typedef Scene_surface_mesh_item Scene_facegraph_item; typedef Scene_facegraph_item::Face_graph FaceGraph; using namespace CGAL::Three; -class Polyhedron_demo_acvd_simplification_plugin : +namespace PMP = CGAL::Polygon_mesh_processing; + +class Polyhedron_demo_acvd_remeshing_plugin : public QObject, public Polyhedron_demo_plugin_helper { Q_OBJECT Q_INTERFACES(CGAL::Three::Polyhedron_demo_plugin_interface) - Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.PluginInterface/1.0" FILE "acvd_simplification_plugin.json") + Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.PluginInterface/1.0" FILE "acvd_remeshing_plugin.json") public: bool applicable(QAction*) const { return qobject_cast(scene->item(scene->mainSelectionIndex())); @@ -84,9 +86,21 @@ public Q_SLOTS: if(!graph) return; QElapsedTimer time; time.start(); - CGAL::Three::Three::information("ACVD Simplification..."); + CGAL::Three::Three::information("ACVD Remeshing..."); + + QApplication::setOverrideCursor(Qt::WaitCursor); - FaceGraph simplified_graph = CGAL::Polygon_mesh_processing::acvd_isotropic_remeshing(*graph, ui.nb_clusters_spin_box->value()); + FaceGraph remeshed; + + // TODO add post-processing + + if (ui.IsotropicClustering->isChecked()) + remeshed = PMP::acvd_isotropic_remeshing(*graph, ui.nb_clusters_spin_box->value()); + else if (ui.QEMClustering->isChecked()) + remeshed = PMP::acvd_isotropic_remeshing(*graph, ui.nb_clusters_spin_box->value(), CGAL::parameters::post_processing_qem(true)); // make it its own option once acvd_qem_remeshing works + // remeshed = PMP::acvd_qem_remeshing(*graph, ui.nb_clusters_spin_box->value()); + else + remeshed = PMP::acvd_isotropic_remeshing(*graph, ui.nb_clusters_spin_box->value(), CGAL::parameters::gradation_factor(0.8)); // update the scene CGAL::Three::Three::information(QString("ok (%1 ms)").arg(time.elapsed())); @@ -94,20 +108,18 @@ public Q_SLOTS: item->invalidateOpenGLBuffers(); // add the simplified mesh to the scene - Scene_facegraph_item* simplified_item = new Scene_facegraph_item(simplified_graph); - simplified_item->setName(QString(item->name() + "_simplified")); - scene->addItem(simplified_item); - simplified_item->invalidateOpenGLBuffers(); - scene->itemChanged(simplified_item); + Scene_facegraph_item* remeshed_item = new Scene_facegraph_item(remeshed); + remeshed_item->setName(QString(item->name() + "_simplified")); + scene->addItem(remeshed_item); + remeshed_item->invalidateOpenGLBuffers(); + scene->itemChanged(remeshed_item); item->setVisible(false); - } private: QAction* actionACVD; Ui::ACVDDialog ui; -}; // end Polyhedron_demo_acvd_simplification_plugin +}; // end Polyhedron_demo_acvd_remeshing_plugin -// Q_EXPORT_PLUGIN2(Polyhedron_demo_acvd_simplification_plugin, Polyhedron_demo_acvd_simplification_plugin) -#include "ACVD_simplification_plugin.moc" +#include "ACVD_remeshing_plugin.moc" diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt b/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt index e98333d9471..923d678ee35 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt @@ -27,12 +27,13 @@ polyhedron_demo_plugin(extrude_plugin Extrude_plugin KEYWORDS PMP) target_link_libraries(extrude_plugin PUBLIC scene_surface_mesh_item scene_selection_item) -qt6_wrap_ui( acvd_remeshingUI_FILES ACVD_remeshing_dialog.ui) -polyhedron_demo_plugin(acvd_remeshing_plugin ACVD_remeshing_plugin ${acvd_remeshingUI_FILES} KEYWORDS PMP) -target_link_libraries(acvd_remeshing_plugin PUBLIC scene_surface_mesh_item scene_points_with_normal_item scene_polygon_soup_item CGAL::Eigen3_support) - if(TARGET CGAL::Eigen3_support) if("${EIGEN3_VERSION}" VERSION_GREATER "3.1.90") + + qt6_wrap_ui( acvd_remeshingUI_FILES ACVD_remeshing_dialog.ui) + polyhedron_demo_plugin(acvd_remeshing_plugin ACVD_remeshing_plugin ${acvd_remeshingUI_FILES} KEYWORDS PMP) + target_link_libraries(acvd_remeshing_plugin PUBLIC scene_surface_mesh_item scene_points_with_normal_item scene_polygon_soup_item CGAL::Eigen3_support) + qt6_wrap_ui( hole_fillingUI_FILES Hole_filling_widget.ui) polyhedron_demo_plugin(hole_filling_plugin Hole_filling_plugin ${hole_fillingUI_FILES} KEYWORDS PMP) target_link_libraries(hole_filling_plugin PUBLIC scene_surface_mesh_item scene_polylines_item scene_selection_item CGAL::Eigen3_support)