From 048d38d18723eadace96d369fbb36f3c09225207 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 10 Jul 2022 03:10:01 +0200 Subject: [PATCH 001/161] interpolated mu i measures (yet to check ) I will replace ExactPredInexactConstructs with a geometry traits template next ISA --- .../interpolated_curvature_measures.h | 88 +++++++++++++++++++ .../Triangulation_2/triangulation_prog1.cpp | 63 ++++++++----- 2 files changed, 131 insertions(+), 20 deletions(-) create mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_curvature_measures.h diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_curvature_measures.h new file mode 100644 index 000000000000..e4f67d03e17a --- /dev/null +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_curvature_measures.h @@ -0,0 +1,88 @@ +#ifndef CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CURVATURE_MEASURES_H +#define CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CURVATURE_MEASURES_H +#endif + +#include +#include +#include +#include + +namespace CGAL { + +namespace Polygon_mesh_processing { + +typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic; + +// enum to specify which measure is computed +enum MEASURE_INDEX { + MU0_AREA_MEASURE, + MU1_MEAN_CURVATURE_MEASURE, + MU2_GAUSSIAN_CURVATURE_MEASURE +}; + +Epic::FT interpolated_mu_i_triangle(const Epic::Vector_3 x0, const Epic::Vector_3 x1, const Epic::Vector_3 x2, + const Epic::Vector_3 u0, const Epic::Vector_3 u1, const Epic::Vector_3 u2, MEASURE_INDEX mu_i) +{ + Epic::Vector_3 um; + switch (mu_i) + { + case MU0_AREA_MEASURE: + + um = (u0 + u1 + u2) / 3.0; + + return 0.5 * um * CGAL::cross_product(x1 - x0, x2 - x0); + + case MU1_MEAN_CURVATURE_MEASURE: + + um = (u0 + u1 + u2) / 3.0; + + return 0.5 * um * (CGAL::cross_product(u2 - u1, x0) + + CGAL::cross_product(u0 - u2, x1) + + CGAL::cross_product(u1 - u0, x2)); + + case MU2_GAUSSIAN_CURVATURE_MEASURE: + + return 0.5 * u0 * CGAL::cross_product(u1, u2); + + default: return 0; + } +} + +Epic::FT interpolated_mu_i_face(std::vector& x, std::vector& u, MEASURE_INDEX mu_i) +{ + std::size_t n = x.size(); + CGAL_precondition(u.size() == n); + CGAL_precondition(n >= 3); + + if (n == 3) + return interpolated_mu_i_triangle(x[0], x[1], x[2], + u[0], u[1], u[2], mu_i); + + /// If Quad measure formulas (Bilinear Interpolation) proved to be better, + /// they will be implemented and called here + //else if(n == 4) + // return interpolated_mu0_quad(x[0], x[1], x[2], x[3] + // u[0], u[1], u[2], u[3]); + else { + Epic::FT mu0 = 0; + + // getting barycenter of points + Epic::Vector_3 xm = std::accumulate(x.begin(), x.end(), Epic::Vector_3(0, 0, 0)); + xm /= n; + + // getting unit average normal of points + Epic::Vector_3 um = std::accumulate(u.begin(), u.end(), Epic::Vector_3(0,0,0)); + um /= sqrt(um * um); + + // summing each triangle's measure after triangulation by barycenter split. + for (std::size_t i = 0; i < n; i++) + { + mu0 += interpolated_mu_i_triangle(x[i], x[(i + 1) % n], xm, + u[i], u[(i + 1) % n], um, mu_i); + } + return mu0; + } +} + +} +} diff --git a/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp b/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp index e68f7973163d..e52400cd5d0c 100644 --- a/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp +++ b/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp @@ -1,27 +1,50 @@ -#include - #include -#include -typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +#include +#include +#include +#include + + +#include +#include +#include +#include +#include + +#define N 4 +namespace PMP = CGAL::Polygon_mesh_processing; -typedef CGAL::Triangulation_2 Triangulation; -typedef Triangulation::Vertex_circulator Vertex_circulator; -typedef Triangulation::Point Point; +typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic; -int main() { - std::ifstream in("data/triangulation_prog1.cin"); - std::istream_iterator begin(in); - std::istream_iterator end; +int main(int argc, char* argv[]) +{ + srand(time(NULL)); - Triangulation t; - t.insert(begin, end); + std::vector X(N); + std::vector U(N); + + for (int i = 0; i < N; i++) { + X[i] = { rand() , rand() , rand() }; + U[i] = { rand() , rand() , rand() }; + U[i] = U[i] / sqrt(U[i] * U[i]); + } + std::cout << PMP::interpolated_mu_i_face(X, U, PMP::MU0_AREA_MEASURE) << std::endl; + std::cout << PMP::interpolated_mu_i_face(X, U, PMP::MU1_MEAN_CURVATURE_MEASURE) << std::endl; + std::cout << PMP::interpolated_mu_i_face(X, U, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE) << std::endl; + + /*srand(time(NULL)); + Epic::Vector_3 x, y; + Epic::FT d = 0; + + Epic::Vector_3 z; + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + for (int i = 0; i < N; i++) + { + x * y; + } + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + std::cout << std::chrono::duration_cast(end - begin).count() << std::endl;*/ - Vertex_circulator vc = t.incident_vertices(t.infinite_vertex()), - done(vc); - if (vc != nullptr) { - do { std::cout << vc->point() << std::endl; - }while(++vc != done); - } - return 0; } + From e63de4f48ae1772992c4c64f158c8c353984e4b1 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 13 Jul 2022 07:59:21 +0200 Subject: [PATCH 002/161] implemented computing mu_i over all faces it works fine, still need to handle if vnm is not given --- ...nterpolated_corrected_curvature_measures.h | 201 ++++++++++++++++++ .../interpolated_curvature_measures.h | 88 -------- .../Triangulation_2/triangulation_prog1.cpp | 76 ++++++- 3 files changed, 267 insertions(+), 98 deletions(-) create mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h delete mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_curvature_measures.h diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h new file mode 100644 index 000000000000..1ebf5fe4dfef --- /dev/null +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -0,0 +1,201 @@ +#ifndef CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H +#define CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H +#endif + +#include +#include + +#include +#include +#include +#include +#include + + +namespace CGAL { + + namespace Polygon_mesh_processing { + + typedef Exact_predicates_inexact_constructions_kernel Epic; + + // enum to specify which measure is computed + enum Measure_index { + MU0_AREA_MEASURE, + MU1_MEAN_CURVATURE_MEASURE, + MU2_GAUSSIAN_CURVATURE_MEASURE + }; + + Epic::FT interpolated_mu_i_triangle(const Epic::Vector_3 x0, const Epic::Vector_3 x1, const Epic::Vector_3 x2, + const Epic::Vector_3 u0, const Epic::Vector_3 u1, const Epic::Vector_3 u2, const Measure_index mu_i) + { + Epic::Vector_3 um; + switch (mu_i) + { + case MU0_AREA_MEASURE: + + um = (u0 + u1 + u2) / 3.0; + + return 0.5 * um * CGAL::cross_product(x1 - x0, x2 - x0); + + case MU1_MEAN_CURVATURE_MEASURE: + + um = (u0 + u1 + u2) / 3.0; + + return 0.5 * um * (CGAL::cross_product(u2 - u1, x0) + + CGAL::cross_product(u0 - u2, x1) + + CGAL::cross_product(u1 - u0, x2)); + + case MU2_GAUSSIAN_CURVATURE_MEASURE: + + return 0.5 * u0 * CGAL::cross_product(u1, u2); + + default: return 0; + } + } + + Epic::FT interpolated_mu_i_quad(const Epic::Vector_3 x0, const Epic::Vector_3 x1, const Epic::Vector_3 x2, const Epic::Vector_3 x3, + const Epic::Vector_3 u0, const Epic::Vector_3 u1, const Epic::Vector_3 u2, const Epic::Vector_3 u3, const Measure_index mu_i) + { + /// x0 _ x1 + /// x2 |_| x3 + + switch (mu_i) + { + case MU0_AREA_MEASURE: + + return (1 / 36.0) * ((4 * u0 + 2 * u1 + 2 * u2 + u3) * CGAL::cross_product(x1 - x0, x2 - x0) + + (2 * u0 + 4 * u1 + u2 + 2 * u3) * CGAL::cross_product(x1 - x0, x3 - x1) + + (2 * u0 + u1 + 4 * u2 + 2 * u3) * CGAL::cross_product(x3 - x2, x2 - x0) + + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * CGAL::cross_product(x3 - x2, x3 - x1)); + + case MU1_MEAN_CURVATURE_MEASURE: + { + const Epic::Vector_3 u03 = u3 - u0; + const Epic::Vector_3 u12 = u2 - u1; + const Epic::Vector_3 x0_cross = CGAL::cross_product(u12, x0); + const Epic::Vector_3 x1_cross = -CGAL::cross_product(u03, x1); + const Epic::Vector_3 x2_cross = CGAL::cross_product(u03, x2); + const Epic::Vector_3 x3_cross = -CGAL::cross_product(u12, x3); + + + return (1 / 12.0) * (u0 * (2 * x0_cross - CGAL::cross_product((u2 + u3), x1) + CGAL::cross_product((u1 + u3), x2) + x3_cross) + + u1 * (CGAL::cross_product((u2 + u3), x0) + 2 * x1_cross + x2_cross - CGAL::cross_product((u0 + u2), x3)) + + u2 * (CGAL::cross_product(-(u1 + u3), x0) + x1_cross + 2 * x2_cross + CGAL::cross_product((u0 + u1), x3)) + + u3 * (x0_cross + CGAL::cross_product((u0 + u2), x1) - CGAL::cross_product((u0 + u1), x2) + 2 * x3_cross)); + } + case MU2_GAUSSIAN_CURVATURE_MEASURE: + + return (1 / 36.0) * ((4 * u0 + 2 * u1 + 2 * u2 + u3) * CGAL::cross_product(u1 - u0, u2 - u0) + + (2 * u0 + 4 * u1 + u2 + 2 * u3) * CGAL::cross_product(u1 - u0, u3 - u1) + + (2 * u0 + u1 + 4 * u2 + 2 * u3) * CGAL::cross_product(u3 - u2, u2 - u0) + + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * CGAL::cross_product(u3 - u2, u3 - u1)); + + default: return 0; + } + } + + Epic::FT interpolated_mu_i_face(const std::vector& x, const std::vector& u, const Measure_index mu_i) + { + std::size_t n = x.size(); + CGAL_precondition(u.size() == n); + CGAL_precondition(n >= 3); + + // Triangle: use triangle formulas + if (n == 3) + return interpolated_mu_i_triangle(x[0], x[1], x[2], + u[0], u[1], u[2], mu_i); + + // Quad: use bilinear interpolation formulas + else if (n == 4) + /// x[0] _ x[1] ---> x0 _ x1 (reason for changing order) + /// x[3] |_| x[2] ---> x2 |_| x3 + return interpolated_mu_i_quad(x[0], x[1], x[3], x[2], + u[0], u[1], u[3], u[2], mu_i); + + // N-gon: split into n triangles by barycenter and use triangle formulas for each + else { + Epic::FT mu0 = 0; + + // getting barycenter of points + Epic::Vector_3 xm = std::accumulate(x.begin(), x.end(), Epic::Vector_3(0, 0, 0)); + xm /= n; + + // getting unit average normal of points + Epic::Vector_3 um = std::accumulate(u.begin(), u.end(), Epic::Vector_3(0, 0, 0)); + um /= sqrt(um * um); + + // summing each triangle's measure after triangulation by barycenter split. + for (std::size_t i = 0; i < n; i++) + { + mu0 += interpolated_mu_i_triangle(x[i], x[(i + 1) % n], xm, + u[i], u[(i + 1) % n], um, mu_i); + } + return mu0; + } + } + + + /// TODO: + /// 1- Handle if VNM is not given + /// 2- use GT instead of Epic + + template + std::vector + interpolated_corrected_measure_i( + const PolygonMesh& pmesh, + const Measure_index mu_i, + VertexNormalMap vnm, + const NamedParameters& np = parameters::default_values()) + { + using parameters::choose_parameter; + using parameters::get_parameter; + + typedef boost::graph_traits::face_descriptor face_descriptor; + typedef boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + typename GetVertexPointMap::const_type + vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), + get_const_property_map(CGAL::vertex_point, pmesh)); + + //std::unordered_map vnm_init; + + //boost::associative_property_map + // ::vertex_descriptor, Epic::Vector_3>> vnm(vnm_init); + + ////if (!vnm) + // { + // compute_vertex_normals(pmesh, vnm, np); + //} + + std::vector mu_i_map; + + + for (face_descriptor f : faces(pmesh)) + { + halfedge_descriptor h_start = pmesh.halfedge(f); + halfedge_descriptor h_iter = h_start; + + std::vector x; + std::vector u; + + // looping over vertices in face + do { + vertex_descriptor v = source(h_iter, pmesh); + Epic::Point_3 p = get(vpm, v); + x.push_back(Epic::Vector_3(p.x(),p.y(),p.z())); + u.push_back(get(vnm, v)); + h_iter = next(h_iter, pmesh); + } while (h_iter != h_start); + + + mu_i_map.push_back(interpolated_mu_i_face(x, u, mu_i)); + } + return mu_i_map; + } + + } +} + diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_curvature_measures.h deleted file mode 100644 index e4f67d03e17a..000000000000 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_curvature_measures.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CURVATURE_MEASURES_H -#define CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CURVATURE_MEASURES_H -#endif - -#include -#include -#include -#include - -namespace CGAL { - -namespace Polygon_mesh_processing { - -typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic; - -// enum to specify which measure is computed -enum MEASURE_INDEX { - MU0_AREA_MEASURE, - MU1_MEAN_CURVATURE_MEASURE, - MU2_GAUSSIAN_CURVATURE_MEASURE -}; - -Epic::FT interpolated_mu_i_triangle(const Epic::Vector_3 x0, const Epic::Vector_3 x1, const Epic::Vector_3 x2, - const Epic::Vector_3 u0, const Epic::Vector_3 u1, const Epic::Vector_3 u2, MEASURE_INDEX mu_i) -{ - Epic::Vector_3 um; - switch (mu_i) - { - case MU0_AREA_MEASURE: - - um = (u0 + u1 + u2) / 3.0; - - return 0.5 * um * CGAL::cross_product(x1 - x0, x2 - x0); - - case MU1_MEAN_CURVATURE_MEASURE: - - um = (u0 + u1 + u2) / 3.0; - - return 0.5 * um * (CGAL::cross_product(u2 - u1, x0) - + CGAL::cross_product(u0 - u2, x1) - + CGAL::cross_product(u1 - u0, x2)); - - case MU2_GAUSSIAN_CURVATURE_MEASURE: - - return 0.5 * u0 * CGAL::cross_product(u1, u2); - - default: return 0; - } -} - -Epic::FT interpolated_mu_i_face(std::vector& x, std::vector& u, MEASURE_INDEX mu_i) -{ - std::size_t n = x.size(); - CGAL_precondition(u.size() == n); - CGAL_precondition(n >= 3); - - if (n == 3) - return interpolated_mu_i_triangle(x[0], x[1], x[2], - u[0], u[1], u[2], mu_i); - - /// If Quad measure formulas (Bilinear Interpolation) proved to be better, - /// they will be implemented and called here - //else if(n == 4) - // return interpolated_mu0_quad(x[0], x[1], x[2], x[3] - // u[0], u[1], u[2], u[3]); - else { - Epic::FT mu0 = 0; - - // getting barycenter of points - Epic::Vector_3 xm = std::accumulate(x.begin(), x.end(), Epic::Vector_3(0, 0, 0)); - xm /= n; - - // getting unit average normal of points - Epic::Vector_3 um = std::accumulate(u.begin(), u.end(), Epic::Vector_3(0,0,0)); - um /= sqrt(um * um); - - // summing each triangle's measure after triangulation by barycenter split. - for (std::size_t i = 0; i < n; i++) - { - mu0 += interpolated_mu_i_triangle(x[i], x[(i + 1) % n], xm, - u[i], u[(i + 1) % n], um, mu_i); - } - return mu0; - } -} - -} -} diff --git a/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp b/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp index e52400cd5d0c..2e625d722cc1 100644 --- a/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp +++ b/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp @@ -1,25 +1,79 @@ #include +#include +#include +#include +#include -#include -#include -#include -#include +#include - -#include -#include +#include +#include #include #include #include +#include -#define N 4 namespace PMP = CGAL::Polygon_mesh_processing; typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic; +typedef CGAL::Polyhedron_3 Polyhedron; +typedef CGAL::Surface_mesh Mesh; +typedef boost::graph_traits::face_descriptor face_descriptor; +typedef boost::graph_traits::vertex_descriptor vertex_descriptor; + + int main(int argc, char* argv[]) { - srand(time(NULL)); + Mesh g1; + if (!PMP::IO::read_polygon_mesh("small_bunny.obj", g1) || !CGAL::is_triangle_mesh(g1)) + { + std::cerr << "Invalid input." << std::endl; + return 1; + } + + //int n1 = g1.size_of_facets(); + + std::unordered_map vnm_vec; + boost::associative_property_map< std::unordered_map> vnm(vnm_vec); + + PMP::compute_vertex_normals(g1, vnm); + + + + std::vector mu0_map, mu1_map, mu2_map; + + mu0_map = PMP::interpolated_corrected_measure_i(g1, PMP::MU0_AREA_MEASURE, vnm); + mu1_map = PMP::interpolated_corrected_measure_i(g1, PMP::MU1_MEAN_CURVATURE_MEASURE, vnm); + mu2_map = PMP::interpolated_corrected_measure_i(g1, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE, vnm); + + int n = g1.faces().size(); + + for (int i = 0; i < n; i++) + { + std::cout << mu0_map[i] << "\n"; + } + + std::cout << "\n"; + + for (int i = 0; i < n; i++) + { + std::cout << mu1_map[i] << "\n"; + } + + std::cout << "\n"; + + for (int i = 0; i < n; i++) + { + std::cout << mu2_map[i] << "\n"; + } + + + + + /*srand(time(NULL)); + + CGAL::GetGeomTraits GT; std::vector X(N); std::vector U(N); @@ -29,9 +83,11 @@ int main(int argc, char* argv[]) U[i] = { rand() , rand() , rand() }; U[i] = U[i] / sqrt(U[i] * U[i]); } + + std::cout << PMP::interpolated_mu_i_face(X, U, PMP::MU0_AREA_MEASURE) << std::endl; std::cout << PMP::interpolated_mu_i_face(X, U, PMP::MU1_MEAN_CURVATURE_MEASURE) << std::endl; - std::cout << PMP::interpolated_mu_i_face(X, U, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE) << std::endl; + std::cout << PMP::interpolated_mu_i_face(X, U, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE) << std::endl;*/ /*srand(time(NULL)); Epic::Vector_3 x, y; From 2eeb88ac96cb49755b487cb01a1bfc3fc8f0f1fb Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 15 Jul 2022 13:18:56 +0200 Subject: [PATCH 003/161] moved my main program to a new example file in PMP --- .../Polygon_mesh_processing/CMakeLists.txt | 12 +- .../interpolated_corrected_curvatures.cpp | 75 +++++++++++ .../Triangulation_2/triangulation_prog1.cpp | 120 +++--------------- 3 files changed, 106 insertions(+), 101 deletions(-) create mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index baa4d41677ac..d6f4e273516f 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -5,11 +5,13 @@ cmake_minimum_required(VERSION 3.1...3.23) project(Polygon_mesh_processing_Examples) # CGAL and its components -find_package(CGAL REQUIRED) +find_package(CGAL REQUIRED OPTIONAL_COMPONENTS Qt5) # Boost and its components find_package(Boost REQUIRED) + + if(NOT Boost_FOUND) message( @@ -100,6 +102,14 @@ create_single_source_cgal_program("orientation_pipeline_example.cpp") #create_single_source_cgal_program( "snapping_example.cpp") create_single_source_cgal_program("match_faces.cpp") create_single_source_cgal_program("cc_compatible_orientations.cpp") +create_single_source_cgal_program("interpolated_corrected_curvatures.cpp") + +if(CGAL_Qt5_FOUND) + + #link it with the required CGAL libraries + target_link_libraries(interpolated_corrected_curvatures PUBLIC CGAL::CGAL_Basic_viewer) + +endif() if(OpenMesh_FOUND) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp new file mode 100644 index 000000000000..f45bec68a03b --- /dev/null +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace PMP = CGAL::Polygon_mesh_processing; + +typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic; +typedef CGAL::Polyhedron_3 Polyhedron; +typedef CGAL::Surface_mesh Mesh; +typedef boost::graph_traits::face_descriptor face_descriptor; +typedef boost::graph_traits::vertex_descriptor vertex_descriptor; + + +int main(int argc, char* argv[]) +{ + const std::string filename = (argc>1) ? argv[1] : CGAL::data_file_path("small_bunny.obj"); + + Mesh g1; + if(!CGAL::IO::read_polygon_mesh(filename, g1)) + { + std::cerr << "Invalid input file." << std::endl; + return EXIT_FAILURE; + } + std::unordered_map vnm_vec; + boost::associative_property_map< std::unordered_map> vnm(vnm_vec); + + PMP::compute_vertex_normals(g1, vnm); + + + std::vector mu0_map, mu1_map, mu2_map; + + mu0_map = PMP::interpolated_corrected_measure_i(g1, PMP::MU0_AREA_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); + mu1_map = PMP::interpolated_corrected_measure_i(g1, PMP::MU1_MEAN_CURVATURE_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); + mu2_map = PMP::interpolated_corrected_measure_i(g1, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); + + int n = g1.faces().size(); + + for (int i = 0; i < n; i++) + { + std::cout << mu0_map[i] << "\n"; + } + + std::cout << "\n"; + + for (int i = 0; i < n; i++) + { + std::cout << mu1_map[i] << "\n"; + } + + std::cout << "\n"; + + for (int i = 0; i < n; i++) + { + std::cout << mu2_map[i] << "\n"; + } + + + CGAL::draw(g1); + + return EXIT_SUCCESS; +} diff --git a/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp b/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp index 2e625d722cc1..86b2c23cb481 100644 --- a/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp +++ b/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp @@ -1,106 +1,26 @@ -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -namespace PMP = CGAL::Polygon_mesh_processing; - -typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic; -typedef CGAL::Polyhedron_3 Polyhedron; -typedef CGAL::Surface_mesh Mesh; -typedef boost::graph_traits::face_descriptor face_descriptor; -typedef boost::graph_traits::vertex_descriptor vertex_descriptor; - - - -int main(int argc, char* argv[]) -{ - Mesh g1; - if (!PMP::IO::read_polygon_mesh("small_bunny.obj", g1) || !CGAL::is_triangle_mesh(g1)) - { - std::cerr << "Invalid input." << std::endl; - return 1; - } - - //int n1 = g1.size_of_facets(); - - std::unordered_map vnm_vec; - boost::associative_property_map< std::unordered_map> vnm(vnm_vec); - - PMP::compute_vertex_normals(g1, vnm); - - - - std::vector mu0_map, mu1_map, mu2_map; - - mu0_map = PMP::interpolated_corrected_measure_i(g1, PMP::MU0_AREA_MEASURE, vnm); - mu1_map = PMP::interpolated_corrected_measure_i(g1, PMP::MU1_MEAN_CURVATURE_MEASURE, vnm); - mu2_map = PMP::interpolated_corrected_measure_i(g1, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE, vnm); +#include - int n = g1.faces().size(); - - for (int i = 0; i < n; i++) - { - std::cout << mu0_map[i] << "\n"; - } - - std::cout << "\n"; - - for (int i = 0; i < n; i++) - { - std::cout << mu1_map[i] << "\n"; - } - - std::cout << "\n"; - - for (int i = 0; i < n; i++) - { - std::cout << mu2_map[i] << "\n"; - } - - - - - /*srand(time(NULL)); - - CGAL::GetGeomTraits GT; - - std::vector X(N); - std::vector U(N); - - for (int i = 0; i < N; i++) { - X[i] = { rand() , rand() , rand() }; - U[i] = { rand() , rand() , rand() }; - U[i] = U[i] / sqrt(U[i] * U[i]); - } - +#include +#include - std::cout << PMP::interpolated_mu_i_face(X, U, PMP::MU0_AREA_MEASURE) << std::endl; - std::cout << PMP::interpolated_mu_i_face(X, U, PMP::MU1_MEAN_CURVATURE_MEASURE) << std::endl; - std::cout << PMP::interpolated_mu_i_face(X, U, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE) << std::endl;*/ +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; - /*srand(time(NULL)); - Epic::Vector_3 x, y; - Epic::FT d = 0; +typedef CGAL::Triangulation_2 Triangulation; +typedef Triangulation::Vertex_circulator Vertex_circulator; +typedef Triangulation::Point Point; - Epic::Vector_3 z; - std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); - for (int i = 0; i < N; i++) - { - x * y; - } - std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); - std::cout << std::chrono::duration_cast(end - begin).count() << std::endl;*/ +int main() { + std::ifstream in("data/triangulation_prog1.cin"); + std::istream_iterator begin(in); + std::istream_iterator end; -} + Triangulation t; + t.insert(begin, end); + Vertex_circulator vc = t.incident_vertices(t.infinite_vertex()), + done(vc); + if (vc != nullptr) { + do { std::cout << vc->point() << std::endl; + }while(++vc != done); + } + return 0; \ No newline at end of file From 5af7795d9450ed2ff27e954b23cf65cfb3524cf5 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 15 Jul 2022 16:47:44 +0200 Subject: [PATCH 004/161] typo fix --- .../examples/Triangulation_2/triangulation_prog1.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp b/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp index 86b2c23cb481..b212837b4ee2 100644 --- a/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp +++ b/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp @@ -23,4 +23,5 @@ int main() { do { std::cout << vc->point() << std::endl; }while(++vc != done); } - return 0; \ No newline at end of file + return 0; +} \ No newline at end of file From 8c943fd433b4d3ce1390aa4802847a6a851d584c Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 15 Jul 2022 17:23:13 +0200 Subject: [PATCH 005/161] typo fix --- .../examples/Triangulation_2/triangulation_prog1.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp b/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp index b212837b4ee2..e68f7973163d 100644 --- a/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp +++ b/Triangulation_2/examples/Triangulation_2/triangulation_prog1.cpp @@ -24,4 +24,4 @@ int main() { }while(++vc != done); } return 0; -} \ No newline at end of file +} From 4b0577a2cff9d47e99577701eae6c3e085cc14c4 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 17 Jul 2022 18:46:04 +0200 Subject: [PATCH 006/161] added doc (yet to build), used generic GT instead of Epic, made VNM a named parameter (WIP) --- .../PackageDescription.txt | 4 + .../doc/Polygon_mesh_processing/examples.txt | 1 + .../interpolated_corrected_curvatures.cpp | 6 +- ...nterpolated_corrected_curvature_measures.h | 422 +++++++++++------- 4 files changed, 271 insertions(+), 162 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 34d2f83f08e2..9f6d7ffd4736 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -16,6 +16,10 @@ /// Functions to triangulate faces, and to refine and fair regions of a polygon mesh. /// \ingroup PkgPolygonMeshProcessingRef +/// \defgroup PMP_corrected_curvatures_grp Corrected Curvature Computation +/// Functions to compute the corrected curvatures of a polygon mesh. +/// \ingroup PkgPolygonMeshProcessingRef + /// \defgroup PMP_normal_grp Normal Computation /// Functions to compute unit normals for individual/all vertices or faces. /// \ingroup PkgPolygonMeshProcessingRef diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index 3ce5384c81ce..0317812aa443 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -19,6 +19,7 @@ \example Polygon_mesh_processing/refine_fair_example.cpp \example Polygon_mesh_processing/mesh_slicer_example.cpp \example Polygon_mesh_processing/isotropic_remeshing_example.cpp +\example Polygon_mesh_processing/interpolated_corrected_curvatures.cpp \example Polygon_mesh_processing/delaunay_remeshing_example.cpp \example Polygon_mesh_processing/compute_normals_example_Polyhedron.cpp \example Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp index f45bec68a03b..c117e3f817fe 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -43,9 +43,9 @@ int main(int argc, char* argv[]) std::vector mu0_map, mu1_map, mu2_map; - mu0_map = PMP::interpolated_corrected_measure_i(g1, PMP::MU0_AREA_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); - mu1_map = PMP::interpolated_corrected_measure_i(g1, PMP::MU1_MEAN_CURVATURE_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); - mu2_map = PMP::interpolated_corrected_measure_i(g1, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); + mu0_map = PMP::interpolated_corrected_measure_mesh(g1, PMP::MU0_AREA_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); + mu1_map = PMP::interpolated_corrected_measure_mesh(g1, PMP::MU1_MEAN_CURVATURE_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); + mu2_map = PMP::interpolated_corrected_measure_mesh(g1, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); int n = g1.faces().size(); diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 1ebf5fe4dfef..df5197b2f005 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -2,7 +2,6 @@ #define CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H #endif -#include #include #include @@ -14,188 +13,293 @@ namespace CGAL { - namespace Polygon_mesh_processing { - - typedef Exact_predicates_inexact_constructions_kernel Epic; - - // enum to specify which measure is computed - enum Measure_index { - MU0_AREA_MEASURE, - MU1_MEAN_CURVATURE_MEASURE, - MU2_GAUSSIAN_CURVATURE_MEASURE - }; - - Epic::FT interpolated_mu_i_triangle(const Epic::Vector_3 x0, const Epic::Vector_3 x1, const Epic::Vector_3 x2, - const Epic::Vector_3 u0, const Epic::Vector_3 u1, const Epic::Vector_3 u2, const Measure_index mu_i) - { - Epic::Vector_3 um; - switch (mu_i) - { - case MU0_AREA_MEASURE: - - um = (u0 + u1 + u2) / 3.0; +namespace Polygon_mesh_processing { + +// enum to specify which measure is computed +enum Measure_index { + MU0_AREA_MEASURE, + MU1_MEAN_CURVATURE_MEASURE, + MU2_GAUSSIAN_CURVATURE_MEASURE +}; + +/** +* \ingroup PMP_corrected_curvatures_grp +* +* computes the interpolated corrected measure of specific triangle. +* +* @tparam GT is the geometric traits class. +* +* @param x0 is the position of vertex #0. +* @param x1 is the position of vertex #1. +* @param x2 is the position of vertex #2. +* @param u0 is the normal vector of vertex #0. +* @param u1 is the normal vector of vertex #1. +* @param u2 is the normal vector of vertex #2. +* @param mu_i an enum for choosing between computing the area measure, +* the mean curvature measure, or the gaussian curvature measure. +* +* @return a scalar of type GT::FT. This is the value of the interpolated corrected measure of the given triangle. +* +* @see `interpolated_corrected_measure_face()` +* @see `interpolated_corrected_measure_quad()` +*/ +template +typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vector_3 x0, const typename GT::Vector_3 x1, const typename GT::Vector_3 x2, + const typename GT::Vector_3 u0, const typename GT::Vector_3 u1, const typename GT::Vector_3 u2, const Measure_index mu_i) +{ + switch (mu_i) + { + case MU0_AREA_MEASURE: + { + const typename GT::Vector_3 um = (u0 + u1 + u2) / 3.0; + + return 0.5 * um * CGAL::cross_product(x1 - x0, x2 - x0); + } + case MU1_MEAN_CURVATURE_MEASURE: + { + const typename GT::Vector_3 um = (u0 + u1 + u2) / 3.0; - return 0.5 * um * CGAL::cross_product(x1 - x0, x2 - x0); + return 0.5 * um * (CGAL::cross_product(u2 - u1, x0) + + CGAL::cross_product(u0 - u2, x1) + + CGAL::cross_product(u1 - u0, x2)); + } + case MU2_GAUSSIAN_CURVATURE_MEASURE: - case MU1_MEAN_CURVATURE_MEASURE: + return 0.5 * u0 * CGAL::cross_product(u1, u2); - um = (u0 + u1 + u2) / 3.0; + default: return 0; + } +} - return 0.5 * um * (CGAL::cross_product(u2 - u1, x0) - + CGAL::cross_product(u0 - u2, x1) - + CGAL::cross_product(u1 - u0, x2)); +/** +* \ingroup PMP_corrected_curvatures_grp +* +* computes the interpolated corrected measure of specific quad +* Note that the vertices 0 to 3 are ordered like this \n +* v0 _ v1 \n +* v2 |_| v3 +* +* @tparam GT is the geometric traits class. +* +* @param x0 is the position of vertex #0. +* @param x1 is the position of vertex #1. +* @param x2 is the position of vertex #2. +* @param x3 is the position of vertex #3. +* @param u0 is the normal vector of vertex #0. +* @param u1 is the normal vector of vertex #1. +* @param u2 is the normal vector of vertex #2. +* @param u3 is the normal vector of vertex #3. +* @param mu_i an enum for choosing between computing the area measure, +* the mean curvature measure, or the gaussian curvature measure. +* +* @return a scalar of type GT::FT. This is the value of the interpolated corrected measure of the given triangle. +* +* @see `interpolated_corrected_measure_face()` +* @see `interpolated_corrected_measure_quad()` +*/ +template +typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 x0, const typename GT::Vector_3 x1, const typename GT::Vector_3 x2, const typename GT::Vector_3 x3, + const typename GT::Vector_3 u0, const typename GT::Vector_3 u1, const typename GT::Vector_3 u2, const typename GT::Vector_3 u3, const Measure_index mu_i) +{ + /// x0 _ x1 + /// x2 |_| x3 + + switch (mu_i) + { + case MU0_AREA_MEASURE: + + return (1 / 36.0) * ((4 * u0 + 2 * u1 + 2 * u2 + u3) * CGAL::cross_product(x1 - x0, x2 - x0) + + (2 * u0 + 4 * u1 + u2 + 2 * u3) * CGAL::cross_product(x1 - x0, x3 - x1) + + (2 * u0 + u1 + 4 * u2 + 2 * u3) * CGAL::cross_product(x3 - x2, x2 - x0) + + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * CGAL::cross_product(x3 - x2, x3 - x1)); + + case MU1_MEAN_CURVATURE_MEASURE: + { + const typename GT::Vector_3 u03 = u3 - u0; + const typename GT::Vector_3 u12 = u2 - u1; + const typename GT::Vector_3 x0_cross = CGAL::cross_product(u12, x0); + const typename GT::Vector_3 x1_cross = -CGAL::cross_product(u03, x1); + const typename GT::Vector_3 x2_cross = CGAL::cross_product(u03, x2); + const typename GT::Vector_3 x3_cross = -CGAL::cross_product(u12, x3); + + + return (1 / 12.0) * (u0 * (2 * x0_cross - CGAL::cross_product((u2 + u3), x1) + CGAL::cross_product((u1 + u3), x2) + x3_cross) + + u1 * (CGAL::cross_product((u2 + u3), x0) + 2 * x1_cross + x2_cross - CGAL::cross_product((u0 + u2), x3)) + + u2 * (CGAL::cross_product(-(u1 + u3), x0) + x1_cross + 2 * x2_cross + CGAL::cross_product((u0 + u1), x3)) + + u3 * (x0_cross + CGAL::cross_product((u0 + u2), x1) - CGAL::cross_product((u0 + u1), x2) + 2 * x3_cross)); + } + case MU2_GAUSSIAN_CURVATURE_MEASURE: - case MU2_GAUSSIAN_CURVATURE_MEASURE: + return (1 / 36.0) * ((4 * u0 + 2 * u1 + 2 * u2 + u3) * CGAL::cross_product(u1 - u0, u2 - u0) + + (2 * u0 + 4 * u1 + u2 + 2 * u3) * CGAL::cross_product(u1 - u0, u3 - u1) + + (2 * u0 + u1 + 4 * u2 + 2 * u3) * CGAL::cross_product(u3 - u2, u2 - u0) + + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * CGAL::cross_product(u3 - u2, u3 - u1)); - return 0.5 * u0 * CGAL::cross_product(u1, u2); + default: return 0; + } +} - default: return 0; - } - } - Epic::FT interpolated_mu_i_quad(const Epic::Vector_3 x0, const Epic::Vector_3 x1, const Epic::Vector_3 x2, const Epic::Vector_3 x3, - const Epic::Vector_3 u0, const Epic::Vector_3 u1, const Epic::Vector_3 u2, const Epic::Vector_3 u3, const Measure_index mu_i) +/** +* \ingroup PMP_corrected_curvatures_grp +* +* computes the interpolated corrected measure of specific face. +* +* @tparam GT is the geometric traits class. +* +* @param x is a vector of the vertex positions of the face. +* @param u is a vector of the vertex nomrals of the face. +* @param mu_i an enum for choosing between computing the area measure, +* the mean curvature measure, or the gaussian curvature measure. +* +* @return a scalar of type GT::FT. This is the value of the interpolated corrected measure of the given face. +* +* @see `interpolated_corrected_measure_triangle()` +* @see `interpolated_corrected_measure_quad()` +* @see `interpolated_corrected_measure_mesh()` +*/ +template +typename GT::FT interpolated_corrected_measure_face(const std::vector& x, const std::vector& u, const Measure_index mu_i) +{ + std::size_t n = x.size(); + CGAL_precondition(u.size() == n); + CGAL_precondition(n >= 3); + + // Triangle: use triangle formulas + if (n == 3) + return interpolated_corrected_measure_triangle(x[0], x[1], x[2], + u[0], u[1], u[2], mu_i); + + // Quad: use bilinear interpolation formulas + else if (n == 4) + /// x[0] _ x[1] ---> x0 _ x1 (reason for changing order) + /// x[3] |_| x[2] ---> x2 |_| x3 + return interpolated_corrected_measure_quad(x[0], x[1], x[3], x[2], + u[0], u[1], u[3], u[2], mu_i); + + // N-gon: split into n triangles by barycenter and use triangle formulas for each + else { + typename GT::FT mu0 = 0; + + // getting barycenter of points + typename GT::Vector_3 xm = std::accumulate(x.begin(), x.end(), GT::Vector_3(0, 0, 0)); + xm /= n; + + // getting unit average normal of points + typename GT::Vector_3 um = std::accumulate(u.begin(), u.end(), GT::Vector_3(0, 0, 0)); + um /= sqrt(um * um); + + // summing each triangle's measure after triangulation by barycenter split. + for (std::size_t i = 0; i < n; i++) { - /// x0 _ x1 - /// x2 |_| x3 - - switch (mu_i) - { - case MU0_AREA_MEASURE: - - return (1 / 36.0) * ((4 * u0 + 2 * u1 + 2 * u2 + u3) * CGAL::cross_product(x1 - x0, x2 - x0) - + (2 * u0 + 4 * u1 + u2 + 2 * u3) * CGAL::cross_product(x1 - x0, x3 - x1) - + (2 * u0 + u1 + 4 * u2 + 2 * u3) * CGAL::cross_product(x3 - x2, x2 - x0) - + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * CGAL::cross_product(x3 - x2, x3 - x1)); - - case MU1_MEAN_CURVATURE_MEASURE: - { - const Epic::Vector_3 u03 = u3 - u0; - const Epic::Vector_3 u12 = u2 - u1; - const Epic::Vector_3 x0_cross = CGAL::cross_product(u12, x0); - const Epic::Vector_3 x1_cross = -CGAL::cross_product(u03, x1); - const Epic::Vector_3 x2_cross = CGAL::cross_product(u03, x2); - const Epic::Vector_3 x3_cross = -CGAL::cross_product(u12, x3); - - - return (1 / 12.0) * (u0 * (2 * x0_cross - CGAL::cross_product((u2 + u3), x1) + CGAL::cross_product((u1 + u3), x2) + x3_cross) - + u1 * (CGAL::cross_product((u2 + u3), x0) + 2 * x1_cross + x2_cross - CGAL::cross_product((u0 + u2), x3)) - + u2 * (CGAL::cross_product(-(u1 + u3), x0) + x1_cross + 2 * x2_cross + CGAL::cross_product((u0 + u1), x3)) - + u3 * (x0_cross + CGAL::cross_product((u0 + u2), x1) - CGAL::cross_product((u0 + u1), x2) + 2 * x3_cross)); - } - case MU2_GAUSSIAN_CURVATURE_MEASURE: - - return (1 / 36.0) * ((4 * u0 + 2 * u1 + 2 * u2 + u3) * CGAL::cross_product(u1 - u0, u2 - u0) - + (2 * u0 + 4 * u1 + u2 + 2 * u3) * CGAL::cross_product(u1 - u0, u3 - u1) - + (2 * u0 + u1 + 4 * u2 + 2 * u3) * CGAL::cross_product(u3 - u2, u2 - u0) - + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * CGAL::cross_product(u3 - u2, u3 - u1)); - - default: return 0; - } + mu0 += interpolated_corrected_measure_triangle(x[i], x[(i + 1) % n], xm, + u[i], u[(i + 1) % n], um, mu_i); } + return mu0; + } +} - Epic::FT interpolated_mu_i_face(const std::vector& x, const std::vector& u, const Measure_index mu_i) - { - std::size_t n = x.size(); - CGAL_precondition(u.size() == n); - CGAL_precondition(n >= 3); - - // Triangle: use triangle formulas - if (n == 3) - return interpolated_mu_i_triangle(x[0], x[1], x[2], - u[0], u[1], u[2], mu_i); - - // Quad: use bilinear interpolation formulas - else if (n == 4) - /// x[0] _ x[1] ---> x0 _ x1 (reason for changing order) - /// x[3] |_| x[2] ---> x2 |_| x3 - return interpolated_mu_i_quad(x[0], x[1], x[3], x[2], - u[0], u[1], u[3], u[2], mu_i); - - // N-gon: split into n triangles by barycenter and use triangle formulas for each - else { - Epic::FT mu0 = 0; - - // getting barycenter of points - Epic::Vector_3 xm = std::accumulate(x.begin(), x.end(), Epic::Vector_3(0, 0, 0)); - xm /= n; - - // getting unit average normal of points - Epic::Vector_3 um = std::accumulate(u.begin(), u.end(), Epic::Vector_3(0, 0, 0)); - um /= sqrt(um * um); - - // summing each triangle's measure after triangulation by barycenter split. - for (std::size_t i = 0; i < n; i++) - { - mu0 += interpolated_mu_i_triangle(x[i], x[(i + 1) % n], xm, - u[i], u[(i + 1) % n], um, mu_i); - } - return mu0; - } - } - - /// TODO: - /// 1- Handle if VNM is not given - /// 2- use GT instead of Epic - - template - std::vector - interpolated_corrected_measure_i( - const PolygonMesh& pmesh, - const Measure_index mu_i, - VertexNormalMap vnm, - const NamedParameters& np = parameters::default_values()) - { - using parameters::choose_parameter; - using parameters::get_parameter; +/** +* \ingroup PMP_corrected_curvatures_grp +* +* computes the interpolated corrected curvature measure on each face of the mesh +* +* @tparam PolygonMesh a model of `FaceGraph` +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* +* @param pmesh the polygon mesh +* @param mu_i an enum for choosing between computing the area measure, the mean curvature measure or the gaussian curvature measure +* @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 `ReadablePropertyMap` 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` +* must be available in `PolygonMesh`.} +* \cgalParamNEnd +* +* \cgalParamNBegin{vertex_normal_map} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%vertex_descriptor` +* as key type and `%Vector_3` as value type} +* \cgalParamDefault{`TODO`} +* \cgalParamExtra{If this parameter is omitted, vertex normals should be computed inside the function body.} +* \cgalParamNEnd +* +* \cgalNamedParamsEnd +* +* @return a vector of the computed measure on all faces. The return type is a std::vector. +* GT is the type of the Geometric Triats deduced from the PolygonMesh and the NamedParameters arguments +* This is to be changed later to a property_map. +* +* @see `interpolated_corrected_measure_face()` +*/ +template +#ifdef DOXYGEN_RUNNING + std::vector +#else + std::vector::type::FT> +#endif + interpolated_corrected_measure_mesh( + const PolygonMesh& pmesh, + const Measure_index mu_i, + NamedParameters& np = parameters::default_values()) +{ + typedef GetGeomTraits::type GT; + typedef dynamic_vertex_property_t Vector_map_tag; + typedef typename boost::property_map::type Default_vector_map; + typedef typename internal_np::Lookup_named_param_def::type VNM; - typedef boost::graph_traits::face_descriptor face_descriptor; - typedef boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef boost::graph_traits::halfedge_descriptor halfedge_descriptor; + using parameters::choose_parameter; + using parameters::get_parameter; + using parameters::is_default_parameter; - typename GetVertexPointMap::const_type - vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), - get_const_property_map(CGAL::vertex_point, pmesh)); - //std::unordered_map vnm_init; + typedef boost::graph_traits::face_descriptor face_descriptor; + typedef boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef boost::graph_traits::halfedge_descriptor halfedge_descriptor; - //boost::associative_property_map - // ::vertex_descriptor, Epic::Vector_3>> vnm(vnm_init); + typename GetVertexPointMap::const_type + vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), + get_const_property_map(CGAL::vertex_point, pmesh)); - ////if (!vnm) - // { - // compute_vertex_normals(pmesh, vnm, np); - //} + VNM vnm = choose_parameter( + get_parameter(np, internal_np::vertex_normal_map), Vector_map_tag(), pmesh); - std::vector mu_i_map; + if (is_default_parameter::value) + compute_vertex_normals(pmesh, vnm, np); + std::vector mu_i_map; - for (face_descriptor f : faces(pmesh)) - { - halfedge_descriptor h_start = pmesh.halfedge(f); - halfedge_descriptor h_iter = h_start; - std::vector x; - std::vector u; + for (face_descriptor f : faces(pmesh)) + { + halfedge_descriptor h_start = pmesh.halfedge(f); + halfedge_descriptor h_iter = h_start; - // looping over vertices in face - do { - vertex_descriptor v = source(h_iter, pmesh); - Epic::Point_3 p = get(vpm, v); - x.push_back(Epic::Vector_3(p.x(),p.y(),p.z())); - u.push_back(get(vnm, v)); - h_iter = next(h_iter, pmesh); - } while (h_iter != h_start); + std::vector x; + std::vector u; + // looping over vertices in face + do { + vertex_descriptor v = source(h_iter, pmesh); + GT::Point_3 p = get(vpm, v); + x.push_back(GT::Vector_3(p.x(),p.y(),p.z())); + u.push_back(get(vnm, v)); + h_iter = next(h_iter, pmesh); + } while (h_iter != h_start); - mu_i_map.push_back(interpolated_mu_i_face(x, u, mu_i)); - } - return mu_i_map; - } + mu_i_map.push_back(interpolated_corrected_measure_face(x, u, mu_i)); } + return mu_i_map; } - +} +} \ No newline at end of file From c3d654b2c372ae23c135d0641faab1edd099c772 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 18 Jul 2022 02:44:14 +0200 Subject: [PATCH 007/161] add documentation for enum --- ...nterpolated_corrected_curvature_measures.h | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index df5197b2f005..0b164f2d8509 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -15,11 +15,16 @@ namespace CGAL { namespace Polygon_mesh_processing { -// enum to specify which measure is computed +/*! + * \ingroup PMP_corrected_curvatures_grp + * Enumeration type used to specify which measure is computed for the + * interpolated corrected curvature functions + */ +// enum enum Measure_index { - MU0_AREA_MEASURE, - MU1_MEAN_CURVATURE_MEASURE, - MU2_GAUSSIAN_CURVATURE_MEASURE + MU0_AREA_MEASURE, ///< corrected area density of the given face + MU1_MEAN_CURVATURE_MEASURE, ///< corrected mean curvature density of the given face + MU2_GAUSSIAN_CURVATURE_MEASURE ///< corrected gaussian curvature density of the given face }; /** @@ -101,8 +106,8 @@ template typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 x0, const typename GT::Vector_3 x1, const typename GT::Vector_3 x2, const typename GT::Vector_3 x3, const typename GT::Vector_3 u0, const typename GT::Vector_3 u1, const typename GT::Vector_3 u2, const typename GT::Vector_3 u3, const Measure_index mu_i) { - /// x0 _ x1 - /// x2 |_| x3 + // x0 _ x1 + // x2 |_| x3 switch (mu_i) { @@ -172,8 +177,8 @@ typename GT::FT interpolated_corrected_measure_face(const std::vector x0 _ x1 (reason for changing order) - /// x[3] |_| x[2] ---> x2 |_| x3 + // x[0] _ x[1] ---> x0 _ x1 (reason for changing order) + // x[3] |_| x[2] ---> x2 |_| x3 return interpolated_corrected_measure_quad(x[0], x[1], x[3], x[2], u[0], u[1], u[3], u[2], mu_i); From e1961c4340bef283b3b4c27afaceecdd89d3c01a Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 18 Jul 2022 02:48:52 +0200 Subject: [PATCH 008/161] minor doc fix --- .../Curvatures/interpolated_corrected_curvature_measures.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 0b164f2d8509..cf6ca10fc8ec 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -100,7 +100,7 @@ typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vecto * @return a scalar of type GT::FT. This is the value of the interpolated corrected measure of the given triangle. * * @see `interpolated_corrected_measure_face()` -* @see `interpolated_corrected_measure_quad()` +* @see `interpolated_corrected_measure_triangle()` */ template typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 x0, const typename GT::Vector_3 x1, const typename GT::Vector_3 x2, const typename GT::Vector_3 x3, From 138f1ea831d9138f008bcb88e0d0db8f7dd26f5b Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 18 Jul 2022 03:31:49 +0200 Subject: [PATCH 009/161] added to reference manual page --- .../doc/Polygon_mesh_processing/PackageDescription.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 9f6d7ffd4736..c25cfb8490d3 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -200,6 +200,12 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage. - \link PMP_locate_grp Nearest Face Location Queries \endlink - \link PMP_locate_grp Random Location Generation \endlink +\cgalCRPSection{Corrected Curvature Functions} +- `CGAL::Polygon_mesh_processing::interpolated_corrected_measure_mesh()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_measure_face()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_measure_triangle()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_measure_quad()` + \cgalCRPSection{Normal Computation Functions} - `CGAL::Polygon_mesh_processing::compute_face_normal()` - `CGAL::Polygon_mesh_processing::compute_face_normals()` From b1e191212c8d727180e0614ceae545acc8527b9f Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 18 Jul 2022 04:35:21 +0200 Subject: [PATCH 010/161] doc typos --- .../interpolated_corrected_curvature_measures.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index cf6ca10fc8ec..38fa4bb05bd8 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -18,7 +18,7 @@ namespace Polygon_mesh_processing { /*! * \ingroup PMP_corrected_curvatures_grp * Enumeration type used to specify which measure is computed for the - * interpolated corrected curvature functions + * interpolated corrected curvature functions. */ // enum enum Measure_index { @@ -30,7 +30,7 @@ enum Measure_index { /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected measure of specific triangle. +* computes the interpolated corrected measure of a specific triangle. * * @tparam GT is the geometric traits class. * @@ -79,8 +79,8 @@ typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vecto /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected measure of specific quad -* Note that the vertices 0 to 3 are ordered like this \n +* computes the interpolated corrected measure of a specific quad. \n +* Note that the vertices 0 to 3 are ordered like this: \n * v0 _ v1 \n * v2 |_| v3 * @@ -148,7 +148,7 @@ typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected measure of specific face. +* computes the interpolated corrected measure of a specific face. * * @tparam GT is the geometric traits class. * @@ -208,7 +208,7 @@ typename GT::FT interpolated_corrected_measure_face(const std::vector Date: Mon, 18 Jul 2022 16:17:03 +0200 Subject: [PATCH 011/161] used a face property map for computing measures, added license will update docs to match void interpolated_corrected_measure_mesh( const PolygonMesh& pmesh, FaceMeasureMap fmm, const Measure_index mu_i, NamedParameters& np = parameters::default_values()) { later if it works --- ...nterpolated_corrected_curvature_measures.h | 54 ++++++++++++++++++ .../interpolated_corrected_curvatures.cpp | 40 ++++---------- ...nterpolated_corrected_curvature_measures.h | 55 ++++++++----------- 3 files changed, 88 insertions(+), 61 deletions(-) create mode 100644 Installation/include/CGAL/license/Polygon_mesh_processing/interpolated_corrected_curvature_measures.h diff --git a/Installation/include/CGAL/license/Polygon_mesh_processing/interpolated_corrected_curvature_measures.h b/Installation/include/CGAL/license/Polygon_mesh_processing/interpolated_corrected_curvature_measures.h new file mode 100644 index 000000000000..15e2a79af8a9 --- /dev/null +++ b/Installation/include/CGAL/license/Polygon_mesh_processing/interpolated_corrected_curvature_measures.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/licence/README.md + +#ifndef CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H +#define CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H + +#include +#include + +#ifdef CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_COMMERCIAL_LICENSE + +# if CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_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 - Interpolated Corrected Curvatures package.") +# endif + +# ifdef CGAL_LICENSE_ERROR +# error "Your commercial license for CGAL does not cover this release \ + of the Polygon Mesh Processing - Interpolated Corrected Curvatures package. \ + You get this error, as you defined CGAL_LICENSE_ERROR." +# endif // CGAL_LICENSE_ERROR + +# endif // CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE + +#else // no CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_COMMERCIAL_LICENSE + +# if defined(CGAL_LICENSE_WARNING) + CGAL_pragma_warning("\nThe macro CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_COMMERCIAL_LICENSE is not defined." + "\nYou use the CGAL Polygon Mesh Processing - Interpolated Corrected Curvatures package under " + "the terms of the GPLv3+.") +# endif // CGAL_LICENSE_WARNING + +# ifdef CGAL_LICENSE_ERROR +# error "The macro CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_COMMERCIAL_LICENSE is not defined.\ + You use the CGAL Polygon Mesh Processing - Interpolated Corrected Curvatures 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_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_COMMERCIAL_LICENSE + +#endif // CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp index c117e3f817fe..8dcf387d0ed5 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -23,6 +23,7 @@ typedef CGAL::Polyhedron_3 Polyhedron; typedef CGAL::Surface_mesh Mesh; typedef boost::graph_traits::face_descriptor face_descriptor; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; +typedef std::unordered_map FaceMeasureMap_tag; int main(int argc, char* argv[]) @@ -35,41 +36,20 @@ int main(int argc, char* argv[]) std::cerr << "Invalid input file." << std::endl; return EXIT_FAILURE; } - std::unordered_map vnm_vec; - boost::associative_property_map< std::unordered_map> vnm(vnm_vec); + std::unordered_map vnm_init; + boost::associative_property_map< std::unordered_map> vnm(vnm_init); PMP::compute_vertex_normals(g1, vnm); + FaceMeasureMap_tag mu0_init, mu1_init, mu2_init; + boost::associative_property_map mu0_map(mu0_init), mu1_map(mu1_init), mu2_map(mu2_init); - std::vector mu0_map, mu1_map, mu2_map; + PMP::interpolated_corrected_measure_mesh(g1, mu0_map, PMP::MU0_AREA_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); + PMP::interpolated_corrected_measure_mesh(g1, mu1_map, PMP::MU1_MEAN_CURVATURE_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); + PMP::interpolated_corrected_measure_mesh(g1, mu2_map, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); - mu0_map = PMP::interpolated_corrected_measure_mesh(g1, PMP::MU0_AREA_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); - mu1_map = PMP::interpolated_corrected_measure_mesh(g1, PMP::MU1_MEAN_CURVATURE_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); - mu2_map = PMP::interpolated_corrected_measure_mesh(g1, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); - - int n = g1.faces().size(); - - for (int i = 0; i < n; i++) + for (face_descriptor f: g1.faces()) { - std::cout << mu0_map[i] << "\n"; + std::cout << f.idx() << ": " << get(mu0_map, f) << ", " << get(mu1_map, f) << ", " << get(mu2_map, f) << ", " << "\n"; } - - std::cout << "\n"; - - for (int i = 0; i < n; i++) - { - std::cout << mu1_map[i] << "\n"; - } - - std::cout << "\n"; - - for (int i = 0; i < n; i++) - { - std::cout << mu2_map[i] << "\n"; - } - - - CGAL::draw(g1); - - return EXIT_SUCCESS; } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 38fa4bb05bd8..6a5c675d4e69 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -3,6 +3,7 @@ #endif #include +#include #include #include @@ -17,10 +18,10 @@ namespace Polygon_mesh_processing { /*! * \ingroup PMP_corrected_curvatures_grp - * Enumeration type used to specify which measure is computed for the - * interpolated corrected curvature functions. + * Enumeration type used to specify which measure is computed for the + * interpolated corrected curvature functions */ -// enum +// enum enum Measure_index { MU0_AREA_MEASURE, ///< corrected area density of the given face MU1_MEAN_CURVATURE_MEASURE, ///< corrected mean curvature density of the given face @@ -30,7 +31,7 @@ enum Measure_index { /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected measure of a specific triangle. +* computes the interpolated corrected measure of specific triangle. * * @tparam GT is the geometric traits class. * @@ -79,9 +80,9 @@ typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vecto /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected measure of a specific quad. \n -* Note that the vertices 0 to 3 are ordered like this: \n -* v0 _ v1 \n +* computes the interpolated corrected measure of specific quad +* Note that the vertices 0 to 3 are ordered like this \n +* v0 _ v1 \n * v2 |_| v3 * * @tparam GT is the geometric traits class. @@ -148,7 +149,7 @@ typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected measure of a specific face. +* computes the interpolated corrected measure of specific face. * * @tparam GT is the geometric traits class. * @@ -208,12 +209,12 @@ typename GT::FT interpolated_corrected_measure_face(const std::vector::%vertex_descriptor` @@ -234,29 +235,27 @@ typename GT::FT interpolated_corrected_measure_face(const std::vector. +* @return a vector of the computed measure on all faces. The return type is a std::vector. * GT is the type of the Geometric Triats deduced from the PolygonMesh and the NamedParameters arguments * This is to be changed later to a property_map. * * @see `interpolated_corrected_measure_face()` */ -template -#ifdef DOXYGEN_RUNNING - std::vector -#else - std::vector::type::FT> -#endif + void interpolated_corrected_measure_mesh( const PolygonMesh& pmesh, + FaceMeasureMap fmm, const Measure_index mu_i, NamedParameters& np = parameters::default_values()) { + typedef GetGeomTraits::type GT; - typedef dynamic_vertex_property_t Vector_map_tag; + typedef dynamic_vertex_property_t Vector_map_tag; typedef typename boost::property_map::type Default_vector_map; typedef typename internal_np::Lookup_named_param_def::face_descriptor face_descriptor; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; typedef boost::graph_traits::halfedge_descriptor halfedge_descriptor; @@ -274,16 +272,13 @@ template::const_type vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), get_const_property_map(CGAL::vertex_point, pmesh)); - - VNM vnm = choose_parameter( - get_parameter(np, internal_np::vertex_normal_map), Vector_map_tag(), pmesh); + + // TODO - handle if vnm is not provided + VNM vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), get(Vector_map_tag(), pmesh)); if (is_default_parameter::value) compute_vertex_normals(pmesh, vnm, np); - std::vector mu_i_map; - - for (face_descriptor f : faces(pmesh)) { halfedge_descriptor h_start = pmesh.halfedge(f); @@ -301,10 +296,8 @@ template(x, u, mu_i)); + put(fmm, f, interpolated_corrected_measure_face(x, u, mu_i)); } - return mu_i_map; } } -} \ No newline at end of file +} From a54033c0c9211b9c9c42b8eeb6ec6f3f7fa86dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 18 Jul 2022 16:32:33 +0200 Subject: [PATCH 012/161] add a couple of missing const and typename --- ...nterpolated_corrected_curvature_measures.h | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 6a5c675d4e69..46b2f5c01fcc 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -188,11 +188,11 @@ typename GT::FT interpolated_corrected_measure_face(const std::vector::type GT; + typedef typename GetGeomTraits::type GT; typedef dynamic_vertex_property_t Vector_map_tag; - typedef typename boost::property_map::type Default_vector_map; + typedef typename boost::property_map::const_type Default_vector_map; typedef typename internal_np::Lookup_named_param_def::type VNM; @@ -265,9 +265,9 @@ template::face_descriptor face_descriptor; - typedef boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; typename GetVertexPointMap::const_type vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), @@ -284,14 +284,14 @@ template x; - std::vector u; + std::vector x; + std::vector u; // looping over vertices in face do { vertex_descriptor v = source(h_iter, pmesh); - GT::Point_3 p = get(vpm, v); - x.push_back(GT::Vector_3(p.x(),p.y(),p.z())); + typename GT::Point_3 p = get(vpm, v); + x.push_back(typename GT::Vector_3(p.x(),p.y(),p.z())); u.push_back(get(vnm, v)); h_iter = next(h_iter, pmesh); } while (h_iter != h_start); From 5cc75c0bc4690df397c7dc14d03ca3049ad113f2 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 18 Jul 2022 21:57:42 +0200 Subject: [PATCH 013/161] Updated Demo Display property plugin, added to license list, --- .../include/CGAL/license/gpl_package_list.txt | 1 + .../interpolated_corrected_curvatures.cpp | 2 +- ...nterpolated_corrected_curvature_measures.h | 10 +- .../Display/Display_property_plugin.cpp | 191 +++++++++++++++++- 4 files changed, 188 insertions(+), 16 deletions(-) diff --git a/Installation/include/CGAL/license/gpl_package_list.txt b/Installation/include/CGAL/license/gpl_package_list.txt index f023d48f375f..485c4234b91d 100644 --- a/Installation/include/CGAL/license/gpl_package_list.txt +++ b/Installation/include/CGAL/license/gpl_package_list.txt @@ -51,6 +51,7 @@ Polygon_mesh_processing/connected_components Polygon Mesh Processing - Connected Polygon_mesh_processing/corefinement Polygon Mesh Processing - Corefinement Polygon_mesh_processing/core Polygon Mesh Processing - Core Polygon_mesh_processing/distance Polygon Mesh Processing - Distance +Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures Polygon Mesh Processing - Interpolated Corrected Curvatures Polygon_mesh_processing/measure Polygon Mesh Processing - Geometric Measure Polygon_mesh_processing/meshing_hole_filling Polygon Mesh Processing - Meshing and Hole Filling Polygon_mesh_processing/orientation Polygon Mesh Processing - Orientation diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp index 8dcf387d0ed5..d17e37726a8a 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -44,7 +44,7 @@ int main(int argc, char* argv[]) FaceMeasureMap_tag mu0_init, mu1_init, mu2_init; boost::associative_property_map mu0_map(mu0_init), mu1_map(mu1_init), mu2_map(mu2_init); - PMP::interpolated_corrected_measure_mesh(g1, mu0_map, PMP::MU0_AREA_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); + PMP::interpolated_corrected_measure_mesh(g1, mu0_map, PMP::MU0_AREA_MEASURE); PMP::interpolated_corrected_measure_mesh(g1, mu1_map, PMP::MU1_MEAN_CURVATURE_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); PMP::interpolated_corrected_measure_mesh(g1, mu2_map, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 46b2f5c01fcc..7f99fbd7cace 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -22,7 +22,7 @@ namespace Polygon_mesh_processing { * interpolated corrected curvature functions */ // enum -enum Measure_index { +enum Curvature_measure_index { MU0_AREA_MEASURE, ///< corrected area density of the given face MU1_MEAN_CURVATURE_MEASURE, ///< corrected mean curvature density of the given face MU2_GAUSSIAN_CURVATURE_MEASURE ///< corrected gaussian curvature density of the given face @@ -51,7 +51,7 @@ enum Measure_index { */ template typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vector_3 x0, const typename GT::Vector_3 x1, const typename GT::Vector_3 x2, - const typename GT::Vector_3 u0, const typename GT::Vector_3 u1, const typename GT::Vector_3 u2, const Measure_index mu_i) + const typename GT::Vector_3 u0, const typename GT::Vector_3 u1, const typename GT::Vector_3 u2, const Curvature_measure_index mu_i) { switch (mu_i) { @@ -105,7 +105,7 @@ typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vecto */ template typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 x0, const typename GT::Vector_3 x1, const typename GT::Vector_3 x2, const typename GT::Vector_3 x3, - const typename GT::Vector_3 u0, const typename GT::Vector_3 u1, const typename GT::Vector_3 u2, const typename GT::Vector_3 u3, const Measure_index mu_i) + const typename GT::Vector_3 u0, const typename GT::Vector_3 u1, const typename GT::Vector_3 u2, const typename GT::Vector_3 u3, const Curvature_measure_index mu_i) { // x0 _ x1 // x2 |_| x3 @@ -165,7 +165,7 @@ typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 * @see `interpolated_corrected_measure_mesh()` */ template -typename GT::FT interpolated_corrected_measure_face(const std::vector& x, const std::vector& u, const Measure_index mu_i) +typename GT::FT interpolated_corrected_measure_face(const std::vector& x, const std::vector& u, const Curvature_measure_index mu_i) { std::size_t n = x.size(); CGAL_precondition(u.size() == n); @@ -250,7 +250,7 @@ template #include #include +#include #include "Scene_points_with_normal_item.h" @@ -31,6 +32,7 @@ #include #include #include +#include #define ARBITRARY_DBL_MIN 1.0E-30 #define ARBITRARY_DBL_MAX 1.0E+30 @@ -41,6 +43,8 @@ typedef CGAL::Three::Triangle_container Tri; typedef CGAL::Three::Viewer_interface VI; +namespace PMP = CGAL::Polygon_mesh_processing; + class Scene_heat_item : public CGAL::Three::Scene_item_rendering_helper { @@ -529,6 +533,9 @@ private Q_SLOTS: dock_widget->propertyBox->addItem("Scaled Jacobian"); dock_widget->propertyBox->addItem("Heat Intensity"); dock_widget->propertyBox->addItem("Heat Intensity (Intrinsic Delaunay)"); + dock_widget->propertyBox->addItem("corrected area density"); + dock_widget->propertyBox->addItem("corrected mean curvature density"); + dock_widget->propertyBox->addItem("corrected gaussian curvature density"); detectSMScalarProperties(sm_item->face_graph()); } @@ -603,6 +610,18 @@ private Q_SLOTS: return; sm_item->setRenderingMode(Gouraud); break; + case 4: // corrected area density + displayInterpolatedCurvatureMeasure(sm_item, PMP::MU0_AREA_MEASURE); + sm_item->setRenderingMode(Flat); + break; + case 5: // corrected mean curvature density + displayInterpolatedCurvatureMeasure(sm_item, PMP::MU1_MEAN_CURVATURE_MEASURE); + sm_item->setRenderingMode(Flat); + break; + case 6: // corrected gaussian curvature density + displayInterpolatedCurvatureMeasure(sm_item, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE); + sm_item->setRenderingMode(Flat); + break; default: if(dock_widget->propertyBox->currentText().contains("v:")) { @@ -637,6 +656,18 @@ private Q_SLOTS: sm_item->face_graph()->property_map("f:angle"); if(does_exist) sm_item->face_graph()->remove_property_map(pmap); + std::tie(pmap, does_exist) = + sm_item->face_graph()->property_map("f:corrected_area_density"); + if (does_exist) + sm_item->face_graph()->remove_property_map(pmap); + std::tie(pmap, does_exist) = + sm_item->face_graph()->property_map("f:corrected_mean_curvature_density"); + if (does_exist) + sm_item->face_graph()->remove_property_map(pmap); + std::tie(pmap, does_exist) = + sm_item->face_graph()->property_map("f:corrected_gaussian_curvature_density"); + if (does_exist) + sm_item->face_graph()->remove_property_map(pmap); }); QApplication::restoreOverrideCursor(); sm_item->invalidateOpenGLBuffers(); @@ -668,13 +699,25 @@ private Q_SLOTS: switch(dock_widget->propertyBox->currentIndex()) { case 0: - dock_widget->zoomToMinButton->setEnabled(angles_max.count(sm_item)>0 ); + dock_widget->zoomToMinButton->setEnabled(angles_min.count(sm_item)>0 ); dock_widget->zoomToMaxButton->setEnabled(angles_max.count(sm_item)>0 ); break; case 1: - dock_widget->zoomToMinButton->setEnabled(jacobian_max.count(sm_item)>0); + dock_widget->zoomToMinButton->setEnabled(jacobian_min.count(sm_item)>0); dock_widget->zoomToMaxButton->setEnabled(jacobian_max.count(sm_item)>0); break; + case 4: + dock_widget->zoomToMinButton->setEnabled(mu0_min.count(sm_item) > 0); + dock_widget->zoomToMaxButton->setEnabled(mu0_max.count(sm_item) > 0); + break; + case 5: + dock_widget->zoomToMinButton->setEnabled(mu1_min.count(sm_item) > 0); + dock_widget->zoomToMaxButton->setEnabled(mu1_max.count(sm_item) > 0); + break; + case 6: + dock_widget->zoomToMinButton->setEnabled(mu2_min.count(sm_item) > 0); + dock_widget->zoomToMaxButton->setEnabled(mu2_max.count(sm_item) > 0); + break; default: break; } @@ -701,11 +744,28 @@ private Q_SLOTS: { smesh.remove_property_map(angles); } + SMesh::Property_map mu0; + std::tie(mu0, found) = smesh.property_map("f:corrected_area_density"); + if (found) + { + smesh.remove_property_map(mu0); + } + SMesh::Property_map mu1; + std::tie(mu1, found) = smesh.property_map("f:corrected_mean_curvature_density"); + if (found) + { + smesh.remove_property_map(mu0); + } + SMesh::Property_map mu2; + std::tie(mu2, found) = smesh.property_map("f:corrected_gaussian_curvature_density"); + if (found) + { + smesh.remove_property_map(mu0); + } } void displayScaledJacobian(Scene_surface_mesh_item* item) { - SMesh& smesh = *item->face_graph(); //compute and store the jacobian per face bool non_init; @@ -742,16 +802,59 @@ private Q_SLOTS: treat_sm_property("f:jacobian", item->face_graph()); } - bool resetScaledJacobian(Scene_surface_mesh_item* item) + void displayInterpolatedCurvatureMeasure(Scene_surface_mesh_item* item, PMP::Curvature_measure_index mu_index) { + std::vector tied_map = { + "f:corrected_area_density", + "f:corrected_mean_curvature_density", + "f:corrected_gaussian_curvature_density" + }; SMesh& smesh = *item->face_graph(); - if(!smesh.property_map("f:jacobian").second) + //compute once and store the value per face + bool non_init; + SMesh::Property_map mu_i_map; + std::tie(mu_i_map, non_init) = + smesh.add_property_map(tied_map[mu_index], 0); + if (non_init) { - return false; + PMP::interpolated_corrected_measure_mesh(smesh, mu_i_map, mu_index); + + double res_min = ARBITRARY_DBL_MAX, + res_max = -ARBITRARY_DBL_MAX; + SMesh::Face_index min_index, max_index; + for (SMesh::Face_index f : faces(smesh)) + { + if (mu_i_map[f] > res_max) + { + res_max = mu_i_map[f]; + max_index = f; + } + if (mu_i_map[f] < res_min) + { + res_min = mu_i_map[f]; + min_index = f; + } + } + switch (mu_index) + { + case PMP::MU0_AREA_MEASURE: + mu0_max[item] = std::make_pair(res_max, max_index); + mu0_min[item] = std::make_pair(res_min, min_index); + break; + case PMP::MU1_MEAN_CURVATURE_MEASURE: + mu1_max[item] = std::make_pair(res_max, max_index); + mu1_min[item] = std::make_pair(res_min, min_index); + break; + case PMP::MU2_GAUSSIAN_CURVATURE_MEASURE: + mu2_max[item] = std::make_pair(res_max, max_index); + mu2_min[item] = std::make_pair(res_min, min_index); + break; + } + + connect(item, &Scene_surface_mesh_item::itemChanged, + this, &DisplayPropertyPlugin::resetProperty); } - dock_widget->minBox->setValue(jacobian_min[item].first-0.01); - dock_widget->maxBox->setValue(jacobian_max[item].first); - return true; + treat_sm_property(tied_map[mu_index], item->face_graph()); } @@ -1054,6 +1157,9 @@ private Q_SLOTS: break; } case 1: + case 4: + case 5: + case 6: dock_widget->groupBox-> setEnabled(true); dock_widget->groupBox_3->setEnabled(true); @@ -1113,6 +1219,34 @@ private Q_SLOTS: dummy_fd, dummy_p); } + break; + case 4: + { + ::zoomToId(*item->face_graph(), + QString("f%1").arg(mu0_min[item].second), + getActiveViewer(), + dummy_fd, + dummy_p); + } + break; + case 5: + { + ::zoomToId(*item->face_graph(), + QString("f%1").arg(mu1_min[item].second), + getActiveViewer(), + dummy_fd, + dummy_p); + } + break; + case 6: + { + ::zoomToId(*item->face_graph(), + QString("f%1").arg(mu2_min[item].second), + getActiveViewer(), + dummy_fd, + dummy_p); + } + break; break; default: break; @@ -1145,6 +1279,33 @@ private Q_SLOTS: getActiveViewer(), dummy_fd, dummy_p); + } + break; + case 4: + { + ::zoomToId(*item->face_graph(), + QString("f%1").arg(mu0_max[item].second), + getActiveViewer(), + dummy_fd, + dummy_p); + } + break; + case 5: + { + ::zoomToId(*item->face_graph(), + QString("f%1").arg(mu1_max[item].second), + getActiveViewer(), + dummy_fd, + dummy_p); + } + break; + case 6: + { + ::zoomToId(*item->face_graph(), + QString("f%1").arg(mu2_max[item].second), + getActiveViewer(), + dummy_fd, + dummy_p); } break; default: @@ -1436,6 +1597,16 @@ private Q_SLOTS: std::unordered_map > angles_min; std::unordered_map > angles_max; + + std::unordered_map > mu0_min; + std::unordered_map > mu0_max; + + std::unordered_map > mu1_min; + std::unordered_map > mu1_max; + + std::unordered_map > mu2_min; + std::unordered_map > mu2_max; + std::unordered_map is_source; @@ -1718,7 +1889,7 @@ private Q_SLOTS: } - EPICK::Vector_3 unit_center_normal = CGAL::Polygon_mesh_processing::compute_face_normal(f, mesh); + EPICK::Vector_3 unit_center_normal = PMP::compute_face_normal(f, mesh); unit_center_normal *= 1.0/CGAL::approximate_sqrt(unit_center_normal.squared_length()); for(std::size_t i = 0; i < corner_areas.size(); ++i) From 5af4a28b1674cb660d1c4575ccf69a5d4925ace5 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 18 Jul 2022 22:23:46 +0200 Subject: [PATCH 014/161] updated doc for interpolated_corrected_measure_mesh() --- ...nterpolated_corrected_curvature_measures.h | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 7f99fbd7cace..93d41ac77db7 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -18,10 +18,10 @@ namespace Polygon_mesh_processing { /*! * \ingroup PMP_corrected_curvatures_grp - * Enumeration type used to specify which measure is computed for the + * Enumeration type used to specify which measure is computed for the * interpolated corrected curvature functions */ -// enum +// enum enum Curvature_measure_index { MU0_AREA_MEASURE, ///< corrected area density of the given face MU1_MEAN_CURVATURE_MEASURE, ///< corrected mean curvature density of the given face @@ -212,9 +212,12 @@ typename GT::FT interpolated_corrected_measure_face(const std::vector::%face_descriptor` as key type and GT::FT as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * -* @param pmesh the polygon mesh +* @param pmesh the polygon mesh +* @param fmm (face measure map) the property map used for storing the computed interpolated corrected measure * @param mu_i an enum for choosing between computing the area measure, the mean curvature measure or the gaussian curvature measure * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * @@ -227,7 +230,7 @@ typename GT::FT interpolated_corrected_measure_face(const std::vector::%vertex_descriptor` @@ -235,12 +238,8 @@ typename GT::FT interpolated_corrected_measure_face(const std::vector. -* GT is the type of the Geometric Triats deduced from the PolygonMesh and the NamedParameters arguments -* This is to be changed later to a property_map. +* \cgalNamedParamsEnd * * @see `interpolated_corrected_measure_face()` */ @@ -272,7 +271,7 @@ template::const_type vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), get_const_property_map(CGAL::vertex_point, pmesh)); - + // TODO - handle if vnm is not provided VNM vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), get(Vector_map_tag(), pmesh)); @@ -291,7 +290,7 @@ template Date: Mon, 18 Jul 2022 22:40:38 +0200 Subject: [PATCH 015/161] minor fix --- .../interpolated_corrected_curvatures.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp index d17e37726a8a..05c74f68cfc7 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -28,7 +28,7 @@ typedef std::unordered_map FaceMeasureMap_tag; int main(int argc, char* argv[]) { - const std::string filename = (argc>1) ? argv[1] : CGAL::data_file_path("small_bunny.obj"); + const std::string filename = (argc>1) ? argv[1] : CGAL::data_file_path("meshes/small_bunny.obj"); Mesh g1; if(!CGAL::IO::read_polygon_mesh(filename, g1)) From 063e058988567727b9ae5421a98212e18958841e Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 22 Jul 2022 15:29:19 +0200 Subject: [PATCH 016/161] minor changes regarding the pull review reordering includes, splitting too long lines, minor addition to doc regarding review on the pull req --- .../interpolated_corrected_curvatures.cpp | 66 +++++++----- ...nterpolated_corrected_curvature_measures.h | 102 +++++++++++------- 2 files changed, 104 insertions(+), 64 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp index 05c74f68cfc7..505d43286451 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -1,55 +1,67 @@ -#include #include -#include -#include -#include +#include #include -#include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include +#include +#include + namespace PMP = CGAL::Polygon_mesh_processing; -typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic; -typedef CGAL::Polyhedron_3 Polyhedron; -typedef CGAL::Surface_mesh Mesh; +typedef CGAL::Exact_predicates_inexact_constructions_kernel EpicKernel; +typedef CGAL::Surface_mesh Mesh; typedef boost::graph_traits::face_descriptor face_descriptor; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; -typedef std::unordered_map FaceMeasureMap_tag; +typedef std::unordered_map FaceMeasureMap_tag; +typedef std::unordered_map vertexVectorMap_tag; int main(int argc, char* argv[]) { - const std::string filename = (argc>1) ? argv[1] : CGAL::data_file_path("meshes/small_bunny.obj"); - Mesh g1; + const std::string filename = (argc>1) ? + argv[1] : + CGAL::data_file_path("meshes/small_bunny.obj"); + if(!CGAL::IO::read_polygon_mesh(filename, g1)) { std::cerr << "Invalid input file." << std::endl; return EXIT_FAILURE; } - std::unordered_map vnm_init; - boost::associative_property_map< std::unordered_map> vnm(vnm_init); + + vertexVectorMap_tag vnm_init; + boost::associative_property_map vnm(vnm_init); PMP::compute_vertex_normals(g1, vnm); FaceMeasureMap_tag mu0_init, mu1_init, mu2_init; - boost::associative_property_map mu0_map(mu0_init), mu1_map(mu1_init), mu2_map(mu2_init); + boost::associative_property_map + mu0_map(mu0_init), mu1_map(mu1_init), mu2_map(mu2_init); - PMP::interpolated_corrected_measure_mesh(g1, mu0_map, PMP::MU0_AREA_MEASURE); - PMP::interpolated_corrected_measure_mesh(g1, mu1_map, PMP::MU1_MEAN_CURVATURE_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); - PMP::interpolated_corrected_measure_mesh(g1, mu2_map, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE, CGAL::parameters::vertex_normal_map(vnm)); + PMP::interpolated_corrected_measure_mesh( + g1, + mu0_map, + PMP::MU0_AREA_MEASURE); + + PMP::interpolated_corrected_measure_mesh( + g1, + mu1_map, + PMP::MU1_MEAN_CURVATURE_MEASURE, + CGAL::parameters::vertex_normal_map(vnm)); + + PMP::interpolated_corrected_measure_mesh( + g1, + mu2_map, + PMP::MU2_GAUSSIAN_CURVATURE_MEASURE, + CGAL::parameters::vertex_normal_map(vnm)); for (face_descriptor f: g1.faces()) - { - std::cout << f.idx() << ": " << get(mu0_map, f) << ", " << get(mu1_map, f) << ", " << get(mu2_map, f) << ", " << "\n"; - } + std::cout << f.idx() << ": " + << get(mu0_map, f) << ", " + << get(mu1_map, f) << ", " + << get(mu2_map, f) << ", " << "\n"; } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 93d41ac77db7..aae957dc0159 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -2,15 +2,15 @@ #define CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H #endif -#include #include #include -#include #include #include +#include #include +#include namespace CGAL { @@ -18,14 +18,14 @@ namespace Polygon_mesh_processing { /*! * \ingroup PMP_corrected_curvatures_grp - * Enumeration type used to specify which measure is computed for the - * interpolated corrected curvature functions + * Enumeration type used to specify which measure of a given face + * is computed for the interpolated corrected curvature functions */ // enum enum Curvature_measure_index { - MU0_AREA_MEASURE, ///< corrected area density of the given face - MU1_MEAN_CURVATURE_MEASURE, ///< corrected mean curvature density of the given face - MU2_GAUSSIAN_CURVATURE_MEASURE ///< corrected gaussian curvature density of the given face + MU0_AREA_MEASURE, ///< corrected area density + MU1_MEAN_CURVATURE_MEASURE, ///< corrected mean curvature density + MU2_GAUSSIAN_CURVATURE_MEASURE ///< corrected gaussian curvature density }; /** @@ -44,14 +44,20 @@ enum Curvature_measure_index { * @param mu_i an enum for choosing between computing the area measure, * the mean curvature measure, or the gaussian curvature measure. * -* @return a scalar of type GT::FT. This is the value of the interpolated corrected measure of the given triangle. +* @return a scalar of type GT::FT. +* This is the value of the interpolated corrected measure of the given triangle. * * @see `interpolated_corrected_measure_face()` * @see `interpolated_corrected_measure_quad()` */ template -typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vector_3 x0, const typename GT::Vector_3 x1, const typename GT::Vector_3 x2, - const typename GT::Vector_3 u0, const typename GT::Vector_3 u1, const typename GT::Vector_3 u2, const Curvature_measure_index mu_i) +typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vector_3 x0, + const typename GT::Vector_3 x1, + const typename GT::Vector_3 x2, + const typename GT::Vector_3 u0, + const typename GT::Vector_3 u1, + const typename GT::Vector_3 u2, + const Curvature_measure_index mu_i) { switch (mu_i) { @@ -98,26 +104,36 @@ typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vecto * @param mu_i an enum for choosing between computing the area measure, * the mean curvature measure, or the gaussian curvature measure. * -* @return a scalar of type GT::FT. This is the value of the interpolated corrected measure of the given triangle. +* @return a scalar of type GT::FT. +* This is the value of the interpolated corrected measure of the given triangle. * * @see `interpolated_corrected_measure_face()` * @see `interpolated_corrected_measure_triangle()` */ template -typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 x0, const typename GT::Vector_3 x1, const typename GT::Vector_3 x2, const typename GT::Vector_3 x3, - const typename GT::Vector_3 u0, const typename GT::Vector_3 u1, const typename GT::Vector_3 u2, const typename GT::Vector_3 u3, const Curvature_measure_index mu_i) +typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 x0, + const typename GT::Vector_3 x1, + const typename GT::Vector_3 x2, + const typename GT::Vector_3 x3, + const typename GT::Vector_3 u0, + const typename GT::Vector_3 u1, + const typename GT::Vector_3 u2, + const typename GT::Vector_3 u3, + const Curvature_measure_index mu_i) { // x0 _ x1 // x2 |_| x3 - + switch (mu_i) { case MU0_AREA_MEASURE: - return (1 / 36.0) * ((4 * u0 + 2 * u1 + 2 * u2 + u3) * CGAL::cross_product(x1 - x0, x2 - x0) + return (1 / 36.0) * ( + (4 * u0 + 2 * u1 + 2 * u2 + u3) * CGAL::cross_product(x1 - x0, x2 - x0) + (2 * u0 + 4 * u1 + u2 + 2 * u3) * CGAL::cross_product(x1 - x0, x3 - x1) + (2 * u0 + u1 + 4 * u2 + 2 * u3) * CGAL::cross_product(x3 - x2, x2 - x0) - + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * CGAL::cross_product(x3 - x2, x3 - x1)); + + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * CGAL::cross_product(x3 - x2, x3 - x1) + ); case MU1_MEAN_CURVATURE_MEASURE: { @@ -129,17 +145,21 @@ typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 const typename GT::Vector_3 x3_cross = -CGAL::cross_product(u12, x3); - return (1 / 12.0) * (u0 * (2 * x0_cross - CGAL::cross_product((u2 + u3), x1) + CGAL::cross_product((u1 + u3), x2) + x3_cross) + return (1 / 12.0) * ( + u0 * (2 * x0_cross - CGAL::cross_product((u2 + u3), x1) + CGAL::cross_product((u1 + u3), x2) + x3_cross) + u1 * (CGAL::cross_product((u2 + u3), x0) + 2 * x1_cross + x2_cross - CGAL::cross_product((u0 + u2), x3)) - + u2 * (CGAL::cross_product(-(u1 + u3), x0) + x1_cross + 2 * x2_cross + CGAL::cross_product((u0 + u1), x3)) - + u3 * (x0_cross + CGAL::cross_product((u0 + u2), x1) - CGAL::cross_product((u0 + u1), x2) + 2 * x3_cross)); + + u2 * (-CGAL::cross_product((u1 + u3), x0) + x1_cross + 2 * x2_cross + CGAL::cross_product((u0 + u1), x3)) + + u3 * (x0_cross + CGAL::cross_product((u0 + u2), x1) - CGAL::cross_product((u0 + u1), x2) + 2 * x3_cross) + ); } case MU2_GAUSSIAN_CURVATURE_MEASURE: - return (1 / 36.0) * ((4 * u0 + 2 * u1 + 2 * u2 + u3) * CGAL::cross_product(u1 - u0, u2 - u0) + return (1 / 36.0) * ( + (4 * u0 + 2 * u1 + 2 * u2 + u3) * CGAL::cross_product(u1 - u0, u2 - u0) + (2 * u0 + 4 * u1 + u2 + 2 * u3) * CGAL::cross_product(u1 - u0, u3 - u1) + (2 * u0 + u1 + 4 * u2 + 2 * u3) * CGAL::cross_product(u3 - u2, u2 - u0) - + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * CGAL::cross_product(u3 - u2, u3 - u1)); + + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * CGAL::cross_product(u3 - u2, u3 - u1) + ); default: return 0; } @@ -158,14 +178,17 @@ typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 * @param mu_i an enum for choosing between computing the area measure, * the mean curvature measure, or the gaussian curvature measure. * -* @return a scalar of type GT::FT. This is the value of the interpolated corrected measure of the given face. +* @return a scalar of type GT::FT. +* This is the value of the interpolated corrected measure of the given face. * * @see `interpolated_corrected_measure_triangle()` * @see `interpolated_corrected_measure_quad()` * @see `interpolated_corrected_measure_mesh()` */ template -typename GT::FT interpolated_corrected_measure_face(const std::vector& x, const std::vector& u, const Curvature_measure_index mu_i) +typename GT::FT interpolated_corrected_measure_face(const std::vector& x, + const std::vector& u, + const Curvature_measure_index mu_i) { std::size_t n = x.size(); CGAL_precondition(u.size() == n); @@ -188,11 +211,13 @@ typename GT::FT interpolated_corrected_measure_face(const std::vector::%vertex_descriptor` +* \cgalParamType{a class model of `ReadablePropertyMap` 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` -* must be available in `PolygonMesh`.} +* \cgalParamExtra{If this parameter is omitted, an internal property map for +* `CGAL::vertex_point_t` must be available in `PolygonMesh`.} * \cgalParamNEnd * * \cgalParamNBegin{vertex_normal_map} * \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} -* \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%vertex_descriptor` +* \cgalParamType{a class model of `ReadablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` * as key type and `%Vector_3` as value type} -* \cgalParamDefault{`TODO`} -* \cgalParamExtra{If this parameter is omitted, vertex normals should be computed inside the function body.} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, vertex normals should be +* computed inside the function body.} * \cgalParamNEnd * * \cgalNamedParamsEnd @@ -246,11 +275,10 @@ typename GT::FT interpolated_corrected_measure_face(const std::vector void - interpolated_corrected_measure_mesh( - const PolygonMesh& pmesh, - FaceMeasureMap fmm, - const Curvature_measure_index mu_i, - const NamedParameters& np = parameters::default_values()) + interpolated_corrected_measure_mesh(const PolygonMesh& pmesh, + FaceMeasureMap fmm, + const Curvature_measure_index mu_i, + const NamedParameters& np = parameters::default_values()) { typedef typename GetGeomTraits::type GT; From 66a26246412d9922610d380c37da5623c432dc69 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 25 Jul 2022 13:31:07 +0200 Subject: [PATCH 017/161] minor doc fix making GT::FT back ticked --- .../interpolated_corrected_curvature_measures.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index aae957dc0159..2409eab42b74 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -44,7 +44,7 @@ enum Curvature_measure_index { * @param mu_i an enum for choosing between computing the area measure, * the mean curvature measure, or the gaussian curvature measure. * -* @return a scalar of type GT::FT. +* @return a scalar of type `GT::FT`. * This is the value of the interpolated corrected measure of the given triangle. * * @see `interpolated_corrected_measure_face()` @@ -104,7 +104,7 @@ typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vecto * @param mu_i an enum for choosing between computing the area measure, * the mean curvature measure, or the gaussian curvature measure. * -* @return a scalar of type GT::FT. +* @return a scalar of type `GT::FT`. * This is the value of the interpolated corrected measure of the given triangle. * * @see `interpolated_corrected_measure_face()` @@ -178,7 +178,7 @@ typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 * @param mu_i an enum for choosing between computing the area measure, * the mean curvature measure, or the gaussian curvature measure. * -* @return a scalar of type GT::FT. +* @return a scalar of type `GT::FT`. * This is the value of the interpolated corrected measure of the given face. * * @see `interpolated_corrected_measure_triangle()` @@ -238,7 +238,7 @@ typename GT::FT interpolated_corrected_measure_face(const std::vector::%face_descriptor` as key type and GT::FT as value type. +* `boost::graph_traits::%face_descriptor` as key type and `GT::FT` as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * * @param pmesh the polygon mesh From 41be3688ae6a0682ad03db7f22a2bb5a215b4a7b Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 26 Jul 2022 19:32:17 +0200 Subject: [PATCH 018/161] Mean and Gaussian Curvatures + Visualizer (Still wip) --- .../interpolated_corrected_curvatures.cpp | 35 +-- ...nterpolated_corrected_curvature_measures.h | 224 +++++++++++++++++- .../Display/Display_property_plugin.cpp | 116 +++------ .../internal/parameters_interface.h | 1 + 4 files changed, 259 insertions(+), 117 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp index 505d43286451..4480366bc2b0 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -33,35 +33,20 @@ int main(int argc, char* argv[]) return EXIT_FAILURE; } - vertexVectorMap_tag vnm_init; - boost::associative_property_map vnm(vnm_init); - - PMP::compute_vertex_normals(g1, vnm); - - FaceMeasureMap_tag mu0_init, mu1_init, mu2_init; + FaceMeasureMap_tag mean_curvature_init, gaussian_curvature_init; boost::associative_property_map - mu0_map(mu0_init), mu1_map(mu1_init), mu2_map(mu2_init); + mean_curvature_map(mean_curvature_init), gaussian_curvature_map(gaussian_curvature_init); - PMP::interpolated_corrected_measure_mesh( + PMP::interpolated_corrected_mean_curvature( g1, - mu0_map, - PMP::MU0_AREA_MEASURE); - - PMP::interpolated_corrected_measure_mesh( - g1, - mu1_map, - PMP::MU1_MEAN_CURVATURE_MEASURE, - CGAL::parameters::vertex_normal_map(vnm)); - - PMP::interpolated_corrected_measure_mesh( + mean_curvature_map + ); + PMP::interpolated_corrected_gaussian_curvature( g1, - mu2_map, - PMP::MU2_GAUSSIAN_CURVATURE_MEASURE, - CGAL::parameters::vertex_normal_map(vnm)); + gaussian_curvature_map + ); for (face_descriptor f: g1.faces()) - std::cout << f.idx() << ": " - << get(mu0_map, f) << ", " - << get(mu1_map, f) << ", " - << get(mu2_map, f) << ", " << "\n"; + std::cout << f.idx() << ": HC = " << get(mean_curvature_map, f) + << ", GC = " << get(gaussian_curvature_map, f) << "\n"; } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 2409eab42b74..737a2525ebd7 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -11,6 +11,8 @@ #include #include +#include +#include namespace CGAL { @@ -210,8 +212,8 @@ typename GT::FT interpolated_corrected_measure_face(const std::vector::value) compute_vertex_normals(pmesh, vnm, np); for (face_descriptor f : faces(pmesh)) { - halfedge_descriptor h_start = pmesh.halfedge(f); - halfedge_descriptor h_iter = h_start; - std::vector x; std::vector u; - // looping over vertices in face - do { - vertex_descriptor v = source(h_iter, pmesh); + for (vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) + { typename GT::Point_3 p = get(vpm, v); x.push_back(typename GT::Vector_3(p.x(), p.y(), p.z())); u.push_back(get(vnm, v)); - h_iter = next(h_iter, pmesh); - } while (h_iter != h_start); + } put(fmm, f, interpolated_corrected_measure_face(x, u, mu_i)); } } + +// +// +//template +//typename GT::FT triangle_in_ball_ratio_1(const typename GT::Vector_3 x1, +// const typename GT::Vector_3 x2, +// const typename GT::Vector_3 x3, +// const typename GT::FT r, +// const typename GT::Vector_3 c, +// const std::size_t res = 3) +//{ +// const typename GT::FT R = r * r; +// const typename GT::FT acc = 1.0 / res; +// std::size_t samples_in = 0; +// for (GT::FT alpha = acc / 3; alpha < 1; alpha += acc) +// for (GT::FT beta = acc / 3; beta < 1 - alpha; beta += acc) +// { +// if ((alpha * x1 + beta * x2 + (1 - alpha - beta) * x3 - c).squared_length() < R) +// samples_in++; +// } +// return samples_in / (typename GT::FT)(res * (res + 1) / 2); +//} + + +template +typename GT::FT face_in_ball_ratio_2(const std::vector& x, + const typename GT::FT r, + const typename GT::Vector_3 c) +{ + std::size_t n = x.size(); + + // getting center of points + typename GT::Vector_3 xm = + std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); + xm /= n; + + typename GT::FT d_min = (xm - c).squared_length(); + typename GT::FT d_max = d_min; + + for (const typename GT::Vector_3 xi : x) + { + const typename GT::FT d_sq = (xi - c).squared_length(); + d_max = std::max(d_sq, d_max); + d_min = std::min(d_sq, d_min); + } + + if (d_max <= r * r) return 1.0; + else if (r * r <= d_min) return 0.0; + + d_max = sqrt(d_max); + d_min = sqrt(d_min); + + return (r - d_min) / (d_max - d_min); +} + +template + void expand_interpolated_corrected_measure_face(const PolygonMesh& pmesh, + FaceMeasureMap fmm, + const typename boost::graph_traits::face_descriptor f, + const NamedParameters& np = parameters::default_values()) +{ + using parameters::choose_parameter; + using parameters::get_parameter; + + typedef typename GetGeomTraits::type GT; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + const typename GetGeomTraits::type::FT + r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0.01); + + if (r < 0.000001) + return; + + typename GetVertexPointMap::const_type + vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), + get_const_property_map(CGAL::vertex_point, pmesh)); + + + std::queue bfs_q; + std::unordered_set bfs_v; + + //get face center c + typename GT::Vector_3 c; + for (vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) + { + typename GT::Point_3 p = get(vpm, v); + c += typename GT::Vector_3(p.x(), p.y(), p.z()); + } + c /= degree(f, pmesh); + + GT::FT corrected_mui = 0; + + bfs_q.push(f); + bfs_v.insert(f); + + while (!bfs_q.empty()) { + face_descriptor fi = bfs_q.front(); + bfs_q.pop(); + + // looping over vertices in face to get point coordinates + std::vector x; + for (vertex_descriptor v : vertices_around_face(halfedge(fi, pmesh), pmesh)) + { + typename GT::Point_3 p = get(vpm, v); + x.push_back(typename GT::Vector_3(p.x(), p.y(), p.z())); + } + + const typename GT::FT f_ratio = face_in_ball_ratio_2(x, r, c); + + if (f_ratio > 0.000001) + { + corrected_mui += f_ratio * get(fmm, fi); + for (face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) + { + if (bfs_v.find(fj) == bfs_v.end()) + { + bfs_q.push(fj); + bfs_v.insert(fj); + } + } + } + } + + put(fmm, f, corrected_mui); +} + +//template +// void expand_interpolated_corrected_measure_mesh(const PolygonMesh& pmesh, +// FaceMeasureMap fmm, +// const NamedParameters& np = parameters::default_values()) +//{ +// typedef typename boost::graph_traits::face_descriptor face_descriptor; +// for (face_descriptor f : faces(pmesh)) +// expand_interpolated_corrected_measure_face(pmesh, fmm, f, np); +//} + +template + void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, + FaceCurvatureMap fcm, + const NamedParameters& np = parameters::default_values()) +{ + typedef typename GetGeomTraits::type GT; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef std::unordered_map FaceMeasureMap_tag; + + FaceMeasureMap_tag mu0_init, mu1_init; + boost::associative_property_map + mu0_map(mu0_init), mu1_map(mu1_init); + + interpolated_corrected_measure_mesh(pmesh, mu0_map, MU0_AREA_MEASURE); + interpolated_corrected_measure_mesh(pmesh, mu1_map, MU1_MEAN_CURVATURE_MEASURE); + + for (face_descriptor f : faces(pmesh)) + { + expand_interpolated_corrected_measure_face(pmesh, mu0_map, f, np); + expand_interpolated_corrected_measure_face(pmesh, mu1_map, f, np); + + GT::FT f_mu0 = get(mu0_map, f); + if (f_mu0 > 0.000001) + put(fcm, f, 0.5 * get(mu1_map, f) / get(mu0_map, f)); + else + put(fcm, f, 0); + } + +} + +template + void interpolated_corrected_gaussian_curvature(const PolygonMesh& pmesh, + FaceCurvatureMap fcm, + const NamedParameters& np = parameters::default_values()) +{ + typedef typename GetGeomTraits::type GT; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef std::unordered_map FaceMeasureMap_tag; + + FaceMeasureMap_tag mu0_init, mu2_init; + boost::associative_property_map + mu0_map(mu0_init), mu2_map(mu2_init); + + interpolated_corrected_measure_mesh(pmesh, mu0_map, MU0_AREA_MEASURE); + interpolated_corrected_measure_mesh(pmesh, mu2_map, MU2_GAUSSIAN_CURVATURE_MEASURE); + + for (face_descriptor f : faces(pmesh)) + { + expand_interpolated_corrected_measure_face(pmesh, mu0_map, f, np); + expand_interpolated_corrected_measure_face(pmesh, mu2_map, f, np); + + GT::FT f_mu0 = get(mu0_map, f); + if(f_mu0 > 0.000001) + put(fcm, f, get(mu2_map, f) / f_mu0); + else + put(fcm, f, 0); + } + +} + + + } } diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index 1ea1394649c2..88d86966f1d7 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -533,9 +533,8 @@ private Q_SLOTS: dock_widget->propertyBox->addItem("Scaled Jacobian"); dock_widget->propertyBox->addItem("Heat Intensity"); dock_widget->propertyBox->addItem("Heat Intensity (Intrinsic Delaunay)"); - dock_widget->propertyBox->addItem("corrected area density"); - dock_widget->propertyBox->addItem("corrected mean curvature density"); - dock_widget->propertyBox->addItem("corrected gaussian curvature density"); + dock_widget->propertyBox->addItem("Interpolated Corrected Mean Curvature"); + dock_widget->propertyBox->addItem("Interpolated Corrected Gaussian Curvature"); detectSMScalarProperties(sm_item->face_graph()); } @@ -610,15 +609,11 @@ private Q_SLOTS: return; sm_item->setRenderingMode(Gouraud); break; - case 4: // corrected area density - displayInterpolatedCurvatureMeasure(sm_item, PMP::MU0_AREA_MEASURE); - sm_item->setRenderingMode(Flat); - break; - case 5: // corrected mean curvature density + case 4: // Interpolated Corrected Mean Curvature displayInterpolatedCurvatureMeasure(sm_item, PMP::MU1_MEAN_CURVATURE_MEASURE); sm_item->setRenderingMode(Flat); break; - case 6: // corrected gaussian curvature density + case 5: // Interpolated Corrected Gaussian Curvature displayInterpolatedCurvatureMeasure(sm_item, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE); sm_item->setRenderingMode(Flat); break; @@ -657,15 +652,11 @@ private Q_SLOTS: if(does_exist) sm_item->face_graph()->remove_property_map(pmap); std::tie(pmap, does_exist) = - sm_item->face_graph()->property_map("f:corrected_area_density"); - if (does_exist) - sm_item->face_graph()->remove_property_map(pmap); - std::tie(pmap, does_exist) = - sm_item->face_graph()->property_map("f:corrected_mean_curvature_density"); + sm_item->face_graph()->property_map("f:interpolated_corrected_mean_curvature"); if (does_exist) sm_item->face_graph()->remove_property_map(pmap); std::tie(pmap, does_exist) = - sm_item->face_graph()->property_map("f:corrected_gaussian_curvature_density"); + sm_item->face_graph()->property_map("f:interpolated_corrected_gaussian_curvature"); if (does_exist) sm_item->face_graph()->remove_property_map(pmap); }); @@ -707,16 +698,12 @@ private Q_SLOTS: dock_widget->zoomToMaxButton->setEnabled(jacobian_max.count(sm_item)>0); break; case 4: - dock_widget->zoomToMinButton->setEnabled(mu0_min.count(sm_item) > 0); - dock_widget->zoomToMaxButton->setEnabled(mu0_max.count(sm_item) > 0); + dock_widget->zoomToMinButton->setEnabled(mean_curvature_min.count(sm_item) > 0); + dock_widget->zoomToMaxButton->setEnabled(mean_curvature_max.count(sm_item) > 0); break; case 5: - dock_widget->zoomToMinButton->setEnabled(mu1_min.count(sm_item) > 0); - dock_widget->zoomToMaxButton->setEnabled(mu1_max.count(sm_item) > 0); - break; - case 6: - dock_widget->zoomToMinButton->setEnabled(mu2_min.count(sm_item) > 0); - dock_widget->zoomToMaxButton->setEnabled(mu2_max.count(sm_item) > 0); + dock_widget->zoomToMinButton->setEnabled(gaussian_curvature_min.count(sm_item) > 0); + dock_widget->zoomToMaxButton->setEnabled(gaussian_curvature_max.count(sm_item) > 0); break; default: break; @@ -744,23 +731,17 @@ private Q_SLOTS: { smesh.remove_property_map(angles); } - SMesh::Property_map mu0; - std::tie(mu0, found) = smesh.property_map("f:corrected_area_density"); + SMesh::Property_map mean_curvature; + std::tie(mean_curvature, found) = smesh.property_map("f:interpolated_corrected_mean_curvature"); if (found) { - smesh.remove_property_map(mu0); + smesh.remove_property_map(mean_curvature); } - SMesh::Property_map mu1; - std::tie(mu1, found) = smesh.property_map("f:corrected_mean_curvature_density"); + SMesh::Property_map gaussian_curvature; + std::tie(gaussian_curvature, found) = smesh.property_map("f:interpolated_corrected_gaussian_curvature"); if (found) { - smesh.remove_property_map(mu0); - } - SMesh::Property_map mu2; - std::tie(mu2, found) = smesh.property_map("f:corrected_gaussian_curvature_density"); - if (found) - { - smesh.remove_property_map(mu0); + smesh.remove_property_map(gaussian_curvature); } } @@ -804,20 +785,20 @@ private Q_SLOTS: void displayInterpolatedCurvatureMeasure(Scene_surface_mesh_item* item, PMP::Curvature_measure_index mu_index) { - std::vector tied_map = { - "f:corrected_area_density", - "f:corrected_mean_curvature_density", - "f:corrected_gaussian_curvature_density" - }; + std::string tied_string = (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE)? + "f:interpolated_corrected_mean_curvature": "f:interpolated_corrected_gaussian_curvature"; SMesh& smesh = *item->face_graph(); //compute once and store the value per face bool non_init; SMesh::Property_map mu_i_map; std::tie(mu_i_map, non_init) = - smesh.add_property_map(tied_map[mu_index], 0); + smesh.add_property_map(tied_string, 0); if (non_init) { - PMP::interpolated_corrected_measure_mesh(smesh, mu_i_map, mu_index); + if (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE) + PMP::interpolated_corrected_mean_curvature(smesh, mu_i_map); + else + PMP::interpolated_corrected_gaussian_curvature(smesh, mu_i_map); double res_min = ARBITRARY_DBL_MAX, res_max = -ARBITRARY_DBL_MAX; @@ -837,24 +818,20 @@ private Q_SLOTS: } switch (mu_index) { - case PMP::MU0_AREA_MEASURE: - mu0_max[item] = std::make_pair(res_max, max_index); - mu0_min[item] = std::make_pair(res_min, min_index); - break; case PMP::MU1_MEAN_CURVATURE_MEASURE: - mu1_max[item] = std::make_pair(res_max, max_index); - mu1_min[item] = std::make_pair(res_min, min_index); + mean_curvature_max[item] = std::make_pair(res_max, max_index); + mean_curvature_min[item] = std::make_pair(res_min, min_index); break; case PMP::MU2_GAUSSIAN_CURVATURE_MEASURE: - mu2_max[item] = std::make_pair(res_max, max_index); - mu2_min[item] = std::make_pair(res_min, min_index); + gaussian_curvature_max[item] = std::make_pair(res_max, max_index); + gaussian_curvature_min[item] = std::make_pair(res_min, min_index); break; } connect(item, &Scene_surface_mesh_item::itemChanged, this, &DisplayPropertyPlugin::resetProperty); } - treat_sm_property(tied_map[mu_index], item->face_graph()); + treat_sm_property(tied_string, item->face_graph()); } @@ -1159,7 +1136,6 @@ private Q_SLOTS: case 1: case 4: case 5: - case 6: dock_widget->groupBox-> setEnabled(true); dock_widget->groupBox_3->setEnabled(true); @@ -1223,7 +1199,7 @@ private Q_SLOTS: case 4: { ::zoomToId(*item->face_graph(), - QString("f%1").arg(mu0_min[item].second), + QString("f%1").arg(mean_curvature_min[item].second), getActiveViewer(), dummy_fd, dummy_p); @@ -1232,20 +1208,12 @@ private Q_SLOTS: case 5: { ::zoomToId(*item->face_graph(), - QString("f%1").arg(mu1_min[item].second), + QString("f%1").arg(gaussian_curvature_min[item].second), getActiveViewer(), dummy_fd, dummy_p); } break; - case 6: - { - ::zoomToId(*item->face_graph(), - QString("f%1").arg(mu2_min[item].second), - getActiveViewer(), - dummy_fd, - dummy_p); - } break; break; default: @@ -1284,7 +1252,7 @@ private Q_SLOTS: case 4: { ::zoomToId(*item->face_graph(), - QString("f%1").arg(mu0_max[item].second), + QString("f%1").arg(mean_curvature_max[item].second), getActiveViewer(), dummy_fd, dummy_p); @@ -1293,21 +1261,12 @@ private Q_SLOTS: case 5: { ::zoomToId(*item->face_graph(), - QString("f%1").arg(mu1_max[item].second), + QString("f%1").arg(gaussian_curvature_max[item].second), getActiveViewer(), dummy_fd, dummy_p); } break; - case 6: - { - ::zoomToId(*item->face_graph(), - QString("f%1").arg(mu2_max[item].second), - getActiveViewer(), - dummy_fd, - dummy_p); - } - break; default: break; } @@ -1598,14 +1557,11 @@ private Q_SLOTS: std::unordered_map > angles_min; std::unordered_map > angles_max; - std::unordered_map > mu0_min; - std::unordered_map > mu0_max; + std::unordered_map > mean_curvature_min; + std::unordered_map > mean_curvature_max; - std::unordered_map > mu1_min; - std::unordered_map > mu1_max; - - std::unordered_map > mu2_min; - std::unordered_map > mu2_max; + std::unordered_map > gaussian_curvature_min; + std::unordered_map > gaussian_curvature_max; std::unordered_map is_source; 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 c224b4982513..dd4785ab7e8a 100644 --- a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h +++ b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h @@ -87,6 +87,7 @@ 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(ball_radius_t, ball_radius, ball_radius) 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 48ff36dcc94d621d27a1fde09811159e3963eb3a Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 27 Jul 2022 12:42:38 +0200 Subject: [PATCH 019/161] fixed some missing typenames --- .../interpolated_corrected_curvature_measures.h | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 737a2525ebd7..9f9a6c0fc14f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -415,7 +415,7 @@ template::type GT; typedef typename boost::graph_traits::face_descriptor face_descriptor; - typedef std::unordered_map FaceMeasureMap_tag; + typedef std::unordered_map FaceMeasureMap_tag; FaceMeasureMap_tag mu0_init, mu1_init; boost::associative_property_map @@ -484,13 +484,12 @@ template 0.000001) put(fcm, f, 0.5 * get(mu1_map, f) / get(mu0_map, f)); else put(fcm, f, 0); } - } template::type GT; typedef typename boost::graph_traits::face_descriptor face_descriptor; - typedef std::unordered_map FaceMeasureMap_tag; + typedef std::unordered_map FaceMeasureMap_tag; FaceMeasureMap_tag mu0_init, mu2_init; boost::associative_property_map @@ -515,13 +514,12 @@ template 0.000001) put(fcm, f, get(mu2_map, f) / f_mu0); else put(fcm, f, 0); } - } From 6b985bfeb86efd80958f42409fdd9fae5f2b5539 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 27 Jul 2022 13:25:35 +0200 Subject: [PATCH 020/161] for boundary faces --- .../Curvatures/interpolated_corrected_curvature_measures.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 9f9a6c0fc14f..3528ffed8a43 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -439,7 +439,7 @@ template::null_face()) { bfs_q.push(fj); bfs_v.insert(fj); From 8d2a5bcf82a026a0c7aa8399dc024c96cf40b0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 27 Jul 2022 13:40:25 +0200 Subject: [PATCH 021/161] add license header --- .../interpolated_corrected_curvature_measures.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 9f9a6c0fc14f..a3c3d9f16e2c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -1,3 +1,16 @@ +// Copyright (c) 2022 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_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H #define CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H #endif From c99008dde1ad696b2e9eea65081908f325e5ac8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 27 Jul 2022 13:40:33 +0200 Subject: [PATCH 022/161] trailing whitespaces --- .../PackageDescription.txt | 2 +- .../interpolated_corrected_curvatures.cpp | 2 +- ...nterpolated_corrected_curvature_measures.h | 28 +++++++++---------- .../Display/Display_property_plugin.cpp | 4 +-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index c25cfb8490d3..d0cab92d6a89 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -16,7 +16,7 @@ /// Functions to triangulate faces, and to refine and fair regions of a polygon mesh. /// \ingroup PkgPolygonMeshProcessingRef -/// \defgroup PMP_corrected_curvatures_grp Corrected Curvature Computation +/// \defgroup PMP_corrected_curvatures_grp Corrected Curvature Computation /// Functions to compute the corrected curvatures of a polygon mesh. /// \ingroup PkgPolygonMeshProcessingRef diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp index 4480366bc2b0..acf4591fe392 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -47,6 +47,6 @@ int main(int argc, char* argv[]) ); for (face_descriptor f: g1.faces()) - std::cout << f.idx() << ": HC = " << get(mean_curvature_map, f) + std::cout << f.idx() << ": HC = " << get(mean_curvature_map, f) << ", GC = " << get(gaussian_curvature_map, f) << "\n"; } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index a3c3d9f16e2c..f618533d2bc9 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -59,7 +59,7 @@ enum Curvature_measure_index { * @param mu_i an enum for choosing between computing the area measure, * the mean curvature measure, or the gaussian curvature measure. * -* @return a scalar of type `GT::FT`. +* @return a scalar of type `GT::FT`. * This is the value of the interpolated corrected measure of the given triangle. * * @see `interpolated_corrected_measure_face()` @@ -69,9 +69,9 @@ template typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vector_3 x0, const typename GT::Vector_3 x1, const typename GT::Vector_3 x2, - const typename GT::Vector_3 u0, - const typename GT::Vector_3 u1, - const typename GT::Vector_3 u2, + const typename GT::Vector_3 u0, + const typename GT::Vector_3 u1, + const typename GT::Vector_3 u2, const Curvature_measure_index mu_i) { switch (mu_i) @@ -102,8 +102,8 @@ typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vecto * \ingroup PMP_corrected_curvatures_grp * * computes the interpolated corrected measure of specific quad -* Note that the vertices 0 to 3 are ordered like this \n -* v0 _ v1 \n +* Note that the vertices 0 to 3 are ordered like this \n +* v0 _ v1 \n * v2 |_| v3 * * @tparam GT is the geometric traits class. @@ -119,7 +119,7 @@ typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vecto * @param mu_i an enum for choosing between computing the area measure, * the mean curvature measure, or the gaussian curvature measure. * -* @return a scalar of type `GT::FT`. +* @return a scalar of type `GT::FT`. * This is the value of the interpolated corrected measure of the given triangle. * * @see `interpolated_corrected_measure_face()` @@ -138,7 +138,7 @@ typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 { // x0 _ x1 // x2 |_| x3 - + switch (mu_i) { case MU0_AREA_MEASURE: @@ -193,7 +193,7 @@ typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 * @param mu_i an enum for choosing between computing the area measure, * the mean curvature measure, or the gaussian curvature measure. * -* @return a scalar of type `GT::FT`. +* @return a scalar of type `GT::FT`. * This is the value of the interpolated corrected measure of the given face. * * @see `interpolated_corrected_measure_triangle()` @@ -231,7 +231,7 @@ typename GT::FT interpolated_corrected_measure_face(const std::vector::%vertex_descriptor` * as key type and `%Point_3` as value type} * \cgalParamDefault{`boost::get(CGAL::vertex_point, pmesh)`} @@ -275,7 +275,7 @@ typename GT::FT interpolated_corrected_measure_face(const std::vector::%vertex_descriptor` * as key type and `%Vector_3` as value type} * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} @@ -405,7 +405,7 @@ template::face_descriptor face_descriptor; typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - const typename GetGeomTraits::type::FT + const typename GetGeomTraits::type::FT r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0.01); if (r < 0.000001) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index 88d86966f1d7..13f704774e38 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -785,13 +785,13 @@ private Q_SLOTS: void displayInterpolatedCurvatureMeasure(Scene_surface_mesh_item* item, PMP::Curvature_measure_index mu_index) { - std::string tied_string = (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE)? + std::string tied_string = (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE)? "f:interpolated_corrected_mean_curvature": "f:interpolated_corrected_gaussian_curvature"; SMesh& smesh = *item->face_graph(); //compute once and store the value per face bool non_init; SMesh::Property_map mu_i_map; - std::tie(mu_i_map, non_init) = + std::tie(mu_i_map, non_init) = smesh.add_property_map(tied_string, 0); if (non_init) { From 1c42a61fa11dc97e0c4629a4c73600e1356a518c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 27 Jul 2022 14:26:28 +0200 Subject: [PATCH 023/161] use traits functor --- ...nterpolated_corrected_curvature_measures.h | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index f618533d2bc9..415737e4991a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -74,25 +74,26 @@ typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vecto const typename GT::Vector_3 u2, const Curvature_measure_index mu_i) { + GT::Construct_cross_product_3 cross_product; switch (mu_i) { case MU0_AREA_MEASURE: { const typename GT::Vector_3 um = (u0 + u1 + u2) / 3.0; - return 0.5 * um * CGAL::cross_product(x1 - x0, x2 - x0); + return 0.5 * um * cross_product(x1 - x0, x2 - x0); } case MU1_MEAN_CURVATURE_MEASURE: { const typename GT::Vector_3 um = (u0 + u1 + u2) / 3.0; - return 0.5 * um * (CGAL::cross_product(u2 - u1, x0) - + CGAL::cross_product(u0 - u2, x1) - + CGAL::cross_product(u1 - u0, x2)); + return 0.5 * um * (cross_product(u2 - u1, x0) + + cross_product(u0 - u2, x1) + + cross_product(u1 - u0, x2)); } case MU2_GAUSSIAN_CURVATURE_MEASURE: - return 0.5 * u0 * CGAL::cross_product(u1, u2); + return 0.5 * u0 * cross_product(u1, u2); default: return 0; } @@ -138,42 +139,42 @@ typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 { // x0 _ x1 // x2 |_| x3 - + GT::Construct_cross_product_3 cross_product; switch (mu_i) { case MU0_AREA_MEASURE: return (1 / 36.0) * ( - (4 * u0 + 2 * u1 + 2 * u2 + u3) * CGAL::cross_product(x1 - x0, x2 - x0) - + (2 * u0 + 4 * u1 + u2 + 2 * u3) * CGAL::cross_product(x1 - x0, x3 - x1) - + (2 * u0 + u1 + 4 * u2 + 2 * u3) * CGAL::cross_product(x3 - x2, x2 - x0) - + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * CGAL::cross_product(x3 - x2, x3 - x1) + (4 * u0 + 2 * u1 + 2 * u2 + u3) * cross_product(x1 - x0, x2 - x0) + + (2 * u0 + 4 * u1 + u2 + 2 * u3) * cross_product(x1 - x0, x3 - x1) + + (2 * u0 + u1 + 4 * u2 + 2 * u3) * cross_product(x3 - x2, x2 - x0) + + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * cross_product(x3 - x2, x3 - x1) ); case MU1_MEAN_CURVATURE_MEASURE: { const typename GT::Vector_3 u03 = u3 - u0; const typename GT::Vector_3 u12 = u2 - u1; - const typename GT::Vector_3 x0_cross = CGAL::cross_product(u12, x0); - const typename GT::Vector_3 x1_cross = -CGAL::cross_product(u03, x1); - const typename GT::Vector_3 x2_cross = CGAL::cross_product(u03, x2); - const typename GT::Vector_3 x3_cross = -CGAL::cross_product(u12, x3); + const typename GT::Vector_3 x0_cross = cross_product(u12, x0); + const typename GT::Vector_3 x1_cross = -cross_product(u03, x1); + const typename GT::Vector_3 x2_cross = cross_product(u03, x2); + const typename GT::Vector_3 x3_cross = -cross_product(u12, x3); return (1 / 12.0) * ( - u0 * (2 * x0_cross - CGAL::cross_product((u2 + u3), x1) + CGAL::cross_product((u1 + u3), x2) + x3_cross) - + u1 * (CGAL::cross_product((u2 + u3), x0) + 2 * x1_cross + x2_cross - CGAL::cross_product((u0 + u2), x3)) - + u2 * (-CGAL::cross_product((u1 + u3), x0) + x1_cross + 2 * x2_cross + CGAL::cross_product((u0 + u1), x3)) - + u3 * (x0_cross + CGAL::cross_product((u0 + u2), x1) - CGAL::cross_product((u0 + u1), x2) + 2 * x3_cross) + u0 * (2 * x0_cross - cross_product((u2 + u3), x1) + cross_product((u1 + u3), x2) + x3_cross) + + u1 * (cross_product((u2 + u3), x0) + 2 * x1_cross + x2_cross - cross_product((u0 + u2), x3)) + + u2 * (-cross_product((u1 + u3), x0) + x1_cross + 2 * x2_cross + cross_product((u0 + u1), x3)) + + u3 * (x0_cross + cross_product((u0 + u2), x1) - cross_product((u0 + u1), x2) + 2 * x3_cross) ); } case MU2_GAUSSIAN_CURVATURE_MEASURE: return (1 / 36.0) * ( - (4 * u0 + 2 * u1 + 2 * u2 + u3) * CGAL::cross_product(u1 - u0, u2 - u0) - + (2 * u0 + 4 * u1 + u2 + 2 * u3) * CGAL::cross_product(u1 - u0, u3 - u1) - + (2 * u0 + u1 + 4 * u2 + 2 * u3) * CGAL::cross_product(u3 - u2, u2 - u0) - + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * CGAL::cross_product(u3 - u2, u3 - u1) + (4 * u0 + 2 * u1 + 2 * u2 + u3) * cross_product(u1 - u0, u2 - u0) + + (2 * u0 + 4 * u1 + u2 + 2 * u3) * cross_product(u1 - u0, u3 - u1) + + (2 * u0 + u1 + 4 * u2 + 2 * u3) * cross_product(u3 - u2, u2 - u0) + + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * cross_product(u3 - u2, u3 - u1) ); default: return 0; From 12a627e23f2d26e98a6b53228fa4c050cef07d2c Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 27 Jul 2022 15:40:06 +0200 Subject: [PATCH 024/161] expanding from and evaluating on vertices instead of faces --- .../interpolated_corrected_curvatures.cpp | 15 ++-- ...nterpolated_corrected_curvature_measures.h | 87 +++++++++++-------- .../Display/Display_property_plugin.cpp | 56 ++++++------ 3 files changed, 85 insertions(+), 73 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp index 4480366bc2b0..63c0b8df528c 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -17,7 +17,7 @@ typedef CGAL::Surface_mesh Mesh; typedef boost::graph_traits::face_descriptor face_descriptor; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; typedef std::unordered_map FaceMeasureMap_tag; -typedef std::unordered_map vertexVectorMap_tag; +typedef std::unordered_map vertexVectorMap_tag; int main(int argc, char* argv[]) @@ -25,7 +25,7 @@ int main(int argc, char* argv[]) Mesh g1; const std::string filename = (argc>1) ? argv[1] : - CGAL::data_file_path("meshes/small_bunny.obj"); + CGAL::data_file_path("meshes/cylinder.off"); if(!CGAL::IO::read_polygon_mesh(filename, g1)) { @@ -33,8 +33,9 @@ int main(int argc, char* argv[]) return EXIT_FAILURE; } - FaceMeasureMap_tag mean_curvature_init, gaussian_curvature_init; - boost::associative_property_map + + vertexVectorMap_tag mean_curvature_init, gaussian_curvature_init; + boost::associative_property_map mean_curvature_map(mean_curvature_init), gaussian_curvature_map(gaussian_curvature_init); PMP::interpolated_corrected_mean_curvature( @@ -46,7 +47,7 @@ int main(int argc, char* argv[]) gaussian_curvature_map ); - for (face_descriptor f: g1.faces()) - std::cout << f.idx() << ": HC = " << get(mean_curvature_map, f) - << ", GC = " << get(gaussian_curvature_map, f) << "\n"; + for (vertex_descriptor v : vertices(g1)) + std::cout << v.idx() << ": HC = " << get(mean_curvature_map, v) + << ", GC = " << get(gaussian_curvature_map, v) << "\n"; } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 3528ffed8a43..3b499a8fe94d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -378,11 +378,12 @@ typename GT::FT face_in_ball_ratio_2(const std::vector& x return (r - d_min) / (d_max - d_min); } -template - void expand_interpolated_corrected_measure_face(const PolygonMesh& pmesh, + void expand_interpolated_corrected_measure_vertex(const PolygonMesh& pmesh, FaceMeasureMap fmm, - const typename boost::graph_traits::face_descriptor f, + VertexCurvatureMap vcm, + const typename boost::graph_traits::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { using parameters::choose_parameter; @@ -406,30 +407,28 @@ template bfs_q; std::unordered_set bfs_v; - //get face center c - typename GT::Vector_3 c; - for (vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) - { - typename GT::Point_3 p = get(vpm, v); - c += typename GT::Vector_3(p.x(), p.y(), p.z()); - } - c /= degree(f, pmesh); + typename GT::Point_3 vp = get(vpm, v); + typename GT::Vector_3 c = typename GT::Vector_3(vp.x(), vp.y(), vp.z()); typename GT::FT corrected_mui = 0; - bfs_q.push(f); - bfs_v.insert(f); - + for (face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + if (f != boost::graph_traits::null_face()) + { + bfs_q.push(f); + bfs_v.insert(f); + } + } while (!bfs_q.empty()) { face_descriptor fi = bfs_q.front(); bfs_q.pop(); // looping over vertices in face to get point coordinates std::vector x; - for (vertex_descriptor v : vertices_around_face(halfedge(fi, pmesh), pmesh)) + for (vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) { - typename GT::Point_3 p = get(vpm, v); - x.push_back(typename GT::Vector_3(p.x(), p.y(), p.z())); + typename GT::Point_3 pi = get(vpm, vi); + x.push_back(typename GT::Vector_3(pi.x(), pi.y(), pi.z())); } const typename GT::FT f_ratio = face_in_ball_ratio_2(x, r, c); @@ -448,7 +447,7 @@ template void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, - FaceCurvatureMap fcm, + VertexCurvatureMap vcm, const NamedParameters& np = parameters::default_values()) { typedef typename GetGeomTraits::type GT; typedef typename boost::graph_traits::face_descriptor face_descriptor; typedef std::unordered_map FaceMeasureMap_tag; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef std::unordered_map VertexMeasureMap_tag; FaceMeasureMap_tag mu0_init, mu1_init; boost::associative_property_map mu0_map(mu0_init), mu1_map(mu1_init); + VertexMeasureMap_tag mu0_expand_init, mu1_expand_init; + boost::associative_property_map + mu0_expand_map(mu0_expand_init), mu1_expand_map(mu1_expand_init); + interpolated_corrected_measure_mesh(pmesh, mu0_map, MU0_AREA_MEASURE); interpolated_corrected_measure_mesh(pmesh, mu1_map, MU1_MEAN_CURVATURE_MEASURE); - for (face_descriptor f : faces(pmesh)) + for (vertex_descriptor v : vertices(pmesh)) { - expand_interpolated_corrected_measure_face(pmesh, mu0_map, f, np); - expand_interpolated_corrected_measure_face(pmesh, mu1_map, f, np); - - const typename GT::FT f_mu0 = get(mu0_map, f); - if (f_mu0 > 0.000001) - put(fcm, f, 0.5 * get(mu1_map, f) / get(mu0_map, f)); - else - put(fcm, f, 0); + expand_interpolated_corrected_measure_vertex(pmesh, mu0_map, mu0_expand_map, v, np); + expand_interpolated_corrected_measure_vertex(pmesh, mu1_map, mu1_expand_map, v, np); + + typename GT::FT v_mu0 = get(mu0_expand_map, v); + if (v_mu0 > 0.000001) + put(vcm, v, 0.5 * get(mu1_expand_map, v) / v_mu0); + else + put(vcm, v, 0); } } -template void interpolated_corrected_gaussian_curvature(const PolygonMesh& pmesh, - FaceCurvatureMap fcm, + VertexCurvatureMap vcm, const NamedParameters& np = parameters::default_values()) { typedef typename GetGeomTraits::type GT; typedef typename boost::graph_traits::face_descriptor face_descriptor; typedef std::unordered_map FaceMeasureMap_tag; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef std::unordered_map VertexMeasureMap_tag; FaceMeasureMap_tag mu0_init, mu2_init; boost::associative_property_map mu0_map(mu0_init), mu2_map(mu2_init); + VertexMeasureMap_tag mu0_expand_init, mu2_expand_init; + boost::associative_property_map + mu0_expand_map(mu0_expand_init), mu2_expand_map(mu2_expand_init); + interpolated_corrected_measure_mesh(pmesh, mu0_map, MU0_AREA_MEASURE); interpolated_corrected_measure_mesh(pmesh, mu2_map, MU2_GAUSSIAN_CURVATURE_MEASURE); - for (face_descriptor f : faces(pmesh)) + for (vertex_descriptor v : vertices(pmesh)) { - expand_interpolated_corrected_measure_face(pmesh, mu0_map, f, np); - expand_interpolated_corrected_measure_face(pmesh, mu2_map, f, np); + expand_interpolated_corrected_measure_vertex(pmesh, mu0_map, mu0_expand_map, v, np); + expand_interpolated_corrected_measure_vertex(pmesh, mu2_map, mu2_expand_map, v, np); - const typename GT::FT f_mu0 = get(mu0_map, f); - if(f_mu0 > 0.000001) - put(fcm, f, get(mu2_map, f) / f_mu0); + typename GT::FT v_mu0 = get(mu0_expand_map, v); + if(v_mu0 > 0.000001) + put(vcm, v, get(mu2_expand_map, v) / v_mu0); else - put(fcm, f, 0); + put(vcm, v, 0); } } diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index 88d86966f1d7..2f9bd19e584b 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -611,11 +611,11 @@ private Q_SLOTS: break; case 4: // Interpolated Corrected Mean Curvature displayInterpolatedCurvatureMeasure(sm_item, PMP::MU1_MEAN_CURVATURE_MEASURE); - sm_item->setRenderingMode(Flat); + sm_item->setRenderingMode(Gouraud); break; case 5: // Interpolated Corrected Gaussian Curvature displayInterpolatedCurvatureMeasure(sm_item, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE); - sm_item->setRenderingMode(Flat); + sm_item->setRenderingMode(Gouraud); break; default: if(dock_widget->propertyBox->currentText().contains("v:")) @@ -652,11 +652,11 @@ private Q_SLOTS: if(does_exist) sm_item->face_graph()->remove_property_map(pmap); std::tie(pmap, does_exist) = - sm_item->face_graph()->property_map("f:interpolated_corrected_mean_curvature"); + sm_item->face_graph()->property_map("v:interpolated_corrected_mean_curvature"); if (does_exist) sm_item->face_graph()->remove_property_map(pmap); std::tie(pmap, does_exist) = - sm_item->face_graph()->property_map("f:interpolated_corrected_gaussian_curvature"); + sm_item->face_graph()->property_map("v:interpolated_corrected_gaussian_curvature"); if (does_exist) sm_item->face_graph()->remove_property_map(pmap); }); @@ -731,14 +731,14 @@ private Q_SLOTS: { smesh.remove_property_map(angles); } - SMesh::Property_map mean_curvature; - std::tie(mean_curvature, found) = smesh.property_map("f:interpolated_corrected_mean_curvature"); + SMesh::Property_map mean_curvature; + std::tie(mean_curvature, found) = smesh.property_map("v:interpolated_corrected_mean_curvature"); if (found) { smesh.remove_property_map(mean_curvature); } - SMesh::Property_map gaussian_curvature; - std::tie(gaussian_curvature, found) = smesh.property_map("f:interpolated_corrected_gaussian_curvature"); + SMesh::Property_map gaussian_curvature; + std::tie(gaussian_curvature, found) = smesh.property_map("v:interpolated_corrected_gaussian_curvature"); if (found) { smesh.remove_property_map(gaussian_curvature); @@ -786,13 +786,13 @@ private Q_SLOTS: void displayInterpolatedCurvatureMeasure(Scene_surface_mesh_item* item, PMP::Curvature_measure_index mu_index) { std::string tied_string = (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE)? - "f:interpolated_corrected_mean_curvature": "f:interpolated_corrected_gaussian_curvature"; + "v:interpolated_corrected_mean_curvature": "v:interpolated_corrected_gaussian_curvature"; SMesh& smesh = *item->face_graph(); //compute once and store the value per face bool non_init; - SMesh::Property_map mu_i_map; + SMesh::Property_map mu_i_map; std::tie(mu_i_map, non_init) = - smesh.add_property_map(tied_string, 0); + smesh.add_property_map(tied_string, 0); if (non_init) { if (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE) @@ -802,18 +802,18 @@ private Q_SLOTS: double res_min = ARBITRARY_DBL_MAX, res_max = -ARBITRARY_DBL_MAX; - SMesh::Face_index min_index, max_index; - for (SMesh::Face_index f : faces(smesh)) + SMesh::Vertex_index min_index, max_index; + for (SMesh::Vertex_index v : vertices(smesh)) { - if (mu_i_map[f] > res_max) + if (mu_i_map[v] > res_max) { - res_max = mu_i_map[f]; - max_index = f; + res_max = mu_i_map[v]; + max_index = v; } - if (mu_i_map[f] < res_min) + if (mu_i_map[v] < res_min) { - res_min = mu_i_map[f]; - min_index = f; + res_min = mu_i_map[v]; + min_index = v; } } switch (mu_index) @@ -831,7 +831,7 @@ private Q_SLOTS: connect(item, &Scene_surface_mesh_item::itemChanged, this, &DisplayPropertyPlugin::resetProperty); } - treat_sm_property(tied_string, item->face_graph()); + treat_sm_property(tied_string, item->face_graph()); } @@ -1199,7 +1199,7 @@ private Q_SLOTS: case 4: { ::zoomToId(*item->face_graph(), - QString("f%1").arg(mean_curvature_min[item].second), + QString("v%1").arg(mean_curvature_min[item].second), getActiveViewer(), dummy_fd, dummy_p); @@ -1208,7 +1208,7 @@ private Q_SLOTS: case 5: { ::zoomToId(*item->face_graph(), - QString("f%1").arg(gaussian_curvature_min[item].second), + QString("v%1").arg(gaussian_curvature_min[item].second), getActiveViewer(), dummy_fd, dummy_p); @@ -1252,7 +1252,7 @@ private Q_SLOTS: case 4: { ::zoomToId(*item->face_graph(), - QString("f%1").arg(mean_curvature_max[item].second), + QString("v%1").arg(mean_curvature_max[item].second), getActiveViewer(), dummy_fd, dummy_p); @@ -1261,7 +1261,7 @@ private Q_SLOTS: case 5: { ::zoomToId(*item->face_graph(), - QString("f%1").arg(gaussian_curvature_max[item].second), + QString("v%1").arg(gaussian_curvature_max[item].second), getActiveViewer(), dummy_fd, dummy_p); @@ -1557,11 +1557,11 @@ private Q_SLOTS: std::unordered_map > angles_min; std::unordered_map > angles_max; - std::unordered_map > mean_curvature_min; - std::unordered_map > mean_curvature_max; + std::unordered_map > mean_curvature_min; + std::unordered_map > mean_curvature_max; - std::unordered_map > gaussian_curvature_min; - std::unordered_map > gaussian_curvature_max; + std::unordered_map > gaussian_curvature_min; + std::unordered_map > gaussian_curvature_max; std::unordered_map is_source; From 4ffd2d2a098b3c0bb69db2ade868e770e153de54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 27 Jul 2022 19:05:30 +0200 Subject: [PATCH 025/161] add missing typename --- .../Curvatures/interpolated_corrected_curvature_measures.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 03e5f396e3ce..0c6a370059c4 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -74,7 +74,7 @@ typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vecto const typename GT::Vector_3 u2, const Curvature_measure_index mu_i) { - GT::Construct_cross_product_3 cross_product; + typename GT::Construct_cross_product_3 cross_product; switch (mu_i) { case MU0_AREA_MEASURE: @@ -139,7 +139,7 @@ typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 { // x0 _ x1 // x2 |_| x3 - GT::Construct_cross_product_3 cross_product; + typename GT::Construct_cross_product_3 cross_product; switch (mu_i) { case MU0_AREA_MEASURE: From 62c91c1479fb2396ed6b0e9ef9d707e9ef643f93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 27 Jul 2022 19:10:52 +0200 Subject: [PATCH 026/161] remove trailing whitespaces --- .../Polyhedron/Plugins/Display/Display_property_plugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index 2f9bd19e584b..0334eb45a972 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -785,13 +785,13 @@ private Q_SLOTS: void displayInterpolatedCurvatureMeasure(Scene_surface_mesh_item* item, PMP::Curvature_measure_index mu_index) { - std::string tied_string = (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE)? + std::string tied_string = (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE)? "v:interpolated_corrected_mean_curvature": "v:interpolated_corrected_gaussian_curvature"; SMesh& smesh = *item->face_graph(); //compute once and store the value per face bool non_init; SMesh::Property_map mu_i_map; - std::tie(mu_i_map, non_init) = + std::tie(mu_i_map, non_init) = smesh.add_property_map(tied_string, 0); if (non_init) { From 184fa0c8a4b5be1b0ca9aab7bdcade05de342cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 27 Jul 2022 19:28:44 +0200 Subject: [PATCH 027/161] fix invalid functor name --- .../Curvatures/interpolated_corrected_curvature_measures.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 0c6a370059c4..c3f349740204 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -74,7 +74,7 @@ typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vecto const typename GT::Vector_3 u2, const Curvature_measure_index mu_i) { - typename GT::Construct_cross_product_3 cross_product; + typename GT::Construct_cross_product_vector_3 cross_product; switch (mu_i) { case MU0_AREA_MEASURE: @@ -139,7 +139,7 @@ typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 { // x0 _ x1 // x2 |_| x3 - typename GT::Construct_cross_product_3 cross_product; + typename GT::Construct_cross_product_vector_3 cross_product; switch (mu_i) { case MU0_AREA_MEASURE: From db753ee6b568c16ce2872a72d21cf9bed62e42c0 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 31 Jul 2022 00:21:55 +0200 Subject: [PATCH 028/161] Demo improvements + minor fixes - Fixed some typos in example file and in comment in display prop plugin - Added an option in random perturbation plugin to compute normals before hand - added slider for expanding radius with an exponential range and with max val dependant on max edge length --- .../interpolated_corrected_curvatures.cpp | 6 +-- ...nterpolated_corrected_curvature_measures.h | 12 ----- .../Plugins/Display/Display_property.ui | 36 ++++++++++++-- .../Display/Display_property_plugin.cpp | 49 ++++++++++++++++++- .../Plugins/PMP/Random_perturbation_dialog.ui | 24 ++++++++- .../PMP/Random_perturbation_plugin.cpp | 7 +++ 6 files changed, 111 insertions(+), 23 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp index 63c0b8df528c..5359315f0fa0 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -17,7 +17,7 @@ typedef CGAL::Surface_mesh Mesh; typedef boost::graph_traits::face_descriptor face_descriptor; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; typedef std::unordered_map FaceMeasureMap_tag; -typedef std::unordered_map vertexVectorMap_tag; +typedef std::unordered_map vertexMeasureMap_tag; int main(int argc, char* argv[]) @@ -34,8 +34,8 @@ int main(int argc, char* argv[]) } - vertexVectorMap_tag mean_curvature_init, gaussian_curvature_init; - boost::associative_property_map + vertexMeasureMap_tag mean_curvature_init, gaussian_curvature_init; + boost::associative_property_map mean_curvature_map(mean_curvature_init), gaussian_curvature_map(gaussian_curvature_init); PMP::interpolated_corrected_mean_curvature( diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index c3f349740204..d36e1c67e363 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -338,7 +338,6 @@ template //typename GT::FT triangle_in_ball_ratio_1(const typename GT::Vector_3 x1, @@ -464,17 +463,6 @@ template -// void expand_interpolated_corrected_measure_mesh(const PolygonMesh& pmesh, -// FaceMeasureMap fmm, -// const NamedParameters& np = parameters::default_values()) -//{ -// typedef typename boost::graph_traits::face_descriptor face_descriptor; -// for (face_descriptor f : faces(pmesh)) -// expand_interpolated_corrected_measure_face(pmesh, fmm, f, np); -//} - template void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property.ui b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property.ui index 7a5a19621f42..70c22f8348c2 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property.ui +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property.ui @@ -84,18 +84,18 @@ Ramp Colors - + - Color Min... + Min - Color Max... + Max @@ -135,7 +135,7 @@ Zoom - + @@ -158,7 +158,7 @@ Ramp Extrema - + @@ -186,6 +186,32 @@ + + + + 0 + + + 100 + + + true + + + Qt::Horizontal + + + QSlider::TicksAbove + + + + + + + Expanding Radius: 0 + + + diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index 0334eb45a972..a2d25d167c6b 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -783,12 +784,56 @@ private Q_SLOTS: treat_sm_property("f:jacobian", item->face_graph()); } + double sliderRangeToExpandRadius(SMesh& smesh, double val) + { + double sliderMin = dock_widget->expandingRadiusSlider->minimum(); + double sliderMax = dock_widget->expandingRadiusSlider->maximum() - sliderMin; + val -= sliderMin; + sliderMin = 0; + + auto vpm = get(CGAL::vertex_point, smesh); + + auto edge_range = CGAL::edges(smesh); + + if (edge_range.begin() == edge_range.end()) + return 0; + + auto edge_reference = std::max_element(edge_range.begin(), edge_range.end(), [&, vpm, smesh](auto l, auto r) { + auto res = EPICK().compare_squared_distance_3_object()( + get(vpm, source((l), smesh)), + get(vpm, target((l), smesh)), + get(vpm, source((r), smesh)), + get(vpm, target((r), smesh))); + return res == CGAL::SMALLER; + }); + + // if edge_reference is not derefrenceble + if (edge_reference == edge_range.end()) + return 0; + + double L = sqrt( + (get(vpm, source((*edge_reference), smesh)) - get(vpm, target((*edge_reference), smesh))) + .squared_length() + ); + + std::cout << L << std::endl; + + double outMin = 0, outMax = 5 * L, base = 1.2; + + return (pow(base, val) - 1) * outMax / (pow(base, sliderMax) - 1); + + } + void displayInterpolatedCurvatureMeasure(Scene_surface_mesh_item* item, PMP::Curvature_measure_index mu_index) { std::string tied_string = (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE)? "v:interpolated_corrected_mean_curvature": "v:interpolated_corrected_gaussian_curvature"; SMesh& smesh = *item->face_graph(); - //compute once and store the value per face + + expandRadius = sliderRangeToExpandRadius(smesh, dock_widget->expandingRadiusSlider->value()); + dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expandRadius)); + + //compute once and store the value per vertex bool non_init; SMesh::Property_map mu_i_map; std::tie(mu_i_map, non_init) = @@ -1565,7 +1610,7 @@ private Q_SLOTS: std::unordered_map is_source; - + double expandRadius; double minBox; double maxBox; QPixmap legend_; diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Random_perturbation_dialog.ui b/Polyhedron/demo/Polyhedron/Plugins/PMP/Random_perturbation_dialog.ui index 1d368c3dd592..3427d5b12e6a 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Random_perturbation_dialog.ui +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Random_perturbation_dialog.ui @@ -135,6 +135,26 @@ + + + keep vertex normals unperturbed + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + seed_spinbox + + + + + + + + + + + Random seed @@ -147,7 +167,7 @@ - + @@ -163,6 +183,8 @@ seed_spinbox seed_label + keep_normal_label + keep_normal_checkbox deterministic_label deterministic_checkbox diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Random_perturbation_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Random_perturbation_plugin.cpp index 53499b823367..9afb452ab13c 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Random_perturbation_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Random_perturbation_plugin.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -102,6 +103,12 @@ public Q_SLOTS: if (poly_item) { SMesh& pmesh = *poly_item->face_graph(); + if(ui.keep_normal_checkbox->isChecked()) + { + SMesh::Property_map vnormals = + pmesh.add_property_map("v:normal").first; + CGAL::Polygon_mesh_processing::compute_vertex_normals(pmesh,vnormals); + } if(ui.deterministic_checkbox->isChecked()) { unsigned int seed = static_cast(ui.seed_spinbox->value()); From 127c87857cbec43e692dfd94aefbe7755029e376 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 31 Jul 2022 00:32:42 +0200 Subject: [PATCH 029/161] Use slider radius (after remapping) in curvature computation --- .../Polyhedron/Plugins/Display/Display_property_plugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index a2d25d167c6b..92f9df7b213d 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -841,9 +841,9 @@ private Q_SLOTS: if (non_init) { if (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE) - PMP::interpolated_corrected_mean_curvature(smesh, mu_i_map); + PMP::interpolated_corrected_mean_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expandRadius)); else - PMP::interpolated_corrected_gaussian_curvature(smesh, mu_i_map); + PMP::interpolated_corrected_gaussian_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expandRadius)); double res_min = ARBITRARY_DBL_MAX, res_max = -ARBITRARY_DBL_MAX; From 22c0859d92fa444f0ca78b2c21bb58cf843d730d Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 31 Jul 2022 00:41:43 +0200 Subject: [PATCH 030/161] trailing spaces --- .../demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index 92f9df7b213d..19afdc6728ec 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -815,7 +815,7 @@ private Q_SLOTS: (get(vpm, source((*edge_reference), smesh)) - get(vpm, target((*edge_reference), smesh))) .squared_length() ); - + std::cout << L << std::endl; double outMin = 0, outMax = 5 * L, base = 1.2; From 9635ec14975c74beaaac9cce6f4fc6c883f5ff98 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 31 Jul 2022 18:55:51 +0200 Subject: [PATCH 031/161] minor changes on demo (wip) --- ...nterpolated_corrected_curvature_measures.h | 9 +-- .../Display/Display_property_plugin.cpp | 77 +++++++++++-------- 2 files changed, 50 insertions(+), 36 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index d36e1c67e363..1ee24fef37f5 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -409,9 +409,6 @@ template::type::FT r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0.01); - if (r < 0.000001) - return; - typename GetVertexPointMap::const_type vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), get_const_property_map(CGAL::vertex_point, pmesh)); @@ -446,7 +443,7 @@ template(x, r, c); - if (f_ratio > 0.000001) + if (f_ratio > 0.00000001) { corrected_mui += f_ratio * get(fmm, fi); for (face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) @@ -492,7 +489,7 @@ template 0.000001) + if (v_mu0 > 0.00000001) put(vcm, v, 0.5 * get(mu1_expand_map, v) / v_mu0); else put(vcm, v, 0); @@ -528,7 +525,7 @@ template 0.000001) + if(v_mu0 > 0.00000001) put(vcm, v, get(mu2_expand_map, v) / v_mu0); else put(vcm, v, 0); diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index 19afdc6728ec..91deb90680a8 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -487,8 +487,10 @@ class DisplayPropertyPlugin : connect(scene_obj, SIGNAL(itemIndexSelected(int)), this,SLOT(detectScalarProperties(int))); - on_propertyBox_currentIndexChanged(0); + connect(dock_widget->expandingRadiusSlider, SIGNAL(valueChanged(int)), + this, SLOT(setExpandingRadius(int))); + on_propertyBox_currentIndexChanged(0); } private: @@ -716,6 +718,10 @@ private Q_SLOTS: { Scene_surface_mesh_item* item = qobject_cast(sender()); + + maxEdgeLength = -1; + setExpandingRadius(dock_widget->expandingRadiusSlider->value()); + if(!item) return; SMesh& smesh = *item->face_graph(); @@ -784,43 +790,56 @@ private Q_SLOTS: treat_sm_property("f:jacobian", item->face_graph()); } - double sliderRangeToExpandRadius(SMesh& smesh, double val) + void setExpandingRadius(int val_int) { double sliderMin = dock_widget->expandingRadiusSlider->minimum(); double sliderMax = dock_widget->expandingRadiusSlider->maximum() - sliderMin; - val -= sliderMin; + double val = val_int - sliderMin; sliderMin = 0; - auto vpm = get(CGAL::vertex_point, smesh); - - auto edge_range = CGAL::edges(smesh); - - if (edge_range.begin() == edge_range.end()) - return 0; + SMesh& smesh = *(qobject_cast(scene->item(scene->mainSelectionIndex())))->face_graph(); - auto edge_reference = std::max_element(edge_range.begin(), edge_range.end(), [&, vpm, smesh](auto l, auto r) { - auto res = EPICK().compare_squared_distance_3_object()( - get(vpm, source((l), smesh)), - get(vpm, target((l), smesh)), - get(vpm, source((r), smesh)), - get(vpm, target((r), smesh))); - return res == CGAL::SMALLER; - }); + auto vpm = get(CGAL::vertex_point, smesh); - // if edge_reference is not derefrenceble - if (edge_reference == edge_range.end()) - return 0; + if (maxEdgeLength < 0) + { + auto edge_range = CGAL::edges(smesh); - double L = sqrt( - (get(vpm, source((*edge_reference), smesh)) - get(vpm, target((*edge_reference), smesh))) - .squared_length() - ); + if (edge_range.begin() == edge_range.end()) + { + expandRadius = 0; + dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expandRadius)); + return; + } - std::cout << L << std::endl; + auto edge_reference = std::max_element(edge_range.begin(), edge_range.end(), [&, vpm, smesh](auto l, auto r) { + auto res = EPICK().compare_squared_distance_3_object()( + get(vpm, source((l), smesh)), + get(vpm, target((l), smesh)), + get(vpm, source((r), smesh)), + get(vpm, target((r), smesh))); + return res == CGAL::SMALLER; + }); + + // if edge_reference is not derefrenceble + if (edge_reference == edge_range.end()) + { + expandRadius = 0; + dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expandRadius)); + return; + } - double outMin = 0, outMax = 5 * L, base = 1.2; + maxEdgeLength = sqrt( + (get(vpm, source((*edge_reference), smesh)) - get(vpm, target((*edge_reference), smesh))) + .squared_length() + ); - return (pow(base, val) - 1) * outMax / (pow(base, sliderMax) - 1); + } + + double outMin = 0, outMax = 5 * maxEdgeLength, base = 1.2; + + expandRadius = (pow(base, val) - 1) * outMax / (pow(base, sliderMax) - 1); + dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expandRadius)); } @@ -830,9 +849,6 @@ private Q_SLOTS: "v:interpolated_corrected_mean_curvature": "v:interpolated_corrected_gaussian_curvature"; SMesh& smesh = *item->face_graph(); - expandRadius = sliderRangeToExpandRadius(smesh, dock_widget->expandingRadiusSlider->value()); - dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expandRadius)); - //compute once and store the value per vertex bool non_init; SMesh::Property_map mu_i_map; @@ -1611,6 +1627,7 @@ private Q_SLOTS: std::unordered_map is_source; double expandRadius; + double maxEdgeLength = -1; double minBox; double maxBox; QPixmap legend_; From 765220a4661743db3367676f6723cdc9dd23ccce Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 31 Jul 2022 20:03:33 +0200 Subject: [PATCH 032/161] removing the switch from measures functions (for optimization) --- ...nterpolated_corrected_curvature_measures.h | 277 ++++++++++-------- 1 file changed, 151 insertions(+), 126 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 1ee24fef37f5..250ea596c1c3 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -26,6 +26,7 @@ #include #include #include +#include namespace CGAL { @@ -46,207 +47,215 @@ enum Curvature_measure_index { /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected measure of specific triangle. +* computes the interpolated corrected area measure of a specific face. * * @tparam GT is the geometric traits class. * -* @param x0 is the position of vertex #0. -* @param x1 is the position of vertex #1. -* @param x2 is the position of vertex #2. -* @param u0 is the normal vector of vertex #0. -* @param u1 is the normal vector of vertex #1. -* @param u2 is the normal vector of vertex #2. -* @param mu_i an enum for choosing between computing the area measure, -* the mean curvature measure, or the gaussian curvature measure. +* @param x is a vector of the vertex positions of the face. +* @param u is a vector of the vertex nomrals of the face. * * @return a scalar of type `GT::FT`. -* This is the value of the interpolated corrected measure of the given triangle. +* This is the value of the interpolated corrected area measure of the given face. * -* @see `interpolated_corrected_measure_face()` -* @see `interpolated_corrected_measure_quad()` +* @see `interpolated_corrected_mean_curvature_measure_face()` +* @see `interpolated_corrected_gaussian_curvature_measure_face()` +* @see `interpolated_corrected_measure_mesh()` */ template -typename GT::FT interpolated_corrected_measure_triangle(const typename GT::Vector_3 x0, - const typename GT::Vector_3 x1, - const typename GT::Vector_3 x2, - const typename GT::Vector_3 u0, - const typename GT::Vector_3 u1, - const typename GT::Vector_3 u2, - const Curvature_measure_index mu_i) +typename GT::FT interpolated_corrected_area_measure_face(const std::vector& x, + const std::vector& u) { + std::size_t n = x.size(); + CGAL_precondition(u.size() == n); + CGAL_precondition(n >= 3); + typename GT::Construct_cross_product_vector_3 cross_product; - switch (mu_i) + + // Triangle: use triangle formula + if (n == 3) { - case MU0_AREA_MEASURE: + const typename GT::Vector_3 um = (u[0] + u[1] + u[2]) / 3.0; + return 0.5 * um * cross_product(x[1] - x[0], x[2] - x[0]); + } + // Quad: use bilinear interpolation formula + else if (n == 4) { - const typename GT::Vector_3 um = (u0 + u1 + u2) / 3.0; - - return 0.5 * um * cross_product(x1 - x0, x2 - x0); + // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. + return (1 / 36.0) * ( + (4 * u[0] + 2 * u[1] + 2 * u[3] + u[2]) * cross_product(u[1] - u[0], u[3] - u[0]) + + (2 * u[0] + 4 * u[1] + u[3] + 2 * u[2]) * cross_product(u[1] - u[0], u[2] - u[1]) + + (2 * u[0] + u[1] + 4 * u[3] + 2 * u[2]) * cross_product(u[2] - u[3], u[3] - u[0]) + + (u[0] + 2 * u[1] + 2 * u[3] + 4 * u[2]) * cross_product(u[2] - u[3], u[2] - u[1]) + ); } - case MU1_MEAN_CURVATURE_MEASURE: + // N-gon: split into n triangles by polygon center and use triangle formula for each + else { - const typename GT::Vector_3 um = (u0 + u1 + u2) / 3.0; + typename GT::FT mu0 = 0; - return 0.5 * um * (cross_product(u2 - u1, x0) - + cross_product(u0 - u2, x1) - + cross_product(u1 - u0, x2)); - } - case MU2_GAUSSIAN_CURVATURE_MEASURE: + // getting center of points + typename GT::Vector_3 xc = + std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); + xc /= n; - return 0.5 * u0 * cross_product(u1, u2); + // getting unit average normal of points + typename GT::Vector_3 uc = + std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); + uc /= sqrt(uc * uc); - default: return 0; + // summing each triangle's measure after triangulation by barycenter split. + for (std::size_t i = 0; i < n; i++) + { + const typename GT::Vector_3 um = (u[i] + u[(i + 1) % n] + uc) / 3.0; + mu0 += 0.5 * um * cross_product(x[(i + 1) % n] - x[i], xc - x[i]); + } + return mu0; } } /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected measure of specific quad -* Note that the vertices 0 to 3 are ordered like this \n -* v0 _ v1 \n -* v2 |_| v3 +* computes the interpolated corrected mean curvature measure of a specific face. * * @tparam GT is the geometric traits class. * -* @param x0 is the position of vertex #0. -* @param x1 is the position of vertex #1. -* @param x2 is the position of vertex #2. -* @param x3 is the position of vertex #3. -* @param u0 is the normal vector of vertex #0. -* @param u1 is the normal vector of vertex #1. -* @param u2 is the normal vector of vertex #2. -* @param u3 is the normal vector of vertex #3. -* @param mu_i an enum for choosing between computing the area measure, -* the mean curvature measure, or the gaussian curvature measure. +* @param x is a vector of the vertex positions of the face. +* @param u is a vector of the vertex nomrals of the face. * * @return a scalar of type `GT::FT`. -* This is the value of the interpolated corrected measure of the given triangle. +* This is the value of the interpolated corrected mean curvature measure of the given face. * -* @see `interpolated_corrected_measure_face()` -* @see `interpolated_corrected_measure_triangle()` +* @see `interpolated_corrected_gaussian_curvature_measure_face()` +* @see `interpolated_corrected_area_measure_face()` +* @see `interpolated_corrected_measure_mesh()` */ template -typename GT::FT interpolated_corrected_measure_quad(const typename GT::Vector_3 x0, - const typename GT::Vector_3 x1, - const typename GT::Vector_3 x2, - const typename GT::Vector_3 x3, - const typename GT::Vector_3 u0, - const typename GT::Vector_3 u1, - const typename GT::Vector_3 u2, - const typename GT::Vector_3 u3, - const Curvature_measure_index mu_i) +typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::vector& x, + const std::vector& u) { - // x0 _ x1 - // x2 |_| x3 + std::size_t n = x.size(); + CGAL_precondition(u.size() == n); + CGAL_precondition(n >= 3); + typename GT::Construct_cross_product_vector_3 cross_product; - switch (mu_i) - { - case MU0_AREA_MEASURE: - return (1 / 36.0) * ( - (4 * u0 + 2 * u1 + 2 * u2 + u3) * cross_product(x1 - x0, x2 - x0) - + (2 * u0 + 4 * u1 + u2 + 2 * u3) * cross_product(x1 - x0, x3 - x1) - + (2 * u0 + u1 + 4 * u2 + 2 * u3) * cross_product(x3 - x2, x2 - x0) - + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * cross_product(x3 - x2, x3 - x1) - ); + // Triangle: use triangle formula + if (n == 3) + { + const typename GT::Vector_3 um = (u[0] + u[1] + u[2]) / 3.0; - case MU1_MEAN_CURVATURE_MEASURE: + return 0.5 * um * (cross_product(u[2] - u[1], x[0]) + + cross_product(u[0] - u[2], x[1]) + + cross_product(u[1] - u[0], x[2])); + } + // Quad: use bilinear interpolation formula + else if (n == 4) { - const typename GT::Vector_3 u03 = u3 - u0; - const typename GT::Vector_3 u12 = u2 - u1; - const typename GT::Vector_3 x0_cross = cross_product(u12, x0); - const typename GT::Vector_3 x1_cross = -cross_product(u03, x1); - const typename GT::Vector_3 x2_cross = cross_product(u03, x2); - const typename GT::Vector_3 x3_cross = -cross_product(u12, x3); + // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. + const typename GT::Vector_3 u02 = u[2] - u[0]; + const typename GT::Vector_3 u13 = u[3] - u[1]; + const typename GT::Vector_3 x0_cross = cross_product(u13, x[0]); + const typename GT::Vector_3 x1_cross = -cross_product(u02, x[1]); + const typename GT::Vector_3 x3_cross = cross_product(u02, x[3]); + const typename GT::Vector_3 x2_cross = -cross_product(u13, x[2]); return (1 / 12.0) * ( - u0 * (2 * x0_cross - cross_product((u2 + u3), x1) + cross_product((u1 + u3), x2) + x3_cross) - + u1 * (cross_product((u2 + u3), x0) + 2 * x1_cross + x2_cross - cross_product((u0 + u2), x3)) - + u2 * (-cross_product((u1 + u3), x0) + x1_cross + 2 * x2_cross + cross_product((u0 + u1), x3)) - + u3 * (x0_cross + cross_product((u0 + u2), x1) - cross_product((u0 + u1), x2) + 2 * x3_cross) + u[0] * (2 * x0_cross - cross_product((u[3] + u[2]), x[1]) + cross_product((u[1] + u[2]), x[3]) + x2_cross) + + u[1] * (cross_product((u[3] + u[2]), x[0]) + 2 * x1_cross + x3_cross - cross_product((u[0] + u[3]), x[2])) + + u[3] * (-cross_product((u[1] + u[2]), x[0]) + x1_cross + 2 * x3_cross + cross_product((u[0] + u[1]), x[2])) + + u[2] * (x0_cross + cross_product((u[0] + u[3]), x[1]) - cross_product((u[0] + u[1]), x[3]) + 2 * x2_cross) ); } - case MU2_GAUSSIAN_CURVATURE_MEASURE: + // N-gon: split into n triangles by polygon center and use triangle formula for each + else + { + typename GT::FT mu1 = 0; - return (1 / 36.0) * ( - (4 * u0 + 2 * u1 + 2 * u2 + u3) * cross_product(u1 - u0, u2 - u0) - + (2 * u0 + 4 * u1 + u2 + 2 * u3) * cross_product(u1 - u0, u3 - u1) - + (2 * u0 + u1 + 4 * u2 + 2 * u3) * cross_product(u3 - u2, u2 - u0) - + (u0 + 2 * u1 + 2 * u2 + 4 * u3) * cross_product(u3 - u2, u3 - u1) - ); + // getting center of points + typename GT::Vector_3 xc = + std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); + xc /= n; + + // getting unit average normal of points + typename GT::Vector_3 uc = + std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); + uc /= sqrt(uc * uc); - default: return 0; + // summing each triangle's measure after triangulation by barycenter split. + for (std::size_t i = 0; i < n; i++) + { + const typename GT::Vector_3 um = (u[i] + u[(i+1)%n] + uc) / 3.0; + mu1 += 0.5 * um * (cross_product(uc - u[(i + 1) % n], x[i]) + + cross_product(u[i] - uc, x[(i + 1) % n]) + + cross_product(u[(i + 1) % n] - u[i], xc)); + } + return mu1; } } - /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected measure of specific face. +* computes the interpolated corrected gaussian curvature measure of a specific face. * * @tparam GT is the geometric traits class. * * @param x is a vector of the vertex positions of the face. * @param u is a vector of the vertex nomrals of the face. -* @param mu_i an enum for choosing between computing the area measure, -* the mean curvature measure, or the gaussian curvature measure. * * @return a scalar of type `GT::FT`. -* This is the value of the interpolated corrected measure of the given face. +* This is the value of the interpolated corrected gaussian curvature measure of the given face. * -* @see `interpolated_corrected_measure_triangle()` -* @see `interpolated_corrected_measure_quad()` +* @see `interpolated_corrected_mean_curvature_measure_face()` +* @see `interpolated_corrected_area_measure_face()` * @see `interpolated_corrected_measure_mesh()` */ template -typename GT::FT interpolated_corrected_measure_face(const std::vector& x, - const std::vector& u, - const Curvature_measure_index mu_i) +typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std::vector& x, + const std::vector& u) { - std::size_t n = x.size(); - CGAL_precondition(u.size() == n); + std::size_t n = u.size(); CGAL_precondition(n >= 3); - // Triangle: use triangle formulas - if (n == 3) - return interpolated_corrected_measure_triangle(x[0], x[1], x[2], - u[0], u[1], u[2], mu_i); + typename GT::Construct_cross_product_vector_3 cross_product; - // Quad: use bilinear interpolation formulas + // Triangle: use triangle formula + if (n == 3) + { + return 0.5 * u[0] * cross_product(u[1], u[2]); + } + // Quad: use bilinear interpolation formula else if (n == 4) - // x[0] _ x[1] ---> x0 _ x1 (reason for changing order) - // x[3] |_| x[2] ---> x2 |_| x3 - return interpolated_corrected_measure_quad(x[0], x[1], x[3], x[2], - u[0], u[1], u[3], u[2], mu_i); - - // N-gon: split into n triangles by barycenter and use triangle formulas for each - else { - typename GT::FT mu0 = 0; - - // getting center of points - typename GT::Vector_3 xm = - std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); - xm /= n; + { + // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. + return (1 / 36.0) * ( + (4 * u[0] + 2 * u[1] + 2 * u[3] + u[2]) * cross_product(x[1] - x[0], x[3] - x[0]) + + (2 * u[0] + 4 * u[1] + u[3] + 2 * u[2]) * cross_product(x[1] - x[0], x[2] - x[1]) + + (2 * u[0] + u[1] + 4 * u[3] + 2 * u[2]) * cross_product(x[2] - x[3], x[3] - x[0]) + + (u[0] + 2 * u[1] + 2 * u[3] + 4 * u[2]) * cross_product(x[2] - x[3], x[2] - x[1]) + ); + } + // N-gon: split into n triangles by polygon center and use triangle formula for each + else + { + typename GT::FT mu2 = 0; // getting unit average normal of points - typename GT::Vector_3 um = + typename GT::Vector_3 uc = std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); - um /= sqrt(um * um); + uc /= sqrt(uc * uc); // summing each triangle's measure after triangulation by barycenter split. for (std::size_t i = 0; i < n; i++) { - mu0 += interpolated_corrected_measure_triangle(x[i], x[(i + 1) % n], xm, - u[i], u[(i + 1) % n], um, mu_i); + mu2 += 0.5 * u[i] * cross_product(u[(i + 1) % n], uc); } - return mu0; + return mu2; } } - /** * \ingroup PMP_corrected_curvatures_grp * @@ -322,6 +331,22 @@ template::value) compute_vertex_normals(pmesh, vnm, np); + std::function + &, const std::vector&)> + iccm_function; + switch (mu_i) + { + case MU0_AREA_MEASURE: + iccm_function = &interpolated_corrected_area_measure_face; + break; + case MU1_MEAN_CURVATURE_MEASURE: + iccm_function = &interpolated_corrected_mean_curvature_measure_face; + break; + case MU2_GAUSSIAN_CURVATURE_MEASURE: + iccm_function = &interpolated_corrected_gaussian_curvature_measure_face; + break; + } + for (face_descriptor f : faces(pmesh)) { std::vector x; @@ -334,7 +359,7 @@ template(x, u, mu_i)); + put(fmm, f, iccm_function(x, u)); } } From 870c27670b52912cad7946c8e93e2993c473bb0d Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 1 Aug 2022 08:15:18 +0200 Subject: [PATCH 033/161] minor fixes (typename, doc) --- .../Curvatures/interpolated_corrected_curvature_measures.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 250ea596c1c3..c9a70ee12ec8 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -295,7 +295,9 @@ typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std * * \cgalNamedParamsEnd * -* @see `interpolated_corrected_measure_face()` +* @see `interpolated_corrected_area_measure_face()` +* @see `interpolated_corrected_mean_curvature_measure_face()` +* @see `interpolated_corrected_gaussian_curvature_measure_face()` */ template @@ -332,7 +334,7 @@ template&, const std::vector&)> + &, const std::vector&)> iccm_function; switch (mu_i) { From 34b776d6c2e6ce0ec8cf54c6139478fb86b3c48e Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 1 Aug 2022 08:35:06 +0200 Subject: [PATCH 034/161] trailing spaces --- .../Polyhedron/Plugins/Display/Display_property_plugin.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index 91deb90680a8..66187583657e 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -721,7 +721,7 @@ private Q_SLOTS: maxEdgeLength = -1; setExpandingRadius(dock_widget->expandingRadiusSlider->value()); - + if(!item) return; SMesh& smesh = *item->face_graph(); @@ -835,9 +835,9 @@ private Q_SLOTS: ); } - + double outMin = 0, outMax = 5 * maxEdgeLength, base = 1.2; - + expandRadius = (pow(base, val) - 1) * outMax / (pow(base, sliderMax) - 1); dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expandRadius)); From 09cb2b1e6d1b78fe4e19031c40abb42fd12505cf Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 2 Aug 2022 23:12:23 +0200 Subject: [PATCH 035/161] some refactoring + implemented the anisotropic formulas function --- ...nterpolated_corrected_curvature_measures.h | 162 ++++++++++++++++-- 1 file changed, 143 insertions(+), 19 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index c9a70ee12ec8..1032413a9c5c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -47,7 +47,7 @@ enum Curvature_measure_index { /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected area measure of a specific face. +* computes the interpolated corrected area measure (mu0) of a specific face. * * @tparam GT is the geometric traits class. * @@ -62,8 +62,8 @@ enum Curvature_measure_index { * @see `interpolated_corrected_measure_mesh()` */ template -typename GT::FT interpolated_corrected_area_measure_face(const std::vector& x, - const std::vector& u) +typename GT::FT interpolated_corrected_area_measure_face(const std::vector& u, + const std::vector& x = {}) { std::size_t n = x.size(); CGAL_precondition(u.size() == n); @@ -81,6 +81,7 @@ typename GT::FT interpolated_corrected_area_measure_face(const std::vector( + std::vector {u[i], u[i + 1 % n], uc}, + std::vector {x[i], x[i + 1 % n], xc} + ); } return mu0; } @@ -116,7 +119,7 @@ typename GT::FT interpolated_corrected_area_measure_face(const std::vector -typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::vector& x, - const std::vector& u) +typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::vector& u, + const std::vector& x = {}) { std::size_t n = x.size(); CGAL_precondition(u.size() == n); @@ -153,6 +156,7 @@ typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::ve else if (n == 4) { // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. + // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 const typename GT::Vector_3 u02 = u[2] - u[0]; const typename GT::Vector_3 u13 = u[3] - u[1]; @@ -186,10 +190,10 @@ typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::ve // summing each triangle's measure after triangulation by barycenter split. for (std::size_t i = 0; i < n; i++) { - const typename GT::Vector_3 um = (u[i] + u[(i+1)%n] + uc) / 3.0; - mu1 += 0.5 * um * (cross_product(uc - u[(i + 1) % n], x[i]) - + cross_product(u[i] - uc, x[(i + 1) % n]) - + cross_product(u[(i + 1) % n] - u[i], xc)); + mu1 += interpolated_corrected_mean_curvature_measure_face( + std::vector {u[i], u[i + 1 % n], uc}, + std::vector {x[i], x[i + 1 % n], xc} + ); } return mu1; } @@ -198,7 +202,7 @@ typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::ve /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected gaussian curvature measure of a specific face. +* computes the interpolated corrected gaussian curvature measure (mu2) of a specific face. * * @tparam GT is the geometric traits class. * @@ -213,8 +217,8 @@ typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::ve * @see `interpolated_corrected_measure_mesh()` */ template -typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std::vector& x, - const std::vector& u) +typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std::vector& u, + const std::vector& x = {}) { std::size_t n = u.size(); CGAL_precondition(n >= 3); @@ -230,6 +234,7 @@ typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std else if (n == 4) { // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. + // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 return (1 / 36.0) * ( (4 * u[0] + 2 * u[1] + 2 * u[3] + u[2]) * cross_product(x[1] - x[0], x[3] - x[0]) + (2 * u[0] + 4 * u[1] + u[3] + 2 * u[2]) * cross_product(x[1] - x[0], x[2] - x[1]) @@ -250,12 +255,131 @@ typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std // summing each triangle's measure after triangulation by barycenter split. for (std::size_t i = 0; i < n; i++) { - mu2 += 0.5 * u[i] * cross_product(u[(i + 1) % n], uc); + mu2 += interpolated_corrected_gaussian_curvature_measure_face( + std::vector {u[i], u[i + 1 % n], uc} + ); } return mu2; } } +/** +* \ingroup PMP_corrected_curvatures_grp +* +* computes the interpolated corrected anisotropic measure (muXY) of a specific face. +* +* @tparam GT is the geometric traits class. +* +* @param u is a vector of the vertex nomrals of the face. +* @param x is a vector of the vertex positions of the face. +* +* @return an array of scalar values for each combination of the standard basis (3x3) of type `std::array`. +* These are the values of the interpolated corrected anisotropic measure of the given face. +* +* @see `interpolated_corrected_anisotropic_measure_mesh()` +*/ +template +std::array interpolated_corrected_anisotropic_measure_face(const std::vector& u, + const std::vector& x) +{ + std::size_t n = x.size(); + CGAL_precondition(u.size() == n); + CGAL_precondition(n >= 3); + + typename GT::Construct_cross_product_vector_3 cross_product; + std::array muXY; + + // Triangle: use triangle formula + if (n == 3) + { + const typename GT::Vector_3 u01 = u[1] - u[0]; + const typename GT::Vector_3 u02 = u[2] - u[0]; + const typename GT::Vector_3 x01 = x[1] - x[0]; + const typename GT::Vector_3 x02 = x[2] - x[0]; + const typename GT::Vector_3 um = (u[0] + u[1] + u[2]) / 3.0; + + for (std::size_t ix = 0; ix < 3; ix++) + { + const typename GT::Vector_3 X(0, 0, 0); + X[ix] = 1; + for (std::size_t iy = 0; iy < 3; iy++) + muXY[ix * 3 + iy] = 0.5 * um * (cross_product(u02[iy] * X, x01) - cross_product(u01[iy] * X, x02)); + } + } + // Quad: use bilinear interpolation formula + else if (n == 4) + { + // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. + // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 + for (std::size_t ix = 0; ix < 3; ix++) + { + const typename GT::Vector_3 X(0, 0, 0); + X[ix] = 1; + const typename GT::Vector_3 u0xX = cross_product(u[0], X); + const typename GT::Vector_3 u1xX = cross_product(u[1], X); + const typename GT::Vector_3 u2xX = cross_product(u[2], X); + const typename GT::Vector_3 u3xX = cross_product(u[3], X); + + for (std::size_t iy = 0; iy < 3; iy++) + muXY[ix * 3 + iy] = (1 / 72.0) * ( + + u[0][iy] * ( u0xX * ( - x[0] - 11 * x[1] + 13 * x[3] - x[2]) + + u1xX * ( -5 * x[0] - 7 * x[1] + 11 * x[3] + x[2]) + + u3xX * ( x[0] - 7 * x[1] + 11 * x[3] - 5 * x[2]) + + u2xX * ( - x[0] - 5 * x[1] + 7 * x[3] - x[2]) + ) + + u[1][iy] * ( u0xX * ( 13 * x[0] - x[1] - 7 * x[3] - 5 * x[2]) + + u1xX * ( 17 * x[0] - 5 * x[1] - 5 * x[3] - 7 * x[2]) + + u3xX * ( 5 * x[0] + x[1] + x[3] - 7 * x[2]) + + u2xX * ( 7 * x[0] - x[1] + 5 * x[3] - 11 * x[2]) + ) + + u[2][iy] * ( u0xX * (-11 * x[0] + 5 * x[1] - x[3] + 7 * x[2]) + + u1xX * (- 7 * x[0] + x[1] + x[3] + 5 * x[2]) + + u3xX * (- 7 * x[0] - 5 * x[1] - 5 * x[3] + 17 * x[2]) + + u2xX * (- 5 * x[0] - 7 * x[1] - x[3] + 13 * x[2]) + ) + + u[3][iy] * ( u0xX * (- x[0] + 7 * x[1] - 5 * x[3] - x[2]) + + u1xX * (- 5 * x[0] + 11 * x[1] - 7 * x[3] + x[2]) + + u3xX * ( x[0] + 11 * x[1] - 7 * x[3] - 5 * x[2]) + + u2xX * (- x[0] + 13 * x[1] - 11 * x[3] - x[2]) + ) + + ); + } + } + // N-gon: split into n triangles by polygon center and use triangle formula for each + else + { + typename GT::FT muXY = 0; + + // getting center of points + typename GT::Vector_3 xc = + std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); + xc /= n; + + // getting unit average normal of points + typename GT::Vector_3 uc = + std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); + uc /= sqrt(uc * uc); + + // summing each triangle's measure after triangulation by barycenter split. + for (std::size_t i = 0; i < n; i++) + { + std::array muXY_curr_triangle = + interpolated_corrected_anisotropic_measure_face( + std::vector {u[i], u[i + 1 % n], uc}, + std::vector {x[i], x[i + 1 % n], xc} + ); + + for (std::size_t ix = 0; ix < 3; ix++) + for (std::size_t iy = 0; iy < 3; iy++) + muXY[ix * 3 + iy] += muXY_curr_triangle[ix * 3 + iy]; + } + } + return muXY; +} + + /** * \ingroup PMP_corrected_curvatures_grp * @@ -333,6 +457,8 @@ template::value) compute_vertex_normals(pmesh, vnm, np); + typedef typename property_map_value::type measure; + std::function &, const std::vector&)> iccm_function; @@ -361,7 +487,7 @@ template Date: Wed, 3 Aug 2022 14:30:52 +0200 Subject: [PATCH 036/161] Principal Curvatures wip --- .../interpolated_corrected_curvatures.cpp | 2 +- ...nterpolated_corrected_curvature_measures.h | 266 +++++++++++++++++- 2 files changed, 260 insertions(+), 8 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp index 5359315f0fa0..4b8a9e6cfe7d 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -25,7 +25,7 @@ int main(int argc, char* argv[]) Mesh g1; const std::string filename = (argc>1) ? argv[1] : - CGAL::data_file_path("meshes/cylinder.off"); + CGAL::data_file_path("meshes/sphere_diff_faces.obj"); if(!CGAL::IO::read_polygon_mesh(filename, g1)) { diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 1032413a9c5c..618124e7fc74 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -13,7 +13,6 @@ #ifndef CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H #define CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H -#endif #include @@ -22,6 +21,7 @@ #include #include #include +#include #include #include @@ -82,7 +82,7 @@ typename GT::FT interpolated_corrected_area_measure_face(const std::vector`. +* @return an array of scalar values for each combination of the standard basis (3x3) of type `std::array`. * These are the values of the interpolated corrected anisotropic measure of the given face. * * @see `interpolated_corrected_anisotropic_measure_mesh()` @@ -321,7 +321,7 @@ std::array interpolated_corrected_anisotropic_measure_fa const typename GT::Vector_3 u3xX = cross_product(u[3], X); for (std::size_t iy = 0; iy < 3; iy++) - muXY[ix * 3 + iy] = (1 / 72.0) * ( + muXY[ix * 3 + iy] = (1.0 / 72.0) * ( u[0][iy] * ( u0xX * ( - x[0] - 11 * x[1] + 13 * x[3] - x[2]) + u1xX * ( -5 * x[0] - 7 * x[1] + 11 * x[3] + x[2]) @@ -433,6 +433,7 @@ template::type GT; + typedef dynamic_vertex_property_t Vector_map_tag; typedef typename boost::property_map::const_type Default_vector_map; typedef typename internal_np::Lookup_named_param_def::%face_descriptor` as key type and `std::array` as value type. +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* +* @param pmesh the polygon mesh +* @param fmm (face measure map) the property map used for storing the computed interpolated corrected measure +* @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 `ReadablePropertyMap` 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` must be available in `PolygonMesh`.} +* \cgalParamNEnd +* +* \cgalParamNBegin{vertex_normal_map} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamType{a class model of `ReadablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` +* as key type and `%Vector_3` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, vertex normals should be +* computed inside the function body.} +* \cgalParamNEnd +* +* \cgalNamedParamsEnd +* +* @see `interpolated_corrected_anisotropic_measure_face()` +* @see `interpolated_corrected_measure_mesh()` +*/ +template + void + interpolated_corrected_anisotropic_measure_mesh(const PolygonMesh& pmesh, + FaceMeasureMap fmm, + const NamedParameters& np = parameters::default_values()) +{ + + typedef typename GetGeomTraits::type GT; + + typedef dynamic_vertex_property_t Vector_map_tag; + typedef typename boost::property_map::const_type Default_vector_map; + typedef typename internal_np::Lookup_named_param_def::type VNM; + + using parameters::choose_parameter; + using parameters::get_parameter; + using parameters::is_default_parameter; + + typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + typename GetVertexPointMap::const_type + vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), + get_const_property_map(CGAL::vertex_point, pmesh)); + + VNM vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), + get(Vector_map_tag(), pmesh)); + + if (is_default_parameter::value) + compute_vertex_normals(pmesh, vnm, np); + + for (face_descriptor f : faces(pmesh)) + { + std::vector x; + std::vector u; + + for (vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) + { + typename GT::Point_3 p = get(vpm, v); + x.push_back(typename GT::Vector_3(p.x(), p.y(), p.z())); + u.push_back(get(vnm, v)); + } + + put(fmm, f, interpolated_corrected_anisotropic_measure_face(u, x)); + } +} + + // //template //typename GT::FT triangle_in_ball_ratio_1(const typename GT::Vector_3 x1, @@ -556,6 +649,7 @@ template::type GT; + typedef typename boost::graph_traits::face_descriptor face_descriptor; typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; @@ -613,6 +707,85 @@ template + void expand_interpolated_corrected_anisotropic_measure_vertex(const PolygonMesh& pmesh, + FaceMeasureMap fmm, + VertexCurvatureMap vcm, + const typename boost::graph_traits::vertex_descriptor v, + const NamedParameters& np = parameters::default_values()) +{ + using parameters::choose_parameter; + using parameters::get_parameter; + + typedef typename GetGeomTraits::type GT; + + typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + const typename GetGeomTraits::type::FT + r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0.01); + + typename GetVertexPointMap::const_type + vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), + get_const_property_map(CGAL::vertex_point, pmesh)); + + + std::queue bfs_q; + std::unordered_set bfs_v; + + typename GT::Point_3 vp = get(vpm, v); + typename GT::Vector_3 c = typename GT::Vector_3(vp.x(), vp.y(), vp.z()); + + Eigen::Matrix corrected_muXY = { + { 0.0, 0.0, 0.0}, + { 0.0, 0.0, 0.0}, + { 0.0, 0.0, 0.0} + }; + + for (face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + if (f != boost::graph_traits::null_face()) + { + bfs_q.push(f); + bfs_v.insert(f); + } + } + while (!bfs_q.empty()) { + face_descriptor fi = bfs_q.front(); + bfs_q.pop(); + + // looping over vertices in face to get point coordinates + std::vector x; + for (vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) + { + typename GT::Point_3 pi = get(vpm, vi); + x.push_back(typename GT::Vector_3(pi.x(), pi.y(), pi.z())); + } + + const typename GT::FT f_ratio = face_in_ball_ratio_2(x, r, c); + + if (f_ratio > 0.00000001) + { + std::array muXY_face = get(fmm, fi); + + for (std::size_t ix = 0; ix < 3; ix++) + for (std::size_t iy = 0; iy < 3; iy++) + corrected_muXY(ix, iy) += muXY_face[ix * 3 + iy]; + + for (face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) + { + if (bfs_v.find(fj) == bfs_v.end() && fj != boost::graph_traits::null_face()) + { + bfs_q.push(fj); + bfs_v.insert(fj); + } + } + } + } + + put(vcm, v, corrected_muXY); +} + template void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, @@ -620,8 +793,10 @@ template::type GT; + typedef typename boost::graph_traits::face_descriptor face_descriptor; typedef std::unordered_map FaceMeasureMap_tag; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef std::unordered_map VertexMeasureMap_tag; @@ -656,8 +831,10 @@ template::type GT; + typedef typename boost::graph_traits::face_descriptor face_descriptor; typedef std::unordered_map FaceMeasureMap_tag; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef std::unordered_map VertexMeasureMap_tag; @@ -685,5 +862,80 @@ template + void interpolated_corrected_principal_curvature(const PolygonMesh& pmesh, + VertexCurvatureMap vcm, + const NamedParameters& np = parameters::default_values()) +{ + typedef typename GetGeomTraits::type GT; + + typedef dynamic_vertex_property_t Vector_map_tag; + typedef typename boost::property_map::const_type Default_vector_map; + typedef typename internal_np::Lookup_named_param_def::type VNM; + + using parameters::choose_parameter; + using parameters::get_parameter; + using parameters::is_default_parameter; + + typename GetVertexPointMap::const_type + vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), + get_const_property_map(CGAL::vertex_point, pmesh)); + + VNM vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), + get(Vector_map_tag(), pmesh)); + + if (is_default_parameter::value) + compute_vertex_normals(pmesh, vnm, np); + + typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef std::unordered_map FaceScalarMeasureMap_tag; + // using std:: array to store FT values on the 9 combinations of the standard 3D basis + typedef std::unordered_map> FaceArrayMeasureMap_tag; + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef std::unordered_map VertexScalarMeasureMap_tag; + // using Eigen matrix to store & Process FT values on the 9 combinations of the standard 3D basis + typedef std::unordered_map> VertexMatrixMeasureMap_tag; + + + FaceScalarMeasureMap_tag mu0_init; + boost::associative_property_map + mu0_map(mu0_init); + + FaceArrayMeasureMap_tag muXY_init; + boost::associative_property_map + muXY_map(muXY_init); + + VertexScalarMeasureMap_tag mu0_expand_init; + boost::associative_property_map + mu0_expand_map(mu0_expand_init); + + VertexMatrixMeasureMap_tag muXY_expand_init; + boost::associative_property_map + muXY_expand_map(muXY_expand_init); + + interpolated_corrected_measure_mesh(pmesh, mu0_map, MU0_AREA_MEASURE); + interpolated_corrected_anisotropic_measure_mesh(pmesh, muXY_map); + + for (vertex_descriptor v : vertices(pmesh)) + { + expand_interpolated_corrected_measure_vertex(pmesh, mu0_map, mu0_expand_map, v, np); + expand_interpolated_corrected_anisotropic_measure_vertex(pmesh, muXY_map, muXY_expand_map, v, np); + + + + /*typename GT::FT v_mu0 = get(mu0_expand_map, v); + if (v_mu0 > 0.00000001) + put(vcm, v, get(mu2_expand_map, v) / v_mu0); + else + put(vcm, v, 0);*/ + } } -} + +} // namespace Polygon_mesh_processing +} // namespace CGAL + +#endif // CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H \ No newline at end of file From 24edaa24b5fc54bf85ac21b9c291610da6bbbf1c Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 3 Aug 2022 18:59:08 +0200 Subject: [PATCH 037/161] principal curvatures (yet to decompose MuXY Matrix) --- .../interpolated_corrected_curvatures.cpp | 23 ++++++++++-- ...nterpolated_corrected_curvature_measures.h | 36 +++++++++++++------ 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp index 4b8a9e6cfe7d..28e6e0ef81cb 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -18,6 +18,14 @@ typedef boost::graph_traits::face_descriptor face_descriptor; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; typedef std::unordered_map FaceMeasureMap_tag; typedef std::unordered_map vertexMeasureMap_tag; +typedef std::unordered_map, + Eigen::Vector + >> +vertexPrincipleCurvatureMap_tag; + int main(int argc, char* argv[]) @@ -38,16 +46,27 @@ int main(int argc, char* argv[]) boost::associative_property_map mean_curvature_map(mean_curvature_init), gaussian_curvature_map(gaussian_curvature_init); + vertexPrincipleCurvatureMap_tag principle_curvature_init; + boost::associative_property_map principle_curvature_map(principle_curvature_init); + PMP::interpolated_corrected_mean_curvature( g1, mean_curvature_map - ); + ); PMP::interpolated_corrected_gaussian_curvature( g1, gaussian_curvature_map ); + PMP::interpolated_corrected_principal_curvatures( + g1, + principle_curvature_map + ); for (vertex_descriptor v : vertices(g1)) + { + auto PC = get(principle_curvature_map, v); std::cout << v.idx() << ": HC = " << get(mean_curvature_map, v) - << ", GC = " << get(gaussian_curvature_map, v) << "\n"; + << ", GC = " << get(gaussian_curvature_map, v) << "\n" + << ", PC = [ " << std::get<0>(PC) << " , " << std::get<1>(PC) << " ]\n"; + } } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 618124e7fc74..83f63b74b54e 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -690,7 +690,7 @@ template(x, r, c); - if (f_ratio > 0.00000001) + if (f_ratio != 0.0) { corrected_mui += f_ratio * get(fmm, fi); for (face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) @@ -764,7 +764,7 @@ template(x, r, c); - if (f_ratio > 0.00000001) + if (f_ratio != 0.0) { std::array muXY_face = get(fmm, fi); @@ -817,7 +817,7 @@ template 0.00000001) + if (v_mu0 != 0.0) put(vcm, v, 0.5 * get(mu1_expand_map, v) / v_mu0); else put(vcm, v, 0); @@ -855,7 +855,7 @@ template 0.00000001) + if(v_mu0 != 0.0) put(vcm, v, get(mu2_expand_map, v) / v_mu0); else put(vcm, v, 0); @@ -864,7 +864,7 @@ template - void interpolated_corrected_principal_curvature(const PolygonMesh& pmesh, + void interpolated_corrected_principal_curvatures(const PolygonMesh& pmesh, VertexCurvatureMap vcm, const NamedParameters& np = parameters::default_values()) { @@ -925,13 +925,27 @@ template v_muXY = get(muXY_expand_map, v); + typename GT::Vector_3 u_GT = get(vnm, v); - - /*typename GT::FT v_mu0 = get(mu0_expand_map, v); - if (v_mu0 > 0.00000001) - put(vcm, v, get(mu2_expand_map, v) / v_mu0); - else - put(vcm, v, 0);*/ + Eigen::Vector u(u_GT.x(), u_GT.y(), u_GT.z()); + + const typename GT::FT K = 1000 * v_mu0; + + v_muXY = 0.5 * (v_muXY + v_muXY.transpose()) + K * u * u.transpose(); + + Eigen::Vector eig_vals; + Eigen::Matrix eig_vecs; + + + //(WIP) + EigenDecomposition< 3, typename GT::FT>::getEigenDecomposition(v_muXY, eig_vecs, eig_vals); + + put(vcm, v, std::make_tuple((v_mu0 != 0.0) ? -eig_vals[0] / v_mu0 : 0.0, + (v_mu0 != 0.0) ? -eig_vals[1] / v_mu0 : 0.0, + eig_vecs.column(0), + eig_vecs.column(1))); } } From 24551e2cbb2b38db5aabc2f31395c38651e3d635 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 4 Aug 2022 10:51:45 +0200 Subject: [PATCH 038/161] principal curvatures completed (but not properly tested) --- .../interpolated_corrected_curvatures.cpp | 2 +- ...nterpolated_corrected_curvature_measures.h | 60 ++++++++++++------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp index 28e6e0ef81cb..b804ae061cf4 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -33,7 +33,7 @@ int main(int argc, char* argv[]) Mesh g1; const std::string filename = (argc>1) ? argv[1] : - CGAL::data_file_path("meshes/sphere_diff_faces.obj"); + CGAL::data_file_path("meshes/small_bunny.obj"); if(!CGAL::IO::read_polygon_mesh(filename, g1)) { diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 83f63b74b54e..4b6a2f5149d9 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include @@ -287,7 +287,7 @@ std::array interpolated_corrected_anisotropic_measure_fa CGAL_precondition(n >= 3); typename GT::Construct_cross_product_vector_3 cross_product; - std::array muXY; + std::array muXY {0}; // Triangle: use triangle formula if (n == 3) @@ -300,8 +300,14 @@ std::array interpolated_corrected_anisotropic_measure_fa for (std::size_t ix = 0; ix < 3; ix++) { - const typename GT::Vector_3 X(0, 0, 0); - X[ix] = 1; + typename GT::Vector_3 X; + if (ix == 0) + X = typename GT::Vector_3(1, 0, 0); + if (ix == 1) + X = typename GT::Vector_3(0, 1, 0); + if (ix == 2) + X = typename GT::Vector_3(0, 0, 1); + for (std::size_t iy = 0; iy < 3; iy++) muXY[ix * 3 + iy] = 0.5 * um * (cross_product(u02[iy] * X, x01) - cross_product(u01[iy] * X, x02)); } @@ -313,8 +319,14 @@ std::array interpolated_corrected_anisotropic_measure_fa // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 for (std::size_t ix = 0; ix < 3; ix++) { - const typename GT::Vector_3 X(0, 0, 0); - X[ix] = 1; + typename GT::Vector_3 X; + if (ix == 0) + X = typename GT::Vector_3(1, 0, 0); + if (ix == 1) + X = typename GT::Vector_3(0, 1, 0); + if (ix == 2) + X = typename GT::Vector_3(0, 0, 1); + const typename GT::Vector_3 u0xX = cross_product(u[0], X); const typename GT::Vector_3 u1xX = cross_product(u[1], X); const typename GT::Vector_3 u2xX = cross_product(u[2], X); @@ -350,8 +362,6 @@ std::array interpolated_corrected_anisotropic_measure_fa // N-gon: split into n triangles by polygon center and use triangle formula for each else { - typename GT::FT muXY = 0; - // getting center of points typename GT::Vector_3 xc = std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); @@ -654,7 +664,7 @@ template::vertex_descriptor vertex_descriptor; const typename GetGeomTraits::type::FT - r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0.01); + r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0.1); typename GetVertexPointMap::const_type vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), @@ -737,11 +747,7 @@ template corrected_muXY = { - { 0.0, 0.0, 0.0}, - { 0.0, 0.0, 0.0}, - { 0.0, 0.0, 0.0} - }; + Eigen::Matrix corrected_muXY = Eigen::Matrix::Zero(); for (face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { if (f != boost::graph_traits::null_face()) @@ -935,17 +941,29 @@ template eig_vals; - Eigen::Matrix eig_vecs; + //(WIP) + Eigen::SelfAdjointEigenSolver> eigensolver; + eigensolver.computeDirect(v_muXY); + + if (eigensolver.info() != Eigen::Success) + { + put(vcm, v, std::make_tuple( + 0, + 0, + Eigen::Vector(.0,.0,.0), + Eigen::Vector(.0, .0, .0))); + continue; + } - //(WIP) - EigenDecomposition< 3, typename GT::FT>::getEigenDecomposition(v_muXY, eig_vecs, eig_vals); + Eigen::Vector eig_vals = eigensolver.eigenvalues(); + Eigen::Matrix eig_vecs = eigensolver.eigenvectors(); - put(vcm, v, std::make_tuple((v_mu0 != 0.0) ? -eig_vals[0] / v_mu0 : 0.0, + put(vcm, v, std::make_tuple( (v_mu0 != 0.0) ? -eig_vals[1] / v_mu0 : 0.0, - eig_vecs.column(0), - eig_vecs.column(1))); + (v_mu0 != 0.0) ? -eig_vals[0] / v_mu0 : 0.0, + eig_vecs.col(1), + eig_vecs.col(0))); } } From c4db1600fda9f9fa3a50bc005db84f5122107382 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 4 Aug 2022 11:01:09 +0200 Subject: [PATCH 039/161] trim whitespaces --- .../Curvatures/interpolated_corrected_curvature_measures.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 4b6a2f5149d9..4525628f5645 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include @@ -305,7 +305,7 @@ std::array interpolated_corrected_anisotropic_measure_fa X = typename GT::Vector_3(1, 0, 0); if (ix == 1) X = typename GT::Vector_3(0, 1, 0); - if (ix == 2) + if (ix == 2) X = typename GT::Vector_3(0, 0, 1); for (std::size_t iy = 0; iy < 3; iy++) @@ -945,7 +945,7 @@ template> eigensolver; eigensolver.computeDirect(v_muXY); - + if (eigensolver.info() != Eigen::Success) { put(vcm, v, std::make_tuple( From 85332fed6d9243a03bcb93edf8a08fc05e091799 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 5 Aug 2022 13:58:52 +0200 Subject: [PATCH 040/161] according to some of the review comments on the pull --- .../Polygon_mesh_processing/CMakeLists.txt | 9 ++-- ...nterpolated_corrected_curvature_measures.h | 51 +++++++++++-------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index d6f4e273516f..4783d957589a 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.1...3.23) project(Polygon_mesh_processing_Examples) # CGAL and its components -find_package(CGAL REQUIRED OPTIONAL_COMPONENTS Qt5) +find_package(CGAL REQUIRED) # Boost and its components find_package(Boost REQUIRED) @@ -102,12 +102,11 @@ create_single_source_cgal_program("orientation_pipeline_example.cpp") #create_single_source_cgal_program( "snapping_example.cpp") create_single_source_cgal_program("match_faces.cpp") create_single_source_cgal_program("cc_compatible_orientations.cpp") -create_single_source_cgal_program("interpolated_corrected_curvatures.cpp") -if(CGAL_Qt5_FOUND) +if(TARGET CGAL::Eigen3_support) - #link it with the required CGAL libraries - target_link_libraries(interpolated_corrected_curvatures PUBLIC CGAL::CGAL_Basic_viewer) + create_single_source_cgal_program("interpolated_corrected_curvatures.cpp") + target_link_libraries(interpolated_corrected_curvatures PUBLIC CGAL::Eigen3_support) endif() diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 4525628f5645..33cbe05e576d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -17,10 +17,10 @@ #include #include -#include #include #include #include +#include #include #include @@ -83,10 +83,10 @@ typename GT::FT interpolated_corrected_area_measure_face(const std::vector( - std::vector {u[i], u[i + 1 % n], uc}, - std::vector {x[i], x[i + 1 % n], xc} + std::vector {u[i], u[(i + 1) % n], uc}, + std::vector {x[i], x[(i + 1) % n], xc} ); } return mu0; @@ -191,8 +191,8 @@ typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::ve for (std::size_t i = 0; i < n; i++) { mu1 += interpolated_corrected_mean_curvature_measure_face( - std::vector {u[i], u[i + 1 % n], uc}, - std::vector {x[i], x[i + 1 % n], xc} + std::vector {u[i], u[(i + 1) % n], uc}, + std::vector {x[i], x[(i + 1) % n], xc} ); } return mu1; @@ -236,10 +236,10 @@ typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 return (1.0 / 36.0) * ( - (4 * u[0] + 2 * u[1] + 2 * u[3] + u[2]) * cross_product(x[1] - x[0], x[3] - x[0]) - + (2 * u[0] + 4 * u[1] + u[3] + 2 * u[2]) * cross_product(x[1] - x[0], x[2] - x[1]) - + (2 * u[0] + u[1] + 4 * u[3] + 2 * u[2]) * cross_product(x[2] - x[3], x[3] - x[0]) - + (u[0] + 2 * u[1] + 2 * u[3] + 4 * u[2]) * cross_product(x[2] - x[3], x[2] - x[1]) + (4 * u[0] + 2 * u[1] + 2 * u[3] + u[2]) * cross_product(u[1] - u[0], u[3] - u[0]) + + (2 * u[0] + 4 * u[1] + u[3] + 2 * u[2]) * cross_product(u[1] - u[0], u[2] - u[1]) + + (2 * u[0] + u[1] + 4 * u[3] + 2 * u[2]) * cross_product(u[2] - u[3], u[3] - u[0]) + + (u[0] + 2 * u[1] + 2 * u[3] + 4 * u[2]) * cross_product(u[2] - u[3], u[2] - u[1]) ); } // N-gon: split into n triangles by polygon center and use triangle formula for each @@ -256,7 +256,7 @@ typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std for (std::size_t i = 0; i < n; i++) { mu2 += interpolated_corrected_gaussian_curvature_measure_face( - std::vector {u[i], u[i + 1 % n], uc} + std::vector {u[i], u[(i + 1) % n], uc} ); } return mu2; @@ -377,8 +377,8 @@ std::array interpolated_corrected_anisotropic_measure_fa { std::array muXY_curr_triangle = interpolated_corrected_anisotropic_measure_face( - std::vector {u[i], u[i + 1 % n], uc}, - std::vector {x[i], x[i + 1 % n], xc} + std::vector {u[i], u[(i + 1) % n], uc}, + std::vector {x[i], x[(i + 1) % n], xc} ); for (std::size_t ix = 0; ix < 3; ix++) @@ -486,10 +486,11 @@ template x; + std::vector u; + for (face_descriptor f : faces(pmesh)) { - std::vector x; - std::vector u; for (vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) { @@ -499,6 +500,8 @@ template::value) compute_vertex_normals(pmesh, vnm, np); + std::vector x; + std::vector u; + for (face_descriptor f : faces(pmesh)) { - std::vector x; - std::vector u; for (vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) { @@ -590,6 +594,9 @@ template(u, x)); + x.clear(); + u.clear(); + } } @@ -634,8 +641,8 @@ typename GT::FT face_in_ball_ratio_2(const std::vector& x for (const typename GT::Vector_3 xi : x) { const typename GT::FT d_sq = (xi - c).squared_length(); - d_max = std::max(d_sq, d_max); - d_min = std::min(d_sq, d_min); + d_max = (std::max)(d_sq, d_max); + d_min = (std::min)(d_sq, d_min); } if (d_max <= r * r) return 1.0; From 7473a3e2dc702b1529e40f4931b95faa81430591 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 8 Aug 2022 05:05:28 +0200 Subject: [PATCH 041/161] Optimizing the expanding functions to compute intersections only once per curvature --- ...nterpolated_corrected_curvature_measures.h | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 33cbe05e576d..289878b9a9bf 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -657,8 +657,10 @@ typename GT::FT face_in_ball_ratio_2(const std::vector& x template void expand_interpolated_corrected_measure_vertex(const PolygonMesh& pmesh, - FaceMeasureMap fmm, - VertexCurvatureMap vcm, + FaceMeasureMap area_fmm, + FaceMeasureMap curvature_fmm, + VertexCurvatureMap area_vcm, + VertexCurvatureMap curvature_vcm, const typename boost::graph_traits::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { @@ -684,6 +686,7 @@ template::null_face()) @@ -720,15 +724,18 @@ template void expand_interpolated_corrected_anisotropic_measure_vertex(const PolygonMesh& pmesh, - FaceMeasureMap fmm, - VertexCurvatureMap vcm, + AreaFaceMeasureMap area_fmm, + AnisotropicFaceMeasureMap aniso_fmm, + AreaVertexCurvatureMap area_vcm, + AnisotropicVertexCurvatureMap aniso_vcm, const typename boost::graph_traits::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { @@ -754,6 +761,7 @@ template corrected_muXY = Eigen::Matrix::Zero(); for (face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { @@ -779,7 +787,9 @@ template muXY_face = get(fmm, fi); + corrected_mu0 += f_ratio * get(area_fmm, fi); + + std::array muXY_face = get(aniso_fmm, fi); for (std::size_t ix = 0; ix < 3; ix++) for (std::size_t iy = 0; iy < 3; iy++) @@ -795,8 +805,8 @@ template v_muXY = get(muXY_expand_map, v); From 36d0fd4e5b3c5d5bfaed59ef3f9f8528aed486f1 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 8 Aug 2022 05:41:34 +0200 Subject: [PATCH 042/161] using internal property (SurfMesh) in example --- .../interpolated_corrected_curvatures.cpp | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp index b804ae061cf4..c8b972aca658 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -9,24 +9,12 @@ #include #include - namespace PMP = CGAL::Polygon_mesh_processing; typedef CGAL::Exact_predicates_inexact_constructions_kernel EpicKernel; typedef CGAL::Surface_mesh Mesh; typedef boost::graph_traits::face_descriptor face_descriptor; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; -typedef std::unordered_map FaceMeasureMap_tag; -typedef std::unordered_map vertexMeasureMap_tag; -typedef std::unordered_map, - Eigen::Vector - >> -vertexPrincipleCurvatureMap_tag; - - int main(int argc, char* argv[]) { @@ -41,13 +29,31 @@ int main(int argc, char* argv[]) return EXIT_FAILURE; } + bool created = false; + + Mesh::Property_map mean_curvature_map, gaussian_curvature_map; + boost::tie(mean_curvature_map, created) = g1.add_property_map("v:mean_curvature_map", 0); + assert(created); + + boost::tie(gaussian_curvature_map, created) = g1.add_property_map("v:gaussian_curvature_map", 0); + assert(created); - vertexMeasureMap_tag mean_curvature_init, gaussian_curvature_init; - boost::associative_property_map - mean_curvature_map(mean_curvature_init), gaussian_curvature_map(gaussian_curvature_init); + Mesh::Property_map, + Eigen::Vector + >> principle_curvature_map; - vertexPrincipleCurvatureMap_tag principle_curvature_init; - boost::associative_property_map principle_curvature_map(principle_curvature_init); + boost::tie(principle_curvature_map, created) = g1.add_property_map, + Eigen::Vector + >>("v:principle_curvature_map", { 0, 0, + Eigen::Vector::Zero(), + Eigen::Vector::Zero() }); + assert(created); PMP::interpolated_corrected_mean_curvature( g1, @@ -64,9 +70,9 @@ int main(int argc, char* argv[]) for (vertex_descriptor v : vertices(g1)) { - auto PC = get(principle_curvature_map, v); - std::cout << v.idx() << ": HC = " << get(mean_curvature_map, v) - << ", GC = " << get(gaussian_curvature_map, v) << "\n" + auto PC = principle_curvature_map[v]; + std::cout << v.idx() << ": HC = " << mean_curvature_map[v] + << ", GC = " << gaussian_curvature_map[v] << "\n" << ", PC = [ " << std::get<0>(PC) << " , " << std::get<1>(PC) << " ]\n"; } } From a74e05a389dabe01022fcb4d2871bb027996f5fb Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 8 Aug 2022 06:16:05 +0200 Subject: [PATCH 043/161] documentation: fixes & improvements --- ...nterpolated_corrected_curvature_measures.h | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 289878b9a9bf..3987f473c145 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -47,15 +47,13 @@ enum Curvature_measure_index { /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected area measure (mu0) of a specific face. +* computes the interpolated corrected area measure \f$ \mu_0 \f$ of a specific face. * -* @tparam GT is the geometric traits class. +* @tparam GT geometric traits class model of `Kernel`. * * @param x is a vector of the vertex positions of the face. -* @param u is a vector of the vertex nomrals of the face. +* @param u is a vector of the vertex normals of the face. * -* @return a scalar of type `GT::FT`. -* This is the value of the interpolated corrected area measure of the given face. * * @see `interpolated_corrected_mean_curvature_measure_face()` * @see `interpolated_corrected_gaussian_curvature_measure_face()` @@ -119,16 +117,13 @@ typename GT::FT interpolated_corrected_area_measure_face(const std::vector`. +* @return an array of scalar values for each combination of the standard basis (3x3) * These are the values of the interpolated corrected anisotropic measure of the given face. * * @see `interpolated_corrected_anisotropic_measure_mesh()` @@ -395,7 +387,7 @@ std::array interpolated_corrected_anisotropic_measure_fa * * computes the interpolated corrected curvature measure on each face of the mesh * -* @tparam PolygonMesh a model of `FaceGraph` +* @tparam PolygonMesh a model of `FaceListGraph` * @tparam FaceMeasureMap a a model of `WritablePropertyMap` with * `boost::graph_traits::%face_descriptor` as key type and `GT::FT` as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" @@ -423,8 +415,8 @@ std::array interpolated_corrected_anisotropic_measure_fa * `boost::graph_traits::%vertex_descriptor` * as key type and `%Vector_3` as value type} * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} -* \cgalParamExtra{If this parameter is omitted, vertex normals should be -* computed inside the function body.} +* \cgalParamExtra{If this parameter is omitted, vertex normals will be +* computed using compute_vertex_normals()} * \cgalParamNEnd * * \cgalNamedParamsEnd @@ -511,7 +503,7 @@ template::%face_descriptor` as key type and `std::array` as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" @@ -537,8 +529,8 @@ template::%vertex_descriptor` * as key type and `%Vector_3` as value type} * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} -* \cgalParamExtra{If this parameter is omitted, vertex normals should be -* computed inside the function body.} +* \cgalParamExtra{If this parameter is omitted, vertex normals will be +* computed using compute_vertex_normals()} * \cgalParamNEnd * * \cgalNamedParamsEnd @@ -956,7 +948,6 @@ template> eigensolver; eigensolver.computeDirect(v_muXY); From 86ac0fcb74f26336d492859b1d650fb28b5251d0 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 8 Aug 2022 07:04:37 +0200 Subject: [PATCH 044/161] Documenting Expansion functions --- ...nterpolated_corrected_curvature_measures.h | 158 +++++++++++++++--- 1 file changed, 134 insertions(+), 24 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 3987f473c145..dbe95c28f459 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -47,7 +47,7 @@ enum Curvature_measure_index { /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected area measure \f$ \mu_0 \f$ of a specific face. +* Computes the interpolated corrected area measure \f$ \mu_0 \f$ of a specific face. * * @tparam GT geometric traits class model of `Kernel`. * @@ -117,7 +117,7 @@ typename GT::FT interpolated_corrected_area_measure_face(const std::vector interpolated_corrected_anisotropic_measure_fa /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected curvature measure on each face of the mesh +* Computes the interpolated corrected curvature measure on each face of the mesh * * @tparam PolygonMesh a model of `FaceListGraph` -* @tparam FaceMeasureMap a a model of `WritablePropertyMap` with +* @tparam FaceMeasureMap a model of `WritablePropertyMap` with * `boost::graph_traits::%face_descriptor` as key type and `GT::FT` as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * @@ -399,6 +399,7 @@ std::array interpolated_corrected_anisotropic_measure_fa * @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 `ReadablePropertyMap` with @@ -501,10 +502,10 @@ template::%face_descriptor` as key type and `std::array` as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * @@ -513,6 +514,7 @@ template -typename GT::FT face_in_ball_ratio_2(const std::vector& x, - const typename GT::FT r, - const typename GT::Vector_3 c) +typename GT::FT face_in_ball_ratio(const std::vector& x, + const typename GT::FT r, + const typename GT::Vector_3 c) { std::size_t n = x.size(); @@ -646,13 +662,59 @@ typename GT::FT face_in_ball_ratio_2(const std::vector& x return (r - d_min) / (d_max - d_min); } -template::%face_descriptor` as key type and `GT::FT` as value type. +* @tparam VertexMeasureMap a model of `WritablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` as key type and `GT::FT` as value type. +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* +* @param pmesh the polygon mesh +* @param area_fmm (area face measure map) the property map storing the already computed area measure on each face. +* @param curvature_fmm (curvature face measure map) the property map storing the already computed curvature measure on each face. +* This curvature measure can be either the Mean Curvature or the Gaussian Curvature. +* @param area_vmm (area vertex measure map) the property map provided to store the expanded area measure on each vertex. +* @param curvature_vmm (curvature vertex measure map) the property map provided to store the expanded curvature measure on each vertex. +* This curvature measure can be either the Mean Curvature or the Gaussian Curvature. +* @param v (vertex) the vertex to expand the area and curvature measure around. +* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below +* +* \cgalNamedParamsBegin +* +* \cgalParamNBegin{ball_radius} +* \cgalParamDescription{the radius of the ball around the vertex `v` to expand the area and curvature measure} +* \cgalParamType{`GT::FT`} +* \cgalParamDefault{`0.01`} +* \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 `%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 +* +* \cgalNamedParamsEnd +* +* @see `expand_interpolated_corrected_anisotropic_measure_vertex()` +* @see `face_in_ball_ratio()` +*/ +template void expand_interpolated_corrected_measure_vertex(const PolygonMesh& pmesh, FaceMeasureMap area_fmm, FaceMeasureMap curvature_fmm, - VertexCurvatureMap area_vcm, - VertexCurvatureMap curvature_vcm, + VertexMeasureMap area_vmm, + VertexMeasureMap curvature_vmm, const typename boost::graph_traits::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { @@ -700,7 +762,7 @@ template(x, r, c); + const typename GT::FT f_ratio = face_in_ball_ratio(x, r, c); if (f_ratio != 0.0) { @@ -716,18 +778,66 @@ template::%face_descriptor` as key type and `GT::FT` as value type. +* @tparam AnisotropicFaceMeasureMap a model of `WritablePropertyMap` with +* `boost::graph_traits::%face_descriptor` as key type and `std::array` as value type. +* @tparam AreaVertexMeasureMap a model of `WritablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` as key type and `GT::FT` as value type. +* @tparam AnisotropicVertexMeasureMap a model of `WritablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` as key type and `std::array` as value type. +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* +* @param pmesh the polygon mesh +* @param area_fmm (area face measure map) the property map storing the already computed area measure on each face. +* @param aniso_fmm (anisotropic face measure map) the property map storing the already computed anisotropic curvature measure on each face. +* @param area_vmm (area vertex measure map) the property map provided to store the expanded area measure on each vertex. +* @param aniso_vmm (anisotropic vertex measure map) the property map provided to store the expanded anisotropic curvature measure on each vertex. +* @param v (vertex) the vertex to expand the area and anisotropic curvature measure around. +* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below +* +* \cgalNamedParamsBegin +* +* \cgalParamNBegin{ball_radius} +* \cgalParamDescription{the radius of the ball around the vertex `v` to expand the area and curvature measure} +* \cgalParamType{`GT::FT`} +* \cgalParamDefault{`0.01`} +* \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 `%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 +* +* \cgalNamedParamsEnd +* +* @see `expand_interpolated_corrected_measure_vertex()` +* @see `face_in_ball_ratio()` +*/ template void expand_interpolated_corrected_anisotropic_measure_vertex(const PolygonMesh& pmesh, AreaFaceMeasureMap area_fmm, AnisotropicFaceMeasureMap aniso_fmm, - AreaVertexCurvatureMap area_vcm, - AnisotropicVertexCurvatureMap aniso_vcm, + AreaVertexMeasureMap area_vmm, + AnisotropicVertexMeasureMap aniso_vmm, const typename boost::graph_traits::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { @@ -775,7 +885,7 @@ template(x, r, c); + const typename GT::FT f_ratio = face_in_ball_ratio(x, r, c); if (f_ratio != 0.0) { @@ -797,8 +907,8 @@ template Date: Mon, 8 Aug 2022 07:13:14 +0200 Subject: [PATCH 045/161] minor documentation fixes --- .../Polygon_mesh_processing/PackageDescription.txt | 13 ++++++++++--- .../interpolated_corrected_curvature_measures.h | 8 ++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index d0cab92d6a89..5c9e39811d46 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -202,9 +202,16 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage. \cgalCRPSection{Corrected Curvature Functions} - `CGAL::Polygon_mesh_processing::interpolated_corrected_measure_mesh()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_measure_face()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_measure_triangle()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_measure_quad()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_anisotropic_measure_mesh()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_area_measure_face()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_measure_face()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_measure_face()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_anisotropic_measure_face()` +- `CGAL::Polygon_mesh_processing::expand_interpolated_corrected_measure_vertex()` +- `CGAL::Polygon_mesh_processing::expand_interpolated_corrected_anisotropic_measure_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures()` \cgalCRPSection{Normal Computation Functions} - `CGAL::Polygon_mesh_processing::compute_face_normal()` diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index dbe95c28f459..00b17269d6f1 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -785,7 +785,7 @@ template Date: Mon, 8 Aug 2022 07:41:02 +0200 Subject: [PATCH 046/161] Documenting new functions + minor doc fixes --- ...nterpolated_corrected_curvature_measures.h | 120 +++++++++++++++--- 1 file changed, 103 insertions(+), 17 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 00b17269d6f1..dc7744a402eb 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -35,7 +35,7 @@ namespace Polygon_mesh_processing { /*! * \ingroup PMP_corrected_curvatures_grp * Enumeration type used to specify which measure of a given face - * is computed for the interpolated corrected curvature functions + * is computed for the interpolated corrected curvature functions. */ // enum enum Curvature_measure_index { @@ -385,17 +385,17 @@ std::array interpolated_corrected_anisotropic_measure_fa /** * \ingroup PMP_corrected_curvatures_grp * -* Computes the interpolated corrected curvature measure on each face of the mesh +* Computes the interpolated corrected curvature measure on each face of the mesh. * -* @tparam PolygonMesh a model of `FaceListGraph` +* @tparam PolygonMesh a model of `FaceListGraph`. * @tparam FaceMeasureMap a model of `WritablePropertyMap` with * `boost::graph_traits::%face_descriptor` as key type and `GT::FT` as value type. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * -* @param pmesh the polygon mesh -* @param fmm (face measure map) the property map used for storing the computed interpolated corrected measure +* @param pmesh the polygon mesh. +* @param fmm (face measure map) the property map used for storing the computed interpolated corrected measure. * @param mu_i an enum for choosing between computing -* the area measure, the mean curvature measure or the gaussian curvature measure +* the area measure, the mean curvature measure or the gaussian curvature measure. * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin @@ -504,13 +504,13 @@ template::%face_descriptor` as key type and `std::array` as value type. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * -* @param pmesh the polygon mesh -* @param fmm (face measure map) the property map used for storing the computed interpolated corrected measure +* @param pmesh the polygon mesh. +* @param fmm (face measure map) the property map used for storing the computed interpolated corrected measure. * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin @@ -622,7 +622,7 @@ template& x, * Expands given face area and curvature (mean or gaussian) measures around a vertex `v`. * Expansion is based on the inclusion ratio of each face in a ball of radius `r` around the vertex `v`. * -* @tparam PolygonMesh a model of `FaceListGraph` +* @tparam PolygonMesh a model of `FaceListGraph`. * @tparam FaceMeasureMap a model of `WritablePropertyMap` with * `boost::graph_traits::%face_descriptor` as key type and `GT::FT` as value type. * @tparam VertexMeasureMap a model of `WritablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` as key type and `GT::FT` as value type. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * -* @param pmesh the polygon mesh +* @param pmesh the polygon mesh. * @param area_fmm (area face measure map) the property map storing the already computed area measure on each face. * @param curvature_fmm (curvature face measure map) the property map storing the already computed curvature measure on each face. * This curvature measure can be either the Mean Curvature or the Gaussian Curvature. @@ -797,9 +797,9 @@ template::%vertex_descriptor` as key type and `GT::FT` as value type. * @tparam AnisotropicVertexMeasureMap a model of `WritablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` as key type and `std::array` as value type. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * -* @param pmesh the polygon mesh +* @param pmesh the polygon mesh. * @param area_fmm (area face measure map) the property map storing the already computed area measure on each face. * @param aniso_fmm (anisotropic face measure map) the property map storing the already computed anisotropic measure on each face. * @param area_vmm (area vertex measure map) the property map provided to store the expanded area measure on each vertex. @@ -911,6 +911,27 @@ template::%vertex_descriptor` as key type and `GT::FT` as value type. +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". +* +* @param pmesh the polygon mesh. +* @param vcm the vertex property map in which the computed mean curvatures are stored. +* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters". +* +* @see `interpolated_corrected_gaussian_curvature()` +* @see `interpolated_corrected_principal_curvatures()` +* @see `interpolated_corrected_measure_mesh()` +* @see `expand_interpolated_corrected_measure_vertex()` +*/ + template void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, @@ -949,6 +970,26 @@ template::%vertex_descriptor` as key type and `GT::FT` as value type. +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". +* +* @param pmesh the polygon mesh. +* @param vcm the vertex property map in which the computed gaussian curvatures are stored. +* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters". +* +* @see `interpolated_corrected_mean_curvature()` +* @see `interpolated_corrected_principal_curvatures()` +* @see `interpolated_corrected_measure_mesh()` +* @see `expand_interpolated_corrected_measure_vertex()` +*/ template void interpolated_corrected_gaussian_curvature(const PolygonMesh& pmesh, @@ -986,6 +1027,51 @@ template::%vertex_descriptor` as key type and +* `std::tuple, Eigen::Vector>` as value type. +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". +* +* @param pmesh the polygon mesh. +* @param vcm the vertex property map in which the computed principal curvatures are stored. +* @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 `ReadablePropertyMap` 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` must be available in `PolygonMesh`.} +* \cgalParamNEnd +* +* \cgalParamNBegin{vertex_normal_map} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamType{a class model of `ReadablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` +* as key type and `%Vector_3` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, vertex normals will be +* computed using compute_vertex_normals()} +* \cgalParamNEnd +* +* \cgalNamedParamsEnd +* +* @see `interpolated_corrected_mean_curvature()` +* @see `interpolated_corrected_gaussian_curvatures()` +* @see `interpolated_corrected_anisotropic_measure_mesh()` +* @see `expand_interpolated_corrected_anisotropic_measure_vertex()` +*/ template void interpolated_corrected_principal_curvatures(const PolygonMesh& pmesh, From 5840eaf9556370c1c4cdc0cb2b76fc1d81380876 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 9 Aug 2022 17:10:15 +0200 Subject: [PATCH 047/161] Update interpolated_corrected_curvature_measures.h --- .../Curvatures/interpolated_corrected_curvature_measures.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index dc7744a402eb..f16413159cab 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -1068,7 +1068,7 @@ template Date: Sat, 27 Aug 2022 18:01:59 +0200 Subject: [PATCH 048/161] fixing eigen vector definitions so that it works with other compilers --- .../interpolated_corrected_curvature_measures.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index f16413159cab..6ddfe5feb1a0 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -1138,7 +1138,7 @@ template v_muXY = get(muXY_expand_map, v); typename GT::Vector_3 u_GT = get(vnm, v); - Eigen::Vector u(u_GT.x(), u_GT.y(), u_GT.z()); + Eigen::Matrix u(u_GT.x(), u_GT.y(), u_GT.z()); const typename GT::FT K = 1000 * v_mu0; @@ -1153,12 +1153,12 @@ template(.0,.0,.0), - Eigen::Vector(.0, .0, .0))); + Eigen::Matrix(.0,.0,.0), + Eigen::Matrix(.0, .0, .0))); continue; } - Eigen::Vector eig_vals = eigensolver.eigenvalues(); + Eigen::Matrix eig_vals = eigensolver.eigenvalues(); Eigen::Matrix eig_vecs = eigensolver.eigenvectors(); put(vcm, v, std::make_tuple( From 48262af4247eb8060e3bda3ccfcec6164512007b Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 27 Aug 2022 19:46:33 +0200 Subject: [PATCH 049/161] fixing eigenvector definitions --- .../interpolated_corrected_curvatures.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp index c8b972aca658..3a36aaac5c88 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -41,18 +41,18 @@ int main(int argc, char* argv[]) Mesh::Property_map, - Eigen::Vector + Eigen::Matrix, + Eigen::Matrix >> principle_curvature_map; boost::tie(principle_curvature_map, created) = g1.add_property_map, - Eigen::Vector + Eigen::Matrix, + Eigen::Matrix >>("v:principle_curvature_map", { 0, 0, - Eigen::Vector::Zero(), - Eigen::Vector::Zero() }); + Eigen::Matrix::Zero(), + Eigen::Matrix::Zero() }); assert(created); PMP::interpolated_corrected_mean_curvature( From 13b056c9d404c9eef9b3cdb630777ecb98db5dcf Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 17 Sep 2022 23:53:18 +0200 Subject: [PATCH 050/161] minor fixes + handled zero expansion radius --- ...nterpolated_corrected_curvature_measures.h | 72 +++++++++++++++---- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 6ddfe5feb1a0..7cd2c67ab060 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -28,10 +29,31 @@ #include #include +#define EXPANDING_RADIUS_EPSILON 1e-6 + namespace CGAL { namespace Polygon_mesh_processing { +namespace internal { + +template +typename GT::FT average_edge_length(const PolygonMesh& pmesh) +{ + const std::size_t n = edges(pmesh).size(); + if (n == 0) + return 0; + + GT::FT avg_edge_length = 0; + for (auto e : edges(pmesh)) + avg_edge_length += edge_length(e, pmesh); + + avg_edge_length /= n; + return avg_edge_length; +} + +} + /*! * \ingroup PMP_corrected_curvatures_grp * Enumeration type used to specify which measure of a given face @@ -63,7 +85,7 @@ template typename GT::FT interpolated_corrected_area_measure_face(const std::vector& u, const std::vector& x = {}) { - std::size_t n = x.size(); + const std::size_t n = x.size(); CGAL_precondition(u.size() == n); CGAL_precondition(n >= 3); @@ -132,7 +154,7 @@ template typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::vector& u, const std::vector& x = {}) { - std::size_t n = x.size(); + const std::size_t n = x.size(); CGAL_precondition(u.size() == n); CGAL_precondition(n >= 3); @@ -212,7 +234,7 @@ template typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std::vector& u, const std::vector& x = {}) { - std::size_t n = u.size(); + const std::size_t n = u.size(); CGAL_precondition(n >= 3); typename GT::Construct_cross_product_vector_3 cross_product; @@ -274,7 +296,7 @@ template std::array interpolated_corrected_anisotropic_measure_face(const std::vector& u, const std::vector& x) { - std::size_t n = x.size(); + const std::size_t n = x.size(); CGAL_precondition(u.size() == n); CGAL_precondition(n >= 3); @@ -636,7 +658,7 @@ typename GT::FT face_in_ball_ratio(const std::vector& x, const typename GT::FT r, const typename GT::Vector_3 c) { - std::size_t n = x.size(); + const std::size_t n = x.size(); // getting center of points typename GT::Vector_3 xm = @@ -726,8 +748,8 @@ template::face_descriptor face_descriptor; typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - const typename GetGeomTraits::type::FT - r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0.1); + const typename GT::FT + r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); typename GetVertexPointMap::const_type vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), @@ -849,8 +871,8 @@ template::face_descriptor face_descriptor; typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - const typename GetGeomTraits::type::FT - r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0.01); + const typename GT::FT + r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); typename GetVertexPointMap::const_type vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), @@ -938,6 +960,9 @@ template::type GT; typedef typename boost::graph_traits::face_descriptor face_descriptor; @@ -946,6 +971,12 @@ template::vertex_descriptor vertex_descriptor; typedef std::unordered_map VertexMeasureMap_tag; + typename GT::FT + r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); + + if (r == 0) + r = internal::average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; + FaceMeasureMap_tag mu0_init, mu1_init; boost::associative_property_map mu0_map(mu0_init), mu1_map(mu1_init); @@ -959,7 +990,7 @@ template::type GT; typedef typename boost::graph_traits::face_descriptor face_descriptor; @@ -1004,6 +1038,12 @@ template::vertex_descriptor vertex_descriptor; typedef std::unordered_map VertexMeasureMap_tag; + typename GT::FT + r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); + + if (r == 0) + r = internal::average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; + FaceMeasureMap_tag mu0_init, mu2_init; boost::associative_property_map mu0_map(mu0_init), mu2_map(mu2_init); @@ -1017,7 +1057,7 @@ template(pmesh) * EXPANDING_RADIUS_EPSILON; + if (is_default_parameter::value) compute_vertex_normals(pmesh, vnm, np); + + typedef typename boost::graph_traits::face_descriptor face_descriptor; typedef std::unordered_map FaceScalarMeasureMap_tag; // using std:: array to store FT values on the 9 combinations of the standard 3D basis @@ -1132,7 +1180,7 @@ template v_muXY = get(muXY_expand_map, v); From 4f1a5dd194822e3e763e5c47bd38c1c4151e575c Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 20 Sep 2022 04:18:11 +0200 Subject: [PATCH 051/161] minor demo updates --- .../Plugins/Display/Display_property_plugin.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index 66187583657e..e38211ecea85 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -1496,10 +1496,10 @@ private Q_SLOTS: void displayMapLegend(const std::vector& values) { // Create a legend_ and display it - const std::size_t size = (std::min)(color_map.size(), (std::size_t)256); + const std::size_t size = (std::min)(color_map.size(), (std::size_t)2048); const int text_height = 20; const int height = text_height*static_cast(size) + text_height; - const int width = 140; + const int width = 170; const int cell_width = width/3; const int top_margin = 15; const int left_margin = 5; @@ -1525,8 +1525,8 @@ private Q_SLOTS: tick_height, color); QRect text_rect(left_margin + cell_width+10, drawing_height - top_margin - j, - 50, text_height); - painter.drawText(text_rect, Qt::AlignCenter, tr("%1").arg(values[i], 0, 'f', 3, QLatin1Char(' '))); + 100, text_height); + painter.drawText(text_rect, Qt::AlignCenter, tr("%1").arg(values[i], 0, 'f', 7, QLatin1Char(' '))); } if(color_map.size() > size){ QRect text_rect(left_margin + cell_width+10, 0, @@ -1545,7 +1545,7 @@ private Q_SLOTS: { // Create a legend_ and display it const int height = 256; - const int width = 140; + const int width = 170; const int cell_width = width/3; const int top_margin = 5; const int left_margin = 5; @@ -1589,11 +1589,11 @@ private Q_SLOTS: painter.setPen(Qt::blue); QRect min_text_rect(left_margin + cell_width+10,drawing_height - top_margin, 100, text_height); - painter.drawText(min_text_rect, Qt::AlignCenter, tr("%1").arg(min_value, 0, 'f', 1)); + painter.drawText(min_text_rect, Qt::AlignCenter, tr("%1").arg(min_value, 0, 'f', 7)); QRect max_text_rect(left_margin + cell_width+10, drawing_height - top_margin - 200, 100, text_height); - painter.drawText(max_text_rect, Qt::AlignCenter, tr("%1").arg(max_value, 0, 'f', 1)); + painter.drawText(max_text_rect, Qt::AlignCenter, tr("%1").arg(max_value, 0, 'f', 7)); dock_widget->legendLabel->setPixmap(legend_); } From 3d3b2c30fd2aedd43140d9c6a9b60b87a9340fba Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 20 Sep 2022 11:18:13 +0200 Subject: [PATCH 052/161] testing init --- ...nterpolated_corrected_curvature_measures.h | 20 ++- .../Polygon_mesh_processing/CMakeLists.txt | 1 + ...est_interopolated_corrected_curvatures.cpp | 147 ++++++++++++++++++ .../Display/Display_property_plugin.cpp | 34 ++-- .../PMP/Random_perturbation_plugin.cpp | 2 +- 5 files changed, 180 insertions(+), 24 deletions(-) create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 7cd2c67ab060..aa05f8aebe47 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -483,8 +483,6 @@ template::value) compute_vertex_normals(pmesh, vnm, np); - typedef typename property_map_value::type measure; - std::function &, const std::vector&)> iccm_function; @@ -985,12 +983,12 @@ template mu0_expand_map(mu0_expand_init), mu1_expand_map(mu1_expand_init); - interpolated_corrected_measure_mesh(pmesh, mu0_map, MU0_AREA_MEASURE); - interpolated_corrected_measure_mesh(pmesh, mu1_map, MU1_MEAN_CURVATURE_MEASURE); + interpolated_corrected_measure_mesh(pmesh, mu0_map, MU0_AREA_MEASURE, np); + interpolated_corrected_measure_mesh(pmesh, mu1_map, MU1_MEAN_CURVATURE_MEASURE, np); for (vertex_descriptor v : vertices(pmesh)) { - expand_interpolated_corrected_measure_vertex(pmesh, mu0_map, mu1_map, mu0_expand_map, mu1_expand_map, v, CGAL::parameters::ball_radius(r)); + expand_interpolated_corrected_measure_vertex(pmesh, mu0_map, mu1_map, mu0_expand_map, mu1_expand_map, v, np.ball_radius(r)); typename GT::FT v_mu0 = get(mu0_expand_map, v); @@ -1052,12 +1050,12 @@ template mu0_expand_map(mu0_expand_init), mu2_expand_map(mu2_expand_init); - interpolated_corrected_measure_mesh(pmesh, mu0_map, MU0_AREA_MEASURE); - interpolated_corrected_measure_mesh(pmesh, mu2_map, MU2_GAUSSIAN_CURVATURE_MEASURE); + interpolated_corrected_measure_mesh(pmesh, mu0_map, MU0_AREA_MEASURE, np); + interpolated_corrected_measure_mesh(pmesh, mu2_map, MU2_GAUSSIAN_CURVATURE_MEASURE, np); for (vertex_descriptor v : vertices(pmesh)) { - expand_interpolated_corrected_measure_vertex(pmesh, mu0_map, mu2_map, mu0_expand_map, mu2_expand_map, v, CGAL::parameters::ball_radius(r)); + expand_interpolated_corrected_measure_vertex(pmesh, mu0_map, mu2_map, mu0_expand_map, mu2_expand_map, v, np.ball_radius(r)); typename GT::FT v_mu0 = get(mu0_expand_map, v); if(v_mu0 != 0.0) @@ -1175,12 +1173,12 @@ template muXY_expand_map(muXY_expand_init); - interpolated_corrected_measure_mesh(pmesh, mu0_map, MU0_AREA_MEASURE); - interpolated_corrected_anisotropic_measure_mesh(pmesh, muXY_map); + interpolated_corrected_measure_mesh(pmesh, mu0_map, MU0_AREA_MEASURE, np); + interpolated_corrected_anisotropic_measure_mesh(pmesh, muXY_map, np); for (vertex_descriptor v : vertices(pmesh)) { - expand_interpolated_corrected_anisotropic_measure_vertex(pmesh, mu0_map, muXY_map, mu0_expand_map, muXY_expand_map, v, CGAL::parameters::ball_radius(r)); + expand_interpolated_corrected_anisotropic_measure_vertex(pmesh, mu0_map, muXY_map, mu0_expand_map, muXY_expand_map, v, np.ball_radius(r)); typename GT::FT v_mu0 = get(mu0_expand_map, v); Eigen::Matrix v_muXY = get(muXY_expand_map, v); diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index 909dd292063a..808e6d3a05ef 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -69,6 +69,7 @@ create_single_source_cgal_program("self_intersection_polyhedron_test.cpp") create_single_source_cgal_program("self_intersection_surface_mesh_test.cpp") create_single_source_cgal_program("pmp_do_intersect_test.cpp") create_single_source_cgal_program("test_is_polygon_soup_a_polygon_mesh.cpp") +create_single_source_cgal_program("test_interopolated_corrected_curvatures.cpp") create_single_source_cgal_program("test_stitching.cpp") create_single_source_cgal_program("remeshing_test.cpp") create_single_source_cgal_program("remeshing_with_isolated_constraints_test.cpp" ) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp new file mode 100644 index 000000000000..9c2408139a3f --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace PMP = CGAL::Polygon_mesh_processing; + +typedef CGAL::Exact_predicates_inexact_constructions_kernel EpicKernel; +typedef CGAL::Surface_mesh SMesh; +typedef boost::graph_traits::face_descriptor face_descriptor; +typedef boost::graph_traits::edge_descriptor edge_descriptor; +typedef boost::graph_traits::vertex_descriptor vertex_descriptor; + +void test(std::string mesh_path, EpicKernel::FT rel_expansion_radius, EpicKernel::FT rel_noise_magnitude) { + SMesh pmesh; + const std::string filename = CGAL::data_file_path(mesh_path); + + if (!CGAL::IO::read_polygon_mesh(filename, pmesh)) + { + std::cerr << "Invalid input file." << std::endl; + } + + bool created = false; + + SMesh::Property_map mean_curvature_map, gaussian_curvature_map; + boost::tie(mean_curvature_map, created) = pmesh.add_property_map("v:mean_curvature_map", 0); + assert(created); + + boost::tie(gaussian_curvature_map, created) = pmesh.add_property_map("v:gaussian_curvature_map", 0); + assert(created); + + // getting the max and min edge lengthes + const auto edge_range = CGAL::edges(pmesh); + + const auto edge_length_comparator = [&, pmesh](auto l, auto r) { + return PMP::edge_length(l, pmesh) < PMP::edge_length(r, pmesh); + }; + + const edge_descriptor longest_edge = *std::max_element(edge_range.begin(), edge_range.end(), edge_length_comparator); + const EpicKernel::FT max_edge_length = PMP::edge_length(longest_edge, pmesh); + + const edge_descriptor shortest_edge = *std::min_element(edge_range.begin(), edge_range.end(), edge_length_comparator); + const EpicKernel::FT min_edge_length = PMP::edge_length(shortest_edge, pmesh); + + + if (rel_noise_magnitude > 0) + { + if (!CGAL::is_triangle_mesh(pmesh)) + return; + + SMesh::Property_map vnm; + boost::tie(vnm, created) = pmesh.add_property_map("v:vnm", { 0 , 0 , 0 }); + assert(created); + + CGAL::Polygon_mesh_processing::compute_vertex_normals(pmesh, vnm); + + PMP::random_perturbation(pmesh, rel_noise_magnitude * min_edge_length, CGAL::parameters::random_seed(0)); + PMP::interpolated_corrected_mean_curvature( + pmesh, + mean_curvature_map, + CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length).vertex_normal_map(vnm) + ); + PMP::interpolated_corrected_gaussian_curvature( + pmesh, + gaussian_curvature_map, + CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length).vertex_normal_map(vnm) + ); + } + else { + PMP::interpolated_corrected_mean_curvature( + pmesh, + mean_curvature_map, + CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) + ); + PMP::interpolated_corrected_gaussian_curvature( + pmesh, + gaussian_curvature_map, + CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) + ); + + } + + + + //PMP::interpolated_corrected_mean_curvature( + // pmesh, + // mean_curvature_map, + // CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) + //); + //PMP::interpolated_corrected_gaussian_curvature( + // pmesh, + // gaussian_curvature_map, + // CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) + //); + + + const EpicKernel::FT max_mean_curvature = *std::max_element(mean_curvature_map.begin(), mean_curvature_map.end()); + const EpicKernel::FT min_mean_curvature = *std::min_element(mean_curvature_map.begin(), mean_curvature_map.end()); + const EpicKernel::FT max_gaussian_curvature = *std::max_element(gaussian_curvature_map.begin(), gaussian_curvature_map.end()); + const EpicKernel::FT min_gaussian_curvature = *std::min_element(gaussian_curvature_map.begin(), gaussian_curvature_map.end()); + + std::cout << "# " << mesh_path << ":\n" + << "expansion radius ratio to max length / expansion radius = " << rel_expansion_radius << " / " << rel_expansion_radius * max_edge_length << ",\n" + << "max perturbation ratio to minlength / max perturbation = " << rel_noise_magnitude << " / " << rel_noise_magnitude * min_edge_length << "\n" + << "mean curvature: min = " << min_mean_curvature << " <-> " << max_mean_curvature << " = max" << "\n" + << "gaussian curvature: min = " << min_gaussian_curvature << " <-> " << max_gaussian_curvature << " = max" << "\n\n\n"; + + +} + +int main() +{ + const std::vector mesh_pathes_to_test = { + "meshes/icc_test/Cube with fillet Quads.obj", + "meshes/icc_test/Lantern Quads.obj", + "meshes/icc_test/Lantern Tris.obj", + "meshes/icc_test/Sphere Ngons + Quads + Tris.obj", + "meshes/icc_test/Sphere Quads + Tris 100352.obj", + "meshes/icc_test/Sphere Quads + Tris.obj", + "meshes/icc_test/Sphere Quads Remesh.obj", + "meshes/icc_test/Sphere Quads.obj", + "meshes/icc_test/Sphere Tris Ico.obj", + "meshes/icc_test/Sphere Tris Oct.obj", + "meshes/icc_test/Sphere Tris Tet.obj" + }; + + const std::vector rel_expansion_radii = { 0, 0.1, 0.5, 1 }; + const std::vector rel_noise_magnitudes = { 0, 0.5, 0.9 }; + + for (auto mesh_path : mesh_pathes_to_test) { + for (EpicKernel::FT rel_expansion_radius : rel_expansion_radii) + for (EpicKernel::FT rel_noise_magnitude : rel_noise_magnitudes) + { + test(mesh_path, rel_expansion_radius, rel_noise_magnitude); + } + + std::cout << "_________________________________________________________________________________\n\n"; + } + +} diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index e38211ecea85..cda67cbd9700 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -807,8 +807,8 @@ private Q_SLOTS: if (edge_range.begin() == edge_range.end()) { - expandRadius = 0; - dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expandRadius)); + expand_radius = 0; + dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expand_radius)); return; } @@ -824,8 +824,8 @@ private Q_SLOTS: // if edge_reference is not derefrenceble if (edge_reference == edge_range.end()) { - expandRadius = 0; - dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expandRadius)); + expand_radius = 0; + dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expand_radius)); return; } @@ -838,8 +838,8 @@ private Q_SLOTS: double outMin = 0, outMax = 5 * maxEdgeLength, base = 1.2; - expandRadius = (pow(base, val) - 1) * outMax / (pow(base, sliderMax) - 1); - dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expandRadius)); + expand_radius = (pow(base, val) - 1) * outMax / (pow(base, sliderMax) - 1); + dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expand_radius)); } @@ -849,6 +849,9 @@ private Q_SLOTS: "v:interpolated_corrected_mean_curvature": "v:interpolated_corrected_gaussian_curvature"; SMesh& smesh = *item->face_graph(); + const auto vnm = smesh.property_map("v:normal_before_perturbation").first; + const bool vnm_exists = smesh.property_map("v:normal_before_perturbation").second; + //compute once and store the value per vertex bool non_init; SMesh::Property_map mu_i_map; @@ -856,11 +859,18 @@ private Q_SLOTS: smesh.add_property_map(tied_string, 0); if (non_init) { - if (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE) - PMP::interpolated_corrected_mean_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expandRadius)); - else - PMP::interpolated_corrected_gaussian_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expandRadius)); - + if (vnm_exists) { + if (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE) + PMP::interpolated_corrected_mean_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius).vertex_normal_map(vnm)); + else + PMP::interpolated_corrected_gaussian_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius).vertex_normal_map(vnm)); + } + else { + if (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE) + PMP::interpolated_corrected_mean_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius)); + else + PMP::interpolated_corrected_gaussian_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius)); + } double res_min = ARBITRARY_DBL_MAX, res_max = -ARBITRARY_DBL_MAX; SMesh::Vertex_index min_index, max_index; @@ -1626,7 +1636,7 @@ private Q_SLOTS: std::unordered_map is_source; - double expandRadius; + double expand_radius; double maxEdgeLength = -1; double minBox; double maxBox; diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Random_perturbation_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Random_perturbation_plugin.cpp index 9afb452ab13c..004768739cd5 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Random_perturbation_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Random_perturbation_plugin.cpp @@ -106,7 +106,7 @@ public Q_SLOTS: if(ui.keep_normal_checkbox->isChecked()) { SMesh::Property_map vnormals = - pmesh.add_property_map("v:normal").first; + pmesh.add_property_map("v:normal_before_perturbation").first; CGAL::Polygon_mesh_processing::compute_vertex_normals(pmesh,vnormals); } if(ui.deterministic_checkbox->isChecked()) From c7a6651c6335f7911c8fb04274abc39030af64bb Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 20 Sep 2022 17:19:43 +0200 Subject: [PATCH 053/161] GroundTruthtests --- .../test_interopolated_corrected_curvatures.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp index 9c2408139a3f..0caf0149de28 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp @@ -118,17 +118,18 @@ void test(std::string mesh_path, EpicKernel::FT rel_expansion_radius, EpicKernel int main() { const std::vector mesh_pathes_to_test = { - "meshes/icc_test/Cube with fillet Quads.obj", - "meshes/icc_test/Lantern Quads.obj", - "meshes/icc_test/Lantern Tris.obj", - "meshes/icc_test/Sphere Ngons + Quads + Tris.obj", - "meshes/icc_test/Sphere Quads + Tris 100352.obj", "meshes/icc_test/Sphere Quads + Tris.obj", - "meshes/icc_test/Sphere Quads Remesh.obj", - "meshes/icc_test/Sphere Quads.obj", + "meshes/icc_test/Sphere Quads + Tris 100352.obj", "meshes/icc_test/Sphere Tris Ico.obj", + "meshes/icc_test/Sphere Tris Tet.obj", "meshes/icc_test/Sphere Tris Oct.obj", - "meshes/icc_test/Sphere Tris Tet.obj" + "meshes/icc_test/Sphere Quads.obj", + "meshes/icc_test/Sphere Quads Remesh.obj", + "meshes/icc_test/Sphere Ngons + Quads + Tris.obj", + "meshes/icc_test/Cube with fillet Quads.obj", + "meshes/cylinder.off", + "meshes/icc_test/Lantern Tris.obj", + "meshes/icc_test/Lantern Quads.obj" }; const std::vector rel_expansion_radii = { 0, 0.1, 0.5, 1 }; From 75c7f83c6848a55b4e15f10ca813df2e0d232659 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 20 Sep 2022 17:21:19 +0200 Subject: [PATCH 054/161] whitespaces --- .../interpolated_corrected_curvature_measures.h | 2 +- .../test_interopolated_corrected_curvatures.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index aa05f8aebe47..c1c1bcd52877 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -49,7 +49,7 @@ typename GT::FT average_edge_length(const PolygonMesh& pmesh) avg_edge_length += edge_length(e, pmesh); avg_edge_length /= n; - return avg_edge_length; + return avg_edge_length; } } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp index 0caf0149de28..e7b473141562 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp @@ -58,7 +58,7 @@ void test(std::string mesh_path, EpicKernel::FT rel_expansion_radius, EpicKernel SMesh::Property_map vnm; boost::tie(vnm, created) = pmesh.add_property_map("v:vnm", { 0 , 0 , 0 }); assert(created); - + CGAL::Polygon_mesh_processing::compute_vertex_normals(pmesh, vnm); PMP::random_perturbation(pmesh, rel_noise_magnitude * min_edge_length, CGAL::parameters::random_seed(0)); @@ -87,7 +87,7 @@ void test(std::string mesh_path, EpicKernel::FT rel_expansion_radius, EpicKernel } - + //PMP::interpolated_corrected_mean_curvature( // pmesh, @@ -106,8 +106,8 @@ void test(std::string mesh_path, EpicKernel::FT rel_expansion_radius, EpicKernel const EpicKernel::FT max_gaussian_curvature = *std::max_element(gaussian_curvature_map.begin(), gaussian_curvature_map.end()); const EpicKernel::FT min_gaussian_curvature = *std::min_element(gaussian_curvature_map.begin(), gaussian_curvature_map.end()); - std::cout << "# " << mesh_path << ":\n" - << "expansion radius ratio to max length / expansion radius = " << rel_expansion_radius << " / " << rel_expansion_radius * max_edge_length << ",\n" + std::cout << "# " << mesh_path << ":\n" + << "expansion radius ratio to max length / expansion radius = " << rel_expansion_radius << " / " << rel_expansion_radius * max_edge_length << ",\n" << "max perturbation ratio to minlength / max perturbation = " << rel_noise_magnitude << " / " << rel_noise_magnitude * min_edge_length << "\n" << "mean curvature: min = " << min_mean_curvature << " <-> " << max_mean_curvature << " = max" << "\n" << "gaussian curvature: min = " << min_gaussian_curvature << " <-> " << max_gaussian_curvature << " = max" << "\n\n\n"; From e0c596d29e8c10724300d40fecbf6015385f6da4 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 25 Sep 2022 22:03:50 +0200 Subject: [PATCH 055/161] principal curvatures and directions visualization --- .../interpolated_corrected_curvatures.cpp | 12 +- ...nterpolated_corrected_curvature_measures.h | 59 +++--- .../Polygon_mesh_processing/compute_normal.h | 12 +- .../Polyhedron/Plugins/PMP/CMakeLists.txt | 14 ++ ..._corrected_principal_curvatures_plugin.cpp | 186 ++++++++++++++++++ 5 files changed, 240 insertions(+), 43 deletions(-) create mode 100644 Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp index 3a36aaac5c88..c011d57db41e 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp @@ -41,18 +41,18 @@ int main(int argc, char* argv[]) Mesh::Property_map, - Eigen::Matrix + EpicKernel::Vector_3, + EpicKernel::Vector_3 >> principle_curvature_map; boost::tie(principle_curvature_map, created) = g1.add_property_map, - Eigen::Matrix + EpicKernel::Vector_3, + EpicKernel::Vector_3 >>("v:principle_curvature_map", { 0, 0, - Eigen::Matrix::Zero(), - Eigen::Matrix::Zero() }); + EpicKernel::Vector_3 (0,0,0), + EpicKernel::Vector_3 (0,0,0)}); assert(created); PMP::interpolated_corrected_mean_curvature( diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index c1c1bcd52877..e2e1292aedb4 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -754,8 +754,8 @@ template bfs_q; - std::unordered_set bfs_v; + std::queue bfs_queue; + std::unordered_set bfs_visited; typename GT::Point_3 vp = get(vpm, v); typename GT::Vector_3 c = typename GT::Vector_3(vp.x(), vp.y(), vp.z()); @@ -766,13 +766,13 @@ template::null_face()) { - bfs_q.push(f); - bfs_v.insert(f); + bfs_queue.push(f); + bfs_visited.insert(f); } } - while (!bfs_q.empty()) { - face_descriptor fi = bfs_q.front(); - bfs_q.pop(); + while (!bfs_queue.empty()) { + face_descriptor fi = bfs_queue.front(); + bfs_queue.pop(); // looping over vertices in face to get point coordinates std::vector x; @@ -790,10 +790,10 @@ template::null_face()) + if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) { - bfs_q.push(fj); - bfs_v.insert(fj); + bfs_queue.push(fj); + bfs_visited.insert(fj); } } } @@ -877,8 +877,8 @@ template bfs_q; - std::unordered_set bfs_v; + std::queue bfs_queue; + std::unordered_set bfs_visited; typename GT::Point_3 vp = get(vpm, v); typename GT::Vector_3 c = typename GT::Vector_3(vp.x(), vp.y(), vp.z()); @@ -889,13 +889,13 @@ template::null_face()) { - bfs_q.push(f); - bfs_v.insert(f); + bfs_queue.push(f); + bfs_visited.insert(f); } } - while (!bfs_q.empty()) { - face_descriptor fi = bfs_q.front(); - bfs_q.pop(); + while (!bfs_queue.empty()) { + face_descriptor fi = bfs_queue.front(); + bfs_queue.pop(); // looping over vertices in face to get point coordinates std::vector x; @@ -915,14 +915,14 @@ template::null_face()) + if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) { - bfs_q.push(fj); - bfs_v.insert(fj); + bfs_queue.push(fj); + bfs_visited.insert(fj); } } } @@ -990,7 +990,6 @@ template v_muXY = get(muXY_expand_map, v); + typename GT::Vector_3 u_GT = get(vnm, v); Eigen::Matrix u(u_GT.x(), u_GT.y(), u_GT.z()); @@ -1199,19 +1199,22 @@ template(.0,.0,.0), - Eigen::Matrix(.0, .0, .0))); + typename GT::Vector_3(0, 0, 0), + typename GT::Vector_3(0, 0, 0))); continue; } - Eigen::Matrix eig_vals = eigensolver.eigenvalues(); - Eigen::Matrix eig_vecs = eigensolver.eigenvectors(); + const Eigen::Matrix eig_vals = eigensolver.eigenvalues(); + const Eigen::Matrix eig_vecs = eigensolver.eigenvectors(); + + const typename GT::Vector_3 min_eig_vec(eig_vecs(0, 1), eig_vecs(1, 1), eig_vecs(2, 1)); + const typename GT::Vector_3 max_eig_vec(eig_vecs(0, 0), eig_vecs(1, 0), eig_vecs(2, 0)); put(vcm, v, std::make_tuple( (v_mu0 != 0.0) ? -eig_vals[1] / v_mu0 : 0.0, (v_mu0 != 0.0) ? -eig_vals[0] / v_mu0 : 0.0, - eig_vecs.col(1), - eig_vecs.col(0))); + min_eig_vec, + max_eig_vec)); } } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h index 2cd837e7bf84..8dfb16af1bdd 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h @@ -717,15 +717,9 @@ compute_vertex_normal(typename boost::graph_traits::vertex_descript } #endif - Vector_3 normal = internal::compute_vertex_normal_most_visible_min_circle(v, face_normals, pmesh, traits); - if(traits.equal_3_object()(normal, CGAL::NULL_VECTOR)) // can't always find a most visible normal - { -#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP - std::cout << "Failed to find most visible normal, use weighted sum of normals" << std::endl; -#endif - normal = internal::compute_vertex_normal_as_sum_of_weighted_normals( - v, internal::SIN_WEIGHT, face_normals, vpmap, pmesh, traits); - } + // Change for debugging (comparing with DGtal) + Vector_3 normal = internal::compute_vertex_normal_as_sum_of_weighted_normals( + v, internal::NO_WEIGHT, face_normals, vpmap, pmesh, traits); if(!traits.equal_3_object()(normal, CGAL::NULL_VECTOR)) internal::normalize(normal, traits); diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt b/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt index 53cfdc9b825a..7d2177b03a11 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt @@ -13,6 +13,20 @@ else() ) endif() +if(TARGET CGAL::Eigen3_support) + + polyhedron_demo_plugin(interpolated_corrected_principal_curvatures_plugin Interpolated_corrected_principal_curvatures_plugin) + target_link_libraries( + interpolated_corrected_principal_curvatures_plugin PUBLIC scene_surface_mesh_item scene_polylines_item + CGAL::Eigen3_support) + +else() + message( + STATUS + "NOTICE: Eigen 3.1 (or greater) was not found. Interpolated corrected principal curvatures plugin will not be available." + ) +endif() + polyhedron_demo_plugin(extrude_plugin Extrude_plugin KEYWORDS PMP) target_link_libraries(extrude_plugin PUBLIC scene_surface_mesh_item scene_selection_item) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp new file mode 100644 index 000000000000..69c2b4fe2dd3 --- /dev/null +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp @@ -0,0 +1,186 @@ +#include +#include +#include +#include "Scene_surface_mesh_item.h" +#include "Scene_polylines_item.h" + +#include + +#include "Scene.h" +#include +#include + +#include +#include + +using namespace CGAL::Three; +class Polyhedron_demo_interpolated_corrected_principal_curvatures_plugin : + public QObject, + public Polyhedron_demo_plugin_interface +{ + Q_OBJECT + Q_INTERFACES(CGAL::Three::Polyhedron_demo_plugin_interface) + Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.PluginInterface/1.0") + +public: + + QList actions() const { + return _actions; + } + void init(QMainWindow* mw, + Scene_interface* scene_interface, + Messages_interface*) + { + scene = scene_interface; + QAction *actionEstimateCurvature = new QAction(tr("Interpolated Corrected Principal Curvatures"), mw); + connect(actionEstimateCurvature, SIGNAL(triggered()), this, SLOT(on_actionEstimateCurvature_triggered())); + _actions <(scene->item(scene->mainSelectionIndex())); + } + +public Q_SLOTS: + void on_actionEstimateCurvature_triggered(); +private : + Scene_interface *scene; + QList _actions; +}; // end Polyhedron_demo_interpolated_corrected_principal_curvatures_plugin + + +void compute(SMesh* sMesh, + Scene_polylines_item* max_curv, + Scene_polylines_item* min_curv, + Scene_polylines_item* max_negative_curv, + Scene_polylines_item* min_negative_curv) +{ + namespace PMP = CGAL::Polygon_mesh_processing; + typedef CGAL::Exact_predicates_inexact_constructions_kernel EpicKernel; + typedef EpicKernel::Point_3 Point; + typedef EpicKernel::Point_3 Point; + typedef EpicKernel::Vector_3 Vector; + typedef boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef std::tuple< + EpicKernel::FT, + EpicKernel::FT, + Vector, + Vector + > PrincipalCurvatureTuple; + + typename boost::property_map::type vpmap = get(CGAL::vertex_point, *sMesh); + + bool created = false; + SMesh::Property_map principle_curvature_map; + + boost::tie(principle_curvature_map, created) = sMesh->add_property_map + ("v:principle_curvature_map", { 0, 0, + Vector(0,0,0), + Vector(0,0,0)}); + assert(created); + + PMP::interpolated_corrected_principal_curvatures( + *sMesh, + principle_curvature_map + ); + + typename EpicKernel::FT max_curvature_magnitude_on_mesh = 0; + for (vertex_descriptor v : vertices(*sMesh)) + { + const PrincipalCurvatureTuple pc = principle_curvature_map[v]; + max_curvature_magnitude_on_mesh = max(max_curvature_magnitude_on_mesh, max(abs(get<0>(pc)), get<1>(pc))); + } + + for(vertex_descriptor v : vertices(*sMesh)) + { + std::vector points; + + // pick central point + const Point& central_point = get(vpmap,v); + points.push_back(central_point); + + // compute min edge len around central vertex + // to scale the ribbons used to display the directions + + typedef EPICK::FT FT; + + const std::size_t n = CGAL::edges(*sMesh).size(); + + EpicKernel::FT avg_edge_length = 0; + if (n > 0) { + for (auto e : CGAL::edges(*sMesh)) + avg_edge_length += PMP::edge_length(e, *sMesh); + avg_edge_length /= n; + } + + const PrincipalCurvatureTuple pc = principle_curvature_map[v]; + + Vector umin = (std::get<0>(pc)/ max_curvature_magnitude_on_mesh) * std::get<2>(pc) * avg_edge_length; + Vector umax = (std::get<1>(pc)/ max_curvature_magnitude_on_mesh) * std::get<3>(pc) * avg_edge_length; + + Scene_polylines_item::Polyline max_segment(2), min_segment(2); + + const double du = 0.4; + + min_segment[0] = central_point + du * umin; + min_segment[1] = central_point - du * umin; + max_segment[0] = central_point + du * umax; + max_segment[1] = central_point - du * umax; + + (std::get<0>(pc) > 0 ? min_curv : min_negative_curv)->polylines.push_back(min_segment); + (std::get<1>(pc) > 0 ? max_curv : max_negative_curv)->polylines.push_back(max_segment); + } +} + +void Polyhedron_demo_interpolated_corrected_principal_curvatures_plugin::on_actionEstimateCurvature_triggered() +{ + // get active polyhedron + const CGAL::Three::Scene_interface::Item_id index = scene->mainSelectionIndex(); + QString name = scene->item(index)->name(); + Scene_surface_mesh_item* sm_item = + qobject_cast(scene->item(index)); + if(! sm_item){ + return; + } + // wait cursor + QApplication::setOverrideCursor(Qt::WaitCursor); + + // types + Scene_polylines_item* max_curv = new Scene_polylines_item; + max_curv->setColor(Qt::red); + max_curv->setWidth(3); + max_curv->setName(tr("%1 (max curvatures)").arg(name)); + + Scene_polylines_item* min_curv = new Scene_polylines_item; + min_curv->setColor(QColor(255,210,0)); + min_curv->setWidth(4); + min_curv->setName(tr("%1 (min curvatures)").arg(name)); + + Scene_polylines_item* max_negative_curv = new Scene_polylines_item; + max_negative_curv->setColor(Qt::cyan); + max_negative_curv->setWidth(4); + max_negative_curv->setName(tr("%1 (min negative curvatures)").arg(name)); + + Scene_polylines_item* min_negative_curv = new Scene_polylines_item; + min_negative_curv->setColor(Qt::blue); + min_negative_curv->setWidth(3); + min_negative_curv->setName(tr("%1 (max negative curvatures)").arg(name)); + + SMesh* pMesh = sm_item->polyhedron(); + compute(pMesh, max_curv, min_curv, max_negative_curv, min_negative_curv); + + scene->addItem(max_curv); + scene->addItem(min_curv); + max_curv->invalidateOpenGLBuffers(); + min_curv->invalidateOpenGLBuffers(); + scene->addItem(max_negative_curv); + scene->addItem(min_negative_curv); + max_negative_curv->invalidateOpenGLBuffers(); + min_negative_curv->invalidateOpenGLBuffers(); + + // default cursor + QApplication::restoreOverrideCursor(); +} + +#include "Interpolated_corrected_principal_curvatures_plugin.moc" From 419745088537cfc6425758ae527048132fde193a Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 26 Sep 2022 02:33:04 +0200 Subject: [PATCH 056/161] restored compute_normal.h --- .../CGAL/Polygon_mesh_processing/compute_normal.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h index 8dfb16af1bdd..2cd837e7bf84 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h @@ -717,9 +717,15 @@ compute_vertex_normal(typename boost::graph_traits::vertex_descript } #endif - // Change for debugging (comparing with DGtal) - Vector_3 normal = internal::compute_vertex_normal_as_sum_of_weighted_normals( - v, internal::NO_WEIGHT, face_normals, vpmap, pmesh, traits); + Vector_3 normal = internal::compute_vertex_normal_most_visible_min_circle(v, face_normals, pmesh, traits); + if(traits.equal_3_object()(normal, CGAL::NULL_VECTOR)) // can't always find a most visible normal + { +#ifdef CGAL_PMP_COMPUTE_NORMAL_DEBUG_PP + std::cout << "Failed to find most visible normal, use weighted sum of normals" << std::endl; +#endif + normal = internal::compute_vertex_normal_as_sum_of_weighted_normals( + v, internal::SIN_WEIGHT, face_normals, vpmap, pmesh, traits); + } if(!traits.equal_3_object()(normal, CGAL::NULL_VECTOR)) internal::normalize(normal, traits); From 7118646bec18cb0d4192daad64d1f52eb16cae97 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 26 Sep 2022 09:19:17 +0200 Subject: [PATCH 057/161] typo fix --- .../Interpolated_corrected_principal_curvatures_plugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp index 69c2b4fe2dd3..0b729978c7e7 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp @@ -160,12 +160,12 @@ void Polyhedron_demo_interpolated_corrected_principal_curvatures_plugin::on_acti Scene_polylines_item* max_negative_curv = new Scene_polylines_item; max_negative_curv->setColor(Qt::cyan); max_negative_curv->setWidth(4); - max_negative_curv->setName(tr("%1 (min negative curvatures)").arg(name)); + max_negative_curv->setName(tr("%1 (max negative curvatures)").arg(name)); Scene_polylines_item* min_negative_curv = new Scene_polylines_item; min_negative_curv->setColor(Qt::blue); min_negative_curv->setWidth(3); - min_negative_curv->setName(tr("%1 (max negative curvatures)").arg(name)); + min_negative_curv->setName(tr("%1 (min negative curvatures)").arg(name)); SMesh* pMesh = sm_item->polyhedron(); compute(pMesh, max_curv, min_curv, max_negative_curv, min_negative_curv); From a1e9345a1ece1571b583326a217cb5833822a7b6 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 26 Sep 2022 19:59:36 +0200 Subject: [PATCH 058/161] added principal curvatures plugin to test with cmake --- Polyhedron/demo/Polyhedron/cgal_test_with_cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/Polyhedron/demo/Polyhedron/cgal_test_with_cmake b/Polyhedron/demo/Polyhedron/cgal_test_with_cmake index 6fea79491769..59eb5c1d0211 100755 --- a/Polyhedron/demo/Polyhedron/cgal_test_with_cmake +++ b/Polyhedron/demo/Polyhedron/cgal_test_with_cmake @@ -149,6 +149,7 @@ hole_filling_plugin \ hole_filling_sm_plugin \ hole_filling_polyline_plugin \ inside_out_plugin \ +interpolated_corrected_principal_curvatures_plugin\ surface_intersection_plugin \ surface_intersection_sm_plugin \ io_image_plugin \ From 4f76f267d5b3e9dd08529baf06fefdffba256b4f Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 5 Oct 2022 00:38:23 +0200 Subject: [PATCH 059/161] mostly docs and examples, moved utils to internal --- .../PackageDescription.txt | 8 - .../Polygon_mesh_processing.txt | 54 ++++ .../doc/Polygon_mesh_processing/examples.txt | 3 +- .../Polygon_mesh_processing/CMakeLists.txt | 6 +- ...rpolated_corrected_curvatures_example.cpp} | 59 ++-- ...orrected_curvatures_polyhedron_example.cpp | 74 +++++ ...nterpolated_corrected_curvature_measures.h | 290 +----------------- 7 files changed, 184 insertions(+), 310 deletions(-) rename Polygon_mesh_processing/examples/Polygon_mesh_processing/{interpolated_corrected_curvatures.cpp => interpolated_corrected_curvatures_example.cpp} (52%) create mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 5c9e39811d46..eae47c610011 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -201,14 +201,6 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage. - \link PMP_locate_grp Random Location Generation \endlink \cgalCRPSection{Corrected Curvature Functions} -- `CGAL::Polygon_mesh_processing::interpolated_corrected_measure_mesh()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_anisotropic_measure_mesh()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_area_measure_face()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_measure_face()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_measure_face()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_anisotropic_measure_face()` -- `CGAL::Polygon_mesh_processing::expand_interpolated_corrected_measure_vertex()` -- `CGAL::Polygon_mesh_processing::expand_interpolated_corrected_anisotropic_measure_vertex()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures()` 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 743d6bc35079..6c684c4419b4 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 @@ -876,6 +876,60 @@ not provide storage for the normals. \cgalExample{Polygon_mesh_processing/compute_normals_example_Polyhedron.cpp} +**************************************** +\section PMPICC Computing Curvatures + +This package provides methods to compute curvatures on polygonal meshes based on #PAPER#. +This includes mean curvature, gaussian curvature, principal curvatures and directions. +These can be computed on triangle meshes, quad meshes, and meshes with n-gon faces. +The algorithms used prove to work well in general. Also, on meshes with noise +on vertex positions, they give accurate results, on the condition that the +correct vertex normals are provided. + +The implementation is generic in terms of mesh data structure. It can be used on Surface_mesh, +Polyhedron_3 and other polygonal mesh structures based on the Face Graph Model. + +These computations are performed using : +- `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures()` + +\cgalFigureRef{icc_diff_radius} shows how the mean curvature changes depending on +the ball_radius named parameter which can be set to a value > 0 to get a smoother +distribution of valuesa and "diffuse" the extreme values of curvatures across the mesh. + +\cgalFigureAnchor{icc_diff_radius} +
+ +
+\cgalFigureCaptionBegin{icc_diff_radius} +The mean curvature distrubution on a bear mesh with different values for the expanding ball radius +\cgalFigureCaptionEnd + +Property maps are used to record the computed curvatures as shown in examples. + +\subsection ICCExample Interpolated Corrected Curvature Examples + +Property maps are an API introduced in the boost library that allows to +associate values to keys. In the following examples, for each proberty map, we associate +a curvature value to each vertex. + +\subsubsection ICCExample Interpolated Corrected Curvature on a Surface Mesh. + +The following example illustrates how to +compute the curvatures on vertices +and store them in property maps provided by the class `Surface_mesh`. + +\cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp} + +\subsubsection NormalsExampleNP Interpolated Corrected Curvature on a Polyhedron + +The following example illustrates how to +compute the curvatures on vertices +and store them in unordered (or ordered) maps as the class `Polyhedron_3` does +not provide storage for the curvatures. + +\cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp} **************************************** \section PMPSlicer Slicer diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index 0317812aa443..0588d1c76e14 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -19,7 +19,8 @@ \example Polygon_mesh_processing/refine_fair_example.cpp \example Polygon_mesh_processing/mesh_slicer_example.cpp \example Polygon_mesh_processing/isotropic_remeshing_example.cpp -\example Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +\example Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp +\example Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp \example Polygon_mesh_processing/delaunay_remeshing_example.cpp \example Polygon_mesh_processing/compute_normals_example_Polyhedron.cpp \example Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index 4783d957589a..ab084384835b 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -105,8 +105,10 @@ create_single_source_cgal_program("cc_compatible_orientations.cpp") if(TARGET CGAL::Eigen3_support) - create_single_source_cgal_program("interpolated_corrected_curvatures.cpp") - target_link_libraries(interpolated_corrected_curvatures PUBLIC CGAL::Eigen3_support) + create_single_source_cgal_program("interpolated_corrected_curvatures_example.cpp") + target_link_libraries(interpolated_corrected_curvatures_example PUBLIC CGAL::Eigen3_support) + create_single_source_cgal_program("interpolated_corrected_curvatures_polyhedron_example.cpp") + target_link_libraries(interpolated_corrected_curvatures_polyhedron_example PUBLIC CGAL::Eigen3_support) endif() diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp similarity index 52% rename from Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp rename to Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp index c011d57db41e..caaa52eb3af0 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp @@ -7,18 +7,17 @@ #include #include -#include namespace PMP = CGAL::Polygon_mesh_processing; -typedef CGAL::Exact_predicates_inexact_constructions_kernel EpicKernel; -typedef CGAL::Surface_mesh Mesh; -typedef boost::graph_traits::face_descriptor face_descriptor; -typedef boost::graph_traits::vertex_descriptor vertex_descriptor; +typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic_Kernel; +typedef CGAL::Surface_mesh Surface_Mesh; +typedef boost::graph_traits::face_descriptor face_descriptor; +typedef boost::graph_traits::vertex_descriptor vertex_descriptor; int main(int argc, char* argv[]) { - Mesh g1; + Surface_Mesh g1; const std::string filename = (argc>1) ? argv[1] : CGAL::data_file_path("meshes/small_bunny.obj"); @@ -29,36 +28,54 @@ int main(int argc, char* argv[]) return EXIT_FAILURE; } + // creating and tying surface mesh property maps for curvatures (with defaults = 0) bool created = false; - - Mesh::Property_map mean_curvature_map, gaussian_curvature_map; - boost::tie(mean_curvature_map, created) = g1.add_property_map("v:mean_curvature_map", 0); + Surface_Mesh::Property_map mean_curvature_map, gaussian_curvature_map; + boost::tie(mean_curvature_map, created) = g1.add_property_map("v:mean_curvature_map", 0); assert(created); - boost::tie(gaussian_curvature_map, created) = g1.add_property_map("v:gaussian_curvature_map", 0); + boost::tie(gaussian_curvature_map, created) = g1.add_property_map("v:gaussian_curvature_map", 0); assert(created); - Mesh::Property_map> principle_curvature_map; boost::tie(principle_curvature_map, created) = g1.add_property_map>("v:principle_curvature_map", { 0, 0, - EpicKernel::Vector_3 (0,0,0), - EpicKernel::Vector_3 (0,0,0)}); + Epic_Kernel::Vector_3 (0,0,0), + Epic_Kernel::Vector_3 (0,0,0)}); assert(created); PMP::interpolated_corrected_mean_curvature( g1, mean_curvature_map ); + + // uncomment this to compute a curvature while specifying named parameters + // Example: an expansion ball radius of 0.5 and a vertex normals map (does not have to depend on positions) + + /*Surface_Mesh::Property_map vnm; + boost::tie(vnm, created) = g1.add_property_map( + "v:vnm", Epic_Kernel::Vector_3(0, 0, 0) + ); + + assert(created); + + PMP::interpolated_corrected_mean_curvature( + g1, + mean_curvature_map, + CGAL::parameters::ball_radius(0.5).vertex_normal_map(vnm) + );*/ + PMP::interpolated_corrected_gaussian_curvature( g1, gaussian_curvature_map diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp new file mode 100644 index 000000000000..3e56905068f3 --- /dev/null +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace PMP = CGAL::Polygon_mesh_processing; + +typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic_Kernel; +typedef CGAL::Polyhedron_3 Polyhedron; +typedef boost::graph_traits::face_descriptor face_descriptor; +typedef boost::graph_traits::vertex_descriptor vertex_descriptor; + +int main(int argc, char* argv[]) +{ + Polyhedron g1; + const std::string filename = (argc>1) ? + argv[1] : + CGAL::data_file_path("meshes/small_bunny.obj"); + + if(!CGAL::IO::read_polygon_mesh(filename, g1)) + { + std::cerr << "Invalid input file." << std::endl; + return EXIT_FAILURE; + } + + std::unordered_map mean_curvature_map, gaussian_curvature_map; + std::unordered_map> principle_curvature_map; + + PMP::interpolated_corrected_mean_curvature( + g1, + boost::make_assoc_property_map(mean_curvature_map) + ); + + // uncomment this to compute a curvature while specifying named parameters + // Example: an expansion ball radius of 0.5 and a vertex normals map (does not have to depend on positions) + + /*std::unordered_map vnm; + + PMP::interpolated_corrected_mean_curvature( + g1, + boost::make_assoc_property_map(mean_curvature_map), + CGAL::parameters::ball_radius(0.5).vertex_normal_map(boost::make_assoc_property_map(vnm)) + );*/ + + PMP::interpolated_corrected_gaussian_curvature( + g1, + boost::make_assoc_property_map(gaussian_curvature_map) + ); + PMP::interpolated_corrected_principal_curvatures( + g1, + boost::make_assoc_property_map(principle_curvature_map) + ); + + int i = 0; + for (vertex_descriptor v : vertices(g1)) + { + auto PC = principle_curvature_map[v]; + std::cout << i << ": HC = " << mean_curvature_map[v] + << ", GC = " << gaussian_curvature_map[v] << "\n" + << ", PC = [ " << std::get<0>(PC) << " , " << std::get<1>(PC) << " ]\n"; + i++; + } +} diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index e2e1292aedb4..8f5e1d338edb 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -44,7 +44,7 @@ typename GT::FT average_edge_length(const PolygonMesh& pmesh) if (n == 0) return 0; - GT::FT avg_edge_length = 0; + typename GT::FT avg_edge_length = 0; for (auto e : edges(pmesh)) avg_edge_length += edge_length(e, pmesh); @@ -52,35 +52,12 @@ typename GT::FT average_edge_length(const PolygonMesh& pmesh) return avg_edge_length; } -} - -/*! - * \ingroup PMP_corrected_curvatures_grp - * Enumeration type used to specify which measure of a given face - * is computed for the interpolated corrected curvature functions. - */ -// enum enum Curvature_measure_index { MU0_AREA_MEASURE, ///< corrected area density MU1_MEAN_CURVATURE_MEASURE, ///< corrected mean curvature density MU2_GAUSSIAN_CURVATURE_MEASURE ///< corrected gaussian curvature density }; -/** -* \ingroup PMP_corrected_curvatures_grp -* -* Computes the interpolated corrected area measure \f$ \mu_0 \f$ of a specific face. -* -* @tparam GT geometric traits class model of `Kernel`. -* -* @param x is a vector of the vertex positions of the face. -* @param u is a vector of the vertex normals of the face. -* -* -* @see `interpolated_corrected_mean_curvature_measure_face()` -* @see `interpolated_corrected_gaussian_curvature_measure_face()` -* @see `interpolated_corrected_measure_mesh()` -*/ template typename GT::FT interpolated_corrected_area_measure_face(const std::vector& u, const std::vector& x = {}) @@ -136,20 +113,6 @@ typename GT::FT interpolated_corrected_area_measure_face(const std::vector typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::vector& u, const std::vector& x = {}) @@ -216,20 +179,6 @@ typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::ve } } -/** -* \ingroup PMP_corrected_curvatures_grp -* -* Computes the interpolated corrected gaussian curvature measure \f$ \mu_2 \f$ of a specific face. -* -* @tparam GT geometric traits class model of `Kernel`. -* -* @param x is a vector of the vertex positions of the face. -* @param u is a vector of the vertex nomrals of the face. -* -* @see `interpolated_corrected_mean_curvature_measure_face()` -* @see `interpolated_corrected_area_measure_face()` -* @see `interpolated_corrected_measure_mesh()` -*/ template typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std::vector& u, const std::vector& x = {}) @@ -277,21 +226,6 @@ typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std } } -/** -* \ingroup PMP_corrected_curvatures_grp -* -* Computes the interpolated corrected anisotropic measure \f$ \mu_{XY} \f$ of a specific face. -* -* @tparam GT geometric traits class model of `Kernel`. -* -* @param u is a vector of the vertex nomrals of the face. -* @param x is a vector of the vertex positions of the face. -* -* @return an array of scalar values for each combination of the standard basis (3x3) -* These are the values of the interpolated corrected anisotropic measure of the given face. -* -* @see `interpolated_corrected_anisotropic_measure_mesh()` -*/ template std::array interpolated_corrected_anisotropic_measure_face(const std::vector& u, const std::vector& x) @@ -403,51 +337,6 @@ std::array interpolated_corrected_anisotropic_measure_fa return muXY; } - -/** -* \ingroup PMP_corrected_curvatures_grp -* -* Computes the interpolated corrected curvature measure on each face of the mesh. -* -* @tparam PolygonMesh a model of `FaceListGraph`. -* @tparam FaceMeasureMap a model of `WritablePropertyMap` with -* `boost::graph_traits::%face_descriptor` as key type and `GT::FT` as value type. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". -* -* @param pmesh the polygon mesh. -* @param fmm (face measure map) the property map used for storing the computed interpolated corrected measure. -* @param mu_i an enum for choosing between computing -* the area measure, the mean curvature measure or the gaussian curvature measure. -* @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 `ReadablePropertyMap` 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` must be available in `PolygonMesh`.} -* \cgalParamNEnd -* -* \cgalParamNBegin{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} -* \cgalParamType{a class model of `ReadablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` -* as key type and `%Vector_3` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} -* \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using compute_vertex_normals()} -* \cgalParamNEnd -* -* \cgalNamedParamsEnd -* -* @see `interpolated_corrected_area_measure_face()` -* @see `interpolated_corrected_mean_curvature_measure_face()` -* @see `interpolated_corrected_gaussian_curvature_measure_face()` -*/ template void @@ -518,48 +407,6 @@ template::%face_descriptor` as key type and `std::array` as value type. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". -* -* @param pmesh the polygon mesh. -* @param fmm (face measure map) the property map used for storing the computed interpolated corrected measure. -* @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 `ReadablePropertyMap` 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` must be available in `PolygonMesh`.} -* \cgalParamNEnd -* -* \cgalParamNBegin{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} -* \cgalParamType{a class model of `ReadablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` -* as key type and `%Vector_3` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} -* \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using compute_vertex_normals()} -* \cgalParamNEnd -* -* \cgalNamedParamsEnd -* -* @see `interpolated_corrected_anisotropic_measure_face()` -* @see `interpolated_corrected_measure_mesh()` -*/ template void @@ -636,21 +483,6 @@ template typename GT::FT face_in_ball_ratio(const std::vector& x, const typename GT::FT r, @@ -682,52 +514,6 @@ typename GT::FT face_in_ball_ratio(const std::vector& x, return (r - d_min) / (d_max - d_min); } -/** -* \ingroup PMP_corrected_curvatures_grp -* -* Expands given face area and curvature (mean or gaussian) measures around a vertex `v`. -* Expansion is based on the inclusion ratio of each face in a ball of radius `r` around the vertex `v`. -* -* @tparam PolygonMesh a model of `FaceListGraph`. -* @tparam FaceMeasureMap a model of `WritablePropertyMap` with -* `boost::graph_traits::%face_descriptor` as key type and `GT::FT` as value type. -* @tparam VertexMeasureMap a model of `WritablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` as key type and `GT::FT` as value type. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". -* -* @param pmesh the polygon mesh. -* @param area_fmm (area face measure map) the property map storing the already computed area measure on each face. -* @param curvature_fmm (curvature face measure map) the property map storing the already computed curvature measure on each face. -* This curvature measure can be either the Mean Curvature or the Gaussian Curvature. -* @param area_vmm (area vertex measure map) the property map provided to store the expanded area measure on each vertex. -* @param curvature_vmm (curvature vertex measure map) the property map provided to store the expanded curvature measure on each vertex. -* This curvature measure can be either the Mean Curvature or the Gaussian Curvature. -* @param v (vertex) the vertex to expand the area and curvature measure around. -* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below -* -* \cgalNamedParamsBegin -* -* \cgalParamNBegin{ball_radius} -* \cgalParamDescription{the radius of the ball around the vertex `v` to expand the area and curvature measure} -* \cgalParamType{`GT::FT`} -* \cgalParamDefault{`0.01`} -* \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 `%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 -* -* \cgalNamedParamsEnd -* -* @see `expand_interpolated_corrected_anisotropic_measure_vertex()` -* @see `face_in_ball_ratio()` -*/ template void expand_interpolated_corrected_measure_vertex(const PolygonMesh& pmesh, @@ -802,54 +588,6 @@ template::%face_descriptor` as key type and `GT::FT` as value type. -* @tparam AnisotropicFaceMeasureMap a model of `WritablePropertyMap` with -* `boost::graph_traits::%face_descriptor` as key type and `std::array` as value type. -* @tparam AreaVertexMeasureMap a model of `WritablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` as key type and `GT::FT` as value type. -* @tparam AnisotropicVertexMeasureMap a model of `WritablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` as key type and `std::array` as value type. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". -* -* @param pmesh the polygon mesh. -* @param area_fmm (area face measure map) the property map storing the already computed area measure on each face. -* @param aniso_fmm (anisotropic face measure map) the property map storing the already computed anisotropic measure on each face. -* @param area_vmm (area vertex measure map) the property map provided to store the expanded area measure on each vertex. -* @param aniso_vmm (anisotropic vertex measure map) the property map provided to store the expanded anisotropic measure on each vertex. -* @param v (vertex) the vertex to expand the area and anisotropic measure around. -* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below -* -* \cgalNamedParamsBegin -* -* \cgalParamNBegin{ball_radius} -* \cgalParamDescription{the radius of the ball around the vertex `v` to expand the area and curvature measure} -* \cgalParamType{`GT::FT`} -* \cgalParamDefault{`0.01`} -* \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 `%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 -* -* \cgalNamedParamsEnd -* -* @see `expand_interpolated_corrected_measure_vertex()` -* @see `face_in_ball_ratio()` -*/ template @@ -931,6 +669,8 @@ template mu0_expand_map(mu0_expand_init), mu1_expand_map(mu1_expand_init); - interpolated_corrected_measure_mesh(pmesh, mu0_map, MU0_AREA_MEASURE, np); - interpolated_corrected_measure_mesh(pmesh, mu1_map, MU1_MEAN_CURVATURE_MEASURE, np); + internal::interpolated_corrected_measure_mesh(pmesh, mu0_map, internal::MU0_AREA_MEASURE, np); + internal::interpolated_corrected_measure_mesh(pmesh, mu1_map, internal::MU1_MEAN_CURVATURE_MEASURE, np); for (vertex_descriptor v : vertices(pmesh)) { - expand_interpolated_corrected_measure_vertex(pmesh, mu0_map, mu1_map, mu0_expand_map, mu1_expand_map, v, np.ball_radius(r)); + internal::expand_interpolated_corrected_measure_vertex(pmesh, mu0_map, mu1_map, mu0_expand_map, mu1_expand_map, v, np.ball_radius(r)); typename GT::FT v_mu0 = get(mu0_expand_map, v); if (v_mu0 != 0.0) @@ -1015,8 +753,6 @@ template @@ -1049,12 +785,12 @@ template mu0_expand_map(mu0_expand_init), mu2_expand_map(mu2_expand_init); - interpolated_corrected_measure_mesh(pmesh, mu0_map, MU0_AREA_MEASURE, np); - interpolated_corrected_measure_mesh(pmesh, mu2_map, MU2_GAUSSIAN_CURVATURE_MEASURE, np); + internal::interpolated_corrected_measure_mesh(pmesh, mu0_map, internal::MU0_AREA_MEASURE, np); + internal::interpolated_corrected_measure_mesh(pmesh, mu2_map, internal::MU2_GAUSSIAN_CURVATURE_MEASURE, np); for (vertex_descriptor v : vertices(pmesh)) { - expand_interpolated_corrected_measure_vertex(pmesh, mu0_map, mu2_map, mu0_expand_map, mu2_expand_map, v, np.ball_radius(r)); + internal::expand_interpolated_corrected_measure_vertex(pmesh, mu0_map, mu2_map, mu0_expand_map, mu2_expand_map, v, np.ball_radius(r)); typename GT::FT v_mu0 = get(mu0_expand_map, v); if(v_mu0 != 0.0) @@ -1106,8 +842,6 @@ template @@ -1172,12 +906,12 @@ template muXY_expand_map(muXY_expand_init); - interpolated_corrected_measure_mesh(pmesh, mu0_map, MU0_AREA_MEASURE, np); - interpolated_corrected_anisotropic_measure_mesh(pmesh, muXY_map, np); + internal::interpolated_corrected_measure_mesh(pmesh, mu0_map, internal::MU0_AREA_MEASURE, np); + internal::interpolated_corrected_anisotropic_measure_mesh(pmesh, muXY_map, np); for (vertex_descriptor v : vertices(pmesh)) { - expand_interpolated_corrected_anisotropic_measure_vertex(pmesh, mu0_map, muXY_map, mu0_expand_map, muXY_expand_map, v, np.ball_radius(r)); + internal::expand_interpolated_corrected_anisotropic_measure_vertex(pmesh, mu0_map, muXY_map, mu0_expand_map, muXY_expand_map, v, np.ball_radius(r)); typename GT::FT v_mu0 = get(mu0_expand_map, v); Eigen::Matrix v_muXY = get(muXY_expand_map, v); From bb6d3e4f07b372972e942f4198a1666289ac3916 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 5 Oct 2022 10:30:27 +0200 Subject: [PATCH 060/161] doc fix --- .../doc/Polygon_mesh_processing/Polygon_mesh_processing.txt | 4 ++-- 1 file changed, 2 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 6c684c4419b4..8f222497a527 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 @@ -914,7 +914,7 @@ Property maps are an API introduced in the boost library that allows to associate values to keys. In the following examples, for each proberty map, we associate a curvature value to each vertex. -\subsubsection ICCExample Interpolated Corrected Curvature on a Surface Mesh. +\subsubsection ICCExampleSM Interpolated Corrected Curvature on a Surface Mesh. The following example illustrates how to compute the curvatures on vertices @@ -922,7 +922,7 @@ and store them in property maps provided by the class `Surface_mesh`. \cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp} -\subsubsection NormalsExampleNP Interpolated Corrected Curvature on a Polyhedron +\subsubsection ICCExamplePH Interpolated Corrected Curvature on a Polyhedron The following example illustrates how to compute the curvatures on vertices From e80a4c8cc57e375356090e07da3a7ff4c5d452a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 5 Oct 2022 10:32:45 +0200 Subject: [PATCH 061/161] change ref --- .../doc/Polygon_mesh_processing/Polygon_mesh_processing.txt | 2 +- 1 file changed, 1 insertion(+), 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 6c684c4419b4..6bb9140939c2 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 @@ -914,7 +914,7 @@ Property maps are an API introduced in the boost library that allows to associate values to keys. In the following examples, for each proberty map, we associate a curvature value to each vertex. -\subsubsection ICCExample Interpolated Corrected Curvature on a Surface Mesh. +\subsubsection ICCExampleSM Interpolated Corrected Curvature on a Surface Mesh. The following example illustrates how to compute the curvatures on vertices From 2cb8906639e3aea577bd9c98dcd5005b78c44004 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 7 Oct 2022 21:36:30 +0200 Subject: [PATCH 062/161] principle -> principal typo fixed --- .../interpolated_corrected_curvatures_example.cpp | 12 ++++++------ ...lated_corrected_curvatures_polyhedron_example.cpp | 8 ++++---- ...polated_corrected_principal_curvatures_plugin.cpp | 12 ++++++------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp index caaa52eb3af0..78307b952e4f 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp @@ -43,14 +43,14 @@ int main(int argc, char* argv[]) Epic_Kernel::FT, Epic_Kernel::Vector_3, Epic_Kernel::Vector_3 - >> principle_curvature_map; + >> principal_curvature_map; - boost::tie(principle_curvature_map, created) = g1.add_property_map>("v:principle_curvature_map", { 0, 0, + >>("v:principal_curvature_map", { 0, 0, Epic_Kernel::Vector_3 (0,0,0), Epic_Kernel::Vector_3 (0,0,0)}); assert(created); @@ -62,7 +62,7 @@ int main(int argc, char* argv[]) // uncomment this to compute a curvature while specifying named parameters // Example: an expansion ball radius of 0.5 and a vertex normals map (does not have to depend on positions) - + /*Surface_Mesh::Property_map vnm; boost::tie(vnm, created) = g1.add_property_map( "v:vnm", Epic_Kernel::Vector_3(0, 0, 0) @@ -82,12 +82,12 @@ int main(int argc, char* argv[]) ); PMP::interpolated_corrected_principal_curvatures( g1, - principle_curvature_map + principal_curvature_map ); for (vertex_descriptor v : vertices(g1)) { - auto PC = principle_curvature_map[v]; + auto PC = principal_curvature_map[v]; std::cout << v.idx() << ": HC = " << mean_curvature_map[v] << ", GC = " << gaussian_curvature_map[v] << "\n" << ", PC = [ " << std::get<0>(PC) << " , " << std::get<1>(PC) << " ]\n"; diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp index 3e56905068f3..43aff72dec2b 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp @@ -35,7 +35,7 @@ int main(int argc, char* argv[]) Epic_Kernel::FT, Epic_Kernel::Vector_3, Epic_Kernel::Vector_3 - >> principle_curvature_map; + >> principal_curvature_map; PMP::interpolated_corrected_mean_curvature( g1, @@ -44,7 +44,7 @@ int main(int argc, char* argv[]) // uncomment this to compute a curvature while specifying named parameters // Example: an expansion ball radius of 0.5 and a vertex normals map (does not have to depend on positions) - + /*std::unordered_map vnm; PMP::interpolated_corrected_mean_curvature( @@ -59,13 +59,13 @@ int main(int argc, char* argv[]) ); PMP::interpolated_corrected_principal_curvatures( g1, - boost::make_assoc_property_map(principle_curvature_map) + boost::make_assoc_property_map(principal_curvature_map) ); int i = 0; for (vertex_descriptor v : vertices(g1)) { - auto PC = principle_curvature_map[v]; + auto PC = principal_curvature_map[v]; std::cout << i << ": HC = " << mean_curvature_map[v] << ", GC = " << gaussian_curvature_map[v] << "\n" << ", PC = [ " << std::get<0>(PC) << " , " << std::get<1>(PC) << " ]\n"; diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp index 0b729978c7e7..59e6438b6763 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp @@ -72,23 +72,23 @@ void compute(SMesh* sMesh, typename boost::property_map::type vpmap = get(CGAL::vertex_point, *sMesh); bool created = false; - SMesh::Property_map principle_curvature_map; + SMesh::Property_map principal_curvature_map; - boost::tie(principle_curvature_map, created) = sMesh->add_property_map - ("v:principle_curvature_map", { 0, 0, + boost::tie(principal_curvature_map, created) = sMesh->add_property_map + ("v:principal_curvature_map", { 0, 0, Vector(0,0,0), Vector(0,0,0)}); assert(created); PMP::interpolated_corrected_principal_curvatures( *sMesh, - principle_curvature_map + principal_curvature_map ); typename EpicKernel::FT max_curvature_magnitude_on_mesh = 0; for (vertex_descriptor v : vertices(*sMesh)) { - const PrincipalCurvatureTuple pc = principle_curvature_map[v]; + const PrincipalCurvatureTuple pc = principal_curvature_map[v]; max_curvature_magnitude_on_mesh = max(max_curvature_magnitude_on_mesh, max(abs(get<0>(pc)), get<1>(pc))); } @@ -114,7 +114,7 @@ void compute(SMesh* sMesh, avg_edge_length /= n; } - const PrincipalCurvatureTuple pc = principle_curvature_map[v]; + const PrincipalCurvatureTuple pc = principal_curvature_map[v]; Vector umin = (std::get<0>(pc)/ max_curvature_magnitude_on_mesh) * std::get<2>(pc) * avg_edge_length; Vector umax = (std::get<1>(pc)/ max_curvature_magnitude_on_mesh) * std::get<3>(pc) * avg_edge_length; From 48fc5aeebd9cb076daf20d42c769bb0fa73f797a Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 6 Nov 2022 18:19:39 +0200 Subject: [PATCH 063/161] dynamic property maps --- ...nterpolated_corrected_curvature_measures.h | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 8f5e1d338edb..d2765c8aa527 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -702,10 +702,12 @@ template::type GT; typedef typename boost::graph_traits::face_descriptor face_descriptor; - typedef std::unordered_map FaceMeasureMap_tag; + typedef typename boost::property_map>::const_type FaceMeasureMap; typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef std::unordered_map VertexMeasureMap_tag; + typedef typename boost::property_map>::const_type VertexMeasureMap; typename GT::FT r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); @@ -713,13 +715,11 @@ template(pmesh) * EXPANDING_RADIUS_EPSILON; - FaceMeasureMap_tag mu0_init, mu1_init; - boost::associative_property_map - mu0_map(mu0_init), mu1_map(mu1_init); + FaceMeasureMap mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); + FaceMeasureMap mu1_map = get(CGAL::dynamic_face_property_t(), pmesh); - VertexMeasureMap_tag mu0_expand_init, mu1_expand_init; - boost::associative_property_map - mu0_expand_map(mu0_expand_init), mu1_expand_map(mu1_expand_init); + VertexMeasureMap mu0_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); + VertexMeasureMap mu1_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); internal::interpolated_corrected_measure_mesh(pmesh, mu0_map, internal::MU0_AREA_MEASURE, np); internal::interpolated_corrected_measure_mesh(pmesh, mu1_map, internal::MU1_MEAN_CURVATURE_MEASURE, np); @@ -766,10 +766,12 @@ template::type GT; typedef typename boost::graph_traits::face_descriptor face_descriptor; - typedef std::unordered_map FaceMeasureMap_tag; + typedef typename boost::property_map>::const_type FaceMeasureMap; typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef std::unordered_map VertexMeasureMap_tag; + typedef typename boost::property_map>::const_type VertexMeasureMap; typename GT::FT r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); @@ -777,13 +779,11 @@ template(pmesh) * EXPANDING_RADIUS_EPSILON; - FaceMeasureMap_tag mu0_init, mu2_init; - boost::associative_property_map - mu0_map(mu0_init), mu2_map(mu2_init); + FaceMeasureMap mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); + FaceMeasureMap mu2_map = get(CGAL::dynamic_face_property_t(), pmesh); - VertexMeasureMap_tag mu0_expand_init, mu2_expand_init; - boost::associative_property_map - mu0_expand_map(mu0_expand_init), mu2_expand_map(mu2_expand_init); + VertexMeasureMap mu0_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); + VertexMeasureMap mu2_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); internal::interpolated_corrected_measure_mesh(pmesh, mu0_map, internal::MU0_AREA_MEASURE, np); internal::interpolated_corrected_measure_mesh(pmesh, mu2_map, internal::MU2_GAUSSIAN_CURVATURE_MEASURE, np); From 38c66a61e338950689bf6efcbe6aea42f6b74e8a Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 6 Nov 2022 18:23:51 +0200 Subject: [PATCH 064/161] typo fixes --- .../interpolated_corrected_curvatures_example.cpp | 2 +- .../Curvatures/interpolated_corrected_curvature_measures.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp index 78307b952e4f..3916b1ea3e1a 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp @@ -37,7 +37,7 @@ int main(int argc, char* argv[]) boost::tie(gaussian_curvature_map, created) = g1.add_property_map("v:gaussian_curvature_map", 0); assert(created); - // we use a tuble of 2 scalar values and 2 vectors for principal curvatures and directions + // we use a tuple of 2 scalar values and 2 vectors for principal curvatures and directions Surface_Mesh::Property_map Date: Sun, 6 Nov 2022 19:06:19 +0200 Subject: [PATCH 065/161] tab to space --- ...est_interopolated_corrected_curvatures.cpp | 238 +++++++++--------- 1 file changed, 119 insertions(+), 119 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp index e7b473141562..7e376c887716 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp @@ -19,130 +19,130 @@ typedef boost::graph_traits::edge_descriptor edge_descriptor; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; void test(std::string mesh_path, EpicKernel::FT rel_expansion_radius, EpicKernel::FT rel_noise_magnitude) { - SMesh pmesh; - const std::string filename = CGAL::data_file_path(mesh_path); - - if (!CGAL::IO::read_polygon_mesh(filename, pmesh)) - { - std::cerr << "Invalid input file." << std::endl; - } - - bool created = false; - - SMesh::Property_map mean_curvature_map, gaussian_curvature_map; - boost::tie(mean_curvature_map, created) = pmesh.add_property_map("v:mean_curvature_map", 0); - assert(created); - - boost::tie(gaussian_curvature_map, created) = pmesh.add_property_map("v:gaussian_curvature_map", 0); - assert(created); - - // getting the max and min edge lengthes - const auto edge_range = CGAL::edges(pmesh); - - const auto edge_length_comparator = [&, pmesh](auto l, auto r) { - return PMP::edge_length(l, pmesh) < PMP::edge_length(r, pmesh); - }; - - const edge_descriptor longest_edge = *std::max_element(edge_range.begin(), edge_range.end(), edge_length_comparator); - const EpicKernel::FT max_edge_length = PMP::edge_length(longest_edge, pmesh); - - const edge_descriptor shortest_edge = *std::min_element(edge_range.begin(), edge_range.end(), edge_length_comparator); - const EpicKernel::FT min_edge_length = PMP::edge_length(shortest_edge, pmesh); - - - if (rel_noise_magnitude > 0) - { - if (!CGAL::is_triangle_mesh(pmesh)) - return; - - SMesh::Property_map vnm; - boost::tie(vnm, created) = pmesh.add_property_map("v:vnm", { 0 , 0 , 0 }); - assert(created); - - CGAL::Polygon_mesh_processing::compute_vertex_normals(pmesh, vnm); - - PMP::random_perturbation(pmesh, rel_noise_magnitude * min_edge_length, CGAL::parameters::random_seed(0)); - PMP::interpolated_corrected_mean_curvature( - pmesh, - mean_curvature_map, - CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length).vertex_normal_map(vnm) - ); - PMP::interpolated_corrected_gaussian_curvature( - pmesh, - gaussian_curvature_map, - CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length).vertex_normal_map(vnm) - ); - } - else { - PMP::interpolated_corrected_mean_curvature( - pmesh, - mean_curvature_map, - CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) - ); - PMP::interpolated_corrected_gaussian_curvature( - pmesh, - gaussian_curvature_map, - CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) - ); - - } - - - - //PMP::interpolated_corrected_mean_curvature( - // pmesh, - // mean_curvature_map, - // CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) - //); - //PMP::interpolated_corrected_gaussian_curvature( - // pmesh, - // gaussian_curvature_map, - // CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) - //); - - - const EpicKernel::FT max_mean_curvature = *std::max_element(mean_curvature_map.begin(), mean_curvature_map.end()); - const EpicKernel::FT min_mean_curvature = *std::min_element(mean_curvature_map.begin(), mean_curvature_map.end()); - const EpicKernel::FT max_gaussian_curvature = *std::max_element(gaussian_curvature_map.begin(), gaussian_curvature_map.end()); - const EpicKernel::FT min_gaussian_curvature = *std::min_element(gaussian_curvature_map.begin(), gaussian_curvature_map.end()); - - std::cout << "# " << mesh_path << ":\n" - << "expansion radius ratio to max length / expansion radius = " << rel_expansion_radius << " / " << rel_expansion_radius * max_edge_length << ",\n" - << "max perturbation ratio to minlength / max perturbation = " << rel_noise_magnitude << " / " << rel_noise_magnitude * min_edge_length << "\n" - << "mean curvature: min = " << min_mean_curvature << " <-> " << max_mean_curvature << " = max" << "\n" - << "gaussian curvature: min = " << min_gaussian_curvature << " <-> " << max_gaussian_curvature << " = max" << "\n\n\n"; + SMesh pmesh; + const std::string filename = CGAL::data_file_path(mesh_path); + + if (!CGAL::IO::read_polygon_mesh(filename, pmesh)) + { + std::cerr << "Invalid input file." << std::endl; + } + + bool created = false; + + SMesh::Property_map mean_curvature_map, gaussian_curvature_map; + boost::tie(mean_curvature_map, created) = pmesh.add_property_map("v:mean_curvature_map", 0); + assert(created); + + boost::tie(gaussian_curvature_map, created) = pmesh.add_property_map("v:gaussian_curvature_map", 0); + assert(created); + + // getting the max and min edge lengthes + const auto edge_range = CGAL::edges(pmesh); + + const auto edge_length_comparator = [&, pmesh](auto l, auto r) { + return PMP::edge_length(l, pmesh) < PMP::edge_length(r, pmesh); + }; + + const edge_descriptor longest_edge = *std::max_element(edge_range.begin(), edge_range.end(), edge_length_comparator); + const EpicKernel::FT max_edge_length = PMP::edge_length(longest_edge, pmesh); + + const edge_descriptor shortest_edge = *std::min_element(edge_range.begin(), edge_range.end(), edge_length_comparator); + const EpicKernel::FT min_edge_length = PMP::edge_length(shortest_edge, pmesh); + + + if (rel_noise_magnitude > 0) + { + if (!CGAL::is_triangle_mesh(pmesh)) + return; + + SMesh::Property_map vnm; + boost::tie(vnm, created) = pmesh.add_property_map("v:vnm", { 0 , 0 , 0 }); + assert(created); + + CGAL::Polygon_mesh_processing::compute_vertex_normals(pmesh, vnm); + + PMP::random_perturbation(pmesh, rel_noise_magnitude * min_edge_length, CGAL::parameters::random_seed(0)); + PMP::interpolated_corrected_mean_curvature( + pmesh, + mean_curvature_map, + CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length).vertex_normal_map(vnm) + ); + PMP::interpolated_corrected_gaussian_curvature( + pmesh, + gaussian_curvature_map, + CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length).vertex_normal_map(vnm) + ); + } + else { + PMP::interpolated_corrected_mean_curvature( + pmesh, + mean_curvature_map, + CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) + ); + PMP::interpolated_corrected_gaussian_curvature( + pmesh, + gaussian_curvature_map, + CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) + ); + + } + + + + //PMP::interpolated_corrected_mean_curvature( + // pmesh, + // mean_curvature_map, + // CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) + //); + //PMP::interpolated_corrected_gaussian_curvature( + // pmesh, + // gaussian_curvature_map, + // CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) + //); + + + const EpicKernel::FT max_mean_curvature = *std::max_element(mean_curvature_map.begin(), mean_curvature_map.end()); + const EpicKernel::FT min_mean_curvature = *std::min_element(mean_curvature_map.begin(), mean_curvature_map.end()); + const EpicKernel::FT max_gaussian_curvature = *std::max_element(gaussian_curvature_map.begin(), gaussian_curvature_map.end()); + const EpicKernel::FT min_gaussian_curvature = *std::min_element(gaussian_curvature_map.begin(), gaussian_curvature_map.end()); + + std::cout << "# " << mesh_path << ":\n" + << "expansion radius ratio to max length / expansion radius = " << rel_expansion_radius << " / " << rel_expansion_radius * max_edge_length << ",\n" + << "max perturbation ratio to minlength / max perturbation = " << rel_noise_magnitude << " / " << rel_noise_magnitude * min_edge_length << "\n" + << "mean curvature: min = " << min_mean_curvature << " <-> " << max_mean_curvature << " = max" << "\n" + << "gaussian curvature: min = " << min_gaussian_curvature << " <-> " << max_gaussian_curvature << " = max" << "\n\n\n"; } int main() { - const std::vector mesh_pathes_to_test = { - "meshes/icc_test/Sphere Quads + Tris.obj", - "meshes/icc_test/Sphere Quads + Tris 100352.obj", - "meshes/icc_test/Sphere Tris Ico.obj", - "meshes/icc_test/Sphere Tris Tet.obj", - "meshes/icc_test/Sphere Tris Oct.obj", - "meshes/icc_test/Sphere Quads.obj", - "meshes/icc_test/Sphere Quads Remesh.obj", - "meshes/icc_test/Sphere Ngons + Quads + Tris.obj", - "meshes/icc_test/Cube with fillet Quads.obj", - "meshes/cylinder.off", - "meshes/icc_test/Lantern Tris.obj", - "meshes/icc_test/Lantern Quads.obj" - }; - - const std::vector rel_expansion_radii = { 0, 0.1, 0.5, 1 }; - const std::vector rel_noise_magnitudes = { 0, 0.5, 0.9 }; - - for (auto mesh_path : mesh_pathes_to_test) { - for (EpicKernel::FT rel_expansion_radius : rel_expansion_radii) - for (EpicKernel::FT rel_noise_magnitude : rel_noise_magnitudes) - { - test(mesh_path, rel_expansion_radius, rel_noise_magnitude); - } - - std::cout << "_________________________________________________________________________________\n\n"; - } + const std::vector mesh_pathes_to_test = { + "meshes/icc_test/Sphere Quads + Tris.obj", + "meshes/icc_test/Sphere Quads + Tris 100352.obj", + "meshes/icc_test/Sphere Tris Ico.obj", + "meshes/icc_test/Sphere Tris Tet.obj", + "meshes/icc_test/Sphere Tris Oct.obj", + "meshes/icc_test/Sphere Quads.obj", + "meshes/icc_test/Sphere Quads Remesh.obj", + "meshes/icc_test/Sphere Ngons + Quads + Tris.obj", + "meshes/icc_test/Cube with fillet Quads.obj", + "meshes/cylinder.off", + "meshes/icc_test/Lantern Tris.obj", + "meshes/icc_test/Lantern Quads.obj" + }; + + const std::vector rel_expansion_radii = { 0, 0.1, 0.5, 1 }; + const std::vector rel_noise_magnitudes = { 0, 0.5, 0.9 }; + + for (auto mesh_path : mesh_pathes_to_test) { + for (EpicKernel::FT rel_expansion_radius : rel_expansion_radii) + for (EpicKernel::FT rel_noise_magnitude : rel_noise_magnitudes) + { + test(mesh_path, rel_expansion_radius, rel_noise_magnitude); + } + + std::cout << "_________________________________________________________________________________\n\n"; + } } From 4295fd4e07645213ab3ac097cfa60821a2900d7b Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 6 Nov 2022 19:49:37 +0200 Subject: [PATCH 066/161] enum minor fix --- .../Display/Display_property_plugin.cpp | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index cda67cbd9700..c69b73694980 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -338,7 +338,10 @@ class DisplayPropertyPlugin : typedef CGAL::Heat_method_3::Surface_mesh_geodesic_distances_3 Heat_method_idt; typedef CGAL::dynamic_vertex_property_t Vertex_source_tag; typedef boost::property_map::type Vertex_source_map; - + enum CurvatureType { + MEAN_CURVATURE, + GAUSSIAN_CURVATURE, +}; public: bool applicable(QAction* action) const Q_DECL_OVERRIDE @@ -613,11 +616,11 @@ private Q_SLOTS: sm_item->setRenderingMode(Gouraud); break; case 4: // Interpolated Corrected Mean Curvature - displayInterpolatedCurvatureMeasure(sm_item, PMP::MU1_MEAN_CURVATURE_MEASURE); + displayInterpolatedCurvatureMeasure(sm_item, MEAN_CURVATURE); sm_item->setRenderingMode(Gouraud); break; case 5: // Interpolated Corrected Gaussian Curvature - displayInterpolatedCurvatureMeasure(sm_item, PMP::MU2_GAUSSIAN_CURVATURE_MEASURE); + displayInterpolatedCurvatureMeasure(sm_item, GAUSSIAN_CURVATURE); sm_item->setRenderingMode(Gouraud); break; default: @@ -843,9 +846,11 @@ private Q_SLOTS: } - void displayInterpolatedCurvatureMeasure(Scene_surface_mesh_item* item, PMP::Curvature_measure_index mu_index) + void displayInterpolatedCurvatureMeasure(Scene_surface_mesh_item* item, CurvatureType mu_index) { - std::string tied_string = (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE)? + if (mu_index != MEAN_CURVATURE && mu_index != GAUSSIAN_CURVATURE) return; + + std::string tied_string = (mu_index == MEAN_CURVATURE)? "v:interpolated_corrected_mean_curvature": "v:interpolated_corrected_gaussian_curvature"; SMesh& smesh = *item->face_graph(); @@ -860,13 +865,13 @@ private Q_SLOTS: if (non_init) { if (vnm_exists) { - if (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE) + if (mu_index == MEAN_CURVATURE) PMP::interpolated_corrected_mean_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius).vertex_normal_map(vnm)); else PMP::interpolated_corrected_gaussian_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius).vertex_normal_map(vnm)); } else { - if (mu_index == PMP::MU1_MEAN_CURVATURE_MEASURE) + if (mu_index == MEAN_CURVATURE) PMP::interpolated_corrected_mean_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius)); else PMP::interpolated_corrected_gaussian_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius)); @@ -887,16 +892,13 @@ private Q_SLOTS: min_index = v; } } - switch (mu_index) - { - case PMP::MU1_MEAN_CURVATURE_MEASURE: + if (mu_index == MEAN_CURVATURE){ mean_curvature_max[item] = std::make_pair(res_max, max_index); mean_curvature_min[item] = std::make_pair(res_min, min_index); - break; - case PMP::MU2_GAUSSIAN_CURVATURE_MEASURE: + } + else { gaussian_curvature_max[item] = std::make_pair(res_max, max_index); gaussian_curvature_min[item] = std::make_pair(res_min, min_index); - break; } connect(item, &Scene_surface_mesh_item::itemChanged, From aeaf881c49c8969ec6b1024fb93e4de8e3306ff6 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 6 Nov 2022 20:35:05 +0200 Subject: [PATCH 067/161] minor fix max func --- .../PMP/Interpolated_corrected_principal_curvatures_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp index 59e6438b6763..21b292b05d0c 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp @@ -89,7 +89,7 @@ void compute(SMesh* sMesh, for (vertex_descriptor v : vertices(*sMesh)) { const PrincipalCurvatureTuple pc = principal_curvature_map[v]; - max_curvature_magnitude_on_mesh = max(max_curvature_magnitude_on_mesh, max(abs(get<0>(pc)), get<1>(pc))); + max_curvature_magnitude_on_mesh = std::max(max_curvature_magnitude_on_mesh, std::max(abs(get<0>(pc)), get<1>(pc))); } for(vertex_descriptor v : vertices(*sMesh)) From 8efd947d53a12aa751eaec67f2ae4a37a1b862a3 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 7 Nov 2022 10:58:45 +0200 Subject: [PATCH 068/161] doc fixes --- ...nterpolated_corrected_curvature_measures.h | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 6426cae12de7..6e659e4e6ec8 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -686,6 +686,41 @@ template::%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` must be available in `PolygonMesh`.} +* \cgalParamNEnd +* +* \cgalParamNBegin{vertex_normal_map} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamType{a class model of `ReadablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` +* as key type and `%Vector_3` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, vertex normals will be +* computed using compute_vertex_normals()} +* \cgalParamNEnd +* +* \cgalParamNBegin{ball_radius} +* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures +* by summing measures of faces inside a ball of this radius centered at the +* vertex expanded from. The summed face measures are weighted by their +* inclusion ratio inside this ball} +* \cgalParamType{`GT::FT`} +* \cgalParamDefault{`-1`} +* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of +* measures on faces around the vertex} +* \cgalParamNEnd +* +* \cgalNamedParamsEnd +* * @see `interpolated_corrected_gaussian_curvature()` * @see `interpolated_corrected_principal_curvatures()` */ @@ -751,6 +786,41 @@ template::%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` must be available in `PolygonMesh`.} +* \cgalParamNEnd +* +* \cgalParamNBegin{vertex_normal_map} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamType{a class model of `ReadablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` +* as key type and `%Vector_3` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, vertex normals will be +* computed using compute_vertex_normals()} +* \cgalParamNEnd +* +* \cgalParamNBegin{ball_radius} +* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures +* by summing measures of faces inside a ball of this radius centered at the +* vertex expanded from. The summed face measures are weighted by their +* inclusion ratio inside this ball} +* \cgalParamType{`GT::FT`} +* \cgalParamDefault{`-1`} +* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of +* measures on faces around the vertex} +* \cgalParamNEnd +* +* \cgalNamedParamsEnd +* * @see `interpolated_corrected_mean_curvature()` * @see `interpolated_corrected_principal_curvatures()` */ @@ -837,6 +907,17 @@ template Date: Mon, 7 Nov 2022 11:00:29 +0200 Subject: [PATCH 069/161] trailing white spaces --- ...nterpolated_corrected_curvature_measures.h | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 6e659e4e6ec8..7658f1f4dbb1 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -720,7 +720,7 @@ template Date: Tue, 8 Nov 2022 19:19:40 +0200 Subject: [PATCH 070/161] dynamic property maps --- ...nterpolated_corrected_curvature_measures.h | 44 +++++++------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 7658f1f4dbb1..8a9949b073c8 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -26,7 +26,6 @@ #include #include -#include #include #define EXPANDING_RADIUS_EPSILON 1e-6 @@ -932,6 +931,18 @@ template::type GT; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef typename boost::property_map>::const_type FaceScalarMeasureMap; + typedef typename boost::property_map>>::const_type FaceArrayMeasureMap; + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::property_map>::const_type VertexScalarMeasureMap; + typedef typename boost::property_map>>::const_type VertexMatrixMeasureMap; + typedef dynamic_vertex_property_t Vector_map_tag; typedef typename boost::property_map::const_type Default_vector_map; typedef typename internal_np::Lookup_named_param_def::value) compute_vertex_normals(pmesh, vnm, np); + FaceScalarMeasureMap mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); + FaceArrayMeasureMap muXY_map = get(CGAL::dynamic_face_property_t>(), pmesh); - - typedef typename boost::graph_traits::face_descriptor face_descriptor; - typedef std::unordered_map FaceScalarMeasureMap_tag; - // using std:: array to store FT values on the 9 combinations of the standard 3D basis - typedef std::unordered_map> FaceArrayMeasureMap_tag; - - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef std::unordered_map VertexScalarMeasureMap_tag; - // using Eigen matrix to store & Process FT values on the 9 combinations of the standard 3D basis - typedef std::unordered_map> VertexMatrixMeasureMap_tag; - - - FaceScalarMeasureMap_tag mu0_init; - boost::associative_property_map - mu0_map(mu0_init); - - FaceArrayMeasureMap_tag muXY_init; - boost::associative_property_map - muXY_map(muXY_init); - - VertexScalarMeasureMap_tag mu0_expand_init; - boost::associative_property_map - mu0_expand_map(mu0_expand_init); - - VertexMatrixMeasureMap_tag muXY_expand_init; - boost::associative_property_map - muXY_expand_map(muXY_expand_init); + VertexScalarMeasureMap mu0_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); + VertexMatrixMeasureMap muXY_expand_map = get(CGAL::dynamic_vertex_property_t>(), pmesh); internal::interpolated_corrected_measure_mesh(pmesh, mu0_map, internal::MU0_AREA_MEASURE, np); internal::interpolated_corrected_anisotropic_measure_mesh(pmesh, muXY_map, np); From 866287a98ee0aa02e213873d8c50a1240e109f20 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 8 Nov 2022 20:13:04 +0200 Subject: [PATCH 071/161] minor naming conventions fixes --- ...nterpolated_corrected_curvature_measures.h | 140 +++++++++--------- .../internal/parameters_interface.h | 3 + 2 files changed, 73 insertions(+), 70 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 8a9949b073c8..32c8fcdfcd47 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -351,21 +351,21 @@ template::const_type Default_vector_map; typedef typename internal_np::Lookup_named_param_def::type VNM; + Default_vector_map>::type Vertex_normal_map; using parameters::choose_parameter; using parameters::get_parameter; using parameters::is_default_parameter; - typedef typename boost::graph_traits::face_descriptor face_descriptor; - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::face_descriptor Face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; typename GetVertexPointMap::const_type vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), get_const_property_map(CGAL::vertex_point, pmesh)); - VNM vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), + Vertex_normal_map vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), get(Vector_map_tag(), pmesh)); if (is_default_parameter::value) @@ -390,10 +390,10 @@ template x; std::vector u; - for (face_descriptor f : faces(pmesh)) + for (Face_descriptor f : faces(pmesh)) { - for (vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) + for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) { typename GT::Point_3 p = get(vpm, v); x.push_back(typename GT::Vector_3(p.x(), p.y(), p.z())); @@ -420,21 +420,21 @@ template::const_type Default_vector_map; typedef typename internal_np::Lookup_named_param_def::type VNM; + Default_vector_map>::type VertexNormalMap; using parameters::choose_parameter; using parameters::get_parameter; using parameters::is_default_parameter; - typedef typename boost::graph_traits::face_descriptor face_descriptor; - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::face_descriptor Face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; typename GetVertexPointMap::const_type vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), get_const_property_map(CGAL::vertex_point, pmesh)); - VNM vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), + VertexNormalMap vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), get(Vector_map_tag(), pmesh)); if (is_default_parameter::value) @@ -443,10 +443,10 @@ template x; std::vector u; - for (face_descriptor f : faces(pmesh)) + for (Face_descriptor f : faces(pmesh)) { - for (vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) + for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) { typename GT::Point_3 p = get(vpm, v); x.push_back(typename GT::Vector_3(p.x(), p.y(), p.z())); @@ -528,8 +528,8 @@ template::type GT; - typedef typename boost::graph_traits::face_descriptor face_descriptor; - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::face_descriptor Face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; const typename GT::FT r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); @@ -539,8 +539,8 @@ template bfs_queue; - std::unordered_set bfs_visited; + std::queue bfs_queue; + std::unordered_set bfs_visited; typename GT::Point_3 vp = get(vpm, v); typename GT::Vector_3 c = typename GT::Vector_3(vp.x(), vp.y(), vp.z()); @@ -548,7 +548,7 @@ template::null_face()) { bfs_queue.push(f); @@ -556,12 +556,12 @@ template x; - for (vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) + for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) { typename GT::Point_3 pi = get(vpm, vi); x.push_back(typename GT::Vector_3(pi.x(), pi.y(), pi.z())); @@ -573,7 +573,7 @@ template::null_face()) { @@ -603,8 +603,8 @@ template::type GT; - typedef typename boost::graph_traits::face_descriptor face_descriptor; - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::face_descriptor Face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; const typename GT::FT r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); @@ -614,8 +614,8 @@ template bfs_queue; - std::unordered_set bfs_visited; + std::queue bfs_queue; + std::unordered_set bfs_visited; typename GT::Point_3 vp = get(vpm, v); typename GT::Vector_3 c = typename GT::Vector_3(vp.x(), vp.y(), vp.z()); @@ -623,7 +623,7 @@ template corrected_muXY = Eigen::Matrix::Zero(); - for (face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { if (f != boost::graph_traits::null_face()) { bfs_queue.push(f); @@ -631,12 +631,12 @@ template x; - for (vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) + for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) { typename GT::Point_3 pi = get(vpm, vi); x.push_back(typename GT::Vector_3(pi.x(), pi.y(), pi.z())); @@ -654,7 +654,7 @@ template::null_face()) { @@ -678,7 +678,7 @@ template::%vertex_descriptor` as key type and `GT::FT` as value type. +* `boost::graph_traits::%Vertex_descriptor` as key type and `GT::FT` as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * * @param pmesh the polygon mesh. @@ -690,7 +690,7 @@ template::%vertex_descriptor` +* `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 @@ -700,7 +700,7 @@ template::%vertex_descriptor` +* `boost::graph_traits::%Vertex_descriptor` * as key type and `%Vector_3` as value type} * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} * \cgalParamExtra{If this parameter is omitted, vertex normals will be @@ -735,13 +735,13 @@ template::type GT; - typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef typename boost::graph_traits::face_descriptor Face_descriptor; typedef typename boost::property_map>::const_type FaceMeasureMap; + CGAL::dynamic_face_property_t>::const_type Face_measure_map; - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; typedef typename boost::property_map>::const_type VertexMeasureMap; + CGAL::dynamic_vertex_property_t>::const_type Vertex_measure_map; typename GT::FT r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); @@ -749,16 +749,16 @@ template(pmesh) * EXPANDING_RADIUS_EPSILON; - FaceMeasureMap mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); - FaceMeasureMap mu1_map = get(CGAL::dynamic_face_property_t(), pmesh); + Face_measure_map mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); + Face_measure_map mu1_map = get(CGAL::dynamic_face_property_t(), pmesh); - VertexMeasureMap mu0_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); - VertexMeasureMap mu1_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); + Vertex_measure_map mu0_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); + Vertex_measure_map mu1_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); internal::interpolated_corrected_measure_mesh(pmesh, mu0_map, internal::MU0_AREA_MEASURE, np); internal::interpolated_corrected_measure_mesh(pmesh, mu1_map, internal::MU1_MEAN_CURVATURE_MEASURE, np); - for (vertex_descriptor v : vertices(pmesh)) + for (Vertex_descriptor v : vertices(pmesh)) { internal::expand_interpolated_corrected_measure_vertex(pmesh, mu0_map, mu1_map, mu0_expand_map, mu1_expand_map, v, np.ball_radius(r)); @@ -778,7 +778,7 @@ template::%vertex_descriptor` as key type and `GT::FT` as value type. +* `boost::graph_traits::%Vertex_descriptor` as key type and `GT::FT` as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * * @param pmesh the polygon mesh. @@ -790,7 +790,7 @@ template::%vertex_descriptor` +* `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 @@ -800,7 +800,7 @@ template::%vertex_descriptor` +* `boost::graph_traits::%Vertex_descriptor` * as key type and `%Vector_3` as value type} * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} * \cgalParamExtra{If this parameter is omitted, vertex normals will be @@ -834,13 +834,13 @@ template::type GT; - typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef typename boost::graph_traits::face_descriptor Face_descriptor; typedef typename boost::property_map>::const_type FaceMeasureMap; + CGAL::dynamic_face_property_t>::const_type Face_measure_map; - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; typedef typename boost::property_map>::const_type VertexMeasureMap; + CGAL::dynamic_vertex_property_t>::const_type Vertex_measure_map; typename GT::FT r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); @@ -848,16 +848,16 @@ template(pmesh) * EXPANDING_RADIUS_EPSILON; - FaceMeasureMap mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); - FaceMeasureMap mu2_map = get(CGAL::dynamic_face_property_t(), pmesh); + Face_measure_map mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); + Face_measure_map mu2_map = get(CGAL::dynamic_face_property_t(), pmesh); - VertexMeasureMap mu0_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); - VertexMeasureMap mu2_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); + Vertex_measure_map mu0_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); + Vertex_measure_map mu2_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); internal::interpolated_corrected_measure_mesh(pmesh, mu0_map, internal::MU0_AREA_MEASURE, np); internal::interpolated_corrected_measure_mesh(pmesh, mu2_map, internal::MU2_GAUSSIAN_CURVATURE_MEASURE, np); - for (vertex_descriptor v : vertices(pmesh)) + for (Vertex_descriptor v : vertices(pmesh)) { internal::expand_interpolated_corrected_measure_vertex(pmesh, mu0_map, mu2_map, mu0_expand_map, mu2_expand_map, v, np.ball_radius(r)); @@ -877,7 +877,7 @@ template::%vertex_descriptor` as key type and +* `boost::graph_traits::%Vertex_descriptor` as key type and * `std::tuple, Eigen::Vector>` as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * @@ -890,7 +890,7 @@ template::%vertex_descriptor` +* `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 @@ -900,7 +900,7 @@ template::%vertex_descriptor` +* `boost::graph_traits::%Vertex_descriptor` * as key type and `%Vector_3` as value type} * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} * \cgalParamExtra{If this parameter is omitted, vertex normals will be @@ -931,23 +931,23 @@ template::type GT; - typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef typename boost::graph_traits::face_descriptor Face_descriptor; typedef typename boost::property_map>::const_type FaceScalarMeasureMap; + CGAL::dynamic_face_property_t>::const_type Face_scalar_measure_map; typedef typename boost::property_map>>::const_type FaceArrayMeasureMap; + CGAL::dynamic_face_property_t>>::const_type Face_array_measure_map; - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; typedef typename boost::property_map>::const_type VertexScalarMeasureMap; + CGAL::dynamic_vertex_property_t>::const_type Vertex_scalar_measure_map; typedef typename boost::property_map>>::const_type VertexMatrixMeasureMap; + CGAL::dynamic_vertex_property_t>>::const_type Vertex_matrix_measure_map; typedef dynamic_vertex_property_t Vector_map_tag; typedef typename boost::property_map::const_type Default_vector_map; typedef typename internal_np::Lookup_named_param_def::type VNM; + Default_vector_map>::type VertexNormalMap; using parameters::choose_parameter; using parameters::get_parameter; @@ -957,7 +957,7 @@ template::value) compute_vertex_normals(pmesh, vnm, np); - FaceScalarMeasureMap mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); - FaceArrayMeasureMap muXY_map = get(CGAL::dynamic_face_property_t>(), pmesh); + Face_scalar_measure_map mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); + Face_array_measure_map muXY_map = get(CGAL::dynamic_face_property_t>(), pmesh); - VertexScalarMeasureMap mu0_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); - VertexMatrixMeasureMap muXY_expand_map = get(CGAL::dynamic_vertex_property_t>(), pmesh); + Vertex_scalar_measure_map mu0_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); + Vertex_matrix_measure_map muXY_expand_map = get(CGAL::dynamic_vertex_property_t>(), pmesh); internal::interpolated_corrected_measure_mesh(pmesh, mu0_map, internal::MU0_AREA_MEASURE, np); internal::interpolated_corrected_anisotropic_measure_mesh(pmesh, muXY_map, np); - for (vertex_descriptor v : vertices(pmesh)) + for (Vertex_descriptor v : vertices(pmesh)) { internal::expand_interpolated_corrected_anisotropic_measure_vertex(pmesh, mu0_map, muXY_map, mu0_expand_map, muXY_expand_map, v, np.ball_radius(r)); 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 9eea7e71e442..03ee822d0bc9 100644 --- a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h +++ b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h @@ -87,6 +87,9 @@ 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_curvature_map_t, vertex_principal_curvature_map, vertex_principal_curvature_map) CGAL_add_named_parameter(ball_radius_t, ball_radius, ball_radius) 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) From aff46b61624328468311bfe3e83172ade9bed7ec Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 9 Nov 2022 17:32:42 +0200 Subject: [PATCH 072/161] incomplete (integrating class for combined curvature computations) --- ...erpolated_corrected_curvatures_example.cpp | 19 +- ...nterpolated_corrected_curvature_measures.h | 1054 ++++++++++------- 2 files changed, 663 insertions(+), 410 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp index 3916b1ea3e1a..da6e9d2252f0 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp @@ -38,19 +38,10 @@ int main(int argc, char* argv[]) assert(created); // we use a tuple of 2 scalar values and 2 vectors for principal curvatures and directions - Surface_Mesh::Property_map> principal_curvature_map; - - boost::tie(principal_curvature_map, created) = g1.add_property_map>("v:principal_curvature_map", { 0, 0, + Surface_Mesh::Property_map> principal_curvature_map; + + boost::tie(principal_curvature_map, created) = g1.add_property_map> + ("v:principal_curvature_map", { 0, 0, Epic_Kernel::Vector_3 (0,0,0), Epic_Kernel::Vector_3 (0,0,0)}); assert(created); @@ -90,6 +81,6 @@ int main(int argc, char* argv[]) auto PC = principal_curvature_map[v]; std::cout << v.idx() << ": HC = " << mean_curvature_map[v] << ", GC = " << gaussian_curvature_map[v] << "\n" - << ", PC = [ " << std::get<0>(PC) << " , " << std::get<1>(PC) << " ]\n"; + << ", PC = [ " << PC.min_curvature << " , " << PC.max_curvature << " ]\n"; } } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 32c8fcdfcd47..c4a9154c1aa2 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -26,6 +26,7 @@ #include #include +#include #include #define EXPANDING_RADIUS_EPSILON 1e-6 @@ -34,6 +35,32 @@ namespace CGAL { namespace Polygon_mesh_processing { +template +struct Principal_curvature { + typename GT::FT min_curvature; + typename GT::FT max_curvature; + typename GT::Vector_3 min_direction; + typename GT::Vector_3 max_direction; + + Principal_curvature() { + min_curvature = 0; + max_curvature = 0; + min_direction = typename GT::Vector_3(0, 0, 0); + max_direction = typename GT::Vector_3(0, 0, 0); + } + + Principal_curvature( + typename GT::FT min_curvature, + typename GT::FT max_curvature, + typename GT::Vector_3 min_direction, + typename GT::Vector_3 max_direction) { + this->min_curvature = min_curvature; + this->max_curvature = max_curvature; + this->min_direction = min_direction; + this->max_direction = max_direction; + } +}; + namespace internal { template @@ -57,6 +84,16 @@ enum Curvature_measure_index { MU2_GAUSSIAN_CURVATURE_MEASURE ///< corrected gaussian curvature density }; +template +struct Vertex_measures { + typename GT::FT area_measure = 0; + typename GT::FT mean_curvature_measure = 0; + typename GT::FT gaussian_curvature_measure = 0; + std::array anisotropic_measure = { 0, 0, 0, + 0, 0, 0, + 0, 0, 0 }; +}; + template typename GT::FT interpolated_corrected_area_measure_face(const std::vector& u, const std::vector& x = {}) @@ -336,130 +373,129 @@ std::array interpolated_corrected_anisotropic_measure_fa return muXY; } -template - void - interpolated_corrected_measure_mesh(const PolygonMesh& pmesh, - FaceMeasureMap fmm, - const Curvature_measure_index mu_i, - const NamedParameters& np = parameters::default_values()) -{ - - typedef typename GetGeomTraits::type GT; - - typedef dynamic_vertex_property_t Vector_map_tag; - typedef typename boost::property_map::const_type Default_vector_map; - typedef typename internal_np::Lookup_named_param_def::type Vertex_normal_map; - - using parameters::choose_parameter; - using parameters::get_parameter; - using parameters::is_default_parameter; - - typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; - typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; - - typename GetVertexPointMap::const_type - vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), - get_const_property_map(CGAL::vertex_point, pmesh)); - - Vertex_normal_map vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), - get(Vector_map_tag(), pmesh)); - - if (is_default_parameter::value) - compute_vertex_normals(pmesh, vnm, np); - - std::function - &, const std::vector&)> - iccm_function; - switch (mu_i) - { - case MU0_AREA_MEASURE: - iccm_function = &interpolated_corrected_area_measure_face; - break; - case MU1_MEAN_CURVATURE_MEASURE: - iccm_function = &interpolated_corrected_mean_curvature_measure_face; - break; - case MU2_GAUSSIAN_CURVATURE_MEASURE: - iccm_function = &interpolated_corrected_gaussian_curvature_measure_face; - break; - } - - std::vector x; - std::vector u; - - for (Face_descriptor f : faces(pmesh)) - { - - for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) - { - typename GT::Point_3 p = get(vpm, v); - x.push_back(typename GT::Vector_3(p.x(), p.y(), p.z())); - u.push_back(get(vnm, v)); - } - - put(fmm, f, iccm_function(u, x)); - x.clear(); - u.clear(); - } -} - -template - void - interpolated_corrected_anisotropic_measure_mesh(const PolygonMesh& pmesh, - FaceMeasureMap fmm, - const NamedParameters& np = parameters::default_values()) -{ - - typedef typename GetGeomTraits::type GT; - - typedef dynamic_vertex_property_t Vector_map_tag; - typedef typename boost::property_map::const_type Default_vector_map; - typedef typename internal_np::Lookup_named_param_def::type VertexNormalMap; - - using parameters::choose_parameter; - using parameters::get_parameter; - using parameters::is_default_parameter; - - typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; - typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; - - typename GetVertexPointMap::const_type - vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), - get_const_property_map(CGAL::vertex_point, pmesh)); - - VertexNormalMap vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), - get(Vector_map_tag(), pmesh)); - - if (is_default_parameter::value) - compute_vertex_normals(pmesh, vnm, np); - - std::vector x; - std::vector u; - - for (Face_descriptor f : faces(pmesh)) - { - - for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) - { - typename GT::Point_3 p = get(vpm, v); - x.push_back(typename GT::Vector_3(p.x(), p.y(), p.z())); - u.push_back(get(vnm, v)); - } - - put(fmm, f, interpolated_corrected_anisotropic_measure_face(u, x)); - x.clear(); - u.clear(); - - } -} - +//template +// void +// interpolated_corrected_measure_mesh(const PolygonMesh& pmesh, +// FaceMeasureMap fmm, +// const Curvature_measure_index mu_i, +// const NamedParameters& np = parameters::default_values()) +//{ +// +// typedef typename GetGeomTraits::type GT; +// +// typedef dynamic_vertex_property_t Vector_map_tag; +// typedef typename boost::property_map::const_type Default_vector_map; +// typedef typename internal_np::Lookup_named_param_def::type Vertex_normal_map; +// +// using parameters::choose_parameter; +// using parameters::get_parameter; +// using parameters::is_default_parameter; +// +// typedef typename boost::graph_traits::face_descriptor Face_descriptor; +// typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; +// typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; +// +// typename GetVertexPointMap::const_type +// vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), +// get_const_property_map(CGAL::vertex_point, pmesh)); +// +// Vertex_normal_map vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), +// get(Vector_map_tag(), pmesh)); +// +// if (is_default_parameter::value) +// compute_vertex_normals(pmesh, vnm, np); +// +// std::function +// &, const std::vector&)> +// iccm_function; +// switch (mu_i) +// { +// case MU0_AREA_MEASURE: +// iccm_function = &interpolated_corrected_area_measure_face; +// break; +// case MU1_MEAN_CURVATURE_MEASURE: +// iccm_function = &interpolated_corrected_mean_curvature_measure_face; +// break; +// case MU2_GAUSSIAN_CURVATURE_MEASURE: +// iccm_function = &interpolated_corrected_gaussian_curvature_measure_face; +// break; +// } +// +// std::vector x; +// std::vector u; +// +// for (Face_descriptor f : faces(pmesh)) +// { +// +// for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) +// { +// typename GT::Point_3 p = get(vpm, v); +// x.push_back(typename GT::Vector_3(p.x(), p.y(), p.z())); +// u.push_back(get(vnm, v)); +// } +// +// put(fmm, f, iccm_function(u, x)); +// x.clear(); +// u.clear(); +// } +//} +// +//template +// void +// interpolated_corrected_anisotropic_measure_mesh(const PolygonMesh& pmesh, +// FaceMeasureMap fmm, +// const NamedParameters& np = parameters::default_values()) +//{ +// +// typedef typename GetGeomTraits::type GT; +// +// typedef dynamic_vertex_property_t Vector_map_tag; +// typedef typename boost::property_map::const_type Default_vector_map; +// typedef typename internal_np::Lookup_named_param_def::type VertexNormalMap; +// +// using parameters::choose_parameter; +// using parameters::get_parameter; +// using parameters::is_default_parameter; +// +// typedef typename boost::graph_traits::face_descriptor Face_descriptor; +// typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; +// typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; +// +// typename GetVertexPointMap::const_type +// vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), +// get_const_property_map(CGAL::vertex_point, pmesh)); +// +// VertexNormalMap vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), +// get(Vector_map_tag(), pmesh)); +// +// if (is_default_parameter::value) +// compute_vertex_normals(pmesh, vnm, np); +// +// std::vector x; +// std::vector u; +// +// for (Face_descriptor f : faces(pmesh)) +// { +// +// for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) +// { +// typename GT::Point_3 p = get(vpm, v); +// x.push_back(typename GT::Vector_3(p.x(), p.y(), p.z())); +// u.push_back(get(vnm, v)); +// } +// +// put(fmm, f, interpolated_corrected_anisotropic_measure_face(u, x)); +// x.clear(); +// u.clear(); +// +// } +//} // //template @@ -513,160 +549,472 @@ typename GT::FT face_in_ball_ratio(const std::vector& x, return (r - d_min) / (d_max - d_min); } -template - void expand_interpolated_corrected_measure_vertex(const PolygonMesh& pmesh, - FaceMeasureMap area_fmm, - FaceMeasureMap curvature_fmm, - VertexMeasureMap area_vmm, - VertexMeasureMap curvature_vmm, - const typename boost::graph_traits::vertex_descriptor v, - const NamedParameters& np = parameters::default_values()) +//template +// void expand_interpolated_corrected_measure_vertex(const PolygonMesh& pmesh, +// FaceMeasureMap area_fmm, +// FaceMeasureMap curvature_fmm, +// VertexMeasureMap area_vmm, +// VertexMeasureMap curvature_vmm, +// const typename boost::graph_traits::vertex_descriptor v, +// const NamedParameters& np = parameters::default_values()) +//{ +// using parameters::choose_parameter; +// using parameters::get_parameter; +// +// typedef typename GetGeomTraits::type GT; +// +// typedef typename boost::graph_traits::face_descriptor Face_descriptor; +// typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; +// +// const typename GT::FT +// r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); +// +// typename GetVertexPointMap::const_type +// vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), +// get_const_property_map(CGAL::vertex_point, pmesh)); +// +// +// std::queue bfs_queue; +// std::unordered_set bfs_visited; +// +// typename GT::Point_3 vp = get(vpm, v); +// typename GT::Vector_3 c = typename GT::Vector_3(vp.x(), vp.y(), vp.z()); +// +// typename GT::FT corrected_mu0 = 0; +// typename GT::FT corrected_mui = 0; +// +// for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { +// if (f != boost::graph_traits::null_face()) +// { +// bfs_queue.push(f); +// bfs_visited.insert(f); +// } +// } +// while (!bfs_queue.empty()) { +// Face_descriptor fi = bfs_queue.front(); +// bfs_queue.pop(); +// +// // looping over vertices in face to get point coordinates +// std::vector x; +// for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) +// { +// typename GT::Point_3 pi = get(vpm, vi); +// x.push_back(typename GT::Vector_3(pi.x(), pi.y(), pi.z())); +// } +// +// const typename GT::FT f_ratio = face_in_ball_ratio(x, r, c); +// +// if (f_ratio != 0.0) +// { +// corrected_mu0 += f_ratio * get(area_fmm, fi); +// corrected_mui += f_ratio * get(curvature_fmm, fi); +// for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) +// { +// if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) +// { +// bfs_queue.push(fj); +// bfs_visited.insert(fj); +// } +// } +// } +// } +// put(area_vmm, v, corrected_mu0); +// put(curvature_vmm, v, corrected_mui); +//} +// +//template +// void expand_interpolated_corrected_anisotropic_measure_vertex(const PolygonMesh& pmesh, +// AreaFaceMeasureMap area_fmm, +// AnisotropicFaceMeasureMap aniso_fmm, +// AreaVertexMeasureMap area_vmm, +// AnisotropicVertexMeasureMap aniso_vmm, +// const typename boost::graph_traits::vertex_descriptor v, +// const NamedParameters& np = parameters::default_values()) +//{ +// using parameters::choose_parameter; +// using parameters::get_parameter; +// +// typedef typename GetGeomTraits::type GT; +// +// typedef typename boost::graph_traits::face_descriptor Face_descriptor; +// typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; +// +// const typename GT::FT +// r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); +// +// typename GetVertexPointMap::const_type +// vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), +// get_const_property_map(CGAL::vertex_point, pmesh)); +// +// +// std::queue bfs_queue; +// std::unordered_set bfs_visited; +// +// typename GT::Point_3 vp = get(vpm, v); +// typename GT::Vector_3 c = typename GT::Vector_3(vp.x(), vp.y(), vp.z()); +// +// typename GT::FT corrected_mu0 = 0; +// Eigen::Matrix corrected_muXY = Eigen::Matrix::Zero(); +// +// for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { +// if (f != boost::graph_traits::null_face()) +// { +// bfs_queue.push(f); +// bfs_visited.insert(f); +// } +// } +// while (!bfs_queue.empty()) { +// Face_descriptor fi = bfs_queue.front(); +// bfs_queue.pop(); +// +// // looping over vertices in face to get point coordinates +// std::vector x; +// for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) +// { +// typename GT::Point_3 pi = get(vpm, vi); +// x.push_back(typename GT::Vector_3(pi.x(), pi.y(), pi.z())); +// } +// +// const typename GT::FT f_ratio = face_in_ball_ratio(x, r, c); +// +// if (f_ratio != 0.0) +// { +// corrected_mu0 += f_ratio * get(area_fmm, fi); +// +// std::array muXY_face = get(aniso_fmm, fi); +// +// for (std::size_t ix = 0; ix < 3; ix++) +// for (std::size_t iy = 0; iy < 3; iy++) +// corrected_muXY(ix, iy) += f_ratio * muXY_face[ix * 3 + iy]; +// +// for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) +// { +// if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) +// { +// bfs_queue.push(fj); +// bfs_visited.insert(fj); +// } +// } +// } +// } +// put(area_vmm, v, corrected_mu0); +// put(aniso_vmm, v, corrected_muXY); +//} + + +template +Principal_curvature principal_curvature_from_anisotropic_measures( + const std::array anisotropic_measure, + const typename GT::FT v_mu0, + const typename GT::Vector_3 u_GT +) { - using parameters::choose_parameter; - using parameters::get_parameter; + Eigen::Matrix v_muXY = Eigen::Matrix::Zero(); + + for (std::size_t ix = 0; ix < 3; ix++) + for (std::size_t iy = 0; iy < 3; iy++) + v_muXY(ix, iy) = anisotropic_measure[ix * 3 + iy]; + + Eigen::Matrix u(u_GT.x(), u_GT.y(), u_GT.z()); + const typename GT::FT K = 1000 * v_mu0; + v_muXY = 0.5 * (v_muXY + v_muXY.transpose()) + K * u * u.transpose(); + + Eigen::SelfAdjointEigenSolver> eigensolver; + + eigensolver.computeDirect(v_muXY); + + if (eigensolver.info() != Eigen::Success) + return Principal_curvature(); + + const Eigen::Matrix eig_vals = eigensolver.eigenvalues(); + const Eigen::Matrix eig_vecs = eigensolver.eigenvectors(); + + const typename GT::Vector_3 min_eig_vec(eig_vecs(0, 1), eig_vecs(1, 1), eig_vecs(2, 1)); + const typename GT::Vector_3 max_eig_vec(eig_vecs(0, 0), eig_vecs(1, 0), eig_vecs(2, 0)); + + return Principal_curvature( + (v_mu0 != 0.0) ? -eig_vals[1] / v_mu0 : 0.0, + (v_mu0 != 0.0) ? -eig_vals[0] / v_mu0 : 0.0, + min_eig_vec, + max_eig_vec + ); +} + +template +class Interpolated_corrected_curvatures_computer +{ typedef typename GetGeomTraits::type GT; + typedef typename GT::FT FT; + typedef typename GT::Point_3 Point_3; + typedef typename GT::Vector_3 Vector_3; + + typedef typename GetVertexPointMap::const_type Vertex_position_map; + + typedef dynamic_vertex_property_t Vector_map_tag; + typedef typename boost::property_map::const_type Default_vector_map; + typedef typename internal_np::Lookup_named_param_def::type Vertex_normal_map; + + typedef dynamic_vertex_property_t Scalar_map_tag; + typedef typename boost::property_map::const_type Default_scalar_map; + typedef typename internal_np::Lookup_named_param_def::type Vertex_scalar_curvature_map; + + typedef dynamic_vertex_property_t> Principal_map_tag; + typedef typename boost::property_map::const_type Default_principal_map; + typedef typename internal_np::Lookup_named_param_def::type Vertex_principal_curvature_map; + + + + typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; + typedef typename boost::graph_traits::edge_descriptor Edge_descriptor; typedef typename boost::graph_traits::face_descriptor Face_descriptor; typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; - const typename GT::FT - r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); + typedef typename boost::property_map>::type Face_scalar_measure_map; + typedef typename boost::property_map>>::type Face_anisotropic_measure_map; + +private: + const PolygonMesh& pmesh; + Vertex_position_map vpm; + Vertex_normal_map vnm; + FT ball_radius; + + bool is_mean_curvature_selected; + bool is_gaussian_curvature_selected; + bool is_principal_curvature_selected; + + Vertex_scalar_curvature_map mean_curvature_map, gaussian_curvature_map; + Vertex_principal_curvature_map principal_curvature_map; + + Face_scalar_measure_map mu0_map, mu1_map, mu2_map; + Face_anisotropic_measure_map muXY_map; + + void set_property_maps() { + mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); + mu1_map = get(CGAL::dynamic_face_property_t(), pmesh); + mu2_map = get(CGAL::dynamic_face_property_t(), pmesh); + muXY_map = get(CGAL::dynamic_face_property_t>(), pmesh); + + } + + void set_named_params(const NamedParameters& np = parameters::default_values()) + { + using parameters::choose_parameter; + using parameters::get_parameter; + using parameters::is_default_parameter; - typename GetVertexPointMap::const_type vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), get_const_property_map(CGAL::vertex_point, pmesh)); + vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), + get(Vector_map_tag(), pmesh)); - std::queue bfs_queue; - std::unordered_set bfs_visited; + if (is_default_parameter::value) + compute_vertex_normals(pmesh, vnm, np); - typename GT::Point_3 vp = get(vpm, v); - typename GT::Vector_3 c = typename GT::Vector_3(vp.x(), vp.y(), vp.z()); + const FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1); - typename GT::FT corrected_mu0 = 0; - typename GT::FT corrected_mui = 0; + if (is_mean_curvature_selected) + mean_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_mean_curvature_map), get(CGAL::dynamic_vertex_property_t(), pmesh)); + if (is_gaussian_curvature_selected) + gaussian_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_gaussian_curvature_map), get(CGAL::dynamic_vertex_property_t(), pmesh)); + if (is_principal_curvature_selected) + principal_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvature_map), get(CGAL::dynamic_vertex_property_t>(), pmesh)); - for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { - if (f != boost::graph_traits::null_face()) - { - bfs_queue.push(f); - bfs_visited.insert(f); - } + set_ball_radius(radius); } - while (!bfs_queue.empty()) { - Face_descriptor fi = bfs_queue.front(); - bfs_queue.pop(); - // looping over vertices in face to get point coordinates - std::vector x; - for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) - { - typename GT::Point_3 pi = get(vpm, vi); - x.push_back(typename GT::Vector_3(pi.x(), pi.y(), pi.z())); - } + void set_ball_radius(const FT radius) { + if (radius == 0) + ball_radius = average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; + } + +public: + + Interpolated_corrected_curvatures_computer(const PolygonMesh& pmesh, + bool is_mean_curvature_selected, + bool is_gaussian_curvature_selected, + bool is_principal_curvature_selected, + const NamedParameters& np = parameters::default_values() + ) : + pmesh(pmesh), + is_mean_curvature_selected(is_mean_curvature_selected), + is_gaussian_curvature_selected(is_gaussian_curvature_selected), + is_principal_curvature_selected(is_principal_curvature_selected) + { + if (!is_mean_curvature_selected && !is_gaussian_curvature_selected && !is_principal_curvature_selected) + return; + + set_named_params(); - const typename GT::FT f_ratio = face_in_ball_ratio(x, r, c); + set_property_maps(); - if (f_ratio != 0.0) + compute_selected_curvatures(); + + } + + void interpolated_corrected_all_measures_all_faces() + { + std::vector x; + std::vector u; + + for (Face_descriptor f : faces(pmesh)) { - corrected_mu0 += f_ratio * get(area_fmm, fi); - corrected_mui += f_ratio * get(curvature_fmm, fi); - for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) + for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) { - if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) - { - bfs_queue.push(fj); - bfs_visited.insert(fj); - } + Point_3 p = get(vpm, v); + x.push_back(Vector_3(p.x(), p.y(), p.z())); + u.push_back(get(vnm, v)); } - } - } - put(area_vmm, v, corrected_mu0); - put(curvature_vmm, v, corrected_mui); -} - -template - void expand_interpolated_corrected_anisotropic_measure_vertex(const PolygonMesh& pmesh, - AreaFaceMeasureMap area_fmm, - AnisotropicFaceMeasureMap aniso_fmm, - AreaVertexMeasureMap area_vmm, - AnisotropicVertexMeasureMap aniso_vmm, - const typename boost::graph_traits::vertex_descriptor v, - const NamedParameters& np = parameters::default_values()) -{ - using parameters::choose_parameter; - using parameters::get_parameter; + put(mu0_map, f, interpolated_corrected_area_measure_face(u, x)); - typedef typename GetGeomTraits::type GT; + if (is_mean_curvature_selected) + put(mu1_map, f, interpolated_corrected_mean_curvature_measure_face(u, x)); - typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + if (is_gaussian_curvature_selected) + put(mu2_map, f, interpolated_corrected_gaussian_curvature_measure_face(u, x)); - const typename GT::FT - r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); + if (is_principal_curvature_selected) + put(muXY_map, f, interpolated_corrected_anisotropic_measure_face(u, x)); - typename GetVertexPointMap::const_type - vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), - get_const_property_map(CGAL::vertex_point, pmesh)); + x.clear(); + u.clear(); + } + } + Vertex_measures expand_interpolated_corrected_measure_vertex_no_radius(Vertex_descriptor v) + { + Vertex_measures vertex_curvatures; - std::queue bfs_queue; - std::unordered_set bfs_visited; + for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + vertex_curvatures.area_measure += get(mu0_map, f); - typename GT::Point_3 vp = get(vpm, v); - typename GT::Vector_3 c = typename GT::Vector_3(vp.x(), vp.y(), vp.z()); + if (is_mean_curvature_selected) + vertex_curvatures.mean_curvature_measure += get(mu1_map, f); - typename GT::FT corrected_mu0 = 0; - Eigen::Matrix corrected_muXY = Eigen::Matrix::Zero(); + if (is_gaussian_curvature_selected) + vertex_curvatures.gaussian_curvature_measure += get(mu2_map, f); - for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { - if (f != boost::graph_traits::null_face()) - { - bfs_queue.push(f); - bfs_visited.insert(f); + if (is_principal_curvature_selected) + { + const std::array face_anisotropic_measure = get(muXY_map, f); + for (std::size_t i = 0; i < 3 * 3; i++) + vertex_curvatures.anisotropic_measure[i] += face_anisotropic_measure[i]; + } } + + return vertex_curvatures; } - while (!bfs_queue.empty()) { - Face_descriptor fi = bfs_queue.front(); - bfs_queue.pop(); - // looping over vertices in face to get point coordinates - std::vector x; - for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) - { - typename GT::Point_3 pi = get(vpm, vi); - x.push_back(typename GT::Vector_3(pi.x(), pi.y(), pi.z())); - } + Vertex_measures expand_interpolated_corrected_measure_vertex(Vertex_descriptor v) + { + std::queue bfs_queue; + std::unordered_set bfs_visited; - const typename GT::FT f_ratio = face_in_ball_ratio(x, r, c); + Point_3 vp = get(vpm, v); + Vector_3 c = Vector_3(vp.x(), vp.y(), vp.z()); - if (f_ratio != 0.0) - { - corrected_mu0 += f_ratio * get(area_fmm, fi); + Vertex_measures vertex_curvatures; + + for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + if (f != boost::graph_traits::null_face()) + { + bfs_queue.push(f); + bfs_visited.insert(f); + } + } + while (!bfs_queue.empty()) { + Face_descriptor fi = bfs_queue.front(); + bfs_queue.pop(); - std::array muXY_face = get(aniso_fmm, fi); + // looping over vertices in face to get point coordinates + std::vector x; + for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) + { + Point_3 pi = get(vpm, vi); + x.push_back(Vector_3(pi.x(), pi.y(), pi.z())); + } - for (std::size_t ix = 0; ix < 3; ix++) - for (std::size_t iy = 0; iy < 3; iy++) - corrected_muXY(ix, iy) += f_ratio * muXY_face[ix * 3 + iy]; + const FT f_ratio = face_in_ball_ratio(x, ball_radius, c); - for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) + if (f_ratio != 0.0) { - if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) + vertex_curvatures.area_measure += f_ratio * get(mu0_map, fi); + + if (is_mean_curvature_selected) + vertex_curvatures.mean_curvature_measure += f_ratio * get(mu1_map, fi); + + if (is_gaussian_curvature_selected) + vertex_curvatures.gaussian_curvature_measure += f_ratio * get(mu2_map, fi); + + if (is_principal_curvature_selected) { - bfs_queue.push(fj); - bfs_visited.insert(fj); + const std::array face_anisotropic_measure = get(muXY_map, fi); + for (std::size_t i = 0; i < 3 * 3; i++) + vertex_curvatures.anisotropic_measure[i] += f_ratio * face_anisotropic_measure[i]; + } + + for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) + { + if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) + { + bfs_queue.push(fj); + bfs_visited.insert(fj); + } } } } + return vertex_curvatures; + } + + void compute_selected_curvatures() { + interpolated_corrected_all_measures_all_faces(); + + for (Vertex_descriptor v : vertices(pmesh)) + { + Vertex_measures vertex_curvatures = (ball_radius < 0)? + expand_interpolated_corrected_measure_vertex_no_radius(v) : + expand_interpolated_corrected_measure_vertex(v); + + if (is_mean_curvature_selected) { + vertex_curvatures.area_measure != 0 ? + put(mean_curvature_map, v, 0.5 * vertex_curvatures.mean_curvature_measure / vertex_curvatures.area_measure) : + put(mean_curvature_map, v, 0); + } + + if (is_gaussian_curvature_selected) { + vertex_curvatures.area_measure != 0 ? + put(gaussian_curvature_map, v, vertex_curvatures.gaussian_curvature_measure / vertex_curvatures.area_measure) : + put(gaussian_curvature_map, v, 0); + } + + if (is_principal_curvature_selected) { + const Vector_3 v_normal = get(vnm, v); + const Principal_curvature principal_curvature = principal_curvature_from_anisotropic_measures( + vertex_curvatures.anisotropic_measure, + vertex_curvatures.area_measure, + v_normal + ); + put(principal_curvature_map, v, principal_curvature); + } + } } - put(area_vmm, v, corrected_mu0); - put(aniso_vmm, v, corrected_muXY); -} + + + +}; } // namespace internal @@ -722,6 +1070,7 @@ template::type GT; - - typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::property_map>::const_type Face_measure_map; - - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; - typedef typename boost::property_map>::const_type Vertex_measure_map; - - typename GT::FT - r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); - - if (r == 0) - r = internal::average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; - - Face_measure_map mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); - Face_measure_map mu1_map = get(CGAL::dynamic_face_property_t(), pmesh); - - Vertex_measure_map mu0_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); - Vertex_measure_map mu1_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); - - internal::interpolated_corrected_measure_mesh(pmesh, mu0_map, internal::MU0_AREA_MEASURE, np); - internal::interpolated_corrected_measure_mesh(pmesh, mu1_map, internal::MU1_MEAN_CURVATURE_MEASURE, np); - - for (Vertex_descriptor v : vertices(pmesh)) - { - internal::expand_interpolated_corrected_measure_vertex(pmesh, mu0_map, mu1_map, mu0_expand_map, mu1_expand_map, v, np.ball_radius(r)); - - typename GT::FT v_mu0 = get(mu0_expand_map, v); - if (v_mu0 != 0.0) - put(vcm, v, 0.5 * get(mu1_expand_map, v) / v_mu0); - else - put(vcm, v, 0); - } + internal::Interpolated_corrected_curvatures_computer(pmesh, true, false, false, np.vertex_mean_curvature_map(vcm)); } /** @@ -822,6 +1134,7 @@ template @@ -829,44 +1142,7 @@ template::type GT; - - typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::property_map>::const_type Face_measure_map; - - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; - typedef typename boost::property_map>::const_type Vertex_measure_map; - - typename GT::FT - r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); - - if (r == 0) - r = internal::average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; - - Face_measure_map mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); - Face_measure_map mu2_map = get(CGAL::dynamic_face_property_t(), pmesh); - - Vertex_measure_map mu0_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); - Vertex_measure_map mu2_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); - - internal::interpolated_corrected_measure_mesh(pmesh, mu0_map, internal::MU0_AREA_MEASURE, np); - internal::interpolated_corrected_measure_mesh(pmesh, mu2_map, internal::MU2_GAUSSIAN_CURVATURE_MEASURE, np); - - for (Vertex_descriptor v : vertices(pmesh)) - { - internal::expand_interpolated_corrected_measure_vertex(pmesh, mu0_map, mu2_map, mu0_expand_map, mu2_expand_map, v, np.ball_radius(r)); - - typename GT::FT v_mu0 = get(mu0_expand_map, v); - if(v_mu0 != 0.0) - put(vcm, v, get(mu2_expand_map, v) / v_mu0); - else - put(vcm, v, 0); - } + internal::Interpolated_corrected_curvatures_computer(pmesh, false, true, false, np.vertex_gaussian_curvature_map(vcm)); } /** @@ -922,6 +1198,7 @@ template @@ -929,98 +1206,83 @@ template::type GT; - - typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::property_map>::const_type Face_scalar_measure_map; - typedef typename boost::property_map>>::const_type Face_array_measure_map; - - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; - typedef typename boost::property_map>::const_type Vertex_scalar_measure_map; - typedef typename boost::property_map>>::const_type Vertex_matrix_measure_map; - - typedef dynamic_vertex_property_t Vector_map_tag; - typedef typename boost::property_map::const_type Default_vector_map; - typedef typename internal_np::Lookup_named_param_def::type VertexNormalMap; - - using parameters::choose_parameter; - using parameters::get_parameter; - using parameters::is_default_parameter; - - typename GetVertexPointMap::const_type - vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), - get_const_property_map(CGAL::vertex_point, pmesh)); - - VertexNormalMap vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), - get(Vector_map_tag(), pmesh)); - - typename GT::FT - r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); - - if (r == 0) - r = internal::average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; - - if (is_default_parameter::value) - compute_vertex_normals(pmesh, vnm, np); - - Face_scalar_measure_map mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); - Face_array_measure_map muXY_map = get(CGAL::dynamic_face_property_t>(), pmesh); - - Vertex_scalar_measure_map mu0_expand_map = get(CGAL::dynamic_vertex_property_t(), pmesh); - Vertex_matrix_measure_map muXY_expand_map = get(CGAL::dynamic_vertex_property_t>(), pmesh); - - internal::interpolated_corrected_measure_mesh(pmesh, mu0_map, internal::MU0_AREA_MEASURE, np); - internal::interpolated_corrected_anisotropic_measure_mesh(pmesh, muXY_map, np); + internal::Interpolated_corrected_curvatures_computer(pmesh, false, false, true, np.vertex_principal_curvature_map(vcm)); +} - for (Vertex_descriptor v : vertices(pmesh)) +// TODO: DOC +/** +* \ingroup PMP_corrected_curvatures_grp +* +* Computes the interpolated corrected curvatures across the mesh, based on the provided property maps. +* By providing mean, gaussian and/or principal curvature property maps, the user +* can choose which curvatures to compute. +* +* @tparam PolygonMesh a model of `FaceListGraph`. +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". +* +* @param pmesh the 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 `ReadablePropertyMap` 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` must be available in `PolygonMesh`.} +* \cgalParamNEnd +* +* \cgalParamNBegin{vertex_normal_map} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamType{a class model of `ReadablePropertyMap` with +* `boost::graph_traits::%Vertex_descriptor` +* as key type and `%Vector_3` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, vertex normals will be +* computed using compute_vertex_normals()} +* \cgalParamNEnd +* +* \cgalParamNBegin{ball_radius} +* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures +* by summing measures of faces inside a ball of this radius centered at the +* vertex expanded from. The summed face measures are weighted by their +* inclusion ratio inside this ball} +* \cgalParamType{`GT::FT`} +* \cgalParamDefault{`-1`} +* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of +* measures on faces around the vertex} +* \cgalParamNEnd +* +* \cgalNamedParamsEnd +* +* @see `interpolated_corrected_mean_curvature()` +* @see `interpolated_corrected_gaussian_curvature()` +* @see `interpolated_corrected_principal_curvatures()` +*/ +template + void interpolated_corrected_curvatures(const PolygonMesh& pmesh, + bool is_mean_curvature_selected, + bool is_gaussian_curvature_selected, + bool is_principal_curvature_selected, + const NamedParameters& np = parameters::default_values()) +{ + if (is_mean_curvature_selected || is_gaussian_curvature_selected || is_principal_curvature_selected) { - internal::expand_interpolated_corrected_anisotropic_measure_vertex(pmesh, mu0_map, muXY_map, mu0_expand_map, muXY_expand_map, v, np.ball_radius(r)); - - typename GT::FT v_mu0 = get(mu0_expand_map, v); - Eigen::Matrix v_muXY = get(muXY_expand_map, v); - - typename GT::Vector_3 u_GT = get(vnm, v); - - Eigen::Matrix u(u_GT.x(), u_GT.y(), u_GT.z()); - - const typename GT::FT K = 1000 * v_mu0; - - v_muXY = 0.5 * (v_muXY + v_muXY.transpose()) + K * u * u.transpose(); - - Eigen::SelfAdjointEigenSolver> eigensolver; - - eigensolver.computeDirect(v_muXY); - - if (eigensolver.info() != Eigen::Success) - { - put(vcm, v, std::make_tuple( - 0, - 0, - typename GT::Vector_3(0, 0, 0), - typename GT::Vector_3(0, 0, 0))); - continue; - } - - const Eigen::Matrix eig_vals = eigensolver.eigenvalues(); - const Eigen::Matrix eig_vecs = eigensolver.eigenvectors(); - - const typename GT::Vector_3 min_eig_vec(eig_vecs(0, 1), eig_vecs(1, 1), eig_vecs(2, 1)); - const typename GT::Vector_3 max_eig_vec(eig_vecs(0, 0), eig_vecs(1, 0), eig_vecs(2, 0)); - - put(vcm, v, std::make_tuple( - (v_mu0 != 0.0) ? -eig_vals[1] / v_mu0 : 0.0, - (v_mu0 != 0.0) ? -eig_vals[0] / v_mu0 : 0.0, - min_eig_vec, - max_eig_vec)); + internal::Interpolated_corrected_curvatures_computer( + pmesh, + is_mean_curvature_selected, + is_gaussian_curvature_selected, + is_principal_curvature_selected, + np + ); } } + } // namespace Polygon_mesh_processing } // namespace CGAL From 2dcb2939b9011cb3e6fa0b199d1ac226f996a0e8 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 13 Nov 2022 13:32:38 +0200 Subject: [PATCH 073/161] compute multiple curvatures at same time + no radius handled --- ...erpolated_corrected_curvatures_example.cpp | 8 ++ ...nterpolated_corrected_curvature_measures.h | 83 ++++++++----------- 2 files changed, 42 insertions(+), 49 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp index da6e9d2252f0..a2fd69fff1a7 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp @@ -76,6 +76,14 @@ int main(int argc, char* argv[]) principal_curvature_map ); + PMP::interpolated_corrected_curvatures( + g1, + CGAL::parameters::ball_radius(0) + .vertex_mean_curvature_map(mean_curvature_map) + .vertex_gaussian_curvature_map(gaussian_curvature_map) + .vertex_principal_curvature_map(principal_curvature_map) + ); + for (vertex_descriptor v : vertices(g1)) { auto PC = principal_curvature_map[v]; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index c4a9154c1aa2..b83bf0e70dff 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -753,6 +753,11 @@ class Interpolated_corrected_curvatures_computer typedef typename GT::Point_3 Point_3; typedef typename GT::Vector_3 Vector_3; + typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; + typedef typename boost::graph_traits::edge_descriptor Edge_descriptor; + typedef typename boost::graph_traits::face_descriptor Face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename GetVertexPointMap::const_type Vertex_position_map; typedef dynamic_vertex_property_t Vector_map_tag; @@ -761,29 +766,24 @@ class Interpolated_corrected_curvatures_computer NamedParameters, Default_vector_map>::type Vertex_normal_map; - typedef dynamic_vertex_property_t Scalar_map_tag; - typedef typename boost::property_map::const_type Default_scalar_map; + typedef Constant_property_map Default_scalar_map; typedef typename internal_np::Lookup_named_param_def::type Vertex_scalar_curvature_map; + Default_scalar_map>::type Vertex_mean_curvature_map; - typedef dynamic_vertex_property_t> Principal_map_tag; - typedef typename boost::property_map::const_type Default_principal_map; + typedef typename internal_np::Lookup_named_param_def::type Vertex_gaussian_curvature_map; + + typedef Constant_property_map> Default_principal_map; typedef typename internal_np::Lookup_named_param_def::type Vertex_principal_curvature_map; - - - typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; - typedef typename boost::graph_traits::edge_descriptor Edge_descriptor; - typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; - typedef typename boost::property_map>::type Face_scalar_measure_map; + CGAL::dynamic_face_property_t>::const_type Face_scalar_measure_map; typedef typename boost::property_map>>::type Face_anisotropic_measure_map; + CGAL::dynamic_face_property_t>>::const_type Face_anisotropic_measure_map; private: const PolygonMesh& pmesh; @@ -795,7 +795,8 @@ class Interpolated_corrected_curvatures_computer bool is_gaussian_curvature_selected; bool is_principal_curvature_selected; - Vertex_scalar_curvature_map mean_curvature_map, gaussian_curvature_map; + Vertex_mean_curvature_map mean_curvature_map; + Vertex_gaussian_curvature_map gaussian_curvature_map; Vertex_principal_curvature_map principal_curvature_map; Face_scalar_measure_map mu0_map, mu1_map, mu2_map; @@ -809,7 +810,7 @@ class Interpolated_corrected_curvatures_computer } - void set_named_params(const NamedParameters& np = parameters::default_values()) + void set_named_params(const NamedParameters& np) { using parameters::choose_parameter; using parameters::get_parameter; @@ -826,12 +827,15 @@ class Interpolated_corrected_curvatures_computer const FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1); - if (is_mean_curvature_selected) - mean_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_mean_curvature_map), get(CGAL::dynamic_vertex_property_t(), pmesh)); - if (is_gaussian_curvature_selected) - gaussian_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_gaussian_curvature_map), get(CGAL::dynamic_vertex_property_t(), pmesh)); - if (is_principal_curvature_selected) - principal_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvature_map), get(CGAL::dynamic_vertex_property_t>(), pmesh)); + is_mean_curvature_selected = !is_default_parameter::value; + is_gaussian_curvature_selected = !is_default_parameter::value; + is_principal_curvature_selected = !is_default_parameter::value; + + mean_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_mean_curvature_map), Default_scalar_map()); + gaussian_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_gaussian_curvature_map), Default_scalar_map()); + principal_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvature_map), Default_principal_map()); + + std::cout << is_mean_curvature_selected << is_gaussian_curvature_selected << is_principal_curvature_selected << std::endl; set_ball_radius(radius); } @@ -839,25 +843,18 @@ class Interpolated_corrected_curvatures_computer void set_ball_radius(const FT radius) { if (radius == 0) ball_radius = average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; + else + ball_radius = radius; } public: Interpolated_corrected_curvatures_computer(const PolygonMesh& pmesh, - bool is_mean_curvature_selected, - bool is_gaussian_curvature_selected, - bool is_principal_curvature_selected, const NamedParameters& np = parameters::default_values() ) : - pmesh(pmesh), - is_mean_curvature_selected(is_mean_curvature_selected), - is_gaussian_curvature_selected(is_gaussian_curvature_selected), - is_principal_curvature_selected(is_principal_curvature_selected) + pmesh(pmesh) { - if (!is_mean_curvature_selected && !is_gaussian_curvature_selected && !is_principal_curvature_selected) - return; - - set_named_params(); + set_named_params(np); set_property_maps(); @@ -1079,7 +1076,7 @@ template(pmesh, true, false, false, np.vertex_mean_curvature_map(vcm)); + internal::Interpolated_corrected_curvatures_computer(pmesh, np.vertex_mean_curvature_map(vcm)); } /** @@ -1142,7 +1139,7 @@ template(pmesh, false, true, false, np.vertex_gaussian_curvature_map(vcm)); + internal::Interpolated_corrected_curvatures_computer(pmesh, np.vertex_gaussian_curvature_map(vcm)); } /** @@ -1206,7 +1203,7 @@ template(pmesh, false, false, true, np.vertex_principal_curvature_map(vcm)); + internal::Interpolated_corrected_curvatures_computer(pmesh, np.vertex_principal_curvature_map(vcm)); } // TODO: DOC @@ -1265,21 +1262,9 @@ template void interpolated_corrected_curvatures(const PolygonMesh& pmesh, - bool is_mean_curvature_selected, - bool is_gaussian_curvature_selected, - bool is_principal_curvature_selected, const NamedParameters& np = parameters::default_values()) { - if (is_mean_curvature_selected || is_gaussian_curvature_selected || is_principal_curvature_selected) - { - internal::Interpolated_corrected_curvatures_computer( - pmesh, - is_mean_curvature_selected, - is_gaussian_curvature_selected, - is_principal_curvature_selected, - np - ); - } + internal::Interpolated_corrected_curvatures_computer(pmesh, np); } From dbd18ed101a6e407f05201468bd04c814778f060 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 13 Nov 2022 13:34:18 +0200 Subject: [PATCH 074/161] trailing whitespaces --- .../interpolated_corrected_curvature_measures.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index b83bf0e70dff..183f823e8483 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -848,7 +848,7 @@ class Interpolated_corrected_curvatures_computer } public: - + Interpolated_corrected_curvatures_computer(const PolygonMesh& pmesh, const NamedParameters& np = parameters::default_values() ) : @@ -911,7 +911,7 @@ class Interpolated_corrected_curvatures_computer vertex_curvatures.anisotropic_measure[i] += face_anisotropic_measure[i]; } } - + return vertex_curvatures; } @@ -981,8 +981,8 @@ class Interpolated_corrected_curvatures_computer for (Vertex_descriptor v : vertices(pmesh)) { - Vertex_measures vertex_curvatures = (ball_radius < 0)? - expand_interpolated_corrected_measure_vertex_no_radius(v) : + Vertex_measures vertex_curvatures = (ball_radius < 0)? + expand_interpolated_corrected_measure_vertex_no_radius(v) : expand_interpolated_corrected_measure_vertex(v); if (is_mean_curvature_selected) { @@ -1241,7 +1241,7 @@ template Date: Sun, 13 Nov 2022 17:59:01 +0200 Subject: [PATCH 075/161] minor fix --- ...nterpolated_corrected_curvature_measures.h | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 183f823e8483..1a0ef9891b00 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -835,8 +835,6 @@ class Interpolated_corrected_curvatures_computer gaussian_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_gaussian_curvature_map), Default_scalar_map()); principal_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvature_map), Default_principal_map()); - std::cout << is_mean_curvature_selected << is_gaussian_curvature_selected << is_principal_curvature_selected << std::endl; - set_ball_radius(radius); } @@ -856,12 +854,16 @@ class Interpolated_corrected_curvatures_computer { set_named_params(np); - set_property_maps(); - - compute_selected_curvatures(); + if (is_mean_curvature_selected || is_gaussian_curvature_selected || is_principal_curvature_selected) + { + set_property_maps(); + compute_selected_curvatures(); + } } +private: + void interpolated_corrected_all_measures_all_faces() { std::vector x; @@ -1073,10 +1075,10 @@ class Interpolated_corrected_curvatures_computer template void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, - VertexCurvatureMap vcm, - const NamedParameters& np = parameters::default_values()) + VertexCurvatureMap& vcm, + NamedParameters& np = parameters::default_values()) { - internal::Interpolated_corrected_curvatures_computer(pmesh, np.vertex_mean_curvature_map(vcm)); + interpolated_corrected_curvatures(pmesh, np.vertex_mean_curvature_map(vcm)); } /** @@ -1136,10 +1138,10 @@ template void interpolated_corrected_gaussian_curvature(const PolygonMesh& pmesh, - VertexCurvatureMap vcm, - const NamedParameters& np = parameters::default_values()) + VertexCurvatureMap& vcm, + NamedParameters& np = parameters::default_values()) { - internal::Interpolated_corrected_curvatures_computer(pmesh, np.vertex_gaussian_curvature_map(vcm)); + interpolated_corrected_curvatures(pmesh, np.vertex_gaussian_curvature_map(vcm)); } /** @@ -1200,10 +1202,10 @@ template void interpolated_corrected_principal_curvatures(const PolygonMesh& pmesh, - VertexCurvatureMap vcm, - const NamedParameters& np = parameters::default_values()) + VertexCurvatureMap& vcm, + NamedParameters& np = parameters::default_values()) { - internal::Interpolated_corrected_curvatures_computer(pmesh, np.vertex_principal_curvature_map(vcm)); + interpolated_corrected_curvatures(pmesh, np.vertex_principal_curvature_map(vcm)); } // TODO: DOC From 6ca73315496d290e77c67d5c8c10930801364adc Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 14 Nov 2022 11:26:13 +0200 Subject: [PATCH 076/161] fixes, doc, examples, general cleanup --- .../PackageDescription.txt | 2 + .../Polygon_mesh_processing.txt | 19 +- ...erpolated_corrected_curvatures_example.cpp | 68 ++-- ...orrected_curvatures_polyhedron_example.cpp | 61 ++-- ...nterpolated_corrected_curvature_measures.h | 343 +++--------------- 5 files changed, 136 insertions(+), 357 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index eae47c610011..93f25c89f1fb 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -204,6 +204,8 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage. - `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` +- `CGAL::Polygon_mesh_processing::Principal_curvature` \cgalCRPSection{Normal Computation Functions} - `CGAL::Polygon_mesh_processing::compute_face_normal()` 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 8f222497a527..7932051ec000 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 @@ -879,7 +879,10 @@ not provide storage for the normals. **************************************** \section PMPICC Computing Curvatures -This package provides methods to compute curvatures on polygonal meshes based on #PAPER#. +This package provides methods to compute curvatures on polygonal meshes based on + + Interpolated corrected curvature measures for polygonal surfaces +. This includes mean curvature, gaussian curvature, principal curvatures and directions. These can be computed on triangle meshes, quad meshes, and meshes with n-gon faces. The algorithms used prove to work well in general. Also, on meshes with noise @@ -893,25 +896,29 @@ These computations are performed using : - `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` + +Where it is recommended to use the last function for computing multiple curvatures (example: mean and gaussian) +as the implementation performs the shared computations only once, making it more efficient. \cgalFigureRef{icc_diff_radius} shows how the mean curvature changes depending on the ball_radius named parameter which can be set to a value > 0 to get a smoother -distribution of valuesa and "diffuse" the extreme values of curvatures across the mesh. +distribution of values and "diffuse" the extreme values of curvatures across the mesh. \cgalFigureAnchor{icc_diff_radius}
\cgalFigureCaptionBegin{icc_diff_radius} -The mean curvature distrubution on a bear mesh with different values for the expanding ball radius +The mean curvature distribution on a bear mesh with different values for the expanding ball radius \cgalFigureCaptionEnd Property maps are used to record the computed curvatures as shown in examples. \subsection ICCExample Interpolated Corrected Curvature Examples -Property maps are an API introduced in the boost library that allows to -associate values to keys. In the following examples, for each proberty map, we associate +Property maps are an API introduced in the boost library that allows associating +values to keys. In the following examples, for each property map, we associate a curvature value to each vertex. \subsubsection ICCExampleSM Interpolated Corrected Curvature on a Surface Mesh. @@ -926,7 +933,7 @@ and store them in property maps provided by the class `Surface_mesh`. The following example illustrates how to compute the curvatures on vertices -and store them in unordered (or ordered) maps as the class `Polyhedron_3` does +and store them in dynamic property maps as the class `Polyhedron_3` does not provide storage for the curvatures. \cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp} diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp index a2fd69fff1a7..ffb86d94795c 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp @@ -10,19 +10,19 @@ namespace PMP = CGAL::Polygon_mesh_processing; -typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic_Kernel; -typedef CGAL::Surface_mesh Surface_Mesh; +typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic_kernel; +typedef CGAL::Surface_mesh Surface_Mesh; typedef boost::graph_traits::face_descriptor face_descriptor; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; int main(int argc, char* argv[]) { - Surface_Mesh g1; + Surface_Mesh smesh; const std::string filename = (argc>1) ? argv[1] : CGAL::data_file_path("meshes/small_bunny.obj"); - if(!CGAL::IO::read_polygon_mesh(filename, g1)) + if(!CGAL::IO::read_polygon_mesh(filename, smesh)) { std::cerr << "Invalid input file." << std::endl; return EXIT_FAILURE; @@ -30,65 +30,67 @@ int main(int argc, char* argv[]) // creating and tying surface mesh property maps for curvatures (with defaults = 0) bool created = false; - Surface_Mesh::Property_map mean_curvature_map, gaussian_curvature_map; - boost::tie(mean_curvature_map, created) = g1.add_property_map("v:mean_curvature_map", 0); + Surface_Mesh::Property_map mean_curvature_map, gaussian_curvature_map; + boost::tie(mean_curvature_map, created) = smesh.add_property_map("v:mean_curvature_map", 0); assert(created); - boost::tie(gaussian_curvature_map, created) = g1.add_property_map("v:gaussian_curvature_map", 0); + boost::tie(gaussian_curvature_map, created) = smesh.add_property_map("v:gaussian_curvature_map", 0); assert(created); // we use a tuple of 2 scalar values and 2 vectors for principal curvatures and directions - Surface_Mesh::Property_map> principal_curvature_map; + Surface_Mesh::Property_map> principal_curvature_map; - boost::tie(principal_curvature_map, created) = g1.add_property_map> + boost::tie(principal_curvature_map, created) = smesh.add_property_map> ("v:principal_curvature_map", { 0, 0, - Epic_Kernel::Vector_3 (0,0,0), - Epic_Kernel::Vector_3 (0,0,0)}); + Epic_kernel::Vector_3(0,0,0), + Epic_kernel::Vector_3(0,0,0) }); assert(created); + // user can call these fucntions to compute a specfic curvature type on each vertex. PMP::interpolated_corrected_mean_curvature( - g1, + smesh, mean_curvature_map ); + PMP::interpolated_corrected_gaussian_curvature( + smesh, + gaussian_curvature_map + ); + + PMP::interpolated_corrected_principal_curvatures( + smesh, + principal_curvature_map + ); + // uncomment this to compute a curvature while specifying named parameters // Example: an expansion ball radius of 0.5 and a vertex normals map (does not have to depend on positions) - /*Surface_Mesh::Property_map vnm; - boost::tie(vnm, created) = g1.add_property_map( - "v:vnm", Epic_Kernel::Vector_3(0, 0, 0) + /*Surface_Mesh::Property_map vnm; + boost::tie(vnm, created) = smesh.add_property_map( + "v:vnm", Epic_kernel::Vector_3(0, 0, 0) ); assert(created); PMP::interpolated_corrected_mean_curvature( - g1, + smesh, mean_curvature_map, CGAL::parameters::ball_radius(0.5).vertex_normal_map(vnm) );*/ - PMP::interpolated_corrected_gaussian_curvature( - g1, - gaussian_curvature_map - ); - PMP::interpolated_corrected_principal_curvatures( - g1, - principal_curvature_map - ); - + // This function can be used to compute multiple curvature types by specifiying them as named parameters + // This is more efficient than computing each one separately (shared computations). PMP::interpolated_corrected_curvatures( - g1, - CGAL::parameters::ball_radius(0) - .vertex_mean_curvature_map(mean_curvature_map) - .vertex_gaussian_curvature_map(gaussian_curvature_map) + smesh, + CGAL::parameters::vertex_mean_curvature_map(mean_curvature_map) .vertex_principal_curvature_map(principal_curvature_map) ); - for (vertex_descriptor v : vertices(g1)) + for (vertex_descriptor v : vertices(smesh)) { auto PC = principal_curvature_map[v]; - std::cout << v.idx() << ": HC = " << mean_curvature_map[v] - << ", GC = " << gaussian_curvature_map[v] << "\n" - << ", PC = [ " << PC.min_curvature << " , " << PC.max_curvature << " ]\n"; + std::cout << v.idx() << ": HC = " << mean_curvature_map[v] + << ", GC = " << gaussian_curvature_map[v] << "\n" + << ", PC = [ " << PC.min_curvature << " , " << PC.max_curvature << " ]\n"; } } diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp index 43aff72dec2b..207625fd8485 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp @@ -11,35 +11,43 @@ namespace PMP = CGAL::Polygon_mesh_processing; -typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic_Kernel; -typedef CGAL::Polyhedron_3 Polyhedron; +typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic_kernel; +typedef CGAL::Polyhedron_3 Polyhedron; typedef boost::graph_traits::face_descriptor face_descriptor; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; int main(int argc, char* argv[]) { - Polyhedron g1; + Polyhedron polyhedron; const std::string filename = (argc>1) ? argv[1] : CGAL::data_file_path("meshes/small_bunny.obj"); - if(!CGAL::IO::read_polygon_mesh(filename, g1)) + if(!CGAL::IO::read_polygon_mesh(filename, polyhedron)) { std::cerr << "Invalid input file." << std::endl; return EXIT_FAILURE; } - std::unordered_map mean_curvature_map, gaussian_curvature_map; - std::unordered_map> principal_curvature_map; + boost::property_map>::type + mean_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron), + gaussian_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron); + boost::property_map>>::type + principal_curvature_map = get(CGAL::dynamic_vertex_property_t< PMP::Principal_curvature>(), polyhedron); PMP::interpolated_corrected_mean_curvature( - g1, - boost::make_assoc_property_map(mean_curvature_map) + polyhedron, + mean_curvature_map + ); + + PMP::interpolated_corrected_gaussian_curvature( + polyhedron, + gaussian_curvature_map + ); + + PMP::interpolated_corrected_principal_curvatures( + polyhedron, + principal_curvature_map ); // uncomment this to compute a curvature while specifying named parameters @@ -48,27 +56,26 @@ int main(int argc, char* argv[]) /*std::unordered_map vnm; PMP::interpolated_corrected_mean_curvature( - g1, - boost::make_assoc_property_map(mean_curvature_map), + polyhedron, + mean_curvature_map, CGAL::parameters::ball_radius(0.5).vertex_normal_map(boost::make_assoc_property_map(vnm)) );*/ - PMP::interpolated_corrected_gaussian_curvature( - g1, - boost::make_assoc_property_map(gaussian_curvature_map) - ); - PMP::interpolated_corrected_principal_curvatures( - g1, - boost::make_assoc_property_map(principal_curvature_map) + // This function can be used to compute multiple curvature types by specifiying them as named parameters + // This is more efficient than computing each one separately (shared computations). + PMP::interpolated_corrected_curvatures( + polyhedron, + CGAL::parameters::vertex_mean_curvature_map(mean_curvature_map) + .vertex_principal_curvature_map(principal_curvature_map) ); int i = 0; - for (vertex_descriptor v : vertices(g1)) + for (vertex_descriptor v : vertices(polyhedron)) { - auto PC = principal_curvature_map[v]; - std::cout << i << ": HC = " << mean_curvature_map[v] - << ", GC = " << gaussian_curvature_map[v] << "\n" - << ", PC = [ " << std::get<0>(PC) << " , " << std::get<1>(PC) << " ]\n"; + auto PC = get(principal_curvature_map, v); + std::cout << i << ": HC = " << get(mean_curvature_map, v) + << ", GC = " << get(gaussian_curvature_map, v) << "\n" + << ", PC = [ " << PC.min_curvature << " , " << PC.max_curvature << " ]\n"; i++; } } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 1a0ef9891b00..0ed47db00584 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -35,11 +35,26 @@ namespace CGAL { namespace Polygon_mesh_processing { +/** +* \ingroup PMP_corrected_curvatures_grp +* +* \brief a struct for storing principal curvatures and directions. +* +* @tparam GT is the geometric traits class. +*/ template struct Principal_curvature { + + /// min curvature magnitude typename GT::FT min_curvature; + + /// max curvature magnitude typename GT::FT max_curvature; + + /// min curvature direction vector typename GT::Vector_3 min_direction; + + /// max curvature direction vector typename GT::Vector_3 max_direction; Principal_curvature() { @@ -373,138 +388,13 @@ std::array interpolated_corrected_anisotropic_measure_fa return muXY; } -//template -// void -// interpolated_corrected_measure_mesh(const PolygonMesh& pmesh, -// FaceMeasureMap fmm, -// const Curvature_measure_index mu_i, -// const NamedParameters& np = parameters::default_values()) -//{ -// -// typedef typename GetGeomTraits::type GT; -// -// typedef dynamic_vertex_property_t Vector_map_tag; -// typedef typename boost::property_map::const_type Default_vector_map; -// typedef typename internal_np::Lookup_named_param_def::type Vertex_normal_map; -// -// using parameters::choose_parameter; -// using parameters::get_parameter; -// using parameters::is_default_parameter; -// -// typedef typename boost::graph_traits::face_descriptor Face_descriptor; -// typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; -// typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; -// -// typename GetVertexPointMap::const_type -// vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), -// get_const_property_map(CGAL::vertex_point, pmesh)); -// -// Vertex_normal_map vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), -// get(Vector_map_tag(), pmesh)); -// -// if (is_default_parameter::value) -// compute_vertex_normals(pmesh, vnm, np); -// -// std::function -// &, const std::vector&)> -// iccm_function; -// switch (mu_i) -// { -// case MU0_AREA_MEASURE: -// iccm_function = &interpolated_corrected_area_measure_face; -// break; -// case MU1_MEAN_CURVATURE_MEASURE: -// iccm_function = &interpolated_corrected_mean_curvature_measure_face; -// break; -// case MU2_GAUSSIAN_CURVATURE_MEASURE: -// iccm_function = &interpolated_corrected_gaussian_curvature_measure_face; -// break; -// } -// -// std::vector x; -// std::vector u; -// -// for (Face_descriptor f : faces(pmesh)) -// { -// -// for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) -// { -// typename GT::Point_3 p = get(vpm, v); -// x.push_back(typename GT::Vector_3(p.x(), p.y(), p.z())); -// u.push_back(get(vnm, v)); -// } -// -// put(fmm, f, iccm_function(u, x)); -// x.clear(); -// u.clear(); -// } -//} -// -//template -// void -// interpolated_corrected_anisotropic_measure_mesh(const PolygonMesh& pmesh, -// FaceMeasureMap fmm, -// const NamedParameters& np = parameters::default_values()) -//{ -// -// typedef typename GetGeomTraits::type GT; -// -// typedef dynamic_vertex_property_t Vector_map_tag; -// typedef typename boost::property_map::const_type Default_vector_map; -// typedef typename internal_np::Lookup_named_param_def::type VertexNormalMap; -// -// using parameters::choose_parameter; -// using parameters::get_parameter; -// using parameters::is_default_parameter; -// -// typedef typename boost::graph_traits::face_descriptor Face_descriptor; -// typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; -// typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; -// -// typename GetVertexPointMap::const_type -// vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), -// get_const_property_map(CGAL::vertex_point, pmesh)); -// -// VertexNormalMap vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), -// get(Vector_map_tag(), pmesh)); -// -// if (is_default_parameter::value) -// compute_vertex_normals(pmesh, vnm, np); -// -// std::vector x; -// std::vector u; -// -// for (Face_descriptor f : faces(pmesh)) -// { -// -// for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) -// { -// typename GT::Point_3 p = get(vpm, v); -// x.push_back(typename GT::Vector_3(p.x(), p.y(), p.z())); -// u.push_back(get(vnm, v)); -// } -// -// put(fmm, f, interpolated_corrected_anisotropic_measure_face(u, x)); -// x.clear(); -// u.clear(); -// -// } -//} - -// //template -//typename GT::FT triangle_in_ball_ratio_1(const typename GT::Vector_3 x1, -// const typename GT::Vector_3 x2, -// const typename GT::Vector_3 x3, -// const typename GT::FT r, -// const typename GT::Vector_3 c, -// const std::size_t res = 3) +//typename GT::FT triangle_in_ball_ratio(const typename GT::Vector_3 x1, +// const typename GT::Vector_3 x2, +// const typename GT::Vector_3 x3, +// const typename GT::FT r, +// const typename GT::Vector_3 c, +// const std::size_t res = 3) //{ // const typename GT::FT R = r * r; // const typename GT::FT acc = 1.0 / res; @@ -549,162 +439,6 @@ typename GT::FT face_in_ball_ratio(const std::vector& x, return (r - d_min) / (d_max - d_min); } -//template -// void expand_interpolated_corrected_measure_vertex(const PolygonMesh& pmesh, -// FaceMeasureMap area_fmm, -// FaceMeasureMap curvature_fmm, -// VertexMeasureMap area_vmm, -// VertexMeasureMap curvature_vmm, -// const typename boost::graph_traits::vertex_descriptor v, -// const NamedParameters& np = parameters::default_values()) -//{ -// using parameters::choose_parameter; -// using parameters::get_parameter; -// -// typedef typename GetGeomTraits::type GT; -// -// typedef typename boost::graph_traits::face_descriptor Face_descriptor; -// typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; -// -// const typename GT::FT -// r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); -// -// typename GetVertexPointMap::const_type -// vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), -// get_const_property_map(CGAL::vertex_point, pmesh)); -// -// -// std::queue bfs_queue; -// std::unordered_set bfs_visited; -// -// typename GT::Point_3 vp = get(vpm, v); -// typename GT::Vector_3 c = typename GT::Vector_3(vp.x(), vp.y(), vp.z()); -// -// typename GT::FT corrected_mu0 = 0; -// typename GT::FT corrected_mui = 0; -// -// for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { -// if (f != boost::graph_traits::null_face()) -// { -// bfs_queue.push(f); -// bfs_visited.insert(f); -// } -// } -// while (!bfs_queue.empty()) { -// Face_descriptor fi = bfs_queue.front(); -// bfs_queue.pop(); -// -// // looping over vertices in face to get point coordinates -// std::vector x; -// for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) -// { -// typename GT::Point_3 pi = get(vpm, vi); -// x.push_back(typename GT::Vector_3(pi.x(), pi.y(), pi.z())); -// } -// -// const typename GT::FT f_ratio = face_in_ball_ratio(x, r, c); -// -// if (f_ratio != 0.0) -// { -// corrected_mu0 += f_ratio * get(area_fmm, fi); -// corrected_mui += f_ratio * get(curvature_fmm, fi); -// for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) -// { -// if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) -// { -// bfs_queue.push(fj); -// bfs_visited.insert(fj); -// } -// } -// } -// } -// put(area_vmm, v, corrected_mu0); -// put(curvature_vmm, v, corrected_mui); -//} -// -//template -// void expand_interpolated_corrected_anisotropic_measure_vertex(const PolygonMesh& pmesh, -// AreaFaceMeasureMap area_fmm, -// AnisotropicFaceMeasureMap aniso_fmm, -// AreaVertexMeasureMap area_vmm, -// AnisotropicVertexMeasureMap aniso_vmm, -// const typename boost::graph_traits::vertex_descriptor v, -// const NamedParameters& np = parameters::default_values()) -//{ -// using parameters::choose_parameter; -// using parameters::get_parameter; -// -// typedef typename GetGeomTraits::type GT; -// -// typedef typename boost::graph_traits::face_descriptor Face_descriptor; -// typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; -// -// const typename GT::FT -// r = choose_parameter(get_parameter(np, internal_np::ball_radius), 0); -// -// typename GetVertexPointMap::const_type -// vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), -// get_const_property_map(CGAL::vertex_point, pmesh)); -// -// -// std::queue bfs_queue; -// std::unordered_set bfs_visited; -// -// typename GT::Point_3 vp = get(vpm, v); -// typename GT::Vector_3 c = typename GT::Vector_3(vp.x(), vp.y(), vp.z()); -// -// typename GT::FT corrected_mu0 = 0; -// Eigen::Matrix corrected_muXY = Eigen::Matrix::Zero(); -// -// for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { -// if (f != boost::graph_traits::null_face()) -// { -// bfs_queue.push(f); -// bfs_visited.insert(f); -// } -// } -// while (!bfs_queue.empty()) { -// Face_descriptor fi = bfs_queue.front(); -// bfs_queue.pop(); -// -// // looping over vertices in face to get point coordinates -// std::vector x; -// for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) -// { -// typename GT::Point_3 pi = get(vpm, vi); -// x.push_back(typename GT::Vector_3(pi.x(), pi.y(), pi.z())); -// } -// -// const typename GT::FT f_ratio = face_in_ball_ratio(x, r, c); -// -// if (f_ratio != 0.0) -// { -// corrected_mu0 += f_ratio * get(area_fmm, fi); -// -// std::array muXY_face = get(aniso_fmm, fi); -// -// for (std::size_t ix = 0; ix < 3; ix++) -// for (std::size_t iy = 0; iy < 3; iy++) -// corrected_muXY(ix, iy) += f_ratio * muXY_face[ix * 3 + iy]; -// -// for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) -// { -// if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) -// { -// bfs_queue.push(fj); -// bfs_visited.insert(fj); -// } -// } -// } -// } -// put(area_vmm, v, corrected_mu0); -// put(aniso_vmm, v, corrected_muXY); -//} - - template Principal_curvature principal_curvature_from_anisotropic_measures( const std::array anisotropic_measure, @@ -1208,12 +942,11 @@ template::%Vertex_descriptor` +* as key type and `%FT` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, mean curvatures won't be computed} +* \cgalParamNEnd +* +* \cgalParamNBegin{vertex_gaussian_curvature_map} +* \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} +* \cgalParamType{a class model of `WritablePropertyMap` with +* `boost::graph_traits::%Vertex_descriptor` +* as key type and `%FT` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, gaussian curvatures won't be computed} +* \cgalParamNEnd +* +* +* \cgalParamNBegin{vertex_prinicipal_curvature_map} +* \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} +* \cgalParamType{a class model of `WritablePropertyMap` with +* `boost::graph_traits::%Vertex_descriptor` +* as key type and `%Principal_curvature` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t>(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, mean principal won't be computed} +* \cgalParamNEnd * * \cgalParamNBegin{ball_radius} * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures * by summing measures of faces inside a ball of this radius centered at the * vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball} +* inclusion ratio inside this ball.} * \cgalParamType{`GT::FT`} * \cgalParamDefault{`-1`} -* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of +* \cgalParamExtra{If this parameter is omitted (`-1`), the epansion is then just a sum of * measures on faces around the vertex} * \cgalParamNEnd * From 59c3605a0689e20a54a8998d9ed3296fd8862cbb Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 14 Nov 2022 11:28:37 +0200 Subject: [PATCH 077/161] trailing whitespace --- .../interpolated_corrected_curvature_measures.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 0ed47db00584..5bcff27cc2ee 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -37,14 +37,14 @@ namespace Polygon_mesh_processing { /** * \ingroup PMP_corrected_curvatures_grp -* +* * \brief a struct for storing principal curvatures and directions. * * @tparam GT is the geometric traits class. */ template struct Principal_curvature { - + /// min curvature magnitude typename GT::FT min_curvature; @@ -976,7 +976,7 @@ template(), pmesh)`} * \cgalParamExtra{If this parameter is omitted, gaussian curvatures won't be computed} * \cgalParamNEnd -* +* * * \cgalParamNBegin{vertex_prinicipal_curvature_map} * \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} From b0a82c4569c6f9483ed45f76f2224f9b9212fad8 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 14 Nov 2022 11:37:14 +0200 Subject: [PATCH 078/161] typo fix --- .../Curvatures/interpolated_corrected_curvature_measures.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 5bcff27cc2ee..9da751c7f83b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -996,7 +996,7 @@ template::%Vertex_descriptor` From 25eb94f1ec4996a46ec443888e768ff3e9ba6dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 14 Nov 2022 14:24:24 +0100 Subject: [PATCH 079/161] force copy fig --- .../doc/Polygon_mesh_processing/Doxyfile.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in index fa3b8b8f1bf5..f2e738b4d23c 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in @@ -21,4 +21,5 @@ EXCLUDE_SYMBOLS += experimental HTML_EXTRA_FILES = ${CGAL_PACKAGE_DOC_DIR}/fig/selfintersections.jpg \ ${CGAL_PACKAGE_DOC_DIR}/fig/mesh_smoothing.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/shape_smoothing.png + ${CGAL_PACKAGE_DOC_DIR}/fig/shape_smoothing.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/icc_diff_radius.png From b56500d0fc8edbf4971c367bfbde6a9d2dd1a915 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 16 Nov 2022 12:51:14 +0200 Subject: [PATCH 080/161] 4-space -> 2-space indents --- ...erpolated_corrected_curvatures_example.cpp | 36 +- ...orrected_curvatures_polyhedron_example.cpp | 26 +- ...nterpolated_corrected_curvature_measures.h | 920 +++++++++--------- ...est_interopolated_corrected_curvatures.cpp | 226 ++--- .../Display/Display_property_plugin.cpp | 214 ++-- 5 files changed, 708 insertions(+), 714 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp index ffb86d94795c..7bf182c3ce3d 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp @@ -18,11 +18,11 @@ typedef boost::graph_traits::vertex_descriptor vertex_descriptor; int main(int argc, char* argv[]) { Surface_Mesh smesh; - const std::string filename = (argc>1) ? - argv[1] : - CGAL::data_file_path("meshes/small_bunny.obj"); + const std::string filename = (argc > 1) ? + argv[1] : + CGAL::data_file_path("meshes/small_bunny.obj"); - if(!CGAL::IO::read_polygon_mesh(filename, smesh)) + if (!CGAL::IO::read_polygon_mesh(filename, smesh)) { std::cerr << "Invalid input file." << std::endl; return EXIT_FAILURE; @@ -41,25 +41,25 @@ int main(int argc, char* argv[]) Surface_Mesh::Property_map> principal_curvature_map; boost::tie(principal_curvature_map, created) = smesh.add_property_map> - ("v:principal_curvature_map", { 0, 0, - Epic_kernel::Vector_3(0,0,0), - Epic_kernel::Vector_3(0,0,0) }); + ("v:principal_curvature_map", { 0, 0, + Epic_kernel::Vector_3(0,0,0), + Epic_kernel::Vector_3(0,0,0) }); assert(created); // user can call these fucntions to compute a specfic curvature type on each vertex. PMP::interpolated_corrected_mean_curvature( - smesh, - mean_curvature_map + smesh, + mean_curvature_map ); PMP::interpolated_corrected_gaussian_curvature( - smesh, - gaussian_curvature_map + smesh, + gaussian_curvature_map ); PMP::interpolated_corrected_principal_curvatures( - smesh, - principal_curvature_map + smesh, + principal_curvature_map ); // uncomment this to compute a curvature while specifying named parameters @@ -81,16 +81,16 @@ int main(int argc, char* argv[]) // This function can be used to compute multiple curvature types by specifiying them as named parameters // This is more efficient than computing each one separately (shared computations). PMP::interpolated_corrected_curvatures( - smesh, - CGAL::parameters::vertex_mean_curvature_map(mean_curvature_map) - .vertex_principal_curvature_map(principal_curvature_map) + smesh, + CGAL::parameters::vertex_mean_curvature_map(mean_curvature_map) + .vertex_principal_curvature_map(principal_curvature_map) ); for (vertex_descriptor v : vertices(smesh)) { auto PC = principal_curvature_map[v]; std::cout << v.idx() << ": HC = " << mean_curvature_map[v] - << ", GC = " << gaussian_curvature_map[v] << "\n" - << ", PC = [ " << PC.min_curvature << " , " << PC.max_curvature << " ]\n"; + << ", GC = " << gaussian_curvature_map[v] << "\n" + << ", PC = [ " << PC.min_curvature << " , " << PC.max_curvature << " ]\n"; } } diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp index 207625fd8485..929fc8d96a67 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp @@ -16,14 +16,12 @@ typedef CGAL::Polyhedron_3 Polyhedron; typedef boost::graph_traits::face_descriptor face_descriptor; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; -int main(int argc, char* argv[]) +int main(int argc, char *argv[]) { Polyhedron polyhedron; - const std::string filename = (argc>1) ? - argv[1] : - CGAL::data_file_path("meshes/small_bunny.obj"); + const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/small_bunny.obj"); - if(!CGAL::IO::read_polygon_mesh(filename, polyhedron)) + if (!CGAL::IO::read_polygon_mesh(filename, polyhedron)) { std::cerr << "Invalid input file." << std::endl; return EXIT_FAILURE; @@ -33,22 +31,19 @@ int main(int argc, char* argv[]) mean_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron), gaussian_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron); boost::property_map>>::type - principal_curvature_map = get(CGAL::dynamic_vertex_property_t< PMP::Principal_curvature>(), polyhedron); + principal_curvature_map = get(CGAL::dynamic_vertex_property_t>(), polyhedron); PMP::interpolated_corrected_mean_curvature( polyhedron, - mean_curvature_map - ); + mean_curvature_map); PMP::interpolated_corrected_gaussian_curvature( polyhedron, - gaussian_curvature_map - ); + gaussian_curvature_map); PMP::interpolated_corrected_principal_curvatures( polyhedron, - principal_curvature_map - ); + principal_curvature_map); // uncomment this to compute a curvature while specifying named parameters // Example: an expansion ball radius of 0.5 and a vertex normals map (does not have to depend on positions) @@ -66,16 +61,15 @@ int main(int argc, char* argv[]) PMP::interpolated_corrected_curvatures( polyhedron, CGAL::parameters::vertex_mean_curvature_map(mean_curvature_map) - .vertex_principal_curvature_map(principal_curvature_map) - ); + .vertex_principal_curvature_map(principal_curvature_map)); int i = 0; for (vertex_descriptor v : vertices(polyhedron)) { auto PC = get(principal_curvature_map, v); std::cout << i << ": HC = " << get(mean_curvature_map, v) - << ", GC = " << get(gaussian_curvature_map, v) << "\n" - << ", PC = [ " << PC.min_curvature << " , " << PC.max_curvature << " ]\n"; + << ", GC = " << get(gaussian_curvature_map, v) << "\n" + << ", PC = [ " << PC.min_curvature << " , " << PC.max_curvature << " ]\n"; i++; } } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 9da751c7f83b..4006b761679f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -45,74 +45,74 @@ namespace Polygon_mesh_processing { template struct Principal_curvature { - /// min curvature magnitude - typename GT::FT min_curvature; - - /// max curvature magnitude - typename GT::FT max_curvature; - - /// min curvature direction vector - typename GT::Vector_3 min_direction; - - /// max curvature direction vector - typename GT::Vector_3 max_direction; - - Principal_curvature() { - min_curvature = 0; - max_curvature = 0; - min_direction = typename GT::Vector_3(0, 0, 0); - max_direction = typename GT::Vector_3(0, 0, 0); - } - - Principal_curvature( - typename GT::FT min_curvature, - typename GT::FT max_curvature, - typename GT::Vector_3 min_direction, - typename GT::Vector_3 max_direction) { - this->min_curvature = min_curvature; - this->max_curvature = max_curvature; - this->min_direction = min_direction; - this->max_direction = max_direction; - } + /// min curvature magnitude + typename GT::FT min_curvature; + + /// max curvature magnitude + typename GT::FT max_curvature; + + /// min curvature direction vector + typename GT::Vector_3 min_direction; + + /// max curvature direction vector + typename GT::Vector_3 max_direction; + + Principal_curvature() { + min_curvature = 0; + max_curvature = 0; + min_direction = typename GT::Vector_3(0, 0, 0); + max_direction = typename GT::Vector_3(0, 0, 0); + } + + Principal_curvature( + typename GT::FT min_curvature, + typename GT::FT max_curvature, + typename GT::Vector_3 min_direction, + typename GT::Vector_3 max_direction + ) { + min_curvature = min_curvature; + max_curvature = max_curvature; + min_direction = min_direction; + max_direction = max_direction; + } }; namespace internal { -template -typename GT::FT average_edge_length(const PolygonMesh& pmesh) -{ + template + typename GT::FT average_edge_length(const PolygonMesh& pmesh) { const std::size_t n = edges(pmesh).size(); if (n == 0) - return 0; + return 0; typename GT::FT avg_edge_length = 0; for (auto e : edges(pmesh)) - avg_edge_length += edge_length(e, pmesh); + avg_edge_length += edge_length(e, pmesh); avg_edge_length /= n; return avg_edge_length; -} + } -enum Curvature_measure_index { + enum Curvature_measure_index { MU0_AREA_MEASURE, ///< corrected area density MU1_MEAN_CURVATURE_MEASURE, ///< corrected mean curvature density MU2_GAUSSIAN_CURVATURE_MEASURE ///< corrected gaussian curvature density -}; + }; -template -struct Vertex_measures { + template + struct Vertex_measures { typename GT::FT area_measure = 0; typename GT::FT mean_curvature_measure = 0; typename GT::FT gaussian_curvature_measure = 0; std::array anisotropic_measure = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; -}; + }; -template -typename GT::FT interpolated_corrected_area_measure_face(const std::vector& u, - const std::vector& x = {}) -{ + template + typename GT::FT interpolated_corrected_area_measure_face(const std::vector& u, + const std::vector& x = {}) + { const std::size_t n = x.size(); CGAL_precondition(u.size() == n); CGAL_precondition(n >= 3); @@ -122,52 +122,52 @@ typename GT::FT interpolated_corrected_area_measure_face(const std::vector( - std::vector {u[i], u[(i + 1) % n], uc}, - std::vector {x[i], x[(i + 1) % n], xc} - ); - } - return mu0; + // getting center of points + typename GT::Vector_3 xc = + std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); + xc /= n; + + // getting unit average normal of points + typename GT::Vector_3 uc = + std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); + uc /= sqrt(uc * uc); + + // summing each triangle's measure after triangulation by barycenter split. + for (std::size_t i = 0; i < n; i++) + { + mu0 += interpolated_corrected_area_measure_face( + std::vector {u[i], u[(i + 1) % n], uc}, + std::vector {x[i], x[(i + 1) % n], xc} + ); + } + return mu0; } -} + } -template -typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::vector& u, - const std::vector& x = {}) -{ + template + typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::vector& u, + const std::vector& x = {}) + { const std::size_t n = x.size(); CGAL_precondition(u.size() == n); CGAL_precondition(n >= 3); @@ -177,63 +177,63 @@ typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::ve // Triangle: use triangle formula if (n == 3) { - const typename GT::Vector_3 um = (u[0] + u[1] + u[2]) / 3.0; + const typename GT::Vector_3 um = (u[0] + u[1] + u[2]) / 3.0; - return 0.5 * um * (cross_product(u[2] - u[1], x[0]) - + cross_product(u[0] - u[2], x[1]) - + cross_product(u[1] - u[0], x[2])); + return 0.5 * um * (cross_product(u[2] - u[1], x[0]) + + cross_product(u[0] - u[2], x[1]) + + cross_product(u[1] - u[0], x[2])); } // Quad: use bilinear interpolation formula else if (n == 4) { - // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. - // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 - - const typename GT::Vector_3 u02 = u[2] - u[0]; - const typename GT::Vector_3 u13 = u[3] - u[1]; - const typename GT::Vector_3 x0_cross = cross_product(u13, x[0]); - const typename GT::Vector_3 x1_cross = -cross_product(u02, x[1]); - const typename GT::Vector_3 x3_cross = cross_product(u02, x[3]); - const typename GT::Vector_3 x2_cross = -cross_product(u13, x[2]); - - return (1.0 / 12.0) * ( - u[0] * (2 * x0_cross - cross_product((u[3] + u[2]), x[1]) + cross_product((u[1] + u[2]), x[3]) + x2_cross) - + u[1] * (cross_product((u[3] + u[2]), x[0]) + 2 * x1_cross + x3_cross - cross_product((u[0] + u[3]), x[2])) - + u[3] * (-cross_product((u[1] + u[2]), x[0]) + x1_cross + 2 * x3_cross + cross_product((u[0] + u[1]), x[2])) - + u[2] * (x0_cross + cross_product((u[0] + u[3]), x[1]) - cross_product((u[0] + u[1]), x[3]) + 2 * x2_cross) - ); + // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. + // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 + + const typename GT::Vector_3 u02 = u[2] - u[0]; + const typename GT::Vector_3 u13 = u[3] - u[1]; + const typename GT::Vector_3 x0_cross = cross_product(u13, x[0]); + const typename GT::Vector_3 x1_cross = -cross_product(u02, x[1]); + const typename GT::Vector_3 x3_cross = cross_product(u02, x[3]); + const typename GT::Vector_3 x2_cross = -cross_product(u13, x[2]); + + return (1.0 / 12.0) * ( + u[0] * (2 * x0_cross - cross_product((u[3] + u[2]), x[1]) + cross_product((u[1] + u[2]), x[3]) + x2_cross) + + u[1] * (cross_product((u[3] + u[2]), x[0]) + 2 * x1_cross + x3_cross - cross_product((u[0] + u[3]), x[2])) + + u[3] * (-cross_product((u[1] + u[2]), x[0]) + x1_cross + 2 * x3_cross + cross_product((u[0] + u[1]), x[2])) + + u[2] * (x0_cross + cross_product((u[0] + u[3]), x[1]) - cross_product((u[0] + u[1]), x[3]) + 2 * x2_cross) + ); } // N-gon: split into n triangles by polygon center and use triangle formula for each else { - typename GT::FT mu1 = 0; + typename GT::FT mu1 = 0; - // getting center of points - typename GT::Vector_3 xc = - std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); - xc /= n; - - // getting unit average normal of points - typename GT::Vector_3 uc = - std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); - uc /= sqrt(uc * uc); - - // summing each triangle's measure after triangulation by barycenter split. - for (std::size_t i = 0; i < n; i++) - { - mu1 += interpolated_corrected_mean_curvature_measure_face( - std::vector {u[i], u[(i + 1) % n], uc}, - std::vector {x[i], x[(i + 1) % n], xc} - ); - } - return mu1; + // getting center of points + typename GT::Vector_3 xc = + std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); + xc /= n; + + // getting unit average normal of points + typename GT::Vector_3 uc = + std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); + uc /= sqrt(uc * uc); + + // summing each triangle's measure after triangulation by barycenter split. + for (std::size_t i = 0; i < n; i++) + { + mu1 += interpolated_corrected_mean_curvature_measure_face( + std::vector {u[i], u[(i + 1) % n], uc}, + std::vector {x[i], x[(i + 1) % n], xc} + ); + } + return mu1; } -} + } -template -typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std::vector& u, - const std::vector& x = {}) -{ + template + typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std::vector& u, + const std::vector& x = {}) + { const std::size_t n = u.size(); CGAL_precondition(n >= 3); @@ -242,182 +242,182 @@ typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std // Triangle: use triangle formula if (n == 3) { - return 0.5 * u[0] * cross_product(u[1], u[2]); + return 0.5 * u[0] * cross_product(u[1], u[2]); } // Quad: use bilinear interpolation formula else if (n == 4) { - // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. - // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 - return (1.0 / 36.0) * ( - (4 * u[0] + 2 * u[1] + 2 * u[3] + u[2]) * cross_product(u[1] - u[0], u[3] - u[0]) - + (2 * u[0] + 4 * u[1] + u[3] + 2 * u[2]) * cross_product(u[1] - u[0], u[2] - u[1]) - + (2 * u[0] + u[1] + 4 * u[3] + 2 * u[2]) * cross_product(u[2] - u[3], u[3] - u[0]) - + (u[0] + 2 * u[1] + 2 * u[3] + 4 * u[2]) * cross_product(u[2] - u[3], u[2] - u[1]) - ); + // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. + // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 + return (1.0 / 36.0) * ( + (4 * u[0] + 2 * u[1] + 2 * u[3] + u[2]) * cross_product(u[1] - u[0], u[3] - u[0]) + + (2 * u[0] + 4 * u[1] + u[3] + 2 * u[2]) * cross_product(u[1] - u[0], u[2] - u[1]) + + (2 * u[0] + u[1] + 4 * u[3] + 2 * u[2]) * cross_product(u[2] - u[3], u[3] - u[0]) + + (u[0] + 2 * u[1] + 2 * u[3] + 4 * u[2]) * cross_product(u[2] - u[3], u[2] - u[1]) + ); } // N-gon: split into n triangles by polygon center and use triangle formula for each else { - typename GT::FT mu2 = 0; - - // getting unit average normal of points - typename GT::Vector_3 uc = - std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); - uc /= sqrt(uc * uc); - - // summing each triangle's measure after triangulation by barycenter split. - for (std::size_t i = 0; i < n; i++) - { - mu2 += interpolated_corrected_gaussian_curvature_measure_face( - std::vector {u[i], u[(i + 1) % n], uc} - ); - } - return mu2; + typename GT::FT mu2 = 0; + + // getting unit average normal of points + typename GT::Vector_3 uc = + std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); + uc /= sqrt(uc * uc); + + // summing each triangle's measure after triangulation by barycenter split. + for (std::size_t i = 0; i < n; i++) + { + mu2 += interpolated_corrected_gaussian_curvature_measure_face( + std::vector {u[i], u[(i + 1) % n], uc} + ); + } + return mu2; } -} + } -template -std::array interpolated_corrected_anisotropic_measure_face(const std::vector& u, - const std::vector& x) -{ + template + std::array interpolated_corrected_anisotropic_measure_face(const std::vector& u, + const std::vector& x) + { const std::size_t n = x.size(); CGAL_precondition(u.size() == n); CGAL_precondition(n >= 3); typename GT::Construct_cross_product_vector_3 cross_product; - std::array muXY {0}; + std::array muXY{ 0 }; // Triangle: use triangle formula if (n == 3) { - const typename GT::Vector_3 u01 = u[1] - u[0]; - const typename GT::Vector_3 u02 = u[2] - u[0]; - const typename GT::Vector_3 x01 = x[1] - x[0]; - const typename GT::Vector_3 x02 = x[2] - x[0]; - const typename GT::Vector_3 um = (u[0] + u[1] + u[2]) / 3.0; + const typename GT::Vector_3 u01 = u[1] - u[0]; + const typename GT::Vector_3 u02 = u[2] - u[0]; + const typename GT::Vector_3 x01 = x[1] - x[0]; + const typename GT::Vector_3 x02 = x[2] - x[0]; + const typename GT::Vector_3 um = (u[0] + u[1] + u[2]) / 3.0; + + for (std::size_t ix = 0; ix < 3; ix++) + { + typename GT::Vector_3 X; + if (ix == 0) + X = typename GT::Vector_3(1, 0, 0); + if (ix == 1) + X = typename GT::Vector_3(0, 1, 0); + if (ix == 2) + X = typename GT::Vector_3(0, 0, 1); - for (std::size_t ix = 0; ix < 3; ix++) - { - typename GT::Vector_3 X; - if (ix == 0) - X = typename GT::Vector_3(1, 0, 0); - if (ix == 1) - X = typename GT::Vector_3(0, 1, 0); - if (ix == 2) - X = typename GT::Vector_3(0, 0, 1); - - for (std::size_t iy = 0; iy < 3; iy++) - muXY[ix * 3 + iy] = 0.5 * um * (cross_product(u02[iy] * X, x01) - cross_product(u01[iy] * X, x02)); - } + for (std::size_t iy = 0; iy < 3; iy++) + muXY[ix * 3 + iy] = 0.5 * um * (cross_product(u02[iy] * X, x01) - cross_product(u01[iy] * X, x02)); + } } // Quad: use bilinear interpolation formula else if (n == 4) { - // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. - // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 - for (std::size_t ix = 0; ix < 3; ix++) - { - typename GT::Vector_3 X; - if (ix == 0) - X = typename GT::Vector_3(1, 0, 0); - if (ix == 1) - X = typename GT::Vector_3(0, 1, 0); - if (ix == 2) - X = typename GT::Vector_3(0, 0, 1); - - const typename GT::Vector_3 u0xX = cross_product(u[0], X); - const typename GT::Vector_3 u1xX = cross_product(u[1], X); - const typename GT::Vector_3 u2xX = cross_product(u[2], X); - const typename GT::Vector_3 u3xX = cross_product(u[3], X); - - for (std::size_t iy = 0; iy < 3; iy++) - muXY[ix * 3 + iy] = (1.0 / 72.0) * ( - - u[0][iy] * ( u0xX * ( - x[0] - 11 * x[1] + 13 * x[3] - x[2]) - + u1xX * ( -5 * x[0] - 7 * x[1] + 11 * x[3] + x[2]) - + u3xX * ( x[0] - 7 * x[1] + 11 * x[3] - 5 * x[2]) - + u2xX * ( - x[0] - 5 * x[1] + 7 * x[3] - x[2]) - ) - + u[1][iy] * ( u0xX * ( 13 * x[0] - x[1] - 7 * x[3] - 5 * x[2]) - + u1xX * ( 17 * x[0] - 5 * x[1] - 5 * x[3] - 7 * x[2]) - + u3xX * ( 5 * x[0] + x[1] + x[3] - 7 * x[2]) - + u2xX * ( 7 * x[0] - x[1] + 5 * x[3] - 11 * x[2]) - ) - + u[2][iy] * ( u0xX * (-11 * x[0] + 5 * x[1] - x[3] + 7 * x[2]) - + u1xX * (- 7 * x[0] + x[1] + x[3] + 5 * x[2]) - + u3xX * (- 7 * x[0] - 5 * x[1] - 5 * x[3] + 17 * x[2]) - + u2xX * (- 5 * x[0] - 7 * x[1] - x[3] + 13 * x[2]) - ) - + u[3][iy] * ( u0xX * (- x[0] + 7 * x[1] - 5 * x[3] - x[2]) - + u1xX * (- 5 * x[0] + 11 * x[1] - 7 * x[3] + x[2]) - + u3xX * ( x[0] + 11 * x[1] - 7 * x[3] - 5 * x[2]) - + u2xX * (- x[0] + 13 * x[1] - 11 * x[3] - x[2]) - ) - - ); - } + // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. + // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 + for (std::size_t ix = 0; ix < 3; ix++) + { + typename GT::Vector_3 X; + if (ix == 0) + X = typename GT::Vector_3(1, 0, 0); + if (ix == 1) + X = typename GT::Vector_3(0, 1, 0); + if (ix == 2) + X = typename GT::Vector_3(0, 0, 1); + + const typename GT::Vector_3 u0xX = cross_product(u[0], X); + const typename GT::Vector_3 u1xX = cross_product(u[1], X); + const typename GT::Vector_3 u2xX = cross_product(u[2], X); + const typename GT::Vector_3 u3xX = cross_product(u[3], X); + + for (std::size_t iy = 0; iy < 3; iy++) + muXY[ix * 3 + iy] = (1.0 / 72.0) * ( + + u[0][iy] * (u0xX * (-x[0] - 11 * x[1] + 13 * x[3] - x[2]) + + u1xX * (-5 * x[0] - 7 * x[1] + 11 * x[3] + x[2]) + + u3xX * (x[0] - 7 * x[1] + 11 * x[3] - 5 * x[2]) + + u2xX * (-x[0] - 5 * x[1] + 7 * x[3] - x[2]) + ) + + u[1][iy] * (u0xX * (13 * x[0] - x[1] - 7 * x[3] - 5 * x[2]) + + u1xX * (17 * x[0] - 5 * x[1] - 5 * x[3] - 7 * x[2]) + + u3xX * (5 * x[0] + x[1] + x[3] - 7 * x[2]) + + u2xX * (7 * x[0] - x[1] + 5 * x[3] - 11 * x[2]) + ) + + u[2][iy] * (u0xX * (-11 * x[0] + 5 * x[1] - x[3] + 7 * x[2]) + + u1xX * (-7 * x[0] + x[1] + x[3] + 5 * x[2]) + + u3xX * (-7 * x[0] - 5 * x[1] - 5 * x[3] + 17 * x[2]) + + u2xX * (-5 * x[0] - 7 * x[1] - x[3] + 13 * x[2]) + ) + + u[3][iy] * (u0xX * (-x[0] + 7 * x[1] - 5 * x[3] - x[2]) + + u1xX * (-5 * x[0] + 11 * x[1] - 7 * x[3] + x[2]) + + u3xX * (x[0] + 11 * x[1] - 7 * x[3] - 5 * x[2]) + + u2xX * (-x[0] + 13 * x[1] - 11 * x[3] - x[2]) + ) + + ); + } } // N-gon: split into n triangles by polygon center and use triangle formula for each else { - // getting center of points - typename GT::Vector_3 xc = - std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); - xc /= n; - - // getting unit average normal of points - typename GT::Vector_3 uc = - std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); - uc /= sqrt(uc * uc); - - // summing each triangle's measure after triangulation by barycenter split. - for (std::size_t i = 0; i < n; i++) - { - std::array muXY_curr_triangle = - interpolated_corrected_anisotropic_measure_face( - std::vector {u[i], u[(i + 1) % n], uc}, - std::vector {x[i], x[(i + 1) % n], xc} - ); - - for (std::size_t ix = 0; ix < 3; ix++) - for (std::size_t iy = 0; iy < 3; iy++) - muXY[ix * 3 + iy] += muXY_curr_triangle[ix * 3 + iy]; - } + // getting center of points + typename GT::Vector_3 xc = + std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); + xc /= n; + + // getting unit average normal of points + typename GT::Vector_3 uc = + std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); + uc /= sqrt(uc * uc); + + // summing each triangle's measure after triangulation by barycenter split. + for (std::size_t i = 0; i < n; i++) + { + std::array muXY_curr_triangle = + interpolated_corrected_anisotropic_measure_face( + std::vector {u[i], u[(i + 1) % n], uc}, + std::vector {x[i], x[(i + 1) % n], xc} + ); + + for (std::size_t ix = 0; ix < 3; ix++) + for (std::size_t iy = 0; iy < 3; iy++) + muXY[ix * 3 + iy] += muXY_curr_triangle[ix * 3 + iy]; + } } return muXY; -} - -//template -//typename GT::FT triangle_in_ball_ratio(const typename GT::Vector_3 x1, -// const typename GT::Vector_3 x2, -// const typename GT::Vector_3 x3, -// const typename GT::FT r, -// const typename GT::Vector_3 c, -// const std::size_t res = 3) -//{ -// const typename GT::FT R = r * r; -// const typename GT::FT acc = 1.0 / res; -// std::size_t samples_in = 0; -// for (GT::FT alpha = acc / 3; alpha < 1; alpha += acc) -// for (GT::FT beta = acc / 3; beta < 1 - alpha; beta += acc) -// { -// if ((alpha * x1 + beta * x2 + (1 - alpha - beta) * x3 - c).squared_length() < R) -// samples_in++; -// } -// return samples_in / (typename GT::FT)(res * (res + 1) / 2); -//} - -template -typename GT::FT face_in_ball_ratio(const std::vector& x, - const typename GT::FT r, - const typename GT::Vector_3 c) -{ + } + + //template + //typename GT::FT triangle_in_ball_ratio(const typename GT::Vector_3 x1, + // const typename GT::Vector_3 x2, + // const typename GT::Vector_3 x3, + // const typename GT::FT r, + // const typename GT::Vector_3 c, + // const std::size_t res = 3) + //{ + // const typename GT::FT R = r * r; + // const typename GT::FT acc = 1.0 / res; + // std::size_t samples_in = 0; + // for (GT::FT alpha = acc / 3; alpha < 1; alpha += acc) + // for (GT::FT beta = acc / 3; beta < 1 - alpha; beta += acc) + // { + // if ((alpha * x1 + beta * x2 + (1 - alpha - beta) * x3 - c).squared_length() < R) + // samples_in++; + // } + // return samples_in / (typename GT::FT)(res * (res + 1) / 2); + //} + + template + typename GT::FT face_in_ball_ratio(const std::vector& x, + const typename GT::FT r, + const typename GT::Vector_3 c) + { const std::size_t n = x.size(); // getting center of points typename GT::Vector_3 xm = - std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); + std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); xm /= n; typename GT::FT d_min = (xm - c).squared_length(); @@ -425,9 +425,9 @@ typename GT::FT face_in_ball_ratio(const std::vector& x, for (const typename GT::Vector_3 xi : x) { - const typename GT::FT d_sq = (xi - c).squared_length(); - d_max = (std::max)(d_sq, d_max); - d_min = (std::min)(d_sq, d_min); + const typename GT::FT d_sq = (xi - c).squared_length(); + d_max = (std::max)(d_sq, d_max); + d_min = (std::min)(d_sq, d_min); } if (d_max <= r * r) return 1.0; @@ -437,20 +437,20 @@ typename GT::FT face_in_ball_ratio(const std::vector& x, d_min = sqrt(d_min); return (r - d_min) / (d_max - d_min); -} + } -template -Principal_curvature principal_curvature_from_anisotropic_measures( + template + Principal_curvature principal_curvature_from_anisotropic_measures( const std::array anisotropic_measure, const typename GT::FT v_mu0, const typename GT::Vector_3 u_GT -) -{ + ) + { Eigen::Matrix v_muXY = Eigen::Matrix::Zero(); for (std::size_t ix = 0; ix < 3; ix++) - for (std::size_t iy = 0; iy < 3; iy++) - v_muXY(ix, iy) = anisotropic_measure[ix * 3 + iy]; + for (std::size_t iy = 0; iy < 3; iy++) + v_muXY(ix, iy) = anisotropic_measure[ix * 3 + iy]; Eigen::Matrix u(u_GT.x(), u_GT.y(), u_GT.z()); const typename GT::FT K = 1000 * v_mu0; @@ -462,7 +462,7 @@ Principal_curvature principal_curvature_from_anisotropic_measures( eigensolver.computeDirect(v_muXY); if (eigensolver.info() != Eigen::Success) - return Principal_curvature(); + return Principal_curvature(); const Eigen::Matrix eig_vals = eigensolver.eigenvalues(); const Eigen::Matrix eig_vecs = eigensolver.eigenvectors(); @@ -471,16 +471,16 @@ Principal_curvature principal_curvature_from_anisotropic_measures( const typename GT::Vector_3 max_eig_vec(eig_vecs(0, 0), eig_vecs(1, 0), eig_vecs(2, 0)); return Principal_curvature( - (v_mu0 != 0.0) ? -eig_vals[1] / v_mu0 : 0.0, - (v_mu0 != 0.0) ? -eig_vals[0] / v_mu0 : 0.0, - min_eig_vec, - max_eig_vec - ); -} - -template -class Interpolated_corrected_curvatures_computer -{ + (v_mu0 != 0.0) ? -eig_vals[1] / v_mu0 : 0.0, + (v_mu0 != 0.0) ? -eig_vals[0] / v_mu0 : 0.0, + min_eig_vec, + max_eig_vec + ); + } + + template + class Interpolated_corrected_curvatures_computer + { typedef typename GetGeomTraits::type GT; typedef typename GT::FT FT; @@ -497,29 +497,29 @@ class Interpolated_corrected_curvatures_computer typedef dynamic_vertex_property_t Vector_map_tag; typedef typename boost::property_map::const_type Default_vector_map; typedef typename internal_np::Lookup_named_param_def::type Vertex_normal_map; + NamedParameters, + Default_vector_map>::type Vertex_normal_map; typedef Constant_property_map Default_scalar_map; typedef typename internal_np::Lookup_named_param_def::type Vertex_mean_curvature_map; + NamedParameters, + Default_scalar_map>::type Vertex_mean_curvature_map; typedef typename internal_np::Lookup_named_param_def::type Vertex_gaussian_curvature_map; + NamedParameters, + Default_scalar_map>::type Vertex_gaussian_curvature_map; typedef Constant_property_map> Default_principal_map; typedef typename internal_np::Lookup_named_param_def::type Vertex_principal_curvature_map; + NamedParameters, + Default_principal_map>::type Vertex_principal_curvature_map; typedef typename boost::property_map>::const_type Face_scalar_measure_map; + CGAL::dynamic_face_property_t>::const_type Face_scalar_measure_map; typedef typename boost::property_map>>::const_type Face_anisotropic_measure_map; + CGAL::dynamic_face_property_t>>::const_type Face_anisotropic_measure_map; -private: + private: const PolygonMesh& pmesh; Vertex_position_map vpm; Vertex_normal_map vnm; @@ -537,217 +537,217 @@ class Interpolated_corrected_curvatures_computer Face_anisotropic_measure_map muXY_map; void set_property_maps() { - mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); - mu1_map = get(CGAL::dynamic_face_property_t(), pmesh); - mu2_map = get(CGAL::dynamic_face_property_t(), pmesh); - muXY_map = get(CGAL::dynamic_face_property_t>(), pmesh); + mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); + mu1_map = get(CGAL::dynamic_face_property_t(), pmesh); + mu2_map = get(CGAL::dynamic_face_property_t(), pmesh); + muXY_map = get(CGAL::dynamic_face_property_t>(), pmesh); } void set_named_params(const NamedParameters& np) { - using parameters::choose_parameter; - using parameters::get_parameter; - using parameters::is_default_parameter; + using parameters::choose_parameter; + using parameters::get_parameter; + using parameters::is_default_parameter; - vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), - get_const_property_map(CGAL::vertex_point, pmesh)); + vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), + get_const_property_map(CGAL::vertex_point, pmesh)); - vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), - get(Vector_map_tag(), pmesh)); + vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), + get(Vector_map_tag(), pmesh)); - if (is_default_parameter::value) - compute_vertex_normals(pmesh, vnm, np); + if (is_default_parameter::value) + compute_vertex_normals(pmesh, vnm, np); - const FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1); + const FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1); - is_mean_curvature_selected = !is_default_parameter::value; - is_gaussian_curvature_selected = !is_default_parameter::value; - is_principal_curvature_selected = !is_default_parameter::value; + is_mean_curvature_selected = !is_default_parameter::value; + is_gaussian_curvature_selected = !is_default_parameter::value; + is_principal_curvature_selected = !is_default_parameter::value; - mean_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_mean_curvature_map), Default_scalar_map()); - gaussian_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_gaussian_curvature_map), Default_scalar_map()); - principal_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvature_map), Default_principal_map()); + mean_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_mean_curvature_map), Default_scalar_map()); + gaussian_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_gaussian_curvature_map), Default_scalar_map()); + principal_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvature_map), Default_principal_map()); - set_ball_radius(radius); + set_ball_radius(radius); } void set_ball_radius(const FT radius) { - if (radius == 0) - ball_radius = average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; - else - ball_radius = radius; + if (radius == 0) + ball_radius = average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; + else + ball_radius = radius; } -public: + public: Interpolated_corrected_curvatures_computer(const PolygonMesh& pmesh, - const NamedParameters& np = parameters::default_values() + const NamedParameters& np = parameters::default_values() ) : - pmesh(pmesh) + pmesh(pmesh) { - set_named_params(np); + set_named_params(np); - if (is_mean_curvature_selected || is_gaussian_curvature_selected || is_principal_curvature_selected) - { - set_property_maps(); + if (is_mean_curvature_selected || is_gaussian_curvature_selected || is_principal_curvature_selected) + { + set_property_maps(); - compute_selected_curvatures(); - } + compute_selected_curvatures(); + } } -private: + private: void interpolated_corrected_all_measures_all_faces() { - std::vector x; - std::vector u; + std::vector x; + std::vector u; - for (Face_descriptor f : faces(pmesh)) + for (Face_descriptor f : faces(pmesh)) + { + for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) { - for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) - { - Point_3 p = get(vpm, v); - x.push_back(Vector_3(p.x(), p.y(), p.z())); - u.push_back(get(vnm, v)); - } - put(mu0_map, f, interpolated_corrected_area_measure_face(u, x)); + Point_3 p = get(vpm, v); + x.push_back(Vector_3(p.x(), p.y(), p.z())); + u.push_back(get(vnm, v)); + } + put(mu0_map, f, interpolated_corrected_area_measure_face(u, x)); - if (is_mean_curvature_selected) - put(mu1_map, f, interpolated_corrected_mean_curvature_measure_face(u, x)); + if (is_mean_curvature_selected) + put(mu1_map, f, interpolated_corrected_mean_curvature_measure_face(u, x)); - if (is_gaussian_curvature_selected) - put(mu2_map, f, interpolated_corrected_gaussian_curvature_measure_face(u, x)); + if (is_gaussian_curvature_selected) + put(mu2_map, f, interpolated_corrected_gaussian_curvature_measure_face(u, x)); - if (is_principal_curvature_selected) - put(muXY_map, f, interpolated_corrected_anisotropic_measure_face(u, x)); + if (is_principal_curvature_selected) + put(muXY_map, f, interpolated_corrected_anisotropic_measure_face(u, x)); - x.clear(); - u.clear(); - } + x.clear(); + u.clear(); + } } Vertex_measures expand_interpolated_corrected_measure_vertex_no_radius(Vertex_descriptor v) { - Vertex_measures vertex_curvatures; + Vertex_measures vertex_curvatures; - for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { - vertex_curvatures.area_measure += get(mu0_map, f); + for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + vertex_curvatures.area_measure += get(mu0_map, f); - if (is_mean_curvature_selected) - vertex_curvatures.mean_curvature_measure += get(mu1_map, f); + if (is_mean_curvature_selected) + vertex_curvatures.mean_curvature_measure += get(mu1_map, f); - if (is_gaussian_curvature_selected) - vertex_curvatures.gaussian_curvature_measure += get(mu2_map, f); + if (is_gaussian_curvature_selected) + vertex_curvatures.gaussian_curvature_measure += get(mu2_map, f); - if (is_principal_curvature_selected) - { - const std::array face_anisotropic_measure = get(muXY_map, f); - for (std::size_t i = 0; i < 3 * 3; i++) - vertex_curvatures.anisotropic_measure[i] += face_anisotropic_measure[i]; - } + if (is_principal_curvature_selected) + { + const std::array face_anisotropic_measure = get(muXY_map, f); + for (std::size_t i = 0; i < 3 * 3; i++) + vertex_curvatures.anisotropic_measure[i] += face_anisotropic_measure[i]; } + } - return vertex_curvatures; + return vertex_curvatures; } Vertex_measures expand_interpolated_corrected_measure_vertex(Vertex_descriptor v) { - std::queue bfs_queue; - std::unordered_set bfs_visited; + std::queue bfs_queue; + std::unordered_set bfs_visited; - Point_3 vp = get(vpm, v); - Vector_3 c = Vector_3(vp.x(), vp.y(), vp.z()); + Point_3 vp = get(vpm, v); + Vector_3 c = Vector_3(vp.x(), vp.y(), vp.z()); - Vertex_measures vertex_curvatures; + Vertex_measures vertex_curvatures; - for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { - if (f != boost::graph_traits::null_face()) - { - bfs_queue.push(f); - bfs_visited.insert(f); - } + for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + if (f != boost::graph_traits::null_face()) + { + bfs_queue.push(f); + bfs_visited.insert(f); } - while (!bfs_queue.empty()) { - Face_descriptor fi = bfs_queue.front(); - bfs_queue.pop(); + } + while (!bfs_queue.empty()) { + Face_descriptor fi = bfs_queue.front(); + bfs_queue.pop(); - // looping over vertices in face to get point coordinates - std::vector x; - for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) - { - Point_3 pi = get(vpm, vi); - x.push_back(Vector_3(pi.x(), pi.y(), pi.z())); - } + // looping over vertices in face to get point coordinates + std::vector x; + for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) + { + Point_3 pi = get(vpm, vi); + x.push_back(Vector_3(pi.x(), pi.y(), pi.z())); + } - const FT f_ratio = face_in_ball_ratio(x, ball_radius, c); + const FT f_ratio = face_in_ball_ratio(x, ball_radius, c); - if (f_ratio != 0.0) + if (f_ratio != 0.0) + { + vertex_curvatures.area_measure += f_ratio * get(mu0_map, fi); + + if (is_mean_curvature_selected) + vertex_curvatures.mean_curvature_measure += f_ratio * get(mu1_map, fi); + + if (is_gaussian_curvature_selected) + vertex_curvatures.gaussian_curvature_measure += f_ratio * get(mu2_map, fi); + + if (is_principal_curvature_selected) + { + const std::array face_anisotropic_measure = get(muXY_map, fi); + for (std::size_t i = 0; i < 3 * 3; i++) + vertex_curvatures.anisotropic_measure[i] += f_ratio * face_anisotropic_measure[i]; + } + + for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) + { + if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) { - vertex_curvatures.area_measure += f_ratio * get(mu0_map, fi); - - if (is_mean_curvature_selected) - vertex_curvatures.mean_curvature_measure += f_ratio * get(mu1_map, fi); - - if (is_gaussian_curvature_selected) - vertex_curvatures.gaussian_curvature_measure += f_ratio * get(mu2_map, fi); - - if (is_principal_curvature_selected) - { - const std::array face_anisotropic_measure = get(muXY_map, fi); - for (std::size_t i = 0; i < 3 * 3; i++) - vertex_curvatures.anisotropic_measure[i] += f_ratio * face_anisotropic_measure[i]; - } - - for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) - { - if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) - { - bfs_queue.push(fj); - bfs_visited.insert(fj); - } - } + bfs_queue.push(fj); + bfs_visited.insert(fj); } + } } - return vertex_curvatures; + } + return vertex_curvatures; } void compute_selected_curvatures() { - interpolated_corrected_all_measures_all_faces(); - - for (Vertex_descriptor v : vertices(pmesh)) - { - Vertex_measures vertex_curvatures = (ball_radius < 0)? - expand_interpolated_corrected_measure_vertex_no_radius(v) : - expand_interpolated_corrected_measure_vertex(v); - - if (is_mean_curvature_selected) { - vertex_curvatures.area_measure != 0 ? - put(mean_curvature_map, v, 0.5 * vertex_curvatures.mean_curvature_measure / vertex_curvatures.area_measure) : - put(mean_curvature_map, v, 0); - } + interpolated_corrected_all_measures_all_faces(); + + for (Vertex_descriptor v : vertices(pmesh)) + { + Vertex_measures vertex_curvatures = (ball_radius < 0) ? + expand_interpolated_corrected_measure_vertex_no_radius(v) : + expand_interpolated_corrected_measure_vertex(v); + + if (is_mean_curvature_selected) { + vertex_curvatures.area_measure != 0 ? + put(mean_curvature_map, v, 0.5 * vertex_curvatures.mean_curvature_measure / vertex_curvatures.area_measure) : + put(mean_curvature_map, v, 0); + } - if (is_gaussian_curvature_selected) { - vertex_curvatures.area_measure != 0 ? - put(gaussian_curvature_map, v, vertex_curvatures.gaussian_curvature_measure / vertex_curvatures.area_measure) : - put(gaussian_curvature_map, v, 0); - } + if (is_gaussian_curvature_selected) { + vertex_curvatures.area_measure != 0 ? + put(gaussian_curvature_map, v, vertex_curvatures.gaussian_curvature_measure / vertex_curvatures.area_measure) : + put(gaussian_curvature_map, v, 0); + } - if (is_principal_curvature_selected) { - const Vector_3 v_normal = get(vnm, v); - const Principal_curvature principal_curvature = principal_curvature_from_anisotropic_measures( - vertex_curvatures.anisotropic_measure, - vertex_curvatures.area_measure, - v_normal - ); - put(principal_curvature_map, v, principal_curvature); - } + if (is_principal_curvature_selected) { + const Vector_3 v_normal = get(vnm, v); + const Principal_curvature principal_curvature = principal_curvature_from_anisotropic_measures( + vertex_curvatures.anisotropic_measure, + vertex_curvatures.area_measure, + v_normal + ); + put(principal_curvature_map, v, principal_curvature); } + } } -}; + }; } // namespace internal @@ -807,12 +807,12 @@ class Interpolated_corrected_curvatures_computer */ template - void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, - VertexCurvatureMap& vcm, - NamedParameters& np = parameters::default_values()) + typename NamedParameters = parameters::Default_named_parameters> + void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, + VertexCurvatureMap& vcm, + NamedParameters& np = parameters::default_values()) { - interpolated_corrected_curvatures(pmesh, np.vertex_mean_curvature_map(vcm)); + interpolated_corrected_curvatures(pmesh, np.vertex_mean_curvature_map(vcm)); } /** @@ -870,12 +870,12 @@ template - void interpolated_corrected_gaussian_curvature(const PolygonMesh& pmesh, - VertexCurvatureMap& vcm, - NamedParameters& np = parameters::default_values()) + typename NamedParameters = parameters::Default_named_parameters> + void interpolated_corrected_gaussian_curvature(const PolygonMesh& pmesh, + VertexCurvatureMap& vcm, + NamedParameters& np = parameters::default_values()) { - interpolated_corrected_curvatures(pmesh, np.vertex_gaussian_curvature_map(vcm)); + interpolated_corrected_curvatures(pmesh, np.vertex_gaussian_curvature_map(vcm)); } /** @@ -934,12 +934,12 @@ template - void interpolated_corrected_principal_curvatures(const PolygonMesh& pmesh, - VertexCurvatureMap& vcm, - NamedParameters& np = parameters::default_values()) + typename NamedParameters = parameters::Default_named_parameters> + void interpolated_corrected_principal_curvatures(const PolygonMesh& pmesh, + VertexCurvatureMap& vcm, + NamedParameters& np = parameters::default_values()) { - interpolated_corrected_curvatures(pmesh, np.vertex_principal_curvature_map(vcm)); + interpolated_corrected_curvatures(pmesh, np.vertex_principal_curvature_map(vcm)); } /** @@ -1023,11 +1023,11 @@ template - void interpolated_corrected_curvatures(const PolygonMesh& pmesh, - const NamedParameters& np = parameters::default_values()) + typename NamedParameters = parameters::Default_named_parameters> + void interpolated_corrected_curvatures(const PolygonMesh& pmesh, + const NamedParameters& np = parameters::default_values()) { - internal::Interpolated_corrected_curvatures_computer(pmesh, np); + internal::Interpolated_corrected_curvatures_computer(pmesh, np); } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp index 7e376c887716..b2dbcc0850bb 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp @@ -19,130 +19,130 @@ typedef boost::graph_traits::edge_descriptor edge_descriptor; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; void test(std::string mesh_path, EpicKernel::FT rel_expansion_radius, EpicKernel::FT rel_noise_magnitude) { - SMesh pmesh; - const std::string filename = CGAL::data_file_path(mesh_path); + SMesh pmesh; + const std::string filename = CGAL::data_file_path(mesh_path); - if (!CGAL::IO::read_polygon_mesh(filename, pmesh)) - { - std::cerr << "Invalid input file." << std::endl; - } + if (!CGAL::IO::read_polygon_mesh(filename, pmesh)) + { + std::cerr << "Invalid input file." << std::endl; + } - bool created = false; + bool created = false; - SMesh::Property_map mean_curvature_map, gaussian_curvature_map; - boost::tie(mean_curvature_map, created) = pmesh.add_property_map("v:mean_curvature_map", 0); - assert(created); + SMesh::Property_map mean_curvature_map, gaussian_curvature_map; + boost::tie(mean_curvature_map, created) = pmesh.add_property_map("v:mean_curvature_map", 0); + assert(created); + + boost::tie(gaussian_curvature_map, created) = pmesh.add_property_map("v:gaussian_curvature_map", 0); + assert(created); + + // getting the max and min edge lengthes + const auto edge_range = CGAL::edges(pmesh); + + const auto edge_length_comparator = [&, pmesh](auto l, auto r) { + return PMP::edge_length(l, pmesh) < PMP::edge_length(r, pmesh); + }; + + const edge_descriptor longest_edge = *std::max_element(edge_range.begin(), edge_range.end(), edge_length_comparator); + const EpicKernel::FT max_edge_length = PMP::edge_length(longest_edge, pmesh); + + const edge_descriptor shortest_edge = *std::min_element(edge_range.begin(), edge_range.end(), edge_length_comparator); + const EpicKernel::FT min_edge_length = PMP::edge_length(shortest_edge, pmesh); + + + if (rel_noise_magnitude > 0) + { + if (!CGAL::is_triangle_mesh(pmesh)) + return; - boost::tie(gaussian_curvature_map, created) = pmesh.add_property_map("v:gaussian_curvature_map", 0); + SMesh::Property_map vnm; + boost::tie(vnm, created) = pmesh.add_property_map("v:vnm", { 0 , 0 , 0 }); assert(created); - // getting the max and min edge lengthes - const auto edge_range = CGAL::edges(pmesh); - - const auto edge_length_comparator = [&, pmesh](auto l, auto r) { - return PMP::edge_length(l, pmesh) < PMP::edge_length(r, pmesh); - }; - - const edge_descriptor longest_edge = *std::max_element(edge_range.begin(), edge_range.end(), edge_length_comparator); - const EpicKernel::FT max_edge_length = PMP::edge_length(longest_edge, pmesh); - - const edge_descriptor shortest_edge = *std::min_element(edge_range.begin(), edge_range.end(), edge_length_comparator); - const EpicKernel::FT min_edge_length = PMP::edge_length(shortest_edge, pmesh); - - - if (rel_noise_magnitude > 0) - { - if (!CGAL::is_triangle_mesh(pmesh)) - return; - - SMesh::Property_map vnm; - boost::tie(vnm, created) = pmesh.add_property_map("v:vnm", { 0 , 0 , 0 }); - assert(created); - - CGAL::Polygon_mesh_processing::compute_vertex_normals(pmesh, vnm); - - PMP::random_perturbation(pmesh, rel_noise_magnitude * min_edge_length, CGAL::parameters::random_seed(0)); - PMP::interpolated_corrected_mean_curvature( - pmesh, - mean_curvature_map, - CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length).vertex_normal_map(vnm) - ); - PMP::interpolated_corrected_gaussian_curvature( - pmesh, - gaussian_curvature_map, - CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length).vertex_normal_map(vnm) - ); - } - else { - PMP::interpolated_corrected_mean_curvature( - pmesh, - mean_curvature_map, - CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) - ); - PMP::interpolated_corrected_gaussian_curvature( - pmesh, - gaussian_curvature_map, - CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) - ); - - } - - - - //PMP::interpolated_corrected_mean_curvature( - // pmesh, - // mean_curvature_map, - // CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) - //); - //PMP::interpolated_corrected_gaussian_curvature( - // pmesh, - // gaussian_curvature_map, - // CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) - //); - - - const EpicKernel::FT max_mean_curvature = *std::max_element(mean_curvature_map.begin(), mean_curvature_map.end()); - const EpicKernel::FT min_mean_curvature = *std::min_element(mean_curvature_map.begin(), mean_curvature_map.end()); - const EpicKernel::FT max_gaussian_curvature = *std::max_element(gaussian_curvature_map.begin(), gaussian_curvature_map.end()); - const EpicKernel::FT min_gaussian_curvature = *std::min_element(gaussian_curvature_map.begin(), gaussian_curvature_map.end()); - - std::cout << "# " << mesh_path << ":\n" - << "expansion radius ratio to max length / expansion radius = " << rel_expansion_radius << " / " << rel_expansion_radius * max_edge_length << ",\n" - << "max perturbation ratio to minlength / max perturbation = " << rel_noise_magnitude << " / " << rel_noise_magnitude * min_edge_length << "\n" - << "mean curvature: min = " << min_mean_curvature << " <-> " << max_mean_curvature << " = max" << "\n" - << "gaussian curvature: min = " << min_gaussian_curvature << " <-> " << max_gaussian_curvature << " = max" << "\n\n\n"; + CGAL::Polygon_mesh_processing::compute_vertex_normals(pmesh, vnm); + + PMP::random_perturbation(pmesh, rel_noise_magnitude * min_edge_length, CGAL::parameters::random_seed(0)); + PMP::interpolated_corrected_mean_curvature( + pmesh, + mean_curvature_map, + CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length).vertex_normal_map(vnm) + ); + PMP::interpolated_corrected_gaussian_curvature( + pmesh, + gaussian_curvature_map, + CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length).vertex_normal_map(vnm) + ); + } + else { + PMP::interpolated_corrected_mean_curvature( + pmesh, + mean_curvature_map, + CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) + ); + PMP::interpolated_corrected_gaussian_curvature( + pmesh, + gaussian_curvature_map, + CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) + ); + + } + + + + //PMP::interpolated_corrected_mean_curvature( + // pmesh, + // mean_curvature_map, + // CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) + //); + //PMP::interpolated_corrected_gaussian_curvature( + // pmesh, + // gaussian_curvature_map, + // CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) + //); + + + const EpicKernel::FT max_mean_curvature = *std::max_element(mean_curvature_map.begin(), mean_curvature_map.end()); + const EpicKernel::FT min_mean_curvature = *std::min_element(mean_curvature_map.begin(), mean_curvature_map.end()); + const EpicKernel::FT max_gaussian_curvature = *std::max_element(gaussian_curvature_map.begin(), gaussian_curvature_map.end()); + const EpicKernel::FT min_gaussian_curvature = *std::min_element(gaussian_curvature_map.begin(), gaussian_curvature_map.end()); + + std::cout << "# " << mesh_path << ":\n" + << "expansion radius ratio to max length / expansion radius = " << rel_expansion_radius << " / " << rel_expansion_radius * max_edge_length << ",\n" + << "max perturbation ratio to minlength / max perturbation = " << rel_noise_magnitude << " / " << rel_noise_magnitude * min_edge_length << "\n" + << "mean curvature: min = " << min_mean_curvature << " <-> " << max_mean_curvature << " = max" << "\n" + << "gaussian curvature: min = " << min_gaussian_curvature << " <-> " << max_gaussian_curvature << " = max" << "\n\n\n"; } int main() { - const std::vector mesh_pathes_to_test = { - "meshes/icc_test/Sphere Quads + Tris.obj", - "meshes/icc_test/Sphere Quads + Tris 100352.obj", - "meshes/icc_test/Sphere Tris Ico.obj", - "meshes/icc_test/Sphere Tris Tet.obj", - "meshes/icc_test/Sphere Tris Oct.obj", - "meshes/icc_test/Sphere Quads.obj", - "meshes/icc_test/Sphere Quads Remesh.obj", - "meshes/icc_test/Sphere Ngons + Quads + Tris.obj", - "meshes/icc_test/Cube with fillet Quads.obj", - "meshes/cylinder.off", - "meshes/icc_test/Lantern Tris.obj", - "meshes/icc_test/Lantern Quads.obj" - }; - - const std::vector rel_expansion_radii = { 0, 0.1, 0.5, 1 }; - const std::vector rel_noise_magnitudes = { 0, 0.5, 0.9 }; - - for (auto mesh_path : mesh_pathes_to_test) { - for (EpicKernel::FT rel_expansion_radius : rel_expansion_radii) - for (EpicKernel::FT rel_noise_magnitude : rel_noise_magnitudes) - { - test(mesh_path, rel_expansion_radius, rel_noise_magnitude); - } - - std::cout << "_________________________________________________________________________________\n\n"; - } + const std::vector mesh_pathes_to_test = { + "meshes/icc_test/Sphere Quads + Tris.obj", + "meshes/icc_test/Sphere Quads + Tris 100352.obj", + "meshes/icc_test/Sphere Tris Ico.obj", + "meshes/icc_test/Sphere Tris Tet.obj", + "meshes/icc_test/Sphere Tris Oct.obj", + "meshes/icc_test/Sphere Quads.obj", + "meshes/icc_test/Sphere Quads Remesh.obj", + "meshes/icc_test/Sphere Ngons + Quads + Tris.obj", + "meshes/icc_test/Cube with fillet Quads.obj", + "meshes/cylinder.off", + "meshes/icc_test/Lantern Tris.obj", + "meshes/icc_test/Lantern Quads.obj" + }; + + const std::vector rel_expansion_radii = { 0, 0.1, 0.5, 1 }; + const std::vector rel_noise_magnitudes = { 0, 0.5, 0.9 }; + + for (auto mesh_path : mesh_pathes_to_test) { + for (EpicKernel::FT rel_expansion_radius : rel_expansion_radii) + for (EpicKernel::FT rel_noise_magnitude : rel_noise_magnitudes) + { + test(mesh_path, rel_expansion_radius, rel_noise_magnitude); + } + + std::cout << "_________________________________________________________________________________\n\n"; + } } diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index c69b73694980..db26f59168a8 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -658,13 +658,13 @@ private Q_SLOTS: if(does_exist) sm_item->face_graph()->remove_property_map(pmap); std::tie(pmap, does_exist) = - sm_item->face_graph()->property_map("v:interpolated_corrected_mean_curvature"); + sm_item->face_graph()->property_map("v:interpolated_corrected_mean_curvature"); if (does_exist) sm_item->face_graph()->remove_property_map(pmap); std::tie(pmap, does_exist) = - sm_item->face_graph()->property_map("v:interpolated_corrected_gaussian_curvature"); + sm_item->face_graph()->property_map("v:interpolated_corrected_gaussian_curvature"); if (does_exist) - sm_item->face_graph()->remove_property_map(pmap); + sm_item->face_graph()->remove_property_map(pmap); }); QApplication::restoreOverrideCursor(); sm_item->invalidateOpenGLBuffers(); @@ -704,13 +704,13 @@ private Q_SLOTS: dock_widget->zoomToMaxButton->setEnabled(jacobian_max.count(sm_item)>0); break; case 4: - dock_widget->zoomToMinButton->setEnabled(mean_curvature_min.count(sm_item) > 0); - dock_widget->zoomToMaxButton->setEnabled(mean_curvature_max.count(sm_item) > 0); - break; + dock_widget->zoomToMinButton->setEnabled(mean_curvature_min.count(sm_item) > 0); + dock_widget->zoomToMaxButton->setEnabled(mean_curvature_max.count(sm_item) > 0); + break; case 5: - dock_widget->zoomToMinButton->setEnabled(gaussian_curvature_min.count(sm_item) > 0); - dock_widget->zoomToMaxButton->setEnabled(gaussian_curvature_max.count(sm_item) > 0); - break; + dock_widget->zoomToMinButton->setEnabled(gaussian_curvature_min.count(sm_item) > 0); + dock_widget->zoomToMaxButton->setEnabled(gaussian_curvature_max.count(sm_item) > 0); + break; default: break; } @@ -745,13 +745,13 @@ private Q_SLOTS: std::tie(mean_curvature, found) = smesh.property_map("v:interpolated_corrected_mean_curvature"); if (found) { - smesh.remove_property_map(mean_curvature); + smesh.remove_property_map(mean_curvature); } SMesh::Property_map gaussian_curvature; std::tie(gaussian_curvature, found) = smesh.property_map("v:interpolated_corrected_gaussian_curvature"); if (found) { - smesh.remove_property_map(gaussian_curvature); + smesh.remove_property_map(gaussian_curvature); } } @@ -795,54 +795,54 @@ private Q_SLOTS: void setExpandingRadius(int val_int) { - double sliderMin = dock_widget->expandingRadiusSlider->minimum(); - double sliderMax = dock_widget->expandingRadiusSlider->maximum() - sliderMin; - double val = val_int - sliderMin; - sliderMin = 0; + double sliderMin = dock_widget->expandingRadiusSlider->minimum(); + double sliderMax = dock_widget->expandingRadiusSlider->maximum() - sliderMin; + double val = val_int - sliderMin; + sliderMin = 0; - SMesh& smesh = *(qobject_cast(scene->item(scene->mainSelectionIndex())))->face_graph(); + SMesh& smesh = *(qobject_cast(scene->item(scene->mainSelectionIndex())))->face_graph(); - auto vpm = get(CGAL::vertex_point, smesh); + auto vpm = get(CGAL::vertex_point, smesh); - if (maxEdgeLength < 0) + if (maxEdgeLength < 0) + { + auto edge_range = CGAL::edges(smesh); + + if (edge_range.begin() == edge_range.end()) { - auto edge_range = CGAL::edges(smesh); - - if (edge_range.begin() == edge_range.end()) - { - expand_radius = 0; - dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expand_radius)); - return; - } - - auto edge_reference = std::max_element(edge_range.begin(), edge_range.end(), [&, vpm, smesh](auto l, auto r) { - auto res = EPICK().compare_squared_distance_3_object()( - get(vpm, source((l), smesh)), - get(vpm, target((l), smesh)), - get(vpm, source((r), smesh)), - get(vpm, target((r), smesh))); - return res == CGAL::SMALLER; - }); - - // if edge_reference is not derefrenceble - if (edge_reference == edge_range.end()) - { - expand_radius = 0; - dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expand_radius)); - return; - } - - maxEdgeLength = sqrt( - (get(vpm, source((*edge_reference), smesh)) - get(vpm, target((*edge_reference), smesh))) - .squared_length() - ); - + expand_radius = 0; + dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expand_radius)); + return; + } + + auto edge_reference = std::max_element(edge_range.begin(), edge_range.end(), [&, vpm, smesh](auto l, auto r) { + auto res = EPICK().compare_squared_distance_3_object()( + get(vpm, source((l), smesh)), + get(vpm, target((l), smesh)), + get(vpm, source((r), smesh)), + get(vpm, target((r), smesh))); + return res == CGAL::SMALLER; + }); + + // if edge_reference is not derefrenceble + if (edge_reference == edge_range.end()) + { + expand_radius = 0; + dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expand_radius)); + return; } + + maxEdgeLength = sqrt( + (get(vpm, source((*edge_reference), smesh)) - get(vpm, target((*edge_reference), smesh))) + .squared_length() + ); + + } - double outMin = 0, outMax = 5 * maxEdgeLength, base = 1.2; + double outMin = 0, outMax = 5 * maxEdgeLength, base = 1.2; - expand_radius = (pow(base, val) - 1) * outMax / (pow(base, sliderMax) - 1); - dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expand_radius)); + expand_radius = (pow(base, val) - 1) * outMax / (pow(base, sliderMax) - 1); + dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expand_radius)); } @@ -864,45 +864,45 @@ private Q_SLOTS: smesh.add_property_map(tied_string, 0); if (non_init) { - if (vnm_exists) { - if (mu_index == MEAN_CURVATURE) - PMP::interpolated_corrected_mean_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius).vertex_normal_map(vnm)); - else - PMP::interpolated_corrected_gaussian_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius).vertex_normal_map(vnm)); - } - else { - if (mu_index == MEAN_CURVATURE) - PMP::interpolated_corrected_mean_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius)); - else - PMP::interpolated_corrected_gaussian_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius)); - } - double res_min = ARBITRARY_DBL_MAX, - res_max = -ARBITRARY_DBL_MAX; - SMesh::Vertex_index min_index, max_index; - for (SMesh::Vertex_index v : vertices(smesh)) + if (vnm_exists) { + if (mu_index == MEAN_CURVATURE) + PMP::interpolated_corrected_mean_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius).vertex_normal_map(vnm)); + else + PMP::interpolated_corrected_gaussian_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius).vertex_normal_map(vnm)); + } + else { + if (mu_index == MEAN_CURVATURE) + PMP::interpolated_corrected_mean_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius)); + else + PMP::interpolated_corrected_gaussian_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius)); + } + double res_min = ARBITRARY_DBL_MAX, + res_max = -ARBITRARY_DBL_MAX; + SMesh::Vertex_index min_index, max_index; + for (SMesh::Vertex_index v : vertices(smesh)) + { + if (mu_i_map[v] > res_max) { - if (mu_i_map[v] > res_max) - { - res_max = mu_i_map[v]; - max_index = v; - } - if (mu_i_map[v] < res_min) - { - res_min = mu_i_map[v]; - min_index = v; - } - } - if (mu_index == MEAN_CURVATURE){ - mean_curvature_max[item] = std::make_pair(res_max, max_index); - mean_curvature_min[item] = std::make_pair(res_min, min_index); + res_max = mu_i_map[v]; + max_index = v; } - else { - gaussian_curvature_max[item] = std::make_pair(res_max, max_index); - gaussian_curvature_min[item] = std::make_pair(res_min, min_index); + if (mu_i_map[v] < res_min) + { + res_min = mu_i_map[v]; + min_index = v; } + } + if (mu_index == MEAN_CURVATURE){ + mean_curvature_max[item] = std::make_pair(res_max, max_index); + mean_curvature_min[item] = std::make_pair(res_min, min_index); + } + else { + gaussian_curvature_max[item] = std::make_pair(res_max, max_index); + gaussian_curvature_min[item] = std::make_pair(res_min, min_index); + } - connect(item, &Scene_surface_mesh_item::itemChanged, - this, &DisplayPropertyPlugin::resetProperty); + connect(item, &Scene_surface_mesh_item::itemChanged, + this, &DisplayPropertyPlugin::resetProperty); } treat_sm_property(tied_string, item->face_graph()); } @@ -1271,20 +1271,20 @@ private Q_SLOTS: break; case 4: { - ::zoomToId(*item->face_graph(), - QString("v%1").arg(mean_curvature_min[item].second), - getActiveViewer(), - dummy_fd, - dummy_p); + ::zoomToId(*item->face_graph(), + QString("v%1").arg(mean_curvature_min[item].second), + getActiveViewer(), + dummy_fd, + dummy_p); } break; case 5: { - ::zoomToId(*item->face_graph(), - QString("v%1").arg(gaussian_curvature_min[item].second), - getActiveViewer(), - dummy_fd, - dummy_p); + ::zoomToId(*item->face_graph(), + QString("v%1").arg(gaussian_curvature_min[item].second), + getActiveViewer(), + dummy_fd, + dummy_p); } break; break; @@ -1324,20 +1324,20 @@ private Q_SLOTS: break; case 4: { - ::zoomToId(*item->face_graph(), - QString("v%1").arg(mean_curvature_max[item].second), - getActiveViewer(), - dummy_fd, - dummy_p); + ::zoomToId(*item->face_graph(), + QString("v%1").arg(mean_curvature_max[item].second), + getActiveViewer(), + dummy_fd, + dummy_p); } break; case 5: { - ::zoomToId(*item->face_graph(), - QString("v%1").arg(gaussian_curvature_max[item].second), - getActiveViewer(), - dummy_fd, - dummy_p); + ::zoomToId(*item->face_graph(), + QString("v%1").arg(gaussian_curvature_max[item].second), + getActiveViewer(), + dummy_fd, + dummy_p); } break; default: From e79e34df4fe4e69279865c3b8294159774ad04dc Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 16 Nov 2022 12:53:43 +0200 Subject: [PATCH 081/161] trim trailing whitespaces --- .../Plugins/Display/Display_property_plugin.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index db26f59168a8..6efc7f213fe2 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -807,14 +807,14 @@ private Q_SLOTS: if (maxEdgeLength < 0) { auto edge_range = CGAL::edges(smesh); - + if (edge_range.begin() == edge_range.end()) { expand_radius = 0; dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expand_radius)); return; } - + auto edge_reference = std::max_element(edge_range.begin(), edge_range.end(), [&, vpm, smesh](auto l, auto r) { auto res = EPICK().compare_squared_distance_3_object()( get(vpm, source((l), smesh)), @@ -823,7 +823,7 @@ private Q_SLOTS: get(vpm, target((r), smesh))); return res == CGAL::SMALLER; }); - + // if edge_reference is not derefrenceble if (edge_reference == edge_range.end()) { @@ -831,12 +831,12 @@ private Q_SLOTS: dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expand_radius)); return; } - + maxEdgeLength = sqrt( (get(vpm, source((*edge_reference), smesh)) - get(vpm, target((*edge_reference), smesh))) .squared_length() ); - + } double outMin = 0, outMax = 5 * maxEdgeLength, base = 1.2; From 400f9de47c68df5ac92a9eba42f0865af1c60515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 17 Nov 2022 19:49:42 +0100 Subject: [PATCH 082/161] first draft version for getting the triangulation of a face WARNING: COMPILATION NOT TESTED! --- .../triangulate_faces.h | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/triangulate_faces.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/triangulate_faces.h index 0b48967f2733..abe56ea35a61 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/triangulate_faces.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/triangulate_faces.h @@ -499,6 +499,153 @@ bool triangulate_face(typename boost::graph_traits::face_descriptor return modifier.triangulate_face(f, pmesh, use_cdt, visitor); } + +#ifndef CGAL_TRIANGULATE_FACES_DO_NOT_USE_CDT2 +template +OutputIterator +face_triangulation(typename boost::graph_traits::face_descriptor f, + PolygonMesh& pmesh, + OutputIterator out, + const NamedParameters& np = parameters::default_values()) +{ + using parameters::choose_parameter; + using parameters::get_parameter; + + //VertexPointMap + typedef typename GetVertexPointMap::type VPMap; + VPMap vpmap = choose_parameter(get_parameter(np, internal_np::vertex_point), + get_property_map(vertex_point, pmesh)); + + //Kernel + typedef typename GetGeomTraits::type Kernel; + Kernel traits = choose_parameter(get_parameter(np, internal_np::geom_traits)); + + //Face_info + typedef typename internal::Triangulate_modifier>::Face_info Face_info; + + //CDT + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef CGAL::Projection_traits_3 P_traits; + typedef CGAL::Triangulation_vertex_base_with_info_2 Vb; + typedef CGAL::Triangulation_face_base_with_info_2 Fb1; + typedef CGAL::Constrained_triangulation_face_base_2 Fb; + typedef CGAL::Triangulation_data_structure_2 TDS; + typedef CGAL::Exact_intersections_tag Itag; + typedef CGAL::Constrained_Delaunay_triangulation_2 CDT; + P_traits cdt_traits(normal); + CDT cdt(cdt_traits); + + std::size_t original_size = CGAL::halfedges_around_face(halfedge(f, pmesh), pmesh).size(); + + // Halfedge_around_facet_circulator + typedef typename CDT::Vertex_handle Tr_Vertex_handle; + halfedge_descriptor start = halfedge(f, pmesh); + halfedge_descriptor h = start; + Tr_Vertex_handle previous, first; + do + { + Tr_Vertex_handle vh = cdt.insert(get(_vpmap, target(h, pmesh))); + if (first == Tr_Vertex_handle()) { + first = vh; + } + vh->info() = h; + if(previous != Tr_Vertex_handle() && previous != vh) { + cdt.insert_constraint(previous, vh); + } + previous = vh; + h = next(h, pmesh); + + } while( h != start ); + cdt.insert_constraint(previous, first); + + // sets mark is_external + for(typename CDT::All_faces_iterator fit = cdt.all_faces_begin(), + end = cdt.all_faces_end(); + fit != end; ++fit) + { + fit->info().is_external = false; + } + std::queue face_queue; + face_queue.push(cdt.infinite_vertex()->face()); + while(! face_queue.empty() ) + { + typename CDT::Face_handle fh = face_queue.front(); + face_queue.pop(); + + if(fh->info().is_external) + continue; + + fh->info().is_external = true; + for(int i = 0; i <3; ++i) + { + if(!cdt.is_constrained(typename CDT::Edge(fh, i))) + { + face_queue.push(fh->neighbor(i)); + } + } + } + + if(cdt.dimension() != 2 || cdt.number_of_vertices() != original_size) + return out; + + for(typename CDT::Finite_edges_iterator eit = cdt.finite_edges_begin(), + end = cdt.finite_edges_end(); + eit != end; ++eit) + { + typename CDT::Face_handle fh = eit->first; + const int index = eit->second; + typename CDT::Face_handle opposite_fh = fh->neighbor(eit->second); + const int opposite_index = opposite_fh->index(fh); + + const Tr_Vertex_handle va = fh->vertex(cdt. cw(index)); + const Tr_Vertex_handle vb = fh->vertex(cdt.ccw(index)); + + if( ! (is_external(fh) && is_external(opposite_fh))//not both fh are external + && ! cdt.is_constrained(*eit) ) //and edge is not constrained + { + // strictly internal edge + halfedge_descriptor hnew = halfedge(add_edge(pmesh), pmesh), + hnewopp = opposite(hnew, pmesh); + + fh->info().e[index] = hnew; + opposite_fh->info().e[opposite_index] = hnewopp; + + set_target(hnew, target(va->info(), pmesh), pmesh); + set_target(hnewopp, target(vb->info(), pmesh), pmesh); + } + if( cdt.is_constrained(*eit) ) //edge is constrained + { + if(!is_external(fh)) { + fh->info().e[index] = va->info(); + } + if(!is_external(opposite_fh)) { + opposite_fh->info().e[opposite_index] = vb->info(); + } + } + } + for(typename CDT::Finite_faces_iterator fit = cdt.finite_faces_begin(), + end = cdt.finite_faces_end(); + fit != end; ++fit) + { + if(!is_external(fit)) + { + halfedge_descriptor h0 = fit->info().e[0]; + halfedge_descriptor h1 = fit->info().e[1]; + halfedge_descriptor h2 = fit->info().e[2]; + *out++=std::make_tuple(target(h0, pmesh), target(h1, pmesh), target(h2,pmesh)); + } + } + + return out; +} +#endif + + + /** * \ingroup PMP_meshing_grp * From 80e3522eaa294462a26c758745908c5ff5cc9ffb Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 18 Nov 2022 12:56:08 +0200 Subject: [PATCH 083/161] incomplete (single vertex curvature) --- ...nterpolated_corrected_curvature_measures.h | 465 +++++++++++++++++- 1 file changed, 455 insertions(+), 10 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 4006b761679f..3f118bdba45d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -598,7 +598,7 @@ namespace internal { private: - void interpolated_corrected_all_measures_all_faces() + void interpolated_corrected_selected_measures_all_faces() { std::vector x; std::vector u; @@ -629,23 +629,239 @@ namespace internal { Vertex_measures expand_interpolated_corrected_measure_vertex_no_radius(Vertex_descriptor v) { - Vertex_measures vertex_curvatures; + Vertex_measures vertex_measures; for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { - vertex_curvatures.area_measure += get(mu0_map, f); + vertex_measures.area_measure += get(mu0_map, f); if (is_mean_curvature_selected) - vertex_curvatures.mean_curvature_measure += get(mu1_map, f); + vertex_measures.mean_curvature_measure += get(mu1_map, f); if (is_gaussian_curvature_selected) - vertex_curvatures.gaussian_curvature_measure += get(mu2_map, f); + vertex_measures.gaussian_curvature_measure += get(mu2_map, f); if (is_principal_curvature_selected) { const std::array face_anisotropic_measure = get(muXY_map, f); + for (std::size_t i = 0; i < 3 * 3; i++) + vertex_measures.anisotropic_measure[i] += face_anisotropic_measure[i]; + } + } + + return vertex_measures; + } + + Vertex_measures expand_interpolated_corrected_measure_vertex(Vertex_descriptor v) + { + std::queue bfs_queue; + std::unordered_set bfs_visited; + + Point_3 vp = get(vpm, v); + Vector_3 c = Vector_3(vp.x(), vp.y(), vp.z()); + + Vertex_measures vertex_measures; + + for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + if (f != boost::graph_traits::null_face()) + { + bfs_queue.push(f); + bfs_visited.insert(f); + } + } + while (!bfs_queue.empty()) { + Face_descriptor fi = bfs_queue.front(); + bfs_queue.pop(); + + // looping over vertices in face to get point coordinates + std::vector x; + for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) + { + Point_3 pi = get(vpm, vi); + x.push_back(Vector_3(pi.x(), pi.y(), pi.z())); + } + + const FT f_ratio = face_in_ball_ratio(x, ball_radius, c); + + if (f_ratio != 0.0) + { + vertex_measures.area_measure += f_ratio * get(mu0_map, fi); + + if (is_mean_curvature_selected) + vertex_measures.mean_curvature_measure += f_ratio * get(mu1_map, fi); + + if (is_gaussian_curvature_selected) + vertex_measures.gaussian_curvature_measure += f_ratio * get(mu2_map, fi); + + if (is_principal_curvature_selected) + { + const std::array face_anisotropic_measure = get(muXY_map, fi); + for (std::size_t i = 0; i < 3 * 3; i++) + vertex_measures.anisotropic_measure[i] += f_ratio * face_anisotropic_measure[i]; + } + + for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) + { + if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) + { + bfs_queue.push(fj); + bfs_visited.insert(fj); + } + } + } + } + return vertex_measures; + } + + void compute_selected_curvatures() { + interpolated_corrected_selected_measures_all_faces(); + + for (Vertex_descriptor v : vertices(pmesh)) + { + Vertex_measures vertex_measures = (ball_radius < 0) ? + expand_interpolated_corrected_measure_vertex_no_radius(v) : + expand_interpolated_corrected_measure_vertex(v); + + if (is_mean_curvature_selected) { + vertex_measures.area_measure != 0 ? + put(mean_curvature_map, v, 0.5 * vertex_measures.mean_curvature_measure / vertex_measures.area_measure) : + put(mean_curvature_map, v, 0); + } + + if (is_gaussian_curvature_selected) { + vertex_measures.area_measure != 0 ? + put(gaussian_curvature_map, v, vertex_measures.gaussian_curvature_measure / vertex_measures.area_measure) : + put(gaussian_curvature_map, v, 0); + } + + if (is_principal_curvature_selected) { + const Vector_3 v_normal = get(vnm, v); + const Principal_curvature principal_curvature = principal_curvature_from_anisotropic_measures( + vertex_measures.anisotropic_measure, + vertex_measures.area_measure, + v_normal + ); + put(principal_curvature_map, v, principal_curvature); + } + } + } + }; + + template + class Interpolated_corrected_curvatures_point_computer + { + typedef typename GetGeomTraits::type GT; + + typedef typename GT::FT FT; + typedef typename GT::Point_3 Point_3; + typedef typename GT::Vector_3 Vector_3; + + typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; + typedef typename boost::graph_traits::edge_descriptor Edge_descriptor; + typedef typename boost::graph_traits::face_descriptor Face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + + typedef typename GetVertexPointMap::const_type Vertex_position_map; + + typedef dynamic_vertex_property_t Vector_map_tag; + typedef typename boost::property_map::const_type Default_vector_map; + typedef typename internal_np::Lookup_named_param_def::type Vertex_normal_map; + + private: + const PolygonMesh& pmesh; + Vertex_position_map vpm; + Vertex_normal_map vnm; + FT ball_radius; + + bool is_mean_curvature_selected; + bool is_gaussian_curvature_selected; + bool is_principal_curvature_selected; + + Point_3 point; + + void set_named_params(const NamedParameters& np) + { + using parameters::choose_parameter; + using parameters::get_parameter; + using parameters::is_default_parameter; + + vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), + get_const_property_map(CGAL::vertex_point, pmesh)); + + vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), + get(Vector_map_tag(), pmesh)); + + if (is_default_parameter::value) + compute_vertex_normals(pmesh, vnm, np); + + const FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1); + + is_mean_curvature_selected = !is_default_parameter::value; + is_gaussian_curvature_selected = !is_default_parameter::value; + is_principal_curvature_selected = !is_default_parameter::value; + + set_ball_radius(radius); + } + + void set_ball_radius(const FT radius) { + if (radius == 0) + ball_radius = average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; + else + ball_radius = radius; + } + + public: + + Interpolated_corrected_curvatures_computer(const PolygonMesh& pmesh, + const NamedParameters& np = parameters::default_values() + ) : + pmesh(pmesh) + { + set_named_params(np); + + if (is_mean_curvature_selected || is_gaussian_curvature_selected || is_principal_curvature_selected) + { + set_property_maps(); + + compute_selected_curvatures(); + } + } + + private: + Vertex_measures expand_interpolated_corrected_measure_vertex_no_radius(Vertex_descriptor v) + { + Vertex_measures vertex_curvatures; + + std::vector x; + std::vector u; + + for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + + for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) + { + Point_3 p = get(vpm, v); + x.push_back(Vector_3(p.x(), p.y(), p.z())); + u.push_back(get(vnm, v)); + } + + vertex_curvatures.area_measure += interpolated_corrected_area_measure_face(u, x); + + if (is_mean_curvature_selected) + vertex_curvatures.mean_curvature_measure += interpolated_corrected_mean_curvature_measure_face(u, x); + + if (is_gaussian_curvature_selected) + vertex_curvatures.gaussian_curvature_measure += interpolated_corrected_gaussian_curvature_measure_face(u, x); + + if (is_principal_curvature_selected) + { + const std::array face_anisotropic_measure = interpolated_corrected_anisotropic_measure_face(u, x); for (std::size_t i = 0; i < 3 * 3; i++) vertex_curvatures.anisotropic_measure[i] += face_anisotropic_measure[i]; } + + x.clear(); + u.clear(); } return vertex_curvatures; @@ -668,12 +884,15 @@ namespace internal { bfs_visited.insert(f); } } + + std::vector x; + std::vector u; + while (!bfs_queue.empty()) { Face_descriptor fi = bfs_queue.front(); bfs_queue.pop(); // looping over vertices in face to get point coordinates - std::vector x; for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) { Point_3 pi = get(vpm, vi); @@ -684,6 +903,11 @@ namespace internal { if (f_ratio != 0.0) { + for (Vertex_descriptor vi : vertices_around_face(halfedge(f, pmesh), pmesh)) + { + u.push_back(get(vnm, vi)); + } + vertex_curvatures.area_measure += f_ratio * get(mu0_map, fi); if (is_mean_curvature_selected) @@ -699,6 +923,9 @@ namespace internal { vertex_curvatures.anisotropic_measure[i] += f_ratio * face_anisotropic_measure[i]; } + x.clear(); + u.clear(); + for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) { if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) @@ -713,13 +940,11 @@ namespace internal { } void compute_selected_curvatures() { - interpolated_corrected_all_measures_all_faces(); - for (Vertex_descriptor v : vertices(pmesh)) { Vertex_measures vertex_curvatures = (ball_radius < 0) ? - expand_interpolated_corrected_measure_vertex_no_radius(v) : - expand_interpolated_corrected_measure_vertex(v); + compute_expanded_interpolated_corrected_measure_vertex_no_radius(v) : + compute_expanded_interpolated_corrected_measure_vertex(v); if (is_mean_curvature_selected) { vertex_curvatures.area_measure != 0 ? @@ -745,7 +970,227 @@ namespace internal { } } + }; + template + class Interpolated_corrected_curvatures_element_computer + { + typedef typename GetGeomTraits::type GT; + + typedef typename GT::FT FT; + typedef typename GT::Point_3 Point_3; + typedef typename GT::Vector_3 Vector_3; + + typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; + typedef typename boost::graph_traits::edge_descriptor Edge_descriptor; + typedef typename boost::graph_traits::face_descriptor Face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + + typedef typename GetVertexPointMap::const_type Vertex_position_map; + + typedef dynamic_vertex_property_t Vector_map_tag; + typedef typename boost::property_map::const_type Default_vector_map; + typedef typename internal_np::Lookup_named_param_def::type Vertex_normal_map; + + private: + const PolygonMesh& pmesh; + Vertex_position_map vpm; + Vertex_normal_map vnm; + FT ball_radius; + + bool is_mean_curvature_selected; + bool is_gaussian_curvature_selected; + bool is_principal_curvature_selected; + + void set_named_params(const NamedParameters& np) + { + using parameters::choose_parameter; + using parameters::get_parameter; + using parameters::is_default_parameter; + + vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), + get_const_property_map(CGAL::vertex_point, pmesh)); + + vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), + get(Vector_map_tag(), pmesh)); + + if (is_default_parameter::value) + compute_vertex_normals(pmesh, vnm, np); + + const FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1); + + is_mean_curvature_selected = !is_default_parameter::value; + is_gaussian_curvature_selected = !is_default_parameter::value; + is_principal_curvature_selected = !is_default_parameter::value; + + set_ball_radius(radius); + } + + void set_ball_radius(const FT radius) { + if (radius == 0) + ball_radius = average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; + else + ball_radius = radius; + } + + public: + + Interpolated_corrected_curvatures_computer(const PolygonMesh& pmesh, + const NamedParameters& np = parameters::default_values() + ) : + pmesh(pmesh) + { + set_named_params(np); + + if (is_mean_curvature_selected || is_gaussian_curvature_selected || is_principal_curvature_selected) + { + set_property_maps(); + } + } + + private: + Vertex_measures expand_interpolated_corrected_measure_vertex_no_radius(Vertex_descriptor v) + { + Vertex_measures vertex_curvatures; + + std::vector x; + std::vector u; + + for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + + for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) + { + Point_3 p = get(vpm, v); + x.push_back(Vector_3(p.x(), p.y(), p.z())); + u.push_back(get(vnm, v)); + } + + vertex_curvatures.area_measure += interpolated_corrected_area_measure_face(u, x); + + if (is_mean_curvature_selected) + vertex_curvatures.mean_curvature_measure += interpolated_corrected_mean_curvature_measure_face(u, x); + + if (is_gaussian_curvature_selected) + vertex_curvatures.gaussian_curvature_measure += interpolated_corrected_gaussian_curvature_measure_face(u, x); + + if (is_principal_curvature_selected) + { + const std::array face_anisotropic_measure = interpolated_corrected_anisotropic_measure_face(u, x); + for (std::size_t i = 0; i < 3 * 3; i++) + vertex_curvatures.anisotropic_measure[i] += face_anisotropic_measure[i]; + } + + x.clear(); + u.clear(); + } + + return vertex_curvatures; + } + + Vertex_measures expand_interpolated_corrected_measure_vertex(Vertex_descriptor v) + { + std::queue bfs_queue; + std::unordered_set bfs_visited; + + Point_3 vp = get(vpm, v); + Vector_3 c = Vector_3(vp.x(), vp.y(), vp.z()); + + Vertex_measures vertex_curvatures; + + for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + if (f != boost::graph_traits::null_face()) + { + bfs_queue.push(f); + bfs_visited.insert(f); + } + } + + std::vector x; + std::vector u; + + while (!bfs_queue.empty()) { + Face_descriptor fi = bfs_queue.front(); + bfs_queue.pop(); + + // looping over vertices in face to get point coordinates + for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) + { + Point_3 pi = get(vpm, vi); + x.push_back(Vector_3(pi.x(), pi.y(), pi.z())); + } + + const FT f_ratio = face_in_ball_ratio(x, ball_radius, c); + + if (f_ratio != 0.0) + { + for (Vertex_descriptor vi : vertices_around_face(halfedge(f, pmesh), pmesh)) + { + u.push_back(get(vnm, vi)); + } + + vertex_curvatures.area_measure += f_ratio * get(mu0_map, fi); + + if (is_mean_curvature_selected) + vertex_curvatures.mean_curvature_measure += f_ratio * get(mu1_map, fi); + + if (is_gaussian_curvature_selected) + vertex_curvatures.gaussian_curvature_measure += f_ratio * get(mu2_map, fi); + + if (is_principal_curvature_selected) + { + const std::array face_anisotropic_measure = get(muXY_map, fi); + for (std::size_t i = 0; i < 3 * 3; i++) + vertex_curvatures.anisotropic_measure[i] += f_ratio * face_anisotropic_measure[i]; + } + + x.clear(); + u.clear(); + + for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) + { + if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) + { + bfs_queue.push(fj); + bfs_visited.insert(fj); + } + } + } + } + return vertex_curvatures; + } + + void compute_selected_curvatures() { + for (Vertex_descriptor v : vertices(pmesh)) + { + Vertex_measures vertex_curvatures = (ball_radius < 0) ? + compute_expanded_interpolated_corrected_measure_vertex_no_radius(v) : + compute_expanded_interpolated_corrected_measure_vertex(v); + + if (is_mean_curvature_selected) { + vertex_curvatures.area_measure != 0 ? + put(mean_curvature_map, v, 0.5 * vertex_curvatures.mean_curvature_measure / vertex_curvatures.area_measure) : + put(mean_curvature_map, v, 0); + } + + if (is_gaussian_curvature_selected) { + vertex_curvatures.area_measure != 0 ? + put(gaussian_curvature_map, v, vertex_curvatures.gaussian_curvature_measure / vertex_curvatures.area_measure) : + put(gaussian_curvature_map, v, 0); + } + + if (is_principal_curvature_selected) { + const Vector_3 v_normal = get(vnm, v); + const Principal_curvature principal_curvature = principal_curvature_from_anisotropic_measures( + vertex_curvatures.anisotropic_measure, + vertex_curvatures.area_measure, + v_normal + ); + put(principal_curvature_map, v, principal_curvature); + } + } + } }; From e302b02f764301209548fae1c435f8a92c892d02 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 19 Nov 2022 11:02:26 +0200 Subject: [PATCH 084/161] Docs: adding bibtex, ack of DGtal, Authors + Renaming vars + minor fix + removing last commit's approach --- Documentation/doc/biblio/geom.bib | 11 + .../PackageDescription.txt | 2 +- .../Polygon_mesh_processing.txt | 11 +- ...erpolated_corrected_curvatures_example.cpp | 14 +- ...orrected_curvatures_polyhedron_example.cpp | 10 +- ...nterpolated_corrected_curvature_measures.h | 496 +----------------- ..._corrected_principal_curvatures_plugin.cpp | 22 +- .../internal/parameters_interface.h | 2 +- 8 files changed, 66 insertions(+), 502 deletions(-) diff --git a/Documentation/doc/biblio/geom.bib b/Documentation/doc/biblio/geom.bib index 270f537e1394..37c42071a85f 100644 --- a/Documentation/doc/biblio/geom.bib +++ b/Documentation/doc/biblio/geom.bib @@ -152060,6 +152060,7 @@ @article{cvl-ew-12 Pages = {215--224}, Year = {2012}, Url = {http://monge.univ-mlv.fr/~colinde/pub/09edgewidth.pdf} +} @inproceedings{tang2009interactive, title={Interactive Hausdorff distance computation for general polygonal models}, @@ -152071,3 +152072,13 @@ @inproceedings{tang2009interactive year={2009}, organization={ACM} } + +@article{lachaud2020, + author = {Jacques-Olivier Lachaud and Pascal Romon and Boris Thibert and David Coeurjolly}, + journal = {Computer Graphics Forum (Proceedings of Symposium on Geometry Processing)}, + number = {5}, + title = {Interpolated corrected curvature measures for polygonal surfaces}, + volume = {39}, + month = jul, + year = {2020} +} diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 93f25c89f1fb..5090535be250 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -203,7 +203,7 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage. \cgalCRPSection{Corrected Curvature Functions} - `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` - `CGAL::Polygon_mesh_processing::Principal_curvature` 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 7932051ec000..2347ae1c7ae8 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 @@ -879,10 +879,7 @@ not provide storage for the normals. **************************************** \section PMPICC Computing Curvatures -This package provides methods to compute curvatures on polygonal meshes based on - - Interpolated corrected curvature measures for polygonal surfaces -. +This package provides methods to compute curvatures on polygonal meshes based on \cgalCite{lachaud2020} This includes mean curvature, gaussian curvature, principal curvatures and directions. These can be computed on triangle meshes, quad meshes, and meshes with n-gon faces. The algorithms used prove to work well in general. Also, on meshes with noise @@ -895,7 +892,7 @@ Polyhedron_3 and other polygonal mesh structures based on the Face Graph Model. These computations are performed using : - `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` Where it is recommended to use the last function for computing multiple curvatures (example: mean and gaussian) @@ -1145,6 +1142,10 @@ 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 interpolated corrected curvatures were implemented during GSoC 2022. This was implemented by Hossam Saeed and under +supervision of Sebastien Loriot, Jaques-Olivier Lachaud and David Coeurjolly. The implementation is based \cgalCite{lachaud2020}. +DGtal's implementation was also +used as a reference during the project. */ } /* namespace CGAL */ diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp index 7bf182c3ce3d..71aa06a8caec 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp @@ -38,10 +38,10 @@ int main(int argc, char* argv[]) assert(created); // we use a tuple of 2 scalar values and 2 vectors for principal curvatures and directions - Surface_Mesh::Property_map> principal_curvature_map; + Surface_Mesh::Property_map> principal_curvatures_and_directions_map; - boost::tie(principal_curvature_map, created) = smesh.add_property_map> - ("v:principal_curvature_map", { 0, 0, + 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); @@ -57,9 +57,9 @@ int main(int argc, char* argv[]) gaussian_curvature_map ); - PMP::interpolated_corrected_principal_curvatures( + PMP::interpolated_corrected_principal_curvatures_and_directions( smesh, - principal_curvature_map + principal_curvatures_and_directions_map ); // uncomment this to compute a curvature while specifying named parameters @@ -83,12 +83,12 @@ int main(int argc, char* argv[]) PMP::interpolated_corrected_curvatures( smesh, CGAL::parameters::vertex_mean_curvature_map(mean_curvature_map) - .vertex_principal_curvature_map(principal_curvature_map) + .vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) ); for (vertex_descriptor v : vertices(smesh)) { - auto PC = principal_curvature_map[v]; + auto PC = principal_curvatures_and_directions_map[v]; std::cout << v.idx() << ": HC = " << mean_curvature_map[v] << ", GC = " << gaussian_curvature_map[v] << "\n" << ", PC = [ " << PC.min_curvature << " , " << PC.max_curvature << " ]\n"; diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp index 929fc8d96a67..50fe1a014d8e 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp @@ -31,7 +31,7 @@ int main(int argc, char *argv[]) mean_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron), gaussian_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron); boost::property_map>>::type - principal_curvature_map = get(CGAL::dynamic_vertex_property_t>(), polyhedron); + principal_curvatures_and_directions_map = get(CGAL::dynamic_vertex_property_t>(), polyhedron); PMP::interpolated_corrected_mean_curvature( polyhedron, @@ -41,9 +41,9 @@ int main(int argc, char *argv[]) polyhedron, gaussian_curvature_map); - PMP::interpolated_corrected_principal_curvatures( + PMP::interpolated_corrected_principal_curvatures_and_directions( polyhedron, - principal_curvature_map); + principal_curvatures_and_directions_map); // uncomment this to compute a curvature while specifying named parameters // Example: an expansion ball radius of 0.5 and a vertex normals map (does not have to depend on positions) @@ -61,12 +61,12 @@ int main(int argc, char *argv[]) PMP::interpolated_corrected_curvatures( polyhedron, CGAL::parameters::vertex_mean_curvature_map(mean_curvature_map) - .vertex_principal_curvature_map(principal_curvature_map)); + .vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map)); int i = 0; for (vertex_descriptor v : vertices(polyhedron)) { - auto PC = get(principal_curvature_map, v); + auto PC = get(principal_curvatures_and_directions_map, v); std::cout << i << ": HC = " << get(mean_curvature_map, v) << ", GC = " << get(gaussian_curvature_map, v) << "\n" << ", PC = [ " << PC.min_curvature << " , " << PC.max_curvature << " ]\n"; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 3f118bdba45d..3b02341a2129 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -70,10 +70,10 @@ struct Principal_curvature { typename GT::Vector_3 min_direction, typename GT::Vector_3 max_direction ) { - min_curvature = min_curvature; - max_curvature = max_curvature; - min_direction = min_direction; - max_direction = max_direction; + this->min_curvature = min_curvature; + this->max_curvature = max_curvature; + this->min_direction = min_direction; + this->max_direction = max_direction; } }; @@ -440,7 +440,7 @@ namespace internal { } template - Principal_curvature principal_curvature_from_anisotropic_measures( + Principal_curvature principal_curvatures_and_directions_from_anisotropic_measures( const std::array anisotropic_measure, const typename GT::FT v_mu0, const typename GT::Vector_3 u_GT @@ -510,9 +510,9 @@ namespace internal { Default_scalar_map>::type Vertex_gaussian_curvature_map; typedef Constant_property_map> Default_principal_map; - typedef typename internal_np::Lookup_named_param_def::type Vertex_principal_curvature_map; + Default_principal_map>::type Vertex_principal_curvatures_and_directions_map; typedef typename boost::property_map>::const_type Face_scalar_measure_map; @@ -527,11 +527,11 @@ namespace internal { bool is_mean_curvature_selected; bool is_gaussian_curvature_selected; - bool is_principal_curvature_selected; + bool is_principal_curvatures_and_directions_selected; Vertex_mean_curvature_map mean_curvature_map; Vertex_gaussian_curvature_map gaussian_curvature_map; - Vertex_principal_curvature_map principal_curvature_map; + Vertex_principal_curvatures_and_directions_map principal_curvatures_and_directions_map; Face_scalar_measure_map mu0_map, mu1_map, mu2_map; Face_anisotropic_measure_map muXY_map; @@ -563,11 +563,11 @@ namespace internal { is_mean_curvature_selected = !is_default_parameter::value; is_gaussian_curvature_selected = !is_default_parameter::value; - is_principal_curvature_selected = !is_default_parameter::value; + is_principal_curvatures_and_directions_selected = !is_default_parameter::value; mean_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_mean_curvature_map), Default_scalar_map()); gaussian_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_gaussian_curvature_map), Default_scalar_map()); - principal_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvature_map), Default_principal_map()); + principal_curvatures_and_directions_map = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions_map), Default_principal_map()); set_ball_radius(radius); } @@ -588,7 +588,7 @@ namespace internal { { set_named_params(np); - if (is_mean_curvature_selected || is_gaussian_curvature_selected || is_principal_curvature_selected) + if (is_mean_curvature_selected || is_gaussian_curvature_selected || is_principal_curvatures_and_directions_selected) { set_property_maps(); @@ -619,7 +619,7 @@ namespace internal { if (is_gaussian_curvature_selected) put(mu2_map, f, interpolated_corrected_gaussian_curvature_measure_face(u, x)); - if (is_principal_curvature_selected) + if (is_principal_curvatures_and_directions_selected) put(muXY_map, f, interpolated_corrected_anisotropic_measure_face(u, x)); x.clear(); @@ -640,7 +640,7 @@ namespace internal { if (is_gaussian_curvature_selected) vertex_measures.gaussian_curvature_measure += get(mu2_map, f); - if (is_principal_curvature_selected) + if (is_principal_curvatures_and_directions_selected) { const std::array face_anisotropic_measure = get(muXY_map, f); for (std::size_t i = 0; i < 3 * 3; i++) @@ -692,7 +692,7 @@ namespace internal { if (is_gaussian_curvature_selected) vertex_measures.gaussian_curvature_measure += f_ratio * get(mu2_map, fi); - if (is_principal_curvature_selected) + if (is_principal_curvatures_and_directions_selected) { const std::array face_anisotropic_measure = get(muXY_map, fi); for (std::size_t i = 0; i < 3 * 3; i++) @@ -733,467 +733,19 @@ namespace internal { put(gaussian_curvature_map, v, 0); } - if (is_principal_curvature_selected) { + if (is_principal_curvatures_and_directions_selected) { const Vector_3 v_normal = get(vnm, v); - const Principal_curvature principal_curvature = principal_curvature_from_anisotropic_measures( + const Principal_curvature principal_curvatures_and_directions = principal_curvatures_and_directions_from_anisotropic_measures( vertex_measures.anisotropic_measure, vertex_measures.area_measure, v_normal ); - put(principal_curvature_map, v, principal_curvature); + put(principal_curvatures_and_directions_map, v, principal_curvatures_and_directions); } } } }; - template - class Interpolated_corrected_curvatures_point_computer - { - typedef typename GetGeomTraits::type GT; - - typedef typename GT::FT FT; - typedef typename GT::Point_3 Point_3; - typedef typename GT::Vector_3 Vector_3; - - typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; - typedef typename boost::graph_traits::edge_descriptor Edge_descriptor; - typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; - - typedef typename GetVertexPointMap::const_type Vertex_position_map; - - typedef dynamic_vertex_property_t Vector_map_tag; - typedef typename boost::property_map::const_type Default_vector_map; - typedef typename internal_np::Lookup_named_param_def::type Vertex_normal_map; - - private: - const PolygonMesh& pmesh; - Vertex_position_map vpm; - Vertex_normal_map vnm; - FT ball_radius; - - bool is_mean_curvature_selected; - bool is_gaussian_curvature_selected; - bool is_principal_curvature_selected; - - Point_3 point; - - void set_named_params(const NamedParameters& np) - { - using parameters::choose_parameter; - using parameters::get_parameter; - using parameters::is_default_parameter; - - vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), - get_const_property_map(CGAL::vertex_point, pmesh)); - - vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), - get(Vector_map_tag(), pmesh)); - - if (is_default_parameter::value) - compute_vertex_normals(pmesh, vnm, np); - - const FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1); - - is_mean_curvature_selected = !is_default_parameter::value; - is_gaussian_curvature_selected = !is_default_parameter::value; - is_principal_curvature_selected = !is_default_parameter::value; - - set_ball_radius(radius); - } - - void set_ball_radius(const FT radius) { - if (radius == 0) - ball_radius = average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; - else - ball_radius = radius; - } - - public: - - Interpolated_corrected_curvatures_computer(const PolygonMesh& pmesh, - const NamedParameters& np = parameters::default_values() - ) : - pmesh(pmesh) - { - set_named_params(np); - - if (is_mean_curvature_selected || is_gaussian_curvature_selected || is_principal_curvature_selected) - { - set_property_maps(); - - compute_selected_curvatures(); - } - } - - private: - Vertex_measures expand_interpolated_corrected_measure_vertex_no_radius(Vertex_descriptor v) - { - Vertex_measures vertex_curvatures; - - std::vector x; - std::vector u; - - for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { - - for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) - { - Point_3 p = get(vpm, v); - x.push_back(Vector_3(p.x(), p.y(), p.z())); - u.push_back(get(vnm, v)); - } - - vertex_curvatures.area_measure += interpolated_corrected_area_measure_face(u, x); - - if (is_mean_curvature_selected) - vertex_curvatures.mean_curvature_measure += interpolated_corrected_mean_curvature_measure_face(u, x); - - if (is_gaussian_curvature_selected) - vertex_curvatures.gaussian_curvature_measure += interpolated_corrected_gaussian_curvature_measure_face(u, x); - - if (is_principal_curvature_selected) - { - const std::array face_anisotropic_measure = interpolated_corrected_anisotropic_measure_face(u, x); - for (std::size_t i = 0; i < 3 * 3; i++) - vertex_curvatures.anisotropic_measure[i] += face_anisotropic_measure[i]; - } - - x.clear(); - u.clear(); - } - - return vertex_curvatures; - } - - Vertex_measures expand_interpolated_corrected_measure_vertex(Vertex_descriptor v) - { - std::queue bfs_queue; - std::unordered_set bfs_visited; - - Point_3 vp = get(vpm, v); - Vector_3 c = Vector_3(vp.x(), vp.y(), vp.z()); - - Vertex_measures vertex_curvatures; - - for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { - if (f != boost::graph_traits::null_face()) - { - bfs_queue.push(f); - bfs_visited.insert(f); - } - } - - std::vector x; - std::vector u; - - while (!bfs_queue.empty()) { - Face_descriptor fi = bfs_queue.front(); - bfs_queue.pop(); - - // looping over vertices in face to get point coordinates - for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) - { - Point_3 pi = get(vpm, vi); - x.push_back(Vector_3(pi.x(), pi.y(), pi.z())); - } - - const FT f_ratio = face_in_ball_ratio(x, ball_radius, c); - - if (f_ratio != 0.0) - { - for (Vertex_descriptor vi : vertices_around_face(halfedge(f, pmesh), pmesh)) - { - u.push_back(get(vnm, vi)); - } - - vertex_curvatures.area_measure += f_ratio * get(mu0_map, fi); - - if (is_mean_curvature_selected) - vertex_curvatures.mean_curvature_measure += f_ratio * get(mu1_map, fi); - - if (is_gaussian_curvature_selected) - vertex_curvatures.gaussian_curvature_measure += f_ratio * get(mu2_map, fi); - - if (is_principal_curvature_selected) - { - const std::array face_anisotropic_measure = get(muXY_map, fi); - for (std::size_t i = 0; i < 3 * 3; i++) - vertex_curvatures.anisotropic_measure[i] += f_ratio * face_anisotropic_measure[i]; - } - - x.clear(); - u.clear(); - - for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) - { - if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) - { - bfs_queue.push(fj); - bfs_visited.insert(fj); - } - } - } - } - return vertex_curvatures; - } - - void compute_selected_curvatures() { - for (Vertex_descriptor v : vertices(pmesh)) - { - Vertex_measures vertex_curvatures = (ball_radius < 0) ? - compute_expanded_interpolated_corrected_measure_vertex_no_radius(v) : - compute_expanded_interpolated_corrected_measure_vertex(v); - - if (is_mean_curvature_selected) { - vertex_curvatures.area_measure != 0 ? - put(mean_curvature_map, v, 0.5 * vertex_curvatures.mean_curvature_measure / vertex_curvatures.area_measure) : - put(mean_curvature_map, v, 0); - } - - if (is_gaussian_curvature_selected) { - vertex_curvatures.area_measure != 0 ? - put(gaussian_curvature_map, v, vertex_curvatures.gaussian_curvature_measure / vertex_curvatures.area_measure) : - put(gaussian_curvature_map, v, 0); - } - - if (is_principal_curvature_selected) { - const Vector_3 v_normal = get(vnm, v); - const Principal_curvature principal_curvature = principal_curvature_from_anisotropic_measures( - vertex_curvatures.anisotropic_measure, - vertex_curvatures.area_measure, - v_normal - ); - put(principal_curvature_map, v, principal_curvature); - } - } - } - - }; - - template - class Interpolated_corrected_curvatures_element_computer - { - typedef typename GetGeomTraits::type GT; - - typedef typename GT::FT FT; - typedef typename GT::Point_3 Point_3; - typedef typename GT::Vector_3 Vector_3; - - typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; - typedef typename boost::graph_traits::edge_descriptor Edge_descriptor; - typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; - - typedef typename GetVertexPointMap::const_type Vertex_position_map; - - typedef dynamic_vertex_property_t Vector_map_tag; - typedef typename boost::property_map::const_type Default_vector_map; - typedef typename internal_np::Lookup_named_param_def::type Vertex_normal_map; - - private: - const PolygonMesh& pmesh; - Vertex_position_map vpm; - Vertex_normal_map vnm; - FT ball_radius; - - bool is_mean_curvature_selected; - bool is_gaussian_curvature_selected; - bool is_principal_curvature_selected; - - void set_named_params(const NamedParameters& np) - { - using parameters::choose_parameter; - using parameters::get_parameter; - using parameters::is_default_parameter; - - vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), - get_const_property_map(CGAL::vertex_point, pmesh)); - - vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), - get(Vector_map_tag(), pmesh)); - - if (is_default_parameter::value) - compute_vertex_normals(pmesh, vnm, np); - - const FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1); - - is_mean_curvature_selected = !is_default_parameter::value; - is_gaussian_curvature_selected = !is_default_parameter::value; - is_principal_curvature_selected = !is_default_parameter::value; - - set_ball_radius(radius); - } - - void set_ball_radius(const FT radius) { - if (radius == 0) - ball_radius = average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; - else - ball_radius = radius; - } - - public: - - Interpolated_corrected_curvatures_computer(const PolygonMesh& pmesh, - const NamedParameters& np = parameters::default_values() - ) : - pmesh(pmesh) - { - set_named_params(np); - - if (is_mean_curvature_selected || is_gaussian_curvature_selected || is_principal_curvature_selected) - { - set_property_maps(); - } - } - - private: - Vertex_measures expand_interpolated_corrected_measure_vertex_no_radius(Vertex_descriptor v) - { - Vertex_measures vertex_curvatures; - - std::vector x; - std::vector u; - - for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { - - for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) - { - Point_3 p = get(vpm, v); - x.push_back(Vector_3(p.x(), p.y(), p.z())); - u.push_back(get(vnm, v)); - } - - vertex_curvatures.area_measure += interpolated_corrected_area_measure_face(u, x); - - if (is_mean_curvature_selected) - vertex_curvatures.mean_curvature_measure += interpolated_corrected_mean_curvature_measure_face(u, x); - - if (is_gaussian_curvature_selected) - vertex_curvatures.gaussian_curvature_measure += interpolated_corrected_gaussian_curvature_measure_face(u, x); - - if (is_principal_curvature_selected) - { - const std::array face_anisotropic_measure = interpolated_corrected_anisotropic_measure_face(u, x); - for (std::size_t i = 0; i < 3 * 3; i++) - vertex_curvatures.anisotropic_measure[i] += face_anisotropic_measure[i]; - } - - x.clear(); - u.clear(); - } - - return vertex_curvatures; - } - - Vertex_measures expand_interpolated_corrected_measure_vertex(Vertex_descriptor v) - { - std::queue bfs_queue; - std::unordered_set bfs_visited; - - Point_3 vp = get(vpm, v); - Vector_3 c = Vector_3(vp.x(), vp.y(), vp.z()); - - Vertex_measures vertex_curvatures; - - for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { - if (f != boost::graph_traits::null_face()) - { - bfs_queue.push(f); - bfs_visited.insert(f); - } - } - - std::vector x; - std::vector u; - - while (!bfs_queue.empty()) { - Face_descriptor fi = bfs_queue.front(); - bfs_queue.pop(); - - // looping over vertices in face to get point coordinates - for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) - { - Point_3 pi = get(vpm, vi); - x.push_back(Vector_3(pi.x(), pi.y(), pi.z())); - } - - const FT f_ratio = face_in_ball_ratio(x, ball_radius, c); - - if (f_ratio != 0.0) - { - for (Vertex_descriptor vi : vertices_around_face(halfedge(f, pmesh), pmesh)) - { - u.push_back(get(vnm, vi)); - } - - vertex_curvatures.area_measure += f_ratio * get(mu0_map, fi); - - if (is_mean_curvature_selected) - vertex_curvatures.mean_curvature_measure += f_ratio * get(mu1_map, fi); - - if (is_gaussian_curvature_selected) - vertex_curvatures.gaussian_curvature_measure += f_ratio * get(mu2_map, fi); - - if (is_principal_curvature_selected) - { - const std::array face_anisotropic_measure = get(muXY_map, fi); - for (std::size_t i = 0; i < 3 * 3; i++) - vertex_curvatures.anisotropic_measure[i] += f_ratio * face_anisotropic_measure[i]; - } - - x.clear(); - u.clear(); - - for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) - { - if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) - { - bfs_queue.push(fj); - bfs_visited.insert(fj); - } - } - } - } - return vertex_curvatures; - } - - void compute_selected_curvatures() { - for (Vertex_descriptor v : vertices(pmesh)) - { - Vertex_measures vertex_curvatures = (ball_radius < 0) ? - compute_expanded_interpolated_corrected_measure_vertex_no_radius(v) : - compute_expanded_interpolated_corrected_measure_vertex(v); - - if (is_mean_curvature_selected) { - vertex_curvatures.area_measure != 0 ? - put(mean_curvature_map, v, 0.5 * vertex_curvatures.mean_curvature_measure / vertex_curvatures.area_measure) : - put(mean_curvature_map, v, 0); - } - - if (is_gaussian_curvature_selected) { - vertex_curvatures.area_measure != 0 ? - put(gaussian_curvature_map, v, vertex_curvatures.gaussian_curvature_measure / vertex_curvatures.area_measure) : - put(gaussian_curvature_map, v, 0); - } - - if (is_principal_curvature_selected) { - const Vector_3 v_normal = get(vnm, v); - const Principal_curvature principal_curvature = principal_curvature_from_anisotropic_measures( - vertex_curvatures.anisotropic_measure, - vertex_curvatures.area_measure, - v_normal - ); - put(principal_curvature_map, v, principal_curvature); - } - } - } - - }; - } // namespace internal /** @@ -1247,7 +799,7 @@ namespace internal { * \cgalNamedParamsEnd * * @see `interpolated_corrected_gaussian_curvature()` -* @see `interpolated_corrected_principal_curvatures()` +* @see `interpolated_corrected_principal_curvatures_and_directions()` * @see `interpolated_corrected_curvatures()` */ @@ -1311,7 +863,7 @@ template - void interpolated_corrected_principal_curvatures(const PolygonMesh& pmesh, + void interpolated_corrected_principal_curvatures_and_directions(const PolygonMesh& pmesh, VertexCurvatureMap& vcm, NamedParameters& np = parameters::default_values()) { - interpolated_corrected_curvatures(pmesh, np.vertex_principal_curvature_map(vcm)); + interpolated_corrected_curvatures(pmesh, np.vertex_principal_curvatures_and_directions_map(vcm)); } /** @@ -1441,7 +993,7 @@ template::%Vertex_descriptor` @@ -1465,7 +1017,7 @@ template diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp index 21b292b05d0c..09df28bc2816 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp @@ -14,7 +14,7 @@ #include using namespace CGAL::Three; -class Polyhedron_demo_interpolated_corrected_principal_curvatures_plugin : +class Polyhedron_demo_interpolated_corrected_principal_curvatures_and_directions_plugin : public QObject, public Polyhedron_demo_plugin_interface { @@ -47,7 +47,7 @@ public Q_SLOTS: private : Scene_interface *scene; QList _actions; -}; // end Polyhedron_demo_interpolated_corrected_principal_curvatures_plugin +}; // end Polyhedron_demo_interpolated_corrected_principal_curvatures_and_directions_plugin void compute(SMesh* sMesh, @@ -72,23 +72,23 @@ void compute(SMesh* sMesh, typename boost::property_map::type vpmap = get(CGAL::vertex_point, *sMesh); bool created = false; - SMesh::Property_map principal_curvature_map; + SMesh::Property_map principal_curvatures_and_directions_map; - boost::tie(principal_curvature_map, created) = sMesh->add_property_map - ("v:principal_curvature_map", { 0, 0, + boost::tie(principal_curvatures_and_directions_map, created) = sMesh->add_property_map + ("v:principal_curvatures_and_directions_map", { 0, 0, Vector(0,0,0), Vector(0,0,0)}); assert(created); - PMP::interpolated_corrected_principal_curvatures( + PMP::interpolated_corrected_principal_curvatures_and_directions( *sMesh, - principal_curvature_map + principal_curvatures_and_directions_map ); typename EpicKernel::FT max_curvature_magnitude_on_mesh = 0; for (vertex_descriptor v : vertices(*sMesh)) { - const PrincipalCurvatureTuple pc = principal_curvature_map[v]; + const PrincipalCurvatureTuple pc = principal_curvatures_and_directions_map[v]; max_curvature_magnitude_on_mesh = std::max(max_curvature_magnitude_on_mesh, std::max(abs(get<0>(pc)), get<1>(pc))); } @@ -114,7 +114,7 @@ void compute(SMesh* sMesh, avg_edge_length /= n; } - const PrincipalCurvatureTuple pc = principal_curvature_map[v]; + const PrincipalCurvatureTuple pc = principal_curvatures_and_directions_map[v]; Vector umin = (std::get<0>(pc)/ max_curvature_magnitude_on_mesh) * std::get<2>(pc) * avg_edge_length; Vector umax = (std::get<1>(pc)/ max_curvature_magnitude_on_mesh) * std::get<3>(pc) * avg_edge_length; @@ -133,7 +133,7 @@ void compute(SMesh* sMesh, } } -void Polyhedron_demo_interpolated_corrected_principal_curvatures_plugin::on_actionEstimateCurvature_triggered() +void Polyhedron_demo_interpolated_corrected_principal_curvatures_and_directions_plugin::on_actionEstimateCurvature_triggered() { // get active polyhedron const CGAL::Three::Scene_interface::Item_id index = scene->mainSelectionIndex(); @@ -183,4 +183,4 @@ void Polyhedron_demo_interpolated_corrected_principal_curvatures_plugin::on_acti QApplication::restoreOverrideCursor(); } -#include "Interpolated_corrected_principal_curvatures_plugin.moc" +#include "Interpolated_corrected_principal_curvatures_and_directions_plugin.moc" 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 03ee822d0bc9..21b8227e86c9 100644 --- a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h +++ b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h @@ -89,7 +89,7 @@ CGAL_add_named_parameter(nb_points_per_area_unit_t, nb_points_per_area_unit, num 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_curvature_map_t, vertex_principal_curvature_map, vertex_principal_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(ball_radius_t, ball_radius, ball_radius) 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) From f35faf60e18ace1d90646823bd7c1ff1ff8ddb1e Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 19 Nov 2022 11:54:20 +0200 Subject: [PATCH 085/161] minor doc fixes and renaming --- .../PackageDescription.txt | 4 ++-- .../Polygon_mesh_processing.txt | 17 ++++++++-------- ...erpolated_corrected_curvatures_example.cpp | 4 ++-- ...orrected_curvatures_polyhedron_example.cpp | 4 ++-- ...nterpolated_corrected_curvature_measures.h | 20 +++++++++---------- 5 files changed, 24 insertions(+), 25 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 5090535be250..f62b2816cf7f 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -200,12 +200,12 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage. - \link PMP_locate_grp Nearest Face Location Queries \endlink - \link PMP_locate_grp Random Location Generation \endlink -\cgalCRPSection{Corrected Curvature Functions} +\cgalCRPSection{Corrected Curvatures} - `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` -- `CGAL::Polygon_mesh_processing::Principal_curvature` +- `CGAL::Polygon_mesh_processing::Principal_curvatures_and_directions` \cgalCRPSection{Normal Computation Functions} - `CGAL::Polygon_mesh_processing::compute_face_normal()` 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 2347ae1c7ae8..1d7184561a80 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 @@ -879,11 +879,11 @@ not provide storage for the normals. **************************************** \section PMPICC Computing Curvatures -This package provides methods to compute curvatures on polygonal meshes based on \cgalCite{lachaud2020} -This includes mean curvature, gaussian curvature, principal curvatures and directions. -These can be computed on triangle meshes, quad meshes, and meshes with n-gon faces. -The algorithms used prove to work well in general. Also, on meshes with noise -on vertex positions, they give accurate results, on the condition that the +This package provides methods to compute curvatures on polygonal meshes based on Interpolated Corrected Curvatures +on Polyhedral Surfaces \cgalCite{lachaud2020}. This includes mean curvature, gaussian curvature, +principal curvatures and directions. These can be computed on triangle meshes, quad meshes, +and meshes with n-gon faces. The algorithms used prove to work well in general. Also, on meshes +with noise on vertex positions, they give accurate results, on the condition that the correct vertex normals are provided. The implementation is generic in terms of mesh data structure. It can be used on Surface_mesh, @@ -912,13 +912,12 @@ The mean curvature distribution on a bear mesh with different values for the exp Property maps are used to record the computed curvatures as shown in examples. -\subsection ICCExample Interpolated Corrected Curvature Examples Property maps are an API introduced in the boost library that allows associating values to keys. In the following examples, for each property map, we associate a curvature value to each vertex. -\subsubsection ICCExampleSM Interpolated Corrected Curvature on a Surface Mesh. +\subsection ICCExampleSM Interpolated Corrected Curvature on a Surface Mesh Example The following example illustrates how to compute the curvatures on vertices @@ -926,7 +925,7 @@ and store them in property maps provided by the class `Surface_mesh`. \cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp} -\subsubsection ICCExamplePH Interpolated Corrected Curvature on a Polyhedron +\subsection ICCExamplePH Interpolated Corrected Curvature on a Polyhedron Example The following example illustrates how to compute the curvatures on vertices @@ -1143,7 +1142,7 @@ is covered by a set of prisms, where each prism is an offset for an input triang That is, the implementation in \cgal does not use indirect predicates. The interpolated corrected curvatures were implemented during GSoC 2022. This was implemented by Hossam Saeed and under -supervision of Sebastien Loriot, Jaques-Olivier Lachaud and David Coeurjolly. The implementation is based \cgalCite{lachaud2020}. +supervision of Sebastien Loriot, Jaques-Olivier Lachaud and David Coeurjolly. The implementation is based on \cgalCite{lachaud2020}. DGtal's implementation was also used as a reference during the project. diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp index 71aa06a8caec..33d34cd34f13 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp @@ -38,9 +38,9 @@ int main(int argc, char* argv[]) assert(created); // we use a tuple of 2 scalar values and 2 vectors for principal curvatures and directions - Surface_Mesh::Property_map> principal_curvatures_and_directions_map; + Surface_Mesh::Property_map> principal_curvatures_and_directions_map; - boost::tie(principal_curvatures_and_directions_map, created) = smesh.add_property_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) }); diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp index 50fe1a014d8e..6bbe49ce37e5 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp @@ -30,8 +30,8 @@ int main(int argc, char *argv[]) boost::property_map>::type mean_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron), gaussian_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron); - boost::property_map>>::type - principal_curvatures_and_directions_map = get(CGAL::dynamic_vertex_property_t>(), polyhedron); + boost::property_map>>::type + principal_curvatures_and_directions_map = get(CGAL::dynamic_vertex_property_t>(), polyhedron); PMP::interpolated_corrected_mean_curvature( polyhedron, diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 3b02341a2129..6ed11404a257 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -43,7 +43,7 @@ namespace Polygon_mesh_processing { * @tparam GT is the geometric traits class. */ template -struct Principal_curvature { +struct Principal_curvatures_and_directions { /// min curvature magnitude typename GT::FT min_curvature; @@ -57,14 +57,14 @@ struct Principal_curvature { /// max curvature direction vector typename GT::Vector_3 max_direction; - Principal_curvature() { + Principal_curvatures_and_directions() { min_curvature = 0; max_curvature = 0; min_direction = typename GT::Vector_3(0, 0, 0); max_direction = typename GT::Vector_3(0, 0, 0); } - Principal_curvature( + Principal_curvatures_and_directions( typename GT::FT min_curvature, typename GT::FT max_curvature, typename GT::Vector_3 min_direction, @@ -440,7 +440,7 @@ namespace internal { } template - Principal_curvature principal_curvatures_and_directions_from_anisotropic_measures( + Principal_curvatures_and_directions principal_curvatures_and_directions_from_anisotropic_measures( const std::array anisotropic_measure, const typename GT::FT v_mu0, const typename GT::Vector_3 u_GT @@ -462,7 +462,7 @@ namespace internal { eigensolver.computeDirect(v_muXY); if (eigensolver.info() != Eigen::Success) - return Principal_curvature(); + return Principal_curvatures_and_directions(); const Eigen::Matrix eig_vals = eigensolver.eigenvalues(); const Eigen::Matrix eig_vecs = eigensolver.eigenvectors(); @@ -470,7 +470,7 @@ namespace internal { const typename GT::Vector_3 min_eig_vec(eig_vecs(0, 1), eig_vecs(1, 1), eig_vecs(2, 1)); const typename GT::Vector_3 max_eig_vec(eig_vecs(0, 0), eig_vecs(1, 0), eig_vecs(2, 0)); - return Principal_curvature( + return Principal_curvatures_and_directions( (v_mu0 != 0.0) ? -eig_vals[1] / v_mu0 : 0.0, (v_mu0 != 0.0) ? -eig_vals[0] / v_mu0 : 0.0, min_eig_vec, @@ -509,7 +509,7 @@ namespace internal { NamedParameters, Default_scalar_map>::type Vertex_gaussian_curvature_map; - 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; @@ -735,7 +735,7 @@ namespace internal { if (is_principal_curvatures_and_directions_selected) { const Vector_3 v_normal = get(vnm, v); - const Principal_curvature principal_curvatures_and_directions = principal_curvatures_and_directions_from_anisotropic_measures( + const Principal_curvatures_and_directions principal_curvatures_and_directions = principal_curvatures_and_directions_from_anisotropic_measures( vertex_measures.anisotropic_measure, vertex_measures.area_measure, v_normal @@ -997,8 +997,8 @@ template::%Vertex_descriptor` -* as key type and `%Principal_curvature` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t>(), pmesh)`} +* as key type and `%Principal_curvatures_and_directions` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t>(), pmesh)`} * \cgalParamExtra{If this parameter is omitted, mean principal won't be computed} * \cgalParamNEnd * From fe8988b650046236a9dc26c39707c915de4c4147 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 19 Nov 2022 12:16:27 +0200 Subject: [PATCH 086/161] demo fixes --- .../demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index 6efc7f213fe2..404bb20f8072 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -1638,7 +1638,7 @@ private Q_SLOTS: std::unordered_map is_source; - double expand_radius; + double expand_radius = 0; double maxEdgeLength = -1; double minBox; double maxBox; From dd49b0c0d87eb344efc3ef994c32b74ca2916706 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 19 Nov 2022 12:18:28 +0200 Subject: [PATCH 087/161] demo fixes --- ..._corrected_principal_curvatures_plugin.cpp | 64 +++++++++---------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp index 09df28bc2816..789ba4a06567 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp @@ -57,42 +57,38 @@ void compute(SMesh* sMesh, Scene_polylines_item* min_negative_curv) { namespace PMP = CGAL::Polygon_mesh_processing; - typedef CGAL::Exact_predicates_inexact_constructions_kernel EpicKernel; - typedef EpicKernel::Point_3 Point; - typedef EpicKernel::Point_3 Point; - typedef EpicKernel::Vector_3 Vector; - typedef boost::graph_traits::vertex_descriptor vertex_descriptor; - typedef std::tuple< - EpicKernel::FT, - EpicKernel::FT, - Vector, - Vector - > PrincipalCurvatureTuple; + typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic_kernel; + typedef Epic_kernel::Point_3 Point; + typedef Epic_kernel::Point_3 Point; + typedef Epic_kernel::Vector_3 Vector; + typedef boost::graph_traits::vertex_descriptor Vertex_descriptor; typename boost::property_map::type vpmap = get(CGAL::vertex_point, *sMesh); bool created = false; - SMesh::Property_map principal_curvatures_and_directions_map; + SMesh::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, + Vector(0,0,0), + Vector(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, - Vector(0,0,0), - Vector(0,0,0)}); - assert(created); PMP::interpolated_corrected_principal_curvatures_and_directions( - *sMesh, - principal_curvatures_and_directions_map + *sMesh, + principal_curvatures_and_directions_map, + CGAL::parameters::ball_radius(0) ); - typename EpicKernel::FT max_curvature_magnitude_on_mesh = 0; - for (vertex_descriptor v : vertices(*sMesh)) + typename Epic_kernel::FT max_curvature_magnitude_on_mesh = 0; + for (Vertex_descriptor v : vertices(*sMesh)) { - const PrincipalCurvatureTuple pc = principal_curvatures_and_directions_map[v]; - max_curvature_magnitude_on_mesh = std::max(max_curvature_magnitude_on_mesh, std::max(abs(get<0>(pc)), get<1>(pc))); + const PMP::Principal_curvatures_and_directions pc = principal_curvatures_and_directions_map[v]; + max_curvature_magnitude_on_mesh = std::max(max_curvature_magnitude_on_mesh, std::max(abs(pc.min_curvature), abs(pc.max_curvature))); } - for(vertex_descriptor v : vertices(*sMesh)) + for(Vertex_descriptor v : vertices(*sMesh)) { std::vector points; @@ -107,17 +103,17 @@ void compute(SMesh* sMesh, const std::size_t n = CGAL::edges(*sMesh).size(); - EpicKernel::FT avg_edge_length = 0; + Epic_kernel::FT avg_edge_length = 0; if (n > 0) { - for (auto e : CGAL::edges(*sMesh)) - avg_edge_length += PMP::edge_length(e, *sMesh); - avg_edge_length /= n; + for (auto e : CGAL::edges(*sMesh)) + avg_edge_length += PMP::edge_length(e, *sMesh); + avg_edge_length /= n; } - const PrincipalCurvatureTuple pc = principal_curvatures_and_directions_map[v]; + const PMP::Principal_curvatures_and_directions pc = principal_curvatures_and_directions_map[v]; - Vector umin = (std::get<0>(pc)/ max_curvature_magnitude_on_mesh) * std::get<2>(pc) * avg_edge_length; - Vector umax = (std::get<1>(pc)/ max_curvature_magnitude_on_mesh) * std::get<3>(pc) * avg_edge_length; + Vector umin = (pc.min_curvature / max_curvature_magnitude_on_mesh) * pc.min_direction * avg_edge_length; + Vector umax = (pc.max_curvature / max_curvature_magnitude_on_mesh) * pc.max_direction * avg_edge_length; Scene_polylines_item::Polyline max_segment(2), min_segment(2); @@ -128,8 +124,8 @@ void compute(SMesh* sMesh, max_segment[0] = central_point + du * umax; max_segment[1] = central_point - du * umax; - (std::get<0>(pc) > 0 ? min_curv : min_negative_curv)->polylines.push_back(min_segment); - (std::get<1>(pc) > 0 ? max_curv : max_negative_curv)->polylines.push_back(max_segment); + (pc.min_curvature > 0 ? min_curv : min_negative_curv)->polylines.push_back(min_segment); + (pc.max_curvature > 0 ? max_curv : max_negative_curv)->polylines.push_back(max_segment); } } @@ -183,4 +179,4 @@ void Polyhedron_demo_interpolated_corrected_principal_curvatures_and_directions_ QApplication::restoreOverrideCursor(); } -#include "Interpolated_corrected_principal_curvatures_and_directions_plugin.moc" +#include "Interpolated_corrected_principal_curvatures_plugin.moc" From d96dca1264c3f53a836d6410165c60910967e005 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 19 Nov 2022 17:16:28 +0200 Subject: [PATCH 088/161] Skipping concave n-gons case for now remove face triangulation function draft --- .../triangulate_faces.h | 147 ------------------ 1 file changed, 147 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/triangulate_faces.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/triangulate_faces.h index abe56ea35a61..0b48967f2733 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/triangulate_faces.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/triangulate_faces.h @@ -499,153 +499,6 @@ bool triangulate_face(typename boost::graph_traits::face_descriptor return modifier.triangulate_face(f, pmesh, use_cdt, visitor); } - -#ifndef CGAL_TRIANGULATE_FACES_DO_NOT_USE_CDT2 -template -OutputIterator -face_triangulation(typename boost::graph_traits::face_descriptor f, - PolygonMesh& pmesh, - OutputIterator out, - const NamedParameters& np = parameters::default_values()) -{ - using parameters::choose_parameter; - using parameters::get_parameter; - - //VertexPointMap - typedef typename GetVertexPointMap::type VPMap; - VPMap vpmap = choose_parameter(get_parameter(np, internal_np::vertex_point), - get_property_map(vertex_point, pmesh)); - - //Kernel - typedef typename GetGeomTraits::type Kernel; - Kernel traits = choose_parameter(get_parameter(np, internal_np::geom_traits)); - - //Face_info - typedef typename internal::Triangulate_modifier>::Face_info Face_info; - - //CDT - typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; - typedef CGAL::Projection_traits_3 P_traits; - typedef CGAL::Triangulation_vertex_base_with_info_2 Vb; - typedef CGAL::Triangulation_face_base_with_info_2 Fb1; - typedef CGAL::Constrained_triangulation_face_base_2 Fb; - typedef CGAL::Triangulation_data_structure_2 TDS; - typedef CGAL::Exact_intersections_tag Itag; - typedef CGAL::Constrained_Delaunay_triangulation_2 CDT; - P_traits cdt_traits(normal); - CDT cdt(cdt_traits); - - std::size_t original_size = CGAL::halfedges_around_face(halfedge(f, pmesh), pmesh).size(); - - // Halfedge_around_facet_circulator - typedef typename CDT::Vertex_handle Tr_Vertex_handle; - halfedge_descriptor start = halfedge(f, pmesh); - halfedge_descriptor h = start; - Tr_Vertex_handle previous, first; - do - { - Tr_Vertex_handle vh = cdt.insert(get(_vpmap, target(h, pmesh))); - if (first == Tr_Vertex_handle()) { - first = vh; - } - vh->info() = h; - if(previous != Tr_Vertex_handle() && previous != vh) { - cdt.insert_constraint(previous, vh); - } - previous = vh; - h = next(h, pmesh); - - } while( h != start ); - cdt.insert_constraint(previous, first); - - // sets mark is_external - for(typename CDT::All_faces_iterator fit = cdt.all_faces_begin(), - end = cdt.all_faces_end(); - fit != end; ++fit) - { - fit->info().is_external = false; - } - std::queue face_queue; - face_queue.push(cdt.infinite_vertex()->face()); - while(! face_queue.empty() ) - { - typename CDT::Face_handle fh = face_queue.front(); - face_queue.pop(); - - if(fh->info().is_external) - continue; - - fh->info().is_external = true; - for(int i = 0; i <3; ++i) - { - if(!cdt.is_constrained(typename CDT::Edge(fh, i))) - { - face_queue.push(fh->neighbor(i)); - } - } - } - - if(cdt.dimension() != 2 || cdt.number_of_vertices() != original_size) - return out; - - for(typename CDT::Finite_edges_iterator eit = cdt.finite_edges_begin(), - end = cdt.finite_edges_end(); - eit != end; ++eit) - { - typename CDT::Face_handle fh = eit->first; - const int index = eit->second; - typename CDT::Face_handle opposite_fh = fh->neighbor(eit->second); - const int opposite_index = opposite_fh->index(fh); - - const Tr_Vertex_handle va = fh->vertex(cdt. cw(index)); - const Tr_Vertex_handle vb = fh->vertex(cdt.ccw(index)); - - if( ! (is_external(fh) && is_external(opposite_fh))//not both fh are external - && ! cdt.is_constrained(*eit) ) //and edge is not constrained - { - // strictly internal edge - halfedge_descriptor hnew = halfedge(add_edge(pmesh), pmesh), - hnewopp = opposite(hnew, pmesh); - - fh->info().e[index] = hnew; - opposite_fh->info().e[opposite_index] = hnewopp; - - set_target(hnew, target(va->info(), pmesh), pmesh); - set_target(hnewopp, target(vb->info(), pmesh), pmesh); - } - if( cdt.is_constrained(*eit) ) //edge is constrained - { - if(!is_external(fh)) { - fh->info().e[index] = va->info(); - } - if(!is_external(opposite_fh)) { - opposite_fh->info().e[opposite_index] = vb->info(); - } - } - } - for(typename CDT::Finite_faces_iterator fit = cdt.finite_faces_begin(), - end = cdt.finite_faces_end(); - fit != end; ++fit) - { - if(!is_external(fit)) - { - halfedge_descriptor h0 = fit->info().e[0]; - halfedge_descriptor h1 = fit->info().e[1]; - halfedge_descriptor h2 = fit->info().e[2]; - *out++=std::make_tuple(target(h0, pmesh), target(h1, pmesh), target(h2,pmesh)); - } - } - - return out; -} -#endif - - - /** * \ingroup PMP_meshing_grp * From 0ac812bd2f57e4efbea92c7cbaf0bc5579166dac Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 19 Nov 2022 17:23:15 +0200 Subject: [PATCH 089/161] minor change, position shouldn't be optional that is, in area and mean measures, in gaussian, it is not needed --- .../Curvatures/interpolated_corrected_curvature_measures.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 6ed11404a257..f14904c6799a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -111,7 +111,7 @@ namespace internal { template typename GT::FT interpolated_corrected_area_measure_face(const std::vector& u, - const std::vector& x = {}) + const std::vector& x) { const std::size_t n = x.size(); CGAL_precondition(u.size() == n); @@ -166,7 +166,7 @@ namespace internal { template typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::vector& u, - const std::vector& x = {}) + const std::vector& x) { const std::size_t n = x.size(); CGAL_precondition(u.size() == n); @@ -617,7 +617,7 @@ namespace internal { put(mu1_map, f, interpolated_corrected_mean_curvature_measure_face(u, x)); if (is_gaussian_curvature_selected) - put(mu2_map, f, interpolated_corrected_gaussian_curvature_measure_face(u, x)); + put(mu2_map, f, interpolated_corrected_gaussian_curvature_measure_face(u)); if (is_principal_curvatures_and_directions_selected) put(muXY_map, f, interpolated_corrected_anisotropic_measure_face(u, x)); From d5a2cf1f0555a015ea3d17c24624565cbb4aebcc Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 19 Nov 2022 19:27:33 +0200 Subject: [PATCH 090/161] fixed an expansion bug when mesh has boundary and no input radius is specified --- .../Curvatures/interpolated_corrected_curvature_measures.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index f14904c6799a..4a50a618e66f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -632,6 +632,9 @@ namespace internal { Vertex_measures vertex_measures; for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + if (f == boost::graph_traits::null_face()) + continue; + vertex_measures.area_measure += get(mu0_map, f); if (is_mean_curvature_selected) From 1fd56cdd1dfd5fa06184c1ea38fe581f29c02fdf Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 20 Nov 2022 00:28:10 +0200 Subject: [PATCH 091/161] testing file for implemented functions --- .../Polygon_mesh_processing/CMakeLists.txt | 2 +- ...est_interopolated_corrected_curvatures.cpp | 148 --------------- ...test_interpolated_corrected_curvatures.cpp | 178 ++++++++++++++++++ 3 files changed, 179 insertions(+), 149 deletions(-) delete mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index 808e6d3a05ef..c1aedddedf4e 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -69,7 +69,7 @@ create_single_source_cgal_program("self_intersection_polyhedron_test.cpp") create_single_source_cgal_program("self_intersection_surface_mesh_test.cpp") create_single_source_cgal_program("pmp_do_intersect_test.cpp") create_single_source_cgal_program("test_is_polygon_soup_a_polygon_mesh.cpp") -create_single_source_cgal_program("test_interopolated_corrected_curvatures.cpp") +create_single_source_cgal_program("test_interpolated_corrected_curvatures.cpp") create_single_source_cgal_program("test_stitching.cpp") create_single_source_cgal_program("remeshing_test.cpp") create_single_source_cgal_program("remeshing_with_isolated_constraints_test.cpp" ) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp deleted file mode 100644 index b2dbcc0850bb..000000000000 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interopolated_corrected_curvatures.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -namespace PMP = CGAL::Polygon_mesh_processing; - -typedef CGAL::Exact_predicates_inexact_constructions_kernel EpicKernel; -typedef CGAL::Surface_mesh SMesh; -typedef boost::graph_traits::face_descriptor face_descriptor; -typedef boost::graph_traits::edge_descriptor edge_descriptor; -typedef boost::graph_traits::vertex_descriptor vertex_descriptor; - -void test(std::string mesh_path, EpicKernel::FT rel_expansion_radius, EpicKernel::FT rel_noise_magnitude) { - SMesh pmesh; - const std::string filename = CGAL::data_file_path(mesh_path); - - if (!CGAL::IO::read_polygon_mesh(filename, pmesh)) - { - std::cerr << "Invalid input file." << std::endl; - } - - bool created = false; - - SMesh::Property_map mean_curvature_map, gaussian_curvature_map; - boost::tie(mean_curvature_map, created) = pmesh.add_property_map("v:mean_curvature_map", 0); - assert(created); - - boost::tie(gaussian_curvature_map, created) = pmesh.add_property_map("v:gaussian_curvature_map", 0); - assert(created); - - // getting the max and min edge lengthes - const auto edge_range = CGAL::edges(pmesh); - - const auto edge_length_comparator = [&, pmesh](auto l, auto r) { - return PMP::edge_length(l, pmesh) < PMP::edge_length(r, pmesh); - }; - - const edge_descriptor longest_edge = *std::max_element(edge_range.begin(), edge_range.end(), edge_length_comparator); - const EpicKernel::FT max_edge_length = PMP::edge_length(longest_edge, pmesh); - - const edge_descriptor shortest_edge = *std::min_element(edge_range.begin(), edge_range.end(), edge_length_comparator); - const EpicKernel::FT min_edge_length = PMP::edge_length(shortest_edge, pmesh); - - - if (rel_noise_magnitude > 0) - { - if (!CGAL::is_triangle_mesh(pmesh)) - return; - - SMesh::Property_map vnm; - boost::tie(vnm, created) = pmesh.add_property_map("v:vnm", { 0 , 0 , 0 }); - assert(created); - - CGAL::Polygon_mesh_processing::compute_vertex_normals(pmesh, vnm); - - PMP::random_perturbation(pmesh, rel_noise_magnitude * min_edge_length, CGAL::parameters::random_seed(0)); - PMP::interpolated_corrected_mean_curvature( - pmesh, - mean_curvature_map, - CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length).vertex_normal_map(vnm) - ); - PMP::interpolated_corrected_gaussian_curvature( - pmesh, - gaussian_curvature_map, - CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length).vertex_normal_map(vnm) - ); - } - else { - PMP::interpolated_corrected_mean_curvature( - pmesh, - mean_curvature_map, - CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) - ); - PMP::interpolated_corrected_gaussian_curvature( - pmesh, - gaussian_curvature_map, - CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) - ); - - } - - - - //PMP::interpolated_corrected_mean_curvature( - // pmesh, - // mean_curvature_map, - // CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) - //); - //PMP::interpolated_corrected_gaussian_curvature( - // pmesh, - // gaussian_curvature_map, - // CGAL::parameters::ball_radius(rel_expansion_radius * max_edge_length) - //); - - - const EpicKernel::FT max_mean_curvature = *std::max_element(mean_curvature_map.begin(), mean_curvature_map.end()); - const EpicKernel::FT min_mean_curvature = *std::min_element(mean_curvature_map.begin(), mean_curvature_map.end()); - const EpicKernel::FT max_gaussian_curvature = *std::max_element(gaussian_curvature_map.begin(), gaussian_curvature_map.end()); - const EpicKernel::FT min_gaussian_curvature = *std::min_element(gaussian_curvature_map.begin(), gaussian_curvature_map.end()); - - std::cout << "# " << mesh_path << ":\n" - << "expansion radius ratio to max length / expansion radius = " << rel_expansion_radius << " / " << rel_expansion_radius * max_edge_length << ",\n" - << "max perturbation ratio to minlength / max perturbation = " << rel_noise_magnitude << " / " << rel_noise_magnitude * min_edge_length << "\n" - << "mean curvature: min = " << min_mean_curvature << " <-> " << max_mean_curvature << " = max" << "\n" - << "gaussian curvature: min = " << min_gaussian_curvature << " <-> " << max_gaussian_curvature << " = max" << "\n\n\n"; - - -} - -int main() -{ - const std::vector mesh_pathes_to_test = { - "meshes/icc_test/Sphere Quads + Tris.obj", - "meshes/icc_test/Sphere Quads + Tris 100352.obj", - "meshes/icc_test/Sphere Tris Ico.obj", - "meshes/icc_test/Sphere Tris Tet.obj", - "meshes/icc_test/Sphere Tris Oct.obj", - "meshes/icc_test/Sphere Quads.obj", - "meshes/icc_test/Sphere Quads Remesh.obj", - "meshes/icc_test/Sphere Ngons + Quads + Tris.obj", - "meshes/icc_test/Cube with fillet Quads.obj", - "meshes/cylinder.off", - "meshes/icc_test/Lantern Tris.obj", - "meshes/icc_test/Lantern Quads.obj" - }; - - const std::vector rel_expansion_radii = { 0, 0.1, 0.5, 1 }; - const std::vector rel_noise_magnitudes = { 0, 0.5, 0.9 }; - - for (auto mesh_path : mesh_pathes_to_test) { - for (EpicKernel::FT rel_expansion_radius : rel_expansion_radii) - for (EpicKernel::FT rel_noise_magnitude : rel_noise_magnitudes) - { - test(mesh_path, rel_expansion_radius, rel_noise_magnitude); - } - - std::cout << "_________________________________________________________________________________\n\n"; - } - -} diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp new file mode 100644 index 000000000000..a4f43470332e --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp @@ -0,0 +1,178 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define ABS_ERROR 1e-6 + +namespace PMP = CGAL::Polygon_mesh_processing; + +typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic_kernel; +typedef CGAL::Surface_mesh SMesh; +typedef CGAL::Polyhedron_3 Polyhedron; + +struct Average_test_info { + Epic_kernel::FT expansion_radius = -1; + Epic_kernel::FT mean_curvature_avg; + Epic_kernel::FT gaussian_curvature_avg; + Epic_kernel::FT principal_curvature_avg; + Epic_kernel::FT tolerance = 0.9; + + Average_test_info( + Epic_kernel::FT mean_curvature_avg, + Epic_kernel::FT gaussian_curvature_avg, + Epic_kernel::FT principal_curvature_avg, + Epic_kernel::FT expansion_radius = -1, + Epic_kernel::FT tolerance = 0.9 + ): + expansion_radius(expansion_radius), + mean_curvature_avg(mean_curvature_avg), + gaussian_curvature_avg(gaussian_curvature_avg), + principal_curvature_avg(principal_curvature_avg), + tolerance(tolerance) + { + } + +}; + +bool passes_comparison(Epic_kernel::FT result, Epic_kernel::FT expected, Epic_kernel::FT tolerance) +{ + if (abs(expected) < ABS_ERROR && abs(result) < ABS_ERROR) + return true; // expected 0, got 0 + else if (abs(expected) < ABS_ERROR) + return false; // expected 0, got non-0 + + return std::min(result, expected) / std::max(result, expected) > tolerance; +} + +template +void test_average_curvatures(std::string mesh_path, Average_test_info test_info){ + PolygonMesh pmesh; + const std::string filename = CGAL::data_file_path(mesh_path); + + if (!CGAL::IO::read_polygon_mesh(filename, pmesh) || faces(pmesh).size() == 0) + { + std::cerr << "Invalid input file." << std::endl; + } + + typedef boost::graph_traits::vertex_descriptor vertex_descriptor; + + boost::property_map>::type + mean_curvature_map = get(CGAL::dynamic_vertex_property_t(), pmesh), + gaussian_curvature_map = get(CGAL::dynamic_vertex_property_t(), pmesh); + boost::property_map>>::type + principal_curvatures_and_directions_map = get(CGAL::dynamic_vertex_property_t>(), pmesh); + + // test_info.expansion_radius -> test if no radius is provided by user. + if (test_info.expansion_radius < 0) { + PMP::interpolated_corrected_mean_curvature(pmesh, mean_curvature_map); + PMP::interpolated_corrected_gaussian_curvature(pmesh, gaussian_curvature_map); + PMP::interpolated_corrected_principal_curvatures_and_directions(pmesh, principal_curvatures_and_directions_map); + } + else { + PMP::interpolated_corrected_mean_curvature( + pmesh, + mean_curvature_map, + CGAL::parameters::ball_radius(test_info.expansion_radius) + ); + + PMP::interpolated_corrected_gaussian_curvature( + pmesh, + gaussian_curvature_map, + CGAL::parameters::ball_radius(test_info.expansion_radius) + ); + + PMP::interpolated_corrected_principal_curvatures_and_directions( + pmesh, + principal_curvatures_and_directions_map, + CGAL::parameters::ball_radius(test_info.expansion_radius) + ); + } + + Epic_kernel::FT mean_curvature_avg = 0, gaussian_curvature_avg = 0, principal_curvature_avg = 0; + + for (vertex_descriptor v : vertices(pmesh)) + { + mean_curvature_avg += get(mean_curvature_map, v); + gaussian_curvature_avg += get(gaussian_curvature_map, v); + principal_curvature_avg += get(principal_curvatures_and_directions_map, v).min_curvature + + get(principal_curvatures_and_directions_map, v).max_curvature; + } + + mean_curvature_avg /= vertices(pmesh).size(); + gaussian_curvature_avg /= vertices(pmesh).size(); + principal_curvature_avg /= vertices(pmesh).size() * 2; + + // are average curvatures equal to expected? + assert(passes_comparison(mean_curvature_avg, test_info.mean_curvature_avg, test_info.tolerance)); + assert(passes_comparison(gaussian_curvature_avg, test_info.gaussian_curvature_avg, test_info.tolerance)); + assert(passes_comparison(principal_curvature_avg, test_info.principal_curvature_avg, test_info.tolerance)); + + PMP::interpolated_corrected_curvatures( + pmesh, + CGAL::parameters::ball_radius(test_info.expansion_radius) + .vertex_mean_curvature_map(mean_curvature_map) + .vertex_gaussian_curvature_map(gaussian_curvature_map) + .vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) + ); + + // are average curvatures computed from interpolated_corrected_curvatures() equal to average curvatures each computed on its own? + Epic_kernel::FT new_mean_curvature_avg = 0, new_gaussian_curvature_avg = 0, new_principal_curvature_avg = 0; + + for (vertex_descriptor v : vertices(pmesh)) + { + new_mean_curvature_avg += get(mean_curvature_map, v); + new_gaussian_curvature_avg += get(gaussian_curvature_map, v); + new_principal_curvature_avg += get(principal_curvatures_and_directions_map, v).min_curvature + + get(principal_curvatures_and_directions_map, v).max_curvature; + } + + new_mean_curvature_avg /= vertices(pmesh).size(); + new_gaussian_curvature_avg /= vertices(pmesh).size(); + new_principal_curvature_avg /= vertices(pmesh).size() * 2; + + assert(passes_comparison(mean_curvature_avg, new_mean_curvature_avg, 0.99)); + assert(passes_comparison(gaussian_curvature_avg, new_gaussian_curvature_avg, 0.99)); + assert(passes_comparison(principal_curvature_avg, new_principal_curvature_avg, 0.99)); +} + +int main() +{ + // testing on a simple sphere(r = 0.5), on both Polyhedron & SurfaceMesh: + // Expected: Mean Curvature = 2, Gaussian Curvature = 4, Principal Curvatures = 2 & 2 so 2 on avg. + test_average_curvatures("meshes/sphere.off", Average_test_info(2,4,2)); + test_average_curvatures("meshes/sphere.off", Average_test_info(2, 4, 2)); + + // Same mesh but with specified expansion radii of 0 and 0.25 (half radius of sphere) + test_average_curvatures("meshes/sphere.off", Average_test_info(2, 4, 2, 0)); + test_average_curvatures("meshes/sphere.off", Average_test_info(2, 4, 2, 0.25)); + + // testing on a simple sphere(r = 10), on both Polyhedron & SurfaceMesh: + // Expected: Mean Curvature = 0.1, Gaussian Curvature = 0.01, Principal Curvatures = 0.1 & 0.1 so 0.1 on avg. + test_average_curvatures("meshes/sphere966.off", Average_test_info(0.1, 0.01, 0.1)); + test_average_curvatures("meshes/sphere966.off", Average_test_info(0.1, 0.01, 0.1)); + + // Same mesh but with specified expansion radii of 0 and 5 (half radius of sphere) + test_average_curvatures("meshes/sphere966.off", Average_test_info(0.1, 0.01, 0.1, 0)); + test_average_curvatures("meshes/sphere966.off", Average_test_info(0.1, 0.01, 0.1, 5)); + + // testing on a simple half cylinder(r = 1), on both Polyhedron & SurfaceMesh: + // Expected: Mean Curvature = 0.5, Gaussian Curvature = 0, Principal Curvatures = 0 & 1 so 0.5 on avg. + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5)); + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5)); + + // Same mesh but with specified expansion radii of 0 and 0.5 (half radius of cylinder) + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0)); + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0.5)); + + + +} From 2af64657636b1fadbacee3dac9e6b2108e1ffedf Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 20 Nov 2022 00:39:14 +0200 Subject: [PATCH 092/161] trailing spaces --- .../test_interpolated_corrected_curvatures.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp index a4f43470332e..163c9b8d334d 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp @@ -32,13 +32,13 @@ struct Average_test_info { Epic_kernel::FT principal_curvature_avg, Epic_kernel::FT expansion_radius = -1, Epic_kernel::FT tolerance = 0.9 - ): + ): expansion_radius(expansion_radius), mean_curvature_avg(mean_curvature_avg), gaussian_curvature_avg(gaussian_curvature_avg), principal_curvature_avg(principal_curvature_avg), tolerance(tolerance) - { + { } }; From f6855fef22c428a701719578097f4051ce8525e1 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 3 Jan 2023 12:13:26 +0200 Subject: [PATCH 093/161] single vertex computation implemented single vertex curvature computation compared against the whole mesh computation on both cases of no radius and with radius on some vertices still need to add tests, documentation and an example file --- ...nterpolated_corrected_curvature_measures.h | 1374 ++++++++++------- .../internal/parameters_interface.h | 3 + 2 files changed, 830 insertions(+), 547 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 4a50a618e66f..94aaff9b8b52 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -79,675 +79,911 @@ struct Principal_curvatures_and_directions { namespace internal { - template - typename GT::FT average_edge_length(const PolygonMesh& pmesh) { - const std::size_t n = edges(pmesh).size(); - if (n == 0) - return 0; - - typename GT::FT avg_edge_length = 0; - for (auto e : edges(pmesh)) - avg_edge_length += edge_length(e, pmesh); - - avg_edge_length /= n; - return avg_edge_length; - } +template +typename GT::FT average_edge_length(const PolygonMesh& pmesh) { + const std::size_t n = edges(pmesh).size(); + if (n == 0) + return 0; + + typename GT::FT avg_edge_length = 0; + for (auto e : edges(pmesh)) + avg_edge_length += edge_length(e, pmesh); + + avg_edge_length /= n; + return avg_edge_length; +} - enum Curvature_measure_index { - MU0_AREA_MEASURE, ///< corrected area density - MU1_MEAN_CURVATURE_MEASURE, ///< corrected mean curvature density - MU2_GAUSSIAN_CURVATURE_MEASURE ///< corrected gaussian curvature density - }; - - template - struct Vertex_measures { - typename GT::FT area_measure = 0; - typename GT::FT mean_curvature_measure = 0; - typename GT::FT gaussian_curvature_measure = 0; - std::array anisotropic_measure = { 0, 0, 0, - 0, 0, 0, - 0, 0, 0 }; - }; - - template - typename GT::FT interpolated_corrected_area_measure_face(const std::vector& u, - const std::vector& x) +enum Curvature_measure_index { + MU0_AREA_MEASURE, ///< corrected area density + MU1_MEAN_CURVATURE_MEASURE, ///< corrected mean curvature density + MU2_GAUSSIAN_CURVATURE_MEASURE ///< corrected gaussian curvature density +}; + +template +struct Vertex_measures { + typename GT::FT area_measure = 0; + typename GT::FT mean_curvature_measure = 0; + typename GT::FT gaussian_curvature_measure = 0; + std::array anisotropic_measure = { 0, 0, 0, + 0, 0, 0, + 0, 0, 0 }; +}; + +template +typename GT::FT interpolated_corrected_area_measure_face(const std::vector& u, + const std::vector& x) +{ + const std::size_t n = x.size(); + CGAL_precondition(u.size() == n); + CGAL_precondition(n >= 3); + + typename GT::Construct_cross_product_vector_3 cross_product; + + // Triangle: use triangle formula + if (n == 3) + { + const typename GT::Vector_3 um = (u[0] + u[1] + u[2]) / 3.0; + return 0.5 * um * cross_product(x[1] - x[0], x[2] - x[0]); + } + // Quad: use bilinear interpolation formula + else if (n == 4) { - const std::size_t n = x.size(); - CGAL_precondition(u.size() == n); - CGAL_precondition(n >= 3); + // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. + // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 + return (1.0 / 36.0) * ( + (4 * u[0] + 2 * u[1] + 2 * u[3] + u[2]) * cross_product(x[1] - x[0], x[3] - x[0]) + + (2 * u[0] + 4 * u[1] + u[3] + 2 * u[2]) * cross_product(x[1] - x[0], x[2] - x[1]) + + (2 * u[0] + u[1] + 4 * u[3] + 2 * u[2]) * cross_product(x[2] - x[3], x[3] - x[0]) + + (u[0] + 2 * u[1] + 2 * u[3] + 4 * u[2]) * cross_product(x[2] - x[3], x[2] - x[1]) + ); + } + // N-gon: split into n triangles by polygon center and use triangle formula for each + else + { + typename GT::FT mu0 = 0; - typename GT::Construct_cross_product_vector_3 cross_product; + // getting center of points + typename GT::Vector_3 xc = + std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); + xc /= n; - // Triangle: use triangle formula - if (n == 3) - { - const typename GT::Vector_3 um = (u[0] + u[1] + u[2]) / 3.0; - return 0.5 * um * cross_product(x[1] - x[0], x[2] - x[0]); - } - // Quad: use bilinear interpolation formula - else if (n == 4) + // getting unit average normal of points + typename GT::Vector_3 uc = + std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); + uc /= sqrt(uc * uc); + + // summing each triangle's measure after triangulation by barycenter split. + for (std::size_t i = 0; i < n; i++) { - // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. - // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 - return (1.0 / 36.0) * ( - (4 * u[0] + 2 * u[1] + 2 * u[3] + u[2]) * cross_product(x[1] - x[0], x[3] - x[0]) - + (2 * u[0] + 4 * u[1] + u[3] + 2 * u[2]) * cross_product(x[1] - x[0], x[2] - x[1]) - + (2 * u[0] + u[1] + 4 * u[3] + 2 * u[2]) * cross_product(x[2] - x[3], x[3] - x[0]) - + (u[0] + 2 * u[1] + 2 * u[3] + 4 * u[2]) * cross_product(x[2] - x[3], x[2] - x[1]) - ); + mu0 += interpolated_corrected_area_measure_face( + std::vector {u[i], u[(i + 1) % n], uc}, + std::vector {x[i], x[(i + 1) % n], xc} + ); } - // N-gon: split into n triangles by polygon center and use triangle formula for each - else - { - typename GT::FT mu0 = 0; + return mu0; + } +} - // getting center of points - typename GT::Vector_3 xc = - std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); - xc /= n; +template +typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::vector& u, + const std::vector& x) +{ + const std::size_t n = x.size(); + CGAL_precondition(u.size() == n); + CGAL_precondition(n >= 3); - // getting unit average normal of points - typename GT::Vector_3 uc = - std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); - uc /= sqrt(uc * uc); + typename GT::Construct_cross_product_vector_3 cross_product; - // summing each triangle's measure after triangulation by barycenter split. - for (std::size_t i = 0; i < n; i++) - { - mu0 += interpolated_corrected_area_measure_face( - std::vector {u[i], u[(i + 1) % n], uc}, - std::vector {x[i], x[(i + 1) % n], xc} - ); - } - return mu0; - } - } + // Triangle: use triangle formula + if (n == 3) + { + const typename GT::Vector_3 um = (u[0] + u[1] + u[2]) / 3.0; - template - typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::vector& u, - const std::vector& x) + return 0.5 * um * (cross_product(u[2] - u[1], x[0]) + + cross_product(u[0] - u[2], x[1]) + + cross_product(u[1] - u[0], x[2])); + } + // Quad: use bilinear interpolation formula + else if (n == 4) { - const std::size_t n = x.size(); - CGAL_precondition(u.size() == n); - CGAL_precondition(n >= 3); + // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. + // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 + + const typename GT::Vector_3 u02 = u[2] - u[0]; + const typename GT::Vector_3 u13 = u[3] - u[1]; + const typename GT::Vector_3 x0_cross = cross_product(u13, x[0]); + const typename GT::Vector_3 x1_cross = -cross_product(u02, x[1]); + const typename GT::Vector_3 x3_cross = cross_product(u02, x[3]); + const typename GT::Vector_3 x2_cross = -cross_product(u13, x[2]); + + return (1.0 / 12.0) * ( + u[0] * (2 * x0_cross - cross_product((u[3] + u[2]), x[1]) + cross_product((u[1] + u[2]), x[3]) + x2_cross) + + u[1] * (cross_product((u[3] + u[2]), x[0]) + 2 * x1_cross + x3_cross - cross_product((u[0] + u[3]), x[2])) + + u[3] * (-cross_product((u[1] + u[2]), x[0]) + x1_cross + 2 * x3_cross + cross_product((u[0] + u[1]), x[2])) + + u[2] * (x0_cross + cross_product((u[0] + u[3]), x[1]) - cross_product((u[0] + u[1]), x[3]) + 2 * x2_cross) + ); + } + // N-gon: split into n triangles by polygon center and use triangle formula for each + else + { + typename GT::FT mu1 = 0; - typename GT::Construct_cross_product_vector_3 cross_product; + // getting center of points + typename GT::Vector_3 xc = + std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); + xc /= n; - // Triangle: use triangle formula - if (n == 3) - { - const typename GT::Vector_3 um = (u[0] + u[1] + u[2]) / 3.0; + // getting unit average normal of points + typename GT::Vector_3 uc = + std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); + uc /= sqrt(uc * uc); - return 0.5 * um * (cross_product(u[2] - u[1], x[0]) - + cross_product(u[0] - u[2], x[1]) - + cross_product(u[1] - u[0], x[2])); - } - // Quad: use bilinear interpolation formula - else if (n == 4) + // summing each triangle's measure after triangulation by barycenter split. + for (std::size_t i = 0; i < n; i++) { - // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. - // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 - - const typename GT::Vector_3 u02 = u[2] - u[0]; - const typename GT::Vector_3 u13 = u[3] - u[1]; - const typename GT::Vector_3 x0_cross = cross_product(u13, x[0]); - const typename GT::Vector_3 x1_cross = -cross_product(u02, x[1]); - const typename GT::Vector_3 x3_cross = cross_product(u02, x[3]); - const typename GT::Vector_3 x2_cross = -cross_product(u13, x[2]); - - return (1.0 / 12.0) * ( - u[0] * (2 * x0_cross - cross_product((u[3] + u[2]), x[1]) + cross_product((u[1] + u[2]), x[3]) + x2_cross) - + u[1] * (cross_product((u[3] + u[2]), x[0]) + 2 * x1_cross + x3_cross - cross_product((u[0] + u[3]), x[2])) - + u[3] * (-cross_product((u[1] + u[2]), x[0]) + x1_cross + 2 * x3_cross + cross_product((u[0] + u[1]), x[2])) - + u[2] * (x0_cross + cross_product((u[0] + u[3]), x[1]) - cross_product((u[0] + u[1]), x[3]) + 2 * x2_cross) - ); + mu1 += interpolated_corrected_mean_curvature_measure_face( + std::vector {u[i], u[(i + 1) % n], uc}, + std::vector {x[i], x[(i + 1) % n], xc} + ); } - // N-gon: split into n triangles by polygon center and use triangle formula for each - else - { - typename GT::FT mu1 = 0; + return mu1; + } +} - // getting center of points - typename GT::Vector_3 xc = - std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); - xc /= n; +template +typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std::vector& u, + const std::vector& x = {}) +{ + const std::size_t n = u.size(); + CGAL_precondition(n >= 3); - // getting unit average normal of points - typename GT::Vector_3 uc = - std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); - uc /= sqrt(uc * uc); + typename GT::Construct_cross_product_vector_3 cross_product; - // summing each triangle's measure after triangulation by barycenter split. - for (std::size_t i = 0; i < n; i++) - { - mu1 += interpolated_corrected_mean_curvature_measure_face( - std::vector {u[i], u[(i + 1) % n], uc}, - std::vector {x[i], x[(i + 1) % n], xc} - ); - } - return mu1; - } + // Triangle: use triangle formula + if (n == 3) + { + return 0.5 * u[0] * cross_product(u[1], u[2]); } - - template - typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std::vector& u, - const std::vector& x = {}) + // Quad: use bilinear interpolation formula + else if (n == 4) + { + // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. + // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 + return (1.0 / 36.0) * ( + (4 * u[0] + 2 * u[1] + 2 * u[3] + u[2]) * cross_product(u[1] - u[0], u[3] - u[0]) + + (2 * u[0] + 4 * u[1] + u[3] + 2 * u[2]) * cross_product(u[1] - u[0], u[2] - u[1]) + + (2 * u[0] + u[1] + 4 * u[3] + 2 * u[2]) * cross_product(u[2] - u[3], u[3] - u[0]) + + (u[0] + 2 * u[1] + 2 * u[3] + 4 * u[2]) * cross_product(u[2] - u[3], u[2] - u[1]) + ); + } + // N-gon: split into n triangles by polygon center and use triangle formula for each + else { - const std::size_t n = u.size(); - CGAL_precondition(n >= 3); + typename GT::FT mu2 = 0; - typename GT::Construct_cross_product_vector_3 cross_product; + // getting unit average normal of points + typename GT::Vector_3 uc = + std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); + uc /= sqrt(uc * uc); - // Triangle: use triangle formula - if (n == 3) + // summing each triangle's measure after triangulation by barycenter split. + for (std::size_t i = 0; i < n; i++) { - return 0.5 * u[0] * cross_product(u[1], u[2]); + mu2 += interpolated_corrected_gaussian_curvature_measure_face( + std::vector {u[i], u[(i + 1) % n], uc} + ); } - // Quad: use bilinear interpolation formula - else if (n == 4) + return mu2; + } +} + +template +std::array interpolated_corrected_anisotropic_measure_face(const std::vector& u, + const std::vector& x) +{ + const std::size_t n = x.size(); + CGAL_precondition(u.size() == n); + CGAL_precondition(n >= 3); + + typename GT::Construct_cross_product_vector_3 cross_product; + std::array muXY{ 0 }; + + // Triangle: use triangle formula + if (n == 3) + { + const typename GT::Vector_3 u01 = u[1] - u[0]; + const typename GT::Vector_3 u02 = u[2] - u[0]; + const typename GT::Vector_3 x01 = x[1] - x[0]; + const typename GT::Vector_3 x02 = x[2] - x[0]; + const typename GT::Vector_3 um = (u[0] + u[1] + u[2]) / 3.0; + + for (std::size_t ix = 0; ix < 3; ix++) { - // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. - // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 - return (1.0 / 36.0) * ( - (4 * u[0] + 2 * u[1] + 2 * u[3] + u[2]) * cross_product(u[1] - u[0], u[3] - u[0]) - + (2 * u[0] + 4 * u[1] + u[3] + 2 * u[2]) * cross_product(u[1] - u[0], u[2] - u[1]) - + (2 * u[0] + u[1] + 4 * u[3] + 2 * u[2]) * cross_product(u[2] - u[3], u[3] - u[0]) - + (u[0] + 2 * u[1] + 2 * u[3] + 4 * u[2]) * cross_product(u[2] - u[3], u[2] - u[1]) - ); + typename GT::Vector_3 X; + if (ix == 0) + X = typename GT::Vector_3(1, 0, 0); + if (ix == 1) + X = typename GT::Vector_3(0, 1, 0); + if (ix == 2) + X = typename GT::Vector_3(0, 0, 1); + + for (std::size_t iy = 0; iy < 3; iy++) + muXY[ix * 3 + iy] = 0.5 * um * (cross_product(u02[iy] * X, x01) - cross_product(u01[iy] * X, x02)); } - // N-gon: split into n triangles by polygon center and use triangle formula for each - else + } + // Quad: use bilinear interpolation formula + else if (n == 4) + { + // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. + // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 + for (std::size_t ix = 0; ix < 3; ix++) { - typename GT::FT mu2 = 0; + typename GT::Vector_3 X; + if (ix == 0) + X = typename GT::Vector_3(1, 0, 0); + if (ix == 1) + X = typename GT::Vector_3(0, 1, 0); + if (ix == 2) + X = typename GT::Vector_3(0, 0, 1); + + const typename GT::Vector_3 u0xX = cross_product(u[0], X); + const typename GT::Vector_3 u1xX = cross_product(u[1], X); + const typename GT::Vector_3 u2xX = cross_product(u[2], X); + const typename GT::Vector_3 u3xX = cross_product(u[3], X); - // getting unit average normal of points - typename GT::Vector_3 uc = - std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); - uc /= sqrt(uc * uc); - - // summing each triangle's measure after triangulation by barycenter split. - for (std::size_t i = 0; i < n; i++) - { - mu2 += interpolated_corrected_gaussian_curvature_measure_face( - std::vector {u[i], u[(i + 1) % n], uc} - ); - } - return mu2; + for (std::size_t iy = 0; iy < 3; iy++) + muXY[ix * 3 + iy] = (1.0 / 72.0) * ( + + u[0][iy] * (u0xX * (-x[0] - 11 * x[1] + 13 * x[3] - x[2]) + + u1xX * (-5 * x[0] - 7 * x[1] + 11 * x[3] + x[2]) + + u3xX * (x[0] - 7 * x[1] + 11 * x[3] - 5 * x[2]) + + u2xX * (-x[0] - 5 * x[1] + 7 * x[3] - x[2]) + ) + + u[1][iy] * (u0xX * (13 * x[0] - x[1] - 7 * x[3] - 5 * x[2]) + + u1xX * (17 * x[0] - 5 * x[1] - 5 * x[3] - 7 * x[2]) + + u3xX * (5 * x[0] + x[1] + x[3] - 7 * x[2]) + + u2xX * (7 * x[0] - x[1] + 5 * x[3] - 11 * x[2]) + ) + + u[2][iy] * (u0xX * (-11 * x[0] + 5 * x[1] - x[3] + 7 * x[2]) + + u1xX * (-7 * x[0] + x[1] + x[3] + 5 * x[2]) + + u3xX * (-7 * x[0] - 5 * x[1] - 5 * x[3] + 17 * x[2]) + + u2xX * (-5 * x[0] - 7 * x[1] - x[3] + 13 * x[2]) + ) + + u[3][iy] * (u0xX * (-x[0] + 7 * x[1] - 5 * x[3] - x[2]) + + u1xX * (-5 * x[0] + 11 * x[1] - 7 * x[3] + x[2]) + + u3xX * (x[0] + 11 * x[1] - 7 * x[3] - 5 * x[2]) + + u2xX * (-x[0] + 13 * x[1] - 11 * x[3] - x[2]) + ) + + ); } } - - template - std::array interpolated_corrected_anisotropic_measure_face(const std::vector& u, - const std::vector& x) + // N-gon: split into n triangles by polygon center and use triangle formula for each + else { - const std::size_t n = x.size(); - CGAL_precondition(u.size() == n); - CGAL_precondition(n >= 3); + // getting center of points + typename GT::Vector_3 xc = + std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); + xc /= n; - typename GT::Construct_cross_product_vector_3 cross_product; - std::array muXY{ 0 }; + // getting unit average normal of points + typename GT::Vector_3 uc = + std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); + uc /= sqrt(uc * uc); - // Triangle: use triangle formula - if (n == 3) + // summing each triangle's measure after triangulation by barycenter split. + for (std::size_t i = 0; i < n; i++) { - const typename GT::Vector_3 u01 = u[1] - u[0]; - const typename GT::Vector_3 u02 = u[2] - u[0]; - const typename GT::Vector_3 x01 = x[1] - x[0]; - const typename GT::Vector_3 x02 = x[2] - x[0]; - const typename GT::Vector_3 um = (u[0] + u[1] + u[2]) / 3.0; + std::array muXY_curr_triangle = + interpolated_corrected_anisotropic_measure_face( + std::vector {u[i], u[(i + 1) % n], uc}, + std::vector {x[i], x[(i + 1) % n], xc} + ); for (std::size_t ix = 0; ix < 3; ix++) - { - typename GT::Vector_3 X; - if (ix == 0) - X = typename GT::Vector_3(1, 0, 0); - if (ix == 1) - X = typename GT::Vector_3(0, 1, 0); - if (ix == 2) - X = typename GT::Vector_3(0, 0, 1); - for (std::size_t iy = 0; iy < 3; iy++) - muXY[ix * 3 + iy] = 0.5 * um * (cross_product(u02[iy] * X, x01) - cross_product(u01[iy] * X, x02)); - } + muXY[ix * 3 + iy] += muXY_curr_triangle[ix * 3 + iy]; } - // Quad: use bilinear interpolation formula - else if (n == 4) + } + return muXY; +} + +//template +//typename GT::FT triangle_in_ball_ratio(const typename GT::Vector_3 x1, +// const typename GT::Vector_3 x2, +// const typename GT::Vector_3 x3, +// const typename GT::FT r, +// const typename GT::Vector_3 c, +// const std::size_t res = 3) +//{ +// const typename GT::FT R = r * r; +// const typename GT::FT acc = 1.0 / res; +// std::size_t samples_in = 0; +// for (GT::FT alpha = acc / 3; alpha < 1; alpha += acc) +// for (GT::FT beta = acc / 3; beta < 1 - alpha; beta += acc) +// { +// if ((alpha * x1 + beta * x2 + (1 - alpha - beta) * x3 - c).squared_length() < R) +// samples_in++; +// } +// return samples_in / (typename GT::FT)(res * (res + 1) / 2); +//} + +template +typename GT::FT face_in_ball_ratio(const std::vector& x, + const typename GT::FT r, + const typename GT::Vector_3 c) +{ + const std::size_t n = x.size(); + + // getting center of points + typename GT::Vector_3 xm = + std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); + xm /= n; + + typename GT::FT d_min = (xm - c).squared_length(); + typename GT::FT d_max = d_min; + + for (const typename GT::Vector_3 xi : x) + { + const typename GT::FT d_sq = (xi - c).squared_length(); + d_max = (std::max)(d_sq, d_max); + d_min = (std::min)(d_sq, d_min); + } + + if (d_max <= r * r) return 1.0; + else if (r * r <= d_min) return 0.0; + + d_max = sqrt(d_max); + d_min = sqrt(d_min); + + return (r - d_min) / (d_max - d_min); +} + +template +Principal_curvatures_and_directions principal_curvatures_and_directions_from_anisotropic_measures( + const std::array anisotropic_measure, + const typename GT::FT v_mu0, + const typename GT::Vector_3 u_GT +) +{ + Eigen::Matrix v_muXY = Eigen::Matrix::Zero(); + + for (std::size_t ix = 0; ix < 3; ix++) + for (std::size_t iy = 0; iy < 3; iy++) + v_muXY(ix, iy) = anisotropic_measure[ix * 3 + iy]; + + Eigen::Matrix u(u_GT.x(), u_GT.y(), u_GT.z()); + const typename GT::FT K = 1000 * v_mu0; + + v_muXY = 0.5 * (v_muXY + v_muXY.transpose()) + K * u * u.transpose(); + + Eigen::SelfAdjointEigenSolver> eigensolver; + + eigensolver.computeDirect(v_muXY); + + if (eigensolver.info() != Eigen::Success) + return Principal_curvatures_and_directions(); + + const Eigen::Matrix eig_vals = eigensolver.eigenvalues(); + const Eigen::Matrix eig_vecs = eigensolver.eigenvectors(); + + const typename GT::Vector_3 min_eig_vec(eig_vecs(0, 1), eig_vecs(1, 1), eig_vecs(2, 1)); + const typename GT::Vector_3 max_eig_vec(eig_vecs(0, 0), eig_vecs(1, 0), eig_vecs(2, 0)); + + return Principal_curvatures_and_directions( + (v_mu0 != 0.0) ? -eig_vals[1] / v_mu0 : 0.0, + (v_mu0 != 0.0) ? -eig_vals[0] / v_mu0 : 0.0, + min_eig_vec, + max_eig_vec + ); +} + +template +typename Vertex_measures interpolated_corrected_measures_one_vertex_no_radius( + const PolygonMesh pmesh, + const typename boost::graph_traits::vertex_descriptor v, + const bool is_mean_curvature_selected, + const bool is_gaussian_curvature_selected, + const bool is_principal_curvatures_and_directions_selected, + const VPM vpm, + const VNM vnm +) +{ + typedef typename boost::graph_traits::face_descriptor Face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename GT::Point_3 Point_3; + typedef typename GT::Vector_3 Vector_3; + typedef typename GT::FT FT; + + std::queue bfs_queue; + std::unordered_set bfs_visited; + + typename Vertex_measures vertex_measures; + + std::vector x; + std::vector u; + + for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + if (f != boost::graph_traits::null_face()) { - // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. - // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 - for (std::size_t ix = 0; ix < 3; ix++) + for (Vertex_descriptor vi : vertices_around_face(halfedge(f, pmesh), pmesh)) { - typename GT::Vector_3 X; - if (ix == 0) - X = typename GT::Vector_3(1, 0, 0); - if (ix == 1) - X = typename GT::Vector_3(0, 1, 0); - if (ix == 2) - X = typename GT::Vector_3(0, 0, 1); - - const typename GT::Vector_3 u0xX = cross_product(u[0], X); - const typename GT::Vector_3 u1xX = cross_product(u[1], X); - const typename GT::Vector_3 u2xX = cross_product(u[2], X); - const typename GT::Vector_3 u3xX = cross_product(u[3], X); - - for (std::size_t iy = 0; iy < 3; iy++) - muXY[ix * 3 + iy] = (1.0 / 72.0) * ( - - u[0][iy] * (u0xX * (-x[0] - 11 * x[1] + 13 * x[3] - x[2]) - + u1xX * (-5 * x[0] - 7 * x[1] + 11 * x[3] + x[2]) - + u3xX * (x[0] - 7 * x[1] + 11 * x[3] - 5 * x[2]) - + u2xX * (-x[0] - 5 * x[1] + 7 * x[3] - x[2]) - ) - + u[1][iy] * (u0xX * (13 * x[0] - x[1] - 7 * x[3] - 5 * x[2]) - + u1xX * (17 * x[0] - 5 * x[1] - 5 * x[3] - 7 * x[2]) - + u3xX * (5 * x[0] + x[1] + x[3] - 7 * x[2]) - + u2xX * (7 * x[0] - x[1] + 5 * x[3] - 11 * x[2]) - ) - + u[2][iy] * (u0xX * (-11 * x[0] + 5 * x[1] - x[3] + 7 * x[2]) - + u1xX * (-7 * x[0] + x[1] + x[3] + 5 * x[2]) - + u3xX * (-7 * x[0] - 5 * x[1] - 5 * x[3] + 17 * x[2]) - + u2xX * (-5 * x[0] - 7 * x[1] - x[3] + 13 * x[2]) - ) - + u[3][iy] * (u0xX * (-x[0] + 7 * x[1] - 5 * x[3] - x[2]) - + u1xX * (-5 * x[0] + 11 * x[1] - 7 * x[3] + x[2]) - + u3xX * (x[0] + 11 * x[1] - 7 * x[3] - 5 * x[2]) - + u2xX * (-x[0] + 13 * x[1] - 11 * x[3] - x[2]) - ) - - ); + Point_3 pi = get(vpm, vi); + Vector_3 ui = get(vnm, vi); + x.push_back(Vector_3(pi.x(), pi.y(), pi.z())); + u.push_back(ui); } - } - // N-gon: split into n triangles by polygon center and use triangle formula for each - else - { - // getting center of points - typename GT::Vector_3 xc = - std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); - xc /= n; - - // getting unit average normal of points - typename GT::Vector_3 uc = - std::accumulate(u.begin(), u.end(), typename GT::Vector_3(0, 0, 0)); - uc /= sqrt(uc * uc); - - // summing each triangle's measure after triangulation by barycenter split. - for (std::size_t i = 0; i < n; i++) + + vertex_measures.area_measure += interpolated_corrected_area_measure_face(u, x); + + if (is_mean_curvature_selected) + vertex_measures.mean_curvature_measure += interpolated_corrected_mean_curvature_measure_face(u, x); + + if (is_gaussian_curvature_selected) + vertex_measures.gaussian_curvature_measure += interpolated_corrected_gaussian_curvature_measure_face(u, x); + + if (is_principal_curvatures_and_directions_selected) { - std::array muXY_curr_triangle = - interpolated_corrected_anisotropic_measure_face( - std::vector {u[i], u[(i + 1) % n], uc}, - std::vector {x[i], x[(i + 1) % n], xc} - ); - - for (std::size_t ix = 0; ix < 3; ix++) - for (std::size_t iy = 0; iy < 3; iy++) - muXY[ix * 3 + iy] += muXY_curr_triangle[ix * 3 + iy]; + const std::array face_anisotropic_measure = interpolated_corrected_anisotropic_measure_face(u, x); + for (std::size_t i = 0; i < 3 * 3; i++) + vertex_measures.anisotropic_measure[i] += face_anisotropic_measure[i]; } } - return muXY; + + x.clear(); + u.clear(); } + return vertex_measures; +} - //template - //typename GT::FT triangle_in_ball_ratio(const typename GT::Vector_3 x1, - // const typename GT::Vector_3 x2, - // const typename GT::Vector_3 x3, - // const typename GT::FT r, - // const typename GT::Vector_3 c, - // const std::size_t res = 3) - //{ - // const typename GT::FT R = r * r; - // const typename GT::FT acc = 1.0 / res; - // std::size_t samples_in = 0; - // for (GT::FT alpha = acc / 3; alpha < 1; alpha += acc) - // for (GT::FT beta = acc / 3; beta < 1 - alpha; beta += acc) - // { - // if ((alpha * x1 + beta * x2 + (1 - alpha - beta) * x3 - c).squared_length() < R) - // samples_in++; - // } - // return samples_in / (typename GT::FT)(res * (res + 1) / 2); - //} - - template - typename GT::FT face_in_ball_ratio(const std::vector& x, - const typename GT::FT r, - const typename GT::Vector_3 c) - { - const std::size_t n = x.size(); - // getting center of points - typename GT::Vector_3 xm = - std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); - xm /= n; +template +typename Vertex_measures interpolated_corrected_measures_one_vertex( + const PolygonMesh pmesh, + const typename boost::graph_traits::vertex_descriptor v, + const typename GT::FT radius, + const bool is_mean_curvature_selected, + const bool is_gaussian_curvature_selected, + const bool is_principal_curvatures_and_directions_selected, + const VPM vpm, + const VNM vnm +) +{ + typedef typename boost::graph_traits::face_descriptor Face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename GT::Point_3 Point_3; + typedef typename GT::Vector_3 Vector_3; + typedef typename GT::FT FT; + + std::queue bfs_queue; + std::unordered_set bfs_visited; - typename GT::FT d_min = (xm - c).squared_length(); - typename GT::FT d_max = d_min; + typename Vertex_measures vertex_measures; - for (const typename GT::Vector_3 xi : x) + typename Point_3 vp = get(vpm, v); + typename Vector_3 c = Vector_3(vp.x(), vp.y(), vp.z()); + + for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + if (f != boost::graph_traits::null_face()) { - const typename GT::FT d_sq = (xi - c).squared_length(); - d_max = (std::max)(d_sq, d_max); - d_min = (std::min)(d_sq, d_min); + bfs_queue.push(f); + bfs_visited.insert(f); } + } + std::vector x; + std::vector u; + while (!bfs_queue.empty()) { + Face_descriptor fi = bfs_queue.front(); + bfs_queue.pop(); - if (d_max <= r * r) return 1.0; - else if (r * r <= d_min) return 0.0; - - d_max = sqrt(d_max); - d_min = sqrt(d_min); + // looping over vertices in face to get point coordinates - return (r - d_min) / (d_max - d_min); - } + for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) + { + Point_3 pi = get(vpm, vi); + Vector_3 ui = get(vnm, vi); + x.push_back(Vector_3(pi.x(), pi.y(), pi.z())); + u.push_back(ui); + } - template - Principal_curvatures_and_directions principal_curvatures_and_directions_from_anisotropic_measures( - const std::array anisotropic_measure, - const typename GT::FT v_mu0, - const typename GT::Vector_3 u_GT - ) - { - Eigen::Matrix v_muXY = Eigen::Matrix::Zero(); + const FT f_ratio = face_in_ball_ratio(x, radius, c); - for (std::size_t ix = 0; ix < 3; ix++) - for (std::size_t iy = 0; iy < 3; iy++) - v_muXY(ix, iy) = anisotropic_measure[ix * 3 + iy]; + if (f_ratio != 0.0) + { + vertex_measures.area_measure += f_ratio * interpolated_corrected_area_measure_face(u, x); - Eigen::Matrix u(u_GT.x(), u_GT.y(), u_GT.z()); - const typename GT::FT K = 1000 * v_mu0; + if (is_mean_curvature_selected) + vertex_measures.mean_curvature_measure += f_ratio * interpolated_corrected_mean_curvature_measure_face(u, x); - v_muXY = 0.5 * (v_muXY + v_muXY.transpose()) + K * u * u.transpose(); + if (is_gaussian_curvature_selected) + vertex_measures.gaussian_curvature_measure += f_ratio * interpolated_corrected_gaussian_curvature_measure_face(u, x); - Eigen::SelfAdjointEigenSolver> eigensolver; + if (is_principal_curvatures_and_directions_selected) + { + const std::array face_anisotropic_measure = interpolated_corrected_anisotropic_measure_face(u, x); + for (std::size_t i = 0; i < 3 * 3; i++) + vertex_measures.anisotropic_measure[i] += f_ratio * face_anisotropic_measure[i]; + } - eigensolver.computeDirect(v_muXY); + for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) + { + if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) + { + bfs_queue.push(fj); + bfs_visited.insert(fj); + } + } + } - if (eigensolver.info() != Eigen::Success) - return Principal_curvatures_and_directions(); + x.clear(); + u.clear(); + } + return vertex_measures; +} - const Eigen::Matrix eig_vals = eigensolver.eigenvalues(); - const Eigen::Matrix eig_vecs = eigensolver.eigenvectors(); +template + void interpolated_corrected_curvatures_one_vertex( + const PolygonMesh pmesh, + const typename boost::graph_traits::vertex_descriptor v, + NamedParameters& np = parameters::default_values() + ) +{ + typedef typename GetGeomTraits::type GT; + typedef typename GetVertexPointMap::const_type Vertex_position_map; + + typedef dynamic_vertex_property_t Vector_map_tag; + typedef typename boost::property_map::const_type Default_vector_map; + typedef typename internal_np::Lookup_named_param_def::type Vertex_normal_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_const_property_map(CGAL::vertex_point, pmesh)); + + Vertex_normal_map vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), + get(Vector_map_tag(), pmesh)); + + if (is_default_parameter::value) + compute_vertex_normals(pmesh, vnm, np); + + typename GT::FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1); + if (radius == 0) + radius = average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; + else + radius = radius; + + typename GT::FT* vertex_mean_curvature = choose_parameter(get_parameter(np, internal_np::vertex_mean_curvature), nullptr); + typename GT::FT* vertex_gaussian_curvature = choose_parameter(get_parameter(np, internal_np::vertex_gaussian_curvature), nullptr); + typename Principal_curvatures_and_directions* vertex_principal_curvatures_and_directions = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions), nullptr); + + const bool is_mean_curvature_selected = (vertex_mean_curvature != nullptr); + const bool is_gaussian_curvature_selected = (vertex_gaussian_curvature != nullptr); + const bool is_principal_curvatures_and_directions_selected = (vertex_principal_curvatures_and_directions != nullptr); + + std::cout << is_mean_curvature_selected << is_gaussian_curvature_selected << is_principal_curvatures_and_directions_selected << std::endl; + + Vertex_measures vertex_measures; + + if (radius < 0) + vertex_measures = interpolated_corrected_measures_one_vertex_no_radius( + pmesh, + v, + is_mean_curvature_selected, + is_gaussian_curvature_selected, + is_principal_curvatures_and_directions_selected, + vpm, + vnm + ); + else + vertex_measures = interpolated_corrected_measures_one_vertex( + pmesh, + v, + radius, + is_mean_curvature_selected, + is_gaussian_curvature_selected, + is_principal_curvatures_and_directions_selected, + vpm, + vnm + ); + + + if (is_mean_curvature_selected) { + *vertex_mean_curvature = vertex_measures.area_measure != 0 ? + 0.5 * vertex_measures.mean_curvature_measure / vertex_measures.area_measure : 0; + } - const typename GT::Vector_3 min_eig_vec(eig_vecs(0, 1), eig_vecs(1, 1), eig_vecs(2, 1)); - const typename GT::Vector_3 max_eig_vec(eig_vecs(0, 0), eig_vecs(1, 0), eig_vecs(2, 0)); + if (is_gaussian_curvature_selected) { + *vertex_gaussian_curvature = vertex_measures.area_measure != 0 ? + vertex_measures.gaussian_curvature_measure / vertex_measures.area_measure : 0; + } - return Principal_curvatures_and_directions( - (v_mu0 != 0.0) ? -eig_vals[1] / v_mu0 : 0.0, - (v_mu0 != 0.0) ? -eig_vals[0] / v_mu0 : 0.0, - min_eig_vec, - max_eig_vec + if (is_principal_curvatures_and_directions_selected) { + const GT::Vector_3 v_normal = get(vnm, v); + const Principal_curvatures_and_directions principal_curvatures_and_directions = principal_curvatures_and_directions_from_anisotropic_measures( + vertex_measures.anisotropic_measure, + vertex_measures.area_measure, + v_normal ); + *vertex_principal_curvatures_and_directions = principal_curvatures_and_directions; } +} - template - class Interpolated_corrected_curvatures_computer - { - typedef typename GetGeomTraits::type GT; - - typedef typename GT::FT FT; - typedef typename GT::Point_3 Point_3; - typedef typename GT::Vector_3 Vector_3; - - typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; - typedef typename boost::graph_traits::edge_descriptor Edge_descriptor; - typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; - - typedef typename GetVertexPointMap::const_type Vertex_position_map; - - typedef dynamic_vertex_property_t Vector_map_tag; - typedef typename boost::property_map::const_type Default_vector_map; - typedef typename internal_np::Lookup_named_param_def::type Vertex_normal_map; - - typedef Constant_property_map Default_scalar_map; - typedef typename internal_np::Lookup_named_param_def::type Vertex_mean_curvature_map; - - typedef typename internal_np::Lookup_named_param_def::type Vertex_gaussian_curvature_map; - - typedef Constant_property_map> Default_principal_map; - typedef typename internal_np::Lookup_named_param_def::type Vertex_principal_curvatures_and_directions_map; - - typedef typename boost::property_map>::const_type Face_scalar_measure_map; - typedef typename boost::property_map>>::const_type Face_anisotropic_measure_map; - - private: - const PolygonMesh& pmesh; - Vertex_position_map vpm; - Vertex_normal_map vnm; - FT ball_radius; - - bool is_mean_curvature_selected; - bool is_gaussian_curvature_selected; - bool is_principal_curvatures_and_directions_selected; - - Vertex_mean_curvature_map mean_curvature_map; - Vertex_gaussian_curvature_map gaussian_curvature_map; - Vertex_principal_curvatures_and_directions_map principal_curvatures_and_directions_map; - - Face_scalar_measure_map mu0_map, mu1_map, mu2_map; - Face_anisotropic_measure_map muXY_map; - - void set_property_maps() { - mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); - mu1_map = get(CGAL::dynamic_face_property_t(), pmesh); - mu2_map = get(CGAL::dynamic_face_property_t(), pmesh); - muXY_map = get(CGAL::dynamic_face_property_t>(), pmesh); - } +template +class Interpolated_corrected_curvatures_computer +{ + typedef typename GetGeomTraits::type GT; + + typedef typename GT::FT FT; + typedef typename GT::Point_3 Point_3; + typedef typename GT::Vector_3 Vector_3; + + typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; + typedef typename boost::graph_traits::edge_descriptor Edge_descriptor; + typedef typename boost::graph_traits::face_descriptor Face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + + typedef typename GetVertexPointMap::const_type Vertex_position_map; + + typedef dynamic_vertex_property_t Vector_map_tag; + typedef typename boost::property_map::const_type Default_vector_map; + typedef typename internal_np::Lookup_named_param_def::type Vertex_normal_map; + + typedef Constant_property_map Default_scalar_map; + typedef typename internal_np::Lookup_named_param_def::type Vertex_mean_curvature_map; + + typedef typename internal_np::Lookup_named_param_def::type Vertex_gaussian_curvature_map; + + typedef Constant_property_map> Default_principal_map; + typedef typename internal_np::Lookup_named_param_def::type Vertex_principal_curvatures_and_directions_map; + + typedef typename boost::property_map>::const_type Face_scalar_measure_map; + typedef typename boost::property_map>>::const_type Face_anisotropic_measure_map; + +private: + const PolygonMesh& pmesh; + Vertex_position_map vpm; + Vertex_normal_map vnm; + FT ball_radius; + + bool is_mean_curvature_selected; + bool is_gaussian_curvature_selected; + bool is_principal_curvatures_and_directions_selected; + + Vertex_mean_curvature_map mean_curvature_map; + Vertex_gaussian_curvature_map gaussian_curvature_map; + Vertex_principal_curvatures_and_directions_map principal_curvatures_and_directions_map; + + Face_scalar_measure_map mu0_map, mu1_map, mu2_map; + Face_anisotropic_measure_map muXY_map; + + void set_property_maps() { + mu0_map = get(CGAL::dynamic_face_property_t(), pmesh); + mu1_map = get(CGAL::dynamic_face_property_t(), pmesh); + mu2_map = get(CGAL::dynamic_face_property_t(), pmesh); + muXY_map = get(CGAL::dynamic_face_property_t>(), pmesh); - void set_named_params(const NamedParameters& np) - { - using parameters::choose_parameter; - using parameters::get_parameter; - using parameters::is_default_parameter; + } - vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), - get_const_property_map(CGAL::vertex_point, pmesh)); + void set_named_params(const NamedParameters& np) + { + using parameters::choose_parameter; + using parameters::get_parameter; + using parameters::is_default_parameter; - vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), - get(Vector_map_tag(), pmesh)); + vpm = choose_parameter(get_parameter(np, CGAL::vertex_point), + get_const_property_map(CGAL::vertex_point, pmesh)); - if (is_default_parameter::value) - compute_vertex_normals(pmesh, vnm, np); + vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), + get(Vector_map_tag(), pmesh)); - const FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1); + if (is_default_parameter::value) + compute_vertex_normals(pmesh, vnm, np); - is_mean_curvature_selected = !is_default_parameter::value; - is_gaussian_curvature_selected = !is_default_parameter::value; - is_principal_curvatures_and_directions_selected = !is_default_parameter::value; + const FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1); - mean_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_mean_curvature_map), Default_scalar_map()); - gaussian_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_gaussian_curvature_map), Default_scalar_map()); - principal_curvatures_and_directions_map = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions_map), Default_principal_map()); + is_mean_curvature_selected = !is_default_parameter::value; + is_gaussian_curvature_selected = !is_default_parameter::value; + is_principal_curvatures_and_directions_selected = !is_default_parameter::value; - set_ball_radius(radius); - } + mean_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_mean_curvature_map), Default_scalar_map()); + gaussian_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_gaussian_curvature_map), Default_scalar_map()); + principal_curvatures_and_directions_map = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions_map), Default_principal_map()); - void set_ball_radius(const FT radius) { - if (radius == 0) - ball_radius = average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; - else - ball_radius = radius; - } + set_ball_radius(radius); + } - public: + void set_ball_radius(const FT radius) { + if (radius == 0) + ball_radius = average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; + else + ball_radius = radius; + } - Interpolated_corrected_curvatures_computer(const PolygonMesh& pmesh, - const NamedParameters& np = parameters::default_values() - ) : - pmesh(pmesh) - { - set_named_params(np); +public: - if (is_mean_curvature_selected || is_gaussian_curvature_selected || is_principal_curvatures_and_directions_selected) - { - set_property_maps(); + Interpolated_corrected_curvatures_computer(const PolygonMesh& pmesh, + const NamedParameters& np = parameters::default_values() + ) : + pmesh(pmesh) + { + set_named_params(np); - compute_selected_curvatures(); - } + if (is_mean_curvature_selected || is_gaussian_curvature_selected || is_principal_curvatures_and_directions_selected) + { + set_property_maps(); + + compute_selected_curvatures(); } + } - private: +private: - void interpolated_corrected_selected_measures_all_faces() - { - std::vector x; - std::vector u; + void interpolated_corrected_selected_measures_all_faces() + { + std::vector x; + std::vector u; - for (Face_descriptor f : faces(pmesh)) + for (Face_descriptor f : faces(pmesh)) + { + for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) { - for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) - { - Point_3 p = get(vpm, v); - x.push_back(Vector_3(p.x(), p.y(), p.z())); - u.push_back(get(vnm, v)); - } - put(mu0_map, f, interpolated_corrected_area_measure_face(u, x)); + Point_3 p = get(vpm, v); + x.push_back(Vector_3(p.x(), p.y(), p.z())); + u.push_back(get(vnm, v)); + } + put(mu0_map, f, interpolated_corrected_area_measure_face(u, x)); - if (is_mean_curvature_selected) - put(mu1_map, f, interpolated_corrected_mean_curvature_measure_face(u, x)); + if (is_mean_curvature_selected) + put(mu1_map, f, interpolated_corrected_mean_curvature_measure_face(u, x)); - if (is_gaussian_curvature_selected) - put(mu2_map, f, interpolated_corrected_gaussian_curvature_measure_face(u)); + if (is_gaussian_curvature_selected) + put(mu2_map, f, interpolated_corrected_gaussian_curvature_measure_face(u)); - if (is_principal_curvatures_and_directions_selected) - put(muXY_map, f, interpolated_corrected_anisotropic_measure_face(u, x)); + if (is_principal_curvatures_and_directions_selected) + put(muXY_map, f, interpolated_corrected_anisotropic_measure_face(u, x)); - x.clear(); - u.clear(); - } + x.clear(); + u.clear(); } + } - Vertex_measures expand_interpolated_corrected_measure_vertex_no_radius(Vertex_descriptor v) - { - Vertex_measures vertex_measures; + Vertex_measures expand_interpolated_corrected_measure_vertex_no_radius(Vertex_descriptor v) + { + Vertex_measures vertex_measures; - for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { - if (f == boost::graph_traits::null_face()) - continue; + for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + if (f == boost::graph_traits::null_face()) + continue; - vertex_measures.area_measure += get(mu0_map, f); + vertex_measures.area_measure += get(mu0_map, f); - if (is_mean_curvature_selected) - vertex_measures.mean_curvature_measure += get(mu1_map, f); + if (is_mean_curvature_selected) + vertex_measures.mean_curvature_measure += get(mu1_map, f); - if (is_gaussian_curvature_selected) - vertex_measures.gaussian_curvature_measure += get(mu2_map, f); + if (is_gaussian_curvature_selected) + vertex_measures.gaussian_curvature_measure += get(mu2_map, f); - if (is_principal_curvatures_and_directions_selected) - { - const std::array face_anisotropic_measure = get(muXY_map, f); - for (std::size_t i = 0; i < 3 * 3; i++) - vertex_measures.anisotropic_measure[i] += face_anisotropic_measure[i]; - } + if (is_principal_curvatures_and_directions_selected) + { + const std::array face_anisotropic_measure = get(muXY_map, f); + for (std::size_t i = 0; i < 3 * 3; i++) + vertex_measures.anisotropic_measure[i] += face_anisotropic_measure[i]; } - - return vertex_measures; } - Vertex_measures expand_interpolated_corrected_measure_vertex(Vertex_descriptor v) - { - std::queue bfs_queue; - std::unordered_set bfs_visited; + return vertex_measures; + } - Point_3 vp = get(vpm, v); - Vector_3 c = Vector_3(vp.x(), vp.y(), vp.z()); + Vertex_measures expand_interpolated_corrected_measure_vertex(Vertex_descriptor v) + { + std::queue bfs_queue; + std::unordered_set bfs_visited; - Vertex_measures vertex_measures; + Point_3 vp = get(vpm, v); + Vector_3 c = Vector_3(vp.x(), vp.y(), vp.z()); - for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { - if (f != boost::graph_traits::null_face()) - { - bfs_queue.push(f); - bfs_visited.insert(f); - } + Vertex_measures vertex_measures; + + for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + if (f != boost::graph_traits::null_face()) + { + bfs_queue.push(f); + bfs_visited.insert(f); } - while (!bfs_queue.empty()) { - Face_descriptor fi = bfs_queue.front(); - bfs_queue.pop(); + } + while (!bfs_queue.empty()) { + Face_descriptor fi = bfs_queue.front(); + bfs_queue.pop(); - // looping over vertices in face to get point coordinates - std::vector x; - for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) - { - Point_3 pi = get(vpm, vi); - x.push_back(Vector_3(pi.x(), pi.y(), pi.z())); - } + // looping over vertices in face to get point coordinates + std::vector x; + for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) + { + Point_3 pi = get(vpm, vi); + x.push_back(Vector_3(pi.x(), pi.y(), pi.z())); + } - const FT f_ratio = face_in_ball_ratio(x, ball_radius, c); + const FT f_ratio = face_in_ball_ratio(x, ball_radius, c); - if (f_ratio != 0.0) - { - vertex_measures.area_measure += f_ratio * get(mu0_map, fi); + if (f_ratio != 0.0) + { + vertex_measures.area_measure += f_ratio * get(mu0_map, fi); - if (is_mean_curvature_selected) - vertex_measures.mean_curvature_measure += f_ratio * get(mu1_map, fi); + if (is_mean_curvature_selected) + vertex_measures.mean_curvature_measure += f_ratio * get(mu1_map, fi); - if (is_gaussian_curvature_selected) - vertex_measures.gaussian_curvature_measure += f_ratio * get(mu2_map, fi); + if (is_gaussian_curvature_selected) + vertex_measures.gaussian_curvature_measure += f_ratio * get(mu2_map, fi); - if (is_principal_curvatures_and_directions_selected) - { - const std::array face_anisotropic_measure = get(muXY_map, fi); - for (std::size_t i = 0; i < 3 * 3; i++) - vertex_measures.anisotropic_measure[i] += f_ratio * face_anisotropic_measure[i]; - } + if (is_principal_curvatures_and_directions_selected) + { + const std::array face_anisotropic_measure = get(muXY_map, fi); + for (std::size_t i = 0; i < 3 * 3; i++) + vertex_measures.anisotropic_measure[i] += f_ratio * face_anisotropic_measure[i]; + } - for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) + for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) + { + if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) { - if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) - { - bfs_queue.push(fj); - bfs_visited.insert(fj); - } + bfs_queue.push(fj); + bfs_visited.insert(fj); } } } - return vertex_measures; } + return vertex_measures; + } - void compute_selected_curvatures() { - interpolated_corrected_selected_measures_all_faces(); + void compute_selected_curvatures() { + interpolated_corrected_selected_measures_all_faces(); - for (Vertex_descriptor v : vertices(pmesh)) - { - Vertex_measures vertex_measures = (ball_radius < 0) ? - expand_interpolated_corrected_measure_vertex_no_radius(v) : - expand_interpolated_corrected_measure_vertex(v); - - if (is_mean_curvature_selected) { - vertex_measures.area_measure != 0 ? - put(mean_curvature_map, v, 0.5 * vertex_measures.mean_curvature_measure / vertex_measures.area_measure) : - put(mean_curvature_map, v, 0); - } + for (Vertex_descriptor v : vertices(pmesh)) + { + Vertex_measures vertex_measures = (ball_radius < 0) ? + expand_interpolated_corrected_measure_vertex_no_radius(v) : + expand_interpolated_corrected_measure_vertex(v); + + if (is_mean_curvature_selected) { + vertex_measures.area_measure != 0 ? + put(mean_curvature_map, v, 0.5 * vertex_measures.mean_curvature_measure / vertex_measures.area_measure) : + put(mean_curvature_map, v, 0); + } - if (is_gaussian_curvature_selected) { - vertex_measures.area_measure != 0 ? - put(gaussian_curvature_map, v, vertex_measures.gaussian_curvature_measure / vertex_measures.area_measure) : - put(gaussian_curvature_map, v, 0); - } + if (is_gaussian_curvature_selected) { + vertex_measures.area_measure != 0 ? + put(gaussian_curvature_map, v, vertex_measures.gaussian_curvature_measure / vertex_measures.area_measure) : + put(gaussian_curvature_map, v, 0); + } - if (is_principal_curvatures_and_directions_selected) { - const Vector_3 v_normal = get(vnm, v); - const Principal_curvatures_and_directions principal_curvatures_and_directions = principal_curvatures_and_directions_from_anisotropic_measures( - vertex_measures.anisotropic_measure, - vertex_measures.area_measure, - v_normal - ); - put(principal_curvatures_and_directions_map, v, principal_curvatures_and_directions); - } + if (is_principal_curvatures_and_directions_selected) { + const Vector_3 v_normal = get(vnm, v); + const Principal_curvatures_and_directions principal_curvatures_and_directions = principal_curvatures_and_directions_from_anisotropic_measures( + vertex_measures.anisotropic_measure, + vertex_measures.area_measure, + v_normal + ); + put(principal_curvatures_and_directions_map, v, principal_curvatures_and_directions); } } - }; + } +}; } // namespace internal @@ -1030,6 +1266,50 @@ template(pmesh, np); } +template + typename GT::FT interpolated_corrected_mean_curvature_at_vertex(const PolygonMesh& pmesh, + typename boost::graph_traits::vertex_descriptor v, + const NamedParameters& np = parameters::default_values()) +{ + // use interpolated_corrected_curvatures_at_vertex to compute mean curvature + typename GT::FT* mean_curvature = new typename GT::FT(); + interpolated_corrected_curvatures_at_vertex(pmesh, v, np.vertex_mean_curvature(mean_curvature)); + return *mean_curvature; +} + +template + typename GT::FT interpolated_corrected_gaussian_curvature_at_vertex(const PolygonMesh& pmesh, + typename boost::graph_traits::vertex_descriptor v, + const NamedParameters& np = parameters::default_values()) +{ + // use interpolated_corrected_curvatures_at_vertex to compute gaussian curvature + typename GT::FT* gc = new typename GT::FT(); + interpolated_corrected_curvatures_at_vertex(pmesh, v, np.vertex_gaussian_curvature(gc)); + return *gc; +} + +template + Principal_curvatures_and_directions interpolated_corrected_principal_curvatures_and_directions_at_vertex(const PolygonMesh& pmesh, + typename boost::graph_traits::vertex_descriptor v, + const NamedParameters& np = parameters::default_values()) +{ + // use interpolated_corrected_curvatures_at_vertex to compute principal curvatures + Principal_curvatures_and_directions* pcd = new Principal_curvatures_and_directions(); + interpolated_corrected_curvatures_at_vertex(pmesh, v, np.vertex_principal_curvatures_and_directions(pcd)); + return *pcd; +} + +template + void interpolated_corrected_curvatures_at_vertex(const PolygonMesh& pmesh, + typename boost::graph_traits::vertex_descriptor v, + const NamedParameters& np = parameters::default_values()) +{ + internal::interpolated_corrected_curvatures_one_vertex(pmesh, v, np); +} } // namespace Polygon_mesh_processing } // namespace CGAL 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 21b8227e86c9..0d5469775614 100644 --- a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h +++ b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h @@ -90,6 +90,9 @@ CGAL_add_named_parameter(nb_points_per_distance_unit_t, nb_points_per_distance_u 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(outward_orientation_t, outward_orientation, outward_orientation) CGAL_add_named_parameter(overlap_test_t, overlap_test, do_overlap_test_of_bounded_sides) From 73bde6daa0c57675f5eccd157fab07f6b0370d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 20 Jan 2023 16:02:25 +0100 Subject: [PATCH 094/161] Eigen is needed --- .../test/Polygon_mesh_processing/CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index c1aedddedf4e..c446a7395dee 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -1,4 +1,4 @@ -# Created by the script cgal_create_CMakeLists +0# Created by the script cgal_create_CMakeLists # This is the CMake script for compiling a set of CGAL applications. cmake_minimum_required(VERSION 3.1...3.23) @@ -44,6 +44,8 @@ if(TARGET CGAL::Eigen3_support) target_link_libraries(test_shape_smoothing PUBLIC CGAL::Eigen3_support) create_single_source_cgal_program("delaunay_remeshing_test.cpp") target_link_libraries(delaunay_remeshing_test PUBLIC CGAL::Eigen3_support) + create_single_source_cgal_program("test_interpolated_corrected_curvatures.cpp") + target_link_libraries(test_interpolated_corrected_curvatures PUBLIC CGAL::Eigen3_support) endif() find_package(OpenMesh QUIET) @@ -69,7 +71,6 @@ create_single_source_cgal_program("self_intersection_polyhedron_test.cpp") create_single_source_cgal_program("self_intersection_surface_mesh_test.cpp") create_single_source_cgal_program("pmp_do_intersect_test.cpp") create_single_source_cgal_program("test_is_polygon_soup_a_polygon_mesh.cpp") -create_single_source_cgal_program("test_interpolated_corrected_curvatures.cpp") create_single_source_cgal_program("test_stitching.cpp") create_single_source_cgal_program("remeshing_test.cpp") create_single_source_cgal_program("remeshing_with_isolated_constraints_test.cpp" ) From 8af5c620fafb7c6771357f91f05fb9000f748a90 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 23 Jan 2023 13:49:34 +0200 Subject: [PATCH 095/161] reference documentation + minor fix + added documentation for the at_vertext curvature functions - removed dynamically allocated pointers for storing the curvature on vertex --- .../PackageDescription.txt | 4 + ...nterpolated_corrected_curvature_measures.h | 761 ++++++++++++------ 2 files changed, 504 insertions(+), 261 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index f62b2816cf7f..f6696dc1ba8c 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -205,6 +205,10 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage. - `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_at_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_at_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_at_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures_at_vertex()` - `CGAL::Polygon_mesh_processing::Principal_curvatures_and_directions` \cgalCRPSection{Normal Computation Functions} diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 94aaff9b8b52..cdd25aefc0e5 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -36,11 +36,11 @@ namespace CGAL { namespace Polygon_mesh_processing { /** -* \ingroup PMP_corrected_curvatures_grp -* -* \brief a struct for storing principal curvatures and directions. -* -* @tparam GT is the geometric traits class. + * \ingroup PMP_corrected_curvatures_grp + * + * \brief a struct for storing principal curvatures and directions. + * + * @tparam GT is the geometric traits class. */ template struct Principal_curvatures_and_directions { @@ -665,11 +665,11 @@ template vertex_measures; if (radius < 0) + { + std::cout << -1; vertex_measures = interpolated_corrected_measures_one_vertex_no_radius( pmesh, v, @@ -679,18 +679,21 @@ template( - pmesh, - v, - radius, - is_mean_curvature_selected, - is_gaussian_curvature_selected, - is_principal_curvatures_and_directions_selected, - vpm, - vnm - ); - + pmesh, + v, + radius, + is_mean_curvature_selected, + is_gaussian_curvature_selected, + is_principal_curvatures_and_directions_selected, + vpm, + vnm + ); + } if (is_mean_curvature_selected) { *vertex_mean_curvature = vertex_measures.area_measure != 0 ? @@ -988,58 +991,58 @@ class Interpolated_corrected_curvatures_computer } // namespace internal /** -* \ingroup PMP_corrected_curvatures_grp -* -* Computes the interpolated corrected mean curvature across the mesh -* and stores it in a vertex property map `vcm`. -* -* @tparam PolygonMesh a model of `FaceListGraph`. -* @tparam VertexCurvatureMap model of `WritablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` as key type and `GT::FT` as value type. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". -* -* @param pmesh the polygon mesh. -* @param vcm the vertex property map in which the computed mean curvatures are stored. -* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters". -* -* \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 `%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{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} -* \cgalParamType{a class model of `ReadablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` -* as key type and `%Vector_3` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} -* \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using compute_vertex_normals()} -* \cgalParamNEnd -* -* \cgalParamNBegin{ball_radius} -* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures -* by summing measures of faces inside a ball of this radius centered at the -* vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball} -* \cgalParamType{`GT::FT`} -* \cgalParamDefault{`-1`} -* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of -* measures on faces around the vertex} -* \cgalParamNEnd -* -* \cgalNamedParamsEnd -* -* @see `interpolated_corrected_gaussian_curvature()` -* @see `interpolated_corrected_principal_curvatures_and_directions()` -* @see `interpolated_corrected_curvatures()` + * \ingroup PMP_corrected_curvatures_grp + * + * Computes the interpolated corrected mean curvature across the mesh + * and stores it in a vertex property map `vcm`. + * + * @tparam PolygonMesh a model of `FaceListGraph`. + * @tparam VertexCurvatureMap model of `WritablePropertyMap` with + * `boost::graph_traits::%Vertex_descriptor` as key type and `GT::FT` as value type. + * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". + * + * @param pmesh the polygon mesh. + * @param vcm the vertex property map in which the computed mean curvatures are stored. + * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters". + * + * \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 `%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{vertex_normal_map} + * \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} + * \cgalParamType{a class model of `ReadablePropertyMap` with + * `boost::graph_traits::%Vertex_descriptor` + * as key type and `%Vector_3` as value type} + * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} + * \cgalParamExtra{If this parameter is omitted, vertex normals will be + * computed using compute_vertex_normals()} + * \cgalParamNEnd + * + * \cgalParamNBegin{ball_radius} + * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures + * by summing measures of faces inside a ball of this radius centered at the + * vertex expanded from. The summed face measures are weighted by their + * inclusion ratio inside this ball} + * \cgalParamType{`GT::FT`} + * \cgalParamDefault{`-1`} + * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of + * measures on faces around the vertex} + * \cgalParamNEnd + * + * \cgalNamedParamsEnd + * + * @see `interpolated_corrected_gaussian_curvature()` + * @see `interpolated_corrected_principal_curvatures_and_directions()` + * @see `interpolated_corrected_curvatures()` */ template::%Vertex_descriptor` as key type and `GT::FT` as value type. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". -* -* @param pmesh the polygon mesh. -* @param vcm the vertex property map in which the computed gaussian curvatures are stored. -* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters". -* -* \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 `%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{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} -* \cgalParamType{a class model of `ReadablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` -* as key type and `%Vector_3` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} -* \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using compute_vertex_normals()} -* \cgalParamNEnd -* -* \cgalParamNBegin{ball_radius} -* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures -* by summing measures of faces inside a ball of this radius centered at the -* vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball} -* \cgalParamType{`GT::FT`} -* \cgalParamDefault{`-1`} -* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of -* measures on faces around the vertex} -* \cgalParamNEnd -* -* \cgalNamedParamsEnd -* -* @see `interpolated_corrected_mean_curvature()` -* @see `interpolated_corrected_principal_curvatures_and_directions()` -* @see `interpolated_corrected_curvatures()` + * \ingroup PMP_corrected_curvatures_grp + * + * Computes the interpolated corrected gaussian curvature across the mesh + * and stores it in a vertex property map `vcm`. + * + * @tparam PolygonMesh a model of `FaceListGraph`. + * @tparam VertexCurvatureMap model of `WritablePropertyMap` with + * `boost::graph_traits::%Vertex_descriptor` as key type and `GT::FT` as value type. + * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". + * + * @param pmesh the polygon mesh. + * @param vcm the vertex property map in which the computed gaussian curvatures are stored. + * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters". + * + * \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 `%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{vertex_normal_map} + * \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} + * \cgalParamType{a class model of `ReadablePropertyMap` with + * `boost::graph_traits::%Vertex_descriptor` + * as key type and `%Vector_3` as value type} + * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} + * \cgalParamExtra{If this parameter is omitted, vertex normals will be + * computed using compute_vertex_normals()} + * \cgalParamNEnd + * + * \cgalParamNBegin{ball_radius} + * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures + * by summing measures of faces inside a ball of this radius centered at the + * vertex expanded from. The summed face measures are weighted by their + * inclusion ratio inside this ball} + * \cgalParamType{`GT::FT`} + * \cgalParamDefault{`-1`} + * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of + * measures on faces around the vertex} + * \cgalParamNEnd + * + * \cgalNamedParamsEnd + * + * @see `interpolated_corrected_mean_curvature()` + * @see `interpolated_corrected_principal_curvatures_and_directions()` + * @see `interpolated_corrected_curvatures()` */ template @@ -1115,59 +1118,59 @@ template::%Vertex_descriptor` as key type and -* `std::tuple, Eigen::Vector>` as value type. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". -* -* @param pmesh the polygon mesh. -* @param vcm the vertex property map in which the computed principal curvatures are stored. -* @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 `ReadablePropertyMap` 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` must be available in `PolygonMesh`.} -* \cgalParamNEnd -* -* \cgalParamNBegin{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} -* \cgalParamType{a class model of `ReadablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` -* as key type and `%Vector_3` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} -* \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using compute_vertex_normals()} -* \cgalParamNEnd -* -* \cgalParamNBegin{ball_radius} -* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures -* by summing measures of faces inside a ball of this radius centered at the -* vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball} -* \cgalParamType{`GT::FT`} -* \cgalParamDefault{`-1`} -* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of -* measures on faces around the vertex} -* \cgalParamNEnd -* -* \cgalNamedParamsEnd -* -* @see `interpolated_corrected_mean_curvature()` -* @see `interpolated_corrected_gaussian_curvature()` -* @see `interpolated_corrected_curvatures()` + * \ingroup PMP_corrected_curvatures_grp + * + * Computes the interpolated corrected principal curvatures across the mesh + * and stores it in a vertex property map `vcm`. + * + * @tparam PolygonMesh a model of `FaceListGraph`. + * @tparam VertexCurvatureMap model of `WritablePropertyMap` with + * `boost::graph_traits::%Vertex_descriptor` as key type and + * `%Principal_curvatures_and_directions` as value type. + * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". + * + * @param pmesh the polygon mesh. + * @param vcm the vertex property map in which the computed principal curvatures are stored. + * @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 `ReadablePropertyMap` 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` must be available in `PolygonMesh`.} + * \cgalParamNEnd + * + * \cgalParamNBegin{vertex_normal_map} + * \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} + * \cgalParamType{a class model of `ReadablePropertyMap` with + * `boost::graph_traits::%Vertex_descriptor` + * as key type and `%Vector_3` as value type} + * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} + * \cgalParamExtra{If this parameter is omitted, vertex normals will be + * computed using compute_vertex_normals()} + * \cgalParamNEnd + * + * \cgalParamNBegin{ball_radius} + * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures + * by summing measures of faces inside a ball of this radius centered at the + * vertex expanded from. The summed face measures are weighted by their + * inclusion ratio inside this ball} + * \cgalParamType{`GT::FT`} + * \cgalParamDefault{`-1`} + * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of + * measures on faces around the vertex} + * \cgalParamNEnd + * + * \cgalNamedParamsEnd + * + * @see `interpolated_corrected_mean_curvature()` + * @see `interpolated_corrected_gaussian_curvature()` + * @see `interpolated_corrected_curvatures()` */ template @@ -1179,84 +1182,84 @@ template::%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` must be available in `PolygonMesh`.} -* \cgalParamNEnd -* -* \cgalParamNBegin{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} -* \cgalParamType{a class model of `ReadablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` -* as key type and `%Vector_3` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} -* \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using compute_vertex_normals()} -* \cgalParamNEnd -* -* \cgalParamNBegin{vertex_mean_curvature_map} -* \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} -* \cgalParamType{a class model of `WritablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` -* as key type and `%FT` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} -* \cgalParamExtra{If this parameter is omitted, mean curvatures won't be computed} -* \cgalParamNEnd -* -* \cgalParamNBegin{vertex_gaussian_curvature_map} -* \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} -* \cgalParamType{a class model of `WritablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` -* as key type and `%FT` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} -* \cgalParamExtra{If this parameter is omitted, gaussian curvatures won't be computed} -* \cgalParamNEnd -* -* -* \cgalParamNBegin{vertex_principal_curvatures_and_directions_map} -* \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} -* \cgalParamType{a class model of `WritablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` -* as key type and `%Principal_curvatures_and_directions` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t>(), pmesh)`} -* \cgalParamExtra{If this parameter is omitted, mean principal won't be computed} -* \cgalParamNEnd -* -* \cgalParamNBegin{ball_radius} -* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures -* by summing measures of faces inside a ball of this radius centered at the -* vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball.} -* \cgalParamType{`GT::FT`} -* \cgalParamDefault{`-1`} -* \cgalParamExtra{If this parameter is omitted (`-1`), the epansion is then just a sum of -* measures on faces around the vertex} -* \cgalParamNEnd -* -* \cgalNamedParamsEnd -* -* @see `interpolated_corrected_mean_curvature()` -* @see `interpolated_corrected_gaussian_curvature()` -* @see `interpolated_corrected_principal_curvatures_and_directions()` + * \ingroup PMP_corrected_curvatures_grp + * + * Computes the interpolated corrected curvatures across the mesh, based on the provided property maps. + * By providing mean, gaussian and/or principal curvature property maps as named parameters, the user + * can choose which curvatures to compute. + * + * @tparam PolygonMesh a model of `FaceListGraph`. + * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". + * + * @param pmesh the 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 `ReadablePropertyMap` 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` must be available in `PolygonMesh`.} + * \cgalParamNEnd + * + * \cgalParamNBegin{vertex_normal_map} + * \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} + * \cgalParamType{a class model of `ReadablePropertyMap` with + * `boost::graph_traits::%Vertex_descriptor` + * as key type and `%Vector_3` as value type} + * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} + * \cgalParamExtra{If this parameter is omitted, vertex normals will be + * computed using compute_vertex_normals()} + * \cgalParamNEnd + * + * \cgalParamNBegin{vertex_mean_curvature_map} + * \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} + * \cgalParamType{a class model of `WritablePropertyMap` with + * `boost::graph_traits::%Vertex_descriptor` + * as key type and `%FT` as value type} + * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} + * \cgalParamExtra{If this parameter is omitted, mean curvatures won't be computed} + * \cgalParamNEnd + * + * \cgalParamNBegin{vertex_gaussian_curvature_map} + * \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} + * \cgalParamType{a class model of `WritablePropertyMap` with + * `boost::graph_traits::%Vertex_descriptor` + * as key type and `%FT` as value type} + * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} + * \cgalParamExtra{If this parameter is omitted, gaussian curvatures won't be computed} + * \cgalParamNEnd + * + * + * \cgalParamNBegin{vertex_principal_curvatures_and_directions_map} + * \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} + * \cgalParamType{a class model of `WritablePropertyMap` with + * `boost::graph_traits::%Vertex_descriptor` + * as key type and `%Principal_curvatures_and_directions` as value type} + * \cgalParamDefault{`get(dynamic_vertex_property_t>(), pmesh)`} + * \cgalParamExtra{If this parameter is omitted, mean principal won't be computed} + * \cgalParamNEnd + * + * \cgalParamNBegin{ball_radius} + * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures + * by summing measures of faces inside a ball of this radius centered at the + * vertex expanded from. The summed face measures are weighted by their + * inclusion ratio inside this ball.} + * \cgalParamType{`GT::FT`} + * \cgalParamDefault{`-1`} + * \cgalParamExtra{If this parameter is omitted (`-1`), the epansion is then just a sum of + * measures on faces around the vertex} + * \cgalParamNEnd + * + * \cgalNamedParamsEnd + * + * @see `interpolated_corrected_mean_curvature()` + * @see `interpolated_corrected_gaussian_curvature()` + * @see `interpolated_corrected_principal_curvatures_and_directions()` */ template @@ -1266,6 +1269,60 @@ template(pmesh, np); } +/** + * \ingroup PMP_corrected_curvatures_grp + * computes the interpolated corrected mean curvature at a vertex of a triangle mesh. + * + * @tparam GT a geometric traits class that provides the nested type `FT`, + * @tparam PolygonMesh a model of `FaceListGraph` + * @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" + * + * @param pmesh the polygon mesh + * @param v the vertex of `pmesh` to compute the mean curvature at + * @param np optional sequence of \ref pmp_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 `ReadablePropertyMap` 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` must be available in `PolygonMesh`.} + * \cgalParamNEnd + * + * \cgalParamNBegin{vertex_normal_map} + * \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} + * \cgalParamType{a class model of `ReadablePropertyMap` with + * `boost::graph_traits::%Vertex_descriptor` + * as key type and `%Vector_3` as value type} + * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} + * \cgalParamExtra{If this parameter is omitted, vertex normals will be + * computed using compute_vertex_normals()} + * \cgalParamNEnd + * + * \cgalParamNBegin{ball_radius} + * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures + * by summing measures of faces inside a ball of this radius centered at the + * vertex expanded from. The summed face measures are weighted by their + * inclusion ratio inside this ball} + * \cgalParamType{`GT::FT`} + * \cgalParamDefault{`-1`} + * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of + * measures on faces around the vertex} + * \cgalParamNEnd + * + * \cgalNamedParamsEnd + * + * @return the interpolated corrected mean curvature at the vertex `v` + * + * @see `interpolated_corrected_mean_curvature()` + * @see `interpolated_corrected_gaussian_curvature_at_vertex()` + * @see `interpolated_corrected_principal_curvatures_and_directions_at_vertex()` + * @see `interpolated_corrected_curvatures_at_vertex()` +*/ + template typename GT::FT interpolated_corrected_mean_curvature_at_vertex(const PolygonMesh& pmesh, @@ -1273,11 +1330,65 @@ template::%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` must be available in `PolygonMesh`.} + * \cgalParamNEnd + * + * \cgalParamNBegin{vertex_normal_map} + * \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} + * \cgalParamType{a class model of `ReadablePropertyMap` with + * `boost::graph_traits::%Vertex_descriptor` + * as key type and `%Vector_3` as value type} + * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} + * \cgalParamExtra{If this parameter is omitted, vertex normals will be + * computed using compute_vertex_normals()} + * \cgalParamNEnd + * + * \cgalParamNBegin{ball_radius} + * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures + * by summing measures of faces inside a ball of this radius centered at the + * vertex expanded from. The summed face measures are weighted by their + * inclusion ratio inside this ball} + * \cgalParamType{`GT::FT`} + * \cgalParamDefault{`-1`} + * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of + * measures on faces around the vertex} + * \cgalParamNEnd + * + * \cgalNamedParamsEnd + * + * @return the interpolated corrected Gaussian curvature at the vertex `v` + * + * @see `interpolated_corrected_gaussian_curvature()` + * @see `interpolated_corrected_mean_curvature_at_vertex()` + * @see `interpolated_corrected_principal_curvatures_and_directions_at_vertex()` + * @see `interpolated_corrected_curvatures_at_vertex()` +*/ + template typename GT::FT interpolated_corrected_gaussian_curvature_at_vertex(const PolygonMesh& pmesh, @@ -1285,11 +1396,64 @@ template::%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` must be available in `PolygonMesh`.} + * \cgalParamNEnd + * + * \cgalParamNBegin{vertex_normal_map} + * \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} + * \cgalParamType{a class model of `ReadablePropertyMap` with + * `boost::graph_traits::%Vertex_descriptor` + * as key type and `%Vector_3` as value type} + * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} + * \cgalParamExtra{If this parameter is omitted, vertex normals will be + * computed using compute_vertex_normals()} + * \cgalParamNEnd + * + * \cgalParamNBegin{ball_radius} + * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures + * by summing measures of faces inside a ball of this radius centered at the + * vertex expanded from. The summed face measures are weighted by their + * inclusion ratio inside this ball} + * \cgalParamType{`GT::FT`} + * \cgalParamDefault{`-1`} + * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of + * measures on faces around the vertex} + * \cgalParamNEnd + * \cgalNamedParamsEnd + * + * @return the interpolated corrected principal curvatures and directions at the vertex `v` + * + * @see `interpolated_corrected_principal_curvatures_and_directions()` + * @see `interpolated_corrected_mean_curvature_at_vertex()` + * @see `interpolated_corrected_gaussian_curvature_at_vertex()` + * @see `interpolated_corrected_curvatures_at_vertex()` +*/ + template Principal_curvatures_and_directions interpolated_corrected_principal_curvatures_and_directions_at_vertex(const PolygonMesh& pmesh, @@ -1297,11 +1461,86 @@ template* pcd = new Principal_curvatures_and_directions(); - interpolated_corrected_curvatures_at_vertex(pmesh, v, np.vertex_principal_curvatures_and_directions(pcd)); - return *pcd; + Principal_curvatures_and_directions pcd; + interpolated_corrected_curvatures_at_vertex(pmesh, v, np.vertex_principal_curvatures_and_directions(&pcd)); + return pcd; } +/** + * \ingroup PMP_corrected_curvatures_grp + * Computes the interpolated corrected curvatures at a certain vertex, based on the provided pointers. + * By providing mean, gaussian and/or principal curvature pointers as named parameters, the user + * can choose which curvatures to compute. + * The pointers are used to store the computed curvatures. + * The user is responsible for the memory management of the pointers. + * + * @tparam PolygonMesh a model of `FaceListGraph` + * @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" + * + * @param pmesh the polygon mesh + * @param v the vertex of `pmesh` to compute the curvatures at + * @param np optional sequence of \ref pmp_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 `ReadablePropertyMap` 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` must be available in `PolygonMesh`.} + * \cgalParamNEnd + * + * \cgalParamNBegin{vertex_normal_map} + * \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} + * \cgalParamType{a class model of `ReadablePropertyMap` with + * `boost::graph_traits::%Vertex_descriptor` + * as key type and `%Vector_3` as value type} + * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} + * \cgalParamExtra{If this parameter is omitted, vertex normals will be + * computed using compute_vertex_normals()} + * \cgalParamNEnd + * + * \cgalParamNBegin{vertex_mean_curvature} + * \cgalParamDescription{a pointer to a scalar value to store the mean curvature at the vertex `v`} + * \cgalParamType{`GT::FT*`} + * \cgalParamDefault{`nullptr`} + * \cgalParamExtra{If this parameter is omitted, mean curvature won't be computed} + * \cgalParamNEnd + * + * \cgalParamNBegin{vertex_gaussian_curvature} + * \cgalParamDescription{a pointer to a scalar value to store the gaussian curvature at the vertex `v`} + * \cgalParamType{`GT::FT*`} + * \cgalParamDefault{`nullptr`} + * \cgalParamExtra{If this parameter is omitted, gaussian curvature won't be computed} + * \cgalParamNEnd + * + * \cgalParamNBegin{vertex_principal_curvatures_and_directions} + * \cgalParamDescription{a pointer to a Principal_curvatures_and_directions object to store the principal curvatures and directions at the vertex `v`} + * \cgalParamType{`Principal_curvatures_and_directions*`} + * \cgalParamDefault{`nullptr`} + * \cgalParamExtra{If this parameter is omitted, principal curvatures and directions won't be computed} + * \cgalParamNEnd + * + * \cgalParamNBegin{ball_radius} + * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures + * by summing measures of faces inside a ball of this radius centered at the + * vertex expanded from. The summed face measures are weighted by their + * inclusion ratio inside this ball.} + * \cgalParamType{`GT::FT`} + * \cgalParamDefault{`-1`} + * \cgalParamExtra{If this parameter is omitted (`-1`), the epansion is then just a sum of + * measures on faces around the vertex} + * \cgalParamNEnd + * + * \cgalNamedParamsEnd + * + * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` + * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_at_vertex()` + * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_at_vertex()` + * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_at_vertex()` +*/ template void interpolated_corrected_curvatures_at_vertex(const PolygonMesh& pmesh, From 483e8b8e509ee4420ba0861ab70dd5a50133f7b0 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 24 Jan 2023 08:44:09 +0200 Subject: [PATCH 096/161] User manual + add example file for 1 vertex curvature --- .../Polygon_mesh_processing.txt | 16 ++++- .../Polygon_mesh_processing/CMakeLists.txt | 2 + ...corrected_curvatures_at_vertex_example.cpp | 60 +++++++++++++++++++ ...erpolated_corrected_curvatures_example.cpp | 2 + ...nterpolated_corrected_curvature_measures.h | 2 - 5 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp 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 1d7184561a80..49f912ee5ca7 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 @@ -889,7 +889,7 @@ correct vertex normals are provided. The implementation is generic in terms of mesh data structure. It can be used on Surface_mesh, Polyhedron_3 and other polygonal mesh structures based on the Face Graph Model. -These computations are performed using : +These computations are performed using (on all vertices of mesh): - `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions()` @@ -898,6 +898,13 @@ These computations are performed using : Where it is recommended to use the last function for computing multiple curvatures (example: mean and gaussian) as the implementation performs the shared computations only once, making it more efficient. +Similarly, we can use the following example functions to compute the curvatures on a specific vertex: +- `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_at_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_at_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_at_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures_at_vertex()` + + \cgalFigureRef{icc_diff_radius} shows how the mean curvature changes depending on the ball_radius named parameter which can be set to a value > 0 to get a smoother distribution of values and "diffuse" the extreme values of curvatures across the mesh. @@ -934,6 +941,13 @@ not provide storage for the curvatures. \cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp} +\subsection ICCExamplePH Interpolated Corrected Curvature on a Vertex Example + +The following example illustrates how to +compute the curvatures on a specific vertex + +\cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp} + **************************************** \section PMPSlicer Slicer diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index ab084384835b..5e840c38a508 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -109,6 +109,8 @@ if(TARGET CGAL::Eigen3_support) target_link_libraries(interpolated_corrected_curvatures_example PUBLIC CGAL::Eigen3_support) create_single_source_cgal_program("interpolated_corrected_curvatures_polyhedron_example.cpp") target_link_libraries(interpolated_corrected_curvatures_polyhedron_example PUBLIC CGAL::Eigen3_support) + create_single_source_cgal_program("interpolated_corrected_curvatures_at_vertex_example.cpp") + target_link_libraries(interpolated_corrected_curvatures_at_vertex_example PUBLIC CGAL::Eigen3_support) endif() diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp new file mode 100644 index 000000000000..6ed061db93b8 --- /dev/null +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +#include + +#include + +namespace PMP = CGAL::Polygon_mesh_processing; + +typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic_kernel; +typedef Epic_kernel::FT FT; +typedef CGAL::Surface_mesh Surface_Mesh; +typedef boost::graph_traits::face_descriptor face_descriptor; +typedef boost::graph_traits::vertex_descriptor vertex_descriptor; + +int main(int argc, char* argv[]) +{ + // instantiating and reading mesh + Surface_Mesh smesh; + const std::string filename = (argc > 1) ? + argv[1] : + CGAL::data_file_path("meshes/small_bunny.obj"); + + if (!CGAL::IO::read_polygon_mesh(filename, smesh)) + { + std::cerr << "Invalid input file." << std::endl; + return EXIT_FAILURE; + } + + // loop over vertices and use vertex_descriptor to compute a curvature on one vertex + for (vertex_descriptor v : vertices(smesh)) + { + FT h = PMP::interpolated_corrected_mean_curvature_at_vertex(smesh, v); + FT g = PMP::interpolated_corrected_gaussian_curvature_at_vertex(smesh, v); + PMP::Principal_curvatures_and_directions p = + PMP::interpolated_corrected_principal_curvatures_and_directions_at_vertex(smesh, v); + + // we can also specify a ball radius for expansion and a user defined vertex normals map using + // named parameters. Refer to interpolated_corrected_curvatures_example.cpp to see example usage. + + // Can also use interpolated_corrected_curvatures_at_vertex() to compute multiple curvatures + // on the vertex at the same time. This is more efficient than computing each one separately. + // The following commented lines show this (all mentioned named parameters work on it as well) + // we specify which curvatures we want to compute by passing pointers as named parameters + // as shown. These pointers are used for storing the result as well. in this example we + // selected mean and gaussian curvatures + // PMP::interpolated_corrected_curvatures_at_vertex( + // smesh, + // v, + // CGAL::parameters::vertex_mean_curvature(&h) + // .vertex_gaussian_curvature(&g) + // ); + + std::cout << v.idx() << ": HC = " << h + << ", GC = " << g << "\n" + << ", PC = [ " << p.min_curvature << " , " << p.max_curvature << " ]\n"; + } +} diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp index 33d34cd34f13..91266f594135 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp @@ -47,6 +47,8 @@ int main(int argc, char* argv[]) assert(created); // user can call these fucntions to compute a specfic curvature type on each vertex. + // (Note: if no ball radius is specified, the measure expansion of each vertex happens by + // summing measures on faces adjacent to each vertex.) PMP::interpolated_corrected_mean_curvature( smesh, mean_curvature_map diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index cdd25aefc0e5..847bac1f5c06 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -669,7 +669,6 @@ template( pmesh, v, @@ -682,7 +681,6 @@ template( pmesh, v, From a7cd6a275ecabaad05c802b4593e1f7235e25e65 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 24 Jan 2023 09:00:24 +0200 Subject: [PATCH 097/161] trailling whitespaces --- ...olated_corrected_curvatures_at_vertex_example.cpp | 12 ++++++------ .../interpolated_corrected_curvatures_example.cpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp index 6ed061db93b8..009c5881f262 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp @@ -17,7 +17,7 @@ typedef boost::graph_traits::vertex_descriptor vertex_descriptor; int main(int argc, char* argv[]) { - // instantiating and reading mesh + // instantiating and reading mesh Surface_Mesh smesh; const std::string filename = (argc > 1) ? argv[1] : @@ -34,21 +34,21 @@ int main(int argc, char* argv[]) { FT h = PMP::interpolated_corrected_mean_curvature_at_vertex(smesh, v); FT g = PMP::interpolated_corrected_gaussian_curvature_at_vertex(smesh, v); - PMP::Principal_curvatures_and_directions p = + PMP::Principal_curvatures_and_directions p = PMP::interpolated_corrected_principal_curvatures_and_directions_at_vertex(smesh, v); - // we can also specify a ball radius for expansion and a user defined vertex normals map using + // we can also specify a ball radius for expansion and a user defined vertex normals map using // named parameters. Refer to interpolated_corrected_curvatures_example.cpp to see example usage. // Can also use interpolated_corrected_curvatures_at_vertex() to compute multiple curvatures // on the vertex at the same time. This is more efficient than computing each one separately. // The following commented lines show this (all mentioned named parameters work on it as well) - // we specify which curvatures we want to compute by passing pointers as named parameters - // as shown. These pointers are used for storing the result as well. in this example we + // we specify which curvatures we want to compute by passing pointers as named parameters + // as shown. These pointers are used for storing the result as well. in this example we // selected mean and gaussian curvatures // PMP::interpolated_corrected_curvatures_at_vertex( // smesh, - // v, + // v, // CGAL::parameters::vertex_mean_curvature(&h) // .vertex_gaussian_curvature(&g) // ); diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp index 91266f594135..511288702336 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp @@ -47,7 +47,7 @@ int main(int argc, char* argv[]) assert(created); // user can call these fucntions to compute a specfic curvature type on each vertex. - // (Note: if no ball radius is specified, the measure expansion of each vertex happens by + // (Note: if no ball radius is specified, the measure expansion of each vertex happens by // summing measures on faces adjacent to each vertex.) PMP::interpolated_corrected_mean_curvature( smesh, From fc943a4c317588c51b7e64da23dd84c4fef9672a Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 24 Jan 2023 09:18:05 +0200 Subject: [PATCH 098/161] minor doc fixes --- .../Polygon_mesh_processing/Polygon_mesh_processing.txt | 8 ++++---- .../doc/Polygon_mesh_processing/examples.txt | 1 + 2 files changed, 5 insertions(+), 4 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 49f912ee5ca7..426e9e3b54a6 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 @@ -898,7 +898,7 @@ These computations are performed using (on all vertices of mesh): Where it is recommended to use the last function for computing multiple curvatures (example: mean and gaussian) as the implementation performs the shared computations only once, making it more efficient. -Similarly, we can use the following example functions to compute the curvatures on a specific vertex: +Similarly, we can use the following functions to compute curvatures on a specific vertex: - `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_at_vertex()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_at_vertex()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_at_vertex()` @@ -924,7 +924,7 @@ Property maps are an API introduced in the boost library that allows associating values to keys. In the following examples, for each property map, we associate a curvature value to each vertex. -\subsection ICCExampleSM Interpolated Corrected Curvature on a Surface Mesh Example +\subsection ICCExampleSM Interpolated Corrected Curvatures on a Surface Mesh Example The following example illustrates how to compute the curvatures on vertices @@ -932,7 +932,7 @@ and store them in property maps provided by the class `Surface_mesh`. \cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp} -\subsection ICCExamplePH Interpolated Corrected Curvature on a Polyhedron Example +\subsection ICCExamplePH Interpolated Corrected Curvatures on a Polyhedron Example The following example illustrates how to compute the curvatures on vertices @@ -941,7 +941,7 @@ not provide storage for the curvatures. \cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp} -\subsection ICCExamplePH Interpolated Corrected Curvature on a Vertex Example +\subsection ICCExampleSV Interpolated Corrected Curvatures on a Vertex Example The following example illustrates how to compute the curvatures on a specific vertex diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index 0588d1c76e14..58af6e391b56 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -21,6 +21,7 @@ \example Polygon_mesh_processing/isotropic_remeshing_example.cpp \example Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp \example Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp +\example Polygon_mesh_processing/interpolated_corrected_gaussian_curvature_at_vertex.cpp \example Polygon_mesh_processing/delaunay_remeshing_example.cpp \example Polygon_mesh_processing/compute_normals_example_Polyhedron.cpp \example Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp From ca4e412e0e1d98c605ef6cc16eb448a43f66158f Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 24 Jan 2023 09:35:20 +0200 Subject: [PATCH 099/161] minor fix --- .../test/Polygon_mesh_processing/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index c446a7395dee..8608e203c6eb 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -1,4 +1,4 @@ -0# Created by the script cgal_create_CMakeLists +# Created by the script cgal_create_CMakeLists # This is the CMake script for compiling a set of CGAL applications. cmake_minimum_required(VERSION 3.1...3.23) From da3db9a40674ac30071ec9e579412e1c2ba9a8a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 24 Jan 2023 09:58:35 +0100 Subject: [PATCH 100/161] typo --- .../test/Polygon_mesh_processing/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index c446a7395dee..8608e203c6eb 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -1,4 +1,4 @@ -0# Created by the script cgal_create_CMakeLists +# Created by the script cgal_create_CMakeLists # This is the CMake script for compiling a set of CGAL applications. cmake_minimum_required(VERSION 3.1...3.23) From 50ba18725ca32ab402357f13409bcee7f1e9fd79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 24 Jan 2023 11:10:29 +0100 Subject: [PATCH 101/161] fix typename usage --- ...nterpolated_corrected_curvature_measures.h | 26 +++++++++---------- ...test_interpolated_corrected_curvatures.cpp | 6 ++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 94aaff9b8b52..94520cf403dd 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -479,7 +479,7 @@ Principal_curvatures_and_directions principal_curvatures_and_directions_from } template -typename Vertex_measures interpolated_corrected_measures_one_vertex_no_radius( +Vertex_measures interpolated_corrected_measures_one_vertex_no_radius( const PolygonMesh pmesh, const typename boost::graph_traits::vertex_descriptor v, const bool is_mean_curvature_selected, @@ -498,7 +498,7 @@ typename Vertex_measures interpolated_corrected_measures_one_vertex_no_radiu std::queue bfs_queue; std::unordered_set bfs_visited; - typename Vertex_measures vertex_measures; + Vertex_measures vertex_measures; std::vector x; std::vector u; @@ -538,7 +538,7 @@ typename Vertex_measures interpolated_corrected_measures_one_vertex_no_radiu template -typename Vertex_measures interpolated_corrected_measures_one_vertex( +Vertex_measures interpolated_corrected_measures_one_vertex( const PolygonMesh pmesh, const typename boost::graph_traits::vertex_descriptor v, const typename GT::FT radius, @@ -558,10 +558,10 @@ typename Vertex_measures interpolated_corrected_measures_one_vertex( std::queue bfs_queue; std::unordered_set bfs_visited; - typename Vertex_measures vertex_measures; + Vertex_measures vertex_measures; - typename Point_3 vp = get(vpm, v); - typename Vector_3 c = Vector_3(vp.x(), vp.y(), vp.z()); + Point_3 vp = get(vpm, v); + Vector_3 c = Vector_3(vp.x(), vp.y(), vp.z()); for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { if (f != boost::graph_traits::null_face()) @@ -626,13 +626,13 @@ template::vertex_descriptor v, - NamedParameters& np = parameters::default_values() + const NamedParameters& np = parameters::default_values() ) { typedef typename GetGeomTraits::type GT; typedef typename GetVertexPointMap::const_type Vertex_position_map; - typedef dynamic_vertex_property_t Vector_map_tag; + typedef dynamic_vertex_property_t Vector_map_tag; typedef typename boost::property_map::const_type Default_vector_map; typedef typename internal_np::Lookup_named_param_def* vertex_principal_curvatures_and_directions = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions), nullptr); + Principal_curvatures_and_directions* vertex_principal_curvatures_and_directions = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions), nullptr); const bool is_mean_curvature_selected = (vertex_mean_curvature != nullptr); const bool is_gaussian_curvature_selected = (vertex_gaussian_curvature != nullptr); @@ -703,7 +703,7 @@ template principal_curvatures_and_directions = principal_curvatures_and_directions_from_anisotropic_measures( vertex_measures.anisotropic_measure, vertex_measures.area_measure, @@ -1046,7 +1046,7 @@ template void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, VertexCurvatureMap& vcm, - NamedParameters& np = parameters::default_values()) + const NamedParameters& np = parameters::default_values()) { interpolated_corrected_curvatures(pmesh, np.vertex_mean_curvature_map(vcm)); } @@ -1109,7 +1109,7 @@ template void interpolated_corrected_gaussian_curvature(const PolygonMesh& pmesh, VertexCurvatureMap& vcm, - NamedParameters& np = parameters::default_values()) + const NamedParameters& np = parameters::default_values()) { interpolated_corrected_curvatures(pmesh, np.vertex_gaussian_curvature_map(vcm)); } @@ -1173,7 +1173,7 @@ template void interpolated_corrected_principal_curvatures_and_directions(const PolygonMesh& pmesh, VertexCurvatureMap& vcm, - NamedParameters& np = parameters::default_values()) + const NamedParameters& np = parameters::default_values()) { interpolated_corrected_curvatures(pmesh, np.vertex_principal_curvatures_and_directions_map(vcm)); } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp index 163c9b8d334d..7f8aa1f7d691 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp @@ -63,12 +63,12 @@ void test_average_curvatures(std::string mesh_path, Average_test_info test_info) std::cerr << "Invalid input file." << std::endl; } - typedef boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; - boost::property_map>::type + typename boost::property_map>::type mean_curvature_map = get(CGAL::dynamic_vertex_property_t(), pmesh), gaussian_curvature_map = get(CGAL::dynamic_vertex_property_t(), pmesh); - boost::property_map>>::type + typename boost::property_map>>::type principal_curvatures_and_directions_map = get(CGAL::dynamic_vertex_property_t>(), pmesh); // test_info.expansion_radius -> test if no radius is provided by user. From 69610f6958dc51f31ede8a0e75c32e4f4c9a5c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 24 Jan 2023 11:15:04 +0100 Subject: [PATCH 102/161] move function --- ...nterpolated_corrected_curvature_measures.h | 128 +++++++++--------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 94520cf403dd..821436dc05f6 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -990,17 +990,15 @@ class Interpolated_corrected_curvatures_computer /** * \ingroup PMP_corrected_curvatures_grp * -* Computes the interpolated corrected mean curvature across the mesh -* and stores it in a vertex property map `vcm`. +* Computes the interpolated corrected curvatures across the mesh, based on the provided property maps. +* By providing mean, gaussian and/or principal curvature property maps as named parameters, the user +* can choose which curvatures to compute. * * @tparam PolygonMesh a model of `FaceListGraph`. -* @tparam VertexCurvatureMap model of `WritablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` as key type and `GT::FT` as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * * @param pmesh the polygon mesh. -* @param vcm the vertex property map in which the computed mean curvatures are stored. -* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters". +* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin * @@ -1024,37 +1022,63 @@ class Interpolated_corrected_curvatures_computer * computed using compute_vertex_normals()} * \cgalParamNEnd * +* \cgalParamNBegin{vertex_mean_curvature_map} +* \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} +* \cgalParamType{a class model of `WritablePropertyMap` with +* `boost::graph_traits::%Vertex_descriptor` +* as key type and `%FT` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, mean curvatures won't be computed} +* \cgalParamNEnd +* +* \cgalParamNBegin{vertex_gaussian_curvature_map} +* \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} +* \cgalParamType{a class model of `WritablePropertyMap` with +* `boost::graph_traits::%Vertex_descriptor` +* as key type and `%FT` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, gaussian curvatures won't be computed} +* \cgalParamNEnd +* +* +* \cgalParamNBegin{vertex_principal_curvatures_and_directions_map} +* \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} +* \cgalParamType{a class model of `WritablePropertyMap` with +* `boost::graph_traits::%Vertex_descriptor` +* as key type and `%Principal_curvatures_and_directions` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t>(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, mean principal won't be computed} +* \cgalParamNEnd +* * \cgalParamNBegin{ball_radius} * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures * by summing measures of faces inside a ball of this radius centered at the * vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball} +* inclusion ratio inside this ball.} * \cgalParamType{`GT::FT`} * \cgalParamDefault{`-1`} -* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of +* \cgalParamExtra{If this parameter is omitted (`-1`), the epansion is then just a sum of * measures on faces around the vertex} * \cgalParamNEnd * * \cgalNamedParamsEnd * +* @see `interpolated_corrected_mean_curvature()` * @see `interpolated_corrected_gaussian_curvature()` * @see `interpolated_corrected_principal_curvatures_and_directions()` -* @see `interpolated_corrected_curvatures()` */ - -template - void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, - VertexCurvatureMap& vcm, + void interpolated_corrected_curvatures(const PolygonMesh& pmesh, const NamedParameters& np = parameters::default_values()) { - interpolated_corrected_curvatures(pmesh, np.vertex_mean_curvature_map(vcm)); + internal::Interpolated_corrected_curvatures_computer(pmesh, np); } /** * \ingroup PMP_corrected_curvatures_grp * -* Computes the interpolated corrected gaussian curvature across the mesh +* Computes the interpolated corrected mean curvature across the mesh * and stores it in a vertex property map `vcm`. * * @tparam PolygonMesh a model of `FaceListGraph`. @@ -1063,7 +1087,7 @@ template - void interpolated_corrected_gaussian_curvature(const PolygonMesh& pmesh, + void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, VertexCurvatureMap& vcm, const NamedParameters& np = parameters::default_values()) { - interpolated_corrected_curvatures(pmesh, np.vertex_gaussian_curvature_map(vcm)); + interpolated_corrected_curvatures(pmesh, np.vertex_mean_curvature_map(vcm)); } /** * \ingroup PMP_corrected_curvatures_grp * -* Computes the interpolated corrected principal curvatures across the mesh +* Computes the interpolated corrected gaussian curvature across the mesh * and stores it in a vertex property map `vcm`. * * @tparam PolygonMesh a model of `FaceListGraph`. * @tparam VertexCurvatureMap model of `WritablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` as key type and -* `std::tuple, Eigen::Vector>` as value type. +* `boost::graph_traits::%Vertex_descriptor` as key type and `GT::FT` as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * * @param pmesh the polygon mesh. -* @param vcm the vertex property map in which the computed principal curvatures are stored. -* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below +* @param vcm the vertex property map in which the computed gaussian curvatures are stored. +* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters". * * \cgalNamedParamsBegin * @@ -1166,29 +1190,32 @@ template - void interpolated_corrected_principal_curvatures_and_directions(const PolygonMesh& pmesh, + void interpolated_corrected_gaussian_curvature(const PolygonMesh& pmesh, VertexCurvatureMap& vcm, const NamedParameters& np = parameters::default_values()) { - interpolated_corrected_curvatures(pmesh, np.vertex_principal_curvatures_and_directions_map(vcm)); + interpolated_corrected_curvatures(pmesh, np.vertex_gaussian_curvature_map(vcm)); } /** * \ingroup PMP_corrected_curvatures_grp * -* Computes the interpolated corrected curvatures across the mesh, based on the provided property maps. -* By providing mean, gaussian and/or principal curvature property maps as named parameters, the user -* can choose which curvatures to compute. +* Computes the interpolated corrected principal curvatures across the mesh +* and stores it in a vertex property map `vcm`. * * @tparam PolygonMesh a model of `FaceListGraph`. +* @tparam VertexCurvatureMap model of `WritablePropertyMap` with +* `boost::graph_traits::%Vertex_descriptor` as key type and +* `std::tuple, Eigen::Vector>` as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * * @param pmesh the polygon mesh. +* @param vcm the vertex property map in which the computed principal curvatures are stored. * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin @@ -1213,42 +1240,14 @@ template::%Vertex_descriptor` -* as key type and `%FT` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} -* \cgalParamExtra{If this parameter is omitted, mean curvatures won't be computed} -* \cgalParamNEnd -* -* \cgalParamNBegin{vertex_gaussian_curvature_map} -* \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} -* \cgalParamType{a class model of `WritablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` -* as key type and `%FT` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} -* \cgalParamExtra{If this parameter is omitted, gaussian curvatures won't be computed} -* \cgalParamNEnd -* -* -* \cgalParamNBegin{vertex_principal_curvatures_and_directions_map} -* \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} -* \cgalParamType{a class model of `WritablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` -* as key type and `%Principal_curvatures_and_directions` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t>(), pmesh)`} -* \cgalParamExtra{If this parameter is omitted, mean principal won't be computed} -* \cgalParamNEnd -* * \cgalParamNBegin{ball_radius} * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures * by summing measures of faces inside a ball of this radius centered at the * vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball.} +* inclusion ratio inside this ball} * \cgalParamType{`GT::FT`} * \cgalParamDefault{`-1`} -* \cgalParamExtra{If this parameter is omitted (`-1`), the epansion is then just a sum of +* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of * measures on faces around the vertex} * \cgalParamNEnd * @@ -1256,14 +1255,15 @@ template - void interpolated_corrected_curvatures(const PolygonMesh& pmesh, + void interpolated_corrected_principal_curvatures_and_directions(const PolygonMesh& pmesh, + VertexCurvatureMap& vcm, const NamedParameters& np = parameters::default_values()) { - internal::Interpolated_corrected_curvatures_computer(pmesh, np); + interpolated_corrected_curvatures(pmesh, np.vertex_principal_curvatures_and_directions_map(vcm)); } template Date: Tue, 24 Jan 2023 11:27:31 +0100 Subject: [PATCH 103/161] fix warning --- .../Interpolated_corrected_principal_curvatures_plugin.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp index 789ba4a06567..e5a01f7b3c3b 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp @@ -81,7 +81,7 @@ void compute(SMesh* sMesh, CGAL::parameters::ball_radius(0) ); - typename Epic_kernel::FT max_curvature_magnitude_on_mesh = 0; + double max_curvature_magnitude_on_mesh = 0; for (Vertex_descriptor v : vertices(*sMesh)) { const PMP::Principal_curvatures_and_directions pc = principal_curvatures_and_directions_map[v]; @@ -99,11 +99,9 @@ void compute(SMesh* sMesh, // compute min edge len around central vertex // to scale the ribbons used to display the directions - typedef EPICK::FT FT; - const std::size_t n = CGAL::edges(*sMesh).size(); - Epic_kernel::FT avg_edge_length = 0; + double avg_edge_length = 0; if (n > 0) { for (auto e : CGAL::edges(*sMesh)) avg_edge_length += PMP::edge_length(e, *sMesh); From 999b475e4ca5142589b239fb8418a7ff78254c56 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 24 Jan 2023 14:08:02 +0200 Subject: [PATCH 104/161] tests (incomplete) + minor typename fix still not passing single vertex on polyhedron due to a problem with vertex normal map --- ...nterpolated_corrected_curvature_measures.h | 10 ++-- ...test_interpolated_corrected_curvatures.cpp | 58 ++++++++++++++++--- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 847bac1f5c06..4cf352e32e48 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -399,8 +399,8 @@ std::array interpolated_corrected_anisotropic_measure_fa // const typename GT::FT R = r * r; // const typename GT::FT acc = 1.0 / res; // std::size_t samples_in = 0; -// for (GT::FT alpha = acc / 3; alpha < 1; alpha += acc) -// for (GT::FT beta = acc / 3; beta < 1 - alpha; beta += acc) +// for (typename GT::FT alpha = acc / 3; alpha < 1; alpha += acc) +// for (typename GT::FT beta = acc / 3; beta < 1 - alpha; beta += acc) // { // if ((alpha * x1 + beta * x2 + (1 - alpha - beta) * x3 - c).squared_length() < R) // samples_in++; @@ -632,7 +632,7 @@ template::type GT; typedef typename GetVertexPointMap::const_type Vertex_position_map; - typedef dynamic_vertex_property_t Vector_map_tag; + typedef dynamic_vertex_property_t Vector_map_tag; typedef typename boost::property_map::const_type Default_vector_map; typedef typename internal_np::Lookup_named_param_def(pmesh) * EXPANDING_RADIUS_EPSILON; - else - radius = radius; typename GT::FT* vertex_mean_curvature = choose_parameter(get_parameter(np, internal_np::vertex_mean_curvature), nullptr); typename GT::FT* vertex_gaussian_curvature = choose_parameter(get_parameter(np, internal_np::vertex_gaussian_curvature), nullptr); @@ -704,7 +702,7 @@ template principal_curvatures_and_directions = principal_curvatures_and_directions_from_anisotropic_measures( vertex_measures.anisotropic_measure, vertex_measures.area_measure, diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp index 163c9b8d334d..a6fa6f712680 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp @@ -54,7 +54,11 @@ bool passes_comparison(Epic_kernel::FT result, Epic_kernel::FT expected, Epic_ke } template -void test_average_curvatures(std::string mesh_path, Average_test_info test_info){ +void test_average_curvatures(std::string mesh_path, + Average_test_info test_info, + bool compare_single_vertex = false +){ + PolygonMesh pmesh; const std::string filename = CGAL::data_file_path(mesh_path); @@ -116,6 +120,7 @@ void test_average_curvatures(std::string mesh_path, Average_test_info test_info) assert(passes_comparison(gaussian_curvature_avg, test_info.gaussian_curvature_avg, test_info.tolerance)); assert(passes_comparison(principal_curvature_avg, test_info.principal_curvature_avg, test_info.tolerance)); + // computing curvatures together from interpolated_corrected_curvatures() PMP::interpolated_corrected_curvatures( pmesh, CGAL::parameters::ball_radius(test_info.expansion_radius) @@ -124,7 +129,6 @@ void test_average_curvatures(std::string mesh_path, Average_test_info test_info) .vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) ); - // are average curvatures computed from interpolated_corrected_curvatures() equal to average curvatures each computed on its own? Epic_kernel::FT new_mean_curvature_avg = 0, new_gaussian_curvature_avg = 0, new_principal_curvature_avg = 0; for (vertex_descriptor v : vertices(pmesh)) @@ -138,22 +142,62 @@ void test_average_curvatures(std::string mesh_path, Average_test_info test_info) new_mean_curvature_avg /= vertices(pmesh).size(); new_gaussian_curvature_avg /= vertices(pmesh).size(); new_principal_curvature_avg /= vertices(pmesh).size() * 2; - + + // are average curvatures computed from interpolated_corrected_curvatures() + // equal to average curvatures each computed on its own? assert(passes_comparison(mean_curvature_avg, new_mean_curvature_avg, 0.99)); assert(passes_comparison(gaussian_curvature_avg, new_gaussian_curvature_avg, 0.99)); assert(passes_comparison(principal_curvature_avg, new_principal_curvature_avg, 0.99)); + + if (compare_single_vertex) + { + // computing curvatures together from interpolated_corrected_curvatures() + + Epic_kernel::FT single_vertex_mean_curvature_avg = 0, + single_vertex_gaussian_curvature_avg = 0, + single_vertex_principal_curvature_avg = 0; + + Epic_kernel::FT h, g; + PMP::Principal_curvatures_and_directions p; + + for (vertex_descriptor v : vertices(pmesh)) + { + PMP::interpolated_corrected_curvatures_at_vertex( + pmesh, + v, + CGAL::parameters::vertex_gaussian_curvature(&g) + .vertex_mean_curvature(&h) + .vertex_principal_curvatures_and_directions(&p) + ); + + single_vertex_mean_curvature_avg += h; + single_vertex_gaussian_curvature_avg += g; + single_vertex_principal_curvature_avg += p.min_curvature + p.max_curvature; + } + + single_vertex_mean_curvature_avg /= vertices(pmesh).size(); + single_vertex_gaussian_curvature_avg /= vertices(pmesh).size(); + single_vertex_principal_curvature_avg /= vertices(pmesh).size() * 2; + + assert(passes_comparison(mean_curvature_avg, single_vertex_mean_curvature_avg, 0.99)); + assert(passes_comparison(gaussian_curvature_avg, single_vertex_gaussian_curvature_avg, 0.99)); + assert(passes_comparison(principal_curvature_avg, single_vertex_principal_curvature_avg, 0.99)); + } + } int main() { // testing on a simple sphere(r = 0.5), on both Polyhedron & SurfaceMesh: + // For this mesh, ina addition to the whole mesh functions, we also compare against the single vertex + // curvature functions to make sure the produce the same results // Expected: Mean Curvature = 2, Gaussian Curvature = 4, Principal Curvatures = 2 & 2 so 2 on avg. - test_average_curvatures("meshes/sphere.off", Average_test_info(2,4,2)); - test_average_curvatures("meshes/sphere.off", Average_test_info(2, 4, 2)); + test_average_curvatures("meshes/sphere.off", Average_test_info(2,4,2), true); + test_average_curvatures("meshes/sphere.off", Average_test_info(2, 4, 2), true); // Same mesh but with specified expansion radii of 0 and 0.25 (half radius of sphere) - test_average_curvatures("meshes/sphere.off", Average_test_info(2, 4, 2, 0)); - test_average_curvatures("meshes/sphere.off", Average_test_info(2, 4, 2, 0.25)); + test_average_curvatures("meshes/sphere.off", Average_test_info(2, 4, 2, 0), true); + test_average_curvatures("meshes/sphere.off", Average_test_info(2, 4, 2, 0.25), true); // testing on a simple sphere(r = 10), on both Polyhedron & SurfaceMesh: // Expected: Mean Curvature = 0.1, Gaussian Curvature = 0.01, Principal Curvatures = 0.1 & 0.1 so 0.1 on avg. From 7303c7401e476d85784715ab3c28e05a8ca3eb8a Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 24 Jan 2023 14:42:39 +0200 Subject: [PATCH 105/161] move function + minor func doc fix --- ...nterpolated_corrected_curvature_measures.h | 155 +++++++++--------- 1 file changed, 78 insertions(+), 77 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 078fcaeb48e3..2ae9a6856e38 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -1210,7 +1210,7 @@ template::%Vertex_descriptor` as key type and -* `std::tuple, Eigen::Vector>` as value type. +* `%Principal_curvatures_and_directions` as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * * @param pmesh the polygon mesh. @@ -1265,16 +1265,20 @@ template*`} + * \cgalParamDefault{`nullptr`} + * \cgalParamExtra{If this parameter is omitted, principal curvatures and directions won't be computed} + * \cgalParamNEnd + * * \cgalParamNBegin{ball_radius} * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures * by summing measures of faces inside a ball of this radius centered at the * vertex expanded from. The summed face measures are weighted by their - * inclusion ratio inside this ball} + * inclusion ratio inside this ball.} * \cgalParamType{`GT::FT`} * \cgalParamDefault{`-1`} - * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of + * \cgalParamExtra{If this parameter is omitted (`-1`), the epansion is then just a sum of * measures on faces around the vertex} * \cgalParamNEnd * * \cgalNamedParamsEnd * - * @return the interpolated corrected mean curvature at the vertex `v` - * - * @see `interpolated_corrected_mean_curvature()` - * @see `interpolated_corrected_gaussian_curvature_at_vertex()` - * @see `interpolated_corrected_principal_curvatures_and_directions_at_vertex()` - * @see `interpolated_corrected_curvatures_at_vertex()` + * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` + * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_at_vertex()` + * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_at_vertex()` + * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_at_vertex()` */ - -template - typename GT::FT interpolated_corrected_mean_curvature_at_vertex(const PolygonMesh& pmesh, + void interpolated_corrected_curvatures_at_vertex(const PolygonMesh& pmesh, typename boost::graph_traits::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { - // use interpolated_corrected_curvatures_at_vertex to compute mean curvature - typename GT::FT mean_curvature; - interpolated_corrected_curvatures_at_vertex(pmesh, v, np.vertex_mean_curvature(&mean_curvature)); - return mean_curvature; + internal::interpolated_corrected_curvatures_one_vertex(pmesh, v, np); } /** * \ingroup PMP_corrected_curvatures_grp - * computes the interpolated corrected Gaussian curvature at a vertex of a triangle mesh. + * computes the interpolated corrected mean curvature at a vertex of a triangle mesh. * * @tparam GT a geometric traits class that provides the nested type `FT`, * @tparam PolygonMesh a model of `FaceListGraph` * @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" * * @param pmesh the polygon mesh - * @param v the vertex of `pmesh` to compute the Gaussian curvature at + * @param v the vertex of `pmesh` to compute the mean curvature at * @param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin @@ -1377,36 +1396,36 @@ template - typename GT::FT interpolated_corrected_gaussian_curvature_at_vertex(const PolygonMesh& pmesh, + typename GT::FT interpolated_corrected_mean_curvature_at_vertex(const PolygonMesh& pmesh, typename boost::graph_traits::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { - // use interpolated_corrected_curvatures_at_vertex to compute gaussian curvature - typename GT::FT gc; - interpolated_corrected_curvatures_at_vertex(pmesh, v, np.vertex_gaussian_curvature(&gc)); - return gc; + // use interpolated_corrected_curvatures_at_vertex to compute mean curvature + typename GT::FT mean_curvature; + interpolated_corrected_curvatures_at_vertex(pmesh, v, np.vertex_mean_curvature(&mean_curvature)); + return mean_curvature; } /** * \ingroup PMP_corrected_curvatures_grp - * computes the interpolated corrected principal curvatures and directions at a vertex of a triangle mesh. + * computes the interpolated corrected Gaussian curvature at a vertex of a triangle mesh. * - * @tparam GT the geometric traits class, + * @tparam GT a geometric traits class that provides the nested type `FT`, * @tparam PolygonMesh a model of `FaceListGraph` * @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" * * @param pmesh the polygon mesh - * @param v the vertex of `pmesh` to compute the principal curvatures and directions at + * @param v the vertex of `pmesh` to compute the Gaussian curvature at * @param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin @@ -1440,41 +1459,39 @@ template - Principal_curvatures_and_directions interpolated_corrected_principal_curvatures_and_directions_at_vertex(const PolygonMesh& pmesh, + typename GT::FT interpolated_corrected_gaussian_curvature_at_vertex(const PolygonMesh& pmesh, typename boost::graph_traits::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { - // use interpolated_corrected_curvatures_at_vertex to compute principal curvatures - Principal_curvatures_and_directions pcd; - interpolated_corrected_curvatures_at_vertex(pmesh, v, np.vertex_principal_curvatures_and_directions(&pcd)); - return pcd; + // use interpolated_corrected_curvatures_at_vertex to compute gaussian curvature + typename GT::FT gc; + interpolated_corrected_curvatures_at_vertex(pmesh, v, np.vertex_gaussian_curvature(&gc)); + return gc; } /** * \ingroup PMP_corrected_curvatures_grp - * Computes the interpolated corrected curvatures at a certain vertex, based on the provided pointers. - * By providing mean, gaussian and/or principal curvature pointers as named parameters, the user - * can choose which curvatures to compute. - * The pointers are used to store the computed curvatures. - * The user is responsible for the memory management of the pointers. + * computes the interpolated corrected principal curvatures and directions at a vertex of a triangle mesh. * + * @tparam GT the geometric traits class, * @tparam PolygonMesh a model of `FaceListGraph` * @tparam NamedParameters a sequence of \ref pmp_namedparameters "Named Parameters" * * @param pmesh the polygon mesh - * @param v the vertex of `pmesh` to compute the curvatures at + * @param v the vertex of `pmesh` to compute the principal curvatures and directions at * @param np optional sequence of \ref pmp_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin @@ -1498,52 +1515,36 @@ template*`} - * \cgalParamDefault{`nullptr`} - * \cgalParamExtra{If this parameter is omitted, principal curvatures and directions won't be computed} - * \cgalParamNEnd - * * \cgalParamNBegin{ball_radius} * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures * by summing measures of faces inside a ball of this radius centered at the * vertex expanded from. The summed face measures are weighted by their - * inclusion ratio inside this ball.} + * inclusion ratio inside this ball} * \cgalParamType{`GT::FT`} * \cgalParamDefault{`-1`} - * \cgalParamExtra{If this parameter is omitted (`-1`), the epansion is then just a sum of + * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of * measures on faces around the vertex} * \cgalParamNEnd - * * \cgalNamedParamsEnd * - * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` - * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_at_vertex()` - * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_at_vertex()` - * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_at_vertex()` + * @return the interpolated corrected principal curvatures and directions at the vertex `v` + * + * @see `interpolated_corrected_principal_curvatures_and_directions()` + * @see `interpolated_corrected_mean_curvature_at_vertex()` + * @see `interpolated_corrected_gaussian_curvature_at_vertex()` + * @see `interpolated_corrected_curvatures_at_vertex()` */ -template - void interpolated_corrected_curvatures_at_vertex(const PolygonMesh& pmesh, + Principal_curvatures_and_directions interpolated_corrected_principal_curvatures_and_directions_at_vertex(const PolygonMesh& pmesh, typename boost::graph_traits::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { - internal::interpolated_corrected_curvatures_one_vertex(pmesh, v, np); + // use interpolated_corrected_curvatures_at_vertex to compute principal curvatures + Principal_curvatures_and_directions pcd; + interpolated_corrected_curvatures_at_vertex(pmesh, v, np.vertex_principal_curvatures_and_directions(&pcd)); + return pcd; } } // namespace Polygon_mesh_processing From f9c21faf02f7296a6285909dffa9c3758599e564 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 24 Jan 2023 15:11:58 +0200 Subject: [PATCH 106/161] trailling white spaces --- .../test_interpolated_corrected_curvatures.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp index cd71fbb20f34..367d3c7045d1 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp @@ -54,7 +54,7 @@ bool passes_comparison(Epic_kernel::FT result, Epic_kernel::FT expected, Epic_ke } template -void test_average_curvatures(std::string mesh_path, +void test_average_curvatures(std::string mesh_path, Average_test_info test_info, bool compare_single_vertex = false ){ @@ -142,8 +142,8 @@ void test_average_curvatures(std::string mesh_path, new_mean_curvature_avg /= vertices(pmesh).size(); new_gaussian_curvature_avg /= vertices(pmesh).size(); new_principal_curvature_avg /= vertices(pmesh).size() * 2; - - // are average curvatures computed from interpolated_corrected_curvatures() + + // are average curvatures computed from interpolated_corrected_curvatures() // equal to average curvatures each computed on its own? assert(passes_comparison(mean_curvature_avg, new_mean_curvature_avg, 0.99)); assert(passes_comparison(gaussian_curvature_avg, new_gaussian_curvature_avg, 0.99)); @@ -156,15 +156,15 @@ void test_average_curvatures(std::string mesh_path, Epic_kernel::FT single_vertex_mean_curvature_avg = 0, single_vertex_gaussian_curvature_avg = 0, single_vertex_principal_curvature_avg = 0; - + Epic_kernel::FT h, g; PMP::Principal_curvatures_and_directions p; - + for (vertex_descriptor v : vertices(pmesh)) { PMP::interpolated_corrected_curvatures_at_vertex( pmesh, - v, + v, CGAL::parameters::vertex_gaussian_curvature(&g) .vertex_mean_curvature(&h) .vertex_principal_curvatures_and_directions(&p) @@ -189,8 +189,8 @@ void test_average_curvatures(std::string mesh_path, int main() { // testing on a simple sphere(r = 0.5), on both Polyhedron & SurfaceMesh: - // For this mesh, ina addition to the whole mesh functions, we also compare against the single vertex - // curvature functions to make sure the produce the same results + // For this mesh, ina addition to the whole mesh functions, we also compare against the single vertex + // curvature functions to make sure the produce the same results // Expected: Mean Curvature = 2, Gaussian Curvature = 4, Principal Curvatures = 2 & 2 so 2 on avg. test_average_curvatures("meshes/sphere.off", Average_test_info(2,4,2), true); test_average_curvatures("meshes/sphere.off", Average_test_info(2, 4, 2), true); From 92e22644357462fd3b13af386d4795284999639e Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 24 Jan 2023 17:58:03 +0200 Subject: [PATCH 107/161] Update test_interpolated_corrected_curvatures.cpp --- .../test_interpolated_corrected_curvatures.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp index 367d3c7045d1..12ab2bff3872 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp @@ -168,6 +168,7 @@ void test_average_curvatures(std::string mesh_path, CGAL::parameters::vertex_gaussian_curvature(&g) .vertex_mean_curvature(&h) .vertex_principal_curvatures_and_directions(&p) + .ball_radius(test_info.expansion_radius) ); single_vertex_mean_curvature_avg += h; From 6f2f912c4d03e5a418d37c2f9b1de7c0d4de4a5a Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 24 Jan 2023 18:41:25 +0200 Subject: [PATCH 108/161] minor fix --- .../Curvatures/interpolated_corrected_curvature_measures.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 2ae9a6856e38..32287848fb31 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -480,7 +480,7 @@ Principal_curvatures_and_directions principal_curvatures_and_directions_from template Vertex_measures interpolated_corrected_measures_one_vertex_no_radius( - const PolygonMesh pmesh, + const PolygonMesh& pmesh, const typename boost::graph_traits::vertex_descriptor v, const bool is_mean_curvature_selected, const bool is_gaussian_curvature_selected, @@ -539,7 +539,7 @@ Vertex_measures interpolated_corrected_measures_one_vertex_no_radius( template Vertex_measures interpolated_corrected_measures_one_vertex( - const PolygonMesh pmesh, + const PolygonMesh& pmesh, const typename boost::graph_traits::vertex_descriptor v, const typename GT::FT radius, const bool is_mean_curvature_selected, @@ -624,7 +624,7 @@ Vertex_measures interpolated_corrected_measures_one_vertex( template void interpolated_corrected_curvatures_one_vertex( - const PolygonMesh pmesh, + const PolygonMesh& pmesh, const typename boost::graph_traits::vertex_descriptor v, const NamedParameters& np = parameters::default_values() ) From 1294548acc0444ec23145fe29d3e428baa515f8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 24 Jan 2023 17:58:27 +0100 Subject: [PATCH 109/161] avoid macro substitution --- .../test_interpolated_corrected_curvatures.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp index 12ab2bff3872..2aa963fea52b 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp @@ -50,7 +50,7 @@ bool passes_comparison(Epic_kernel::FT result, Epic_kernel::FT expected, Epic_ke else if (abs(expected) < ABS_ERROR) return false; // expected 0, got non-0 - return std::min(result, expected) / std::max(result, expected) > tolerance; + return (std::min)(result, expected) / (std::max)(result, expected) > tolerance; } template From 00cf0970e56f8eafdbb74afe728e8b7e8bcf3c3e Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 26 Jan 2023 12:04:51 +0200 Subject: [PATCH 110/161] changed the mesh file used in examples --- ...corrected_curvatures_at_vertex_example.cpp | 3 +- ...erpolated_corrected_curvatures_example.cpp | 3 +- ...orrected_curvatures_polyhedron_example.cpp | 35 ++++++++++--------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp index 009c5881f262..046f9101d1e6 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp @@ -12,7 +12,6 @@ namespace PMP = CGAL::Polygon_mesh_processing; typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic_kernel; typedef Epic_kernel::FT FT; typedef CGAL::Surface_mesh Surface_Mesh; -typedef boost::graph_traits::face_descriptor face_descriptor; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; int main(int argc, char* argv[]) @@ -21,7 +20,7 @@ int main(int argc, char* argv[]) Surface_Mesh smesh; const std::string filename = (argc > 1) ? argv[1] : - CGAL::data_file_path("meshes/small_bunny.obj"); + CGAL::data_file_path("meshes/sphere.off"); if (!CGAL::IO::read_polygon_mesh(filename, smesh)) { diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp index 511288702336..6a991bafdfb8 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp @@ -12,7 +12,6 @@ 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::face_descriptor face_descriptor; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; int main(int argc, char* argv[]) @@ -20,7 +19,7 @@ int main(int argc, char* argv[]) Surface_Mesh smesh; const std::string filename = (argc > 1) ? argv[1] : - CGAL::data_file_path("meshes/small_bunny.obj"); + CGAL::data_file_path("meshes/sphere.off"); if (!CGAL::IO::read_polygon_mesh(filename, smesh)) { diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp index 6bbe49ce37e5..0d1ffcfa35d1 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp @@ -13,13 +13,14 @@ namespace PMP = CGAL::Polygon_mesh_processing; typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic_kernel; typedef CGAL::Polyhedron_3 Polyhedron; -typedef boost::graph_traits::face_descriptor face_descriptor; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; -int main(int argc, char *argv[]) +int main(int argc, char* argv[]) { Polyhedron polyhedron; - const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/small_bunny.obj"); + const std::string filename = (argc > 1) ? + argv[1] : + CGAL::data_file_path("meshes/sphere.off"); if (!CGAL::IO::read_polygon_mesh(filename, polyhedron)) { @@ -28,22 +29,22 @@ int main(int argc, char *argv[]) } boost::property_map>::type - mean_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron), - gaussian_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron); + mean_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron), + gaussian_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron); boost::property_map>>::type - principal_curvatures_and_directions_map = get(CGAL::dynamic_vertex_property_t>(), polyhedron); + principal_curvatures_and_directions_map = get(CGAL::dynamic_vertex_property_t>(), polyhedron); PMP::interpolated_corrected_mean_curvature( - polyhedron, - mean_curvature_map); + polyhedron, + mean_curvature_map); PMP::interpolated_corrected_gaussian_curvature( - polyhedron, - gaussian_curvature_map); + polyhedron, + gaussian_curvature_map); PMP::interpolated_corrected_principal_curvatures_and_directions( - polyhedron, - principal_curvatures_and_directions_map); + polyhedron, + principal_curvatures_and_directions_map); // uncomment this to compute a curvature while specifying named parameters // Example: an expansion ball radius of 0.5 and a vertex normals map (does not have to depend on positions) @@ -59,17 +60,17 @@ int main(int argc, char *argv[]) // This function can be used to compute multiple curvature types by specifiying them as named parameters // This is more efficient than computing each one separately (shared computations). PMP::interpolated_corrected_curvatures( - polyhedron, - CGAL::parameters::vertex_mean_curvature_map(mean_curvature_map) - .vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map)); + polyhedron, + CGAL::parameters::vertex_mean_curvature_map(mean_curvature_map) + .vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map)); int i = 0; for (vertex_descriptor v : vertices(polyhedron)) { auto PC = get(principal_curvatures_and_directions_map, v); std::cout << i << ": HC = " << get(mean_curvature_map, v) - << ", GC = " << get(gaussian_curvature_map, v) << "\n" - << ", PC = [ " << PC.min_curvature << " , " << PC.max_curvature << " ]\n"; + << ", GC = " << get(gaussian_curvature_map, v) << "\n" + << ", PC = [ " << PC.min_curvature << " , " << PC.max_curvature << " ]\n"; i++; } } From 8d2043d0aab934e3d97cd2de8be8f112638fc94b Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 28 Jan 2023 15:54:35 +0200 Subject: [PATCH 111/161] renaming mostly to pass the max path error --- .../PackageDescription.txt | 8 ++-- .../Polygon_mesh_processing.txt | 14 +++--- .../doc/Polygon_mesh_processing/examples.txt | 6 +-- .../Polygon_mesh_processing/CMakeLists.txt | 12 ++--- ...lated_corrected_curvatures_example_PH.cpp} | 0 ...lated_corrected_curvatures_example_SM.cpp} | 0 ...d_corrected_curvatures_vertex_example.cpp} | 12 ++--- ...nterpolated_corrected_curvature_measures.h | 44 +++++++++---------- ...test_interpolated_corrected_curvatures.cpp | 2 +- 9 files changed, 49 insertions(+), 49 deletions(-) rename Polygon_mesh_processing/examples/Polygon_mesh_processing/{interpolated_corrected_curvatures_polyhedron_example.cpp => interpolated_corrected_curvatures_example_PH.cpp} (100%) rename Polygon_mesh_processing/examples/Polygon_mesh_processing/{interpolated_corrected_curvatures_example.cpp => interpolated_corrected_curvatures_example_SM.cpp} (100%) rename Polygon_mesh_processing/examples/Polygon_mesh_processing/{interpolated_corrected_curvatures_at_vertex_example.cpp => interpolated_corrected_curvatures_vertex_example.cpp} (83%) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index d5790ec725ee..52ae8934ddf2 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -212,10 +212,10 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage. - `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_at_vertex()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_at_vertex()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_at_vertex()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures_at_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_one_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_one_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_one_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures_one_vertex()` - `CGAL::Polygon_mesh_processing::Principal_curvatures_and_directions` \cgalCRPSection{Normal Computation Functions} 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 fedebf4b8f8a..751db0992492 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 @@ -909,10 +909,10 @@ Where it is recommended to use the last function for computing multiple curvatur as the implementation performs the shared computations only once, making it more efficient. Similarly, we can use the following functions to compute curvatures on a specific vertex: -- `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_at_vertex()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_at_vertex()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_at_vertex()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures_at_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_one_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_one_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_one_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures_one_vertex()` \cgalFigureRef{icc_diff_radius} shows how the mean curvature changes depending on @@ -940,7 +940,7 @@ The following example illustrates how to compute the curvatures on vertices and store them in property maps provided by the class `Surface_mesh`. -\cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp} +\cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_example_SM.cpp} \subsection ICCExamplePH Interpolated Corrected Curvatures on a Polyhedron Example @@ -949,14 +949,14 @@ compute the curvatures on vertices and store them in dynamic property maps as the class `Polyhedron_3` does not provide storage for the curvatures. -\cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp} +\cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_example_PH.cpp} \subsection ICCExampleSV Interpolated Corrected Curvatures on a Vertex Example The following example illustrates how to compute the curvatures on a specific vertex -\cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp} +\cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_vertex_example.cpp} **************************************** \section PMPSlicer Slicer diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index 58af6e391b56..33e68eea8091 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -19,9 +19,9 @@ \example Polygon_mesh_processing/refine_fair_example.cpp \example Polygon_mesh_processing/mesh_slicer_example.cpp \example Polygon_mesh_processing/isotropic_remeshing_example.cpp -\example Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp -\example Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp -\example Polygon_mesh_processing/interpolated_corrected_gaussian_curvature_at_vertex.cpp +\example Polygon_mesh_processing/interpolated_corrected_curvatures_example_SM.cpp +\example Polygon_mesh_processing/interpolated_corrected_curvatures_example_PH.cpp +\example Polygon_mesh_processing/interpolated_corrected_curvatures_vertex_example.cpp \example Polygon_mesh_processing/delaunay_remeshing_example.cpp \example Polygon_mesh_processing/compute_normals_example_Polyhedron.cpp \example Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index 5e840c38a508..93e321e837c8 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -105,12 +105,12 @@ create_single_source_cgal_program("cc_compatible_orientations.cpp") if(TARGET CGAL::Eigen3_support) - create_single_source_cgal_program("interpolated_corrected_curvatures_example.cpp") - target_link_libraries(interpolated_corrected_curvatures_example PUBLIC CGAL::Eigen3_support) - create_single_source_cgal_program("interpolated_corrected_curvatures_polyhedron_example.cpp") - target_link_libraries(interpolated_corrected_curvatures_polyhedron_example PUBLIC CGAL::Eigen3_support) - create_single_source_cgal_program("interpolated_corrected_curvatures_at_vertex_example.cpp") - target_link_libraries(interpolated_corrected_curvatures_at_vertex_example PUBLIC CGAL::Eigen3_support) + create_single_source_cgal_program("interpolated_corrected_curvatures_example_SM.cpp") + target_link_libraries(interpolated_corrected_curvatures_example_SM PUBLIC CGAL::Eigen3_support) + create_single_source_cgal_program("interpolated_corrected_curvatures_example_PH.cpp") + target_link_libraries(interpolated_corrected_curvatures_example_PH PUBLIC CGAL::Eigen3_support) + create_single_source_cgal_program("interpolated_corrected_curvatures_vertex_example.cpp") + target_link_libraries(interpolated_corrected_curvatures_vertex_example PUBLIC CGAL::Eigen3_support) endif() diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_PH.cpp similarity index 100% rename from Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_polyhedron_example.cpp rename to Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_PH.cpp diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_SM.cpp similarity index 100% rename from Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example.cpp rename to Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_SM.cpp diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex_example.cpp similarity index 83% rename from Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp rename to Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex_example.cpp index 046f9101d1e6..a897066facb6 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_at_vertex_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex_example.cpp @@ -31,21 +31,21 @@ int main(int argc, char* argv[]) // loop over vertices and use vertex_descriptor to compute a curvature on one vertex for (vertex_descriptor v : vertices(smesh)) { - FT h = PMP::interpolated_corrected_mean_curvature_at_vertex(smesh, v); - FT g = PMP::interpolated_corrected_gaussian_curvature_at_vertex(smesh, v); + FT h = PMP::interpolated_corrected_mean_curvature_one_vertex(smesh, v); + FT g = PMP::interpolated_corrected_gaussian_curvature_one_vertex(smesh, v); PMP::Principal_curvatures_and_directions p = - PMP::interpolated_corrected_principal_curvatures_and_directions_at_vertex(smesh, v); + PMP::interpolated_corrected_principal_curvatures_and_directions_one_vertex(smesh, v); // we can also specify a ball radius for expansion and a user defined vertex normals map using - // named parameters. Refer to interpolated_corrected_curvatures_example.cpp to see example usage. + // named parameters. Refer to interpolated_corrected_curvatures_example_SM.cpp to see example usage. - // Can also use interpolated_corrected_curvatures_at_vertex() to compute multiple curvatures + // Can also use interpolated_corrected_curvatures_one_vertex() to compute multiple curvatures // on the vertex at the same time. This is more efficient than computing each one separately. // The following commented lines show this (all mentioned named parameters work on it as well) // we specify which curvatures we want to compute by passing pointers as named parameters // as shown. These pointers are used for storing the result as well. in this example we // selected mean and gaussian curvatures - // PMP::interpolated_corrected_curvatures_at_vertex( + // PMP::interpolated_corrected_curvatures_one_vertex( // smesh, // v, // CGAL::parameters::vertex_mean_curvature(&h) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 32287848fb31..2142e57fd194 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -1337,13 +1337,13 @@ template - void interpolated_corrected_curvatures_at_vertex(const PolygonMesh& pmesh, + void interpolated_corrected_curvatures_one_vertex(const PolygonMesh& pmesh, typename boost::graph_traits::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { @@ -1399,20 +1399,20 @@ template - typename GT::FT interpolated_corrected_mean_curvature_at_vertex(const PolygonMesh& pmesh, + typename GT::FT interpolated_corrected_mean_curvature_one_vertex(const PolygonMesh& pmesh, typename boost::graph_traits::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { - // use interpolated_corrected_curvatures_at_vertex to compute mean curvature + // use interpolated_corrected_curvatures_one_vertex to compute mean curvature typename GT::FT mean_curvature; - interpolated_corrected_curvatures_at_vertex(pmesh, v, np.vertex_mean_curvature(&mean_curvature)); + interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_mean_curvature(&mean_curvature)); return mean_curvature; } @@ -1465,20 +1465,20 @@ template - typename GT::FT interpolated_corrected_gaussian_curvature_at_vertex(const PolygonMesh& pmesh, + typename GT::FT interpolated_corrected_gaussian_curvature_one_vertex(const PolygonMesh& pmesh, typename boost::graph_traits::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { - // use interpolated_corrected_curvatures_at_vertex to compute gaussian curvature + // use interpolated_corrected_curvatures_one_vertex to compute gaussian curvature typename GT::FT gc; - interpolated_corrected_curvatures_at_vertex(pmesh, v, np.vertex_gaussian_curvature(&gc)); + interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_gaussian_curvature(&gc)); return gc; } @@ -1530,20 +1530,20 @@ template - Principal_curvatures_and_directions interpolated_corrected_principal_curvatures_and_directions_at_vertex(const PolygonMesh& pmesh, + Principal_curvatures_and_directions interpolated_corrected_principal_curvatures_and_directions_one_vertex(const PolygonMesh& pmesh, typename boost::graph_traits::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { - // use interpolated_corrected_curvatures_at_vertex to compute principal curvatures + // use interpolated_corrected_curvatures_one_vertex to compute principal curvatures Principal_curvatures_and_directions pcd; - interpolated_corrected_curvatures_at_vertex(pmesh, v, np.vertex_principal_curvatures_and_directions(&pcd)); + interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_principal_curvatures_and_directions(&pcd)); return pcd; } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp index 2aa963fea52b..37fc6f050df4 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp @@ -162,7 +162,7 @@ void test_average_curvatures(std::string mesh_path, for (vertex_descriptor v : vertices(pmesh)) { - PMP::interpolated_corrected_curvatures_at_vertex( + PMP::interpolated_corrected_curvatures_one_vertex( pmesh, v, CGAL::parameters::vertex_gaussian_curvature(&g) From 4e669b79a73682b97aca64b9e04ef46edbf56143 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 29 Jan 2023 06:26:16 +0200 Subject: [PATCH 112/161] conversion warnings --- ...interpolated_corrected_curvature_measures.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h index 2142e57fd194..cfe3b79b6e19 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h @@ -89,7 +89,7 @@ typename GT::FT average_edge_length(const PolygonMesh& pmesh) { for (auto e : edges(pmesh)) avg_edge_length += edge_length(e, pmesh); - avg_edge_length /= n; + avg_edge_length /= static_cast(n); return avg_edge_length; } @@ -145,7 +145,7 @@ typename GT::FT interpolated_corrected_area_measure_face(const std::vector(n); // getting unit average normal of points typename GT::Vector_3 uc = @@ -211,7 +211,7 @@ typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::ve // getting center of points typename GT::Vector_3 xc = std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); - xc /= n; + xc /= static_cast(n); // getting unit average normal of points typename GT::Vector_3 uc = @@ -297,7 +297,7 @@ std::array interpolated_corrected_anisotropic_measure_fa const typename GT::Vector_3 x02 = x[2] - x[0]; const typename GT::Vector_3 um = (u[0] + u[1] + u[2]) / 3.0; - for (std::size_t ix = 0; ix < 3; ix++) + for (unsigned int ix = 0; ix < 3; ix++) { typename GT::Vector_3 X; if (ix == 0) @@ -307,7 +307,7 @@ std::array interpolated_corrected_anisotropic_measure_fa if (ix == 2) X = typename GT::Vector_3(0, 0, 1); - for (std::size_t iy = 0; iy < 3; iy++) + for (unsigned int iy = 0; iy < 3; iy++) muXY[ix * 3 + iy] = 0.5 * um * (cross_product(u02[iy] * X, x01) - cross_product(u01[iy] * X, x02)); } } @@ -316,7 +316,7 @@ std::array interpolated_corrected_anisotropic_measure_fa { // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. // the indices in paper vs in here are: 00 = 0, 10 = 1, 11 = 2, 01 = 3 - for (std::size_t ix = 0; ix < 3; ix++) + for (unsigned int ix = 0; ix < 3; ix++) { typename GT::Vector_3 X; if (ix == 0) @@ -331,7 +331,7 @@ std::array interpolated_corrected_anisotropic_measure_fa const typename GT::Vector_3 u2xX = cross_product(u[2], X); const typename GT::Vector_3 u3xX = cross_product(u[3], X); - for (std::size_t iy = 0; iy < 3; iy++) + for (unsigned int iy = 0; iy < 3; iy++) muXY[ix * 3 + iy] = (1.0 / 72.0) * ( u[0][iy] * (u0xX * (-x[0] - 11 * x[1] + 13 * x[3] - x[2]) @@ -364,7 +364,7 @@ std::array interpolated_corrected_anisotropic_measure_fa // getting center of points typename GT::Vector_3 xc = std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); - xc /= n; + xc /= static_cast(n); // getting unit average normal of points typename GT::Vector_3 uc = @@ -418,7 +418,7 @@ typename GT::FT face_in_ball_ratio(const std::vector& x, // getting center of points typename GT::Vector_3 xm = std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); - xm /= n; + xm /= static_cast(n); typename GT::FT d_min = (xm - c).squared_length(); typename GT::FT d_max = d_min; From 2ccabc92899980c65ce17ff9e165905af3d2e6c0 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 29 Jan 2023 09:39:39 +0200 Subject: [PATCH 113/161] renaming files --- ....h => interpolated_corrected_curvatures.h} | 20 +++++++++---------- .../include/CGAL/license/gpl_package_list.txt | 2 +- ...olated_corrected_curvatures_example_PH.cpp | 2 +- ...olated_corrected_curvatures_example_SM.cpp | 2 +- ...ed_corrected_curvatures_vertex_example.cpp | 2 +- ....h => interpolated_corrected_curvatures.h} | 8 ++++---- ...test_interpolated_corrected_curvatures.cpp | 2 +- .../Display/Display_property_plugin.cpp | 2 +- ..._corrected_principal_curvatures_plugin.cpp | 2 +- 9 files changed, 21 insertions(+), 21 deletions(-) rename Installation/include/CGAL/license/Polygon_mesh_processing/{interpolated_corrected_curvature_measures.h => interpolated_corrected_curvatures.h} (83%) rename Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/{Curvatures/interpolated_corrected_curvature_measures.h => interpolated_corrected_curvatures.h} (99%) diff --git a/Installation/include/CGAL/license/Polygon_mesh_processing/interpolated_corrected_curvature_measures.h b/Installation/include/CGAL/license/Polygon_mesh_processing/interpolated_corrected_curvatures.h similarity index 83% rename from Installation/include/CGAL/license/Polygon_mesh_processing/interpolated_corrected_curvature_measures.h rename to Installation/include/CGAL/license/Polygon_mesh_processing/interpolated_corrected_curvatures.h index 15e2a79af8a9..e484daaf5eae 100644 --- a/Installation/include/CGAL/license/Polygon_mesh_processing/interpolated_corrected_curvature_measures.h +++ b/Installation/include/CGAL/license/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -11,15 +11,15 @@ // // Warning: this file is generated, see include/CGAL/licence/README.md -#ifndef CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H -#define CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H +#ifndef CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURES_H +#define CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURES_H #include #include -#ifdef CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_COMMERCIAL_LICENSE +#ifdef CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURES_COMMERCIAL_LICENSE -# if CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE +# if CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURES_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE # if defined(CGAL_LICENSE_WARNING) @@ -33,22 +33,22 @@ You get this error, as you defined CGAL_LICENSE_ERROR." # endif // CGAL_LICENSE_ERROR -# endif // CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE +# endif // CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURES_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE -#else // no CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_COMMERCIAL_LICENSE +#else // no CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURES_COMMERCIAL_LICENSE # if defined(CGAL_LICENSE_WARNING) - CGAL_pragma_warning("\nThe macro CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_COMMERCIAL_LICENSE is not defined." + CGAL_pragma_warning("\nThe macro CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURES_COMMERCIAL_LICENSE is not defined." "\nYou use the CGAL Polygon Mesh Processing - Interpolated Corrected Curvatures package under " "the terms of the GPLv3+.") # endif // CGAL_LICENSE_WARNING # ifdef CGAL_LICENSE_ERROR -# error "The macro CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_COMMERCIAL_LICENSE is not defined.\ +# error "The macro CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURES_COMMERCIAL_LICENSE is not defined.\ You use the CGAL Polygon Mesh Processing - Interpolated Corrected Curvatures 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_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_COMMERCIAL_LICENSE +#endif // no CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURES_COMMERCIAL_LICENSE -#endif // CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H +#endif // CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURES_H diff --git a/Installation/include/CGAL/license/gpl_package_list.txt b/Installation/include/CGAL/license/gpl_package_list.txt index c6ac0b619df0..14d68b5d9a6a 100644 --- a/Installation/include/CGAL/license/gpl_package_list.txt +++ b/Installation/include/CGAL/license/gpl_package_list.txt @@ -51,7 +51,7 @@ Polygon_mesh_processing/connected_components Polygon Mesh Processing - Connected Polygon_mesh_processing/corefinement Polygon Mesh Processing - Corefinement Polygon_mesh_processing/core Polygon Mesh Processing - Core Polygon_mesh_processing/distance Polygon Mesh Processing - Distance -Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures Polygon Mesh Processing - Interpolated Corrected Curvatures +Polygon_mesh_processing/interpolated_corrected_curvatures Polygon Mesh Processing - Interpolated Corrected Curvatures Polygon_mesh_processing/measure Polygon Mesh Processing - Geometric Measure Polygon_mesh_processing/meshing_hole_filling Polygon Mesh Processing - Meshing and Hole Filling Polygon_mesh_processing/orientation Polygon Mesh Processing - Orientation diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_PH.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_PH.cpp index 0d1ffcfa35d1..3cf675c53ca4 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_PH.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_PH.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_SM.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_SM.cpp index 6a991bafdfb8..d92ec7b8096d 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_SM.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_SM.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex_example.cpp index a897066facb6..beb2eda31c04 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex_example.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h similarity index 99% rename from Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h rename to Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index cfe3b79b6e19..7f0bbc8197ce 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/Curvatures/interpolated_corrected_curvature_measures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -11,10 +11,10 @@ // Author(s) : Hossam Saeed // -#ifndef CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H -#define CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURE_MEASURES_H +#ifndef CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURES_H +#define CGAL_POLYGON_MESH_PROCESSING_INTERPOLATED_CORRECTED_CURVATURES_H -#include +#include #include #include @@ -1550,4 +1550,4 @@ template -#include +#include #include #include #include diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index 67a2061fc5e8..f515c7c12e08 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include "Scene_points_with_normal_item.h" diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp index e5a01f7b3c3b..8bb6a229967b 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp @@ -11,7 +11,7 @@ #include #include -#include +#include using namespace CGAL::Three; class Polyhedron_demo_interpolated_corrected_principal_curvatures_and_directions_plugin : From eef0f5fd80cf703486b1f72b862898f45c266d00 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 31 Jan 2023 11:23:46 +0200 Subject: [PATCH 114/161] removed unused parameter --- .../interpolated_corrected_curvatures.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index 7f0bbc8197ce..a71fdf164449 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -27,7 +27,6 @@ #include #include #include -#include #define EXPANDING_RADIUS_EPSILON 1e-6 @@ -231,8 +230,7 @@ typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::ve } template -typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std::vector& u, - const std::vector& x = {}) +typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std::vector& u) { const std::size_t n = u.size(); CGAL_precondition(n >= 3); From 4f4eeea292f09dc7e7a84c55787346f2ec51a607 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 31 Jan 2023 11:55:15 +0200 Subject: [PATCH 115/161] removing _example suffix (renaming) --- .../Polygon_mesh_processing.txt | 6 +++--- .../doc/Polygon_mesh_processing/examples.txt | 6 +++--- .../examples/Polygon_mesh_processing/CMakeLists.txt | 12 ++++++------ ....cpp => interpolated_corrected_curvatures_PH.cpp} | 0 ....cpp => interpolated_corrected_curvatures_SM.cpp} | 0 ... => interpolated_corrected_curvatures_vertex.cpp} | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) rename Polygon_mesh_processing/examples/Polygon_mesh_processing/{interpolated_corrected_curvatures_example_PH.cpp => interpolated_corrected_curvatures_PH.cpp} (100%) rename Polygon_mesh_processing/examples/Polygon_mesh_processing/{interpolated_corrected_curvatures_example_SM.cpp => interpolated_corrected_curvatures_SM.cpp} (100%) rename Polygon_mesh_processing/examples/Polygon_mesh_processing/{interpolated_corrected_curvatures_vertex_example.cpp => interpolated_corrected_curvatures_vertex.cpp} (98%) 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 751db0992492..1da140d96791 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 @@ -940,7 +940,7 @@ The following example illustrates how to compute the curvatures on vertices and store them in property maps provided by the class `Surface_mesh`. -\cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_example_SM.cpp} +\cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp} \subsection ICCExamplePH Interpolated Corrected Curvatures on a Polyhedron Example @@ -949,14 +949,14 @@ compute the curvatures on vertices and store them in dynamic property maps as the class `Polyhedron_3` does not provide storage for the curvatures. -\cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_example_PH.cpp} +\cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp} \subsection ICCExampleSV Interpolated Corrected Curvatures on a Vertex Example The following example illustrates how to compute the curvatures on a specific vertex -\cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_vertex_example.cpp} +\cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp} **************************************** \section PMPSlicer Slicer diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index 33e68eea8091..3a5620d82424 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -19,9 +19,9 @@ \example Polygon_mesh_processing/refine_fair_example.cpp \example Polygon_mesh_processing/mesh_slicer_example.cpp \example Polygon_mesh_processing/isotropic_remeshing_example.cpp -\example Polygon_mesh_processing/interpolated_corrected_curvatures_example_SM.cpp -\example Polygon_mesh_processing/interpolated_corrected_curvatures_example_PH.cpp -\example Polygon_mesh_processing/interpolated_corrected_curvatures_vertex_example.cpp +\example Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp +\example Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp +\example Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp \example Polygon_mesh_processing/delaunay_remeshing_example.cpp \example Polygon_mesh_processing/compute_normals_example_Polyhedron.cpp \example Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index 93e321e837c8..19a3aaf5313d 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -105,12 +105,12 @@ create_single_source_cgal_program("cc_compatible_orientations.cpp") if(TARGET CGAL::Eigen3_support) - create_single_source_cgal_program("interpolated_corrected_curvatures_example_SM.cpp") - target_link_libraries(interpolated_corrected_curvatures_example_SM PUBLIC CGAL::Eigen3_support) - create_single_source_cgal_program("interpolated_corrected_curvatures_example_PH.cpp") - target_link_libraries(interpolated_corrected_curvatures_example_PH PUBLIC CGAL::Eigen3_support) - create_single_source_cgal_program("interpolated_corrected_curvatures_vertex_example.cpp") - target_link_libraries(interpolated_corrected_curvatures_vertex_example PUBLIC CGAL::Eigen3_support) + create_single_source_cgal_program("interpolated_corrected_curvatures_SM.cpp") + target_link_libraries(interpolated_corrected_curvatures_SM PUBLIC CGAL::Eigen3_support) + create_single_source_cgal_program("interpolated_corrected_curvatures_PH.cpp") + target_link_libraries(interpolated_corrected_curvatures_PH PUBLIC CGAL::Eigen3_support) + create_single_source_cgal_program("interpolated_corrected_curvatures_vertex.cpp") + target_link_libraries(interpolated_corrected_curvatures_vertex PUBLIC CGAL::Eigen3_support) endif() diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_PH.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp similarity index 100% rename from Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_PH.cpp rename to Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_SM.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp similarity index 100% rename from Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_example_SM.cpp rename to Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp similarity index 98% rename from Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex_example.cpp rename to Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp index beb2eda31c04..47fbcfd68711 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp @@ -37,7 +37,7 @@ int main(int argc, char* argv[]) PMP::interpolated_corrected_principal_curvatures_and_directions_one_vertex(smesh, v); // we can also specify a ball radius for expansion and a user defined vertex normals map using - // named parameters. Refer to interpolated_corrected_curvatures_example_SM.cpp to see example usage. + // named parameters. Refer to interpolated_corrected_curvatures_SM.cpp to see example usage. // Can also use interpolated_corrected_curvatures_one_vertex() to compute multiple curvatures // on the vertex at the same time. This is more efficient than computing each one separately. From bd5d9df950ea845efa3b6bdf926a1b548c8c4ffd Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 31 Jan 2023 11:57:00 +0200 Subject: [PATCH 116/161] fix some functions were passing x to interpolated_corrected_gaussian_curvature_measure_face() when it was not needed (and causes a compilation error for 1 vertex) --- .../interpolated_corrected_curvatures.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index a71fdf164449..3c7c647e8a6b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -518,7 +518,7 @@ Vertex_measures interpolated_corrected_measures_one_vertex_no_radius( vertex_measures.mean_curvature_measure += interpolated_corrected_mean_curvature_measure_face(u, x); if (is_gaussian_curvature_selected) - vertex_measures.gaussian_curvature_measure += interpolated_corrected_gaussian_curvature_measure_face(u, x); + vertex_measures.gaussian_curvature_measure += interpolated_corrected_gaussian_curvature_measure_face(u); if (is_principal_curvatures_and_directions_selected) { @@ -594,7 +594,7 @@ Vertex_measures interpolated_corrected_measures_one_vertex( vertex_measures.mean_curvature_measure += f_ratio * interpolated_corrected_mean_curvature_measure_face(u, x); if (is_gaussian_curvature_selected) - vertex_measures.gaussian_curvature_measure += f_ratio * interpolated_corrected_gaussian_curvature_measure_face(u, x); + vertex_measures.gaussian_curvature_measure += f_ratio * interpolated_corrected_gaussian_curvature_measure_face(u); if (is_principal_curvatures_and_directions_selected) { From 6a7e7d267ebf9be9eedd306ce311b7c0d5c7dc2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 31 Jan 2023 11:56:48 +0100 Subject: [PATCH 117/161] fix link --- .../interpolated_corrected_curvatures.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index 3c7c647e8a6b..88110842b67d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -1273,11 +1273,11 @@ template Date: Tue, 31 Jan 2023 12:06:10 +0100 Subject: [PATCH 118/161] remove unused variable --- .../demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index f515c7c12e08..c4c7d6a3c916 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -839,7 +839,7 @@ private Q_SLOTS: } - double outMin = 0, outMax = 5 * maxEdgeLength, base = 1.2; + double outMax = 5 * maxEdgeLength, base = 1.2; expand_radius = (pow(base, val) - 1) * outMax / (pow(base, sliderMax) - 1); dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius : %1").arg(expand_radius)); From e71fcd899a44a07a53d65fbdf976d6224141fd0c Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 2 Feb 2023 02:15:34 +0200 Subject: [PATCH 119/161] removed unused enum --- .../interpolated_corrected_curvatures.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index 88110842b67d..7adc37f0b4a2 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -92,12 +92,6 @@ typename GT::FT average_edge_length(const PolygonMesh& pmesh) { return avg_edge_length; } -enum Curvature_measure_index { - MU0_AREA_MEASURE, ///< corrected area density - MU1_MEAN_CURVATURE_MEASURE, ///< corrected mean curvature density - MU2_GAUSSIAN_CURVATURE_MEASURE ///< corrected gaussian curvature density -}; - template struct Vertex_measures { typename GT::FT area_measure = 0; From fcbc89b50314db6f17508bcc8a63046c18c910fc Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 2 Feb 2023 12:07:20 +0200 Subject: [PATCH 120/161] added comments for clarity --- .../interpolated_corrected_curvatures.h | 63 +++++++++++++++---- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index 7adc37f0b4a2..952a7e749ecf 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -118,7 +118,7 @@ typename GT::FT interpolated_corrected_area_measure_face(const std::vector interpolated_corrected_anisotropic_measure_fa muXY[ix * 3 + iy] = 0.5 * um * (cross_product(u02[iy] * X, x01) - cross_product(u01[iy] * X, x02)); } } - // Quad: use bilinear interpolation formula + // Quad: use the bilinear interpolation formula else if (n == 4) { // for the formulas below, values of verices 2 & 3 are swapped (compared to paper) to correct order. @@ -412,6 +412,7 @@ typename GT::FT face_in_ball_ratio(const std::vector& x, std::accumulate(x.begin(), x.end(), typename GT::Vector_3(0, 0, 0)); xm /= static_cast(n); + // computing squared distance of furthest and closest point to ball center typename GT::FT d_min = (xm - c).squared_length(); typename GT::FT d_max = d_min; @@ -422,12 +423,14 @@ typename GT::FT face_in_ball_ratio(const std::vector& x, d_min = (std::min)(d_sq, d_min); } + // if the furthest point is inside ball, return 1 if (d_max <= r * r) return 1.0; + // if the closest point is outside ball, return 0 else if (r * r <= d_min) return 0.0; + // else, approximate inclusion ratio of the triangle: d_max = sqrt(d_max); d_min = sqrt(d_min); - return (r - d_min) / (d_max - d_min); } @@ -438,17 +441,22 @@ Principal_curvatures_and_directions principal_curvatures_and_directions_from const typename GT::Vector_3 u_GT ) { + // putting anisotropic measure in matrix form Eigen::Matrix v_muXY = Eigen::Matrix::Zero(); for (std::size_t ix = 0; ix < 3; ix++) for (std::size_t iy = 0; iy < 3; iy++) v_muXY(ix, iy) = anisotropic_measure[ix * 3 + iy]; + // constant factor K to force the principal direction eigenvectors to be tangential to the surface Eigen::Matrix u(u_GT.x(), u_GT.y(), u_GT.z()); const typename GT::FT K = 1000 * v_mu0; + // symmetrizing and adding the constant term v_muXY = 0.5 * (v_muXY + v_muXY.transpose()) + K * u * u.transpose(); + + // computing eigenvalues and eigenvectors Eigen::SelfAdjointEigenSolver> eigensolver; eigensolver.computeDirect(v_muXY); @@ -462,6 +470,7 @@ Principal_curvatures_and_directions principal_curvatures_and_directions_from const typename GT::Vector_3 min_eig_vec(eig_vecs(0, 1), eig_vecs(1, 1), eig_vecs(2, 1)); const typename GT::Vector_3 max_eig_vec(eig_vecs(0, 0), eig_vecs(1, 0), eig_vecs(2, 0)); + // returning principal curvatures and directions (with the correct sign) return Principal_curvatures_and_directions( (v_mu0 != 0.0) ? -eig_vals[1] / v_mu0 : 0.0, (v_mu0 != 0.0) ? -eig_vals[0] / v_mu0 : 0.0, @@ -470,6 +479,7 @@ Principal_curvatures_and_directions principal_curvatures_and_directions_from ); } +// measures are computed for faces only if they are adjacent to the vertex template Vertex_measures interpolated_corrected_measures_one_vertex_no_radius( const PolygonMesh& pmesh, @@ -495,9 +505,11 @@ Vertex_measures interpolated_corrected_measures_one_vertex_no_radius( std::vector x; std::vector u; + // compute for each face around the vertex (except the null (boundary) face) for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { if (f != boost::graph_traits::null_face()) { + // looping over vertices in face to get point coordinates and normal vectors for (Vertex_descriptor vi : vertices_around_face(halfedge(f, pmesh), pmesh)) { Point_3 pi = get(vpm, vi); @@ -506,6 +518,7 @@ Vertex_measures interpolated_corrected_measures_one_vertex_no_radius( u.push_back(ui); } + // compute measures for selected curvatures (area is always computed) vertex_measures.area_measure += interpolated_corrected_area_measure_face(u, x); if (is_mean_curvature_selected) @@ -528,7 +541,7 @@ Vertex_measures interpolated_corrected_measures_one_vertex_no_radius( return vertex_measures; } - +// measures are computed for faces only if they are in the ball of radius 'radius' centered at the vertex template Vertex_measures interpolated_corrected_measures_one_vertex( const PolygonMesh& pmesh, @@ -547,6 +560,7 @@ Vertex_measures interpolated_corrected_measures_one_vertex( typedef typename GT::Vector_3 Vector_3; typedef typename GT::FT FT; + // the ball expansion is done using a BFS traversal from the vertex std::queue bfs_queue; std::unordered_set bfs_visited; @@ -568,8 +582,7 @@ Vertex_measures interpolated_corrected_measures_one_vertex( Face_descriptor fi = bfs_queue.front(); bfs_queue.pop(); - // looping over vertices in face to get point coordinates - + // looping over vertices in face to get point coordinates and normal vectors for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) { Point_3 pi = get(vpm, vi); @@ -578,8 +591,11 @@ Vertex_measures interpolated_corrected_measures_one_vertex( u.push_back(ui); } + // approximate inclusion ratio of the face in the ball const FT f_ratio = face_in_ball_ratio(x, radius, c); + // if it is not 0 (not completely outside), compute measures for selected curvatures (area is always computed) + // and add neighboring faces to the bfs queue if (f_ratio != 0.0) { vertex_measures.area_measure += f_ratio * interpolated_corrected_area_measure_face(u, x); @@ -613,6 +629,7 @@ Vertex_measures interpolated_corrected_measures_one_vertex( return vertex_measures; } +// computes selected curvatures for one specific vertex template void interpolated_corrected_curvatures_one_vertex( @@ -640,23 +657,29 @@ template::value) compute_vertex_normals(pmesh, vnm, np); + // if no radius is given, we pass -1 which will make the expansion be only on the incident faces instead of a ball typename GT::FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1); + // if the radius is 0, we use a small epsilon to expand the ball (scaled with the average edge length) if (radius == 0) radius = average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; + // get parameters (pointers) for curvatures typename GT::FT* vertex_mean_curvature = choose_parameter(get_parameter(np, internal_np::vertex_mean_curvature), nullptr); typename GT::FT* vertex_gaussian_curvature = choose_parameter(get_parameter(np, internal_np::vertex_gaussian_curvature), nullptr); Principal_curvatures_and_directions* vertex_principal_curvatures_and_directions = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions), nullptr); + // determine which curvatures are selected (by checking if the pointers are not null) const bool is_mean_curvature_selected = (vertex_mean_curvature != nullptr); const bool is_gaussian_curvature_selected = (vertex_gaussian_curvature != nullptr); const bool is_principal_curvatures_and_directions_selected = (vertex_principal_curvatures_and_directions != nullptr); Vertex_measures vertex_measures; + // if the radius is negative, we do not expand the ball (only the incident faces) if (radius < 0) { vertex_measures = interpolated_corrected_measures_one_vertex_no_radius( @@ -683,6 +706,7 @@ template principal_curvatures_and_directions = principal_curvatures_and_directions_from_anisotropic_measures( vertex_measures.anisotropic_measure, @@ -783,11 +808,14 @@ class Interpolated_corrected_curvatures_computer vnm = choose_parameter(get_parameter(np, internal_np::vertex_normal_map), get(Vector_map_tag(), pmesh)); + // if no normal map is given, compute normals if (is_default_parameter::value) compute_vertex_normals(pmesh, vnm, np); + // if no radius is given, we pass -1 which will make the expansion be only on the incident faces instead of a ball const FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1); + // check which curvature maps are provided by the user (determines which curvatures are computed) is_mean_curvature_selected = !is_default_parameter::value; is_gaussian_curvature_selected = !is_default_parameter::value; is_principal_curvatures_and_directions_selected = !is_default_parameter::value; @@ -800,6 +828,7 @@ class Interpolated_corrected_curvatures_computer } void set_ball_radius(const FT radius) { + // if given radius is 0, we use a small epsilon to expand the ball (scaled by the average edge length) if (radius == 0) ball_radius = average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; else @@ -825,6 +854,8 @@ class Interpolated_corrected_curvatures_computer private: + // Computes the (selected) interpolated corrected measures for all faces + // and stores them in the property maps void interpolated_corrected_selected_measures_all_faces() { std::vector x; @@ -854,14 +885,17 @@ class Interpolated_corrected_curvatures_computer } } + // expand the measures of the faces incident to v Vertex_measures expand_interpolated_corrected_measure_vertex_no_radius(Vertex_descriptor v) { Vertex_measures vertex_measures; + // add the measures of the faces incident to v (excluding the null (boundary) face) for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { if (f == boost::graph_traits::null_face()) continue; + // only add the measures for the selected curvatures (area measure is always added) vertex_measures.area_measure += get(mu0_map, f); if (is_mean_curvature_selected) @@ -881,8 +915,10 @@ class Interpolated_corrected_curvatures_computer return vertex_measures; } + // expand the measures of the faces inside the ball of radius r around v Vertex_measures expand_interpolated_corrected_measure_vertex(Vertex_descriptor v) { + // the ball expansion is done using a BFS traversal from the vertex std::queue bfs_queue; std::unordered_set bfs_visited; @@ -891,6 +927,7 @@ class Interpolated_corrected_curvatures_computer Vertex_measures vertex_measures; + // add the measures of the faces incident to v (excluding the null (boundary) face) for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { if (f != boost::graph_traits::null_face()) { @@ -910,8 +947,11 @@ class Interpolated_corrected_curvatures_computer x.push_back(Vector_3(pi.x(), pi.y(), pi.z())); } + // compute the inclusion ratio of the face in the ball const FT f_ratio = face_in_ball_ratio(x, ball_radius, c); + // if the face is inside the ball, add the measures + // only add the measures for the selected curvatures (area measure is always added) if (f_ratio != 0.0) { vertex_measures.area_measure += f_ratio * get(mu0_map, fi); @@ -947,10 +987,13 @@ class Interpolated_corrected_curvatures_computer for (Vertex_descriptor v : vertices(pmesh)) { + // expand the computed measures (on faces) to the vertices Vertex_measures vertex_measures = (ball_radius < 0) ? expand_interpolated_corrected_measure_vertex_no_radius(v) : expand_interpolated_corrected_measure_vertex(v); + // compute the selected curvatures from the expanded measures and store them in the property maps + // if the area measure is zero, the curvature is set to zero if (is_mean_curvature_selected) { vertex_measures.area_measure != 0 ? put(mean_curvature_map, v, 0.5 * vertex_measures.mean_curvature_measure / vertex_measures.area_measure) : @@ -964,6 +1007,7 @@ class Interpolated_corrected_curvatures_computer } if (is_principal_curvatures_and_directions_selected) { + // compute the principal curvatures and directions from the anisotropic measure const Vector_3 v_normal = get(vnm, v); const Principal_curvatures_and_directions principal_curvatures_and_directions = principal_curvatures_and_directions_from_anisotropic_measures( vertex_measures.anisotropic_measure, @@ -1402,7 +1446,6 @@ template::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { - // use interpolated_corrected_curvatures_one_vertex to compute mean curvature typename GT::FT mean_curvature; interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_mean_curvature(&mean_curvature)); return mean_curvature; @@ -1468,7 +1511,6 @@ template::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { - // use interpolated_corrected_curvatures_one_vertex to compute gaussian curvature typename GT::FT gc; interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_gaussian_curvature(&gc)); return gc; @@ -1533,7 +1575,6 @@ template::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { - // use interpolated_corrected_curvatures_one_vertex to compute principal curvatures Principal_curvatures_and_directions pcd; interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_principal_curvatures_and_directions(&pcd)); return pcd; From ec4312695a5136411b53ae572f9445fd7e385983 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Thu, 2 Feb 2023 12:23:50 +0200 Subject: [PATCH 121/161] remove unused var + minor change --- .../interpolated_corrected_curvatures.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index 952a7e749ecf..b298f8cf495f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -497,9 +497,6 @@ Vertex_measures interpolated_corrected_measures_one_vertex_no_radius( typedef typename GT::Vector_3 Vector_3; typedef typename GT::FT FT; - std::queue bfs_queue; - std::unordered_set bfs_visited; - Vertex_measures vertex_measures; std::vector x; @@ -814,6 +811,7 @@ class Interpolated_corrected_curvatures_computer // if no radius is given, we pass -1 which will make the expansion be only on the incident faces instead of a ball const FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1); + set_ball_radius(radius); // check which curvature maps are provided by the user (determines which curvatures are computed) is_mean_curvature_selected = !is_default_parameter::value; @@ -823,8 +821,6 @@ class Interpolated_corrected_curvatures_computer mean_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_mean_curvature_map), Default_scalar_map()); gaussian_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_gaussian_curvature_map), Default_scalar_map()); principal_curvatures_and_directions_map = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions_map), Default_principal_map()); - - set_ball_radius(radius); } void set_ball_radius(const FT radius) { From 19fd037731d50f20e703c6dddc9997441bad9d06 Mon Sep 17 00:00:00 2001 From: David Coeurjolly Date: Mon, 20 Feb 2023 11:20:07 +0100 Subject: [PATCH 122/161] Minor API doc edits --- .../interpolated_corrected_curvatures.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index b298f8cf495f..ea01b0fe4c34 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -1022,8 +1022,8 @@ class Interpolated_corrected_curvatures_computer * \ingroup PMP_corrected_curvatures_grp * * Computes the interpolated corrected curvatures across the mesh, based on the provided property maps. -* By providing mean, gaussian and/or principal curvature property maps as named parameters, the user -* can choose which curvatures to compute. +* By providing mean, gaussian and/or principal curvature and direction property maps as named parameters, the user +* can choose which quantites to compute. * * @tparam PolygonMesh a model of `FaceListGraph`. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". @@ -1039,7 +1039,7 @@ class Interpolated_corrected_curvatures_computer * `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 +* \cgalParamExtra{If this parameter is omitted, an internal property map forBy * `CGAL::vertex_point_t` must be available in `PolygonMesh`.} * \cgalParamNEnd * @@ -1301,9 +1301,10 @@ template Date: Wed, 22 Mar 2023 18:13:27 +0100 Subject: [PATCH 123/161] first pass on the API --- .../Polygon_mesh_processing.txt | 2 +- ...terpolated_corrected_curvatures_vertex.cpp | 7 +- .../interpolated_corrected_curvatures.h | 695 ++++++++++-------- 3 files changed, 386 insertions(+), 318 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 1da140d96791..2eb85a80bfbb 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 @@ -1166,7 +1166,7 @@ is covered by a set of prisms, where each prism is an offset for an input triang That is, the implementation in \cgal does not use indirect predicates. The interpolated corrected curvatures were implemented during GSoC 2022. This was implemented by Hossam Saeed and under -supervision of Sebastien Loriot, Jaques-Olivier Lachaud and David Coeurjolly. The implementation is based on \cgalCite{lachaud2020}. +supervision of David Coeurjolly, Jaques-Olivier Lachaud, and Sébastien Loriot. The implementation is based on \cgalCite{lachaud2020}. DGtal's implementation was also used as a reference during the project. diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp index 47fbcfd68711..e5845a1b3a5a 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp @@ -10,7 +10,6 @@ namespace PMP = CGAL::Polygon_mesh_processing; typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic_kernel; -typedef Epic_kernel::FT FT; typedef CGAL::Surface_mesh Surface_Mesh; typedef boost::graph_traits::vertex_descriptor vertex_descriptor; @@ -31,10 +30,10 @@ int main(int argc, char* argv[]) // loop over vertices and use vertex_descriptor to compute a curvature on one vertex for (vertex_descriptor v : vertices(smesh)) { - FT h = PMP::interpolated_corrected_mean_curvature_one_vertex(smesh, v); - FT g = PMP::interpolated_corrected_gaussian_curvature_one_vertex(smesh, v); + double h = PMP::interpolated_corrected_mean_curvature_one_vertex(smesh, v); + double g = PMP::interpolated_corrected_gaussian_curvature_one_vertex(smesh, v); PMP::Principal_curvatures_and_directions p = - PMP::interpolated_corrected_principal_curvatures_and_directions_one_vertex(smesh, v); + PMP::interpolated_corrected_principal_curvatures_and_directions_one_vertex(smesh, v); // we can also specify a ball radius for expansion and a user defined vertex normals map using // named parameters. Refer to interpolated_corrected_curvatures_SM.cpp to see example usage. diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index ea01b0fe4c34..24c1d12a59e3 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -28,8 +28,6 @@ #include #include -#define EXPANDING_RADIUS_EPSILON 1e-6 - namespace CGAL { namespace Polygon_mesh_processing { @@ -39,7 +37,7 @@ namespace Polygon_mesh_processing { * * \brief a struct for storing principal curvatures and directions. * - * @tparam GT is the geometric traits class. + * @tparam GT is the geometric traits class, model of `Kernel`. */ template struct Principal_curvatures_and_directions { @@ -662,7 +660,7 @@ template(pmesh) * EXPANDING_RADIUS_EPSILON; + radius = average_edge_length(pmesh) * 1e-6; // get parameters (pointers) for curvatures typename GT::FT* vertex_mean_curvature = choose_parameter(get_parameter(np, internal_np::vertex_mean_curvature), nullptr); @@ -826,7 +824,7 @@ class Interpolated_corrected_curvatures_computer void set_ball_radius(const FT radius) { // if given radius is 0, we use a small epsilon to expand the ball (scaled by the average edge length) if (radius == 0) - ball_radius = average_edge_length(pmesh) * EXPANDING_RADIUS_EPSILON; + ball_radius = average_edge_length(pmesh) * 1e-6; else ball_radius = radius; } @@ -1021,7 +1019,7 @@ class Interpolated_corrected_curvatures_computer /** * \ingroup PMP_corrected_curvatures_grp * -* Computes the interpolated corrected curvatures across the mesh, based on the provided property maps. +* computes the interpolated corrected curvatures across the mesh `pmesh`. * By providing mean, gaussian and/or principal curvature and direction property maps as named parameters, the user * can choose which quantites to compute. * @@ -1029,66 +1027,70 @@ class Interpolated_corrected_curvatures_computer * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * * @param pmesh the polygon mesh. -* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below +* @param np an 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 `%Point_3` as value type} +* `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 forBy +* \cgalParamExtra{If this parameter is omitted, an internal property map for * `CGAL::vertex_point_t` must be available in `PolygonMesh`.} * \cgalParamNEnd * * \cgalParamNBegin{vertex_normal_map} * \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} * \cgalParamType{a class model of `ReadablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` -* as key type and `%Vector_3` as value type} +* `boost::graph_traits::%vertex_descriptor` +* as key type and `GT::Vector_3` as value type} * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} * \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using compute_vertex_normals()} +* computed using `compute_vertex_normals()`} +* \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 * * \cgalParamNBegin{vertex_mean_curvature_map} * \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} * \cgalParamType{a class model of `WritablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` -* as key type and `%FT` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* `boost::graph_traits::%vertex_descriptor` +* as key type and `GT::FT` as value type} * \cgalParamExtra{If this parameter is omitted, mean curvatures won't be computed} * \cgalParamNEnd * * \cgalParamNBegin{vertex_gaussian_curvature_map} * \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} * \cgalParamType{a class model of `WritablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` -* as key type and `%FT` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* `boost::graph_traits::%vertex_descriptor` +* as key type and `GT::FT` as value type} * \cgalParamExtra{If this parameter is omitted, gaussian curvatures won't be computed} * \cgalParamNEnd * -* * \cgalParamNBegin{vertex_principal_curvatures_and_directions_map} * \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} * \cgalParamType{a class model of `WritablePropertyMap` with -* `boost::graph_traits::%Vertex_descriptor` -* as key type and `%Principal_curvatures_and_directions` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t>(), pmesh)`} +* `boost::graph_traits::%vertex_descriptor` +* as key type and `Principal_curvatures_and_directions` as value type} * \cgalParamExtra{If this parameter is omitted, mean principal won't be computed} * \cgalParamNEnd * * \cgalParamNBegin{ball_radius} -* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures +* \cgalParamDescription{a strictly positive scalar value specifying the radius used for expanding curvature measures * by summing measures of faces inside a ball of this radius centered at the * vertex expanded from. The summed face measures are weighted by their * inclusion ratio inside this ball.} * \cgalParamType{`GT::FT`} * \cgalParamDefault{`-1`} -* \cgalParamExtra{If this parameter is omitted (`-1`), the epansion is then just a sum of +* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion is then just a sum of * measures on faces around the vertex} * \cgalParamNEnd * @@ -1099,9 +1101,9 @@ class Interpolated_corrected_curvatures_computer * @see `interpolated_corrected_principal_curvatures_and_directions()` */ template - void interpolated_corrected_curvatures(const PolygonMesh& pmesh, - const NamedParameters& np = parameters::default_values()) + typename NamedParameters = parameters::Default_named_parameters> +void interpolated_corrected_curvatures(const PolygonMesh& pmesh, + const NamedParameters& np = parameters::default_values()) { internal::Interpolated_corrected_curvatures_computer(pmesh, np); } @@ -1109,25 +1111,25 @@ template::%Vertex_descriptor` as key type and `GT::FT` as value type. +* @tparam VertexCurvatureMap a model of `WritablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` as key type and `GT::FT` as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * * @param pmesh the polygon mesh. * @param vcm the vertex property map in which the computed mean curvatures are stored. * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters". +* `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 `%Point_3` as value type} +* `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`.} @@ -1136,11 +1138,18 @@ template::%Vertex_descriptor` -* as key type and `%Vector_3` as value type} +* `boost::graph_traits::%vertex_descriptor` +* as key type and `GT::Vector_3` as value type} * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} * \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using compute_vertex_normals()} +* computed using `compute_vertex_normals()`} +* \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 * * \cgalParamNBegin{ball_radius} @@ -1162,10 +1171,10 @@ template - void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, - VertexCurvatureMap& vcm, - const NamedParameters& np = parameters::default_values()) + typename NamedParameters = parameters::Default_named_parameters> +void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, + VertexCurvatureMap vcm, + const NamedParameters& np = parameters::default_values()) { interpolated_corrected_curvatures(pmesh, np.vertex_mean_curvature_map(vcm)); } @@ -1173,24 +1182,24 @@ template::%Vertex_descriptor` as key type and `GT::FT` as value type. +* @tparam VertexCurvatureMap a model of `WritablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` as key type and `GT::FT` as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * * @param pmesh the polygon mesh. * @param vcm the vertex property map in which the computed gaussian curvatures are stored. * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters". +* `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` +* `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 @@ -1199,12 +1208,19 @@ template::%Vertex_descriptor` +* \cgalParamType{a class model of `GT::ReadablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` * as key type and `%Vector_3` as value type} * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} * \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using compute_vertex_normals()} +* computed using `compute_vertex_normals()`} +* \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 * * \cgalParamNBegin{ball_radius} @@ -1225,10 +1241,10 @@ template - void interpolated_corrected_gaussian_curvature(const PolygonMesh& pmesh, - VertexCurvatureMap& vcm, - const NamedParameters& np = parameters::default_values()) + typename NamedParameters = parameters::Default_named_parameters> +void interpolated_corrected_gaussian_curvature(const PolygonMesh& pmesh, + VertexCurvatureMap vcm, + const NamedParameters& np = parameters::default_values()) { interpolated_corrected_curvatures(pmesh, np.vertex_gaussian_curvature_map(vcm)); } @@ -1236,26 +1252,26 @@ template::%Vertex_descriptor` as key type and -* `%Principal_curvatures_and_directions` as value type. +* @tparam VertexCurvatureMap a model of `WritablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` as key type and +* `Principal_curvatures_and_directions` as value type. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * * @param pmesh the polygon mesh. * @param vcm the vertex property map in which the computed principal curvatures are stored. * @param np an 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 `%Point_3` as value type} +* `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`.} @@ -1264,11 +1280,18 @@ template::%Vertex_descriptor` -* as key type and `%Vector_3` as value type} +* `boost::graph_traits::%vertex_descriptor` +* as key type and `GT::Vector_3` as value type} * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} * \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using compute_vertex_normals()} +* computed using `compute_vertex_normals()`} +* \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 * * \cgalParamNBegin{ball_radius} @@ -1289,289 +1312,335 @@ template - void interpolated_corrected_principal_curvatures_and_directions(const PolygonMesh& pmesh, - VertexCurvatureMap& vcm, - const NamedParameters& np = parameters::default_values()) +typename NamedParameters = parameters::Default_named_parameters> +void interpolated_corrected_principal_curvatures_and_directions(const PolygonMesh& pmesh, + VertexCurvatureMap vcm, + const NamedParameters& np = parameters::default_values()) { interpolated_corrected_curvatures(pmesh, np.vertex_principal_curvatures_and_directions_map(vcm)); } /** - * \ingroup PMP_corrected_curvatures_grp - * Computes the interpolated corrected curvatures at a certain vertex, based on the provided pointers. - * By providing mean, gaussian and/or principal curvature and direction property maps as named parameters, the user - * can choose which quantites to compute. - * - * The pointers are used to store the computed quantities. - * The user is responsible for the memory management of the pointers. - * - * @tparam PolygonMesh a model of `FaceListGraph` - * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" - * - * @param pmesh the polygon mesh - * @param v the vertex of `pmesh` to compute the curvatures at - * @param np 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 `ReadablePropertyMap` 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` must be available in `PolygonMesh`.} - * \cgalParamNEnd - * - * \cgalParamNBegin{vertex_normal_map} - * \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} - * \cgalParamType{a class model of `ReadablePropertyMap` with - * `boost::graph_traits::%Vertex_descriptor` - * as key type and `%Vector_3` as value type} - * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} - * \cgalParamExtra{If this parameter is omitted, vertex normals will be - * computed using compute_vertex_normals()} - * \cgalParamNEnd - * - * \cgalParamNBegin{vertex_mean_curvature} - * \cgalParamDescription{a pointer to a scalar value to store the mean curvature at the vertex `v`} - * \cgalParamType{`GT::FT*`} - * \cgalParamDefault{`nullptr`} - * \cgalParamExtra{If this parameter is omitted, mean curvature won't be computed} - * \cgalParamNEnd - * - * \cgalParamNBegin{vertex_gaussian_curvature} - * \cgalParamDescription{a pointer to a scalar value to store the gaussian curvature at the vertex `v`} - * \cgalParamType{`GT::FT*`} - * \cgalParamDefault{`nullptr`} - * \cgalParamExtra{If this parameter is omitted, gaussian curvature won't be computed} - * \cgalParamNEnd - * - * \cgalParamNBegin{vertex_principal_curvatures_and_directions} - * \cgalParamDescription{a pointer to a Principal_curvatures_and_directions object to store the principal curvatures and directions at the vertex `v`} - * \cgalParamType{`Principal_curvatures_and_directions*`} - * \cgalParamDefault{`nullptr`} - * \cgalParamExtra{If this parameter is omitted, principal curvatures and directions won't be computed} - * \cgalParamNEnd - * - * \cgalParamNBegin{ball_radius} - * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures - * by summing measures of faces inside a ball of this radius centered at the - * vertex expanded from. The summed face measures are weighted by their - * inclusion ratio inside this ball.} - * \cgalParamType{`GT::FT`} - * \cgalParamDefault{`-1`} - * \cgalParamExtra{If this parameter is omitted (`-1`), the epansion is then just a sum of - * measures on faces around the vertex} - * \cgalParamNEnd - * - * \cgalNamedParamsEnd - * - * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` - * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_one_vertex()` - * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_one_vertex()` - * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_one_vertex()` +* \ingroup PMP_corrected_curvatures_grp +* computes the interpolated corrected curvatures at a certain vertex, based on the provided pointers. +* By providing mean, gaussian and/or principal curvature and direction property maps as named parameters, the user +* can choose which quantites to compute. +* +* The pointers are used to store the computed quantities. +* The user is responsible for the memory management of the pointers. +* +* @tparam PolygonMesh a model of `FaceListGraph` +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* +* @param pmesh the polygon mesh +* @param v the vertex of `pmesh` to compute the curvatures at +* @param np an 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{vertex_normal_map} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamType{a class model of `ReadablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` +* as key type and `GT::Vector_3` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, vertex normals will be +* computed using `compute_vertex_normals()`} +* \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 +* +* \cgalParamNBegin{vertex_mean_curvature} +* \cgalParamDescription{a pointer to a scalar value to store the mean curvature at the vertex `v`} +* \cgalParamType{`GT::FT*`} +* \cgalParamDefault{`nullptr`} +* \cgalParamExtra{If this parameter is omitted, mean curvature won't be computed} +* \cgalParamNEnd +* +* \cgalParamNBegin{vertex_gaussian_curvature} +* \cgalParamDescription{a pointer to a scalar value to store the gaussian curvature at the vertex `v`} +* \cgalParamType{`GT::FT*`} +* \cgalParamDefault{`nullptr`} +* \cgalParamExtra{If this parameter is omitted, gaussian curvature won't be computed} +* \cgalParamNEnd +* +* \cgalParamNBegin{vertex_principal_curvatures_and_directions} +* \cgalParamDescription{a pointer to a Principal_curvatures_and_directions object to store the principal curvatures and directions at the vertex `v`} +* \cgalParamType{`Principal_curvatures_and_directions*`} +* \cgalParamDefault{`nullptr`} +* \cgalParamExtra{If this parameter is omitted, principal curvatures and directions won't be computed} +* \cgalParamNEnd +* +* \cgalParamNBegin{ball_radius} +* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures +* by summing measures of faces inside a ball of this radius centered at the +* vertex expanded from. The summed face measures are weighted by their +* inclusion ratio inside this ball.} +* \cgalParamType{`GT::FT`} +* \cgalParamDefault{`-1`} +* \cgalParamExtra{If this parameter is omitted (`-1`), the epansion is then just a sum of +* measures on faces around the vertex} +* \cgalParamNEnd +* +* \cgalNamedParamsEnd +* +* @see `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` +* @see `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_one_vertex()` +* @see `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_one_vertex()` +* @see `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_one_vertex()` */ template - void interpolated_corrected_curvatures_one_vertex(const PolygonMesh& pmesh, - typename boost::graph_traits::vertex_descriptor v, - const NamedParameters& np = parameters::default_values()) + typename NamedParameters = parameters::Default_named_parameters> +void interpolated_corrected_curvatures_one_vertex(const PolygonMesh& pmesh, + typename boost::graph_traits::vertex_descriptor v, + const NamedParameters& np = parameters::default_values()) { internal::interpolated_corrected_curvatures_one_vertex(pmesh, v, np); } /** - * \ingroup PMP_corrected_curvatures_grp - * computes the interpolated corrected mean curvature at a vertex of a triangle mesh. - * - * @tparam GT a geometric traits class that provides the nested type `FT`, - * @tparam PolygonMesh a model of `FaceListGraph` - * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" - * - * @param pmesh the polygon mesh - * @param v the vertex of `pmesh` to compute the mean curvature at - * @param np 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 `ReadablePropertyMap` 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` must be available in `PolygonMesh`.} - * \cgalParamNEnd - * - * \cgalParamNBegin{vertex_normal_map} - * \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} - * \cgalParamType{a class model of `ReadablePropertyMap` with - * `boost::graph_traits::%Vertex_descriptor` - * as key type and `%Vector_3` as value type} - * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} - * \cgalParamExtra{If this parameter is omitted, vertex normals will be - * computed using compute_vertex_normals()} - * \cgalParamNEnd - * - * \cgalParamNBegin{ball_radius} - * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures - * by summing measures of faces inside a ball of this radius centered at the - * vertex expanded from. The summed face measures are weighted by their - * inclusion ratio inside this ball} - * \cgalParamType{`GT::FT`} - * \cgalParamDefault{`-1`} - * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of - * measures on faces around the vertex} - * \cgalParamNEnd - * - * \cgalNamedParamsEnd - * - * @return the interpolated corrected mean curvature at the vertex `v` - * - * @see `interpolated_corrected_mean_curvature()` - * @see `interpolated_corrected_gaussian_curvature_one_vertex()` - * @see `interpolated_corrected_principal_curvatures_and_directions_one_vertex()` - * @see `interpolated_corrected_curvatures_one_vertex()` +* \ingroup PMP_corrected_curvatures_grp +* computes the interpolated corrected mean curvature at a vertex of a triangle mesh. +* +* @tparam PolygonMesh a model of `FaceListGraph` +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* +* @param pmesh the polygon mesh +* @param v the vertex of `pmesh` to compute the mean curvature at +* @param np an 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{vertex_normal_map} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamType{a class model of `ReadablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` +* as key type and `GT::Vector_3` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, vertex normals will be +* computed using `compute_vertex_normals()`} +* \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 +* +* \cgalParamNBegin{ball_radius} +* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures +* by summing measures of faces inside a ball of this radius centered at the +* vertex expanded from. The summed face measures are weighted by their +* inclusion ratio inside this ball} +* \cgalParamType{`GT::FT`} +* \cgalParamDefault{`-1`} +* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of +* measures on faces around the vertex} +* \cgalParamNEnd +* +* \cgalNamedParamsEnd +* +* @return the interpolated corrected mean curvature at the vertex `v` +* +* @see `interpolated_corrected_mean_curvature()` +* @see `interpolated_corrected_gaussian_curvature_one_vertex()` +* @see `interpolated_corrected_principal_curvatures_and_directions_one_vertex()` +* @see `interpolated_corrected_curvatures_one_vertex()` */ -template - typename GT::FT interpolated_corrected_mean_curvature_one_vertex(const PolygonMesh& pmesh, - typename boost::graph_traits::vertex_descriptor v, - const NamedParameters& np = parameters::default_values()) +template +#ifdef DOXYGEN_RUNNING +typename GT::FT +#else +typename GetGeomTraits::type::FT +#endif +interpolated_corrected_mean_curvature_one_vertex(const PolygonMesh& pmesh, + typename boost::graph_traits::vertex_descriptor v, + const NamedParameters& np = parameters::default_values()) { - typename GT::FT mean_curvature; + typename GetGeomTraits::type::FT mean_curvature; interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_mean_curvature(&mean_curvature)); return mean_curvature; } /** - * \ingroup PMP_corrected_curvatures_grp - * computes the interpolated corrected Gaussian curvature at a vertex of a triangle mesh. - * - * @tparam GT a geometric traits class that provides the nested type `FT`, - * @tparam PolygonMesh a model of `FaceListGraph` - * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" - * - * @param pmesh the polygon mesh - * @param v the vertex of `pmesh` to compute the Gaussian curvature at - * @param np 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 `ReadablePropertyMap` 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` must be available in `PolygonMesh`.} - * \cgalParamNEnd - * - * \cgalParamNBegin{vertex_normal_map} - * \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} - * \cgalParamType{a class model of `ReadablePropertyMap` with - * `boost::graph_traits::%Vertex_descriptor` - * as key type and `%Vector_3` as value type} - * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} - * \cgalParamExtra{If this parameter is omitted, vertex normals will be - * computed using compute_vertex_normals()} - * \cgalParamNEnd - * - * \cgalParamNBegin{ball_radius} - * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures - * by summing measures of faces inside a ball of this radius centered at the - * vertex expanded from. The summed face measures are weighted by their - * inclusion ratio inside this ball} - * \cgalParamType{`GT::FT`} - * \cgalParamDefault{`-1`} - * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of - * measures on faces around the vertex} - * \cgalParamNEnd - * - * \cgalNamedParamsEnd - * - * @return the interpolated corrected Gaussian curvature at the vertex `v` - * - * @see `interpolated_corrected_gaussian_curvature()` - * @see `interpolated_corrected_mean_curvature_one_vertex()` - * @see `interpolated_corrected_principal_curvatures_and_directions_one_vertex()` - * @see `interpolated_corrected_curvatures_one_vertex()` +* \ingroup PMP_corrected_curvatures_grp +* computes the interpolated corrected Gaussian curvature at a vertex of a triangle mesh. +* +* @tparam PolygonMesh a model of `FaceListGraph` +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* +* @param pmesh the polygon mesh +* @param v the vertex of `pmesh` to compute the Gaussian curvature at +* @param np an 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{vertex_normal_map} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamType{a class model of `ReadablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` +* as key type and `GT::Vector_3` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, vertex normals will be +* computed using `compute_vertex_normals()`} +* \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 +* +* \cgalParamNBegin{ball_radius} +* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures +* by summing measures of faces inside a ball of this radius centered at the +* vertex expanded from. The summed face measures are weighted by their +* inclusion ratio inside this ball} +* \cgalParamType{`GT::FT`} +* \cgalParamDefault{`-1`} +* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of +* measures on faces around the vertex} +* \cgalParamNEnd +* +* \cgalNamedParamsEnd +* +* @return the interpolated corrected Gaussian curvature at the vertex `v` +* +* @see `interpolated_corrected_gaussian_curvature()` +* @see `interpolated_corrected_mean_curvature_one_vertex()` +* @see `interpolated_corrected_principal_curvatures_and_directions_one_vertex()` +* @see `interpolated_corrected_curvatures_one_vertex()` */ -template - typename GT::FT interpolated_corrected_gaussian_curvature_one_vertex(const PolygonMesh& pmesh, - typename boost::graph_traits::vertex_descriptor v, - const NamedParameters& np = parameters::default_values()) +#ifdef DOXYGEN_RUNNING +typename GT::FT +#else +typename GetGeomTraits::type::FT +#endif +interpolated_corrected_gaussian_curvature_one_vertex(const PolygonMesh& pmesh, + typename boost::graph_traits::vertex_descriptor v, + const NamedParameters& np = parameters::default_values()) { - typename GT::FT gc; + typename GetGeomTraits::type::FT gc; interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_gaussian_curvature(&gc)); return gc; } /** - * \ingroup PMP_corrected_curvatures_grp - * computes the interpolated corrected principal curvatures and directions at a vertex of a triangle mesh. - * - * @tparam GT the geometric traits class, - * @tparam PolygonMesh a model of `FaceListGraph` - * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" - * - * @param pmesh the polygon mesh - * @param v the vertex of `pmesh` to compute the principal curvatures and directions at - * @param np 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 `ReadablePropertyMap` 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` must be available in `PolygonMesh`.} - * \cgalParamNEnd - * - * \cgalParamNBegin{vertex_normal_map} - * \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} - * \cgalParamType{a class model of `ReadablePropertyMap` with - * `boost::graph_traits::%Vertex_descriptor` - * as key type and `%Vector_3` as value type} - * \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} - * \cgalParamExtra{If this parameter is omitted, vertex normals will be - * computed using compute_vertex_normals()} - * \cgalParamNEnd - * - * \cgalParamNBegin{ball_radius} - * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures - * by summing measures of faces inside a ball of this radius centered at the - * vertex expanded from. The summed face measures are weighted by their - * inclusion ratio inside this ball} - * \cgalParamType{`GT::FT`} - * \cgalParamDefault{`-1`} - * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of - * measures on faces around the vertex} - * \cgalParamNEnd - * \cgalNamedParamsEnd - * - * @return the interpolated corrected principal curvatures and directions at the vertex `v` - * - * @see `interpolated_corrected_principal_curvatures_and_directions()` - * @see `interpolated_corrected_mean_curvature_one_vertex()` - * @see `interpolated_corrected_gaussian_curvature_one_vertex()` - * @see `interpolated_corrected_curvatures_one_vertex()` +* \ingroup PMP_corrected_curvatures_grp +* computes the interpolated corrected principal curvatures and directions at a vertex of a triangle mesh. +* +* @tparam PolygonMesh a model of `FaceListGraph` +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* +* @param pmesh the polygon mesh +* @param v the vertex of `pmesh` to compute the principal curvatures and directions at +* @param np an 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 `%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{vertex_normal_map} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamType{a class model of `ReadablePropertyMap` with +* `boost::graph_traits::%vertex_descriptor` +* as key type and `%Vector_3` as value type} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* \cgalParamExtra{If this parameter is omitted, vertex normals will be +* computed using `compute_vertex_normals()`} +* \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 +* +* \cgalParamNBegin{ball_radius} +* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures +* by summing measures of faces inside a ball of this radius centered at the +* vertex expanded from. The summed face measures are weighted by their +* inclusion ratio inside this ball} +* \cgalParamType{`GT::FT`} +* \cgalParamDefault{`-1`} +* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of +* measures on faces around the vertex} +* \cgalParamNEnd +* \cgalNamedParamsEnd +* +* @return the interpolated corrected principal curvatures and directions at the vertex `v` +* +* @see `interpolated_corrected_principal_curvatures_and_directions()` +* @see `interpolated_corrected_mean_curvature_one_vertex()` +* @see `interpolated_corrected_gaussian_curvature_one_vertex()` +* @see `interpolated_corrected_curvatures_one_vertex()` */ -template - Principal_curvatures_and_directions interpolated_corrected_principal_curvatures_and_directions_one_vertex(const PolygonMesh& pmesh, - typename boost::graph_traits::vertex_descriptor v, - const NamedParameters& np = parameters::default_values()) +template +#ifdef DOXYGEN_RUNNING +Principal_curvatures_and_directions +#else +Principal_curvatures_and_directions::type> +#endif +interpolated_corrected_principal_curvatures_and_directions_one_vertex(const PolygonMesh& pmesh, + typename boost::graph_traits::vertex_descriptor v, + const NamedParameters& np = parameters::default_values()) { + using GT=typename GetGeomTraits::type; Principal_curvatures_and_directions pcd; interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_principal_curvatures_and_directions(&pcd)); return pcd; From 5ef5d67920724435a903a932866b9525260125a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 22 Mar 2023 18:40:44 +0100 Subject: [PATCH 124/161] do not use pointers --- .../interpolated_corrected_curvatures.h | 75 +++++++++---------- ...test_interpolated_corrected_curvatures.cpp | 6 +- 2 files changed, 39 insertions(+), 42 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index 24c1d12a59e3..cc898d2fc943 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -624,6 +624,16 @@ Vertex_measures interpolated_corrected_measures_one_vertex( return vertex_measures; } +template +void set_value(const T& value, std::reference_wrapper rw) +{ + rw.get()=value; +} + +template +void set_value(const T&, internal_np::Param_not_found) +{} + // computes selected curvatures for one specific vertex template @@ -662,15 +672,10 @@ template(pmesh) * 1e-6; - // get parameters (pointers) for curvatures - typename GT::FT* vertex_mean_curvature = choose_parameter(get_parameter(np, internal_np::vertex_mean_curvature), nullptr); - typename GT::FT* vertex_gaussian_curvature = choose_parameter(get_parameter(np, internal_np::vertex_gaussian_curvature), nullptr); - Principal_curvatures_and_directions* vertex_principal_curvatures_and_directions = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions), nullptr); - - // determine which curvatures are selected (by checking if the pointers are not null) - const bool is_mean_curvature_selected = (vertex_mean_curvature != nullptr); - const bool is_gaussian_curvature_selected = (vertex_gaussian_curvature != nullptr); - const bool is_principal_curvatures_and_directions_selected = (vertex_principal_curvatures_and_directions != nullptr); + // determine which curvatures are selected + const bool is_mean_curvature_selected = !is_default_parameter::value; + const bool is_gaussian_curvature_selected = !is_default_parameter::value; + const bool is_principal_curvatures_and_directions_selected = !is_default_parameter::value; Vertex_measures vertex_measures; @@ -703,13 +708,13 @@ template principal_curvatures_and_directions = principal_curvatures_and_directions_from_anisotropic_measures( vertex_measures.anisotropic_measure, vertex_measures.area_measure, - v_normal - ); - *vertex_principal_curvatures_and_directions = principal_curvatures_and_directions; + v_normal); + set_value(principal_curvatures_and_directions, get_parameter(np, internal_np::vertex_principal_curvatures_and_directions)); } } @@ -1064,7 +1068,7 @@ class Interpolated_corrected_curvatures_computer * \cgalParamType{a class model of `WritablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` * as key type and `GT::FT` as value type} -* \cgalParamExtra{If this parameter is omitted, mean curvatures won't be computed} +* \cgalParamExtra{If this parameter is omitted, mean curvatures will not be computed} * \cgalParamNEnd * * \cgalParamNBegin{vertex_gaussian_curvature_map} @@ -1072,7 +1076,7 @@ class Interpolated_corrected_curvatures_computer * \cgalParamType{a class model of `WritablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` * as key type and `GT::FT` as value type} -* \cgalParamExtra{If this parameter is omitted, gaussian curvatures won't be computed} +* \cgalParamExtra{If this parameter is omitted, gaussian curvatures will not be computed} * \cgalParamNEnd * * \cgalParamNBegin{vertex_principal_curvatures_and_directions_map} @@ -1080,7 +1084,7 @@ class Interpolated_corrected_curvatures_computer * \cgalParamType{a class model of `WritablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` * as key type and `Principal_curvatures_and_directions` as value type} -* \cgalParamExtra{If this parameter is omitted, mean principal won't be computed} +* \cgalParamExtra{If this parameter is omitted, mean principal will not be computed} * \cgalParamNEnd * * \cgalParamNBegin{ball_radius} @@ -1323,13 +1327,10 @@ void interpolated_corrected_principal_curvatures_and_directions(const PolygonMes /** * \ingroup PMP_corrected_curvatures_grp -* computes the interpolated corrected curvatures at a certain vertex, based on the provided pointers. +* computes the interpolated corrected curvatures at a vertex `v`. * By providing mean, gaussian and/or principal curvature and direction property maps as named parameters, the user * can choose which quantites to compute. * -* The pointers are used to store the computed quantities. -* The user is responsible for the memory management of the pointers. -* * @tparam PolygonMesh a model of `FaceListGraph` * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * @@ -1359,7 +1360,6 @@ void interpolated_corrected_principal_curvatures_and_directions(const PolygonMes * computed using `compute_vertex_normals()`} * \cgalParamNEnd * -* * \cgalParamNBegin{geom_traits} * \cgalParamDescription{an instance of a geometric traits class} * \cgalParamType{a class model of `Kernel`} @@ -1368,24 +1368,21 @@ void interpolated_corrected_principal_curvatures_and_directions(const PolygonMes * \cgalParamNEnd * * \cgalParamNBegin{vertex_mean_curvature} -* \cgalParamDescription{a pointer to a scalar value to store the mean curvature at the vertex `v`} -* \cgalParamType{`GT::FT*`} -* \cgalParamDefault{`nullptr`} -* \cgalParamExtra{If this parameter is omitted, mean curvature won't be computed} +* \cgalParamDescription{a reference to a scalar value to store the mean curvature at the vertex `v`} +* \cgalParamType{`std::reference_wrapper`} +* \cgalParamExtra{If this parameter is omitted, mean curvature will not be computed} * \cgalParamNEnd * * \cgalParamNBegin{vertex_gaussian_curvature} -* \cgalParamDescription{a pointer to a scalar value to store the gaussian curvature at the vertex `v`} -* \cgalParamType{`GT::FT*`} -* \cgalParamDefault{`nullptr`} -* \cgalParamExtra{If this parameter is omitted, gaussian curvature won't be computed} +* \cgalParamDescription{a reference to a scalar value to store the gaussian curvature at the vertex `v`} +* \cgalParamType{`std::reference_wrapper`} +* \cgalParamExtra{If this parameter is omitted, gaussian curvature will not be computed} * \cgalParamNEnd * * \cgalParamNBegin{vertex_principal_curvatures_and_directions} -* \cgalParamDescription{a pointer to a Principal_curvatures_and_directions object to store the principal curvatures and directions at the vertex `v`} -* \cgalParamType{`Principal_curvatures_and_directions*`} -* \cgalParamDefault{`nullptr`} -* \cgalParamExtra{If this parameter is omitted, principal curvatures and directions won't be computed} +* \cgalParamDescription{a reference to an`Principal_curvatures_and_directions` object to store the principal curvatures and directions at the vertex `v`} +* \cgalParamType{`std::reference_wrapper>`} +* \cgalParamExtra{If this parameter is omitted, principal curvatures and directions will not be computed} * \cgalParamNEnd * * \cgalParamNBegin{ball_radius} @@ -1488,7 +1485,7 @@ interpolated_corrected_mean_curvature_one_vertex(const PolygonMesh& pmesh, const NamedParameters& np = parameters::default_values()) { typename GetGeomTraits::type::FT mean_curvature; - interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_mean_curvature(&mean_curvature)); + interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_mean_curvature(std::ref(mean_curvature))); return mean_curvature; } @@ -1565,7 +1562,7 @@ interpolated_corrected_gaussian_curvature_one_vertex(const PolygonMesh& pmesh, const NamedParameters& np = parameters::default_values()) { typename GetGeomTraits::type::FT gc; - interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_gaussian_curvature(&gc)); + interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_gaussian_curvature(std::ref(gc))); return gc; } @@ -1642,7 +1639,7 @@ interpolated_corrected_principal_curvatures_and_directions_one_vertex(const Poly { using GT=typename GetGeomTraits::type; Principal_curvatures_and_directions pcd; - interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_principal_curvatures_and_directions(&pcd)); + interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_principal_curvatures_and_directions(std::ref(pcd))); return pcd; } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp index c8d3894720cc..bd0594652206 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp @@ -165,9 +165,9 @@ void test_average_curvatures(std::string mesh_path, PMP::interpolated_corrected_curvatures_one_vertex( pmesh, v, - CGAL::parameters::vertex_gaussian_curvature(&g) - .vertex_mean_curvature(&h) - .vertex_principal_curvatures_and_directions(&p) + CGAL::parameters::vertex_gaussian_curvature(std::ref(g)) + .vertex_mean_curvature(std::ref(h)) + .vertex_principal_curvatures_and_directions(std::ref(p)) .ball_radius(test_info.expansion_radius) ); From 7f4597720eed4bc4c9c215668f99e02495357826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 22 Mar 2023 18:44:11 +0100 Subject: [PATCH 125/161] the mesh does not need to be triangulated --- .../interpolated_corrected_curvatures.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index cc898d2fc943..94a8533ca42b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -1414,7 +1414,7 @@ void interpolated_corrected_curvatures_one_vertex(const PolygonMesh& pmesh, /** * \ingroup PMP_corrected_curvatures_grp -* computes the interpolated corrected mean curvature at a vertex of a triangle mesh. +* computes the interpolated corrected mean curvature at vertex `v` of mesh `pmesh`. * * @tparam PolygonMesh a model of `FaceListGraph` * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" @@ -1491,7 +1491,7 @@ interpolated_corrected_mean_curvature_one_vertex(const PolygonMesh& pmesh, /** * \ingroup PMP_corrected_curvatures_grp -* computes the interpolated corrected Gaussian curvature at a vertex of a triangle mesh. +* computes the interpolated corrected Gaussian curvature at vertex `v` of mesh `pmesh`. * * @tparam PolygonMesh a model of `FaceListGraph` * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" @@ -1568,7 +1568,7 @@ interpolated_corrected_gaussian_curvature_one_vertex(const PolygonMesh& pmesh, /** * \ingroup PMP_corrected_curvatures_grp -* computes the interpolated corrected principal curvatures and directions at a vertex of a triangle mesh. +* computes the interpolated corrected principal curvatures and directions at vertex `v` of mesh `pmesh`. * * @tparam PolygonMesh a model of `FaceListGraph` * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" From 661513b16ee05509e00d0975213e928c75443dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 22 Mar 2023 19:12:28 +0100 Subject: [PATCH 126/161] gaussian -> Gaussian --- .../PackageDescription.txt | 4 +- .../Polygon_mesh_processing.txt | 4 +- .../interpolated_corrected_curvatures_PH.cpp | 2 +- .../interpolated_corrected_curvatures_SM.cpp | 2 +- ...terpolated_corrected_curvatures_vertex.cpp | 4 +- .../interpolated_corrected_curvatures.h | 76 +++++++++---------- ...test_interpolated_corrected_curvatures.cpp | 24 +++--- .../Display/Display_property_plugin.cpp | 10 +-- .../internal/parameters_interface.h | 4 +- 9 files changed, 65 insertions(+), 65 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 9cb257338a72..9c8b5d6f2948 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -209,11 +209,11 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage. \cgalCRPSection{Corrected Curvatures} - `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_Gaussian_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_one_vertex()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_one_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_Gaussian_curvature_one_vertex()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_one_vertex()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures_one_vertex()` - `CGAL::Polygon_mesh_processing::Principal_curvatures_and_directions` 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 2eb85a80bfbb..65c236e99fc3 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 @@ -901,7 +901,7 @@ Polyhedron_3 and other polygonal mesh structures based on the Face Graph Model. These computations are performed using (on all vertices of mesh): - `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_Gaussian_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` @@ -910,7 +910,7 @@ as the implementation performs the shared computations only once, making it more Similarly, we can use the following functions to compute curvatures on a specific vertex: - `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_one_vertex()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_one_vertex()` +- `CGAL::Polygon_mesh_processing::interpolated_corrected_Gaussian_curvature_one_vertex()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_one_vertex()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures_one_vertex()` diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp index 3cf675c53ca4..0e3923dc67b8 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp @@ -38,7 +38,7 @@ int main(int argc, char* argv[]) polyhedron, mean_curvature_map); - PMP::interpolated_corrected_gaussian_curvature( + PMP::interpolated_corrected_Gaussian_curvature( polyhedron, gaussian_curvature_map); diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp index d92ec7b8096d..811146dd6412 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp @@ -53,7 +53,7 @@ int main(int argc, char* argv[]) mean_curvature_map ); - PMP::interpolated_corrected_gaussian_curvature( + PMP::interpolated_corrected_Gaussian_curvature( smesh, gaussian_curvature_map ); diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp index e5845a1b3a5a..c0722b25675a 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp @@ -31,7 +31,7 @@ int main(int argc, char* argv[]) for (vertex_descriptor v : vertices(smesh)) { double h = PMP::interpolated_corrected_mean_curvature_one_vertex(smesh, v); - double g = PMP::interpolated_corrected_gaussian_curvature_one_vertex(smesh, v); + double g = PMP::interpolated_corrected_Gaussian_curvature_one_vertex(smesh, v); PMP::Principal_curvatures_and_directions p = PMP::interpolated_corrected_principal_curvatures_and_directions_one_vertex(smesh, v); @@ -48,7 +48,7 @@ int main(int argc, char* argv[]) // smesh, // v, // CGAL::parameters::vertex_mean_curvature(&h) - // .vertex_gaussian_curvature(&g) + // .vertex_Gaussian_curvature(&g) // ); std::cout << v.idx() << ": HC = " << h diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index 94a8533ca42b..bffd2657e96c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -222,7 +222,7 @@ typename GT::FT interpolated_corrected_mean_curvature_measure_face(const std::ve } template -typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std::vector& u) +typename GT::FT interpolated_corrected_Gaussian_curvature_measure_face(const std::vector& u) { const std::size_t n = u.size(); CGAL_precondition(n >= 3); @@ -259,7 +259,7 @@ typename GT::FT interpolated_corrected_gaussian_curvature_measure_face(const std // summing each triangle's measure after triangulation by barycenter split. for (std::size_t i = 0; i < n; i++) { - mu2 += interpolated_corrected_gaussian_curvature_measure_face( + mu2 += interpolated_corrected_Gaussian_curvature_measure_face( std::vector {u[i], u[(i + 1) % n], uc} ); } @@ -483,7 +483,7 @@ Vertex_measures interpolated_corrected_measures_one_vertex_no_radius( const PolygonMesh& pmesh, const typename boost::graph_traits::vertex_descriptor v, const bool is_mean_curvature_selected, - const bool is_gaussian_curvature_selected, + const bool is_Gaussian_curvature_selected, const bool is_principal_curvatures_and_directions_selected, const VPM vpm, const VNM vnm @@ -519,8 +519,8 @@ Vertex_measures interpolated_corrected_measures_one_vertex_no_radius( if (is_mean_curvature_selected) vertex_measures.mean_curvature_measure += interpolated_corrected_mean_curvature_measure_face(u, x); - if (is_gaussian_curvature_selected) - vertex_measures.gaussian_curvature_measure += interpolated_corrected_gaussian_curvature_measure_face(u); + if (is_Gaussian_curvature_selected) + vertex_measures.gaussian_curvature_measure += interpolated_corrected_Gaussian_curvature_measure_face(u); if (is_principal_curvatures_and_directions_selected) { @@ -543,7 +543,7 @@ Vertex_measures interpolated_corrected_measures_one_vertex( const typename boost::graph_traits::vertex_descriptor v, const typename GT::FT radius, const bool is_mean_curvature_selected, - const bool is_gaussian_curvature_selected, + const bool is_Gaussian_curvature_selected, const bool is_principal_curvatures_and_directions_selected, const VPM vpm, const VNM vnm @@ -598,8 +598,8 @@ Vertex_measures interpolated_corrected_measures_one_vertex( if (is_mean_curvature_selected) vertex_measures.mean_curvature_measure += f_ratio * interpolated_corrected_mean_curvature_measure_face(u, x); - if (is_gaussian_curvature_selected) - vertex_measures.gaussian_curvature_measure += f_ratio * interpolated_corrected_gaussian_curvature_measure_face(u); + if (is_Gaussian_curvature_selected) + vertex_measures.gaussian_curvature_measure += f_ratio * interpolated_corrected_Gaussian_curvature_measure_face(u); if (is_principal_curvatures_and_directions_selected) { @@ -674,7 +674,7 @@ template::value; - const bool is_gaussian_curvature_selected = !is_default_parameter::value; + const bool is_Gaussian_curvature_selected = !is_default_parameter::value; const bool is_principal_curvatures_and_directions_selected = !is_default_parameter::value; Vertex_measures vertex_measures; @@ -686,7 +686,7 @@ template::type Vertex_mean_curvature_map; - typedef typename internal_np::Lookup_named_param_def::type Vertex_gaussian_curvature_map; + Default_scalar_map>::type Vertex_Gaussian_curvature_map; typedef Constant_property_map> Default_principal_map; typedef typename internal_np::Lookup_named_param_def::value; - is_gaussian_curvature_selected = !is_default_parameter::value; + is_Gaussian_curvature_selected = !is_default_parameter::value; is_principal_curvatures_and_directions_selected = !is_default_parameter::value; mean_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_mean_curvature_map), Default_scalar_map()); - gaussian_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_gaussian_curvature_map), Default_scalar_map()); + gaussian_curvature_map = choose_parameter(get_parameter(np, internal_np::vertex_Gaussian_curvature_map), Default_scalar_map()); principal_curvatures_and_directions_map = choose_parameter(get_parameter(np, internal_np::vertex_principal_curvatures_and_directions_map), Default_principal_map()); } @@ -842,7 +842,7 @@ class Interpolated_corrected_curvatures_computer { set_named_params(np); - if (is_mean_curvature_selected || is_gaussian_curvature_selected || is_principal_curvatures_and_directions_selected) + if (is_mean_curvature_selected || is_Gaussian_curvature_selected || is_principal_curvatures_and_directions_selected) { set_property_maps(); @@ -872,8 +872,8 @@ class Interpolated_corrected_curvatures_computer if (is_mean_curvature_selected) put(mu1_map, f, interpolated_corrected_mean_curvature_measure_face(u, x)); - if (is_gaussian_curvature_selected) - put(mu2_map, f, interpolated_corrected_gaussian_curvature_measure_face(u)); + if (is_Gaussian_curvature_selected) + put(mu2_map, f, interpolated_corrected_Gaussian_curvature_measure_face(u)); if (is_principal_curvatures_and_directions_selected) put(muXY_map, f, interpolated_corrected_anisotropic_measure_face(u, x)); @@ -899,7 +899,7 @@ class Interpolated_corrected_curvatures_computer if (is_mean_curvature_selected) vertex_measures.mean_curvature_measure += get(mu1_map, f); - if (is_gaussian_curvature_selected) + if (is_Gaussian_curvature_selected) vertex_measures.gaussian_curvature_measure += get(mu2_map, f); if (is_principal_curvatures_and_directions_selected) @@ -957,7 +957,7 @@ class Interpolated_corrected_curvatures_computer if (is_mean_curvature_selected) vertex_measures.mean_curvature_measure += f_ratio * get(mu1_map, fi); - if (is_gaussian_curvature_selected) + if (is_Gaussian_curvature_selected) vertex_measures.gaussian_curvature_measure += f_ratio * get(mu2_map, fi); if (is_principal_curvatures_and_directions_selected) @@ -998,7 +998,7 @@ class Interpolated_corrected_curvatures_computer put(mean_curvature_map, v, 0); } - if (is_gaussian_curvature_selected) { + if (is_Gaussian_curvature_selected) { vertex_measures.area_measure != 0 ? put(gaussian_curvature_map, v, vertex_measures.gaussian_curvature_measure / vertex_measures.area_measure) : put(gaussian_curvature_map, v, 0); @@ -1071,7 +1071,7 @@ class Interpolated_corrected_curvatures_computer * \cgalParamExtra{If this parameter is omitted, mean curvatures will not be computed} * \cgalParamNEnd * -* \cgalParamNBegin{vertex_gaussian_curvature_map} +* \cgalParamNBegin{vertex_Gaussian_curvature_map} * \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} * \cgalParamType{a class model of `WritablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` @@ -1101,7 +1101,7 @@ class Interpolated_corrected_curvatures_computer * \cgalNamedParamsEnd * * @see `interpolated_corrected_mean_curvature()` -* @see `interpolated_corrected_gaussian_curvature()` +* @see `interpolated_corrected_Gaussian_curvature()` * @see `interpolated_corrected_principal_curvatures_and_directions()` */ template -void interpolated_corrected_gaussian_curvature(const PolygonMesh& pmesh, +void interpolated_corrected_Gaussian_curvature(const PolygonMesh& pmesh, VertexCurvatureMap vcm, const NamedParameters& np = parameters::default_values()) { - interpolated_corrected_curvatures(pmesh, np.vertex_gaussian_curvature_map(vcm)); + interpolated_corrected_curvatures(pmesh, np.vertex_Gaussian_curvature_map(vcm)); } /** @@ -1312,7 +1312,7 @@ void interpolated_corrected_gaussian_curvature(const PolygonMesh& pmesh, * \cgalNamedParamsEnd * * @see `interpolated_corrected_mean_curvature()` -* @see `interpolated_corrected_gaussian_curvature()` +* @see `interpolated_corrected_Gaussian_curvature()` * @see `interpolated_corrected_curvatures()` */ template`} * \cgalParamExtra{If this parameter is omitted, gaussian curvature will not be computed} @@ -1400,7 +1400,7 @@ void interpolated_corrected_principal_curvatures_and_directions(const PolygonMes * * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_one_vertex()` -* @see `CGAL::Polygon_mesh_processing::interpolated_corrected_gaussian_curvature_one_vertex()` +* @see `CGAL::Polygon_mesh_processing::interpolated_corrected_Gaussian_curvature_one_vertex()` * @see `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_one_vertex()` */ template::type::FT #endif -interpolated_corrected_gaussian_curvature_one_vertex(const PolygonMesh& pmesh, +interpolated_corrected_Gaussian_curvature_one_vertex(const PolygonMesh& pmesh, typename boost::graph_traits::vertex_descriptor v, const NamedParameters& np = parameters::default_values()) { typename GetGeomTraits::type::FT gc; - interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_gaussian_curvature(std::ref(gc))); + interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_Gaussian_curvature(std::ref(gc))); return gc; } @@ -1622,7 +1622,7 @@ interpolated_corrected_gaussian_curvature_one_vertex(const PolygonMesh& pmesh, * * @see `interpolated_corrected_principal_curvatures_and_directions()` * @see `interpolated_corrected_mean_curvature_one_vertex()` -* @see `interpolated_corrected_gaussian_curvature_one_vertex()` +* @see `interpolated_corrected_Gaussian_curvature_one_vertex()` * @see `interpolated_corrected_curvatures_one_vertex()` */ diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp index bd0594652206..c0772ce989bc 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp @@ -78,7 +78,7 @@ void test_average_curvatures(std::string mesh_path, // test_info.expansion_radius -> test if no radius is provided by user. if (test_info.expansion_radius < 0) { PMP::interpolated_corrected_mean_curvature(pmesh, mean_curvature_map); - PMP::interpolated_corrected_gaussian_curvature(pmesh, gaussian_curvature_map); + PMP::interpolated_corrected_Gaussian_curvature(pmesh, gaussian_curvature_map); PMP::interpolated_corrected_principal_curvatures_and_directions(pmesh, principal_curvatures_and_directions_map); } else { @@ -88,7 +88,7 @@ void test_average_curvatures(std::string mesh_path, CGAL::parameters::ball_radius(test_info.expansion_radius) ); - PMP::interpolated_corrected_gaussian_curvature( + PMP::interpolated_corrected_Gaussian_curvature( pmesh, gaussian_curvature_map, CGAL::parameters::ball_radius(test_info.expansion_radius) @@ -125,28 +125,28 @@ void test_average_curvatures(std::string mesh_path, pmesh, CGAL::parameters::ball_radius(test_info.expansion_radius) .vertex_mean_curvature_map(mean_curvature_map) - .vertex_gaussian_curvature_map(gaussian_curvature_map) + .vertex_Gaussian_curvature_map(gaussian_curvature_map) .vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) ); - Epic_kernel::FT new_mean_curvature_avg = 0, new_gaussian_curvature_avg = 0, new_principal_curvature_avg = 0; + Epic_kernel::FT new_mean_curvature_avg = 0, new_Gaussian_curvature_avg = 0, new_principal_curvature_avg = 0; for (vertex_descriptor v : vertices(pmesh)) { new_mean_curvature_avg += get(mean_curvature_map, v); - new_gaussian_curvature_avg += get(gaussian_curvature_map, v); + new_Gaussian_curvature_avg += get(gaussian_curvature_map, v); new_principal_curvature_avg += get(principal_curvatures_and_directions_map, v).min_curvature + get(principal_curvatures_and_directions_map, v).max_curvature; } new_mean_curvature_avg /= vertices(pmesh).size(); - new_gaussian_curvature_avg /= vertices(pmesh).size(); + new_Gaussian_curvature_avg /= vertices(pmesh).size(); new_principal_curvature_avg /= vertices(pmesh).size() * 2; // are average curvatures computed from interpolated_corrected_curvatures() // equal to average curvatures each computed on its own? assert(passes_comparison(mean_curvature_avg, new_mean_curvature_avg, 0.99)); - assert(passes_comparison(gaussian_curvature_avg, new_gaussian_curvature_avg, 0.99)); + assert(passes_comparison(gaussian_curvature_avg, new_Gaussian_curvature_avg, 0.99)); assert(passes_comparison(principal_curvature_avg, new_principal_curvature_avg, 0.99)); if (compare_single_vertex) @@ -154,7 +154,7 @@ void test_average_curvatures(std::string mesh_path, // computing curvatures together from interpolated_corrected_curvatures() Epic_kernel::FT single_vertex_mean_curvature_avg = 0, - single_vertex_gaussian_curvature_avg = 0, + single_vertex_Gaussian_curvature_avg = 0, single_vertex_principal_curvature_avg = 0; Epic_kernel::FT h, g; @@ -165,23 +165,23 @@ void test_average_curvatures(std::string mesh_path, PMP::interpolated_corrected_curvatures_one_vertex( pmesh, v, - CGAL::parameters::vertex_gaussian_curvature(std::ref(g)) + CGAL::parameters::vertex_Gaussian_curvature(std::ref(g)) .vertex_mean_curvature(std::ref(h)) .vertex_principal_curvatures_and_directions(std::ref(p)) .ball_radius(test_info.expansion_radius) ); single_vertex_mean_curvature_avg += h; - single_vertex_gaussian_curvature_avg += g; + single_vertex_Gaussian_curvature_avg += g; single_vertex_principal_curvature_avg += p.min_curvature + p.max_curvature; } single_vertex_mean_curvature_avg /= vertices(pmesh).size(); - single_vertex_gaussian_curvature_avg /= vertices(pmesh).size(); + single_vertex_Gaussian_curvature_avg /= vertices(pmesh).size(); single_vertex_principal_curvature_avg /= vertices(pmesh).size() * 2; assert(passes_comparison(mean_curvature_avg, single_vertex_mean_curvature_avg, 0.99)); - assert(passes_comparison(gaussian_curvature_avg, single_vertex_gaussian_curvature_avg, 0.99)); + assert(passes_comparison(gaussian_curvature_avg, single_vertex_Gaussian_curvature_avg, 0.99)); assert(passes_comparison(principal_curvature_avg, single_vertex_principal_curvature_avg, 0.99)); } diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index c4c7d6a3c916..050487c9fec2 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -662,7 +662,7 @@ private Q_SLOTS: if (does_exist) sm_item->face_graph()->remove_property_map(pmap); std::tie(pmap, does_exist) = - sm_item->face_graph()->property_map("v:interpolated_corrected_gaussian_curvature"); + sm_item->face_graph()->property_map("v:interpolated_corrected_Gaussian_curvature"); if (does_exist) sm_item->face_graph()->remove_property_map(pmap); }); @@ -748,7 +748,7 @@ private Q_SLOTS: smesh.remove_property_map(mean_curvature); } SMesh::Property_map gaussian_curvature; - std::tie(gaussian_curvature, found) = smesh.property_map("v:interpolated_corrected_gaussian_curvature"); + std::tie(gaussian_curvature, found) = smesh.property_map("v:interpolated_corrected_Gaussian_curvature"); if (found) { smesh.remove_property_map(gaussian_curvature); @@ -851,7 +851,7 @@ private Q_SLOTS: if (mu_index != MEAN_CURVATURE && mu_index != GAUSSIAN_CURVATURE) return; std::string tied_string = (mu_index == MEAN_CURVATURE)? - "v:interpolated_corrected_mean_curvature": "v:interpolated_corrected_gaussian_curvature"; + "v:interpolated_corrected_mean_curvature": "v:interpolated_corrected_Gaussian_curvature"; SMesh& smesh = *item->face_graph(); const auto vnm = smesh.property_map("v:normal_before_perturbation").first; @@ -868,13 +868,13 @@ private Q_SLOTS: if (mu_index == MEAN_CURVATURE) PMP::interpolated_corrected_mean_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius).vertex_normal_map(vnm)); else - PMP::interpolated_corrected_gaussian_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius).vertex_normal_map(vnm)); + PMP::interpolated_corrected_Gaussian_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius).vertex_normal_map(vnm)); } else { if (mu_index == MEAN_CURVATURE) PMP::interpolated_corrected_mean_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius)); else - PMP::interpolated_corrected_gaussian_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius)); + PMP::interpolated_corrected_Gaussian_curvature(smesh, mu_i_map, CGAL::parameters::ball_radius(expand_radius)); } double res_min = ARBITRARY_DBL_MAX, res_max = -ARBITRARY_DBL_MAX; 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 4990dca1ba15..30272955bb00 100644 --- a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h +++ b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h @@ -91,10 +91,10 @@ CGAL_add_named_parameter(number_of_points_on_edges_t, 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_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_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(outward_orientation_t, outward_orientation, outward_orientation) From 556218bf153dcfd7b82861445e0a77e135753c0f Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 25 Mar 2023 11:57:22 +0200 Subject: [PATCH 127/161] gaussian -> Gaussian in docs and comments --- .../Polygon_mesh_processing.txt | 4 ++-- .../interpolated_corrected_curvatures_vertex.cpp | 2 +- .../interpolated_corrected_curvatures.h | 14 +++++++------- 3 files changed, 10 insertions(+), 10 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 65c236e99fc3..e2bee6e295d8 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 @@ -890,7 +890,7 @@ not provide storage for the normals. \section PMPICC Computing Curvatures This package provides methods to compute curvatures on polygonal meshes based on Interpolated Corrected Curvatures -on Polyhedral Surfaces \cgalCite{lachaud2020}. This includes mean curvature, gaussian curvature, +on Polyhedral Surfaces \cgalCite{lachaud2020}. This includes mean curvature, Gaussian curvature, principal curvatures and directions. These can be computed on triangle meshes, quad meshes, and meshes with n-gon faces. The algorithms used prove to work well in general. Also, on meshes with noise on vertex positions, they give accurate results, on the condition that the @@ -905,7 +905,7 @@ These computations are performed using (on all vertices of mesh): - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` -Where it is recommended to use the last function for computing multiple curvatures (example: mean and gaussian) +Where it is recommended to use the last function for computing multiple curvatures (example: mean and Gaussian) as the implementation performs the shared computations only once, making it more efficient. Similarly, we can use the following functions to compute curvatures on a specific vertex: diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp index c0722b25675a..c6bef5faa740 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp @@ -43,7 +43,7 @@ int main(int argc, char* argv[]) // The following commented lines show this (all mentioned named parameters work on it as well) // we specify which curvatures we want to compute by passing pointers as named parameters // as shown. These pointers are used for storing the result as well. in this example we - // selected mean and gaussian curvatures + // selected mean and Gaussian curvatures // PMP::interpolated_corrected_curvatures_one_vertex( // smesh, // v, diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index bffd2657e96c..2324b0e9e88c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -1024,7 +1024,7 @@ class Interpolated_corrected_curvatures_computer * \ingroup PMP_corrected_curvatures_grp * * computes the interpolated corrected curvatures across the mesh `pmesh`. -* By providing mean, gaussian and/or principal curvature and direction property maps as named parameters, the user +* By providing mean, Gaussian and/or principal curvature and direction property maps as named parameters, the user * can choose which quantites to compute. * * @tparam PolygonMesh a model of `FaceListGraph`. @@ -1076,7 +1076,7 @@ class Interpolated_corrected_curvatures_computer * \cgalParamType{a class model of `WritablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` * as key type and `GT::FT` as value type} -* \cgalParamExtra{If this parameter is omitted, gaussian curvatures will not be computed} +* \cgalParamExtra{If this parameter is omitted, Gaussian curvatures will not be computed} * \cgalParamNEnd * * \cgalParamNBegin{vertex_principal_curvatures_and_directions_map} @@ -1186,7 +1186,7 @@ void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected gaussian curvature across the mesh `pmesh`. +* computes the interpolated corrected Gaussian curvature across the mesh `pmesh`. * * @tparam PolygonMesh a model of `FaceListGraph`. * @tparam VertexCurvatureMap a model of `WritablePropertyMap` with @@ -1194,7 +1194,7 @@ void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * * @param pmesh the polygon mesh. -* @param vcm the vertex property map in which the computed gaussian curvatures are stored. +* @param vcm the vertex property map in which the computed Gaussian curvatures are stored. * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters". * `GT` stands for the type of the object provided to the named parameter `geom_traits()`. * @@ -1328,7 +1328,7 @@ void interpolated_corrected_principal_curvatures_and_directions(const PolygonMes /** * \ingroup PMP_corrected_curvatures_grp * computes the interpolated corrected curvatures at a vertex `v`. -* By providing mean, gaussian and/or principal curvature and direction property maps as named parameters, the user +* By providing mean, Gaussian and/or principal curvature and direction property maps as named parameters, the user * can choose which quantites to compute. * * @tparam PolygonMesh a model of `FaceListGraph` @@ -1374,9 +1374,9 @@ void interpolated_corrected_principal_curvatures_and_directions(const PolygonMes * \cgalParamNEnd * * \cgalParamNBegin{vertex_Gaussian_curvature} -* \cgalParamDescription{a reference to a scalar value to store the gaussian curvature at the vertex `v`} +* \cgalParamDescription{a reference to a scalar value to store the Gaussian curvature at the vertex `v`} * \cgalParamType{`std::reference_wrapper`} -* \cgalParamExtra{If this parameter is omitted, gaussian curvature will not be computed} +* \cgalParamExtra{If this parameter is omitted, Gaussian curvature will not be computed} * \cgalParamNEnd * * \cgalParamNBegin{vertex_principal_curvatures_and_directions} From 2bc8b1b49565f9f033c40d30dd08d1f6192d99f5 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 25 Mar 2023 13:29:15 +0200 Subject: [PATCH 128/161] user man doc improvements --- .../Polygon_mesh_processing.txt | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 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 e2bee6e295d8..49614be67c91 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 @@ -896,10 +896,10 @@ and meshes with n-gon faces. The algorithms used prove to work well in general. with noise on vertex positions, they give accurate results, on the condition that the correct vertex normals are provided. -The implementation is generic in terms of mesh data structure. It can be used on Surface_mesh, -Polyhedron_3 and other polygonal mesh structures based on the Face Graph Model. +The implementation is generic in terms of mesh data structure. It can be used on `Surface_mesh`, +`Polyhedron_3` and other polygonal mesh structures based on the concept `FaceGraph`. -These computations are performed using (on all vertices of mesh): +These computations are performed using (on all vertices of the mesh): - `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_Gaussian_curvature()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions()` @@ -916,29 +916,25 @@ Similarly, we can use the following functions to compute curvatures on a specifi \cgalFigureRef{icc_diff_radius} shows how the mean curvature changes depending on -the ball_radius named parameter which can be set to a value > 0 to get a smoother -distribution of values and "diffuse" the extreme values of curvatures across the mesh. +the named parameter `ball_radius`, which can be set to a value > 0 to get a smoother +distribution of values and "diffuses" the extreme values of curvatures across the mesh. \cgalFigureAnchor{icc_diff_radius}
- +
\cgalFigureCaptionBegin{icc_diff_radius} -The mean curvature distribution on a bear mesh with different values for the expanding ball radius +The mean curvature distribution on a bear mesh with different values for the ball radius parameter \cgalFigureCaptionEnd -Property maps are used to record the computed curvatures as shown in examples. - - -Property maps are an API introduced in the boost library that allows associating -values to keys. In the following examples, for each property map, we associate +\ref BGLPropertyMaps are used to record the computed curvatures as shown in examples. In the following examples, for each property map, we associate a curvature value to each vertex. \subsection ICCExampleSM Interpolated Corrected Curvatures on a Surface Mesh Example The following example illustrates how to compute the curvatures on vertices -and store them in property maps provided by the class `Surface_mesh`. +and store them in the property maps provided by the class `Surface_mesh`. \cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp} From 49c12d9265f2b0c02fd816f82d82f5e9141c4504 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 25 Mar 2023 13:44:36 +0200 Subject: [PATCH 129/161] ref doc fixes --- .../interpolated_corrected_curvatures.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index 2324b0e9e88c..71a2ceaaa911 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -1072,7 +1072,7 @@ class Interpolated_corrected_curvatures_computer * \cgalParamNEnd * * \cgalParamNBegin{vertex_Gaussian_curvature_map} -* \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} +* \cgalParamDescription{a property map associating Gaussian curvatures to the vertices of `pmesh`} * \cgalParamType{a class model of `WritablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` * as key type and `GT::FT` as value type} @@ -1080,11 +1080,11 @@ class Interpolated_corrected_curvatures_computer * \cgalParamNEnd * * \cgalParamNBegin{vertex_principal_curvatures_and_directions_map} -* \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} +* \cgalParamDescription{a property map associating principal curvatures and directions to the vertices of `pmesh`} * \cgalParamType{a class model of `WritablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` * as key type and `Principal_curvatures_and_directions` as value type} -* \cgalParamExtra{If this parameter is omitted, mean principal will not be computed} +* \cgalParamExtra{If this parameter is omitted, principal curvatures and directions will not be computed} * \cgalParamNEnd * * \cgalParamNBegin{ball_radius} @@ -1256,7 +1256,7 @@ void interpolated_corrected_Gaussian_curvature(const PolygonMesh& pmesh, /** * \ingroup PMP_corrected_curvatures_grp * -* computes the interpolated corrected principal curvatures across the mesh `pmesh`. +* computes the interpolated corrected principal curvatures and directions across the mesh `pmesh`. * * @tparam PolygonMesh a model of `FaceListGraph`. * @tparam VertexCurvatureMap a model of `WritablePropertyMap` with @@ -1265,7 +1265,7 @@ void interpolated_corrected_Gaussian_curvature(const PolygonMesh& pmesh, * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * * @param pmesh the polygon mesh. -* @param vcm the vertex property map in which the computed principal curvatures are stored. +* @param vcm the vertex property map in which the computed principal curvatures and directions are stored. * @param np an 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()`. * From 057b6fc2dd5011052b1049bbbfbc3728c3b875e8 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 27 Mar 2023 17:20:26 +0200 Subject: [PATCH 130/161] gaussian -> Gaussian in example files --- .../interpolated_corrected_curvatures_PH.cpp | 6 +++--- .../interpolated_corrected_curvatures_SM.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp index 0e3923dc67b8..ecf40b840829 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp @@ -30,7 +30,7 @@ int main(int argc, char* argv[]) boost::property_map>::type mean_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron), - gaussian_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron); + Gaussian_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron); boost::property_map>>::type principal_curvatures_and_directions_map = get(CGAL::dynamic_vertex_property_t>(), polyhedron); @@ -40,7 +40,7 @@ int main(int argc, char* argv[]) PMP::interpolated_corrected_Gaussian_curvature( polyhedron, - gaussian_curvature_map); + Gaussian_curvature_map); PMP::interpolated_corrected_principal_curvatures_and_directions( polyhedron, @@ -69,7 +69,7 @@ int main(int argc, char* argv[]) { auto PC = get(principal_curvatures_and_directions_map, v); std::cout << i << ": HC = " << get(mean_curvature_map, v) - << ", GC = " << get(gaussian_curvature_map, v) << "\n" + << ", GC = " << get(Gaussian_curvature_map, v) << "\n" << ", PC = [ " << PC.min_curvature << " , " << PC.max_curvature << " ]\n"; i++; } diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp index 811146dd6412..ab985e5068d0 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp @@ -29,11 +29,11 @@ int main(int argc, char* argv[]) // creating and tying surface mesh property maps for curvatures (with defaults = 0) bool created = false; - Surface_Mesh::Property_map mean_curvature_map, gaussian_curvature_map; + Surface_Mesh::Property_map mean_curvature_map, Gaussian_curvature_map; boost::tie(mean_curvature_map, created) = smesh.add_property_map("v:mean_curvature_map", 0); assert(created); - boost::tie(gaussian_curvature_map, created) = smesh.add_property_map("v:gaussian_curvature_map", 0); + boost::tie(Gaussian_curvature_map, created) = smesh.add_property_map("v:Gaussian_curvature_map", 0); assert(created); // we use a tuple of 2 scalar values and 2 vectors for principal curvatures and directions @@ -55,7 +55,7 @@ int main(int argc, char* argv[]) PMP::interpolated_corrected_Gaussian_curvature( smesh, - gaussian_curvature_map + Gaussian_curvature_map ); PMP::interpolated_corrected_principal_curvatures_and_directions( @@ -91,7 +91,7 @@ int main(int argc, char* argv[]) { auto PC = principal_curvatures_and_directions_map[v]; std::cout << v.idx() << ": HC = " << mean_curvature_map[v] - << ", GC = " << gaussian_curvature_map[v] << "\n" + << ", GC = " << Gaussian_curvature_map[v] << "\n" << ", PC = [ " << PC.min_curvature << " , " << PC.max_curvature << " ]\n"; } } From 5aa995dbe7a24727d22bcbee6d3d2170b0d3274b Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 27 Mar 2023 17:45:16 +0200 Subject: [PATCH 131/161] missing dots in ref documentation --- .../interpolated_corrected_curvatures.h | 250 +++++++++--------- 1 file changed, 125 insertions(+), 125 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index 71a2ceaaa911..a22761691b18 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -1037,54 +1037,54 @@ class Interpolated_corrected_curvatures_computer * \cgalNamedParamsBegin * * \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 `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)`} +* 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{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`.} * \cgalParamType{a class model of `ReadablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` -* as key type and `GT::Vector_3` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* as key type and `GT::Vector_3` as value type.} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`.} * \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using `compute_vertex_normals()`} +* computed using `compute_vertex_normals()`.} * \cgalParamNEnd * * \cgalParamNBegin{geom_traits} -* \cgalParamDescription{an instance of a geometric traits class} +* \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`} +* \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 * * \cgalParamNBegin{vertex_mean_curvature_map} -* \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`} +* \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`.} * \cgalParamType{a class model of `WritablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` * as key type and `GT::FT` as value type} -* \cgalParamExtra{If this parameter is omitted, mean curvatures will not be computed} +* \cgalParamExtra{If this parameter is omitted, mean curvatures will not be computed.} * \cgalParamNEnd * * \cgalParamNBegin{vertex_Gaussian_curvature_map} -* \cgalParamDescription{a property map associating Gaussian curvatures to the vertices of `pmesh`} +* \cgalParamDescription{a property map associating Gaussian curvatures to the vertices of `pmesh`.} * \cgalParamType{a class model of `WritablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` -* as key type and `GT::FT` as value type} -* \cgalParamExtra{If this parameter is omitted, Gaussian curvatures will not be computed} +* as key type and `GT::FT` as value type.} +* \cgalParamExtra{If this parameter is omitted, Gaussian curvatures will not be computed.} * \cgalParamNEnd * * \cgalParamNBegin{vertex_principal_curvatures_and_directions_map} -* \cgalParamDescription{a property map associating principal curvatures and directions to the vertices of `pmesh`} +* \cgalParamDescription{a property map associating principal curvatures and directions to the vertices of `pmesh`.} * \cgalParamType{a class model of `WritablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` -* as key type and `Principal_curvatures_and_directions` as value type} -* \cgalParamExtra{If this parameter is omitted, principal curvatures and directions will not be computed} +* as key type and `Principal_curvatures_and_directions` as value type.} +* \cgalParamExtra{If this parameter is omitted, principal curvatures and directions will not be computed.} * \cgalParamNEnd * * \cgalParamNBegin{ball_radius} @@ -1095,7 +1095,7 @@ class Interpolated_corrected_curvatures_computer * \cgalParamType{`GT::FT`} * \cgalParamDefault{`-1`} * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion is then just a sum of -* measures on faces around the vertex} +* measures on faces around the vertex.} * \cgalParamNEnd * * \cgalNamedParamsEnd @@ -1130,29 +1130,29 @@ void interpolated_corrected_curvatures(const PolygonMesh& pmesh, * \cgalNamedParamsBegin * * \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 `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)`} +* 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{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`.} * \cgalParamType{a class model of `ReadablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` -* as key type and `GT::Vector_3` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* as key type and `GT::Vector_3` as value type.} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`.} * \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using `compute_vertex_normals()`} +* computed using `compute_vertex_normals()`.} * \cgalParamNEnd * * \cgalParamNBegin{geom_traits} -* \cgalParamDescription{an instance of a geometric traits class} +* \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`} +* \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 * @@ -1160,11 +1160,11 @@ void interpolated_corrected_curvatures(const PolygonMesh& pmesh, * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures * by summing measures of faces inside a ball of this radius centered at the * vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball} +* inclusion ratio inside this ball.} * \cgalParamType{`GT::FT`} * \cgalParamDefault{`-1`} * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of -* measures on faces around the vertex} +* measures on faces around the vertex.} * \cgalParamNEnd * * \cgalNamedParamsEnd @@ -1201,29 +1201,29 @@ void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, * \cgalNamedParamsBegin * * \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 `pmesh`.} * \cgalParamType{a class model of `ReadablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` -* as key type and `%Point_3` as value type} -* \cgalParamDefault{`boost::get(CGAL::vertex_point, pmesh)`} +* 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` must be available in `PolygonMesh`.} * \cgalParamNEnd * * \cgalParamNBegin{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`.} * \cgalParamType{a class model of `GT::ReadablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` -* as key type and `%Vector_3` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* as key type and `%Vector_3` as value type.} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`.} * \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using `compute_vertex_normals()`} +* computed using `compute_vertex_normals()`.} * \cgalParamNEnd * * \cgalParamNBegin{geom_traits} -* \cgalParamDescription{an instance of a geometric traits class} +* \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`} +* \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 * @@ -1231,11 +1231,11 @@ void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures * by summing measures of faces inside a ball of this radius centered at the * vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball} +* inclusion ratio inside this ball.} * \cgalParamType{`GT::FT`} * \cgalParamDefault{`-1`} * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of -* measures on faces around the vertex} +* measures on faces around the vertex.} * \cgalParamNEnd * * \cgalNamedParamsEnd @@ -1272,29 +1272,29 @@ void interpolated_corrected_Gaussian_curvature(const PolygonMesh& pmesh, * \cgalNamedParamsBegin * * \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 `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)`} +* 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{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`.} * \cgalParamType{a class model of `ReadablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` -* as key type and `GT::Vector_3` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* as key type and `GT::Vector_3` as value type.} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`.} * \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using `compute_vertex_normals()`} +* computed using `compute_vertex_normals()`.} * \cgalParamNEnd * * \cgalParamNBegin{geom_traits} -* \cgalParamDescription{an instance of a geometric traits class} +* \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`} +* \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 * @@ -1302,11 +1302,11 @@ void interpolated_corrected_Gaussian_curvature(const PolygonMesh& pmesh, * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures * by summing measures of faces inside a ball of this radius centered at the * vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball} +* inclusion ratio inside this ball.} * \cgalParamType{`GT::FT`} * \cgalParamDefault{`-1`} * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of -* measures on faces around the vertex} +* measures on faces around the vertex.} * \cgalParamNEnd * * \cgalNamedParamsEnd @@ -1331,58 +1331,58 @@ void interpolated_corrected_principal_curvatures_and_directions(const PolygonMes * By providing mean, Gaussian and/or principal curvature and direction property maps as named parameters, the user * can choose which quantites to compute. * -* @tparam PolygonMesh a model of `FaceListGraph` -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* @tparam PolygonMesh a model of `FaceListGraph`. +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * -* @param pmesh the polygon mesh -* @param v the vertex of `pmesh` to compute the curvatures at +* @param pmesh the polygon mesh. +* @param v the vertex of `pmesh` to compute the curvatures at. * @param np an 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`} +* \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)`} +* 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{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`.} * \cgalParamType{a class model of `ReadablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` -* as key type and `GT::Vector_3` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* as key type and `GT::Vector_3` as value type.} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`.} * \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using `compute_vertex_normals()`} +* computed using `compute_vertex_normals()`.} * \cgalParamNEnd * * \cgalParamNBegin{geom_traits} -* \cgalParamDescription{an instance of a geometric traits class} +* \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`} +* \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 * * \cgalParamNBegin{vertex_mean_curvature} -* \cgalParamDescription{a reference to a scalar value to store the mean curvature at the vertex `v`} -* \cgalParamType{`std::reference_wrapper`} -* \cgalParamExtra{If this parameter is omitted, mean curvature will not be computed} +* \cgalParamDescription{a reference to a scalar value to store the mean curvature at the vertex `v`.} +* \cgalParamType{`std::reference_wrapper`.} +* \cgalParamExtra{If this parameter is omitted, mean curvature will not be computed.} * \cgalParamNEnd * * \cgalParamNBegin{vertex_Gaussian_curvature} -* \cgalParamDescription{a reference to a scalar value to store the Gaussian curvature at the vertex `v`} -* \cgalParamType{`std::reference_wrapper`} -* \cgalParamExtra{If this parameter is omitted, Gaussian curvature will not be computed} +* \cgalParamDescription{a reference to a scalar value to store the Gaussian curvature at the vertex `v`.} +* \cgalParamType{`std::reference_wrapper`.} +* \cgalParamExtra{If this parameter is omitted, Gaussian curvature will not be computed.} * \cgalParamNEnd * * \cgalParamNBegin{vertex_principal_curvatures_and_directions} -* \cgalParamDescription{a reference to an`Principal_curvatures_and_directions` object to store the principal curvatures and directions at the vertex `v`} -* \cgalParamType{`std::reference_wrapper>`} -* \cgalParamExtra{If this parameter is omitted, principal curvatures and directions will not be computed} +* \cgalParamDescription{a reference to an`Principal_curvatures_and_directions` object to store the principal curvatures and directions at the vertex `v`.} +* \cgalParamType{`std::reference_wrapper>`.} +* \cgalParamExtra{If this parameter is omitted, principal curvatures and directions will not be computed.} * \cgalParamNEnd * * \cgalParamNBegin{ball_radius} @@ -1393,7 +1393,7 @@ void interpolated_corrected_principal_curvatures_and_directions(const PolygonMes * \cgalParamType{`GT::FT`} * \cgalParamDefault{`-1`} * \cgalParamExtra{If this parameter is omitted (`-1`), the epansion is then just a sum of -* measures on faces around the vertex} +* measures on faces around the vertex.} * \cgalParamNEnd * * \cgalNamedParamsEnd @@ -1416,39 +1416,39 @@ void interpolated_corrected_curvatures_one_vertex(const PolygonMesh& pmesh, * \ingroup PMP_corrected_curvatures_grp * computes the interpolated corrected mean curvature at vertex `v` of mesh `pmesh`. * -* @tparam PolygonMesh a model of `FaceListGraph` -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* @tparam PolygonMesh a model of `FaceListGraph`. +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * -* @param pmesh the polygon mesh -* @param v the vertex of `pmesh` to compute the mean curvature at +* @param pmesh the polygon mesh. +* @param v the vertex of `pmesh` to compute the mean curvature at. * @param np an 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`} +* \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)`} +* 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{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`.} * \cgalParamType{a class model of `ReadablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` -* as key type and `GT::Vector_3` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* as key type and `GT::Vector_3` as value type.} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`.} * \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using `compute_vertex_normals()`} +* computed using `compute_vertex_normals()`.} * \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`} +* \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 * @@ -1456,16 +1456,16 @@ void interpolated_corrected_curvatures_one_vertex(const PolygonMesh& pmesh, * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures * by summing measures of faces inside a ball of this radius centered at the * vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball} +* inclusion ratio inside this ball.} * \cgalParamType{`GT::FT`} * \cgalParamDefault{`-1`} * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of -* measures on faces around the vertex} +* measures on faces around the vertex.} * \cgalParamNEnd * * \cgalNamedParamsEnd * -* @return the interpolated corrected mean curvature at the vertex `v` +* @return the interpolated corrected mean curvature at the vertex `v`. * * @see `interpolated_corrected_mean_curvature()` * @see `interpolated_corrected_Gaussian_curvature_one_vertex()` @@ -1493,39 +1493,39 @@ interpolated_corrected_mean_curvature_one_vertex(const PolygonMesh& pmesh, * \ingroup PMP_corrected_curvatures_grp * computes the interpolated corrected Gaussian curvature at vertex `v` of mesh `pmesh`. * -* @tparam PolygonMesh a model of `FaceListGraph` -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* @tparam PolygonMesh a model of `FaceListGraph`. +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * -* @param pmesh the polygon mesh -* @param v the vertex of `pmesh` to compute the Gaussian curvature at +* @param pmesh the polygon mesh. +* @param v the vertex of `pmesh` to compute the Gaussian curvature at. * @param np an 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`} +* \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)`} +* 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{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`.} * \cgalParamType{a class model of `ReadablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` -* as key type and `GT::Vector_3` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* as key type and `GT::Vector_3` as value type.} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`.} * \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using `compute_vertex_normals()`} +* computed using `compute_vertex_normals()`.} * \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`} +* \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 * @@ -1533,16 +1533,16 @@ interpolated_corrected_mean_curvature_one_vertex(const PolygonMesh& pmesh, * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures * by summing measures of faces inside a ball of this radius centered at the * vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball} +* inclusion ratio inside this ball.} * \cgalParamType{`GT::FT`} * \cgalParamDefault{`-1`} * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of -* measures on faces around the vertex} +* measures on faces around the vertex.} * \cgalParamNEnd * * \cgalNamedParamsEnd * -* @return the interpolated corrected Gaussian curvature at the vertex `v` +* @return the interpolated corrected Gaussian curvature at the vertex `v`. * * @see `interpolated_corrected_Gaussian_curvature()` * @see `interpolated_corrected_mean_curvature_one_vertex()` @@ -1570,39 +1570,39 @@ interpolated_corrected_Gaussian_curvature_one_vertex(const PolygonMesh& pmesh, * \ingroup PMP_corrected_curvatures_grp * computes the interpolated corrected principal curvatures and directions at vertex `v` of mesh `pmesh`. * -* @tparam PolygonMesh a model of `FaceListGraph` -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* @tparam PolygonMesh a model of `FaceListGraph`. +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * -* @param pmesh the polygon mesh -* @param v the vertex of `pmesh` to compute the principal curvatures and directions at +* @param pmesh the polygon mesh. +* @param v the vertex of `pmesh` to compute the principal curvatures and directions at. * @param np an 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`} +* \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 `%Point_3` as value type} -* \cgalParamDefault{`boost::get(CGAL::vertex_point, pmesh)`} +* 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` must be available in `PolygonMesh`.} * \cgalParamNEnd * * \cgalParamNBegin{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`} +* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`.} * \cgalParamType{a class model of `ReadablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` -* as key type and `%Vector_3` as value type} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`} +* as key type and `%Vector_3` as value type.} +* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`.} * \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using `compute_vertex_normals()`} +* computed using `compute_vertex_normals()`.} * \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`} +* \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 * @@ -1610,15 +1610,15 @@ interpolated_corrected_Gaussian_curvature_one_vertex(const PolygonMesh& pmesh, * \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures * by summing measures of faces inside a ball of this radius centered at the * vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball} +* inclusion ratio inside this ball.} * \cgalParamType{`GT::FT`} * \cgalParamDefault{`-1`} * \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of -* measures on faces around the vertex} +* measures on faces around the vertex.} * \cgalParamNEnd * \cgalNamedParamsEnd * -* @return the interpolated corrected principal curvatures and directions at the vertex `v` +* @return the interpolated corrected principal curvatures and directions at the vertex `v`. * * @see `interpolated_corrected_principal_curvatures_and_directions()` * @see `interpolated_corrected_mean_curvature_one_vertex()` From 2884d8b3cbdd14341d63142d4e96949e3a06be97 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 27 Mar 2023 18:06:06 +0200 Subject: [PATCH 132/161] using is_zero() & is_negative() for FT variables --- .../interpolated_corrected_curvatures.h | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index a22761691b18..72f7b1b93d07 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -470,8 +470,8 @@ Principal_curvatures_and_directions principal_curvatures_and_directions_from // returning principal curvatures and directions (with the correct sign) return Principal_curvatures_and_directions( - (v_mu0 != 0.0) ? -eig_vals[1] / v_mu0 : 0.0, - (v_mu0 != 0.0) ? -eig_vals[0] / v_mu0 : 0.0, + (!is_zero(v_mu0)) ? -eig_vals[1] / v_mu0 : 0.0, + (!is_zero(v_mu0)) ? -eig_vals[0] / v_mu0 : 0.0, min_eig_vec, max_eig_vec ); @@ -591,7 +591,7 @@ Vertex_measures interpolated_corrected_measures_one_vertex( // if it is not 0 (not completely outside), compute measures for selected curvatures (area is always computed) // and add neighboring faces to the bfs queue - if (f_ratio != 0.0) + if (!is_zero(f_ratio)) { vertex_measures.area_measure += f_ratio * interpolated_corrected_area_measure_face(u, x); @@ -669,7 +669,7 @@ template(pmesh) * 1e-6; // determine which curvatures are selected @@ -680,7 +680,7 @@ template vertex_measures; // if the radius is negative, we do not expand the ball (only the incident faces) - if (radius < 0) + if (is_negative(radius)) { vertex_measures = interpolated_corrected_measures_one_vertex_no_radius( pmesh, @@ -708,12 +708,12 @@ template(pmesh) * 1e-6; else ball_radius = radius; @@ -950,7 +950,7 @@ class Interpolated_corrected_curvatures_computer // if the face is inside the ball, add the measures // only add the measures for the selected curvatures (area measure is always added) - if (f_ratio != 0.0) + if (!is_zero(f_ratio)) { vertex_measures.area_measure += f_ratio * get(mu0_map, fi); @@ -986,20 +986,20 @@ class Interpolated_corrected_curvatures_computer for (Vertex_descriptor v : vertices(pmesh)) { // expand the computed measures (on faces) to the vertices - Vertex_measures vertex_measures = (ball_radius < 0) ? + Vertex_measures vertex_measures = (is_negative(ball_radius)) ? expand_interpolated_corrected_measure_vertex_no_radius(v) : expand_interpolated_corrected_measure_vertex(v); // compute the selected curvatures from the expanded measures and store them in the property maps // if the area measure is zero, the curvature is set to zero if (is_mean_curvature_selected) { - vertex_measures.area_measure != 0 ? + !is_zero(vertex_measures.area_measure) ? put(mean_curvature_map, v, 0.5 * vertex_measures.mean_curvature_measure / vertex_measures.area_measure) : put(mean_curvature_map, v, 0); } if (is_Gaussian_curvature_selected) { - vertex_measures.area_measure != 0 ? + !is_zero(vertex_measures.area_measure) ? put(gaussian_curvature_map, v, vertex_measures.gaussian_curvature_measure / vertex_measures.area_measure) : put(gaussian_curvature_map, v, 0); } From 4279a734bfb71821609ee14ea5bf33bcc90831cf Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 27 Mar 2023 19:35:31 +0200 Subject: [PATCH 133/161] minor linting changing --- .../interpolated_corrected_curvatures_PH.cpp | 16 ++++------ .../interpolated_corrected_curvatures_SM.cpp | 31 +++++++++---------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp index ecf40b840829..0aa0c196d3f5 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp @@ -31,20 +31,16 @@ int main(int argc, char* argv[]) boost::property_map>::type mean_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron), Gaussian_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron); + boost::property_map>>::type - principal_curvatures_and_directions_map = get(CGAL::dynamic_vertex_property_t>(), polyhedron); + principal_curvatures_and_directions_map = + get(CGAL::dynamic_vertex_property_t>(), polyhedron); - PMP::interpolated_corrected_mean_curvature( - polyhedron, - mean_curvature_map); + PMP::interpolated_corrected_mean_curvature(polyhedron, mean_curvature_map); - PMP::interpolated_corrected_Gaussian_curvature( - polyhedron, - Gaussian_curvature_map); + PMP::interpolated_corrected_Gaussian_curvature(polyhedron, Gaussian_curvature_map); - PMP::interpolated_corrected_principal_curvatures_and_directions( - polyhedron, - principal_curvatures_and_directions_map); + PMP::interpolated_corrected_principal_curvatures_and_directions(polyhedron, principal_curvatures_and_directions_map); // uncomment this to compute a curvature while specifying named parameters // Example: an expansion ball radius of 0.5 and a vertex normals map (does not have to depend on positions) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp index ab985e5068d0..d75ab14f6984 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp @@ -29,17 +29,23 @@ int main(int argc, char* argv[]) // creating and tying surface mesh property maps for curvatures (with defaults = 0) bool created = false; - Surface_Mesh::Property_map mean_curvature_map, Gaussian_curvature_map; - boost::tie(mean_curvature_map, created) = smesh.add_property_map("v:mean_curvature_map", 0); + Surface_Mesh::Property_map + mean_curvature_map, Gaussian_curvature_map; + + boost::tie(mean_curvature_map, created) = + smesh.add_property_map("v:mean_curvature_map", 0); assert(created); - boost::tie(Gaussian_curvature_map, created) = smesh.add_property_map("v:Gaussian_curvature_map", 0); + boost::tie(Gaussian_curvature_map, created) = + smesh.add_property_map("v:Gaussian_curvature_map", 0); assert(created); // we use a tuple of 2 scalar values and 2 vectors for principal curvatures and directions - Surface_Mesh::Property_map> principal_curvatures_and_directions_map; + Surface_Mesh::Property_map> + principal_curvatures_and_directions_map; - boost::tie(principal_curvatures_and_directions_map, created) = smesh.add_property_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) }); @@ -48,20 +54,11 @@ int main(int argc, char* argv[]) // user can call these fucntions to compute a specfic curvature type on each vertex. // (Note: if no ball radius is specified, the measure expansion of each vertex happens by // summing measures on faces adjacent to each vertex.) - PMP::interpolated_corrected_mean_curvature( - smesh, - mean_curvature_map - ); + PMP::interpolated_corrected_mean_curvature(smesh, mean_curvature_map); - PMP::interpolated_corrected_Gaussian_curvature( - smesh, - Gaussian_curvature_map - ); + PMP::interpolated_corrected_Gaussian_curvature(smesh, Gaussian_curvature_map); - PMP::interpolated_corrected_principal_curvatures_and_directions( - smesh, - principal_curvatures_and_directions_map - ); + PMP::interpolated_corrected_principal_curvatures_and_directions(smesh, principal_curvatures_and_directions_map); // uncomment this to compute a curvature while specifying named parameters // Example: an expansion ball radius of 0.5 and a vertex normals map (does not have to depend on positions) From 33c7f5c03aabadea45c0ed1078d38df38455efa2 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 27 Mar 2023 19:37:31 +0200 Subject: [PATCH 134/161] traillling spaces --- .../interpolated_corrected_curvatures_PH.cpp | 2 +- .../interpolated_corrected_curvatures_SM.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp index 0aa0c196d3f5..c93de26f26cc 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp @@ -33,7 +33,7 @@ int main(int argc, char* argv[]) Gaussian_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron); boost::property_map>>::type - principal_curvatures_and_directions_map = + principal_curvatures_and_directions_map = get(CGAL::dynamic_vertex_property_t>(), polyhedron); PMP::interpolated_corrected_mean_curvature(polyhedron, mean_curvature_map); diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp index d75ab14f6984..dae2ec0716ec 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp @@ -29,22 +29,22 @@ int main(int argc, char* argv[]) // creating and tying surface mesh property maps for curvatures (with defaults = 0) bool created = false; - Surface_Mesh::Property_map + Surface_Mesh::Property_map mean_curvature_map, Gaussian_curvature_map; - boost::tie(mean_curvature_map, created) = + boost::tie(mean_curvature_map, created) = smesh.add_property_map("v:mean_curvature_map", 0); assert(created); - boost::tie(Gaussian_curvature_map, created) = + boost::tie(Gaussian_curvature_map, created) = smesh.add_property_map("v:Gaussian_curvature_map", 0); assert(created); // we use a tuple of 2 scalar values and 2 vectors for principal curvatures and directions - Surface_Mesh::Property_map> + Surface_Mesh::Property_map> principal_curvatures_and_directions_map; - boost::tie(principal_curvatures_and_directions_map, 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), From 796d7cc57d79a06454afac969a329d49a428f6b3 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Fri, 31 Mar 2023 01:55:26 +0200 Subject: [PATCH 135/161] handled scale dependency and add tests for it --- .../interpolated_corrected_curvatures.h | 22 ++++-- ...test_interpolated_corrected_curvatures.cpp | 68 ++++++++++++++----- 2 files changed, 66 insertions(+), 24 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index 72f7b1b93d07..4b940f157e88 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -436,7 +436,8 @@ template Principal_curvatures_and_directions principal_curvatures_and_directions_from_anisotropic_measures( const std::array anisotropic_measure, const typename GT::FT v_mu0, - const typename GT::Vector_3 u_GT + const typename GT::Vector_3 u_GT, + const typename GT::FT avg_edge_length ) { // putting anisotropic measure in matrix form @@ -448,7 +449,7 @@ Principal_curvatures_and_directions principal_curvatures_and_directions_from // constant factor K to force the principal direction eigenvectors to be tangential to the surface Eigen::Matrix u(u_GT.x(), u_GT.y(), u_GT.z()); - const typename GT::FT K = 1000 * v_mu0; + const typename GT::FT K = 1000 * avg_edge_length * v_mu0; // symmetrizing and adding the constant term v_muXY = 0.5 * (v_muXY + v_muXY.transpose()) + K * u * u.transpose(); @@ -668,9 +669,13 @@ template(pmesh); + // if the radius is 0, we use a small epsilon to expand the ball (scaled with the average edge length) if (is_zero(radius)) - radius = average_edge_length(pmesh) * 1e-6; + radius = avg_edge_length * 1e-6; // determine which curvatures are selected const bool is_mean_curvature_selected = !is_default_parameter::value; @@ -723,7 +728,9 @@ template principal_curvatures_and_directions = principal_curvatures_and_directions_from_anisotropic_measures( vertex_measures.anisotropic_measure, vertex_measures.area_measure, - v_normal); + v_normal, + avg_edge_length + ); set_value(principal_curvatures_and_directions, get_parameter(np, internal_np::vertex_principal_curvatures_and_directions)); } } @@ -775,6 +782,7 @@ class Interpolated_corrected_curvatures_computer Vertex_position_map vpm; Vertex_normal_map vnm; FT ball_radius; + FT avg_edge_length; bool is_mean_curvature_selected; bool is_Gaussian_curvature_selected; @@ -813,6 +821,7 @@ class Interpolated_corrected_curvatures_computer // if no radius is given, we pass -1 which will make the expansion be only on the incident faces instead of a ball const FT radius = choose_parameter(get_parameter(np, internal_np::ball_radius), -1); + avg_edge_length = average_edge_length(pmesh); set_ball_radius(radius); // check which curvature maps are provided by the user (determines which curvatures are computed) @@ -828,7 +837,7 @@ class Interpolated_corrected_curvatures_computer void set_ball_radius(const FT radius) { // if given radius is 0, we use a small epsilon to expand the ball (scaled by the average edge length) if (is_zero(radius)) - ball_radius = average_edge_length(pmesh) * 1e-6; + ball_radius = avg_edge_length * 1e-6; else ball_radius = radius; } @@ -1010,7 +1019,8 @@ class Interpolated_corrected_curvatures_computer const Principal_curvatures_and_directions principal_curvatures_and_directions = principal_curvatures_and_directions_from_anisotropic_measures( vertex_measures.anisotropic_measure, vertex_measures.area_measure, - v_normal + v_normal, + avg_edge_length ); put(principal_curvatures_and_directions_map, v, principal_curvatures_and_directions); } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp index c0772ce989bc..2f1af8adb106 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp @@ -32,7 +32,7 @@ struct Average_test_info { Epic_kernel::FT principal_curvature_avg, Epic_kernel::FT expansion_radius = -1, Epic_kernel::FT tolerance = 0.9 - ): + ) : expansion_radius(expansion_radius), mean_curvature_avg(mean_curvature_avg), gaussian_curvature_avg(gaussian_curvature_avg), @@ -43,8 +43,7 @@ struct Average_test_info { }; -bool passes_comparison(Epic_kernel::FT result, Epic_kernel::FT expected, Epic_kernel::FT tolerance) -{ +bool passes_comparison(Epic_kernel::FT result, Epic_kernel::FT expected, Epic_kernel::FT tolerance) { if (abs(expected) < ABS_ERROR && abs(result) < ABS_ERROR) return true; // expected 0, got 0 else if (abs(expected) < ABS_ERROR) @@ -56,8 +55,10 @@ bool passes_comparison(Epic_kernel::FT result, Epic_kernel::FT expected, Epic_ke template void test_average_curvatures(std::string mesh_path, Average_test_info test_info, - bool compare_single_vertex = false -){ + bool compare_single_vertex = false, + int scale_factor_exponent = 0 +) { + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; PolygonMesh pmesh; const std::string filename = CGAL::data_file_path(mesh_path); @@ -67,14 +68,33 @@ void test_average_curvatures(std::string mesh_path, std::cerr << "Invalid input file." << std::endl; } - typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + // The following part is used to scale the given mesh and expected curvatures by a constant factor + // this is used to test the stability of the implementation across different scales + if (scale_factor_exponent) { + Epic_kernel::FT factor = pow(10, scale_factor_exponent); + + test_info.expansion_radius *= factor; + test_info.mean_curvature_avg /= factor; + test_info.gaussian_curvature_avg /= factor * factor; + test_info.principal_curvature_avg /= factor; + + auto vpm = get(CGAL::vertex_point, pmesh); + + for (vertex_descriptor vi : vertices(pmesh)) { + Epic_kernel::Point_3 pi = get(vpm, vi); + Epic_kernel::Point_3 pi_new(pi.x() * factor, pi.y() * factor, pi.z() * factor); + put(vpm, vi, pi_new); + } + } + typename boost::property_map>::type mean_curvature_map = get(CGAL::dynamic_vertex_property_t(), pmesh), gaussian_curvature_map = get(CGAL::dynamic_vertex_property_t(), pmesh); - typename boost::property_map>>::type - principal_curvatures_and_directions_map = get(CGAL::dynamic_vertex_property_t>(), pmesh); - + typename boost::property_map + >>::type + principal_curvatures_and_directions_map = + get(CGAL::dynamic_vertex_property_t>(), pmesh); // test_info.expansion_radius -> test if no radius is provided by user. if (test_info.expansion_radius < 0) { PMP::interpolated_corrected_mean_curvature(pmesh, mean_curvature_map); @@ -103,8 +123,7 @@ void test_average_curvatures(std::string mesh_path, Epic_kernel::FT mean_curvature_avg = 0, gaussian_curvature_avg = 0, principal_curvature_avg = 0; - for (vertex_descriptor v : vertices(pmesh)) - { + for (vertex_descriptor v : vertices(pmesh)) { mean_curvature_avg += get(mean_curvature_map, v); gaussian_curvature_avg += get(gaussian_curvature_map, v); principal_curvature_avg += get(principal_curvatures_and_directions_map, v).min_curvature @@ -131,8 +150,7 @@ void test_average_curvatures(std::string mesh_path, Epic_kernel::FT new_mean_curvature_avg = 0, new_Gaussian_curvature_avg = 0, new_principal_curvature_avg = 0; - for (vertex_descriptor v : vertices(pmesh)) - { + for (vertex_descriptor v : vertices(pmesh)) { new_mean_curvature_avg += get(mean_curvature_map, v); new_Gaussian_curvature_avg += get(gaussian_curvature_map, v); new_principal_curvature_avg += get(principal_curvatures_and_directions_map, v).min_curvature @@ -149,8 +167,7 @@ void test_average_curvatures(std::string mesh_path, assert(passes_comparison(gaussian_curvature_avg, new_Gaussian_curvature_avg, 0.99)); assert(passes_comparison(principal_curvature_avg, new_principal_curvature_avg, 0.99)); - if (compare_single_vertex) - { + if (compare_single_vertex) { // computing curvatures together from interpolated_corrected_curvatures() Epic_kernel::FT single_vertex_mean_curvature_avg = 0, @@ -160,8 +177,7 @@ void test_average_curvatures(std::string mesh_path, Epic_kernel::FT h, g; PMP::Principal_curvatures_and_directions p; - for (vertex_descriptor v : vertices(pmesh)) - { + for (vertex_descriptor v : vertices(pmesh)) { PMP::interpolated_corrected_curvatures_one_vertex( pmesh, v, @@ -193,7 +209,7 @@ int main() // For this mesh, ina addition to the whole mesh functions, we also compare against the single vertex // curvature functions to make sure the produce the same results // Expected: Mean Curvature = 2, Gaussian Curvature = 4, Principal Curvatures = 2 & 2 so 2 on avg. - test_average_curvatures("meshes/sphere.off", Average_test_info(2,4,2), true); + test_average_curvatures("meshes/sphere.off", Average_test_info(2, 4, 2), true); test_average_curvatures("meshes/sphere.off", Average_test_info(2, 4, 2), true); // Same mesh but with specified expansion radii of 0 and 0.25 (half radius of sphere) @@ -218,6 +234,22 @@ int main() test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0)); test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0.5)); + // Same tests as last one, but with a scaling on the mesh with different values to check for scale stability + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0), false, -6); + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0.5), false, -6); + + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0), false, -3); + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0.5), false, -3); + + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0), false, -1); + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0.5), false, -1); + + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0), false, 1); + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0.5), false, 1); + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0), false, 3); + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0.5), false, 3); + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0), false, 6); + test_average_curvatures("meshes/cylinder.off", Average_test_info(0.5, 0, 0.5, 0.5), false, 6); } From cef23a90456ece3a592b33fcdf17317726759bb3 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 8 Apr 2023 10:24:47 +0200 Subject: [PATCH 136/161] moving captions out of the figure --- .../doc/Polygon_mesh_processing/Polygon_mesh_processing.txt | 6 ++++-- 1 file changed, 4 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 49614be67c91..70f9cf00c0e7 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 @@ -921,10 +921,12 @@ distribution of values and "diffuses" the extreme values of curvatures across th \cgalFigureAnchor{icc_diff_radius}
- +
\cgalFigureCaptionBegin{icc_diff_radius} -The mean curvature distribution on a bear mesh with different values for the ball radius parameter +The mean curvature distribution on a bear mesh with different values for the ball +radius parameter. R = 0 (a), R = 0.025 (b), R = 0.05 (c), R = 0.16 (d). Note that +the max edge length is 0.031 and the size of bounding box of the mesh is 1 x 0.7 x 0.8. \cgalFigureCaptionEnd \ref BGLPropertyMaps are used to record the computed curvatures as shown in examples. In the following examples, for each property map, we associate From 8393933630960d1b23198aac9bc37b6ce38ea0f0 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sat, 8 Apr 2023 10:27:13 +0200 Subject: [PATCH 137/161] minor grammer fixes --- .../Polygon_mesh_processing.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 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 70f9cf00c0e7..987b88b0a217 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 @@ -905,8 +905,8 @@ These computations are performed using (on all vertices of the mesh): - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` -Where it is recommended to use the last function for computing multiple curvatures (example: mean and Gaussian) -as the implementation performs the shared computations only once, making it more efficient. +Where it is recommended to use the last function for computing multiple curvatures (for example: mean and +Gaussian) as the implementation performs the shared computations only once, making it more efficient. Similarly, we can use the following functions to compute curvatures on a specific vertex: - `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_one_vertex()` @@ -924,9 +924,9 @@ distribution of values and "diffuses" the extreme values of curvatures across th \cgalFigureCaptionBegin{icc_diff_radius} -The mean curvature distribution on a bear mesh with different values for the ball -radius parameter. R = 0 (a), R = 0.025 (b), R = 0.05 (c), R = 0.16 (d). Note that -the max edge length is 0.031 and the size of bounding box of the mesh is 1 x 0.7 x 0.8. +The mean curvature distribution on a bear mesh with different values for the ball radius +parameter. R = 0 (a), R = 0.025 (b), R = 0.05 (c), R = 0.16 (d). Note that the max +edge length is 0.031 and the size of the bounding box of the mesh is 1 x 0.7 x 0.8. \cgalFigureCaptionEnd \ref BGLPropertyMaps are used to record the computed curvatures as shown in examples. In the following examples, for each property map, we associate From f8a9862abfd91f33622830b9142e3137c57842b6 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 17 Apr 2023 15:14:37 +0200 Subject: [PATCH 138/161] incomplete update to user man doc --- .../Polygon_mesh_processing.txt | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 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 987b88b0a217..b454706b9723 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 @@ -892,10 +892,16 @@ not provide storage for the normals. This package provides methods to compute curvatures on polygonal meshes based on Interpolated Corrected Curvatures on Polyhedral Surfaces \cgalCite{lachaud2020}. This includes mean curvature, Gaussian curvature, principal curvatures and directions. These can be computed on triangle meshes, quad meshes, -and meshes with n-gon faces. The algorithms used prove to work well in general. Also, on meshes +and meshes with n-gon faces (for n-gons, the centroid must be inside the n-gon face). +The algorithms used prove to work well in general. Also, on meshes with noise on vertex positions, they give accurate results, on the condition that the correct vertex normals are provided. +\subsection ICCBackground Brief Background + + +\subsection ICCAPI API + The implementation is generic in terms of mesh data structure. It can be used on `Surface_mesh`, `Polyhedron_3` and other polygonal mesh structures based on the concept `FaceGraph`. @@ -914,6 +920,9 @@ Similarly, we can use the following functions to compute curvatures on a specifi - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_one_vertex()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures_one_vertex()` +\subsection ICCResults Results + +**To be updated** \cgalFigureRef{icc_diff_radius} shows how the mean curvature changes depending on the named parameter `ball_radius`, which can be set to a value > 0 to get a smoother @@ -921,12 +930,12 @@ distribution of values and "diffuses" the extreme values of curvatures across th \cgalFigureAnchor{icc_diff_radius}
- +
\cgalFigureCaptionBegin{icc_diff_radius} -The mean curvature distribution on a bear mesh with different values for the ball radius -parameter. R = 0 (a), R = 0.025 (b), R = 0.05 (c), R = 0.16 (d). Note that the max -edge length is 0.031 and the size of the bounding box of the mesh is 1 x 0.7 x 0.8. +The mean curvature on a mesh with different values for the ball radius +parameter: (a) R = 0, (b) R = 0.025, (c) R = 0.05, (d) R = 0.16. Note that the max +edge length is 0.031 and the size of the bounding box of the mesh is 1 x .7 x .8. \cgalFigureCaptionEnd \ref BGLPropertyMaps are used to record the computed curvatures as shown in examples. In the following examples, for each property map, we associate From 261eac81e9847bdc2b313ac19ae7a00edeaa9405 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Tue, 18 Apr 2023 14:30:37 +0200 Subject: [PATCH 139/161] user manual - incomplete --- .../Polygon_mesh_processing.txt | 57 ++++++++++++++++--- 1 file changed, 49 insertions(+), 8 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 b454706b9723..ead5b5a8c80c 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 @@ -889,16 +889,57 @@ not provide storage for the normals. **************************************** \section PMPICC Computing Curvatures -This package provides methods to compute curvatures on polygonal meshes based on Interpolated Corrected Curvatures -on Polyhedral Surfaces \cgalCite{lachaud2020}. This includes mean curvature, Gaussian curvature, -principal curvatures and directions. These can be computed on triangle meshes, quad meshes, -and meshes with n-gon faces (for n-gons, the centroid must be inside the n-gon face). -The algorithms used prove to work well in general. Also, on meshes -with noise on vertex positions, they give accurate results, on the condition that the -correct vertex normals are provided. +This package provides methods to compute curvatures on polygonal meshes based on Interpolated +Corrected Curvatures on Polyhedral Surfaces \cgalCite{lachaud2020}. This includes mean curvature, +Gaussian curvature, principal curvatures and directions. These can be computed on triangle meshes, +quad meshes, and meshes with n-gon faces (for n-gons, the centroid must be inside the n-gon face). +The algorithms used prove to work well in general. Also, on meshes with noise on vertex positions, +they give accurate results, on the condition that the correct vertex normals are provided. \subsection ICCBackground Brief Background +Curvatures are quantities that describe the local geometry of a surface. They are important in many +geometry processing applications. since surfaces are 2-dimensional objects (embedded in 3D), they can bend +in 2 independent directions. These directions are called principal directions, and the amount of bending +in each direction is called the principal curvature: \f$ k_1 \f$ and \f$ k_2 \f$. Curvature is usually +expressed as scalar quantities like the mean curvature \f$ H \f$ and the Gaussian curvature \f$ K \f$ +which are defined in terms of the principal curvatures. + +The algorithms are based on the following paper \cgalCite{lachaud2020}. It introduces a new way to +compute curvatures on polygonal meshes. The main idea is based on decoupling the normal information from +the position information, which is useful for dealing with digital surfaces, or meshes with noise on +vertex positions. To compute the curvatures, we first compute interpolated curvature measures for each face +as described below. For a triangle \f$ \tau_{ijk} \f$, with vertices \a i, \a j, \a k: + +\f[ + \begin{align*} + \mu^{(0)}(\tau_{ijk}) = &\frac{1}{2} \langle \bar{\mathbf{u}} \mid (\mathbf{x}_j - \mathbf{x}_i) \times (\mathbf{x}_k - \mathbf{x}_i) \rangle, \\ + \mu^{(1)}(\tau_{ijk}) = &\frac{1}{2} \langle \bar{\mathbf{u}} \mid (\mathbf{u}_k - \mathbf{u}_j) \times \mathbf{x}_i + (\mathbf{u}_i - \mathbf{u}_k) \times \mathbf{x}_j + (\mathbf{u}_j - \mathbf{u}_i) \times \mathbf{x}_k \rangle, \\ + \mu^{(2)}(\tau_{ijk}) = &\frac{1}{2} \langle \mathbf{u}_i \mid \mathbf{u}_j \times \mathbf{u}_k \rangle, \\ + \mu^{\mathbf{X},\mathbf{Y}}(\tau_{ijk}) = & \frac{1}{2} \big\langle \bar{\mathbf{u}} \big| \langle \mathbf{Y} | \mathbf{u}_k -\mathbf{u}_i \rangle \mathbf{X} \times (\mathbf{x}_j - \mathbf{x}_i) \big\rangle + -\frac{1}{2} \big\langle \bar{\mathbf{u}} \big| \langle \mathbf{Y} | \mathbf{u}_j -\mathbf{u}_i \rangle \mathbf{X} \times (\mathbf{x}_k - \mathbf{x}_i) \big\rangle, + \end{align*} +\f] +where \f$ \langle \cdot \mid \cdot \rangle \f$ denotes the usual scalar product, +\f$ \bar{\mathbf{u}}=\frac{1}{3}( \mathbf{u}_i + \mathbf{u}_j + \mathbf{u}_k )\f$. + +The first measure \f$ \mu^{(0)} \f$ is the area measure of the triangle, and the second and third measures +\f$ \mu^{(1)} \f$ and \f$ \mu^{(2)} \f$ are the mean and Gaussian corrected curvature measures of the triangle. +The last measure \f$ \mu^{\mathbf{X},\mathbf{Y}} \f$ is the anisotropic corrected curvature measure of the triangle. +The anisotropic measure is later used to compute the principal curvatures and directions through an eigenvalue +solver. + +The interpolated curvature measures are then computed for each vertex \f$ v \f$ as the sum of +the curvature measures of the faces in a ball around \f$ v \f$ weighted by the inclusion ratio of the +triangle in the ball. if the ball radius is not specified, the sum is instead over the incident faces +of \f$ v \f$. + +To get the final curvature value for a vertex \f$ v \f$, the respective interpolated curvature measure +is divided by the interpolated area measure. + +\f[ +\mu^{(k)}( B ) = \sum_{\tau : \text{triangle} } \mu^{(k)}( \tau ) \frac{\mathrm{Area}( \tau \cap B )}{\mathrm{Area}(\tau)}. +\f] \subsection ICCAPI API @@ -920,7 +961,7 @@ Similarly, we can use the following functions to compute curvatures on a specifi - `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_one_vertex()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures_one_vertex()` -\subsection ICCResults Results +\subsection ICCResults Results & Performance **To be updated** From 585e79b67e5750a541d18e97487f9b23d07d5d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 26 Apr 2023 13:06:19 +0200 Subject: [PATCH 140/161] add authors from the history section --- .../doc/Polygon_mesh_processing/PackageDescription.txt | 2 +- .../doc/Polygon_mesh_processing/Polygon_mesh_processing.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 9c8b5d6f2948..bf667377a96a 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -79,7 +79,7 @@ \cgalPkgPicture{hole_filling_ico.png} \cgalPkgSummaryBegin -\cgalPkgAuthors{Sébastien Loriot, Mael Rouxel-Labbé, Jane Tournois, and Ilker %O. Yaz} +\cgalPkgAuthors{David Coeurjolly, Jaques-Olivier Lachaud, Konstantinos Katriopla, Sébastien Loriot, Mael Rouxel-Labbé, Hossam Saeed, Jane Tournois, 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.} 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 ead5b5a8c80c..eac246d8ab47 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 David Coeurjolly, Jaques-Olivier Lachaud, Konstantinos Katriopla, Sébastien Loriot, Mael Rouxel-Labbé, Hossam Saeed, Jane Tournois, and Ilker %O. Yaz \image html neptun_head.jpg \image latex neptun_head.jpg From ae5d32ba2e425853d4cf822ffd64e19c1d45e7ad Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 1 May 2023 12:23:20 +0200 Subject: [PATCH 141/161] citing/referencing "corrected curvature measures" + refining the theo background --- Documentation/doc/biblio/geom.bib | 12 +++++++ .../Polygon_mesh_processing.txt | 33 ++++++++++--------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Documentation/doc/biblio/geom.bib b/Documentation/doc/biblio/geom.bib index 7c9fec6e420d..f072116a2e27 100644 --- a/Documentation/doc/biblio/geom.bib +++ b/Documentation/doc/biblio/geom.bib @@ -152065,3 +152065,15 @@ @article{lachaud2020 month = jul, year = {2020} } + +@article{lachaud2022 + author = {Jacques-Olivier Lachaud and Pascal Romon and Boris Thibert}, + journal = {Discrete & Computational Geometry}, + title = {Corrected Curvature Measures}, + volume = {68}, + pages = {477-524}, + month = jul, + year = {2022}, + url = {https://doi.org/10.1007/s00454-022-00399-4} +} + 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 eac246d8ab47..354c622f0e99 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 @@ -898,18 +898,21 @@ they give accurate results, on the condition that the correct vertex normals are \subsection ICCBackground Brief Background -Curvatures are quantities that describe the local geometry of a surface. They are important in many -geometry processing applications. since surfaces are 2-dimensional objects (embedded in 3D), they can bend +Surface curvatures are quantities that describe the local geometry of a surface. They are important in many +geometry processing applications. As surfaces are 2-dimensional objects (embedded in 3D), they can bend in 2 independent directions. These directions are called principal directions, and the amount of bending -in each direction is called the principal curvature: \f$ k_1 \f$ and \f$ k_2 \f$. Curvature is usually -expressed as scalar quantities like the mean curvature \f$ H \f$ and the Gaussian curvature \f$ K \f$ -which are defined in terms of the principal curvatures. - -The algorithms are based on the following paper \cgalCite{lachaud2020}. It introduces a new way to -compute curvatures on polygonal meshes. The main idea is based on decoupling the normal information from -the position information, which is useful for dealing with digital surfaces, or meshes with noise on -vertex positions. To compute the curvatures, we first compute interpolated curvature measures for each face -as described below. For a triangle \f$ \tau_{ijk} \f$, with vertices \a i, \a j, \a k: +in each direction is called the principal curvature: \f$ k_1 \f$ and \f$ k_2 \f$ (denoting max and min +curvatures). Curvature is usually expressed as scalar quantities like the mean curvature \f$ H \f$ and +the Gaussian curvature \f$ K \f$ which are defined in terms of the principal curvatures. + +The algorithms are based on the two papers \cgalCite{lachaud2022} and \cgalCite{lachaud2020}. They +introduce a new way to compute curvatures on polygonal meshes. The main idea in \cgalCite{lachaud2022} is +based on decoupling the normal information from the position information, which is useful for dealing with +digital surfaces, or meshes with noise on vertex positions. \cgalCite{lachaud2020} introduces some +extensions to this framework. As it uses linear interpolation on the corrected normal vector field +to derive new closed form equations for the corrected curvature measures. These interpolated +curvature measures are the first step for computing the curvatures. For a triangle \f$ \tau_{ijk} \f$, +with vertices \a i, \a j, \a k: \f[ \begin{align*} @@ -923,10 +926,10 @@ as described below. For a triangle \f$ \tau_{ijk} \f$, with vertices \a i, \a j, where \f$ \langle \cdot \mid \cdot \rangle \f$ denotes the usual scalar product, \f$ \bar{\mathbf{u}}=\frac{1}{3}( \mathbf{u}_i + \mathbf{u}_j + \mathbf{u}_k )\f$. -The first measure \f$ \mu^{(0)} \f$ is the area measure of the triangle, and the second and third measures -\f$ \mu^{(1)} \f$ and \f$ \mu^{(2)} \f$ are the mean and Gaussian corrected curvature measures of the triangle. -The last measure \f$ \mu^{\mathbf{X},\mathbf{Y}} \f$ is the anisotropic corrected curvature measure of the triangle. -The anisotropic measure is later used to compute the principal curvatures and directions through an eigenvalue +The first measure \f$ \mu^{(0)} \f$ is the area measure of the triangle, and the measures \f$ \mu^{(1)} \f$ and +\f$ \mu^{(2)} \f$ are the mean and Gaussian corrected curvature measures of the triangle. The last measure +\f$ \mu^{\mathbf{X},\mathbf{Y}} \f$ is the anisotropic corrected curvature measure of the triangle. The +anisotropic measure is later used to compute the principal curvatures and directions through an eigenvalue solver. The interpolated curvature measures are then computed for each vertex \f$ v \f$ as the sum of From 83bf49bf3977565330e6316cd2640fc60c38cb46 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Mon, 1 May 2023 12:29:25 +0200 Subject: [PATCH 142/161] Computing curvatures mentioned in the outline section (1.3) --- .../doc/Polygon_mesh_processing/Polygon_mesh_processing.txt | 1 + 1 file changed, 1 insertion(+) 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 354c622f0e99..6b6f979ba59f 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 @@ -49,6 +49,7 @@ mesh, which includes point location and self intersection tests. - \ref PMPCombinatorialRepair : repair of polygon meshes and polygon soups. - \ref PMPGeometricRepair : repair of the geometry of polygon meshes. - \ref PMPNormalComp : normal computation at vertices and on faces of a polygon mesh. +- \ref PMPICC : computing curvatures (mean, gaussian, principal) on a polygon mesh. - \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, ...). From 4187e40c2551780dbe3823b5db36fc38ba5d97db Mon Sep 17 00:00:00 2001 From: David Coeurjolly Date: Wed, 23 Aug 2023 15:56:58 +0200 Subject: [PATCH 143/161] (WIP) documentation update --- .../Polygon_mesh_processing.txt | 36 +++++++++++++----- .../fig/bimba-dmax0.040000-0.000000.jpg | Bin 0 -> 140843 bytes .../fig/bimba-dmin0.040000-0.000000.jpg | Bin 0 -> 167482 bytes .../fig/bimba-gaussian0.040000-0.000000.jpg | Bin 0 -> 48609 bytes .../fig/bimba-mean0.020000-0.000000.jpg | Bin 0 -> 53722 bytes .../fig/bimba-mean0.020000-0.002000.jpg | Bin 0 -> 78071 bytes .../fig/bimba-mean0.030000-0.000000.jpg | Bin 0 -> 51750 bytes .../fig/bimba-mean0.030000-0.002000.jpg | Bin 0 -> 75293 bytes .../fig/bimba-mean0.040000-0.000000.jpg | Bin 0 -> 50398 bytes .../fig/bimba-mean0.040000-0.002000.jpg | Bin 0 -> 73835 bytes .../fig/bimba-mean0.050000-0.000000.jpg | Bin 0 -> 49763 bytes .../fig/bimba-mean0.050000-0.002000.jpg | Bin 0 -> 72992 bytes 12 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-dmax0.040000-0.000000.jpg create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-dmin0.040000-0.000000.jpg create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-gaussian0.040000-0.000000.jpg create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.020000-0.000000.jpg create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.020000-0.002000.jpg create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.030000-0.000000.jpg create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.030000-0.002000.jpg create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.040000-0.000000.jpg create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.040000-0.002000.jpg create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.050000-0.000000.jpg create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.050000-0.002000.jpg 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 6b6f979ba59f..9baee74449b5 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 @@ -969,20 +969,38 @@ Similarly, we can use the following functions to compute curvatures on a specifi **To be updated** -\cgalFigureRef{icc_diff_radius} shows how the mean curvature changes depending on -the named parameter `ball_radius`, which can be set to a value > 0 to get a smoother -distribution of values and "diffuses" the extreme values of curvatures across the mesh. +First, \cgalFigureRef{icc_measures} first illustrates various curvature measure on a triangular mesh. -\cgalFigureAnchor{icc_diff_radius} +\cgalFigureAnchor{icc_measures}
- + + + +
-\cgalFigureCaptionBegin{icc_diff_radius} -The mean curvature on a mesh with different values for the ball radius -parameter: (a) R = 0, (b) R = 0.025, (c) R = 0.05, (d) R = 0.16. Note that the max -edge length is 0.031 and the size of the bounding box of the mesh is 1 x .7 x .8. +\cgalFigureCaptionBegin{icc_measures} +Mean curvature, Gaussian curvature, minimal principal curvature direction and minimal principal curvature direction on a mesh (ball radius set to 0.04). \cgalFigureCaptionEnd +\cgalFigureAnchor{icc_various_ball_radii} +
+ + + +
+ + + + +
+\cgalFigureCaptionBegin{icc_various_ball_radii} +When changing the integration ball radius, we obtain a scale space of curvature measure that can be used to tackle possible noise in the input as illustrated in the second row (mean curvature only with fixed colormap ranges and ball radii in {0.02,0.03,0.04,0.05}). +\cgalFigureCaptionEnd + + + + + \ref BGLPropertyMaps are used to record the computed curvatures as shown in examples. In the following examples, for each property map, we associate a curvature value to each vertex. diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-dmax0.040000-0.000000.jpg b/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-dmax0.040000-0.000000.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8a878efff69fcd973fc96180c5bde195b7736486 GIT binary patch literal 140843 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�IPIg4fx`?Gqtl&gFA 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

{sYyCzV&4_(M(Umr#!n5s`wnph{G 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

p4%RkLT}k0`nQ{ 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

u* z+Q{B#V^n4F`X8T@)py1jpu&yvqSkMa6EuijcbK;(uWM09hEI9Ex$$k6#qsm7ESX&l zUN;jU5@dQB8hS!bg=POx!KnfPFHmP;aj5FO&!qGshrkXmi{jOwtemxVoST zYqcs<^*Z`1CJvklAP}{=3blfQBJtoU^A)ZN8^K-q&1;38sfM$PDzkwkJ;*Kn3DS}P z&&kyemyxM$tvWQb=4`#j-!`*&T)Wkdw7!DwU~CNU@`o%*@3^7a#L_coabGzpjjw7K z2BZ+O&V}q+7dwyg&i{$CV+Octk|11zXB6JP1u_P@^PmnEN`syrxu)v7mTdfHHUyGk3zl@!8wI+j{NCE{}&6Kv+m_G|}n~nxDOjGTA z1!rXDFM@^=8}h~82ce+wp}r^SuhJ)|G!smlXuor)p4AMM15Z?(<#5^HkM!ha)W^aO z!S<=={s$QZkkb~eKFofex|ymfU!s^l>xw0ORl^ ztE(0vsYFOiTeClLePkGg(8kMt?!RF}f*T%u#NijNn&Q;~;RM` zP-KMAbvd~8@*fl+1@#b}Cln+e1D3a_A`-a3S8oqd=!}~E1^0wpD|=6Bno8=OwCt=U zouIRl8|+4GYZR~Y)Q|q1DIVU-xy)Il_Nt=Lm;*0|@C_KOb8Ou@BO)KLc=6Yuc7f@B z!gaT?MQWb)XDqwR=3&;foz8jWt#kB3aVgsQMxc*TcT}$VItsgFNnfwAHT}2L2ELPc zZ>|z?oLwm7FY=|3--6Qrvt0ZCctV%Gf?gFWU=s5$$AydOL4D!~nt11_CaYwtB-eW= z#GvwO>l#;U^+fe zyhOP)3aGR@Sn3$~oDeL|8%JQznO-SBdBi$tzj{~YPH9nu%RJjWQp(6(aIdb1R%hYs?42;PbX~-Z!%Cb_VP!fR`7~q9=$A>UBG8C{47UzC5s4QVmnj4 zu#gy2(2t`wJ+LVcLOH76JTb@#H~tf6VvAEYz*hN2#T(xXJ%TJZ9OOa{8*oj$q1A({%il?5P6w5uWOj z(-OtmxyVafkzdKx$fXS}&M=ZB8@w}``5%d!PoT@eyf56!A-`ERnApJJn1>=fDflev zgkPGV5N7OXQ1o904^w`+!>GX>Hwc=a8@AUZr2^f96#lP%+QS%wxRw=Eyk+-X?!HY( z5}mQFt(|U0nOE8#9MqxFLk+~>O^~%3eKT>FKNtYdw(eOr$2+;6*!^#O5FM;>%@ORD z<3_#t>V_21(Fh{O`Yr#Xm{}iG7SH)M0{5b7q{Yu0u*G-jr$1^VBrV@vHN%V^TR0*t>HV;BC-?)Gv3ZW+%jZnOn%Pi~Yl_o#}j6_fPYl_7397#ro*gVPQXj=fb< zsIMF90;2K&1KUt&!Wu<%wM&Fz^7y<^d^|uWs>_xk7|Oe8qA%aHK)wKe0kq;9Sc1t zL&n;5C(ku8=DZ@g4d}}bC=xw1*Y0>5$3Nwr(J19G)gMn?G;YL)7tlKhFgX?Ob}SNr zyU1pv;uKVxA2e_?Y(g?S=H6FsJ^JM!AAMQvaU#exG5Pw3ZlA6%aW^2=0!wL#>P?dS zc==k%=n7;DtM5@ICBWwLJ?hvWmv`Ts%mF+hupnJz&W$=^S0Odc=R!^^Y|51Z^VcGp zGO`2niU$t|U30w?GK)$vGglVGKN{41B~lx~T&>$l(NzIklrvB?DoYW2O+g(^gUMAJ z*~Dpfh^3FEUumqJ{v9|!YNdrD^do_7AWb(L-b=bt9%WQm^#8WJRXB1_(n0u(HXi$E zbK=e3fSmc{_6xY*y^rly`J%`*$Rv8TM?K1SMOt!FpGO=zRIUFI@>{1kJmzEP29!!% zT#Q|uyXVuI6tJniaZNPwAd3gu{*$QP3c@>%g zCUVe&;JEAnu_Y=M4UzyvtC#8wF+3hk{pd%6EkgrX?m;xy(VZ&&zxm=W04G8z=}EoJ z3m_vq#-F%=^f?LOs!a2=Naq{?B*vE-|8J}2%@bo1mkqJEA*5WRI`%}t0vc^9^PV%? zVywU{v%rBYN=4Ilh!|KH^JJ0`FPXu%(?WAVZcy?n2Qmk@Eqn(6#=HYQKmaZZ!3yL- zlF*Ixd(JKu0#CGXM&wwp?(u&X$VTW10(5l!ek-`VvttYrovXgWPP%eD#nL$Lf!_tb zi1Urz4evy8P<8Nznawh2vUS3C7I%n}V-Hwnp93eZaUE5J|65g18gARTwYQU054~6ce2n(zcj5mMs zsfSUrAR`cw`9e)$Ft<7LfGZ7SG{w%kCC<(`HFa~ zpxhv+;`}pb+w4gx2U)i7&jBy&hr*ur!{E1tLlpXxBefGA)C+de!JkqkmUmPi94gD% z+mY=YGh_0Z&JMo2MTT&8&T$lBZ-bJ&Hi9r9d2qv-SIL-rrTD<)CiU$evEeD&gYOkh z)CLgScC_$1(|&rXJSBzwQ7`&sB+RdGgd=A;u^bzg|N?Id*-p zQ?11!goE|acQ=SM`^Oz;bPD#G`Hk7Dr}$+NLklY5(;*<)uIC@)`72r!UQpqXF(%5u z5S3pvkKoK9LM~3H(d0hDxCrFX&N6DletCLhM$C^!RHBPh;)GWDqX)&0l!c=_o(M7F zQv8415&hp^`RxY!c9GdU?@`afap+DKw_TIp`h#)^d4K(=X{xwDsEqJBn$^wHE~gqI zp-P{yu`W^UwQ$6aoIB58AP~;Tu6V6aF=YXJ_}H}Rb-$ko@={$ZKU6cyG-m-al+6Pn z7l#0po7(s04~B8`H4>}_AhHvHHna7LAuZpb$gruc2aUqXOL@3UdR=7)r*QWgq@z7DkuVt}zYAwT{tMdt>fe*&TEu~Nl6QB>O zDWJRFX-;7IzPnMh!TgEzk~6CG-YVA8)f^%kGet*+x54pq9wq35%@NM50Ld>xdJ2|* zR8~ikxe6oLj90I#801wZ@&E%4-c9nP5sb9^o$5%*l9i%O+xRoteF=WGk!enMb;VzL zNcrbQ^h@_mL&M&cFjp5?S-gbO6A!xxHfStYWC^<&5XSQj@0#j&3*#ipVzvqrZ+vah zj+tY9!iSmXYQ3FfEN!eQ1(GNP8{p-+|41|V>r8`4=YPhNpgkGju-7a2ei|YuFMSGf zFqucOi{H?<8rzo_8`UvcR@k&f>zZmuJ!Q%Zevtkmk7}Vk8hdiR?m}m5(}-oym@{dQ zn}cT3Dn(+OtcQ%2-BgFFOlz3U4i$IQML~@g)z==6P+p!^Yo2_hwCP73$Z)vp`N2@} zJU@MrCw-76u{Y-j=Y9}(U+frLetfD5jdLAv3YoCH8><}W_@ibPF;fnKSreP!49mu_>^SllK}?9#Q7ONU^is%@ z!lIv^Sr-+WO!!+Mluv=c^2g3`lPn5QvH9yYBI1ww9uiET0MQz5U3g5e84ql)y~c3b zG0SRBd#WBtX+n}ig2j5X&nhzDDQE9(g=9R%z&z!v+s`u=sOqD-x3Z|6byY@v#_*&<(iov5C7n~xRCI2UQxuL2$dtLtKX9GAQELci7I1VfQDU!75 z4>5N(H(sj&K*@>0US%8%v!KTbZK(xvalS?EB@$=9Tt=KV}vLbw)C1;pAX2JG0Kc2Uq5sJL`1*4{t@uue8k)KOUl zM#M3px6f+)U*T-QLTm~wWp+CTdtrMU1S6=-)VS9e&xgbC2W4vPni>-L9A5?Ix#$kf zHrX>VHL=$v?Yl18pr?=AdCoyxi50kdq9lDMnw$@8D#KR$NJ{E|VH>U)CFCz}SL-M= zB<$T)XFo@{R)B?6MN}usx8gUtzWHOcA`W_agrF<6nm&u8b=`3pyDx`+HFLviIQjw*DDn-v$H`%Va9xP6OJE~Fv)o5p36sZt)rG=>xG z%)UAC^rEEs_CU${Mn74`Pe1ABxar#AHYY+uOr)7B5pEIw0iPii$+d+Me-Xz2puXWH zco6&zS2M6*U*gQNQExY|UZ=x!7UdkcC9D=&4AI-bur(iRLT>AN*?d3EGq-=dvTwd^ zWW18WNFu`?Z=%yrM!W{<@o3N}J<6QRc0Fx*fP94FOduy<_-$$SG-hyXZw9|>sMRLQ zG_!8K>+7+FgJK`^m{kl$+c(+B@fT)#ri)8a4`)p254dNA-uuejLNe`zD406{S(PYpHw*EFriRc5<#M7&Dk*;2%S@G>s$u z6|M=7mZ~!B1C=CXTSr-%AY0TJY}f@@Y^i^{r173%?|C_3}Sd3XwHx25ztXo72>J6R(vz zu>9@}1*PXeCU<7oklqwD-5|I<`4SFA8Q1&9HX3PKz45AwU;$U*U98KzKoxpl(m`xU zGstsWjqCdJriErT;Z;eAQ^YiknukgO+h{D&5|f`uHsG`5KJz=etyZXi90oUT9Yp2sC;8nSieL#Rl~H8h z{k=-lsLGqnvWv?^X9=*mQ{nOrVP`yCt{|V}dz?(#fHXP08T|*<1Y_)r5yu}{W+MBs zzZpU)?DP=6`v!F*UUsTlK0dYiLzH0iJBCQ=>x!G&+<#E{=(vR4j*zinzU=?+C;{;k z{a^Z1a03Jl-DW6{$q>yuff|6G3T}N_nf$VE-kz0}s$xx#(t3kj$nTxO z{!}p%LxA8!F#V~qag_ctRGq=LY-QTMzu)@p-7lCIYA56*CXF-EpWcMYt>FjtT`e_w zdGnPaY&wfI^S&}s=l=9Du@rtd9p#PDTbqFQlBKiVDZkrgk(#p-DOKHz`wCC__7L-V zih+cit`d=;{!duHogrbftN)R{HA5^dmA zpfs*v63ehvQA112lX87?ApdHbP*%P+8mlPosX52G)+yde|L~8lIVD!NcVW zOgz#PyO6(Gt_?ph4WbncqCj)9e(2`0jp6+j4h)@j-6;DHN?C&f*%Bwy4NaxS+_Z#P_w&@e zFxIsY^Y!IIt-8w8G{CUrn#A9T2}mZJUY!0n#N5czVaNNXDd;uHX!1gv1PtHeA|IwD zKlN7!>Jo4L`)THNraPMB)pZuK0*PA7jP1Z>F{Y35i&DzdYd|lHE&%Avt7g<|Qw_>a zHqC0yJbY)&YkHu)bTxEE^$|?@S{!P$DVaXMRMQAf{U*$Ef%9c=6TCtjt5qcU_&k3E zUBMjGE??zHm`6CO><{|M-Hv#@*}myOuNFsjr6-gCBYTPXq9&euqQ(LIwmyN>XqA2H zq(Ag*U+MNH3##;oIJkS>3WloLZ&E%YyTKb<7a|O(x8V?0vEI1z*)Vsm& z*a|yS&~*-9+Y??Z>L1+QUN9TGk7|vF{>b~hkIpcAK|c+bDA7TTqf1gU62u5+gk(Zd`NHNedB^l6?(e9-4rG zV-lp}GhO`rOZ3phGNU0-NhB-Zd%%w8{TDlYsBX@Q`G?*5y3E|&72@9Cvh{ZA^X$}g z*3?WDKe1 zkUq^rYv)9Qa3iLZgqiRtx~&otdEtRXs8s`FxFch>D}+)rE_YU&A(b8j|3Mw7q&kBx zUW4DQz4H_rD{RhHGIZFlT(hcD?@4K1A-x|&s$Qh0G%S9mqEGpJ?|e%<&C2Et3^Jp@ z?-*>&;#>u3)|YQ3nL~(#ZZL;_6Z(q_DW!f{1=Q%KBJKHeQs-#qpf;w}L)StL%<%e5 ze}2`3zf+|neFBFy6p=8DQ#Nt|^2t;n+l2vgf%aX<#|Bcr2ha>s<9RC#usC#vltzTJ zo^^_^Yl7Vz&lalFmx zAbc0;oafj&Hx}D4w8f{bLN_tf<42NEdEON>L9Tg741Q?*S0-c;1HmVK%)S%vAQ&c=z0zvn32jK zIZLz+=MBR*b|tR;tl4*_WIB(Ps0Q;FaaSIhnomK$(ui|H1XY*P$8fWw;7|uB95W(F zYF)<)3sG>GVzl2|o@t3g7x^}mUM14J%vM_Px%;>G@9BcEw#nh`6;jxqAzFX&Y!qfev2 z(B&PY4NW4Z_U+z5@|CPr-zU6C7h%~{vMev!)-LS282y@!9c|mx+(1q{_G^c)uH(Foye^v?b|4uzP^blS9`Y`qTQmBj~z*qn0QI_v2 zsjrteZooRdb>p8lo+2XE7|ix&$*;FB(JJ@j`yEcEC% zZ3JXJ;}%p1+F)yi@f!0$v5Z*m*XRGX@^G(j4`$mn)dNjw-gPf7mdBA{vCX84oG30Q zzFC%Y@Bda`{1Y!M2(z`j&+pIDXqpw}Ow}6kT59&jI5kK4uJCA19WAwT@jGrcnIaes z%z4uY0&4xXK8M?+2zEnRN{>c;&wW)LBB^$sTPP zX)2&TY+i~BW?RIlT{BOwC?24i=dQ8@A8$^9jF^7d`OpjGAz)ba6X?|T zZG%Pn=$PI;TzIJ-p;i4PInzsJc2SSHpJQjrWNq{+7tn;I_m?WzM#e*?zP_#7v{jY$ z3k&7`u)H%NSxFLo*&Yfv>t$?Y-7v%jS5_*@CS@W}rm|jVz80MBwrUlj&=aP~A%HVC zD1K3%GzsE5A2YWYtvkHVW8(FEDq@xH-CU8&RhM}Rq6oT@*Dd$GmUA?|LsTi~BqyI13&MCT*C|cJ$=-9T=vCZz-9Xp+l zZQHh;j_vH&+Of@!JGRkxpNI2s#~F8wi*sM=p&n{g)tYPm|NOsM%s&{fZt2cmyFvWl zo(@M~`1dDQCTQj=$RvC9Bk#Ck-K+Fwpv3i%%9UH&-{uv3{kPoxX7wEh zI2s_+DoWS6LWEZVvp?_t)`aQkIEFn(?wEF0XzLp=vjDB7DL+G0X<%m{8x>kj&*My! zyhn%;9qK-}z<=g4y(;|1OoQX?twF4Y8-A=ws&K*Q<4hx}anX_Ce zM%WNgm7C1q7tq6W$t9@JRS-2FF`;kSaq$w(p?VTjLDO8&23#UXpVZ-#q8tMA9r(|4 ztE8X9fKxk$c0xZkQT_p>6FgH6Fiz5AlD&E5fH&8Y($H}s#I5&LMcuk_+*%rfHrIEg zu|{LITp_1Jah{q)u@*2_DF(Xn&4kiwe%^J(Td-DvHb%vS{@3{h7KB zgM&fhS!%0ci*v^+#4!ZQ|vvXfa_&Uu?lMz3D{P2IMlu5W$8C znnXQZ^pSX$*_8f^_QX!hA6&|jahQDJhr-%_+dEqLNT}>^Kxq9{wMTJ#hs7~Af_x+F zQaT0Q3%ZWHN@&hu;uad#{ZZcuT)<*LMDY5$85!JcVz0r^M6OH~O%o)n#Kifks@Pl6 zzx#^^Wl-y5P`SS~IGr}PuPZUBW_C$Dd9j6575Tlt@UY4y$6?u{JxBQs)n*&L#%zrL zsV2Jv<(lfQZ(BXZkc&};+z2=!2#)=w<2Cc}1BjX!4c&+g7)h^7QV2}*p~(FdJ3r8D z89Dtl3&L3^YumHu=vA6-q14Be4H+{p^Ohm~kXXj^k#u`S81roWxf%>_trD7H9=`^3|PHXDAiV!=z&5M*cG=wGv z8-5m}vU)~rp`Rd7A79yu1l7ZF?3A@c4S8O0#KE8WK{cyl&gv7oBpD@G8~L`5Vh zoA{Lx^y;LzLpt7_CEgv#i>;2ER6c^5KhkK2zMoB%FIn|h=Zv3hZ(;Bh+m*Uqux0nB zPare#o=PGI3HG#yFNa+5ywq^tp+^`BD!(7i2Kic-G%f-D5FZJ@bl~ zl{`I2(0@``>^SN!-VzC-TPZGDz4Vgmo&^3va(+ifPR-pq^Ih>Q<|~!`aqcwz2lpJ1 zK~9K|BD18U4MnzmPo36D7>d^G`?f~jrCB$PA%%nEf?YoQ7vEm#`SS~=8uKXh9Yn3Y zD>$<2zynXObJSe7ER@BkxQN7ONxAQElr61MaAA~@kYqDtFkiGKup5*Ai}&RJ=7Qr^ zM_Y@oMo8OL3{zq+h1-X9L#-z8RWJRUU3ZQGwBz+CRvsD0qATgyd}@uQ3l+rRmM%QR z-dW0pa+5EC_ZABY$a%1zpbidDP8GNSmTe4jX-Z1;^@mQ8TY1})*rq#9E}1@}rf@hQ zt9QlC5d&cOFErLpxL@sxxHd5yh8-X$U{=RYS$!>K^(!qiY0%9v{r7jc=>CRG+fh1v z=}o;`V1FU_%ZcbMSH)*$V%8QW>Np&Li#ILYD;o+jHGPZ*#<^%}OGk0zp>)063*46f zMcX>**w@t?xXC2<3DeJjm?V)p)Xo0hNH9(tVZi+5;?$qF`c%i0tlZMuY5vr!8Id}x zeAT@w_%KUOM^C@t^oMHesY3T&d}rUEcailbSGlw-kKp00`k$xM{s=Q`iq58?owZjQ z&825Lh*W~%1$4=K%?t4_H_!7O;EH9B{oOLfkaL5^IH1YSr%SE7PcHiru0NO;Up31q zB2Y)_K2vDG;5VisDv|JnjA$hK%zK9a6Y2T#PaGxmU2S2@j+L*<{K84Y7D=Bt1Ko10Q-2a3m|u9dEY zf$kNoq5|2DeZecFt;ZS{xErv?;dZ6`co@;)hz|!LWqaSJN>mLe} zJv~>N%nR&uD_o@(wK%G$G_GCJZ#qD(g+{Y}LW${5-COBF1d502pNdg&xbj>vcx^1$IcQOMZBQ*hnji{M5*-WGg^Sfwc z2v!8aF}*l{^8sF7dH$i)oEqurup6D_7X>zOfAGu&M~MO8am1Ns%5tIPZ-f4!0@`?wAS9g%j#u$^R7L&-wz2~XknfQ3b5fwYS4H9`c<{`JF3u^0v z339{n@N3~2rN(jJNS%1&6g{MJHB`s5qS9tFOn68YeO@X_g$^$86JJXyI&^g#7-fl} zV0VADSj(v`zP)7tLg+_@&X4LxDOz2&IIWu29L?J!<)oz&@IWiriOm&;W#LG- zN|M!T%2;ero+#1Pq>7EYvGV0B7ad7|x<{PSIhd$~Gab+!_f|-kB(*9;~L7AB0FF>AnMRR0#`1^*)vNSI3VC5AM#dIw=2^T#fE90hyF(iBLlOm zYr{Wl`yT+JY(uCaj<9NtCrbu3R0!hac5~UbeSxA4acitxk8MB#kKFn2$WlReU;c8q zJB4wc8EUgBpEPf?YDz#kZtj_U(#k)R6+bcjzV3eh!X!7-fw}$qZPfva%aLgaqQ!tg z_D*lfPVR(NhY`UUzco_i8_;;BOn^4^4rsaOh~DHRvE^Xi zVm+3gIOp9D&Et%!*<8O{7{_2X!Zd5IpLy5v&r6=b&rw@7f-FpfF7Y;AF&ItWt$uLe z+FPq-u#GW7P;>zx0_B-XCUmnyTT}Y@M$xY0^XccXSeuE=IeNdRaIs^sUdrmPiN^?p z540RO5C3Snsbfn%)BkBsFwNjyQTsbG2YqYoD>RBv)9CGX{S|3XXPSK2o{{tMkFxxS zST^XiSlN_@&x~xjOBv~5EQcH)`gZCoHOQjLtd+$Ym*;casV;Y+w_nAcT86e`qIWYK zhuVS&d`mH`i+QuCDdlZ4a07~!8c)aohcYvg6TKiyPiFipN_Rf;e!fFpjf8=;`S>8q&hT{qOSoS^e%S3(OD3W}=XPVniz#E*iKBa1N4T?l)CHe%gA zP1{xORAcxc&(JMCT!h`4A92vsDVZY7_3vi8QRWhm&OA%HEg*t>{5MiQ$FbHlSXiaQ z+f~E|+w_b&IReu$HRYULmif8$uJyKC3H?)C-g#b@Q)4?Fb;Rt3DOLM239n+>Uq87( z>*G|NfmiqkLEJ>&GIChb^}88b=1S6D(&Q|P-4Zh?;kx8Kq5-0(O6l^>)s>^fn4*L; z;pBgEf<$jU{jg^rr4%~5m$1+Gw9#ob*`;~ajU}fKJo9oL|5~3fWf%NiUdKttPHpv2 zm%N}fjeHO9v(-`eL3>naxh;I!12f%_kzVeTX?>~!1n*u_YRum#J77q;xD*opp>hp6EIvK1mZl(}u}%sD;ei66RX-c~|QBD?3(LZdx)E zn{UVCt@qpG+b6lDz%KTh;SN}spRNI4CHIBBZSzxISI}yPP%)hhr5NX93s6J22Bs1A zg>g5{a3_GQiQ0H3Nn|A4lm#C;tfs(8gPD-FE`q$V1YBm*pUn~X;tzl;(9D5QM1p+=bS&B88yK>@XBQgD+#O0my zJRFDkAbBq>NmIu95=XoA&&VcoudEqj#Bj-EL6>YB=;^x5m8@ywoBcrUdhKz5qxoDz zV$b^-f<9B2Jj*{oYOj}yq@23q-%ZxdjXq)@Rb&^3aUu?-le>ufkpTAMx?W6kSmEZJ z<=MB2>U* zjtDct=*Om8YA8`KgZB9~MiZ6rgzp_TRHw~hxG|1%&Yyu}^)Mf^X~ zvBN^7bjyzw&U!P1j&(|u4lLU+DqmzuH~DmxCZeQc=bM_hbPVf5sjo%5B-t8B6`6X$ zQ<3l13})jer@EBkpR3?A?`dweVHC#Phra{^VmqT9QX+eW>DQt1U%`K+N;pi(nI}n6 zc){k7{J|2kIcc!+*lucUe+*sYkd2P^^Yq<=)vpw-qGBM@d2^=;rdwiT=r|jVoH^|plibeSL z0DWCmuLrIUr>uT5rH@F;N}#au-3ztWzUUZvvCenIYonBpJFBy7`N zqQ%L#rs7_PD|w*Xh}?-rM7Y~@9SBm;d}l41wk^ig?Gv?>-W?Ng`l#f0mQB0kM3`}P zfEzt(i1&Ckxwx%ChWAY~^Uik3)K-Fa!VK6paT4K=`EK3TyuH!=>OjI@+XXU2(R}}u z$L1c?+oBZnTo2ZwO#e- z-^%hL5d= z&TDw;D7<_=g#Twb~BRqDixdl>^tl<7a;RZi%?TFhy{=E;nJW$h{}AhfjunUj;;D5>pOR zJR05;cNm)d-o$nt0P(!6Hp^CLhx?>(>9qfhIXAku;C9569(0t^)d=|A)X)boNmfSd zMcao)GWR-lsnS@eJ(Oc_+!-pnym2JL@Nk}m(BRHV=ilQaPrR6X``DAZl$y_ao+Wa+ z4D_q43+Glu~OB#v&bO{cY(%O+ZFE1suz(Elt-Z26RHr3DQvDPKI zx%Kf&UW#P875P<>#(e$^^*wHQ96_1d7+u?MXA&@f2T}e5*dwzDyjdm8I@up@>0&xa zI9KQ{Dog0K{lAJV;Nk-9f4T=fm_`$AX}ZxD+Io*)MW>)V>18wI3gK6bx?K45%hLVJ za&!Xj)Rlvs2^?xjDAb~+NO?i)%Jj=km7hAWTo0IYE8ivfU6GN80=}Bb^uSiE+o4hm zvaQ$+QGY}0?;okgCQJ0SO)EIzvn_28S3X|{e}pEb?df~}6h9#N8?xW~J6d*Ks>92O zyr^dz7J)w8qRe~rXTyJ9*Cj@ocd2_{RH>XE+yxi|{R1R*H~vDd*&5pj zWJLviR5-G7!=s?0Ijx;URURECD{@EWNSglxIONa~fEZQUgCiy{~ zqw_ny7SWACvdW|!f(Eo5>$jAmp(;lTTul>RLB2jNeuK5>5fghaNtTDhXQSF00(YS~ zu6ds!#1d*`RPrqPOQHUok~59ZZ3bVGbxG(=`4|1qt2cXXkMe$%u^YpW;&`-9M&_v1 z*66C5T4>0h2uWiDyeX_N!D_1(8XYkUV-6vVj_J-MXqXGX>R(V&jG8Ihf;O6-qNpH` z+z@bUMH(%M3kR{3(irw#!<8D%5?8mEMGkQpNL6I)({#nVt-YnO9dRRyNIO`Cu~Y2} z&Gxu57bUeExjhk82iFo3KN5GR;1OfVV;4YnyT4!&Ku&(C zMc%dqQnsbua|4+^w#lExN7bq^=#!c;rE+1(&|M+^KsOy)e(74fpTtf)&r`wF?g4$U;6Y~RI+Qz!&|h}wW;Ugtb& zBWOi&@d9JZSl2pZ1wo4D`DLkvKvu^QI7B9!gn~2e2E~UR1Fr#%0e2u_FSM(VH^CTK zjGpAavv6I<2Fz5ILN_M$*mghVS${`87RsU(`|&Q(;_MTDTVH*61(eH-e40qSCR9Pa zY8|QdmSIXY&U9FvcAlXWsZl!%U>k2~ab+>jXDj9h$Rm-j9? zRSZv`_md8CKoHQItKHCG$QTPIk5FYmg_wDo>?LE z(&J}T7jWLMR%ac2EIub0E*;pXkQt-rzm5ZDJ-H)Vp8K9$^0tk;?A_Id0}~S~#LM%I zqPN6h^^89lzFd$9iEebHnHrX8W(t2#4O8a)b-Clj2k?t}svp@~zL&Rjo`Utzkc5m$t}Q}ZVTCVJ?f8^hZmhLUn|F)j$~1mTP|RToJ`zDq)we`y)r3ca2T|Vs z9LceW8F7c?;HM|jkFh6r@d~RFu43~fghLI5Rf@%(faqv-WP-;Fc)Y@2MAPAC`|p;-=FH(j%Y zP16%1i`poD8XNp^k6lHb07!*A$P$M_;0&9W^+oBpL`8Oi^qo>;^8 z<6}o_Xa_Z5hQ7zUIQU1S_BS3}guwjwp<0<&r&|AJp6!K7nI3gPGF5{O zlB#t%TnQ7=Q-GZ}(Nc9`7o&IBp3L<>vY1WBp^gI)~&Gt&0a7Ab=g> z9kegy)^yfk+46%jlY|;MFmS*@1VKv5NI>0BaL@5MN~~!_rci_32(?9C|7RY zMVpKbD!XHLacmaprAL>gP^2}uBiH`=S%M;d&%5;0=f=VBfWmU=BLt6xF23DPG{j}_O zL0(y09K-fzpNVU}8wo`AKzUqJ&rPdK8i) zMfB^#xB15DR$TVcOh79;!^TaO~hUa>KfKqjSX(tDD`L$DV~^${Lp^{3u=)Ocj*9{$eTu=#>e zGoetUfDrtV%%`d<*AkaR5zDSAN9d}ZRiO+Y*B9ClN9Qw@DeGWImp~kbafbEV5C$o7 zRlVQ@M`gl5)!J2bqc-HU+}ht0r0%R&lmcj0V@!L1DJWC^3W2D}h&!wUPc=|J|BTrT z+QhnQCmeq)=v>vVlAQyo@Yr$Zy-t*|?<-ZFs4Bs0`6Uxps?hii`hm!Hxh7kigTuFy z+Ttwlr_7ubs8Hv2VW7IJKqdXIakd*`^2>`WzoqyO=|48-0R|=^YP6kCGb<(wL}ndz0*AMt?FgU8NoPDS8p+PaTPq?(2u4TApq~Yqt?{A<6_^F&Jd*SgnRy$1p&_> zrNmP_r(3>O+>U;bh;is%v)zrFsf_LGyZfQm>o;7TuGg5z5J~8mW2C77;mlW+j{Bp1 zoeuxI{S#dRja<$w;vDYEu+z^2a8O-QC^tUsXc%Xck%?M$k3=8hEnmJ|)x_pYJVgF1 zgSRm*Ow(|@Cx`k8vaP+LsReXvm;v65$HYLWV{RNn;e}!-cR|(K`nr*IP{_EZiNuDJ zPq!6~tw0m}=GwJ^mB$U31j6#_(<69_rY3nxBa!L2$+hAS=%iA_M)4CxKE6|**&5{c z56{#FOZ^(hy~paCYteFA=5i(O2`1Hs&%W@bwRQ$K^V*6gyMsIrM9KLhC3tWA+fle3 z+T$Z*%mr^@6HzoQpjfaO&jtd^j_B-G= zpSi=S)tqSkcen_JsJS99X?n~Uh)e@CPCZdW%{vN9`Sf1!&3Js9TK-^u|7tJ-jO&cM zw8rg`JUF}MI)B2{9x);>+$T%=25bLRqbtNcBO7T~DnoGQkT@0tl^YM*H~41mxUMh5 zR99<;Hi92i%PI^@ig0$O%S|43ZdX=@^3|3tZsgH%mf6c!;;1i4Dw(L!tQ3KDb7}KU)en?8&l=I>r@ZH@P zT160SPYqJQXe_^eS(YH@?Vlty9V-c$@2B33TC;>K+;^k(AC0f!D;;Lat3XPUNRPeQ zN)A#Y?;YvXx4Yc-o71k0goPd*LIWH(9BZ@dTtBv>#RPw~FZ~B7VNp^uiHRgMJW7Hk zPwlB3`!%o|a0Ex^}Z>p}TTo5@=m0z4J+zZdDr$9vfK{Ks$jwwsS`d{L>^6(mkV@B`rdarN4u z%m5uW-+t#$j#lKAR zm?Na!icEocHOo_&x+ILz&9~a6JV|F3T*_tS?SG;O&6qK?0S-l%FMrOhIY&YB&86;t z=J!xGzXY=%8#i#}w@e^08HZl_g>p3ocS`YJ z-Q$yc`5)yXO-1A0MLp+PI~wYmGvqw=uqK0YTF%?(pY4pV79vQHo_mO`6}>w-!h<8f z9fkX7sCl`b{dQ?;GyRnyE*>PDde+ZL249fG@##u=S0&AoS!a`Fx2Xm(h;M1vxgup> zJmFgwUF9xi|IX4Ke^p{O8e*!2Hdt^Xw`zq;;8Q5A7ti(Ax;)NTq*?iSH0U1He`fz! zW=vm*D11+1pOzMRGvc*sLjhY1*2w0jT;P@22xzYvwjtZH3h6yU8q45YwrTnFg(e;< z<5x%GP03|h+TJ4{R32P9RIRTtWP3AI)4B-2_9>nvTBYCm4EtNp|B!f8Z6SHLtjHB<7KayYrY^6zOuQCS#< zou2eW{1dlod63Luk|>kSofa25uD2_Kv7im^O+|u)t%bu{gdOv7@bNcIyNGxEnX+AkMF4AdRoLQY$0r*Ea;_)*^yk0B5aw29 z&n)ZtBN*9ZZCc}9Nlf=5HOq{tfu5^#SmaAeu7Hw?e9n>D)~>pJ@El;AM=EcqL6hp%U`5B7SXFpX??6aP z==?CuXjhysA4bbZ*Nc8iwLj#fVh8gw5j>08PjGdv zSFPHy*pj0;@MG_iox^x)Fd1+VVa4I!?1nHA_>kW!qUiCb=GH2`eW`}ia8)1O^pC$ii5yC0E3q;7I!^0C(k zA^>3^STGpu7VQ2EwbP6=ioBxQNaUmU)7F{Fn*rHx{@H=~2cR46Y_o6sIaF7#{hg_r z=?{XM1jcXGNkfRJ9x&DJzfG_FFC%h3cdcRGa?j@NL>d^Zl(1MYrkArAzCQaKKLLe50k~VDYe<8H->W13_$naE~joG1`vCun4 zR&Y)=fEW93OexgH>%4Q7eu4$2z>$_w;T2)FK0?V|xXKN*R=6hSWW=Xo zn_Ha~d*zmaOwI@@j4RDtzA_dhaIIawK3{zUy=|xD5ecOVh%5o?`s{`Oc-(3ZQ{RkV zB|V*LxK53>w|S~?#p0b;@~aC<0+|E^Dh=#frZD{VwjrJ`{nRT^qDxQYX%|_3wYidx zD(_OoI3M{V%=*oi6dV5X9VM_*I_9jhG|OELwm}WhTjY>TN5>bX8{rMln0_c>t|=RZ zBTzFWrI0$)fPdqEV%{FvW2+ykcJjB%>^N`RSR?!g*z)>b*g`kzh=J%Zahe&GB^_wH z>Q(I>9YJJ6RKiKJ}i|Sgtt)y+-#8i(6<@xNeQo226ia`&D*4+8| z@6UMz90E02^DVvn(M&Zf+W_~_3hPeIrgjg~tXpB@g5q@g!2+24tdq-mtOV1A0m4aj ztY*nLBXESj!g?HR#9`-{E|+L|{DZJ#8CySxFL20soRyAP-J_1ND=5h?GE0+!XX`ZC_ zrH|GDAlN!$6*ps%_KKWvfzn*How^5XalCvHKoueX2q}p+xKKYc7^y*S`J+I2O-4%6 z%eZ)yAjc8IJRss+H|U@jTap-KbnNh&nhqO9y@7wC+w>Uq`4d5Y3=L0x+JS+8Z<+!A zOP9Oy=5<}*u6KzdexzAIj0L3u^nrkKUz<_@)cxUq15xR%z=KoAj>?v0;B&SQ z>#S5OyV_Xd&W_PhTj(m+Yrz)rI07+c@yH>)0A$$6j+MwP0zwRFUGwpQ&~G+llunZ=%7mbm@_3gjumDWsu>broKd(#LX}=$H(~-}Jq!3XP)W>aVL$ z;WY@IKwV(&+N?LcC;SCn8Z##Lr2Zz7$Ztrge}EIcu%e2oqDPd00SRp#i<`bdLT*Lw z=apIi-IfHhpreaL_7O^8NbcyLpXe{i&je%fm=mI-oY~HhE5fu}f;Jnu2Z*(wQD1AY zzyEJ`5C6|kZSC9ZBcx4@X2N8IJ0srRafG`$zSalMGVUnZ_QZQJ%{Inh`^?3Jo%&OD zW_RAaWzdbg#Z0^)(#pu2WRul;ib_8za`#Xkon8zKtrM)EhOH|81`qnm92+_>a#%F; z>#}2+6am?lnfetGaRBImLtD9GPwiVzAB5uBm%Z+&5jq2lWJ7I9#hq?Xi!cc4$tX2( zIDEVtr;jr!v-al2c_p97iG1i>E%wRLizUzU(WLF~#)kbyBb#?EtEJ4gg-zamgdy&{ z%$Zxdwc7^GrRn%dn!PzBaJPMlllS03&t!S?HqX_!WBX9u?d(fKdzVhc2amjI^+J*ftByZJhZzO`k!>EYqFg`9ejJ>+;6ObOGz_Dc}IBe;39DYL+&F~7;^bbTerpW)h(*7Dp?1mgt-T{W8r-?~lR*-|<9fTC^Bt(>Hu7SCrYEZT-n3Gc_Y@Q6bp3 ztkEQup{91@BSmW)-A9YN_HHnsJ5}T}YG5h0*gZ@DCh>P4*=S$R!@(CX9oYHS``usA z$kMOXQEAiOGj4iOfA3G;xNIoA17;Y1Idy4E+7-vM$o3oqHCkGkU7nf9*-2h^e zAHi6MQ018wD$=*RX{D|tNnvwq7~)t1^n=~v2*Z&*JvlqyB6ndQ-m7vJStvVxZ534z zV(2CKNjAM?weU`W0SNTI=~(Q>mS58vZh7G0Yv`95HlF|F`R!rm(Xr*&9_q`xCkv&x z#nM?AfdE1PJnX4KQV4bB9xvA#T}(|G9un0UYfAcyj&9Ju|lUJA#pIUA;++kR`)pwxj8*E-dKUy*PHRjvT8rV z(&SNJ4OboCXpAbDQDWkAP79oE9vk5fK zKCL_pyx+S3P`&)ewNPWM2#5kUu$h$%6zi@Uf>Dcv239u3I-qSSMC9D2$yZdCP@A)KrV`8LSJUPY zhg!Y3vbudeahxaIl|-G53eQqIY^YkF4+iEiI%3)DpJq9M2VBB*>*3AvV0KHlub5nK z>jie#QWmf$b~)a0>EoYHNd~RWGUDJ+gy^F~AeE=ToctxghrU_&28R zEZ&b~CGSWiSOL9TEjH;|5B;CtI=tI%yMXIf9JqR>gXyow03E7Y(CNQ$^)Fd zF78W5qW!JeCoHZvOWol%sp11cyIB*pRe|S8*=Q&&@7*qn3*Q*@)@)N3Q4u%w*D=`f z8kdQNq!{RqQ0bRi=UMDs;U#@zn{sAScn@1P=55YNLzvD6m=vY-P=y+>`1yjJWeOFb8_G=#U{|ynl;V1 zj*qNCR&6RAnCa1;VE@Vtx0=^hdYlD!XeW|!fX&7(RlhrH)<6+kl1lk4%?S&Y-MaQd zBU`{9b|De2d0!fWXofsLT<9#VzWq@_a@!FtvnYWbl;cQr(L2%;^_t_(KWXH`a6Xsm z5wy(~eJuU< z0IeqmHi+`Vw&Y^j`nb8BgpD9qeLB%*G_ zda+H+OAW`aZlU-)31AB%QBzVE73f!vJ9!) z;{@WJL9H8AXWhzD`YLD?!qZ!&@USl%vF9I|#$QNe`3B z->GyJ0_fd%Plbf6C=|@*x0sdu>gz|1*xB@g(##Dkwr7tqYui7RsU~fUoMj-lL>fEQ z0(IoSL9o-@;`Ix-(2h4Mb?V@BG{3q8-EN}LNkQEYdsn)JSaHW<>I~5saj<}2N)Ubx zJ^ZoZx!y#8jlyT7PDYXA1yqgivNod^LPTtdt2n+J8N`u-e%b1lReGegO_vHE*hIsh z1ZKgJHg?!0J7V3PQC-;ab+(AdYq;1ik%8~sQA6L#%LSYh6p{2@FeB%aE0v};rr$6idA1(*zB&y& zF!L$7F?gBBIuE)deEvu)UMC4QN90>HYfseN+J!^SJo2?>U9XfD*Zalq9v#Fm$2C#6 zA*<-VOBCvzPAib)wG2cSQYF&=@wVgsM|yDzF$^!;)P!MkJc;=-gmosPw(|5)0hrfo zO%Pok@9G#=Rv;2FQ(mj?J>VF#VqK_na;$_Vw=G9Je?95ZB8f+t&UD%h{?%HWdhoBt zk)5Kp9pPH&Jf*XBd!ZLvxdb<_`?qL2L?4h)2{^v`Sf_JfIVDjt(DlO>)S#O>uT42B zG}kb7Vn_<@vE`sIzSDPmt2Qs{z>^zMjbSj*!Ez~1Rr#<-c}(+8hkJWso9go>hH1vI z5X1>#R8jEIU{PF;olGYIk^uNLEzJ{z^)V*R41`WeO7NrrFCPZGMqo+{2Nt8~AOoHn)^BZgV#64&_G1+2JS&A`{ z7-CV^O(so623w5av#3vPoUCY1jbr=-@py`w<0#l1*Q{A{g+-G2SY9;zvf%7SBz?O6-jfkmEf9!u zLC-*eXf!R&dZLdR;BdHkfmlp(tgnsM zT4Gt&SO}s{L3Vl+r(pr#arH^ zoi|;p+wZM20zIVLql=`jd_Wgjo!>98I@Ai((a9;1(A{S;1*a^TdyTyr1m~XE*=8_2 zl+OKJb$>QnC{*js?FsKnx1^N`tiCYo1)-pfWY;e+KhVj$)u&TtQUQdimVp=D(`9Uz z2JjRT101Yhn!w<2BKKGE0rN1ez9Yw75n=T*n0YQXfkuF!1^mhyEj3=~K7OZ1s76_- zz9o+tVJJS{V8M8PU}XUq12_|821?w{_4PZI$=q?ZZByLGK5n*5*Hy5iaDFNj6bWY; zsB#s%4qF!UGaB+ou#If(7R)1HP;G9^7;>i@X&)uK6?OqmH#dz=MrVmH zoU(qinaeO|X9#Z4L)X!vPE8#SmU9NAwv9fm8XC{eH_gwov#D%^-F~XiO{pAb#MaF` zOAB>(k2ih)80v=Q0=uZIsw%FUE!Jgn$BDQVo#RCY(iS26o%uyCwqVE=y)@3(ISJg- zsh9*fLp|0ahyeSJpTO}lH+kc{cga7kj2FF|%mZxg{WUEbg17=jlC&$flmY?o<_8?F znhM?w>nH1Rzt0A!?BK?Z`lZ-TlBCAlV_}XMd`*dGx)8`moIt-`R#eLwKYk}Mac|G& z0lIHAAdD2n4?eLK7pa$!dnbNfWi`g=P}H!-K8! zy`n{q6b&lv`29x=LH__@kB)4Mt~f1;W|jgO2POm8$o1g@mpmI2x$aB!W*x%^?`yTA zk_v>05?HGA&Hr6fEdQk|j15j|*j}5mnN*W5@d#%-)11>ILpKV&_VouxjWum?)|y56 zF_UG?Bp$Ein(3Y1>UR(E^p_CnvhOM}OWA%3o?Q`a$HFuiQ^_a7UkkKrL(qSl5jOOD zxZYl{0DW5u%bNqkTKh8>ld9qH5ZFGlR0!O-4aX$bT@L-vtJ9?LCNeAC5HqM7E={t7 zLtvtC{1K3RHB!^i8o8WXdh1LM@xejcXo_<529B}dD5dR$Vs z018`pzikEXSXJWfd%AI0Z?EtZ#uw(qJB!wL1CMaLjjxK9xte`IZ=##ZOA?{;1~1{Co)ha=QeI3wv*jjb*G?>HV< zLV>i_Md!xp5|cQO`pZ{mEqF>`%5@d5xS(uFhbk`rY+Fc(N>hhJf+EPODDa||1}TJ& z+8u93N2xLUjaYKp$hLmL9%$#< zpjUliErFB;BkN2-wwh*fwmUN97fm{c$z-Ys58X-674Q zz$g4UMrVr*+zX4FA0(puAGEziSX*!SuA3J3BE_}AgS%6pxVyVM!Ci}cC|UxgxH|-Q z*W&K3#l64Hp8d~kpM9M($spr(t@VEI_dNIg&d=IXzp||xLg86)4DW3gISTFDhW`5z zE9PK*d5*U-^?td}FZGzX`dM>-uXX|tI-XV`HXh?b`s~i_XUj-I=k1wDhX`T3*YK!`mxoR~ApB^b&GDHUlqxX!- zDy%_3tO1%PI4q<(65sO{?@CalZ%e#zz&Ap&hxO@^c>e5GO|e8^$ff3UT&-EQZV!n$ zaI$3X_dmKy18Lk+O8|k$Cl~(RqPqiN45QwIWEz^;fdDz0<&21($@_Y|*cjowsnz`j zd1hf*A=q>u0H%mOlp85u+t_Oy0hU#5p6XspayN`8R3sF`YpQ=>*j2$1n<5KZXhfVW z0FiA5A1xQ*SZpCJn=h%Z3~8Jb@y8D z=8H_@lxH8}_?d2aS21b`IIaZ@kA$4KWum1jQ4D9wySi^pHn4iZ^=@@`_PQZJ~bOu2^O%3;igtcSE6zHL;=~mDQr?N zYCExl|7qFAELT^nOj`NtL>G2kK7~OXac$#S>edJ{c;S5fpLa=?HG4698tEd7PL9Q5s>f?u z=+!_*;GqMU^itj{_S7gOad7$q#QZK6HR_Tu8W2j#g{wizKr+87XWoL`yEivWL9F>S zR9mV&Fhovz_SDMh<5I2Tt^m@a#!zkQWVq$_Sm>yOMXsP!?8vk{7LKc6qQt(@h@YEZ zKAsO0hJ`Y6AYeUNPkf-MmN$$suBo~y+$u|4Rs-(z)pGw)g}se<&+AOROKa;1OdYg^ ze!zG1+>D2ri|f=9ZFlpD8fg0zJ)DeBjL^+Bqw(0GQ1CQgX}up@ZA4CDgh^va;jv2a}_39_!No2$J15?2vD z*q67IE3xp?zJer5Zx?iVmF0mEGN)R_u+_3IE% z`in1h2>oI198&eKJO{bT)Z0qZd(AYpg}BV-dmJ;Pf+w!W-#59Fd#~-p0o(+94}duw z=|9aB$8zPO(kA){4o`n4fx!7&rFl=X)PczqQs@GkNo*kQU%@1$yPI_hr7Swg#txA5 z3W9pm^Enrd1g(vK)iZZ2-2Z#KS=`|hB);%!rrIKNm!l%zZ6MKLKAm?)-jmLAT!9U+ zhrnKB<7&9{y?v*NbViB@y)!5OzSxN?mZ|`}ibKp9vE%r24sLAfv-^$s&_lh;MGiuj zcpj-Me~SfX0BW_y9RA5fWwO#1K9eZ6LBX)k6RruH8Xf92H>IZRoDuY^bzprWp1%mqzkix}_9bO{XrL0Do!N9aOk>p1NCp_6 z(DBistPm$9-BhK<3C{#MO8gB>0K0c#tB4WZd4RCFa*m~e+GgcYCSx?rv)TE8wQuU? zaX}@c{ByF5Y3*>g9#~KI<5lJ3mN2g|IyyP0y0SU_Pa_ye~KM%<=+d!umzQha< zl2^{XsyCwBi?-QEU3LXRlkDgDCF)>A**yfo>Qna03N3cM9&}VTm;vc6H?+{sbGkjz zr7xa<{d|1RbS0nw>qkxK`s&o-Z|eL$pl&R%!`5KzmE(`&j)TEiTg^+?xvrJe*e4k# zwxQC;nM;4V)C2!C^ILLF%Qm#18QWjilzOD*N9C+)6S95}T#Rpev4nW~$Y%p%Y2zZ- z<*I6RS)jY2>lSnr_Fo=9S-WNBYc=CHlTW6r4zqUwC=(@;pKuMya`NF9@_?EtA&8W4 zL58jKBuWi2CPyTm&|R;?(7J?d&wd#PseLFoPr~iPy_w(qq@^HJ{2QhFzZ-+It0`-c z7@Gw+RGa^`+*q@{72Wl6+4*g9Y>JJ-*%KPRju zE@~)8@kr*7=5Ci_!BMz&!MN$#R@6CC(>u|sKT#6S7}vx>5|Yl8rgj&iKEJsxzY{z@ z`~%^qwu&JPj-zNr-hWbi_RI$d`4Ep{n}-l9;Te1c_oW<8LLhZTzhorC^!=`86E((c zx1*Y#AhG9i*JR@RmaSguy(w_Svod5(Gmi! zD>7I;A--=MFemj_D^?OB&&phWJyaAQ5&v4RB!SZp85%uYFdf4z~eA_1S1e1@#L3Z5@ z;VcCS+grQTNV;yY!I#}!Mx8~J423h;nxk{|ydJ|}KIHY>Ea<+N+9%mSu{%4jG89gv7cQ0BWvpN|;;_ z^bST1BO2nI%T-rAE7{K_WF5vb(%vemN>1A5a|2CI*(|JBC>=YyCUYd9upuJ7r;kY5 zO7>adzOlpPXy?9scp{h~Tfq z>2;x#!%HI;(Iu6rzq9;+BR#FY7vl+M_$2smksU)`rAbMIqW4`QT5^j>a@BvO3- zSNT^v95J6l3Y*%NG(XQP&U&y#N~b-4Ln`|y&{>;6*|4Ebg`Q#f`mb55uwK z@9T;7?C&i89}Q}t628&wOZ6(2K10vu-Davo&5>X>BhLMnA-SVU?hHBv%P+jfeu5va zrdV?qN)rmq*hkr5cBrtSG+L6-hLT^Oq{N1~)a45P|2rTjR@tzfOe3oHxZ&&30??b* z{)PF@<#<=-*%rAZmyEF=>-eVIF`rLYz5_r^2R+fUetY#VtVkrQn;R&uz$$%M-M8+W zo7$9k+(Ii4d*8jR#0TD)b)%Aa`4r z@WB8PVDVtxrqUzydrh$9nFpNw^=qqTUE|Jn_r;ZNGd`MEZ{@w3^@q;m0;h`V2LAN8 zl3io8d<*Qir-k!YSs_JRTvkv2fK?t$>Ezc^&{#B8ROT06lxz`SEY<1f**OnaG)a>K z=+JSH`Bq5|V(#4Zm9EQ1LG0Q(y`@lAd(NN+(f5a>X7=$!!?F>s#|v{#9Z}-DxLllh z{AN!5Ve=#Ya`#G>VIf@ko!O`4TiIRanxEZ?fpYzco}l7&DW{#7AQARkS66{V&>}O&xT17B?y1l6 zf8mA8@0C~d;MUY}x_akOD09s*JFuB~w?ThxiE_$JE?RG3g-sc;AEcx zQ5ULa5Nw%1-It-ft~}<7vTjZ|VjRM&(BR|S+}0?HwRaPcbSy!%;@h@ZGxtp{D+>K% zHtjUIuyFs!MS`p2#eTaQ0YXT8oBh%5$^9U~7Kn=l=W-a9yQ`$Ps;jq>J_m$s4KKw> ztxLYfF(;nMnk&i-!%Z?0F?-KPJyGL$H3&z z<#Qt8oJo3%Ex6cVuj-SboY$)vSclSuE)_wtA}#fL{fkDp|7;Q!p}wC%6|t4H>y2Aw zDh6gs(b#TkBT9V}61w?rr}84+MWIV)f6{1GiVU3eCrT#S=}+t}1@0i4q_NU<`g?h& z$jdIt*XZ|x?Qh$?wid{adb(SdZ4C-70OE3>EZn=|CW4wSr%Nurs4ekYYl!%>bi%%f zy8$^l-AqrDoR(ZO>|>fHTxq3FFdW=yV=lZ~GkWMYC5^k)84u%vkk1Zv%G{zD@d}v$ zgNI;9XeZ&W8P9R9-a$bU<=ty3kyCGizs`w|=I2|p!gw1#2X8+RgEc!JgwP`|7n`yv%A})c5h5 z?xztVCe%29a;g*4m60PD4*ZU$`^Ni}(9k$g(KHdM-uIxNS4|0nti$qwB8@FHq%6hf z^uYtLFlWOzte8>DZ=nvK>6o`-p97NlVi;ZupcYQ#tjL1syFsks;!re4U)OA&2c)cA zk1jhc9gC1hYPI5p%3tL;&GpHfCEV~jPXSVLWo7=DgFR~kpPYo|ezl~n*ONH-l&%qH zFKV5bLq0Dz3N+wPX_+HDYpEXS`&i|2YZwNfib z?f(ZDP?FwZg!Qd{Yu0j_LbHxa4!QY!2I6j)T%k!@kI-=&It{h!0ozg?!u1wkz^+=m1%e1?o|l)k%oGgjMEMN zecmJfPQXs$sEC$_;5Ye;+~ipm?EYJ(s3~M8DP{1;C0D5^O9nVk$)Ws7n&Kw^AP`t7 zu7DBwSzifl3HPSvZ`TDWGF_gUsartp7ZU6;rDH@$_gE>)-XuhNy=$oJnahL&U|a&8~< zlkWD%xYMp^1ka|Wf$c$>&2D|*e)t!83j(@FA=ZaxwaySp*ZZs=_cvAYeEf*~)O^4R zpG4;Na;G5`0$`}p=X5$$PT0R^UyZG*7&BrHA#7?)E5DTY^*3MX{|!6xe*&ET$Lbq; zUus)ip1F5b27a)=#LjlSsp9YXy;l{iB*w4%@H|nJOCO^)K}EzEwoY!%o#?f~&k|Oc z^2JDDPuFbzSAVmFOw86Vw{xX&im31!huENa4C}8Zdu-e|NFu==Os-5iFL!7|94L z@?&u7vf4e^=S|ImR-*xER>=IHEizg%h=oL+R)6ttf;Gfr-a#I8#@CC&zOP<0HKKmk zRdL9`i7N4FdcoN+pqSYX^wB%p608LRywkK zNUi??uvYQ~6}{}q`gCB1J+Su=*KyWyUYPP6*9ZXE?##KJA_B$gh0Jo3RE z2HdpKmz>~>Z*|<2Bk7`e!Hr+Yyp@9F7n@Rm9zsJ<`<=&FO|_iI;jpgcEpNQ=Hc$`S z1r%#u-TTRu`D5+(6lF8_Xg<`w^x9nU0+Z-D_dMMKG6{9qg}4icOrbR# zU$}yrfr$djHX|#wjclZJ`(D@qL%%zdGe}L74D9eOEwi3=jZDTEol1NV-cHFbP|utE z%Zoef+GNbrY!rCKI5Z;#_6xp_%VS{+&aV;;!DY=b@wy1UVwQd0GJ`+8eJQW<(5;Fy z+({su1LUUcoUg1Yt8kSid9#Zb*+6VJdc2}1(im!ANAR(CcrL2!>*>L!Sxbl! zZTCM;Cp4wym^`|~ZGCvq{6L-N?P*QDnEW(-raQ*z3-Hwx{pl$L8u!xNDyB^Y4vC0J;v zdPF2jpEdQlnzt1oiCKHMjH5LdOWcw2fQBuI{pwc4*9a}@YzKk)*6hU1(!?hhQx5-O z(O+CewKS>Wqtd2K*8+RP766x=VC<>tZPl8VpW0m!D_%~guHym#{@uxf7a4PCRf7XT zBh1;rh8Zg^;ib9S*+VdPFtMExT>@3bWFaBbGcC*BXwCAGc2J+r1nE*oU@fi2xIw_Z zUP$g2r`#``b#OvKohU=q{M4{9+^4 zYiH+jBo5W%=OsEoeR`Zi|N5>^SRLqSxgbG8+9?L?>j}!Ka-;8HYj^fijY)+HAy9(f zqO9X`xv=oGF?(01isfaBtGp-GN+CrA6m4 z3QK=3I_;s4L;v3^~Bg<79D3)_j zV319uIMpDnc8${BN1BL;UFNIlagJ0&X*^ISK*O7qg+(&Uxj&XyiJ!Xoe3&`pF=X8@ z`7rC|5jt!M|Lu>-6_v8z?&F(XTPD*AJGHpXJ`+Lfpx7PKuTxFAc)SQ+1YMsn&4mJe z9>Q$uLa@!Ixr;x>E3(XpF0jX9m%t!Ot(55SGCygG>S7RE-a$A(QU7znthhxS;?O$M zz-{3$qYPyzf0&|a5%ghX(n&v>G?gHWc9jjQtn=W`@_ULyk*gG${&#;x7UR*57|R%w z@sRE#+rH0@r}&{_c*WVy!><_32?9u_-Y%vaZgA`I(`?zAC)^dPkAHG4eI?7i!b|Lni#QWfKlwi$7S|5_mMUYrJj(97Drb zj9s?D#W9OB9xs`Ov}~h}%gRI$gJ7}a#i^J58OhQWg_Bqoq9#Bo$&+=e|MQ3+RP|S5 zn=Iwlh}IfFxizq{M7kC#5W$- zHW0OV`Ns=S3nQUmdKiADt-sGqNk+t{a^7T=NQO~Lj5JMSK-|AYn1|LHZ(*`G1AAA(!fWY6_g$bL34zp2uCK6c`k%98EK zn-rPoMuts^{IsgKXlqR(Z0mjE+Y!6e3>;ykx7BWg4@bCPdsdHIPH7;RA1B6?khr{^ z`mH)&LCu3a_CBCYb!VpSa5luzY})LSVppHoFJHRGqILO}2iMYSBBhrWLMdZ7^qhDH zN5dRm6=kVY7umji`(;{idc!3Ufr!7w;zIIIYqONRyDf`E9Td_J=z5b{M_TBERO!B( zl=F4g5=>0OuZNTx%9J#RN4CvA0HOLWS7k1FEd3Yf3uOtM@DZ}}&Y_a!wqrzltI^4*Uy% zg9ZC-^gmuHnx5;CI7Cyw2@gTa_NjAZFVIJN>j=I+&Tc>oK?typmIG`NZLN;4v+HoFBDKi|w<9WXt8H-+sM7{IRJE2oNvf zpWi~Ol{2qL1|Kz<%8&uM24RMu7Ir#kzsy$YS|)sZksKL*-;r?MR`6P8+4DAq^6CDN z5_cesuXFXuYU){-{O4^ML$^c172A3_JC<1pV)*Ts%N)hi&s^D4dsnO716m;-TeI=S z2H&fsZ5)cHbTyd^hmj3>87E$97vO7Oq$jfyU%d;BcVnlv?7{j<5c-|^txyBg9V?~+ z|C^Y@MrIa6?!C4ea{}b?Wb&^*iR`konwiE`M3~ysg2{*7;r=xdyzaxXSd;+!U81R`hB_k_f~Q(mk?-<7e|wxo^&AxMF@h-(;AE>jnVewwDA~VT zYpCL14TlGUoEOaY=f5Yvm%o$|N_+H48m#AK9jKn8qMQE*usPKtd1KT{m^^{ccGtFv zvl+v0H2emv6j8K3TOl%Q@D0r%z(>C(xLDH2%;$roI#{v=Hj)rVtscf2A`z9z;I=`j z+k^IS;OAUzMIlW>OR&fJ{vU4m+D=f7-p26~i<*X?rP;#$mWyOBitGD?cDS!)t2M(? z_^Nh^uCfiBJWZZDZZu($PxvTH2S;Wv^>6RJ<`@CdLIJ@_WB(ownhEi{Hm_l(Vb}9db0hqEtOMsPxmhj9$nVm=)<;(e zn-^O>l<)>HD=0-oRh&TU--`P*peYNqNS*R*OXvO)ZN(uo)W7U3uvs&kF5e96%e59>38uKc ziy@(Mb;TKnjT!q1$wT}j@v`2pR-$Y{^|HZ8j84x%f3Nh4TW~HDD!jP%&OGr4$h9}S zF#<=U)>w@G1N7D}Ns)d38g=b0L3TAO(unhwi2GdavcDTExX2w9DhJzCYN~Xo!^Y*s zR`IHE$-C{fU6~n+2Y;)Shz-o*vT(7(zRKC^w|j zh=;4NAqE;m&NMkXLuSTxR$BWA0fK|S+OmD!MAPgtQdSBi+5buv_m%{kV8*mZPW`Q1 zXV8|n@`$&zMWnvurS`EZ;rfgls!s&aS@pRR`01qOuc7)0txCxt5~2kUi$F?P0227R z-pGh0N0wG%uqxGJV_usz_h&cc96zd`+hgnyIgs=V9}!-N;1#G;)IF%R4$Ph-88iB# z%8886sSoQNoW=~UOLny5)Xiy_W6O0*G&KDSH)i<>9>g0+nb0BYlxml)K1)O$`w|2Z zOIA-479S+X8?Q;}iUr-3?RIv=eHNY_n&DkD-<5!J8$T!UNWGsXlsd5fd_&$M8oLi}I__%p&8&lU@q-d&Qqn{5!OWGz51;Ga;ZqZ{S zY2g|n*>n8LDRxnuH_RYZxva9L%mfRU6heVditRz)$EVfDw+@Hl68<^{O_ow-BP3wz z>_;CcS1R+rAG#P6I>)}ZN*S>{kdlgGcm4-}s*)z+mY055bP2Hk<8acf@zv-{pCiJx zNa*tZ@8R-d2absJjprYrI@!TE%}5p7t{oJ{@9nYeM`WI+>heEYk-F+H*(Y}3v9AVH znH$?i%i+(^+;FO~kRNBJ&gj6LgGU>pF6&(f1mt!H_q7ek+2pgZx5Pg(OA>q&g}t@^ zqItM?s_O>TE=FM99e-WApEZ=A;0bct_a?HK774SIJ9gLma*PE-?U)28NTP23HSf){)E@h(e)VZDw8?%w*Cee@EU@iI*^|>Rc}}CAuNbQ9P&C z%mY~!AyY=7!mh0BAx(JgkY*E#S`g0pi!{?zvK(uJ7kWDfz4 z(rR-P6K0NM2Y5H=sCl6{D0QvwC*i8~8|?g=BEyYCT@GCHQUf-Cp~Tr z_EU(4i-MAYf1QOuk?+rMMsDSoPbb92AN~Wh>tA$Q+K&AM*~fb0UmMhtZ4$qoKl#U7 zE(%ZiDe7NrdnP+FL(iDkfrC%Qw%ztJAsw@3LV@{@y4Qxfpvv-QA8QdJDNIv`TcMUW zkkvbRfXH!iMPslh5yt{d{?nR*SbRA@RM$NuTqQ%iueusaRW!jwb^!IQLzZx<2&Sla z`njmcnX@Rs6zkyAQY?+Zxx;(6*imEqVL2?FQtGl9Gxq47*X#ll*h{S*sX9H&JxX1- z3iwmcHChb+iSJFQxw3=tJ;BFp&H<@UhQtT^Kl`|fqShs})axcZb^PH%$c)Di$5Yiq zasPjR`4&z@t&`7kh{~VUJLjiti_pG4*6c$BCnV@|etmqZ^L$XYsQunCD~4$ieyGzY z@&0<)=~cmCVw-#Z-d+lYUrSWqKb7Ti0}XfLqgR)TUXe^S73v+BIHh}GwNAHm349OT zkz(bP;@KH`kH-k}-@X&5zXPgXO$MY9kWl&BR=FrD7KmUy)gWJqk4E+|9O!YaE$JrkD~1P@GU7Ym+2cCHkb!RPI>Qnq zP`q-UKjdW%5g6Y42WNNJC|?>wEf+@qSyLf5RY~S>AtoD>#T&30HTimDV4goyq<`%M z<*IWyw$fDquMT5jN(cqhJ!ehYSRu=5As1)vYoW#F(V@X@uq=UKr=#sz4lLJPDWv2~ zXI-e2{{e7{p3bSh>~gqo{vx^o4&eBcZZfa%9*O2E(~y5~_-&j1j&)_mI4>0%wfP*Zwh{ID+?D~-^zAbVJgv5wd=sfu{GePJ1>S?qexxCEtw@T> zxF$slQOj1%tc$oZuRGuQ+Ta9)SC6srErTWC(q$7x}jO!m5~;wX%Zd zG9}cmH0pxbmp7GEAp0Kx`A_l=jx`1AZ6yk<@-VVpEzcC*x=LeS3#WmA>106Jb6F%~ z1I8Yeea(X8?Zyjk4z4leQwaIB^5m@(;8eG?Lyr9i7FqnzXfQS42496&j_!ENO}|`u znBaYHv&6T`M|lwYgnm5HeS7TOg`o>H(E1uhQ<=M{Xq0J{vpO82djz{u4aHyACOyC{kG> z<*Xma4hkEay?Qj?&7A*!{IKPY+yCxkv2=wVQxsv9 zF1&@!=^UM2Ll=dVxcEC&!2+@QOJ9Cv#1(r_5ZK18jxegwFB8 zYhu2I=aF9Y>v_&I>AFsiIu%MPcTz-1JD|E&;xT2oAkAWSf0iRBjJK95DeB^O*PQK5 z`-D7g_(l97HaU$T38$h8N2&FtXr_@=FH_=e{liA(mF*P!a;1+!2jZU3L4}CO)<2jF zuDT+r@d06SBY%~Cm}7<<8n0j_HgWmHQsVCCKYcQP)JtR?AvD`v~6&Z9u z3zAT~V4#QC%!PqTFQE?@?s+k{_m1#Cz#-vpK^J_AoLfcVZ{IvWJG#8{vK?O^e)Hv} zyy+kdRX^gfFga0W4}O7s#p5NW+CK1{%9(4C#^_ZI^TF=0q2}ysGx}XZ@|MTFa{`t{ z9)I}8hJ^GT`%zux9SDM^N5Vj|qWrGfBrnUP&wGkyLW=J^M!v9$d{X0h#V~x;IzuNw z7s{}z6f3S{7m@=vli+rZrr-Oq0qp++;Pbw)-c@y0TTW29UoBlR@jrcpt@s`-!7Ib| zr01pNhmwi>VCk`$I^Em`Ug~|XEzm&3Jho_B6!Zwq?Ge{@J`s&Gz3-}DYsLT&T5=Y$ zC~o(i^zFXw;=vg&H1eh|FR5V91GQzE-YvdoyQoHf!+9rkhs?EB#O`9JU-IQD%;qsl zWIa*gA}x3oo;+R(7#*9aDWRlJ#iMDkP<@i3uR7QxEf!k-V0C3d!}{FtiK!3$NQd86 z_pdMvkow?0bX{5j-m9Ybj8Xk>murcSQRIe3jD3h9oq~=l3zFZ+nQw>w|q$ z3?icqE!lU()%&u|%@>j&$(6}h{VXF925=FXe+BLoIlS;2FL~UpMNG;rD~UIx|kH10OApo^Jd-grUy) zlV|dL1Iey0v&qZ0F;VrRB98Fng*Iom;mB=lz;{}*gv5Tqjf8c1SX$_o+kYiaYo)+5 zF^55Wh!TK)P2RG4U7WRO))A{J@w-Vv=~~@?6BL`3fhv*exhk{cO&cWTo;x-{ zmD0+Wqxov^<5=+-hDqRQxEBLHBrGfs`snz9Dvx(eJZu9?nR{c&AU4)=WV{UE;&V84 zsHjb3TI%(8$it>bhToEo;uHAPkUOmy7lIcuNjX%Sv%FxgUxb*H z@$$_RnR3M2TVR@rE#Xt51W2uc)ECN?OtUg4aJ=7LRfjb|N-6&VloF=p*=z7GP7mDw z!o4SD<}cEuGk1$2(dMHanpS+8)K4F@zFh&YiGPkOv11f&)p^7a&7+We3+Rgu^=*)7B@x zHx;MlFR{@i9315V)2o{0&ZcLTc1lL_N&#{^pn95Lm=H=}wChGo#bZiH4?xysHJG2Zw3x*dVufN|;uRgok%ktw zFTbfTk~N0YnwHEl+2M7(FLM^UG5&4LJW#8prSWcSniqZQ+q?^3ZR?}{7%L`Sm6*7~ z7-i%4P@T7e-1&c@`81>7PDzdEx5=??cr#0Pmnz99o^E{HB=7D@CCqB{W7~2hJQAnI zxPSF2;#p2TeW0|wf$hn_4Lia0j`AryEO)UJh@O*#JTPWzNEH7FxL^^o4wRqe`q5z1aMkFHFOdDPOft2=m1hTrykHB@x7Lq`gA<1@< zeabSL_EK3>KtYNO_CNjMivQKhhR?6kMzDMIA9d9nmw+k}|AFjw=LUf3)M~3WJc5Ii zY|wYH*;MaBy*Ixfj@TE9$lvEdFE%E95LwK`a*w?6U((XmEZT$x>Q3i!n*_jrJsRVOS=SaIovk*fDhw;dtj!&j}4P{ zU=2XlmIVjdRM6JOTp)_jen-N%GV;(WE&5=)i9vo%5{)PDMQVwkZ_PO|Vh|c)Q4b+H zztC?tg~;s&u~?rfuAQ|091XV;;zvtGqN5v4b>vpyfnd9^`r#nzg<@^5RdY(vxk_X< zRw4ewH(*LO>@O^+hXRObl7f+DvRZdjlpClp@=fy+#JkGAD|gy8_R7k#+24}KI>2tX1{Q3C25Y{1rGIXNnW)?5-2WaahqeDG z0-Uf(-!s~;V0>8*$RZraf7ML}?g$0pPX-ZtZmgYDnRTRb*Q-K^M&q9W$h4*Hv$rIE z_3&D;9JPchgt)mk){YajPljCIHFl;1F#uODpp_mHv6yu^#cDq)svUcrmc#oQ=R0m; zfmOM-c8IW9xKD!!$IdSEqX$CQ;iQAu^7br%}+{+ zU=*n7?=mI$9uo{77^HKWb*-F{CLEw7UZv^~2CvAvZ!IjStm=hKP=3un3ox|*jR)CG>SFjUyQA1Lw?SXf-{s9c)%7%lBrNokJWLmHsCqMxv*!WWuGtxxZ zSrKQ#$^Ply2Y22#=SmJQQ@aV)Iu8Do+HD;had^x5mNX8}k6>l=V|`+6TDPGXUHWyv zpdxI}og2#JRU6SvqlG^dl284bV82A0HV?~lfSbVFTF^9_s;u~^y_B|}8sf4G_J5)? zDJv@GBXPkZ8w#~C@J72I&mBT&zDH3xLpV$dCu)UUaAS}@v`0pmMT+{ST`Mguoc zAWd|st8{Ow^S0I|W#4$ywygQk;!l>CVIh}}7y2TXo-hgyNhQmMW(s>73Ag?f^xZwX zva;enItwcn(Z9Bf(BAA%JW}S^!J>Q8MYcTd@xd`J%SV`4Gg*eR%WA-nB5a6W)bp|) z=nU`m24~`%HE^H@J!>@~Bcs&RSRa|ptcXo_PR^b)%o-ZAm0H`LanSf+b?H8Z@Lndp zalAPYpSfP%7@fHPG)M>#{|x60Rg$^m*G5Dj&(k0dPPkY>v}H2)AgOQr7?_G&n3er; za`FiciGV`;!%5LShq|?SGY)_}UPcim(#nzeFl-;5soJe#oUY`NpdbhUdled%8BX>Mp8=@rBa!7*Ez&i3=gJ<5Eu zyPy3Pj<0K-G^o@sojAnppyyGdwmv?CXb6L9ljr+>mr#!oR~ZEEzrTf(f`}(m>@+&x z;=l4NY*%#2uk(%4>pRVWjOB4I1*xkgOU#(b9w|1Eiuj$CdW0bQQ69+D-t4K_=s%to zo58vOjBflYZ6Vdi6cL@WvqqVhAE6q~R_@ylXVSv|QW>l)H93s|EXi4|3ofKi974>w z^ODJ}M6s5X>?;^UX}&a;XpWv^2)%7o%hT7l+y&`Zn|d$8Qy=B9!don!ebjh85l2E4 zxUgFJ_JS5tDalQ=@=Ji#5&X+^Cum;X4-~oIqRKp&5~3$Af@EJR(d?H7y6QIAE~a1; z56Xib5z9FP?-5d_1TlbhB$D~*OtO(qu>(s$nv1@7whQdGtc;$fzQqX9EvRdm4N}XjkeS*ZVqr4gMdj^bl z5;dn~|8F@}^{>M4O7n_ML8C+A6CAa9Wh%FbAcs_*`XMCWdI+TlRSn8e6#^X*r%6BI z9XSrxOm!BK+4imh2t90ijxwbs)8eEprd_<7rX;MLoNG;n!VtB(!DAUO(^TLR?oq)A z%eE=uTsn5`hwsyjeE>~rLob&$2RdLP7z=7M;oUj;fXGSBQAPF%lX5e1*I;$rk}3-H zPkpBTUP96lR9bA?i)qMO_?YM|rk;2r=3E!n}9f@RCAg)eNXU#=oQw%&|kXs)#nF>K+VEm5h zxZ+B2Vf)sh@81pstpcsT4`U=0C+|*QD$pU+L-jT!@!!oACMKRYpwnahruqyc!6^N{ z1WLC{R+XSG;0#O{q|Q_Kp#E4{@Sdwpx~ohb=iq^IO|2;vFBf5iwwV(Zc*dho%U7AR zc{c>;cSRj#6qH)T?Rk)5ExlCdE!(keptEQxSd_jUk8X3FE;glF=-hBI0nC_oYUCx&nRbR@`dVk|{?44P$4fR^y}U)S-EYjYvgbHJ zKQjn4GAniKQ2P3(R$HxdPYQ#N_CAA1T~3?QgiEXrs|qAJ)0##55$%D%^1w-4MT*uh zs@})?UgjG!VXiGia-@5Uj3Moh;`&;=1rEq0FS#ht5B>ROf^o%THJlbFMn5t}8F^CJ ze)()uUf(NcOP+N3@=N^Fhv7hOs4i#!VyKCMvsL!fzT*>_x{K5-Sz%l-@9|;4@`WBg zab=u}Op3k>sfi2e-qWl7@S!PTdka;;h)zBz&b*4Tsda&gBGDNOk$Z{LqTS!K4xX;uXA53yTAX$->|w)dy>lxw4% z2>;$Y>agwQ!Iks|A|=&kH|N4u^pX1!Yqlvv;`rj;LUeWkYLsAtRv#O;XDuWlgt9pxeFA>+1SJPvr#LhtZ;s&*9_x`LaL zcMw$CnkDak1&GV)ox`8?(GNc$GQdjHt z_gJ&SlBznioR|vj^LbI>*jxb_i zXb%Uc+j`(BiF0vj>qP08EkS+naX-;bzwGegv{|*c=EI)Z1)O z5+SCDy-&BbW0NC%4iX^*JY(YlEcF;|2|C>5JL=g|L!^H>V}c?lE92*ZKPcK^6vRIU z#!cN2rVxhv3{Lr$n3npZlu@CBym&ju+6cdqhrfj=9THAKy-ReqP=oY*+>a~M2xxw5 z_-K#cQDBsV?B->qbWA^9bUb6#c}C#RA)#;)dl#yW8oey<14B7h^G^TW`Li|q_A?}Z zAywL(y($OFdXVhgoBuDu&MGLbE()`q9}g1T-QAtW2`&lJcnCDsI5ZBy-95OwyF+ld z;O-J!gF~jLY96MhYGz*hVJz#yP^yV4W0H3XilF_Vh_Fh)ZA-kO1A$bC)V0LGKJ!GOjJt znB~XWm@(y^u_xGqrbji=OJ;XGJ<^)Z?PXtY@-X){i}b)zl&D49KrZo0`Okjs?32Pc zWQb#J0rYEjoSNFQ{I17k_g(@`8Q1tKuYSs@gPw-I+n8-89FT^qXy-m`-?s<83Ooll z@hWHD-%T}yCR8eNpna7iE#4(7bVDK)6jduFEzb{Ury#jAt}8BVV8^!>>C?2|ro{Op zi_XkXI9jC=H-!19v;EjaFmL2Y5}?{ObuqH7_x1|>=t@&A{qe!kOWF4K{#XAm?-Xva z`+GB-f`udSM`5vnSQ;UCPV3qsm|c3H(g=k=WVF}ct<_%ZN$Lp6S}7^sboguSHZ#BkwHScyL|B z*kONnosg!JvIDKWQZFbuuG7TUiD zd`sV~=OYwIg0WJM2>+`}U(^>$w##=Rg5N8>1D5xr(~#M)bvM_xU1^0glX8TumDLv} ztWWvyQM#6hKPs14v<5v##g0`)sx_tc#D0+y4rrzP0pYhzXB;9h%#4(O*5y9WQQvd4 zaHcHJo!@V1N}NTBiwCaQiWM4hmx_X!q4_=DR&zecA`k1mI>mY^+Exyg$ z$R(ImhrxUe-bb^3#Q9DB`&))rw+y*fQJrhxc8GjByPP`)|5Fzaib|g1iYa)scIHh~ zLvgWd)Jz>;nYCPq>GrO&V-j1Q%41g3)F7d7`?d49V%S>b{kq^QS^-))=cnfdH@5`6 z@&5$n8o$d?aQECj!5V=VH73YZ9r>P?FPjVArIi~rvKi@OX7}}fD!t*Oh%Cw3ma*sA zpUsv6Spo{}`Wo09%ldhn>(Y#eF?ZJeuL`Ns&M&ZrgOgio*)`LnbGb`?dUBG} zD%`z|62O&VNAb8N+MxM+De(xoSmMOQ+vDkW+4ojo6paF!>PQN22P zjXpi04B!^P1+j;FE4))sA?zv#W`hOapS(}4n(~tCYehR|YaqsTu&@%S^#6n0PA!>U zGHjYcgo3I$XEp~YWZs|)xKm8XL_>dhL2P;OfHzqz&uq`tj zXSFA{rCVlBoY;G_;+k5a3ZIB;{IF?BzlWi1pEWYOvPP$0zy|9oh2Nq{j((uX(|E@M z+eBh-Qg;TWP60Ijz|t|odMaY4s%}|dYBYFFF@0S0{%C(&15z@0R^;G-JIMsCN;*1P zWE&yGyc>o@Zm-tr6yXkiE4mz*5+mgJ>$0b``)B@b;Iy7Cb-~A~k;8OTRI)Yha3j(7$$Cu z2IZstQG3znn*w?q5T7&qTIx6m;tO$d*#*Q`%$ALmjGgFWk!a9L5gnkp=1AnDIo`F5He427i6K<9%*6NPi=WUl*Y4jA6UVGsTPXY5-Z?|{R-}(7(Vuy$ zmuq>^9i5P-j+fKP01?I}uQC@cduWKKO#RUe_nZ&UsQGIRZU$7I#h?yZ^lPeeo zcg0v7>60ir>|Et8>s9*}xzi{ws8#+c=d#Mdy9q zEWySN?4U*p33HX2o$SAimSU zew!BrD@n7VnJv#D*w#IS}oP- z^3o4p+W?A>6QdO>`^77^tzTrShO-{S2W^%#gjFYo*5VEmXMUu(@bDpZ&Swh6tiKCY{j7`Ugytsolp=w#9LXmAtc@D{>0WKsPlmq= zU`S}-=WFg_9$pWu(&u2>6w9!96r?!8k&1$jCYq4lQ3Bw;pF>V6ZliGWK|6fN_i|yp zkqXs1hpoxx<|G%^R`FyFBluaB*g64Xp=yE_*qCU~)ZI%GTuQ^i2i9eYc~P4el-Nkx z<|?HNXBp<3=L%2&+Wwj?ZJTFX#kb$-#6AxVyAYE~>PZe1=9lQXM5a$Jz&p&FwCq!@ zhVtC;GeyJ(pZU{oTf)`9RKdNn14FwZYDo(3e>ERmzxMq|AJR1{P?S4Rx~b73o5CWB zeUi1-S`*WbT`F}vHh-bj*Br{34Rc@5=NTH`bQn(HiHd97aw!h@uz;CEnRBXHo#Vm4 zqU&S%S(~jh<(&WkHuc>kQDAwORQLX6B^xj!+O2*zzti@9uK<_rHrYLW%pF9`AvUYse zj3S00h1^JZnhUE7LaI^^I!aBo4N24(sdY*w+Omrh@D&hR6aOhH zZ4%cB6tU<`zRyL->jCHr(zTX+fext<3`+WA>%v9M-FSJ`#js;Ets*~a zoLRQ;zhO9r|Ek-9&n+xX;ljP!h`(SXQ*KABbPm2};GO&faErtL`t@)G(p(VBE~ana z$f*AVV+TGX8{5GAQ<)a?_PX9$B_=ncVCtbQ2%@3pjqyB4OQdLe-H1x;)LnnuDyvOp z?YD40|3hcfU3>OqMnfqn#y8)w7BxG*4jBa9(PYV6isg!$%T@N>Ljo-vgiGA&SLvmq zFLGBKUs6V*b?&A^3alZoiWVUZ1B^p5dxVc4Z|1%XUZZCP-pNA*X3)~1yL>0jd!@eS zV8=J5_`l>It5o4}B7?|@lKABgG}pHjSUCqqT4U|ROy_p#Q>OIghP?=)Iic}77u@Lf zHl=y-`9c7WuzmNyyhCK&rkeOr67=-z644!J*Chp)l0lyOkg#Aoj-G!2AD4sU#&kHM z&k>056-Q?FGulFU<9!K?r1B?{MtW;?-Syeadeam*yvB+*>rR`U=XR5&1(_o~4sZ^# zt`x(3Wop{E*CDM@GUgvkBs-G#tu;-?G?Qt-FN;1U=8K65JUzoT(pnV?g(rr)#exdV zD98HTQ|VDarxsETG;E(eP|_{&$L57T4qU%)Ytvi4sfu!hWkJ1Emn~{jD2C*-9&D># zuDS$3ZW&j>TXL5(HuAKxjGz50QyqTXJo3Z9XO%Vmwijw|>F3Za!9&6?gItqI{mff4 zR_Q~X^Fq(flF7edRMI_>e%N7U3ENqqGwWXKTZGdshd|RBr7n2OkMtq@K-cX#CX(rnq zNm?k#yYS#njLKDZFG}3!?*h!}SDW=-kJ@aRbd@kXRw2rbW-$uB_L>rk}T*FhRLlha}nktkquYXZ`r z@p+13vE=CTlSF+3U1~&B!aYG76oF+5f26A__||uU!VRF5{82EB;7z4;WcTe({)xaR z!&Ps-zl3Suv)T7%pF^FRpi>Af5Q^`J`O9Qap*5$R;vfQ~Yiypoiv%H(TSNV%>>e0RE-GxU?3?s{*Vb`r zURgCJf`Nw>A)Yp(iKBha-4_P-6U-dEkr}k%%LNYu!B|`j_TtgD$8~I#Ospsa6^$dG zHji5me+Yr-(|9LK*ZiqS8LA)Qpidf1BGM%W8^b}6qA`ak@Q>!_1$}klG~#7i>L-W; ziX)|2dykQi)fj`!tf}pJc84Dzhxb0-b@9H10DAzdV=;LOC`a~be!&d{3-R#%Vc#~> zq4)OB{7!CC1oh=z#qIkp_|j;ym$oZ@tOUV7FK1Ela59Zpa>OG22bi9Gsd=Y5q%Y^b5BUgNYA`W-u^=9hCOL8QkR{?h;1ux|wfg zYmIE&5c7?oO+{~GutdMt!l^^K(o}1?lp)*up@FS(Vv?W^8K~Lfc^$oA#?I_bK{D=21KuYBb3VA3 zb>)+!RUP<{A9E|($LRa;kxA(ww=_k3qe7uAB}t62I8|C6WLua;q6J0|Q{9~SByLfl3O{;Kr?j zo!+Y>Om{`*OHHuqezSmBGE;nk{Kn6Z+sR@sIkkU`R6NSLq4j{Lrz{P9#iIK6Ox;Fv zwfv+@$I^|SI(p+^CjyehD4b)^$^h(9Jvx5F@DcZ;6Zis4?i)1QYT*^Ul95A+g^oNH z2kO|(G0b7P_*ju!KGb4q?D)i?15bR8b8+GuOyaJ$#+^#2wXq_5KEA~1EQ*fADS}hD z=1I~?XdW$LuMbtCB=3KjZi0U)`Y4ED1Wh2hqh>9Mj9x1?rPHHWZd(aK~@m`)Q&IF27{l-HQCs-#H9Q#!Xt@m8l@fH6$oSy&gNSZQafsqGwF zUBq)vbxTYnkCfT6<}fa}VxC!VmuoIHZOvU9x9fT^rTyvq>1LS{sUM&JnAU~BK+;l!Iry}hgR6$Eq0)dP?It^O(2*lp&N=Qy)#g~ zNqCi{(PC$c#(0O{t7i{cLqoS2h_0Gl%dzz2G{2^p0LUAE|E9ZyYuZ=x_bUd+j&%_m zSmu(vm;$~dfSaUptedJFN|S>wn%*>v@@Qj5W13%9lp#F+X|w_2&xI7lR%{Rpg4-rW z?f3Yc)dpJpDoUOG%Z6zo!N;XMJP$^S59)qPTAcDL=^wVYV6LiB1YY)@Kcm3u72(ya z;_P0VLr8HPESq+7SDIy{;XItmKP-rxbirim z5^~!RsEd$++jQ)6dHp~Xwu;QWae>7&53$hz|Kc@L}+D^nS_{4#^v}(WA4!LJKO%Qh?1yfhH-mo)@#G$UN_i52?CY^sw`outdb%vva z%KNE=3GDctEr{GghD;YUu{KyTgcnXL?}SthO`ookQK-h0BP-WiZTnE@>mK!{!s2Uh zYtMnuHT~mI&@nbkyPg@c!jeZ=C%DyVr>1Y|d01qGR{h-vM2Zj^{P#*xyJF0yKO(S6BYw7zG)jkprtZ)m*3P>)}6+`)$RSwT55O0hRt?g0ZxtS!GA?oNIfMk zBcn-OVRRhlKy|}_Z83v30?6XnCkAIFXSt|Cl3N6=R;jy1;xL%wjg!5m-8SV2-8xI^ zf|zz|uyC414y28Yo<92Uo#AwK<|V~(qrg=Od0Eg5aLBb{;l@_~HSErQ;OAB&x~B0e zDZMK&A60hMF&zLXKu*-|KkL&!_8p=PhW#nv;Y)t-!bi5pK8i z-0Pl`e7Pc}c(!IwUqQFS3G*rN3c4ZoWM&y1V7n3N1iFB>RwB(JE63 znaG1fZcv2##P6=a)(eu@>vHoOE)Ot`AgUwc1mX+KMaZ9_#ub@5(%fbcI6c+^ao^->^F?)oxzqQ|chA1f}YyQh)BVr1bg`l_<+ zdt4Q&Os( z3An!@CZLcb??QRTnDp`xe85*)2J;? z^P+39aqODL@v`ThFVe$s#G9Pe?od_rk)!(Qd8>-#yy|fo7z;gC^}kuO{tr6(t1~tY zjnj9@GVM@7K|pp|9_p@Wew5r<#)02=*WsU+%J=4HBS&fwcerfE8*WsvgVpBz>PX7L zJ^%=fll5>G539CA`9_4T3H#_ptjhOq%HXLLj*JO;K(R_w5w(EKjg9Bdov|IJT&lHy zJ9(_*5(4{e8o_DI*<9=p@W>q4*~Z4KU2>96>Xx=Gn7mWjfU)&+Ew%~4r#Cfp3xUJ7 zWkXWmhNKifyqi2n_@zUP`w&%Y*~FXrVVj)1b*KhcU4n^l)P+FcuhPFC|KfW-kKS9O zzcBXi`%M6c*-sJYck<@nxvhH34n)^Lc^))`C&-ark{$ z1K~wqqbe<-Cd2rFypX(QJ@}44>Kmy4dn^X?P2cRtseZJp2PRG9Mo%D0FC~ zS&k3<129GF%cid$LfCdKn#d`~_`jaQwlS!-?g<9Nz=sihMxCEqUemZLH+2zI6F^}P8$-0dmf&h9zvX0*?( zKD%@^x5dcVIL2{mEa8!X45g-UTcAUnGH!q~-(Sqb&F$GkK8&LvjervSD(jU~se{$c zXIf6BNy!Jb)WY&VRx~|y-xKK3WmQ|Q$Ek&iDa*niBguj<)}aJC@g=SifzYS)xrJ(- z)fF8Jhe+Di8`99FzvWG+pX=IGkH?4l!xbKv>ibF*BmTrTCx9@V9GCvfu1paQRUhef zzt$g?5XHXYx<*OgvsnA>$qt%U%Kp-$#!nL~`avYUcoAZgrnk~cgh(I~o5e1NrM`Cc zQZUwh7M+r3)$CN>>rl|kNd7Q9Rg)Hw(Ue}+x_S$?U)@-GUMnw);rSke6Vzw`61?mGWpNY_Ppd^##dxum@66j6rEq_DsW3Btc3B&B8mTPr4nVa&)>+yYNF z5O)%;^M=6jYM3{}dXrhJ`fW$wB@?9j#SB6Zv_jSB=P}1q&0rQp069@vJPBh|)WRZC zKhI%i$A4t_ix@4~X(Zc1K2lvxS!~YZ$7z1_?3mtVAZ{z-tlCkmQk4FqY+vS-JdPi} zPDnTX2yl7B&$IbLCDHTMghov4KtJmK^OEw;ZozeW#Pja}C_i%$l}Gl?nye zdGibL*ywE)7mZ}TTiCM2ZY*iNCn#nkt~qSzx6>}k(|+PgB}VChN+3*O;Bk=%eEe($ zxh1rvjq@Fk*FP9p)KnbBu4uve&;ib})OD|_!I=WaCd=7!idbao*S-X^WuI!H+W)H0 zEdVg!n}6 zlAvT-9Hx>M{zx`+aIXf?=b7+8YQa1$H$Om_r+#1Sr~U`vljHP0iOgwf2-(Zv@P`cw z%V*Ck5BFX|Es)}I2RKMuH1uT(CAsmX)To*HRDZxnfg5`4<~my*c<;y~zJ$y-O%a*k z(`4`MHk@GOJtf}If-+s>#L{SE&Ke4E{CHZ}wKBdX5T`Qtr>~#c*a1^y;zkj|$4hOTAs)|_ScUdrjKD9>2tuWJET-@^F-*_T&E&*Q5+CC_CrtUQbm zw`fM;Y5w@o{bviBRnEO^Fd!HuQ8a*_ z&6X(Vo~->ZcO%AORbkWjr1te%o$u{pUxgf{uBangma zABQ#SJ#968uJM>diL+0pqk+6TptEIe0c;&Pbde_|iDV*w$Q&81Y8|o@dS*Lsj0>gC ze@dOZT4M{ue?TYM>n!6Jz*+;ft~qRa6SUBaTg6k6GMA9v=45GTudZ-3@J(ERgOP&% zf;Cyobxduun~Ib~i2D&)RlgGep9_z|2m6!-_4X2GcKL-R9h(M!Z>xR2eVwF@z>=z= zG zi>&yJiHBDNcdY$kjVf{&=@*stSm3W8g+eV`B~qRQVUjbUr#Fb#%kVt$epG4vqvrk% z3C^+3g5!L5*#vq=WMI?p+?8L`^)vpz`7zHxmhdhN+>M7_90l=cmH#;)0l?LJi=_=v zDr<8F{~13~t5k`10{4Q1T$%%|zKQyZT-usQ;A5f_8M4lrawEblNT>DaM40VU3RE2;W*SuG+Z61Agc1l5WBh!KW3&rmuvA?aA zNZp#34cwq;HBaB_80gMyjhx z2rmpP&1fZ=e})flyibD1?Oj%0>d){k^;w2W`rflc8f{$I{{f_}bqWx5LL_}>JG;{L z&j~^ps3-qHBnr3TTvTyjzKzn;u>!x}1L4_xJJZl!OpIc&hvaa=;9GcT5HOP&c zHZUX}Nm&2T^6>V(KExEbML?C#&KDC`OTVl$s$yRY9#E3@lCc~Mljq-TDw%B8xMs>% zb*~H!AC2!sJUMn|pQ=TEjb2-`S;Hp17+(^{GDLu_m>v1LUWv>Mlyjk`V$X{R%5%-G z<6IMNDs4Y{Td(!v>UoGvy>2P8BadN9n|Qi#+UP(Uzp?&OV65jA;Ce z*`2;dZIpx$Bh0Rp&x;Yg@UNU)Y zCa1!VXlM?#hvLk$ivvqnI_s5Yu?+-q{v&XpWyKaOnRWTEe1H-0LIJL;(Y0+$4;^1} zb0;aTXvyP&eUtL@O*3$$*961+kq#>QQ)Jn_GMYm-Ofqo7amSLuyAeYZa{`Tp_ofju zuTB}o-<{piJ|~1@n$G?Q@T=e>YCRt{oR}l^CNNgh)IzE4>rH&P`8AJ(^_vYB5vVTs z+I|0wjc=NAiTibIc_)$1Z8N`KFlvY}2%G>s-q7ghmcWqV3VvE}^)x%USLs9>6T4pi zp}OEjXWH1O7-=kYR8-?w!@`_7%xyAKDIBQZoRurO$v!hm{Q_GA zDcx3LM}=Y(sl^jksPk;Nr5WJw47nW`RIc1De0L4x>9jtK@GQ#}ofIS^kaR=nu8(!G z8DR&BF?}I-r^tftWkD>p$*0TZ61Nk3wuB4Ro9|VmEInCluZ}fsuAU)TG7l#B9iEnQ z0nMe&mV?rtqt86A_2M6hvlD(NX|@Ntm>D>J(vv#3%bWY|zmSiyvm`2~@#w?b=mfMO zBTI_OUEIv#wsMtIX|r!6>8JVw`B<*Ngh7+}p?8s>!#A=xH!Csn(_a`W`wpTb>K!SY z#95oTR_tUif!D6@Wd(|!*(1u)QaGr?D`;`1epz5mq)G==a-HrI}l%+54r8& z$S2`3pQ-9X_X>6lw)ypd&n+bx+A1Pm0OY|D)58ly2?h>PXs~s|snS+?W(U-Npv}#IUPKG00FHm%BVIrXJsZ;JME58(KVNR2h4RLG_@Qk<@uTM5p$rAGT zA*RKIIm|QUQGo?oSX9CK|!utyz{)OGz) zD^PCHVp*-niY?hQ3`1&ys}3#Gl9GY*`x?dI4N1>jB5ELdPS2ugQDb6#{s&i}@qw3{ z_nj&y(VnyV>!iTd)`RXuFk!`i$cMWs7_(LHmWNS&PY)I&9#9S-P+9o={I(&%$XGd{Y zc_$Yike>pc`REkWPF&gnBGK2sIQf~KcJ&LcQ>ex`xFi{@{=rUH5brFWmz!|iIM_Yx zptf8I{hnkofcu2=!3mQc*Ioa)i@CkLjHwVC%e`d{bo-|SjWs#AfZ<}>!$o(&)Io_^ zWSE4zq*LPHn*z?j0j+waFw8!8Xl=0@BGnDfD=zlVN)dJ-b+d@S)|6cY_cKv4=^Ko~ zNA|N&?>mBjbmMN-txhu~kSs5!j4BX>ie+eb{mpuxt7(|>J>;Od#*H5A(4PRCR5eR# z#C}Uf@gibNWh1)nzMjCnZdAF9`P~tHceUO`*DOOSr*Mhu5@K+)j{z}FRCMxM z)1@+?z=Fn3B`6fdJ~_es4WNrDpiHL6yzvU2gzjzot8{E#_lv#Z4{!L5x>!une}JIV zbqJ&pG|i4N7*27i)m_ESP8q=nd(WgU70MfGHPt6v0T?d%DZ9NQr~X)pc7&s&@r)lG{lrs-7ToMYG-*ap7V9bFgNT^@FuMb8&<`TM`g zP0Xrf!RGxa4Z7we8&6MxPubytgy`p|WNvnL-zwI|hkhkt3bM^TIldQH&7C7m6zvNJ za1ES4t49>2GT-uUybW0Y2f43A1EcfAc6yh!`4vPrYUZ4X_;y4Ti_8P()oxm@0A(E= z0!yM7T-fd=MpQ@Nm`47V2dju)cXN9#CbRJ{exv~Wz zQ^EXpHNj}zLIXL38Hl7E-_CHh_lcei%ig-Wl;r_)B#~@%nuDsuw~VEf)N~?SHGWcsYrKQU ziEz#X<^Ug2hfGPtVaZmy@Cf+fx)x@^r{X;N5U zmk=?^F^>;aLR{73{6I@8l_&*QM(dw-eW0R@{sYxKmLXEmg*aHoRDC{m>n1Z+>L=)B zWG!&QBVw*?j_MtsBYmGJj2yw>&AU8WaZyBph6`(BjJr)_E?qO+5yFySERoyg|UeJ^-Xz-9-MRQxBz?C3? zGMsGfzjmLJ@3y3ynRJ-HVvO5FCMtIpDD3JsP+bG_l4vrg=H`MBOE()<~l zCNYLt&UR=k{5uUpB+j(s)xGlBQTE`Zxh+7X^Fgng(8Y~dc4Uq;9(@ZCGSn5s01Ti7ba z^t3VCt9{+Gd_O!jiEITk9bC_3M4)@+I{bG&*%vvnkg{+w`8K3DXoJ zgsWKci_q+E!H|ZAzTw8zqphz`o$R;uORBcCb#bI?6mZU_BGGRGPbMv=N1k?-HY+Q% zNu>`chqS$cUQob7v__YsUGTCil^|HThnFY+1F zR-n6wo&agzo8fkPtlMo>MBJ=7<3)yUfdu{jAu}&uf6vBE=IH z1v0nJV&26wGmjQo(zrwzS@$&c!HrxusS4kdviZ>!(Z0Ox8yb4lR&`sv(@siK->fhp4Pkdr=UbgA zJ8QuJ7#TM4dJ5jzbta37X~=L5oWT}9r?YUavB@exScU!tJXhCmuCB5PsB-tXCx5NY zaQ9qXteZvI04?%S`MAmxk!4$W| zn8-I4LgFD?!s8{Tv53P0SljG|z8IGQuG(CMX1U^$un$S74J7;#MTVI-!`&$HK>aw4?eD?=apHlPM};syjrD1iGW@%rN^7=-05@v0Of?@0|KX7Pn~Hcpm3H1Out zCCln^B!;Ae)mu+41|1639vI)LIE%g}?pZTmXIVSduP)-zHaB!eOX$+$ zUie)*=7d}&qIR8?-P&?^CE)(+(vy7*lKSMD6ZZ{%3j!!bNlD_l4k&;<`=~A@!8_s6abU~kI95n@tO-E zZaaRg$g0!Lp(Z1Cs_8`*S-Em{=}!K;V;tA&=GaOe|7|C2fOZ<*f4OmLDuep2uX@p= ze@yudwWMhdi%k;_q%7l(P$CqF13{{uWc`ks`>{$ltCh=Qa)=kTa$+HP|7m$vm*8%X~HBen8+v{ zvbtrXJo`x_+_0wNUy=-N&ISF9*PeW|vdhQQxPQ}d*2b+o;)imGv&!Y55W4!I3c+hc zO0zCe7W$OJuXXdcsP}e6SCE-UOWS-;OXUmf+BM9-#IbmWK@sb&^%U@(UFb}#wrEu( z=<~as8~UY`Z_J~`u;Q;Ng2)dNU#Is@)rQq*`9kD}k2?)+XnbRDQoO4YGP$;RoDT)v zS*rLDo1`f|5!&hQ15dQM_<~o*QC|43sqSuyXjRY0Qq<+daN^Zgwqz*rA5e#P2P{G( zi5}0y=;JDj9<|%c>36|5ULIt7!y9~R0ye)MXfn2N`&jE7K?eIaIwLWE0toamd9ay5%QImSAkm_`#sWXEb}LB9q6cBA7tam zG<|Qa0MdV9N3q@K&V3R3Z%-^Jy)Hq(!k^dtN<_ro_94bV_CPQ9?X9ETVW_KGTs+7w8mwHCC4gY;&^H!a8 z;!HQS4mg@OClZm|)cBJdPsMK|_2#P#Z_=HL^R$5LgNV^yu1w7#p`W4lNO_o(70Q$BCFjEycX+ZEnJo3GRtv)mTUbRyU-1@G#+a+ zKKpImOR!03YfiAF2kFAk-@VB4G}lud9f7rX6&p(`Vf|R>PJ2Up7qL|G)I6*TJcE^EyzeD5&5HaZbUb>$LD-IE%WR&3GCdues`zeG;DZL4#pn7i&1Tv zHA$$MPd{ND5~SQ${V3vb%tsFYKIMQs^f$egyBwQSAO0v=$Udk$eTxvZtBjKqY=Ot9 znEicw&Wl}ob(By@5GnS#>4rIdF$(QR1KHe8{_NuUIZcWXTh3D;o$R(Zx+y63ZBLmd zqYrvh$kdh0^1>j^)R8JLDuDMHN)jdC`+di^J(EA=uMQIFmw+CahA^qCanjl;p@vT+ z!j<=jm?e8_AZ_OL!IuTLAOQo&Ya0^wY|>PardTS;Fx|*$LFj@JXWYPws+w8NoTK&$ zxUDthox*whTu;Dc_DJq$3=R#ZSy)-GhJV&6<~O)v1*_A6erJXv$nmjgC*tA>H*J(- z>3b24H;7Kj2u|Xc4~|`xum^=p9+pkW{Y9q-**`#LifW)SMo}p*&NVz&9fBIo%-q~c zj&U38hMb}g9;hr4!b@|YP@Nx@k_y=+6v5Ji$~Uhz5=cJ@u=H&S|6au&XIY{73&1-ppL6^2O_F9en>8{c=O}+Z&lqe(a*NKK=5E}X=pQ&V+^LSl&2M~5k?gjS z)^VRjM#DC6f7H$}fV2O=5XsaIL=Vyy9XQBiUhl8K z{1qZMn^+Jx%$7CtcjMf2ViLn_SZfu=__oIX-x*)+u?{VVRSrlt7<-(gGHi5&yCn=Z zi!Sf3FoIzX;mtC=2;1ajNpZqaw@(y(t%OTmdCxfZz^*0I-9_vTmzzn)XS6@*anUi< zt)QTz(GZrh*)#Tn-|EXFeh+nH$l!Wx6wS?sn`Ozr(b{!7K@%K73z!B0j0% z0QIXSd`YvMkPZ~w`sk}zVKrs=bw!F@fgJo&{Z)npUm&yLMttSF6@gQAhX--n#bKQ6 zsh_*w8pXMK`v-eF4mfhYozKI6&*P9%#Dzf%11)aCK@k8c)8VJ^WsSPE37fsPIOYfM z_!zVpwayR6iA{z&;1VzJTs!fF2U`2`hrJ(TxI43rzYH#f%%QimkF;2Zrs)^^n19wV zzKP$JpRM;yy8khhWXbN7fxf?R3D{obTkn4PBJ6rs7K6p4l z>LZ?v4Qje>iy3J}U6ZY~Bi-nDmqagNb9b2d&)dK!@Q-@@$l~JAh4fFG!jCYo#rvC8 zYc*(f>dW;{>%cw?Zs8+7#LLSX>fNxRLipfMl(2u`Q(xp$ic%h$(MSs^>dA(RtFSSn z4X7?|b8B`eV{PC$Z#IQM?4gfuckiA&BGov-1H||h?l^0y5%iuoJhF0+^-NtB) zR1duQ)l-B)^$}vu%s;>rY&&}`aG-mlAV`v^EyojW6Q3Aotyb1vcHOd*e#O)FOka8N z<7X!lkedS)oJBzFwLA=P+q zqN=qls$ey5c(HQYo=q)-9kG;IG`&m2=M+vg)Eatn=l5_Ys&1@bH;1HXEJl_J41M=A zWULnj>Od*k_^R_4A=wtPPZWG?Ledbo+EO@xAV7W9{eSRvPSKHe(Vwn#(m}@^+eXJm zcWm3XE9}^|RrzAuwktL}wyjQ2&02Fe|CyV*thH*@`<}DUKF@xB)A@jZ0G?_@9{HL| zpw$Kg1r(^Tn5qQoLj?_$hBIeeONaq(;wU^Gd-a`qbJQ=6NwhAHQ-5y1GY3)#65LmE z3{MwBzJbdo62*b)(f#9m^~ef$tQ`qzgm|klRU+gI2`>zFEQ8$8>jAXBI3XO%*$|97(mW2Z-~9vp*1|6}O|sJ3+S?yX zsHz31wOw%uMQubNcU_|MjG3QY1^u8Sg) zfMQj$VCYYBf_9x5so%n|AzaQam~t0t{SI*`>H>R_b1;ReMTi7vsTn2YH$DWxl`#NXPe z-*!V}pB{85ipRq)25cVN9V%OsM80g)Nmb8vnd@bI!~eCEI8jHIvJ+2F;T|RK%46h$ z%;^twbe|i?C3g{8glf&7$<-rb;xvE;5@tCLT<+(uR8)n@!!i;6QGQ0qFq4-U+nDH6 zL<8IXj;B}NwWaGF5}B4H8zZUZ%SWVulNdzqzvDRID+XKlv*NIVOn!0~6&F>M{4j&Y zRM*L{EQ<1ZDnO=&+@o- zRnz8>&ZqE0`AvMDj=|p3-mWJie8DN9E^4*9vcS0nQ!3VADbLuZi`B_odWZbuOmiWw z@5iGu04jimw#2@gs?!g5#ds>7Ok)y=m0-o#m*k+x9}{?k923&=-i zD1w#DVcPS{(M;Tsv_}Ko^S>ve*xwdA{4h^r%h%vEO{!qMt1OI?JV=!VcPK(5m4n=%&3+YTUu zRMEXGI}YxvCM1RVe}GF8bz4J1kMeCrSEnO26c0ve2$gnb+mDwRR5pnb0H^bCS6bcI z(99jjPT^^6$bPJ-uErJxx+Kiv$?a!?Q7B6rx~H;ivM0cDqaQRu_EH60X}4v@X}xB3 ztP_78@w~Vo{DC8Is|*RPH%%x1IQqO>CF0D zNnDw_7?o8WpT4#LHO>z8p-ofE@;apn%cMXRf(dV8?|e#AZq{EjV4B4(3E#c40K0CV z@S%9}t?-4lXQZ}LoEKKIH~^Go9cTVh7klqNpnsUsSEQ{PB;;h#%RXKa? z5}rYPkcufA;ftOVcRT4WN7Eb!I~)4gUVziDyK4UTJnIEA^GE=aXB^ww%R*zi$_kef ze}i^u&HiDM0l&PQmV!Olbvv!~9{|Ucss1^6BsJSvpz#-`!YF#npKm7XKu6akS2+Jr z@A98y9aO%fw@j)x^$ooZesMe-{{Uq7s92VE$Ev0dg&n^ggb;{2V&V#?I|#Z>wC(I` zk|=j$ZWb?r-Y91sKsC|2xbV+rrMEwv+0R{gdBqjBh)X}NFwJr*7p~! zQWqButm_OUdZ=#di0FFvyMZ08~#5TZkiAf?c_QS{B`VM29 zi02$3BBi7F&1W|MtF*7 zH^G7NnN@ImaqjO`)w3>~uBzGc$`*+I{me4=ebalHD@>ia@(_w^H`H#30LwyTK;qAE zZ~ue#_0S7DZ`~WopR8<(%Q1DwkB?qH6es;oo&zwLTQY^8JYzrQiTPloJla6?kA53sA7 zGZ)BVMToy&?DB=sAPFnqR=B>`?shzT21KkzhbXuHK&%D(Ni5CG(<0|$3>+6Ov@iXc`QkL; zvQXSB^vRIbo~6&&9YZA-am;3#m$t=KVv>Z}-kNqfDHhyUC1q*4Ld3h|Tck`}XCddg zqAhx{RSzcJBdS!CB#T?GuYry4qw-V_XnDN_HMjPJxj-ski&WwrTi zLYKyAMMjw<@oGAYF@ss_K2GS)(3C{XtC;>ozmYu|eH<^}N;x_l05ysZd1Os32i=@6 zpT7|jU*?d64elq8MgmSM}9B#ddzh0w>tv)D|wGLUJwpcRG~ zh9W7(@kK*~=1V9IP5FIw<@<0;t3B6hGCZ$|!tuV0bzN1fh_rAj{59bRgNH?$IA&@b zwtW>(0{2VKvb_Zz@q`ob&sbiarZiF-zCb1Y&A32R+ssdP%?+LGT>AA*Za80lRy#{5 z$u||Y?vb)EbDpU0W=5lLkpR?Gn@R#QKK4UNNuZ6`8jB#EnQ|Wh30+(NSDW(*7commzA&F%?l%a3Sxogl( zv=smZa#~`oVY_L{xT?JwLd%8ss+UQ9K@Kj}fp4TxAPZlNtb0P{K0?)o3xcTI|LmIM3Qz*i8IYqCWHj z!;xrVk-mReUMmQf5Dn4?(pba6X5b*OXu7Ti-)~IwxTuEKgZF37)vcA&y3{mxO^4J2 z4m>!z(4?J)#B#*dl27PzS=-!rP+#k{;4DQIW!Z7h+c@If!2ufoRVHZ{0z*|a*ycoF%>&{Hk{SojdlXO_ z>IU)V_cPVtuD{ja-xWL)O+}8)xDVc9@vDv&ZPBP1?KQ6P97+l#5R*h!jYx7d^SJes z@#aF|5F@Pcaf$k}vTc)A6EPkg7M|SJ6^AprVOfASac=VNO}ef0w!3KCO%9+G!r@zz zuj~l>d?FEC5Kjs0MI-JJywHn4*d{U~DBR}@k0nf7rfspAqO`^d;u?LGSQIdX_;*=R zN!SA?`>*=IbRiOP(c^?M;=khE*j2X`qaFyPwB%}(4^?%dfL|~~{g{t^Pq9LH^+|aa z{{Y{lkdVHCSJ5X%#z;msuh@jVFs~N93AJs<j{HIaCFu_CQyGACA0EeW*P;0@c04jAX99=9 z!()X>OXNF`CnTPLThRdc7fsVzUotzatSkRZ0cHX1SD#ZZko3x^OM|K^P@O(c%ES2NOk z1VW200CS&_CC9<6a2DD5d~QvT>hzyNltOjjLXR+{Jx`D(;WN-{>_f(4~dWR zeoLY*5!++dA5x<|f9Ok=jwFYhe1IVBQBH zH^c{}*5!p4ot8)%TmKO$&WY^3=|;R~q_16uQyO~pP^W(b^Z}p_4Q8Pz0c?sg3&jOt zsYMd(e97f!D!@f))@y6XjMlzW4xwZ??}&RDx{lxwHhjbOOU`@RIrzg?px#6`fLg)x z*R&u*%2L7%MJ9@ojeY8ip_hIdNNSac`tQbKVVfuV5~lfR@8WNj7u*rLh`B?jh~kX9 zFPXNuNrxuZ{j@ni)1)_x^duQR1j1LMZQp4xP%byrPK~V%eHQ*)vdiJ$m!eRRy;-{l z`beS4AJ6$A^y%Erwx>)?sKIY`k%#AM6?nMx)w5nLfi!UQQLH_{C z1#j2g%~H|jX&!rtzn;}xKSJqR&M4C)lt$mHfv-oZ1bo!>Va4xF(hTgkQlAeY^{)#W z%FpS}zt%;pONgiZMwPcDbYHUe4q9kDPEJ4BVi*tNl z-jY_x-8p(KAcFqcJ z*c3Gng*k5?J*>S{08g#&?7?Hpoov6nZ9Q-iF{Ar_u6~>0idl|#!ZlX-_E_4~s$KnF zx;eD>E06J7yVLj*$ge>V_I(9KOm~5S`UL-|6FRis0##hDMfM-SnlvUJCXT=~wqrqO zlSY1-sV49%aA0&{x$BaCcw5rLYRGAy63JAc-oT)&XFep@fa5-37}dmi!l9p46n;mZKE+skqv>wU#fmBUoI?CmH=^) z=8jel9pKd4YYjJT@a2nVNCIN`%KG4HtIbZhg=Q+na9=3jxnA!+the|-EFwLuJgh$F z7?|%Sj%7R1!7TeIRqVkl6r6lo`$%a4hbH#3N2sBT7T)!2MJ~NcElirhTD=SA0LIf@ zVl(7kq`!1iTxZe`FXDrZ9N)9Oc)H?$ES$<1hNo-H*H+))y96I88jdvE+a>NSV~cSu zc&rZA;o;(v*Hr|tz!87>h*rQj5=_gXjD#mtOq{ZPqJP*pbF^egU@`jUvoe(TE=_{} z_SGkCY;484j2SMFDw%L;-;NM9PIA5{zf_;#QV1~#=MqN3Rc26P$&U*8@ zrflEaNt{-MT^uPcGt_T#-YOT5F-NOG`Rh^r4{-Cg5SBUJqyUbo*{u25bAD=`eyjHP zG~haxb0)mN!uzDSXCxQKeR%9^--r;_;YH#oW+H{NRGVFLr;yk2d_!u~OZqk(#q4s9 z#&?lAeLb`pmOU|naT*g}z9r61Hd?|3_DC!I&tv{KNd_+lx=IpSau#OA079RY^xr;Q zX{<6Us8rwpcKg6&uup{u)Cb`JA%z}Y>IU9BYaNNeCr3{)x0iDtiiThhO|?EDtW?3* zcyG^LCzO7uyARujtYA_2gjL>^6K<3=98nlU3^FS)Dtp)CW^Ge^5Cfuuyfcl@KfnX? z{`+IuloNqMPrKu1!2x~nlIFv)viG6B$djwe)~4SKJW0murHuZXG;FR#V-Bki%=9b4 z^+VM&=MuY#yZC{5Z*9w3bW^yMus{=&M?0L_pFwDnap zf4ci3P1T>?ZMXlv$}{KaC$Z3P4>YgFck4N!8zN?my9t5Zzkio~8-`X`BOABUpz40B zzvX5$^mwkkS`OuXGuXrUI+XYwL4tz3+2J2x)GA>!_8{O!c%X%*j1h#Vb3XEU&5*P1 z7YVZTL+j_JMLbg$bJ_K>NSM5YONzdgacKI*$jwPq^1_+3o!2uiKnP&=$E%J}Uhzhm1cz&iH;^i!ZhF zUQ>*{L{;4F5-2o@f^HGyD|uv$DuQs%iL(93rryxac8${TC>`QwisP3QM$BRzuwDhs z?2px%x9t*iFds`dtToZx;!&CoMgPcCR8lxGIvWXoUH8ii z{DGm%{*}KU#mcRFpi&`>_o}mD%pXbY+pZZ}}3Elj!DJ^`UY7Et` z`Lh|+=yWL~6C@O|DKoq-aJTGE*(`kLYD#BEabXER3#N15&_#(*CFP-Tog?Ee4EMfI)9;QO-Dqe+3c0;eXBxxLvuBGUr z%ALfV)YzGN%1*Fy#hLYPWjORb+zpzhN#>1oWIOt+NmcGn(w?YY4cSb1jH!_4L)j?B zFsbKiB}ze|xRh$s09kq$ld11tO{CJMOT4XsGhCW6W!lH}R&c84=PJ}}&P7vcY%*+* zW)snYg<^mjC5H4zTkb!=sfu;Pg_K6pk$4d0b2RWxUByQ8E{1d5@D1~uj>+?PSdhpL zVd%)>O5Mj>O!gqDAhfZ$GxhEam?J}C;=ieZ zE9d#r9K&jz=17~ynI3L!hWBS&@?2JMDK6(4lYt8S@+S@Iqx`FPwT>}kf`wv7z;`vT zJo?G9vhsD8Cuc$yM`!F_VqybpNdF`@BQGb1Q;FA-jUUJTXbQZz7kZo^iGLWY@8*aV zBYTMvn%XW=7?FH9osTs+NWf@BXB_6xR^3)%;v=5I#pALw_e}KTQM)$u($h_T`2NVB zw}d*E5xx3S%6fWU{;yC_Wpjhzc$ycI#<9BvfBVCI#ij5yVzj>%|DG_r{MHLr@uHcw zB}Km@`nqtg>0;0D)7|KDJUq}Y#kwbEs*91qJL20iVTm`=OB@%I7*8mZ7|$ineZD9V}n z?&a$;r!L8X#GLgDbVW?K=;$DSuoIiF=tNu!or${LI+>XhZk^Z7X7oYY}*mwX6#D2?s*){7n-Ze;k%hMTS z4Nvd##{~HqertM;k(Cld4-oIX*AOgFRRW=-z|Ni>~!+A6YTA&0}tUR6=|;)k=5L0@WO&Y}~@60-;tC z61k>-5(Enzk$yf9yQVbVi99VY5~}0`d*#A~Jq%LW1uP6P_Lm>_PN{pdZAdUOZJ2Ls zka$&WQEzWc6=xX;-NGZpX;&TM667Se)09`I)PrQ<3u8N*^(HA^y-;Hnx_cq3GO+3u z)oc3`S{&cf{qgILhYlO@xGkcrcel*-y4kL{1(_tfn%+%ybG^nMA{kV;POBme#~pfm zY@E+23}B>3Ys!UQnyL&9=a3jBi{Q1U$Gw4hmYaL;S@Tc)?NEGA)RKLpK$<2CHl>p@ z?F^$TzJ0Qd%a}Q@);^hHtxUlf?saF&c`E0)4#CeiksY%b=yV9CQ$MuW;>5Uec;34YKR(t)b|pP3rRnlEggXMEH;KnH{9 zur>&dK%lll)!Dxd4B3-1e04Qt#YpnYv%>?@UtE)NQEyDj#X{~GZb6IZUDHpz1H1)3vM+1(;nQ70{ zc|R-tjOkh~*RAPw8^k(ra#xh33aFoi7t*Oo+CHOf zi0`2LGe#NnCHo7T)XRlm>asiAj&wn`PfXtd)ThQRDmXsXNwwAaQm!+?Otz@+sB|0p z#IJeI-A1=k-vKj3V!8EeL1sMb03x%X2lTP_LwG}mf9H=12Zwu9s_p+jVi5{{DwU2_ za=oTZgh@(!3JaQW-0!S`6{8H? zges;mg2L9G=iVQ}I&s)Lcp3@Ny`>}hRH0o&C2^7#QVya53PnbO8uki>{%-cZG_VB$ zj0#*D&}rLELp5O@>Dgh};fCJQT$C=K#j-GoO&mUb>l+bx$1_2ON(bA=&`s>3!72abBl6=IbL^hQ(Hy5omcyV9F zW**Vs{oKUG;wr+vpZ+MT!`lSF_rCXZE+y27QU-8sTz9OWjvJTlCs@+Z_y9{C9o1`f z+l*8=t`>+;=(%WlEZZEEM}EHK_WstHCw@Aj4Vb}*`E2f0#-OK%_a7$!o=MJnSB-&A z3cmKT&;0kPzHZ)*b5r<5rXDzaJYBuyh<$X2spO1lDWpxqF+Vx%;SbC$jH&o|!pgNI z@%2XXdC7pzqgSDhib`8d46WxN0FvhYG6#-FVZd|$aGuF;?EorIba%IY63Pf!bT9n_ zTo1!-iW?5(vqwI<+qZdd`UKWGw$(6S(Zpa!2&1AMsunhym7;R-=w0KWKkJpXn-v!_ z1Rhjo%?{ead>BOnTX;=!y5H@;Pb*I$h-%o(7N>?*7lLc~lAMC=RNN2rC5e(}HuIAO zB%SD)D_mz=DwW1jOy7VwcVu6%M=A0bFeLQ;0XpvGI=IqrWfypiPi%fiF}0ME1qK>0 zQ&E)IZsg)$ExW2<6}m%NsP6s&Ka|;MJaZ*iag#bj?K-y^A0hL4mQ>m$@?dutq5Deg z@8+v{><85E1kJ-AsIM&BUnKmggw=L8D%|2^qSr*%7w;b>D1pCwtaO!5@;qFygbXJH zyy6>7{gGP=8e<*UL=mK;X+BD`A=w0POtrSPvX-h1>dkSu@#iL<=f79opyHsg0b^7> zE?L{u^CIq5b}e>xq&XpS{q0h zhR%{;6t{LIg{DN;$C5j?+OY zXJbhgkvTPQ7%uZB&112|W~IWAv$sSp*QWOFgRyp13AdSXm}(G1Hr-%rZY{Sn>7QtY z@DpV{RC7G5Iy_hY@}_K2%CzI*}en>#faY7IwkgGAe)ARb6&a13c(kA3?bpyFcD5wJ#lev&v$t-U`5j zngqb{6+_0BRf8aajd}G!3ow#G82yNDV^ZtNn7c(30>-kjJ}c((+33R@WmO@eyeeG3 zfc#D!hGa?X;lNvxiwr*a-M!*nG5+URDHG~Zb&w`7GP=y1R82S%%CFMx>9Z$R_b8$~ zcc=zP)L6aUni#Wi!6kw)-SXNQ%HbNIGG+|!4Eg7unXAULF{7kCWlY(0fiPz!b2F2w zVyPqe{<2#vUYOt`6b2JH3pSJH%hZR@aah=Jq~DHv)mhuN?z>rBqG!BIt_OV0i?V%( zmhly1>~f<*iO!&>t$g5tg0Rh)`ywdAKRYDO9_){>D^6JYKuLrm`KqmE(WJw~2(_&z zjc($c1Yfbe45q>KI3*RRYa87c<@$>lIvH_>x`wi*PP)Ewi{R|xQWv4(E;TCt_k8+k zPilFc*ApuaFPfMlPsf~Nu#Yn92)B^`AScI%_L?I@Vv2LYKf&0)H4PyoA1t zSY+D31cS-%;~_ulw6D`Tn#{-U)P>@bg1Xmw3n5l0qL4S~yF&>pufKh2ujRHEpahM)o+-Vh2U&A;DZaBtedwy++g25;;}< zZMrp1MhWAE!YdPgybRB*B*d7UBt2hVIqP1Jq@3-4tOQURRr4>HiTln0_XuK=U z_RUo*KL)>J9Q{U(EXi3t9Yy_QhRCOoedlf=eWF-{xv-oa!Q)(fcHz;e3(O=LR;z6q zahEi%J8Dq(&`-~+5?sjQeXF(kt+-83NU;aSv)kspvNHN2I+7)qLO!4(C{(F}1<;2X zHjxja>G68>Al%yX3*^*QC0JkGpY=qg!e>GLE%B~8XRVTQ%}iv!cb}X$-*iTATQnQH_@PVwdP8EY^Owtq4=g^DHCDLBE-ueqOC`TkBOE$*>VO?U8% zwGXwz%Uun1U5wO9WP0B7_IUwiHaOT)=MHlMjgleCLqGLmlC}BuFF+mC(2<2o?0m<5 z?KD<8C#Rrut=wbEhlY(@CK&iWO5~u`?e@cGoLwCkFva}72%DKk3<+~@vBf?#=5l}N zMUn^64@Yu`@^uwBE*8$_kBjUD|7$U?e9Y>`XGOfv;1X3n$vNxwL` z2%P>Efb}Z;Y#Xb3e3l&OY&79`WLMpzMB?g|^+!yNzce;$0c8J~5;l!A0j>UhocIuD zfC)Wr2?Gvex_FnSKj)}bn_oGY`crh{+YEmDk63rZzyG>sFUk7bQ-Pv~?)pq2=|)8F^_aRCHf(J^%Ec40~Bj zQ+ZQUwH$S%rFuU7RW$n%w?FYo{z(_%G|Ioxc|@Z26wB0HlIP_}gPy9_3FwRq zzNvVy+6~t1YypX*b4Ox;U*n5)zh+NXNi&*xxcnNDgcy7qCcD=E7AcKveTarOJ_nFA zQxF~dq;U^Tg2c4mqh0CJb+{V#CAEOPH@?^o#W2dYem7S{gU;wJ>^)1fhTh0My^ezQ zJY6c3eK$FeFUeYe3)SM>^w3@z1==7p{2<%$e}IuE3p}sxPA5Vy|7L3>Rn?!*Nl=}r zaX()JQYLtdY$Z&)1USD^B_4>I_+TlOJHbe}MKZuZsUNc=I=j zN@=K>t^<~h=MQ&W5}F(3YEyk1gh_?mnv18LO;NJ_5Xz9#q;hQQJ3c*PP&uRumTFC+3%nKqtD}ZT1m^s<^gaJpBh_zOICh|@H?uNl z{c}Yt{QhpvS0ZZUqNfz4xZqbmskO1Vv>H%F;FH2X*KWB}k8pOkn_fH?f-0+}qyJA? z(xKC~L3p)WxG!l`hxKrb?!w`bu;v+NQ*wqY({Xbcs0d!fMcmg0K;TG!pB>+j5l~`R+#F4?=i~u0H|>(m0}>&W(y_65(s~-KEjpD!0VMgi^m- z6%5pKYxEHuV5A$$HkGSRrb<{+SUigoQ-8j<@k8HLRdv=g4{yaCPdCxJ>vKC2iSu?D zVdN3~6^fL}TJxq6XI6M)ShkckI4}*|h)Ckza4fc7`-O={HA#0ZW={1Bkleeh$i5qHp($1 zV(dEeHSkeKPDdWKC*fAcVvzel2rJj1k)xgxfPEVT_~>aXpRske+T=#-viul?Tpzi+ z3L}|CtbW4 z>|&3@zAKZxRfthy%TqV zN%b|bO3#OBIBpFR#GKCLbOJD}q%PG&)=(YNi}q}=Gu2-IwW6iXy~>hM9LsKPO5OBB zk$Nzy#dTXpRv5U4SbmMRLuI*rAX6t~!mPoq_4&~8%jcoMc2C$8r?>%!>b>HFqXp*= zlALqTRlx5G@y>AD5x2DijTL`0NnRs!?qvJxPb(FZD(e3M3JG-hiwsA9FLsNijjSJW ze&g}PMP-$u$k@_X_?8njyAa=`wYg5>O=x{vwGNt^Y%WX|pgixc0sE@i-1jSO)(DEM zb~}(cmJPRJ^K`=(yGa)$o$*q@T}pgv4||)tM%y!06-FAm;f>J$j*aTynu6ME@m;7v zO*K=6Mh4Q|YJ(|JpIMIJl=3nn>2jHBh)m}O86MPEw+W;bBrz3OPQ1`H&#-OLWFt_q zI^z+hJ$)JYnmEpvB6YTRoOOGKyfi?w#V=lAv!SgNM}(5-U{tYO#;q!gG*R0VubYzu zfwOnnHM>7?0txO6$Lca&?0hjd!MiicDy^OcnFdzi2;TpAri6Ke8e%$zr^sKCK7RBD z=?Kk({zF_1-rXPfBGX7{AVG8tVvTy@Ee>rfNKAOc_YanC|J0k;Wj%S1zdqI!FBlQU zZetcYjRZborEL{9W-E}E(J;+e_qH}TFE(4KQfkb2^3tbkUaL`Eb|~~N!s2R%F|k>y z63$D6yAb%OWy`f_?0%9)Z7V?LfLrELT0NQVvq`-9_fMrVt+aEoq~p`fJilN-rJ_K< zf3$Gm#AlSPu_fs{lMMAsjj&Egmb5zMX5X0mTAb~eYdNuVM#yY%CmBI}u8?ad3yYm6 zoZd|Q*pi#K#&0KR8<=#Gc|qVCUB5VC$r1Smkb#%6Y0{~{yGk}DReaOueB~2K&0=}a z36*OM?ymBR`Ql1QCALyp=v{;dEK%BID}mXwQ2@TGl8Ql_&IShJ zxSy%K9M!{@NngX}*GecM=O4NFAybn^I*UC^fovqK{CDh31mD9ktK_!4RalZc@h-8Bt5)%0P6T^_k6&1mbjjTCcQ zuhQ7@o`!PVu9)Jqv?UtRkzVFhfn`k-hSKQ%;1VlN&BlY>df16CeN)p_M@HbN3zw#D zc~4GC43kORkhW;OcS+%Dz0-wXSGBiJ0xO0_JXEc4l~YJsU6R*!4LkRamKS(gbU84T zuPfdiBR-TB6xj|0s+r|{yCfo#D&zdVmcEE|y_i>U@QCg%Lao2N?H7C%MF3EOSR^K&K&Q$CeXpAUD6+OEgr?@cWL0elcuC zQDa9l>*}YI7(?C5UZJz!vg202IgEjV6!5WD=x?dCGQSw>Q#`(v;OC@#6L1)B zNjfd@kvx*HIT2efWp?0z&xrOtiChe218JtcDKGXq{ejpNl2D3t%OuIQBA3zk7n6+E zkj?3OhObO@G-e6vp zC!F>#uZ;7o;qfB^z${*&Zn=ZiIf2D~@N2qw{74X`9@+{!TTZ!)+MBAojR8Z(8;3=L zYAPI96;tiPVLo?I*&#DJPpP6TbtH+kQ&?;pw8>rnb}7wwGjSec9PwduQgl_U2`xaw zQ*q>X=S8`@yKlEYk%K115Yy)&YrpT`pf|A~zn1%}H#CJ|vNO^$iI^N9PPzkpS!up_ z0yAmsGYm`(22Pi%6H-eRB`%X5f^)a&S%iL$))(x=d~jDIl*IXB;t|5`Zp={pJ(Z^l zxtW2JHr)d(UP$_vZt42&@T8^GXXtE=l09ph#?$vlCBD#?>$U~Kl|4aO3%Ntm1V7>% z93>X7OnP*IG$C;Ssm|njF@5+`S*nOsHb$F9;+XBC3$zOq#|FR$Se)uqBpZcVDE0pXC7FASI z7Q-vI@J3ZOl;9nLCJy2b`&>}WBr}a9US_F58 zFb2tO1544?uCRHDf2>$STM`GJm*KX^TaYDQU2FBlLKW}xQ*E<`Q+Mi)v;;df8ls?t+p5=1FAnR#MmaZFYE zCden*|D`xKa*vZH_x7;5cYsErIv$ezF?e5tqXxRMX-ViSi)QzT(0Zi#s(XBB`0J9Q z>$i_Wp)ubr7yAH<$J<_eFmfYbLP*g!)Qq!gj{lY?u*qfH1hcus6{E|H6p&c+2;8xhMwqT`B~ zAx5;B=_YS(KE2bse){k%_8n937dGx=u%bxkzab-y^Km%FcVeWHy3{Vnm4 zVJPiPzz6^Xj*}-McddAMHrYGEjF+TOY&6p18SBT!yg^K>u`QcRsxl4^klW+xy55B9 zu?+H>fl&`p-~sGUuT4-imn6=D{veynk>YKV4J_9a+e}g|+0|^EDHtNnVBj3wR7?X< zTA{P95Kgg{v3wu3$*y7;Y}Ghd+tlrD3~?z+Kp9Oh7Kx)h&e0Hf&QU-gGZSRLRVni> zj96$gV*<_?PDiCFfpM<~C+G9R6^Bf`Td&BD*sr_&RI1JnsJEcLAtn9;%5_V)+uds4moi3o*9o4JZ~EOJ3-hPC8gd0lzU^K zRv7eQA<8rn62fZOK_&rqMeVcYZ&eUPFvPJdW*hqM^sa)YzHSMtq&|@>hlEgX@hzTy zVo5k$VE5sPO6bmLS24fBKJ7HRxh|QuOHRp!xJlLSn6soNNo7+9&i`qDtiYyDe^sH+ ze()rp(h(_C3vI$#PG=k!#GNQgUdPy6Yfbt4D(?cAz`TbSIb$d@EbF0L4-?+$z>K{| zSZVUDo}V(xICN@!FdcpDN=g>a?5Ibef8j!7y3IHXcTEv95%hh~r&jK55%PGBoXqOo zd-hD7W=B(8sIIfag1(}}!6FuWnG<#W{x#V`qoD!k!*UpP?|RbtuE5eMXkxkbzT~3pVO;lZn(i)_ z7;d=o3fEx69Y%RB$r9*841IqF==PYJW{D?9 z?G{E4@9z?^E1vp0n!DL~1#bTs((p$|9UeOt=5tAJb)U;QUUFnj=`fIQu{6@1n6BY)?hbCc z)kbArdWTG32c?*{oPcj{bnW%c{{ZmUDcQk`l$EM79Wf)z+ys5^U;Ivse_VE_UGBek zt?bG|lsAfsD6^%g1bs~W{hKLq+H~ej{0$V&@&-EHxr1U5RlJbtbh$cGzb-BfoIu6t z)j@k0XG)97nqo!;LlcHP2UTOsG#;_fD0#a&q4OA$#U<~YVAp|a^)68H8ByQPn6Si_mc>N=pUA@ zC}O|E@BaJ`Fzh3_xa4A^69RgiBY9wE=+2pruZv271$r!~gPp^l;xqORguiqM@+)bz z=~zYrZo-|~D{vG$GNU4ne29I_;%l5`<;-)PsfI=&c4k)Qd~ep<%{oI$+=?|xZGwg{ zfmiECdWW*WeO{7h&B1&QK&E*V;ni?9lXB)mrPM_Cpu)la#Z&(sw9k^pQ_Di7w1+FY zTD<>|;&Sa%EQxvrSx%DkaaGzo}=o3FHp^E!ljB8kvDi;31dFn5-!H!JjyM>GxNF{9?E=%$Hl#y zQH(Xzo8oQwfJEA>FZzxMMuRr4h8!?JI-j+!=9n*&T08<7T6 z7W_>AGVqK$v$Jnbn3=fat)j}%LLx)$Gm+Tbhth>3x#=@)DHFRh0Neo*3}E%-e$G1Z z>{u|k9Qs_QDP}teWV?Fno?Setw_w89WM8-drq-Qo#6ym|lsG=t^v78EZ+*T`%D=|( z@oyz5a^(Q4cH_nK^qpDis(+E0fM>~mg`GWA49*@<2Wa!$_yfL8O0{4Fax9&czAj*I zKKRK?Gcp8n%RrC1bpjFY^K5Xi#UMAv^Bunc$sAu<@UJKhE3Svzk3WXTb_+K7>?GgsZh-mffkc{-`TW1+2yMJ{)U(Q}dmBsDxHmwL9ww!#x6B@&}7#)9*m?u%d;E zZN_V7xHQOl0dO)s+X{%y2}qvzk7ZkuTly2ZJPkr{zc2Zbbj8xoo}l;G z=-JwY5{&0+9@GI^0Z^28Un&Krgo`<(%ZBc`uoNWL#!uDHa%XPwIs^JWTVYK7WoBRM z=zH)tYF%56youM}FVd|DbrJ4(WS!y#mTrAp5}irZ$cBISS{H)5O`VpG(td%Y;_1+$ z#;dZV|I5a%Z$qEe*-lsJCooOpw|Y8CUAV$tztq3)^I?jo*Iwng3x%O?hZeLlryo{i zaaM+gxo+pBT+Sw?F5QF7ZQ4gX$?z$WUFflYRT>;xn%y>_?9NcCqsRe(M=8-;+{;g% z+@D&nnAh|s5q6=ZaRngKg6ZT|t)iC2YGX!IV#+;&Wv;pEu>AhHHx`*FK^++HTUcxVR+<&Wf z^;XEX_7u89S`kJnLD*0AJurf(Gk}}xn@?Ok8Z;h}G6y+MtvdVC!`60r4+jA;S$hKC z-Ttss67=2c1HsGPB|%aL=3!3)TqsZ*HdAfO{{Z1<%A!o2SS=_q6B`{ZPLQ6jhN|FR zaree^^><+;xuMjrPENw#E|`q!)AibHLC{!-ARLN3a_75YOSNA_bk-@RxKfkpYen%e zn<_m=r@#_YiAWKc(K9G51Tb9$(FdRSDUJ7Gm zoTi8i%^&>E>B|NQ3Jly$^-Nr>M3vdCWlTe`Y)Qc*h@JmF==RkK;c+9c+Hz^vEH-iP z5=?^ruw;leDXU}WLf>n}P^Qa2Qbbt`hXaw#_Ko&I-4{1?^80d9QY}t1%dk%G0_%k3 zKF;ttcNf8TiVhUoL0#Sl*oG*;I8#K;Y3R-W#nw5+Rr-c)do?u~lUun-lWo^z+qP}j zWY=Wd#>zF>wr$)0Z@u5{`|ds)YwP(v>%Q*mJdb0u${R1!k&#di8s&hB5@u*w8Qmu2 z=UyeVcZmWZa)oi)M(e60c`ril}(TEyT~&qmsDCB5PH69`X}wu2yDe3c*J=u zHt*CG*N)x$wZ_jOD8zHw;#N(CA?`?$Flvbxe*C8HzW_g1$*jZ_kO=pEX$xNvW}F!< z-Y|QL9f+qN4*X2yz(%mPddj;Q!T5PS%~M}B$V*9V$K^8UP2;|d#9}8&k#12KC9m#E zp{d>L91ZdLrP?SeM`cJ+$RXu?yLs)Jt4Nh2NHW6c?f0FA{Ax*hG_5YqL4M2m^Gl`P zEg^m5FzsO8ky#HX4^Z8SFi3SG5AM(#@=pFND991=sm$b78&}c|UDZH2wHVRQY~=e! zy`l+=y`9VQ1Ydy!thT08I!UvoQ-YO*@E94M$e6DxOT;pYN$V5@@qiJvG6^d>=N`H= zmc@EoQso%PEYDd#3Xhdzt{!wnA_{lXVJPtb-Bl-8S2p!dmxzBR9#eV|^`nhmS>`}^&2SpSbQJcIR2&l(6bCG^ef*j&^7otCfV?@@;6x%;{ zXnk0}?Q4l_8Xl;ase5uE^M{V(&d<0%Y-Ae^x&xl?U{sWwVSNKvyF@+34 zIF1d^mIL1dH+NL;eE0-yYLgJEbABIjRAQ^8D2LU9QvU-myr~p-G>cO?7CSH=VeCIv z2t=RJ3r8p%+dyr6vGG)DzZ!;WkEcat&q0maB#fs#Ca>U_S({ph)NGNpdpDtp9W#a{ zd@afjZe^Vrg>L;$N*g-xkvx@}KP{H19CjviJN+_FdZ<-<>YEJK5_?9ARpMyp_Mwr( zU9p5L01q3(4%5rqtCZxtNvqcu(Vkv@u9}ZW&_Y9dCC@CnAa7!<8OHoNld+ zC-8o_1CHH?VI7s{s==W+8k%9^yw3yt%)w3@zf7`y+D4tSb=hi)lp*ty>qXATs5v<}Shyrh(OUyXHdVEG+)FC%HYQXSoUS z(quJPLmZmYwJOL|PvUW+r7~Imb9Lmoi79NSIVRQP@v*d=h8l^{n64y&^0imHTUBu= zB;Opzj)3_|rVphhq$-tvym54_>nP8(Bs9f z#h#aNTl@aIs&poO7xpd=z+bTO96y8xxc9x1y`wL|Q%s*xW7rx)`l{eGl$6A4hFL{J zPDH?ntuJ7!JQU7~U~|$`6fvn)A|X3Y6j+)oEkPpWEC6-2qOkz|cEwSaGXte2Z7qbM z3+Sa>Sn_GF9v>gtT;*MqvMEh*JdR$HS=5%xTg%~v)Xvm-J={IZiN)m%JL^F`ELaPZ zX#hn_y9;1(5EEh&@%K? zLi8+T(m-9ii5&gK+O&8MNW<6Ny;iGzbEotiO4WmBY;5Hw5SpXE1b&Fz(#EC^tx%tN zCI>N%&?W06tJ&9icM>?2PpGfwpG@VvMujN*J*LxbCLuVmjj);X90#~!b255dC?2sW zI>Xt~RN>lY{~*`Zl3>PJ!;qnS+y7%oK2@u@xr=@K=e(@Cz%obFeH9#n#O~&58ls(* z!`jBiHv>?L0y4@stdGj-y!6XKO{Np1uvv$Np?NEh;qW$XMYpg{qh+jwR!I0@)7RLZ zbG;r5VoFHqX2`F=cU3n!`gC)W&e`Y+?WkCZAr+tJN-Geuxqg(FID((6%DlHwf2aq$fit5Z)x zQBhl{q~9bEJ$_PGUPf%zmbP(9$7sN_4e%FH`$RbkK7sDB~1|mnpw!do$#mMt1ijHVwlG`o@CTq^o?b(`+Oks0nruo8BO$zhK zJ?+|VYL>=zGsSA+0ADY4a?KGD*6`d~Nfb49haF?O)sobpZo}ZZ=5Tw zN)8y=PcQ0Rwv~Ny@kJniglF594NCiwrL|gL|G#>_u523|Prr`ZHj#&b@3NT9(N~B(Uk;Wah!58e-P&V!NbQJ!jI(VYVlqnhHhI{fJ!ISl`a_n;V&LKRDM#%f zgi%f2>r--YH83MYS?-`JO}gT=$GmTB#9O6e5Fk?fOmkIK8n(kh6k@5fD(MN4a@$cP zW;*6B{yd0opE>LEPJjNd^&9;><4%qE?+%Cvj+!AmZ*9-ciZZkNK{pM99LuZVKz;C|x0s3-L*Fm+lX^Fb- ziBx`tmn$f0yg&)R;QIJ3ooqQHc(Ol8Idw6;-v2vWCGfg$JGl*I%6h_HPN;ZWJ}Bi1 z=275}udKSIH{IVDGEPMGFfyk~!n>~v7NDs^2MDmDVr5b_9E!!hq2qK1tmzx`;Vg3O z0q~cfcX7*d4^n2D(u_(m)+^B;Vd*0(ICrm$-JFOoe zk3UU*6p zqa{o=`0LhwCmUuJLl2XBz|1g4#6d4O=QlCt& zq5$Q!*z_Na@?dGd$S|9>9!WXfZ0v-?f%u|+)gYQaYuz`})FGIJ^Ot$Kq5H;iN-nx> zm|evuPWq;C@Kv)Vu^%xD2qmwU6&sChFw?tH%z;mopxhO~l;ZkN)SKLOI&= z9cpHhkmdUXJ2SVow6OE{zq>H(Y=YFYBIx*~$6*Uo=5Cn3on=W$Y$$P+Hc^C=FXm30tqFj%>7tmh8R)CtEt#zRZUtgbVFjeT2JI@L0%8)!-yCcT@v7| zW0=b80^OxkhKrB=$kcy;(l=F9z0HBCb9Ph5pbM731Kx56%W-KBlbEB{R*eFY;3MmX zi#Lg%E{o>CE!okVmK=oWiX8P&p-sGix8fJ__P@~A6!F=OJ-b>z=a$Ac4;G4cjd*{> zGV`-ZpJl-s&l~>8Um4pUSj{zg?^^@z5Y<7hdxfq{=xgJ*>X0))7r_%4Hy}_-T=|86=^!&A2 zlIwjqtQ+^S=dK=krT$F$TL+b|I%(0i`{jN-bFlme*exXi)9jiNItG7f2q80~@h7Vm zXDuVD!QOw$&iKf3-)o#|+q%0S@GS~#XVdUH4Bc35n5H_51^2rv)yB;6wFDTL-G5YE zy5!&yK53@FE~k1RYj`fuRhe~i1v+E7C4|B6PyKD{PP`I)Ht&=P-h>Pp^7L8;c?~We z2jrZg*2S{Kl!x|MK^n|J%-GrM9#mwlF|$z8c_bIyowi%j{0H!9BXaSs1PA(bFE1w_ zB27&-LO<7>9*P$i5FrBfZp+Wr2OIICrIq%cW4Vz7Ro?plG?0XJmzE200!wI{Ix3&` zM0caAY8&FVGGZPw@jQ;RKP$|D8UZUrijsQg>Kt2G@uIMY_};dPiS3>nMnz`n*!Tf& z$xHL%cp}H{0gD#${F%kttUrcQLBLUSK|%f;;d&Hb-m&<6%e3QArsWy<>_V;P2}?(k zSTu);LPuTFp8C^E@*p8oIJ|Lw?_olZA^Bo**6rPrX$ncAVTv!OCz+w?D1sT>rPr#F zCQjP@rx>Fh3im6;hT%PbD|OW+(Ia9|h!IGySR8ej?sZA!8Er$#Bt^_kz_U_ny76ur zr)`1kiAT-^)V)9B@{&v zC4&s(H#xp5URR4)z+!uBl%rcFe>s)N+fsU|cU6)YB^pF`M-zP@&-njEQN9&M< zJlD`y($V$_tix|WNRnD}H~fQe3oJG5Xv_4ZOlUk8ZNT(6NY>H14RHtTmx%e?N~-0W zH%sJoU)c>4WF`XP)+udri}@HaW|AOzq4jj;W|LS0q^{YzU2J(2^!S(`Nd0%EocKG> zneU~#>b5*^7{u|)Un9ZlL-shAhIJl1P4|d@hbOXJh1R%5HHINbBdX*Hot6AlXZwHT z@doPeNBf86S;zMS@7(MyjoZwyxFwYBjK{KKIWi~`S!pA#e{d(b6}OBi#8?x$)~6UdAlASJvK?0RC#zDQuwh0 zV27Z67EadiSMmJU8uvin0UtOwHF7@X3e4=)e7}y|_0OUsC-D#oGgWU@xE7rRF9A>< zXO%v(KiGxJDNZ|9_k8&$o+EV#zd?**G(9h}hx4tubbEl$rax9Ql+hwepDfajOG`NLVJ3EuExfl;R%OVfcN>-%ow!9Vk zQO+jN`etfxQeN))N2JZ>Y{^+Kyfnn4)%Jb(zIXUqUA&xkjdpJ{3|AOSNu82(T!7nJ zvh#QIpHiqqSVDF^1{WK5BI@DU4OwSzY=980$xW;X%%}CZMmyY3Z~s{iLi$Nt9XPG0 z+{f>6i8Zrpa#K;Iz zKc4d_wgm5#Zxa0|=*+3cE}ljSQ8&*L8~k?(%QvjYraDE9(!8c5&FzqU zI0p8|fr)N+FOj_=n^`d)C-y1tzA1%66bY|`zRZyjP$qW2svm;>Z|=(Ya-rFLe>sjg z5XzkE=Bp>$N(p!T*F2eWkVZh^#IZoJvJyl{?+vwMCaAM^q0 zJ?3apqZVFfox>f=&)l=oZL2f-bTUR}{P1IVxAVjzHNr~nX-9MT-U@}+3o)-qu^@F` zSl#z3xE(y_%fy!A0KdG=v@<6a?TII)D35TK3BmNn1~8OWcT=4AMyqw_r^pZrtvTR0 zWv|fL+p)s>*HV3B`?tujRGp7;NPVswswDivO-tN@# zTkH5Ct=hdk3Q{l_h@A>M_qbGG#>esI>x0I$JP!FbHoTB;Zobf_uH|V~SY|v9s+tF@ zki7N>uLgM6r&8$eMx=k}#vU7XX}goO`u&=TvXFNRE3x#N%R~O#UjEi0^sl4yk&3%g z3<_z+dgA(>&fcJ*3E0(vxc%Y)2C|>3QFPMgr|_{a8tvh+5R5*)E%|7>2Eayx?-;ekv#46wuvH*gRjQuGrH;0-lp!AOMs(xP$Z%HLk9?=}O& zzR9LypOUh4sm1nvYuYM%;6T!xa~ONmmCxTxUZviaQc>+s61r~@!|CB&=`U#@SD)Q6 zNBj9luj3I)fnF(k0X7!kh?7t!t{o(v{_v^ZmMslR3KQ zpXPCCtg^dS;xI(^;e=AQ0m7ArI5!m}rA2|k)1}nH98RKrRlRBacGYwv@4*{_3f4P= z7fXUN3JzaBVRAG2)nF0x?|i5QvAa1k$UivcQ{KNY@vK?E?4j^c)Zd!vxA&0ydKNou z`=p3rF`+ao6e-Jc3#O9rXx#Y74BRn3N%KpStm3?M&9Cvyx9!O^U=1PX7Duk2)ZOBu zjeS2OzM_G7dD*J25EPO_g-O^GmA#&%R#hiqS>E?>@4B)%i+MG6MH`<2ph&ODgtDzC}qj_*33jddf1mQgq z!%$boo%QU+Qv$Erexg`6#mj#H&*Bi3S&m&J&I2NrSXlzUv3V$azQ!Re0>DCvX{WTc zzu|nobY|Ah=w@(`zt#8Z<_KlC&4v$NwKZk=(v`C%$#IJz z?EW~Y4VA1kLK(jk7r1cG%l%7Y#Msi*?3*%glHkAX8ASFqqjB9%JKclqpJa*||A zWyDc`BA`qZWmis9wW5+ianyXrw#j}J3Cs}VUVg{=9T{^x8bSZiekqZzvleTg;m%O4 z!SPH5=d!sVg(sS_LoQmTvvxsweB0Va=@bt^DZQSmzp}4!wJyTufKW|?ND9%CVolV< z;#|yL*`bb3w9W&~(YgGQp43y^q6gB=T<4?< z7WCrFJqBbH$appKD@jkX7+U4|_0PM_?#xA{A>lA>%OTcMnufgFUA#SVQ`Ko=C%X&q zIjHf}E+hEQl5TNx-UcWL`5!>;KR`CUi?5P56@zGDaqIUed}3lx$mv7P77)t!4X5G6 z-pg2X9#to|B+jlx69J2s&M7OKe>h0aY+LrF#2sDmuME{DGtG=DcfuPBUwe(Z3@%8b zf6;16frRIgbFH9IKB1x}z-EzQ5I$3a#J2qzQQTCF9^CcAioLODf-y|Y%pGB2w(-15 zBM7noO=)fC1mDB;W1g}kWHX26c{Lwz`1r`OC}X&ZDUb^5InQQ4$;aoM@NR6c&+f`) zmk;(}8&b2rO$-VRMVQ8KTL)c}t@w?$G7b_Xq7{;KvGw5l(rRxnN1FO9878io)KRd zkNRD-x+B(VbM^JEOlDb;AAx&I-{AjbSjXg!9q-UA35cs#IX#{y%?Veec&PCw;N`uX!=0k>s5A5;Wwv;`I`n`|IPWgfO@rnX^SJ}kxx zljljTHh?+sJhH@ipY0S&`24cT(9qVL<6}4`MYpFC%9l%>tC+*FI$)j3Ojy=Nn*$M7 z@)118H_ft@!PVV}1 z3p{lC?Jw%r+cfo<;aKkTfhNSr65mrr(C8bKl5zroYxk z7DgJE*OUVLT9f|VvGL`~@n{nJxkEX3NX4j>LXz9NSHeO3Jo+oK!Dn(R~#-rzkh+E5PPQEN!kW3!X-4lSdt#7gB zQ3>TKwzwh+MZ3W^@}!sZhhJV8V7M&6M${tPgDe&Ld{a+m_KOml*x3G>YpHu4-86jE zS5Q?^sK{3p13&`cpAzTjKRYCv+xYK?ID5|Uhz4-ox_%Hxg=B$K@9GNwo2V$RVbpFD z&FN6YV)6jP<8_SjNLpFgok=Ck3mNk_`WB*F0K9%Qkg_3w!>-s-3H=PiS{D&VINNQ&F4S*RiG*AQ1IwnJ6DmetEXe{;zZ6*BmmUQ#iKs}GR@UvXr|%iAf&<$-py%tEjqPH1Cb+EU9Dez&qE(tZOrRIA_Vz>6xiKzb{E(8iQ|w1w=&2y_6isnv0x_HOoAJ0QhO&P>}kPUg-k~ z;Hc5p^({bBI;^&nx^k-B&`qXYO^X-VSBu77G4fCW`*C(+`N&;(+OYI_XamD2zzr0| zLoIZXQmeY^ze)qCsI@B+(Cl9+MP}t5-UO;ZlYozXX(X>$UN(GmFgAt$M=Mv)oX6*q z3vQLusjR7o^bZubL+3unW)@TKGcTT%4STZRtPI(M+Rml)`6V~vcEk&`VF&}zoXd~* zFxAd?gM3*PWa5!B(12)sTq9f$D-CeEzzt@v6?$`iY(4x8Q%RbP-rkCded&zr{=1z; z{vRjHpjv zn%MqQ&FWP0qY~N-1mEtzeqrt?yjz7zL>t=rX?I3<56tS~1m{xS)%p!&)=85TqkOoV z&?!DEg$YAHVrJw4&jnKWxr#(Vy69JXS#cTT^1od@B{sXG{rc_s^F|?orJeM=@cz=| z4150VDB%x~OQr6CB~lfuXgYxj2S@@2DJ{-h>r%L4RJ$wY`1hL5;}|J!qrfSG&9D@( z6uO?8oNe)vvE||_$WtHlD@uw2-b#tkd-Zi~47Kmx5lU%;T4O(t!0H^&HF-@^9uYOV z!|(54$l2QI^W;Y5k91nOm%FMjc8d&p(#?9P8qcMLWR_{Ng_~&dmiqJBlAKDexQ!3jl24xAM_^2o(i62^_XlLX}Hi)V^`C zb$yma2HM($Wyf|*#OMm_@MFVstr(9rzWnBh)*7?T0REkdKwWAGnp(__VASDH{*p!? znTb0NjA*9`uO*qDFDVgpfjO8?|FW3=_`MwSS2385V=(kN@)i3~K1v}N|+uitGBng@^rm4`W@nKjj>h{BU?GZ5n^m>3IJ<(_SITWYS!5v>yqIaix} z$9%ru#ooXtuR`{v)zF!5g<(cp&8km%%f-e*n`@~SJ9o@yedy{fLrc~K7y2|GJ! z6Dq(j!a}dH&hDHqyF0fjln`+eX&%-FAeqsXXMHK_I;yXOV$qH$zKmBwj>xiCmdhT_ z7e(uyxBJZj%uT_oOzeeaNQS$kXmZx_y@Ob*Vh7hj;ZjTMXPkTvA74-&OlPZezy(X) zeZkYwybS0^>rS+H9my@%y|K>VS0UfKZ$840XCvLeYSIVgYuEia{Xh15Y6&xws%=>5 zI%2kxEwkIQa*7@+Mk&(^a^_viAE{vD;LyNbQniD^NB-o~Z8C@9h@(jaf@+u8M$u7e zZLLXn?tUnnI}T!jZq->>mrue)Ta(zebH(@_mgowwGNzx;QoBoQSt78iDIpzsZiCR| zKY%YyW}i^1_DI6ZbEVra`n#b}&PO zC^fZLoz2miZKFijKjK4vKB&N}REjY=vzMZp_OPcTb`32^ zoCn(mev4G>;^cdHPY+&n+&3bjw~RxcVML$?m>NVtRojv&yqY}=fum!}F=xT9t6Nji zS|XRr!CyAL>?nB$0^Gw(Y+X|BDO0D*7uztwhM?49lYdalO70H7=lEGd@Wav{ib(Pk zX4llAS%TeT&ArT|Lc`plDIEm_o7wnh&bZr14*gMkW4Pbcyeyv;bhV4d4BQihLxF}# z(_z_4&&rnSl4R%%{=lP-z|)LIxvthImF9&j&Q+O?df>E3^oM2LKXiwAR_OJC+{xF9 z8a;a|jwgh&g^qFWR##XU2(rTXPR?XGWO14Mw`Jv$GY1{5%!R)3u|4{@^EiR2I)nBw z^#%J&GJ*wl18Gzr5By*MUcXdR?Oh#9C&vERIE=>&y+#U4k*dr6{5Hpk;A}a8g2Wwb z^|Sy>&cIc_d{5+BS#v6B`Kzs&q}mi;3SyIwA2X#iTx~INvfKhhqQ&kyjYw<{Ct4j`FN}Izu-xi6^0Qb0~;E z358))n#NgRVKUE#Xurdyd_+|(3gOZgxAE8fSQfs4I&-Vj3Hkab|JN&Z2f)Bh%^BGj ztsr;)fHRRPme#SxXOI}#YK7v8Qj=@viu$uy#>!G3XW3n$vM5cIMaM<%%-vbHx-)hQ zTHhT%hPcecHO8|Zpe#4e7Tgf+?&Jt@fJBFQv~@_XP?SG~mrT-tm@wCk=e{DRm(Qky zdzwl9UegeBS-7&vi|nLw;WL~qEf12;%sCzZPMiz3m9|u4(%PZNPe=$*|5h=F0tVX( zeJLwrI!QMoiN*Fgd30{#?CYyPXx3BevHkweku(FaFnoRN>r1F7{MTC{Z2}hC6n`9 zs)~xs6&z}Zg9Ao+*U}eTbO;zzGpVa{k`}9)UGs;OxyS0h zfJu{-@ukSwj_#|R+1MLqHiRZ7cSSD#1fhjQ{{d`>rt0xEZ{`y!O&$iYq8#03gY)fY ze{k7NN{DwMwh?7(ugNlPts%(~YZCYvkGKeA^VnjRenz`+C>Ao&4s-qEV|u81uY9kp z85!6D$#_)$n$1@__@e&GwNG(+BJurTo-^`iaC`K z96Z%QKc(|iYjtF&F4Rp2**o}sS)jHPrAp3rkEs?roUn8%d$sv{GMbV$v1CuAUJda{ zCf_5#i#|-dp(jZ+c5nU9b46ehA?ab9PDVcjDgH48|c;>;p#sIqUs#C1iDmN8Xs%{@BM;ke@hE9#)hOzqsDdvxXX zPy09G{{YD}B|di>A~%NGi@k0rng|$f(aiL!6ogiqfY-a4uiO$37Xyd!QAOg$+Cgwb zRWI*aKe|Ow*haIoI!N?weaE z*Lar2ti7{H>+qRIZT%a@VfM7kDev#v#@`x56auSNcSGXzD|?ibkeCLpvh1SD`w6|IGyZ19w)Ql- z{lJJjsTNN8j%fiz`;5gx)CSF>4dgF%%7r2;gpHBa&i?=aZ<^XAoSHRGaJ?J&y|J64 za$nt+8wZ=np4}ag5XV5T(iCF0n8toK|DBU_-B61Inuec3CIn0nq2l5)2Aw|^Rh(xN zkUg!KlGV~SZ7LD3&mrt>#l<)kF)+~`f* ziuUa^4FaBf$)BPd*e zy1awCkpVO2g9YW(AMVrYMf51uz_@#^AZ3ev)VOchule}UY?`)r+QewIQp z5$X3YvO6@}k*|jV7I{Gj)htH>g6j6WdsRt%SH2Ty>=^M?DZ<&`w)=_7Q6me;{6s$f za+a4;qinu(Cc%Te(u3HU466LiW1E)CDbyuL(9r@v9faOg+wIEvLMH6UjB&FUa*}m2 zDIqYm`Id>3YJNLl-6`9;5gS(629CeVKDq7mm;E*30Vx6HweB>a(`zdv@(Yk*M@Fcv zHtjesujjs|`x*?xhbhig&QVADw$91tem@VBel@W>vMHOGkFq%A$U$xp53lxVTxYO2 zPGHiUGqTm}5Mm||!wu7B6ub@X%e7sV=nz`U)26@};Vj?5M3RjKrYwLs2SlQ=L(G(UecZb*b( zwP75d?=8!=5@5rCjl)0a?l}*MQC`&9)lcPd_VBl{+P?;+Kt1Sj-%!#TFi>_m268O0 zI)m0E=hOt5&~Dj%D#5(XgJ=GlN;wTZJa4=viG__D~ouF+4Oxciz z7Mu+mS|!x-ghd!;2kP->6rXsC+jRn0FX8e7>pT!kg0UtwV~x?=-rP zEc;V?L32Rth0%Y2M*`ktq@UF8Kb##w0kW%_U5_InurNTf2h79i?_Kv*lon|K`LuJT zW=DIBXWP06WRz-BA|e1Ynh8%m@4b@E+_>J!zbjTVuH0RRp&@0Mge^+?Ksj~KQp+Nv zsZDvI18Piar?DZ)z*ZB&Y3zvASP00x`;wd!OOkF`3za~!J4bShd*Ulo>8)SmeFstP>ELZ75zdemKgfEv~apIFZSPB?&dPByk$+M=1(?HoGws>An_=lUQaecy_v9{3kF5Vg?JDbNmB4?T$hmPo(BgOGhP*VZjRX4`q z^NL{qD+o?nCYl{oOWZaV#fX1inkbDtx!z=jcCP43k_l9q7W%x_^dih?Prl7LVx%o3 zFXo~~c@-7Pl#De#tVgs9ixavWl$hh>s_OB$+FdkZNjjnFMfd?A%MKv0Hg3XBan=!s z&P8O>{>^^1U>cXsJ5As8AHdvvp4M1$c5PoFiC8RLQeRL&>mXAEiLtEL&IRwogT5sd zV>5*$<>4q|wakVDH>E+9ku#Cv^vq|v*v$H~nuA#qIg6)k#i1$Ss#ne3($)KXQdq)H zry_ri(bLD0@8{VZTyA$ghdUwwBxgN%#=T@rrO)^>)_uD%oA>e2?l=!SZ3wL+DaERk zCUaH2ncKY)@o-;KvRuTiqJ~vZ-VX6p6$~wMBx`$)wY1c3y?CL5*7sq?GtbW zM(j!L1Ir06snj8>y+bQUr^>6Gc5H;@OW?*60v!{SsTmG+wRX#75ixnIMtKc zK+qk@pZ-pX8IqJ8Wyji_Pv274bO#I}gFa@hCAyxH?VY&t83m4d0tKZ0*d_!|s0}3* zvz$gDND#Q<`MuHA|ND6zH%z3}zo`xNL+HSA4Y+dm)U-7`^BFT7q*g-70q1sm{#m6~ zlIr*7x;N3bd(c0jRsu4qP*!UG`rS$iKC)*k53uM$2bYN{KAbnEk->!VAt^TfbV0$a zM_8vsrh5^#*{z`r^EaQh{d z^{;;ych}hQq7VoJB9%WhS!(I&WT!jEG+BHL$dUHeB+~?U9Hr5NZ>FV;+G5!lIqfJH zsLQi>fI>UyZg(TzY%cjL-+coF;Kv&ma0kuc1K$^Sxp*t3`Ums3Yg7IMgg9R~Sw&iC zk~O39>R_W-S5hQQn#fyDjqA$}wDUdiiARAs6H+m`Yfo-h{AC{^H?=NU6Zd_7AL8KW zUIl$;coNr!X$f2O*fv)zQYr&iOJ;RCo3W6aObA1;8~e#w@cL2?8P$#GafJLG>KSJ@ zR`YLJi&)kF+!6i*NSA+DcF)x!SAi)gxL%537EBvj;tB1V>2s|PB-$n2-h3X=ml#2U z{31^kJbDKk1Ht&T`S<5iT2^}fw$4PZTW|{uUD3|lDIaHAj}z*S8toEQdq40YbI=2) z5?yK5^~gxuCBf+WD|EW_nv#B>-nZJk{{a2xRF{vL#*W11k?S4sodKkz#{v8$-KF}! zX3Z@MTYD_Yf;hkBnQ+p3V5cr)J`he>p`CfLs-yo0;2_sd@$+lWmE?5J1}>`{)UUuG zVKrkY%2)i~;q}mF3;QPP4p*qQ=q3Yyt(Vl>Z`~}lW!7ai>YF)?tF`URLp%!g+G8_N z2IUS%bS+ooXsxfdcQs(yM+xOE`;CCHDG(To4NCq<5sfa`T02w5aB`${4Feu|d0kMA z(wpl_G2??Aks0MThxYRQ>Lf6<#W#dVt^r)cfXw}A2S3+x#1s@uClnXE{`f!M8@!}v z9IVBr#yZFR!@R;9JbC8(-jxOc&H13Y)!T{W>D9_jJ!06wqWBi<9G09M;jVT|y*ttL zmp|r#&J+99SiYAr555QVWO5c_INx}?p^F-aDH`s+qX5&q$LsN2*vdn4(b09*o2y#S z5<5Du<87p?RG(&tp0H(%p;i7NO0M`iN-kANe80Q^d`ANI;)j?-x`h@^vZiPLSVp*l z^6k?qA)I2r#fIsvCZIT*TXM5*)e|L!3<=+TTx*A*#hztl_i76SN6gxhi za8{hViCCFjij24i5{gP?bj>tE*bXP$YdvS@b~Xkc(P7k=91N|lPgTx0N1cONz{Wq5 z0)fCOD=o8p44K)jamS5>kgIJRewPg|A4%rTljV~;igWx{wZr3)FN~%}cU|Zi3o@UQ z0oKD|cU5Q7$p)oF&KQZCt+LD&HcgLs1e)xZR6^+94=PQU_f7s^43WN>hQawG`rzQX zy{BP$&b122TD$hc1&6Za%asfFyfKK_RC^rQ080bvCqh}OjIGh=4awVnBj_~up`Upv z?}p}l`tu)PfrHR?bEmTI5`IE&St-V(E^0BH_27XHJ9-U;)WY`bKN%3mAd&!25 zx+KMUoe~(-fH&tsRapLV}BOU&peimx{%H)$Asss$-@py-Mjw1Vq(Ly{so=S90G=^N`@k0KwoomToR948Y21*T58$;cD z4P;<}T0Jr|GS*jdf;TZm0R9uau9}NabSM$ac47Z4&$xA3*l%TGC+(?&oIE)aDymSh zeR>vA%SUNb-_$Xp^)&F(!T(mpEq0Nwh44`aiCS>`na8&OZYD5^hZ;&a#P72C%PrhP zV80U7%1>3rccUTatx_@V`Ysfk?hu%T%o8R#JfGaKo<7a|*k4dClpbA2QjAh-i4cFg zTb{=Hm(GQ?1sJHO>EQ79**FGlMDk58zQj zXLx<3$c>(KQ*l+q!O@`OkaI$K#WD|>yZ@@n_Ec6Do8&%m6)d!;`WF;QIo%^z0iX`S z4;X~!74)1G-KLm>su9Et&1F0V>MFB*BMBcWI6C=(1LoAT zkKR>{)2MZ{N(A;EO&)M({9A2+Zuc~q8s>0pemGzrwOr!(5fi<|x|23tTddc)aNB&qsqeHRNEr&L+y z5z&L(Us4*OCP@ZDE?&(JfN$Hjr(OWfs!=UA1C@RSxku++`H!C}HHOkr&scFprOV$L z@qV#IIeI&LvtYX!lX@54e5**YEcMPWXw=%&oep>}Z{XawNGv$WhM~9CI(pMJ+hslf zer2m}gA~f0bgHz>uykoSXJdpD(@TRJfU`eeEvBXmBXfljXWI&N{)8NBeArZ4mkag$ zZSx#vv2h2ue_e8MfbpxMqqj!Z(K-JZxPb#nh&zcGU3wxzW~>Emx~%Yp!eQDK0XF2L z!w1RztF=$-*kY3={sXY@Tuu40O*bF*vqqcwj5w`uj#gvO(-q8wJlwan{Wbl`i39#- z=k}FY{8n;nuI>KGj5p0MGWuJ^*bd%ptzQrFO6koklfBx_Bdbb!FGYFA?_sL3sxLl< zL;glvyq@@dxaJ}AJPC>1-a%Oc_ z@H_Ax)Q*%k+j%RH?!65Az15g2!JQKf0!ne(c=c2lvGBj>O6YI~r+w_uLyQF+<#Vf* zi`PU=tz4sS;Det~bGx?}AFH&_PwlUAJSAy|4G*Yav#iM&K?mS9gn#MvL1nZX(k04- z%ll_)mHC2wgbD-eWJ9fzwRhxlL(3{Y`q>=PdRn+A%2vX~;lhlL+|S?eqfbe7TWt6@ zzW!F?ItzO}ohXR9Yrl3Oxn37p*yg6F1e*;0Uuka@6jv8@Yj=VN3nX~s7Cg9nkVb<$ z1b24`9^BpC-Q9vWH15*4yF20i`<&bFoT^jx-|pHMy?57MbFQ(*e1^I49wxI47;Iu@ z6<6~2!j0M!cCzGaE$%O9;5ns8F zoXUd`&i&WiMZtCg3vwx&-+K@4O=gMG1BXaI9ft5M{r2+8NQ#5lt&jUxqK_qlU*@Z7 zXzJQ^!bn5ZCNW2>uCu^HX$gaSiHp_NsPdtZlf1)^EZuIq5xegsE1<+7w5U=NEy|m! zKjhOS&D|@?t%9*RNbAbG!QCH*{n#~WxLVp%*8lApmG02lCX!*xqs;TVUSoRg#!MCNv z?1hzBpiC0BJ;}mBN7=A}yy2yAVeuos_A-{7ywlc>e87=l;Bl!}@gj_-zPZKuu`bYw6_}PiVmGznP~741R$7=~4Y` zf;OMkwS<_(auhzzl&-qJB#7+NSMQJnmI2lj`+8SexGPzD#$KbGe)CkCIA<%9VzpWg z*cA$!B61)*bQ!=k9_3f9qVhN5lhrsOdaB#a}TwKvX>LC;9bIPoj+*~s1l9bsK z7IhNr<$CUQE5j9lA7okcOuz8BK`6c$%xx1)PT30s!}xXL{V4cv^qg1?R%vSyljdH~ zrboT-$r!v$le5|hb;V>WtN#FRl`tn#;aye5{K6zhHvfC_YXSEdRPjV3-Z4?ylqt)xdoN7E{Zc>zcb~Vj!2niR-lg64Zq-w6s^C`|K1tXu~ z?YKC)Voc(*+v`V%ycP&k&7dCVIWS_`8IOHJEV&wlDxvjj%;biRv%HR$p1~y3Ihm)a zT?DUJ;_kw=(3Kb3b$gusi|9A;mX@VXb(4Cpj0_t#0 z`Hg=sO6|oh*|l=xRj|UGs53hmDV?))l}XLs9669vDP-%9q2vIm;Pp+d+0r5e)KXWc z7R83x=JJ>xE0D{lxu7b9*dD(!c>^446aQjDJ_+`!si8Z!&DiMQ05L1ivdwEI@6^Y<;bXg`o1@E^6Bk@)&j{6Dyf|NFx4%~nF;CY1dkPutwc zKWHiHAHqiRX>sl%_B##iT2eyQT0ny3dgAa@p#Ue!R5`QqRD7lcL2dhtM29%-b7TYF znKUb0JVIGgBA@DZL7>a?<$Vc9Wu91>I(ainx?fZnGWn@Y5In;fI87EwQrw{Oqp;ll+ z%lE6LM-p`Iqz3b4wga*5NAjwc@qW0vbb7A3*<=RW`F)7;#X%-~Ncek*>o2jE-H7D2 zTN~X<2iBHk0D!t<-n!pM%WXM_og=m(IPe%0h`yYKT*aJG zS0fhhIA~p>4a9sVzgbl`4ZNL)B0UVT@(dbvafQsUs)?gU*T_8aOe5`!tL&Juy+vnI za5pBGrWg~w)cz%X9<}#2Nt8U#5%5U03(XdIK`4GncgY+Q{+F(R{6xCZJ~LSry%!@g z)2@CbI%G8^_02OSM{L#=i7OX<1LfNZzBT~^Gfz~+bf~PXcOq_;NsDr+e+`DtLy6JZ zso*MS^PY-mRu1mA6FV4gF>~IAQ>Rh^e*+clW;1jd8~=|XL-4+;SP`g92L%Sw(R*51 z8?%_?5r5y4(x)6gPS--`AJFZWBF0><2~wFM^yI0gF^k6|nc^J?NCfBjZSwWCamzlq z6T?Uy40}#@96P6pT}BM~o8KM{cwC{`( z6;fNeT;&&3qy$7Hoe5$hNrtypHzdBlmih(rSNB1osj<&0{_`=J*m`=8Y$<&z1N|Z! z)m@#QRXeTbeF1F_p7A<4*%WWvEIB64{dU zBI_v^;te+nA4_D|wqlNTdNn%RZF^!{T+WGjP)~o&>NLb(b?)u#o~XYjL{fK%W$e;t z%*!OeE(4D;^UkNo^t5$VydasIuVlk&$sk&(=H6$EpRLJXnDS zCA8G^G<59G4K^^gXm-1&f@8Q?rzU3 zY$m`OEBZP+#aP!H8o@R4cWWtr_?G(Z0rHXK5VIu2*soRXG zh7PH8g?luk+_~T;`Ew0V){!h4_LSpXnVeKpV1aC9c69;Kp&jMEv;<0Tx2A1;62FI9 zjDx_jReI)k9|605ZFYYD?ZvSpW5xl7p$jLVin6ZXmlSKrUTV9+Cx`DqsynDXNK2n4 z*P}6Qu#2}pC#Pw+)|pVc#qIF>UmmIF%yP~+5G_r<1S+wD2(EDhTq;Sa8htu&#ooKk z{_x?Gy9V2)p1>^jyXuKH zqYft~8Jif8E1smbJgmC#xkd#{$MBjMbXhV94R0K*9_$>rhwz91{(z0V%o0t~a;y$U z3uz+F`yX}`3k=5_e_$Obn4fP|&v4weC0{c8KgjxQMkbO(zQUNau)jvAy|Us*ar#;? zULNVjy;**R6iqrpZ~=$ow^uRGM$b8IIm@D>IV&g3mdrj3&w%;2seM(MXM8;svdiC9 z#P0V(W5b+9$l6W|Lkz253;`j-Ls?;+H}FZXLEeJn~j;Q{waE6wX>=9)n%nlgSo=(kxnlaj2n zb*|Y8^4k9ne}@f&bHo~rx@tJi+`PI6Vn3XNX-UM9thWCHVBOPqQq`jf?n48zF^pP9 zdK8s^ho(L_@3|#N#&;j^a-QbmxOicjrR;1zmEAe{sz*T2If?Cl1{&!e3ZS8-h&9tD#4xqR6C`F4@*!{LZJK<=Eq*PO%<#P>YYB3mcmK7g7 zZ-P~DC<;dGV4@z*FIFsVzs}o@<><-V*eqN%aCRYvhBTZXT6=mdLEt?j<}-0z=@@an z6?C)ao(~dY=I6AA&nd^h4t{xM>K_|Na$7OZ4$ZdR9D!eKfB>Qz>WoI)$(95(*;C*E zSphm;R&og-TSp>VZ_+_e5=TE7?0v-n~*7b$*h&D9$X>rNHocHZt9ag zn;7d1chq|YOISX@;Fj>gKl*M74_BgqcW3=&5~3%}=OLZ);i*cw2)6MxOu>rOxb>1* z_qx|gLKaDp_u2xntOYxDd7)uy^&PdoNr7_fFzS}nj%2Ym0!p&oV+HF^f}x5wXlWq| zf(VlBeYYwpS@k}6mH{?Sf$R;#WeAL7W&i64$!csfwh6bx1eGM+80hQ}!<#ucxa%{) zsrK{9AFHk{htMN?ERDkYGJIJ?R_j6WXS)*96hac|g3!1x>eNj|vyLFm5WM=W;CR~m zF99Kzkk_B9SsjTD>*M=tsgWNWnSXh^Z}XS${XV6qRg+f=bl;*Bp!9$-E*=JenuT@@ zm;7B{xK`IEb4x}oZHCZd2gi}*j2bqik|CWik6NO#Dbq!u1DgY%dQav`W+!-P0>YGC zD)gWiiFs3NFqZ34Qp}vb2!G=K?u25NUh>OwP-}>5+ zc_cM~ZC!*w5A9f;6<4r$)^T}lsL#1QhI!yzObQvi)9PFEwX_>6oqMY9++zXjpKwglRiCNOvT|75CvjcJVC zW>0|f0D__}kjZ{e&fkk^7Us}IsG&uD7cJ{<%g`BeEg8hQR#?#L4xTq6SYB1&AB-Z7 zd-h$h%Xcl`IC@A`v z>f)>}8~!d85Y1FkAOn^ZS{kE~L!{-CJZkc*9q?PiVqM#kuP(fbCS-9xz+7?)1^mWc z#^4qtF|4%z$A;jyb!;k2HYeK5@Ycz9!c)!q;labsoQiNt?+hI!Cr%848sC7)4*qqg zu~WGel(UcPJx*i!GIjfjy8E^>^F3|}PY`6TsiQ_Ka+@EBojCKMe91DK9o$%tdtq&% zq?iJ()xFytOfc7zastgwvbgWO>f(@`NWugEne^Qcj;SOOMB{V_m^9v+x&JhZ`y zKBogX4;KMxr($&z9r?7K1HD6XG)S|t2jn|aRxY-J_gdA->Y%Ymc1bzWfU7Nv(#|&#-7n+iI4fMy@;odVB$t$Fz~Rs`uA{awX;U6! zU*FJzL#9jSEx7@Pic$e5f@+UuNLY12M{bC+f&WLL%g2nF=7yB9Rqtq$eY4073tf*< z;INcnMHr``U}?n8`y$tlI0YTSPfw@)>{G&?lAQd$@h~jMwC9)`WN=KSSO$R+uJfaX z%YIHox0I@$+1U1?5XKS9xP7;|MVogz`7dOTNg+<^&U!oz$VYHWBl#8r%1Fs3pN4$K zH&=X9j046qD1Uq`d4}1yG%!8gnztba-jeL$vYEFyB?KVDGlv9Ke=>zhXgiVqr||oowDOH*sR)_B~w-J%kS;cV0b=9?gus z?yzx(^Q64$h7p?%l~Od5pY&A1e}{vY-#MzQZ?&r`{}P+%epWQ#%Z`3aaYl-c2x>GQ zYg|!OQADdBXj{+WjmvbYgHV#vO8LyWU4gzXaXB*+#0ApfP?b#yH9fzgtMjraB^}b% zSb_>KE~pi0g}YT6PxxhOU_boq7j2~z{a9I@c<76+RKNtQ=YIhA2GV^_u|j@?pCHg=^q=ZB zSxeq8Z&IXCF^a?StHNQ`L%n8+2)XgImB>4DoUcYXn}BeG^0??E$M2KN&Ew9K<5GAl zA@A`^IKeKF#89w*Fop>Hq0N$5YB*xn1nO6;AF{^wSXJuL1aOc+1Gw-=XHLn<-?O$T z&^Q0O`n$WSA%*h#h7_)7Kj!Mcbu&>#M&5<}J%++d4^3312HY zE0aRL-9d~Wl)MjLGAimiTkGcR%ry|N-;moLrE(wlwGvpF5kIE~3s{SEO`~T>2vpt-U#pNXXWZ z_c%qZorXm@!m&sFQuqrvO#3In+l9-x{K=(z5o46p3>%2Y6^%Z5=eY<7LC@yUw)gaAjd z3AlgJ;4km1JSCH`MXhqm%DKAg>OG(=cyk*W@#8pzXeoJ4_#gh_b-lmR?yCx9kYx5* zqANSm;49vRLfg%aA!98~(TE%ToC~h47gdwjA{Zq0lt;bcZ`QJnepe@t8ogUxY(r)Q zNmC!M;i95K_?_`jd>Br>?2R~>xOhaG9$R1 zmc&hD?u5qp9c02SQ&BKqn zG5$L{8<&dCjkScot(lSv*;>0w5vyl1G%t5E$P-ESo!fyi0?&(4DG#X~)nIfA+@II} zh$yES8&4_9>H<8Ex^Nv>@pluZX?lYEww3Z-cpVWVOxexjN>ccSA;@`Xb2MA1I{dSL z-OYnXVJS6QZL>8@Vb_BmNouRAPI}9_I`sTN#~MUS`gjWGJ0CShMq;1-=QO2n zB<7<6o@W#3VkD#f&1p9&d|r}Q9O)8p1*ry6vr})!t7V^5G*XVMRa>$&WcDJ5^1!!^ z@(u$IbuiXt>NxOt0zjBvwOczRmir12`yU+duc=KbXaFc4!cIHa$2rc-zx|8$M z*TD&Ym^#!7YHU2AwnOOh@@XevVaFP}f?-S@G$6{COL6sG!QBeJG6tW(Qpc@WhYvH( zRnd-)1NNxcBYlLXu><3adl^hnFWpe36jb-UNJ`UpDolD`0vC+Q6q6L$wSr<0Eo#vC zFPEo9#tENQhUAD<;BdA7xCk7C(MQNzg?+xGdcwvS%~-(fwT%=~VH75U;@KH{{^_~d zhh`S9b?#gYgY>X%Cq7n=B=ZfybaTU+F#iwV1X(I7b{TD?{cuaQi;vK-QNt+CI@Egb z-@)|AHE`{9HAlAmb)-E)YVhy3v^=yem+0U1(a*L^_(i#}!k)H^C_c2QI2oQYz_1cL zg1jYEiS$0z<&ImZ+iof$g2uX4r+3wkl)Kw)PbAy<>QQZLD9Zp48~(HHq-elC|1P9f zn)kqCoux6S=k8e41|;D!LVSg>(O6sg&JNCUG6m<^xqsL9LyguA&LE!gQFpgP>IbT7 zi0y+qAp!E;SsHL^iJr>`O|!c?sNqxzoUaPxJHAD=si~MO*|Lr9GnRIY#ch_@2O<>u zXkUH$b~Y)6u&!%{>tHk!Q$M#ypZjrcG;zn_w=g2$XCGd*$>Kh;1Ypcn0d=QCx1Eye zBb(n#xi2hP>~2P(LTz420$Jj#lGy)n<$R?Mp<4>kOpv&7h1cM9U4Dw170mhvRfZd} zrB9U<(#Z~8?69J5r!Wbe{3P#a7rEng5sIET_TYqIfkFGH=g4^ibU+`8ZvS)ETrCN# zLa^mW!Bv~D`v5y)LMVE$4vTN=k9rK^x)MO)+VA&ZE8chm1_M9DI$(dO z-#4q5P(?p*aq@?SZE-93nt>bp<=0!RidGyHhc`kQ^I3tx4P#bQ^M!lc0=F^v?yxq# zVImQZ%Grv@$+?Z8eA|aXK15JOGJJdeM-W25lrpc66_$>h+q(potY9N)=TQ99qWnc+ zxh!&XhbQiLM5O?N5enbr+$^UuaZ&yZ6E<9d#G3?d&*gO)nY4_pX{bEzyykOoyJxBH zDuirqLnl(%ErCoy5QCR+?AK7bj)2ytz^(eKRci|CqucFQxx)}xpi@(7ZNHgju_>>* z$*9T!{nIvop^J{15dkai3jFXw&bqC5D0r z(ppSt%YIKBBh|CjTEY8*63W#`x`BW6iY*>o0Ni@}Wn$Lhn(y~0jp6sVU60PlepN>4cX^xv2Y;t2FIbEj>S*Y5#c7+$ zvdx|dV3X%|qnRpI@{$Ua$EM45puirGqXVZ8!A(+U0I~3wP472|*u;a)Z!0mTW9v&X z3=xW~*vL$5heVWno03A|!iZmpmo&)^n$P`)j&4QVJruFu=z@xma^01Zev5E~o8Ar|sHBRK}pha!k>?#cuy zYFoi6EKqAs0R=Q2r(4j+tQA8yrZ;>-HjbwtsmuZbqzz)%0S;d36ecTdEq!+88HJ3Bmma7Zj-oCZt;5b0&^tu}+O z5d3|VV0Gl})v>mqi0d**$TO#Xtl__ZK+G9Nn1DKPc)^#%lH-qI{rZuvoOA)NAWlvlBPA@hG|7m zEvAAY(t~f)!taZ(i?Ihj{@1Sv@{A&ojhXgbgmiZ*XJ}WvWPLvE9~MT>1$>Iq=z4vX zc&U{-w#PbI!R0KyA=NR>sE?qvk1d=HOtwrM`;=!(;n(y30lFz|8QmV0pqUvM3cIGGU;g7xh;cs=Y@sT4WFfyT^(-$+arhr0#b zxKdtJnVPK7{vB4t5W^y206UR%rG(U0uN}99f@VVDjh`Da?7Xs`m#CEXlhtstp<4hp5T`xh zmb19v#JX@@V28@wbX$wS$@udjXIJpWTKC&}P%Lx!BEUO)lK7xqgJ* zo;U-x65YOUYD+c!E66$C4MbU>RB#*=V2BYHwB}b{gs~#~8|k^(6vKOH?{G2+KA4E( z5%FEsW@JT=JTAtEL|B^O;CRLtx?@gp3?^gMcP9}zt8450^<8}si;M2B?WG-n?CRMC zxLU(Zb|a41I+nO^bwFDf!mt)^NpXWCWVtBRu^vwl}rK$HWnx zjnnyftdpu-#?5k$z^p{_BS9g86M7;!W5d6vZN!@`(2##C*P{xtlxcO3@7TxeCjcm; zL!?DZG}I>v;ECC_nOe%XIOEMiQ|e@Yv6rPS-FDqVmG!%OURdR6F()xk(b09|WU=HM z**5OOP*RCIG?n`fW7hKcry&jZ9=bZD$qj(PnBQ2=@uCGB!m4fNGY zyopkU`3tv1h1av2drzftvG4mv384I+TV#HQfeD10b22d zXWv+KSO^Ov?pVr8aXk~!A@RjqZ0_+@MaF>h^qu8>wxQ=yWU+;O3^x*Yi8Y_ zXF1vyqBwCH7P8I6nYueRLe~f8H}^R{XM4F-1no7)Q@3IabTEpNw2jbvUF!8V7~)o! zP?qlvBC0=;sHu9@1k+755@}&_DM;<`s@)?NsN=c)2%KhS+_LT2*1$6v(INSjn4^6$ zAw#SK>W&Xys1WB}27fje`dz^S@ZNZ#%QwAv(sYcfyM?RG{bOPp}hxp6u$wa;rdVum;hx6;LMcs%0z7 zM0rl;77C%XLG=*f{X3Fvanjm%R$ZvB@^xY(%1+i{_ok{oVtFO%ONbiJqksQzY@C?0 zdNB@~qtblhi);>`pVyyih=m?pF>#7+==PM^MBf-B2c#C%_bQj=>Ko!f_?2#zyBrsn zX!7IcUGzs8*WbX)`v=9=E8(Sqyn9{E5p-X_Ut>G?u*%iII_dA$(Yn|e4q-?KmPlud zT@wRPo-p7?)1QDRvqqM?wMxWS2LjMvSDPSEi&lkRa5EDg6hQl0f=c;Ew_mq9Y>k@lE_!>uYXm zK^Ci_y@|<{{kqs(BiFPTb93fhxZjoGuW?@{urJ5xIlQwNhb^3cMXW!v2F!@FudJjKpMP~Gg86-IJj0z00ML3blxj9+nb1x3r<@Y=dA z;AmpY-zGS}ijliT2~3()WphjBpgym@Xwr5hP?86%eM9DV#F`?}ZLO&lJzE&A+PVG7 zoKk)8scRRE*^wLmLbxdyIqQ?c;QA1Z((cH*WyUP7&>)^p!rAj8`qX z#CvaqGr9@x2LYT;a3t6;V&iKF{vuDDL&dkhaMP+s1v#n(5#RD*F-qUW{?FjXZv}rS z{?{Ef>i+>Gx^wRO>-_#$2Rky_Yr6FAL(7Lv|R>9*>+6aAm&o}rRJ;ef8x`Rd;obi~P zh9KyXmnl>(K}&>QF}vr8t|+ME6Fc^=_>1<$x(_?Q>LE0d<;bg zxd?&wKzUxo#p+Px^j2c_Tco4plt3hC-SWGyRofcF3M2FE0a@`J54f7!hgJg`VCdo% zuw{J0_qzh@!m|AmIQHl;h33fuJW?87vt6udo364??5Pdph1pA22bUreerEUwo|C zer}ao5B6148I5l*DGjWEl6i`m7~2U=b7OJJ(L;hOd2ky+mUOS}JEY>hyXr#)P)lxS z4%~RF&)=7Gutg3pG7d^xI_FA1S`>wdg5zWJPgPpxYtD(ac{c%v@Q0i(NjM6YXQr5j zq@4!u;(|~$n+ku@c{4l%w;T!k36eyUe+%z%R{}W#*SX`Q{gn#^)0uLLX7MRopHEO2 zb*7(<8adI8F4Zk|GZB71eMP^q#8sE1A5#ncE{X1xXad$8poGq%L+l+>F-XdC+5;xh zEx2*Jt5lCFZO%)&vqV2$ON)|WBeAF`^qSwR9-1A#ClK9mwo}WYxuKAJ*pk+Y3XwvS zx1=m6RcHu#KM)mMU3(JUVaTC`jB7}l8l|Y;NZRMOw;FaB!~bT;Ak@z@!Fv6=9V<@o zwbGHC-U)d*NK4Iyxq>*0&40YJ1rgdePn%c%Im_w$&aQKANTeI{q#*R-N)cPf@pILo z^A(BKYJ)u!b$z=9ExzGCIF?m0+GulSk=*L~`_C>%A&gDp^5Dg$0wm93N?5ARKv-@# zN3)>*Z16)4+d)*r$A2&wECMjP%MMV5LB?R9SYJ19Gw3^O)HN!^;t~^B+rDt;$wWv! zr|O6;BHAx{JK>xcCkh{QWG&h#uSI}{5&riQj-xz=&mB$OL*UEOs_SCbQx4nL*9PVj zU&M?#rfR#w1nbKDuQ2kERF|LC!$1?eNcPZbWEgUvI>Bmn&m6}Z7-8Yh0gW46JAc%R zr*h+x;un7UoNYb<)Yz@cos#g25j+n+5V%~vw$G9vNG6y08hA}^d8bb$;Jw|q|aG+gBNhZXIUW$ zTI|z~3}HcX-5sIbf$Qt+Q4uG1=v2o>bWuh=>Zb$N*q@<*=rsr|jd=B;0zFB4?v|t#r_yu#sS@WR7YO#YKvDK&f_)#0Z zIOZd%ckcMCHn$md@K(nHGoe;~U&L4AJ_2IU>&R72Mg;xQu8nQ~bSO_76jqqZ%l>zX zD&}O+VsSaH-iedEK*W!V4&xCD#ebf2(Ev_wiEebDM=#;K2&(g2>Pi8@__5~BofyT6 zBXPpVy8HX%Eh!1I*6vhngTK}1@+a4@4l`cLKLjAfFE7!$uR!`->DPvAIm@rhvS6B_ zA|+($6!tE{AbMZy2OZ_vjJCw1Q;|q*F0KbS=qTNINXcV- zti?6YLfr_}8gqu)vpd1Qg#yAG3q-{1YJn^ACHe4$p{C4IC?jH$ch?CC0P13VQXil+ zsqsM)T8@|&w`Z3`Z^vXPXkH)AS}?gk){FaMbX&;alRgh8szd@W$7zTZ5?MYztZUnW zsU{CnUnFRR%T>aBBo|y`Dp&b8Sm8w@KEy36c#g2KgBNNOif07k#*qZF07zj1Q?+V{ zEuSCOaLH3Lg2y?~@sNOeYtADW->N4R70wa!otT+zG#m01Kd7{Tj(n{6G|Vq*YQj32 zQ+N_Lu+4;60w`~)H5n{-O4)e)UCBN>U+p9$Ph>D-42nYb0}HU;e96EM@J4mGAV5V3VWQ(6Ae z8PA%hw~U1KK&QER2IQUuEfazy;T3Wfmv+}9(K%+wfalA*+D08WBJt)Q7S&>U%m@1* zmZ50y&J>3GvI_2cYD2{NLA$Y6r@N(7ew)A)BS|(_HR!a2`6EqC>G@kRMbR%L0wL4` zLQJy@!F;opFoV&H-L`Rt)4?@!NV40kC1Lj4^opWAXA>>;g;TXZQXIc|vRfYDJ0hT< z(X`}zgDQP!(9@Ha($5Ua8i@cKB3XuLCKVTtx16XV+I^lJDQEosR_WG1DJV2dq&dn! zxEpp0Gm)Gq$g@l6g46nCC6Z*J65%kJQytdG{XeyIu-pD;(OIi7ti=8KD*3#v9&9N7o6sAe{v}>mNq}8`EUn90AuZ1;`mW{AnLagNG z=6@jly0t@6WD>+_+x5I`9r4;)Qf;5pW4bR2^uRZzg6A<36!ub(MW@xu0iCn>q^~!w zHv|gLd7a*4^h>cj{xHt|E&>V{6GCvC0&Bpzt{ewLA!a`$1uzbFL`Vfic!r^^<#Vfe zy1fmKTOSOs_~;K1c9tDm-}eR{?3L?Pys<*m{KG#)Hz;5@M>Gg6fGDo?SBD&WGAJ>7#$i!IxG zP%0k`ky4&Y zF9vbx0edUucGfN}8&ntP+1tM%mFclTR?;=UH7A3X0q6Qwgt~kY;Z4>cE&*mdASs0+ z-@$w`c|adggoibu|9^lJ{Yu}d$_UAx9OY9h5)jFld_}++Q?kF`AVcrjMlCEaG50@! z^7}neHk@*E%NK~9hwqh2LhomWzG^Gd_wu86*57)va+(;S@lOj+EoBtvhQQkUp94Qc zCpN;ZJudRvBTSm($L)X^A4U5nT&%4f%Mns`G?`TE?GUNr@Ae^AMMTYn`Tqej3io9c zs|*yj*LTf;;iA$SGmDGMX#>)m5wP?zXJ&(?KCS$q9 zp6h1v6M{mvbCDEU=+6)sL#!VMjQ_0S_fRl7{U5-8HZSZyfCgY{DO>JAe|X)LRu__G8FaaURgQBx zn6p`t4KEjYe>c`&8GS^5o!{y8KB_p`5@^EF71SL*kh;oRtnNl=KyM5L1yiz_3tLkz zHn^#f;1e{Cvm$&2QKe(vSI0q)K6ImocBL#+ltj73KFpB17tX5eyO~jURv4^s?>CR+ z^3y>J`E1jvXNmrI}k@<|Z<9!qWwL(A-4+MN%z&AFI+L?|0P%J91hrpoDUH6i`?IW`LE@$7y18$1(r zlFXH%hI!lvostYy<+6Z}6^FU$Q`quVZW+Ds|!A~v9qO%n+QuR2o9d(lglRJ8%{BYWS4A{i^4`RN= zo@=Y?CC%Do)wN#Uu!jG2z)S-#FRtVKcA1g7X;wcbIhRq`1kIf@XB=!y;vX2saEc(~ z4{sRIJ4MiM0cJ#DGeEBY4;sY(v0(k*Ws6jAR9@87rhv%T{X(S3-9tGgT{7aIbu8ZO zU)V-Vd74s%7){DaF>>i8Pa9q$+dOK2xx)cX<;<^kCS048rwRfN_S-9U*jE`pJ^!0% zsV(+jqE7heAXL2U8{DtiI^< zId!oQ04w)S@O?STSE4Zn2kZ(?royST`hF{_`cC5m_1B~qPn^6gQZ! z*GxPPyUhRY(tr3+-?nd{*l{))?-tXbRS^oL`Y|4KRP;&mu%YYA@QdkG!|?ZN0tA0z znU@!pSRXlu5K*%@KRb<6@7D@=M?N`a^Q9+U1`9&z{{XLlX6~-@%1g~$9;FvtkvviQ z$VHHrh`DOcJNaE7In7A##V}8$p>EVQ1nbT4m!v?&v$ZV0g9?71%74C_rb7L(FFs53 z{}LPIC*EM}I5Oe*3YEqVuX;ACF6*F%k$$NWXFdG0h4nwzYf^^jP*b<$m^J5}Noh{w z2fO1w^nz9UDeMnFpVrGz@M9A-dkU`*(P58Et?qHt`pWySH^W zhk3CYzi24YnqBUhOoXW^1xC7v2n)H&_znDMYYDh$W+-l>WiMzmg%e2HB@388$PVeX zkVbYyZ+-R4q}~L{%{j2_wC@%ibG<@OKlxyZ{F8THjXtmLeydx>OW`DAz~)|??TMv* zw;O4Qb?*+2>J7F% zSt|gkc-oRYz6IAl-GD{YRVO~ph8gyTsnGmn7U!Ba=Be}sl|X0`kzW`P;7;|Xnr~Lk9H;E2I2(|6*Pz};#bwXf_+$!+zYhhss zt**dk0<7IXyDRu^$7zuuK2D3Jbl9_CTM}F<1N}baQvm^AVb=$pNU@~X=IOZU$oPjO zz`4@i>$Z~k>R)c_01yBG01<#sm55jr*Ez^<2XNoK5ZZ`D?pu^Rcr1K%DTB0nkE+Fg zNcGB_+DCE-z=58}G%sswt}ctOYwHZb`^^ysPmN607Gw?X0B9c65dG~w!oIbglmGh% M_y42q{BPy|10gqObpQYW literal 0 HcmV?d00001 diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-gaussian0.040000-0.000000.jpg b/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-gaussian0.040000-0.000000.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cde52f1122656974259b90793a511aa67c278c4b GIT binary patch literal 48609 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 z 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 )$4h=I2S_u$GLJ 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

~tCZ)>5QIRj&xFBV%F}wu^^%iBEo&@yF zc$So1#qM{6{$TBUCQOVh#Pi61lg{dco%4V}4Ot73AAEvuv{dw}MOi6&CLILJ_Qj3K zTX@m^^5%bM>i^x3Use=d|MY&I=)R8_^b@#dDL=m^l9{kA`t9!=7Pf?kNi1ys*URPQ zk!juI-^KAj#qf6y`!j(70Mrse*dj4mU^i+qOeNa0^YW*A0CjUe^a zX?S$WDk?v3Hg{sNmq~U-_+VM>?XX!_mmkye(5uDEC}^7iLUfXCCBkzM+&%7Q_>A_6 zM3}LQ8dt*s&kB=Cy7e&ZJo5b#>`AhmK<=JNGqE;gr}X=M{`292aUpG$wg`0d<~K?7e#E;NU(=EAL0;Gs7WC z#mJ7#Xxq7b^GPmts54)Bnm?aAWsTT+*eSgu1U$pC1{v~~2Y+P3*rHwiK@}c*3cY$&c=cQ-Yd60QP5Dd({$?=Cp?&uVrRlUU8$E z!;WQ4u6}Jpn_(-tO)_rVF z#^$fc1cf3hC0ly8r5N~#=z+CyQp)VMaWA~0Fn-mi?nd)G>rU~nX)z840;kwyBY0@R zb#eF1p*PS~Jnq!B{6SCfJ0CwQcgCHV4gkyu(VVpqyL%N7Ly6(2_y0tvApd`t@c;c^ zH+p@l|5Oq9_;Ze(rCrZd{5)=r2N~9jSCqpaYD~vwos5^qM6I>$zq#gYy1=@ds`fMm zr$pPHY(#sG`Z3vr$2TT_&4*6qoF_&pMO=?F>FWZvafuC)b zQtylFtDd-^F}SoK%}p8K3ZTfG@$mZvXR+}wDJ8N_tr}vyIoCGgy9Y@nuQ2Ya2#+5LI4mA21A(~7P+<5p2W&op*WQtv}KuHbgBCOKfOWcsvP4Ua{-N4)p~oT zkV1oiT`v;&d316eh_Q4qD>T>4x&q_889hQa5%tt4SoRg6l$fpJT~^FAqY{i>Q-&2i z#_nVQMB!0w^8F9tR2Mk~8trH{E-4phO;-tj3|sW?`(WB)U^kjCz)%i8^S2qEM{V(I~CzDbvGW|2|y z+u7EYg>ohUdgsOF>66jX4;=4H{%ln+v>dj$mo#Mlre<_SJjDXzmv@za^4N3%wysi$ zzi+DL>Y4rihjA22<)T4A_hADem$1`$E9*e%jA~n3tV_QzT6z{HT{fg^($ElV_5N?Y zZ25fffvbsfI}A@Ry+nhr)5z=b(r?bD*TeEJPZK&z#A*X7om0Oy0-?%mSnp?0xr7sj zmka_HCMMe2yuCFub$)k1OzwRw(V%Lv>VYDfVy=1D)3vCx$lJzuTiUkB(WN*lkTzC1 zMSp6=7O3|@&;2~z>&SXi`8Un}6mQ`2xvXdDm|=naGbQSyFvZ4jb%*%0DqEc2F8>&( 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 z≧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 bQ)2ohE#`kJOaC??{JTB<=Tav27xBLU4;%j; literal 0 HcmV?d00001 diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.030000-0.000000.jpg b/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.030000-0.000000.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e19a87475ab6c387e9632b005d94492617a22a3e GIT binary patch literal 51750 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 literal 0 HcmV?d00001 diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.030000-0.002000.jpg b/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.030000-0.002000.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a54258caef14f705cf10977646e8bb94034d32ca GIT binary patch literal 75293 zcmb@tWmH{3ur7FTw*=>4K@v2aOdFe?(Q7i-5+;;%w6lg zcW36UdA)!2s@`3_x@*_2`ueN4g|`g=wv2?d1ONsG1|aqR0N$1Xq5xz>L?lE6WF#ac z6cl7sbX*K{G&FRg44lLY%=z&-I_=(jJK?K(!d-H7xfc`Fn#eu^C2mziRvHb!5 zlmJR(VPp|(*#FvT+~X)4*#ZEiRb_vCX9|Yhu$Er%K1`4Fs1fMxPuVEA2}`$I`l*V< z*VT`IwY<}`c;-3T`VRznDF=s2bu#i=wX+Yvd|1+x#N+XCR6M|{5$s@C}Mfg=J0+*n@tqiRkwe9ye_ z)Mjb(6qf!x&CJ7Z(zM~=@MIlRoD3Q9aq5B#bQFK43t7*YBinxj`?=B8*KVg|$@McD zW(5Ll#+w+K8+Axpp+P>)y?3gUl8K=>c7O0d$9)8ii)93#V`+*9;(%26#@dquXw0$X zX9Q87ac-r1si1#rFyP9C54;4!T95jvJs74HSYqlrJI~rRV)LL$q_m*lXx@Wx%JO}3 zYhd)Eh*+fHVEellE1gI=)yk3DujM+ecl#N zV!xdlRr^kAvc9Z7(YcQ9yaAp_pXiH9HhSoYloXCfoI9aYTu9)?Ft7qQts#Zh%+p)! zA0WvIBw1uOWF+y+$I6rL9`Mv(SWi*{RP~Ch*otKmH6rO&ZzkqmC>wO=h0U}+>5BdS5s{OY=WyWq^RZfq*fs$bHt&?f4UC6Wf^ON}bHXp~SkXAjAkP8qK*uqnA1l zILAMnu)|cqlA%eN?&8^3UD%m6CFi}8gW)O-H3IJ1Ml-wbHVI}uw(8F;u1**I586nR zYK-nEI~5=M_B|#xSC<|l=k!Tx^=cAN3%UVT-!Bxz2QSINX+n_|7e4)$505t&{lx%3 zXG3dSbLIx{1E|VVB)8{qDZ&+21ZjZ2H7C0d>Mjk{S8frIegpK}y#baBXLS3xl&HL9 zN~J~?LG{n&ZF&s9;!VHC^jz^VK74zuZ^71cDB_Zx6E}Ee$U5_R0|>nV^v^0|x0rkA z(SO8!r=%6x{yA^oz}D$;N!o84*6G4khgA>?WWY71GPWN04BI`p>XWuN>V;sz_{y&+ z6x5F8-O75Ty{}Je*5qV=m(!PP|i*jTRJB=D&x_{>gF5N z!=rgg-$WnWEvvsVum5=9bh|%7)>94Hs66RA>=3Q*T>Q=u3KS(F-r9Z zIDM}j!&)2nbMwJPFl!xrtir0hs2EZ(W{0hu1&t3U?}vB)P?Hb#S8Xx}10ZdGJ07eP z(SO6!Y)23D)s3;VEo0bSDJR4x%TlG+%XbIE=gCIb6dxA;U0u~L-Tbn-H);M)^Z&5n z$_I=MN7`FS({^eYZD+hjwE2yv2)pvQHnKnW+a0|eR+*N$ms|hs-7M&UY25xHx!&5Ddg}6K|t*a-+;}|26-znHa*S-OKd65W>&$nwJK$GV7dfrP`;23-doGRt7R^lrg{G&;%)pNszG@U^C*{WK1c82gn`IyK%{plUm zziq>R$_t|XDPh!vTbL`Gv>7`V{OqVNcZ0C~rfgk|YUp)I7*@7_a!{AKHy2=|)MPjU z_}&1n`ewBp05OZL$?@5}LK=aG&p3ra-JY#deLW8D^q-ssSU?u(w$Al-`;|0*#}*Lk z@B}t(LoD|43#fS(*97qoAwoNct6rZ2FE+O4qhgXU$tk?84u*yfLzjS@{IjrU z#Pul|7Pb_fu~)c!Qm;6$oW%nAZQ)`;Z57uo?NFWjYtcGF``OwX_!}$9!~A!+S8WjLi=5%~&Jj-f+;n2|&u=P9NdX5kR^?GFX+)iYkq4eg}1f;>A z@?BcBi?mP?*-;C^cq}{X-k5D;F=a|;2yXIGTMcJ&ZE?!TFxri}XZGLrt*+sPNw=oK zpKU*vzo6dM;fCAO1omH+kCTH6lXkW)91@*T8!u491%-$2qDfP?8)6hf>441q0;DAM5{9$i zsMUT&Ff(~&w!8MPMbz*nVchOiKBlcJyr+KP_C9h+f#i*L(9Rguwm1+>ODvL@nG!%T z5*&L!yJ$YOrUhXDr$^bSBf|CiCutbR;xFHiHnC>efKgdn(zTS>Aao+os*)G!-rZJE z7vF@vbd=u~9=|0q+WTYCQ_Oz3Z_Gv8g>Okc2-?`-$d!V0H}w=c4}VvNv#cSfF-3 zTIz0>Z?W)3Uhgq+uy}#nTu@zB<`RMe7b-%(C`TP~nbB=fp~v)CkX;m+-C$f-ckHSt zGmM{&{Qdv`PvWB?sM>GG+GWI<4+f$NdD*|X*AygqnVOEBCpJkeY?*JusQC_be*^e{ zYY!l*Pdd)%1cy1g+a+s+JQ!1TpoDRIrpm1xcGU8lTnYAsk)3DehcB(nj_u!&t?PNi zz;)GWB8~|$Zd?RoR$N5PFEid+}rVQ?4(3ASa# zw;bf|vG)dO+O_<-n%sL~<|i#mXE@#JHq?3O^uUnL(x!MlJ1~go&+Wu8{j;^Yq+5GP z`}{D}FxLdBuXDKK4%}DLY0k%mY|Ef?qBhrTK_~C$vvO5=jbOrAd>YO3N;pVhjBw0@ z_Ij}Q$S~7hQQysHX&7m~B1<*_T^BV5H!#iVBLz=xxp|OFJdZ? zc%d*-I;~S5Rl(qwkX|EPGYcI~t7Si#BFv(MZpVK<`p8$Nxf4sfnBA7`C{w)9&v)Jd z2m1F|!6C?_#T?EU?DJ**u!eq}NmcQ~zyn&UWXQZ4Ulmu#^h{IT-6*Z3!Isru6;oYX z0*C<$CkFMofx&Sk@k291(8z=K#QrHGvnzwQ$NHRrLsmre{?pX?W{I_}yUpDfHVLe@ zuagcJ)87k>?wp0H6)eOf(hM)r$ev@pxi0R@UheI;DkYHJ9jKyPrwVJoeb9+j*9kEf z0uN_OHf6M~S~03@Zvc*u13$4b`HmH|k(3Z@9u6?LU~`-DewcS5LmgJ}&i- zs<2V|7b60aOhuEKPqcYTHj6{EAvyN^{n%isG)pZFRvpu+bMC``2i7PPFpw{ew9Q5I zA(jiHBwO^h%&x4(UUc5s_4G{by@NveCMlQXIMdO>A)5{Lp_|Bm)|A=okrS$=5CYTV z_n!3M0Quj`cbXGc;)8^cdAdth{tsGf4J!wV%chgtwQQmyww#u z6+RJVl)Mgy{mX<0KPw&$7*`WH5Wq_MzHj!7;$dQ7q~(6DiE+m=)M0_icIwA2Mv3{< z?eDJcqut#2*3`%7bTbDRg2Zgizve1k|mr)1xC_gf^zR{1Z~x@bb?ksNxQ zzIgDC0~{u(lOGA4YAl*i`mQM3D|f=J+!t>duh{N!{Q2M>8}iM0 zzGio(5%aT#Ei}+bh{aavikHc)87G8r80y3V_-efvHLVDKr11GxfToc20A31@f*~VN z3(}fNmZuTReUS`r_Vu7D`@tPp6DbOU;Ae6gymJy$f-|l3wvN1C}4OQ>4^+T`B*bJre}pzpBm^b zO-tej*~;B8;RfkU2M&{8NH>MV7fnorA*V1clZX0SgT&7Y#K_b!dn`ZOeiRq?kkTyU zEv7Jrf`mpU@c7SqMiI_A*A!R{8u0e@p8!SMZC zM|mMU5@DsUzisQX)yCmJ%0a`z!vE*Wak{F09&JMT);BRdNM6h|-t((hGFCK!kl4@p ziGSe8mbJ)0iC&*=x#QdkD`)E&Mz_0xr>`hN)>YbpOGK1^)8XYv9OsTc6*N^>zfP;M z|2y{>B&XrZ&^?uel%@K{zCbAzkXIq)XVDO8Ng5{8r>5k7Nxj0umJAMoHvmqz0!olx z!Nx-A<7NYyMs^meO}V~ew7w5sU=H#yks-mi^`>~|8u2_wNKiB{xtMI|khTuLTuh-i zf^nio?&g(oj|Q)s{>A~hEphqJmz=Ur+Go12Xc4OB~z ze9u~2XKv3G1(e1=GqQv5pFeXo6~peP19_qrtqZQY{woObpC0S~LDDE?P8|)HiT%^n zcky|8u2&8)7%wIN81qOA-Zr-%IVa|XDr=VFsLi5eBm+}Ox@F^hz`(nq<8dxdt()8W zy&X-mzNdd#94PHL4NL^cu)}$}*dYi_8uX3+T8Ln&By)LbNQq%+8lX!yks8R7X1q@Kw_mJln05< z_6_hdFIP`5!?_wGjwZSDliYOhPzSkcc0HbP(bThiU^$qISMHuTgEUc?FJ)`a>88Dsta!M0J9jMI|Lki6h_P2YA z!Nyi8RAT;CM7+Lju4-XCcQ)Ju>m#lV?2z8Yut;0i*{>?MUP|+{d(I@973nHlbxybF znZihbE&O!Syb^pwkL#G+)0|7l@-hiPngRx*S$Dr+x}8_$(x(77XsT*-gI9_(1g&dN z@a=fW+jY@g`ArkLN~gq@pK&SB+mj*92M*DtJJdyO$F@oBMz?#9lq%tpfvQWz96jLLzjkU zx09tccV!MQ42cS<+{!5R(3x-iZI&d_+56dWG5(A64KN$MB$d{>3RUJVr9fVpG%V6j z)i#F`1qG~h4fdG-a^|tp)VF2bC4gu@vfa7|-TgBS{XEQ8YhrET;IIQw$$Dn#KQdqZ z@nB(?Fvjep+_HjcQj6mqc0iidMdUvf)PkfY#eC;&u#0BJ<34F-IJVA{^9I1Q)FaVh zrcxNmnTsfQT^kl}-^`~pE$GPyon)IB=I%PSIz=}y^b$lJ-h+B zgGB5QEaH;7eA-p>GUAK#Um56;fsVd{hF;ilX}umd>Qy@qGFL&op1m{qIinPjf0^$y zCcR{S%V=k9Pm5iRG#I1~6(6^e3s$923Sjil8qPR581J!)ol2V}VE|Fk=EfHb?@l9$ z6Ni(0Fro^IxeQq(q&#%@Fs{6ZvkJ{3phVangT(%=>SXhNE)|L@VsOlJf0O!XgZv>p{uTTL$Y z!l$LAz5}8A+wJb}z4XloVoY~byqUJIAv)&|X)qU5X0irpchSDU588tEEW{DKjsNw5jvp7WK3$uHmR{? zD#tOg(j$t>c}toVxk%s^&clvnctiavT?bYn=YIy#>R$xa*+8HHdIRO{(=e(X#@fzG z0%|~UY<2{(Td3CAXN?bPVk`OJ8`hydKeO7%T-9M3lgy*PTDAlPDxsOu9Q8^gg&A z9MM=^NKRazl6BL3tCdrrb*L$u{($y}fKHm&IA(+&b-MH4I76ZRWGdJA(^JV!*QCMX zZ{p{%Sj2kK*F0|m@BUf$->=;o-(9Ep71jqlnP1w5_y3g_RVlYIabT$Nua3-IRx1uc z(-J(n$>?}$-Mpj^jVU~bo(OfBfbnXV56)aB7t^m{M&vKjNDF%3j%g-aM75N=jkj+- znx~+{`%{zEZee75wWOD6x{gxKfPdr%e6N=^)wPNIFX);~q~Q+HZ-8+I5c$D=osOE! z8^FcwMbFfuQU>NVto@8WX*1cx`w98VU2Bne*e8s{hUvawT*xFKX7+57+!%i!j`AI5ZFqGPR3AF1~N| z#Ve@U3Wae1ARs*J;uB?d)4P|Ysx`_#eeKZKxz^gzc+A6i{GnS0W_4 z!xyuMvai!!6HNalkI8sginrmfw(5OHa&X=+jf~)ha{Y2%?man$cf6@_J@kmw#jgWQ zZO^YJSL}o}GKI_+X(j0S+WvJrx8mZZ;OcP#!PQxX;b*iZfIq%@A)}_W{ATpojNj|( z31v6q-)1J#s-mEJ-YKBi)nYY7KapVP5|b27TcgfsLP8g!=V=XFV3_97dYRkV-wr3C9uR!o!_bV zrbR!w*4@tibyPm3Kddb`-auze;M$cd%Eql64_~ah`6{ljgt_`czs_oGFWkBy@ts?) zz>e#VE4hG|%#G}hO<5XxuTf9#OucLiR?#Ju_|0$2;KPHQqJYKRgzOUm)Q{@_n;4K% z%og{`Rfo-Z4vght96GbL_XEkR!C|Ga_Qxcdt>u*yZ&T$ZS+v$S01elP#rS}XvNhp^ zY{owr9?3vRxpfV9ZG`Ce`B^>$eh2i;wod}vP2o5A=M7F3D#@U*SsCp=^8kcLy6XH2 zVmR+&nDWDYukD?h+q#Oc{jvq^Q*m`*m92I+Ey`2yfRVs+sz zZQKU2ui#RB1c&3qIW9kz2OE8cYvw*MY}u4e@) zPpb9&Zb39vqr!1VQ_tTeqU~B^;08j|nrbLkZTsO(@=)epm*5_{FqjgMv-|kF>`+Qv zfbSC!u`or~31^T^-HVMpX`NCF6F@V>S1!YQz(?RZ*Htp|p zxlof!$Q3_>)cNy(s_i5D+RT>5*Ia6f!JD9O{WQY+qS6hMW;3%O^(|}^s*nA~hGmTb zamm#ZjkXwF-U$gx67zj)mnaC`dIg5#da(&OgoR8)+Q^Mau{)PChZieWX##({jhH`N zvhHW6L>A&eHh2o=KKmMu=&nlIk@EMOv-Qg37^hDe*kCj=nwQWopU$bmy#ZD~@2kH7 zEVRY2F%f!R2+}dCWaJF=EbcG(4gFm;)5b3cKaxJerjX|MS**RkJ!tEILuvEl)(#W{&0@#WgM*lcS|)>!atjNwK;q>S(qDZrc(Iy_SvZdl7*qY*o8t%J(=(kOjx(Gv zoCJ~f;LW8Gk3Pe9R-t^6;|{m6rtroG-ABA;TF!0E)`C9b{V@L8*gj(&S?cO4T-0-&YPwhtBdwTNhvFOOf^=M4xG(JSm2!)?e^C$3aebSu zFuzZAeS+IBa4wt|A{Q1ZC(11fh!Z)#Ut^xfr0Q5s^wy+)ph}o* zX@$tT9Th$3ajblL8E9o!I2@Y&;XH8+}dehbEXj5Z5{_6qU~Vuas;#pdLIb+&_m#ry}6f67=N7 z*u%%!%DjR;)O;?Fb@-JYbBW}m7Pai$T9Xo!tz>}9WyNo|!->jXo~Ue+uoUxe19rS1 zs3mr9m848_g>Y(nA8%l2+F>w05Yp2a_IFU53<)+mq-P9;H-37@3Mu%JUS#duksdR{ zuNxvg$d{4pgbc@E(;uo`{4r*CPcMlnCwNs3EzhbLjLtmdY`C5?HNsOG&6pLim}7BZ zVqWE98$62_TS6pqfy`Fq6?2c+v=n(fyI*Y&m9@0mx4Jpo!22F_}uz3$MdDB-Uf5I>tuyjZuAxOjW4ZtibSfc-n|5ixXGOsN=Z!tF-4OTLcbC)N0 zh;JK|D<11qiFm4Rq@QbpAF60kUn+d$NzpamK;NhmK_Cs?cq|mE+y+lDe<8J^o{m*lH>*0;- zM%rGuVF@2}Ng^hwN0?hNY5VaQG4m~c&V0r$w?clVz0bScJ*?B_=O?5{mseqaqN_P;o^=2;PT!f^=@SB|eIA zg=cZ$?vmm$ePQ-RWEorSu=G-IS@@Vz%IdqO@Y1C;6W7J>kcLV1qvB&R$K_wQd?x2{iXw`y`hX8&3pdevF~?BQw!+d*0%t2EQWl+Y)cB^UGa&-$Q&yS;RZG9C%F;VC?C=oqBfLhzqNb&(gqYwVdNS z!RTD7?JX;4MCpLZxs#xZ5^AAiG}8Tzj}bx-JR$k_!;tESwR`b&->r$BS*M;Idea-= zn?ev0=Ltomp#WhFgFg})-f($gzEKi;i%5lYqKTy@29}5*7Cdvn{7%P6vDb(nbe>_`+}`e8PJivvR1KLX~VgCF0$C zxa@2_wh;J$Ae$fadbQ#?B*Qy~WmhCucjDG8MQ5>@OJPDQNy1SEVHgx@DFxG%F~>^k z0vf82TCZcK#nAGTcL9zhuOTg5=#hU@A~M*5LROmKBmRmnjNKZT_hh9)U%D|f3vVAP z6cKQpCXxRI#Po4m3B!EDh_3-KOTmF1MV9eg6?P&IR+7kd-~{>m$*=elPAn=s4dqInu$G7TTu~s zMEp5y_EZ~PF~9YWT4K;sp?K!b*lZ{8QC#$pS^>QHj0=cEx zUEd&CYAnqQ+5@S}tCc`IOpFuC&2Yb`WE8KECr0QD}Gb-*k(~`5>w^Xb1beID%l9Eyr>xt)NmHTD6RBEK#u1#l^*3DS`UO|Hyi;!_&THqj62A&F9Zd>PeP*MB9SL-$n z&HkWneo4lOc7EF(iT0dF8$2C`B13SL#8AhPfvxeh$$dLZR-)JJz6f5;|B3_npYZok z-)Aan3~GbEv+0`$1aJ$63lnwY$f+C17>jX0JQz+MvY$KX$Z;Wmq=Tc{1m_|pDO?h!N-V-_W5$g-X3?ALxKfBge1YWESe`<3(k+~W_gzdXl}L#4nv+Up&$B%Cv$%cxnCCilIZ zyMQcb;Yk1`qkueqQ{>OdEecE%aF&5A;oPsR+oM~lW%e`XKe}-?1g3#JX=e2=ZvK%Z zI>3|YTA?pgI<{G7+1u%l@ZglVNkP6ux8)mEvHk(as=UnaR)adS*v+YD%%2azY$=Ow zQpQ-672vo4uXM?0jiU#h;v4#VekLCAzkCRtilggd)09@)h=sF=fySfK`TbT2<^$`0 z=t3kvfoTsb2NkhWt18>Wg8nI=>CC#e+Ht7hIP1nM4J{M2fQE#d3ZHY*u7O9p`-uRf5Dn#5~av*PPe>E+P(5?6sqa@T2$HWzQPF zQgTLsr+`dB>bEmgA232ZMg#8{Xu(Be$7Pd#hna~SFAZ3QF27u3+vXa90@mO-5}^a( z$#;?jWs|=^r|;g*A6rlLJYTsKNm?|+u@inB_(uB`@unG<#|2Qf3UF@oCW z(SHTRsIDpGO*}TDJ*RX| zGFjzpNcOjCGk(%F9n;iD9<3sxAhBKE>~1zGS!{`r5^QVn&;s?NG-yTYa8X_wQ#h<& zTgI#c8NQp&`$rtrG#rSeDnq!gGVoO`pjK4D(2#o_23i1>_5AMic!zid;h{!47*v$>V@gb z)74HjE@{`VzaR4-zo*~Bn_zGr9Y((#ece||M_+fM%=o>*Eh%f<$ep428CgO z&dv(Xs;%!ZdAz*XqCnfCg5Lnoh~NW-}5BCb^pSAHY(|s*ONTqu#$VDAm^96 zU!FaLA4xc|hj{%81r2$?`a7AlC6Qxulf%Rbw3Zw@rhdC4AAxpC^SiA-6Jffg5f4)6 zoWO@z70!%?%wM(VR?ajs#OvLRAsNed9fzJn%j<-XHs<2Es%014zxWb_uHXnM!y8{s zGs%F?yf=EE#q#+ugs_WT#xO2Xq)TX>`+sjA<>X%)NSutSC*;D+tcS*XUv9yrPP<|Q z)^-Uw>}AJQ7cyen=&-tnM;VlYc}`Cc&dO8TO_8^7*yBnfcj+AlLz1L1jpA=zQ_=|M zStbgz17$+Q9BLuGcB1l#q&PPIagA;vBtBvZsFNJgDls1H7LK_b7E3I)`C2rYTe3P8 z6eTQ@4netT&?sT`eXCe?|^_sX3KcgWIY3*!BhPb>(Oh3I+iUvtIt78qY&Z9Hac{ zSLb7!PJ09Jww}#QmjgSjI~|65Il`=T-d<>V(QYIPym4~f43Xa)pWD(p7yLaniJz&l zSwb`a<006Hj{BT9ZFNclDMcs;l93gjHu~ev$sF!ZF7$wqd21PCa8ikxw{L%-CewA& zpvNM)@;71oBqslwwA}lR(^SI?cMt_Y)6wWkw$ObwQXc;?fA}gE^o##A%J0Z17eB&} z5o64UWr8uKFxK@~%x-@L0&LU3I^}t70w-7u6{c#EbBLLhZ-SNx*WsxQaJ7{)7t31c z#8hKu6GA{hocGI=?eFQ@=7j$%=O|@dvBm4F6*Zpjbtc4oQWW?jJdc1+120>`aYp9~ zjWN`9U#G|NWAqqQX3Dur=V}^_BvG&0&n)~|{Yo8nx(aE~pxQpeL@RxsWZC9chENgi zN;|HGpW1=gKaI*Wa;Ps>-AnBX1yXLi$$YiNFd->2hdxcR{AZDghMhH5?x(wXW!a9Q zbZ*5c_1-Uc25-+VQd6H2`{R7|YNscTF!(@NH#Qk->1|T+XY|G2v{RO7lIv5+$CWFG`} zyQGmfh3yxEBNi>)+pqa^bYP}mPdV0Wy z^JZwG;oK0%hhVy$_~%crU=4ItKQ@o*J;X?HvteP~spYy8eqdF0553`?sRJ;hNE>@Q zVnPwGJteCe%Q;I&_)$aGa`drb{7&;azpQT!C_XT~ga2GZwCmJxSAVIg8SpK#t_}yi zpqu-}dtF?KRC2kQChVOZ^)vj8^mnijs?MoR)eX5$+3v+_k8xY~60w_}{m`(;Mc3Y{ zkPyV+ry9|OKPBsbA7W|z=%!(>YH+9`}MbRv zugSebO3gkvBVcS8#)6Gwk(Zctx2FpZfaF!PwN8$+v$Cnx1|PE#Z%Gc_GSnozB>YwG z${O7dzgEX(*{t43Rk|uR;!}B)A@?ZPd^10+i%F;xE!e$L-(SCITsOIOmooiG{g9ua$?#L+YWBGdh7V+cTOP!Ogt3}O{6 zSFy#P73r}>zEW2ixW5MJ#SNQG(^JGr43k7rY=CKFl@SN8NEA*iu9es?vI5s+s)$ud z$}{4~2F5V$e|YizsnP#B5}?E(ZEt0}1%N8Y8DZG;h_XOkFeGzR) zyu@D*3JIeUwN|%i`lFR?m_%vBPn z!G{bW#wcT%`C*MoZ%T6Tf-X-6HU?)Ztu@LUz@e#u1-8?q`9s38sa;O-ta7`%-a(G) zw&@gNgcO-&l6u$bj$J_sFfA?4`D5LTS~p?76RYwLOz%9rd3EZBc;}>u*qD5qEM?B9)WE6U# z{?;7tCBnfGrKHwed`f%+D1;G<3IMY!cmhxCw|IPOFEoPG{$T$7%Q(*}^^_85K0aEQ zESFK(L->T0UJg}tNI6ji3O_;j253zG#S-ZCU1T|Lo6-LY$B%gaw0y3jhElF4Kf;gq zYvV9q$%en42k6B2Ws3zev!#xfJUSjT0ZyIgvrBcPPhL(X3h2aVhNkC?heL~3f))9Mid(BfQD5SK3!dqB4lb&dl1*M(@Ew&3+K-9a8q#l|eEMKWr3rd=x z>7b#ZlRjm;{+Wq{f8}A=1t0pe7f0L5=<=ohd-p^cp~e}9T$Hekzq7V>n+3j5s0i`v zcNl>2!oRc8zX%lRSnqa90yX{p_w<|;Vs+KK+U!uGxSzBB#18eDODpI}XE}5~Gf#c4 ze^tq5SW%TCUX^)ByZ*f_00vcu6o*j2*uOk34L*Dx_3QohVkdP))#RuquR1_ANL2%O zZubTVh+UibuJnszZEU-F2RS#E4*-ootkH}=Jzp=sk`=R%ewJz%+S zX6wr?P0yZ->0Q&cnu;M_xV^(bXAgnFaoxerKVJq>B^03E8m!Fr1**iE5_xv}qRr<% zda+Tm}RHsALHDQ=sVO-p~*q_4iX)w;5VtVDB`g4d8urjJk z_LAXnlUyW3(OPBrJ9sWC;ft%NsAVut(G&l{c4CO~FgElUz#vJ8(9%BiOK6h7^vg;$ zpEq9wfre<`NR{v$h=6Q^O zE~&AF^iSP887JhxnB&YdlG!6H-IaP3r1oz=*X0-$F{1nqgau}={Kti?AsF8!n&)PP zveuIotuV!hX%l7^F%G>lzN7;NP-Lfi*(9{AWD$C@bX-`UGJQH#@I`Kl)TN8Kg=Zi2euV*ct=)@5q;mu%86My#1|%ul~Vm1BaeNh>q4h} z5vO~o>@dbz!D1GrFF5H%7)d|SRa(*-A~$uVr8yRLf!tI$qtx=oG){&aE47vhtoMQ6 zhCKRFu#!SHX7|>W*kg&vsnWM0H2n6`SArd&OJD1GR&NVfwkleXiF zGh}`97#%%rB_q)i;Ew&W!-RREiOJ_ZJlvm~*jS8v5$ad3=WWn5^|BlPFnuVrITJe* zuI$=6Zmd6`EiHHs-?ViXg0!CqDzD|G@dG*|H77s5FkYmGJkX<(=Fj@{orf=||>8 zBZi*NDN2adh@@j6&T0A3I2doqTiolvh18yKtx})NF4W3Mfe}PHKbaki&e2)cofER2 z@&{HjqfQQ8KQxz})pueuKIo}1xvBXf;p3h0PPN4Tsu_KOgEbbD1L6wmt!9*I!7pPu z=s3=7&aHD>uRFg;thU6!U@l0$-oAQ>!&GHbu)2WPjZlv|a1_P^962z%F;jZlzC{Up z215LI(fIm;+sxz#6ii#%#Rtc`*$M@&krii0Zq0yr>3)4kOw&3czL{3bGt)##Hfs;q z(?_sF7YS{713YGula5`A=eGNRC;Vy_NUcEAkR1n%I3*}%wm?9_ZhAhEyS+hIFIV*U2Oik_($YW&hG@Y0Op*VPY+vjG&BgJsrp7ByN7Ezak;(`zCmX0 z9SwQ)gGlvztvj^{*x~ZJc79`Z&VCX=H~@>8zA0%j5gHfjD(2o0tee;tEA5yX$i^)&Rnxgd8Z#Y>r`@kk1f@`Bx(Xp79$ zTxDuXHL9}&i~rC3ZqwDYp|evYb$>=yN@`KRyz&;h{g5Io@ef5+`qhuh&e0rf3W$(0Xi_v% zVlBU$TLKT-+VIMPy(%6jy=rT!Xo+Lir-Eq}vVi>Vm|5`(evHt%nnqjZ>^%86B#%)L z){nG5(|vq{yAchG<@Pcm#HXABYAO=*d_5C(BUJLHI(&TZ3bic^Mk|l5syj=%_yp)^ ziRrdKhnY029m!3Jo&2?nksXEq@idE5^Rff~TKFx1nLKE!YBW?PFo|SMqHf)aL7oi@ zqD0a};&)q}qdmkaMRM|^F>B~tq^jI1VCg%sQv!pur;N)S=_QZvxM1GO40hg;o>OaK zjOtZSSXS@8;=GUd>iQ+NdoFEhir=L{wY1E)2Sos}WL(+Eu?e47#01__de*#G3E16r z;!Zlj^?hiMCs&yZCDo-j-qiRzNqmGckad)(X1Oruy(Gbe_uk-wD=W5G#jt6hd0;qj zL-T=A2MLa(gDOV<4=ImtrJ0jNfqE~MLCn%FS2d+Kn`LFC?LNCu^lJ#c9I;Hw%ERfv z-@3+xePR%12}ye_ZwNU_aC0c5dQVljAg78XBTHcLGc`>2(^CZ>e|2LpC_Pp4tLD-^ z_>&U{9J8U3#PEo$)I$~F0ilFziv;qabru)&Gvrodvc2nc)I;pF-%{(I<-^bqiAa~; z*EGzAowmxNoLoPwoWRIL79e&dO9T+00gfYDey;*I`Cr%m!u8(P;P&zfg^6lN^Efk! zTJM`)u(7!|;5gVcu9}5{8Ex|yGQ=_D1&lQTGk1#im5uxujb8JW#YI?sp9=7KsA%W! zBi>0*Y8gxT0}I6~+rK`12S#DdO054%5pY_i(P1z?Ed;dK3lXTEJ$MnCf6C;uKd)f! zPuvB`r*pu6l?3Ym0EIO-8XDFR_C+|E!BwaXipOg2JgIO@alD;PKa{+e+Ld6g3CgknxF?kr(h&|rWj zYrFhA;63ib`X7wFWm}w4x20P^2=4AASa2)c-62Tf?(Xg(XmEG;!W{~S!rdK$yAw3h z?CuYJo$h}3b$&p7dupw@<{0B1L4V#)nESsKff!6^6Qr>L-N-f40YGc|$S0%!NfxN! z2+5<17`8GCA8Nsd6$Y=Hajqpv^U7|aJ^xZ_C53dVHJ+FKzA2*>N*6i^C6n`;h+j;R z*FPso5+1*Utxn*g?PIEOcN;G&od5W82a;oRtL`_M$%;f0`wFgPip--E#pfLQwLiqE z&Kf{ZwM7j7)!_m5NsEdFoapR`m$xOv+g_2_73}iHI83B}6teEsQoipei_)-*g7rz@CF(| z(42{*;rM#x*6w{zws@Ls3vlh74`-ToOd``BCzT;AZye*q~RfQeWPY=3LG_8!f%j7N3 zuY)N;lga8QI1ImVMk!;)fRnM`U5~Yy#($(Oe#{tSze20=IA(jcW!H_jt4+LXC?Cw( z#F=%~JN}yanOr=>nW1HZ+D|6Ct}PFj+!`DD%F`T0P^=TT;Lb(YjLqpeJ2;D& zw|bqnorof9g?crcT;fXo!qMwv=wc$mNQxu37ST!pzMX_F(<~D#_NxEiIobK^fF1&$ z(D9O?nAI@Jm~mgm;yuVw1PUIWbJ_5j@#dFy{A@gu)ry+W6V+M2#jae!-#sg_n+ugf zcR#TSL3 z*Y8F}3Rr_+cH3fV53D2W@y{MZ5fU}aO|e;WKk?lRbuU(yh`r^rQR=?c#_4)lJ0dnv zNcIg@u^@&GF2Xk{>7!+`6a2giv{~U%h)tA?k*z#;JxJs3C|A;F!8f0ppvoDnBGo;2 zrmR!biicWw&~kqLSnc+W`Pdobtc)c_fuPC)$24wg%K7x;e@`$;pgs{ z)8g=0!KboI=XS;^<@vRoh4$I1m{(tf{l!E{^7&(-!A&^TqsB}<>IR`(=to1#bH2TE z>8S88BrcUpz*9ckvZR+hhf(Wp9%JVxEzcj};@Y2ocy>b}%glTc)60gwkZg#TcmK2F zD_-Rqe=)qoMn=qf2nJT$SqlFj?6Bmu-`b|_&TjGhLqAj81TpHi&vo?J{-ex-kXlx0 z-rjju$}%oTRnGXz%w)^G^m9Hf;UYhBp5N&Ka$lWH?MUScK>9(vrcFV_V^L$)0B^=W ze$sVeE@AlM*yq{-ae%b)XBMS4a>#I9@_fL{)RN&sg>I&8@9r9X*t0w@9^O?WACAZW z(F2F}6)kY6&)yqPZ{OV&p9H5bHXd}W{WPq{6+rM*}3tTtaR^Unz*?ML`3o3&UM(I`kVlrg5g#>yCfe%=gd7#8dPJMp931DJ^cdh z8lx=ZJHM3a_ISEv5rWh2LJx%xL zzeF;aNo57VL>EE=pNBu5hGw{zTLz-+Chvj8$7z2RstaN8ft7FK1JbRpAbr8I1_yVi zW%8b|gln2mZC?q#hnTPn#hO1PF0B(gb4l602|FnA-Y=h{rMR!$>z3LKK}o*L%5I~R z;^H7XE&q$kMM@y&+OgqeZny5jTQy=mzo+e!(D8IX_qe=wFltosj9Se1^?cqqNP0T)|U?E1ZP!d278UOTn9ZXUbZy(-Q>Kq*x*s&6s_w@Lh8ZuYo=dS!^W-JEml^$WmK@wkW-2cqQ?qyxR^hO)as$ zeT8yP)hjmS`)ZGz_ajFNhIT#aa_m8>osM6coBPZ9Fp#STA3$Gjph)#p{A&vUv zg9p$_9g3=-;^wNUDpABnxO4E#wby`2xbGyE$;uyEF3yU4Q^p+(5!1)kTL?=WTGUw5 z^M-#qJ|vT`v8HIl4Y}6ZmJ4CalCPQwI%fh{=XBG6+s(Aa(9NEap7;~CM z@B}iNppBUMrh7iKd9NvB!07HX&7TXh)vKPZOpT)rDGiHj=p8r+1L58zQ)3HFZLh@+ zWK}$HG_kdBGOdpnDox*o9D!QkAHI}G_3nf~6adX#Zw#5!|>hIvlo_PDX{dgeDx&%JsiN+qz z&vyN#)_Mxc?26wx150}7x-WQrn{rby-0>+7$w-+SH?kX~jVF!gI<{>L`0Fi{c z3bn-*;W%Y%Oz-*-6+mt7hmS_o<~%#fZ+6}Sk_R4n*NRiN>TK>9mU$cc2c5e*kFKbh zQ&Kn(x%eC=kMNiz7UA4;6s6sfJ{Xke(w$deV#`~}2Rb=f)pQM$9^c269 zf4w9X@~)A$m$x=2igZ)n8ykP+*VlyE=3z>^k>47*!tx<~l+Gq72G?VIIfvQ1F^|Q+cpNH5 zn)+^Xzz9+JKd=w&MwYspOLt*#B#~_x_()Gt))25nFrW=kO#(3A(Aih?$_V@kTyV(<^n}Dn_M7rUj zUhZ0QaHH5rdrdVc^VvhJzI1@j4~1&{PCD3HGY)SpvS@-#!moHHYaV9-%CvBYlDED5R^zqC@jP!QS`XXjj8gllAW0N1O8eflsaco1rER}M z=WdGWsw>`YMlFrJ^>~QBs2%+pgVydBs2NgVy`8!6%^ee9*nE||@n93}n*(%XpEX&ZS>_97fY7x+zVjV3wNbjwY# z)hfedp`mFW8bM~M#FmgLBvOq)Tt`yUe0ch()`#?ipQ`W@sN)y&*f39`y)zHG=mhw3 z^K&EI#KW>XzE=p5kcmA0EF@R2c z<124bzd!8}IbQz)BBax>dz`b8qGiBQGgZCcbp{vQsJ(2w6rfGp)JCqB`m=@N%Lj&M zu-hyz?{jclkuLxC z3*9X2@)Wpvv=_VAY2B3pc*^0g75=-u0g&+-OsZ+#t^JGJk^8L(2 z>3ym}U9Jvpl(9uKP-D868uv%CtYMURJ)^TEV0*0Jx2~kklNSn;dzZ#)g?TN{P?W}) z_sHL*^BcyN@fa{^)I9OZFYX|_hCz79RJ~c~m$UTXP#vp?l5mx6b_1r2nW~XsiBn=> z@wvOP&$^h}%XfOWr-<(uTfT&utX^A-iqt{ab0&vN94D3a&r?Dq96Q^d2d|}dRt8oJ z$_8va_ju%c8@#_$pCG$B%ZVQwM#Rs5HC{XWgmIsOTjY1?={HGS%;xCm0(TxpNuPA< z_}4VW(PH%XJX|zK_=tITDs@?6`xMLqj(a%gr95mpm)~>pEUHR!- zb2nuX2bNr$JEj2oxxuN@!^jp zT=uS+dj%CNh-13!*pZrC1r1sZ_Xd?TDZD$Ly1C6>H9ViXSrY$L8<*yZUlVWYL{(~T zBwyE(R8H~-eTLBnjdYzWc*&Zky1p_UQ>J2SpDgiIaaLnz%Ifi)hNSnL`g^1ld?`5J z@->Rcl|?-DoI5KkPwnT4Hu_(z^oxvt0M_VTR*GNAPax?9)G5!))f!mQ%>ry7{<=(l z`b9gv;f$x+j^tW`Gl43r%bj90(GE_v{+e)@t4$`Cz($1^3V4USq%BasdjWc4CZT5` zG*B2n?wXt0&p`^ptL9J>DzGcZ>E;*lDV-J3{X=R9R*aEM_R?e*|*E#n~CouKWJUL1IN<9)V3Xxey6{b@8N~DfbM;ZgaLaJ9@KE}wU zzgdU61C1?k;g;XyZL;YvFK;R#CUACo2XH>pH9N#k{_sZ8=Se)kI)qHuUa*LWBjJXW ze)m%E<(XamgR89N>wAHM>Btzy^>iDLGV(c-G6K^JrO!AcOc^DLkOo^~d#%=N%@hO9 zlmMJojtx-2Dawsd-EC;B!l8$pX`fMbuC7=d;R|L+3H&l~!;o97oJZClviZe)L>Fb3WH%$Rx)S(2J3HtqQt1g7=$$cs1g zSUda!sCg2do0UC|b7K#m(#qJ*uup&&vZC?d6tzReqY29c@Wg}(gi34ZGU&`6WT#&* z@B@!Z%d}P6@1gD@MLmWKS}No+GRktq=^Rh5Yczjdbha|A%sCeFAM!Do$@NqJmb|eu z=upkMx|g=CdrK5w2}H`VF}d6bPq+O>6GlWB%ptQVG~uE+Voa;mlmqS;BxXEbu-2p+jL z)OtkiZ8Tfsm9Qu(|3B;J7Gi%qcqiCE)zTlc1J&fs8&~a+Uy&uU&s)U|V{R3Ib zs=eCYz6IM~YhrsAVex)r4+gXZ^X^B`X8UG2a;<$W$yv31jlfAd2}_tP5N%d!M5CVC zUSoqX{I#6HUV9;RMYBD;x%~ouz7Zrk6DVDON8>4%Ny9e&^XDZC+QMf`HA8*x>`GWt ztgSY%K#vq5u-sWmG(Pi{?x@J@msQw~Wb+c%yfB|R{O|#QKw3cFD^B%9#NA}POx6hN zwrwRO#Oe25R9^&vUX|LN#w@QP-yOMB*^YU=4UhHyQTqurE0*F(V@eN>d^hj)206l1 zk^G$0|3TDK%nFx75ki&{P`<9T31{HYQA;a*kJ?79mj#8M?Sq-adAxNU4f;}s?DU^? zCl71r7M9HANQzzTh9XsvXIJwE>#2&!jj*2d7bc_WhiHb?Aq$(79NCOz)T4XHWJol0 z+7~tUfu#cKobWoavDRpC>~K*EpaqDI858+qM3LKr$MZwGhrLpvnL@OJJ27j$ry8WF z4L$}W26UB#lPHO47HXZ1j&(P}{#MLnPugeXa1ra&>gkw~>^10Z@P07uDA4^;xor>q z1PuL;0AToI#%yj1!+n$qvt;PpU`6Z3pMeLv{>SW=#i=UXheSWUB2Tp=$dr?icmA4+ zQS|<=bL%sBMVNgV`}cVCV5A`l!X=0>vrPZSa;|@G|A?SG8_RC(9{}~SyZn!IcD$r( z05g=jAW8VDF*Wtw$!ccgly%#JJt!9?l+ZF+lCsUVdqqw)e(tlvpDQncSzuvK({uiW z0A5)!BO90rp^x?(T|Xy7BB5`nE>G`6oGK*HxmhM=?W^HK30TR^5nL*(jK6I@LWum@ z(vo41=jA8k!OGrnFFUIO`nG~ZKH_D5=uN`we%8DTHF0Pf%=`zKz7&>t(p)laNfz6J z8b>-ixuXP#Df`p>`jb~Q^chb>*`7nt65aZybJE#RtqHZZI8cs~^2Xyg-DsKdB|hEv ziLFI3ta6v`j17#}Ad8dAPB9rHgdc22WQh18Y@qS2g9;r zw81mSGx_j=e}M5n@l)oDa~f(CLowg_2fqe@uyG^SJmLqgCxv#;avY8Vt#~^#X-|1G z4DE$&=1)$EKZJ6;8M!Sn5X5rNxi7Xrc+clFb$~S=1v`|X0v;G<=`VqHEa~H$Yc1VZ zVR|;@CbMnhXOGb9m~RJ7HRf7&C%6ivS@qWS$ZaTRW#BKPK2@15?G?A;AO+Y{RfMcl zQDhcx>$CQ!4KYY1hf0^1q*r;uSAAKgYu0`@j*ukOFABfueCWZ)f00D#{OA$xRT(2u zYQtoy$#4hzx2W91BnP1wk^_yp`t>c>9|c7B+$!so2<0ht}OmT;@g|u!Dmd6E+A>$>BKxtC%SE1B~5ChS~iU69`j85Y>yV4t5j~)Ov$6y2Y~G2o^q5@%F3T%dGIz zgS#hVYLYC#l)AzRlNXBnAwFV#lyVDZ?|VwyY$-YE&>&GK@Sn3Gx{fo;0=IN8!!eb> z?Z~c@obJ(`>|}k3fw&&G%(!&s?8)h-Fkc_V!9F(3I9s!<8c^W~w6AYVfL4+=gvy{~ zv&5-eM_50I2|$oRDLtBgPjg|OKnDJc|_^7pXR@XenYjpM7-4z_X3u>Zy+ zX!=-3NJwYJ03K|d)Y}^T`(!Pwn=f@^*)Ld!5y9&t{Od9Z^w_g)ve(p9X5Z!k3{g>v z-~JnY7;D!#9KN-}uVb;JqxanYm^D98zg*9r>L#ou;EMiz%wgdA?gfq>Ozm#An*ppf9wjY8_yLR>zQfxkhV@R7bz%=`ce;~`p7Dm3plk8(!9R`AhK7nxo% z&V%{um|t9Pp<#73&m1Ddhm`#29kSjzT9OL_iiJjd#XXp1L_a9(q$@j~t+|v}~ z!vf(l3Q$Lg7G6kyYwDbmo!2IV-OCf<@99lqMVm&?p& zrx~95`SIP_Fh!g(ydoTlM>Ei;?I!=tgS|9?;CsLiJbAI-vo?j!n4Sir$YPR?kl-_L z@v|Wq+O1!025<}JS^H2>%V40N`pcQSO&dWx@YGN6U!PIxaYCney9+ zdfuWhSZyZYf#gfN;MFk&s4r7v1CsRJON%Q<-UiLlL3w^5dw^k70K6=P9Gerq36cc-_c9T(P49h<-|4w z;#WlR{o|QGTxr-yWgWLa=cs$_BqB1B-~FEgPW_92%k~>Y?58|0qWuFzR0(bN`E$S&{(?Kg;L1&_3yDhHtQW# zRhP!;t`O7<4fk0t4~8t@x=#W81T`r}@!3oDLYF2uOR(jSH(^;cYrWj)33n|-JdfCG zmZ3mB@mvs=x-lRjt*|_r!&F+#>{ElyHCj?) z>%g)5B@=$@50{*hU#bGNY|l|AO;^oFbX0Xkl~5S){sAD5=Ic{70Xd9??m#M+3JbGU zSaIZLDG$R6N?cXF#kI7Fb8xb*J)G3iRJqGjvmy_3^(<_Xw87#}45Srs=@c{f=dpQk z>TR>mfe+VNlGqnp(^7kT0)ZE1!VNTP9XFWCc-wu z+$6Fa5^x+LUM`#Mzi5w_6!Srj5~JdHl>hDdfP&DP!U`22iyFicORdk;M%+#?=Wam*2kZz=64NFI+aU5zo3yobVdNP#YshsgvzC1JB2of6iRA*@5fJ zjn)o+>snDnMK~GspDmT|U_2^HVQRN(N8NCJ;TQuc*|IYAT0M zD(UkDuidDOJEKshg0dDC=7ft%cSG3gI1jqe`QkLpxQMs;E#DH2&U{jPV-H>^6!I|( zM{briRprTv&oSNIkbdqCn&&cQqN_J7jCl)B%};UpZIQ;u(bhkOm92GP%#kMW! zJSNLAnK$UY5^$A*y7<9HFtHeq7fi;|O5&!PB)&hVAs?;vc}Yp4h9d9BFl3im?L4h* zNxC^v?I*r#4!f|`q4B*S>`HhXt>n8M*2q=07*XI|<;F}AZ6dF%@rNz;HEPqgwW zQpw~+v2i8(n8uU!4&rNR>{!Q^oO39W>L)-#NV_cT!79CBH9EX5`KX>(JFGX;lf1tD z76R7?I!-xXT&a0*{sAJ&Fy>59(8<@`BXfXdF=!#XV;yb(`$_`FOBmN9#V~x{b7P6wN;58 zehJFgUkrxpJ}-XK7dxqrzygOH@xjS$9yrX*eep6tM1zT4fvGNdsi%+eY(@zrI@4{! zrAd$+@V-IpFK0)e*L)KwwDKI`)$T6XM9-?@@GYaJ=}YpUQIlGLo`R`tz< z1?xOW&v$eU%%xeMQnL88gpGq#$GUG1s#Mdv*d&;OTjpUex*}liG-x8q+?pMC?+rcS zb`(@a5JnvivW6J^vfM~iq6Q7Wfs`mbl!sm5jJVWjXa4~_th$eE2K6c&x70!NUM}IW zo9Sh+yoP`2L@Qxs(xS}+ud0}niR^n`KPtrXi3i}pe+KHgDHag^B9p|sQzXN+28mQ@U6x3Kb zB(i&Ln!h&g!`Se#evh;5i}gx}72`d>(U@P#C(8e~ne+eCO$0X*6q%dP+lsvY>WQNl zLH;Pc%SW^}o!n>DyY4@?eZPq_h`z0uK z6g=eV5eAmc(KNFh9Rr9G_N@eZfe(w1R=wSwyFGBJ%~)aXPpRpTqGr_L4mx z-A&f$f8?%LUT*#v9*Y28uP}B*zdL@%a^a(#$qM2JVo88dv8SIEbZh@`YYaXT|>j(Oy3}tps-H(*LT13fS1^g6I5n81X5M z{R-V+>{QUu!7t#SdAa>{P2sJcQR`|97NRDd4{)x{>zqLR1YN0q1dqexj8S``9f%Vu zREhJg(%_lS>_z3GRA~hmj#D;XF;(J})gz87`Q7=wKcwz`?+<<@Kf8kplIl&oGVN)V zBuTfl33@Xzj9bB)M|yiPlj7=jphRd_H0J)|Y>y=bvS_@o0~d-jQLsS64)hH+|MJJ-pvB)*L9>`s_2O&~6 zCJ{H={MEn96IDz~fTN@ie!71vi2b8$b!0I;zwj5>!du|Ds&G}ZhSTMR`RGevcJGFs zHsuKS6LL+8_p-Bz`tl84;O#B9!N+2DRg-SwkF(juh4VO(sAPNv*5d?TU{G0D3*shz z2XSNuZyFs{Uu7>T6-yC}rHet>ej&8PkUFMQCVTLZq(ZTq>DMHK;jf*5#MHq^0v|%> ziHrhCVNG3)EP>BnbXgUW%V!?Tc9eMAn$Xaw{*WUuR~SaK_(*ck)T{lD+HYP zT+1*T-h4}XM=@mq@{mlD{c`%vy>9mKja4etcm%D8I%h$y>T)mvk+%-VDTH&edZfEy z__$LPetZQw@>@XVJ}}J3U%r^L^F4!E)ip=ioD$~&`ufgIen63gG2JhgKBSYvN-`B) z)iKigV&~UNff88yVY(_|LSz*N#A;kT`-Z2fC8ldx4dr1n1GM#gT)>2 zW4bNCnt-lA13Iu@^yc;KvL<^o$()gEj>eS=2lUCVR@`-zyzGN|GsOKB{Z+St`cg<}i=F!cz12_NWK%Fi~(9oIk-Fripcx|5Tx+*I30ycG`H=duUWe^mm;be-gIGEO8cn zfkTr}>$~YzX7O{LI*p*~#oluJ%bE%C0-iHp9Uk*i`M~W&BB)t^%SQZYLfMgJ#abM6 zgBVGi;DOBL4Z7uey->T8KC;~{=PFJ?N9q!aNvoD%lqgOtjYAltvYa=aE{h!aX>K)G zQ(c))Uh#4ATg-c+m+n#1bX6n6LOR_Y{fRtq1S}f7ThXrAQYsTC$A_BH*0oAvt=_YK z#YDk%ln>x`Zp_bqxNwP)@?K(iyIfg2O5|C~RQge1j`g>Ii@ z;~=3K;f_8er9(77M%;uG@%fueA-)#)Fi>;VQ5|`Qri*zetI~NBPrnIq!w*{p5JQ|* zE2o2b+x{UXl?!98nDP{&-Ey!Q^8WJZV0@O|(fXW6&p@_4xu?sGU|Ogk{SxQJzO~Aq z?bv*GIKSNX!Q_-LVlSm+hSke+Mq^1%Xg_OfhFm_a9;od<7gjNXY1x@)izBQ!6_VR= zoGcw@pdvZ=z7d}waT7>dEH1R5QfPLcdD?M!MHk~((w4>?$MPb-^E#)_FVL$bVx+J5 zbZtw6)(hDhyF-~-c}y!Sx}9r=4&M4(J*xj$j<5z7l$27+Za*ic2@Bf|%it!rCjjZR za5roew#{?bHa%Jv(8jlvV~JBHexcSBm5nbv$1kSla<-r;K|C#JF+H#^_NU-P(kH~X@4rV{O}kqNkc(&$dy&u zw%w^HfC)ZW_Gh)bHa-POvztw^%{>~@!R6wA)*e3mpQ;31^w$^#9$e;VaoU^O`{|~@MOGC#RF1TRsM?-o6rR>qu`*~ew9uibg3r7S z;ahJ7g?QRPgO*9WSxp`SiR`uzqJGe`N4!dl%{s6?S+{sq$sx^($Pcv>!?xqb5>!6 zI~Ur!j}cchfqsa$EHxR+c?8bUcD+4oC5nnC_B90GLH!phDc6@U!;C#%M>@F5V^7gr z(>35P1dGE4RAEKog8G~}0qp}4UV7;diK|z*)bMi|h6V}QtK@=9P&Jj*n5P^B$>vmt zwHmr9&$6i5q+Ku0d{$tsMjai$fKt>LLAdl$eA%w>saY1w9S;Ipyi`2yM};#0QkwYZ zrIO7n$ENeNt_Rce0?JizsnhJlCPOq384`(G=Kg-V-y z+mj(M%sS^uTKA#?A462RG9zQNyG!ChkcWkkJ({P@=cf1}$fTNE*67u~aV3W2_&3T< z@)h(SI$Kjd(kqBtF1h6yD*dssc}HCyv&HC|TQeN(D!?whdgYHY(RU1Ext4lSvJ+!A zNHl6E6CT82u9(`o=U^vxN5kd3QYtM0-GR~V72Xrq>{U8?ZQU|6rZ$8DATq2t&ys|O z_T9TXtH1A7&87iTG$hwiX95;o$RVDW2SGcasXnoH?2uqwq1@jCCJJE#Y9(j0%>q$; z7+LaV1l)GK+B$#=f;DZzk7uF)pn6p9LABM9Gm_2z?AM{KStxTQQIGANs12P7Bmn^U z*l%U}t6^SUXZlufeb8M@PX3mu)(1|l>5Lt5?`FuV^8}j1O3HmOaKfz|Evnh$<%nf1fH5y;G@oIIvph+Hr1IWd z^2R>XvMDm{dAHE8KUrorV5r-@e5lS!;NPrs_)D}ve6=S2yFf+`=jijwZbE=TkW{%y z%Yw`uXj>rtDWh-f$SFK&2ruYk}Z% z*y+1sr~7F5ar2htXOC8zj`{QiOpjWd^Q7ucN5f(WYnf>rzspOPj$v&pgyBpb&*4il zT~r6T9=JaeMscXYjG@YRv5f}RmD@~wW!VQPg=9j}aDZy)|E{1r|D_Y7Que2#n9 zumRB%Sp_Mi`W0+*didEBKgnGXBT*NG*2(Frr}|N2Ze$zCS8Gg-HHUdV3cYAVmysM? zCFidpo<#7P7x)LbZU6H_f^ls(@ts$WDc(Mv)>1o<4FUqTZcc(&FIvbax9t)aCg__8 zRSc2$2(i+o@0d+_ac7d0p$*#mJ_hC;?gI3D>tPD1ESXc>`1MC+&G`{xYUr}a=`JwZ3_i`j7ozHS zDR1QD$*AI^9RLTJ(Oqe4pMB09gl*J*(s=izGaOo(7L$bUY8;)L7HY+)FBnt{UF2lB zlUrCx=m|L}bgt+WLbxD+TFzc#y8c=_-!+e=mLj&JDIRwqdPmz7ttmoLuHY}Anpetv zN8IY6KwR;Uz|xD}4Vs{CV7lPuSABzP-h7Vy@*y@z6(`4#ekKd;ttL4hm7_Gdud6n$5OvH%3WDTxMo&{&8or#F{Jgx~Lw&t#2SY2+-v&RIq&g64C4;3JsQ9 zY1<4cR64fV!{ZVa&fVgYjeNC2bWZ4)z~kBo9_387(>JhHbWV0d z|J>&zi1Vv)yxL#O#D1>JuFAlDyb2y8$crR*lwtk0FC?X^Vv5dvp*Y|gh~<^NnX1Z7 zz~Cyvh~z9M_sJ`r8cm;^T#f_{_I2l3KvYjGYLCU~*212NE?H`-TVGh=pxJ!^v^b=o zJn&NL9mgFX1>LyPI<-eYjE3zyWm}c~jM(Q^bIp6Ue-ydcs<3 z%i-s+ZsVAQ{rMVCX>`~$@LUlGQbqmv@fM-{X}Vfk=-$o16aSN!R$ zTbD@Ap5xruF|3kLCPLaJB_2LlMSu#R3> zS4_F7N(Vf1jsjk(M;p9y7k~?d7BhBnpKyys)sIckXE8GeDSYwaI+1K17|{I8js29? zrTXPtCD1e)a4ZF*5He@~^nf)N0z`>9)u&kW^&R(Jiy&YP?X&%6#zJgsmgdylANr1J zEh$IR1oZ~{5y1Hbv$!d`ISv!`cHWM-<<~RFt1LyAQm>f#VF1p)@Cpf{Tfk#3XIyUJ z8LoC1n&@wOL4Vy_D-5Z4)m}2(e*o&Ry{pOTSpGk1AfY@vV*T-4KEA3sVd;-MY;9TM zTA2oJZGc9|wi&u0P0U}$O_fhYt_<3rpUfOya^;O(Yli@4PqpBUfj3Y#5)>fx8p+QsXEwM#|LgL}mV6Vzu$>DyZRi7sjO2(uTwy>f-J^V~U?We@2b42r@Un~Q#e7jr z#$=MMQT+i9s(VUfl(Ap2_u2(F!@B0eVlY*uwb!9@!@K;NIx5vv|FXr>f!#FCqw$os zH2M-OkZ#*-!)Q`Lq^=*@C`)<*Fl+Yc2o zkhyx7Gs1unZ~3!`w)=Z=s9ViDz}m_5zE}pl84X)fU>%^>UF3)rODy7ksAXM2zkhYQ zO)4GC6+Wfyy6@l?9qQyGsBfRX9z>;dHPv5ZJ<|ut;?!*Y}c;H zdwTe<_^EHDL~epTPFz&$f>f@^4RX%*7>Tda~(&_ly#eWMPj5!ZO%YFeU9PLtCr5-$m5v} zUl{!JZvXj^mI!IwDj?^2e6A|K0N#i_U+eiPf0xG+O-XL#Ec2NSd)!#2myW;(rr`Xb z2bx|`SUzY?SyURsfk;R=94GNhkwNr7x+(wnWCHVlG%3LyJl8NAS$2wPK1x5T%F+8f zGSTwD)f(y=Zbw@twIM)vF*sU@!=p0>hjRD>Cw4GN5h*ds|mr$9;lx%py$1g>MWtERM9PNK` zX8x3NX>UBa#v#z(i7dkB9Ua>^ad`l1ucnyR^#e)bXX$k+EPBH>ZWNgw7CEf&h3~`% znYEEB71N_5QGIv_TI^ZP1Xw4{M@?rypmk3$HG_-xey0i1)=co9Webq~eLl3^ z>dxdY?DN^5>Lu6maS2dOPg*wB?e#Rl# z0ClV-UQ9CT>c7_8dJk*9_9{*FONP37Qrs`^1xXVVDk{vt+8^Fqq_JD2N{1yEtCcnQ zY^E^#ccU;GTAHSAVw={+&-piD|#nB~`p) zhZ>qMPDzP{J4?t^8G9Ff1g}6e@ri$w4~J}KP+u=gUQQ@&STpat{IP)Ff;iupqN>WX zoCa-Tj0Q%p|D^oZ_P5d+Cz}i7SS^m!i9x$FAiWuWFhVbdo?F@Ltj~(k@pgwS#UYGJ zvN(Cat*?0rD`){q$KcLLxst`^ieLYvCe2)^kEx>xn4@oQLxZH|_(rF9ipa;+#}HZl z*4P&h`Qgo+!54zwG)dYKBA9;$dcKo9rl%1~kaOP}o-2anJt=+Q)05nOYw-`e-J?#| zD~xJCVPJsg!Dg13Lj%WI((+np+D>yW?k7hKS2-gFW&26u{_=Tryaas1Z$M_EJd?0% zXUc|>1fQ@&xvz_h?Q{PC@|gVXC6-V|1{KedPNQ(PHn!h@DWs|zCocej9k<&*fN`b+ zscP$NOZ5q?9tPvISg3|VbuCt$UMhe1{A?g8`_q!hRE3jzzlec`2`%C1%+OByYc+p* zWHGZ-&z~_KI&kHGk?LhS+Nk-Uo9(xtjz$5$&H{}$bu&9E;;!5dW`3RTyCnM7nq9Zm z*XB}!YjB-nPH`p9dFn|BCTiJLfPCD#+lJQ{eoz=qZ0t(loVripxpZX5I5oGg0+$Ls^zR zHAnZ0a#7IH(Rgf`cH1Ogj+Ntv@ySsja$gBgNgHVhBJgoVOwe`18rnc&TfltrWZC3- zVw_&$n|IthC3TeBnR54*is&9G+9i_CYQNRulQF>c!FQ=I1xoOeOvjeLhwkaggNg~w0_5pq%ZjiXQTrhjs z#}p!p{)b8Hzg1=b>o+sQ5B+CI2g1A7Oc!pP^&M1b?Fr%SccgzkMSTd`*y|4>GwfAR zt~%xR(RzMrmsJu{kj8j%D0=tc?#|`uzY%H|B2Tl{Oe*Nnu(6yP}6TWI9GTs9yjk>##~0iE?8gIx4Iu(m5c70W`FD z7}oFA7w&b@S{`i|o2Clt=(sZ2GKN4-wgt)PnCi!L;b1+hN`XW$vylV0@IrwuF8n(6 zbAHq-!M5|>dvdU@KG8;XnCMb_ohsW(OSZEkxXesRIF`hSWr8rr_05mVz&BH)JIcIT zy$!54JX{Gq>CCfq#4*R;DRZMwIjzyF5T;czl>=^srzD+TXBm9H3*f3Ve|#NX=HL#U zkpEn@eNoSD?Nw)wkI0d8@P?DF-6-%**`mH4$CWvfEV>v{Dn&TlW3ML}!A;OQVCLLw zBTV%Se_mseI<&aB3`fu{(b-X65IO?VP5oG#;J&QTG-5JG{Wy+lC&$=>*d-`0a$7mT zUS$h$mg|u019v8u9)B*meBY3ij=(8m7ciy0<)3;mSPKjfkut^*!zE(m3746SrZo|VK-}68r43O%r3lH^Q6`Ap-?W_z z;*vIQ?UefDf-L8ZuUD;O=2kNJHIHH_*H(R+#FeDOmE&NrYI%?F3LklIb1X{MYn{bI zJRgmp2HSCV1RA##Py@+5u!f;a{YKk)nClE@diNt-Dsd9gZ>?OLC3D{MJ$i+HwOnAj z6E#_c?H}OX&^4}IRQ1#t`VGEXQ_;>k?4S$fN744)cy5c;%BQ2Gu-TcZ8CBg=6C1l- zJg}a(nJ_fU+*{)k9iKrxNo8IxTRisxz>Pet>?XadQD3;j@SCWhHQ+T|`L@2^E$fh_ zo$|LUM&9`O+fKL8)aZ?-aB*!ZTS#iUSu5;pbJ$z*n;>&I+dL5+7&4*p#zK>S@?A2E6x^O$b-~dHT4K5V9xuLnXKWO7f zt!gA&!`)s3&@PYTd&#ria(WMy;Aev0}Tjq_VeAytI^b$A)ldEJJP@-aZ> zqrq&f3(IWtz6PVQ!^V$;^QIn~`Dgj}1!6fk9}ufhd5>F#NEFG1^Uy?iN*VuUppv+)_;SW4JVYlZ9(6~Gub}Kc zzehF+1I8nR5^`m9(BKR}DZXkG5vl|B2(CV?P;vqYRN*l4sGO}28NQx&vi;LSf9p|o zF}x-#a|e`lBAwy$l)BfA&gO0WaK5i;f>!I?)vPWIx@TO26xYTE6cA_izJ?qZg^3vi z^YltvDTLl^S9PbQ(=$lVo9p;AScr6#fkJ=WUbCG7ocHEF2)-~-v!HSF92TkngBcM- z8!m=*XC1Oop3W4<$;&5y-_DE6`C^^esW=hS=HeLp^WF-z_(MOxMNj8iqW+0#E~brp$^0dB4W}(&d@1?M|ZPQO~Rwq zb(EFOH~%I^fS9If4$&xo^u0mRo5khy zF7ZnOB}bk zP@#e1iCKu`uQw$nqcgLx9P&{N|1p01xrYgcM;IOV?h}YQSSuzf+D^TMQI48YA>qjU z7%P7utIQ1pe-lrGk~+RIu_j`KZy08iw-QxWvh{N&_ut$jng$x}!TUr7pYSdr zQ1Wf5@&ZF*SWoFr*plUv{Y)ADONFKw2E@AE%7auNBN$cPFL|-c=6Y16`jneOF*TBf zRO{DE#h)aHJre|>Wtm<0^CTxOb=$$k)%h&$m9k%R1nfPa4b+4>qZOS2FAOSY?p=d= z3IFl=cs?|$FHseb02mlYHKXfrYudcA$z)T!lak|1tFT3E!DKp1Q-oxK?__i-qAn!iB6H#E!jFQ(kbdG`M5YN%-~%pjpKtk zB@g!+2hytu)Vb@>J&+%l6xV>y3$Hf4he!OD?Btt*Ekg&A6`-YXiTqWjHo%cZ3Cxr^ zPNa^S-SG(%Qsi8s=?;>aDq>k!TvIgP4}J|VQ(pLrNdKWkLPA;RwWj`iYxLhgks_Z; z3hGw{mO43t0|pthINCVuUy=Wm%Lr{nlwOhlnA0i=eBt7NGd6E?Wf78{sj9gD4NQEe zYOp(RyV;MX-A|UR0Xo!yXN18R(aX)dAcnBoWr7B5LJV4r1(VV~iC?SpV#YSjEVIwd z;0peT;?l&9tHJ%fMgP=-0h(Zq^w8ceYZt0p>2us~DEzPUb@kPDGNIG+5#nDbo=6K) z7|ZH;+AXfoo`bdl(i8zk+>};R#&>G>)}1lrbfn%s5Y2+P!X*?bwn7~!&_uRLhE&dD z5wKsoO2TZn<}ljS{KR5MRnl7tVM7-i`jdQmA(l2h^t(ih(;0H$EIm?bmbWulSm*{t zf1XatLKpqJwsu4Ary4qmU~X3K+fV+lhV6ZGlmoyrfWy3ujkOn^X(w)WS!wz~8&?#~ zfbXyC{nOn=UlR;I>lZiYoxi3An2o6c+5ccZry+g3iA*{~z21CsnPTHq8DEcPGnjg^!+^j@l1f%ehl5LTS1Sgfe7=ejlzy>h&YWZoJpKd~$wl zGj5e=#&lojl9F;jbn_oftOz8i&spWcdU(U_?ZflKqk`zM-XMR`S#3qBXQkjW)6|{i zQ+0S}TWKm+6ieJLR}_iNB>b~6V$Vw8=B$b=W9nOXmsMKKVThm+^BDu%rm{)I17AMu=9~paSG*w(a*BUZ zbKU%y#+-TsjpSAOkkCXSWKxn;0{Z^g-&7 zUEEk39ui>;=#X(!+H91#`><6TIZoRXKd*r&a z?fB>63Uz>CfzokKotM0_>+GbkXqViXf%h@^_c59ELGmpT-Fb34_lB~=Ush8^=`}G0 zEwRSSJ;sSW;uUut_-E_D#o@W|CSO}@B^r2fX! zlU?cKR$<3-R7@rU=|>Qd{9v|9cV&@NSIBxI@}%-EVz}C3a461kp;x&oNQK>^jg zfQYVVX5w2gx9pQijwo@8ESc&nTZm&MKD`VUQR^1k>r!@wu=iCHV^-oSl-jwnp(a9U zh(~TdSPl-d};)av<-`OzKQ5h@eS#XkHmD5SPzlyOb_RUElfZQ|7nDn(SIJa3{aRia)D;AkOs7@>BjQ2* z25A?7`?V@`lE09Fp%puFqQ3O2wuVD7J*l-3am*M2l$8sNQmvfyQ*d0sAwCFkGy^tAEB=&L92!wK26}fY^`zw zp>5`jw&G7PO$%F4?EVri_6eUZCcejGgQ{L=90*QujL!~dNUd+b5)kR}(DL!1=Qo%3 zl$xTF9!;zuk%qJp(}2cU!&)zQNM*+$b~s+6;(0Qh_)f@s=I+l0*ctC+cn)(WtxoF{ zS7+$>Q@$8;R2g!KGx|^*CSqx!H%I8g-Yf4WrrEyE=84bI3u_H4`yvV2Bfcs9R#qB= zIjws}FTS-95n|;+2|%e|NcX>{JpVVnZ00W z0j7Y-PNq0kU|H~-uAgd)*qe0}?Y*G<2oN}wJc|6g$;-ca1!0rDsVDpqyf=@~eN4Io zu9gHOm)dYD^UL(w7_5yW85J43Mk5*&ts}Nadte0L>v~2e?uWCSWwL_bQs7$x-=toH z)0D}f*9H)@6_r*E>;lRlL?a{cOt3|Br|{Y%jVmwMjtCrho0Iaw)~J*e#aAhw1vYpp zy%szuscb5_BNROm`#3{#vU`r(nYoE&K2z3m?j1Yd{kj>+sA_1$6~BBWgy*#g3y{Rh zpN_zfT9)nZ8U2k(PeCjxAa!SApJkKo$rH5BDj-mE#wfyfqeUdf8LZU1j1cCD^ZIV7eUoZ`w2Jg? zdL$Rk(m{y1y}P0RStuY%ppnoIK4%K(Zd}kK--=xt}RLERlVU%^sYT2 zj=7)yshk;s9kD{hsGG_5E*YUKQp_)2^kQZc+;uJEep7dfnj7D@PO^m0yP(d1sk(@i zKj~sN9=~P54*`% zZ7f6UCMpnxcWvUZ(rCG)2GDYvYXdWehiT7e=Av~6#-JK!?&RV#DB{g}CVSIy6<8~* zE$7f=SrIx8n#N7Iob$*LjO<#FW7V0r(mAu^h)XvWegTOqrt|hiEHif>hiLu~nmk~7 zK*9dZlFTTtz}g_l+cb~YI4c(6gbWG~mcUud_{rJKxow44taoo7>jcdnZI^m<@E4D9 zsmz`-R+8MX0DkIl{DelP$s;V+KWN=i@{S_vRgRw_$IYAWeyrp3s*!LPqPcT2D25mL ze=6PoZ#&4KGqO)#8H9?nnu?Rw4~Znt^`t^Z;s+WV^cAx|9UFLZunF@-R#|T2k9bH0 zZ>1B#bz8c@I~8nvEZOd+ShFV8*g zXH~}cJk*vwTc-BP^cGiA7lcCzj;qRwyzBLP#;xaZ*b9vNj@$ z(nnY^T`U&e`CI@^`#i3>Kf~!A@E@2gtyoa9y)hdzx1Op=9K1l7VGO1t#Bu>#&?GWk ztovksE8g`dH`+2<5b)m_APgTcFjCLu%X3~(;8DgPeulc{%*=5 z15FAWGcojs0g*Cmc^*AkxHJVYnHSzUjEo3W%8cTz{S8JUz>?j`kSzesU+=}f1 zbi`fgdlIp44+k3WPbjKyW-7eoFEj&LDQdXI2qSahbl4#|3=5a3tWaSrP62aT!^bN! z2?cUpHpY0U!C&wmLZ>tXw{TO(DHbj1H#jGFtfW{>E=kp;qs#DB+i&`ai4NMI5gK$7 zykJny^|zYMi%<7SRgNEOlhmUP*}X+s4HSEiolx2{PEp<9erYnMsfO;=x)Div_1HbE z{lPrYX&mF(6`PiSGxs@rToM~Hsfu(X+dnci&b{TQ-K;-fjbydVdW+k!wmd? zYhM3DPh3nF@;!V~PUh!I?T&a~i65z|g{Y_8=PvWyisZC;*2tAnd#W)_Ie0k(k5FM8 zg8>J9r87NL2V{12B|>L;sx=F+hr9dLQ+2jV`?icRRCtTwn)Ww$>?m{cYoP{!?!_|x#H(b~autoalfLskw;i5Bft_fvl z@O}$eOX_&x_lNHEmdcJ`LHX`wCAQ$Df~EJZ=eLwx|7Nq1&V5DpB^~Ohc|nr77+ubN zK_=Xkdb1jlRBO-qXrE6)c)_f}(`th{iymczU=Or_7>)ONE!*&_kacr+%xx~Xdu_nH z{5ORK7VzgOc|kfoit|Fir(Njb2g!&dq$h$M9KigblHgoyZCcw9r-a?K;&GtgUY%nE z+Ju~vs_iqLj7>fF5ut}b;7ECy=rP&WbJatPKueCg+Vq2UXRnLAocW&N!lo1E9E2Bc znRsYnPL@Dh3Dd-=%WjUH3h34X(y^Z1Wt{5-t-|$#UYT>Qnq9^$={1Mv?aFpP|ENQW z-Cntb=j7QhwU}(-YFDU=Pr)n+#nw@Z)w)rn!snqbVGB2#9bPiGaK1<+P0n=qBc*E` z6B6$xE{wRem^m=Qye=6v>*xGwhvsi3QGx+}Rbk2J+tW}_D%H`dXiv|6 zo^q{;zed?#424z${CMDv=4KRroHk-5!__}n-sw$U?Pjn+NmtHv?9k+^Gjz+z5ETE~ zYao%soEsvR^#b00Z8@XI!GlAFBznmWt8(;~o0YLoo!X~8fnId8oF#Yn(hN8b;qTsP zaLyCaVbd93`Cc1!fGr96w_f3=g;~YDt;@;a7890NF&5O2>wt${oXj))igf*Yq4-__ zRdowQXXzeG20cOML&V-cUMVXzWaC?FS^1x5*|l=KHGe;3m)tIiNGT{u$&qp~$wO5m zNs$IMuzDbQ(?9pXuRlGReA-wCuQe#y8wxfl7PZ$%SjyNXT`esSH3BkHy+QeK(V7tg z=@~>&RppY_3N?9mLeG>FrG=^Q@=cxDV_%z?cD?W7CfPFcvZ;TgPXRJ;R2LR^Kf5%+g0; zKe?um%%H71mQ|3dB zx!}zXuS8m6yaisJ7WP_#GGq zPQAHL%zz5)xrn|C*fCg`Ww^mz-J%kc<3(&(WytQAX1e$1FGrok{_bJdKP+;I?SnfZ zT-7{CZ9QDN8N4e$pEIm5JQz2TEY}$AN`YS!Ey&)n>$G{pjQ?C>?CDaEV)I%g)IH|; zc9zX!>V#nh77#18*HJ9$p5n&4@fbL4M=`h$e_}MlXK~$XxP%i`O@X?D-TjtI%d|H% zG!PoFQX30jG;JxhNwvu#9U2UZq`D38jhR>ouFGMD0v1N3tm|H)H2+kiM95y+ej5J z37d3IR;E=j{CD)Yl}ic;CH59BqQjLb-m4}hEv_6<4Q<7%)==3Nk05(WLRyj`Qg}Ij zP*KE~7ClgKNMK}LVR*c{F9Xlw>-9bn-=@U|i>dL#pM`9!@}$J5hUhW+W$w#kCHM%= z>ZH93s#}zJ5tRepcxUR2c(9a|dZcEbgcPA?9We|yT$rG*_ItxCpRa+lZc$&Fl#)yL z#^>o3NuHb>ijIfCt%gPQk7`bFp<7{K0N)K=nxO2+={T&?(kpwSJ&#~WA5+u36!3Z% zKi?STml4;maWC`t)i>8O)!#L^ctI5T(Yj)o@!dP==v&Stm{=cHZE+<=+xz~i#w+8< zMd*mG|7Bk9qi<6}NBrf0Y@^Sif#0<7lO|*vr2>u(-bFh2zD4yJ9KR)7nt6C5Wh4of zZef5}t4~GNh48gqIEwh_ryEXjT9uM?SIk5$N`ky6`qL&<8O4Y~LGU|n+^Y`AbJ35x z2BWqs*85orpC1!!sY)pX0&`H>o^T6KwC;8$YI6fARoVV8Z-DfYeWKsX)y8yq zb=1S%ACHfl0tjy-XL0O!X%BBaIPsWVci&p+4JgQ&Ik`&G$3gp!VF4uLLcs=w%kn%# zw&uUCwk_mU6NY`%K5enz71Kj(JR3g2vK15)I5+~%e9P95-EjJl6&m8E+xa1`JeX??G4xJ=dk%dzrh%QGkoXQ^c-6`&YZ*>q4eTi{Ijuza6gyd}hB%DxwT z#$So--W$L&zFU=^9l~OObUc!9tBtFyn(!!viB2B7M{ZT*o%uLfR_&j@H4YsZ4zJDs zIx(;G7WHU(48A_ru&Oyo!G@QJ`5^Q5IU{(`r8sWghK3MfKE+3g0PRAHxra@vsi(=v z(@(Y;wY)hvtLL!g*q!2?iezS%E~rc?6kOQ!_1wMfJ&c`M{-!~O&i}a)g2G5+X6VqO zT!QZ|=#O$@mfp&oLD}klg*+;UX^M-KC}`#qd9z;^s-4=3l4pEw-DBlmfZ>gqo;MxO zZMUZ4jN*%J%_Cn&(k02`&^Y%)BbX4wRhGony|VK1P9D4m@)*3K9 zmK+XOo9H%pxUoL6yrmo1V_&zhb|Wt`Yma}scUYZ++u4ay%_`VN3%^LBNfZ}97ow~} z3KVo(<>$E*QNPQ?sMoSZl%*^vL7nNnDc_hIctIMB{%&QFVeE(eW-b!R>RLUUJ~?IP z`#A>k6W=C3NV-DsM%DPn0#wTnzp(suBzni>Gz33SgZcop8?W6#Ix(fFo~KNY1_U@a z6|ziEZ1e#y&`3%JK1Vv+6uAC*2(Iy1;GSY+TTw(JEdAu3441Ufp|0bV+hfKZtWD#( z7mc5A55SV9u{9WI@L{Xda$HoTm+{t&tb;3;b(WOYrU_W_uyza5zA)bCU-wkoXV@^; z8E1ZCXh3h_Qz8S+kM?wATS1xTyqIw#Ii(i&$=Qs;0DGAlm@5*Ts<5%S;;kPISXD=? zIYE{n)1pf)2>T`JK-?Q%1;>B@hvdl(iB!78>{EXr&b3^NjN;{sP&5%DyvmkyZ)a2? z(^U9_eVSQlaszQ{-`Hz%pyM}a31d6DfZ|{69F^={7)bf?|H#w$S)Yo^*1QF_IUTOb z-b#kHi$?E;s>+2J9{XYRbbP84#ohf*lRcbyBd+jZ;f$U-n<;7|Y&Rz~o?U{!3XY5v?Sn=tJ7b>_(qm`afAbvG+{ie<2Z`J?VVgkMQv5<Vr_=~M}5lG zocu;#PE#^V*#pScOzjfXjiAH=E7(o-%lj#scts-X=cladro7GA^l(jx;}8;^?jx=u z#kbiOdkU|_i8=l5$2(S64u(ZepP+Y7HC>g@Fi72rp8cr zygDRXbRi%mR1mM6+Qk3V-u_S>s4BrBF!1e+b^>louzR4q2SiWJp$eWbTu^dj`m^)K za{ygXIrs3S2j^CNWd{WRgDFd2C1c4nV_86v!y*N!aXvaqxg{yt+!0qH{h}D3$=V_88VIym0A66G++c>M+m^>zK4tr8O z8k3#)0Oz!cf7qZQmE2h*9k@X&h0?ET%K%HFO~AJ-C3v=zB6dPY)m`GYl_}W7ZURoC z8wO*JMu(c8c(%{kvaGSETy&xz8n&KgWvr-&P&}#D9Tu70$rS>+2{t~D;~)P?;vMW} zOK$2KOJOHj8+bWP>|sl*l}(;d(@mDLKtF%;pXu9a-*}_yI3RytZV|>&j*whVAPN4a z5VC)_$zN^bXMm{;k%I8bS5jE80Bk&To`S4Z`hpCl7)7lOxT1)3Rzw?WQrE~xaSD0j zzpo2s+s?W83{1pa^VxYhzbL?Vl@m!HL&&-vW3YB{&q}EE59`~_kE8=W@t;;%Ro3VX zN^Uu!Fr6hi(k{e0&oX}hXi)~MHBU~1kB>CgW=R5OPagf(HhWq(rZTHvA%O|6YOzsP`uG^KvEXd)$+A^4(019V7Glt6M)29q7 zbVhN%$$23R+wd`K>9K&*ev;>Nl-=ut?5uSD+^Cx}@oW}BotWn(N>|t`AYUg|7MnG~ zhg9cgZ-z>?JBCjE!5hjZPM` z;U4IJ(JccrY$XzAxnbbz1z##jeDbx-;+A%KoGbV+XvC3#5n(g~uB5|s(J~&TJKHBM zS-$VXBg?lN(+;SvS5n2++C zHF+L0L`rn2C?omBtp|QoY}L)yN%HdUGE6YXNv82WPVh9c2Ei$aM zx*KKopswY97kY*-@fcV77Teo}i`Fr2u`nn9rg?cr;tw!DFY3T9^~9=cQW2Rm80TA2 zniGqJAVuh&x7hN5t`NcX5>yIV#?Q<@d#Z{!=g^zF{=t}9%nS|>+nZSFNJf8jPyu-` zY+=-3b4Z~Z(!d{;jBaeTN}=m&xG*4xYn81ZQC&71?%N_yocF8}qiqbLb-O^Uuan^l z6g#Z=H5ON`)wUa#vxn8@*Un1VWxWObZcxTGe`**yBOnjTkwQVsTcfx~l$B*A@1L*P z9MNZXE6aNI_2n&3jH6xb9q@okY?8x&t}eD8ugyVbzTp5xvb=(2kfdq0>4S909SfnP~K%k@YMbJkkAS%4>*o|_4E$h%PJJ5+9Fd^LMQ zW@lqiS;5wiNj^y@`G=t{c{{AM5v-E=JRPL?5Fk zLhcwK63l6NoLw2KHooA=glF@9qKX>8OhN6je*%#iVEJY9=3YFbzN6J4PPLO;Jhj(( zEE4;+P!pVW4R}B!ncei~B_Je*18~Fg5e;XRFGCBh{D3xi^JH~SomFD#N>d>?q`-T17$*o)-72t7y4tCNh2#oks zEX`gr$?tp?m*kjrPMrRAa88D81U@AF#Ft7fa)M1#zrE#SK)I>=L&UE^szslHPNzKu zyE=j18ET(YdpRYJ@?}(8v`~A24NzO~VD|z_LtuchGf7`_D|)3!K9^pZ2ZBA|Ac8Es zbUy{P>|E~}@h`>RLjJUGn4q{pUZ_fVI?&LyS$QG9Ox0L60rSFoC?jc=1R>;JshUwr z??7yk+^uJa>WYh8a zhxHQk?Q)WLPM;vv^NriOx_zIdy-IsoZt9vTG}h;MW4_UL*CRuk)wc(Gw%ci4Tsmah zy;wC_Sxg9d^k<1_791p8v`{DE&9pz~d^)bM z3iK5U#LN1wkwVh?JB3RZSa@8T!AKw;>SWSF-nk^1=#4gkE$i^CDtoR#K|eFoII9eo zH)8L32heLwsp60sQcpZYYGL4-LeheLy4EnlgnXNplDm->woD!2YTsdPyd#sjdDgsK zv`X!I-@8DJu(mn!s2F~3iP*6UjEi%4tW9>@mPjr_`w5gIS>QZ}RY+OIGPEt%uw#>K z$K2fS*Nt6tXo}FQX++-g z>92p!+a5<8`oT4Z50t+7bVt;afL3NUivADAQcjqj>R|`y*_Tp8g9g99!TBe-tw-h~ zmd9n?A3Q0wZ*x=q?@Ats$4!L=VP}_|T(4alW9IGAQGHc-^ib(RZRcU+Te9Ez7#h_k z-FEOV?`_t`sy%GGq{esTpyBeo{?bMH+Zlu#EAfy~T`x(kNX2G!`0wTmkFWn=h?K7R-#d8D zu-EqnW*N)g>Ok^|mmTJg#z-%$WeVa!W5Sk4mb-~S1L~(6`6fxp$-x3E05^=**KCXj zO`pMkFv?qBTj?Uzw8klm?QSx1V&60pJn#nB(%p4`A|hl4>~+rl0dh=W3i=aGzmB`p zRN^zrY;XGzI`5sgZFUFje>>1}cDl?2*l#?L(dEvJ z{UNM*zkTOehf2^I6KZFU%*Z;UD%weQQ;8^^XoWN8aqAol~yXny%NFDH8w;pcb7{^ zLR|5$A%Uyr@8V(b3_>>Kw+G6;`JUr!NmX!xsAK1K>#asdG>o;Od4W1h&r!^?_+%2E zM*K-5+)tH0c52;KQAA$9(KQwg7q0#Eu%#Wu(8(%%Oj}&1i~0!nh_aSKM=u>Rl9eFU zA%rb8nnCBqat_31ai?ab1;(_e0$#P2<%dlW247XlQsIuqtXtVGDNCInAn)%P)qX8? zqS&-RYRNOz`*-G0ix*dhcz?l3NNR6q*B~(e+IK3g|y$ z$?@1R<^cb#JaCrdn0Pcnglq3s#q8OO@;e3Q5K9hhX%enPq5`ovoj(99s);As6e=!g zbmp!f8r&l3sJ99@f)`dj|I(*}T;^1T@n6No2X9on%cP!MT?JUA2frWX|H&X5KL%>7Ghq1-5RP`jT2(lbs|6NF^WDdnN5|G`u?Cdi@EuUBA}GgYHUHo^LgQC;D72{ zLdKC@r1Y$Q&_ZIcA|@@X|0O+B9_;$0uT=U>fBZbAN%G=MMT^hxI>eNHv#z5GoBmZ* z(xdpO_ibh}l?J#&B)hiUvfFs1SHeN!k9{A{Yn@Td$B|-BR3`eD#OVDuXmx#jhb&}U z2=7(i@nOC|Np1Y?6xgP?)BTz>r`TY`-a6Kf$7#*N4J(4>v3mVrcAl0E0 zs^D#bSEU|ha#gKDynNDECvy3%=Y`!FesJeSDxRA|d+LK;F17rfsQ$Q)#@=diliAog z_o|l$)Z>gnzIRR)O73*4FoZ)ql*{BgNGz9hz=*QUK^pXwRG-$?J(JU~J5f4@V-^Zs z-Q8+&vGe0YyYCp##`wA=;E-4uR)E)97Fw9yY;XFbg#y&B!%{`qRvG%>B&%beY3XyL zVpyzf48e>Figp{nh6_C~dnX$l-ty2dA) zV``$}sjWmu3y|||D*u?5gx&MXN3!mF*ce*65)?RuAFZs?m!h3{4P*3BC z3h)mm@7HHL+@E1E%p~EiyK=%hSQDMVP<2VguGr0FUUe6;D|J<##u?6~%_sl{F{@%f zY>fh!??9sOccOqk;{~@4vziqdc~PKYq(3J&ygoc9MRMr!^#N`_A+l?`K6Bw;^D zo$G83MlMm)myA(~FKoW1{r=&c`-Yx>`+yx9KIwd; zv6|p$TRe#({bfsy@hXS7YQ+I*S*wC0u55|nQ6N1y=QeW_L!u;?$Qm>%nQLSmb!JBm z7}oq5yHvCqhXW#}d;Pxaq`-MtpW(T^vi6HcEzXXBc^|n0%*;at883$kh8n>ALDs4?A~7IM-{#aaxz(SCl~YAkmIC(qG&m7 z7sL!*GVmM*-{ok2SxjAAN$My`2R94i6HV4%otiP>pgD_I9A#|1W?@>UwBubCRN z7M1j(vYmXjb=O);^J-Tt9tO!a@$P;k3v$mycmG|^aFDfkJ!H-|03GEQ@@Jckk$2nK zUHJ1HcSa4NLKAR3DkixO(@&eeE`!Xcc;9J+dRW1xw@((rG7RAwYSYkCE9|VjlU~m@ z9c0Zxc%faec(UR*qkLSR6LxOxC4-y}NG+!~E)87ggYSDE-Ydxb&nm+!-qLLJNi~S7 z;|X8zGT!A|JARX%N5fQoTT%S_Hxg*xC;6IGydATrkh$#T&ALkCKt{(NU1$QXRy>bZ zEEl3tax`HPvq54L*_UVd{KGU71uIcBB5gyelJcq^ZGpuFBvRmUX0&XGCABYHm!DH1 zGaBO8=JLTDNJxkh4#c;c)5g=j6dRZaOsK0aPO{e35w)O*f^Awb!xXPA{l`)2 z8)G6H&#e45x9rRAOIa}7KChnc@7O{Uwks}nSaew8df)L>Ukd{hTbLxDs69MSHBP$o z{@P5)SGX^+xf5)9^n;u6r~9n74q6jvDn2zP#edtG^_tB>YVi=-0?kx48JRqLa!Ko_ zpM3JPvERl#!>6fy+SMgYl!Jcs(#JIr7jIQPmj61c*v6tYGL#6m9IJVhzYFm%EuUnU zx1Zfhi~fTlUOBF!Vg2JB``1T(^vHA9L74*~qV@Y2EAW;h>qiUqn6YByoZ1oD+eE(J zaW;otsj=e2wK`Gf@XWC~cZ+h}FD0r*-01-*LK&|l$xsMtmoPGsnZ_``N z+(e09~9v3EdOLU0lF@=PME*bRV57#OXO{euBi);4Gc zHOO7k+nS;hNjHRdGkNDEvU;*dfNd)K=Hz28)nLx1O`u@>tc|nv0L2%)9Gs_H6m5d# z-+aerVBjnH8Brwx!;b*Q2G=!aMsYOXnx=3+ds|LMoyGCSuo^YRorEzLx-=@Q=vOtG zD?v`0(Pyl%Hg&LF@kxbQI-|IC#ANq+4{_A_)?VGklZTt&Yl023EUUT-jtz0agW2tH za(N^nlWxuY>U?A!0$a0$AJCp)>kRf3O97RR~2@mc%0yV|fxa1sWE0r0w}9 zt8~Dp*{>E%z8_E^G-PRNm3a!nXeEE#Lh;iHeCCC}!Ri!H$ezUAX5d(OXVf=umv~O+%NPGanv(jZ81JqieA(s;s6Ig&<51}W6C1E zkp*9Y{pi)~70Ptun1^`!cgtDKHWnQ1M-_2;H1^euOA?K0)2kTU@`@6=t`%WQTVnE= z{f49vPbh{r?UPus;s5Sa(Gn`Z+?pJO8C?pBkt43$i&>UNlO*cr@xI06J9Lt(?gPFM zVq)}I?z2=g(-gv|NHO-J;ZrnsOT|xy=kmsGnWM`QlHwO0ZKNh=VbHh}6;TVVRwiZM zDe{d`RuS_ugP}J{cxi79I3zhFPOWSE*kv{Tdo7JFM;qM`qr4OI-7hmq{jzbCRKoEi z>ApddnW~5MKSh=CM*N3$$5x8J?`sGK)tb96)Mg~oae)I#3rC<4q9e#3e*$Cv3^yNB z)ij!Ot&4b_mlTA)-#=$xcgoF zD3|GO>#U+_7G${i_mdN&PO(!C>vyVori5hJ*keYZ2|l)E6Oz}$?|q@>D1`=pWFDpLjtiWX_MzUvh zUXRw=FUGx4&4F5$05|(_z57MrN^!}95DN~S+qhL3N16End@3^=CzVl{W_#%KUa!um zMNV0#4uf$oiUb2Q;20z&OQ*8MZ%5v*6uz?Iu;W~W2m?~gPE6K6gd4qZ;Bt50D5h0) z*k=Zw$sZH>*u~~CL+bP|PaXr6?3QpVW`^FYkGahWOv@qHILJK3Pj*}>PP`G4Y{s?V z&Iuk5lt-Pldwpkj@tf6>qkZzr*cJh)-B>8A`s_&}!2%1u?N%PUk_O9w?hrH1K1VBN z$oQ3PDiD~h zT=CdPHmLiIEvkE}!>ojpD&Zu8&G=e@IbQj`sBQ6*{zp0Fgfb~E6CEM^zy4FB`x)R7 z!Pq@L@XHTKL3*u4ay56-ncF<_d!qcmjJC&T*^ukhy2=i9%1z z3R4~@ER;y&V{zK$&%rH*tH-9VEb$r*mj{eOdx%z(#Z0$k9@)(|Lh`*D+DdGH75z%1 zqwtensi|zN&C6Jtko?++_CU>jz-W58r=NbwSJ2?+atE>0+O_r+GI?KqR!1;uke+EU z4`wOxlfoG+`YFTtX#SSQ-mHQl>Yz>a3w3Kz=Vjz@r36l#+l!=+$`EQ6zS&4D*~O)| zCzn*hwck5+n)No5cVm+k59Cl9ZP z+g`e7zInQVI`dq9f!?|*G;}GmY-`w?=6AqlA>n)Fo&g)F;3@CEj-%w@W!v&oZ_lCu z=t|OVM{05WV=nBvq3{$Mf8dfM3job-qA4RdJ(Hh!Wb|91b&M5(XEJ9|=IP>wr=lS2 zHC<2j;WqvCtz*zMRuM|uM%ZIMEf>Mr2C+7*ZQk*I`Eibg17S}t_xx#K({QQ)Oh{?g z$kpyHs`jOX~`Wg-P(9-GK=%0Tz?dUuQ8pc19BMVCT-0H&uirAPIb=LlBEvOHVE z_j_NvQQXX+sjOn*UiIw0l6(VAhE73Tcz@Q*wMze#v7=OXZZz(D&h%Sz8yolgUtXR6 z6z=py$&>v}$Vtp5BuuLl@pEV=RU|OP>g1HvDd~tDUI-%nYHCn8B}yWu9EYn9rc&Hv zTUghNQm(cAA0UiK3tkyJxAP)jipoHQpzwFfMVGcY{!pDiJoKY6GMa!Nnnl%Wim-VG zYAseY_fxD`24@FeI*Y>(-wExJn*EtNhtcLl!H5J`dK)mu^Kwu1>#0b>JlyT)bmo0` zyGwA1!!C`C+bG(eVAhr2MF*Vs5(_QWLq=M5_|Kd=SgQI4?GO*)lzgJ*AVJl|Y-pt< z4n>Li3yF|INvQmW=o&<0-}p>2SC@k$*}S`7c0>#d!B%RG`cu0{O4@ZFmm`&cRZeU= z{tIIPSm4tCZu;$49Q`TOcMi2ccI{g*eTBDBoV&R1i~=gA04}qGJC<0Rr(;9oV=*%> zGl*L+=BzaJHRSQ--$t5!N+;ez#^Y`Ypm?RFfU*5bchUZwJ{h+rpO!F5B#AjDpmxcs z?XDsv$)nLP+ayU($y0LRX*Sn{ENBIJV|YnS+AIJ`*n-#wi{ReLO*DO5U#;u5*tqFO zcsCPKHYvJ1tp1Dn@XGZQ`%Qz<4Q~sT$*X(Q-s`th2A6La97^tqi%|C0Ew|SySaR5| zWS3IX!B127HixT6zRgp}<`W;=r0yV2q8eK!1quajUt$sld}R2w|Mn16)TVvuy(Y3s zD4C%*hu&AWrtf^=4u*i*)tu{XQrkfk4+$JYoyjScp5503_w+18Gv-nbis znaFR>r=HoS-2OJY>EiVGw$Ic~YD#m4UyQBRy2b;ex5KkP+XzPvEKnhX-!;RGwciH| z*p~z?FZj9oU+7f(SyR5Hj2=_3$;up;>W?0+@QRCPVr~-Y!-74DKjsRr=uP^qvOzy{ z|0TUVI#?IQJEn+pAq{98=^fQ;D*5?smUx5~cl5D#R5T$|wFkKT0A&cYWe|Fv!;d?l zF>u7=VA1gMn^KY6xH;1~ZmfNddRQ?uOFcf)`=dL^Es~?@vuZMzw&Dlk%j`V_r716> z`UhV#CJ>J)txAS1K1!E~=mO?qb;^xg0>!8ignD?->6+Qt&oRd**|5=Zg|s#Nz!CNk zVlrIJJIxBx>7CP{eN(mbF{(A7SrW5f0mWhpC=V~SbD&IWN0;%_1)pbC0oS-TQH60_ z=r+209%ap#CPjZg<6ygOs)Zv94`;trWB{=+Ggx!bO>u`)mCUzT%h|8{m-bj&j|#p!zq+WlycwGYvEE-{fU z=WLR?+Ng5Bfaia8hql$KFpY?;$%~pAk-F;|&=DUX4{pV=<@mQLQ-a0M79Wc&z!o!ZN0(fVrqgRUUJf!7_JtM6S0DuvZI z?tgxkC4ZJ>I$@futonrBC-*dpEOn~JnrFs-rZ#9;a6yKf;ABwAjT{&C!faB_X^j=S zM(5Xe#@?N0ky*Bo`*?h2Ok#`>y5;0%9U7jPVj-2D$Db*6I5x7UrukeRKt z?XcAC3KsdSvBmZRsyrEfv{PcM10~_EnKtx(Z|KF0EOSWe?Pnez{u}bTA$r7r=Z-09 zU7eMmPFh`joQpRu{%Px%ceznKZS(uf{4rAs#f9nm_v*+N3PP>Bp;5-2%^&AS2!r4gbEXqN z#G+Tk)U03e!RDWYdh@>yocID^OfM7v-z)vIpW8`EECqiW(QUN37VN;9YjckYF7Bju z#44{t%uH6S^lr$N^0xhcS@EnXFQxMyi8$+DgOj&8(n|vqqMEwI-O{QPr8ET=oaS*T z!zThB8Pw-0MI{|)GA}zW%{}L|3VWwWW;l%NSG2K@&xqG0Xu*Eq5f0k?sZe%G6Ot>( zQz5ccfD=$n?H5cs)8j76^(y|Wrx6#IwBD(GKN3eV?ji*qpXC`=)3h{KP@5LA*KWBk zO@0J$V7_XsJev%s?QrcUvBy5DF0M586b9O{e(Qc4cAQ|55x6>N(ggsI9SN=D6ze=RfZttzI)~Q>1Vz{dfdtExqHS z-1S~fr^3U2g=%~CNtnS$ISl_VB#Ok@74N(Fz2qK+rT3?!NWYbUu7@|7${2Zs#wbjV z&zF7YHq-miw?aGWWddK@Ot~ilSqwq6J=;^}zX+={Sr&Ug!C09rd0yGj@?%XxtVwDc zH)$PnO2hHx*7WyC@Vp!Nu2C6Q$~_c+HgX!X3sI@@wB$QU&$AKutn2|>P}5%PJNfK2 zFDxJIvZB7{(=BVpbv7<630yYsm9~BMsUF_IV+TV36-)-+?JO>^@B}IxD?MFQ-uv2F zx&-03@MzPGNtH?Lm_2AdTK$8ve8^u@oIZB52^7IW96nRdA(j)Kwcg% z7W_&`=-IWFQ}^USYBXY!(*8>2r;JlWqy~EyO!1+EK!%<@%aBm}BaCrt?YnB%#kq_s z_8SWHcbNKUqrRntes@$*pG8F5rM?8vtl~esDaqyyG7+pR>m6dF{$sDpe2Lt}rUboSat7#Y4`^(|CmeCI5!R~#IF%wkw$)@ zYFiH2d$>x41R?J-E-x{(%gMJ$eZ2(QqbMH^8Z5jw>B#P9(?Z1l!w{@P`*ub~tvxed zIC*)jmUwm4owM+4c0&Sdr-V2d`p5C)!^H0MuU;ls?6v`?A*8;3QGP25hc5c<_Li;> z37JM2B{V=5`^CivcMS?~;g!y;f&qTxcTucq@i_U%enJz@gOBipHQ3kr&J(``g`x+B zAYdv^ojZ~J%#6*6ByLn>*CQ^{WnCG1pnQC7FmAW{P-h>;WWxA=(!(7h9Pi&;aunIM z1&+^ronO!X#Ov9y;K=y;9SB2&u|5xLIic-=RpV8OYhma|72_Kj(5EPgh=lxqlr|I* zR%l6sv7brS3IEBDZ=Dm5<@Sj+K21uKk~a`Oq;zR}Xwmn|(sK)}eQh+9iwi|)>V?WS zczrwQhd*)l*-dG9Le=;B^~giutLF(UFtfi|YOXl{jLRTB+_aTF8$`utW%T><5BPyx z%|l1XX`rzjvUB?2fsFrWi&;^T8TVm2*IRODjl6W5M|V-{PCc*|rsJ72^6V^PX| znAiKEuG`-L&ZOMZKZmqj^g-UL!(1E3%r%os_JfhboC?eLqZ$LdM*p--S7fvgX0Os3 z0hwo)q_TUu7Yx0W8>vYVc?OEgD3{NUL+nn0Jk$GL&uWskw`3}VXIL!d>iCDbglXA0 zSVq}&o4y|=6Hz$;r!_^Girux80{WQ9hT7BX$?ju6O;7cm2F+0ZGW1G4n37eeuu;- z(Vjs=Qhdbv2!Hi44Kg4!N@_We!&D}9P~6oWN+@LfoU5!4@LRQ;GjSBQ&Ri}`dd(6( z7@Y?(LEA_aTDSVAktmASZ>p%uJ%C(#mesH1rxKI)>H=-wtxolEwJ^MNSanQJ*Bcq$ zb57=4D(W&n;B^k|?Urr1^j-rq+kO41xTzPT=L6aomg_R!x4uBIa=s=NLY!=(ovl`p zky?bcg<5|G3ualITeV*;4bD>D7gzDO7mU0$0kyw1eH7oWh`rzV>;!dK4f^Nw8EYhO z!jfF%NtR&j*oh%O+~4@kPFXdV-_%AhxL@1q)O}=lGv0oDiCH1bKZJ*TA?qaQO91$h zDxaKP@a^&!t2_MZ8rB8rOn0Ui@Bg>sjL(cnO5bF?`v+LwzWu9@(xeDoc86z#5A!Eu z>3<@ERUKn!cPEITtdOOU( zZ(J&^Q*-|ig}2h-RYddMD^#r03ng#FXMaAXXgfHj!;fMleeu_zq|DA&9pJY+XS1z$ zn+!^e7dLxkpS{=l>s|87qvqBu@m?SI{VCqb1fdy!{hzkbZtC9DWn$PEn<#4IL{sP~mK3}vX@?J*w(4cwz$Qe(;Q7+}n+~1^ z*1lYrXpg`PJ+-A8c-z@<$JOLvgR=jEUBr-@0`_Vw3BkoRZsT$hAzpKAP{zgzrAiTMkq@tyv% zp#vX1Dv-xhJnNByu~>gul6mbx**eavOOn9!y_7`nE4F_%>TuQCO934BSVKL&;;IH} z`VS*3W!zNBMCFNWuO`u4)E`vwA4bIHoM2);i^YfzZRq>$H{ZW9ZkJggHDx3R_Mk7H zXNM6wZ9pPYC`(`^Hi2=mzbt{Wf87qgr+w#URVXY<8k2dGH~sbu|KxIL$O0>H^Sow=VEU7}RF+g(|5CkWPL zc26mdy5OI@%gneAFtKSTj*9c;qcn6WPkB(mYY;&`?3ardC+_p^wGmc zc>-b=N+g{e&ugk*Z=NzHoDYMG$gneg9Twq5pN$U^*fE-qUXy{1eK-8Mstm`&&Jc&? z=K$FH#bb4gP{X+sdSDn#6BDD8*DWM>`ufXzAvHpLmBoKU_< zqHtQnZ=y@?RRLw5kCo36?wV@k7#iL3#=38il)ggCq%$_OG?BXxmCBkB{3bqP&NIX} zbgchX+ypexdFWiS()QU$WRa?|-=V)L(f6>56mL=mZ?+L{!#35Uh@UjuZo>AAF`@2o zod(We9`#_O>oJ?4fj9ui?9c3rWiLQ!7yC!eWY4m`(1%(iE+XuO^;=9G7opba-Z|6C z`T}gR&~9S;0B7BQJ*H6UE@)$O>g8aQtADQUv}H?{a=R0ejW{_$j0JS8hDv19AgMf9 zj%p)4^Ogp@E6N|wwtD7+$#_l{C8bkNq7yGV;yjQXN9*>{rK{!zr=>dqBlvTa1)N+X zUf`}>O9)eYy}f9<;_by5bJ3XJRxIuLr=5-(BJa~W0H+TmS4WsQM(`a9CM{h;#I9W% z?Rbw~so8f|g@2pUHhOxtM^WP)4R5+<{yXBVcbIR}&&l&`_0yY%k}}$7MHol;I+mx< z0*{q7j;ReRrCM&38cBdvTw|guAwmmzNc+s?UliWHxqfS+hVE>mJv1iQ*=sAY8utg! z1~^Jd&r-xY%|(3;!Y(zh8Fy9{&zAX%gE3h>lGP*ECynSdkJsxxouqo1p={m8g^k2a*qDLYMH%7aq17Zweb3E!a8`0I`O!(a=Su4)qV>IjHHJRoe| z+FZTBzyiAyF$H~SSW>pBSYCLRE+(?M#c}LY4V9k-r z-vtnpN31#x(~0Oza)I767W5bZchSTp)L-+CD&Yz1PnS;dZ&pDF94n5%s? z<5bT{TPsyBe>qD*HLK1-=oFLFUEA?AcSP75tv4`9YhqE)tv;>SFS%-Kr7-*}q~EH! z@jJUsP+RGV-%YPv*{A^GOJ82W>#X`gTbbRQ`|#yyI!^i-cN5en{(N2a>`UgURb}Bo z0pA8(kHv>dko6Z&Q!9HOpArtlj=j$K+OTCH&zb{9M<7h#hqBn$A^W`IO4tQKvY0rE z1kV$ept$#I+@p#W8Lr^#=bNFAW*cnW8x!+YsRR_BIWjQghG_c|X#j)1D9x4!G5L9T zuuv4tH`>~|hLacn+{*pUs$ds;5A=(R{=}uKvPJBw9dxtAZ=~`|_d9#si^+K0tm~We zAg(fToN7c9j1GUBYWP8)7%AiLZG*GI=;zRDsC}!ED)54X-FCYaCQ2j7A7%LO8|t z=pd(+&e=kdo~jb;;A1JnO0WyFrrsu=lYLOh&wGYGG8oLhu5%CVf^WzH?Yq^*%~sCfuNt#rp>>?wBm zvxj>YZn~vWc67G&hwRFXJelr$CFhP7dER@j&LL~r>Sn_RthO%9BEac~uUi><`{HLjXpAFhi`KU>Dj^ANE0nY$Ox?Li zpo}sYGAqYc<+pKXp)qwu@52oV|R|MzMkVDIZFPXUfJz{zlK=-EYMW z@PlmjKMch<#h@MVN}T)+%vs ztcC{FgKY3V7%D|4q@jB509}@zoW{);j*<$xu-3oZadO#n&V4Vg4R$EEQhM^>OPR%I zXuSO!k~jO?E9_6La~%CAhI;ca)Ct|>#hX65J6|rcPrT(K5d;YyQm%GGwrCTf_FH4` z`$rH-crnTUyB5KP4RHL;r?W2bW21@4`d}$DIHxlb3)6@j>^VhI6fYat1W|@6i~UWb zkh`=40*n43^5gsq&JR}yjtm6@VHqYrHgm)!Z{AyUiM2fmB*5X+ompi?(%Hq?yKvFX z;osI%gUs)s0O-JSSj+)XIBw$xbht0{8z&hb5iEx{aU)%TIPj~S+tgZc-n8mhy%pIg z`FjTEp-tB2FwD-O9!;%eN!B=wsMna>+O{Z#g`h(Q33`X%fC4X6bJ^3J*SR;hh7qP4 zE>=si%_;?T9J4ug%Ymxa8Lq%%+ye7dACE2-b+At<8baJz?U2-#n@Z+%Wk55TQI^`g zTTS=GCxIWNUQx9w8MW z+p$csh7+Y&d71Rs>B^e>V%X^GfLr>~4?=a&mz^jaYfVxxP+D(WomE>Ujpyo)@hII% z4%@vsZTKS;U!%{+<~b#@aXHjR=k?6Ji*_C};OEw0;gNWGdM>oIsM@)(v~|AEcsu%6 zJb0AM+M?Z*hhiv3D=E$=3=2;~Rn3X!hT{%D9Q&f#g6YvIW6A)^97NV$rk$s8(d9|1N7(z*Lg(JwuHgkSHnp0)|U`ZdR}Cwl+x3f$9(kZWb_Wd`qXox519ieoj(Xgsi=n8#EBt?dc6 zQHl-QKB+$u0_r&b-XYbzJuQ^1c#bhq9gAl?sDb7>B-R0bmH+~?TLpX^A7HqWoKx8# zDqiitBl41?Tmay_u9hq{a-44Hj`v#f zP9@~FiMU-%>f@@?FczAQ94^~&&gQZHe4QmV(ifFJc4h*~r)vgqRoSn2+erFm=0zO8 zC94g;5xC>wNfk03-w+tWr zj3#1Lt`Im9vw7PHMzx4j1Ms81@6GG!_4n6$#3#w^csRY$Qg}D5s-(i={$721T2a#KIW~jr9 z<@2@&u0b$s>BQO)>PEUGva^T&IKL<0gOgkDZ*!mUhsws>WoPtn&i-X{xE?=3oo%-d zf60t1F3YdWB?j?>ufZyN$$ap`MK{wLCdXsuxTv?FF;L-Fs#>p>r@wHVj1_=be4u#G z{EsHG$W~$`P@bxbNX2xvWCU0hFJ$aE8@Vv^b^N%p((z@8-+MKKt&B7@L42_n{Mt0lS{kochzEqE2b_F%{M$$|HVy^3|&IZC9mJU`mg^fjdq65bv zz+RMnOh~F#3}H`>e)jl5O*v!zVFe2-8qVWhu6P`!e7$BG)eHn@FLR8CuZ}C9G+iv` z?GCUrJZq-ITUoQv?>?FH555rFW2n%)_+cW7)Z4?{Emycx?bfoKJo?#a`H|&gQrQM$ z+A}*d!xZZh`%`&`cp}$`BgY3X_-^dGvGOqrhgJ&GjiwqV+<}9MVhsoXva=P7bB3Z> zwxb5}kE{Hx`!-@_oL0#SJ`EE;7;9X}&+fS-U=22-j7w8mcedwD6T98~i8lsfjz#b6 z+hp}FsCiUNyIj}OZ9IHRAsL8+5RIev7XF9h-igRo!7N#QTMR>1cwg2trC=SDz9VvL zGP^xdt(DB(pC_u;jWhAH{ZyRP>ViT@8Qvp}Go9bs(=Sv|L*E8)5`B7rSi0$eyb5E- zKC37XS$qF1FlM8kIKfd~DtwFaP|Ohk4q9-{&XO=xS|z;4@CIdsnon=6IJF!+F;p~% zudBM!t!jvhHs*~)r~5tghv}5{%RAsc#y=GQNWX5=5Bn|F)~LoADro-HbE*U=aoZeH zzDg-H=%(@+&G$R2QPF(MY=+-BG(EEry&)os|LNTlC%*aQULKCEo`tK+n#OU$kdBl_ z(!%(hpmz$PLS^Y|eKGgf%jH|}h!5UdPx0a6s(m?uO5U~hbNxDZN6tk=DEO>JOrvtl z#UeduBYBmxFq4)+O&SwHf#-YHtlW)lF z4lIrI)Wt4n9{EioDRzsNdoSfczSdD@oY0s?ZG3DmL7IhxgXb?EHU6K_ow0o?vFv2^VPnC8HQ?XBf~G;6h1cf1fTW4K z>Zb3n2~oT%3cOGq!=Q!3<8J>?+v^5%^hB`#Fgz0*ZvJ&c0yM21l!m={0yTcfzk4L< z6nOX#;}!s>}7P`v??X4IR+ zY1FW1@q;dPaa!NRek}A-2=Mz~6f&mwy<0$zNmGvNyFI<3mY5vZSlW0enWtBh@S$Nf zjeW`Mc3G)9RLunHb41|yS&lcrpT(zP0LMgdbT3&XrH!XeZ#+^{g*OD9-p01?RLJe- zFre}$vN#w0pyZKjBd2o;ki@5ZKvEQlsBj*_gYvY@CvTnQs2q+b&Z5MZQ}a~<$!5kNl}*E+udTEBHs=7l2X9<3K2Cvr&8%0NBd>s z1*t86;=0k9QNELLcfC6er^e-t6UY{=W*=SDw@oj(6htytAkUT78JuICp87Va4@Wk( zA-H}SoYVCi-zUpwcBWDPk~UsBnM>TXq{c;dRXLsls!f*{Km{W-l~=;N4{`~hzV3N z?G(D=j!KM1+g8Td6_)qV=TZufxltY(E7x=TYtVD}tB%kaz53raiRr?VaZL=2@Z=wA z;GYJ}&$EL<|A!hv|FmDEekG&ei-dUzB?RRhH~-K{Ix?bT4mIY{)tZiQJSY49)$E0{ z6(cPt9ymfTSJBd>If3g)tjreua`{1yQjHyc!|GboG&oecKFGIp%6*OR;U28?4cq(O zBC7_+Zumm9GT#lSs-rQq(pC4V(sp`areH=Dz-yObLb9u+a#Xpv0i^T?`v+0M~N$<Zu?KVA0lvdLI8$T$k#P50EdTQF29C z1t!~{;bs++QHH5Jxc&W0K7?%u=cgizn3$;~L~!)86&-TRh)u@Q|o{ z_Y@?YsFEIcAfId~?DYmOEhhWY5^PyjI zZj*K99fJ;rCME96^x3ioe79}^YR08P5nV)@mg<&W<5g4kw>=&CX6YfVr+zztdNoSqQ${H zCl;nCd0cbqT?C4}KCa@gbEhN{xz=IH9iO|3hs`Rly*CzF1(`Kkj1y6U(uG>(;bpu< z)qG@t-{H$*o=3GJE>E&(P=rzCH^N2b*QpXG4b9WGxIqigj92hE@j_dT74^(}*8Dg& zXM8DK!w1IF^>085`f%&WOJ?Y2s?guEuB^M$YS(7*F3;Vv*cvdx(+!TzEu*7MX}P3E zj3Ni5X6&@m1NFtTuhbSl)#_NgCN34vnNaYH2-u6WEu#mEdT>6x0pry!3-UK8g^U=B z%rO%ucd{7#hL4B{B10vpTd_x``tcww)(*WBwV1^s+Ndh|T085fS~?}39VcE=EIr0c zHMBx6r#RK~nN%vemc|oUzV=pe`9)=c#hv-)KD=_4l-PUxt>WVm<{OJ>Nh;vP)g+tb zz~_&ZeoaG1mC5>wJa-r{J@L#App2ttZjH?~eqI%+@xEj)nak{1N-|Ap(5R`VD2J~5 zINkWJ&3_oH}oZ~a4vTuJWFlfo5?!OA1Jxb0ZtPJnj0w+y*%?N{` z%6|2z;Gk)$J&y9=90WYTIXPDVlI_5=-7ZEBc=q>nO>Y#A)|wW`Xn3@!FzoVyF>RDF zTHs6yYl0eJIhTpP>_-%LMeWi%jk8E()oao&6WgBk-n#9A*Y7d(DqgP{Z?SDq~4)_zP zRMQlxRMzrtFh1BYz1908T9ld4RP4^ezIbsPLX`!J53v*2q0Ghe326ZjrzvDcLi_Ek z%$=-Tj)*L<{)F3fQvsObNB+=7M^AI&hJ2k|61bO}L(!}FhKN!!k;ah5Qqkd@!gJ?l zr*@Ufs&m_HoI~vh z%5VPaX)7A6#l)O?t-x8_>(NEU^sN$NbW+&6EAdnH6%Rs(hmwWGSxP5E;yaDTxCMqc z>O|k`Bc0#tB8`7V3S9tVrJ;D!u7`qM@rZshJ6MAPa8N7)bwqN1U<)tQkUok zWf|qnD&@)+Qkvp-!~L_!9_J#V`fy24>4NNRM@YTISt})s@(cN!hP5>{O{F~FA{INl3_*Xl zY160pE1uGJh<+Kg3iDc>EtkvXE^q3n%G+AOseOp+_k8RGrhp)G>^h?w<}Rvu$@079 z^!E6`VK|jN4@^(rQ*kBY>kjE|A8OZd<1Tx-TX7IpI^mA-rs^9hMjJ%Df3PUDbnKJ6 z>CY)}o-y6LT|;(N0Op*eR{Vyn3$4pXQy`3Kgx;zaM0+W2_P zN}n%!7tJ}eD)m$x$jY4V48-2mlD%-;$0lQp1ClLjG_sXenB_tiG~ZdaS;}h?C6MFm zk!Z6;+`E_B$De%Mulmk)K!bhQ>PUU!aLAyT^=GPdRO#W!=!_kFWQ=Xdn>9 zu~y-Rl#mIVC_uW<$gmssup>7;)o7{SFs>;+!~A=M;dQ%)<2R=n_s;I5aG*>BN^lH^i=x`MJlcx^tR5& zC5>fXSyqSrkv53gU?GR8#iskOW|v2n4%A|}d8r#;sTm6RpVgGvI=FScF8Zb^=zLQ6 zh|T~8>JU6qm*>JLlEpHteZbH9@MG$H7d!Ocl7<9hJ#zrk{ku>;ARf2)PAQVu3s5kg zUn+2K8O}quY^&T1%^_Fu`-8JuVcgXJJB(23ODzO217D6T9N{J{{Ml>PLFQP#7bq>; zn@Y!r(k3+O?H^;s3h6X);HpDvKPHvdE>^34D(_|Dk|9G%vaK0t_wp8KkD4cX*>W`I z;bn?C>_Fo4jB*4`G@^-VU-g?+@9=AK=g_KlFbpzhz7wtu2>#e+AOR&fomle{#i#w1 za@E7I!YO>mrD zpXRl>Wt? z#O+K7Hz_NHE{l(WpSc+MjqRqz?0{0PYdF7x&i$~}n$PczriFZheH%MCt z>}(29^p+Q5`KSh-|5qemAXQ{5Imt{L+69~=3dsGwnv8G8M=Pp3&Z)I!QrS6@1^*Jp z22n~k9+XD z5oWz))l*ijDr;{q%Y9yImoEH+1nu$?U(>?RQ}JLsnw((YY1xIT*6O25Cx{)80Q#s{ z;c2}1f4n!agUu8DMaxg~6OzQ!HGhohxtnj`b7tkJ=L6+6gqp6B!Nskh919&P)@R&kGBH~~a zGrmG@zN7*PdfL>&b?2ZD%t3=>HVPfuFn8BazC_%jco8AnsjjCalrsWTA7Y|^Fntat z^?g71ZXSUPSP9mu%h1tMyG+VR@O~-q`m47pe z!-;R$LVQdY8wuL{tvWOb+#IUt*fl*ATA}lKUVRRPpaqtfV$Q~IY2Q3D5`issj-g$Kp>ODE8ZFf|BE zvZ+|a1)jH{gzue4h%r&H2WXM6a;G7v*v;&p%9ab)awK6F2a@{Xw-XTX@NMetb2?4J zc+<1JQ(dCnVSzcWk5&pM^=9t6jWH2Xn~pC(MR$`7t$n!FhF1;yfCEi6S#!KaB}*eg zC%elbNH`UMBkK8}VuLZmFnC0f3IN~Fqct2$8KVC6Bbm>s4Z;F`(D0|7T;tNHdj5kx zEnrZUK>IX%>uqDKwiH6R0J&lgY7psEvyQRt%yP%(*|*zs8~4A}8ag#sP#!CzQ6Up` zwR#%rnVFj-v=&zgUmHyxhK{0eMtUSSrV8wrc+|s(8=u#rY%`t{U9LPaS&o~y5rFrv zK})*)l;bpomm}Y8^pL5p3z`0ush9;i_S_@lYIcamk*lM{)BP9Hc z3hE3#nnzrlqE36YI~o=M9=g?4gkb`LTYj@wcrA;;2J9c9n9SBgF{Z}o;1^Kd6DW5& zEtdfWl$z1d#>q3s4*3(YVlNkaK{afwYB5&eQ~0D=WA65o>`Q_x1KV(SxL;V0Sg%U` zYTa9@Jv&ePMNSXA^jSUBqi|UlD^{7)M}}xJl}{eq>_rv=?g!Jg<&h|xpB=sj5dwW= zs5Swmm1-&d;*Jc(nN8b&PR5NMn$Jokm&bu4*NW+4DV`do&s%biHlqE$YzMc*UGmHf zY%^_eWpSUydsMFI#daA=7vu{i&4ixCPF!#P{cXHHDcgqC>%9jm6Pz99zmNMX#zvs6 zYVurzu?&5Ty2Qh4s`Jnp%;)Mkq-B%7##jq(Pf(nDZ!;wwsTN0mM(=d`O>I&yz?@E9 z_dg5?HkE6PLf5_jgE_}dKXnm}&Z;c9j2k?93kk=sT?y$dlT<$ylU7h_;`5FBA#!|+ z{B2Ix*Z1!tApS0rdE9PcWF@8pv3PQdqxfe69hS4qW@|@OjayVv7X6V#X`r7}P>M0G2hJYIJ|)b6`bu4vJKLC9 z*}r)HnQ5@%D9b9F8I7(W8aXsdmPe=mYGR$N$AsF@WfPqZE)3H^^;Dz&CBMb9Qt$b* z;2iD{wq=osPoLp`Z&k%?q)C9_zObRCNzk0U?xiC|VRx^5mV9mTJ333bJqmbqv3=l( z^^txr^JdsDa+dJ7`konYdwhdu|s`L+z{Bz4Ns}IHorK#75;eV%ZGH z_?xKA_^1D`v$Kqf!t2)fAkqRN0t!fobi)iC0@B?L0#XvvEg=m8lG2?+58cuX9Ygmp zz(`3-!=3l8yWanOKi#$N$FtV?ywBeIJkRsP0@hr7nBd|W^Zb6m4(U!$)lx7OM#Mh? zMm-EZwaTt*Y(dp|4Hj>ks9%veQHo3jEA&dJOut#h2f-2=4WC&C*7aadK22~*p8sBd zM9l8gv)K?8^wg0pL;tFnW=R0TH(*VOjSwurgzU4tf@aINbOhq@=&v)+%bem8(2vvi zC3w=SFj4bXtcD>9Fu{;ad*9U!Tjnd|mQ~dWRU7LsfF9n{m~5M8<8RJ}J}$;=?WpSl z_1s1CEetMgCdIZvz|oS>X^{m&@W9c!CUI=YW45Fu$C9id|ONE#A*o~}4ZT$m&?1!ovBQR3s5e5zx&Bz|B z(978@VnjJl+1}<`?WB#<7U~(H7e^RVJ_y{T&H5(M;uQW93ibZ&pjziw{pSVcDJk#$ zFqdKm(N9e!Oe`9Kqc11t(6;Puo2VT4>Gm(xSFS5XJdZ>U1kDa8a9eFf zbaY04`ANvuDGn=_x!=8io)Ko%Y}wKB1ac%%s{>kEWf!p>UaluCdLRK`a zt<}H`{kio5R%AFqcD4|;&>Bzp>e5%GH^g4si zlUlp*#tND{weo_udxMW*E68Y7NYF+~*qAB4q{q_$Z#XQW$3V{2+r?nPgLVy6tK{d4 zMa9f?A$N>EROFbEWWtlg4#7sZaLNi=OxIurzZIUIsl`dw=jG+OlT^2}boh%Sly2YT zXQ4uupauV-6$E2RchkV2H-~euffvdpn$^9Uo8R|wg3)M@xw{SdM^5Nzd|KkqORUB@ z#D|HK`G6Gc=bmHiky^6KifhCBTz2l+cj|+3Tj=F6a}50IqKY(Tz9@P!(81lN)`RMr zTS#&?Y2P-IiKEG;us#euQnoz0X`nY+a8I(qQKr#0NWN7`a9kp$m__#N7YQIblP24QJQ5YjZOTW`r`8S>d zD@m(%>!OM|%qJHN{v0R<-=+pfMg&yCU4u(lVc|jO`9l#i z$H*D2F!AG);jeCkt5&9>8g=sOtl0ENT~B?otH7n7AUpBBbYJKEi?kM3nd`ZKx(fkX z@G-|z9|6>3mW36+xMS0$w>snV2CSUbP|kkCaaQ;>^vgmZu}+qE)+b6x zz-EI;5(xK79Ib44wL=mn75;hJlrA*$Y;fdVv*l} ze^NChmay})My5nL?E>69SLuMt6*8N))7qYapxptna$cvF492LxjZNyWh%9Xd2zgOyu5aXoG)XtL#N^OctbLMKo8 z=tY&owPW~kNQXE3RphA_DCM5qutd~qBK}xW;EarjwQvgb|qq0wpNkL zFHCio$JAMci^)Vc2qGGc`tNsix{UF##W~kTMq#5?neo+_q3+V3W~pAl=lpdlvqwFw zNe9d69N088)Ynf~M&tklvT&h;q-2lLn;U8~Zrsn57{>~`PZ;@XI@1h7_i9GT!*TK~ z>Dk0E_l(ZcCPd^JT|&Efps`{%S?;#yw75V1$%v5KdMQiP^O+fy4|8~&o%>BJPb5UY zgu^&4k*gEubZDA#rj%9f*#Uv==rmdWV4KhqEO#Fpg+AsM8>Fn*lzLcn$1>QmU1}6&(Ibs%#k={iI{$YCip?<2ha(xE_ zy;?8_{-?>Bh3?QnIi3~z702lccj3ddLID>1CHoBt6o6Y)c?Nd*BXz}3dAU^Th$7W- zRdT}aRNnblC9(PB&Vlq?V)0iehj<>nrQ`i@yoz}cESbJi2;JiXCsWKGB9^rxqI9ZR z6)mh7-j13hvzK4{z^^P{fU2qKSyEk}{Eg$PV=g(eLL2;bPbl@PvN)rLgrtOx)zc*> zUb`cW+T*rFEvKrO_IiP1rkV1E2wR?efwXC+C$F$(fsxlab8>jD&LFeUi!VEYv+Rw- zZsQ|ry`zhkJ0cTfZlb+vaFHmKzFR$)Oo$~*9nUj42ecc4$a&2OhHUKNYDt~RtFD9j=A6WXaOA#xN-wvB~5aCbxf^i2W$XE)#^(5Zpza}{0xE_r&0gz=5J+H;5ImQi1GLQ;nWWx^dT|59v$(Wih*4ceCmL z(!2C^Fn+SAurOWx+g66V!FTsT?dc7)cMAmc&j$c@{AUVJS})0(n~#)-li3*Z^Wu*gP2Z`6 zgKrF&S(NYRoSpAUnhWR(i(+5ZyJop~Wdv;7M}uLO$0%pF(Fk$p`W(fXU+7W> z9d=PM`&=v194+3-i_qo5wH=Z^?5~fhgEZfzcH}k(nf(H!WYa&B6zHX>fwyS<<>8`q zwJY#WntL5;?zJuJEQMZ3w?@}9K;Y;}I3H-Lb}(S4K_kKSDdZS1K&OkFGkAP(x>$Ux*JRR5;W=3r@ z2`D0eeDg$}E-OHB8QDH>XG@pcU%<^bH9}=BwDOQ*DrG|0B*vyd`Up5jqrN1`Nq7Xz zHidn$k1+LG$`cjNqRGyY4-0YfLTJEveW$Vp@Y)%M76<)_6>_6O_(7oLD1KlFaLx?CCrMtJg`kK9LmkQV?Dl z{1xpJ%|Spjndc76r)4OEo$y}R%nWdhcP!9xt%%&e7Wa1sMRcYcWTK#VzfHnqSV3+i z`=0Z-w%tgTromZ6^kjZUgS+EisnW*IKy)|3jqLQ=v9n-t1(ywv!2UgGmJl#T1NSBf z{?;k@*BY@uZGDmU?GX@uMfLpEW%pq+x;G0({|&#{g{W>B0F(;=kOKrb^1d(#vNxUo zhk^Egd@ZGA1tk^VA^D_o*o_4!JvR1>t=;BTwp0}wi_5%#D1IuV_MQn4(eO(3Q_g(2 zmoXo^5^X5~K$zF42R#T`hh1PwUcEZlx{Li9aCmck5a7-=w{fD% zBVA&=&|3UtPkxbWo%N36K5ojqQ}FjYF0BFNw&S78M3v9(MU$xO`U9v)6*M4)tmYsf zr6HQNfY_rb4T+Vhg1NCQ?j{kiUmTQUoZb5J%ts?{+H|Q7MnyqdLK-8ax5FiJ`=J*E zZp6}RW8Jlsb3eTn$*E>MnvK|U=anX*CNdU9mHAbIyS=NcT0fBT#|oIi6A3NL9PL(n zwqY6usA`JH=w%gNc%~{IO%-0cQ%al&wbqrqJ46BaOjdvKX&;uJ59ayjaa^S_wom!n z2}P3=*1MaKIb^T6*J76acn2cDS0t8d@>Cf@X6R2p;~)a619X_P^V<9+G<=$xbnf$D|M}d`65AWGR|^i+S^P`bQV9 zOIFWkik&V%a4+x#JFU9iQ}>O5ELX&L7u#~G+@C@A!%ic_LLE1o#hB zy>xxawVeXtSi*gORH(|=W~L*^@epnhe`}{xN@6_u5!U$|PFX3%@EN}BSWGEA4?B$T z9L-s-5SI6-tu)z$ZvbmvzwL_NK`{WWF=WTR|3MKc@3Ta1FOz&e>&&N@)uu{N5U;r| zHbd@pd%kZ<5>+&oDd*XRQT1CQY6Gb$ntPyaLTaimonHLi?jy(5pn9J>wR9-Hoc(L6 z4K}wHqYhW={mI?3m4ypQyP6kX#+>hRhg8xF4NZx{#9xUKLBI;g-YR*(31qCP;xnHy zZj~A3SY7|k3622Rkd9;iNimf?gup4&Sxh)3vo{>ryRT>gz=Mt?Naojn;NjIhL&X0v z&fv4~w@u!WiCMdRu~lZ|gtA2xurv$rRQ-_(?QR8jyr6yfEEuu!(lbldaccG|duKr7fG=RCN{cu)7Ym$<@(o@Hu|y=)JcO zxC|^~>?hM5vvpgozIaEPpA}l4)H!4F2zZu2Y?F)Jz5mtnyWhu;u13Ivg4HV9ww%Fn z@n9}a;lI=;O$rRKky$q`S?+27WLtK?;(ij)XX zlSgxar&=&kQS#*P=4Mk!Pp+=s^>5&6Xl8fCcI`2eQiK)VR5w2;lBP;P?$_9l)l& z+FimPXh}IOglEGLPKk}I+m9Z-nHfavw#5@{!)~NKrLR5NPj_C11V+C}AU6Nv5&x_p zZI$(qmutr^x+UCM!JutakoAMh8!X9+Fo+ys==_&$VS4FL+DapS5d7d|?-`g6|7KOx zS5A}+H>o`OXZon8Qg@`7FzC|PKaa(Q?|a~9hjYl z>(dd%9!XG-)aH7Ee08e_QGDy|pu54u?8oS|Yi7)bcA_*+kuK~D>5{iRBPd$|iSrQp zchsjxRlY*82?o0e&0kK`wVFxT_}s?b#u08`pj49qgL2(^H{pm_9I7K_GF`2qzfG-ltexx<=ToBkM#Fmd z>8YUbSoM$Vdk~|$ZmQlXeXN9-Aqk)0BK$L%I9V3cD=|w{x-jpV!QQtgK%Z~g#93%M z@v{o#EK%1P7$U0ih=YMdvoUf}gI6ScIzmOgx?cL*ePHK6;|MO`i(M`~LY|JVWLB)c z#oXOLxO34`SC)s^!31MdQ?qikv zoj}~5oCn>;D5lNtvj2_lQO5Ir=juMYk9zu*Y5yJgi?Mxj)Hi9qxLe#nuReE3g_T`- zFFWfKLjS1?yjwoDaMgf9{rfkkSG$h@E5QJ5&2oK$ zyrpv!Fz4|MNpkO!Gp-JQWE}4y^1Y#(_+~L78ewIE&!95_>VGPudRFViUElpdH`f2F z-zXe1bUY_p9ur8O(0~-?U!qj9ipERf%ei1NUX6=~-8|`RlM30qvT&K4l2COvxA^$< zI%@*#Y%9Whv$KEKS&i_Ui@sw^vBu%{)4ACvVP#`~*!3To-CT|S#r11#pCDE?E;gV~ z+!!m@5NV+D>CW{6v0uHkfrjeSP%8lp7_G*CICB2a1C!!Q*8z(ZKDpjDlSzqlO-XdB z637C(_DHEdMMT3UgRl=Uuom2gfqkMrr-a`D&2kRlzU4Fz<&XWccYW$UvF+FO{9xPo zIcG}p2EJ0v2|w)kLO+`?fa|8Z_6K$?86MQa_=m+A%q-@a|53?e$$fi!Sd}2^F z?k>{N?Zg4q_d}>hnIvXv?N#uO8``l{S! z5bp#mywF5c?hv2SAhT`PlZ4v*9$u54y_2^Jf@ow|(sZEn&mjK9*z7SH$Di~%lx=Po z0(;S6W{!4+rR!QaI7-6{DDi(x?+;RDZngdG_@X$3bPBX(5-)j1?j32;v-XHqgR%k0 zW~@P)aC4CJ){uAQ9-Eku8r2`luGHgGjiQ981hFG2N;TKtV^D9ve^9( zZS<|vuC#X};?Mi~0R4DDNk;*k7y2`QL*v?LBM)q^pcuVF0oqD#R%I=}_vn5`K3(h` zrNg^CQP1jI$w=(0y4;OIB!QgQj+-9=O{NeHSurlh!JUmskSIW@6@``#lpHIIZN`n>z@D9#$g;wmS z4)Pmc%p)bAm;F;Lp8i{j{Lh}|pRfCW*OdRyHQrjK7ty5OBe$LydRaQgQg?w*9S)S8 zD!4tTd<9_2eQ|%!Zrn{SaD}$Ay9}JS|3oIVXLf`~)O&)a8-9JJ1TxNi0n3;S!?_6Y z3>e~E1S=5{Re~C5`)tNem{Wc%&s!W#_+klIzwdOVs^i|HM=oxQ!Qd|5jND8 z=B~Yufco`Q&Qmd0)bxB`6kpI5^(s9em9Vqa)2#M-0uI+2wOyO=?My-Y`WkmPI(7W| zwmNI@fvjZ*Gi4Pl0{bgOw(=A~_z4u*z&$vAB(8AZE zyz84{JqbL`BiB@nl=ZE{h%FFW*hQR-5bp9U)tjERw0!D@wS^xk5 literal 0 HcmV?d00001 diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.040000-0.000000.jpg b/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.040000-0.000000.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2e0d19d1fcd3aa04701ac55f670dc470804645c1 GIT binary patch literal 50398 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 Q*8ewS;s5!b=W6yp0IAjW_5c6? literal 0 HcmV?d00001 diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.050000-0.000000.jpg b/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.050000-0.000000.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4ff6410b764337e731bcdf61d6f17da288a4798e GIT binary patch literal 49763 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 K7kcTRx&H%N)ZRt_ literal 0 HcmV?d00001 diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.050000-0.002000.jpg b/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/bimba-mean0.050000-0.002000.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2e1274ae6d6d231dbedcc627fc2caf541da33e9d GIT binary patch literal 72992 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

1idj5|XDIA1GcCtEY2|GT+V@Ifa`a~% zwNeawR_A3qooNpSav8XOMy&>rujC`hOQePk*^KKwk+n4Hubv(2!Dhs(M*J>Yx^2g- zSP4OPlsUsJ5h!)2ta_3TFNq_RyEprVr=g0iY@`6=v$nef3*c*38{qYRg@Bt+;u0ke z>EidEQ{+)>b$pMY7RJ&jF!| z_GAC#&^HveCYGp>mL}Y^`leOsa^4IH+dkGut}m%%Yryr8Xb-&US9m+0i=<%#hR1yD z3%|pv5f~~@k?Z%I+s_F;40vE1u7k90@Tjwf8{Z;!3CcL}(Y(H$BfYNVxyv^ft4+&I zV{4YEG51hig`fgQum!YwRWq&`Y3yWgA7B(!tZXQrwq;O*NuUt(fg5H$DHVsF+>a!C zU})JVt<;p~BCKWLRUgzrfoI4z`MB^0L1uS4&+3*5sJbNNv48CsiQbZ?B;Tc5ZG_O^ zT&de!%?1%!7^?IWtJss_l^xy(0c}@dMp7@I2mT+wmFE3K$$AfUPP!$&!#8;aMYr{d$a3<|# z?R|&K@9OjuzMn_F@9g7<+K85dD{xF?^BTNR3g0_c$JUrOiJ zdSSWuMX8x1b}^O$+u!15UbcU7>Fs9jPmvBLn&Q_2iM(R1{l@|z;$1ST#wyP`vBs#v z!-qE{gr|U+wv7`uj&m^|LghfuxpZ2jqvCPoVy}MvY|rBvs{G zs?gSOCB+Z55;Gl+x0FN+e5|LA3^1@sPmNQw{f-vC@h#jl{fD}c?wJ!2JO#TZ%Llvw%LTT)H7+Oyor{<4eS5!4>@+Zu=&8ryiU`D01 zskjV>6nrL?$z}^yjal`No8<^K*vLq_Jaki&SEO__*^=;*q1Pl|-WoRH1PR(%Q@+}X z>fkq+`*z~@RoG1&Ut=6U+sLxo21j;v34_tqYjV^PRMmHi^xgd0cZI)q!a}rNEt;v> zuPv)=09ErJ;Nu9}@qTH8DQ{;V_=gQq*oVxVr>ch(n6__wr686pQqRFq-KYvLVanvA z$X05R9<4Oyd@RKD?uq%w+KK2rS8d5Li^I=V3>KIbb|~5l zcKq#G8|%OFbN^Z9|L3(h3Bx5B+B>mjEY|GD#@4b1*B6Ga>8v`w9j$*4kGCE({L~7M z#P#Hn%Td~uE?8#z%YcO?&CvGm{SNKN#tvF^?~|4pkB%T}yRm2A)96GN{Qk*FlCzm1 zn3Bl%*de4D@YvZVg8~LP%YckR*xT&%^&@fov0L~;-K>RHkHXwmnzV%Q2|UTbuu~aZ zSbr8A$=lNG1| zBB9Ui0W~eIkMps5E40(@EGt}PcWB@alx<90*JxV@xQEBA&_;C=8&&8}7-B{K(y_On z2>>YQ?A8r=_42jnAw5=(!dH%@qFi4d(WHsQctKp!{m3?l{hXCzU$;jtsmSRgH2a94 zUHiQS=khsiuh}Gj9MRv$m9`yL#sS)Hy2ARRiUvt0=l<-U+t?d;@>jq+BNUq6;-8-8-dw>^Dnf6_2Pqq~lo6bW%vwtQbhh zBXpEOsC+iKR|JmozYHHrVRrRI_1a$;Hb>&i=r7{abh2sMasN`)E5A9~q(vC=`}_lR z3fK0MUA26^Qx&bYszVtDLJ&P3H{S74-f0qj>Yx(c!3n;YuqpoJ6Jr63?;j`1E9yO{ zy|UsJUth7=8_RfPyEMF?%^V~%Jwm1$Op-AL$;BzYWTO_KR3scHX1#eiby*L!n}u_? z)V}z&#;2iei?^;wUJZ6qx0qM*M3&P{mB+|FH160mg{n5LaUCseAkLEfCl;%7c*5bl z5NtcIroxl4kDR)UG|=p{7H;}Bwy8_#La|i>L`5)sxc=>k(0y+}W+L@zV;|-ut4~h0 zuzx-Ro*kyepfW1Zm2Ivi)SdcNttIDiy6L*H8mWr z47ARxpZFw>XER)dX=Yc)R!cREMSj13k?@kblD=}PA~}d@ONtkJmvbXkSfB6ZCj7k( zZU4A+^<>;9Ln6izx}+=I^4Iun-+azr8L^a&=C`kkSRByGV6S2l}GjwT2qmhT8MA9GP&nccuLw(T@b0SyP^PRA0iyM ztQeXRpPK~7;;Kz#-S)4{WUfJ0AD4i zBJJTkE17?ild(xDxoNIw?9?BW?m-faP0L@2JkKXMW(ApXt+N6ver?jq;!;LvYjgvm zG1e$Oo6a~P&8#oTTgd;{$u$2leBEx(c3DD3JYt;_H7g!n3@eFyEeZpia6t9fOJ+ZJ zeHB+Zk$XA0=}xOyq|XK66m6T>x|bM#*y?#ct_47XGNDr> z#A?q#Rv6V}lp>@0RNDC3qN*kvE*ql5Ym|;NHkx0IlpcWNbr+Xr%4Y>=@A&EXn`&HA zqh|&w|KPVLSN-}MLo{f7vCWu^5?u#fjv+1!Oz5yHpE1%_RE*ZXbr0__YP-!b+Sf~m z!T6UZ)U_05d*9)~4746)H7SQfB}e*{5m{A(?7HgKFd6y=6^yw~t4Ai=Ta(%rp)R?Z ziLt%T>ySp}w0ww*k}Q!z~M7%}18Cs4K;d7iNXdtp&DTH9BGU50FOM(VPp< z%}=;j1Kwxp`!8HQO6fcs|AC5+NZA}44)4awV>`t&pDo6q8kNOmb(j^d%Na>zFJ8ia zfu-PHS;%l&gAlY$GqTh{#`iHb-ZK`N!We5-H=ve^?nNHEq~reQ2Jb3Bw}wwGfN zfYUYN%2iSmi1i+u_2N?PtmxZ(6$|TjXs=)+;gI{65s>);QQy|fTboY3N%!kD)VKd< zMBFVyqG>4{xR@k|bMAI1VCtyVI05DR_Fdjx!hcD!$bZ(N8mhI(-}*9frRjwGu+MJH zPH`+)Z%%taWsKW9{z5D@V2N$rM_f(O4%&u>E~%#@5(kzmww20feWG0%K5?JdMPghF zcU}xUP+z%O+e0*dMM=DOnMWkQfOe${4i46^taeItm^k3HB6M@I~}jQiq%*z=QiAV7BvBEI7OkqBz1} zxxO_9svuOe_`4-%%+PP!8ptT zylX)<+-fiwnw{h>Ojw%0@Y|pcW+pQrQB_blK`r#8(s!M^E$JUX8r$y%e^Om4&)Dxu zY>-P(ldO<>>SDjM&g{}6-nyP$TnDLfPIUi`Ax%3{rWF652?VD_Kp<<+IhL{9HjYk~ z?5i&vsi~S@I-yH}qt3Uw;nm#Bb+Og&_F{sR!(zs?TT=V$MCxASJSLjSq1-8)uI0)? zPI+i;%U#1a{bha=i1kpCk>3aj-g9_aBzPzv(%0gQ83QlAJU)MG{PJ;`dYl2i$Z1a@ zknfJ%W2e)%(lq4~aR!&L`vdwKl;NjydP3<_ZpG_V>TN%BJsv>1t{3$WfW^Hy<3}@E zfje6-ue3MVeSmVT4!`~HDF**}^7>z{(>R>MCQj~N&&Ts&!MQ(Ytp1?Ntl4*Ypu5sg z8`SO04;{+E0~M)o+{KI8QV{2o@|rW5I$G+%>obhsNEt z(Ez~-E{(fe<23H>?hqijyKBDfZ=a@SXKHu0s$cHyuDbnl&bj}eHj?t#qj_Cfk?s?c zZNz+-BDY!>lrYc4$gIyisdys!iN}sZ~6j96r6iDjbcGFa^nd+SUDc}W{ zZ<(wJxR$1FX0Th=Hzf=Q@Rp%@g=oyUx%M-z(iJhi8)D;O;H;k!{arWdygpW!5mhF7 zz0SddP-&yr}aHWY?JpDC41VX!SryhoBWt0nM3Sy@t1(d*Oj>Yrg| zmzpBmW0Gtlb{<_6^dxz9JGBd=bA&jMxS%cQH8a_l>1>1U2YTgp zJ=?0nIc~d$bTCM8NoK-gt6AWMw$;0aRNgaZ<;N!~GnIEz(~H;WF$B)<2R$`cWWTQn z$mXQ^5tt+0c@B%>FU-*uED>AbNa^=8|Fjvy zEZWwRw8FkOk&5o7UD?}0ssS7hsyCHiQ|drDE(#Yn3gzjSu~iJ~5@tB1Ps;-rMq9WI zy+{SU9A}gUD=SlB4zyh#*HsIThNA~5MnBVQ#6DUEw0tkI-|eEvE-K^=rIGw5^4BCU z{u$R?dc7mF5Knl*II&xMNdA+@OI_rfL;Z9}@qcNa|G(edf(t}NBJI%^$I8)?SW#r+ z>69k;lNB))1Cisqfoit(Zy;NlaG=ajQAt8O`eEbKKB4&&i@w11E#BSN`F`rCqkhuf ze0^%S#Si!&)#S*DmP~l@i@1~>rX}PZ1iGs<1?0<3ayZ63owU|1B7=&Lu6%N{DUo_K z*$y?}%C)`AikVwk^4y5XlR9mVO+%Kb3n8>1YRYIb$m^2a{6BEmWX3jH!uh{ssoIqo z-cJWkd$QDyra0AEL_H40fB$;TF#09)IJf5tUGJ1o<^ag)T3oD4`e|LyPqLYq7*Vu_ z7%mUVS*@SSG&CdQT%*V>O5-ECkZd-d3^zlWIon)IxBie+Kz~+JsZ{0PyRZR;b?rV6 zuPdNrx7+u;-j^?cC&gnj7UeyaiKKy8B0boAQHD#M*rEMOaW2VLo@o)XlYj8TL6W0M z^dem@x21xnc!@w*UP7A9FiaLUA^D^*>YI5jO$$r6oW=>AQ@4qPRnR{?f0L@}XD*cE_BBN=Akg-oct5gB{B;MWBD4s2>;JfC(5uXGgVSnxoXca` zA?5Kk;GLFbkUkY7@Xrl#b8hmacc*}dKgr2Uel>h~Z~|f)qZf`z>b(Ub)j`G2!Uy7? zCAS7)ja!_aDqI=@k#zpc4nQ}u5JNmqUmXOUJy2{9W8E8aN_l!V{h*GiZTsK|!3!0s zZMHFlEx@*6=HN|yLVJ?e4e-5-S%A}eKJsmmWDBL&uEXh`zZ3KH6BLoT*8lfjY7zB4 z2{vsXPDtNdNLOp0BR$2}b2LH!j@il3;>r~glJvQf6xF7Zo=H;8N3$$&^6I|8p_fhZ z>>xU)Hhp=Fgv@Y(tuTQ<93Y9eJwF4j6y!?NBH4QMT%5k`jucB1eMX%-CxQBT(#!ew zZ;_M^IEmqDKlNii?x`OyfIeVMs&?{T>GvBlOic$`#*5+DRH-#i;NJa`-jC2 zQo(+Y$d-+cB@5WW%<|97#m$gg9lZa9u34V~QZD*74%M8_o(D2%(~4ITxRzDnGZh>Zi8OI!XH>!kxA| znzE#2yE_ysyiaVGP*(3pl#d8pHe^=W)8trSXxK?%ksDRH4OWBB?HRK-RHn-tqSdAb zD7hjp_~B+?BWoJoQWReSShH)B+c9Yxu-Cm-hZGNeG3h8|CsQD=8WD?#Xlkhl9{lZ~ zUv{gTlxfKPt>_^}nunzyZ-Ri2iM@VaiWXz}RQfQU7_#x3${xgP#Qfyl0V zj9zugc_75N__PTmHbPc#`! zZd45+Rkoy+naqysGHgLUe`rRRbVe0t{9Opt)p;y`;0=b`lpHSJwGLE)Ic#;HL-8^r z6cKd8?p8E5;~TR)JlSIt+jH)&Sy^{pO}VNXwEeH)F zu?R9xA)R{9KQjBTGHkkFX%W+6xXF-0VakO97=rttd2HW;&shA zxnIdG5CW{?Yg!Np$-XVf)wx!e#EKEcVxiaC={#IY_YBHB;}9`EgqBmy%QHC*nkAi? zzS|q@@j8`Q{D(ETbMNIn!EF!W7r$Q^TDHv=&_+0sp|dmqPnnDwmpH>bN%bm zfSy@t^0>8YEM=wDwvs>V?4uOf8soeCz7<=w-sN-NI4uW9=^4#yP_thn^osbh{ryd2q-WuDBIY${C08Jt^keeUOY;5Xz@7vy>nY9 zyGll6xB8Iq1HPzo#tb@GPx;eI`fL84{*#6}C+4m?RfVkxY8=^FRdrfRzSEsHKFbtG z3kKf(;*P~}lLOSK%7>a;z9Z**JyKLsz^N0ZvZ69eIb|5%YyEdy&%}OV)^A^pLfS{8 z0^*l-xgL-XD?mZIE-8o{;m$1#gSL{X6MA;GpK+uXPl+e+vSOF&mf}+c*^x7-E5ob= zhK!OX^%!zV2RlY+9XiA%}F;=IjeTzPq} zNrOYguwGYX6$}F(@Mldzb1dYwfEEeobYen&`n}JJ*P-BsiXB&cW&l$ZgUAj2b`&mZ zO79WMm}rMO*~nbu;E!->!E{9FUyGDC;T#T}`EB9=+3xvDK%g_t_v)LG{h!2Mb|XYmA6wi_cD0Hdj2(EB6y) zfuAL;)j9_AiDpwIaJKn4z^689lC}?g0^t8~oW}V9;Kncv3pn~i0o9?b!E6yQOuyx| zr*O3gi#1M&Ko)Bw58G_4sN>i^zG#!TRsl!D(J;Ln#qww5Ca^p6cLLL!#l*$Zf@#SS zLo4SQ)KzU&x$m91+5@G&+^C0MzB>^c?A@(c+_hNZVS4If({ba{!IUDqyAw=!?2y6c zp^N0IszKASJF~r>8CjA%V9wPGjc(b)XR4sxvKOn5vgyO|c8P+biCv9gtjF^4GXj@5SwLlZp{JfuscZZAcLDYDl#d^p{Q(vXlL%M+-@EpW@D zcy|E=d>N2WIl_nN>UQ*dnV8)S|&e4Yy(iH!_M{lEnND!G zIr&a3@uSccY2ZI_I~wmWRhsU!pjahV?P+^du+jgSmO8$ct!2NP36egf z7uRTxq~8J=!B+yLQVt2+@kNgcA65grGiu0$G;Vp#9v2+6cR;yeC-#{>Pplxx)7$T% z>0q)gC`p0%Z=^i$fh7&$=a zlL5;sZB0AZq-lU%)apNQ3v#s=X1*iGDd}IC%uA-PjhL&X(d*#+?#np z?@p4c)&?bdqW7u@D9YA3bTHiytS)$tKN_v)ff^R20jiSGyBRc0ZvahFGs#>8Q3KuI zbg6HvzuS2YNB^|mKGYhh{Wjgmg=LxK{Ds_{!6H}jg(n*4=L9mkpRkVGAL{W#%L%=# z&!yOT=KLxb_mD;N?%l2be%?u$5+zKu^_I&Z^-NmI`LVTr_4z~uYAYWTWVV4TV^hmp zKs({+JNO%B$pX(}1Gbd{V7*g^h*qRJjNUgH^<)^tSPQW2Y;YVet{dLe;bPoFfoq^d zTul80r*@&s+(=MOfqgUu3%gk>oGMY9)C(_qI^Fme!kkH2Mx8B4ZodXZJH~-qmCX*T znLs6^iv{=k#Kx~=AMmZMIeh=Y3!$8R=s%y^>ZlOuNHmL|x+4G9K_b8}g-NaVTplsu zz)>sl=p0j6(NGYPeJhctvlX(A5TT2c@|TRjP~V|0tJR@nXG4#*0FE3Y&GWHB?pgU! zNRsL*Q>u$~YX~lghy4Hrwl*DCd;`2fOKSeAcqV(C^ZwJIxd zpP`E5i~dhY`v1t>BQ!@dmODKit!^&K{6=tZeL6whj=vm6H;eAa_Qby(*!L~&1?>E_ zRQTNyI9G#3BHhm)TY9a1Zd*cXJ``A8==w1Y$Vw7@`>0G^2H)ez@43;t#KR#o57OE9 zC;T|Hk_k^p30ktVNX=MKVo=?2k^#(B6`Co-wQ;AcD6CXc$HAd~;Vjwmm{8$j9PZSY zrojovrAGn^97-L9=N4O9fld8I$e5h+SAuxqs0LP{`MaqE*pex=T{yw@hFH(k;& zijKalEf{8IpDoPhOZVpag&9dg+9x3k3GbNOK@8Xr`uY%a5;j(gHheN-9FrF)MT)j# zPEYm|oOheerhv&!cb!zTf(qU7A|xhA%KC2Hci>(O)NzHcWTAt_fNHVx%&hhdX01kb z2TmO}-;q95(wcDLoNr#e_9!NVg8(gVt4E{@@%*#80ea;#BXz2?hu0OfV+;i!5iGf) zFcIbdxc!m{uBJT8d;LN6TJ;jr>P+W1M$WbOR$k_Mt=jel4h5@jiDT5`FVpjJq;aBa zP<+zg!ryl?bB!xG{TcMKDXi9%)cz>5J!;f%37miY^$W%p+{U>yYE?N;sQjG)C=ZZl zJ(apIIsl`0TzQp<{^+=t&=$9E4?=DXy>PP%jMH)e)J4*4v^N-ofPNBv&Mi!Di=GIGmi3 zHsaQlO^SFX17`$VZW33h8C#eh`IAnyR1pyot@(zYo?iA9%(f7@joV0<>8NJpXiO=0$cKYXm?x^rdYf>}x1i4%%hDDtLV}CrE^3(7csScO#pmykgTO1q+-*;Y zQ-1zkM?Q}s{l);Ra=4^G#XoR;ahC$*8g?;lQ;GzpuI;I&XX%8$Vx+}OZHtS1!&tkZ`|`*o0c<7x-aXDU|@CX8EU8OuLCr&!j`1W7dwGmo1j zzCZ^WjK>9&@raeCxXOs?VZQT8>3wxlfSolcQtxUc^nrri`s)SHJ4ev`6?NLv>*G#-y6%-_HrS81aMP16?rL{gWJj8{WZPQv-`n!loz(J8w-tk*x(Wj9NLcO!WE;*l>bFX`?)Me*EVS_V$!tm zM6-FBN#Td!e0*D?KlyjfXbpQIKB0=$*P8NWOKP8{ zyh}2ej9Mz@pUUa!(ILxE6=#kqfyOiJ@GJdH8$Jv33RCLcZ8F!Zx7a*P%%m)tMR8I$ zAYIza8vXNGXCGoQUDcDL5I`NzJ{1)Xlq!r8ZBq3(&ha@djD$ET zOp0L+XUgC<%v9kx&Ma-aD%$!9`StCz-J6b1n23i|{!LcCRpK&41=D>K2EixZuxCjG z2)$1qVRG91Dc`bd)M*LFU$MMaZ0VpQyXhvQsJWO~aY5#TP?cXS8>)Jf=&M?&!#1bv z{iiJ!JXV!SBtYTn@Yj08d<3;AEE@ll5{^#PHgg~tWrAa3b@6&%8Gb<lNUc-AMxYMcSs@^*;qh7vitkPmH!5K5T zLi32-pmLOwV)uM>i#==!jplK zhbr~|s$>5zcLueOeYpcxG=-)1%U5~ZbKNmz^%RfU^!^hPI?MtUhD1%No6_mU$;8Rx z+b0i-LHYx}(yQ&Cw($ZXsACXgGJ?pIrj#7=^rtmUPq6l`wQWzQDw8zwSXs>Liuw?T zfjzVZZ};OX4Wov9_Jw6+*nXn^Q~fS(S9 za5pJOiAU(yJi!I2b~|%s4xkJM%=*l#NDs-HJtGmQAcx4c)=ZvH{JI?ayH_MnXsR%% ztE=~vPa5Gec?3VP>-TNVKE~jdmDy==O?*n0~r?<$Oh+*9=_TuS^-#OZ%0Ap}`1{tPJYb+YF{^?4k-#E9h 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 GOZpE8(TG(5 literal 0 HcmV?d00001 From 3779d21163b3a80f3e1cce95c2f41fb6ff3c58aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 25 Aug 2023 14:36:51 +0200 Subject: [PATCH 144/161] doc that the functions depend on Eigen --- .../interpolated_corrected_curvatures.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index 4b940f157e88..b90d66cbde2e 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -1037,6 +1037,8 @@ class Interpolated_corrected_curvatures_computer * By providing mean, Gaussian and/or principal curvature and direction property maps as named parameters, the user * can choose which quantites to compute. * +* \note This function depends on the \eigen 3.1 (or later) library. +* * @tparam PolygonMesh a model of `FaceListGraph`. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * @@ -1127,6 +1129,8 @@ void interpolated_corrected_curvatures(const PolygonMesh& pmesh, * * computes the interpolated corrected mean curvature across the mesh `pmesh`. * +* \note This function depends on the \eigen 3.1 (or later) library. +* * @tparam PolygonMesh a model of `FaceListGraph`. * @tparam VertexCurvatureMap a model of `WritablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` as key type and `GT::FT` as value type. @@ -1198,6 +1202,8 @@ void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, * * computes the interpolated corrected Gaussian curvature across the mesh `pmesh`. * +* \note This function depends on the \eigen 3.1 (or later) library. +* * @tparam PolygonMesh a model of `FaceListGraph`. * @tparam VertexCurvatureMap a model of `WritablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` as key type and `GT::FT` as value type. @@ -1268,6 +1274,8 @@ void interpolated_corrected_Gaussian_curvature(const PolygonMesh& pmesh, * * computes the interpolated corrected principal curvatures and directions across the mesh `pmesh`. * +* \note This function depends on the \eigen 3.1 (or later) library. +* * @tparam PolygonMesh a model of `FaceListGraph`. * @tparam VertexCurvatureMap a model of `WritablePropertyMap` with * `boost::graph_traits::%vertex_descriptor` as key type and @@ -1341,6 +1349,8 @@ void interpolated_corrected_principal_curvatures_and_directions(const PolygonMes * By providing mean, Gaussian and/or principal curvature and direction property maps as named parameters, the user * can choose which quantites to compute. * +* \note This function depends on the \eigen 3.1 (or later) library. +* * @tparam PolygonMesh a model of `FaceListGraph`. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * @@ -1426,6 +1436,8 @@ void interpolated_corrected_curvatures_one_vertex(const PolygonMesh& pmesh, * \ingroup PMP_corrected_curvatures_grp * computes the interpolated corrected mean curvature at vertex `v` of mesh `pmesh`. * +* \note This function depends on the \eigen 3.1 (or later) library. +* * @tparam PolygonMesh a model of `FaceListGraph`. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * @@ -1503,6 +1515,8 @@ interpolated_corrected_mean_curvature_one_vertex(const PolygonMesh& pmesh, * \ingroup PMP_corrected_curvatures_grp * computes the interpolated corrected Gaussian curvature at vertex `v` of mesh `pmesh`. * +* \note This function depends on the \eigen 3.1 (or later) library. +* * @tparam PolygonMesh a model of `FaceListGraph`. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * @@ -1580,6 +1594,8 @@ interpolated_corrected_Gaussian_curvature_one_vertex(const PolygonMesh& pmesh, * \ingroup PMP_corrected_curvatures_grp * computes the interpolated corrected principal curvatures and directions at vertex `v` of mesh `pmesh`. * +* \note This function depends on the \eigen 3.1 (or later) library. +* * @tparam PolygonMesh a model of `FaceListGraph`. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * From 1db314ee4424a115df56520a15a526ab27cf30b9 Mon Sep 17 00:00:00 2001 From: David Coeurjolly Date: Fri, 25 Aug 2023 14:39:38 +0200 Subject: [PATCH 145/161] Fixing image filenames in the doxyfile --- .../doc/Polygon_mesh_processing/Doxyfile.in | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in index 8c263a409995..ca887f906691 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in @@ -23,7 +23,40 @@ EXCLUDE_SYMBOLS += experimental HTML_EXTRA_FILES = ${CGAL_PACKAGE_DOC_DIR}/fig/selfintersections.jpg \ ${CGAL_PACKAGE_DOC_DIR}/fig/mesh_smoothing.png \ ${CGAL_PACKAGE_DOC_DIR}/fig/shape_smoothing.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/icc_diff_radius.png + ${CGAL_PACKAGE_DOC_DIR}/fig/icc_diff_radius.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/icc_diff_radius.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/ bimba-dmax0.020000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.020000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.030000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.030000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.040000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.040000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.050000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.050000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.020000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/ bimba-dmin0.020000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.030000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.030000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.040000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.040000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.050000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.050000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.020000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.020000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.030000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.030000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.040000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.040000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.050000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.050000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.020000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.020000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.030000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.030000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.040000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.040000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.050000-0.000000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.050000-0.002000.png\ ${CGAL_PACKAGE_DOC_DIR}/fig/decimate_cheese.png \ ${CGAL_PACKAGE_DOC_DIR}/fig/decimate_colors.png \ ${CGAL_PACKAGE_DOC_DIR}/fig/decimate_rg_joint.png From c16b155d4ee72ef3fd0f44086b84dee79b683269 Mon Sep 17 00:00:00 2001 From: David Coeurjolly Date: Fri, 25 Aug 2023 14:39:59 +0200 Subject: [PATCH 146/161] Removing old image file. --- Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in | 2 -- 1 file changed, 2 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in index ca887f906691..8afaceb878f4 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in @@ -23,8 +23,6 @@ EXCLUDE_SYMBOLS += experimental HTML_EXTRA_FILES = ${CGAL_PACKAGE_DOC_DIR}/fig/selfintersections.jpg \ ${CGAL_PACKAGE_DOC_DIR}/fig/mesh_smoothing.png \ ${CGAL_PACKAGE_DOC_DIR}/fig/shape_smoothing.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/icc_diff_radius.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/icc_diff_radius.png \ ${CGAL_PACKAGE_DOC_DIR}/fig/ bimba-dmax0.020000-0.000000.png\ ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.020000-0.002000.png\ ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.030000-0.000000.png\ From 4b450a594840de692804db70433aa900ed670dca Mon Sep 17 00:00:00 2001 From: David Coeurjolly Date: Fri, 25 Aug 2023 15:12:56 +0200 Subject: [PATCH 147/161] Cleaning up not used images --- .../doc/Polygon_mesh_processing/Doxyfile.in | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in index 8afaceb878f4..5cdf93414ac8 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in @@ -23,30 +23,9 @@ EXCLUDE_SYMBOLS += experimental HTML_EXTRA_FILES = ${CGAL_PACKAGE_DOC_DIR}/fig/selfintersections.jpg \ ${CGAL_PACKAGE_DOC_DIR}/fig/mesh_smoothing.png \ ${CGAL_PACKAGE_DOC_DIR}/fig/shape_smoothing.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/ bimba-dmax0.020000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.020000-0.002000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.030000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.030000-0.002000.png\ ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.040000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.040000-0.002000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.050000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.050000-0.002000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.020000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/ bimba-dmin0.020000-0.002000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.030000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.030000-0.002000.png\ ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.040000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.040000-0.002000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.050000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.050000-0.002000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.020000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.020000-0.002000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.030000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.030000-0.002000.png\ ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.040000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.040000-0.002000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.050000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.050000-0.002000.png\ ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.020000-0.000000.png\ ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.020000-0.002000.png\ ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.030000-0.000000.png\ From 28efeb20569b20b5baa1ac8b15d1476caa8681b7 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 27 Aug 2023 19:13:42 +0300 Subject: [PATCH 148/161] integrated curvature vis to display property still need to add the radius setting part --- .../Display/Display_property_plugin.cpp | 77 ++++++++++++++++++- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index 994dcd2e8d9b..ed0de184db33 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -22,11 +22,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -47,6 +49,8 @@ using namespace CGAL::Three; + + Viewer_interface* (&getActiveViewer)() = Three::activeViewer; class DockWidget @@ -99,6 +103,11 @@ class Display_property_plugin MAX_VALUE }; + enum CurvatureType { + MEAN_CURVATURE, + GAUSSIAN_CURVATURE + }; + public: bool applicable(QAction*) const Q_DECL_OVERRIDE { @@ -238,6 +247,14 @@ private Q_SLOTS: dock_widget->maxBox->setRange(0, 360); dock_widget->maxBox->setValue(0); } + else if (property_name == "Interpolated Corrected Gaussian Curvature" || + property_name == "Interpolated Corrected Mean Curvature") + { + dock_widget->minBox->setRange(-1000, 1000); + dock_widget->minBox->setValue(0); + dock_widget->maxBox->setRange(-1000, 1000); + dock_widget->maxBox->setValue(0); + } else if(property_name == "Scaled Jacobian") { dock_widget->minBox->setRange(-1000, 1000); @@ -432,11 +449,15 @@ private Q_SLOTS: dock_widget->propertyBox->addItems({"Smallest Angle Per Face", "Largest Angle Per Face", "Scaled Jacobian", - "Face Area"}); + "Face Area", + "Interpolated Corrected Mean Curvature", + "Interpolated Corrected Gaussian Curvature"}); property_simplex_types = { Property_simplex_type::FACE, Property_simplex_type::FACE, Property_simplex_type::FACE, - Property_simplex_type::FACE }; + Property_simplex_type::FACE, + Property_simplex_type::VERTEX, + Property_simplex_type::VERTEX }; detectSMScalarProperties(*(sm_item->face_graph())); } else if(ps_item) @@ -506,10 +527,15 @@ private Q_SLOTS: { CGAL_assertion(static_cast(dock_widget->propertyBox->count()) == property_simplex_types.size()); + const int property_index = dock_widget->propertyBox->currentIndex(); + // leave it flat if it was, otherwise set to flat+edges - if(sm_item->renderingMode() != Flat && sm_item->renderingMode() != FlatPlusEdges) + if(sm_item->renderingMode() != Flat && sm_item->renderingMode() != FlatPlusEdges && property_simplex_types.at(property_index) == Property_simplex_type::FACE) sm_item->setRenderingMode(FlatPlusEdges); + if(sm_item->renderingMode() != Gouraud && sm_item->renderingMode() != GouraudPlusEdges && property_simplex_types.at(property_index) == Property_simplex_type::VERTEX) + sm_item->setRenderingMode(GouraudPlusEdges); + const std::string& property_name = dock_widget->propertyBox->currentText().toStdString(); if(property_name == "Smallest Angle Per Face") { @@ -527,6 +553,14 @@ private Q_SLOTS: { displayArea(sm_item); } + else if(property_name == "Interpolated Corrected Mean Curvature") + { + displayCurvature(sm_item, MEAN_CURVATURE); + } + else if(property_name == "Interpolated Corrected Gaussian Curvature") + { + displayCurvature(sm_item, GAUSSIAN_CURVATURE); + } else { const int property_index = dock_widget->propertyBox->currentIndex(); @@ -629,6 +663,8 @@ private Q_SLOTS: removeDisplayPluginProperty(item, "f:display_plugin_largest_angle"); removeDisplayPluginProperty(item, "f:display_plugin_scaled_jacobian"); removeDisplayPluginProperty(item, "f:display_plugin_area"); + removeDisplayPluginProperty(item, "f:display_plugin_mean_curvature"); + removeDisplayPluginProperty(item, "f:display_plugin_gaussian_curvature"); } void displayExtremumAnglePerFace(Scene_surface_mesh_item* sm_item, @@ -809,6 +845,32 @@ private Q_SLOTS: displaySMProperty("f:display_plugin_area", *sm); } + void displayCurvature(Scene_surface_mesh_item* sm_item, + const CurvatureType curvature_type) + { + SMesh* sm = sm_item->face_graph(); + if(sm == nullptr) + return; + + bool not_initialized; + std::string tied_string = (curvature_type == MEAN_CURVATURE) ? + "v:display_plugin_mean_curvature" : "v:display_plugin_gaussian_curvature"; + + SMesh::Property_map vcurvature; + std::tie(vcurvature, not_initialized) = sm->add_property_map(tied_string, 0); + + if (curvature_type == MEAN_CURVATURE) + { + CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature(*sm, vcurvature); + } + else if (curvature_type == GAUSSIAN_CURVATURE) + { + CGAL::Polygon_mesh_processing::interpolated_corrected_Gaussian_curvature(*sm, vcurvature); + } + + displaySMProperty(tied_string, *sm); + } + private: template bool call_on_PS_property(const std::string& name, @@ -964,6 +1026,10 @@ private Q_SLOTS: zoomToSimplexWithPropertyExtremum(faces(mesh), mesh, "f:display_plugin_scaled_jacobian", extremum); else if(property_name == "Face Area") zoomToSimplexWithPropertyExtremum(faces(mesh), mesh, "f:display_plugin_area", extremum); + else if(property_name == "Interpolated Corrected Mean Curvature") + zoomToSimplexWithPropertyExtremum(vertices(mesh), mesh, "v:display_plugin_mean_curvature", extremum); + else if(property_name == "Interpolated Corrected Gaussian Curvature") + zoomToSimplexWithPropertyExtremum(vertices(mesh), mesh, "v:display_plugin_gaussian_curvature", extremum); else if(property_simplex_types.at(property_index) == Property_simplex_type::VERTEX) zoomToSimplexWithPropertyExtremum(vertices(mesh), mesh, property_name, extremum); else if(property_simplex_types.at(property_index) == Property_simplex_type::FACE) @@ -1298,7 +1364,10 @@ isSMPropertyScalar(const std::string& name, if(name == "f:display_plugin_smallest_angle" || name == "f:display_plugin_largest_angle" || name == "f:display_plugin_scaled_jacobian" || - name == "f:display_plugin_area") + name == "f:display_plugin_area" || + name == "f:display_plugin_mean_curvature" || + name == "f:display_plugin_gaussian_curvature") + return false; // the dispatch function does the filtering we want: if it founds a property From 46ac0f90604291123bacd57e40b9bfa8c9b2b044 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Sun, 27 Aug 2023 19:27:23 +0300 Subject: [PATCH 149/161] trim whitespaces --- .../demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index ed0de184db33..e5baf54cfd7e 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -247,7 +247,7 @@ private Q_SLOTS: dock_widget->maxBox->setRange(0, 360); dock_widget->maxBox->setValue(0); } - else if (property_name == "Interpolated Corrected Gaussian Curvature" || + else if (property_name == "Interpolated Corrected Gaussian Curvature" || property_name == "Interpolated Corrected Mean Curvature") { dock_widget->minBox->setRange(-1000, 1000); From 167db62e0753cf73965e97eccbe6b2545e96d59c Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 6 Sep 2023 12:50:39 +0300 Subject: [PATCH 150/161] moved bib to cgal_manual.bib & restored geom.bib --- Documentation/doc/biblio/cgal_manual.bib | 23 +++++++++++++++++ Documentation/doc/biblio/geom.bib | 25 +------------------ .../Polygon_mesh_processing.txt | 10 ++++---- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Documentation/doc/biblio/cgal_manual.bib b/Documentation/doc/biblio/cgal_manual.bib index 89d062ddddf5..453f1968678e 100644 --- a/Documentation/doc/biblio/cgal_manual.bib +++ b/Documentation/doc/biblio/cgal_manual.bib @@ -1331,6 +1331,29 @@ @INPROCEEDINGS{cgal:la-srpss-13 address = {Girona, Spain} } +@article{cgal:lrtc-iccmps-20, + author = {Jacques-Olivier Lachaud and Pascal Romon and Boris Thibert and David Coeurjolly}, + journal = {Computer Graphics Forum (Proceedings of Symposium on Geometry Processing)}, + number = {5}, + title = {Interpolated Corrected Curvature Measures for Polygonal Surfaces}, + volume = {39}, + month = aug, + year = {2020}, + url = {https://doi.org/10.1111/cgf.14067}, + doi = {10.1111/cgf.14067} +} + +@article{cgal:lrt-ccm-22, + author = {Jacques-Olivier Lachaud and Pascal Romon and Boris Thibert}, + journal = {Discrete & Computational Geometry}, + title = {Corrected Curvature Measures}, + volume = {68}, + pages = {477-524}, + month = jul, + year = {2022}, + url = {https://doi.org/10.1007/s00454-022-00399-4} +} + @article{cgal:lm-clscm-12, author = {Lafarge, Florent and Mallet, Clement}, title = {{Creating large-scale city models from 3D-point clouds: a robust approach with hybrid representation}}, diff --git a/Documentation/doc/biblio/geom.bib b/Documentation/doc/biblio/geom.bib index f072116a2e27..5d6a1f80b0c4 100644 --- a/Documentation/doc/biblio/geom.bib +++ b/Documentation/doc/biblio/geom.bib @@ -152043,7 +152043,6 @@ @article{cvl-ew-12 Pages = {215--224}, Year = {2012}, Url = {https://monge.univ-mlv.fr/~colinde/pub/09edgewidth.pdf} -} @inproceedings{tang2009interactive, title={Interactive Hausdorff distance computation for general polygonal models}, @@ -152054,26 +152053,4 @@ @inproceedings{tang2009interactive pages={74}, year={2009}, organization={ACM} -} - -@article{lachaud2020, - author = {Jacques-Olivier Lachaud and Pascal Romon and Boris Thibert and David Coeurjolly}, - journal = {Computer Graphics Forum (Proceedings of Symposium on Geometry Processing)}, - number = {5}, - title = {Interpolated corrected curvature measures for polygonal surfaces}, - volume = {39}, - month = jul, - year = {2020} -} - -@article{lachaud2022 - author = {Jacques-Olivier Lachaud and Pascal Romon and Boris Thibert}, - journal = {Discrete & Computational Geometry}, - title = {Corrected Curvature Measures}, - volume = {68}, - pages = {477-524}, - month = jul, - year = {2022}, - url = {https://doi.org/10.1007/s00454-022-00399-4} -} - +} \ No newline at end of file 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 9773216f3cb6..d81a1f6029e4 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 @@ -940,7 +940,7 @@ not provide storage for the normals. \section PMPICC Computing Curvatures This package provides methods to compute curvatures on polygonal meshes based on Interpolated -Corrected Curvatures on Polyhedral Surfaces \cgalCite{lachaud2020}. This includes mean curvature, +Corrected Curvatures on Polyhedral Surfaces \cgalCite{cgal:lrtc-iccmps-20}. This includes mean curvature, Gaussian curvature, principal curvatures and directions. These can be computed on triangle meshes, quad meshes, and meshes with n-gon faces (for n-gons, the centroid must be inside the n-gon face). The algorithms used prove to work well in general. Also, on meshes with noise on vertex positions, @@ -955,10 +955,10 @@ in each direction is called the principal curvature: \f$ k_1 \f$ and \f$ k_2 \f$ curvatures). Curvature is usually expressed as scalar quantities like the mean curvature \f$ H \f$ and the Gaussian curvature \f$ K \f$ which are defined in terms of the principal curvatures. -The algorithms are based on the two papers \cgalCite{lachaud2022} and \cgalCite{lachaud2020}. They -introduce a new way to compute curvatures on polygonal meshes. The main idea in \cgalCite{lachaud2022} is +The algorithms are based on the two papers \cgalCite{cgal:lrt-ccm-22} and \cgalCite{cgal:lrtc-iccmps-20}. They +introduce a new way to compute curvatures on polygonal meshes. The main idea in \cgalCite{cgal:lrt-ccm-22} is based on decoupling the normal information from the position information, which is useful for dealing with -digital surfaces, or meshes with noise on vertex positions. \cgalCite{lachaud2020} introduces some +digital surfaces, or meshes with noise on vertex positions. \cgalCite{cgal:lrtc-iccmps-20} introduces some extensions to this framework. As it uses linear interpolation on the corrected normal vector field to derive new closed form equations for the corrected curvature measures. These interpolated curvature measures are the first step for computing the curvatures. For a triangle \f$ \tau_{ijk} \f$, @@ -1285,7 +1285,7 @@ is covered by a set of prisms, where each prism is an offset for an input triang That is, the implementation in \cgal does not use indirect predicates. The interpolated corrected curvatures were implemented during GSoC 2022. This was implemented by Hossam Saeed and under -supervision of David Coeurjolly, Jaques-Olivier Lachaud, and Sébastien Loriot. The implementation is based on \cgalCite{lachaud2020}. +supervision of David Coeurjolly, Jaques-Olivier Lachaud, and Sébastien Loriot. The implementation is based on \cgalCite{cgal:lrtc-iccmps-20}. DGtal's implementation was also used as a reference during the project. From 72fd73e2b7f4db9e9b4c1f20cdfaddfafe6ee05f Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 6 Sep 2023 12:51:28 +0300 Subject: [PATCH 151/161] Update geom.bib --- Documentation/doc/biblio/geom.bib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/doc/biblio/geom.bib b/Documentation/doc/biblio/geom.bib index 5d6a1f80b0c4..969a1a79b5ba 100644 --- a/Documentation/doc/biblio/geom.bib +++ b/Documentation/doc/biblio/geom.bib @@ -152053,4 +152053,4 @@ @inproceedings{tang2009interactive pages={74}, year={2009}, organization={ACM} -} \ No newline at end of file +} From 5ddcf716b618c7683e6427e6310e7b361d6177d1 Mon Sep 17 00:00:00 2001 From: hoskillua <47090776+hoskillua@users.noreply.github.com> Date: Wed, 6 Sep 2023 12:52:34 +0300 Subject: [PATCH 152/161] fixed order in bib --- Documentation/doc/biblio/cgal_manual.bib | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Documentation/doc/biblio/cgal_manual.bib b/Documentation/doc/biblio/cgal_manual.bib index 453f1968678e..5fc304486601 100644 --- a/Documentation/doc/biblio/cgal_manual.bib +++ b/Documentation/doc/biblio/cgal_manual.bib @@ -1331,6 +1331,16 @@ @INPROCEEDINGS{cgal:la-srpss-13 address = {Girona, Spain} } +@article{cgal:lm-clscm-12, + author = {Lafarge, Florent and Mallet, Clement}, + title = {{Creating large-scale city models from 3D-point clouds: a robust approach with hybrid representation}}, + journal = {International Journal of Computer Vision}, + volume = {99}, + number = {1}, + pages = {69-85}, + year = {2012}, +} + @article{cgal:lrtc-iccmps-20, author = {Jacques-Olivier Lachaud and Pascal Romon and Boris Thibert and David Coeurjolly}, journal = {Computer Graphics Forum (Proceedings of Symposium on Geometry Processing)}, @@ -1354,16 +1364,6 @@ @article{cgal:lrt-ccm-22 url = {https://doi.org/10.1007/s00454-022-00399-4} } -@article{cgal:lm-clscm-12, - author = {Lafarge, Florent and Mallet, Clement}, - title = {{Creating large-scale city models from 3D-point clouds: a robust approach with hybrid representation}}, - journal = {International Journal of Computer Vision}, - volume = {99}, - number = {1}, - pages = {69-85}, - year = {2012}, -} - @inproceedings{ cgal:lt-fmeps-98, author = "Peter Lindstrom and Greg Turk", title = "Fast and memory efficient polygonal simplification", From df0e0ee9bb5b31c93918217b3843786076530206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 9 Oct 2023 08:50:17 +0200 Subject: [PATCH 153/161] implement comments from review --- .../doc/Polygon_mesh_processing/Doxyfile.in | 22 +- .../PackageDescription.txt | 8 - .../Polygon_mesh_processing.txt | 58 +-- .../interpolated_corrected_curvatures_PH.cpp | 29 +- .../interpolated_corrected_curvatures_SM.cpp | 36 +- ...terpolated_corrected_curvatures_vertex.cpp | 24 +- .../interpolated_corrected_curvatures.h | 490 +----------------- ...test_interpolated_corrected_curvatures.cpp | 46 +- 8 files changed, 87 insertions(+), 626 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in index 5cdf93414ac8..c2db3e16577f 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in @@ -23,17 +23,17 @@ EXCLUDE_SYMBOLS += experimental HTML_EXTRA_FILES = ${CGAL_PACKAGE_DOC_DIR}/fig/selfintersections.jpg \ ${CGAL_PACKAGE_DOC_DIR}/fig/mesh_smoothing.png \ ${CGAL_PACKAGE_DOC_DIR}/fig/shape_smoothing.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.040000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.040000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.040000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.020000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.020000-0.002000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.030000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.030000-0.002000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.040000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.040000-0.002000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.050000-0.000000.png\ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.050000-0.002000.png\ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.040000-0.000000.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.040000-0.000000.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.040000-0.000000.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.020000-0.000000.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.020000-0.002000.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.030000-0.000000.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.030000-0.002000.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.040000-0.000000.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.040000-0.002000.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.050000-0.000000.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.050000-0.002000.png \ ${CGAL_PACKAGE_DOC_DIR}/fig/decimate_cheese.png \ ${CGAL_PACKAGE_DOC_DIR}/fig/decimate_colors.png \ ${CGAL_PACKAGE_DOC_DIR}/fig/decimate_rg_joint.png diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 83929a699818..0a1c08909eeb 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -213,15 +213,7 @@ The page \ref bgl_namedparameters "Named Parameters" describes their usage. - \link PMP_locate_grp Random Location Generation \endlink \cgalCRPSection{Corrected Curvatures} -- `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_Gaussian_curvature()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions()` - `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_one_vertex()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_Gaussian_curvature_one_vertex()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_one_vertex()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures_one_vertex()` -- `CGAL::Polygon_mesh_processing::Principal_curvatures_and_directions` \cgalCRPSection{Normal Computation Functions} - `CGAL::Polygon_mesh_processing::compute_face_normal()` 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 d81a1f6029e4..1f6249c0ee89 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 @@ -999,20 +999,9 @@ is divided by the interpolated area measure. The implementation is generic in terms of mesh data structure. It can be used on `Surface_mesh`, `Polyhedron_3` and other polygonal mesh structures based on the concept `FaceGraph`. -These computations are performed using (on all vertices of the mesh): -- `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_Gaussian_curvature()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` - -Where it is recommended to use the last function for computing multiple curvatures (for example: mean and -Gaussian) as the implementation performs the shared computations only once, making it more efficient. - -Similarly, we can use the following functions to compute curvatures on a specific vertex: -- `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_one_vertex()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_Gaussian_curvature_one_vertex()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_one_vertex()` -- `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures_one_vertex()` +These computations are performed using (on all vertices of the mesh) `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` +where function named parameters are use to select the curvatures (and possibly directions) to be computed. An overlay with the same named +but accepting and accepting a given vertex is also available in case the computation should be done only for that vertex. \subsection ICCResults Results & Performance @@ -1022,34 +1011,45 @@ First, \cgalFigureRef{icc_measures} first illustrates various curvature measure \cgalFigureAnchor{icc_measures}

- - - - + + + + + + + + +
(a)(b)
+ + \cgalFigureCaptionBegin{icc_measures} Mean curvature, Gaussian curvature, minimal principal curvature direction and minimal principal curvature direction on a mesh (ball radius set to 0.04). \cgalFigureCaptionEnd \cgalFigureAnchor{icc_various_ball_radii}
- - - -
- - - - + + + + + + + + + + + + + + +
(a)(b)
\cgalFigureCaptionBegin{icc_various_ball_radii} When changing the integration ball radius, we obtain a scale space of curvature measure that can be used to tackle possible noise in the input as illustrated in the second row (mean curvature only with fixed colormap ranges and ball radii in {0.02,0.03,0.04,0.05}). \cgalFigureCaptionEnd - - - \ref BGLPropertyMaps are used to record the computed curvatures as shown in examples. In the following examples, for each property map, we associate a curvature value to each vertex. diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp index c93de26f26cc..09bc213068d1 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_PH.cpp @@ -28,6 +28,7 @@ int main(int argc, char* argv[]) return EXIT_FAILURE; } + // define property map to store curvature value and directions boost::property_map>::type mean_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron), Gaussian_curvature_map = get(CGAL::dynamic_vertex_property_t(), polyhedron); @@ -36,29 +37,13 @@ int main(int argc, char* argv[]) principal_curvatures_and_directions_map = get(CGAL::dynamic_vertex_property_t>(), polyhedron); - PMP::interpolated_corrected_mean_curvature(polyhedron, mean_curvature_map); - - PMP::interpolated_corrected_Gaussian_curvature(polyhedron, Gaussian_curvature_map); - - PMP::interpolated_corrected_principal_curvatures_and_directions(polyhedron, principal_curvatures_and_directions_map); - - // uncomment this to compute a curvature while specifying named parameters - // Example: an expansion ball radius of 0.5 and a vertex normals map (does not have to depend on positions) - - /*std::unordered_map vnm; - - PMP::interpolated_corrected_mean_curvature( - polyhedron, - mean_curvature_map, - CGAL::parameters::ball_radius(0.5).vertex_normal_map(boost::make_assoc_property_map(vnm)) - );*/ - - // This function can be used to compute multiple curvature types by specifiying them as named parameters - // This is more efficient than computing each one separately (shared computations). - PMP::interpolated_corrected_curvatures( - polyhedron, + PMP::interpolated_corrected_curvatures(polyhedron, CGAL::parameters::vertex_mean_curvature_map(mean_curvature_map) - .vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map)); + .vertex_Gaussian_curvature_map(Gaussian_curvature_map) + .vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) + // uncomment to use an expansion ball radius of 0.5 to estimate the curvatures + // .ball_radius(0.5) + ); int i = 0; for (vertex_descriptor v : vertices(polyhedron)) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp index dae2ec0716ec..74a678ea0624 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_SM.cpp @@ -51,39 +51,15 @@ int main(int argc, char* argv[]) Epic_kernel::Vector_3(0,0,0) }); assert(created); - // user can call these fucntions to compute a specfic curvature type on each vertex. - // (Note: if no ball radius is specified, the measure expansion of each vertex happens by - // summing measures on faces adjacent to each vertex.) - PMP::interpolated_corrected_mean_curvature(smesh, mean_curvature_map); - - PMP::interpolated_corrected_Gaussian_curvature(smesh, Gaussian_curvature_map); - - PMP::interpolated_corrected_principal_curvatures_and_directions(smesh, principal_curvatures_and_directions_map); - - // uncomment this to compute a curvature while specifying named parameters - // Example: an expansion ball radius of 0.5 and a vertex normals map (does not have to depend on positions) - - /*Surface_Mesh::Property_map vnm; - boost::tie(vnm, created) = smesh.add_property_map( - "v:vnm", Epic_kernel::Vector_3(0, 0, 0) - ); - - assert(created); - - PMP::interpolated_corrected_mean_curvature( - smesh, - mean_curvature_map, - CGAL::parameters::ball_radius(0.5).vertex_normal_map(vnm) - );*/ - - // This function can be used to compute multiple curvature types by specifiying them as named parameters - // This is more efficient than computing each one separately (shared computations). - PMP::interpolated_corrected_curvatures( - smesh, + PMP::interpolated_corrected_curvatures(smesh, CGAL::parameters::vertex_mean_curvature_map(mean_curvature_map) - .vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) + .vertex_Gaussian_curvature_map(Gaussian_curvature_map) + .vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) + // uncomment to use an expansion ball radius of 0.5 to estimate the curvatures + // .ball_radius(0.5) ); + for (vertex_descriptor v : vertices(smesh)) { auto PC = principal_curvatures_and_directions_map[v]; diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp index c6bef5faa740..bdcdba6b7fd0 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp @@ -30,26 +30,18 @@ int main(int argc, char* argv[]) // loop over vertices and use vertex_descriptor to compute a curvature on one vertex for (vertex_descriptor v : vertices(smesh)) { - double h = PMP::interpolated_corrected_mean_curvature_one_vertex(smesh, v); - double g = PMP::interpolated_corrected_Gaussian_curvature_one_vertex(smesh, v); - PMP::Principal_curvatures_and_directions p = - PMP::interpolated_corrected_principal_curvatures_and_directions_one_vertex(smesh, v); + double h, g; + PMP::Principal_curvatures_and_directions p; + + PMP::interpolated_corrected_curvatures(v, + smesh, + CGAL::parameters::vertex_mean_curvature(std::ref(h)) + .vertex_Gaussian_curvature(std::ref(g)) + .vertex_principal_curvatures_and_directions(std::ref(p))); // we can also specify a ball radius for expansion and a user defined vertex normals map using // named parameters. Refer to interpolated_corrected_curvatures_SM.cpp to see example usage. - // Can also use interpolated_corrected_curvatures_one_vertex() to compute multiple curvatures - // on the vertex at the same time. This is more efficient than computing each one separately. - // The following commented lines show this (all mentioned named parameters work on it as well) - // we specify which curvatures we want to compute by passing pointers as named parameters - // as shown. These pointers are used for storing the result as well. in this example we - // selected mean and Gaussian curvatures - // PMP::interpolated_corrected_curvatures_one_vertex( - // smesh, - // v, - // CGAL::parameters::vertex_mean_curvature(&h) - // .vertex_Gaussian_curvature(&g) - // ); std::cout << v.idx() << ": HC = " << h << ", GC = " << g << "\n" diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index b90d66cbde2e..c2c27b1733b2 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -1048,33 +1048,6 @@ class Interpolated_corrected_curvatures_computer * * \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{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`.} -* \cgalParamType{a class model of `ReadablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` -* as key type and `GT::Vector_3` as value type.} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`.} -* \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using `compute_vertex_normals()`.} -* \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 -* * \cgalParamNBegin{vertex_mean_curvature_map} * \cgalParamDescription{a property map associating mean curvatures to the vertices of `pmesh`.} * \cgalParamType{a class model of `WritablePropertyMap` with @@ -1110,185 +1083,6 @@ class Interpolated_corrected_curvatures_computer * measures on faces around the vertex.} * \cgalParamNEnd * -* \cgalNamedParamsEnd -* -* @see `interpolated_corrected_mean_curvature()` -* @see `interpolated_corrected_Gaussian_curvature()` -* @see `interpolated_corrected_principal_curvatures_and_directions()` -*/ -template -void interpolated_corrected_curvatures(const PolygonMesh& pmesh, - const NamedParameters& np = parameters::default_values()) -{ - internal::Interpolated_corrected_curvatures_computer(pmesh, np); -} - -/** -* \ingroup PMP_corrected_curvatures_grp -* -* computes the interpolated corrected mean curvature across the mesh `pmesh`. -* -* \note This function depends on the \eigen 3.1 (or later) library. -* -* @tparam PolygonMesh a model of `FaceListGraph`. -* @tparam VertexCurvatureMap a model of `WritablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` as key type and `GT::FT` as value type. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". -* -* @param pmesh the polygon mesh. -* @param vcm the vertex property map in which the computed mean curvatures are stored. -* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters". -* `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{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`.} -* \cgalParamType{a class model of `ReadablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` -* as key type and `GT::Vector_3` as value type.} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`.} -* \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using `compute_vertex_normals()`.} -* \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 -* -* \cgalParamNBegin{ball_radius} -* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures -* by summing measures of faces inside a ball of this radius centered at the -* vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball.} -* \cgalParamType{`GT::FT`} -* \cgalParamDefault{`-1`} -* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of -* measures on faces around the vertex.} -* \cgalParamNEnd -* -* \cgalNamedParamsEnd -* -* @see `interpolated_corrected_Gaussian_curvature()` -* @see `interpolated_corrected_principal_curvatures_and_directions()` -* @see `interpolated_corrected_curvatures()` -*/ - -template -void interpolated_corrected_mean_curvature(const PolygonMesh& pmesh, - VertexCurvatureMap vcm, - const NamedParameters& np = parameters::default_values()) -{ - interpolated_corrected_curvatures(pmesh, np.vertex_mean_curvature_map(vcm)); -} - -/** -* \ingroup PMP_corrected_curvatures_grp -* -* computes the interpolated corrected Gaussian curvature across the mesh `pmesh`. -* -* \note This function depends on the \eigen 3.1 (or later) library. -* -* @tparam PolygonMesh a model of `FaceListGraph`. -* @tparam VertexCurvatureMap a model of `WritablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` as key type and `GT::FT` as value type. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". -* -* @param pmesh the polygon mesh. -* @param vcm the vertex property map in which the computed Gaussian curvatures are stored. -* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters". -* `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 `%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{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`.} -* \cgalParamType{a class model of `GT::ReadablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` -* as key type and `%Vector_3` as value type.} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`.} -* \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using `compute_vertex_normals()`.} -* \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 -* -* \cgalParamNBegin{ball_radius} -* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures -* by summing measures of faces inside a ball of this radius centered at the -* vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball.} -* \cgalParamType{`GT::FT`} -* \cgalParamDefault{`-1`} -* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of -* measures on faces around the vertex.} -* \cgalParamNEnd -* -* \cgalNamedParamsEnd -* -* @see `interpolated_corrected_mean_curvature()` -* @see `interpolated_corrected_principal_curvatures_and_directions()` -* @see `interpolated_corrected_curvatures()` -*/ -template -void interpolated_corrected_Gaussian_curvature(const PolygonMesh& pmesh, - VertexCurvatureMap vcm, - const NamedParameters& np = parameters::default_values()) -{ - interpolated_corrected_curvatures(pmesh, np.vertex_Gaussian_curvature_map(vcm)); -} - -/** -* \ingroup PMP_corrected_curvatures_grp -* -* computes the interpolated corrected principal curvatures and directions across the mesh `pmesh`. -* -* \note This function depends on the \eigen 3.1 (or later) library. -* -* @tparam PolygonMesh a model of `FaceListGraph`. -* @tparam VertexCurvatureMap a model of `WritablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` as key type and -* `Principal_curvatures_and_directions` as value type. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". -* -* @param pmesh the polygon mesh. -* @param vcm the vertex property map in which the computed principal curvatures and directions are stored. -* @param np an 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 @@ -1316,33 +1110,20 @@ void interpolated_corrected_Gaussian_curvature(const PolygonMesh& pmesh, * \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.} * \cgalParamNEnd * -* \cgalParamNBegin{ball_radius} -* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures -* by summing measures of faces inside a ball of this radius centered at the -* vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball.} -* \cgalParamType{`GT::FT`} -* \cgalParamDefault{`-1`} -* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of -* measures on faces around the vertex.} -* \cgalParamNEnd -* * \cgalNamedParamsEnd * * @see `interpolated_corrected_mean_curvature()` * @see `interpolated_corrected_Gaussian_curvature()` -* @see `interpolated_corrected_curvatures()` +* @see `interpolated_corrected_principal_curvatures_and_directions()` */ -template -void interpolated_corrected_principal_curvatures_and_directions(const PolygonMesh& pmesh, - VertexCurvatureMap vcm, - const NamedParameters& np = parameters::default_values()) +template +void interpolated_corrected_curvatures(const PolygonMesh& pmesh, + const CGAL_NP_CLASS& np = parameters::default_values()) { - interpolated_corrected_curvatures(pmesh, np.vertex_principal_curvatures_and_directions_map(vcm)); + internal::Interpolated_corrected_curvatures_computer(pmesh, np); } - /** * \ingroup PMP_corrected_curvatures_grp * computes the interpolated corrected curvatures at a vertex `v`. @@ -1352,6 +1133,7 @@ void interpolated_corrected_principal_curvatures_and_directions(const PolygonMes * \note This function depends on the \eigen 3.1 (or later) library. * * @tparam PolygonMesh a model of `FaceListGraph`. +* @tparam VertexDescriptor must be convertible to `boost::graph_traits::%vertex_descriptor`. * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". * * @param pmesh the polygon mesh. @@ -1360,32 +1142,6 @@ void interpolated_corrected_principal_curvatures_and_directions(const PolygonMes * `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{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`.} -* \cgalParamType{a class model of `ReadablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` -* as key type and `GT::Vector_3` as value type.} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`.} -* \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using `compute_vertex_normals()`.} -* \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 * * \cgalParamNBegin{vertex_mean_curvature} * \cgalParamDescription{a reference to a scalar value to store the mean curvature at the vertex `v`.} @@ -1412,120 +1168,10 @@ void interpolated_corrected_principal_curvatures_and_directions(const PolygonMes * inclusion ratio inside this ball.} * \cgalParamType{`GT::FT`} * \cgalParamDefault{`-1`} -* \cgalParamExtra{If this parameter is omitted (`-1`), the epansion is then just a sum of -* measures on faces around the vertex.} -* \cgalParamNEnd -* -* \cgalNamedParamsEnd -* -* @see `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` -* @see `CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature_one_vertex()` -* @see `CGAL::Polygon_mesh_processing::interpolated_corrected_Gaussian_curvature_one_vertex()` -* @see `CGAL::Polygon_mesh_processing::interpolated_corrected_principal_curvatures_and_directions_one_vertex()` -*/ -template -void interpolated_corrected_curvatures_one_vertex(const PolygonMesh& pmesh, - typename boost::graph_traits::vertex_descriptor v, - const NamedParameters& np = parameters::default_values()) -{ - internal::interpolated_corrected_curvatures_one_vertex(pmesh, v, np); -} - -/** -* \ingroup PMP_corrected_curvatures_grp -* computes the interpolated corrected mean curvature at vertex `v` of mesh `pmesh`. -* -* \note This function depends on the \eigen 3.1 (or later) library. -* -* @tparam PolygonMesh a model of `FaceListGraph`. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". -* -* @param pmesh the polygon mesh. -* @param v the vertex of `pmesh` to compute the mean curvature at. -* @param np an 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{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`.} -* \cgalParamType{a class model of `ReadablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` -* as key type and `GT::Vector_3` as value type.} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`.} -* \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using `compute_vertex_normals()`.} -* \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 -* -* \cgalParamNBegin{ball_radius} -* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures -* by summing measures of faces inside a ball of this radius centered at the -* vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball.} -* \cgalParamType{`GT::FT`} -* \cgalParamDefault{`-1`} -* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of +* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion is then just a sum of * measures on faces around the vertex.} * \cgalParamNEnd * -* \cgalNamedParamsEnd -* -* @return the interpolated corrected mean curvature at the vertex `v`. -* -* @see `interpolated_corrected_mean_curvature()` -* @see `interpolated_corrected_Gaussian_curvature_one_vertex()` -* @see `interpolated_corrected_principal_curvatures_and_directions_one_vertex()` -* @see `interpolated_corrected_curvatures_one_vertex()` -*/ - -template -#ifdef DOXYGEN_RUNNING -typename GT::FT -#else -typename GetGeomTraits::type::FT -#endif -interpolated_corrected_mean_curvature_one_vertex(const PolygonMesh& pmesh, - typename boost::graph_traits::vertex_descriptor v, - const NamedParameters& np = parameters::default_values()) -{ - typename GetGeomTraits::type::FT mean_curvature; - interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_mean_curvature(std::ref(mean_curvature))); - return mean_curvature; -} - -/** -* \ingroup PMP_corrected_curvatures_grp -* computes the interpolated corrected Gaussian curvature at vertex `v` of mesh `pmesh`. -* -* \note This function depends on the \eigen 3.1 (or later) library. -* -* @tparam PolygonMesh a model of `FaceListGraph`. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". -* -* @param pmesh the polygon mesh. -* @param v the vertex of `pmesh` to compute the Gaussian curvature at. -* @param np an 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 @@ -1548,125 +1194,21 @@ interpolated_corrected_mean_curvature_one_vertex(const PolygonMesh& pmesh, * * \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 -* -* \cgalParamNBegin{ball_radius} -* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures -* by summing measures of faces inside a ball of this radius centered at the -* vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball.} -* \cgalParamType{`GT::FT`} -* \cgalParamDefault{`-1`} -* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of -* measures on faces around the vertex.} -* \cgalParamNEnd -* -* \cgalNamedParamsEnd -* -* @return the interpolated corrected Gaussian curvature at the vertex `v`. -* -* @see `interpolated_corrected_Gaussian_curvature()` -* @see `interpolated_corrected_mean_curvature_one_vertex()` -* @see `interpolated_corrected_principal_curvatures_and_directions_one_vertex()` -* @see `interpolated_corrected_curvatures_one_vertex()` -*/ - -template -#ifdef DOXYGEN_RUNNING -typename GT::FT -#else -typename GetGeomTraits::type::FT -#endif -interpolated_corrected_Gaussian_curvature_one_vertex(const PolygonMesh& pmesh, - typename boost::graph_traits::vertex_descriptor v, - const NamedParameters& np = parameters::default_values()) -{ - typename GetGeomTraits::type::FT gc; - interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_Gaussian_curvature(std::ref(gc))); - return gc; -} - -/** -* \ingroup PMP_corrected_curvatures_grp -* computes the interpolated corrected principal curvatures and directions at vertex `v` of mesh `pmesh`. -* -* \note This function depends on the \eigen 3.1 (or later) library. -* -* @tparam PolygonMesh a model of `FaceListGraph`. -* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters". -* -* @param pmesh the polygon mesh. -* @param v the vertex of `pmesh` to compute the principal curvatures and directions at. -* @param np an 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 `%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{vertex_normal_map} -* \cgalParamDescription{a property map associating normal vectors to the vertices of `pmesh`.} -* \cgalParamType{a class model of `ReadablePropertyMap` with -* `boost::graph_traits::%vertex_descriptor` -* as key type and `%Vector_3` as value type.} -* \cgalParamDefault{`get(dynamic_vertex_property_t(), pmesh)`.} -* \cgalParamExtra{If this parameter is omitted, vertex normals will be -* computed using `compute_vertex_normals()`.} -* \cgalParamNEnd -* -* \cgalParamNBegin{geom_traits} -* \cgalParamDescription{an instance of a geometric traits class.} -* \cgalParamType{a class model of `Kernel`.} +* \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 * -* \cgalParamNBegin{ball_radius} -* \cgalParamDescription{a scalar value specifying the radius used for expanding curvature measures -* by summing measures of faces inside a ball of this radius centered at the -* vertex expanded from. The summed face measures are weighted by their -* inclusion ratio inside this ball.} -* \cgalParamType{`GT::FT`} -* \cgalParamDefault{`-1`} -* \cgalParamExtra{If this parameter is omitted (`-1`), the expansion will just by a weightless sum of -* measures on faces around the vertex.} -* \cgalParamNEnd * \cgalNamedParamsEnd -* -* @return the interpolated corrected principal curvatures and directions at the vertex `v`. -* -* @see `interpolated_corrected_principal_curvatures_and_directions()` -* @see `interpolated_corrected_mean_curvature_one_vertex()` -* @see `interpolated_corrected_Gaussian_curvature_one_vertex()` -* @see `interpolated_corrected_curvatures_one_vertex()` */ - -template -#ifdef DOXYGEN_RUNNING -Principal_curvatures_and_directions -#else -Principal_curvatures_and_directions::type> -#endif -interpolated_corrected_principal_curvatures_and_directions_one_vertex(const PolygonMesh& pmesh, - typename boost::graph_traits::vertex_descriptor v, - const NamedParameters& np = parameters::default_values()) +template +void interpolated_corrected_curvatures(VertexDescriptor v, + const PolygonMesh& pmesh, + const CGAL_NP_CLASS& np = parameters::default_values()) { - using GT=typename GetGeomTraits::type; - Principal_curvatures_and_directions pcd; - interpolated_corrected_curvatures_one_vertex(pmesh, v, np.vertex_principal_curvatures_and_directions(std::ref(pcd))); - return pcd; + internal::interpolated_corrected_curvatures_one_vertex(pmesh, v, np); } } // namespace Polygon_mesh_processing diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp index 2f1af8adb106..c6a578c795a8 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_interpolated_corrected_curvatures.cpp @@ -95,31 +95,14 @@ void test_average_curvatures(std::string mesh_path, >>::type principal_curvatures_and_directions_map = get(CGAL::dynamic_vertex_property_t>(), pmesh); - // test_info.expansion_radius -> test if no radius is provided by user. - if (test_info.expansion_radius < 0) { - PMP::interpolated_corrected_mean_curvature(pmesh, mean_curvature_map); - PMP::interpolated_corrected_Gaussian_curvature(pmesh, gaussian_curvature_map); - PMP::interpolated_corrected_principal_curvatures_and_directions(pmesh, principal_curvatures_and_directions_map); - } - else { - PMP::interpolated_corrected_mean_curvature( - pmesh, - mean_curvature_map, - CGAL::parameters::ball_radius(test_info.expansion_radius) - ); - - PMP::interpolated_corrected_Gaussian_curvature( - pmesh, - gaussian_curvature_map, - CGAL::parameters::ball_radius(test_info.expansion_radius) - ); - - PMP::interpolated_corrected_principal_curvatures_and_directions( - pmesh, - principal_curvatures_and_directions_map, - CGAL::parameters::ball_radius(test_info.expansion_radius) - ); - } + + PMP::interpolated_corrected_curvatures( + pmesh, + CGAL::parameters::ball_radius(test_info.expansion_radius) + .vertex_mean_curvature_map(mean_curvature_map) + .vertex_Gaussian_curvature_map(gaussian_curvature_map) + .vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) + ); Epic_kernel::FT mean_curvature_avg = 0, gaussian_curvature_avg = 0, principal_curvature_avg = 0; @@ -139,15 +122,6 @@ void test_average_curvatures(std::string mesh_path, assert(passes_comparison(gaussian_curvature_avg, test_info.gaussian_curvature_avg, test_info.tolerance)); assert(passes_comparison(principal_curvature_avg, test_info.principal_curvature_avg, test_info.tolerance)); - // computing curvatures together from interpolated_corrected_curvatures() - PMP::interpolated_corrected_curvatures( - pmesh, - CGAL::parameters::ball_radius(test_info.expansion_radius) - .vertex_mean_curvature_map(mean_curvature_map) - .vertex_Gaussian_curvature_map(gaussian_curvature_map) - .vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) - ); - Epic_kernel::FT new_mean_curvature_avg = 0, new_Gaussian_curvature_avg = 0, new_principal_curvature_avg = 0; for (vertex_descriptor v : vertices(pmesh)) { @@ -178,9 +152,9 @@ void test_average_curvatures(std::string mesh_path, PMP::Principal_curvatures_and_directions p; for (vertex_descriptor v : vertices(pmesh)) { - PMP::interpolated_corrected_curvatures_one_vertex( - pmesh, + PMP::interpolated_corrected_curvatures( v, + pmesh, CGAL::parameters::vertex_Gaussian_curvature(std::ref(g)) .vertex_mean_curvature(std::ref(h)) .vertex_principal_curvatures_and_directions(std::ref(p)) From dbd706a3f43ced1af96095148ea4809c737316de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 9 Oct 2023 09:14:05 +0200 Subject: [PATCH 154/161] remove remaining see also --- .../interpolated_corrected_curvatures.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index c2c27b1733b2..438bdaad2625 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -1112,9 +1112,6 @@ class Interpolated_corrected_curvatures_computer * * \cgalNamedParamsEnd * -* @see `interpolated_corrected_mean_curvature()` -* @see `interpolated_corrected_Gaussian_curvature()` -* @see `interpolated_corrected_principal_curvatures_and_directions()` */ template From 5b67e30e8678cb3b84bf5e20bcbebb99a8d88934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 9 Oct 2023 17:08:49 -0700 Subject: [PATCH 155/161] fix demo --- .../Polyhedron/Plugins/Display/Display_property_plugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp index e5baf54cfd7e..687cb6a92dfd 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Display/Display_property_plugin.cpp @@ -861,11 +861,11 @@ private Q_SLOTS: if (curvature_type == MEAN_CURVATURE) { - CGAL::Polygon_mesh_processing::interpolated_corrected_mean_curvature(*sm, vcurvature); + CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures(*sm, CGAL::parameters::vertex_mean_curvature(vcurvature)); } else if (curvature_type == GAUSSIAN_CURVATURE) { - CGAL::Polygon_mesh_processing::interpolated_corrected_Gaussian_curvature(*sm, vcurvature); + CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures(*sm, CGAL::parameters::vertex_Gaussian_curvature(vcurvature)); } displaySMProperty(tied_string, *sm); From ffea30946f999afce6b8d918728dc49d87f36354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 9 Oct 2023 18:55:33 -0700 Subject: [PATCH 156/161] fix the second demo plugin --- .../Interpolated_corrected_principal_curvatures_plugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp index 8bb6a229967b..33250cf3e81a 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Interpolated_corrected_principal_curvatures_plugin.cpp @@ -75,10 +75,10 @@ void compute(SMesh* sMesh, assert(created); - PMP::interpolated_corrected_principal_curvatures_and_directions( + PMP::interpolated_corrected_curvatures( *sMesh, - principal_curvatures_and_directions_map, CGAL::parameters::ball_radius(0) + .vertex_principal_curvatures_and_directions_map(principal_curvatures_and_directions_map) ); double max_curvature_magnitude_on_mesh = 0; From 9f9a6f600f65282796c3e8de3dc8841dc3024aac Mon Sep 17 00:00:00 2001 From: Sebastien Loriot Date: Mon, 23 Oct 2023 09:07:43 +0200 Subject: [PATCH 157/161] Apply suggestions from code review Co-authored-by: Jane Tournois --- .../PackageDescription.txt | 2 +- .../Polygon_mesh_processing.txt | 18 +++++++++--------- .../interpolated_corrected_curvatures.h | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 0a1c08909eeb..035781b35c7e 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -79,7 +79,7 @@ \cgalPkgPicture{hole_filling_ico.png} \cgalPkgSummaryBegin -\cgalPkgAuthors{David Coeurjolly, Jaques-Olivier Lachaud, Konstantinos Katriopla, Sébastien Loriot, Mael Rouxel-Labbé, Hossam Saeed, Jane Tournois, and Ilker %O. Yaz} +\cgalPkgAuthors{David Coeurjolly, Jaques-Olivier Lachaud, Konstantinos Katrioplas, Sébastien Loriot, Mael Rouxel-Labbé, Hossam Saeed, Jane Tournois, 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.} 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 1f6249c0ee89..fa1e527cd775 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 David Coeurjolly, Jaques-Olivier Lachaud, Konstantinos Katriopla, Sébastien Loriot, Mael Rouxel-Labbé, Hossam Saeed, Jane Tournois, and Ilker %O. Yaz +\authors David Coeurjolly, Jaques-Olivier Lachaud, Konstantinos Katrioplas, Sébastien Loriot, Mael Rouxel-Labbé, Hossam Saeed, Jane Tournois, and Ilker %O. Yaz \image html neptun_head.jpg \image latex neptun_head.jpg @@ -944,7 +944,7 @@ Corrected Curvatures on Polyhedral Surfaces \cgalCite{cgal:lrtc-iccmps-20}. This Gaussian curvature, principal curvatures and directions. These can be computed on triangle meshes, quad meshes, and meshes with n-gon faces (for n-gons, the centroid must be inside the n-gon face). The algorithms used prove to work well in general. Also, on meshes with noise on vertex positions, -they give accurate results, on the condition that the correct vertex normals are provided. +they give accurate results, under the condition that the correct vertex normals are provided. \subsection ICCBackground Brief Background @@ -959,7 +959,7 @@ The algorithms are based on the two papers \cgalCite{cgal:lrt-ccm-22} and \cgalC introduce a new way to compute curvatures on polygonal meshes. The main idea in \cgalCite{cgal:lrt-ccm-22} is based on decoupling the normal information from the position information, which is useful for dealing with digital surfaces, or meshes with noise on vertex positions. \cgalCite{cgal:lrtc-iccmps-20} introduces some -extensions to this framework. As it uses linear interpolation on the corrected normal vector field +extensions to this framework, as it uses linear interpolation on the corrected normal vector field to derive new closed form equations for the corrected curvature measures. These interpolated curvature measures are the first step for computing the curvatures. For a triangle \f$ \tau_{ijk} \f$, with vertices \a i, \a j, \a k: @@ -984,7 +984,7 @@ solver. The interpolated curvature measures are then computed for each vertex \f$ v \f$ as the sum of the curvature measures of the faces in a ball around \f$ v \f$ weighted by the inclusion ratio of the -triangle in the ball. if the ball radius is not specified, the sum is instead over the incident faces +triangle in the ball. If the ball radius is not specified, the sum is instead computed over the incident faces of \f$ v \f$. To get the final curvature value for a vertex \f$ v \f$, the respective interpolated curvature measure @@ -1000,14 +1000,14 @@ The implementation is generic in terms of mesh data structure. It can be used on `Polyhedron_3` and other polygonal mesh structures based on the concept `FaceGraph`. These computations are performed using (on all vertices of the mesh) `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` -where function named parameters are use to select the curvatures (and possibly directions) to be computed. An overlay with the same named -but accepting and accepting a given vertex is also available in case the computation should be done only for that vertex. +where function named parameters are used to select the curvatures (and possibly directions) to be computed. An overlay with the same name +but taking a given vertex is also available in case the computation should be done only for that vertex. \subsection ICCResults Results & Performance **To be updated** -First, \cgalFigureRef{icc_measures} first illustrates various curvature measure on a triangular mesh. +First, \cgalFigureRef{icc_measures} illustrates various curvature measures on a triangular mesh. \cgalFigureAnchor{icc_measures}
@@ -1024,7 +1024,7 @@ First, \cgalFigureRef{icc_measures} first illustrates various curvature measure \cgalFigureCaptionBegin{icc_measures} -Mean curvature, Gaussian curvature, minimal principal curvature direction and minimal principal curvature direction on a mesh (ball radius set to 0.04). +Mean curvature, Gaussian curvature, minimal principal curvature direction and maximal principal curvature direction on a mesh (ball radius set to 0.04). \cgalFigureCaptionEnd \cgalFigureAnchor{icc_various_ball_radii} @@ -1073,7 +1073,7 @@ not provide storage for the curvatures. \subsection ICCExampleSV Interpolated Corrected Curvatures on a Vertex Example The following example illustrates how to -compute the curvatures on a specific vertex +compute the curvatures on a specific vertex. \cgalExample{Polygon_mesh_processing/interpolated_corrected_curvatures_vertex.cpp} diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index 438bdaad2625..e5587f27aa07 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -1135,7 +1135,7 @@ void interpolated_corrected_curvatures(const PolygonMesh& pmesh, * * @param pmesh the polygon mesh. * @param v the vertex of `pmesh` to compute the curvatures at. -* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below +* @param np an 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 @@ -1153,7 +1153,7 @@ void interpolated_corrected_curvatures(const PolygonMesh& pmesh, * \cgalParamNEnd * * \cgalParamNBegin{vertex_principal_curvatures_and_directions} -* \cgalParamDescription{a reference to an`Principal_curvatures_and_directions` object to store the principal curvatures and directions at the vertex `v`.} +* \cgalParamDescription{a reference to a `Principal_curvatures_and_directions` object to store the principal curvatures and directions at the vertex `v`.} * \cgalParamType{`std::reference_wrapper>`.} * \cgalParamExtra{If this parameter is omitted, principal curvatures and directions will not be computed.} * \cgalParamNEnd From 4c315c1ff8692813a4e7b3e11594e0476fa6f1b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 13 Nov 2023 15:18:26 +0100 Subject: [PATCH 158/161] fixes after review --- .../Polygon_mesh_processing.txt | 2 +- .../interpolated_corrected_curvatures.h | 103 ++++++++---------- 2 files changed, 44 insertions(+), 61 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 fa1e527cd775..132669736b7a 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 @@ -1000,7 +1000,7 @@ The implementation is generic in terms of mesh data structure. It can be used on `Polyhedron_3` and other polygonal mesh structures based on the concept `FaceGraph`. These computations are performed using (on all vertices of the mesh) `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` -where function named parameters are used to select the curvatures (and possibly directions) to be computed. An overlay with the same name +where function named parameters are used to select the curvatures (and possibly directions) to be computed. An overload function with the same name but taking a given vertex is also available in case the computation should be done only for that vertex. \subsection ICCResults Results & Performance diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h index e5587f27aa07..25de9ec9387f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h @@ -378,26 +378,6 @@ std::array interpolated_corrected_anisotropic_measure_fa return muXY; } -//template -//typename GT::FT triangle_in_ball_ratio(const typename GT::Vector_3 x1, -// const typename GT::Vector_3 x2, -// const typename GT::Vector_3 x3, -// const typename GT::FT r, -// const typename GT::Vector_3 c, -// const std::size_t res = 3) -//{ -// const typename GT::FT R = r * r; -// const typename GT::FT acc = 1.0 / res; -// std::size_t samples_in = 0; -// for (typename GT::FT alpha = acc / 3; alpha < 1; alpha += acc) -// for (typename GT::FT beta = acc / 3; beta < 1 - alpha; beta += acc) -// { -// if ((alpha * x1 + beta * x2 + (1 - alpha - beta) * x3 - c).squared_length() < R) -// samples_in++; -// } -// return samples_in / (typename GT::FT)(res * (res + 1) / 2); -//} - template typename GT::FT face_in_ball_ratio(const std::vector& x, const typename GT::FT r, @@ -490,8 +470,8 @@ Vertex_measures interpolated_corrected_measures_one_vertex_no_radius( const VNM vnm ) { - typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename GT::Point_3 Point_3; typedef typename GT::Vector_3 Vector_3; typedef typename GT::FT FT; @@ -502,14 +482,14 @@ Vertex_measures interpolated_corrected_measures_one_vertex_no_radius( std::vector u; // compute for each face around the vertex (except the null (boundary) face) - for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + for (face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { if (f != boost::graph_traits::null_face()) { // looping over vertices in face to get point coordinates and normal vectors - for (Vertex_descriptor vi : vertices_around_face(halfedge(f, pmesh), pmesh)) + for (vertex_descriptor vi : vertices_around_face(halfedge(f, pmesh), pmesh)) { - Point_3 pi = get(vpm, vi); - Vector_3 ui = get(vnm, vi); + const Point_3& pi = get(vpm, vi); + const Vector_3& ui = get(vnm, vi); x.push_back(Vector_3(pi.x(), pi.y(), pi.z())); u.push_back(ui); } @@ -550,22 +530,22 @@ Vertex_measures interpolated_corrected_measures_one_vertex( const VNM vnm ) { - typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename GT::Point_3 Point_3; typedef typename GT::Vector_3 Vector_3; typedef typename GT::FT FT; // the ball expansion is done using a BFS traversal from the vertex - std::queue bfs_queue; - std::unordered_set bfs_visited; + std::queue bfs_queue; + std::unordered_set bfs_visited; Vertex_measures vertex_measures; - Point_3 vp = get(vpm, v); - Vector_3 c = Vector_3(vp.x(), vp.y(), vp.z()); + const Point_3& vp = get(vpm, v); + Vector_3 c(vp.x(), vp.y(), vp.z()); - for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + for (face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { if (f != boost::graph_traits::null_face()) { bfs_queue.push(f); @@ -575,14 +555,14 @@ Vertex_measures interpolated_corrected_measures_one_vertex( std::vector x; std::vector u; while (!bfs_queue.empty()) { - Face_descriptor fi = bfs_queue.front(); + face_descriptor fi = bfs_queue.front(); bfs_queue.pop(); // looping over vertices in face to get point coordinates and normal vectors - for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) + for (vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) { - Point_3 pi = get(vpm, vi); - Vector_3 ui = get(vnm, vi); + const Point_3& pi = get(vpm, vi); + const Vector_3& ui = get(vnm, vi); x.push_back(Vector_3(pi.x(), pi.y(), pi.z())); u.push_back(ui); } @@ -609,7 +589,7 @@ Vertex_measures interpolated_corrected_measures_one_vertex( vertex_measures.anisotropic_measure[i] += f_ratio * face_anisotropic_measure[i]; } - for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) + for (face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) { if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) { @@ -724,7 +704,7 @@ template principal_curvatures_and_directions = principal_curvatures_and_directions_from_anisotropic_measures( vertex_measures.anisotropic_measure, vertex_measures.area_measure, @@ -747,8 +727,8 @@ class Interpolated_corrected_curvatures_computer typedef typename boost::graph_traits::halfedge_descriptor Halfedge_descriptor; typedef typename boost::graph_traits::edge_descriptor Edge_descriptor; - typedef typename boost::graph_traits::face_descriptor Face_descriptor; - typedef typename boost::graph_traits::vertex_descriptor Vertex_descriptor; + typedef typename boost::graph_traits::face_descriptor face_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; typedef typename GetVertexPointMap::const_type Vertex_position_map; @@ -758,7 +738,7 @@ class Interpolated_corrected_curvatures_computer NamedParameters, Default_vector_map>::type Vertex_normal_map; - typedef Constant_property_map Default_scalar_map; + typedef Constant_property_map Default_scalar_map; typedef typename internal_np::Lookup_named_param_def::type Vertex_mean_curvature_map; @@ -767,7 +747,7 @@ class Interpolated_corrected_curvatures_computer NamedParameters, Default_scalar_map>::type Vertex_Gaussian_curvature_map; - 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; @@ -867,12 +847,15 @@ class Interpolated_corrected_curvatures_computer { std::vector x; std::vector u; + // minimal number of vertices per face is 3 + x.reserve(3); + u.reserve(3); - for (Face_descriptor f : faces(pmesh)) + for (face_descriptor f : faces(pmesh)) { - for (Vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) + for (vertex_descriptor v : vertices_around_face(halfedge(f, pmesh), pmesh)) { - Point_3 p = get(vpm, v); + const Point_3& p = get(vpm, v); x.push_back(Vector_3(p.x(), p.y(), p.z())); u.push_back(get(vnm, v)); } @@ -893,12 +876,12 @@ class Interpolated_corrected_curvatures_computer } // expand the measures of the faces incident to v - Vertex_measures expand_interpolated_corrected_measure_vertex_no_radius(Vertex_descriptor v) + Vertex_measures expand_interpolated_corrected_measure_vertex_no_radius(vertex_descriptor v) { Vertex_measures vertex_measures; // add the measures of the faces incident to v (excluding the null (boundary) face) - for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + for (face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { if (f == boost::graph_traits::null_face()) continue; @@ -923,19 +906,19 @@ class Interpolated_corrected_curvatures_computer } // expand the measures of the faces inside the ball of radius r around v - Vertex_measures expand_interpolated_corrected_measure_vertex(Vertex_descriptor v) + Vertex_measures expand_interpolated_corrected_measure_vertex(vertex_descriptor v) { // the ball expansion is done using a BFS traversal from the vertex - std::queue bfs_queue; - std::unordered_set bfs_visited; + std::queue bfs_queue; + std::unordered_set bfs_visited; - Point_3 vp = get(vpm, v); - Vector_3 c = Vector_3(vp.x(), vp.y(), vp.z()); + const Point_3& vp = get(vpm, v); + const Vector_3& c = Vector_3(vp.x(), vp.y(), vp.z()); Vertex_measures vertex_measures; // add the measures of the faces incident to v (excluding the null (boundary) face) - for (Face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { + for (face_descriptor f : faces_around_target(halfedge(v, pmesh), pmesh)) { if (f != boost::graph_traits::null_face()) { bfs_queue.push(f); @@ -943,14 +926,14 @@ class Interpolated_corrected_curvatures_computer } } while (!bfs_queue.empty()) { - Face_descriptor fi = bfs_queue.front(); + face_descriptor fi = bfs_queue.front(); bfs_queue.pop(); // looping over vertices in face to get point coordinates std::vector x; - for (Vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) + for (vertex_descriptor vi : vertices_around_face(halfedge(fi, pmesh), pmesh)) { - Point_3 pi = get(vpm, vi); + const Point_3& pi = get(vpm, vi); x.push_back(Vector_3(pi.x(), pi.y(), pi.z())); } @@ -976,7 +959,7 @@ class Interpolated_corrected_curvatures_computer vertex_measures.anisotropic_measure[i] += f_ratio * face_anisotropic_measure[i]; } - for (Face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) + for (face_descriptor fj : faces_around_face(halfedge(fi, pmesh), pmesh)) { if (bfs_visited.find(fj) == bfs_visited.end() && fj != boost::graph_traits::null_face()) { @@ -992,7 +975,7 @@ class Interpolated_corrected_curvatures_computer void compute_selected_curvatures() { interpolated_corrected_selected_measures_all_faces(); - for (Vertex_descriptor v : vertices(pmesh)) + for (vertex_descriptor v : vertices(pmesh)) { // expand the computed measures (on faces) to the vertices Vertex_measures vertex_measures = (is_negative(ball_radius)) ? @@ -1015,7 +998,7 @@ class Interpolated_corrected_curvatures_computer if (is_principal_curvatures_and_directions_selected) { // compute the principal curvatures and directions from the anisotropic measure - const Vector_3 v_normal = get(vnm, v); + const Vector_3& v_normal = get(vnm, v); const Principal_curvatures_and_directions principal_curvatures_and_directions = principal_curvatures_and_directions_from_anisotropic_measures( vertex_measures.anisotropic_measure, vertex_measures.area_measure, From 724c1f0552ed62688bd450b7b1305d04d8ea33e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 13 Nov 2023 15:22:12 +0100 Subject: [PATCH 159/161] remove TODO added in an issue --- .../doc/Polygon_mesh_processing/Polygon_mesh_processing.txt | 2 -- 1 file changed, 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 132669736b7a..b2e02abbcbaa 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 @@ -1005,8 +1005,6 @@ but taking a given vertex is also available in case the computation should be do \subsection ICCResults Results & Performance -**To be updated** - First, \cgalFigureRef{icc_measures} illustrates various curvature measures on a triangular mesh. \cgalFigureAnchor{icc_measures} From c7be554c0fa6645cf4937bfce02ec407d9fb541a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 13 Nov 2023 16:08:42 +0100 Subject: [PATCH 160/161] update after rebase --- .../doc/Polygon_mesh_processing/Doxyfile.in | 22 ++++++++--------- .../Polygon_mesh_processing.txt | 24 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in index c2db3e16577f..f6ed67912458 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in @@ -23,17 +23,17 @@ EXCLUDE_SYMBOLS += experimental HTML_EXTRA_FILES = ${CGAL_PACKAGE_DOC_DIR}/fig/selfintersections.jpg \ ${CGAL_PACKAGE_DOC_DIR}/fig/mesh_smoothing.png \ ${CGAL_PACKAGE_DOC_DIR}/fig/shape_smoothing.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.040000-0.000000.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.040000-0.000000.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.040000-0.000000.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.020000-0.000000.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.020000-0.002000.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.030000-0.000000.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.030000-0.002000.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.040000-0.000000.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.040000-0.002000.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.050000-0.000000.png \ - ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.050000-0.002000.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmax0.040000-0.000000.jpg \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-dmin0.040000-0.000000.jpg \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-gaussian0.040000-0.000000.jpg \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.020000-0.000000.jpg \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.020000-0.002000.jpg \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.030000-0.000000.jpg \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.030000-0.002000.jpg \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.040000-0.000000.jpg \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.040000-0.002000.jpg \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.050000-0.000000.jpg \ + ${CGAL_PACKAGE_DOC_DIR}/fig/bimba-mean0.050000-0.002000.jpg \ ${CGAL_PACKAGE_DOC_DIR}/fig/decimate_cheese.png \ ${CGAL_PACKAGE_DOC_DIR}/fig/decimate_colors.png \ ${CGAL_PACKAGE_DOC_DIR}/fig/decimate_rg_joint.png 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 b2e02abbcbaa..930a03523c90 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 @@ -1011,10 +1011,10 @@ First, \cgalFigureRef{icc_measures} illustrates various curvature measures on a
- - - - + + + +
(a)(b)
@@ -1029,16 +1029,16 @@ Mean curvature, Gaussian curvature, minimal principal curvature direction and ma
- - - - + + + + - - - - + + + +
(a)(b)
From 8518e7d0d988adfb6a815aae9a1f0df2f1c88a6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 13 Nov 2023 18:40:03 +0100 Subject: [PATCH 161/161] update changes --- Installation/CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index 479984002682..47c8f2679634 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -35,6 +35,10 @@ Release date: October 2023 - Removed the class templates `Gray_image_mesh_domain_3`, `Implicit_mesh_domain_3`, and `Labeled_image_mesh_domain_3` which are deprecated since CGAL-4.13. +### [Polygon Mesh Processing](https://doc.cgal.org/6.0/Manual/packages.html#PkgPolygonMeshProcessing) + +- Added the function `CGAL::Polygon_mesh_processing::interpolated_corrected_curvatures()` which can be used to compute + the mean and Gaussian curvatures, as well as the principal curvature and directions. [Release 5.6](https://github.com/CGAL/cgal/releases/tag/v5.6) -----------