From 2577fd912e4f3cb00dd62c79b45197047e0ee68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 18 Jul 2016 15:02:06 +0200 Subject: [PATCH 01/72] WIP to add Hausdorff distance to a mesh --- .../PackageDescription.txt | 10 + .../CGAL/Polygon_mesh_processing/distance.h | 319 +++++++++++++++--- .../Polygon_mesh_processing/CMakeLists.txt | 9 + .../test_pmp_distance.cpp | 62 ++++ 4 files changed, 359 insertions(+), 41 deletions(-) create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index dd4bbd9fd2d..b17816a7e2b 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -40,6 +40,12 @@ /// Functions to orient polygon soups and to stitch geometrically identical boundaries. /// \ingroup PkgPolygonMeshProcessing +/// \defgroup PMP_distance_grp Combinatorial Repairing +/// Functions to compute the distance between meshes. +/// \ingroup PkgPolygonMeshProcessing + + + /*! \addtogroup PkgPolygonMeshProcessing @@ -126,6 +132,10 @@ and provides a list of the parameters that are used in this package. - \link measure_grp `CGAL::Polygon_mesh_processing::edge_length()` \endlink - \link measure_grp `CGAL::Polygon_mesh_processing::face_border_length()` \endlink +## Distance Functions ## +- `CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance()` +- `CGAL::Polygon_mesh_processing::approximated_symmetric_Hausdorff_distance()` + ## Miscellaneous ## - `CGAL::Polygon_mesh_slicer` - `CGAL::Side_of_triangle_mesh` diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index ba90c8e4031..0490a784929 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -28,8 +28,18 @@ #include #include #include +#include +#include + + +#include + +#ifdef CGAL_LINKED_WITH_TBB +#include +#include +#include +#endif // CGAL_LINKED_WITH_TBB -/// \cond SKIP_IN_MANUAL namespace CGAL{ namespace Polygon_mesh_processing { @@ -37,17 +47,17 @@ namespace internal{ template OutputIterator -triangle_grid_sampling(const typename Kernel::Point_3& p0, - const typename Kernel::Point_3& p1, - const typename Kernel::Point_3& p2, - double distance, - OutputIterator out) +triangle_grid_sampling( const typename Kernel::Point_3& p0, + const typename Kernel::Point_3& p1, + const typename Kernel::Point_3& p2, + double distance, + OutputIterator out) { const double d_p0p1 = CGAL::sqrt( CGAL::squared_distance(p0, p1) ); const double d_p0p2 = CGAL::sqrt( CGAL::squared_distance(p0, p2) ); const double n = (std::max)(std::ceil( d_p0p1 / distance ), - std::ceil( d_p0p2 / distance )); + std::ceil( d_p0p2 / distance )); for (double i=1; i +#ifdef CGAL_LINKED_WITH_TBB +template +struct Distance_computation{ + const AABB_tree& tree; + const std::vector& sample_points; + cpp11::atomic* distance; + + Distance_computation(const AABB_tree& tree, const std::vector& sample_points, cpp11::atomic* d) + : tree(tree) + , sample_points(sample_points) + , distance(d) + {} + + void + operator()(const tbb::blocked_range& range) const + { + double hdist = 0; + Point_3 hint = sample_points.front(); + + for( std::size_t i = range.begin(); i != range.end(); ++i) + { + hint = tree.closest_point(sample_points[i], hint); + double d = CGAL::sqrt( squared_distance(hint,sample_points[i]) ); + if (d>hdist) hdist=d; + } + + if (hdist > distance->load(cpp11::memory_order_acquire)) + distance->store(hdist, cpp11::memory_order_release); + } +}; +#endif + +/// \todo update me to work with a triangulated surface mesh +template +double approximated_Hausdorff_distance( + std::vector& sample_points, + const TriangleRange& triangles) +{ + #ifdef CGAL_HAUSDORFF_DEBUG + std::cout << "Nb sample points " << sample_points.size() << "\n"; + #endif + spatial_sort(sample_points.begin(), sample_points.end()); + + /// \todo shall we use Simple_cartesian for the distance computation? + /// check if this can be problematic to have non-exact predicates. + typedef typename TriangleRange::const_iterator Triangle_iterator; + typedef AABB_triangle_primitive Primitive; + typedef AABB_traits Traits; + typedef AABB_tree< Traits > Tree; + + Tree tree(triangles.begin(), triangles.end()); + tree.accelerate_distance_queries(); + tree.build(); + +#ifndef CGAL_LINKED_WITH_TBB + CGAL_static_assertion_msg (!(boost::is_convertible::value), + "Parallel_tag is enabled but TBB is unavailable."); +#else + if (boost::is_convertible::value) + { + cpp11::atomic distance(0); + Distance_computation f(tree, sample_points, &distance); + tbb::parallel_for(tbb::blocked_range(0, sample_points.size()), f); + return distance; + } + else +#endif + { + double hdist = 0; + typename Traits::Point_3 hint = sample_points.front(); + BOOST_FOREACH(const typename Kernel::Point_3& pt, sample_points) + { + hint = tree.closest_point(pt, hint); + double d = CGAL::sqrt( squared_distance(hint,pt) ); + if (d>hdist) hdist=d; + } + return hdist; + } +} + +/// \todo remove me (except if you find an easy way avoid the ambiguity with TriangleMesh overload +template double approximated_Hausdorff_distance( const TriangleRange1& triangles_1, const TriangleRange2& triangles_2, @@ -124,32 +215,15 @@ double approximated_Hausdorff_distance( ) { std::vector sample_points; + sample_triangles( triangles_1, targeted_precision, std::back_inserter(sample_points) ); - // std::ofstream out("/tmp/samples.xyz"); - // std::copy(sample_points.begin(), sample_points.end(), std::ostream_iterator(out,"\n")); - - typedef typename TriangleRange2::const_iterator Triangle_iterator; - typedef AABB_triangle_primitive Primitive; - typedef AABB_traits Traits; - typedef AABB_tree< Traits > Tree; - - Tree tree(triangles_2.begin(), triangles_2.end()); - tree.accelerate_distance_queries(); - - double hdist = 0; - BOOST_FOREACH(const typename Kernel::Point_3& pt, sample_points) - { - double d = CGAL::sqrt( tree.squared_distance(pt) ); - // if ( d > 1e-1 ) std::cout << pt << "\n"; - if (d>hdist) hdist=d; - } - - return hdist; + return approximated_Hausdorff_distance(sample_points, triangles_2); } -template +/// \todo remove me (except if you find an easy way avoid the ambiguity with TriangleMesh overload +template double approximated_symmetric_Hausdorff_distance( const TriangleRange1& triangles_1, const TriangleRange2& triangles_2, @@ -157,26 +231,189 @@ double approximated_symmetric_Hausdorff_distance( ) { return (std::max)( - approximated_Hausdorff_distance(triangles_1, triangles_2, targeted_precision), - approximated_Hausdorff_distance(triangles_2, triangles_1, targeted_precision) + approximated_Hausdorff_distance(triangles_1, triangles_2, targeted_precision), + approximated_Hausdorff_distance(triangles_2, triangles_1, targeted_precision) ); } -///\todo add approximated_Hausdorff_distance(FaceGraph, FaceGraph) -///\todo add approximated_Hausdorff_distance(TriangleRange, FaceGraph) -///\todo add approximated_Hausdorff_distance(FaceGraph, TriangleRange) -///\todo add approximated_Hausdorff_distance(PointRange, FaceGraph) -///\todo add approximated_Hausdorff_distance(PointRange, TriangleRange) -///\todo add barycentric_triangle_sampling(Triangle, PointPerAreaUnit) -///\todo add random_triangle_sampling(Triangle, PointPerAreaUnit) -/// \todo maybe we should use a sampling using epec to avoid being too far from the sampled triangle - +/// \todo add a function `sample_triangle_mesh()` (or better name) that provide different sampling methods: +/// - random uniform ( points/area unit) +/// - grid (warning mininum 1 pt / triangle) (parameter = distance between points?) +/// - monte carlo? (random points in each triangle proportional to the face area with 1 pt mini per triangle) +/// The goal is to test different strategies and put the better one in `approximated_Hausdorff_distance()` +/// for particular cases one can still use a specific sampling method together with `max_distance_to_triangle_mesh()` + +/// \todo add a plugin in the demo to display the distance betweem 2 meshes as a texture (if not complicated) + +// documented functions + +/** + * \ingroup PMP_distance_grp + * computes the approximated Hausdorff distance of `tm1` from `tm2` by + * by generating a uniform random point sampling on `tm1`, and by then + * returning the distance of the furthest point from `tm2`. + * + * A parallel version is provided and requires the executable to be + * linked against the Intel TBB library. + * To control the number of threads used, the user may use the `tbb::task_scheduler_init` class. + * See the TBB documentation + * for more details. + * + * @tparam Concurrency_tag enables sequential versus parallel algorithm. + * Possible values are `Sequential_tag` + * and `Parallel_tag`. + * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map + * for `CGAL::vertex_point_t` + * @tparam NamedParameters1 a sequence of \ref namedparameters for `tm1` + * @tparam NamedParameters2 a sequence of \ref namedparameters for `tm2` + * + * @param tm1 the triangle mesh that will be sampled + * @param tm2 the triangle mesh to compute the distance to + * @param precision TODO: either the number of points per squared area unit or something else depending on the result of the testing + * @param np1 optional sequence of \ref namedparameters for `tm1` among the ones listed below + * @param np2 optional sequence of \ref namedparameters for `tm2` among the ones listed below + * + * \cgalNamedParamsBegin + * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh` \cgalParamEnd + * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd + * \cgalNamedParamsEnd + * \todo When using TBB, runtime is slower. The chunk size should probably be tuned. see PMP/test/test_pmp_distance.cpp + */ +template< class Concurrency_tag, + class TriangleMesh, + class NamedParameters1, + class NamedParameters2> +double approximated_Hausdorff_distance( const TriangleMesh& tm1, + const TriangleMesh& tm2, + double precision, + const NamedParameters1& np1, + const NamedParameters2& np2) +{ + typedef typename GetGeomTraits::type Geom_traits; + + /// \todo implement the missing function + return approximated_Hausdorff_distance( + tm1, tm2, + precision, + choose_const_pmap(get_param(np1, boost::vertex_point), + tm1, + vertex_point), + choose_const_pmap(get_param(np2, boost::vertex_point), + tm2, + vertex_point) + + ); +} + +/** + * \ingroup PMP_distance_grp + * computes the approximated symmetric Hausdorff distance between `tm1` and `tm2`. + * It returns the maximum of `approximated_Hausdorff_distance(tm1,tm2)` and + * `approximated_Hausdorff_distance(tm1,tm2)`. + * + * \copydetails CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance() + */ +template< class Concurrency_tag, + class TriangleMesh, + class NamedParameters1, + class NamedParameters2> +double approximated_symmetric_Hausdorff_distance( + const TriangleMesh& tm1, + const TriangleMesh& tm2, + double precision, + const NamedParameters1& np1, + const NamedParameters2& np2) +{ + return (std::max)( + approximated_Hausdorff_distance(tm1,tm2,precision,np1,np2), + approximated_Hausdorff_distance(tm2,tm1,precision,np2,np1) + ); +} + +/// \todo document and implement me +template< class Concurrency_tag, + class TriangleMesh, + class PointRange, + class NamedParameters> +double max_distance_to_triangle_mesh(const PointRange& points, + const TriangleMesh& tm, + const NamedParameters& np) +{ + return 0; +} + +/// \todo document and implement me +/// see with @sgiraudot for the implementation +/// that should be put in a seperate header file +/// with copyright INRIA +/// Maybe find a better name too +template< class Concurrency_tag, + class TriangleMesh, + class PointRange, + class NamedParameters> +double max_distance_to_point_set(const TriangleMesh& tm, + const PointRange& points, + const NamedParameters& np) +{ + return 0; +} + +// convenience functions with default parameters + +template< class Concurrency_tag, + class TriangleMesh, + class NamedParameters1> +double approximated_Hausdorff_distance( const TriangleMesh& tm1, + const TriangleMesh& tm2, + double precision, + const NamedParameters1& np1) +{ + return approximated_Hausdorff_distance( + tm1, tm2, precision, np1, parameters::all_default()); +} + +template< class Concurrency_tag, + class TriangleMesh, + class NamedParameters1> +double approximated_Hausdorff_distance( const TriangleMesh& tm1, + const TriangleMesh& tm2, + double precision) +{ + return approximated_Hausdorff_distance( + tm1, tm2, precision, + parameters::all_default(), + parameters::all_default()); +} +template< class Concurrency_tag, + class TriangleMesh, + class NamedParameters1> +double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1, + const TriangleMesh& tm2, + double precision, + const NamedParameters1& np1) +{ + return approximated_symmetric_Hausdorff_distance( + tm1, tm2, precision, np1, parameters::all_default()); +} + +template< class Concurrency_tag, + class TriangleMesh, + class NamedParameters1> +double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1, + const TriangleMesh& tm2, + double precision) +{ + return approximated_symmetric_Hausdorff_distance( + tm1, tm2, precision, + parameters::all_default(), + parameters::all_default()); +} } } // end of namespace CGAL::Polygon_mesh_processing -/// \endcond #endif //CGAL_POLYGON_MESH_PROCESSING_DISTANCE_H diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index 7eb4eaa9848..69c5bb193ee 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -51,6 +51,14 @@ include_directories( BEFORE ../../include ) find_package(Eigen3 3.1.0) #(requires 3.1.0 or greater) +find_package( TBB ) +if( TBB_FOUND ) + include( ${TBB_USE_FILE} ) + list( APPEND CGAL_3RD_PARTY_LIBRARIES ${TBB_LIBRARIES} ) +else() + message( STATUS "NOTICE: Intel TBB was not found. test_pmp_distance will use sequential code." ) +endif() + if (EIGEN3_FOUND) # Executables that require Eigen 3.1 include( ${EIGEN3_USE_FILE} ) @@ -77,6 +85,7 @@ if (EIGEN3_FOUND) create_single_source_cgal_program("measures_test.cpp") create_single_source_cgal_program("triangulate_faces_test.cpp") create_single_source_cgal_program("test_pmp_remove_border_edge.cpp") + create_single_source_cgal_program("test_pmp_distance.cpp") if(NOT (${EIGEN3_VERSION} VERSION_LESS 3.2.0)) create_single_source_cgal_program("fairing_test.cpp") diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp new file mode 100644 index 00000000000..a35c6f3f656 --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include + + +#include + +#include +#include + +// typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef CGAL::Simple_cartesian K; +typedef CGAL::Surface_mesh Mesh; + + +int main(int argc, char** argv) +{ + Mesh m1,m2; + + std::ifstream input(argv[1]); + input >> m1; + input.close(); + + input.open(argv[2]); + input >> m2; + + std::cout << "First mesh has " << num_faces(m1) << " faces\n"; + std::cout << "Second mesh has " << num_faces(m2) << " faces\n"; + + std::vector t1; + t1.reserve(num_faces(m1)); + CGAL::Triangle_from_face_descriptor_map map1(&m1); + BOOST_FOREACH(Mesh::Face_index f, faces(m1)) + t1.push_back(get(map1,f)); + + std::vector t2; + t2.reserve(num_faces(m2)); + CGAL::Triangle_from_face_descriptor_map map2(&m2); + BOOST_FOREACH(Mesh::Face_index f, faces(m2)) + t2.push_back(get(map2,f)); + + + CGAL::Timer time; + time.start(); + std::cout << "Distance between meshes (parallel)" + << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(t1,t2,0.001) + << "\n"; + time.stop(); + std::cout << "done in " << time.time() << "s.\n"; + + time.reset(); + time.start(); + std::cout << "Distance between meshes (sequential)" + << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(t1,t2,0.001) + << "\n"; + time.stop(); + std::cout << "done in " << time.time() << "s.\n"; +} + + From 09040105a1aa283974192af9c0ac253a643f23e0 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Thu, 11 Aug 2016 10:21:36 +0200 Subject: [PATCH 02/72] Create an example file to test the Hausdorff distance. --- .../examples/Polygon_mesh_processing/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index 0aea8b58750..b1b173b7397 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -82,6 +82,7 @@ create_single_source_cgal_program( "triangulate_polyline_example.cpp") create_single_source_cgal_program( "mesh_slicer_example.cpp") #create_single_source_cgal_program( "remove_degeneracies_example.cpp") create_single_source_cgal_program( "isotropic_remeshing_example.cpp") +create_single_source_cgal_program( "haussdorf_distance_example.cpp") if(NOT (${EIGEN3_VERSION} VERSION_LESS 3.2.0)) create_single_source_cgal_program( "hole_filling_example.cpp" ) From c5a17ce354eeb57c75e12b0d233d2468fc7e1be8 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Thu, 11 Aug 2016 11:44:11 +0200 Subject: [PATCH 03/72] Change `approximated_Hausdorff_distance()` to use a Surface_mesh instead of a triangle range. --- .../Polygon_mesh_processing/CMakeLists.txt | 2 +- .../CGAL/Polygon_mesh_processing/distance.h | 31 +++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index b1b173b7397..e9341d8ac64 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -82,7 +82,7 @@ create_single_source_cgal_program( "triangulate_polyline_example.cpp") create_single_source_cgal_program( "mesh_slicer_example.cpp") #create_single_source_cgal_program( "remove_degeneracies_example.cpp") create_single_source_cgal_program( "isotropic_remeshing_example.cpp") -create_single_source_cgal_program( "haussdorf_distance_example.cpp") +create_single_source_cgal_program( "hausdorff_distance_example.cpp") if(NOT (${EIGEN3_VERSION} VERSION_LESS 3.2.0)) create_single_source_cgal_program( "hole_filling_example.cpp" ) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 0490a784929..75bf799930c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -26,11 +26,14 @@ #include #include #include +#include #include #include #include #include - +#include +#include +#include #include @@ -159,11 +162,20 @@ struct Distance_computation{ #endif /// \todo update me to work with a triangulated surface mesh -template +template double approximated_Hausdorff_distance( - std::vector& sample_points, - const TriangleRange& triangles) + Surface_mesh m, + std::size_t nb_sample_points) { + typedef Surface_mesh Mesh; + typedef Point_3 Point_3; + bool is_triangle = is_triangle_mesh(m); + CGAL_assertion_msg (is_triangle, + "Mesh is not triangulated. Distance computing impossible."); + std::vector sample_points; + Random_points_in_triangle_mesh_3 + g(m); + CGAL::cpp11::copy_n(g, nb_sample_points, std::back_inserter(sample_points)); #ifdef CGAL_HAUSDORFF_DEBUG std::cout << "Nb sample points " << sample_points.size() << "\n"; #endif @@ -171,12 +183,12 @@ double approximated_Hausdorff_distance( /// \todo shall we use Simple_cartesian for the distance computation? /// check if this can be problematic to have non-exact predicates. - typedef typename TriangleRange::const_iterator Triangle_iterator; - typedef AABB_triangle_primitive Primitive; + typedef typename Mesh::face_iterator Triangle_iterator; + typedef AABB_face_graph_triangle_primitive Primitive; typedef AABB_traits Traits; typedef AABB_tree< Traits > Tree; - Tree tree(triangles.begin(), triangles.end()); + Tree tree( faces(m).first, faces(m).second, m); tree.accelerate_distance_queries(); tree.build(); @@ -206,7 +218,8 @@ double approximated_Hausdorff_distance( } } -/// \todo remove me (except if you find an easy way avoid the ambiguity with TriangleMesh overload + +/*/// \todo remove me (except if you find an easy way avoid the ambiguity with TriangleMesh overload template double approximated_Hausdorff_distance( const TriangleRange1& triangles_1, @@ -235,7 +248,7 @@ double approximated_symmetric_Hausdorff_distance( approximated_Hausdorff_distance(triangles_2, triangles_1, targeted_precision) ); } - +*/ /// \todo add a function `sample_triangle_mesh()` (or better name) that provide different sampling methods: /// - random uniform ( points/area unit) From c4a6da2f8b395183c66777aabc7d5dd50f37947e Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Thu, 11 Aug 2016 14:03:52 +0200 Subject: [PATCH 04/72] Transform the overloads to use Surface_mesh instead of triangle meshes. --- .../CGAL/Polygon_mesh_processing/distance.h | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 75bf799930c..05af93fa805 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -161,10 +161,10 @@ struct Distance_computation{ }; #endif -/// \todo update me to work with a triangulated surface mesh template double approximated_Hausdorff_distance( - Surface_mesh m, + std::vector& sample_points, + Surface_mesh& m, std::size_t nb_sample_points) { typedef Surface_mesh Mesh; @@ -172,7 +172,6 @@ double approximated_Hausdorff_distance( bool is_triangle = is_triangle_mesh(m); CGAL_assertion_msg (is_triangle, "Mesh is not triangulated. Distance computing impossible."); - std::vector sample_points; Random_points_in_triangle_mesh_3 g(m); CGAL::cpp11::copy_n(g, nb_sample_points, std::back_inserter(sample_points)); @@ -183,6 +182,7 @@ double approximated_Hausdorff_distance( /// \todo shall we use Simple_cartesian for the distance computation? /// check if this can be problematic to have non-exact predicates. + typedef typename Mesh::face_iterator Triangle_iterator; typedef AABB_face_graph_triangle_primitive Primitive; typedef AABB_traits Traits; @@ -218,37 +218,32 @@ double approximated_Hausdorff_distance( } } - -/*/// \todo remove me (except if you find an easy way avoid the ambiguity with TriangleMesh overload -template +template double approximated_Hausdorff_distance( - const TriangleRange1& triangles_1, - const TriangleRange2& triangles_2, - double targeted_precision + Surface_mesh& m1, + Surface_mesh& m2, + int nb_points ) { std::vector sample_points; - - sample_triangles( triangles_1, - targeted_precision, - std::back_inserter(sample_points) ); - return approximated_Hausdorff_distance(sample_points, triangles_2); + Random_points_in_triangle_mesh_3 > + g(m1); + CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sample_points)); + return approximated_Hausdorff_distance(sample_points, m2,4000); } -/// \todo remove me (except if you find an easy way avoid the ambiguity with TriangleMesh overload -template +template double approximated_symmetric_Hausdorff_distance( - const TriangleRange1& triangles_1, - const TriangleRange2& triangles_2, - double targeted_precision + Surface_mesh& m1, + Surface_mesh& m2, + int nb_points ) { return (std::max)( - approximated_Hausdorff_distance(triangles_1, triangles_2, targeted_precision), - approximated_Hausdorff_distance(triangles_2, triangles_1, targeted_precision) + approximated_Hausdorff_distance(m1, m2, nb_points), + approximated_Hausdorff_distance(m2, m1, nb_points) ); } -*/ /// \todo add a function `sample_triangle_mesh()` (or better name) that provide different sampling methods: /// - random uniform ( points/area unit) From 62e78f34e584a2c9c20fc061c47134f603541aba Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Thu, 11 Aug 2016 14:47:37 +0200 Subject: [PATCH 05/72] WIP --- .../CGAL/Polygon_mesh_processing/distance.h | 175 +++++++++++++----- 1 file changed, 127 insertions(+), 48 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 05af93fa805..dd3877c24fd 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -29,13 +29,12 @@ #include #include #include -#include -#include +//#include +//#include #include -#include -#include #include +#include #ifdef CGAL_LINKED_WITH_TBB #include @@ -46,8 +45,8 @@ namespace CGAL{ namespace Polygon_mesh_processing { +namespace PMP = CGAL::Polygon_mesh_processing; namespace internal{ - template OutputIterator triangle_grid_sampling( const typename Kernel::Point_3& p0, @@ -161,18 +160,18 @@ struct Distance_computation{ }; #endif -template +template ::type> double approximated_Hausdorff_distance( std::vector& sample_points, - Surface_mesh& m, + TriangleMesh& m, std::size_t nb_sample_points) { - typedef Surface_mesh Mesh; typedef Point_3 Point_3; bool is_triangle = is_triangle_mesh(m); CGAL_assertion_msg (is_triangle, "Mesh is not triangulated. Distance computing impossible."); - Random_points_in_triangle_mesh_3 + Random_points_in_triangle_mesh_3 g(m); CGAL::cpp11::copy_n(g, nb_sample_points, std::back_inserter(sample_points)); #ifdef CGAL_HAUSDORFF_DEBUG @@ -180,11 +179,8 @@ double approximated_Hausdorff_distance( #endif spatial_sort(sample_points.begin(), sample_points.end()); - /// \todo shall we use Simple_cartesian for the distance computation? - /// check if this can be problematic to have non-exact predicates. - - typedef typename Mesh::face_iterator Triangle_iterator; - typedef AABB_face_graph_triangle_primitive Primitive; + typedef typename TriangleMesh::face_iterator Triangle_iterator; + typedef AABB_face_graph_triangle_primitive Primitive; typedef AABB_traits Traits; typedef AABB_tree< Traits > Tree; @@ -208,40 +204,48 @@ double approximated_Hausdorff_distance( { double hdist = 0; typename Traits::Point_3 hint = sample_points.front(); - BOOST_FOREACH(const typename Kernel::Point_3& pt, sample_points) + BOOST_FOREACH(const typename Traits::Point_3& pt, sample_points) { hint = tree.closest_point(pt, hint); - double d = CGAL::sqrt( squared_distance(hint,pt) ); + typename Kernel::FT dist = squared_distance(hint,pt); + double d = to_double(CGAL::approximate_sqrt(dist)); if (d>hdist) hdist=d; } return hdist; } } -template +template ::type, + class VertexPointMap2 = typename boost::property_map::type> double approximated_Hausdorff_distance( - Surface_mesh& m1, - Surface_mesh& m2, + TriangleMesh& m1, + TriangleMesh& m2, int nb_points ) { std::vector sample_points; - Random_points_in_triangle_mesh_3 > + Random_points_in_triangle_mesh_3 g(m1); CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sample_points)); - return approximated_Hausdorff_distance(sample_points, m2,4000); + return approximated_Hausdorff_distance(sample_points, m2,4000); } -template +template ::type, + class VertexPointMap2 = typename boost::property_map::type> double approximated_symmetric_Hausdorff_distance( - Surface_mesh& m1, - Surface_mesh& m2, + TriangleMesh& m1, + TriangleMesh& m2, int nb_points ) { return (std::max)( - approximated_Hausdorff_distance(m1, m2, nb_points), - approximated_Hausdorff_distance(m2, m1, nb_points) + approximated_Hausdorff_distance(m1, m2, nb_points), + approximated_Hausdorff_distance(m2, m1, nb_points) ); } @@ -252,14 +256,72 @@ double approximated_symmetric_Hausdorff_distance( /// The goal is to test different strategies and put the better one in `approximated_Hausdorff_distance()` /// for particular cases one can still use a specific sampling method together with `max_distance_to_triangle_mesh()` -/// \todo add a plugin in the demo to display the distance betweem 2 meshes as a texture (if not complicated) +enum Sampling_method{ + RANDOM_UNIFORM =0, + GRID, + MONTE_CARLO }; +template +void sample_triangle_mesh(TriangleMesh& m, + double precision, + std::vector& sampled_points, + Sampling_method method = RANDOM_UNIFORM) +{ + switch(method) + { + std::size_t nb_points = std::ceil(precision * PMP::area(m, + PMP::parameters::geom_traits(Kernel()))); + case RANDOM_UNIFORM: + Random_points_in_triangle_mesh_3 + g(m); + CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); + return; + case GRID: + { + typedef typename boost::property_map::type Pmap; + CGAL::Property_map_to_unary_function pmap(&m); + BOOST_FOREACH(typename TriangleMesh::face_iterator f, faces(m)) + { + typename Kernel::Point_3 points[3]; + typename TriangleMesh::Halfedge_around_face_circulator hc = halfedge(f,m); + for(int i=0; i<3; ++i) + { + points[i] = get(pmap, target(hc, m)); + ++hc; + } + internal::triangle_grid_sampling(points[0], points[1], points[2],100.0/nb_points, std::back_inserter(sampled_points)); + } + return; + } + case MONTE_CARLO: + + break; + } +} +/// \todo add a plugin in the demo to display the distance between 2 meshes as a texture (if not complicated) + +template< class Concurrency_tag, + class Kernel, + class TriangleMesh, + class PMap1, + class PMap2> +double approximated_Hausdorff_distance( TriangleMesh& tm1, + TriangleMesh& tm2, + double precision, + const PMap1&, + const PMap2&) +{ + std::size_t nb_points = std::max(std::ceil(to_double(precision * PMP::area(tm1, + PMP::parameters::geom_traits(Kernel())))), + std::ceil(to_double(precision * PMP::area(tm2, + PMP::parameters::geom_traits(Kernel()))))); + return approximated_Hausdorff_distance(tm1, tm2, nb_points); +} // documented functions - /** * \ingroup PMP_distance_grp * computes the approximated Hausdorff distance of `tm1` from `tm2` by - * by generating a uniform random point sampling on `tm1`, and by then + * generating a uniform random point sampling on `tm1`, and by then * returning the distance of the furthest point from `tm2`. * * A parallel version is provided and requires the executable to be @@ -278,7 +340,7 @@ double approximated_symmetric_Hausdorff_distance( * * @param tm1 the triangle mesh that will be sampled * @param tm2 the triangle mesh to compute the distance to - * @param precision TODO: either the number of points per squared area unit or something else depending on the result of the testing + * @param precision the number of points per squared area unit * @param np1 optional sequence of \ref namedparameters for `tm1` among the ones listed below * @param np2 optional sequence of \ref namedparameters for `tm2` among the ones listed below * @@ -292,8 +354,8 @@ template< class Concurrency_tag, class TriangleMesh, class NamedParameters1, class NamedParameters2> -double approximated_Hausdorff_distance( const TriangleMesh& tm1, - const TriangleMesh& tm2, +double approximated_Hausdorff_distance( TriangleMesh& tm1, + TriangleMesh& tm2, double precision, const NamedParameters1& np1, const NamedParameters2& np2) @@ -301,7 +363,6 @@ double approximated_Hausdorff_distance( const TriangleMesh& tm1, typedef typename GetGeomTraits::type Geom_traits; - /// \todo implement the missing function return approximated_Hausdorff_distance( tm1, tm2, precision, @@ -328,8 +389,8 @@ template< class Concurrency_tag, class NamedParameters1, class NamedParameters2> double approximated_symmetric_Hausdorff_distance( - const TriangleMesh& tm1, - const TriangleMesh& tm2, + TriangleMesh& tm1, + TriangleMesh& tm2, double precision, const NamedParameters1& np1, const NamedParameters2& np2) @@ -341,15 +402,34 @@ double approximated_symmetric_Hausdorff_distance( } /// \todo document and implement me +/// \todo find a way to define precision through named parameters +/** + * \ingroup PMP_distance_grp + * computes the approximated Hausdorff distance between `points` and `tm`. + * \copydetails CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance() + */ template< class Concurrency_tag, class TriangleMesh, class PointRange, class NamedParameters> double max_distance_to_triangle_mesh(const PointRange& points, - const TriangleMesh& tm, + TriangleMesh& tm, + double precision, const NamedParameters& np) { - return 0; + typedef typename GetGeomTraits::type Geom_traits; + std::vector sample_points; + BOOST_FOREACH(typename PointRange::value_type point, points) + sample_points.push_back(point); + + std::size_t nb_points = std::ceil(to_double(precision * PMP::area(tm, + PMP::parameters::geom_traits(Geom_traits())))); + return approximated_Hausdorff_distance + (sample_points,tm, nb_points); } /// \todo document and implement me @@ -361,7 +441,7 @@ template< class Concurrency_tag, class TriangleMesh, class PointRange, class NamedParameters> -double max_distance_to_point_set(const TriangleMesh& tm, +double max_distance_to_point_set(TriangleMesh& tm, const PointRange& points, const NamedParameters& np) { @@ -373,8 +453,8 @@ double max_distance_to_point_set(const TriangleMesh& tm, template< class Concurrency_tag, class TriangleMesh, class NamedParameters1> -double approximated_Hausdorff_distance( const TriangleMesh& tm1, - const TriangleMesh& tm2, +double approximated_Hausdorff_distance( TriangleMesh& tm1, + TriangleMesh& tm2, double precision, const NamedParameters1& np1) { @@ -383,10 +463,9 @@ double approximated_Hausdorff_distance( const TriangleMesh& tm1, } template< class Concurrency_tag, - class TriangleMesh, - class NamedParameters1> -double approximated_Hausdorff_distance( const TriangleMesh& tm1, - const TriangleMesh& tm2, + class TriangleMesh> +double approximated_Hausdorff_distance( TriangleMesh& tm1, + TriangleMesh& tm2, double precision) { return approximated_Hausdorff_distance( @@ -399,8 +478,8 @@ double approximated_Hausdorff_distance( const TriangleMesh& tm1, template< class Concurrency_tag, class TriangleMesh, class NamedParameters1> -double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1, - const TriangleMesh& tm2, +double approximated_symmetric_Hausdorff_distance(TriangleMesh& tm1, + TriangleMesh& tm2, double precision, const NamedParameters1& np1) { @@ -411,8 +490,8 @@ double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1, template< class Concurrency_tag, class TriangleMesh, class NamedParameters1> -double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1, - const TriangleMesh& tm2, +double approximated_symmetric_Hausdorff_distance(TriangleMesh& tm1, + TriangleMesh& tm2, double precision) { return approximated_symmetric_Hausdorff_distance( From 0f13cde758f715c6d3c969a285b434d4be6d868a Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Tue, 16 Aug 2016 16:49:50 +0200 Subject: [PATCH 06/72] Implementation: - cherry-pick Random_generator - implement hausdorff distance functions - implement sample_triangle_mesh --- .../CGAL/Polygon_mesh_processing/distance.h | 91 +++++++++++++------ 1 file changed, 63 insertions(+), 28 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index dd3877c24fd..606cc53a98d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -249,53 +249,88 @@ double approximated_symmetric_Hausdorff_distance( ); } -/// \todo add a function `sample_triangle_mesh()` (or better name) that provide different sampling methods: -/// - random uniform ( points/area unit) -/// - grid (warning mininum 1 pt / triangle) (parameter = distance between points?) -/// - monte carlo? (random points in each triangle proportional to the face area with 1 pt mini per triangle) -/// The goal is to test different strategies and put the better one in `approximated_Hausdorff_distance()` +/// \todo test different strategies and put the better one in `approximated_Hausdorff_distance()` /// for particular cases one can still use a specific sampling method together with `max_distance_to_triangle_mesh()` enum Sampling_method{ - RANDOM_UNIFORM =0, - GRID, - MONTE_CARLO }; - + RANDOM_UNIFORM =0, /**< points are generated in a random and uniform way, depending on the area of each triangle.*/ + GRID,/**< points are generated in a grid, with a minimum of one point per triangle.*/ + MONTE_CARLO /**< points are generated randomly in each triangle, proportionally to the face area with a minimum + * of 1 pt per triangle.*/ +}; +/** fills `sampled_points` with points taken on the mesh in a manner depending on `method`. + * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map + * for `CGAL::vertex_point_t` + * @param m the triangle mesh that will be sampled + * @param precision depends on the value of `method` : + * case RANDOM_UNIFORM : the number of points per squared area unit + * case GRID : the distance between the points + * case MONTE_CARLO : the number of points per squared area unit + * @tparam method a Sampling_method. + * @tparam Sampling_method defines the method of sampling. + * Possible values are `RANDOM_UNIFORM`, + * and `GRID` and `MONTE_CARLO. + */ template -void sample_triangle_mesh(TriangleMesh& m, +void sample_triangle_mesh(const TriangleMesh& m, double precision, std::vector& sampled_points, Sampling_method method = RANDOM_UNIFORM) { switch(method) { - std::size_t nb_points = std::ceil(precision * PMP::area(m, - PMP::parameters::geom_traits(Kernel()))); case RANDOM_UNIFORM: - Random_points_in_triangle_mesh_3 + { + std::size_t nb_points = std::ceil(precision * PMP::area(m, + PMP::parameters::geom_traits(Kernel()))); + Random_points_in_triangle_mesh_3 g(m); - CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); - return; + CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); + return; + } case GRID: { - typedef typename boost::property_map::type Pmap; - CGAL::Property_map_to_unary_function pmap(&m); - BOOST_FOREACH(typename TriangleMesh::face_iterator f, faces(m)) - { - typename Kernel::Point_3 points[3]; - typename TriangleMesh::Halfedge_around_face_circulator hc = halfedge(f,m); - for(int i=0; i<3; ++i) + typedef typename boost::property_map::type Pmap; + Pmap pmap = get(vertex_point, m); + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) { - points[i] = get(pmap, target(hc, m)); - ++hc; - } - internal::triangle_grid_sampling(points[0], points[1], points[2],100.0/nb_points, std::back_inserter(sampled_points)); + typename Kernel::Point_3 points[3]; + typename TriangleMesh::Halfedge_around_face_circulator hc(halfedge(f,m), m); + for(int i=0; i<3; ++i) + { + points[i] = get(pmap, target(*hc, m)); + ++hc; + } + internal::triangle_grid_sampling(points[0], points[1], points[2],precision, std::back_inserter(sampled_points)); } return; } case MONTE_CARLO: - - break; + std::size_t nb_points = std::ceil(precision * PMP::area(m, + PMP::parameters::geom_traits(Kernel()))); + typedef typename boost::property_map::type Pmap; + Pmap pmap = get(vertex_point, m); + std::vector triangles; + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) + { + //create the triangles and store them + typename Kernel::Point_3 points[3]; + typename TriangleMesh::Halfedge_around_face_circulator hc(halfedge(f,m), m); + for(int i=0; i<3; ++i) + { + points[i] = get(pmap, target(*hc, m)); + ++hc; + } + triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); + //sample a single point in all triangles(to have at least 1 pt/triangle) + Random_points_in_triangle_3 g(points[0], points[1], points[2]); + CGAL::cpp11::copy_n(g, 1, std::back_inserter(sampled_points)); + } + //sample the triangle range uniformly + Random_points_in_triangles_3 + g(triangles); + CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); + return; } } /// \todo add a plugin in the demo to display the distance between 2 meshes as a texture (if not complicated) From 39e7b76902ec5d326d024fe0ddbdce99054a84ac Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Thu, 18 Aug 2016 13:55:47 +0200 Subject: [PATCH 07/72] WIP --- .../internal/Generic_random_point_generator.h | 2 +- .../CGAL/Polygon_mesh_processing/distance.h | 257 +++++++++--------- 2 files changed, 131 insertions(+), 128 deletions(-) diff --git a/Generator/include/CGAL/internal/Generic_random_point_generator.h b/Generator/include/CGAL/internal/Generic_random_point_generator.h index 619c887c5db..559e927aa2c 100644 --- a/Generator/include/CGAL/internal/Generic_random_point_generator.h +++ b/Generator/include/CGAL/internal/Generic_random_point_generator.h @@ -67,7 +67,7 @@ public: Geometric_object object = object_from_id_map(id); ids.push_back(id); //compute the weight of a face - total_weight += to_double( compute_weight(object) ); + total_weight += to_double(CGAL::approximate_sqrt(compute_weight(object))); weights.push_back(total_weight); } //generate the first point diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 606cc53a98d..7a98823cd0a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -93,7 +93,7 @@ sample_triangles(const TriangleRange& triangles, double distance, OutputIterator std::set< std::pair > sampled_edges; - // sample edges but skip endpoints + // sample edges but skip endpoints BOOST_FOREACH(const Triangle_3& t, triangles) { for (int i=0;i<3; ++i) @@ -113,7 +113,6 @@ sample_triangles(const TriangleRange& triangles, double distance, OutputIterator } } } - // sample triangles BOOST_FOREACH(const Triangle_3& t, triangles) out=internal::triangle_grid_sampling(t, distance, out); @@ -159,21 +158,117 @@ struct Distance_computation{ } }; #endif +/// \todo test different strategies and put the better one in `approximated_Hausdorff_distance()` +/// for particular cases one can still use a specific sampling method together with `max_distance_to_triangle_mesh()` +enum Sampling_method{ + RANDOM_UNIFORM =0, /**< points are generated in a random and uniform way, depending on the area of each triangle.*/ + GRID,/**< points are generated in a grid, with a minimum of one point per vertex.*/ + MONTE_CARLO /**< points are generated randomly in each triangle, proportionally to the face area with a minimum + * of 1 pt per triangle.*/ +}; +/** fills `sampled_points` with points taken on the mesh in a manner depending on `method`. + * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map + * for `CGAL::vertex_point_t` + * @param m the triangle mesh that will be sampled + * @param precision the number of points per squared area unit. Must be greater than 1. + * + * @param method defines the method of sampling. + * + * @tparam Sampling_method defines the method of sampling. + * Possible values are `RANDOM_UNIFORM`, + * and `GRID` and `MONTE_CARLO. + */ +template +void sample_triangle_mesh(const TriangleMesh& m, + double precision, + std::vector& sampled_points, + Sampling_method method = RANDOM_UNIFORM) +{ + switch(method) + { + case RANDOM_UNIFORM: + { + std::size_t nb_points = std::ceil( + precision * PMP::area(m, PMP::parameters::geom_traits(Kernel()))); + Random_points_in_triangle_mesh_3 + g(m); + CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); + return; + } + case GRID: + { + //we take a unit square and grid sample it to approximate the distance between points + //knowing the points density. + CGAL_assertion_msg (precision >1, + "Precision must be greater than 1."); + double distance = 1.0/(sqrt(precision)-1); + + typedef typename boost::property_map::type Pmap; + Pmap pmap = get(vertex_point, m); + std::vector triangles; + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) + { + //create the triangles and store them + typename Kernel::Point_3 points[3]; + typename TriangleMesh::Halfedge_around_face_circulator hc(halfedge(f,m), m); + for(int i=0; i<3; ++i) + { + points[i] = get(pmap, target(*hc, m)); + ++hc; + } + triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); + //sample a single point in all triangles(to have at least 1 pt/triangle) + } + sample_triangles(triangles, distance, std::back_inserter(sampled_points)); + return; + } + case MONTE_CARLO: + std::size_t nb_points = std::ceil(precision * PMP::area(m, + PMP::parameters::geom_traits(Kernel()))); + typedef typename boost::property_map::type Pmap; + Pmap pmap = get(vertex_point, m); + std::vector triangles; + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) + { + //create the triangles and store them + typename Kernel::Point_3 points[3]; + typename TriangleMesh::Halfedge_around_face_circulator hc(halfedge(f,m), m); + for(int i=0; i<3; ++i) + { + points[i] = get(pmap, target(*hc, m)); + ++hc; + } + triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); + //sample a single point in all triangles(to have at least 1 pt/triangle) + Random_points_in_triangle_3 g(points[0], points[1], points[2]); + CGAL::cpp11::copy_n(g, 1, std::back_inserter(sampled_points)); + } + //sample the triangle range uniformly + Random_points_in_triangles_3 + g(triangles); + CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); + return; + } +} template ::type> double approximated_Hausdorff_distance( std::vector& sample_points, - TriangleMesh& m, - std::size_t nb_sample_points) + const TriangleMesh& m, + double precision, + Sampling_method method = RANDOM_UNIFORM + ) { typedef Point_3 Point_3; bool is_triangle = is_triangle_mesh(m); CGAL_assertion_msg (is_triangle, "Mesh is not triangulated. Distance computing impossible."); + /* Random_points_in_triangle_mesh_3 g(m); - CGAL::cpp11::copy_n(g, nb_sample_points, std::back_inserter(sample_points)); + CGAL::cpp11::copy_n(g, nb_sample_points, std::back_inserter(sample_points));*/ + sample_triangle_mesh(m, precision ,sample_points, method); #ifdef CGAL_HAUSDORFF_DEBUG std::cout << "Nb sample points " << sample_points.size() << "\n"; #endif @@ -221,16 +316,15 @@ template ::type> double approximated_Hausdorff_distance( - TriangleMesh& m1, - TriangleMesh& m2, - int nb_points + const TriangleMesh& m1, + const TriangleMesh& m2, + double precision, + Sampling_method method = RANDOM_UNIFORM ) { std::vector sample_points; - Random_points_in_triangle_mesh_3 - g(m1); - CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sample_points)); - return approximated_Hausdorff_distance(sample_points, m2,4000); + sample_triangle_mesh(m1, precision ,sample_points, method ); + return approximated_Hausdorff_distance(sample_points, m2, precision, method ); } template ::type> double approximated_symmetric_Hausdorff_distance( - TriangleMesh& m1, - TriangleMesh& m2, - int nb_points + const TriangleMesh& m1, + const TriangleMesh& m2, + double precision ) { return (std::max)( - approximated_Hausdorff_distance(m1, m2, nb_points), - approximated_Hausdorff_distance(m2, m1, nb_points) + approximated_Hausdorff_distance(m1, m2, precision), + approximated_Hausdorff_distance(m2, m1, precision) ); } -/// \todo test different strategies and put the better one in `approximated_Hausdorff_distance()` -/// for particular cases one can still use a specific sampling method together with `max_distance_to_triangle_mesh()` -enum Sampling_method{ - RANDOM_UNIFORM =0, /**< points are generated in a random and uniform way, depending on the area of each triangle.*/ - GRID,/**< points are generated in a grid, with a minimum of one point per triangle.*/ - MONTE_CARLO /**< points are generated randomly in each triangle, proportionally to the face area with a minimum - * of 1 pt per triangle.*/ -}; -/** fills `sampled_points` with points taken on the mesh in a manner depending on `method`. - * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map - * for `CGAL::vertex_point_t` - * @param m the triangle mesh that will be sampled - * @param precision depends on the value of `method` : - * case RANDOM_UNIFORM : the number of points per squared area unit - * case GRID : the distance between the points - * case MONTE_CARLO : the number of points per squared area unit - * @tparam method a Sampling_method. - * @tparam Sampling_method defines the method of sampling. - * Possible values are `RANDOM_UNIFORM`, - * and `GRID` and `MONTE_CARLO. - */ -template -void sample_triangle_mesh(const TriangleMesh& m, - double precision, - std::vector& sampled_points, - Sampling_method method = RANDOM_UNIFORM) -{ - switch(method) - { - case RANDOM_UNIFORM: - { - std::size_t nb_points = std::ceil(precision * PMP::area(m, - PMP::parameters::geom_traits(Kernel()))); - Random_points_in_triangle_mesh_3 - g(m); - CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); - return; - } - case GRID: - { - typedef typename boost::property_map::type Pmap; - Pmap pmap = get(vertex_point, m); - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) - { - typename Kernel::Point_3 points[3]; - typename TriangleMesh::Halfedge_around_face_circulator hc(halfedge(f,m), m); - for(int i=0; i<3; ++i) - { - points[i] = get(pmap, target(*hc, m)); - ++hc; - } - internal::triangle_grid_sampling(points[0], points[1], points[2],precision, std::back_inserter(sampled_points)); - } - return; - } - case MONTE_CARLO: - std::size_t nb_points = std::ceil(precision * PMP::area(m, - PMP::parameters::geom_traits(Kernel()))); - typedef typename boost::property_map::type Pmap; - Pmap pmap = get(vertex_point, m); - std::vector triangles; - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) - { - //create the triangles and store them - typename Kernel::Point_3 points[3]; - typename TriangleMesh::Halfedge_around_face_circulator hc(halfedge(f,m), m); - for(int i=0; i<3; ++i) - { - points[i] = get(pmap, target(*hc, m)); - ++hc; - } - triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); - //sample a single point in all triangles(to have at least 1 pt/triangle) - Random_points_in_triangle_3 g(points[0], points[1], points[2]); - CGAL::cpp11::copy_n(g, 1, std::back_inserter(sampled_points)); - } - //sample the triangle range uniformly - Random_points_in_triangles_3 - g(triangles); - CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); - return; - } -} /// \todo add a plugin in the demo to display the distance between 2 meshes as a texture (if not complicated) template< class Concurrency_tag, @@ -340,17 +351,13 @@ template< class Concurrency_tag, class TriangleMesh, class PMap1, class PMap2> -double approximated_Hausdorff_distance( TriangleMesh& tm1, - TriangleMesh& tm2, +double approximated_Hausdorff_distance( const TriangleMesh& tm1, + const TriangleMesh& tm2, double precision, const PMap1&, const PMap2&) { - std::size_t nb_points = std::max(std::ceil(to_double(precision * PMP::area(tm1, - PMP::parameters::geom_traits(Kernel())))), - std::ceil(to_double(precision * PMP::area(tm2, - PMP::parameters::geom_traits(Kernel()))))); - return approximated_Hausdorff_distance(tm1, tm2, nb_points); + return approximated_Hausdorff_distance(tm1, tm2, precision); } // documented functions /** @@ -389,8 +396,8 @@ template< class Concurrency_tag, class TriangleMesh, class NamedParameters1, class NamedParameters2> -double approximated_Hausdorff_distance( TriangleMesh& tm1, - TriangleMesh& tm2, +double approximated_Hausdorff_distance( const TriangleMesh& tm1, + const TriangleMesh& tm2, double precision, const NamedParameters1& np1, const NamedParameters2& np2) @@ -424,8 +431,8 @@ template< class Concurrency_tag, class NamedParameters1, class NamedParameters2> double approximated_symmetric_Hausdorff_distance( - TriangleMesh& tm1, - TriangleMesh& tm2, + const TriangleMesh& tm1, + const TriangleMesh& tm2, double precision, const NamedParameters1& np1, const NamedParameters2& np2) @@ -436,8 +443,6 @@ double approximated_symmetric_Hausdorff_distance( ); } -/// \todo document and implement me -/// \todo find a way to define precision through named parameters /** * \ingroup PMP_distance_grp * computes the approximated Hausdorff distance between `points` and `tm`. @@ -448,7 +453,7 @@ template< class Concurrency_tag, class PointRange, class NamedParameters> double max_distance_to_triangle_mesh(const PointRange& points, - TriangleMesh& tm, + const TriangleMesh& tm, double precision, const NamedParameters& np) { @@ -458,13 +463,11 @@ double max_distance_to_triangle_mesh(const PointRange& points, BOOST_FOREACH(typename PointRange::value_type point, points) sample_points.push_back(point); - std::size_t nb_points = std::ceil(to_double(precision * PMP::area(tm, - PMP::parameters::geom_traits(Geom_traits())))); return approximated_Hausdorff_distance - (sample_points,tm, nb_points); + vertex_point)> + (sample_points,tm, precision); } /// \todo document and implement me @@ -476,7 +479,7 @@ template< class Concurrency_tag, class TriangleMesh, class PointRange, class NamedParameters> -double max_distance_to_point_set(TriangleMesh& tm, +double max_distance_to_point_set(const TriangleMesh& tm, const PointRange& points, const NamedParameters& np) { @@ -488,8 +491,8 @@ double max_distance_to_point_set(TriangleMesh& tm, template< class Concurrency_tag, class TriangleMesh, class NamedParameters1> -double approximated_Hausdorff_distance( TriangleMesh& tm1, - TriangleMesh& tm2, +double approximated_Hausdorff_distance( const TriangleMesh& tm1, + const TriangleMesh& tm2, double precision, const NamedParameters1& np1) { @@ -499,8 +502,8 @@ double approximated_Hausdorff_distance( TriangleMesh& tm1, template< class Concurrency_tag, class TriangleMesh> -double approximated_Hausdorff_distance( TriangleMesh& tm1, - TriangleMesh& tm2, +double approximated_Hausdorff_distance( const TriangleMesh& tm1, + const TriangleMesh& tm2, double precision) { return approximated_Hausdorff_distance( @@ -513,8 +516,8 @@ double approximated_Hausdorff_distance( TriangleMesh& tm1, template< class Concurrency_tag, class TriangleMesh, class NamedParameters1> -double approximated_symmetric_Hausdorff_distance(TriangleMesh& tm1, - TriangleMesh& tm2, +double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1, + const TriangleMesh& tm2, double precision, const NamedParameters1& np1) { @@ -525,8 +528,8 @@ double approximated_symmetric_Hausdorff_distance(TriangleMesh& tm1, template< class Concurrency_tag, class TriangleMesh, class NamedParameters1> -double approximated_symmetric_Hausdorff_distance(TriangleMesh& tm1, - TriangleMesh& tm2, +double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1, + const TriangleMesh& tm2, double precision) { return approximated_symmetric_Hausdorff_distance( From f742ad250e387011373c4bf1d8c38c2e1500d296 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Fri, 19 Aug 2016 11:55:35 +0200 Subject: [PATCH 08/72] Fix for use with Polyhedron - Polyhedron does not have a halfedge_around_face_descriptor, thus it does not respect the BGL doc and we must use a regular halfedge_descriptor instead. --- .../CGAL/Polygon_mesh_processing/distance.h | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 7a98823cd0a..9aebcf66a72 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -204,18 +204,19 @@ void sample_triangle_mesh(const TriangleMesh& m, "Precision must be greater than 1."); double distance = 1.0/(sqrt(precision)-1); - typedef typename boost::property_map::type Pmap; + typedef typename boost::property_map::const_type Pmap; Pmap pmap = get(vertex_point, m); std::vector triangles; BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) { //create the triangles and store them typename Kernel::Point_3 points[3]; - typename TriangleMesh::Halfedge_around_face_circulator hc(halfedge(f,m), m); + //typename TriangleMesh::Halfedge_around_face_circulator hc(halfedge(f,m), m); + typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,m)); for(int i=0; i<3; ++i) { - points[i] = get(pmap, target(*hc, m)); - ++hc; + points[i] = get(pmap, target(hd, m)); + hd = next(hd, m); } triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); //sample a single point in all triangles(to have at least 1 pt/triangle) @@ -224,20 +225,21 @@ void sample_triangle_mesh(const TriangleMesh& m, return; } case MONTE_CARLO: + { std::size_t nb_points = std::ceil(precision * PMP::area(m, PMP::parameters::geom_traits(Kernel()))); - typedef typename boost::property_map::type Pmap; - Pmap pmap = get(vertex_point, m); + typedef typename boost::property_map::const_type Pmap; + Pmap pmap = get(CGAL::vertex_point, m); std::vector triangles; BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) { //create the triangles and store them typename Kernel::Point_3 points[3]; - typename TriangleMesh::Halfedge_around_face_circulator hc(halfedge(f,m), m); + typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,m)); for(int i=0; i<3; ++i) { - points[i] = get(pmap, target(*hc, m)); - ++hc; + points[i] = get(pmap, target(hd, m)); + hd = next(hd, m); } triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); //sample a single point in all triangles(to have at least 1 pt/triangle) @@ -250,6 +252,7 @@ void sample_triangle_mesh(const TriangleMesh& m, CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); return; } + } } template ::type> @@ -260,21 +263,15 @@ double approximated_Hausdorff_distance( Sampling_method method = RANDOM_UNIFORM ) { - typedef Point_3 Point_3; bool is_triangle = is_triangle_mesh(m); CGAL_assertion_msg (is_triangle, "Mesh is not triangulated. Distance computing impossible."); - /* - Random_points_in_triangle_mesh_3 - g(m); - CGAL::cpp11::copy_n(g, nb_sample_points, std::back_inserter(sample_points));*/ sample_triangle_mesh(m, precision ,sample_points, method); #ifdef CGAL_HAUSDORFF_DEBUG std::cout << "Nb sample points " << sample_points.size() << "\n"; #endif spatial_sort(sample_points.begin(), sample_points.end()); - typedef typename TriangleMesh::face_iterator Triangle_iterator; typedef AABB_face_graph_triangle_primitive Primitive; typedef AABB_traits Traits; typedef AABB_tree< Traits > Tree; @@ -466,7 +463,7 @@ double max_distance_to_triangle_mesh(const PointRange& points, return approximated_Hausdorff_distance + vertex_point)*/> (sample_points,tm, precision); } From 9d9007739b677f705662618f3ce7d8ee01b626f1 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Mon, 22 Aug 2016 10:09:26 +0200 Subject: [PATCH 09/72] Add a plugin that computes and displays the distance between two polyhedra as a color on those polyhedra. --- .../Polyhedron/Plugins/PMP/CMakeLists.txt | 3 + .../Plugins/PMP/Distance_plugin.cpp | 379 ++++++++++++++++++ .../demo/Polyhedron/triangulate_primitive.h | 113 +++++- 3 files changed, 493 insertions(+), 2 deletions(-) create mode 100644 Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt b/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt index 42af8d59154..fbb1fd7f12d 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/CMakeLists.txt @@ -75,3 +75,6 @@ target_link_libraries(repair_polyhedron_plugin scene_polyhedron_item) qt5_wrap_ui( isotropicRemeshingUI_FILES Isotropic_remeshing_dialog.ui) polyhedron_demo_plugin(isotropic_remeshing_plugin Isotropic_remeshing_plugin ${isotropicRemeshingUI_FILES}) target_link_libraries(isotropic_remeshing_plugin scene_polyhedron_item scene_polyhedron_selection_item) + +polyhedron_demo_plugin(distance_plugin Distance_plugin) +target_link_libraries(distance_plugin scene_polyhedron_item scene_color_ramp) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp new file mode 100644 index 00000000000..dd3df589e62 --- /dev/null +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp @@ -0,0 +1,379 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Messages_interface.h" +#include "Scene_polyhedron_item.h" +#include "Color_ramp.h" +#include "triangulate_primitive.h" +#include +#include +//This plugin crates an action in Operations that displays "Hello World" in the 'console' dockwidet. +using namespace CGAL::Three; +namespace PMP = CGAL::Polygon_mesh_processing; +class Scene_distance_polyhedron_item: public Scene_item +{ + Q_OBJECT +public: + Scene_distance_polyhedron_item(Polyhedron* poly, Polyhedron* polyB, QString other_name) + :Scene_item(NbOfVbos,NbOfVaos), + poly(poly), + poly_B(polyB), + are_buffers_filled(false), + other_poly(other_name) + { + this->setRenderingMode(FlatPlusEdges); + thermal_ramp.build_thermal(); + } + bool supportsRenderingMode(RenderingMode m) const { + return (m == Flat || m == FlatPlusEdges); + } + Scene_item* clone() const {return 0;} + QString toolTip() const {return QString("Item %1 with color indicating distance with %2").arg(this->name()).arg(other_poly);} + void draw(Viewer_interface *viewer) const + { + if(!are_buffers_filled) + { + computeElements(); + initializeBuffers(viewer); + compute_bbox(); + } + vaos[Facets]->bind(); + attribBuffers(viewer, PROGRAM_WITH_LIGHT); + program = getShaderProgram(PROGRAM_WITH_LIGHT); + program->bind(); + program->setUniformValue("is_selected", false); + viewer->glDrawArrays(GL_TRIANGLES, 0, static_cast(nb_pos/3)); + program->release(); + vaos[Facets]->release(); + } + void drawEdges(Viewer_interface* viewer) const + { + vaos[Edges]->bind(); + + attribBuffers(viewer, PROGRAM_WITHOUT_LIGHT); + program = getShaderProgram(PROGRAM_WITHOUT_LIGHT); + program->bind(); + //draw the edges + program->setAttributeValue("colors", QColor(Qt::black)); + program->setUniformValue("is_selected", false); + viewer->glDrawArrays(GL_LINES, 0, static_cast(nb_edge_pos/3)); + vaos[Edges]->release(); + program->release(); + } + void compute_bbox() const { + const Kernel::Point_3& p = *(poly->points_begin()); + CGAL::Bbox_3 bbox(p.x(), p.y(), p.z(), p.x(), p.y(), p.z()); + for(Polyhedron::Point_iterator it = poly->points_begin(); + it != poly->points_end(); + ++it) { + bbox = bbox + it->bbox(); + } + _bbox = Bbox(bbox.xmin(),bbox.ymin(),bbox.zmin(), + bbox.xmax(),bbox.ymax(),bbox.zmax()); + } +private: + Polyhedron* poly; + Polyhedron* poly_B; + mutable bool are_buffers_filled; + QString other_poly; + mutable std::vector vertices; + mutable std::vector edge_vertices; + mutable std::vector normals; + mutable std::vector colors; + Color_ramp thermal_ramp; + + enum VAOs { + Facets=0, + Edges, + NbOfVaos}; + + enum VBOs { + Vertices=0, + Edge_vertices, + Normals, + Colors, + NbOfVbos}; + + mutable int nb_pos; + mutable int nb_edge_pos; + mutable QOpenGLShaderProgram *program; + + //fills 'out' and returns the hausdorff distance for calibration of the color_ramp. + double compute_distances(const Polyhedron& m, std::vector sample_points,double precision, PMP::Sampling_method method, + QMap& out)const + { + PMP::sample_triangle_mesh(m, precision ,sample_points, method); + spatial_sort(sample_points.begin(), sample_points.end()); + + typedef CGAL::AABB_face_graph_triangle_primitive Primitive; + typedef CGAL::AABB_traits Traits; + typedef CGAL::AABB_tree< Traits > Tree; + + Tree tree( faces(m).first, faces(m).second, m); + tree.accelerate_distance_queries(); + tree.build(); + + double hdist = 0; + typename Traits::Point_3 hint = sample_points.front(); + BOOST_FOREACH(const typename Traits::Point_3& pt, sample_points) + { + hint = tree.closest_point(pt, hint); + typename Kernel::FT dist = squared_distance(hint,pt); + double d = CGAL::sqrt(dist); + out[pt]= d; + if (d>hdist) hdist=d; + } + return hdist; + } + + void computeElements()const + { + QApplication::setOverrideCursor(Qt::WaitCursor); + vertices.resize(0); + edge_vertices.resize(0); + normals.resize(0); + colors.resize(0); + + typedef Polyhedron::Traits Kernel; + typedef Kernel::Vector_3 Vector; + typedef Polyhedron::Facet_iterator Facet_iterator; + typedef boost::graph_traits::face_descriptor face_descriptor; + typedef boost::graph_traits::vertex_descriptor vertex_descriptor; + + //facets + { + boost::container::flat_map face_normals_map; + boost::associative_property_map< boost::container::flat_map > + nf_pmap(face_normals_map); + boost::container::flat_map vertex_normals_map; + boost::associative_property_map< boost::container::flat_map > + nv_pmap(vertex_normals_map); + + PMP::compute_normals(*poly, nv_pmap, nf_pmap); + std::vector total_points(0); + Facet_iterator f = poly->facets_begin(); + for(f = poly->facets_begin(); + f != poly->facets_end(); + f++) + { + Vector nf = get(nf_pmap, f); + f->plane() = Kernel::Plane_3(f->halfedge()->vertex()->point(), nf); + typedef FacetTriangulator::vertex_descriptor> FT; + double diagonal; + if(this->diagonalBbox() != std::numeric_limits::infinity()) + diagonal = this->diagonalBbox(); + else + diagonal = 0.0; + + //compute distance with other polyhedron + //sample facet + std::vector sampled_points; + PMP::internal::triangle_grid_sampling(f->halfedge()->vertex()->point(), f->halfedge()->next()->vertex()->point(), + f->halfedge()->next()->next()->vertex()->point(), + 1.0/(std::sqrt(40)-1.0), std::back_inserter(sampled_points)); + + //triangle facets with sample points for color display + FT triangulation(f,sampled_points,nf,poly,diagonal); + + if(triangulation.cdt->dimension() != 2 ) + { + qDebug()<<"Warning : cdt not right. Facet not displayed"; + return; + } + + //iterates on the internal faces to add the vertices to the positions + //and the normals to the appropriate vectors + + for(FT::CDT::Finite_faces_iterator + ffit = triangulation.cdt->finite_faces_begin(), + end = triangulation.cdt->finite_faces_end(); + ffit != end; ++ffit) + { + if(ffit->info().is_external) + continue; + + for (int i = 0; i<3; ++i) + { + total_points.push_back(ffit->vertex(i)->point()); + vertices.push_back(ffit->vertex(i)->point().x()); + vertices.push_back(ffit->vertex(i)->point().y()); + vertices.push_back(ffit->vertex(i)->point().z()); + + normals.push_back(nf.x()); + normals.push_back(nf.y()); + normals.push_back(nf.z()); + } + } + } + //compute the distances + QMap distances; + double hausdorff = compute_distances(*poly_B,total_points,40,PMP::GRID, distances); + //compute the colors + for(std::size_t i=0; iedges_begin(); + he != poly->edges_end(); + he++) + { + const Point& a = he->vertex()->point(); + const Point& b = he->opposite()->vertex()->point(); + { + + edge_vertices.push_back(a.x()); + edge_vertices.push_back(a.y()); + edge_vertices.push_back(a.z()); + + edge_vertices.push_back(b.x()); + edge_vertices.push_back(b.y()); + edge_vertices.push_back(b.z()); + } + } + } + QApplication::restoreOverrideCursor(); + } + void initializeBuffers(Viewer_interface *viewer)const + { + + program = getShaderProgram(PROGRAM_WITH_LIGHT, viewer); + program->bind(); + vaos[Facets]->bind(); + buffers[Vertices].bind(); + buffers[Vertices].allocate(vertices.data(), + static_cast(vertices.size()*sizeof(float))); + program->enableAttributeArray("vertex"); + program->setAttributeBuffer("vertex",GL_FLOAT,0,3); + buffers[Vertices].release(); + buffers[Normals].bind(); + buffers[Normals].allocate(normals.data(), + static_cast(normals.size()*sizeof(float))); + program->enableAttributeArray("normals"); + program->setAttributeBuffer("normals",GL_FLOAT,0,3); + buffers[Normals].release(); + buffers[Colors].bind(); + buffers[Colors].allocate(colors.data(), + static_cast(colors.size()*sizeof(float))); + program->enableAttributeArray("colors"); + program->setAttributeBuffer("colors",GL_FLOAT,0,3); + buffers[Colors].release(); + vaos[Facets]->release(); + program->release(); + + program = getShaderProgram(PROGRAM_WITHOUT_LIGHT, viewer); + program->bind(); + vaos[Edges]->bind(); + buffers[Edge_vertices].bind(); + buffers[Edge_vertices].allocate(edge_vertices.data(), + static_cast(edge_vertices.size()*sizeof(float))); + program->enableAttributeArray("vertex"); + program->setAttributeBuffer("vertex",GL_FLOAT,0,3); + buffers[Edge_vertices].release(); + vaos[Facets]->release(); + program->release(); + + nb_pos = vertices.size(); + vertices.resize(0); + //"Swap trick" insures that the memory is indeed freed and not kept available + std::vector(vertices).swap(vertices); + nb_edge_pos = edge_vertices.size(); + edge_vertices.resize(0); + std::vector(edge_vertices).swap(edge_vertices); + normals.resize(0); + std::vector(normals).swap(normals); + colors.resize(0); + std::vector(colors).swap(colors); + are_buffers_filled = true; + } +}; +class DistancePlugin : + 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") + + typedef Kernel::Point_3 Point_3; +public: + //decides if the plugin's actions will be displayed or not. + bool applicable(QAction*) const + { + + return scene->selectionIndices().size() == 2 && + qobject_cast(scene->item(scene->selectionIndices().first())) && + qobject_cast(scene->item(scene->selectionIndices().last())); + + } + //the list of the actions of the plugin. + QList actions() const + { + return _actions; + } + //this acts like a constructor for the plugin. It gets the references to the mainwindow and the scene, and connects the action. + void init(QMainWindow* mw, Scene_interface* sc, Messages_interface* mi) + { + //gets the reference to the message interface, to display text in the console widget + this->messageInterface = mi; + //get the references + this->scene = sc; + this->mw = mw; + //creates the action + QAction *actionComputeDistance= new QAction(QString("Compute Distance Between Polyhedra"), mw); + //specifies the subMenu + actionComputeDistance->setProperty("submenuName", "Polygon Mesh Processing"); + //links the action + if(actionComputeDistance) { + connect(actionComputeDistance, SIGNAL(triggered()), + this, SLOT(createDistanceItems())); + _actions << actionComputeDistance; + } + } +private Q_SLOTS: + void createDistanceItems() + { + //check the initial conditions + Scene_polyhedron_item* itemA = qobject_cast(scene->item(scene->selectionIndices().first())); + Scene_polyhedron_item* itemB = qobject_cast(scene->item(scene->selectionIndices().last())); + if(!itemA->polyhedron()->is_pure_triangle() || + !itemB->polyhedron()->is_pure_triangle() ){ + messageInterface->error(QString("Distance not computed. (Both polyhedra must be triangulated)")); + return; + } + QApplication::setOverrideCursor(Qt::WaitCursor); + Scene_distance_polyhedron_item* new_itemA = new Scene_distance_polyhedron_item(itemA->polyhedron(),itemB->polyhedron(), itemB->name() ); + Scene_distance_polyhedron_item* new_itemB = new Scene_distance_polyhedron_item(itemB->polyhedron(),itemA->polyhedron(), itemA->name()); + itemA->setVisible(false); + itemB->setVisible(false); + new_itemA->setName(QString("%1 to %2").arg(itemA->name()).arg(itemB->name())); + new_itemB->setName(QString("%1 to %2").arg(itemB->name()).arg(itemA->name())); + scene->addItem(new_itemA); + scene->addItem(new_itemB); + QApplication::restoreOverrideCursor(); + } +private: + QList _actions; + Messages_interface* messageInterface; + //The reference to the scene + Scene_interface* scene; + //The reference to the main window + QMainWindow* mw; +}; +#include "Distance_plugin.moc" diff --git a/Polyhedron/demo/Polyhedron/triangulate_primitive.h b/Polyhedron/demo/Polyhedron/triangulate_primitive.h index 3b45a18b2b4..be9c0b6334c 100644 --- a/Polyhedron/demo/Polyhedron/triangulate_primitive.h +++ b/Polyhedron/demo/Polyhedron/triangulate_primitive.h @@ -7,7 +7,7 @@ #include #include #include - +#include #include @@ -67,6 +67,23 @@ public: } triangulate(idPoints, normal, item_diag); } + FacetTriangulator(typename boost::graph_traits::face_descriptor fd, + const std::vector& more_points, + const Vector& normal, + Mesh *poly, + const double item_diag) + { + std::vector idPoints; + BOOST_FOREACH(halfedge_descriptor he_circ, halfedges_around_face( halfedge(fd, *poly), *poly)) + { + PointAndId idPoint; + idPoint.point = get(boost::vertex_point,*poly,source(he_circ, *poly)); + idPoint.id = source(he_circ, *poly); + idPoints.push_back(idPoint); + + } + triangulate_with_points(idPoints,more_points, normal, item_diag); + } FacetTriangulator(std::vector &idPoints, const Vector& normal, @@ -74,6 +91,13 @@ public: { triangulate(idPoints, normal, item_diag); } + FacetTriangulator(std::vector &idPoints, + const std::vector& more_points, + const Vector& normal, + const double item_diag) + { + triangulate_with_points(idPoints, more_points, normal, item_diag); + } private: void triangulate( std::vector &idPoints, @@ -89,11 +113,16 @@ private: double min_sq_dist = CGAL::square(0.0001*item_diag); // Iterate the points of the facet and decide if they must be inserted in the CDT + typename Kernel::FT x(0), y(0), z(0); + BOOST_FOREACH(PointAndId idPoint, idPoints) { + x += idPoint.point.x(); + y += idPoint.point.y(); + z += idPoint.point.z(); typename CDT::Vertex_handle vh; //Always insert the first point, then only insert - // if the distance with the precedent is reasonable. + // if the distance with the previous is reasonable. if(first == 0 || CGAL::squared_distance(idPoint.point, previous->point()) > min_sq_dist) { vh = cdt->insert(idPoint.point); @@ -150,6 +179,86 @@ private: } } } + + void triangulate_with_points( std::vector &idPoints, + const std::vector& more_points, + const Vector& normal, + const double item_diag ) + { + P_traits cdt_traits(normal); + cdt = new CDT(cdt_traits); + typename CDT::Vertex_handle previous, first, last_inserted; + //Compute a reasonable precision level used to decide + //if two consecutive points in a facet can be estimated + //equal. + + double min_sq_dist = CGAL::square(0.0001*item_diag); + // Iterate the points of the facet and decide if they must be inserted in the CDT + BOOST_FOREACH(PointAndId idPoint, idPoints) + { + typename CDT::Vertex_handle vh; + //Always insert the first point, then only insert + // if the distance with the previous is reasonable. + if(first == 0 || CGAL::squared_distance(idPoint.point, previous->point()) > min_sq_dist) + { + vh = cdt->insert(idPoint.point); + v2v[vh] = idPoint.id; + if(first == 0) { + first = vh; + } + if(previous != 0 && previous != vh) { + double sq_dist = CGAL::squared_distance(previous->point(), vh->point()); + if(sq_dist > min_sq_dist) + { + cdt->insert_constraint(previous, vh); + sq_dist = CGAL::squared_distance(previous->point(), first->point()); + if(sq_dist > min_sq_dist) + { + last_inserted = previous; + } + } + } + previous = vh; + } + } + double sq_dist = CGAL::squared_distance(previous->point(), first->point()); + + if(sq_dist > min_sq_dist) + { + cdt->insert_constraint(previous, first); + } + else + { + cdt->insert_constraint(last_inserted, first); + } + BOOST_FOREACH(typename Kernel::Point_3 point, more_points) + { + cdt->insert(point); + } + // sets mark is_external + for(typename CDT::All_faces_iterator + fit2 = cdt->all_faces_begin(), + end = cdt->all_faces_end(); + fit2 != end; ++fit2) + { + fit2->info().is_external = false; + } + //check if the facet is external or internal + 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(std::make_pair(fh, i))) + { + face_queue.push(fh->neighbor(i)); + } + } + } + } }; #endif // TRIANGULATE_PRIMITIVE From c9ec5a224ebfaa363d46963a84811c6d6e9ac6b1 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Tue, 23 Aug 2016 10:52:53 +0200 Subject: [PATCH 10/72] Remove useless sampling. --- .../CGAL/Polygon_mesh_processing/distance.h | 128 ++++++++---------- 1 file changed, 60 insertions(+), 68 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 9aebcf66a72..07bf8d11e56 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -163,15 +163,18 @@ struct Distance_computation{ enum Sampling_method{ RANDOM_UNIFORM =0, /**< points are generated in a random and uniform way, depending on the area of each triangle.*/ - GRID,/**< points are generated in a grid, with a minimum of one point per vertex.*/ - MONTE_CARLO /**< points are generated randomly in each triangle, proportionally to the face area with a minimum - * of 1 pt per triangle.*/ + GRID,/**< points are generated in a grid, with a minimum of one point per triangle.*/ + MONTE_CARLO /**< points are generated randomly in each triangle. Their number in each triangle is proportional to the corresponding face area with a minimum + * of 1.*/ }; /** fills `sampled_points` with points taken on the mesh in a manner depending on `method`. * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map * for `CGAL::vertex_point_t` * @param m the triangle mesh that will be sampled - * @param precision the number of points per squared area unit. Must be greater than 1. + * @param parameter depends on `method` : + * RANDOM_UNIFORM and MONTE_CARLO: the number of points per squared area unit. + * GRID : The distance between two consecutive points in the grid. + * * * @param method defines the method of sampling. * @@ -181,7 +184,7 @@ enum Sampling_method{ */ template void sample_triangle_mesh(const TriangleMesh& m, - double precision, + double parameter, std::vector& sampled_points, Sampling_method method = RANDOM_UNIFORM) { @@ -189,67 +192,58 @@ void sample_triangle_mesh(const TriangleMesh& m, { case RANDOM_UNIFORM: { - std::size_t nb_points = std::ceil( - precision * PMP::area(m, PMP::parameters::geom_traits(Kernel()))); - Random_points_in_triangle_mesh_3 - g(m); - CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); - return; + std::size_t nb_points = std::ceil( + parameter * PMP::area(m, PMP::parameters::geom_traits(Kernel()))); + Random_points_in_triangle_mesh_3 + g(m); + CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); + return; } case GRID: { - //we take a unit square and grid sample it to approximate the distance between points - //knowing the points density. - CGAL_assertion_msg (precision >1, - "Precision must be greater than 1."); - double distance = 1.0/(sqrt(precision)-1); - - typedef typename boost::property_map::const_type Pmap; - Pmap pmap = get(vertex_point, m); - std::vector triangles; - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) - { - //create the triangles and store them - typename Kernel::Point_3 points[3]; - //typename TriangleMesh::Halfedge_around_face_circulator hc(halfedge(f,m), m); - typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,m)); - for(int i=0; i<3; ++i) - { - points[i] = get(pmap, target(hd, m)); - hd = next(hd, m); - } - triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); - //sample a single point in all triangles(to have at least 1 pt/triangle) - } - sample_triangles(triangles, distance, std::back_inserter(sampled_points)); - return; - } - case MONTE_CARLO: - { - std::size_t nb_points = std::ceil(precision * PMP::area(m, - PMP::parameters::geom_traits(Kernel()))); - typedef typename boost::property_map::const_type Pmap; - Pmap pmap = get(CGAL::vertex_point, m); - std::vector triangles; - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) - { - //create the triangles and store them - typename Kernel::Point_3 points[3]; - typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,m)); - for(int i=0; i<3; ++i) - { + typedef typename boost::property_map::const_type Pmap; + Pmap pmap = get(vertex_point, m); + std::vector triangles; + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) + { + //create the triangles and store them + typename Kernel::Point_3 points[3]; + //typename TriangleMesh::Halfedge_around_face_circulator hc(halfedge(f,m), m); + typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,m)); + for(int i=0; i<3; ++i) + { points[i] = get(pmap, target(hd, m)); hd = next(hd, m); - } - triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); - //sample a single point in all triangles(to have at least 1 pt/triangle) - Random_points_in_triangle_3 g(points[0], points[1], points[2]); - CGAL::cpp11::copy_n(g, 1, std::back_inserter(sampled_points)); - } - //sample the triangle range uniformly - Random_points_in_triangles_3 - g(triangles); - CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); + } + triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); + //sample a single point in all triangles(to have at least 1 pt/triangle) + } + sample_triangles(triangles, parameter, std::back_inserter(sampled_points)); + return; + } + case MONTE_CARLO://pas du tout ca : genere K points par triangle, k dependant de l'aire + { + typedef typename boost::property_map::const_type Pmap; + Pmap pmap = get(CGAL::vertex_point, m); + std::vector triangles; + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) + { + std::size_t nb_points = std::max((int)std::ceil(parameter * PMP::face_area(f,m,PMP::parameters::geom_traits(Kernel()))), + 1); + //create the triangles and store them + typename Kernel::Point_3 points[3]; + typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,m)); + for(int i=0; i<3; ++i) + { + points[i] = get(pmap, target(hd, m)); + hd = next(hd, m); + } + triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); + //sample a single point in all triangles(to have at least 1 pt/triangle) + Random_points_in_triangle_3 g(points[0], points[1], points[2]); + CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); + } + //std::cerr<<"M_C points : "<::type> double approximated_Hausdorff_distance( std::vector& sample_points, - const TriangleMesh& m, - double precision, - Sampling_method method = RANDOM_UNIFORM + const TriangleMesh& m ) { bool is_triangle = is_triangle_mesh(m); CGAL_assertion_msg (is_triangle, "Mesh is not triangulated. Distance computing impossible."); - sample_triangle_mesh(m, precision ,sample_points, method); #ifdef CGAL_HAUSDORFF_DEBUG std::cout << "Nb sample points " << sample_points.size() << "\n"; #endif @@ -295,8 +286,9 @@ double approximated_Hausdorff_distance( #endif { double hdist = 0; - typename Traits::Point_3 hint = sample_points.front(); - BOOST_FOREACH(const typename Traits::Point_3& pt, sample_points) + typename Kernel::Point_3 hint = sample_points.back(); + + BOOST_FOREACH(const typename Kernel::Point_3& pt, sample_points) { hint = tree.closest_point(pt, hint); typename Kernel::FT dist = squared_distance(hint,pt); @@ -321,7 +313,7 @@ double approximated_Hausdorff_distance( { std::vector sample_points; sample_triangle_mesh(m1, precision ,sample_points, method ); - return approximated_Hausdorff_distance(sample_points, m2, precision, method ); + return approximated_Hausdorff_distance(sample_points, m2); } template Date: Tue, 23 Aug 2016 11:22:58 +0200 Subject: [PATCH 11/72] Add a constructor for `Random_points_in_triangle_mesh_3` that takes a vertex point map as argument. --- Generator/doc/Generator/CGAL/point_generators_3.h | 6 ++++++ Generator/include/CGAL/point_generators_3.h | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/Generator/doc/Generator/CGAL/point_generators_3.h b/Generator/doc/Generator/CGAL/point_generators_3.h index 5691d6d983d..ec16b351c86 100644 --- a/Generator/doc/Generator/CGAL/point_generators_3.h +++ b/Generator/doc/Generator/CGAL/point_generators_3.h @@ -477,6 +477,12 @@ Similar to the previous constructor using `get(vertex_point, mesh)` as vertex po Random_points_in_triangle_mesh_3(const TriangleMesh& mesh, Random& rnd = get_default_random() ); +/*! +Creates an input iterator `g` generating points of type `Point_3` uniformly +distributed in the mesh faces based on `vpm`. Each triangle has a probability to be chosen to hold the point depending on its area. +*/ +Random_points_in_triangle_mesh_3(const TriangleMesh& mesh, VertexPointMap vpm, Random& rnd = default_random); + /// @} }; /* end Random_points_in_triangle_mesh_3 */ diff --git a/Generator/include/CGAL/point_generators_3.h b/Generator/include/CGAL/point_generators_3.h index 395737b418c..d43ed365ba0 100644 --- a/Generator/include/CGAL/point_generators_3.h +++ b/Generator/include/CGAL/point_generators_3.h @@ -346,6 +346,13 @@ struct Random_points_in_triangle_mesh_3 rnd ) { } + Random_points_in_triangle_mesh_3( const TriangleMesh& mesh, VertexPointMap vpm, Random& rnd = default_random) + : Base( faces(mesh), + CGAL::Property_map_to_unary_function(Pmap(&mesh, vpm)), + internal::Apply_approx_sqrt::Kernel::Compute_squared_area_3>(), + rnd ) + { + } This& operator++() { Base::generate_point(); return *this; From 218392c7b30f55eae424a7b0e3119379267590a5 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Tue, 16 Aug 2016 14:14:10 +0200 Subject: [PATCH 12/72] Cherry-pick from Generator-branch. Conflicts: Generator/include/CGAL/internal/Generic_random_point_generator.h Generator/include/CGAL/point_generators_3.h --- .../include/CGAL/internal/Generic_random_point_generator.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Generator/include/CGAL/internal/Generic_random_point_generator.h b/Generator/include/CGAL/internal/Generic_random_point_generator.h index 559e927aa2c..ede93b32d07 100644 --- a/Generator/include/CGAL/internal/Generic_random_point_generator.h +++ b/Generator/include/CGAL/internal/Generic_random_point_generator.h @@ -67,7 +67,11 @@ public: Geometric_object object = object_from_id_map(id); ids.push_back(id); //compute the weight of a face +<<<<<<< HEAD total_weight += to_double(CGAL::approximate_sqrt(compute_weight(object))); +======= + total_weight += to_double( CGAL::approximate_sqrt(compute_weight(object)) ); +>>>>>>> 8a7ccb8... Use Compute_squared_area_3 instead of Compute_area_3 in case the Kernel does not have a sqrt() implementation. weights.push_back(total_weight); } //generate the first point From 542efa95d7e3da6f3e2c1fec0d5fc587786827d3 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Tue, 16 Aug 2016 14:14:10 +0200 Subject: [PATCH 13/72] Implement the NamedParameters functions and use the property maps. --- .../internal/Generic_random_point_generator.h | 6 +-- Generator/include/CGAL/point_generators_3.h | 4 +- .../CGAL/Polygon_mesh_processing/distance.h | 47 ++++++------------- 3 files changed, 17 insertions(+), 40 deletions(-) diff --git a/Generator/include/CGAL/internal/Generic_random_point_generator.h b/Generator/include/CGAL/internal/Generic_random_point_generator.h index ede93b32d07..6ea21be2794 100644 --- a/Generator/include/CGAL/internal/Generic_random_point_generator.h +++ b/Generator/include/CGAL/internal/Generic_random_point_generator.h @@ -67,11 +67,7 @@ public: Geometric_object object = object_from_id_map(id); ids.push_back(id); //compute the weight of a face -<<<<<<< HEAD - total_weight += to_double(CGAL::approximate_sqrt(compute_weight(object))); -======= - total_weight += to_double( CGAL::approximate_sqrt(compute_weight(object)) ); ->>>>>>> 8a7ccb8... Use Compute_squared_area_3 instead of Compute_area_3 in case the Kernel does not have a sqrt() implementation. + total_weight += to_double(compute_weight(object)); weights.push_back(total_weight); } //generate the first point diff --git a/Generator/include/CGAL/point_generators_3.h b/Generator/include/CGAL/point_generators_3.h index d43ed365ba0..b19ea8e9aad 100644 --- a/Generator/include/CGAL/point_generators_3.h +++ b/Generator/include/CGAL/point_generators_3.h @@ -346,9 +346,9 @@ struct Random_points_in_triangle_mesh_3 rnd ) { } - Random_points_in_triangle_mesh_3( const TriangleMesh& mesh, VertexPointMap vpm, Random& rnd = default_random) + Random_points_on_triangle_mesh_3( const TriangleMesh& mesh, VertexPointMap vpm, Random& rnd = default_random) : Base( faces(mesh), - CGAL::Property_map_to_unary_function(Pmap(&mesh, vpm)), + CGAL::Property_map_to_unary_function(Object_from_id_map(&mesh, vpm)), internal::Apply_approx_sqrt::Kernel::Compute_squared_area_3>(), rnd ) { diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 07bf8d11e56..fc014412eec 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -182,10 +182,11 @@ enum Sampling_method{ * Possible values are `RANDOM_UNIFORM`, * and `GRID` and `MONTE_CARLO. */ -template +template void sample_triangle_mesh(const TriangleMesh& m, double parameter, std::vector& sampled_points, + PMap pmap, Sampling_method method = RANDOM_UNIFORM) { switch(method) @@ -195,14 +196,12 @@ void sample_triangle_mesh(const TriangleMesh& m, std::size_t nb_points = std::ceil( parameter * PMP::area(m, PMP::parameters::geom_traits(Kernel()))); Random_points_in_triangle_mesh_3 - g(m); + g(m, pmap); CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); return; } case GRID: { - typedef typename boost::property_map::const_type Pmap; - Pmap pmap = get(vertex_point, m); std::vector triangles; BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) { @@ -223,8 +222,6 @@ void sample_triangle_mesh(const TriangleMesh& m, } case MONTE_CARLO://pas du tout ca : genere K points par triangle, k dependant de l'aire { - typedef typename boost::property_map::const_type Pmap; - Pmap pmap = get(CGAL::vertex_point, m); std::vector triangles; BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) { @@ -248,11 +245,11 @@ void sample_triangle_mesh(const TriangleMesh& m, } } } -template ::type> +template double approximated_Hausdorff_distance( std::vector& sample_points, - const TriangleMesh& m + const TriangleMesh& m, + VertexPointMap vpm ) { bool is_triangle = is_triangle_mesh(m); @@ -286,7 +283,7 @@ double approximated_Hausdorff_distance( #endif { double hdist = 0; - typename Kernel::Point_3 hint = sample_points.back(); + typename Kernel::Point_3 hint = get(vpm, *vertices(m).first); BOOST_FOREACH(const typename Kernel::Point_3& pt, sample_points) { @@ -300,20 +297,20 @@ double approximated_Hausdorff_distance( } template ::type, - class VertexPointMap2 = typename boost::property_map::type> + class VertexPointMap1 , + class VertexPointMap2 > double approximated_Hausdorff_distance( const TriangleMesh& m1, const TriangleMesh& m2, - double precision, + double precision, + VertexPointMap1 vpm1, + VertexPointMap2 vpm2, Sampling_method method = RANDOM_UNIFORM ) { std::vector sample_points; - sample_triangle_mesh(m1, precision ,sample_points, method ); - return approximated_Hausdorff_distance(sample_points, m2); + sample_triangle_mesh(m1, precision ,sample_points, vpm1, method ); + return approximated_Hausdorff_distance(sample_points, m2, vpm2); } template -double approximated_Hausdorff_distance( const TriangleMesh& tm1, - const TriangleMesh& tm2, - double precision, - const PMap1&, - const PMap2&) -{ - return approximated_Hausdorff_distance(tm1, tm2, precision); -} // documented functions /** * \ingroup PMP_distance_grp From 033adb29f12323820de28cfcbc98221d5cbdadf8 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Tue, 23 Aug 2016 14:21:28 +0200 Subject: [PATCH 14/72] Update the demo and fix the code for Polyhedron. --- .../include/CGAL/Polygon_mesh_processing/distance.h | 2 +- Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index fc014412eec..0da08f16d12 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -195,7 +195,7 @@ void sample_triangle_mesh(const TriangleMesh& m, { std::size_t nb_points = std::ceil( parameter * PMP::area(m, PMP::parameters::geom_traits(Kernel()))); - Random_points_in_triangle_mesh_3 + Random_points_in_triangle_mesh_3 g(m, pmap); CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); return; diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp index dd3df589e62..bb911ccb29f 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp @@ -108,7 +108,7 @@ private: double compute_distances(const Polyhedron& m, std::vector sample_points,double precision, PMP::Sampling_method method, QMap& out)const { - PMP::sample_triangle_mesh(m, precision ,sample_points, method); + PMP::sample_triangle_mesh(m, precision ,sample_points, get(CGAL::vertex_point, m), method); spatial_sort(sample_points.begin(), sample_points.end()); typedef CGAL::AABB_face_graph_triangle_primitive Primitive; @@ -176,7 +176,7 @@ private: std::vector sampled_points; PMP::internal::triangle_grid_sampling(f->halfedge()->vertex()->point(), f->halfedge()->next()->vertex()->point(), f->halfedge()->next()->next()->vertex()->point(), - 1.0/(std::sqrt(40)-1.0), std::back_inserter(sampled_points)); + 0.05, std::back_inserter(sampled_points)); //triangle facets with sample points for color display FT triangulation(f,sampled_points,nf,poly,diagonal); @@ -213,7 +213,7 @@ private: } //compute the distances QMap distances; - double hausdorff = compute_distances(*poly_B,total_points,40,PMP::GRID, distances); + double hausdorff = compute_distances(*poly_B,total_points,0.05,PMP::GRID, distances); //compute the colors for(std::size_t i=0; i Date: Fri, 9 Sep 2016 10:46:59 +0200 Subject: [PATCH 15/72] Add the hausdorff example file. --- .../hausdorff_distance_example.cpp | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_example.cpp diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_example.cpp new file mode 100644 index 00000000000..b4d8d23f4b1 --- /dev/null +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_example.cpp @@ -0,0 +1,117 @@ +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +//typedef CGAL::Exact_predicates_exact_constructions_kernel K; +typedef CGAL::Simple_cartesian K; +typedef K::Point_3 P_3; +//typedef CGAL::Surface_mesh Mesh; +namespace PMP = CGAL::Polygon_mesh_processing; +typedef CGAL::Polyhedron_3 Mesh; + + +int main(int, char**) +{ + + Mesh m1,m2; + std::ifstream in1("/home/mgimeno/Bureau/Data/00_elephant.off"); + in1 >> m1; + std::ifstream in2("/home/mgimeno/Bureau/Data/02_elephant.off"); + in2 >> m2; + + /*CGAL::make_tetrahedron(P_3(0,1,0), + P_3(3,3,0), + P_3(2,4,0), + P_3(2.5,3.5,1), + m1); + + CGAL::make_tetrahedron(P_3(-1,0,0), + P_3(-2,-3,0), + P_3(-2.5,-3.5,-1), + P_3(-3,-4,0), + m2);*/ + + std::vector test_points; + /*test_points.push_back(P_3(0,0,0)); + test_points.push_back(P_3(1,0,3)); + test_points.push_back(P_3(-1,2,5)); + test_points.push_back(P_3(4.2,1.3,0.6)); + test_points.push_back(P_3(7.1,4.6,.8)); + test_points.push_back(P_3(5.6,6.1,1.7)); + test_points.push_back(P_3(4.6,7.3,2.1)); + test_points.push_back(P_3(1.14,5.20,4.12)); +*/ + int size_RU(20000), size_MC(18000); + + CGAL::Timer timer; + timer.start(); + double min(800), max(0); + for(int i = 0; i<15; ++i) + { + double dist = CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(m1,m2,size_RU); + if(dist > max) max = dist; + if(dist < min) min = dist; + } + std::cerr<< " UNIFORM 15825: ["<(m1,m2,0.01, get(CGAL::vertex_point, m1), get(CGAL::vertex_point, m2), CGAL::Polygon_mesh_processing::GRID); + if(dist > max) max = dist; + if(dist < min) min = dist; + } + std::cerr<< " GRID 0.01: ["<(m1,m2,size_MC, CGAL::Polygon_mesh_processing::MONTE_CARLO); + if(dist > max) max = dist; + if(dist < min) min = dist; + } + std::cerr<< " MONTE_CARLO 15825: ["<(m1,m2,40000)<(m1,m2,40000.0)<(test_points, m1,40000, CGAL::Polygon_mesh_processing::parameters::all_default())<(m1,size_RU,test_points, PMP::RANDOM_UNIFORM); + std::cerr<<"RU nb_pts : "<(m1,0.01,test_points, PMP::GRID); + std::cerr<<"grid nb_pts : "<(m1,size_MC,test_points, PMP::MONTE_CARLO); + std::cerr<<"MC nb_pts : "< Date: Mon, 12 Sep 2016 14:42:50 +0200 Subject: [PATCH 16/72] Implementation of the `max_distance_to_point_set()` function. --- .../CGAL/Polygon_mesh_processing/distance.h | 233 ++++++----- .../mesh_to_point_set_hausdorff_distance.h | 363 ++++++++++++++++++ .../test_pmp_distance.cpp | 20 +- 3 files changed, 503 insertions(+), 113 deletions(-) create mode 100644 Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/mesh_to_point_set_hausdorff_distance.h diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 0da08f16d12..cb192796fb2 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -36,6 +36,7 @@ #include #include +#include #ifdef CGAL_LINKED_WITH_TBB #include #include @@ -132,23 +133,22 @@ template struct Distance_computation{ const AABB_tree& tree; const std::vector& sample_points; + Point_3 hint; cpp11::atomic* distance; - Distance_computation(const AABB_tree& tree, const std::vector& sample_points, cpp11::atomic* d) + Distance_computation(const AABB_tree& tree, const Point_3 p, const std::vector& sample_points, cpp11::atomic* d) : tree(tree) , sample_points(sample_points) , distance(d) + , hint(p) {} void operator()(const tbb::blocked_range& range) const { double hdist = 0; - Point_3 hint = sample_points.front(); - for( std::size_t i = range.begin(); i != range.end(); ++i) { - hint = tree.closest_point(sample_points[i], hint); double d = CGAL::sqrt( squared_distance(hint,sample_points[i]) ); if (d>hdist) hdist=d; } @@ -162,10 +162,10 @@ struct Distance_computation{ /// for particular cases one can still use a specific sampling method together with `max_distance_to_triangle_mesh()` enum Sampling_method{ - RANDOM_UNIFORM =0, /**< points are generated in a random and uniform way, depending on the area of each triangle.*/ - GRID,/**< points are generated in a grid, with a minimum of one point per triangle.*/ - MONTE_CARLO /**< points are generated randomly in each triangle. Their number in each triangle is proportional to the corresponding face area with a minimum - * of 1.*/ + RANDOM_UNIFORM =0, /**< points are generated in a random and uniform way, depending on the area of each triangle.*/ + GRID,/**< points are generated in a grid, with a minimum of one point per triangle.*/ + MONTE_CARLO /**< points are generated randomly in each triangle. Their number in each triangle is proportional to the corresponding face area with a minimum + * of 1.*/ }; /** fills `sampled_points` with points taken on the mesh in a manner depending on `method`. * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map @@ -189,61 +189,60 @@ void sample_triangle_mesh(const TriangleMesh& m, PMap pmap, Sampling_method method = RANDOM_UNIFORM) { - switch(method) - { - case RANDOM_UNIFORM: - { - std::size_t nb_points = std::ceil( - parameter * PMP::area(m, PMP::parameters::geom_traits(Kernel()))); + switch(method) + { + case RANDOM_UNIFORM: + { + std::size_t nb_points = std::ceil( + parameter * PMP::area(m, PMP::parameters::geom_traits(Kernel()))); Random_points_in_triangle_mesh_3 - g(m, pmap); + g(m, pmap); CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); return; - } - case GRID: - { - std::vector triangles; - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) - { - //create the triangles and store them - typename Kernel::Point_3 points[3]; - //typename TriangleMesh::Halfedge_around_face_circulator hc(halfedge(f,m), m); - typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,m)); - for(int i=0; i<3; ++i) - { - points[i] = get(pmap, target(hd, m)); - hd = next(hd, m); - } - triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); - //sample a single point in all triangles(to have at least 1 pt/triangle) - } - sample_triangles(triangles, parameter, std::back_inserter(sampled_points)); - return; - } - case MONTE_CARLO://pas du tout ca : genere K points par triangle, k dependant de l'aire - { - std::vector triangles; - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) - { - std::size_t nb_points = std::max((int)std::ceil(parameter * PMP::face_area(f,m,PMP::parameters::geom_traits(Kernel()))), - 1); - //create the triangles and store them - typename Kernel::Point_3 points[3]; - typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,m)); - for(int i=0; i<3; ++i) - { - points[i] = get(pmap, target(hd, m)); - hd = next(hd, m); - } - triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); - //sample a single point in all triangles(to have at least 1 pt/triangle) - Random_points_in_triangle_3 g(points[0], points[1], points[2]); - CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); - } - //std::cerr<<"M_C points : "< triangles; + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) + { + //create the triangles and store them + typename Kernel::Point_3 points[3]; + //typename TriangleMesh::Halfedge_around_face_circulator hc(halfedge(f,m), m); + typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,m)); + for(int i=0; i<3; ++i) + { + points[i] = get(pmap, target(hd, m)); + hd = next(hd, m); + } + triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); + } + sample_triangles(triangles, parameter, std::back_inserter(sampled_points)); + return; + } + case MONTE_CARLO://pas du tout ca : genere K points par triangle, k dependant de l'aire + { + std::vector triangles; + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) + { + std::size_t nb_points = std::max((int)std::ceil(parameter * PMP::face_area(f,m,PMP::parameters::geom_traits(Kernel()))), + 1); + //create the triangles and store them + typename Kernel::Point_3 points[3]; + typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,m)); + for(int i=0; i<3; ++i) + { + points[i] = get(pmap, target(hd, m)); + hd = next(hd, m); + } + triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); + //sample a single point in all triangles(to have at least 1 pt/triangle) + Random_points_in_triangle_3 g(points[0], points[1], points[2]); + CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); + } + //std::cerr<<"M_C points : "< double approximated_Hausdorff_distance( @@ -252,7 +251,7 @@ double approximated_Hausdorff_distance( VertexPointMap vpm ) { - bool is_triangle = is_triangle_mesh(m); + bool is_triangle = is_triangle_mesh(m); CGAL_assertion_msg (is_triangle, "Mesh is not triangulated. Distance computing impossible."); #ifdef CGAL_HAUSDORFF_DEBUG @@ -267,15 +266,15 @@ double approximated_Hausdorff_distance( Tree tree( faces(m).first, faces(m).second, m); tree.accelerate_distance_queries(); tree.build(); - + typename Kernel::Point_3 hint = get(vpm, *vertices(m).first); #ifndef CGAL_LINKED_WITH_TBB CGAL_static_assertion_msg (!(boost::is_convertible::value), - "Parallel_tag is enabled but TBB is unavailable."); + "Parallel_tag is enabled but TBB is unavailable."); #else if (boost::is_convertible::value) { cpp11::atomic distance(0); - Distance_computation f(tree, sample_points, &distance); + Distance_computation f(tree, hint, sample_points, &distance); tbb::parallel_for(tbb::blocked_range(0, sample_points.size()), f); return distance; } @@ -283,8 +282,6 @@ double approximated_Hausdorff_distance( #endif { double hdist = 0; - typename Kernel::Point_3 hint = get(vpm, *vertices(m).first); - BOOST_FOREACH(const typename Kernel::Point_3& pt, sample_points) { hint = tree.closest_point(pt, hint); @@ -302,7 +299,7 @@ template (sample_points, m2, vpm2); } -template ::type, class VertexPointMap2 = typename boost::property_map::type> double approximated_symmetric_Hausdorff_distance( const TriangleMesh& m1, const TriangleMesh& m2, - double precision + double precision, + VertexPointMap1 vpm1, + VertexPointMap2 vpm2, + Sampling_method method = RANDOM_UNIFORM ) { return (std::max)( - approximated_Hausdorff_distance(m1, m2, precision), - approximated_Hausdorff_distance(m2, m1, precision) + approximated_Hausdorff_distance(m1, m2, precision, vpm1, vpm2, method), + approximated_Hausdorff_distance(m2, m1, precision, vpm2, vpm1, method) ); } - +*/ // documented functions /** * \ingroup PMP_distance_grp @@ -360,7 +360,6 @@ double approximated_symmetric_Hausdorff_distance( * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh` \cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd * \cgalNamedParamsEnd - * \todo When using TBB, runtime is slower. The chunk size should probably be tuned. see PMP/test/test_pmp_distance.cpp */ template< class Concurrency_tag, class TriangleMesh, @@ -416,44 +415,84 @@ double approximated_symmetric_Hausdorff_distance( /** * \ingroup PMP_distance_grp * computes the approximated Hausdorff distance between `points` and `tm`. - * \copydetails CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance() + * @tparam PointRange a Range of Point_3. + * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map + * for `CGAL::vertex_point_t` + * @tparam NamedParameters a sequence of \ref namedparameters for `tm` + * @param points the point_set of interest + * @param tm the triangle mesh to compute the distance to + * @param np a sequence of \ref namedparameters for `tm` among the ones listed below + * + * \cgalNamedParamsBegin + * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh` \cgalParamEnd + * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd + * \cgalNamedParamsEnd */ template< class Concurrency_tag, class TriangleMesh, class PointRange, class NamedParameters> -double max_distance_to_triangle_mesh(const PointRange& points, +double max_distance_to_triangle_mesh(PointRange points, const TriangleMesh& tm, - double precision, const NamedParameters& np) { - typedef typename GetGeomTraits::type Geom_traits; - std::vector sample_points; - BOOST_FOREACH(typename PointRange::value_type point, points) - sample_points.push_back(point); + typedef typename GetGeomTraits::type Geom_traits; - return approximated_Hausdorff_distance - (sample_points,tm, precision); + return approximated_Hausdorff_distance + (points,tm, choose_const_pmap(get_param(np, boost::vertex_point), + tm, + vertex_point)); } -/// \todo document and implement me -/// see with @sgiraudot for the implementation -/// that should be put in a seperate header file -/// with copyright INRIA -/// Maybe find a better name too -template< class Concurrency_tag, - class TriangleMesh, + +/*! + *\ingroup PMP_distance_grp + * Computes the approximated Hausdorff distance between `tm` and `points`. + * + * @tparam PointRange a Range of Point_3. + * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map + * for `CGAL::vertex_point_t` + * @tparam NamedParameters a sequence of \ref namedparameters for `tm` + * @param tm the triangle mesh to compute the distance to + * @param points the point_set of interest. + * @param precision the precision of the approximated value you want. + * @param np a sequence of \ref namedparameters for `tm` among the ones listed below + * + * \cgalNamedParamsBegin + * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh` \cgalParamEnd + * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd + * \cgalNamedParamsEnd + */ +template< class TriangleMesh, class PointRange, class NamedParameters> double max_distance_to_point_set(const TriangleMesh& tm, const PointRange& points, + const double precision, const NamedParameters& np) { - return 0; + typedef typename GetGeomTraits::type Geom_traits; + + typedef CGAL::Orthogonal_k_neighbor_search > Knn; + typedef typename Knn::Tree Tree; + Tree tree(points.begin(), points.end()); + CRefiner ref; + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(tm)) + { + typename Geom_traits::Point_3 points[3]; + typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); + for(int i=0; i<3; ++i) + { + points[i] = get(choose_const_pmap(get_param(np, boost::vertex_point), + tm, + vertex_point), target(hd, tm)); + hd = next(hd, tm); + } + ref.add(points[0], points[1], points[2], tree); + } + return ref.refine(precision, tree); } // convenience functions with default parameters @@ -496,8 +535,7 @@ double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1, } template< class Concurrency_tag, - class TriangleMesh, - class NamedParameters1> + class TriangleMesh> double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1, const TriangleMesh& tm2, double precision) @@ -508,7 +546,8 @@ double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1, parameters::all_default()); } -} } // end of namespace CGAL::Polygon_mesh_processing +} +} // end of namespace CGAL::Polygon_mesh_processing #endif //CGAL_POLYGON_MESH_PROCESSING_DISTANCE_H diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/mesh_to_point_set_hausdorff_distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/mesh_to_point_set_hausdorff_distance.h new file mode 100644 index 00000000000..ee065539d7f --- /dev/null +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/mesh_to_point_set_hausdorff_distance.h @@ -0,0 +1,363 @@ +// Copyright (c) 2016 INRIA Sophia-Antipolis (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// You can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// Licensees holding a valid commercial license may use this file in +// accordance with the commercial license agreement provided with the software. +// +// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +// +// $URL$ +// $Id$ +// +// +// Author(s) : Simon Giraudot and Maxime Gimeno + +#ifndef MESH_TO_POINT_SET_HAUSDORFF_DISTANCE_H +#define MESH_TO_POINT_SET_HAUSDORFF_DISTANCE_H\ + +#include +#include +#include +#include + +template +class CPointH +{ +private: + + typedef typename Kernel::Point_3 Point; + typedef typename Kernel::FT FT; + Point m_point; + FT m_hausdorff; + +public: + + CPointH () + { + m_point = Point (0., 0., 0.); + m_hausdorff = 0.; + } + CPointH (const Point& p, FT h = 0.) + { + m_point = p; + m_hausdorff = h; + } + CPointH (const CPointH& p) + { + m_point = p (); + m_hausdorff = p.hausdorff (); + } + + Point operator() () const { return m_point; } + Point& operator() () { return m_point; } + FT hausdorff () const { return m_hausdorff; } + + static Point mid_point (const CPointH& a, const CPointH& b) + { + return Point (0.5 * (a().x() + b().x()), + 0.5 * (a().y() + b().y()), + 0.5 * (a().z() + b().z())); + } + + +}; + +template +class CRefTriangle +{ + +private: + + typedef typename Kernel::Point_3 Point; + typedef typename Kernel::FT FT; + typedef CPointH PointH; + PointH m_point[3]; + int m_edge; + FT m_upper_bound; + FT m_lower_bound; + Point m_bisector; + +public: + + CRefTriangle (const PointH& a, const PointH& b, const PointH& c) // Triangle + { + m_point[0] = a; + m_point[1] = b; + m_point[2] = c; + + m_edge = -1; + for (unsigned int i = 0; i < 3; ++ i) + { + if (CGAL::angle (m_point[(i+1)%3](), m_point[i](), m_point[(i+2)%3]()) + == CGAL::OBTUSE) + { + m_edge = i; + break; + } + } + + if (m_edge == -1) + m_bisector = CGAL::circumcenter (a(), b(), c()); + else + { + m_bisector = PointH::mid_point (m_point[(m_edge+1)%3], + m_point[(m_edge+2)%3]); + + // Point p0 = m_point[(m_edge+1)%3](); + // Point p1 = m_point[(m_edge+2)%3](); + + + // m_bisector = Point ((p0.x () + p1.x ()) / 2., + // (p0.y () + p1.y ()) / 2., + // (p0.z () + p1.z ()) / 2.); + } + + m_lower_bound = 0.; + m_upper_bound = 0.; //std::numeric_limits::max (); + for (unsigned int i = 0; i < 3; ++ i) + { + if (m_point[i].hausdorff () > m_lower_bound) + m_lower_bound = m_point[i].hausdorff (); + + FT up = m_point[i].hausdorff () + + std::sqrt (CGAL::squared_distance (m_point[i](), m_bisector)); + + if (up > m_upper_bound) + m_upper_bound = up; + } + } + + CRefTriangle (const CRefTriangle& t) + { + m_point[0] = t.points ()[0]; + m_point[1] = t.points ()[1]; + m_point[2] = t.points ()[2]; + m_edge = t.edge (); + m_lower_bound = t.lower_bound (); + m_upper_bound = t.upper_bound (); + m_bisector = t.bisector (); + } + + FT lower_bound () const + { + return m_lower_bound; + } + + FT upper_bound () const + { + return m_upper_bound; + } + + const Point& bisector () const + { + return m_bisector; + } + + friend bool operator< (const CRefTriangle& a, const CRefTriangle& b) + { + return a.upper_bound () < b.upper_bound (); + } + + const PointH* points () const { return m_point; } + const int edge () const { return m_edge; } + + void print () const + { + std::cerr << "[Refinement triangle]" << std::endl + << " Bounds: " << m_lower_bound << " to " + << m_upper_bound << std::endl + << " * " << m_point[0]() << std::endl + << " * " << m_point[1]() << std::endl + << " * " << m_point[2]() << std::endl + << " -> " << m_bisector << std::endl; + } +}; + +namespace CGAL{ +template +class CRefiner +{ +private: + + typedef typename Kernel::Point_3 Point; + typedef typename Kernel::Triangle_3 Triangle; + typedef typename Kernel::FT FT; + typedef CPointH PointH; + typedef CRefTriangle RefTriangle; + typedef CGAL::Orthogonal_k_neighbor_search > Knn; + typedef typename Knn::Tree Tree; + std::priority_queue m_queue; + FT m_lower_bound; + FT m_upper_bound; + Point m_point; + +public: + + CRefiner () + { + m_lower_bound = 0.; + m_upper_bound = std::numeric_limits::max (); + } + ~CRefiner () { } + + bool empty () + { + return m_queue.empty (); + } + void reset (FT lower_bound = 0.) + { + m_queue = std::priority_queue (); + m_lower_bound = lower_bound; + m_upper_bound = std::numeric_limits::max (); + } + + inline FT uncertainty () const + { + return m_upper_bound - m_lower_bound; + } + + inline FT mid () const + { + return ((m_upper_bound + m_lower_bound) / 2.); + } + + inline FT lower_bound () const + { + return m_lower_bound; + } + + inline FT upper_bound () const + { + return m_upper_bound; + } + + inline FT relative_error () const + { + return 0.5 * uncertainty () / mid (); + } + + + const Point& hausdorff_point () const { return m_point; } + + bool add (const Point& a, const Point& b, const Point& c, const Tree& tree) + { + + RefTriangle r (PointH (a, std::sqrt (Knn(tree, a, 1).begin()->second)), + PointH (b, std::sqrt (Knn(tree, b, 1).begin()->second)), + PointH (c, std::sqrt (Knn(tree, c, 1).begin()->second))); + + if (r.lower_bound () > m_lower_bound) + { + m_lower_bound = r.lower_bound (); + } + if (r.upper_bound () > m_lower_bound) + m_queue.push (r); + return true; + } + bool clean_up_queue () + { + unsigned int before = m_queue.size (); + // std::cerr << "Cleaned " << m_queue.size () << " elements to "; + std::vector to_keep; + while (!(m_queue.empty ())) + { + const RefTriangle& current = m_queue.top (); + if (current.upper_bound () > m_lower_bound) + to_keep.push_back (current); + m_queue.pop (); + } + // std::cerr << to_keep.size () << " elements" << std::endl; + + m_queue = std::priority_queue (); + for (auto& r : to_keep) + m_queue.push (r); + + return (m_queue.size () < before); + } + + FT refine (FT limit, const Tree& tree, FT upper_bound = 1e30) + { + // std::ofstream f ("rquick.plot"); + + unsigned int nb_clean = 0; + + while (uncertainty () > limit && !(m_queue.empty ())) + { + if (m_queue.size () > 100000) + { + m_queue.top ().print (); + ++ nb_clean; + if (nb_clean > 5) + return m_upper_bound; + if (!clean_up_queue ()) + return m_upper_bound; + } + + + const RefTriangle& current = m_queue.top (); + + m_upper_bound = current.upper_bound (); + + if (current.lower_bound () > m_lower_bound) + m_lower_bound = current.lower_bound (); + + + if(CGAL::squared_area(current.points()[0](), + current.points()[1](), + current.points()[2]() + ) < 1e-20) + { + m_queue.pop(); + continue; + } + const Point& bisector = current.bisector (); + m_point = bisector; + //squared distance between bisector and its closst point in the mesh + FT hausdorff = std::sqrt (Knn(tree, bisector, 1).begin()->second); + if (hausdorff > m_lower_bound) + m_lower_bound = hausdorff; + + if (m_lower_bound > upper_bound) + return m_upper_bound; + + PointH new_point (bisector, hausdorff); + int i = current.edge (); + if (i == -1) + { + PointH p0 (current.points()[0]); + PointH p1 (current.points()[1]); + PointH p2 (current.points()[2]); + + m_queue.pop (); + + m_queue.push (RefTriangle (new_point, p0, p1)); + m_queue.push (RefTriangle (new_point, p1, p2)); + m_queue.push (RefTriangle (new_point, p2, p0)); + } + else + { + PointH p0 (current.points()[i]); + PointH p1 (current.points()[(i+1)%3]); + PointH p2 (current.points()[(i+2)%3]); + + m_queue.pop (); + + m_queue.push (RefTriangle (new_point, p0, p1)); + m_queue.push (RefTriangle (new_point, p0, p2)); + } + } + + + return m_upper_bound; + + } + +}; +}//CGAL +#endif // MESH_TO_POINT_SET_HAUSDORFF_DISTANCE_H diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp index a35c6f3f656..1d3902ee413 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp @@ -1,12 +1,13 @@ #include #include -#include #include #include #include +#include + #include #include @@ -29,23 +30,10 @@ int main(int argc, char** argv) std::cout << "First mesh has " << num_faces(m1) << " faces\n"; std::cout << "Second mesh has " << num_faces(m2) << " faces\n"; - std::vector t1; - t1.reserve(num_faces(m1)); - CGAL::Triangle_from_face_descriptor_map map1(&m1); - BOOST_FOREACH(Mesh::Face_index f, faces(m1)) - t1.push_back(get(map1,f)); - - std::vector t2; - t2.reserve(num_faces(m2)); - CGAL::Triangle_from_face_descriptor_map map2(&m2); - BOOST_FOREACH(Mesh::Face_index f, faces(m2)) - t2.push_back(get(map2,f)); - - CGAL::Timer time; time.start(); std::cout << "Distance between meshes (parallel)" - << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(t1,t2,0.001) + << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(m1,m2,0.001, get(CGAL::vertex_point, m1), get(CGAL::vertex_point, m2), CGAL::Polygon_mesh_processing::GRID) << "\n"; time.stop(); std::cout << "done in " << time.time() << "s.\n"; @@ -53,7 +41,7 @@ int main(int argc, char** argv) time.reset(); time.start(); std::cout << "Distance between meshes (sequential)" - << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(t1,t2,0.001) + << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(m1,m2,0.001, get(CGAL::vertex_point, m1), get(CGAL::vertex_point, m2), CGAL::Polygon_mesh_processing::GRID) << "\n"; time.stop(); std::cout << "done in " << time.time() << "s.\n"; From 6858d65e5dad765e4ba74af7996fa4b2d860aa29 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Tue, 13 Sep 2016 12:06:23 +0200 Subject: [PATCH 17/72] Update and fix Parallelize the plugin if TBB is linked with CGAL and fix the parallelized code in distance.h. --- .../Polygon_mesh_processing/CMakeLists.txt | 10 +- .../hausdorff_distance_example.cpp | 85 +++-------- .../CGAL/Polygon_mesh_processing/distance.h | 40 ++---- .../mesh_to_point_set_hausdorff_distance.h | 7 +- .../Plugins/PMP/Distance_plugin.cpp | 135 +++++++++++++++--- 5 files changed, 150 insertions(+), 127 deletions(-) rename Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/{ => internal}/mesh_to_point_set_hausdorff_distance.h (97%) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index e9341d8ac64..8d1707d7857 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -70,6 +70,15 @@ find_package(Eigen3 3.1.0) #(requires 3.1.0 or greater) include( CGAL_CreateSingleSourceCGALProgram ) +find_package( TBB ) +if( TBB_FOUND ) + include( ${TBB_USE_FILE} ) + list( APPEND CGAL_3RD_PARTY_LIBRARIES ${TBB_LIBRARIES} ) + create_single_source_cgal_program( "hausdorff_distance_example.cpp") +else() + message( STATUS "NOTICE: Intel TBB was not found. hausdorff_distance_example will use sequential code." ) +endif() + create_single_source_cgal_program( "self_intersections_example.cpp" ) create_single_source_cgal_program( "stitch_borders_example.cpp" ) create_single_source_cgal_program( "compute_normals_example_Polyhedron.cpp" CXX_FEATURES cxx_range_for ) @@ -82,7 +91,6 @@ create_single_source_cgal_program( "triangulate_polyline_example.cpp") create_single_source_cgal_program( "mesh_slicer_example.cpp") #create_single_source_cgal_program( "remove_degeneracies_example.cpp") create_single_source_cgal_program( "isotropic_remeshing_example.cpp") -create_single_source_cgal_program( "hausdorff_distance_example.cpp") if(NOT (${EIGEN3_VERSION} VERSION_LESS 3.2.0)) create_single_source_cgal_program( "hole_filling_example.cpp" ) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_example.cpp index b4d8d23f4b1..80b61dc82f1 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_example.cpp @@ -13,38 +13,37 @@ #include #include - //typedef CGAL::Exact_predicates_exact_constructions_kernel K; typedef CGAL::Simple_cartesian K; typedef K::Point_3 P_3; -//typedef CGAL::Surface_mesh Mesh; +typedef CGAL::Surface_mesh Mesh; namespace PMP = CGAL::Polygon_mesh_processing; -typedef CGAL::Polyhedron_3 Mesh; +//typedef CGAL::Polyhedron_3 Mesh; int main(int, char**) { Mesh m1,m2; - std::ifstream in1("/home/mgimeno/Bureau/Data/00_elephant.off"); + /*std::ifstream in1("/home/mgimeno/Bureau/Data/00_elephant.off"); in1 >> m1; std::ifstream in2("/home/mgimeno/Bureau/Data/02_elephant.off"); - in2 >> m2; + in2 >> m2;*/ - /*CGAL::make_tetrahedron(P_3(0,1,0), - P_3(3,3,0), - P_3(2,4,0), - P_3(2.5,3.5,1), + CGAL::make_tetrahedron(P_3(.000,.000,.000), + P_3(.002,.000,.000), + P_3(.001,.001,.001), + P_3(.001,.000,.002), m1); CGAL::make_tetrahedron(P_3(-1,0,0), P_3(-2,-3,0), P_3(-2.5,-3.5,-1), P_3(-3,-4,0), - m2);*/ + m2); std::vector test_points; - /*test_points.push_back(P_3(0,0,0)); + test_points.push_back(P_3(-1,0,0)); test_points.push_back(P_3(1,0,3)); test_points.push_back(P_3(-1,2,5)); test_points.push_back(P_3(4.2,1.3,0.6)); @@ -52,66 +51,16 @@ int main(int, char**) test_points.push_back(P_3(5.6,6.1,1.7)); test_points.push_back(P_3(4.6,7.3,2.1)); test_points.push_back(P_3(1.14,5.20,4.12)); -*/ - int size_RU(20000), size_MC(18000); - CGAL::Timer timer; - timer.start(); - double min(800), max(0); - for(int i = 0; i<15; ++i) - { - double dist = CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(m1,m2,size_RU); - if(dist > max) max = dist; - if(dist < min) min = dist; - } - std::cerr<< " UNIFORM 15825: ["<(m1,m2,0.01, get(CGAL::vertex_point, m1), get(CGAL::vertex_point, m2), CGAL::Polygon_mesh_processing::GRID); - if(dist > max) max = dist; - if(dist < min) min = dist; - } - std::cerr<< " GRID 0.01: ["<(m1,m2,size_MC, CGAL::Polygon_mesh_processing::MONTE_CARLO); - if(dist > max) max = dist; - if(dist < min) min = dist; - } - std::cerr<< " MONTE_CARLO 15825: ["<(m1,m2,40)<(m1,m2,0.01, get(CGAL::vertex_point, m1), get(CGAL::vertex_point, m2), CGAL::Polygon_mesh_processing::GRID)<(m1,m2,40, CGAL::Polygon_mesh_processing::MONTE_CARLO)<(m1,m2,40)<(m1,m2,40.0)<(m1,m2,40000)<(m1,m2,40000.0)<(test_points, m1,40000, CGAL::Polygon_mesh_processing::parameters::all_default())<(m1,size_RU,test_points, PMP::RANDOM_UNIFORM); - std::cerr<<"RU nb_pts : "<(m1,0.01,test_points, PMP::GRID); - std::cerr<<"grid nb_pts : "<(m1,size_MC,test_points, PMP::MONTE_CARLO); - std::cerr<<"MC nb_pts : "< #include #include -//#include -//#include +#include +#include #include #include #include -#include +#include #ifdef CGAL_LINKED_WITH_TBB #include #include @@ -133,19 +133,20 @@ template struct Distance_computation{ const AABB_tree& tree; const std::vector& sample_points; - Point_3 hint; + Point_3 initial_hint; cpp11::atomic* distance; Distance_computation(const AABB_tree& tree, const Point_3 p, const std::vector& sample_points, cpp11::atomic* d) : tree(tree) , sample_points(sample_points) , distance(d) - , hint(p) + , initial_hint(p) {} void operator()(const tbb::blocked_range& range) const { + Point_3 hint = initial_hint; double hdist = 0; for( std::size_t i = range.begin(); i != range.end(); ++i) { @@ -219,12 +220,11 @@ void sample_triangle_mesh(const TriangleMesh& m, sample_triangles(triangles, parameter, std::back_inserter(sampled_points)); return; } - case MONTE_CARLO://pas du tout ca : genere K points par triangle, k dependant de l'aire + case MONTE_CARLO: { - std::vector triangles; BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) { - std::size_t nb_points = std::max((int)std::ceil(parameter * PMP::face_area(f,m,PMP::parameters::geom_traits(Kernel()))), + std::size_t nb_points = (std::max)((int)std::ceil(parameter * PMP::face_area(f,m,PMP::parameters::geom_traits(Kernel()))), 1); //create the triangles and store them typename Kernel::Point_3 points[3]; @@ -234,12 +234,9 @@ void sample_triangle_mesh(const TriangleMesh& m, points[i] = get(pmap, target(hd, m)); hd = next(hd, m); } - triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); - //sample a single point in all triangles(to have at least 1 pt/triangle) Random_points_in_triangle_3 g(points[0], points[1], points[2]); CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); } - //std::cerr<<"M_C points : "<(sample_points, m2, vpm2); } -/*template ::type, - class VertexPointMap2 = typename boost::property_map::type> -double approximated_symmetric_Hausdorff_distance( - const TriangleMesh& m1, - const TriangleMesh& m2, - double precision, - VertexPointMap1 vpm1, - VertexPointMap2 vpm2, - Sampling_method method = RANDOM_UNIFORM -) -{ - return (std::max)( - approximated_Hausdorff_distance(m1, m2, precision, vpm1, vpm2, method), - approximated_Hausdorff_distance(m2, m1, precision, vpm2, vpm1, method) - ); -} -*/ // documented functions /** * \ingroup PMP_distance_grp diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/mesh_to_point_set_hausdorff_distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h similarity index 97% rename from Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/mesh_to_point_set_hausdorff_distance.h rename to Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h index ee065539d7f..34aa701cfd6 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/mesh_to_point_set_hausdorff_distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h @@ -18,13 +18,12 @@ // // Author(s) : Simon Giraudot and Maxime Gimeno -#ifndef MESH_TO_POINT_SET_HAUSDORFF_DISTANCE_H -#define MESH_TO_POINT_SET_HAUSDORFF_DISTANCE_H\ +#ifndef CGAL_MESH_TO_POINT_SET_HAUSDORFF_DISTANCE_H +#define CGAL_MESH_TO_POINT_SET_HAUSDORFF_DISTANCE_H #include #include #include -#include template class CPointH @@ -360,4 +359,4 @@ public: }; }//CGAL -#endif // MESH_TO_POINT_SET_HAUSDORFF_DISTANCE_H +#endif // CGAL_MESH_TO_POINT_SET_HAUSDORFF_DISTANCE_H diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp index bb911ccb29f..425b347e94f 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp @@ -13,9 +13,56 @@ #include "triangulate_primitive.h" #include #include +#include +#include +#include +#include +#include + //This plugin crates an action in Operations that displays "Hello World" in the 'console' dockwidet. using namespace CGAL::Three; namespace PMP = CGAL::Polygon_mesh_processing; + +template +struct Distance_computation{ + const AABB_tree& tree; + const std::vector& sample_points; + Point_3 initial_hint; + CGAL::cpp11::atomic* distance; + std::vector& output; + + Distance_computation(const AABB_tree& tree, + const Point_3 p, + const std::vector& sample_points, + CGAL::cpp11::atomic* d, + std::vector& out ) + : tree(tree) + , sample_points(sample_points) + , distance(d) + , initial_hint(p) + , output(out) + { + } + + void + operator()(const tbb::blocked_range& range) const + { + Point_3 hint = initial_hint; + double hdist = 0; + for( std::size_t i = range.begin(); i != range.end(); ++i) + { + hint = tree.closest_point(sample_points[i], hint); + Kernel::FT dist = squared_distance(hint,sample_points[i]); + double d = CGAL::sqrt(dist); + output[i] = d; + if (d>hdist) hdist=d; + } + + if (hdist > distance->load(CGAL::cpp11::memory_order_acquire)) + distance->store(hdist, CGAL::cpp11::memory_order_release); + } +}; + class Scene_distance_polyhedron_item: public Scene_item { Q_OBJECT @@ -78,6 +125,7 @@ public: bbox.xmax(),bbox.ymax(),bbox.zmax()); } private: + Polyhedron* poly; Polyhedron* poly_B; mutable bool are_buffers_filled; @@ -105,12 +153,10 @@ private: mutable QOpenGLShaderProgram *program; //fills 'out' and returns the hausdorff distance for calibration of the color_ramp. - double compute_distances(const Polyhedron& m, std::vector sample_points,double precision, PMP::Sampling_method method, - QMap& out)const - { - PMP::sample_triangle_mesh(m, precision ,sample_points, get(CGAL::vertex_point, m), method); - spatial_sort(sample_points.begin(), sample_points.end()); + double compute_distances(const Polyhedron& m, const std::vector& sample_points,double precision, PMP::Sampling_method method, + std::vector& out)const + { typedef CGAL::AABB_face_graph_triangle_primitive Primitive; typedef CGAL::AABB_traits Traits; typedef CGAL::AABB_tree< Traits > Tree; @@ -119,17 +165,25 @@ private: tree.accelerate_distance_queries(); tree.build(); + typename Traits::Point_3 hint = m.vertices_begin()->point(); + +#ifndef CGAL_LINKED_WITH_TBB double hdist = 0; - typename Traits::Point_3 hint = sample_points.front(); - BOOST_FOREACH(const typename Traits::Point_3& pt, sample_points) + for(std::size_t i = 0; ihdist) hdist=d; } - return hdist; + return hdist; +#else + CGAL::cpp11::atomic distance(0); + Distance_computation f(tree, hint, sample_points, &distance, out); + tbb::parallel_for(tbb::blocked_range(0, sample_points.size()), f); + return distance; +#endif } void computeElements()const @@ -174,16 +228,28 @@ private: //compute distance with other polyhedron //sample facet std::vector sampled_points; - PMP::internal::triangle_grid_sampling(f->halfedge()->vertex()->point(), f->halfedge()->next()->vertex()->point(), + +//============== GRID ================ + /*PMP::internal::triangle_grid_sampling(f->halfedge()->vertex()->point(), f->halfedge()->next()->vertex()->point(), f->halfedge()->next()->next()->vertex()->point(), - 0.05, std::back_inserter(sampled_points)); + 0.05, std::back_inserter(sampled_points));*/ +//============== MONTE_CARLO ================ + std::size_t nb_points = (std::max)((int)std::ceil(400 * PMP::face_area(f,*poly,PMP::parameters::geom_traits(Kernel()))), + 1); + CGAL::Random_points_in_triangle_3 g(f->halfedge()->vertex()->point(), f->halfedge()->next()->vertex()->point(), + f->halfedge()->next()->next()->vertex()->point()); + CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); + sampled_points.push_back(f->halfedge()->vertex()->point()); + sampled_points.push_back(f->halfedge()->next()->vertex()->point()); + sampled_points.push_back(f->halfedge()->next()->next()->vertex()->point()); +// FIN MONTE_CARLO //triangle facets with sample points for color display FT triangulation(f,sampled_points,nf,poly,diagonal); if(triangulation.cdt->dimension() != 2 ) { - qDebug()<<"Warning : cdt not right. Facet not displayed"; + qDebug()<<"Error : cdt not right (dimension != 2). Facet not displayed"; return; } @@ -212,15 +278,38 @@ private: } } //compute the distances - QMap distances; - double hausdorff = compute_distances(*poly_B,total_points,0.05,PMP::GRID, distances); - //compute the colors - for(std::size_t i=0; i::type > Search_traits_3; + + std::vector distances(total_points.size()); + std::vector indices; + indices.reserve(total_points.size()); + std::copy(boost::counting_iterator(0), + boost::counting_iterator(total_points.size()), + std::back_inserter(indices)); + spatial_sort(indices.begin(), + indices.end(), + Search_traits_3(CGAL::make_property_map(total_points))); + std::vector sorted_points(total_points.size()); + for(std::size_t i = 0; i < sorted_points.size(); ++i) { - double d = distances[total_points[i]]/hausdorff; - colors.push_back(thermal_ramp.r(d)); - colors.push_back(thermal_ramp.g(d)); - colors.push_back(thermal_ramp.b(d)); + sorted_points[i] = total_points[indices[i]]; + } + + double hausdorff = compute_distances(*poly_B, + sorted_points, + 400, //precision parameter must be the same than for the sampling + PMP::MONTE_CARLO, + distances); + //compute the colors + colors.resize(sorted_points.size()*3); + for(std::size_t i=0; i Date: Fri, 14 Oct 2016 12:14:14 +0200 Subject: [PATCH 18/72] Fix and clean-up --- Generator/doc/Generator/CGAL/point_generators_3.h | 7 ------- .../CGAL/internal/Generic_random_point_generator.h | 2 +- Generator/include/CGAL/point_generators_3.h | 7 ------- Installation/changes.html | 10 ++++++++++ .../include/CGAL/Polygon_mesh_processing/distance.h | 2 -- .../internal/mesh_to_point_set_hausdorff_distance.h | 2 +- .../demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp | 6 ++---- 7 files changed, 14 insertions(+), 22 deletions(-) diff --git a/Generator/doc/Generator/CGAL/point_generators_3.h b/Generator/doc/Generator/CGAL/point_generators_3.h index ec16b351c86..5fd59b5d2fa 100644 --- a/Generator/doc/Generator/CGAL/point_generators_3.h +++ b/Generator/doc/Generator/CGAL/point_generators_3.h @@ -476,13 +476,6 @@ Similar to the previous constructor using `get(vertex_point, mesh)` as vertex po */ Random_points_in_triangle_mesh_3(const TriangleMesh& mesh, Random& rnd = get_default_random() ); - -/*! -Creates an input iterator `g` generating points of type `Point_3` uniformly -distributed in the mesh faces based on `vpm`. Each triangle has a probability to be chosen to hold the point depending on its area. -*/ -Random_points_in_triangle_mesh_3(const TriangleMesh& mesh, VertexPointMap vpm, Random& rnd = default_random); - /// @} }; /* end Random_points_in_triangle_mesh_3 */ diff --git a/Generator/include/CGAL/internal/Generic_random_point_generator.h b/Generator/include/CGAL/internal/Generic_random_point_generator.h index 6ea21be2794..619c887c5db 100644 --- a/Generator/include/CGAL/internal/Generic_random_point_generator.h +++ b/Generator/include/CGAL/internal/Generic_random_point_generator.h @@ -67,7 +67,7 @@ public: Geometric_object object = object_from_id_map(id); ids.push_back(id); //compute the weight of a face - total_weight += to_double(compute_weight(object)); + total_weight += to_double( compute_weight(object) ); weights.push_back(total_weight); } //generate the first point diff --git a/Generator/include/CGAL/point_generators_3.h b/Generator/include/CGAL/point_generators_3.h index b19ea8e9aad..395737b418c 100644 --- a/Generator/include/CGAL/point_generators_3.h +++ b/Generator/include/CGAL/point_generators_3.h @@ -346,13 +346,6 @@ struct Random_points_in_triangle_mesh_3 rnd ) { } - Random_points_on_triangle_mesh_3( const TriangleMesh& mesh, VertexPointMap vpm, Random& rnd = default_random) - : Base( faces(mesh), - CGAL::Property_map_to_unary_function(Object_from_id_map(&mesh, vpm)), - internal::Apply_approx_sqrt::Kernel::Compute_squared_area_3>(), - rnd ) - { - } This& operator++() { Base::generate_point(); return *this; diff --git a/Installation/changes.html b/Installation/changes.html index 974a743dfc8..31a3199dfa8 100644 --- a/Installation/changes.html +++ b/Installation/changes.html @@ -253,6 +253,16 @@ and src/ directories). additional parameter based on a distance threshold to make it easier and more intuitive to use. +

Polygon Mesh Processing

+
    +
  • + Add functions : + sample_triangle_mesh, approximated_Hausdorff_distance, + approximated_symmetric_Hausdorff_distance, + max_distance_to_triangle_mesh, + max_distance_to_point_set and the enum Sampling_method +
  • +
diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index cb7aa4a5bf1..70172a83edf 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -159,8 +159,6 @@ struct Distance_computation{ } }; #endif -/// \todo test different strategies and put the better one in `approximated_Hausdorff_distance()` -/// for particular cases one can still use a specific sampling method together with `max_distance_to_triangle_mesh()` enum Sampling_method{ RANDOM_UNIFORM =0, /**< points are generated in a random and uniform way, depending on the area of each triangle.*/ diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h index 34aa701cfd6..6329c0399cc 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h @@ -164,7 +164,7 @@ public: } const PointH* points () const { return m_point; } - const int edge () const { return m_edge; } + int edge () const { return m_edge; } void print () const { diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp index 425b347e94f..c7d1323199f 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp @@ -38,8 +38,8 @@ struct Distance_computation{ std::vector& out ) : tree(tree) , sample_points(sample_points) - , distance(d) , initial_hint(p) + , distance(d) , output(out) { } @@ -154,7 +154,7 @@ private: //fills 'out' and returns the hausdorff distance for calibration of the color_ramp. - double compute_distances(const Polyhedron& m, const std::vector& sample_points,double precision, PMP::Sampling_method method, + double compute_distances(const Polyhedron& m, const std::vector& sample_points, std::vector& out)const { typedef CGAL::AABB_face_graph_triangle_primitive Primitive; @@ -298,8 +298,6 @@ private: double hausdorff = compute_distances(*poly_B, sorted_points, - 400, //precision parameter must be the same than for the sampling - PMP::MONTE_CARLO, distances); //compute the colors colors.resize(sorted_points.size()*3); From 3cedefa08ed6d44718a5580788030f9f1c406738 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Fri, 14 Oct 2016 15:24:56 +0200 Subject: [PATCH 19/72] Update the doc. --- .../CGAL/Polygon_mesh_processing/distance.h | 96 +++++++++++-------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 70172a83edf..5915a3b2e8c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -160,52 +160,74 @@ struct Distance_computation{ }; #endif +/** + * @brief enum used to select the sampling method in the function `sample_triangle_mesh` + */ enum Sampling_method{ - RANDOM_UNIFORM =0, /**< points are generated in a random and uniform way, depending on the area of each triangle.*/ - GRID,/**< points are generated in a grid, with a minimum of one point per triangle.*/ - MONTE_CARLO /**< points are generated randomly in each triangle. Their number in each triangle is proportional to the corresponding face area with a minimum - * of 1.*/ + RANDOM_UNIFORM =0, + GRID, + MONTE_CARLO }; /** fills `sampled_points` with points taken on the mesh in a manner depending on `method`. - * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map - * for `CGAL::vertex_point_t` + * @tparam TriangleMesh a model of the concept `FaceListGraph`. * @param m the triangle mesh that will be sampled * @param parameter depends on `method` : * RANDOM_UNIFORM and MONTE_CARLO: the number of points per squared area unit. * GRID : The distance between two consecutive points in the grid. * + * @param np a sequence of \ref namedparameters for `tm` among the ones listed below + * + * \cgalNamedParamsBegin + * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh` \cgalParamEnd + * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd + * \cgalNamedParamsEnd * * @param method defines the method of sampling. * * @tparam Sampling_method defines the method of sampling. - * Possible values are `RANDOM_UNIFORM`, - * and `GRID` and `MONTE_CARLO. + * Possible values are : + *- `RANDOM_UNIFORM` : points are generated in a random and uniform way, depending on the area of each triangle., + * + *- `GRID` : points are generated in a grid, with a minimum of one point per triangle. + * + *- `MONTE_CARLO` : points are generated randomly in each triangle. Their number in each triangle is proportional to the corresponding face area with a minimum + * of 1. + * */ -template +template void sample_triangle_mesh(const TriangleMesh& m, double parameter, - std::vector& sampled_points, - PMap pmap, + std::vector::type::Point_3>& sampled_points, + NamedParameters np, Sampling_method method = RANDOM_UNIFORM) { + typedef typename GetGeomTraits::type Geom_traits; + + typedef typename GetVertexPointMap::const_type Vpm; + + Vpm pmap = choose_param(get_param(np, vertex_point), + get_property_map(vertex_point, m)); switch(method) { case RANDOM_UNIFORM: { std::size_t nb_points = std::ceil( - parameter * PMP::area(m, PMP::parameters::geom_traits(Kernel()))); - Random_points_in_triangle_mesh_3 + parameter * PMP::area(m, PMP::parameters::geom_traits(Geom_traits()))); + Random_points_in_triangle_mesh_3 g(m, pmap); CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); return; } case GRID: { - std::vector triangles; + std::vector triangles; BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) { //create the triangles and store them - typename Kernel::Point_3 points[3]; + typename Geom_traits::Point_3 points[3]; //typename TriangleMesh::Halfedge_around_face_circulator hc(halfedge(f,m), m); typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,m)); for(int i=0; i<3; ++i) @@ -213,9 +235,9 @@ void sample_triangle_mesh(const TriangleMesh& m, points[i] = get(pmap, target(hd, m)); hd = next(hd, m); } - triangles.push_back(typename Kernel::Triangle_3(points[0], points[1], points[2])); + triangles.push_back(typename Geom_traits::Triangle_3(points[0], points[1], points[2])); } - sample_triangles(triangles, parameter, std::back_inserter(sampled_points)); + sample_triangles(triangles, parameter, std::back_inserter(sampled_points)); return; } case MONTE_CARLO: @@ -225,14 +247,14 @@ void sample_triangle_mesh(const TriangleMesh& m, std::size_t nb_points = (std::max)((int)std::ceil(parameter * PMP::face_area(f,m,PMP::parameters::geom_traits(Kernel()))), 1); //create the triangles and store them - typename Kernel::Point_3 points[3]; + typename Geom_traits::Point_3 points[3]; typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,m)); for(int i=0; i<3; ++i) { points[i] = get(pmap, target(hd, m)); hd = next(hd, m); } - Random_points_in_triangle_3 g(points[0], points[1], points[2]); + Random_points_in_triangle_3 g(points[0], points[1], points[2]); CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); } return; @@ -328,7 +350,7 @@ double approximated_Hausdorff_distance( * * @param tm1 the triangle mesh that will be sampled * @param tm2 the triangle mesh to compute the distance to - * @param precision the number of points per squared area unit + * @param precision the number of points per squared area unit for the random sampling * @param np1 optional sequence of \ref namedparameters for `tm1` among the ones listed below * @param np2 optional sequence of \ref namedparameters for `tm2` among the ones listed below * @@ -352,13 +374,12 @@ double approximated_Hausdorff_distance( const TriangleMesh& tm1, return approximated_Hausdorff_distance( tm1, tm2, - precision, - choose_const_pmap(get_param(np1, boost::vertex_point), - tm1, - vertex_point), - choose_const_pmap(get_param(np2, boost::vertex_point), - tm2, - vertex_point) + precision, + choose_param(get_param(np1, vertex_point), + get_const_property_map(vertex_point, tm1)), + + choose_param(get_param(np2, vertex_point), + get_const_property_map(vertex_point, tm2)) ); } @@ -391,7 +412,7 @@ double approximated_symmetric_Hausdorff_distance( /** * \ingroup PMP_distance_grp * computes the approximated Hausdorff distance between `points` and `tm`. - * @tparam PointRange a Range of Point_3. + * @tparam PointRange a Range of `Point_3`. * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map * for `CGAL::vertex_point_t` * @tparam NamedParameters a sequence of \ref namedparameters for `tm` @@ -408,7 +429,7 @@ template< class Concurrency_tag, class TriangleMesh, class PointRange, class NamedParameters> -double max_distance_to_triangle_mesh(PointRange points, +double max_distance_to_triangle_mesh(const PointRange& points, const TriangleMesh& tm, const NamedParameters& np) { @@ -416,9 +437,8 @@ double max_distance_to_triangle_mesh(PointRange points, NamedParameters>::type Geom_traits; return approximated_Hausdorff_distance - (points,tm, choose_const_pmap(get_param(np, boost::vertex_point), - tm, - vertex_point)); + (points,tm,choose_param(get_param(np, vertex_point), + get_const_property_map(vertex_point, tm))); } @@ -426,7 +446,7 @@ double max_distance_to_triangle_mesh(PointRange points, *\ingroup PMP_distance_grp * Computes the approximated Hausdorff distance between `tm` and `points`. * - * @tparam PointRange a Range of Point_3. + * @tparam PointRange a Range of `Point_3`. * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map * for `CGAL::vertex_point_t` * @tparam NamedParameters a sequence of \ref namedparameters for `tm` @@ -460,11 +480,11 @@ double max_distance_to_point_set(const TriangleMesh& tm, typename Geom_traits::Point_3 points[3]; typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); for(int i=0; i<3; ++i) - { - points[i] = get(choose_const_pmap(get_param(np, boost::vertex_point), - tm, - vertex_point), target(hd, tm)); - hd = next(hd, tm); + { + points[i] = get(choose_param(get_param(np, vertex_point), + get_const_property_map(vertex_point, tm)), + target(hd, tm)); + hd = next(hd, tm); } ref.add(points[0], points[1], points[2], tree); } From 6cf0504f95ef819817177abd3843817eb1be8712 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Mon, 17 Oct 2016 11:42:30 +0200 Subject: [PATCH 20/72] Fixes and clean-up : first part. --- .../PackageDescription.txt | 3 + .../Polygon_mesh_processing/CMakeLists.txt | 10 +-- .../hausdorff_distance_example.cpp | 66 -------------- .../CGAL/Polygon_mesh_processing/distance.h | 85 +++++++++++++------ .../mesh_to_point_set_hausdorff_distance.h | 28 ++---- .../Plugins/PMP/Distance_plugin.cpp | 8 -- 6 files changed, 74 insertions(+), 126 deletions(-) delete mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_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 b17816a7e2b..9f298ed9652 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -135,6 +135,9 @@ and provides a list of the parameters that are used in this package. ## Distance Functions ## - `CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance()` - `CGAL::Polygon_mesh_processing::approximated_symmetric_Hausdorff_distance()` +- `CGAL::Polygon_mesh_processing::max_distance_to_point_set()` +- `CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh()` +- `CGAL::Polygon_mesh_processing::sample_triangle_mesh()` ## Miscellaneous ## - `CGAL::Polygon_mesh_slicer` diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt index 8d1707d7857..f1af34053e4 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt @@ -59,10 +59,6 @@ endif() include_directories( BEFORE ../../include ) find_package(Eigen3 3.1.0) #(requires 3.1.0 or greater) - - if (EIGEN3_FOUND) - # Executables that require Eigen 3.1 - include( ${EIGEN3_USE_FILE} ) # Creating entries for all .cpp/.C files with "main" routine @@ -74,10 +70,14 @@ find_package( TBB ) if( TBB_FOUND ) include( ${TBB_USE_FILE} ) list( APPEND CGAL_3RD_PARTY_LIBRARIES ${TBB_LIBRARIES} ) - create_single_source_cgal_program( "hausdorff_distance_example.cpp") else() message( STATUS "NOTICE: Intel TBB was not found. hausdorff_distance_example will use sequential code." ) endif() +create_single_source_cgal_program( "hausdorff_distance_remeshing_example.cpp") +if (EIGEN3_FOUND) + # Executables that require Eigen 3.1 + include( ${EIGEN3_USE_FILE} ) + create_single_source_cgal_program( "hausdorff_distance_reconstruction_example.cpp") create_single_source_cgal_program( "self_intersections_example.cpp" ) create_single_source_cgal_program( "stitch_borders_example.cpp" ) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_example.cpp deleted file mode 100644 index 80b61dc82f1..00000000000 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_example.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -//typedef CGAL::Exact_predicates_exact_constructions_kernel K; -typedef CGAL::Simple_cartesian K; -typedef K::Point_3 P_3; -typedef CGAL::Surface_mesh Mesh; -namespace PMP = CGAL::Polygon_mesh_processing; -//typedef CGAL::Polyhedron_3 Mesh; - - -int main(int, char**) -{ - - Mesh m1,m2; - /*std::ifstream in1("/home/mgimeno/Bureau/Data/00_elephant.off"); - in1 >> m1; - std::ifstream in2("/home/mgimeno/Bureau/Data/02_elephant.off"); - in2 >> m2;*/ - - CGAL::make_tetrahedron(P_3(.000,.000,.000), - P_3(.002,.000,.000), - P_3(.001,.001,.001), - P_3(.001,.000,.002), - m1); - - CGAL::make_tetrahedron(P_3(-1,0,0), - P_3(-2,-3,0), - P_3(-2.5,-3.5,-1), - P_3(-3,-4,0), - m2); - - std::vector test_points; - test_points.push_back(P_3(-1,0,0)); - test_points.push_back(P_3(1,0,3)); - test_points.push_back(P_3(-1,2,5)); - test_points.push_back(P_3(4.2,1.3,0.6)); - test_points.push_back(P_3(7.1,4.6,.8)); - test_points.push_back(P_3(5.6,6.1,1.7)); - test_points.push_back(P_3(4.6,7.3,2.1)); - test_points.push_back(P_3(1.14,5.20,4.12)); - - - std::cerr<<"distance = "<(m1,m2,40)<(m1,m2,0.01, get(CGAL::vertex_point, m1), get(CGAL::vertex_point, m2), CGAL::Polygon_mesh_processing::GRID)<(m1,m2,40, CGAL::Polygon_mesh_processing::MONTE_CARLO)<(m1,m2,40)<(m1,m2,40.0)< OutputIterator @@ -136,7 +135,7 @@ struct Distance_computation{ Point_3 initial_hint; cpp11::atomic* distance; - Distance_computation(const AABB_tree& tree, const Point_3 p, const std::vector& sample_points, cpp11::atomic* d) + Distance_computation(const AABB_tree& tree, const Point_3& p, const std::vector& sample_points, cpp11::atomic* d) : tree(tree) , sample_points(sample_points) , distance(d) @@ -150,7 +149,8 @@ struct Distance_computation{ double hdist = 0; for( std::size_t i = range.begin(); i != range.end(); ++i) { - double d = CGAL::sqrt( squared_distance(hint,sample_points[i]) ); + hint = tree.closest_point(sample_points[i], hint); + double d = CGAL::approximate_sqrt( squared_distance(hint,sample_points[i]) ); if (d>hdist) hdist=d; } @@ -161,19 +161,19 @@ struct Distance_computation{ #endif /** - * @brief enum used to select the sampling method in the function `sample_triangle_mesh` + * @brief enum used to select the sampling method in the function `sample_triangle_mesh()` */ enum Sampling_method{ RANDOM_UNIFORM =0, GRID, MONTE_CARLO }; -/** fills `sampled_points` with points taken on the mesh in a manner depending on `method`. - * @tparam TriangleMesh a model of the concept `FaceListGraph`. +/** fills `sampled_points` with points taken on the mesh in a way depending on `method` + * @tparam TriangleMesh a model of the concept `FaceListGraph` * @param m the triangle mesh that will be sampled * @param parameter depends on `method` : - * RANDOM_UNIFORM and MONTE_CARLO: the number of points per squared area unit. - * GRID : The distance between two consecutive points in the grid. + * `RANDOM_UNIFORM` and `MONTE_CARLO`: the number of points per squared area unit + * `GRID` : The distance between two consecutive points in the grid * * @param np a sequence of \ref namedparameters for `tm` among the ones listed below * @@ -182,23 +182,22 @@ enum Sampling_method{ * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd * \cgalNamedParamsEnd * - * @param method defines the method of sampling. + * @param method defines the method of sampling * - * @tparam Sampling_method defines the method of sampling. + * @tparam Sampling_method defines the method of sampling * Possible values are : - *- `RANDOM_UNIFORM` : points are generated in a random and uniform way, depending on the area of each triangle., + *- `RANDOM_UNIFORM` : points are generated in a random and uniform way, depending on the area of each triangle. * - *- `GRID` : points are generated in a grid, with a minimum of one point per triangle. + *- `GRID` : points are generated on a grid, with a minimum of one point per triangle. * - *- `MONTE_CARLO` : points are generated randomly in each triangle. Their number in each triangle is proportional to the corresponding face area with a minimum + *- `MONTE_CARLO` : points are generated randomly in each triangle. The number of points per triangle is proportional to the triangle area with a minimum * of 1. * */ -template +template void sample_triangle_mesh(const TriangleMesh& m, double parameter, - std::vector::type::Point_3>& sampled_points, + std::vector& sampled_points, NamedParameters np, Sampling_method method = RANDOM_UNIFORM) { @@ -209,13 +208,13 @@ void sample_triangle_mesh(const TriangleMesh& m, NamedParameters>::const_type Vpm; Vpm pmap = choose_param(get_param(np, vertex_point), - get_property_map(vertex_point, m)); + get_const_property_map(vertex_point, m)); switch(method) { case RANDOM_UNIFORM: { std::size_t nb_points = std::ceil( - parameter * PMP::area(m, PMP::parameters::geom_traits(Geom_traits()))); + parameter * area(m, parameters::geom_traits(Geom_traits()))); Random_points_in_triangle_mesh_3 g(m, pmap); CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); @@ -244,7 +243,7 @@ void sample_triangle_mesh(const TriangleMesh& m, { BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) { - std::size_t nb_points = (std::max)((int)std::ceil(parameter * PMP::face_area(f,m,PMP::parameters::geom_traits(Kernel()))), + std::size_t nb_points = (std::max)((int)std::ceil(parameter * face_area(f,m,parameters::geom_traits(Geom_traits()))), 1); //create the triangles and store them typename Geom_traits::Point_3 points[3]; @@ -261,6 +260,19 @@ void sample_triangle_mesh(const TriangleMesh& m, } } } +template +void sample_triangle_mesh(const TriangleMesh& m, + double parameter, + std::vector& sampled_points, + Sampling_method method = RANDOM_UNIFORM) +{ + sample_triangle_mesh( + m, + parameter, + sampled_points, + parameters::all_default(), + method); +} template double approximated_Hausdorff_distance( std::vector& sample_points, @@ -311,19 +323,19 @@ double approximated_Hausdorff_distance( } template double approximated_Hausdorff_distance( const TriangleMesh& m1, const TriangleMesh& m2, double precision, - VertexPointMap1 vpm1, + NamedParameters np, VertexPointMap2 vpm2, Sampling_method method = RANDOM_UNIFORM ) { std::vector sample_points; - sample_triangle_mesh(m1, precision ,sample_points, vpm1, method ); + sample_triangle_mesh(m1, precision ,sample_points, np, method ); return approximated_Hausdorff_distance(sample_points, m2, vpm2); } @@ -386,9 +398,9 @@ double approximated_Hausdorff_distance( const TriangleMesh& tm1, /** * \ingroup PMP_distance_grp - * computes the approximated symmetric Hausdorff distance between `tm1` and `tm2`. + * computes the approximated symmetric Hausdorff distance between `tm1` and `tm2` * It returns the maximum of `approximated_Hausdorff_distance(tm1,tm2)` and - * `approximated_Hausdorff_distance(tm1,tm2)`. + * `approximated_Hausdorff_distance(tm1,tm2)` * * \copydetails CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance() */ @@ -411,8 +423,8 @@ double approximated_symmetric_Hausdorff_distance( /** * \ingroup PMP_distance_grp - * computes the approximated Hausdorff distance between `points` and `tm`. - * @tparam PointRange a Range of `Point_3`. + * computes the approximated Hausdorff distance between `points` and `tm` + * @tparam PointRange a Range of `Point_3` * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map * for `CGAL::vertex_point_t` * @tparam NamedParameters a sequence of \ref namedparameters for `tm` @@ -441,12 +453,21 @@ double max_distance_to_triangle_mesh(const PointRange& points, get_const_property_map(vertex_point, tm))); } +template< class Concurrency_tag, + class TriangleMesh, + class PointRange, + class NamedParameters> +double max_distance_to_triangle_mesh(const PointRange& points, + const TriangleMesh& tm) +{ + max_distance_to_triangle_mesh(points, tm, parameters::all_default()); +} /*! *\ingroup PMP_distance_grp - * Computes the approximated Hausdorff distance between `tm` and `points`. + * Computes the approximated Hausdorff distance between `tm` and `points` * - * @tparam PointRange a Range of `Point_3`. + * @tparam PointRange a Range of `Point_3` * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map * for `CGAL::vertex_point_t` * @tparam NamedParameters a sequence of \ref namedparameters for `tm` @@ -491,6 +512,14 @@ double max_distance_to_point_set(const TriangleMesh& tm, return ref.refine(precision, tree); } +template< class TriangleMesh, + class PointRange> +double max_distance_to_point_set(const TriangleMesh& tm, + const PointRange& points, + const double precision) +{ + max_distance_to_point_set(tm, points, precision, parameters::all_default()); +} // convenience functions with default parameters template< class Concurrency_tag, diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h index 6329c0399cc..1e36332a3d2 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h @@ -24,7 +24,9 @@ #include #include #include - +#include +namespace CGAL{ +namespace internal{ template class CPointH { @@ -53,7 +55,7 @@ public: m_hausdorff = p.hausdorff (); } - Point operator() () const { return m_point; } + const Point& operator() () const { return m_point; } Point& operator() () { return m_point; } FT hausdorff () const { return m_hausdorff; } @@ -107,18 +109,10 @@ public: { m_bisector = PointH::mid_point (m_point[(m_edge+1)%3], m_point[(m_edge+2)%3]); - - // Point p0 = m_point[(m_edge+1)%3](); - // Point p1 = m_point[(m_edge+2)%3](); - - - // m_bisector = Point ((p0.x () + p1.x ()) / 2., - // (p0.y () + p1.y ()) / 2., - // (p0.z () + p1.z ()) / 2.); } m_lower_bound = 0.; - m_upper_bound = 0.; //std::numeric_limits::max (); + m_upper_bound = 0.; for (unsigned int i = 0; i < 3; ++ i) { if (m_point[i].hausdorff () > m_lower_bound) @@ -177,8 +171,8 @@ public: << " -> " << m_bisector << std::endl; } }; +}//internal -namespace CGAL{ template class CRefiner { @@ -187,8 +181,8 @@ private: typedef typename Kernel::Point_3 Point; typedef typename Kernel::Triangle_3 Triangle; typedef typename Kernel::FT FT; - typedef CPointH PointH; - typedef CRefTriangle RefTriangle; + typedef internal::CPointH PointH; + typedef internal::CRefTriangle RefTriangle; typedef CGAL::Orthogonal_k_neighbor_search > Knn; typedef typename Knn::Tree Tree; std::priority_queue m_queue; @@ -203,7 +197,6 @@ public: m_lower_bound = 0.; m_upper_bound = std::numeric_limits::max (); } - ~CRefiner () { } bool empty () { @@ -262,7 +255,6 @@ public: bool clean_up_queue () { unsigned int before = m_queue.size (); - // std::cerr << "Cleaned " << m_queue.size () << " elements to "; std::vector to_keep; while (!(m_queue.empty ())) { @@ -271,10 +263,9 @@ public: to_keep.push_back (current); m_queue.pop (); } - // std::cerr << to_keep.size () << " elements" << std::endl; m_queue = std::priority_queue (); - for (auto& r : to_keep) + BOOST_FOREACH(RefTriangle& r, to_keep) m_queue.push (r); return (m_queue.size () < before); @@ -282,7 +273,6 @@ public: FT refine (FT limit, const Tree& tree, FT upper_bound = 1e30) { - // std::ofstream f ("rquick.plot"); unsigned int nb_clean = 0; diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp index c7d1323199f..acc05d01c33 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp @@ -19,7 +19,6 @@ #include #include -//This plugin crates an action in Operations that displays "Hello World" in the 'console' dockwidet. using namespace CGAL::Three; namespace PMP = CGAL::Polygon_mesh_processing; @@ -228,12 +227,6 @@ private: //compute distance with other polyhedron //sample facet std::vector sampled_points; - -//============== GRID ================ - /*PMP::internal::triangle_grid_sampling(f->halfedge()->vertex()->point(), f->halfedge()->next()->vertex()->point(), - f->halfedge()->next()->next()->vertex()->point(), - 0.05, std::back_inserter(sampled_points));*/ -//============== MONTE_CARLO ================ std::size_t nb_points = (std::max)((int)std::ceil(400 * PMP::face_area(f,*poly,PMP::parameters::geom_traits(Kernel()))), 1); CGAL::Random_points_in_triangle_3 g(f->halfedge()->vertex()->point(), f->halfedge()->next()->vertex()->point(), @@ -242,7 +235,6 @@ private: sampled_points.push_back(f->halfedge()->vertex()->point()); sampled_points.push_back(f->halfedge()->next()->vertex()->point()); sampled_points.push_back(f->halfedge()->next()->next()->vertex()->point()); -// FIN MONTE_CARLO //triangle facets with sample points for color display FT triangulation(f,sampled_points,nf,poly,diagonal); From a4127d74c1d8924f376d9ca9e55295ee0bb81f40 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Mon, 17 Oct 2016 14:08:25 +0200 Subject: [PATCH 21/72] Add distance computation to poisson_reconstruction's example. --- .../poisson_reconstruction_example.cpp | 8 ++++ .../Polygon_mesh_processing/CMakeLists.txt | 1 - .../hausdorff_distance_remeshing_example.cpp | 44 +++++++++++++++++++ .../CGAL/Polygon_mesh_processing/distance.h | 21 +++++---- .../Surface_reconstruction_plugin_impl.cpp | 2 - 5 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp diff --git a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp index 0453ddaf5bc..2db70ab4120 100644 --- a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp +++ b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include #include @@ -103,6 +105,12 @@ int main(void) Polyhedron output_mesh; CGAL::output_surface_facets_to_polyhedron(c2t3, output_mesh); out << output_mesh; + std::vector query; + BOOST_FOREACH(Point_with_normal pn, points) + query.push_back(pn.position()); + + double dist = CGAL::Polygon_mesh_processing::max_distance_to_point_set(output_mesh, query, 4000); + std::cerr<<"Max distance to point_set :"< + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +#include +#include + +#if CGAL_LINKED_WITH_TBB +#define TAG CGAL::Parallel_tag +#else +#define TAG CGAL::Sequential_tag +#endif +typedef CGAL::Simple_cartesian K; +typedef K::Point_3 P_3; +typedef CGAL::Surface_mesh Mesh; +namespace PMP = CGAL::Polygon_mesh_processing; + + +int main(int, char**) +{ + + Mesh m1, m2; + CGAL::make_tetrahedron(P_3(.0,.0,.0), + P_3(2,.0,.0), + P_3(1,1,1), + P_3(1,.0,2), + m1); + m2=m1; + PMP::isotropic_remeshing(m2.faces(),.05, m2); + std::cerr<< " Approximated Hausdorff distance : "<< CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(m1,m2,400)< double approximated_Hausdorff_distance( - std::vector& sample_points, + const std::vector& original_sample_points, const TriangleMesh& m, VertexPointMap vpm ) @@ -286,6 +287,8 @@ double approximated_Hausdorff_distance( #ifdef CGAL_HAUSDORFF_DEBUG std::cout << "Nb sample points " << sample_points.size() << "\n"; #endif + std::vector sample_points = original_sample_points; + spatial_sort(sample_points.begin(), sample_points.end()); typedef AABB_face_graph_triangle_primitive Primitive; @@ -316,7 +319,8 @@ double approximated_Hausdorff_distance( hint = tree.closest_point(pt, hint); typename Kernel::FT dist = squared_distance(hint,pt); double d = to_double(CGAL::approximate_sqrt(dist)); - if (d>hdist) hdist=d; + if(d>hdist) + hdist=d; } return hdist; } @@ -455,12 +459,11 @@ double max_distance_to_triangle_mesh(const PointRange& points, template< class Concurrency_tag, class TriangleMesh, - class PointRange, - class NamedParameters> + class PointRange> double max_distance_to_triangle_mesh(const PointRange& points, const TriangleMesh& tm) { - max_distance_to_triangle_mesh(points, tm, parameters::all_default()); + return max_distance_to_triangle_mesh(points, tm, parameters::all_default()); } /*! @@ -518,20 +521,20 @@ double max_distance_to_point_set(const TriangleMesh& tm, const PointRange& points, const double precision) { - max_distance_to_point_set(tm, points, precision, parameters::all_default()); + return max_distance_to_point_set(tm, points, precision, parameters::all_default()); } // convenience functions with default parameters template< class Concurrency_tag, class TriangleMesh, - class NamedParameters1> + class NamedParameters> double approximated_Hausdorff_distance( const TriangleMesh& tm1, const TriangleMesh& tm2, double precision, - const NamedParameters1& np1) + const NamedParameters& np) { return approximated_Hausdorff_distance( - tm1, tm2, precision, np1, parameters::all_default()); + tm1, tm2, precision, np, parameters::all_default()); } template< class Concurrency_tag, diff --git a/Polyhedron/demo/Polyhedron/Plugins/Point_set/Surface_reconstruction_plugin_impl.cpp b/Polyhedron/demo/Polyhedron/Plugins/Point_set/Surface_reconstruction_plugin_impl.cpp index e167db904cb..10c4fc22c8d 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Point_set/Surface_reconstruction_plugin_impl.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Point_set/Surface_reconstruction_plugin_impl.cpp @@ -237,8 +237,6 @@ Polyhedron* poisson_reconstruct(Point_set& points, } } - - return output_mesh; } From 0c3edf822c246a26306decf1819bd9340fba09f3 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Mon, 17 Oct 2016 16:31:22 +0200 Subject: [PATCH 22/72] Add tests for misisng documented functions. --- .../CGAL/Polygon_mesh_processing/distance.h | 10 ++--- .../test_pmp_distance.cpp | 38 ++++++++++++++++++- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index e30cdb0a1b5..27a35406030 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -328,19 +328,19 @@ double approximated_Hausdorff_distance( template + class VertexPointMap > double approximated_Hausdorff_distance( const TriangleMesh& m1, const TriangleMesh& m2, double precision, NamedParameters np, - VertexPointMap2 vpm2, + VertexPointMap vpm, Sampling_method method = RANDOM_UNIFORM ) { std::vector sample_points; sample_triangle_mesh(m1, precision ,sample_points, np, method ); - return approximated_Hausdorff_distance(sample_points, m2, vpm2); + return approximated_Hausdorff_distance(sample_points, m2, vpm); } // documented functions @@ -552,11 +552,11 @@ double approximated_Hausdorff_distance( const TriangleMesh& tm1, template< class Concurrency_tag, class TriangleMesh, - class NamedParameters1> + class NamedParameters> double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1, const TriangleMesh& tm2, double precision, - const NamedParameters1& np1) + const NamedParameters& np) { return approximated_symmetric_Hausdorff_distance( tm1, tm2, precision, np1, parameters::all_default()); diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp index 1d3902ee413..202fc1a2986 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp @@ -15,6 +15,38 @@ typedef CGAL::Simple_cartesian K; typedef CGAL::Surface_mesh Mesh; +bool general_tests(const Mesh& m1, + const Mesh& m2 ) +{ + + std::cout << "Symetric distance between meshes (sequential) " + << CGAL::Polygon_mesh_processing::approximated_symmetric_Hausdorff_distance(m1,m2,4000) + << "\n"; + + std::vector points; + + points.push_back(K::Point_3(0,0,0)); + points.push_back(K::Point_3(0,1,0)); + points.push_back(K::Point_3(1,0,0)); + points.push_back(K::Point_3(0,0,1)); + points.push_back(K::Point_3(0,2,0)); + + Mesh m; + CGAL::make_tetrahedron(points[0], + points[1], + points[2], + points[3], + m); + + std::cout << "Max distance to point set " + << CGAL::Polygon_mesh_processing::max_distance_to_point_set(m,points,4000) + << "\n"; + + std::cout << "Max distance to triangle mesh (sequential) " + << CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh(points,m) + << "\n"; + +} int main(int argc, char** argv) { @@ -32,7 +64,7 @@ int main(int argc, char** argv) CGAL::Timer time; time.start(); - std::cout << "Distance between meshes (parallel)" + std::cout << "Distance between meshes (parallel) " << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(m1,m2,0.001, get(CGAL::vertex_point, m1), get(CGAL::vertex_point, m2), CGAL::Polygon_mesh_processing::GRID) << "\n"; time.stop(); @@ -40,11 +72,13 @@ int main(int argc, char** argv) time.reset(); time.start(); - std::cout << "Distance between meshes (sequential)" + std::cout << "Distance between meshes (sequential) " << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(m1,m2,0.001, get(CGAL::vertex_point, m1), get(CGAL::vertex_point, m2), CGAL::Polygon_mesh_processing::GRID) << "\n"; time.stop(); std::cout << "done in " << time.time() << "s.\n"; + + general_tests(m1,m2); } From 81b11ee2d5a48a6b5820e3e6669dddb2d315ac3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 17 Oct 2016 16:35:01 +0200 Subject: [PATCH 23/72] Bug-fix: accept range of points as input and not only vectors --- .../poisson_reconstruction_example.cpp | 9 +++++---- .../include/CGAL/Polygon_mesh_processing/distance.h | 7 ++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp index 2db70ab4120..ad1162a7d8a 100644 --- a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp +++ b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp @@ -105,11 +105,12 @@ int main(void) Polyhedron output_mesh; CGAL::output_surface_facets_to_polyhedron(c2t3, output_mesh); out << output_mesh; - std::vector query; - BOOST_FOREACH(Point_with_normal pn, points) - query.push_back(pn.position()); - double dist = CGAL::Polygon_mesh_processing::max_distance_to_point_set(output_mesh, query, 4000); + + double dist = CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh( + points, + output_mesh); + std::cerr<<"Max distance to point_set :"< +template double approximated_Hausdorff_distance( - const std::vector& original_sample_points, + const PointRange& original_sample_points, const TriangleMesh& m, VertexPointMap vpm ) @@ -287,7 +287,8 @@ double approximated_Hausdorff_distance( #ifdef CGAL_HAUSDORFF_DEBUG std::cout << "Nb sample points " << sample_points.size() << "\n"; #endif - std::vector sample_points = original_sample_points; + std::vector sample_points + (boost::begin(original_sample_points), boost::end(original_sample_points) ); spatial_sort(sample_points.begin(), sample_points.end()); From cbf7e6b72217464b21b564053c2fae53602e6609 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Mon, 17 Oct 2016 16:44:35 +0200 Subject: [PATCH 24/72] Doc and test fix. --- .../CGAL/Polygon_mesh_processing/distance.h | 22 +++++++++---------- .../test_pmp_distance.cpp | 4 ++-- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index a05f7b5d14d..b92aeb2c2f8 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -175,10 +175,10 @@ enum Sampling_method{ * `RANDOM_UNIFORM` and `MONTE_CARLO`: the number of points per squared area unit * `GRID` : The distance between two consecutive points in the grid * - * @param np a sequence of \ref namedparameters for `tm` among the ones listed below + * @param np a sequence of \ref namedparameters for `m` among the ones listed below * * \cgalNamedParamsBegin - * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh` \cgalParamEnd + * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `m` \cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd * \cgalNamedParamsEnd * @@ -341,7 +341,7 @@ double approximated_Hausdorff_distance( { std::vector sample_points; sample_triangle_mesh(m1, precision ,sample_points, np, method ); - return approximated_Hausdorff_distance(sample_points, m2, vpm); + return approximated_Hausdorff_distance(sample_points, m2, vpm); } // documented functions @@ -360,7 +360,7 @@ double approximated_Hausdorff_distance( * @tparam Concurrency_tag enables sequential versus parallel algorithm. * Possible values are `Sequential_tag` * and `Parallel_tag`. - * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map + * @tparam TriangleMesh a model of the concept `FaceListGraph` * for `CGAL::vertex_point_t` * @tparam NamedParameters1 a sequence of \ref namedparameters for `tm1` * @tparam NamedParameters2 a sequence of \ref namedparameters for `tm2` @@ -372,7 +372,7 @@ double approximated_Hausdorff_distance( * @param np2 optional sequence of \ref namedparameters for `tm2` among the ones listed below * * \cgalNamedParamsBegin - * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh` \cgalParamEnd + * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm1` or `tm2` \cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd * \cgalNamedParamsEnd */ @@ -430,15 +430,14 @@ double approximated_symmetric_Hausdorff_distance( * \ingroup PMP_distance_grp * computes the approximated Hausdorff distance between `points` and `tm` * @tparam PointRange a Range of `Point_3` - * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map - * for `CGAL::vertex_point_t` + * @tparam TriangleMesh a model of the concept `FaceListGraph` * @tparam NamedParameters a sequence of \ref namedparameters for `tm` * @param points the point_set of interest * @param tm the triangle mesh to compute the distance to * @param np a sequence of \ref namedparameters for `tm` among the ones listed below * * \cgalNamedParamsBegin - * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh` \cgalParamEnd + * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm` \cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd * \cgalNamedParamsEnd */ @@ -472,8 +471,7 @@ double max_distance_to_triangle_mesh(const PointRange& points, * Computes the approximated Hausdorff distance between `tm` and `points` * * @tparam PointRange a Range of `Point_3` - * @tparam TriangleMesh a model of the concept `FaceListGraph` that has an internal property map - * for `CGAL::vertex_point_t` + * @tparam TriangleMesh a model of the concept `FaceListGraph` * @tparam NamedParameters a sequence of \ref namedparameters for `tm` * @param tm the triangle mesh to compute the distance to * @param points the point_set of interest. @@ -481,7 +479,7 @@ double max_distance_to_triangle_mesh(const PointRange& points, * @param np a sequence of \ref namedparameters for `tm` among the ones listed below * * \cgalNamedParamsBegin - * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh` \cgalParamEnd + * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm` \cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd * \cgalNamedParamsEnd */ @@ -560,7 +558,7 @@ double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1, const NamedParameters& np) { return approximated_symmetric_Hausdorff_distance( - tm1, tm2, precision, np1, parameters::all_default()); + tm1, tm2, precision, np, parameters::all_default()); } template< class Concurrency_tag, diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp index 202fc1a2986..99f9b3b9acf 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp @@ -65,7 +65,7 @@ int main(int argc, char** argv) CGAL::Timer time; time.start(); std::cout << "Distance between meshes (parallel) " - << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(m1,m2,0.001, get(CGAL::vertex_point, m1), get(CGAL::vertex_point, m2), CGAL::Polygon_mesh_processing::GRID) + << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(m1,m2,40000) << "\n"; time.stop(); std::cout << "done in " << time.time() << "s.\n"; @@ -73,7 +73,7 @@ int main(int argc, char** argv) time.reset(); time.start(); std::cout << "Distance between meshes (sequential) " - << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(m1,m2,0.001, get(CGAL::vertex_point, m1), get(CGAL::vertex_point, m2), CGAL::Polygon_mesh_processing::GRID) + << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(m1,m2,40000) << "\n"; time.stop(); std::cout << "done in " << time.time() << "s.\n"; From 3fca4399c6eef43c36b97c5d22132372349b887a Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Tue, 18 Oct 2016 10:51:15 +0200 Subject: [PATCH 25/72] Add examples and keep fixing the doc --- .../doc/Polygon_mesh_processing/Doxyfile.in | 2 ++ .../PackageDescription.txt | 2 +- .../Polygon_mesh_processing.txt | 19 +++++++++++++++++++ .../doc/Polygon_mesh_processing/examples.txt | 2 ++ .../hausdorff_distance_remeshing_example.cpp | 11 +---------- .../CGAL/Polygon_mesh_processing/distance.h | 5 ++++- 6 files changed, 29 insertions(+), 12 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in index 71e185e06ef..b67c5ca856c 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Doxyfile.in @@ -18,6 +18,8 @@ ALIASES += "cgalNPTableEnd= " ALIASES += "cgalNPBegin{1}=\1 " ALIASES += "cgalNPEnd=" +EXAMPLE_PATH += ${CGAL_Poisson_surface_reconstruction_3_EXAMPLE_DIR} + MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES EXPAND_AS_DEFINED = CGAL_PMP_NP_TEMPLATE_PARAMETERS \ diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 9f298ed9652..14d24b896e1 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -40,7 +40,7 @@ /// Functions to orient polygon soups and to stitch geometrically identical boundaries. /// \ingroup PkgPolygonMeshProcessing -/// \defgroup PMP_distance_grp Combinatorial Repairing +/// \defgroup PMP_distance_grp Approximated Hausdorff Distance /// Functions to compute the distance between meshes. /// \ingroup PkgPolygonMeshProcessing 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 0ae1b22cb07..4d14be65702 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 @@ -487,5 +487,24 @@ the propagation of a connected component index to cross it. A first version of this package was started by Ilker %O. Yaz and Sébastien Loriot. Jane Tournois worked on the finalization of the API, code, and documentation. +**************************************** +\section PMPDistance Approximated Hausdorff Distance + +This package provides methods to compute approximated Hausdorff distances. +These distances can be computed between two meshes, a mesh and a point set or a point set and a mesh. +These computations are performed with : +- `CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance()` +- `CGAL::Polygon_mesh_processing::approximated_symmetric_Hausdorff_distance()` +- `CGAL::Polygon_mesh_processing::max_distance_to_point_set()` +- `CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh()` +- `CGAL::Polygon_mesh_processing::sample_triangle_mesh()` + +The following example shows how to compute the approximated distance between a mesh and its isotropic remeshing. + +\cgalExample{Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp} + +The following example shows how to compute the approximated distance between a point set and its Poisson reconstruction. + +\cgalExample{Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp} */ } /* namespace CGAL */ diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt index 60587796e37..2c1b138146e 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/examples.txt @@ -13,4 +13,6 @@ \example Polygon_mesh_processing/mesh_slicer_example.cpp \example Polygon_mesh_processing/isotropic_remeshing_example.cpp \example Polygon_mesh_processing/compute_normals_example_Polyhedron.cpp +\example Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp +\example Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp */ diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp index 75744d1516b..e40dce53ed5 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp @@ -2,19 +2,10 @@ #include #include -#include - -#include -#include #include -#include -#include #include -#include -#include - #if CGAL_LINKED_WITH_TBB #define TAG CGAL::Parallel_tag #else @@ -37,7 +28,7 @@ int main(int, char**) m1); m2=m1; PMP::isotropic_remeshing(m2.faces(),.05, m2); - std::cerr<< " Approximated Hausdorff distance : "<< CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(m1,m2,400)<(m1,m2,4000)< void sample_triangle_mesh(const TriangleMesh& m, double parameter, From cc44c571e5682cea0427bb7c319c78e5b9f9082d Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Tue, 18 Oct 2016 12:54:43 +0200 Subject: [PATCH 26/72] Shorten the code lines. --- .../hausdorff_distance_remeshing_example.cpp | 5 +++- .../CGAL/Polygon_mesh_processing/distance.h | 26 ++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp index e40dce53ed5..378dada2e23 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp @@ -28,7 +28,10 @@ int main(int, char**) m1); m2=m1; PMP::isotropic_remeshing(m2.faces(),.05, m2); - std::cerr<< " Approximated Hausdorff distance : "<< CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(m1,m2,4000)< + (m1,m2,4000) + <* distance; - Distance_computation(const AABB_tree& tree, const Point_3& p, const std::vector& sample_points, cpp11::atomic* d) + Distance_computation( + const AABB_tree& tree, + const Point_3& p, + const std::vector& sample_points, + cpp11::atomic* d) : tree(tree) , sample_points(sample_points) , distance(d) @@ -245,7 +249,8 @@ void sample_triangle_mesh(const TriangleMesh& m, { BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) { - std::size_t nb_points = (std::max)((int)std::ceil(parameter * face_area(f,m,parameters::geom_traits(Geom_traits()))), + std::size_t nb_points = (std::max)( + (int)std::ceil(parameter * face_area(f,m,parameters::geom_traits(Geom_traits()))), 1); //create the triangles and store them typename Geom_traits::Point_3 points[3]; @@ -342,9 +347,15 @@ double approximated_Hausdorff_distance( Sampling_method method = RANDOM_UNIFORM ) { - std::vector sample_points; - sample_triangle_mesh(m1, precision ,sample_points, np, method ); - return approximated_Hausdorff_distance(sample_points, m2, vpm); + std::vector sample_points; + sample_triangle_mesh( + m1, + precision, + sample_points, + np, + method ); + return approximated_Hausdorff_distance(sample_points, m2, vpm); } // documented functions @@ -466,7 +477,10 @@ template< class Concurrency_tag, double max_distance_to_triangle_mesh(const PointRange& points, const TriangleMesh& tm) { - return max_distance_to_triangle_mesh(points, tm, parameters::all_default()); + return max_distance_to_triangle_mesh + (points, tm, parameters::all_default()); } /*! From 0cc2c59981a8df6455284d29dc62c8017f2cb77f Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Tue, 18 Oct 2016 15:11:52 +0200 Subject: [PATCH 27/72] Fixes for SF. --- Installation/changes.html | 2 +- .../poisson_reconstruction_example.cpp | 9 +- .../PackageDescription.txt | 2 +- .../Polygon_mesh_processing.txt | 7 +- .../Polygon_mesh_processing/CMakeLists.txt | 20 ++-- .../hausdorff_distance_remeshing_example.cpp | 4 +- .../CGAL/Polygon_mesh_processing/distance.h | 101 ++++++++++-------- .../Polygon_mesh_processing/CMakeLists.txt | 22 ++-- 8 files changed, 83 insertions(+), 84 deletions(-) diff --git a/Installation/changes.html b/Installation/changes.html index 31a3199dfa8..e2f9ba01a1d 100644 --- a/Installation/changes.html +++ b/Installation/changes.html @@ -256,7 +256,7 @@ and src/ directories).

Polygon Mesh Processing

  • - Add functions : + Add functions to compute approximated Hausdorff distances between two meshes, a mesh and a point set, or a point set and a mesh: sample_triangle_mesh, approximated_Hausdorff_distance, approximated_symmetric_Hausdorff_distance, max_distance_to_triangle_mesh, diff --git a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp index ad1162a7d8a..97c507b3fd7 100644 --- a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp +++ b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp @@ -107,11 +107,12 @@ int main(void) out << output_mesh; - double dist = CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh( - points, - output_mesh); + double dist = CGAL::Polygon_mesh_processing::max_distance_to_point_set( + output_mesh, + points, + 40000); - std::cerr<<"Max distance to point_set :"< -#include +#include #include #include #include @@ -11,7 +11,7 @@ #else #define TAG CGAL::Sequential_tag #endif -typedef CGAL::Simple_cartesian K; +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; typedef K::Point_3 P_3; typedef CGAL::Surface_mesh Mesh; namespace PMP = CGAL::Polygon_mesh_processing; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 7b11a2979ab..509df3f7e88 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -181,29 +181,31 @@ enum Sampling_method{ * `RANDOM_UNIFORM` and `MONTE_CARLO`: the number of points per squared area unit * `GRID` : The distance between two consecutive points in the grid * - * @param np a sequence of \ref namedparameters for `m` among the ones listed below + * @param np an optionnal sequence of \ref namedparameters for `m` among the ones listed below + * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` and in all places where vertex_point_map is used * * \cgalNamedParamsBegin - * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `m` \cgalParamEnd + * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `m` + * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` + * and in all places where vertex_point_map is used.\cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd * \cgalNamedParamsEnd * * @param method defines the method of sampling * - * @tparam Sampling_method defines the method of sampling - * Possible values are : + * @tparam Sampling_method defines the sampling method. Possible values are : *- `RANDOM_UNIFORM` : points are generated in a random and uniform way, depending on the area of each triangle. * *- `GRID` : points are generated on a grid, with a minimum of one point per triangle. * - *- `MONTE_CARLO` : points are generated randomly in each triangle. The number of points per triangle is proportional to the triangle area with a minimum - * of 1. + *- `MONTE_CARLO` : points are generated randomly in each triangle. + * The number of points per triangle is proportional to the triangle area with a minimum of 1. * */ -template -void sample_triangle_mesh(const TriangleMesh& m, +template +OutputIterator sample_triangle_mesh(const TriangleMesh& m, double parameter, - std::vector& sampled_points, + OutputIterator out, NamedParameters np, Sampling_method method = RANDOM_UNIFORM) { @@ -223,8 +225,8 @@ void sample_triangle_mesh(const TriangleMesh& m, parameter * area(m, parameters::geom_traits(Geom_traits()))); Random_points_in_triangle_mesh_3 g(m, pmap); - CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); - return; + CGAL::cpp11::copy_n(g, nb_points, out); + return out; } case GRID: { @@ -242,8 +244,8 @@ void sample_triangle_mesh(const TriangleMesh& m, } triangles.push_back(typename Geom_traits::Triangle_3(points[0], points[1], points[2])); } - sample_triangles(triangles, parameter, std::back_inserter(sampled_points)); - return; + sample_triangles(triangles, parameter, out); + return out; } case MONTE_CARLO: { @@ -261,23 +263,23 @@ void sample_triangle_mesh(const TriangleMesh& m, hd = next(hd, m); } Random_points_in_triangle_3 g(points[0], points[1], points[2]); - CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); + CGAL::cpp11::copy_n(g, nb_points, out); } - return; + return out; } } } -template -void sample_triangle_mesh(const TriangleMesh& m, +template +OutputIterator sample_triangle_mesh(const TriangleMesh& m, double parameter, - std::vector& sampled_points, + OutputIterator out, Sampling_method method = RANDOM_UNIFORM) { - sample_triangle_mesh( + return sample_triangle_mesh( m, parameter, - sampled_points, + out, parameters::all_default(), method); } @@ -348,11 +350,10 @@ double approximated_Hausdorff_distance( ) { std::vector sample_points; - sample_triangle_mesh( + sample_triangle_mesh( m1, precision, - sample_points, + std::back_inserter(sample_points), np, method ); return approximated_Hausdorff_distance(sample_points, m2, vpm); @@ -374,8 +375,7 @@ double approximated_Hausdorff_distance( * @tparam Concurrency_tag enables sequential versus parallel algorithm. * Possible values are `Sequential_tag` * and `Parallel_tag`. - * @tparam TriangleMesh a model of the concept `FaceListGraph` - * for `CGAL::vertex_point_t` + * @tparam `TriangleMesh` a model of the concept `FaceListGraph` * @tparam NamedParameters1 a sequence of \ref namedparameters for `tm1` * @tparam NamedParameters2 a sequence of \ref namedparameters for `tm2` * @@ -385,8 +385,11 @@ double approximated_Hausdorff_distance( * @param np1 optional sequence of \ref namedparameters for `tm1` among the ones listed below * @param np2 optional sequence of \ref namedparameters for `tm2` among the ones listed below * + * * \cgalNamedParamsBegin - * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm1` or `tm2` \cgalParamEnd + * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm1` or `tm2` + * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` + * and in all places where vertex_point_map is used.\cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd * \cgalNamedParamsEnd */ @@ -444,15 +447,19 @@ double approximated_symmetric_Hausdorff_distance( * \ingroup PMP_distance_grp * computes the approximated Hausdorff distance between `points` and `tm` * @tparam PointRange a Range of `Point_3` - * @tparam TriangleMesh a model of the concept `FaceListGraph` + * @tparam `TriangleMesh` a model of the concept `FaceListGraph` * @tparam NamedParameters a sequence of \ref namedparameters for `tm` * @param points the point_set of interest * @param tm the triangle mesh to compute the distance to - * @param np a sequence of \ref namedparameters for `tm` among the ones listed below + * @param np an optionnal sequence of \ref namedparameters for `tm` among the ones listed below + * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` and in all places where vertex_point_map is used * * \cgalNamedParamsBegin - * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm` \cgalParamEnd + * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm` + * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` + * and in all places where vertex_point_map is used.\cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd + * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` and in all places where vertex_point_map is used. * \cgalNamedParamsEnd */ template< class Concurrency_tag, @@ -471,32 +478,23 @@ double max_distance_to_triangle_mesh(const PointRange& points, get_const_property_map(vertex_point, tm))); } -template< class Concurrency_tag, - class TriangleMesh, - class PointRange> -double max_distance_to_triangle_mesh(const PointRange& points, - const TriangleMesh& tm) -{ - return max_distance_to_triangle_mesh - (points, tm, parameters::all_default()); -} - /*! *\ingroup PMP_distance_grp * Computes the approximated Hausdorff distance between `tm` and `points` * * @tparam PointRange a Range of `Point_3` - * @tparam TriangleMesh a model of the concept `FaceListGraph` - * @tparam NamedParameters a sequence of \ref namedparameters for `tm` + * @tparam `TriangleMesh` a model of the concept `FaceListGraph` + * @tparam NamedParameters an optionnal sequence of \ref namedparameters for `tm` * @param tm the triangle mesh to compute the distance to * @param points the point_set of interest. * @param precision the precision of the approximated value you want. * @param np a sequence of \ref namedparameters for `tm` among the ones listed below + * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` and in all places where vertex_point_map is used * * \cgalNamedParamsBegin - * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm` \cgalParamEnd + * the property map with the points associated to the vertices of `tm` + * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` + * and in all places where vertex_point_map is used.\cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd * \cgalNamedParamsEnd */ @@ -531,6 +529,20 @@ double max_distance_to_point_set(const TriangleMesh& tm, return ref.refine(precision, tree); } +// convenience functions with default parameters + +template< class Concurrency_tag, + class TriangleMesh, + class PointRange> +double max_distance_to_triangle_mesh(const PointRange& points, + const TriangleMesh& tm) +{ + return max_distance_to_triangle_mesh + (points, tm, parameters::all_default()); +} + template< class TriangleMesh, class PointRange> double max_distance_to_point_set(const TriangleMesh& tm, @@ -539,7 +551,6 @@ double max_distance_to_point_set(const TriangleMesh& tm, { return max_distance_to_point_set(tm, points, precision, parameters::all_default()); } -// convenience functions with default parameters template< class Concurrency_tag, class TriangleMesh, diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt index 69c5bb193ee..de04e69c3e3 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/CMakeLists.txt @@ -49,7 +49,7 @@ endif() # include for local package include_directories( BEFORE ../../include ) -find_package(Eigen3 3.1.0) #(requires 3.1.0 or greater) +find_package(Eigen3 3.2.0) #(requires 3.2.0 or greater) find_package( TBB ) if( TBB_FOUND ) @@ -59,14 +59,16 @@ else() message( STATUS "NOTICE: Intel TBB was not found. test_pmp_distance will use sequential code." ) endif() +include( CGAL_CreateSingleSourceCGALProgram ) if (EIGEN3_FOUND) - # Executables that require Eigen 3.1 include( ${EIGEN3_USE_FILE} ) # Creating entries for all .cpp/.C files with "main" routine # ########################################################## - - include( CGAL_CreateSingleSourceCGALProgram ) + create_single_source_cgal_program("fairing_test.cpp") + create_single_source_cgal_program("triangulate_hole_Polyhedron_3_no_delaunay_test.cpp" ) + create_single_source_cgal_program("triangulate_hole_Polyhedron_3_test.cpp") +endif(EIGEN3_FOUND) create_single_source_cgal_program("connected_component_polyhedron.cpp") create_single_source_cgal_program("connected_component_surface_mesh.cpp") @@ -86,16 +88,4 @@ if (EIGEN3_FOUND) create_single_source_cgal_program("triangulate_faces_test.cpp") create_single_source_cgal_program("test_pmp_remove_border_edge.cpp") create_single_source_cgal_program("test_pmp_distance.cpp") - -if(NOT (${EIGEN3_VERSION} VERSION_LESS 3.2.0)) - create_single_source_cgal_program("fairing_test.cpp") - create_single_source_cgal_program("triangulate_hole_Polyhedron_3_no_delaunay_test.cpp" ) - create_single_source_cgal_program("triangulate_hole_Polyhedron_3_test.cpp") create_single_source_cgal_program("triangulate_hole_polyline_test.cpp") -else() - message(STATUS "NOTICE: Some tests require Eigen 3.2 (or greater) and will not be compiled.") -endif() - -else(EIGEN3_FOUND) - message(STATUS "NOTICE: Some examples require Eigen 3.1 (or greater) and will not be compiled.") -endif(EIGEN3_FOUND) From 3710a60b9c53c10e72baec22816a2ab28f48d2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 19 Oct 2016 18:19:54 +0200 Subject: [PATCH 28/72] doc clean up --- .../poisson_reconstruction_example.cpp | 12 +- .../Polygon_mesh_processing.txt | 16 +-- .../hausdorff_distance_remeshing_example.cpp | 37 +++--- .../CGAL/Polygon_mesh_processing/distance.h | 108 +++++++++--------- .../Plugins/PCA/Edit_box_plugin.cpp | 1 + 5 files changed, 85 insertions(+), 89 deletions(-) diff --git a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp index 97c507b3fd7..36a37862b1c 100644 --- a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp +++ b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp @@ -107,12 +107,12 @@ int main(void) out << output_mesh; - double dist = CGAL::Polygon_mesh_processing::max_distance_to_point_set( - output_mesh, - points, - 40000); - - std::cout<<"Max distance to point_set: "< - #include +#include #include #include #include - #if CGAL_LINKED_WITH_TBB #define TAG CGAL::Parallel_tag #else #define TAG CGAL::Sequential_tag #endif + typedef CGAL::Exact_predicates_inexact_constructions_kernel K; -typedef K::Point_3 P_3; +typedef K::Point_3 Point; typedef CGAL::Surface_mesh Mesh; -namespace PMP = CGAL::Polygon_mesh_processing; -int main(int, char**) +int main() { + Mesh tm1, tm2; + CGAL::make_tetrahedron(Point(.0,.0,.0), + Point(2,.0,.0), + Point(1,1,1), + Point(1,.0,2), + tm1); + tm2=tm1; + CGAL::Polygon_mesh_processing::isotropic_remeshing(tm2.faces(),.05, tm2); - Mesh m1, m2; - CGAL::make_tetrahedron(P_3(.0,.0,.0), - P_3(2,.0,.0), - P_3(1,1,1), - P_3(1,.0,2), - m1); - m2=m1; - PMP::isotropic_remeshing(m2.faces(),.05, m2); - std::cerr<< " Approximated Hausdorff distance : " - << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance - (m1,m2,4000) - <(m1, m2, 4000) + << std::endl; } - - diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 509df3f7e88..65e1bd635d9 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -174,40 +174,43 @@ enum Sampling_method{ MONTE_CARLO }; /** \ingroup PMP_distance_grp - * fills `sampled_points` with points taken on `m` in a way depending on `method` + * generates points taken on `tm` in a way depending on `method` and put then in `out`. * @tparam TriangleMesh a model of the concept `FaceListGraph` - * @param m the triangle mesh that will be sampled - * @param parameter depends on `method` : + * @tparam OutputIterator a model of `OutputIterator` that accepts points + * of the same type as the value type of the vertex point map of `tm` + * @param tm the triangle mesh that will be sampled + * @param parameter depends on `method`: * `RANDOM_UNIFORM` and `MONTE_CARLO`: the number of points per squared area unit - * `GRID` : The distance between two consecutive points in the grid + * `GRID`: The distance between two consecutive points in the grid * - * @param np an optionnal sequence of \ref namedparameters for `m` among the ones listed below - * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` and in all places where vertex_point_map is used + * @param out output iterator in which sampled points are put + * @param np an optionnal sequence of \ref namedparameters for `tm` among the ones listed below * * \cgalNamedParamsBegin - * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `m` - * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` - * and in all places where vertex_point_map is used.\cgalParamEnd + * \cgalParamBegin{vertex_point_map} + * the property map with the points associated to the vertices of `tm`. If this parameter is omitted, + * an internal property map for `CGAL::vertex_point_t` should be available for `TriangleMesh` \cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd * \cgalNamedParamsEnd * * @param method defines the method of sampling * - * @tparam Sampling_method defines the sampling method. Possible values are : - *- `RANDOM_UNIFORM` : points are generated in a random and uniform way, depending on the area of each triangle. + * @tparam Sampling_method defines the sampling method. Possible values are: + *- `RANDOM_UNIFORM`: points are generated in a random and uniform way, depending on the area of each triangle. * - *- `GRID` : points are generated on a grid, with a minimum of one point per triangle. + *- `GRID`: points are generated on a grid in each triangle, with a minimum of one point per triangle. * - *- `MONTE_CARLO` : points are generated randomly in each triangle. + *- `MONTE_CARLO`: points are generated randomly in each triangle. * The number of points per triangle is proportional to the triangle area with a minimum of 1. * */ template -OutputIterator sample_triangle_mesh(const TriangleMesh& m, - double parameter, - OutputIterator out, - NamedParameters np, - Sampling_method method = RANDOM_UNIFORM) +OutputIterator +sample_triangle_mesh(const TriangleMesh& tm, + double parameter, + OutputIterator out, + NamedParameters np, + Sampling_method method = RANDOM_UNIFORM) { typedef typename GetGeomTraits::type Geom_traits; @@ -216,31 +219,30 @@ OutputIterator sample_triangle_mesh(const TriangleMesh& m, NamedParameters>::const_type Vpm; Vpm pmap = choose_param(get_param(np, vertex_point), - get_const_property_map(vertex_point, m)); + get_const_property_map(vertex_point, tm)); switch(method) { case RANDOM_UNIFORM: { std::size_t nb_points = std::ceil( - parameter * area(m, parameters::geom_traits(Geom_traits()))); + parameter * area(tm, parameters::geom_traits(Geom_traits()))); Random_points_in_triangle_mesh_3 - g(m, pmap); + g(tm, pmap); CGAL::cpp11::copy_n(g, nb_points, out); return out; } case GRID: { std::vector triangles; - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(tm)) { //create the triangles and store them typename Geom_traits::Point_3 points[3]; - //typename TriangleMesh::Halfedge_around_face_circulator hc(halfedge(f,m), m); - typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,m)); + typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); for(int i=0; i<3; ++i) { - points[i] = get(pmap, target(hd, m)); - hd = next(hd, m); + points[i] = get(pmap, target(hd, tm)); + hd = next(hd, tm); } triangles.push_back(typename Geom_traits::Triangle_3(points[0], points[1], points[2])); } @@ -249,18 +251,18 @@ OutputIterator sample_triangle_mesh(const TriangleMesh& m, } case MONTE_CARLO: { - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(m)) + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(tm)) { std::size_t nb_points = (std::max)( - (int)std::ceil(parameter * face_area(f,m,parameters::geom_traits(Geom_traits()))), + (int)std::ceil(parameter * face_area(f,tm,parameters::geom_traits(Geom_traits()))), 1); //create the triangles and store them typename Geom_traits::Point_3 points[3]; - typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,m)); + typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); for(int i=0; i<3; ++i) { - points[i] = get(pmap, target(hd, m)); - hd = next(hd, m); + points[i] = get(pmap, target(hd, tm)); + hd = next(hd, tm); } Random_points_in_triangle_3 g(points[0], points[1], points[2]); CGAL::cpp11::copy_n(g, nb_points, out); @@ -271,13 +273,14 @@ OutputIterator sample_triangle_mesh(const TriangleMesh& m, } template -OutputIterator sample_triangle_mesh(const TriangleMesh& m, - double parameter, - OutputIterator out, - Sampling_method method = RANDOM_UNIFORM) +OutputIterator +sample_triangle_mesh(const TriangleMesh& tm, + double parameter, + OutputIterator out, + Sampling_method method = RANDOM_UNIFORM) { return sample_triangle_mesh( - m, + tm, parameter, out, parameters::all_default(), @@ -287,11 +290,11 @@ OutputIterator sample_triangle_mesh(const TriangleMesh& m, template double approximated_Hausdorff_distance( const PointRange& original_sample_points, - const TriangleMesh& m, + const TriangleMesh& tm, VertexPointMap vpm ) { - CGAL_assertion_code( bool is_triangle = is_triangle_mesh(m) ); + CGAL_assertion_code( bool is_triangle = is_triangle_mesh(tm) ); CGAL_assertion_msg (is_triangle, "Mesh is not triangulated. Distance computing impossible."); #ifdef CGAL_HAUSDORFF_DEBUG @@ -306,10 +309,10 @@ double approximated_Hausdorff_distance( typedef AABB_traits Traits; typedef AABB_tree< Traits > Tree; - Tree tree( faces(m).first, faces(m).second, m); + Tree tree( faces(tm).first, faces(tm).second, tm); tree.accelerate_distance_queries(); tree.build(); - typename Kernel::Point_3 hint = get(vpm, *vertices(m).first); + typename Kernel::Point_3 hint = get(vpm, *vertices(tm).first); #ifndef CGAL_LINKED_WITH_TBB CGAL_static_assertion_msg (!(boost::is_convertible::value), "Parallel_tag is enabled but TBB is unavailable."); @@ -387,9 +390,9 @@ double approximated_Hausdorff_distance( * * * \cgalNamedParamsBegin - * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm1` or `tm2` - * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` - * and in all places where vertex_point_map is used.\cgalParamEnd + * \cgalParamBegin{vertex_point_map} + * the property map with the points associated to the vertices of `tm1` (`tm2`). If this parameter is omitted, + * an internal property map for `CGAL::vertex_point_t` should be available for `TriangleMesh` \cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd * \cgalNamedParamsEnd */ @@ -422,7 +425,7 @@ double approximated_Hausdorff_distance( const TriangleMesh& tm1, * \ingroup PMP_distance_grp * computes the approximated symmetric Hausdorff distance between `tm1` and `tm2` * It returns the maximum of `approximated_Hausdorff_distance(tm1,tm2)` and - * `approximated_Hausdorff_distance(tm1,tm2)` + * `approximated_Hausdorff_distance(tm2,tm1)` * * \copydetails CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance() */ @@ -452,14 +455,12 @@ double approximated_symmetric_Hausdorff_distance( * @param points the point_set of interest * @param tm the triangle mesh to compute the distance to * @param np an optionnal sequence of \ref namedparameters for `tm` among the ones listed below - * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` and in all places where vertex_point_map is used * * \cgalNamedParamsBegin - * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm` - * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` - * and in all places where vertex_point_map is used.\cgalParamEnd + * \cgalParamBegin{vertex_point_map} + * the property map with the points associated to the vertices of `tm`. If this parameter is omitted, + * an internal property map for `CGAL::vertex_point_t` should be available for `TriangleMesh` \cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd - * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` and in all places where vertex_point_map is used. * \cgalNamedParamsEnd */ template< class Concurrency_tag, @@ -489,13 +490,12 @@ double max_distance_to_triangle_mesh(const PointRange& points, * @param points the point_set of interest. * @param precision the precision of the approximated value you want. * @param np a sequence of \ref namedparameters for `tm` among the ones listed below - * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` and in all places where vertex_point_map is used * * \cgalNamedParamsBegin - * the property map with the points associated to the vertices of `tm` - * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` - * and in all places where vertex_point_map is used.\cgalParamEnd - * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd + * \cgalParamBegin{vertex_point_map} + * the property map with the points associated to the vertices of `tm`. If this parameter is omitted, + * an internal property map for `CGAL::vertex_point_t` should be available for `TriangleMesh` \cgalParamEnd + * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`. \cgalParamEnd * \cgalNamedParamsEnd */ template< class TriangleMesh, diff --git a/Polyhedron/demo/Polyhedron/Plugins/PCA/Edit_box_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PCA/Edit_box_plugin.cpp index 782bc9a50b6..0b7eb1690a1 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PCA/Edit_box_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PCA/Edit_box_plugin.cpp @@ -109,6 +109,7 @@ void Edit_box_plugin::exportToPoly() break; } } + Polyhedron::Point_3 points[8]; for(int i=0; i<8; ++i) { From 9140ab3f3192c6c08eeaa3dcc7a171cc4723cb10 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Wed, 26 Oct 2016 14:37:14 +0200 Subject: [PATCH 29/72] Replace approximated by approximate in the function names. --- .../hausdorff_distance_remeshing_example.cpp | 2 +- .../CGAL/Polygon_mesh_processing/distance.h | 48 +++++++++---------- .../test_pmp_distance.cpp | 6 +-- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp index c485c9cea2f..b7d575e5842 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp @@ -27,7 +27,7 @@ int main() CGAL::Polygon_mesh_processing::isotropic_remeshing(tm2.faces(),.05, tm2); std::cout << "Approximated Hausdorff distance: " - << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance + << CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance (m1, m2, 4000) << std::endl; } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 65e1bd635d9..91dbb247513 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -288,7 +288,7 @@ sample_triangle_mesh(const TriangleMesh& tm, } template -double approximated_Hausdorff_distance( +double approximate_Hausdorff_distance( const PointRange& original_sample_points, const TriangleMesh& tm, VertexPointMap vpm @@ -343,7 +343,7 @@ double approximated_Hausdorff_distance( template -double approximated_Hausdorff_distance( +double approximate_Hausdorff_distance( const TriangleMesh& m1, const TriangleMesh& m2, double precision, @@ -359,13 +359,13 @@ double approximated_Hausdorff_distance( std::back_inserter(sample_points), np, method ); - return approximated_Hausdorff_distance(sample_points, m2, vpm); + return approximate_Hausdorff_distance(sample_points, m2, vpm); } // documented functions /** * \ingroup PMP_distance_grp - * computes the approximated Hausdorff distance of `tm1` from `tm2` by + * computes the approximate Hausdorff distance of `tm1` from `tm2` by * generating a uniform random point sampling on `tm1`, and by then * returning the distance of the furthest point from `tm2`. * @@ -400,7 +400,7 @@ template< class Concurrency_tag, class TriangleMesh, class NamedParameters1, class NamedParameters2> -double approximated_Hausdorff_distance( const TriangleMesh& tm1, +double approximate_Hausdorff_distance( const TriangleMesh& tm1, const TriangleMesh& tm2, double precision, const NamedParameters1& np1, @@ -409,7 +409,7 @@ double approximated_Hausdorff_distance( const TriangleMesh& tm1, typedef typename GetGeomTraits::type Geom_traits; - return approximated_Hausdorff_distance( + return approximate_Hausdorff_distance( tm1, tm2, precision, choose_param(get_param(np1, vertex_point), @@ -423,17 +423,17 @@ double approximated_Hausdorff_distance( const TriangleMesh& tm1, /** * \ingroup PMP_distance_grp - * computes the approximated symmetric Hausdorff distance between `tm1` and `tm2` - * It returns the maximum of `approximated_Hausdorff_distance(tm1,tm2)` and - * `approximated_Hausdorff_distance(tm2,tm1)` + * computes the approximate symmetric Hausdorff distance between `tm1` and `tm2` + * It returns the maximum of `approximate_Hausdorff_distance(tm1,tm2)` and + * `approximate_Hausdorff_distance(tm2,tm1)` * - * \copydetails CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance() + * \copydetails CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance() */ template< class Concurrency_tag, class TriangleMesh, class NamedParameters1, class NamedParameters2> -double approximated_symmetric_Hausdorff_distance( +double approximate_symmetric_Hausdorff_distance( const TriangleMesh& tm1, const TriangleMesh& tm2, double precision, @@ -441,14 +441,14 @@ double approximated_symmetric_Hausdorff_distance( const NamedParameters2& np2) { return (std::max)( - approximated_Hausdorff_distance(tm1,tm2,precision,np1,np2), - approximated_Hausdorff_distance(tm2,tm1,precision,np2,np1) + approximate_Hausdorff_distance(tm1,tm2,precision,np1,np2), + approximate_Hausdorff_distance(tm2,tm1,precision,np2,np1) ); } /** * \ingroup PMP_distance_grp - * computes the approximated Hausdorff distance between `points` and `tm` + * computes the approximate Hausdorff distance between `points` and `tm` * @tparam PointRange a Range of `Point_3` * @tparam `TriangleMesh` a model of the concept `FaceListGraph` * @tparam NamedParameters a sequence of \ref namedparameters for `tm` @@ -474,14 +474,14 @@ double max_distance_to_triangle_mesh(const PointRange& points, typedef typename GetGeomTraits::type Geom_traits; - return approximated_Hausdorff_distance + return approximate_Hausdorff_distance (points,tm,choose_param(get_param(np, vertex_point), get_const_property_map(vertex_point, tm))); } /*! *\ingroup PMP_distance_grp - * Computes the approximated Hausdorff distance between `tm` and `points` + * Computes the approximate Hausdorff distance between `tm` and `points` * * @tparam PointRange a Range of `Point_3` * @tparam `TriangleMesh` a model of the concept `FaceListGraph` @@ -555,22 +555,22 @@ double max_distance_to_point_set(const TriangleMesh& tm, template< class Concurrency_tag, class TriangleMesh, class NamedParameters> -double approximated_Hausdorff_distance( const TriangleMesh& tm1, +double approximate_Hausdorff_distance( const TriangleMesh& tm1, const TriangleMesh& tm2, double precision, const NamedParameters& np) { - return approximated_Hausdorff_distance( + return approximate_Hausdorff_distance( tm1, tm2, precision, np, parameters::all_default()); } template< class Concurrency_tag, class TriangleMesh> -double approximated_Hausdorff_distance( const TriangleMesh& tm1, +double approximate_Hausdorff_distance( const TriangleMesh& tm1, const TriangleMesh& tm2, double precision) { - return approximated_Hausdorff_distance( + return approximate_Hausdorff_distance( tm1, tm2, precision, parameters::all_default(), parameters::all_default()); @@ -580,22 +580,22 @@ double approximated_Hausdorff_distance( const TriangleMesh& tm1, template< class Concurrency_tag, class TriangleMesh, class NamedParameters> -double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1, +double approximate_symmetric_Hausdorff_distance(const TriangleMesh& tm1, const TriangleMesh& tm2, double precision, const NamedParameters& np) { - return approximated_symmetric_Hausdorff_distance( + return approximate_symmetric_Hausdorff_distance( tm1, tm2, precision, np, parameters::all_default()); } template< class Concurrency_tag, class TriangleMesh> -double approximated_symmetric_Hausdorff_distance(const TriangleMesh& tm1, +double approximate_symmetric_Hausdorff_distance(const TriangleMesh& tm1, const TriangleMesh& tm2, double precision) { - return approximated_symmetric_Hausdorff_distance( + return approximate_symmetric_Hausdorff_distance( tm1, tm2, precision, parameters::all_default(), parameters::all_default()); diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp index 99f9b3b9acf..72fe5192608 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp @@ -20,7 +20,7 @@ bool general_tests(const Mesh& m1, { std::cout << "Symetric distance between meshes (sequential) " - << CGAL::Polygon_mesh_processing::approximated_symmetric_Hausdorff_distance(m1,m2,4000) + << CGAL::Polygon_mesh_processing::approximate_symmetric_Hausdorff_distance(m1,m2,4000) << "\n"; std::vector points; @@ -65,7 +65,7 @@ int main(int argc, char** argv) CGAL::Timer time; time.start(); std::cout << "Distance between meshes (parallel) " - << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(m1,m2,40000) + << CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance(m1,m2,40000) << "\n"; time.stop(); std::cout << "done in " << time.time() << "s.\n"; @@ -73,7 +73,7 @@ int main(int argc, char** argv) time.reset(); time.start(); std::cout << "Distance between meshes (sequential) " - << CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance(m1,m2,40000) + << CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance(m1,m2,40000) << "\n"; time.stop(); std::cout << "done in " << time.time() << "s.\n"; From 06c8a7a72b550317b48752d2d622495bbb93ba46 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Thu, 27 Oct 2016 12:06:36 +0200 Subject: [PATCH 30/72] Fix the return type of Apply_approx_sqrt. --- .../include/CGAL/internal/Generic_random_point_generator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Generator/include/CGAL/internal/Generic_random_point_generator.h b/Generator/include/CGAL/internal/Generic_random_point_generator.h index 619c887c5db..0a5418cda68 100644 --- a/Generator/include/CGAL/internal/Generic_random_point_generator.h +++ b/Generator/include/CGAL/internal/Generic_random_point_generator.h @@ -115,7 +115,7 @@ template< class Functor > struct Apply_approx_sqrt: public Functor { template - double operator()(const T& t) const + typename cpp11::result_of::type operator()(const T& t) const { return approximate_sqrt( static_cast(*this)(t) ); } From d5bdf342d491b3808a4b36937d3c2e8924208e49 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Thu, 27 Oct 2016 12:07:08 +0200 Subject: [PATCH 31/72] Use functor instead of free function volume() --- .../include/CGAL/Polygon_mesh_processing/measure.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/measure.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/measure.h index 2ff93f14301..b314d307e84 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/measure.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/measure.h @@ -432,7 +432,9 @@ namespace Polygon_mesh_processing { typename GetGeomTraits::type::FT volume = 0.; BOOST_FOREACH(face_descriptor f, faces(tmesh)) { - volume += CGAL::volume(origin, + typename CGAL::Kernel_traits::type>::Kernel::Volume_3 volume; + volume += volume(origin, get(vpm, target(halfedge(f, tmesh), tmesh)), get(vpm, target(next(halfedge(f, tmesh), tmesh), tmesh)), get(vpm, target(prev(halfedge(f, tmesh), tmesh), tmesh))); @@ -446,6 +448,7 @@ namespace Polygon_mesh_processing { CGAL::vertex_point_t>::type>::Kernel::FT volume(const TriangleMesh& tmesh) { + return volume(tmesh, CGAL::Polygon_mesh_processing::parameters::all_default()); } From 2e804f6e48c69115cae0fda9904239a2d19f67aa Mon Sep 17 00:00:00 2001 From: Jane Tournois Date: Thu, 27 Oct 2016 15:48:18 +0200 Subject: [PATCH 32/72] fix the doc of `sample_triangle_mesh` fix a few typos, and fix the layout of bullet lists --- .../CGAL/Polygon_mesh_processing/distance.h | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 91dbb247513..0f45dbd82ed 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -174,17 +174,25 @@ enum Sampling_method{ MONTE_CARLO }; /** \ingroup PMP_distance_grp - * generates points taken on `tm` in a way depending on `method` and put then in `out`. + * generates points taken on `tm` in a way depending on `method` and + * outputs them to `out`. * @tparam TriangleMesh a model of the concept `FaceListGraph` - * @tparam OutputIterator a model of `OutputIterator` that accepts points - * of the same type as the value type of the vertex point map of `tm` + * @tparam OutputIterator a model of `OutputIterator` + * holding objects of the same point type as + * the value type of the internal vertex point map of `tm` + * @tparam Sampling_method defines the sampling method. Possible values are: + - `RANDOM_UNIFORM`: points are generated in a random and uniform way, depending on the area of each triangle. + - `GRID`: points are generated on a grid in each triangle, with a minimum of one point per triangle. + - `MONTE_CARLO`: points are generated randomly in each triangle. The number of points per triangle is proportional to the triangle area with a minimum of 1. + * * @param tm the triangle mesh that will be sampled * @param parameter depends on `method`: - * `RANDOM_UNIFORM` and `MONTE_CARLO`: the number of points per squared area unit - * `GRID`: The distance between two consecutive points in the grid + - `RANDOM_UNIFORM` and `MONTE_CARLO`: the number of points per squared area unit + - `GRID`: the distance between two consecutive points in the grid * - * @param out output iterator in which sampled points are put - * @param np an optionnal sequence of \ref namedparameters for `tm` among the ones listed below + * @param out output iterator to be filled with sampled points + * @param np an optional sequence of \ref namedparameters among the ones listed below + * @param method defines the method of sampling * * \cgalNamedParamsBegin * \cgalParamBegin{vertex_point_map} @@ -192,17 +200,6 @@ enum Sampling_method{ * an internal property map for `CGAL::vertex_point_t` should be available for `TriangleMesh` \cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd * \cgalNamedParamsEnd - * - * @param method defines the method of sampling - * - * @tparam Sampling_method defines the sampling method. Possible values are: - *- `RANDOM_UNIFORM`: points are generated in a random and uniform way, depending on the area of each triangle. - * - *- `GRID`: points are generated on a grid in each triangle, with a minimum of one point per triangle. - * - *- `MONTE_CARLO`: points are generated randomly in each triangle. - * The number of points per triangle is proportional to the triangle area with a minimum of 1. - * */ template OutputIterator From e9f6885d3efa4cf25d91cfc67a78654f38d36f79 Mon Sep 17 00:00:00 2001 From: Jane Tournois Date: Thu, 27 Oct 2016 17:06:45 +0200 Subject: [PATCH 33/72] minor doc fixes --- .../CGAL/Polygon_mesh_processing/distance.h | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 0f45dbd82ed..37e4a179ec5 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -375,7 +375,7 @@ double approximate_Hausdorff_distance( * @tparam Concurrency_tag enables sequential versus parallel algorithm. * Possible values are `Sequential_tag` * and `Parallel_tag`. - * @tparam `TriangleMesh` a model of the concept `FaceListGraph` + * @tparam TriangleMesh a model of the concept `FaceListGraph` * @tparam NamedParameters1 a sequence of \ref namedparameters for `tm1` * @tparam NamedParameters2 a sequence of \ref namedparameters for `tm2` * @@ -420,9 +420,9 @@ double approximate_Hausdorff_distance( const TriangleMesh& tm1, /** * \ingroup PMP_distance_grp - * computes the approximate symmetric Hausdorff distance between `tm1` and `tm2` + * computes the approximate symmetric Hausdorff distance between `tm1` and `tm2`. * It returns the maximum of `approximate_Hausdorff_distance(tm1,tm2)` and - * `approximate_Hausdorff_distance(tm2,tm1)` + * `approximate_Hausdorff_distance(tm2,tm1)`. * * \copydetails CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance() */ @@ -446,17 +446,18 @@ double approximate_symmetric_Hausdorff_distance( /** * \ingroup PMP_distance_grp * computes the approximate Hausdorff distance between `points` and `tm` - * @tparam PointRange a Range of `Point_3` - * @tparam `TriangleMesh` a model of the concept `FaceListGraph` - * @tparam NamedParameters a sequence of \ref namedparameters for `tm` + * @tparam PointRange a range of `Point_3`, model of `Range`. + * @tparam TriangleMesh a model of the concept `FaceListGraph` + * @tparam NamedParameters a sequence of \ref namedparameters * @param points the point_set of interest * @param tm the triangle mesh to compute the distance to - * @param np an optionnal sequence of \ref namedparameters for `tm` among the ones listed below + * @param np an optional sequence of \ref namedparameters among the ones listed below * * \cgalNamedParamsBegin * \cgalParamBegin{vertex_point_map} * the property map with the points associated to the vertices of `tm`. If this parameter is omitted, - * an internal property map for `CGAL::vertex_point_t` should be available for `TriangleMesh` \cgalParamEnd + * an internal property map for `CGAL::vertex_point_t` should be available for the + vertices of `tm` \cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd * \cgalNamedParamsEnd */ @@ -480,18 +481,19 @@ double max_distance_to_triangle_mesh(const PointRange& points, *\ingroup PMP_distance_grp * Computes the approximate Hausdorff distance between `tm` and `points` * - * @tparam PointRange a Range of `Point_3` - * @tparam `TriangleMesh` a model of the concept `FaceListGraph` - * @tparam NamedParameters an optionnal sequence of \ref namedparameters for `tm` + * @tparam PointRange a range of `Point_3`, model of `Range`. + * @tparam TriangleMesh a model of the concept `FaceListGraph` + * @tparam NamedParameters a sequence of \ref namedparameters * @param tm the triangle mesh to compute the distance to * @param points the point_set of interest. * @param precision the precision of the approximated value you want. - * @param np a sequence of \ref namedparameters for `tm` among the ones listed below + * @param np an optional sequence of \ref namedparameters among the ones listed below * * \cgalNamedParamsBegin * \cgalParamBegin{vertex_point_map} * the property map with the points associated to the vertices of `tm`. If this parameter is omitted, - * an internal property map for `CGAL::vertex_point_t` should be available for `TriangleMesh` \cgalParamEnd + * an internal property map for `CGAL::vertex_point_t` should be available for the + vertices of `tm` \cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`. \cgalParamEnd * \cgalNamedParamsEnd */ From 7ec512e359d750518a09f6cdab28d018d729b04f Mon Sep 17 00:00:00 2001 From: Jane Tournois Date: Thu, 27 Oct 2016 17:26:03 +0200 Subject: [PATCH 34/72] update approximated -> approximate --- .../doc/Polygon_mesh_processing/PackageDescription.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index b6acdad7791..c70fbbd5147 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -40,7 +40,7 @@ /// Functions to orient polygon soups and to stitch geometrically identical boundaries. /// \ingroup PkgPolygonMeshProcessing -/// \defgroup PMP_distance_grp Approximated Hausdorff Distance +/// \defgroup PMP_distance_grp Approximate Hausdorff Distance /// Functions to compute the distance between meshes, between a mesh and a point set and between a point set and a mesh. /// \ingroup PkgPolygonMeshProcessing From 19a7a84983b601265df1f44fcd8256cdd675a235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 2 Nov 2016 10:13:36 +0100 Subject: [PATCH 35/72] correctly use the Creator --- .../doc/Generator/CGAL/point_generators_2.h | 8 ++- .../doc/Generator/CGAL/point_generators_3.h | 24 +++++++-- Generator/include/CGAL/point_generators_2.h | 20 +++++--- Generator/include/CGAL/point_generators_3.h | 50 ++++++++++++------- 4 files changed, 71 insertions(+), 31 deletions(-) diff --git a/Generator/doc/Generator/CGAL/point_generators_2.h b/Generator/doc/Generator/CGAL/point_generators_2.h index d73c1146cc9..9d5c1da1c07 100644 --- a/Generator/doc/Generator/CGAL/point_generators_2.h +++ b/Generator/doc/Generator/CGAL/point_generators_2.h @@ -377,7 +377,9 @@ typedef const Point_2& reference; \sa `std::random_shuffle` */ - template< typename Point_2, typename Triangulation > + template< typename Point_2, + typename Triangulation, + typename Creator = Creator_uniform_2::Kernel::RT,Point_2> > class Random_points_in_triangle_mesh_2 { public: @@ -446,7 +448,9 @@ get_default_random() ); \sa `std::random_shuffle` */ - template< typename Point_2 > + template< typename Point_2, + typename Triangle_2 = typename Kernel_traits::Kernel::Triangle_2, + typename Creator = Creator_uniform_2::Kernel::RT,Point_2> > class Random_points_in_triangles_2 { public: diff --git a/Generator/doc/Generator/CGAL/point_generators_3.h b/Generator/doc/Generator/CGAL/point_generators_3.h index 5fd59b5d2fa..b709e4da72d 100644 --- a/Generator/doc/Generator/CGAL/point_generators_3.h +++ b/Generator/doc/Generator/CGAL/point_generators_3.h @@ -358,7 +358,10 @@ The triangle range must be valid and unchanged while the iterator is used. \sa `std::random_shuffle` */ -template< typename Point_3> +template< typename Point_3, + typename Triangle_3=typename Kernel_traits::Kernel::Triangle_3, + typename Creator = Creator_uniform_3< typename Kernel_traits< Point_3 >::Kernel::RT, + Point_3 > > class Random_points_in_triangles_3 { public: @@ -429,8 +432,12 @@ The triangle mesh must be valid and unchanged while the iterator is used. \sa `std::random_shuffle` */ -template < class TriangleMesh, class VertexPointMap = typename boost::property_map::type> > +template < class TriangleMesh, + class VertexPointMap = typename boost::property_map::type>, + class Creator = Creator_uniform_3< + typename Kernel_traits< typename boost::property_traits::value_type >::Kernel::RT, + typename boost::property_traits::value_type > > class Random_points_in_triangle_mesh_3 { public: @@ -508,7 +515,11 @@ C3T3 is a model of `Mesh_complex_3_in_triangulation_3` \sa `std::random_shuffle` */ -template +template ::Kernel::RT, + typename C3t3::Point > +> class Random_points_in_tetrahedral_mesh_boundary_3 { public: @@ -581,7 +592,10 @@ C3T3 is a model of `Mesh_complex_3_in_triangulation_3` \sa `std::random_shuffle` */ -template +template ::Kernel::RT, + typename C3t3::Point > > class Random_points_in_tetrahedral_mesh_3 { public: diff --git a/Generator/include/CGAL/point_generators_2.h b/Generator/include/CGAL/point_generators_2.h index a9ffb7b522f..25b94a2e0d7 100644 --- a/Generator/include/CGAL/point_generators_2.h +++ b/Generator/include/CGAL/point_generators_2.h @@ -505,7 +505,7 @@ class Random_points_in_triangle_2 : public Random_generator_base

    { void generate_point(); public: typedef P result_type; - typedef Random_points_in_triangle_2

    This; + typedef Random_points_in_triangle_2 This; typedef typename Kernel_traits

    ::Kernel::Triangle_2 Triangle_2; Random_points_in_triangle_2() {} Random_points_in_triangle_2( const This& x,Random& rnd = get_default_random()) @@ -561,7 +561,11 @@ public: } }; }//end namespace internal -template +template ::Kernel::RT,P> +> class Random_points_in_triangle_mesh_2 : public Generic_random_point_generator< typename T::Face_handle , internal::Triangle_from_face_2, @@ -569,11 +573,11 @@ class Random_points_in_triangle_mesh_2 : public Generic_random_point_generator< public: typedef Generic_random_point_generator, - Random_points_in_triangle_2

    , + Random_points_in_triangle_2, P> Base; typedef typename T::Face_handle Id; typedef P result_type; - typedef Random_points_in_triangle_mesh_2 This; + typedef Random_points_in_triangle_mesh_2 This; Random_points_in_triangle_mesh_2( const T& triangulation, Random& rnd = get_default_random()) @@ -621,7 +625,9 @@ struct Address_of { }//namesapce internal template ::Kernel::Triangle_2> + class Triangle_2=typename Kernel_traits::Kernel::Triangle_2, + class Creator = + Creator_uniform_2::Kernel::RT,Point_2> > struct Random_points_in_triangles_2 : public Generic_random_point_generator, @@ -630,11 +636,11 @@ struct Random_points_in_triangles_2 { typedef Generic_random_point_generator, - Random_points_in_triangle_2, + Random_points_in_triangle_2, Point_2> Base; typedef const Triangle_2* Id; typedef Point_2 result_type; - typedef Random_points_in_triangles_2 This; + typedef Random_points_in_triangles_2 This; template Random_points_in_triangles_2( const TriangleRange& triangles, Random& rnd = get_default_random()) diff --git a/Generator/include/CGAL/point_generators_3.h b/Generator/include/CGAL/point_generators_3.h index 395737b418c..9568e2a4709 100644 --- a/Generator/include/CGAL/point_generators_3.h +++ b/Generator/include/CGAL/point_generators_3.h @@ -205,7 +205,7 @@ class Random_points_in_triangle_3 : public Random_generator_base

    { void generate_point(); public: typedef P result_type; - typedef Random_points_in_triangle_3

    This; + typedef Random_points_in_triangle_3 This; typedef typename Kernel_traits

    ::Kernel::Triangle_3 Triangle_3; Random_points_in_triangle_3() {} Random_points_in_triangle_3( const This& x,Random& rnd = get_default_random()) @@ -257,7 +257,7 @@ class Random_points_in_tetrahedron_3 : public Random_generator_base

    { void generate_point(); public: typedef P result_type; - typedef Random_points_in_tetrahedron_3

    This; + typedef Random_points_in_tetrahedron_3 This; typedef typename Kernel_traits

    ::Kernel::Tetrahedron_3 Tetrahedron_3; Random_points_in_tetrahedron_3() {} Random_points_in_tetrahedron_3( const This& x,Random& rnd = get_default_random()) @@ -306,14 +306,19 @@ void Random_points_in_tetrahedron_3::generate_point() { -template ::const_type> +template ::const_type, + class Creator = Creator_uniform_3< + typename Kernel_traits< typename boost::property_traits::value_type >::Kernel::RT, + typename boost::property_traits::value_type > +> struct Random_points_in_triangle_mesh_3 : public Generic_random_point_generator< typename boost::graph_traits ::face_descriptor , CGAL::Property_map_to_unary_function >, - Random_points_in_triangle_3::value_type>, + Random_points_in_triangle_3::value_type, Creator>, typename boost::property_traits::value_type> { typedef typename boost::property_traits::value_type P; @@ -321,15 +326,14 @@ struct Random_points_in_triangle_mesh_3 typename boost::graph_traits ::face_descriptor , CGAL::Property_map_to_unary_function >, - Random_points_in_triangle_3

    , P> Base; + Random_points_in_triangle_3 , P> Base; typedef typename CGAL::Triangle_from_face_descriptor_map< TriangleMesh,VertexPointMap> Pmap; typedef typename CGAL::Triangle_from_face_descriptor_map< TriangleMesh,VertexPointMap> Object_from_id_map; - typedef Random_points_in_triangle_3

    Generator_on_object; typedef typename boost::graph_traits::face_descriptor Id; typedef P result_type; - typedef Random_points_in_triangle_mesh_3< TriangleMesh, VertexPointMap> This; + typedef Random_points_in_triangle_mesh_3< TriangleMesh, VertexPointMap, Creator> This; Random_points_in_triangle_mesh_3( const TriangleMesh& mesh,Random& rnd = get_default_random()) @@ -408,7 +412,11 @@ public: }; }//end namespace internal -template +template ::Kernel::RT, + typename C3t3::Point > +> struct Random_points_in_tetrahedral_mesh_boundary_3 : public Generic_random_point_generator< std::pair, @@ -419,11 +427,11 @@ struct Random_points_in_tetrahedral_mesh_boundary_3 typedef Generic_random_point_generator< std::pair, internal::Triangle_from_face_C3t3, - Random_points_in_triangle_3, + Random_points_in_triangle_3, typename C3t3::Point> Base; typedef std::pair Id; typedef typename C3t3::Point result_type; - typedef Random_points_in_tetrahedral_mesh_boundary_3 This; + typedef Random_points_in_tetrahedral_mesh_boundary_3 This; Random_points_in_tetrahedral_mesh_boundary_3( const C3t3& c3t3,Random& rnd = get_default_random()) @@ -445,7 +453,11 @@ struct Random_points_in_tetrahedral_mesh_boundary_3 } }; -template +template ::Kernel::RT, + typename C3t3::Point > +> struct Random_points_in_tetrahedral_mesh_3 : public Generic_random_point_generator< typename C3t3::Triangulation::Cell_handle, @@ -456,11 +468,11 @@ struct Random_points_in_tetrahedral_mesh_3 typedef Generic_random_point_generator< typename C3t3::Triangulation::Cell_handle, internal::Tetrahedron_from_cell_C3t3, - Random_points_in_tetrahedron_3, + Random_points_in_tetrahedron_3, typename C3t3::Point> Base; typedef typename C3t3::Triangulation::Cell_handle Id; typedef typename C3t3::Point result_type; - typedef Random_points_in_tetrahedral_mesh_3 This; + typedef Random_points_in_tetrahedral_mesh_3 This; Random_points_in_tetrahedral_mesh_3( const C3t3& c3t3,Random& rnd = get_default_random()) @@ -484,7 +496,11 @@ struct Random_points_in_tetrahedral_mesh_3 template ::Kernel::Triangle_3> + class Triangle_3=typename Kernel_traits::Kernel::Triangle_3, + class Creator = Creator_uniform_3< + typename Kernel_traits< Point_3 >::Kernel::RT, + Point_3 > + > struct Random_points_in_triangles_3 : public Generic_random_point_generator, @@ -493,11 +509,11 @@ struct Random_points_in_triangles_3 { typedef Generic_random_point_generator, - Random_points_in_triangle_3, + Random_points_in_triangle_3, Point_3> Base; typedef const Triangle_3* Id; typedef Point_3 result_type; - typedef Random_points_in_triangles_3 This; + typedef Random_points_in_triangles_3 This; template Random_points_in_triangles_3( const TriangleRange& triangles, Random& rnd = get_default_random()) From 06dd4a4522d62244e83850186aaccce130ad6207 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Fri, 28 Oct 2016 13:42:15 +0200 Subject: [PATCH 36/72] Add a geom-traits concept for distance functions Update the code and the doc accordingly --- .../doc/AABB_tree/Concepts/AABBGeomTraits.h | 3 +- .../poisson_reconstruction_example.cpp | 2 +- .../Concepts/PMPDistanceTraits.h | 73 +++++ ...onTraits.h => PMPSelfIntersectionTraits.h} | 2 +- .../PackageDescription.txt | 6 +- .../Polygon_mesh_processing.txt | 6 +- .../doc/Polygon_mesh_processing/dependencies | 1 + .../CGAL/Polygon_mesh_processing/distance.h | 125 ++++---- .../mesh_to_point_set_hausdorff_distance.h | 46 +-- .../self_intersections.h | 6 +- .../test_pmp_distance.cmd | 1 + .../test_pmp_distance.cpp | 278 ++++++++++++++++-- .../CGAL/Orthogonal_k_neighbor_search.h | 4 +- 13 files changed, 428 insertions(+), 125 deletions(-) create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h rename Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/{SelfIntersectionTraits.h => PMPSelfIntersectionTraits.h} (97%) create mode 100644 Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cmd diff --git a/AABB_tree/doc/AABB_tree/Concepts/AABBGeomTraits.h b/AABB_tree/doc/AABB_tree/Concepts/AABBGeomTraits.h index b4b49a1f13b..a579453e9f2 100644 --- a/AABB_tree/doc/AABB_tree/Concepts/AABBGeomTraits.h +++ b/AABB_tree/doc/AABB_tree/Concepts/AABBGeomTraits.h @@ -65,7 +65,6 @@ A functor object to construct the sphere centered at one point and passing throu typedef unspecified_type Construct_sphere_3; /*! -\todo This is not correct! that is not used! A functor object to compute the point on a geometric primitive which is closest from a query. Provides the operator: `Point_3 operator()(const Type_2& type_2, const Point_3& p);` where `Type_2` is any type among `Segment_3` and `Triangle_3`. The operator returns the point on `type_2` which is closest to `p`. */ @@ -76,7 +75,7 @@ A functor object to compare the distance of two points wrt a third one. Provides the operator: `CGAL::Comparision_result operator()(const Point_3& p1, const Point_3& p2, const Point_3& p3)`. The operator compare the distance between `p1 and `p2`, and between `p2` and `p3`. */ -typedef unspecified_type Compare_distance_3 +typedef unspecified_type Compare_distance_3; /*! A functor object to detect if a point lies inside a sphere or not. diff --git a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp index 36a37862b1c..a296f9f2a81 100644 --- a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp +++ b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp @@ -109,7 +109,7 @@ int main(void) // computes the approximation error of the reconstruction double max_dist = - CGAL::Polygon_mesh_processing::max_distance_to_point_set(output_mesh, + CGAL::Polygon_mesh_processing::approximate_max_distance_to_point_set(output_mesh, points, 4000); std::cout << "Max distance to point_set: " << max_dist << std::endl; diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h new file mode 100644 index 00000000000..a222fcbcb5b --- /dev/null +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h @@ -0,0 +1,73 @@ +/// \ingroup PkgPolygonMeshProcessingConcepts +/// \cgalConcept +/// +/// The concept `PMPDistanceTraits` is a refinement of the +/// concepts `AABBGeomTraits` and `SpatialSortingTraits_3`. In addition to the types required by +/// those concepts, it also requires types and functors needed by the functions `approximate_max_distance_to_point_set()`, +/// `sample_triangle_mesh()`, `approximate_Hausdorff_distance()` and `max_distance_to_triangle_mesh()` +/// +/// \cgalRefines `AABBGeomTraits` +/// \cgalRefines `SpatialSortingTraits_3` +/// \cgalHasModel Any 3D Kernel is a model of this concept. + +class PMPDistanceTraits{ +public: + /*! + * A number type model of `Field` and `RealEmbeddable` + */ + typedef unspecified_type FT; + /// + /*! 3D point type + * It must be default constructible, and can be constructed from 3 objects of type `FT`. + * `bool operator<(Point_3, Point_3)` to lexicographically compare two points must be available. + * Access to Cartesian coordinates must be possible using `Point_3::x()`, `Point_3::y(), Point_3::z()` and + * `FT operator[](int i)` with `0 <= i < 3`. + * + * There must be a specialization of `CGAL::Kernel_traits` such that + * `CGAL::Kernel_traits::Kernel` is the model implementing this concept. + */ + typedef unspecified_type Point_3; + + /// 3D vector type + typedef unspecified_type Vector_3; + + /*! + * 3D triangle type + * It must be constructible from three points and access to its vertices + * must be possible with operator `const Point_3& operator[](int i) const` + * with `0 <= i < 3`. + * In addition, it must provide `BBox_3 bbox() const` that returns a + * bounding box of the triangle. + */ + typedef unspecified_type Triangle_3; + + /// @name Functors + /// @{ + + /// Functor for computing squared area of a triangle. + /// It provides `FT operator()(const Point_3&, const Point_3&, const Point_3&) const` + /// and `FT operator()(const Triangle_3&) const` and has `FT` as result_type. + typedef unspecified_type Compute_squared_area_3; + /// Functor for constructing translated points. + /// It provides `Point_3 operator()(const Point_3 &, const Vector_3 &)` + typedef unspecified_type Construct_translated_point_3; + /// Functor for constructing vectors. + /// It provides `Vector_3 operator()(const Point_3 &, const Point_3 &)` + typedef unspecified_type Construct_vector_3; + /// Functor for constructing scaled vectors. + /// It provides `Vector_3 operator()(const Vector_3 &, const FT &)` + typedef unspecified_type Construct_scaled_vector_3; + /// Functor for constructing the circumcenter of three points. + /// It provides `Point_3 operator()(const Point_3&, const Point_3&, const Point_3&)` + typedef unspecified_type Construct_circumcenter_3; + /// @} + + /// @name Functions + /// @{ + Compute_squared_area_3 compute_squared_area_3_object(); + Construct_translated_point_3 construct_translated_point_3_object(); + Construct_vector_3 construct_vector_3_object(); + Construct_scaled_vector_3 construct_scaled_vector_3_object(); + Construct_circumcenter_3 construct_circumcenter_3_object(); + /// @} +}; diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/SelfIntersectionTraits.h b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPSelfIntersectionTraits.h similarity index 97% rename from Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/SelfIntersectionTraits.h rename to Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPSelfIntersectionTraits.h index e50813019ad..f71561f91d9 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/SelfIntersectionTraits.h +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPSelfIntersectionTraits.h @@ -2,7 +2,7 @@ /// \cgalConcept /// /// Geometric traits concept for the functions `CGAL::self_intersections()` and `CGAL::does_self_intersect()`. -class SelfIntersectionTraits{ +class PMPSelfIntersectionTraits{ public: /// @name Geometric Types /// @{ diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index c70fbbd5147..70f98fdfd62 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -133,9 +133,9 @@ and provides a list of the parameters that are used in this package. - \link measure_grp `CGAL::Polygon_mesh_processing::face_border_length()` \endlink ## Distance Functions ## -- `CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance()` -- `CGAL::Polygon_mesh_processing::approximated_symmetric_Hausdorff_distance()` -- `CGAL::Polygon_mesh_processing::max_distance_to_point_set()` +- `CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance()` +- `CGAL::Polygon_mesh_processing::approximate_symmetric_Hausdorff_distance()` +- `CGAL::Polygon_mesh_processing::approximate_max_distance_to_point_set()` - `CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh()` - `CGAL::Polygon_mesh_processing::sample_triangle_mesh()` 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 c5dbd3455ec..379db474f92 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 @@ -486,9 +486,9 @@ the propagation of a connected component index to cross it. This package provides methods to compute approximated Hausdorff distances. These distances can be computed between two meshes, a mesh and a point set, or a point set and a mesh. These computations are performed with : -- `CGAL::Polygon_mesh_processing::approximated_Hausdorff_distance()` -- `CGAL::Polygon_mesh_processing::approximated_symmetric_Hausdorff_distance()` -- `CGAL::Polygon_mesh_processing::max_distance_to_point_set()` +- `CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance()` +- `CGAL::Polygon_mesh_processing::approximate_symmetric_Hausdorff_distance()` +- `CGAL::Polygon_mesh_processing::approximate_max_distance_to_point_set()` - `CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh()` - `CGAL::Polygon_mesh_processing::sample_triangle_mesh()` diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/dependencies b/Polygon_mesh_processing/doc/Polygon_mesh_processing/dependencies index f7787717148..52428e7009d 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/dependencies +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/dependencies @@ -11,3 +11,4 @@ Surface_mesh Surface_mesh_deformation AABB_tree Triangulation_2 +Spatial_sorting diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 37e4a179ec5..089c785851c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -55,8 +55,9 @@ triangle_grid_sampling( const typename Kernel::Point_3& p0, double distance, OutputIterator out) { - const double d_p0p1 = CGAL::sqrt( CGAL::squared_distance(p0, p1) ); - const double d_p0p2 = CGAL::sqrt( CGAL::squared_distance(p0, p2) ); + typename Kernel::Compute_squared_distance_3 squared_distance; + const double d_p0p1 = to_double(approximate_sqrt( squared_distance(p0, p1) )); + const double d_p0p2 = to_double(approximate_sqrt( squared_distance(p0, p2) )); const double n = (std::max)(std::ceil( d_p0p1 / distance ), std::ceil( d_p0p2 / distance )); @@ -102,13 +103,14 @@ sample_triangles(const TriangleRange& triangles, double distance, OutputIterator const Point_3& p1=t[(i+1)%3]; if ( sampled_edges.insert(CGAL::make_sorted_pair(p0, p1)).second ) { - const double d_p0p1 = CGAL::sqrt( CGAL::squared_distance(p0, p1) ); + typename Kernel::Compute_squared_distance_3 squared_distance; + const double d_p0p1 = to_double(approximate_sqrt( squared_distance(p0, p1) )); const double nb_pts = std::ceil( d_p0p1 / distance ); - const Vector_3 step_vec = (p1 - p0) / nb_pts; + const Vector_3 step_vec = typename Kernel::Construct_scaled_vector_3()(typename Kernel::Construct_vector_3()(p1, p0), typename Kernel::FT(1)/typename Kernel::FT(nb_pts)); for (double i=1; i* d) : tree(tree) , sample_points(sample_points) - , distance(d) , initial_hint(p) + , distance(d) {} void @@ -154,7 +156,8 @@ struct Distance_computation{ for( std::size_t i = range.begin(); i != range.end(); ++i) { hint = tree.closest_point(sample_points[i], hint); - double d = CGAL::approximate_sqrt( squared_distance(hint,sample_points[i]) ); + typename Kernel_traits::Kernel::Compute_squared_distance_3 squared_distance; + double d = to_double(CGAL::approximate_sqrt( squared_distance(hint,sample_points[i]) )); if (d>hdist) hdist=d; } @@ -198,7 +201,7 @@ enum Sampling_method{ * \cgalParamBegin{vertex_point_map} * the property map with the points associated to the vertices of `tm`. If this parameter is omitted, * an internal property map for `CGAL::vertex_point_t` should be available for `TriangleMesh` \cgalParamEnd - * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd + * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `PMPDistanceTraits`\cgalParamEnd * \cgalNamedParamsEnd */ template @@ -217,56 +220,59 @@ sample_triangle_mesh(const TriangleMesh& tm, Vpm pmap = choose_param(get_param(np, vertex_point), get_const_property_map(vertex_point, tm)); + typedef Creator_uniform_3 Creator; switch(method) { - case RANDOM_UNIFORM: - { - std::size_t nb_points = std::ceil( - parameter * area(tm, parameters::geom_traits(Geom_traits()))); - Random_points_in_triangle_mesh_3 - g(tm, pmap); - CGAL::cpp11::copy_n(g, nb_points, out); - return out; - } - case GRID: - { - std::vector triangles; - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(tm)) + case RANDOM_UNIFORM: { - //create the triangles and store them - typename Geom_traits::Point_3 points[3]; - typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); - for(int i=0; i<3; ++i) - { - points[i] = get(pmap, target(hd, tm)); - hd = next(hd, tm); - } - triangles.push_back(typename Geom_traits::Triangle_3(points[0], points[1], points[2])); - } - sample_triangles(triangles, parameter, out); - return out; - } - case MONTE_CARLO: - { - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(tm)) - { - std::size_t nb_points = (std::max)( - (int)std::ceil(parameter * face_area(f,tm,parameters::geom_traits(Geom_traits()))), - 1); - //create the triangles and store them - typename Geom_traits::Point_3 points[3]; - typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); - for(int i=0; i<3; ++i) - { - points[i] = get(pmap, target(hd, tm)); - hd = next(hd, tm); - } - Random_points_in_triangle_3 g(points[0], points[1], points[2]); + std::size_t nb_points = std::ceil( to_double( + area(tm, parameters::geom_traits(Geom_traits()))*parameter) ); + Random_points_in_triangle_mesh_3 + g(tm, pmap); CGAL::cpp11::copy_n(g, nb_points, out); + return out; + } + case GRID: + { + std::vector triangles; + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(tm)) + { + //create the triangles and store them + typename Geom_traits::Point_3 points[3]; + typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); + for(int i=0; i<3; ++i) + { + points[i] = get(pmap, target(hd, tm)); + hd = next(hd, tm); + } + triangles.push_back(typename Geom_traits::Triangle_3(points[0], points[1], points[2])); + } + sample_triangles(triangles, parameter, out); + return out; + } + case MONTE_CARLO: + { + BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(tm)) + { + std::size_t nb_points( (std::max)( + std::ceil(to_double(face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter), + 1.) ); + //create the triangles and store them + typename Geom_traits::Point_3 points[3]; + typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); + for(int i=0; i<3; ++i) + { + points[i] = get(pmap, target(hd, tm)); + hd = next(hd, tm); + } + Random_points_in_triangle_3 + g(points[0], points[1], points[2]); + CGAL::cpp11::copy_n(g, nb_points, out); + } } - return out; - } } + return out; } template @@ -328,6 +334,7 @@ double approximate_Hausdorff_distance( BOOST_FOREACH(const typename Kernel::Point_3& pt, sample_points) { hint = tree.closest_point(pt, hint); + typename Kernel::Compute_squared_distance_3 squared_distance; typename Kernel::FT dist = squared_distance(hint,pt); double d = to_double(CGAL::approximate_sqrt(dist)); if(d>hdist) @@ -390,7 +397,7 @@ double approximate_Hausdorff_distance( * \cgalParamBegin{vertex_point_map} * the property map with the points associated to the vertices of `tm1` (`tm2`). If this parameter is omitted, * an internal property map for `CGAL::vertex_point_t` should be available for `TriangleMesh` \cgalParamEnd - * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd + * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `PMPDistanceTraits`\cgalParamEnd * \cgalNamedParamsEnd */ template< class Concurrency_tag, @@ -458,7 +465,7 @@ double approximate_symmetric_Hausdorff_distance( * the property map with the points associated to the vertices of `tm`. If this parameter is omitted, * an internal property map for `CGAL::vertex_point_t` should be available for the vertices of `tm` \cgalParamEnd - * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`\cgalParamEnd + * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `PMPDistanceTraits`\cgalParamEnd * \cgalNamedParamsEnd */ template< class Concurrency_tag, @@ -494,13 +501,13 @@ double max_distance_to_triangle_mesh(const PointRange& points, * the property map with the points associated to the vertices of `tm`. If this parameter is omitted, * an internal property map for `CGAL::vertex_point_t` should be available for the vertices of `tm` \cgalParamEnd - * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `Kernel`. \cgalParamEnd + * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `PMPDistanceTraits`. \cgalParamEnd * \cgalNamedParamsEnd */ template< class TriangleMesh, class PointRange, class NamedParameters> -double max_distance_to_point_set(const TriangleMesh& tm, +double approximate_max_distance_to_point_set(const TriangleMesh& tm, const PointRange& points, const double precision, const NamedParameters& np) @@ -525,7 +532,7 @@ double max_distance_to_point_set(const TriangleMesh& tm, } ref.add(points[0], points[1], points[2], tree); } - return ref.refine(precision, tree); + return to_double(ref.refine(precision, tree)); } // convenience functions with default parameters @@ -544,11 +551,11 @@ double max_distance_to_triangle_mesh(const PointRange& points, template< class TriangleMesh, class PointRange> -double max_distance_to_point_set(const TriangleMesh& tm, +double approximate_max_distance_to_point_set(const TriangleMesh& tm, const PointRange& points, const double precision) { - return max_distance_to_point_set(tm, points, precision, parameters::all_default()); + return approximate_max_distance_to_point_set(tm, points, precision, parameters::all_default()); } template< class Concurrency_tag, diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h index 1e36332a3d2..ffb69221955 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h @@ -25,23 +25,21 @@ #include #include #include +#include + namespace CGAL{ namespace internal{ template class CPointH { -private: - - typedef typename Kernel::Point_3 Point; - typedef typename Kernel::FT FT; - Point m_point; - FT m_hausdorff; public: + typedef typename Kernel::Point_3 Point; + typedef typename Kernel::FT FT; CPointH () { - m_point = Point (0., 0., 0.); + m_point = Point (FT(0.), FT(0.), FT(0.)); m_hausdorff = 0.; } CPointH (const Point& p, FT h = 0.) @@ -67,6 +65,10 @@ public: } +private: + + Point m_point; + FT m_hausdorff; }; template @@ -75,9 +77,9 @@ class CRefTriangle private: - typedef typename Kernel::Point_3 Point; typedef typename Kernel::FT FT; typedef CPointH PointH; + typedef typename PointH::Point Point; PointH m_point[3]; int m_edge; FT m_upper_bound; @@ -93,9 +95,10 @@ public: m_point[2] = c; m_edge = -1; - for (unsigned int i = 0; i < 3; ++ i) + //should disappear when Simon reworks the code + /*for (unsigned int i = 0; i < 3; ++ i) { - if (CGAL::angle (m_point[(i+1)%3](), m_point[i](), m_point[(i+2)%3]()) + if (CGAL::angle(m_point[(i+1)%3](), m_point[i](), m_point[(i+2)%3]()) == CGAL::OBTUSE) { m_edge = i; @@ -103,14 +106,14 @@ public: } } +*/ if (m_edge == -1) - m_bisector = CGAL::circumcenter (a(), b(), c()); + m_bisector = typename Kernel::Construct_circumcenter_3() (a(), b(), c()); else { m_bisector = PointH::mid_point (m_point[(m_edge+1)%3], m_point[(m_edge+2)%3]); } - m_lower_bound = 0.; m_upper_bound = 0.; for (unsigned int i = 0; i < 3; ++ i) @@ -118,8 +121,9 @@ public: if (m_point[i].hausdorff () > m_lower_bound) m_lower_bound = m_point[i].hausdorff (); + typename Kernel::Compute_squared_distance_3 squared_distance; FT up = m_point[i].hausdorff () - + std::sqrt (CGAL::squared_distance (m_point[i](), m_bisector)); + + CGAL::approximate_sqrt (squared_distance (m_point[i](), m_bisector)); if (up > m_upper_bound) m_upper_bound = up; @@ -160,6 +164,7 @@ public: const PointH* points () const { return m_point; } int edge () const { return m_edge; } + #ifdef CGAL_MTPS_HD_DEBUG void print () const { std::cerr << "[Refinement triangle]" << std::endl @@ -170,6 +175,7 @@ public: << " * " << m_point[2]() << std::endl << " -> " << m_bisector << std::endl; } + #endif }; }//internal @@ -240,9 +246,9 @@ public: bool add (const Point& a, const Point& b, const Point& c, const Tree& tree) { - RefTriangle r (PointH (a, std::sqrt (Knn(tree, a, 1).begin()->second)), - PointH (b, std::sqrt (Knn(tree, b, 1).begin()->second)), - PointH (c, std::sqrt (Knn(tree, c, 1).begin()->second))); + RefTriangle r (PointH (a, CGAL::approximate_sqrt (Knn(tree, a, 1).begin()->second)), + PointH (b, CGAL::approximate_sqrt (Knn(tree, b, 1).begin()->second)), + PointH (c, CGAL::approximate_sqrt (Knn(tree, c, 1).begin()->second))); if (r.lower_bound () > m_lower_bound) { @@ -280,7 +286,9 @@ public: { if (m_queue.size () > 100000) { + #ifdef CGAL_MTPS_HD_DEBUG m_queue.top ().print (); + #endif ++ nb_clean; if (nb_clean > 5) return m_upper_bound; @@ -295,9 +303,9 @@ public: if (current.lower_bound () > m_lower_bound) m_lower_bound = current.lower_bound (); + typename Kernel::Compute_squared_area_3 squared_area; - - if(CGAL::squared_area(current.points()[0](), + if(squared_area(current.points()[0](), current.points()[1](), current.points()[2]() ) < 1e-20) @@ -308,7 +316,7 @@ public: const Point& bisector = current.bisector (); m_point = bisector; //squared distance between bisector and its closst point in the mesh - FT hausdorff = std::sqrt (Knn(tree, bisector, 1).begin()->second); + FT hausdorff = CGAL::approximate_sqrt (Knn(tree, bisector, 1).begin()->second); if (hausdorff > m_lower_bound) m_lower_bound = hausdorff; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/self_intersections.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/self_intersections.h index fe0e81f266c..8e4a68f970b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/self_intersections.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/self_intersections.h @@ -241,7 +241,7 @@ self_intersections( const FaceRange& face_range, * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. * If this parameter is omitted, an internal property map for * `CGAL::vertex_point_t` should be available in `TriangleMesh`\cgalParamEnd - * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `SelfIntersectionTraits` \cgalParamEnd + * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `PMPSelfIntersectionTraits` \cgalParamEnd * \cgalNamedParamsEnd * * @return `out` @@ -300,7 +300,7 @@ self_intersections(const TriangleMesh& tmesh, OutputIterator out) * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `pmesh`. * If this parameter is omitted, an internal property map for * `CGAL::vertex_point_t` should be available in `TriangleMesh`\cgalParamEnd - * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `SelfIntersectionTraits` \cgalParamEnd + * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `PMPSelfIntersectionTraits` \cgalParamEnd * \cgalNamedParamsEnd */ @@ -386,7 +386,7 @@ OutputIterator self_intersections(const FaceRange& face_range, * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tmesh`. * If this parameter is omitted, an internal property map for * `CGAL::vertex_point_t` should be available in `TriangleMesh`\cgalParamEnd - * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `SelfIntersectionTraits` \cgalParamEnd + * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `PMPSelfIntersectionTraits` \cgalParamEnd * \cgalNamedParamsEnd * * @return true if `tmesh` self-intersects diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cmd b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cmd new file mode 100644 index 00000000000..b4ec79db85c --- /dev/null +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cmd @@ -0,0 +1 @@ +../data/elephant.off ../data/blobby_3cc.off diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp index 72fe5192608..f559561c4ce 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp @@ -6,49 +6,259 @@ #include -#include #include #include -// typedef CGAL::Exact_predicates_inexact_constructions_kernel K; -typedef CGAL::Simple_cartesian K; +struct Custom_traits_Hausdorff +{ +// New requirements { + struct FT + { + FT(){} + FT(double){} + FT operator/(FT)const{return FT();} + FT operator-(const FT)const{return FT();} + FT operator+(FT)const{return FT();} + FT operator*(FT)const{return FT();} + bool operator<=(FT)const{return false;} + bool operator>=(FT)const{return false;} + bool operator!=(FT)const{return false;} + bool operator<(FT)const{return false;} + bool operator>(FT)const{return false;} + FT& operator+=(FT){ return *this; } + }; + + struct Point_3 + { + Point_3(){} + Point_3(FT, FT, FT){} + FT operator[](int)const{return FT();} + bool operator<(Point_3)const{return false;} + double x()const{return 0;} + double y()const{return 0;} + double z()const{return 0;} + }; + + struct Vector_3{}; + + struct Triangle_3 + { + Triangle_3(){} + Triangle_3(const Point_3&, const Point_3&, const Point_3&){} + Point_3 operator[](int)const{return Point_3();} + CGAL::Bbox_3 bbox(){return CGAL::Bbox_3();} + }; + + struct Compute_squared_area_3 + { + typedef FT result_type; + FT operator()(Point_3,Point_3,Point_3)const{return FT();} + FT operator()(Triangle_3)const{return FT();} + }; + + struct Construct_translated_point_3 + { + Point_3 operator() (const Point_3 &, const Vector_3 &){return Point_3();} + }; + + struct Construct_vector_3{ + Vector_3 operator() (const Point_3 &, const Point_3 &){return Vector_3();} + }; + + struct Construct_scaled_vector_3 + { + Vector_3 operator() (const Vector_3 &, const FT &) + {return Vector_3();} + + }; + + struct Construct_circumcenter_3 + { + Point_3 operator()(const Point_3&, const Point_3&, const Point_3&){return Point_3();} + }; + + Compute_squared_area_3 compute_squared_area_3_object(){return Compute_squared_area_3();} +// } end of new requirements + +// requirements from AABBGeomTraits { + struct Sphere_3 + {}; + + struct Equal_3 + { + bool operator()(Point_3,Point_3) const {return false;} + }; + + struct Do_intersect_3 + { + CGAL::Comparison_result operator()(const Sphere_3& , const CGAL::Bbox_3&){ return CGAL::ZERO;} + }; + + struct Intersect_3 + { + struct result_type{}; + }; + + struct Construct_sphere_3 + { + Sphere_3 operator()(const Point_3& , FT ){return Sphere_3();} + }; + + struct Construct_projected_point_3 + { + const Point_3 operator()(Triangle_3, Point_3)const{return Point_3();} + }; + + struct Compare_distance_3 + { + CGAL::Comparison_result operator()(Point_3, Point_3, Point_3) + {return CGAL::ZERO;} + }; + + struct Has_on_bounded_side_3 + { + //documented as Comparision_result + CGAL::Comparison_result operator()(const Point_3&, const Point_3&, const Point_3&) + {return CGAL::ZERO;} + bool operator()(const Sphere_3&, const Point_3&) + {return false;} + }; + + struct Compute_squared_radius_3 + { + FT operator()(const Sphere_3&){return FT();} + }; + + struct Compute_squared_distance_3 + { + FT operator()(const Point_3& , const Point_3& ){return FT();} + }; + + Compare_distance_3 compare_distance_3_object(){return Compare_distance_3();} + Construct_sphere_3 construct_sphere_3_object(){return Construct_sphere_3();} + Construct_projected_point_3 construct_projected_point_3_object(){return Construct_projected_point_3();} + Compute_squared_distance_3 compute_squared_distance_3_object(){return Compute_squared_distance_3();} + Do_intersect_3 do_intersect_3_object(){return Do_intersect_3();} + Equal_3 equal_3_object(){return Equal_3();} +// } end of requirments from AABBGeomTraits + + +// requirements from SearchGeomTraits_3 { + struct Iso_cuboid_3{}; + + struct Construct_iso_cuboid_3{}; + + struct Construct_min_vertex_3{}; + + struct Construct_max_vertex_3{}; + + struct Construct_center_3{}; + + + typedef const FT* Cartesian_const_iterator_3; + + + struct Construct_cartesian_const_iterator_3{ + Construct_cartesian_const_iterator_3(){} + Construct_cartesian_const_iterator_3(const Point_3&){} + const FT* operator()(const Point_3&) const + { return 0; } + + const FT* operator()(const Point_3&, int) const + { return 0; } + typedef const FT* result_type; + }; +// } end of requirements from SearchGeomTraits_3 + +// requirements from SpatialSortingTraits_3 { + struct Less_x_3{ + bool operator()(Point_3, Point_3){return false;} + }; + struct Less_y_3{ + bool operator()(Point_3, Point_3){return false;} + }; + struct Less_z_3{ + bool operator()(Point_3, Point_3){return false;} + }; + Less_x_3 less_x_3_object()const{return Less_x_3();} + Less_y_3 less_y_3_object()const{return Less_y_3();} + Less_z_3 less_z_3_object()const{return Less_z_3();} +// } end of requirements from SpatialSortingTraits_3 +}; + +namespace CGAL{ +template<>struct Kernel_traits +{ + typedef Custom_traits_Hausdorff Kernel; +}; + +template<> +struct Algebraic_structure_traits< Custom_traits_Hausdorff::FT > +: public Algebraic_structure_traits_base< Custom_traits_Hausdorff::FT, Field_tag > +{ +}; + + +template<> +struct Real_embeddable_traits< Custom_traits_Hausdorff::FT > + : public INTERN_RET::Real_embeddable_traits_base< Custom_traits_Hausdorff::FT , CGAL::Tag_true> +{ + class To_double + : public std::unary_function< Custom_traits_Hausdorff::FT, double > { + public: + double operator()( const Custom_traits_Hausdorff::FT& ) const { return 0; } + }; +}; + +}//end CGAL + + +void exact(Custom_traits_Hausdorff::FT){} + +//}missing +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; + +#include + typedef CGAL::Surface_mesh Mesh; -bool general_tests(const Mesh& m1, - const Mesh& m2 ) +template +void general_tests(const TriangleMesh& m1, + const TriangleMesh& m2 ) { - std::cout << "Symetric distance between meshes (sequential) " + if (m1.number_of_vertices()==0 || m2.number_of_vertices()==0) return; + + std::vector points; + points.push_back(typename GeomTraits::Point_3(0,0,0)); + points.push_back(typename GeomTraits::Point_3(0,1,0)); + points.push_back(typename GeomTraits::Point_3(1,0,0)); + points.push_back(typename GeomTraits::Point_3(0,0,1)); + points.push_back(typename GeomTraits::Point_3(0,2,0)); + + std::cout << "Symmetric distance between meshes (sequential) " << CGAL::Polygon_mesh_processing::approximate_symmetric_Hausdorff_distance(m1,m2,4000) << "\n"; - std::vector points; + std::cout << "Max distance to point set " + << CGAL::Polygon_mesh_processing::approximate_max_distance_to_point_set(m1,points,4000) + << "\n"; - points.push_back(K::Point_3(0,0,0)); - points.push_back(K::Point_3(0,1,0)); - points.push_back(K::Point_3(1,0,0)); - points.push_back(K::Point_3(0,0,1)); - points.push_back(K::Point_3(0,2,0)); - - Mesh m; - CGAL::make_tetrahedron(points[0], - points[1], - points[2], - points[3], - m); - - std::cout << "Max distance to point set " - << CGAL::Polygon_mesh_processing::max_distance_to_point_set(m,points,4000) - << "\n"; - - std::cout << "Max distance to triangle mesh (sequential) " - << CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh(points,m) - << "\n"; + std::cout << "Max distance to triangle mesh (sequential) " + << CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh(points,m1) + << "\n"; } -int main(int argc, char** argv) +void test_concept() +{ + typedef Custom_traits_Hausdorff CK; + CGAL::Surface_mesh m1, m2; + general_tests(m1,m2); +} + +int main(int, char** argv) { Mesh m1,m2; @@ -64,9 +274,9 @@ int main(int argc, char** argv) CGAL::Timer time; time.start(); - std::cout << "Distance between meshes (parallel) " - << CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance(m1,m2,40000) - << "\n"; + std::cout << "Distance between meshes (parallel) " + << CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance(m1,m2,40000) + << "\n"; time.stop(); std::cout << "done in " << time.time() << "s.\n"; @@ -78,7 +288,11 @@ int main(int argc, char** argv) time.stop(); std::cout << "done in " << time.time() << "s.\n"; - general_tests(m1,m2); + general_tests(m1,m2); + + test_concept(); + + return 0; } diff --git a/Spatial_searching/include/CGAL/Orthogonal_k_neighbor_search.h b/Spatial_searching/include/CGAL/Orthogonal_k_neighbor_search.h index 308d2937247..4dc03058bf6 100644 --- a/Spatial_searching/include/CGAL/Orthogonal_k_neighbor_search.h +++ b/Spatial_searching/include/CGAL/Orthogonal_k_neighbor_search.h @@ -145,7 +145,7 @@ private: FT diff2 = val - node->upper_low_value(); if ( (diff1 + diff2 >= FT(0.0)) ) { - new_off = 2*val < node->upper_low_value()+node->upper_high_value() ? + new_off = node->upper_low_value()+node->upper_high_value() > val*2? val - node->upper_high_value(): val - node->upper_low_value(); bestChild = node->lower(); @@ -153,7 +153,7 @@ private: } else // compute new distance { - new_off = 2*val < node->lower_low_value()+node->lower_high_value() ? + new_off = node->lower_low_value()+node->lower_high_value() > val*2 ? val - node->lower_high_value(): val - node->lower_low_value(); bestChild = node->upper(); From 98b5ae4682303a218fe6eb8351c6718b4b528a99 Mon Sep 17 00:00:00 2001 From: Simon Giraudot Date: Wed, 2 Nov 2016 13:59:46 +0100 Subject: [PATCH 37/72] Fix angle/circumcenter/m_edge problems --- .../mesh_to_point_set_hausdorff_distance.h | 65 +++++++------------ 1 file changed, 23 insertions(+), 42 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h index ffb69221955..14a6d86fc10 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h @@ -81,7 +81,7 @@ private: typedef CPointH PointH; typedef typename PointH::Point Point; PointH m_point[3]; - int m_edge; + std::size_t m_edge; FT m_upper_bound; FT m_lower_bound; Point m_bisector; @@ -94,26 +94,23 @@ public: m_point[1] = b; m_point[2] = c; - m_edge = -1; - //should disappear when Simon reworks the code - /*for (unsigned int i = 0; i < 3; ++ i) - { - if (CGAL::angle(m_point[(i+1)%3](), m_point[i](), m_point[(i+2)%3]()) - == CGAL::OBTUSE) + m_edge = 0; + + typename Kernel::Compute_squared_distance_3 squared_distance; + FT length_max = squared_distance(m_point[1](), m_point[2]()); + FT length1 = squared_distance(m_point[2](), m_point[0]()); + if (length1 > length_max) { - m_edge = i; - break; + m_edge = 1; + length_max = length1; } - } + FT length2 = squared_distance(m_point[0](), m_point[1]()); + if (length2 > length_max) + m_edge = 2; + + m_bisector = PointH::mid_point (m_point[(m_edge+1)%3], + m_point[(m_edge+2)%3]); -*/ - if (m_edge == -1) - m_bisector = typename Kernel::Construct_circumcenter_3() (a(), b(), c()); - else - { - m_bisector = PointH::mid_point (m_point[(m_edge+1)%3], - m_point[(m_edge+2)%3]); - } m_lower_bound = 0.; m_upper_bound = 0.; for (unsigned int i = 0; i < 3; ++ i) @@ -121,7 +118,6 @@ public: if (m_point[i].hausdorff () > m_lower_bound) m_lower_bound = m_point[i].hausdorff (); - typename Kernel::Compute_squared_distance_3 squared_distance; FT up = m_point[i].hausdorff () + CGAL::approximate_sqrt (squared_distance (m_point[i](), m_bisector)); @@ -162,7 +158,7 @@ public: } const PointH* points () const { return m_point; } - int edge () const { return m_edge; } + std::size_t edge () const { return m_edge; } #ifdef CGAL_MTPS_HD_DEBUG void print () const @@ -324,30 +320,15 @@ public: return m_upper_bound; PointH new_point (bisector, hausdorff); - int i = current.edge (); - if (i == -1) - { - PointH p0 (current.points()[0]); - PointH p1 (current.points()[1]); - PointH p2 (current.points()[2]); + std::size_t i = current.edge (); + PointH p0 (current.points()[i]); + PointH p1 (current.points()[(i+1)%3]); + PointH p2 (current.points()[(i+2)%3]); - m_queue.pop (); + m_queue.pop (); - m_queue.push (RefTriangle (new_point, p0, p1)); - m_queue.push (RefTriangle (new_point, p1, p2)); - m_queue.push (RefTriangle (new_point, p2, p0)); - } - else - { - PointH p0 (current.points()[i]); - PointH p1 (current.points()[(i+1)%3]); - PointH p2 (current.points()[(i+2)%3]); - - m_queue.pop (); - - m_queue.push (RefTriangle (new_point, p0, p1)); - m_queue.push (RefTriangle (new_point, p0, p2)); - } + m_queue.push (RefTriangle (new_point, p0, p1)); + m_queue.push (RefTriangle (new_point, p0, p2)); } From 581b677a44e0ec630cf0138b6af22a255decb43b Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Wed, 2 Nov 2016 15:23:37 +0100 Subject: [PATCH 38/72] Add sample_face() and fix some doc. --- .../Concepts/PMPDistanceTraits.h | 4 - .../CGAL/Polygon_mesh_processing/distance.h | 100 ++++++++++++++++++ .../mesh_to_point_set_hausdorff_distance.h | 6 +- .../test_pmp_distance.cpp | 14 +-- 4 files changed, 110 insertions(+), 14 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h index a222fcbcb5b..4dc9c58a3fb 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h @@ -57,9 +57,6 @@ public: /// Functor for constructing scaled vectors. /// It provides `Vector_3 operator()(const Vector_3 &, const FT &)` typedef unspecified_type Construct_scaled_vector_3; - /// Functor for constructing the circumcenter of three points. - /// It provides `Point_3 operator()(const Point_3&, const Point_3&, const Point_3&)` - typedef unspecified_type Construct_circumcenter_3; /// @} /// @name Functions @@ -68,6 +65,5 @@ public: Construct_translated_point_3 construct_translated_point_3_object(); Construct_vector_3 construct_vector_3_object(); Construct_scaled_vector_3 construct_scaled_vector_3_object(); - Construct_circumcenter_3 construct_circumcenter_3_object(); /// @} }; diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 089c785851c..efa7d3caeee 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -367,6 +367,106 @@ double approximate_Hausdorff_distance( } // documented functions +/** \ingroup PMP_distance_grp + * generates points taken on `f`, facet of `tm`, in a way depending on `method` and + * outputs them to `out`. + * @tparam TriangleMesh a model of the concept `FaceListGraph` + * @tparam OutputIterator a model of `OutputIterator` + * holding objects of the same point type as + * the value type of the internal vertex point map of `tm` + * @tparam Sampling_method defines the sampling method. Possible values are: + - `RANDOM_UNIFORM`: points are generated in a random and uniform way, depending on the area of the face. + - `GRID`: points are generated on a grid, with a minimum of one point . + - `MONTE_CARLO`: points are generated randomly . The number of points is proportional to the face area with a minimum of 1. + * + * @param tm the triangle mesh that contains `f` + * @param parameter depends on `method`: + - `RANDOM_UNIFORM` and `MONTE_CARLO`: the number of points per squared area unit + - `GRID`: the distance between two consecutive points in the grid + * + * @param out output iterator to be filled with sampled points + * @param np an optional sequence of \ref namedparameters among the ones listed below + * @param method defines the method of sampling + * + * \cgalNamedParamsBegin + * \cgalParamBegin{vertex_point_map} + * the property map with the points associated to the vertices of `tm`. If this parameter is omitted, + * an internal property map for `CGAL::vertex_point_t` should be available for `TriangleMesh` \cgalParamEnd + * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `PMPDistanceTraits`\cgalParamEnd + * \cgalNamedParamsEnd + */ +template +OutputIterator +sample_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + double parameter, + OutputIterator out, + NamedParameters np, + Sampling_method method = RANDOM_UNIFORM) +{ + typedef typename GetGeomTraits::type Geom_traits; + + typedef typename GetVertexPointMap::const_type Vpm; + + Vpm pmap = choose_param(get_param(np, vertex_point), + get_const_property_map(vertex_point, tm)); + typedef Creator_uniform_3 Creator; + switch(method) + { + case RANDOM_UNIFORM: + { + std::size_t nb_points = std::ceil( to_double( + face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter); + typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); + typename Geom_traits::Point_3 points[3]; + for(int i=0; i<3; ++i) + { + points[i] = get(pmap, target(hd, tm)); + hd = next(hd, tm); + } + Random_points_in_triangle_3 + g(points[0], points[1], points[2]); + CGAL::cpp11::copy_n(g, nb_points, out); + return out; + } + case GRID: + { + //create the triangles and store them + typename Geom_traits::Point_3 points[3]; + typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); + for(int i=0; i<3; ++i) + { + points[i] = get(pmap, target(hd, tm)); + hd = next(hd, tm); + } + + internal::triangle_grid_sampling(typename Geom_traits::Triangle_3(points[0], points[1], points[2]), parameter, out); + return out; + } + case MONTE_CARLO: + { + std::size_t nb_points( (std::max)( + std::ceil(to_double(face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter), + 1.) ); + //create the triangles and store them + typename Geom_traits::Point_3 points[3]; + typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); + for(int i=0; i<3; ++i) + { + points[i] = get(pmap, target(hd, tm)); + hd = next(hd, tm); + } + Random_points_in_triangle_3 + g(points[0], points[1], points[2]); + CGAL::cpp11::copy_n(g, nb_points, out); + } + } + return out; +} + /** * \ingroup PMP_distance_grp * computes the approximate Hausdorff distance of `tm1` from `tm2` by diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h index 14a6d86fc10..5db64533332 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h @@ -59,9 +59,9 @@ public: static Point mid_point (const CPointH& a, const CPointH& b) { - return Point (0.5 * (a().x() + b().x()), - 0.5 * (a().y() + b().y()), - 0.5 * (a().z() + b().z())); + return Point (FT(0.5) * (a().x() + b().x()), + FT(0.5) * (a().y() + b().y()), + FT(0.5) * (a().z() + b().z())); } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp index f559561c4ce..644acc6d2b7 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp @@ -73,11 +73,6 @@ struct Custom_traits_Hausdorff }; - struct Construct_circumcenter_3 - { - Point_3 operator()(const Point_3&, const Point_3&, const Point_3&){return Point_3();} - }; - Compute_squared_area_3 compute_squared_area_3_object(){return Compute_squared_area_3();} // } end of new requirements @@ -216,7 +211,6 @@ struct Real_embeddable_traits< Custom_traits_Hausdorff::FT > void exact(Custom_traits_Hausdorff::FT){} -//}missing typedef CGAL::Exact_predicates_inexact_constructions_kernel K; #include @@ -249,6 +243,13 @@ void general_tests(const TriangleMesh& m1, << CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh(points,m1) << "\n"; + std::vector sample; + typename boost::graph_traits::face_descriptor f = *faces(m1).first; + CGAL::Polygon_mesh_processing::sample_face(f, m1, 4000, std::back_inserter(sample),CGAL::Polygon_mesh_processing::parameters::all_default()); + std::cout << "Number of sampled points in the first face of m1 (sequential) " + <> m1; input.close(); From 1d43e641d6ea15bc6b4a0a3c76121fc028441d4f Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Wed, 2 Nov 2016 15:44:57 +0100 Subject: [PATCH 39/72] Fix measure.h and update demo plugin code --- .../CGAL/Polygon_mesh_processing/distance.h | 70 +++++++++---------- .../CGAL/Polygon_mesh_processing/measure.h | 57 +++++++-------- .../Plugins/PMP/Distance_plugin.cpp | 6 +- 3 files changed, 62 insertions(+), 71 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index efa7d3caeee..c6b9d6afcdb 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -414,12 +414,28 @@ sample_face(typename boost::graph_traits::face_descriptor f, get_const_property_map(vertex_point, tm)); typedef Creator_uniform_3 Creator; + std::size_t nb_points = std::ceil( to_double( + face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter); switch(method) { + case GRID: + { + //create the triangles and store them + typename Geom_traits::Point_3 points[3]; + typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); + for(int i=0; i<3; ++i) + { + points[i] = get(pmap, target(hd, tm)); + hd = next(hd, tm); + } + + internal::triangle_grid_sampling(typename Geom_traits::Triangle_3(points[0], points[1], points[2]), parameter, out); + return out; + } + case MONTE_CARLO: + nb_points = (std::max)(nb_points, static_cast(1)); case RANDOM_UNIFORM: { - std::size_t nb_points = std::ceil( to_double( - face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter); typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); typename Geom_traits::Point_3 points[3]; for(int i=0; i<3; ++i) @@ -432,37 +448,6 @@ sample_face(typename boost::graph_traits::face_descriptor f, CGAL::cpp11::copy_n(g, nb_points, out); return out; } - case GRID: - { - //create the triangles and store them - typename Geom_traits::Point_3 points[3]; - typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); - for(int i=0; i<3; ++i) - { - points[i] = get(pmap, target(hd, tm)); - hd = next(hd, tm); - } - - internal::triangle_grid_sampling(typename Geom_traits::Triangle_3(points[0], points[1], points[2]), parameter, out); - return out; - } - case MONTE_CARLO: - { - std::size_t nb_points( (std::max)( - std::ceil(to_double(face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter), - 1.) ); - //create the triangles and store them - typename Geom_traits::Point_3 points[3]; - typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); - for(int i=0; i<3; ++i) - { - points[i] = get(pmap, target(hd, tm)); - hd = next(hd, tm); - } - Random_points_in_triangle_3 - g(points[0], points[1], points[2]); - CGAL::cpp11::copy_n(g, nb_points, out); - } } return out; } @@ -490,15 +475,24 @@ sample_face(typename boost::graph_traits::face_descriptor f, * @param tm2 the triangle mesh to compute the distance to * @param precision the number of points per squared area unit for the random sampling * @param np1 optional sequence of \ref namedparameters for `tm1` among the ones listed below - * @param np2 optional sequence of \ref namedparameters for `tm2` among the ones listed below - * * * \cgalNamedParamsBegin - * \cgalParamBegin{vertex_point_map} - * the property map with the points associated to the vertices of `tm1` (`tm2`). If this parameter is omitted, - * an internal property map for `CGAL::vertex_point_t` should be available for `TriangleMesh` \cgalParamEnd + * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm1` + * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` + * and in all places where vertex_point_map is used. + * \cgalParamEnd * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `PMPDistanceTraits`\cgalParamEnd * \cgalNamedParamsEnd + * + * @param np2 optional sequence of \ref namedparameters for `tm2` among the ones listed below + * \cgalNamedParamsBegin + * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm2` + * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` + * and in all places where vertex_point_map is used. + * \cgalParamEnd + * \cgalNamedParamsEnd + * The function `CGAL::Polygon_mesh_processing::params::all_default()` can be use to indicate to use the default values for + * `np1` and specify custom values for `np2` */ template< class Concurrency_tag, class TriangleMesh, diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/measure.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/measure.h index b314d307e84..0fffe7af464 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/measure.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/measure.h @@ -14,7 +14,7 @@ // // $URL$ // $Id$ -// +// // // Author(s) : Andreas Fabri @@ -413,35 +413,36 @@ namespace Polygon_mesh_processing { #else typename GetGeomTraits::type::FT #endif - volume(const TriangleMesh& tmesh, const CGAL_PMP_NP_CLASS& np) +volume(const TriangleMesh& tmesh, const CGAL_PMP_NP_CLASS& np) +{ + CGAL_assertion(is_triangle_mesh(tmesh)); + CGAL_assertion(is_closed(tmesh)); + + using boost::choose_param; + using boost::get_param; + + typename GetVertexPointMap::const_type + vpm = choose_param(get_param(np, vertex_point), + get_const_property_map(CGAL::vertex_point, tmesh)); + typename GetGeomTraits::type::Point_3 + origin(0, 0, 0); + + typedef typename boost::graph_traits::face_descriptor face_descriptor; + + typename GetGeomTraits::type::FT volume = 0.; + typename CGAL::Kernel_traits::type>::Kernel::Compute_volume_3 cv3; + + BOOST_FOREACH(face_descriptor f, faces(tmesh)) { - CGAL_assertion(is_triangle_mesh(tmesh)); - CGAL_assertion(is_closed(tmesh)); - - using boost::choose_param; - using boost::get_param; - - typename GetVertexPointMap::const_type - vpm = choose_param(get_param(np, vertex_point), - get_const_property_map(CGAL::vertex_point, tmesh)); - typename GetGeomTraits::type::Point_3 - origin(0, 0, 0); - - typedef typename boost::graph_traits::face_descriptor face_descriptor; - - typename GetGeomTraits::type::FT volume = 0.; - BOOST_FOREACH(face_descriptor f, faces(tmesh)) - { - typename CGAL::Kernel_traits::type>::Kernel::Volume_3 volume; - volume += volume(origin, - get(vpm, target(halfedge(f, tmesh), tmesh)), - get(vpm, target(next(halfedge(f, tmesh), tmesh), tmesh)), - get(vpm, target(prev(halfedge(f, tmesh), tmesh), tmesh))); - exact(volume); - } - return volume; + volume += cv3(origin, + get(vpm, target(halfedge(f, tmesh), tmesh)), + get(vpm, target(next(halfedge(f, tmesh), tmesh), tmesh)), + get(vpm, target(prev(halfedge(f, tmesh), tmesh), tmesh))); + exact(volume); } + return volume; +} template typename CGAL::Kernel_traits sampled_points; - std::size_t nb_points = (std::max)((int)std::ceil(400 * PMP::face_area(f,*poly,PMP::parameters::geom_traits(Kernel()))), - 1); - CGAL::Random_points_in_triangle_3 g(f->halfedge()->vertex()->point(), f->halfedge()->next()->vertex()->point(), - f->halfedge()->next()->next()->vertex()->point()); - CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); + CGAL::Polygon_mesh_processing::sample_face(f, *poly, 400, std::back_inserter(sampled_points),CGAL::Polygon_mesh_processing::parameters::all_default()); sampled_points.push_back(f->halfedge()->vertex()->point()); sampled_points.push_back(f->halfedge()->next()->vertex()->point()); sampled_points.push_back(f->halfedge()->next()->next()->vertex()->point()); From 768e52fe54ac286c9727b2d6fe9b45ddd4b53080 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Mon, 7 Nov 2016 11:40:58 +0100 Subject: [PATCH 40/72] Another fix of the doc. --- .../Concepts/PMPDistanceTraits.h | 2 +- .../Polygon_mesh_processing.txt | 1 + .../CGAL/Polygon_mesh_processing/distance.h | 16 ++++++++-------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h index 4dc9c58a3fb..aa4191d4e3a 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h @@ -24,7 +24,7 @@ public: * `FT operator[](int i)` with `0 <= i < 3`. * * There must be a specialization of `CGAL::Kernel_traits` such that - * `CGAL::Kernel_traits::Kernel` is the model implementing this concept. + * `CGAL::Kernel_traits::Kernel` is a model implementing this concept. */ typedef unspecified_type Point_3; 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 379db474f92..d94d2258662 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 @@ -491,6 +491,7 @@ These computations are performed with : - `CGAL::Polygon_mesh_processing::approximate_max_distance_to_point_set()` - `CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh()` - `CGAL::Polygon_mesh_processing::sample_triangle_mesh()` +- `CGAL::Polygon_mesh_processing::sample_face()` In the following example, a mesh is isotropically remeshed and the approximated distance between the input and the output is computed. diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index c6b9d6afcdb..6813c2f60e9 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -195,7 +195,7 @@ enum Sampling_method{ * * @param out output iterator to be filled with sampled points * @param np an optional sequence of \ref namedparameters among the ones listed below - * @param method defines the method of sampling + * @param method defines the sampling method * * \cgalNamedParamsBegin * \cgalParamBegin{vertex_point_map} @@ -368,7 +368,7 @@ double approximate_Hausdorff_distance( // documented functions /** \ingroup PMP_distance_grp - * generates points taken on `f`, facet of `tm`, in a way depending on `method` and + * generates points taken on `f`, a face of `tm`, in a way depending on `method` and * outputs them to `out`. * @tparam TriangleMesh a model of the concept `FaceListGraph` * @tparam OutputIterator a model of `OutputIterator` @@ -454,7 +454,7 @@ sample_face(typename boost::graph_traits::face_descriptor f, /** * \ingroup PMP_distance_grp - * computes the approximate Hausdorff distance of `tm1` from `tm2` by + * computes the approximate Hausdorff distance from `tm1` to `tm2` by * generating a uniform random point sampling on `tm1`, and by then * returning the distance of the furthest point from `tm2`. * @@ -491,7 +491,7 @@ sample_face(typename boost::graph_traits::face_descriptor f, * and in all places where vertex_point_map is used. * \cgalParamEnd * \cgalNamedParamsEnd - * The function `CGAL::Polygon_mesh_processing::params::all_default()` can be use to indicate to use the default values for + * The function `CGAL::Polygon_mesh_processing::params::all_default()` can be used to indicate to use the default values for * `np1` and specify custom values for `np2` */ template< class Concurrency_tag, @@ -550,7 +550,7 @@ double approximate_symmetric_Hausdorff_distance( * @tparam PointRange a range of `Point_3`, model of `Range`. * @tparam TriangleMesh a model of the concept `FaceListGraph` * @tparam NamedParameters a sequence of \ref namedparameters - * @param points the point_set of interest + * @param points the range of points of interest * @param tm the triangle mesh to compute the distance to * @param np an optional sequence of \ref namedparameters among the ones listed below * @@ -585,9 +585,9 @@ double max_distance_to_triangle_mesh(const PointRange& points, * @tparam PointRange a range of `Point_3`, model of `Range`. * @tparam TriangleMesh a model of the concept `FaceListGraph` * @tparam NamedParameters a sequence of \ref namedparameters - * @param tm the triangle mesh to compute the distance to - * @param points the point_set of interest. - * @param precision the precision of the approximated value you want. + * @param tm the triangle mesh to compute the distance from + * @param points the range of points of interest. + * @param precision the precision of the approximate value you want. * @param np an optional sequence of \ref namedparameters among the ones listed below * * \cgalNamedParamsBegin From c84e72f7a5f52b0c55c37ea37cc8ac2ed8ffdb73 Mon Sep 17 00:00:00 2001 From: Andreas Fabri Date: Thu, 10 Nov 2016 11:28:28 +0100 Subject: [PATCH 41/72] Fix typo in example; Functions in manuals always with '()' --- Installation/changes.html | 8 ++++---- .../hausdorff_distance_remeshing_example.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Installation/changes.html b/Installation/changes.html index e2f9ba01a1d..31f29940112 100644 --- a/Installation/changes.html +++ b/Installation/changes.html @@ -257,10 +257,10 @@ and src/ directories).

    • Add functions to compute approximated Hausdorff distances between two meshes, a mesh and a point set, or a point set and a mesh: - sample_triangle_mesh, approximated_Hausdorff_distance, - approximated_symmetric_Hausdorff_distance, - max_distance_to_triangle_mesh, - max_distance_to_point_set and the enum Sampling_method + sample_triangle_mesh(), approximated_Hausdorff_distance(), + approximated_symmetric_Hausdorff_distance(), + max_distance_to_triangle_mesh(), + max_distance_to_point_set() and the enum Sampling_method
    diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp index b7d575e5842..f2b17009cfe 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp @@ -28,6 +28,6 @@ int main() std::cout << "Approximated Hausdorff distance: " << CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance - (m1, m2, 4000) + (tm1, tm2, 4000) << std::endl; } From 18a39a54bdc2b0be99ba6fde2255f3a32f153b47 Mon Sep 17 00:00:00 2001 From: Andreas Fabri Date: Thu, 10 Nov 2016 12:06:44 +0100 Subject: [PATCH 42/72] fix VC++ warnings by adding static_cast --- .../include/CGAL/Polygon_mesh_processing/distance.h | 13 +++++++------ .../internal/mesh_to_point_set_hausdorff_distance.h | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 6813c2f60e9..06b2b2b0b84 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -226,8 +226,8 @@ sample_triangle_mesh(const TriangleMesh& tm, { case RANDOM_UNIFORM: { - std::size_t nb_points = std::ceil( to_double( - area(tm, parameters::geom_traits(Geom_traits()))*parameter) ); + std::size_t nb_points = static_cast( + std::ceil( to_double(area(tm, parameters::geom_traits(Geom_traits()))*parameter)) ); Random_points_in_triangle_mesh_3 g(tm, pmap); CGAL::cpp11::copy_n(g, nb_points, out); @@ -256,8 +256,8 @@ sample_triangle_mesh(const TriangleMesh& tm, BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(tm)) { std::size_t nb_points( (std::max)( - std::ceil(to_double(face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter), - 1.) ); + static_cast(std::ceil(to_double(face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter)), + 1) ); //create the triangles and store them typename Geom_traits::Point_3 points[3]; typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); @@ -414,8 +414,9 @@ sample_face(typename boost::graph_traits::face_descriptor f, get_const_property_map(vertex_point, tm)); typedef Creator_uniform_3 Creator; - std::size_t nb_points = std::ceil( to_double( - face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter); + std::size_t nb_points = static_cast( + std::ceil( to_double( + face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter)); switch(method) { case GRID: diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h index 5db64533332..4ba1bb2c97b 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h @@ -256,7 +256,7 @@ public: } bool clean_up_queue () { - unsigned int before = m_queue.size (); + std::size_t before = m_queue.size (); std::vector to_keep; while (!(m_queue.empty ())) { From 9dee15964b26663f4f15cc718111cb0e3b7f7d3a Mon Sep 17 00:00:00 2001 From: Andreas Fabri Date: Thu, 10 Nov 2016 12:28:24 +0100 Subject: [PATCH 43/72] poisson -> Poisson --- .../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 d94d2258662..2bec5ecc04a 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 @@ -498,7 +498,7 @@ In the following example, a mesh is isotropically remeshed and the approximated \cgalExample{Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp} In the following example, a triangulated surface mesh is constructed from a point set using the -\link PkgPoissonSurfaceReconstructionSummary poisson reconstruction algorithm \endlink, and the distance between the point set and the reconstructed surface is computed. +\link PkgPoissonSurfaceReconstructionSummary Poisson reconstruction algorithm \endlink, and the distance between the point set and the reconstructed surface is computed. \cgalExample{Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp} From cdc76129d0555174f499ee2a0b92b5fac15c7b2c Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Mon, 14 Nov 2016 10:15:15 +0100 Subject: [PATCH 44/72] Keep fixing the doc --- .../doc/Polygon_mesh_processing/Polygon_mesh_processing.txt | 3 +++ .../include/CGAL/Polygon_mesh_processing/distance.h | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt index 2bec5ecc04a..15271fbd503 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 @@ -493,15 +493,18 @@ These computations are performed with : - `CGAL::Polygon_mesh_processing::sample_triangle_mesh()` - `CGAL::Polygon_mesh_processing::sample_face()` +\subsection AHDExample Approximate Hausdorff Distance Example In the following example, a mesh is isotropically remeshed and the approximated distance between the input and the output is computed. \cgalExample{Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp} +\subsection PoissonDistanceExample Max Distance Between Point Set and Surface Example In the following example, a triangulated surface mesh is constructed from a point set using the \link PkgPoissonSurfaceReconstructionSummary Poisson reconstruction algorithm \endlink, and the distance between the point set and the reconstructed surface is computed. \cgalExample{Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp} + \section PMPHistory Implementation History A first version of this package was started by Ilker %O. Yaz and Sébastien Loriot. diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 06b2b2b0b84..d5ca527fb1a 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -379,6 +379,7 @@ double approximate_Hausdorff_distance( - `GRID`: points are generated on a grid, with a minimum of one point . - `MONTE_CARLO`: points are generated randomly . The number of points is proportional to the face area with a minimum of 1. * + * @param f the face descriptor of the sampled face. * @param tm the triangle mesh that contains `f` * @param parameter depends on `method`: - `RANDOM_UNIFORM` and `MONTE_CARLO`: the number of points per squared area unit @@ -474,7 +475,7 @@ sample_face(typename boost::graph_traits::face_descriptor f, * * @param tm1 the triangle mesh that will be sampled * @param tm2 the triangle mesh to compute the distance to - * @param precision the number of points per squared area unit for the random sampling + * @param precision the number of points per squared area unit returned by the random sampling * @param np1 optional sequence of \ref namedparameters for `tm1` among the ones listed below * * \cgalNamedParamsBegin From 4568381657bfaaabf1d2c3c27619110a1529bbcf Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Tue, 15 Nov 2016 11:14:30 +0100 Subject: [PATCH 45/72] Another fix for the doc --- .../include/CGAL/Polygon_mesh_processing/distance.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index d5ca527fb1a..ec12cdba6b5 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -475,7 +475,7 @@ sample_face(typename boost::graph_traits::face_descriptor f, * * @param tm1 the triangle mesh that will be sampled * @param tm2 the triangle mesh to compute the distance to - * @param precision the number of points per squared area unit returned by the random sampling + * @param density the number of points per squared area unit returned by the random sampling * @param np1 optional sequence of \ref namedparameters for `tm1` among the ones listed below * * \cgalNamedParamsBegin @@ -487,6 +487,7 @@ sample_face(typename boost::graph_traits::face_descriptor f, * \cgalNamedParamsEnd * * @param np2 optional sequence of \ref namedparameters for `tm2` among the ones listed below + * * \cgalNamedParamsBegin * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm2` * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` @@ -502,7 +503,7 @@ template< class Concurrency_tag, class NamedParameters2> double approximate_Hausdorff_distance( const TriangleMesh& tm1, const TriangleMesh& tm2, - double precision, + double density, const NamedParameters1& np1, const NamedParameters2& np2) { @@ -511,7 +512,7 @@ double approximate_Hausdorff_distance( const TriangleMesh& tm1, return approximate_Hausdorff_distance( tm1, tm2, - precision, + density, choose_param(get_param(np1, vertex_point), get_const_property_map(vertex_point, tm1)), From fd87870977cf7cd9ce27b1f0d8aa006e3554c86c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 23 Dec 2016 11:13:29 +0100 Subject: [PATCH 46/72] fix constructed vector --- .../include/CGAL/Polygon_mesh_processing/distance.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index ec12cdba6b5..10b8d42e8d7 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -107,7 +107,7 @@ sample_triangles(const TriangleRange& triangles, double distance, OutputIterator const double d_p0p1 = to_double(approximate_sqrt( squared_distance(p0, p1) )); const double nb_pts = std::ceil( d_p0p1 / distance ); - const Vector_3 step_vec = typename Kernel::Construct_scaled_vector_3()(typename Kernel::Construct_vector_3()(p1, p0), typename Kernel::FT(1)/typename Kernel::FT(nb_pts)); + const Vector_3 step_vec = typename Kernel::Construct_scaled_vector_3()(typename Kernel::Construct_vector_3()(p0, p1), typename Kernel::FT(1)/typename Kernel::FT(nb_pts)); for (double i=1; i Date: Fri, 23 Dec 2016 13:56:35 +0100 Subject: [PATCH 47/72] cosmetic changes --- .../CGAL/Polygon_mesh_processing/distance.h | 172 ++++++++++-------- 1 file changed, 95 insertions(+), 77 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 10b8d42e8d7..3184e761414 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -16,7 +16,7 @@ // $Id$ // // -// Author(s) : Sebastien Loriot +// Author(s) : Maxime Gimeno and Sebastien Loriot #ifndef CGAL_POLYGON_MESH_PROCESSING_DISTANCE_H #define CGAL_POLYGON_MESH_PROCESSING_DISTANCE_H @@ -55,7 +55,7 @@ triangle_grid_sampling( const typename Kernel::Point_3& p0, double distance, OutputIterator out) { - typename Kernel::Compute_squared_distance_3 squared_distance; + typename Kernel::Compute_squared_distance_3 squared_distance; const double d_p0p1 = to_double(approximate_sqrt( squared_distance(p0, p1) )); const double d_p0p2 = to_double(approximate_sqrt( squared_distance(p0, p2) )); @@ -77,7 +77,9 @@ triangle_grid_sampling( const typename Kernel::Point_3& p0, template OutputIterator -triangle_grid_sampling(const typename Kernel::Triangle_3& t, double distance, OutputIterator out) +triangle_grid_sampling(const typename Kernel::Triangle_3& t, + double distance, + OutputIterator out) { return triangle_grid_sampling(t[0], t[1], t[2], distance, out); } @@ -86,7 +88,9 @@ triangle_grid_sampling(const typename Kernel::Triangle_3& t, double distance, Ou template OutputIterator -sample_triangles(const TriangleRange& triangles, double distance, OutputIterator out) +sample_triangles(const TriangleRange& triangles, + double distance, + OutputIterator out) { typedef typename Kernel::Point_3 Point_3; typedef typename Kernel::Vector_3 Vector_3; @@ -94,7 +98,7 @@ sample_triangles(const TriangleRange& triangles, double distance, OutputIterator std::set< std::pair > sampled_edges; - // sample edges but skip endpoints + // sample edges but skip endpoints BOOST_FOREACH(const Triangle_3& t, triangles) { for (int i=0;i<3; ++i) @@ -103,14 +107,18 @@ sample_triangles(const TriangleRange& triangles, double distance, OutputIterator const Point_3& p1=t[(i+1)%3]; if ( sampled_edges.insert(CGAL::make_sorted_pair(p0, p1)).second ) { - typename Kernel::Compute_squared_distance_3 squared_distance; + typename Kernel::Compute_squared_distance_3 squared_distance; const double d_p0p1 = to_double(approximate_sqrt( squared_distance(p0, p1) )); const double nb_pts = std::ceil( d_p0p1 / distance ); - const Vector_3 step_vec = typename Kernel::Construct_scaled_vector_3()(typename Kernel::Construct_vector_3()(p0, p1), typename Kernel::FT(1)/typename Kernel::FT(nb_pts)); + const Vector_3 step_vec = typename Kernel::Construct_scaled_vector_3()( + typename Kernel::Construct_vector_3()(p0, p1), + typename Kernel::FT(1)/typename Kernel::FT(nb_pts)); for (double i=1; i::const_type Vpm; + typedef boost::graph_traits GT; + typedef typename GT::face_descriptor face_descriptor; + typedef typename GT::halfedge_descriptor halfedge_descriptor; + Vpm pmap = choose_param(get_param(np, vertex_point), get_const_property_map(vertex_point, tm)); typedef Creator_uniform_3( - std::ceil( to_double(area(tm, parameters::geom_traits(Geom_traits()))*parameter)) ); - Random_points_in_triangle_mesh_3 - g(tm, pmap); + std::size_t nb_points = static_cast( std::ceil( to_double( + area(tm, parameters::geom_traits(Geom_traits())))*parameter) ); + Random_points_in_triangle_mesh_3 g(tm, pmap); CGAL::cpp11::copy_n(g, nb_points, out); return out; } case GRID: { std::vector triangles; - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(tm)) + BOOST_FOREACH(face_descriptor f, faces(tm)) { //create the triangles and store them typename Geom_traits::Point_3 points[3]; - typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); + halfedge_descriptor hd(halfedge(f,tm)); for(int i=0; i<3; ++i) { points[i] = get(pmap, target(hd, tm)); @@ -253,14 +264,16 @@ sample_triangle_mesh(const TriangleMesh& tm, } case MONTE_CARLO: { - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(tm)) + BOOST_FOREACH(face_descriptor f, faces(tm)) { - std::size_t nb_points( (std::max)( - static_cast(std::ceil(to_double(face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter)), - 1) ); + std::size_t nb_points = (std::max)( + static_cast( + std::ceil(to_double( + face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter)) + ,std::size_t(1)); //create the triangles and store them typename Geom_traits::Point_3 points[3]; - typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); + halfedge_descriptor hd(halfedge(f,tm)); for(int i=0; i<3; ++i) { points[i] = get(pmap, target(hd, tm)); @@ -290,12 +303,15 @@ sample_triangle_mesh(const TriangleMesh& tm, method); } -template +template double approximate_Hausdorff_distance( const PointRange& original_sample_points, const TriangleMesh& tm, - VertexPointMap vpm - ) + VertexPointMap vpm) { CGAL_assertion_code( bool is_triangle = is_triangle_mesh(tm) ); CGAL_assertion_msg (is_triangle, @@ -303,19 +319,19 @@ double approximate_Hausdorff_distance( #ifdef CGAL_HAUSDORFF_DEBUG std::cout << "Nb sample points " << sample_points.size() << "\n"; #endif - std::vector sample_points + typedef typename Kernel::Point_3 Point_3; + std::vector sample_points (boost::begin(original_sample_points), boost::end(original_sample_points) ); spatial_sort(sample_points.begin(), sample_points.end()); typedef AABB_face_graph_triangle_primitive Primitive; - typedef AABB_traits Traits; - typedef AABB_tree< Traits > Tree; + typedef AABB_tree< AABB_traits > Tree; Tree tree( faces(tm).first, faces(tm).second, tm); tree.accelerate_distance_queries(); tree.build(); - typename Kernel::Point_3 hint = get(vpm, *vertices(tm).first); + Point_3 hint = get(vpm, *vertices(tm).first); #ifndef CGAL_LINKED_WITH_TBB CGAL_static_assertion_msg (!(boost::is_convertible::value), "Parallel_tag is enabled but TBB is unavailable."); @@ -323,7 +339,7 @@ double approximate_Hausdorff_distance( if (boost::is_convertible::value) { cpp11::atomic distance(0); - Distance_computation f(tree, hint, sample_points, &distance); + Distance_computation f(tree, hint, sample_points, &distance); tbb::parallel_for(tbb::blocked_range(0, sample_points.size()), f); return distance; } @@ -331,7 +347,7 @@ double approximate_Hausdorff_distance( #endif { double hdist = 0; - BOOST_FOREACH(const typename Kernel::Point_3& pt, sample_points) + BOOST_FOREACH(const Point_3& pt, sample_points) { hint = tree.closest_point(pt, hint); typename Kernel::Compute_squared_distance_3 squared_distance; @@ -353,8 +369,7 @@ double approximate_Hausdorff_distance( double precision, NamedParameters np, VertexPointMap vpm, - Sampling_method method = RANDOM_UNIFORM -) + Sampling_method method = RANDOM_UNIFORM) { std::vector sample_points; sample_triangle_mesh( @@ -411,45 +426,46 @@ sample_face(typename boost::graph_traits::face_descriptor f, typedef typename GetVertexPointMap::const_type Vpm; + typedef boost::graph_traits GT; + Vpm pmap = choose_param(get_param(np, vertex_point), get_const_property_map(vertex_point, tm)); typedef Creator_uniform_3 Creator; - std::size_t nb_points = static_cast( - std::ceil( to_double( - face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter)); + std::size_t nb_points = static_cast( std::ceil( to_double( + face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter)); switch(method) { - case GRID: - { + case GRID: + { //create the triangles and store them typename Geom_traits::Point_3 points[3]; - typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); + typename GT::halfedge_descriptor hd(halfedge(f,tm)); for(int i=0; i<3; ++i) { points[i] = get(pmap, target(hd, tm)); hd = next(hd, tm); } - internal::triangle_grid_sampling(typename Geom_traits::Triangle_3(points[0], points[1], points[2]), parameter, out); - return out; - } - case MONTE_CARLO: - nb_points = (std::max)(nb_points, static_cast(1)); - case RANDOM_UNIFORM: - { - typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); + internal::triangle_grid_sampling(typename Geom_traits::Triangle_3(points[0], points[1], points[2]), parameter, out); + return out; + } + case MONTE_CARLO: + nb_points = (std::max)(nb_points, std::size_t(1)); + case RANDOM_UNIFORM: + { + typename GT::halfedge_descriptor hd(halfedge(f,tm)); typename Geom_traits::Point_3 points[3]; for(int i=0; i<3; ++i) { - points[i] = get(pmap, target(hd, tm)); - hd = next(hd, tm); + points[i] = get(pmap, target(hd, tm)); + hd = next(hd, tm); } Random_points_in_triangle_3 - g(points[0], points[1], points[2]); + g(points[0], points[1], points[2]); CGAL::cpp11::copy_n(g, nb_points, out); return out; - } + } } return out; } @@ -611,21 +627,22 @@ double approximate_max_distance_to_point_set(const TriangleMesh& tm, { typedef typename GetGeomTraits::type Geom_traits; + typedef boost::graph_traits GT; - typedef CGAL::Orthogonal_k_neighbor_search > Knn; + typedef Orthogonal_k_neighbor_search > Knn; typedef typename Knn::Tree Tree; Tree tree(points.begin(), points.end()); CRefiner ref; - BOOST_FOREACH(typename boost::graph_traits::face_descriptor f, faces(tm)) + BOOST_FOREACH(typename GT::face_descriptor f, faces(tm)) { typename Geom_traits::Point_3 points[3]; - typename boost::graph_traits::halfedge_descriptor hd(halfedge(f,tm)); + typename GT::halfedge_descriptor hd(halfedge(f,tm)); for(int i=0; i<3; ++i) - { - points[i] = get(choose_param(get_param(np, vertex_point), - get_const_property_map(vertex_point, tm)), - target(hd, tm)); - hd = next(hd, tm); + { + points[i] = get(choose_param(get_param(np, vertex_point), + get_const_property_map(vertex_point, tm)), + target(hd, tm)); + hd = next(hd, tm); } ref.add(points[0], points[1], points[2], tree); } @@ -649,19 +666,20 @@ double max_distance_to_triangle_mesh(const PointRange& points, template< class TriangleMesh, class PointRange> double approximate_max_distance_to_point_set(const TriangleMesh& tm, - const PointRange& points, - const double precision) + const PointRange& points, + const double precision) { - return approximate_max_distance_to_point_set(tm, points, precision, parameters::all_default()); + return approximate_max_distance_to_point_set(tm, points, precision, + parameters::all_default()); } template< class Concurrency_tag, class TriangleMesh, class NamedParameters> -double approximate_Hausdorff_distance( const TriangleMesh& tm1, - const TriangleMesh& tm2, - double precision, - const NamedParameters& np) +double approximate_Hausdorff_distance(const TriangleMesh& tm1, + const TriangleMesh& tm2, + const double precision, + const NamedParameters& np) { return approximate_Hausdorff_distance( tm1, tm2, precision, np, parameters::all_default()); @@ -669,9 +687,9 @@ double approximate_Hausdorff_distance( const TriangleMesh& tm1, template< class Concurrency_tag, class TriangleMesh> -double approximate_Hausdorff_distance( const TriangleMesh& tm1, - const TriangleMesh& tm2, - double precision) +double approximate_Hausdorff_distance(const TriangleMesh& tm1, + const TriangleMesh& tm2, + const double precision) { return approximate_Hausdorff_distance( tm1, tm2, precision, @@ -684,9 +702,9 @@ template< class Concurrency_tag, class TriangleMesh, class NamedParameters> double approximate_symmetric_Hausdorff_distance(const TriangleMesh& tm1, - const TriangleMesh& tm2, - double precision, - const NamedParameters& np) + const TriangleMesh& tm2, + const double precision, + const NamedParameters& np) { return approximate_symmetric_Hausdorff_distance( tm1, tm2, precision, np, parameters::all_default()); @@ -695,8 +713,8 @@ double approximate_symmetric_Hausdorff_distance(const TriangleMesh& tm1, template< class Concurrency_tag, class TriangleMesh> double approximate_symmetric_Hausdorff_distance(const TriangleMesh& tm1, - const TriangleMesh& tm2, - double precision) + const TriangleMesh& tm2, + const double precision) { return approximate_symmetric_Hausdorff_distance( tm1, tm2, precision, From 5ee83b555bc7ffb098f54db4d9aba9cde70a05aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 23 Dec 2016 14:45:51 +0100 Subject: [PATCH 48/72] remove Triangle_3 extra requirements --- .../Concepts/PMPDistanceTraits.h | 10 --- .../CGAL/Polygon_mesh_processing/distance.h | 82 ++++++++----------- 2 files changed, 35 insertions(+), 57 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h index aa4191d4e3a..72b1a169532 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h @@ -31,16 +31,6 @@ public: /// 3D vector type typedef unspecified_type Vector_3; - /*! - * 3D triangle type - * It must be constructible from three points and access to its vertices - * must be possible with operator `const Point_3& operator[](int i) const` - * with `0 <= i < 3`. - * In addition, it must provide `BBox_3 bbox() const` that returns a - * bounding box of the triangle. - */ - typedef unspecified_type Triangle_3; - /// @name Functors /// @{ diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 3184e761414..1030dcb3845 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -43,6 +42,8 @@ #include #endif // CGAL_LINKED_WITH_TBB +#include +#include namespace CGAL{ namespace Polygon_mesh_processing { @@ -75,38 +76,39 @@ triangle_grid_sampling( const typename Kernel::Point_3& p0, return out; } -template -OutputIterator -triangle_grid_sampling(const typename Kernel::Triangle_3& t, - double distance, - OutputIterator out) -{ - return triangle_grid_sampling(t[0], t[1], t[2], distance, out); -} - } //end of namespace internal -template +template OutputIterator -sample_triangles(const TriangleRange& triangles, +sample_triangles(const FaceRange& triangles, + const TriangleMesh& tm, + VertexPointMap vpm, double distance, OutputIterator out) { - typedef typename Kernel::Point_3 Point_3; + typedef typename boost::property_traits::reference Point_ref; typedef typename Kernel::Vector_3 Vector_3; - typedef typename Kernel::Triangle_3 Triangle_3; + typedef boost::graph_traits GT; + typedef typename GT::face_descriptor face_descriptor; + typedef typename GT::halfedge_descriptor halfedge_descriptor; - std::set< std::pair > sampled_edges; + boost::unordered_set sampled_edges; + boost::unordered_set endpoints; - // sample edges but skip endpoints - BOOST_FOREACH(const Triangle_3& t, triangles) + BOOST_FOREACH(face_descriptor fd, triangles) { + // sample edges but skip endpoints + halfedge_descriptor hd = halfedge(fd, tm); for (int i=0;i<3; ++i) { - const Point_3& p0=t[i]; - const Point_3& p1=t[(i+1)%3]; - if ( sampled_edges.insert(CGAL::make_sorted_pair(p0, p1)).second ) + if ( sampled_edges.insert(edge(hd, tm)).second ) { + Point_ref p0 = get(vpm, source(hd, tm)); + Point_ref p1 = get(vpm, target(hd, tm)); typename Kernel::Compute_squared_distance_3 squared_distance; const double d_p0p1 = to_double(approximate_sqrt( squared_distance(p0, p1) )); @@ -121,19 +123,18 @@ sample_triangles(const TriangleRange& triangles, typename Kernel::FT(i))); } } + //add endpoints once + if ( endpoints.insert(target(hd, tm)).second ) + *out++=get(vpm, target(hd, tm)); + hd=next(hd, tm); } - } - // sample triangles - BOOST_FOREACH(const Triangle_3& t, triangles) - out=internal::triangle_grid_sampling(t, distance, out); - //add endpoints - std::set< Point_3 > endpoints; - BOOST_FOREACH(const Triangle_3& t, triangles) - for(int i=0; i<3; ++i) - { - if ( endpoints.insert(t[i]).second ) *out++=t[i]; - } + // sample triangles + Point_ref p0 = get(vpm, source(hd, tm)); + Point_ref p1 = get(vpm, target(hd, tm)); + Point_ref p2 = get(vpm, target(next(hd, tm), tm)); + out=internal::triangle_grid_sampling(p0, p1, p2, distance, out); + } return out; } @@ -246,20 +247,7 @@ sample_triangle_mesh(const TriangleMesh& tm, } case GRID: { - std::vector triangles; - BOOST_FOREACH(face_descriptor f, faces(tm)) - { - //create the triangles and store them - typename Geom_traits::Point_3 points[3]; - halfedge_descriptor hd(halfedge(f,tm)); - for(int i=0; i<3; ++i) - { - points[i] = get(pmap, target(hd, tm)); - hd = next(hd, tm); - } - triangles.push_back(typename Geom_traits::Triangle_3(points[0], points[1], points[2])); - } - sample_triangles(triangles, parameter, out); + sample_triangles(faces(tm), tm, pmap, parameter, out); return out; } case MONTE_CARLO: @@ -438,7 +426,6 @@ sample_face(typename boost::graph_traits::face_descriptor f, { case GRID: { - //create the triangles and store them typename Geom_traits::Point_3 points[3]; typename GT::halfedge_descriptor hd(halfedge(f,tm)); for(int i=0; i<3; ++i) @@ -447,7 +434,8 @@ sample_face(typename boost::graph_traits::face_descriptor f, hd = next(hd, tm); } - internal::triangle_grid_sampling(typename Geom_traits::Triangle_3(points[0], points[1], points[2]), parameter, out); + internal::triangle_grid_sampling( + points[0], points[1], points[2], parameter, out); return out; } case MONTE_CARLO: From db6b7e0a00b02f1dd2317bdb4b5a080cd4313f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 23 Dec 2016 16:18:39 +0100 Subject: [PATCH 49/72] add sampling on edges and vertices for a single face (grid case) also add missing overload --- .../CGAL/Polygon_mesh_processing/distance.h | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 1030dcb3845..bbede5609cf 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -428,16 +428,40 @@ sample_face(typename boost::graph_traits::face_descriptor f, { typename Geom_traits::Point_3 points[3]; typename GT::halfedge_descriptor hd(halfedge(f,tm)); + + // add endpoints for(int i=0; i<3; ++i) { points[i] = get(pmap, target(hd, tm)); + *out++= points[i]; hd = next(hd, tm); } - internal::triangle_grid_sampling( + //sample edges + for (int i=0;i<3; ++i) + { + typename Geom_traits::Compute_squared_distance_3 squared_distance; + const double d_p0p1 = to_double(approximate_sqrt( + squared_distance(points[i], points[(i+1)%3]) )); + + const double nb_pts = std::ceil( d_p0p1 / d_p0p1 ); + const typename Geom_traits::Vector_3 step_vec = + typename Geom_traits::Construct_scaled_vector_3()( + typename Geom_traits::Construct_vector_3()(points[i], points[(i+1)%3]), + typename Geom_traits::FT(1)/typename Geom_traits::FT(nb_pts)); + for (double j=1; j( points[0], points[1], points[2], parameter, out); - return out; } + case MONTE_CARLO: nb_points = (std::max)(nb_points, std::size_t(1)); case RANDOM_UNIFORM: @@ -458,6 +482,17 @@ sample_face(typename boost::graph_traits::face_descriptor f, return out; } +template +OutputIterator +sample_face(typename boost::graph_traits::face_descriptor f, + const TriangleMesh& tm, + double parameter, + OutputIterator out, + Sampling_method method = RANDOM_UNIFORM) +{ + return sample_face(f, tm, parameter, out, parameters::all_default(), method); +} + /** * \ingroup PMP_distance_grp * computes the approximate Hausdorff distance from `tm1` to `tm2` by From e40a84d2f9e8959ed488be243d2f24e9823ba72e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 23 Dec 2016 16:30:37 +0100 Subject: [PATCH 50/72] use Real_timer to avoid since Timer measure cpu time --- .../test/Polygon_mesh_processing/test_pmp_distance.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp index 644acc6d2b7..7d1e41d572a 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include @@ -272,7 +272,7 @@ int main(int, char** argv) std::cout << "First mesh has " << num_faces(m1) << " faces\n"; std::cout << "Second mesh has " << num_faces(m2) << " faces\n"; - CGAL::Timer time; + CGAL::Real_timer time; time.start(); std::cout << "Distance between meshes (parallel) " << CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance(m1,m2,40000) @@ -294,5 +294,3 @@ int main(int, char** argv) return 0; } - - From 468abf708e4c925bac37d634e302fd73ebe580cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 27 Dec 2016 11:49:25 +0100 Subject: [PATCH 51/72] copy-paste random points on 2D segment in 3D --- .../doc/Generator/CGAL/point_generators_3.h | 75 +++++++++++++++++-- Generator/include/CGAL/point_generators_3.h | 49 ++++++++++++ Generator/test/Generator/test_generators.cpp | 7 +- Installation/changes.html | 1 + 4 files changed, 122 insertions(+), 10 deletions(-) diff --git a/Generator/doc/Generator/CGAL/point_generators_3.h b/Generator/doc/Generator/CGAL/point_generators_3.h index b709e4da72d..36dd1b5f0e2 100644 --- a/Generator/doc/Generator/CGAL/point_generators_3.h +++ b/Generator/doc/Generator/CGAL/point_generators_3.h @@ -248,7 +248,71 @@ get_default_random()); /// @} }; /* end Random_points_in_triangle_3 */ - +} /* end namespace CGAL */ + +namespace CGAL{ +/*! + +The class `Random_points_on_segment_3` is an input iterator creating points uniformly +distributed on a segment. The default `Creator` is +`Creator_uniform_3::Kernel::RT,Point_3>`. + +\cgalModels `InputIterator` +\cgalModels `PointGenerator` + +\sa `CGAL::cpp11::copy_n()` +\sa `CGAL::Counting_iterator` +\sa `std::random_shuffle` + +*/ +template< typename Point_3, typename Creator > +class Random_points_on_segment_3 { +public: + +/// \name Types +/// @{ + +/*! + +*/ +typedef std::input_iterator_tag iterator_category; + +/*! + +*/ +typedef Point_3 value_type; + +/*! + +*/ +typedef std::ptrdiff_t difference_type; + +/*! + +*/ +typedef const Point_3* pointer; + +/*! + +*/ +typedef const Point_3& reference; + + +/*! +creates an input iterator `g` generating points of type `Point_3` uniformly +distributed on the segment from \f$ p\f$ to \f$ q\f$ (excluding \f$ q\f$), +i.e.\ \f$ *g == (1-\lambda)\, p + \lambda q\f$ where \f$ 0 \le\lambda< 1\f$. +A single random number is needed from `rnd` for each point. +The expressions `to_double(p.x())`, `to_double(p.y())`, and `to_double(p.z())` must result +in the respective `double` representation of the coordinates of \f$ p\f$, and similarly for \f$ q\f$. +*/ +Random_points_on_segment_3( const Point_3& p, const Point_3& q, +Random& rnd = get_default_random()); + +/// @} + +}; /* end Random_points_on_segment_3 */ + } /* end namespace CGAL */ namespace CGAL { @@ -264,7 +328,6 @@ distributed inside a tetrahedron. The default `Creator` is \sa `CGAL::cpp11::copy_n()` \sa `CGAL::Counting_iterator` -\sa `CGAL::Random_points_in_disc_2` \sa `CGAL::Random_points_in_cube_3` \sa `CGAL::Random_points_in_triangle_3` \sa `CGAL::Random_points_on_sphere_3` @@ -344,13 +407,9 @@ The triangle range must be valid and unchanged while the iterator is used. \sa `CGAL::cpp11::copy_n()` \sa `CGAL::Counting_iterator` -\sa `CGAL::Points_on_segment_2` -\sa `CGAL::Random_points_in_disc_2` -\sa `CGAL::Random_points_on_segment_2` -\sa `CGAL::Random_points_on_square_2` \sa `CGAL::Random_points_in_cube_3` -\sa `CGAL::Random_points_in_triangle_3` -\sa `CGAL::Random_points_in_tetrahedron_3` +\sa `CGAL::Random_points_in_triangle_3` +\sa `CGAL::Random_points_in_tetrahedron_3` \sa `CGAL::Random_points_in_triangle_mesh_3` \sa `CGAL::Random_points_in_tetrahedral_mesh_boundary_3` \sa `CGAL::Random_points_in_tetrahedral_mesh_3` diff --git a/Generator/include/CGAL/point_generators_3.h b/Generator/include/CGAL/point_generators_3.h index 9568e2a4709..0960a3a13e2 100644 --- a/Generator/include/CGAL/point_generators_3.h +++ b/Generator/include/CGAL/point_generators_3.h @@ -251,6 +251,55 @@ void Random_points_in_triangle_3::generate_point() { } template < class P, class Creator = + Creator_uniform_3::Kernel::RT,P> > +class Random_points_on_segment_3 : public Random_generator_base

    { + P _p; + P _q; + void generate_point(); +public: + typedef Random_points_on_segment_3 This; + Random_points_on_segment_3() {} + Random_points_on_segment_3( const P& p, + const P& q, + Random& rnd = CGAL::get_default_random()) + // g is an input iterator creating points of type `P' uniformly + // distributed on the segment from p to q except q, i.e. `*g' == + // \lambda p + (1-\lambda)\, q where 0 <= \lambda < 1 . A single + // random number is needed from `rnd' for each point. + : Random_generator_base

    ( (std::max)( (std::max)( (std::max)(to_double(p.x()), to_double(q.x())), + (std::max)(to_double(p.y()), to_double(q.y()))), + (std::max)(to_double(p.z()), to_double(q.z()))), + rnd) , _p(p), _q(q) + { + generate_point(); + } + const P& source() const { return _p; } + const P& target() const { return _q; } + This& operator++() { + generate_point(); + return *this; + } + This operator++(int) { + This tmp = *this; + ++(*this); + return tmp; + } +}; + +template < class P, class Creator > +void +Random_points_on_segment_3:: +generate_point() { + typedef typename Creator::argument_type T; + double la = this->_rnd.get_double(); + double mu = 1.0 - la; + Creator creator; + this->d_item = creator(T(mu * to_double(_p.x()) + la * to_double(_q.x())), + T(mu * to_double(_p.y()) + la * to_double(_q.y())), + T(mu * to_double(_p.z()) + la * to_double(_q.z()))); +} + +template < class P, class Creator = Creator_uniform_3::Kernel::RT,P> > class Random_points_in_tetrahedron_3 : public Random_generator_base

    { P _p,_q,_r,_s; diff --git a/Generator/test/Generator/test_generators.cpp b/Generator/test/Generator/test_generators.cpp index 9c4788ee9e3..e2c2598b5a8 100644 --- a/Generator/test/Generator/test_generators.cpp +++ b/Generator/test/Generator/test_generators.cpp @@ -115,13 +115,16 @@ void test_point_generators_3() { /* Create test point set. */ std::vector points; - points.reserve(500); + points.reserve(600); Random_points_in_sphere_3 g1( 100.0); CGAL::cpp11::copy_n( g1, 100, std::back_inserter(points)); Random_points_on_sphere_3 g2( 100.0); Random_points_in_cube_3 g3( 100.0); + Random_points_on_segment_3 g4( Point_3(-100,-100, -100), + Point_3( 100, 100, 100)); CGAL::cpp11::copy_n( g2, 100, std::back_inserter(points)); CGAL::cpp11::copy_n( g3, 100, std::back_inserter(points)); + CGAL::cpp11::copy_n( g4, 100, std::back_inserter(points)); points_on_cube_grid_3( 50.0, (std::size_t)1, std::back_inserter(points), Creator()); points_on_cube_grid_3( 50.0, (std::size_t)2, @@ -133,7 +136,7 @@ void test_point_generators_3() { random_selection( points.begin(), points.end(), 100, std::back_inserter(points)); - assert( points.size() == 500); + assert( points.size() == 600); for ( std::vector::iterator i = points.begin(); i != points.end(); i++){ assert( i->x() <= 100); diff --git a/Installation/changes.html b/Installation/changes.html index 31f29940112..ff0918c780f 100644 --- a/Installation/changes.html +++ b/Installation/changes.html @@ -286,6 +286,7 @@ and src/ directories).

  • in a tetrahedral mesh (CGAL::Random_points_in_tetrahedral_mesh_3),
  • in a 2D triangle mesh (CGAL::Random_points_in_triangle_mesh_2),
  • in a range of 2D or 3D triangles (CGAL::Random_points_in_triangles_3 and CGAL::Random_points_in_triangles_2).
  • +
  • on a 3D segment (CGAL::Random_points_on_segment_3).
From 3e018b4d1afa6d12c9e8c1592d63d5bc2ab8f6c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 27 Dec 2016 11:51:58 +0100 Subject: [PATCH 52/72] use snippet to avoid inlining a whole example while we care only of a tiny part --- .../poisson_reconstruction_example.cpp | 2 ++ .../Polygon_mesh_processing.txt | 16 +++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp index a296f9f2a81..664f4de91c8 100644 --- a/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp +++ b/Poisson_surface_reconstruction_3/examples/Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp @@ -107,12 +107,14 @@ int main(void) out << output_mesh; + /// [PMP_distance_snippet] // computes the approximation error of the reconstruction double max_dist = CGAL::Polygon_mesh_processing::approximate_max_distance_to_point_set(output_mesh, points, 4000); std::cout << "Max distance to point_set: " << max_dist << std::endl; + /// [PMP_distance_snippet] return EXIT_SUCCESS; } 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 15271fbd503..974d34d9073 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 @@ -481,9 +481,9 @@ the propagation of a connected component index to cross it. \cgalExample{Polygon_mesh_processing/connected_components_example.cpp} -\section PMPDistance Approximated Hausdorff Distance +\section PMPDistance Approximate Hausdorff Distance -This package provides methods to compute approximated Hausdorff distances. +This package provides methods to compute approximate Hausdorff distances. These distances can be computed between two meshes, a mesh and a point set, or a point set and a mesh. These computations are performed with : - `CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance()` @@ -494,16 +494,18 @@ These computations are performed with : - `CGAL::Polygon_mesh_processing::sample_face()` \subsection AHDExample Approximate Hausdorff Distance Example -In the following example, a mesh is isotropically remeshed and the approximated distance between the input and the output is computed. +In the following example, a mesh is isotropically remeshed and the approximate distance between the input and the output is computed. \cgalExample{Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp} \subsection PoissonDistanceExample Max Distance Between Point Set and Surface Example -In the following example, a triangulated surface mesh is constructed from a point set using the -\link PkgPoissonSurfaceReconstructionSummary Poisson reconstruction algorithm \endlink, and the distance between the point set and the reconstructed surface is computed. - -\cgalExample{Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp} +In \ref Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp, +a triangulated surface mesh is constructed from a point set using the +\link PkgPoissonSurfaceReconstructionSummary Poisson reconstruction algorithm \endlink, +and the distance between the point set and the reconstructed surface is computed +with the following code: +\snippet Poisson_surface_reconstruction_3/poisson_reconstruction_example.cpp PMP_distance_snippet \section PMPHistory Implementation History From 1d1b0301fe4090924e02802e49ecfb2dcb5e39c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 27 Dec 2016 16:30:08 +0100 Subject: [PATCH 53/72] add a random generator on edges of a mesh --- Generator/include/CGAL/point_generators_3.h | 84 +++++++++++++++++++-- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/Generator/include/CGAL/point_generators_3.h b/Generator/include/CGAL/point_generators_3.h index 0960a3a13e2..31d6dee097b 100644 --- a/Generator/include/CGAL/point_generators_3.h +++ b/Generator/include/CGAL/point_generators_3.h @@ -271,8 +271,24 @@ public: (std::max)(to_double(p.z()), to_double(q.z()))), rnd) , _p(p), _q(q) { - generate_point(); + generate_point(); } + + template + Random_points_on_segment_3( const Segment_3& s, + Random& rnd = CGAL::get_default_random()) + // g is an input iterator creating points of type `P' uniformly + // distributed on the segment from p to q except q, i.e. `*g' == + // \lambda p + (1-\lambda)\, q where 0 <= \lambda < 1 . A single + // random number is needed from `rnd' for each point. + : Random_generator_base

( (std::max)( (std::max)( (std::max)(to_double(s[0].x()), to_double(s[1].x())), + (std::max)(to_double(s[0].y()), to_double(s[1].y()))), + (std::max)(to_double(s[0].z()), to_double(s[1].z()))), + rnd) , _p(s[0]), _q(s[1]) + { + generate_point(); + } + const P& source() const { return _p; } const P& target() const { return _q; } This& operator++() { @@ -373,13 +389,11 @@ struct Random_points_in_triangle_mesh_3 typedef typename boost::property_traits::value_type P; typedef Generic_random_point_generator< typename boost::graph_traits ::face_descriptor , - CGAL::Property_map_to_unary_function >, Random_points_in_triangle_3 , P> Base; typedef typename CGAL::Triangle_from_face_descriptor_map< - TriangleMesh,VertexPointMap> Pmap; - typedef typename CGAL::Triangle_from_face_descriptor_map< - TriangleMesh,VertexPointMap> Object_from_id_map; + TriangleMesh,VertexPointMap> Object_from_id; typedef typename boost::graph_traits::face_descriptor Id; typedef P result_type; typedef Random_points_in_triangle_mesh_3< TriangleMesh, VertexPointMap, Creator> This; @@ -387,14 +401,14 @@ struct Random_points_in_triangle_mesh_3 Random_points_in_triangle_mesh_3( const TriangleMesh& mesh,Random& rnd = get_default_random()) : Base( faces(mesh), - CGAL::Property_map_to_unary_function(Pmap(&mesh, get(vertex_point, mesh))), + CGAL::Property_map_to_unary_function(Object_from_id(&mesh, get(vertex_point, mesh))), internal::Apply_approx_sqrt::Kernel::Compute_squared_area_3>(), rnd ) { } Random_points_in_triangle_mesh_3( const TriangleMesh& mesh, VertexPointMap vpm, Random& rnd = get_default_random()) : Base( faces(mesh), - CGAL::Property_map_to_unary_function(Pmap(&mesh, vpm)), + CGAL::Property_map_to_unary_function(Object_from_id(&mesh, vpm)), internal::Apply_approx_sqrt::Kernel::Compute_squared_area_3>(), rnd ) { @@ -414,6 +428,62 @@ struct Random_points_in_triangle_mesh_3 } }; +template ::const_type, + class Creator = Creator_uniform_3< + typename Kernel_traits< typename boost::property_traits::value_type >::Kernel::RT, + typename boost::property_traits::value_type > +> +struct Random_points_on_edge_list_graph_3 + : public Generic_random_point_generator< + typename boost::graph_traits ::edge_descriptor, + CGAL::Property_map_to_unary_function >, + Random_points_on_segment_3::value_type, Creator>, + typename boost::property_traits::value_type> +{ + typedef typename boost::property_traits::value_type P; + typedef Generic_random_point_generator< + typename boost::graph_traits ::edge_descriptor, + CGAL::Property_map_to_unary_function >, + Random_points_on_segment_3 , P> Base; + typedef typename CGAL::Segment_from_edge_descriptor_map< + EdgeListGraph,VertexPointMap> Object_from_id; + typedef typename boost::graph_traits::edge_descriptor Id; + typedef P result_type; + typedef Random_points_on_edge_list_graph_3< EdgeListGraph, VertexPointMap, Creator> This; + + Random_points_on_edge_list_graph_3( const EdgeListGraph& mesh,Random& rnd = get_default_random()) + : Base( edges(mesh), + CGAL::Property_map_to_unary_function(Object_from_id(&mesh, get(vertex_point, mesh))), + internal::Apply_approx_sqrt::Kernel::Compute_squared_length_3>(), + rnd ) + { + } + Random_points_on_edge_list_graph_3( const EdgeListGraph& mesh, VertexPointMap vpm, Random& rnd = get_default_random()) + : Base( edges(mesh), + CGAL::Property_map_to_unary_function(Object_from_id(&mesh, vpm)), + internal::Apply_approx_sqrt::Kernel::Compute_squared_length_3>(), + rnd ) + { + } + This& operator++() { + Base::generate_point(); + return *this; + } + This operator++(int) { + This tmp = *this; + ++(*this); + return tmp; + } + double mesh_length() const + { + return this->sum_of_weights(); + } +}; + namespace internal { From 94b2720fbd2e1ed3ce0f9a676ecfd6ff25c9d2cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 27 Dec 2016 16:41:34 +0100 Subject: [PATCH 54/72] use name parameters for selecting the sampling algorithm A dedicated sampling of edges is also introduced as well as an option to use input vertices to evaluate the distance. --- Installation/changes.html | 2 +- .../NamedParameters.txt | 106 ++++ .../Polygon_mesh_processing.txt | 1 - .../hausdorff_distance_remeshing_example.cpp | 3 +- .../CGAL/Polygon_mesh_processing/distance.h | 541 ++++++++++-------- .../internal/named_function_params.h | 248 +++++++- .../test_pmp_distance.cpp | 26 +- 7 files changed, 659 insertions(+), 268 deletions(-) diff --git a/Installation/changes.html b/Installation/changes.html index ff0918c780f..1022444abe2 100644 --- a/Installation/changes.html +++ b/Installation/changes.html @@ -260,7 +260,7 @@ and src/ directories). sample_triangle_mesh(), approximated_Hausdorff_distance(), approximated_symmetric_Hausdorff_distance(), max_distance_to_triangle_mesh(), - max_distance_to_point_set() and the enum Sampling_method + max_distance_to_point_set(). diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index 2ce6592baef..dd75dc87abe 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -210,7 +210,113 @@ set to `true`, this parameter is ignored. \n \b Type : `bool` \n \b Default value is `true` +\cgalNPEnd +\cgalNPBegin{use_random_uniform_sampling} \anchor PMP_use_random_uniform_sampling +Parameter used in `sample_triangle_mesh()` to indicate if points should be picked +in a random uniform way. +\n +\b Type : `bool` \n +\b Default value is `true` +\cgalNPEnd + +\cgalNPBegin{use_grid_sampling} \anchor PMP_use_grid_sampling +Parameter used in `sample_triangle_mesh()` to indicate if points should be picked +in on a grid in each face. +\n +\b Type : `bool` \n +\b Default value is `false` +\cgalNPEnd + +\cgalNPBegin{use_monte_carlo_sampling} \anchor PMP_use_monte_carlo_sampling +Parameter used in `sample_triangle_mesh()` to indicate if points should be picked +using a Monte-Carlo approach. +\n +\b Type : `bool` \n +\b Default value is `false` +\cgalNPEnd + +\cgalNPBegin{sample_edges} \anchor PMP_sample_edges +Parameter used in `sample_triangle_mesh()` to indicate if a dedicated sampling +of edges should be done. +\n +\b Type : `bool` \n +\b Default value is `true` +\cgalNPEnd + +\cgalNPBegin{sample_vertices} \anchor PMP_sample_vertices +Parameter used in `sample_triangle_mesh()` to indicate if triangle vertices should +be copied in the output iterator. +\n +\b Type : `bool` \n +\b Default value is `true` +\cgalNPEnd + +\cgalNPBegin{sample_faces} \anchor PMP_sample_faces +Parameter used in `sample_triangle_mesh()` to indicate if the interior of faces +should be considered for the sampling. +\n +\b Type : `bool` \n +\b Default value is `true` +\cgalNPEnd + +\cgalNPBegin{set_number_of_points_on_faces} \anchor PMP_set_number_of_points_on_faces +Parameter used in `sample_triangle_mesh()` to set the number of points picked +using the random uniform method on faces. +\n +\b Type : `std::size_t` \n +\b Default value is `0` +\cgalNPEnd + +\cgalNPBegin{set_number_of_points_on_edges} \anchor PMP_set_number_of_points_on_edges +Parameter used in `sample_triangle_mesh()` to set the number of points picked +using the random uniform method on edges. +\n +\b Type : `std::size_t` \n +\b Default value is `0` +\cgalNPEnd + +\cgalNPBegin{set_number_of_points_per_face} \anchor PMP_set_number_of_points_per_face +Parameter used in `sample_triangle_mesh()` to set the number of points picked +per face using the Monte-Carlo method. +\n +\b Type : `std::size_t` \n +\b Default value is `0` +\cgalNPEnd + +\cgalNPBegin{set_number_of_points_per_edge} \anchor PMP_set_number_of_points_per_edge +Parameter used in `sample_triangle_mesh()` to set the number of points picked +per edge using the Monte-Carlo method. +\n +\b Type : `std::size_t` \n +\b Default value is `0` +\cgalNPEnd + +\cgalNPBegin{set_grid_spacing} \anchor PMP_set_grid_spacing +Parameter used in `sample_triangle_mesh()` to set the grid spacing when using +the grid sampling method. +\n +\b Type : `double` \n +\b Default value is `0` +\cgalNPEnd + +\cgalNPBegin{set_number_of_points_per_squared_area_unit} \anchor PMP_set_number_of_points_per_squared_area_unit +Parameter used in `sample_triangle_mesh()` to set the number of points per +squared area unit to be picked up in faces for the random uniform sampling and +Monte-Carlo methods. +\n +\b Type : `double` \n +\b Default value is `0` +\cgalNPEnd + +\cgalNPBegin{set_number_of_points_per_distance_unit} \anchor PMP_set_number_of_points_per_distance_unit +Parameter used in `sample_triangle_mesh()` to set the number of points per +distance unit to be picked up on edges for the random uniform sampling and +Monte-Carlo methods. +\n +\b Type : `double` \n +\b Default value is `0` +\cgalNPEnd \cgalNPTableEnd 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 974d34d9073..36bc5e4f051 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 @@ -491,7 +491,6 @@ These computations are performed with : - `CGAL::Polygon_mesh_processing::approximate_max_distance_to_point_set()` - `CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh()` - `CGAL::Polygon_mesh_processing::sample_triangle_mesh()` -- `CGAL::Polygon_mesh_processing::sample_face()` \subsection AHDExample Approximate Hausdorff Distance Example In the following example, a mesh is isotropically remeshed and the approximate distance between the input and the output is computed. diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp index f2b17009cfe..d85ef766c2f 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp @@ -14,6 +14,7 @@ typedef CGAL::Exact_predicates_inexact_constructions_kernel K; typedef K::Point_3 Point; typedef CGAL::Surface_mesh Mesh; +namespace PMP = CGAL::Polygon_mesh_processing; int main() { @@ -28,6 +29,6 @@ int main() std::cout << "Approximated Hausdorff distance: " << CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance - (tm1, tm2, 4000) + (tm1, tm2, PMP::parameters::set_number_of_points_per_squared_area_unit(4000)) << std::endl; } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index bbede5609cf..2e43d70dee1 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -88,7 +88,10 @@ sample_triangles(const FaceRange& triangles, const TriangleMesh& tm, VertexPointMap vpm, double distance, - OutputIterator out) + OutputIterator out, + bool sample_faces, + bool sample_edges, + bool add_vertices) { typedef typename boost::property_traits::reference Point_ref; typedef typename Kernel::Vector_3 Vector_3; @@ -105,7 +108,7 @@ sample_triangles(const FaceRange& triangles, halfedge_descriptor hd = halfedge(fd, tm); for (int i=0;i<3; ++i) { - if ( sampled_edges.insert(edge(hd, tm)).second ) + if (sample_edges && sampled_edges.insert(edge(hd, tm)).second ) { Point_ref p0 = get(vpm, source(hd, tm)); Point_ref p1 = get(vpm, target(hd, tm)); @@ -124,16 +127,19 @@ sample_triangles(const FaceRange& triangles, } } //add endpoints once - if ( endpoints.insert(target(hd, tm)).second ) + if ( add_vertices && endpoints.insert(target(hd, tm)).second ) *out++=get(vpm, target(hd, tm)); hd=next(hd, tm); } // sample triangles - Point_ref p0 = get(vpm, source(hd, tm)); - Point_ref p1 = get(vpm, target(hd, tm)); - Point_ref p2 = get(vpm, target(next(hd, tm), tm)); - out=internal::triangle_grid_sampling(p0, p1, p2, distance, out); + if (sample_faces) + { + Point_ref p0 = get(vpm, source(hd, tm)); + Point_ref p1 = get(vpm, target(hd, tm)); + Point_ref p2 = get(vpm, target(next(hd, tm), tm)); + out=internal::triangle_grid_sampling(p0, p1, p2, distance, out); + } } return out; } @@ -176,90 +182,223 @@ struct Distance_computation{ }; #endif -/** - * \ingroup PMP_distance_grp - * @brief enum used to select the sampling method in the function `sample_triangle_mesh()` - */ -enum Sampling_method{ - RANDOM_UNIFORM =0, - GRID, - MONTE_CARLO -}; /** \ingroup PMP_distance_grp - * generates points taken on `tm` in a way depending on `method` and - * outputs them to `out`. + * generates points taken on `tm` and outputs them to `out`, the sampling method + * is selected using named parameters. * @tparam TriangleMesh a model of the concept `FaceListGraph` * @tparam OutputIterator a model of `OutputIterator` * holding objects of the same point type as * the value type of the internal vertex point map of `tm` - * @tparam Sampling_method defines the sampling method. Possible values are: - * - `RANDOM_UNIFORM`: points are generated in a random and uniform way, depending on the area of each triangle. - * - `GRID`: points are generated on a grid in each triangle, with a minimum of one point per triangle. - * - `MONTE_CARLO`: points are generated randomly in each triangle. The number of points per triangle is proportional to the triangle area with a minimum of 1. * * @param tm the triangle mesh that will be sampled - * @param parameter depends on `method`: - * - `RANDOM_UNIFORM` and `MONTE_CARLO`: the number of points per squared area unit - * - `GRID`: the distance between two consecutive points in the grid - * * @param out output iterator to be filled with sampled points * @param np an optional sequence of \ref namedparameters among the ones listed below - * @param method defines the sampling method * * \cgalNamedParamsBegin - * \cgalParamBegin{vertex_point_map} - * the property map with the points associated to the vertices of `tm`. If this parameter is omitted, - * an internal property map for `CGAL::vertex_point_t` should be available for `TriangleMesh` \cgalParamEnd - * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `PMPDistanceTraits`\cgalParamEnd + * \cgalParamBegin{vertex_point_map} the property map with the points + * associated to the vertices of `tm`. If this parameter is omitted, + * an internal property map for `CGAL::vertex_point_t` + * should be available for `TriangleMesh`. + * \cgalParamEnd + * \cgalParamBegin{geom_traits} a model of `PMPDistanceTraits`. \cgalParamEnd + * \cgalParamBegin{use_random_uniform_sampling} + * if `true` is passed (the default), points are generated in a random + * and uniform way on the surface of `tm`, and/or on edges of `tm`. + * For faces, the number of sample points is the value passed to the named + * parameter `set_number_of_points_on_faces()`. If not set, + * the value passed to the named parameter `set_number_of_points_per_squared_area_unit()` + * is multiplied by the area of `tm` to get the number of sample points. + * If none of these parameters is set, the number of points sampled is `num_vertices(tm)`. + * For edges, the number of the number of sample points is the value passed to the named + * parameter `set_number_of_points_on_edges()`. If not set, + * the value passed to the named parameter `set_number_of_points_per_distance_unit()` + * is multiplied by the sum of the length of edges of `tm` to get the number of sample points. + * If none of these parameters is set, the number of points sampled is `num_vertices(tm)`. + * \cgalParamEnd + * \cgalParamBegin{set_number_of_points_on_faces} an unsigned integral value used + * for the random sampling method as the number of points to pick on the surface. + * \cgalParamEnd + * \cgalParamBegin{set_number_of_points_on_edges} an unsigned integral value used + * for the random sampling method as the number of points to pick exclusively + * on edges. + * \cgalParamEnd + * \cgalParamBegin{set_number_of_points_per_squared_area_unit} a double value + * used for the random sampling and the Monte Carlo sampling methods to + * repectively determine the total number of points inside faces + * and the number of points per face. + * \cgalParamEnd + * \cgalParamBegin{set_number_of_points_per_distance_unit} a double value + * used for the random sampling and the Monte Carlo sampling methods to + * repectively determine the total number of points on edges and the + * number of points per edge. + * \cgalParamEnd + * \cgalParamBegin{use_grid_sampling} + * if `true` is passed, points are generated on a grid in each triangle, + * with a minimum of one point per triangle. The distance between + * two consecutive points in the grid is that of the length of the + * smallest non-null edge of `tm` or the value passed to + * the named parameter `set_grid_spacing()`. Edges are also split using the + * same distance, if requested. + * \cgalParamEnd + * \cgalParamBegin{set_grid_spacing} a double value used as the grid spacing + * for the grid sampling method. + * \cgalParamEnd + * \cgalParamBegin{use_monte_carlo_sampling} + * if `true` is passed, points are generated randomly in each triangle and/or + * on each edge. + * For faces, the number of points per triangle is the value passed to the named + * parameter `set_number_of_points_per_face()`. If not set, the value passed + * to the named parameter `set_number_of_points_per_squared_area_unit()` is + * used to pick a number of points per face proportional to the triangle + * area with a minimum of one point per face. If none of these parameters + * is set, 2 divided by the square of the length of the smallest non-null + * edge of `tm` is used as if it was passed to + * `set_number_of_points_per_squared_area_unit()`. + * For edges, the number of points per edge is the value passed to the named + * parameter `set_number_of_points_per_edge()`. If not set, the value passed + * to the named parameter `set_number_of_points_per_distance_unit()` is + * used to pick a number of points per edge proportional to the length of + * the edge with a minimum of one point per face. If none of these parameters + * is set, 1 divided by the length of the smallest non-null edge of `tm` + * is used as if it was passed to `set_number_of_points_per_distance_unit()`. + * \cgalParamEnd + * \cgalParamBegin{set_number_of_points_per_face} an unsigned integral value + * used by the Monte-Carlo sampling method as the number of points per face + * to pick. + * \cgalParamEnd + * \cgalParamBegin{set_number_of_points_per_edge} an unsigned integral value + * used by the Monte-Carlo sampling method as the number of points per edge + * to pick. + * \cgalParamEnd + * \cgalParamBegin{sample_vertices} if `true` is passed (default value), + * vertices of `tm` are put into `out`.\cgalParamEnd + * \cgalParamBegin{sample_edges} if `true` is passed (default value), + * edges of `tm` are sampled using a dedicated algorithm.\cgalParamEnd + * \cgalParamBegin{sample_faces} if `true` is passed (default value), + * faces of `tm` are sampled using a dedicated algorithm.\cgalParamEnd * \cgalNamedParamsEnd */ template OutputIterator sample_triangle_mesh(const TriangleMesh& tm, - double parameter, OutputIterator out, - NamedParameters np, - Sampling_method method = RANDOM_UNIFORM) + NamedParameters np) { - typedef typename GetGeomTraits::type Geom_traits; + typedef typename GetGeomTraits::type Geom_traits; - typedef typename GetVertexPointMap::const_type Vpm; + typedef typename GetVertexPointMap::const_type Vpm; - typedef boost::graph_traits GT; - typedef typename GT::face_descriptor face_descriptor; - typedef typename GT::halfedge_descriptor halfedge_descriptor; + typedef boost::graph_traits GT; + typedef typename GT::face_descriptor face_descriptor; + typedef typename GT::halfedge_descriptor halfedge_descriptor; + typedef typename GT::edge_descriptor edge_descriptor; - Vpm pmap = choose_param(get_param(np, vertex_point), - get_const_property_map(vertex_point, tm)); - typedef Creator_uniform_3 Creator; - switch(method) + Vpm pmap = choose_param(get_param(np, vertex_point), + get_const_property_map(vertex_point, tm)); + typedef Creator_uniform_3 Creator; + + Geom_traits geomtraits = choose_param(get_param(np, geom_traits), Geom_traits()); + + using boost::choose_param; + using boost::get_param; + using boost::is_default_param; + + bool use_rs = choose_param(get_param(np, random_uniform_sampling), true); + bool use_gs = choose_param(get_param(np, grid_sampling), false); + bool use_ms = choose_param(get_param(np, monte_carlo_sampling), false); + + if (use_gs || use_ms) + if (is_default_param(get_param(np, random_uniform_sampling))) + use_rs=false; + + bool smpl_vrtcs = choose_param(get_param(np, do_sample_vertices), true); + bool smpl_dgs = choose_param(get_param(np, do_sample_edges), true); + bool smpl_fcs = choose_param(get_param(np, do_sample_faces), true); + + double nb_pts_sq_a_u = choose_param(get_param(np, nb_points_per_sq_area_unit), 0.); + double nb_pts_l_u = choose_param(get_param(np, nb_points_per_distance_unit), 0.); + + // sample vertices + if (smpl_vrtcs) { - case RANDOM_UNIFORM: + Property_map_to_unary_function unary(pmap); + out = std::copy( + boost::make_transform_iterator(boost::begin(vertices(tm)), unary), + boost::make_transform_iterator(boost::end(vertices(tm)), unary), + out); + } + + // grid sampling + if (use_gs) + { + double grid_spacing_ = choose_param(get_param(np, grid_spacing), 0.); + if (grid_spacing_==0.) { - std::size_t nb_points = static_cast( std::ceil( to_double( - area(tm, parameters::geom_traits(Geom_traits())))*parameter) ); - Random_points_in_triangle_mesh_3 g(tm, pmap); - CGAL::cpp11::copy_n(g, nb_points, out); - return out; + // set grid spacing to the shortest edge length + double grid_spacing_ = (std::numeric_limits::max)(); + typedef typename boost::graph_traits + ::edge_descriptor edge_descriptor; + BOOST_FOREACH(edge_descriptor ed, edges(tm)) + { + double el = std::sqrt( + to_double( typename Geom_traits::Compute_squared_distance_3()( + get(pmap, source(ed, tm)), get(pmap, target(ed, tm)) ))); + if (el > 0 && el < grid_spacing_) + grid_spacing_ = el; + } } - case GRID: + out=sample_triangles( + faces(tm), tm, pmap, grid_spacing_, out,smpl_fcs, smpl_dgs, false); + } + + // monte carlo sampling + if (use_ms) + { + typename Geom_traits::Compute_squared_distance_3 squared_distance; + double min_edge_length = (std::numeric_limits::max)(); + + std::size_t nb_points_per_face = + choose_param(get_param(np, number_of_points_per_face), 0); + std::size_t nb_points_per_edge = + choose_param(get_param(np, number_of_points_per_edge), 0); + + if ((nb_points_per_face == 0 && nb_pts_sq_a_u ==0.) || + (nb_points_per_edge == 0 && nb_pts_l_u ==0.) ) { - sample_triangles(faces(tm), tm, pmap, parameter, out); - return out; + typedef typename boost::graph_traits + ::edge_descriptor edge_descriptor; + BOOST_FOREACH(edge_descriptor ed, edges(tm)) + { + double el = std::sqrt( + to_double( squared_distance(get(pmap, source(ed, tm)), + get(pmap, target(ed, tm)) ))); + if (min_edge_length > 0 && el < min_edge_length) + min_edge_length = el; + } } - case MONTE_CARLO: + + // sample faces + if (smpl_fcs) { + // set default value + if (nb_points_per_face == 0 && nb_pts_sq_a_u ==0.) + nb_pts_sq_a_u = 2. / CGAL::square(min_edge_length); + BOOST_FOREACH(face_descriptor f, faces(tm)) { - std::size_t nb_points = (std::max)( - static_cast( - std::ceil(to_double( - face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter)) - ,std::size_t(1)); - //create the triangles and store them + std::size_t nb_points = nb_points_per_face; + if (nb_points == 0) + { + nb_points = (std::max)( + static_cast( + std::ceil(to_double( + face_area(f,tm,parameters::geom_traits(geomtraits)))*nb_pts_sq_a_u)) + ,std::size_t(1)); + } + // extract triangle face points typename Geom_traits::Point_3 points[3]; halfedge_descriptor hd(halfedge(f,tm)); for(int i=0; i<3; ++i) @@ -267,28 +406,81 @@ sample_triangle_mesh(const TriangleMesh& tm, points[i] = get(pmap, target(hd, tm)); hd = next(hd, tm); } + // sample the triangle face Random_points_in_triangle_3 g(points[0], points[1], points[2]); - CGAL::cpp11::copy_n(g, nb_points, out); + out=CGAL::cpp11::copy_n(g, nb_points, out); + } + } + // sample edges + if (smpl_dgs) + { + if (nb_points_per_edge == 0 && nb_pts_l_u == 0) + nb_pts_l_u = 1. / min_edge_length; + BOOST_FOREACH(edge_descriptor ed, edges(tm)) + { + std::size_t nb_points = nb_points_per_edge; + if (nb_points == 0) + { + nb_points = (std::max)( + static_cast( std::ceil( std::sqrt( to_double( + squared_distance(get(pmap, source(ed, tm)), + get(pmap, target(ed, tm)) )) )*nb_pts_l_u ) ), + std::size_t(1)); + } + // now do the sampling of the edge + Random_points_on_segment_3 + g(get(pmap, source(ed,tm)), get(pmap, target(ed,tm))); + out=CGAL::cpp11::copy_n(g, nb_points, out); } } } + + // random uniform sampling + if (use_rs) + { + // sample faces + if(smpl_fcs) + { + std::size_t nb_points = choose_param(get_param(np, number_of_points_on_faces), 0); + Random_points_in_triangle_mesh_3 g(tm, pmap); + if (nb_points == 0) + { + if (nb_pts_sq_a_u == 0.) + nb_points = num_vertices(tm); + else + nb_points = static_cast( + std::ceil(g.mesh_area()*nb_pts_sq_a_u) ); + } + out = CGAL::cpp11::copy_n(g, nb_points, out); + } + // sample edges + if (smpl_dgs) + { + std::size_t nb_points = + choose_param(get_param(np, number_of_points_on_edges), 0); + Random_points_on_edge_list_graph_3 g(tm, pmap); + if (nb_points == 0) + { + if (nb_pts_l_u == 0) + nb_points = num_vertices(tm); + else + nb_points = static_cast( + std::ceil( g.mesh_length()*nb_pts_sq_a_u) ); + } + out = CGAL::cpp11::copy_n(g, nb_points, out); + } + } + return out; } template OutputIterator sample_triangle_mesh(const TriangleMesh& tm, - double parameter, - OutputIterator out, - Sampling_method method = RANDOM_UNIFORM) + OutputIterator out) { - return sample_triangle_mesh( - tm, - parameter, - out, - parameters::all_default(), - method); + return sample_triangle_mesh(tm, parameters::all_default()); } template double approximate_Hausdorff_distance( - const TriangleMesh& m1, - const TriangleMesh& m2, - double precision, + const TriangleMesh& tm1, + const TriangleMesh& tm2, NamedParameters np, - VertexPointMap vpm, - Sampling_method method = RANDOM_UNIFORM) + VertexPointMap vpm_2) { std::vector sample_points; sample_triangle_mesh( - m1, - precision, + tm1, std::back_inserter(sample_points), - np, - method ); - return approximate_Hausdorff_distance(sample_points, m2, vpm); + np); + return approximate_Hausdorff_distance(sample_points, tm2, vpm_2); } // documented functions -/** \ingroup PMP_distance_grp - * generates points taken on `f`, a face of `tm`, in a way depending on `method` and - * outputs them to `out`. - * @tparam TriangleMesh a model of the concept `FaceListGraph` - * @tparam OutputIterator a model of `OutputIterator` - * holding objects of the same point type as - * the value type of the internal vertex point map of `tm` - * @tparam Sampling_method defines the sampling method. Possible values are: - - `RANDOM_UNIFORM`: points are generated in a random and uniform way, depending on the area of the face. - - `GRID`: points are generated on a grid, with a minimum of one point . - - `MONTE_CARLO`: points are generated randomly . The number of points is proportional to the face area with a minimum of 1. - * - * @param f the face descriptor of the sampled face. - * @param tm the triangle mesh that contains `f` - * @param parameter depends on `method`: - - `RANDOM_UNIFORM` and `MONTE_CARLO`: the number of points per squared area unit - - `GRID`: the distance between two consecutive points in the grid - * - * @param out output iterator to be filled with sampled points - * @param np an optional sequence of \ref namedparameters among the ones listed below - * @param method defines the method of sampling - * - * \cgalNamedParamsBegin - * \cgalParamBegin{vertex_point_map} - * the property map with the points associated to the vertices of `tm`. If this parameter is omitted, - * an internal property map for `CGAL::vertex_point_t` should be available for `TriangleMesh` \cgalParamEnd - * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `PMPDistanceTraits`\cgalParamEnd - * \cgalNamedParamsEnd - */ -template -OutputIterator -sample_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - double parameter, - OutputIterator out, - NamedParameters np, - Sampling_method method = RANDOM_UNIFORM) -{ - typedef typename GetGeomTraits::type Geom_traits; - - typedef typename GetVertexPointMap::const_type Vpm; - - typedef boost::graph_traits GT; - - Vpm pmap = choose_param(get_param(np, vertex_point), - get_const_property_map(vertex_point, tm)); - typedef Creator_uniform_3 Creator; - std::size_t nb_points = static_cast( std::ceil( to_double( - face_area(f,tm,parameters::geom_traits(Geom_traits())))*parameter)); - switch(method) - { - case GRID: - { - typename Geom_traits::Point_3 points[3]; - typename GT::halfedge_descriptor hd(halfedge(f,tm)); - - // add endpoints - for(int i=0; i<3; ++i) - { - points[i] = get(pmap, target(hd, tm)); - *out++= points[i]; - hd = next(hd, tm); - } - - //sample edges - for (int i=0;i<3; ++i) - { - typename Geom_traits::Compute_squared_distance_3 squared_distance; - const double d_p0p1 = to_double(approximate_sqrt( - squared_distance(points[i], points[(i+1)%3]) )); - - const double nb_pts = std::ceil( d_p0p1 / d_p0p1 ); - const typename Geom_traits::Vector_3 step_vec = - typename Geom_traits::Construct_scaled_vector_3()( - typename Geom_traits::Construct_vector_3()(points[i], points[(i+1)%3]), - typename Geom_traits::FT(1)/typename Geom_traits::FT(nb_pts)); - for (double j=1; j( - points[0], points[1], points[2], parameter, out); - } - - case MONTE_CARLO: - nb_points = (std::max)(nb_points, std::size_t(1)); - case RANDOM_UNIFORM: - { - typename GT::halfedge_descriptor hd(halfedge(f,tm)); - typename Geom_traits::Point_3 points[3]; - for(int i=0; i<3; ++i) - { - points[i] = get(pmap, target(hd, tm)); - hd = next(hd, tm); - } - Random_points_in_triangle_3 - g(points[0], points[1], points[2]); - CGAL::cpp11::copy_n(g, nb_points, out); - return out; - } - } - return out; -} - -template -OutputIterator -sample_face(typename boost::graph_traits::face_descriptor f, - const TriangleMesh& tm, - double parameter, - OutputIterator out, - Sampling_method method = RANDOM_UNIFORM) -{ - return sample_face(f, tm, parameter, out, parameters::all_default(), method); -} /** * \ingroup PMP_distance_grp - * computes the approximate Hausdorff distance from `tm1` to `tm2` by - * generating a uniform random point sampling on `tm1`, and by then - * returning the distance of the furthest point from `tm2`. + * computes the approximate Hausdorff distance from `tm1` to `tm2` by returning + * the distance of the furthest point from `tm2` amongst a sampling of `tm1` + * generated with the function `sample_triangle_mesh()` with + * `tm1` and `np1` as parameter. * * A parallel version is provided and requires the executable to be * linked against the Intel TBB library. @@ -514,16 +581,7 @@ sample_face(typename boost::graph_traits::face_descriptor f, * * @param tm1 the triangle mesh that will be sampled * @param tm2 the triangle mesh to compute the distance to - * @param density the number of points per squared area unit returned by the random sampling - * @param np1 optional sequence of \ref namedparameters for `tm1` among the ones listed below - * - * \cgalNamedParamsBegin - * \cgalParamBegin{vertex_point_map} the property map with the points associated to the vertices of `tm1` - * If this parameter is omitted, an internal property map for CGAL::vertex_point_t should be available in `TriangleMesh` - * and in all places where vertex_point_map is used. - * \cgalParamEnd - * \cgalParamBegin{geom_traits} an instance of a geometric traits class, model of `PMPDistanceTraits`\cgalParamEnd - * \cgalNamedParamsEnd + * @param np1 optional sequence of \ref namedparameters for `tm1` passed to `sample_triangle_mesh()`. * * @param np2 optional sequence of \ref namedparameters for `tm2` among the ones listed below * @@ -541,33 +599,23 @@ template< class Concurrency_tag, class NamedParameters1, class NamedParameters2> double approximate_Hausdorff_distance( const TriangleMesh& tm1, - const TriangleMesh& tm2, - double density, - const NamedParameters1& np1, - const NamedParameters2& np2) + const TriangleMesh& tm2, + const NamedParameters1& np1, + const NamedParameters2& np2) { typedef typename GetGeomTraits::type Geom_traits; return approximate_Hausdorff_distance( - tm1, tm2, - density, - choose_param(get_param(np1, vertex_point), - get_const_property_map(vertex_point, tm1)), - - choose_param(get_param(np2, vertex_point), - get_const_property_map(vertex_point, tm2)) - - ); + tm1, tm2, np1, choose_param(get_param(np2, vertex_point), + get_const_property_map(vertex_point, tm2))); } /** * \ingroup PMP_distance_grp * computes the approximate symmetric Hausdorff distance between `tm1` and `tm2`. - * It returns the maximum of `approximate_Hausdorff_distance(tm1,tm2)` and - * `approximate_Hausdorff_distance(tm2,tm1)`. - * - * \copydetails CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance() + * It returns the maximum of `approximate_Hausdorff_distance(tm1, tm2, np1, np2)` + * and `approximate_Hausdorff_distance(tm2, tm1, np2, np1)`. */ template< class Concurrency_tag, class TriangleMesh, @@ -576,13 +624,12 @@ template< class Concurrency_tag, double approximate_symmetric_Hausdorff_distance( const TriangleMesh& tm1, const TriangleMesh& tm2, - double precision, const NamedParameters1& np1, const NamedParameters2& np2) { return (std::max)( - approximate_Hausdorff_distance(tm1,tm2,precision,np1,np2), - approximate_Hausdorff_distance(tm2,tm1,precision,np2,np1) + approximate_Hausdorff_distance(tm1,tm2,np1,np2), + approximate_Hausdorff_distance(tm2,tm1,np2,np1) ); } @@ -701,23 +748,19 @@ template< class Concurrency_tag, class NamedParameters> double approximate_Hausdorff_distance(const TriangleMesh& tm1, const TriangleMesh& tm2, - const double precision, const NamedParameters& np) { return approximate_Hausdorff_distance( - tm1, tm2, precision, np, parameters::all_default()); + tm1, tm2, np, parameters::all_default()); } template< class Concurrency_tag, class TriangleMesh> double approximate_Hausdorff_distance(const TriangleMesh& tm1, - const TriangleMesh& tm2, - const double precision) + const TriangleMesh& tm2) { return approximate_Hausdorff_distance( - tm1, tm2, precision, - parameters::all_default(), - parameters::all_default()); + tm1, tm2, parameters::all_default(), parameters::all_default()); } @@ -726,23 +769,19 @@ template< class Concurrency_tag, class NamedParameters> double approximate_symmetric_Hausdorff_distance(const TriangleMesh& tm1, const TriangleMesh& tm2, - const double precision, const NamedParameters& np) { return approximate_symmetric_Hausdorff_distance( - tm1, tm2, precision, np, parameters::all_default()); + tm1, tm2, np, parameters::all_default()); } template< class Concurrency_tag, class TriangleMesh> double approximate_symmetric_Hausdorff_distance(const TriangleMesh& tm1, - const TriangleMesh& tm2, - const double precision) + const TriangleMesh& tm2) { return approximate_symmetric_Hausdorff_distance( - tm1, tm2, precision, - parameters::all_default(), - parameters::all_default()); + tm1, tm2, parameters::all_default(), parameters::all_default()); } } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h index 6695d9eb265..4e203fe6d00 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h @@ -14,7 +14,7 @@ // // $URL$ // $Id$ -// +// // // Author(s) : Jane Tournois @@ -39,6 +39,19 @@ namespace CGAL{ enum relax_constraints_t { relax_constraints }; enum vertex_is_constrained_t { vertex_is_constrained }; enum face_patch_t { face_patch }; + enum random_uniform_sampling_t { random_uniform_sampling }; + enum grid_sampling_t { grid_sampling }; + enum monte_carlo_sampling_t { monte_carlo_sampling }; + enum do_sample_edges_t { do_sample_edges }; + enum do_sample_vertices_t { do_sample_vertices }; + enum do_sample_faces_t { do_sample_faces }; + enum number_of_points_on_faces_t { number_of_points_on_faces }; + enum number_of_points_per_face_t { number_of_points_per_face }; + enum grid_spacing_t { grid_spacing }; + enum nb_points_per_sq_area_unit_t { nb_points_per_sq_area_unit }; + enum number_of_points_per_edge_t { number_of_points_per_edge }; + enum number_of_points_on_edges_t { number_of_points_on_edges }; + enum nb_points_per_distance_unit_t{ nb_points_per_distance_unit }; //to be documented enum face_normal_t { face_normal }; @@ -216,6 +229,122 @@ namespace CGAL{ return Params(p, *this); } + //overload + template + pmp_bgl_named_params + use_random_uniform_sampling(const Boolean& p) const + { + typedef pmp_bgl_named_params Params; + return Params(p, *this); + } + + //overload + template + pmp_bgl_named_params + use_grid_sampling(const Boolean& p) const + { + typedef pmp_bgl_named_params Params; + return Params(p, *this); + } + + //overload + template + pmp_bgl_named_params + use_monte_carlo_sampling(const Boolean& p) const + { + typedef pmp_bgl_named_params Params; + return Params(p, *this); + } + + //overload + template + pmp_bgl_named_params + sample_edges(const Boolean& p) const + { + typedef pmp_bgl_named_params Params; + return Params(p, *this); + } + + //overload + template + pmp_bgl_named_params + sample_vertices(const Boolean& p) const + { + typedef pmp_bgl_named_params Params; + return Params(p, *this); + } + + //overload + template + pmp_bgl_named_params + sample_faces(const Boolean& p) const + { + typedef pmp_bgl_named_params Params; + return Params(p, *this); + } + + //overload + template + pmp_bgl_named_params + set_number_of_points_on_faces(const NT& n) const + { + typedef pmp_bgl_named_params Params; + return Params(n, *this); + } + + //overload + template + pmp_bgl_named_params + set_number_of_points_per_face(const NT& n) const + { + typedef pmp_bgl_named_params Params; + return Params(n, *this); + } + + //overload + template + pmp_bgl_named_params + set_grid_spacing(const NT& n) const + { + typedef pmp_bgl_named_params Params; + return Params(n, *this); + } + + //overload + template + pmp_bgl_named_params + set_number_of_points_per_squared_area_unit(const NT& n) const + { + typedef pmp_bgl_named_params Params; + return Params(n, *this); + } + + //overload + template + pmp_bgl_named_params + set_number_of_points_per_edge(const NT& n) const + { + typedef pmp_bgl_named_params Params; + return Params(n, *this); + } + + //overload + template + pmp_bgl_named_params + set_number_of_points_on_edges(const NT& n) const + { + typedef pmp_bgl_named_params Params; + return Params(n, *this); + } + + //overload + template + pmp_bgl_named_params + set_number_of_points_per_distance_unit(const NT& n) const + { + typedef pmp_bgl_named_params Params; + return Params(n, *this); + } }; namespace Polygon_mesh_processing{ @@ -379,6 +508,123 @@ namespace parameters{ return Params(p); } + //overload + template + pmp_bgl_named_params + use_random_uniform_sampling(const Boolean& p) + { + typedef pmp_bgl_named_params Params; + return Params(p); + } + + //overload + template + pmp_bgl_named_params + use_grid_sampling(const Boolean& p) + { + typedef pmp_bgl_named_params Params; + return Params(p); + } + + //overload + template + pmp_bgl_named_params + use_monte_carlo_sampling(const Boolean& p) + { + typedef pmp_bgl_named_params Params; + return Params(p); + } + + //overload + template + pmp_bgl_named_params + sample_edges(const Boolean& p) + { + typedef pmp_bgl_named_params Params; + return Params(p); + } + + //overload + template + pmp_bgl_named_params + sample_vertices(const Boolean& p) + { + typedef pmp_bgl_named_params Params; + return Params(p); + } + + //overload + template + pmp_bgl_named_params + sample_faces(const Boolean& p) + { + typedef pmp_bgl_named_params Params; + return Params(p); + } + + //overload + template + pmp_bgl_named_params + set_number_of_points_on_faces(const NT& n) + { + typedef pmp_bgl_named_params Params; + return Params(n); + } + + //overload + template + pmp_bgl_named_params + set_number_of_points_per_face(const NT& n) + { + typedef pmp_bgl_named_params Params; + return Params(n); + } + + //overload + template + pmp_bgl_named_params + set_grid_spacing(const NT& n) + { + typedef pmp_bgl_named_params Params; + return Params(n); + } + + //overload + template + pmp_bgl_named_params + set_number_of_points_per_squared_area_unit(const NT& n) + { + typedef pmp_bgl_named_params Params; + return Params(n); + } + + //overload + template + pmp_bgl_named_params + set_number_of_points_per_edge(const NT& n) + { + typedef pmp_bgl_named_params Params; + return Params(n); + } + + //overload + template + pmp_bgl_named_params + set_number_of_points_on_edges(const NT& n) + { + typedef pmp_bgl_named_params Params; + return Params(n); + } + + //overload + template + pmp_bgl_named_params + set_number_of_points_per_distance_unit(const NT& n) + { + typedef pmp_bgl_named_params Params; + return Params(n); + } + } //namespace parameters } //namespace Polygon_mesh_processing diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp index 7d1e41d572a..2f3788aeaa1 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp @@ -215,6 +215,9 @@ typedef CGAL::Exact_predicates_inexact_constructions_kernel K; #include +namespace PMP = CGAL::Polygon_mesh_processing; + + typedef CGAL::Surface_mesh Mesh; template @@ -232,24 +235,19 @@ void general_tests(const TriangleMesh& m1, points.push_back(typename GeomTraits::Point_3(0,2,0)); std::cout << "Symmetric distance between meshes (sequential) " - << CGAL::Polygon_mesh_processing::approximate_symmetric_Hausdorff_distance(m1,m2,4000) + << PMP::approximate_symmetric_Hausdorff_distance( + m1,m2, + PMP::parameters::set_number_of_points_per_squared_area_unit(4000), + PMP::parameters::set_number_of_points_per_squared_area_unit(4000)) << "\n"; std::cout << "Max distance to point set " - << CGAL::Polygon_mesh_processing::approximate_max_distance_to_point_set(m1,points,4000) + << PMP::approximate_max_distance_to_point_set(m1,points,4000) << "\n"; std::cout << "Max distance to triangle mesh (sequential) " - << CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh(points,m1) + << PMP::max_distance_to_triangle_mesh(points,m1) << "\n"; - - std::vector sample; - typename boost::graph_traits::face_descriptor f = *faces(m1).first; - CGAL::Polygon_mesh_processing::sample_face(f, m1, 4000, std::back_inserter(sample),CGAL::Polygon_mesh_processing::parameters::all_default()); - std::cout << "Number of sampled points in the first face of m1 (sequential) " - <(m1,m2,40000) + << PMP::approximate_Hausdorff_distance( + m1,m2,PMP::parameters::set_number_of_points_per_squared_area_unit(4000)) << "\n"; time.stop(); std::cout << "done in " << time.time() << "s.\n"; @@ -283,7 +282,8 @@ int main(int, char** argv) time.reset(); time.start(); std::cout << "Distance between meshes (sequential) " - << CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance(m1,m2,40000) + << PMP::approximate_Hausdorff_distance( + m1,m2,PMP::parameters::set_number_of_points_per_squared_area_unit(4000)) << "\n"; time.stop(); std::cout << "done in " << time.time() << "s.\n"; From 29b28949e9c86880e19b71de8db0fafe766bc187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Tue, 27 Dec 2016 17:29:04 +0100 Subject: [PATCH 55/72] cite metro --- Documentation/doc/biblio/geom.bib | 12 ++++++++++++ .../Polygon_mesh_processing.txt | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Documentation/doc/biblio/geom.bib b/Documentation/doc/biblio/geom.bib index 63be4547305..0d135a1805f 100644 --- a/Documentation/doc/biblio/geom.bib +++ b/Documentation/doc/biblio/geom.bib @@ -151977,6 +151977,7 @@ pages = {179--189} year={2015} } + @incollection{bfhhm-epsph-15, title={Exact Minkowski sums of polygons with holes}, author={Baram, Alon and Fogel, Efi and Halperin, Dan and Hemmer, Michael and Morr, Sebastian}, @@ -151985,3 +151986,14 @@ pages = {179--189} year={2015}, publisher={Springer} } + +@inproceedings{cignoni1998metro, + title={Metro: measuring error on simplified surfaces}, + author={Cignoni, Paolo and Rocchini, Claudio and Scopigno, Roberto}, + booktitle={Computer Graphics Forum}, + volume={17}, + number={2}, + pages={167--174}, + year={1998}, + organization={Blackwell Publishers} +} 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 36bc5e4f051..4a9a09e191b 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 @@ -483,7 +483,7 @@ the propagation of a connected component index to cross it. \section PMPDistance Approximate Hausdorff Distance -This package provides methods to compute approximate Hausdorff distances. +This package provides methods to compute approximate Hausdorff distances \cgalCite{cignoni1998metro}. These distances can be computed between two meshes, a mesh and a point set, or a point set and a mesh. These computations are performed with : - `CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance()` From 283815c875b2498f64bbef1f47066e9735a969be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 28 Dec 2016 08:57:34 +0100 Subject: [PATCH 56/72] improve doc --- .../include/CGAL/Polygon_mesh_processing/distance.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 2e43d70dee1..a9ea963f3c9 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -635,7 +635,8 @@ double approximate_symmetric_Hausdorff_distance( /** * \ingroup PMP_distance_grp - * computes the approximate Hausdorff distance between `points` and `tm` + * returns the distance to `tm` of the point from `points` + * that is the furthest from `tm`. * @tparam PointRange a range of `Point_3`, model of `Range`. * @tparam TriangleMesh a model of the concept `FaceListGraph` * @tparam NamedParameters a sequence of \ref namedparameters @@ -669,7 +670,8 @@ double max_distance_to_triangle_mesh(const PointRange& points, /*! *\ingroup PMP_distance_grp - * Computes the approximate Hausdorff distance between `tm` and `points` + * returns an approximation of the distance to `points` of the triangle from `tm` + * that is the furthest from `points`. * * @tparam PointRange a range of `Point_3`, model of `Range`. * @tparam TriangleMesh a model of the concept `FaceListGraph` @@ -691,9 +693,9 @@ template< class TriangleMesh, class PointRange, class NamedParameters> double approximate_max_distance_to_point_set(const TriangleMesh& tm, - const PointRange& points, - const double precision, - const NamedParameters& np) + const PointRange& points, + const double precision, + const NamedParameters& np) { typedef typename GetGeomTraits::type Geom_traits; From bfc20a922bacbdb923d1007b1ce321356068aaec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Wed, 28 Dec 2016 09:43:55 +0100 Subject: [PATCH 57/72] move using instructions --- .../Polygon_mesh_processing/Concepts/PMPDistanceTraits.h | 2 +- .../doc/Polygon_mesh_processing/PackageDescription.txt | 2 +- .../include/CGAL/Polygon_mesh_processing/distance.h | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h index 72b1a169532..252b5505121 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h @@ -24,7 +24,7 @@ public: * `FT operator[](int i)` with `0 <= i < 3`. * * There must be a specialization of `CGAL::Kernel_traits` such that - * `CGAL::Kernel_traits::Kernel` is a model implementing this concept. + * `CGAL::Kernel_traits::%Kernel` is a model implementing this concept. */ typedef unspecified_type Point_3; diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt index 70f98fdfd62..e62dec98d45 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/PackageDescription.txt @@ -40,7 +40,7 @@ /// Functions to orient polygon soups and to stitch geometrically identical boundaries. /// \ingroup PkgPolygonMeshProcessing -/// \defgroup PMP_distance_grp Approximate Hausdorff Distance +/// \defgroup PMP_distance_grp Distance Functions /// Functions to compute the distance between meshes, between a mesh and a point set and between a point set and a mesh. /// \ingroup PkgPolygonMeshProcessing diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index a9ea963f3c9..0d2cf9217dc 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -295,6 +295,10 @@ sample_triangle_mesh(const TriangleMesh& tm, typedef typename GT::halfedge_descriptor halfedge_descriptor; typedef typename GT::edge_descriptor edge_descriptor; + using boost::choose_param; + using boost::get_param; + using boost::is_default_param; + Vpm pmap = choose_param(get_param(np, vertex_point), get_const_property_map(vertex_point, tm)); typedef Creator_uniform_3 Date: Thu, 29 Dec 2016 17:50:16 +0100 Subject: [PATCH 58/72] remove set_ prefix from named parameters --- .../NamedParameters.txt | 14 ++++---- .../hausdorff_distance_remeshing_example.cpp | 2 +- .../CGAL/Polygon_mesh_processing/distance.h | 36 +++++++++---------- .../internal/named_function_params.h | 28 +++++++-------- .../test_pmp_distance.cpp | 8 ++--- 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index dd75dc87abe..09e67fda22b 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -260,7 +260,7 @@ should be considered for the sampling. \b Default value is `true` \cgalNPEnd -\cgalNPBegin{set_number_of_points_on_faces} \anchor PMP_set_number_of_points_on_faces +\cgalNPBegin{number_of_points_on_faces} \anchor PMP_number_of_points_on_faces Parameter used in `sample_triangle_mesh()` to set the number of points picked using the random uniform method on faces. \n @@ -268,7 +268,7 @@ using the random uniform method on faces. \b Default value is `0` \cgalNPEnd -\cgalNPBegin{set_number_of_points_on_edges} \anchor PMP_set_number_of_points_on_edges +\cgalNPBegin{number_of_points_on_edges} \anchor PMP_number_of_points_on_edges Parameter used in `sample_triangle_mesh()` to set the number of points picked using the random uniform method on edges. \n @@ -276,7 +276,7 @@ using the random uniform method on edges. \b Default value is `0` \cgalNPEnd -\cgalNPBegin{set_number_of_points_per_face} \anchor PMP_set_number_of_points_per_face +\cgalNPBegin{number_of_points_per_face} \anchor PMP_number_of_points_per_face Parameter used in `sample_triangle_mesh()` to set the number of points picked per face using the Monte-Carlo method. \n @@ -284,7 +284,7 @@ per face using the Monte-Carlo method. \b Default value is `0` \cgalNPEnd -\cgalNPBegin{set_number_of_points_per_edge} \anchor PMP_set_number_of_points_per_edge +\cgalNPBegin{number_of_points_per_edge} \anchor PMP_number_of_points_per_edge Parameter used in `sample_triangle_mesh()` to set the number of points picked per edge using the Monte-Carlo method. \n @@ -292,7 +292,7 @@ per edge using the Monte-Carlo method. \b Default value is `0` \cgalNPEnd -\cgalNPBegin{set_grid_spacing} \anchor PMP_set_grid_spacing +\cgalNPBegin{grid_spacing} \anchor PMP_grid_spacing Parameter used in `sample_triangle_mesh()` to set the grid spacing when using the grid sampling method. \n @@ -300,7 +300,7 @@ the grid sampling method. \b Default value is `0` \cgalNPEnd -\cgalNPBegin{set_number_of_points_per_squared_area_unit} \anchor PMP_set_number_of_points_per_squared_area_unit +\cgalNPBegin{number_of_points_per_squared_area_unit} \anchor PMP_number_of_points_per_squared_area_unit Parameter used in `sample_triangle_mesh()` to set the number of points per squared area unit to be picked up in faces for the random uniform sampling and Monte-Carlo methods. @@ -309,7 +309,7 @@ Monte-Carlo methods. \b Default value is `0` \cgalNPEnd -\cgalNPBegin{set_number_of_points_per_distance_unit} \anchor PMP_set_number_of_points_per_distance_unit +\cgalNPBegin{number_of_points_per_distance_unit} \anchor PMP_number_of_points_per_distance_unit Parameter used in `sample_triangle_mesh()` to set the number of points per distance unit to be picked up on edges for the random uniform sampling and Monte-Carlo methods. diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp index d85ef766c2f..8560df1035d 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp @@ -29,6 +29,6 @@ int main() std::cout << "Approximated Hausdorff distance: " << CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance - (tm1, tm2, PMP::parameters::set_number_of_points_per_squared_area_unit(4000)) + (tm1, tm2, PMP::parameters::number_of_points_per_squared_area_unit(4000)) << std::endl; } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index 0d2cf9217dc..f385f0e8987 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -205,29 +205,29 @@ struct Distance_computation{ * if `true` is passed (the default), points are generated in a random * and uniform way on the surface of `tm`, and/or on edges of `tm`. * For faces, the number of sample points is the value passed to the named - * parameter `set_number_of_points_on_faces()`. If not set, - * the value passed to the named parameter `set_number_of_points_per_squared_area_unit()` + * parameter `number_of_points_on_faces()`. If not set, + * the value passed to the named parameter `number_of_points_per_squared_area_unit()` * is multiplied by the area of `tm` to get the number of sample points. * If none of these parameters is set, the number of points sampled is `num_vertices(tm)`. * For edges, the number of the number of sample points is the value passed to the named - * parameter `set_number_of_points_on_edges()`. If not set, - * the value passed to the named parameter `set_number_of_points_per_distance_unit()` + * parameter `number_of_points_on_edges()`. If not set, + * the value passed to the named parameter `number_of_points_per_distance_unit()` * is multiplied by the sum of the length of edges of `tm` to get the number of sample points. * If none of these parameters is set, the number of points sampled is `num_vertices(tm)`. * \cgalParamEnd - * \cgalParamBegin{set_number_of_points_on_faces} an unsigned integral value used + * \cgalParamBegin{number_of_points_on_faces} an unsigned integral value used * for the random sampling method as the number of points to pick on the surface. * \cgalParamEnd - * \cgalParamBegin{set_number_of_points_on_edges} an unsigned integral value used + * \cgalParamBegin{number_of_points_on_edges} an unsigned integral value used * for the random sampling method as the number of points to pick exclusively * on edges. * \cgalParamEnd - * \cgalParamBegin{set_number_of_points_per_squared_area_unit} a double value + * \cgalParamBegin{number_of_points_per_squared_area_unit} a double value * used for the random sampling and the Monte Carlo sampling methods to * repectively determine the total number of points inside faces * and the number of points per face. * \cgalParamEnd - * \cgalParamBegin{set_number_of_points_per_distance_unit} a double value + * \cgalParamBegin{number_of_points_per_distance_unit} a double value * used for the random sampling and the Monte Carlo sampling methods to * repectively determine the total number of points on edges and the * number of points per edge. @@ -237,36 +237,36 @@ struct Distance_computation{ * with a minimum of one point per triangle. The distance between * two consecutive points in the grid is that of the length of the * smallest non-null edge of `tm` or the value passed to - * the named parameter `set_grid_spacing()`. Edges are also split using the + * the named parameter `grid_spacing()`. Edges are also split using the * same distance, if requested. * \cgalParamEnd - * \cgalParamBegin{set_grid_spacing} a double value used as the grid spacing + * \cgalParamBegin{grid_spacing} a double value used as the grid spacing * for the grid sampling method. * \cgalParamEnd * \cgalParamBegin{use_monte_carlo_sampling} * if `true` is passed, points are generated randomly in each triangle and/or * on each edge. * For faces, the number of points per triangle is the value passed to the named - * parameter `set_number_of_points_per_face()`. If not set, the value passed - * to the named parameter `set_number_of_points_per_squared_area_unit()` is + * parameter `number_of_points_per_face()`. If not set, the value passed + * to the named parameter `number_of_points_per_squared_area_unit()` is * used to pick a number of points per face proportional to the triangle * area with a minimum of one point per face. If none of these parameters * is set, 2 divided by the square of the length of the smallest non-null * edge of `tm` is used as if it was passed to - * `set_number_of_points_per_squared_area_unit()`. + * `number_of_points_per_squared_area_unit()`. * For edges, the number of points per edge is the value passed to the named - * parameter `set_number_of_points_per_edge()`. If not set, the value passed - * to the named parameter `set_number_of_points_per_distance_unit()` is + * parameter `number_of_points_per_edge()`. If not set, the value passed + * to the named parameter `number_of_points_per_distance_unit()` is * used to pick a number of points per edge proportional to the length of * the edge with a minimum of one point per face. If none of these parameters * is set, 1 divided by the length of the smallest non-null edge of `tm` - * is used as if it was passed to `set_number_of_points_per_distance_unit()`. + * is used as if it was passed to `number_of_points_per_distance_unit()`. * \cgalParamEnd - * \cgalParamBegin{set_number_of_points_per_face} an unsigned integral value + * \cgalParamBegin{number_of_points_per_face} an unsigned integral value * used by the Monte-Carlo sampling method as the number of points per face * to pick. * \cgalParamEnd - * \cgalParamBegin{set_number_of_points_per_edge} an unsigned integral value + * \cgalParamBegin{number_of_points_per_edge} an unsigned integral value * used by the Monte-Carlo sampling method as the number of points per edge * to pick. * \cgalParamEnd diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h index 4e203fe6d00..c8046efc95c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h @@ -286,7 +286,7 @@ namespace CGAL{ //overload template pmp_bgl_named_params - set_number_of_points_on_faces(const NT& n) const + number_of_points_on_faces(const NT& n) const { typedef pmp_bgl_named_params Params; return Params(n, *this); @@ -295,7 +295,7 @@ namespace CGAL{ //overload template pmp_bgl_named_params - set_number_of_points_per_face(const NT& n) const + number_of_points_per_face(const NT& n) const { typedef pmp_bgl_named_params Params; return Params(n, *this); @@ -304,7 +304,7 @@ namespace CGAL{ //overload template pmp_bgl_named_params - set_grid_spacing(const NT& n) const + grid_spacing(const NT& n) const { typedef pmp_bgl_named_params Params; return Params(n, *this); @@ -313,7 +313,7 @@ namespace CGAL{ //overload template pmp_bgl_named_params - set_number_of_points_per_squared_area_unit(const NT& n) const + number_of_points_per_squared_area_unit(const NT& n) const { typedef pmp_bgl_named_params Params; return Params(n, *this); @@ -322,7 +322,7 @@ namespace CGAL{ //overload template pmp_bgl_named_params - set_number_of_points_per_edge(const NT& n) const + number_of_points_per_edge(const NT& n) const { typedef pmp_bgl_named_params Params; return Params(n, *this); @@ -331,7 +331,7 @@ namespace CGAL{ //overload template pmp_bgl_named_params - set_number_of_points_on_edges(const NT& n) const + number_of_points_on_edges(const NT& n) const { typedef pmp_bgl_named_params Params; return Params(n, *this); @@ -340,7 +340,7 @@ namespace CGAL{ //overload template pmp_bgl_named_params - set_number_of_points_per_distance_unit(const NT& n) const + number_of_points_per_distance_unit(const NT& n) const { typedef pmp_bgl_named_params Params; return Params(n, *this); @@ -565,7 +565,7 @@ namespace parameters{ //overload template pmp_bgl_named_params - set_number_of_points_on_faces(const NT& n) + number_of_points_on_faces(const NT& n) { typedef pmp_bgl_named_params Params; return Params(n); @@ -574,7 +574,7 @@ namespace parameters{ //overload template pmp_bgl_named_params - set_number_of_points_per_face(const NT& n) + number_of_points_per_face(const NT& n) { typedef pmp_bgl_named_params Params; return Params(n); @@ -583,7 +583,7 @@ namespace parameters{ //overload template pmp_bgl_named_params - set_grid_spacing(const NT& n) + grid_spacing(const NT& n) { typedef pmp_bgl_named_params Params; return Params(n); @@ -592,7 +592,7 @@ namespace parameters{ //overload template pmp_bgl_named_params - set_number_of_points_per_squared_area_unit(const NT& n) + number_of_points_per_squared_area_unit(const NT& n) { typedef pmp_bgl_named_params Params; return Params(n); @@ -601,7 +601,7 @@ namespace parameters{ //overload template pmp_bgl_named_params - set_number_of_points_per_edge(const NT& n) + number_of_points_per_edge(const NT& n) { typedef pmp_bgl_named_params Params; return Params(n); @@ -610,7 +610,7 @@ namespace parameters{ //overload template pmp_bgl_named_params - set_number_of_points_on_edges(const NT& n) + number_of_points_on_edges(const NT& n) { typedef pmp_bgl_named_params Params; return Params(n); @@ -619,7 +619,7 @@ namespace parameters{ //overload template pmp_bgl_named_params - set_number_of_points_per_distance_unit(const NT& n) + number_of_points_per_distance_unit(const NT& n) { typedef pmp_bgl_named_params Params; return Params(n); diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp index 2f3788aeaa1..dae217cc8aa 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp @@ -237,8 +237,8 @@ void general_tests(const TriangleMesh& m1, std::cout << "Symmetric distance between meshes (sequential) " << PMP::approximate_symmetric_Hausdorff_distance( m1,m2, - PMP::parameters::set_number_of_points_per_squared_area_unit(4000), - PMP::parameters::set_number_of_points_per_squared_area_unit(4000)) + PMP::parameters::number_of_points_per_squared_area_unit(4000), + PMP::parameters::number_of_points_per_squared_area_unit(4000)) << "\n"; std::cout << "Max distance to point set " @@ -274,7 +274,7 @@ int main(int, char** argv) time.start(); std::cout << "Distance between meshes (parallel) " << PMP::approximate_Hausdorff_distance( - m1,m2,PMP::parameters::set_number_of_points_per_squared_area_unit(4000)) + m1,m2,PMP::parameters::number_of_points_per_squared_area_unit(4000)) << "\n"; time.stop(); std::cout << "done in " << time.time() << "s.\n"; @@ -283,7 +283,7 @@ int main(int, char** argv) time.start(); std::cout << "Distance between meshes (sequential) " << PMP::approximate_Hausdorff_distance( - m1,m2,PMP::parameters::set_number_of_points_per_squared_area_unit(4000)) + m1,m2,PMP::parameters::number_of_points_per_squared_area_unit(4000)) << "\n"; time.stop(); std::cout << "done in " << time.time() << "s.\n"; From a8478189041445d49d10950bc515d1701bfcbf59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 29 Dec 2016 18:02:36 +0100 Subject: [PATCH 59/72] improve description --- .../include/CGAL/Polygon_mesh_processing/distance.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index f385f0e8987..b3cd613f45d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -273,9 +273,9 @@ struct Distance_computation{ * \cgalParamBegin{sample_vertices} if `true` is passed (default value), * vertices of `tm` are put into `out`.\cgalParamEnd * \cgalParamBegin{sample_edges} if `true` is passed (default value), - * edges of `tm` are sampled using a dedicated algorithm.\cgalParamEnd + * edges of `tm` are sampled.\cgalParamEnd * \cgalParamBegin{sample_faces} if `true` is passed (default value), - * faces of `tm` are sampled using a dedicated algorithm.\cgalParamEnd + * faces of `tm` are sampled.\cgalParamEnd * \cgalNamedParamsEnd */ template @@ -563,7 +563,7 @@ double approximate_Hausdorff_distance( /** * \ingroup PMP_distance_grp * computes the approximate Hausdorff distance from `tm1` to `tm2` by returning - * the distance of the furthest point from `tm2` amongst a sampling of `tm1` + * the distance of the farthest point from `tm2` amongst a sampling of `tm1` * generated with the function `sample_triangle_mesh()` with * `tm1` and `np1` as parameter. * From 91278918d71c05215c2a4c47a194c564900335a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 29 Dec 2016 18:06:58 +0100 Subject: [PATCH 60/72] reorder named parameters --- .../CGAL/Polygon_mesh_processing/distance.h | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index b3cd613f45d..ad5032d35c6 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -215,23 +215,6 @@ struct Distance_computation{ * is multiplied by the sum of the length of edges of `tm` to get the number of sample points. * If none of these parameters is set, the number of points sampled is `num_vertices(tm)`. * \cgalParamEnd - * \cgalParamBegin{number_of_points_on_faces} an unsigned integral value used - * for the random sampling method as the number of points to pick on the surface. - * \cgalParamEnd - * \cgalParamBegin{number_of_points_on_edges} an unsigned integral value used - * for the random sampling method as the number of points to pick exclusively - * on edges. - * \cgalParamEnd - * \cgalParamBegin{number_of_points_per_squared_area_unit} a double value - * used for the random sampling and the Monte Carlo sampling methods to - * repectively determine the total number of points inside faces - * and the number of points per face. - * \cgalParamEnd - * \cgalParamBegin{number_of_points_per_distance_unit} a double value - * used for the random sampling and the Monte Carlo sampling methods to - * repectively determine the total number of points on edges and the - * number of points per edge. - * \cgalParamEnd * \cgalParamBegin{use_grid_sampling} * if `true` is passed, points are generated on a grid in each triangle, * with a minimum of one point per triangle. The distance between @@ -240,9 +223,6 @@ struct Distance_computation{ * the named parameter `grid_spacing()`. Edges are also split using the * same distance, if requested. * \cgalParamEnd - * \cgalParamBegin{grid_spacing} a double value used as the grid spacing - * for the grid sampling method. - * \cgalParamEnd * \cgalParamBegin{use_monte_carlo_sampling} * if `true` is passed, points are generated randomly in each triangle and/or * on each edge. @@ -262,20 +242,40 @@ struct Distance_computation{ * is set, 1 divided by the length of the smallest non-null edge of `tm` * is used as if it was passed to `number_of_points_per_distance_unit()`. * \cgalParamEnd - * \cgalParamBegin{number_of_points_per_face} an unsigned integral value - * used by the Monte-Carlo sampling method as the number of points per face - * to pick. - * \cgalParamEnd - * \cgalParamBegin{number_of_points_per_edge} an unsigned integral value - * used by the Monte-Carlo sampling method as the number of points per edge - * to pick. - * \cgalParamEnd * \cgalParamBegin{sample_vertices} if `true` is passed (default value), * vertices of `tm` are put into `out`.\cgalParamEnd * \cgalParamBegin{sample_edges} if `true` is passed (default value), * edges of `tm` are sampled.\cgalParamEnd * \cgalParamBegin{sample_faces} if `true` is passed (default value), * faces of `tm` are sampled.\cgalParamEnd + * \cgalParamBegin{grid_spacing} a double value used as the grid spacing + * for the grid sampling method. + * \cgalParamEnd + * \cgalParamBegin{number_of_points_on_edges} an unsigned integral value used + * for the random sampling method as the number of points to pick exclusively + * on edges. + * \cgalParamEnd + * \cgalParamBegin{number_of_points_on_faces} an unsigned integral value used + * for the random sampling method as the number of points to pick on the surface. + * \cgalParamEnd + * \cgalParamBegin{number_of_points_per_distance_unit} a double value + * used for the random sampling and the Monte Carlo sampling methods to + * repectively determine the total number of points on edges and the + * number of points per edge. + * \cgalParamEnd + * \cgalParamBegin{number_of_points_per_edge} an unsigned integral value + * used by the Monte-Carlo sampling method as the number of points per edge + * to pick. + * \cgalParamEnd + * \cgalParamBegin{number_of_points_per_face} an unsigned integral value + * used by the Monte-Carlo sampling method as the number of points per face + * to pick. + * \cgalParamEnd + * \cgalParamBegin{number_of_points_per_squared_area_unit} a double value + * used for the random sampling and the Monte Carlo sampling methods to + * repectively determine the total number of points inside faces + * and the number of points per face. + * \cgalParamEnd * \cgalNamedParamsEnd */ template From a047641ec919b36e5af8ade690aa7b9801772c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 30 Dec 2016 14:28:37 +0100 Subject: [PATCH 61/72] update user manual and add a picture to illustrate the sampling methods --- .../Polygon_mesh_processing.txt | 33 +++++++++++++----- .../fig/pmp_sampling_bunny.jpg | Bin 0 -> 125874 bytes 2 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/pmp_sampling_bunny.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 4a9a09e191b..e5e39436549 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 @@ -483,14 +483,31 @@ the propagation of a connected component index to cross it. \section PMPDistance Approximate Hausdorff Distance -This package provides methods to compute approximate Hausdorff distances \cgalCite{cignoni1998metro}. -These distances can be computed between two meshes, a mesh and a point set, or a point set and a mesh. -These computations are performed with : -- `CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance()` -- `CGAL::Polygon_mesh_processing::approximate_symmetric_Hausdorff_distance()` -- `CGAL::Polygon_mesh_processing::approximate_max_distance_to_point_set()` -- `CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh()` -- `CGAL::Polygon_mesh_processing::sample_triangle_mesh()` +This package provides methods to compute (approximate) distances between meshes and point sets. + +The function \link CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance() `approximate_Hausdorff_distance()`\endlink +computes an approximation of Hausdorff distance from a mesh `tm1` to a mesh `tm2`. Given a +a sampling of `tm1`, it computes the distance to `tm2` of the farthest sample point to `tm2` \cgalCite{cignoni1998metro}. +The symmetric version (\link CGAL::Polygon_mesh_processing::approximate_symmetric_Hausdorff_distance() `approximate_symmetric_Hausdorff_distance()`\endlink) +is the maximum of the two non-symmetric distances. Internally, points are sampled using +\link CGAL::Polygon_mesh_processing::sample_triangle_mesh() `sample_triangle_mesh()`\endlink and the distance +to each sample point is computed using +\link CGAL::Polygon_mesh_processing::max_distance_to_triangle_mesh() `max_distance_to_triangle_mesh()`\endlink. +The quality of the approximation depends on the quality of the sampling and the runtime depends on the number of sample points. +Three sampling methods with different parameters are provided (see \cgalFigureRef{sampling_bunny}). + +\cgalFigureBegin{sampling_bunny, pmp_sampling_bunny.jpg} +Sampling of a triangle mesh using different sampling methods. From left to right: Grid sampling, Monte-Carlo sampling with fixed number of points per face and per edge, +Monte-Carlo sampling with a number of points proportional to the area/length, and Uniform random sampling. +The four pictures represent the sampling on the same portion of a mesh, parameters were ajusted so that the total number of points sampled in faces (blue points) and on +edges (red points) are roughly the same. +Note that when using the random uniform sampling some faces/edges may not contain any point, but this method is the only one that allows to exactly match a given number of points. +\cgalFigureEnd + +The function \link CGAL::Polygon_mesh_processing::approximate_max_distance_to_point_set() `approximate_max_distance_to_point_set()`\endlink +computes an approximation of the Hausdorff distance from a mesh to a point set. +For each triangle, a lower and upper bound of the Hausdorff distance to the point set are computed. +Triangles are refined until the difference between the bounds is lower than a user-defined precision threshold. \subsection AHDExample Approximate Hausdorff Distance Example In the following example, a mesh is isotropically remeshed and the approximate distance between the input and the output is computed. diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/pmp_sampling_bunny.jpg b/Polygon_mesh_processing/doc/Polygon_mesh_processing/fig/pmp_sampling_bunny.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b81b3d286ad7152595c9cea8ceba5ad42afb6dba GIT binary patch literal 125874 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�`>KXb*mfwPjD<&ba z9Q5W{zZt)o#{M;Xt%Z`(D8o&Td&1qK4BP4XD{>=`BWeHAX3;~!UVKgLnfnwgd zd$cR8q`@g~Ui1@NNioVRZahHg z>1EgEC8oTA6wX-2h-Uf}lblgl^fTBv)Yq5RH$YJbiZKHBa!Z)w;4Nu?BU|cQ5H?bg zQm8+J^sM4P02a~iwE7>s;(L~%>W|)boRErskG+@~1CazSQ~RX#QX3(XalzIdZI=+o zYm}ddPCh-7nqr|Sf@!y4$Bce+aF}OGX}8PRV)mWo|K+vu5)iJbWeg86MR|gmyXezj z>U73?*0C^I;`6P$jHx9CWcyoib^KBlrh7<#Y3|d?Bcb^@{4rUBpv3_YAH)~LUr^Y{ zZph>Ixu7vnFgR^ohrQG%v(tf&aU{>?*JMORg0aa1``;kpNAk_9AQv5Gh2JO3zI zzL3f;q1cxh=8SYxTGf;Z?R)aL(cDF8%k7)nF#sBp7%F&tr*+x}$;q#S?D*`612GGF zg)47Y=1H?<6ap6r_+STZ{5Vmib}fEz9jNh|Rm60> zvR}J>Tx@Pm>+Ngl9u4qtVzx>#^@irw>i=aLyA8w{lo3{ZNqy|mw_=Eb`tNVn0QGxv z>P}Uyipf4|c~4D%uzl~Ps_>*F#RZE`#t*YwSG$d*$fJXg^v6@l`A^3tSc1tvP8#2O zLid$^yD2MB!ZOrM$vxr>Kri@DiO92E)kO(ujgzFFVyjwotvs+k{?n{+{03|oHnI#nT8P2U$|Br zQ8ZD%wJz3l(!^9qUq>W2y}ZhN{qCEi*^i|V$ConF&=8Kt!sx(R4ro6OI;Zu{V@b)4->pRdFYM_50P5oNa=2Ht1W(#@?$^UOJQ$iCPwcXMa}7+i!=SdkmwSCu5GFsJj~dwQi!D0@s;1r+g@lt`F`q` z_6c&8y2!P-`}Pyve<|6CE$4x{-^P1#9OUUNYZCthlpfQUd2i&|m~ikbt63Qi^!0?l z`mAK+S1--U7F1Vj_uPo-!drcCKoISaiz$~o`4U@!QU=arm-NED;RnVIY2s&AOquZG zXTLfD9<#51c&7fB;Gq?A)}Jv{jbwTrR3&DeTiYFW|6Q-{wRZT+!z z^}R-a`FjNVJ)>s8L*lB(OI#h?Rg2Toq&uWD)9Y1kS)*bw`9DSjM^EDXM34`Axt9er zD*sTeguPFOcjD`OW6SI)=xf(cgSfRlYy54i=4Wk|KPYa@eXDpWl&x;7j<;u#q&7n= z(6+MHg9M=%xwH8qU$-;Atva7m$`+ZB_-lGAAxPAcWh>s_x4DCD!K;ns+3@&IFzWn} zGZLzus3ieWHd>fO=FzZYa4rlUzfugc&EXVmdDX0SK38s5V3VJD6M=z&a2-W9#c#?E zq8cskvLRiz6+ciUQwGm@160^?8S-SOpkQRd)n9>cJU~0Z*VAxfCk!H#6_4FgJ6#Yo z?uEEn+{(afc-MHt`A%hj=EM&{>_%aLum`pnjnQz$#bfc2;AdStf-yH+fgmZ8gI^~s zOFnUOb40|YJ#2Q)9d2$-feliO?&OAJ^NE3=D%TL|cHDhybaP_naetib=iT?cRIt7i zF7|J<>Ks2Yi=0jhx3f7MAqEc&3N0DL8kgq%W}K=$m3n4VH({&tp)_|k(V1-ekKRS`flRng zeCVh{ZZ};R+__gr2V)tKvkXECaL5B4*)rto)}CYhK2w<~waHzXC7<|E{{d)zC4M$7 zAyZ2Bq#3VJX#Dkx+2OmmDDL=<4@*7JJV7PEc60wNclSkjA8+^EG=H(W#FQU#v*dW? z(xpYM%v+xY$=bkp!`nS)etWfl^P%}zN-1nAJ~`n307QeIu>0@dYqv2>oed2ZpSoR<=`Rws0O%Z|Aa)&T!!d{*}qn5b)lq=<9f|6sD;t_>`)|DFv0 zDx9+0^e+m}UBurzIDU|uvx$+2Q`)e?3D$-5hIp>NNi9=r| z6xw-@Wvd9+AXq?A4;SS)8cxIGc4d3ePYcKcq6rEqJw#-8*IA`--H=vOT);*7p8 zgr6Tetz)hH(++jHlle|mpnlDFPOt0GxG9wO7PfO>hvvu<78agPu*#bE_{QjuXW+U0 z2l1s6MZS5%;lOGXB>O+_GJ-wL3Pdz)W4}P4`O<@rBsAc~=2;Xxhyo#%(X083Lt2M2 z_T&=&TiS5(4lR-I{u%XTbLcA}CksSJTUO%HdP^JgRAUZ3IHh5g?Q>)tB!r5MrNrSi%H;DK#&n# zV`g=?zQ?T7>%-y95HsRN^N$#Gp^wkER(ekbQZ6P{$R&rr{qPKRcBtUf*mYaHGF~!- zR+dlPr7r!`|Ek44wQ{UY>rUb6@`E8iu-u(dGZ1Ulxgh0CKNOC1mQxusjiAe0i>9t{ zFaW8Gy0auYnI})!PlT%-zW*2~MKn<1OD*`?VHo7Fw^Yo-GkN!+C4@SDLeisK)x{OU zwryPjS9~I!7ym50=zTX>NqP#4fC6_9JwN=u1ygBD+xqvHza)dEkX&*?u_{uN73-5% zm=m$#d|7k-R?U7L7>Unb&q0_Tqj1GxLJbWH7-3R$k%|0 zbRd~K%G(93rSY3OsgV`6Byf9b6n>l*UybbCsG9Y=7x*kf`py%VD%Bv+llq8VZ}MGy z@$z=0)BW+b))KSa3nIX796?;Z?++HjKt?9#gYVt0i}wo8gjVb%K4r4ezcVHaLzfGv z-G5pL@k!{9hm$UD&^wt=DN54Uw}1L9C*fr9*->9Nhj`J(dS+qc3GNWI)JvM<3LFoL zlCnkg#Z;5#uuCasCXD#{9XC~W%;$We{-rAf%(r)wenP0U0QgCi8*eH#KyN3M#Y2Jt z%1-EB zRxm*1TllA}HvF|K;u{ z*R#jt{0GoBgQjN+IxruIeO2n=fNX5rE5`WN#-Q5%X33^2#plA>qqODCJP1b}Qo#I# z<$V2n(;l@J3?jO`5y!xP%jUi5>8O#z+0${YXIkUu zQsGra%nXiSZMaF~=;>!>_!4f%i{Iync$KVlK3qcFfYS-l0&IbvEJ{vkbwzYaE4UV1 z7T)tkNFyu&~Ctk7ZxJ($(VczOrE`{S$5&&Ei`1Tx+FT zSBxirpqXW3!_{QW7aFJREMcL-@cR(31)Oa#KUwN=DBfU?Q{^c5UAd8UHq8SV${3{E+?#M4hM zK3G=SkORFhKDBFVV2)NZ4+$e7`1O`Dd(#FL!%r%+94LNYlG!*pxE9+VgtW>|XS2>} z`t{!)AqA}d1K^%Y>Lm1uk=We70|-0i3a(0gS(14TPgVe$+~}f5W_tZz=QjM1E&zSP z4*KpE33d8ys`?SJkUt|l!{X;Uu`w{_EHXL4&2o~)#LZDDV0`~i@e!J`;zF0=m~Hxg zHt2zqgb|-&mwGZB)vbqz;3ew1O)*>Z)?1lMPLH@sWI#&5?a}To+5Z8m^X)63tax8O zjhvInf(+Z}eZ6OKC?vOI znte~YJU;SI@LRJ%+D%QscpnE-%D9&SQQj-9Q}kg_Sl6qK3QA6*_V!pZ*id&7lT*S* zrL2Z}dr`HR@Gqa?j^ZnO5L`=67+|eM6SPgZw^u+yX)W|UpOGA0$thVGS#nhb&G^Ed zQ7A4%7Wjke*xG=t69dpay2^~7XM$I-tY zjeyuN++P;Vw{Eih*vG0v)ef)ip#uYW!?J=4dOFC#j49 zv79;;Yh*SQ%i_LK@UOrzQ@cFOZ(sQ#_rGZQXFB&8K)3H2DXNa`dFi{=e^qOu_0+(t zFUK8xzYI4pX3TpPEMb4Ku1FgvA_meJ>7-sZA~8lvzlso2%;Dz} z?5v#Zdji%n&jc09qJW7b&MvP=A@V?NQNk;+GpFHwcs_61_PY`EF9Jb+W=c&zK#=pLT=qRn;u)b1?i!|s8orhW`HgOV8+@e6@{zyC zZV&og=|c6CwO%$Mw3g-oyEpFQ=3P+Rhvhu9^T=w*#K%8Jnj>@jDzrQDuS0V+c5S-x zg2o!(9#QFId#T}rBDvECpG>PNPnRVs&$;@=cE10Wp;oYWC7Yw$T`YLmJawZacu9kq zxEc^3SVQ;2BrO}>R zsk7So>TfXDfi48qAv#jZmC?N^Iha60=N8hokX3icvc%X(+}mTdTpHmCef8IGE%)RmC~h z|Li%9JAGyWMT^?<))sDO*2NSV5g3`xH5{Jc3!mXyjV2mjkKi7$jTY^7fAf#rH#j^y zjIHKr|2tB0?8<3O(k>V7pb8C;%~bR6T{(464!oYqMU6w40$Y8w5BvBdqY^Q;+xTD` zMtHX}ippMJq4y_MvtO%!vW@eVxOZ4$0Fnu@fta&EYybd2+MWW7gTSL=7&3(5{WVf7 zEkNrby9jRiI1M>H{k?6iY>ia6b>iXMrFimE9;53p&hI z8azi#3xc9xWC_0nkn`pAdTF|#OU1DiDq)rHD{wu|ibTqd=GaoM=SV zfWI3W6Rf1lu8)AQ{u&nCq?f&em*HVpxAn?5#?o=7@X*(d}& z?g?OOXou7kL^}jZQG}y$N@Ipu2*>%+c(4o<9BmPSH>zLUZ&kBnXXkjM8QopJqpM!z1K&(D21O=n2SZPkmtCeJO=>u-&fhnGn1*urI}+7PO7x!a|j!32rqDNm(JBRJ1r~6kJV-Wg9|D{l_I?qouV{hQ;M+ZK#(B>j=r)&D`ICLxAZmO zPETMI2zXXBUxqsTn<`f?3Xs5yBn7}H5$rjN-_Zy=v?faiViS!3WrTBLp(@b{Ab=@n z1_`_v#ZC%=6%ca>55u@h)$MQ;ypT`OihMEnZ^_N<4u?RQr7{U%s{-WeI-~(DY=?!y zDMk{ekwO53c2VkZv?8o($d17P2^hx@Q-XK_`_U-8wr)NV1RNumh!@x*_=qUT#{v`q zW`ws1QZUHPaA$bds2%#LU4|NCRu3#oha)SHk^=(A<>o|_31r<7qEWDAR34{ldD0LE z8UiODhUHIUBtnWALv$cp7Kb(2rCbyl7&0q?bq5c5I$!@}Cj$Y8A~4tiPXIj}8U46u z2mx9hE@D$=k*N7i!zvEKmWhIAXzozObK^?kWJs}P43R=nxy1pB83Ojym2sES}UwI!*21o0V8?b?JhuG0LaI~ERHVS~5glMo`_V9V8 z5=Mz+(U^fkfzYGy(g4DYfRG3jc9uIkl-3M@g2TX@d4sT46#N1V5<$sgh$+@q#AvaN zB0PVf5;Ge5ni!hD7eL@p77W==W<{&~6G2g+ zSGPRW;bAmiGk};*;YmRDx_&!!z!Rt`OO7c#g4C**c+<-|28|-jzyy|I^aV-Lxb(l4 zRwx&JVGPT@b12yi6_8bctne^ALlFoKgo`9%%|Q6{pTaY^G2LOt1fFRMgF;xa?l?10 z7^z|I*rCBfn3rVr05BSghR9|NV-y@l+6~X3t-yj{Mg8wt5SkgLkK&687?Q)x4QEMA zd(0;T%uqiJic+e?5FDo|8yAm&+$qm*Hoz5pm$nh79UlN}vML#N3AMLiM0 zJT**Qpv(Zp$j%?0k;p=(4U5v?i~z$$kZ{rfu&@aToxqG)i_pMIG*}@H#g0V=&**`p zpH@N{SJ?OcWT7Hoe78grhZK z2|#$1DTYm&UKBh*5e`CtFak|OeusjsKqxpVrVR?tGWtK=L?buTXyF*jJs`Ya0ROxH zKQc5-wjp*Is8Tc}MubHx3I(h~fwIQ=(8Cy*IGQL#9U2cK2waUoI9h`cO;m}QqS-*L zOC)Cw1UBIeK%=+{z&mINrc*c!(=`&1F${+)v!jt<%;JMa;)@Q$BQPCV!|*X9gQ%=W z!7wf3keK#BOo0JzGE7+kAf~_o=ED3zVlE&V3qTk_3e&Upj1Gkvl3e-#EW zMF;}$uX^4Wqj?}tMX|Vv;j7zz}Z`nl*hE+OSiH?j@m;8D{ zAA*1Ragh>|TS@NI`af?8B2W?^_!?Ll;W0k}DUwJV_EI=YH5;CWLrkd3`W2~H<%zqA z8p&08g+7YQb`tRksvD}3ACk!rKHU!BefV%@I{wn-I~v!{-`~k~77!I#zuB!{>PT-t zHKld;uo~87asN@E;I!6hfx!XGvs%R>$~M9_hgmTp>*-5(#zsIC zE0{Zukf7dr^~7}`<2_EKiQCWU+)^NMljzr|!YjavA_zgEY7$iQa3IqC`9z^{Ci(4X zq~RwO7V+=T?bp6c8<8+JNPHYW#OYvi2mJ>KKPxX|$k7Xz ze(^ss=^(W~IErSqW4uTbKF^mRn|!`B4P~TKZc6|{3;P8nW)$J)KxEhNQ(r5{hV0D8Z<=?IHf&< zh1R$zYS;B=Zi@g*gQ~&Y%<(T?daAQ$Gh{E{{Ys4eI3}96ukf@$DZA$-13u8*}BEbz29iN%dsGGFdzH5TJ*YX?j9T!SK|HQ3pQ3sBWL?;q})%F3yCHs(+3NU^%b z-lljNkSKfmKer80+ozvOFVesBDfp0k!1I9B*~PYZ&WM)HFAQ86G-YYV)NsjK7xiS8 zJgC*1+r^JHp?>Wr+bV`T@iaV$$5Dg+58pIZS{lfjM7@^gi2K_YaX<(S;l6%1JKsC@ zu}%lES&AwG-R)B95u9_^@`bBsI5hMy+mkPno?WQIX%?kxji9Wv^T>APK?lpAu_4@C6(3uEHlG#E;IBvjbP8gmvC*d--uA0aXGHBu&(5 zQ$%e8D)@Tb`ng_-T6&VR4kkupJK#=8^vK;&zssr?(qU}@?di5QBy){m`~1A_*v`<} z3HSR+(TpojAM)*=@;v8MfrK8<&9z&J(3e!dE7jyExqEasO~XANe2RC{)@|>tLrshO z2|MUfPbohy<6Tdw>M-hi6!DsQ7{({<-UUPC0F=#O+D#z_pQTmGYO4$VPOJ|RJ~5HN z2tur%zOUVC+Ky`R)nw9s9+xUfL)xm>)Yf2;#iC6QKYuD|eGn}Oad0dUOq`A;d@ltb zJ#4=9F$mkzDtd-k5qBSpBNq8AS$Dwe1opeMdf%;5{0mx2s17bn>f;k7YOB9*iUKJ1CO0i#_zLwAB&Sq5*}c(c6@2;CJiTI|%H2;M z|AYn?K%MzU%5Vzj*%|%qA}`Pq06YiKIqfV}kAEBa_iDc~v)kE0Q-ny|Q((kV7p^k! z%1sODtv8G(WamL=iXMyBe&godWvBwHjU8KC6OQc3Z<}uS8S%zGVEK?=VNY*r^=llU z>pa8Fq9f|Q&=BoG?9CIOy+Iwe0LU-M$F-v~+15)^{kLdFbSC-zW`6;lIfEQWXaAi% ztG{@E&5&dHY+i+-9yH)lGXlzn3npYiJt`BPHA4XF@*y!j-~ z1DFCQnQhi9eeqwceO%|;D{!iRWJi)<8;s6X(F?go1^>NSzs{)nYU~?mVNUNOmCTBr zx`kipMphS?$qNSID8p!R#f69oUi3KN2@F#Eh|W8h6AjP7=nMc^w4Vn!q~0GLis0gb z&-%ols&hJWjZ&HZvN_~Cfa01zOr-p4>`QQ4;mHayPODKwm?!Is{K`|dyE#Lf zf`|mN0W=tnJ^ege^258Gn-r4{SUZF9{i2ZIyIS&u=xv8021X`EZB=m)38JTZ#V3~z z{Iox&x^~=U;1Zg_@D<7EEHt=DrTo|d((vDUim5Ck?B?(yb#+4g(}SUks@8M5OI?RK zrYXY+Y!7i#E&@MXA?as*bZ@R*&&zUXFPr6kU#p3vGRJ52O*tD>Av|X{`BppqHd1$H zU#Eq?!Av!&eLiC3*lk;JLp4=oHf4TK)X8eTvdx~K+RA!^Ktxlmm;hPH!(h>MZVm~7 z?P#)~$k>7DFk$><_unA&`gjOQSy1rTkIO=nEM#Z_@jTG$ftk zR!k0bQ=wdn6vva>&nUz_vkf>AloqdC)Abr7u=KFbWvtNVOHXvZ36EZzlfm!3yp_BE z2LSu3*@)eVjkoWYLOtG7A2r-mvP}NC+?@TeIzT3$MKYX<;uE5%^x@$OR0N?}DSmrR15N~;2~>uQZ8Kv%cJOA1{L!S^F2c*7A~seU`k_v7NbvQBXwXpN+tSs>X#P*(+bgwf zlzuLceW==yvFO+EaM`Z!@_4}TWYeRSFJ~=$^{)2{l1))Q4~}N2wZ;=CdG^AOnLo^M zcPy@(5m2>1xMv;x8kny^Q%~rX=Qmwl_~(1PF%s6SDk_5i8OfbRr>I_&cJ^rhD00LM zXwM(s(kM1P@Bt|664$s@=6kYv7RJ*x@%D1w%*~@AD3yxyRN1TX>*^g#vCN0YoAgw1 z+fXV;P0|8=tn6loxji#qnvU#-S}n-=MpgHyieV3{6L}5eL=U5K!<$O0b}B#Eevicz zspX@yUL)WNL{jm1b3$i1fZJC~1Us9=iz8GmEx&ft*F@MmAk`2NoRq}t%l#&OLxFSPF1J>G$Qa!~>g_TfgNLrSJ22lI@ zNi;Z#B1Z+8&^E|X%mWVMnOyD)a@VMP9@|}4w?{o_od|f#&V$)kzGHQh+oELV;QZU9 zw)nPAPdO>Vl8c#9KxDwEnWZZl@6)j1GaJqmz58(dM5P8k`5(YD6zu|mMGcN`;cBlh zpr=l0d@E@ISm(^(M>MtOqCPpPHs(GriNhjPn46H{ZC>T06-Q9`e~Vt%FkaYupFglZ z0YmC!hWj67qemf{VUMi|r*p%}C*%X;^0HDJ)oIJ)r!Iqr2<1`)uV{e|ZT>!qlajRQ zx@mEBO z#d^T|=11;DIoLE-vZbWvaSa+B3NCo5sWHH_{U)ch#jNcB+r1mu_*)9m!r5wi%;i5H?t^aI8ufj7z zL6M}Qlv377=^6F`;2!X!*qu{Bf%zT%`^P!ojtJR*`rlPEvj${1VqWs}ll6QK@)D8~56omJU^LF|0Mn^UggHP>T7IOm4sl#(yIEH6%cj!T$!nN`cz-NoDi$Kres&GZEKUmNe= zvqH4r4+pf`J1nbHCVcDLjPrB)>9k+^R%-H``QmMNo`CMf*R_cO|BGpA$I76$94XFD zhM5zxLu03z*Ke-KR@%|kEC(BpVL={?h3g?YXKw6crwF!b4;F%K1!Mo|;>I$+A-KmD z!+3ri$FgS&-cA%bcfBSe&&%X}>-ga}_Zj8ItI-zzBnfWo>#%w zV?l4(Y8$0SrQa5~D6xkmk~9jNP5Dj7Cl0TJZ1VYwKTEe1>BcjM_r;$fD8!n9ZOTnk z;*iD7ZEMECN+nuo?!-S=`KtuNi=y@!$J^uB?;5mTUoZD80iNYhD`-fH=hkq)XzW}~ zj6mR!@@V0?T&kWjeUqqEym)}H`91e`lg&oaK6}TI#%Yz?qSBzg{HRDRZnCIV>D(;9tXau3Gu&m8n zD)z5Ue4C5WduFb491T!KdN9`BJdStkX)1MbSu}Mvh$)ufkeO3+j?}2stZqVLz!$=w zwznabcSk(O67Y*qh%CvVPvWRl*)Fx5*kQtNef0Fnn?$O$Bd+EsR%80nf!>n$*#Zd*2^uf^IaHP{m{19O=qyj!M z8-V7S@OOW-^Lf5(z1Xt3I+!ck_qJ$Dw?Pez&24*9I=JX-fXC6;7s1}P5Z}W)J6r?z zju~3y&!s_fOC3=i8VlNyP-}|uhd5=-B*iLcb6Y;^)CTuNl6;#yoO8-=C88$YCtSH+ zu4`p0-Z5azWZ{i!8Z(}%`&kYmWQz&?(|}t|l6rIZd*CBEHaES9+op$XX52tzDjsKv zWsm__#;*o8{>on%a;zY+P|5NsIPQ=?T!+JTI}D$yPCf#oSW_%TFckw7NHuCsl1iVx zuC33#pCI^kOqp2?ReL_I36+n&>Ui1)Go6xd(VIikvGfw}zQxjUY)6fDk)0O;3zJ@+ zZYe^if(r2x6-EpM;l-h+=dLq3{$Rz@+SuKwz7T@VgZd%EstOi9xIWgri8hGi+dGhx zWQbmROk&?M3HA`Yz!q`_%>h9lxx*M3RiMl+s==5i{C+Qs=Y$<$0 zqByZ>62izv(@OX17ytS~uK>y|)BQ`!6idDYLEvdJxfH*?OWQ$&7OMh< z*w6Zg+vNV^ z<*_R`d%<0Yr2&5XVlcmWxG-2W)DO$;5H_ZOi52MpJhSpxcW{^x_iiFC>oOhLGv#Z^ z$#F&bWT}oiCnl_P-WH3j^5XdLZ9&^c6s72&MXePjfUkHQFZ4<4zA;c!?=eFEB%ChL zU-fD5CuivPuL2jp_8+@FCE-vjzL2NbR|LsuAaJ~B%97^aN7dKk1DUYpZ%Tx_LbrC{p=sFL| zGq0fowWpqkuN_xP!vD>{7&s8C zg|fE+UcQ5)&75ZO)TVyjB!~fY0-Zd$E6?i_Co*@3EAEt&yPJzL79&Tr8MEx~^up*r z9Ee`F7)=J56l8Q|2YAHG>Js*Qv%O4UEmLUyX;_&2y;xVD#t0+5aQWEH+bQIZs-%AO zwdv(eDR}(RW`*srRy{G*Rb1es;Il$#d;->cRegh)`_=|2>0*6t=xRtd>O68ZQ{{hB1L*5!1kF;2Zl6hInQ}kVTrAspi zP*|cTPY;h?zod~jI9U6s?8DnK8&brhnm?m~wQ%;6oP4*l&CPBum%;jv!Ro{kArkQbpH{8fd(UV?lV`T{U*8!z_EfqWh%asVIsW_cLGfjD}mFwmG zJ3gMnr>;ZEyR4(o8AYdcAfw6Qu(NbCy~)o~R3Nd= zeRHfx?@Tv;S@XC-&qU}9WK=CfaC4m$7c^xZi`9*hnt*o`w8kEN*Qk4d9crw_Lu` zT|RcpiOWuJ=sWH(G$%T(T8p6fZJE*GB^b^eOrER-MhKk7evQ!s<@CxY zYI&U{MEaG#RmIZ7mr^^?+I?F*3w`o!g{6%j9t1uuRhH(P$kcLblFn}`Q=Cc4SI)EP z3bG%xvD3;Wdbkbs$4VMZldjR!sA*=&?FJsVY&5{iTt4-uv<>*_+Ru-j7&YX87{6ri zc8bsF%TdtQUIl(vInUEpT8|b#kf_Vq6oJ0x_UFpp3FdIOn(0t-GWM?N8T%_BTUZi& zu}&{mYixVKaxWA#^)!OYC$t?{Tj6sqGa>q2T5J8PL2ix#(YT2JdbF*icU`O;QH+ou zBw5dCDpQuj&289F;cE9wXCk1csGb^{-p(_q!3JAVO{v4V-Mm1P$QPw1IbS>`xJ@3q z6k?Bg*qUz-EP@p?19+I7Lw9}3;%j=Z+&Fb<{%2i=P2DMnMeQ9qezG*!AHTkt4F!Qqz_)>KTD-+8~?d3wx-q84SO^)b)m{AGvcizZWA z?lUILHnm8PW?(SRU7kBE_y7>3v~jKq|Mj+rZ;^Cw59^`jjcd_gZpR?7)}@2io@EQY8@$GFPp!9p2@U&YZAOW!nCW@`ck10GxBesH^3= z#`S&S4At-H!R<}rol49eV)Lt-ZXayat8U#^wPpHMFsc6`^=RF*VTFyG#~}89_++-; zC08@8IBo#ZO(CWfIvz(P&~N33!pd~JFK(ae{#S}0Gh36C>PA6iYq2xuQIl>y~Bs0SYw0k{j9ltS8&j|xgQy*mymBSKIOJY2Y z2MsFg?`j`wsQZ;iXlFb;r*YE_uaXF&YWkAC9u^)~svVJWdyI{?|NKo|#CNj%1_h%Kj}SX9}V{EA;LdYjhF# zExB+xB^sypGj|jd;lD>UTAxaEm1a8oyBg|b>7Kq)F7Q~z;rQK2eF}?|>+p9D?_mMQ zo$>3tC||$s?;hzfGu*g7?{}BUHF{zL1b*zR=FjPm3VF?&uS+Znj~|g)D#R!nxz-k1 z$>Bk`u=?WT^e&xrDJ1bG+a2~mwDIh`Sl;5ZkJ*^N5QZ}P_eMpW$>9* z*T2zw(MrX&g?ne3L@hVHJBolqQTG?zPpJgYasLCxHsC2!s?C z#9}6Q`6{bis^Cd5-I$rY*T09W=aPJXb1@NZ^x=ubk-la_N>&RiYD%oUND}?AYf_aK zxguTvQ)7R{3&Sw4U;DOJ2Mq#oJO8-ASNv|v8u(VUhKt0;(fY#4`R)7Km}*lEXVlj} zu)YY7hJ<)8K3|W+RgCqMWg^m_NE~4OTNvJTeGC1`N7dj``rJLO)a4*~(ZYwVNyBt; zDZQs~y)R<=l0Y4uc*#kJNFMZ->8y2A0)BUX!s}%LN>fe zBj%S6k3gsB|96BzIYRp{28Lg^c=f!`<-@MoArBeot?S|%{`;ut`UAapriNg%`aRpx z<3W3uqPivx+MvDr!&E-r&Aa~q8{K3Uk^diAXZ_an1HJuiY%pNNC}~D_BMmZI%F(G( z5=w(~jFfH#B&4N8ke2RlMY>y3K$_1pzu)JF=enN#1-lmKea^XG=e{+(PAhwB00S#NRL zCBod$(&R&Piy^>DIShw2p%>>bUFL)3_Bq5wSBzY?ej#w+4I!2|#D(JE3iy3nLg-k$ zhQe7AwO3zwq`gl?Hur3*fvr2C%!8z?k_efXyN(jv4d3uVTF z%9Gz>Dzv(K=1h<2_eAbk$Z}u(6MF!JAWwii?wBtAC%%bfwO4mfeI+_dETMfpE;%Z= z@6=68-%82_NKo)lZ3TPC6$!R6i2n!33N&5_BBR{II1hUU*gN^EHt=+`W8QFRT6w*c z^4|}(0XXcP5vvr#RhA=uZ#0A7c#Sdk$U5ao_B!?X3-YRQ7=;p)h$^#pd4|CK>JOaQ z^}xRPNE6Mr>*A1tCUF~oDJdKAZQVlp7{)7_etWf8Mm~3UX)FGkZ`+;?qyZp`mwZZYS!wsI@4W5@@GYnUI60V54Ke;mj(K%e3179<#=aA)VL8NH0Gbij@!q5 zk&t20)xQUHrvlUbWYI4X>3@BGWZVVwYe);mc>lRxx#+FFEauLoQzwC?(W>1RI7ni~Z8IGqQ~tlz39+cNfVLL;@Q4rnB1YEZmYtjjKWY z(ox|m$RxbAbdz$_(MN;$B4cYGpkOHUwrLVo{|K^8*>b!PK~wGaOqQQ6q)hAY=^}{E zit9hX%*)9fbSY*N7>Hxx=ClCLT1X?&oSQXw^tE{6d5@DBzZ!jgY~Bgl{*^Z4DK7HD zgGsmAe39yt(8Jn|RC*O$05UWKvGx2$0q`^%ORdQU22a>%a;28%rwW8MIWw+q-7yX1 zgq|`~$ymwQIQ|D9spJr5j2|5M<9)#AtPLK>n z4S|GjcpSeCk9P`~#`Wg(*R8bi{X8Ht)CbYIz|EbVnXu0j)Tpb=^b2x#( zLiRsONXd!D`^)kLa85~dxf38pnj1g=7Lo8snjz5pK@!h##^!>_q_a&}`0j9?#D`;X z0z1WFaz07SSy5@H3baXf#`CcJClk~bPIM^6Rke?K=91Fkk89Irm-i&Uk9O3|FkZiS zF*uI-_0&HG_yaIOZ@1&9uw6WLGH+%~Qck6q#2tuLWLo7EaXee23gc=VWf_|igPVy6 zdSuYalNQdpDZ8q{{@~Md@BAqvn_Pml7VXrj;y0{*$<}lnOo=;f`fHoe^&*H`JOM@U zU1PZ@W2IfHS_(#PM(wdI*!rOHW>Ms`xsqXP4MUVA zSdJzV7X{ZB&$KliDi&lvO_-6Y@d$dXLW6C0i%~>;Z;!hQ;PK<1e`V9;r&7?zl;;vO zyZce;aD3AG4{+O7Ac~55ChT@Qo_>MGeaYJOXs+*YDQYfr-sVesj(vN`ONn}&pb9wnrKcyX%cicjQ~1Yk zN)|V3^)tQ3JjMQPfoffLO4qgruaSiU8?m2Ldd_;vAqPI0<2{M5Q>ntm_{f;L!D>oQ%Y!WA^3)o=IyVGRhA@Y6nVp2|vsDs!3+CEi$$;7UtqO4ti1EMjn|mMSt92=6W6GD zjPGL#UIczfi=orh$b&cuS>?%@#7AIs8Z31!;}iLmi(HRORAUaNVeYa(gpW%={YJ0o zGg^PveRvjL)Bk9!NKGd#WxU^dBfU8S)MU}HYF1tI)A{dT8+3^w(B}n_q-s$s_GKK; zE^|YmVr~wiBVF(E^%sc=^+`b{C-EREU777Mec6W!tfMcUpVs(+5b2_{_4YOSmELdh z$_|OnjA?JOFLd{&Y0Cu=zC<`ab7XAZEg3?t_P@VVJg`~xCCNNr(hBKw>m5PBaK4|1 zx38AFFW&Hhh7(V)j}rzS6LjM1rbaeFkQ;$DVyBFnR-SBZ(zJ`0t@0l$^-ao-TK)o8 z?}U$Qu72#i{C^N3AQ%hq{~*OmvCg65Wx&2$6cmexX>oKNLEfTGr(*O;>k}3)hm8)?6kwd;fR>iW4z00AHFA z=@})PX~O!1CUVS#VZ`PWeDed8pmHqpW*qFk&=jbgprHVF_WXbmAg?(eO6Y|QIqKdR zj!&^gmq!4tXQe-YdeMv@b`9E=ApY@G8!KOIE+h%L^#pk4h8edMkgMJ`*VjMdN-@o- zg+R`T3^6Wx>gc_YwZpnDo^5%v2|ifX!ONzSO?VQ(WAZ+gHPbkTU(v|Oyy^uvrC;pH zK10%N$ zG{N8yy`)G!**|H}b%I?{j>s?blw~E;hE)~YBNhDq18oh3_jmN3JP2R9?+61aR`5~_%u|2&k2=_ z9P8?8lK(EP)`-P*^8-W)=F}xGPs@(!{4Dlq_;m6cR?0^;-~51_c+o76ZanNwVjG3W zI3d>K?wCTSkaj$N`E%HN>`Q6cwOK1*z{Lc-3AbOfypqzkwl!fF5Woh&01g-u{Q$M1 zj=egv@iZ}<( zcj}AR9zss@+t&_O`1~0e6OfnMe4G1boE;GTWR3f#?}~6q#Bq^;p#at@Ko(0U?qiCD z6)PWA@3L#bbXS!TZH*P1nI|P1d5Ie(#TQR*ZbYu=YiekIHzLw#?ISWYlkQUn?cLt0 zg11rN8}Z@G10FwRs%XPiyc-cRWPBNId!Pr&YMtESmy6n7p(N+(0r8^!te31cpr&#&vf>lC*BUC`Id z;$BK@(@@|E4V`A;TzgkgYr!a&Ab?YVL}RODt$cPMQNEtsVzsRMy_eq*#HJ;_0o?D- zR;VnDOS&eItrHt!?q=PsveuU>v$!AW^rxd-472E4+)w3u68Ik=fZ4s$H9O0g=S*;h zcc+->*ZAoy>(dUy(`n#vp>DWF3`B@PNGNx?ej`}S$%)q-PZ)pFq_p?nhJX9hA!f~Ck`3hb6(1<8if~7F*jiyPeJBs2(_t~} zcTtI&-e=U2+|kh5%0xP7Sgn_?L&AOtXb4izqDEFK~>=uL^dzpg$9 zpy@R~y^0M8ospI2MMhJSxoJRPjgQI*={W6lbVyTcX~KcA#&H0>oiaf&be%bYY=K)_>=i=mYV!R5vq zLNQy1T3!6nQ3r)JL_l_M9EMP;xis`;7{4-bj0~W_IR63Ptl5|ZVt0R zn%Sbn^}!k;Tz4?xEQy4DptSX%CZw@b2Ft)>z<$L1#6)g&8z?0?4gz-nwSEoG4xgJ zGY$QtN{b_^I5UTY8LQ`44xr8w-)-*%?K76b1D6vsnj-re>hRk1mPe+7? zfZhvHQE(~^6u*rbUpdQn3@t}jtzHjdBy}>IaNO5n7vXQ#oi5Q`g}TwuCy4g6vf$@j z8{L5-eiIc>OC22^%e!K2`DvFL=8C+tp%k^$+-pyMUSPGiuULbcq@kPXFI<(dJ*P5?MS!V)kB+$lf`PIc|tdvJavYhX;X!q4dYxuVZjWZW#0 zQ7p#xP>ZZACvf3a2A# zw3f3p`f#r=tPlAGSrUvt>y+AYTpF3Tnige!l9v$Qgg8&@J+2QxYS(gJo+s+d5X_t~A z-V}K%!`!Q7tKVO}LKw2ihq%1UIv7key1x7*ImiL}BlU#HZqY!|zG{-ylo?s4YChpZqCuWO_ed?dIw_ zwps?Mc|2uwK0U7Dq-piJ8LpqkLt!;QtvSNQU6+u0-4;dEU$H#L_)(b^udsiGUaQAR zor3+DpY4>#E7oV~5ZH(ZJ2{ESgniw)yWdf(keGPS5Wt~Lz+_pB)K?)sV|t54LCPnJ ze}>-W)6(pWU~znY2t%eEox*nrb6~icS~9n0^D|b@4yzVl%G7-WdecK(qj$ z0}}E47gyN@ZG{o1o_1!ezy^-_n_!Jhy$y$W+Oyhe9tu&K8ZKBvK9wsSi`8rd?w9c6 zZ?PNiH5*;)SEO{GXjZHKL%7C)=#A1SHVskHud%-o(AZbfvd^Ad?jp0_^z`=g^*l-w zsJa1kN_oT15hTledtDKb(~LSVkMeeuZH7v7lc*d>%<* zaRV{NP3`PYMZ(Cn@Kgy;Rm>BPiaxe9LsEd5<2-Lt&2b{ zaVzz0iR*D?*qeo(|~`Y;KQ8;X>O zWZk5@3D*$-l^p=z``hR8{nn9XZ%b3i>H_7%zl&;9-}q{$?8M~>h6?vz=Hk+wIaUVs~d1q4X$0xFv2u49j3eE z9<)3{sQ532D^{MnsgV^HsQGZD_A=1Pt7pu`rQaI+r%oZb&Wo{d#JjYw6rVABkWnQy z!Y0}DI}77!;zN`O8Xnr)5})EQn&6RSRyAOGXTSc83=bcMRgH)(C=!?Mj3pQNsSk7E zp&KFk1mC`M_~B-&*3j0 zI`v^me+DFw$Z|EXV4vG?JAM9ygA@OYoJCcAOk$25@}^IGuB36E|=P zl8R>(ni<6f%j*oe84Yg>_l35z{Q)VVofaMIeKQ-KLWKs47QzMGNyOLn1{&RwFKEKo zcVE0xU{BVElrt?m1qXndUoNM;!M_pO_Jd%%U18~ZyZ_@09?@fNwb4X!}a$O;Ifa#ui z(S!`ID6a>eFiyyAEP$tp){65$nFI+cmDOA?OCXgQ4BhKA_s8>g9HI2l3Z59w49Gct z|LjcTSpW?aoLM!Wkd&JpPf^XY1DF@3?2?tEX_DdZ_;-p+tYDo4S216E92$8LC)M{^ zhL0-Y$@Tqx!$#dSQ)G{3k`Q}NtX3((?AgN`3+twxL0RV;JMh@uj(alL$22Hg6r$7bKvgd-ethuxnML_l4}bWZ(WO6tmQK44_} z^AMHRV8PRtDfxj?W&}OT7ETeWQVqh&Y+Jm&`wQiq{n2cdVej)l=dl|$IJE7Gz&x$0ZEU}g# z16tcGuIutUIQHSm@y%|k{P&jNm>lz}52aWi=&Ia^|7d?o^egDi#>CmYYoGb_W*NXW zZhU|M=zI6!xP6?-_yFCIt(#5(mtDtxtHV7Lro>aq#Zp{(A$m-WEI> zu#H1t8o6Q(N~)rS0LHY_vN>p@%@?t<|58?McplD4g$Y5e$jq#xtr`gN{<9-cS8VHHYr|8gwxAMbWK77rs6hQe2b$G2^`~oQKC7{G9aoqQ#qC|_|8rj z+6atYQxeW{MHap9@B5J=IwM#UwhRH_7m|^MboXbUntVa9z7N(5i1mJw^g>_ggy*4r zjQcmC)5(s56nB&%QxU>VmsL$L`6N7cE$>)}hj+)7l~=k(3Cvz;aoy&rgQq>96x z?>OdhMt@ApC8p4qlCvYf$l)ylh|qwOw_IfzY->Rxaks98c?BB@nNMPx&LP$Gs<({K z5|6)xebYH~SoWLWC_@-41V?`o3?$qLh3h7z`=<+DBo=rv`viLWJo!Rl9TV)jrsfyr z)2iar>&n%5896;(x!Ncd5dWfHdcnC=;znX=aclU)@CF8<1_`w{2Q-sFyBk_XKQ@w~ z{-R&HTpbeYI?X=%P?~$?vctYPKBdOH=BkO=RgNxK=)n`~R^1ea``_g%ZRlsDfU3&6 zR`ITc_R`4`VBHw+I4o@Yg#Ny4A##JVE&ZX&V;4#Y-s}q_9x?N_+N6(a32~36x>oB8 zUx|6DkM1f}oeSBS6MI)j`Lj;-RPke}`WMR5ftNfI^L$aZGIUG`i2f`VcM(uN&xNPN zfrooU`t=9TFs3EIix-$GQEQMtuUQj&=5-9l#rg|a7)ZFxsfQ86+$929E+~76P2W>1 z^0C=JqF}7|b+U7bn@tO3F;f@Li3HTwrl%*JwX#Qz1@EYtVrwfAblBk4V0-^q{b*pu zrz?GDRfO}QP}4Y-=zFez$r}bWJhukM0WG7?vjn0K4-qm7mfF`6B6D$i5_U=Y3u;l& z_;m{gtKwX6Si`72dv{PyCPV40v6RyRlUwJ?d)SZw(~IV3+^OA^ZerXjB+3`7P#o?T zW{3WiKY8}ha!s5ylqMATE|`X3+$`Q?^r8s%y+hV^`Q`6#XOvNmaSSzI{_G3u$6~g> zX+Ey!A%OCUkNIKaWT^{mJ8ksS@BwiZDBE!Q=AugoyFMZYKjfH@hhH%q|MtQ8M?8V! zf{R^fR>`6-srY!tXNSQz&#UjA)gH4am3^3Y*X!$nIh$R;`CZ;q6#>lX_-v~F3=8{= zT;>9KzpZ7ViZXkDjP7EO4QtzCgmR}3Vg1JsfgX!)9DXs!f>-^LC2muSw*M#=SV!fm zQS~34zSOfRKC0<-PF4{LwBSG)ryzH^J2tdV-kZ_nDs%6GgFK{zs|G!EK;=7YrlE+r zk$E%chTc0WB_@*I5^T?T+RhlVEBegkI&5Fn_B-29(u~vPZ^xzAz zJP>LJ=4_dYZd-}{$=X$HcuJ0v-G)}rh5t5`fVh%-Vw=vtcZ7| z9iypt-l?9Tzz4^)AyZy)uKA)Pu;*oV?cbWRNrHiI3OQOlf~{AAUN+9q{&mXAWb4VC z`IXKk$8Ap0z;_&S;5b&6L(OWZGLRl%@!LUs{^8C;d8bUz*`(Sga9%(N`U0=&LjQJ; zo1wj3@{OEcEK-RcNlF_v$wx=M9{fD|=s47iNGGzB%B@o0J)N(3d9(TXjXVV@pNt3KhMsf9T zMVJi+RYnrhWoe1Fyrh$dJFDiZ)}cC@(|F$1E(9>CHMbIbN&TG}5T_Lx5ZBUCF@sf5YZLu~>L%xWir zIX88Sec|lk^=kV4{YuHdLvZPwXTE-WKW&%p_Nsn%)DhW{UlPSx@zNTnB0on6XGl!y zzqgbdV|LeQWSg6&ETj98hHYIjIh!J;Pyf8Y*@JiA+F~gcS@>l4#8dFEJU0F-Po7uH z!j7=&5SkBl3m)CZu;;@w5ytmd6SZ8l=>8L!LE7ynLJsaDyh74&5};ALJ54yKrvmh5pmCI4dn7DB=<>e(wE7D z*#LK=D^4B`Gx#_>Tk^%{#a<0xDvok#?MW?jf%0g8eh07`J%K$>D;L%~USEkh+V+4K zN{DlHcj{jo7QZ#asJoKp*xEI?F46?!>E6cz6R>4vxIkiUOblsl-qyLch?ON^s+3v{ zIYR)KF2ynWcBE-U^2g{lL;gDhfnVD}2^W(*(|wp*R@SSZHb#4oVCS!|Kr1^Hev&|x z=WiP4S0mYESmqhDQVun1gM&-zd>(YFqY)IDJJ_0h7R*{v8Y!3e2l_6Ir-sMxhd*^< z6kAmT#MDtp26lybs%4UVq?iCE$AmhPmxXeOlX4>~1qtv)N3A3(h?ko=D@eU1U;M3< zyHjV#YTY*{W`@;JTHcO`-Fn^a72!H*zv`8v za0aw^Kvkt=ZR2=I!sSV;xu-GVryG9t0yKZoGzxVmYwvw8;G3gX+Op9W>Oeye=l#KG0gatbaBE?DAkuozOi+ z=g*Q`x z-Z}xEj=IFYJ&T;K)Ll+2cmcMfa`iVhtF$^!I*@9Ig6`vH5}LZgrjW8{_xB6J?2+xK zF~aTIw1lPkSHkr#J9DB{xhO-d2G*qG={qL!LMI)hU%gDzo~4Y^2VDar@Won%gF}8{ z<4X)zA8Q1^qz|xV$o4O2?a6$)BYOXxv-U*FBBeW2Hja0A!V=bezT?yMRPddcUw~Ge zJoCnHYhcLCuhdQ=?7+7TfXW5b?Uv}c6p{Jjj$|+kT#S_YuSXQu(e8S|^C}D!BL&gR z)+7I|G=nn>xC6Y-^kh`pr~@XJYbnb+U{d#P_Qq=jK9~*1h&E!5(U#<)J=_+nC$^5H z?lyb8-n=ZsG_J$5kguikgDw168M{rZ-$9?_zs`Z!;6lt@uEi{6*DKZVQ09&On3NRH zZW7T2CqUmNgPt6W5wPVBYFerYN7DgtRdg043O`3%w`s*WDii-R%{cIkUVeIvhHi$IiVzBuZF(p$J;PsQIs&1&u(fq z?Pg$E?FiGmCt#XwG?Tc0Z$h#3NOn?nPAE;WO;J!#HytJRxlLdfbqns-VN zX98wr&d}%OK54Hf4r8+I;7_xXNfQd7G#y8VA{gD!5`5Y(xY2@zKT1(0W_fDw$l5$^ zcJtak*vbLNl}dEj^zapgMQR(_Wn6shl9LIaqM38x@J+>jyE^9A{nPMI(Az`uM&?YD zof$^qx~5~wNF{@^A4c-Tna?jA82W;X*QnW{nK|$)-$yxh!tFn8@z#=HC$;vQUDr~n zlpBvlTPQ?bo1&E_gU6o(bW!@55n{MbxBQirczIC}BZ4vEYM!w&gV_UlTu zPgl2T%fFu!k_$*z&U?BFBz?_bxYn;O+4$Odl8z5=?LoMtlG|i>X zofW+yr>7qloTc1EGq(N zx%qFN2+S+a7!DGh2)8XzAl(zTo5oqbNr^9{uClAFYyC0 z4%ZfNuq#!-oG^u^3HCXL>P+M!%D}2}JL%we=I=b}An_Y>R#r+E5;2DvY?oz8*6ya; zI1UYN(Y?n6dSt9(7EZh) z+4>Ot{9+HCpIyLb92~OcXA^y^!;Vf5)sfyuWcIU3z_~R{L#1y%dvX7IBK_?89uux6 z8vmO)0lxAkK6r*%rOW*@ksXQ~VTvq#FLP*9E9f&_W-87BhLTWM7pPeMbF*vZ(-#s`gAA>~ zW0YGQUR(=h)EwsAVX{ho3sry1KA6dW(J&l8#_q7>* z`=eFD@{^okBeubJyI`QRBEkprr9X&G2{PG}y!VJ@&dthSAPCXYl`^V!qx09i_}tZ@ z#!8obBrPSQ+!-8lqK>(_=yd^0hADV`hA=<5-p=8V>ybrM-yjg#(G-7(XIb|v)|DFjA7QVr^w|! z9kG#J|5J3>*^yeJ*SME@Y1dM}QN)&0?4V zcGUoa*OaJ^I=xO~V~A8bWWm^2b%c-J%-F2P!q3?p$hBybGREc|Q*Vo*Jjx0So`Mcxg|6j(Gg}6nkpb+a-hdu)jIMc3$xXtL^|o&)2;s7 zyl2BPk-XbPofXEOqnT&yG|o9Ei9(nG`}Y;y`L`XdIZ<;lC)C&F(bxb5#xR}54|U5@ zn1Wmx0(Zu=xCfFf*5#sPEbUG2D31V4tnzt7WF-zdhnlxs0dh(^&6!&v7FkU#K(SK% z$){8_7dZcD(yjoGNx)Vve1P*H?>KGOGqb$%0#pTR!ng%MT}$T{#9UffHvGI-O60h% z4>_MUSVZN=ScK==y77r+uVYSb4f^03JQS-XLNb+~&CRg@lz`dWqn$E{F9~lH8eP$M zZPv8MhljGv`XF?jFtK=gw=%czakUj#9joxC$$j|%v<`gZ@~ER6M%uz!nOChVlAHy% zXEQ0&(4Sz0GqJ&GBab}gmk-{JA1`b)Kyd}#7OMAc`xp15@3vvzhU?-oN6GVme*pn# z*(G`NU%}@_WH}iRoIgKMEX~So1-En6sWP1Ox924%AvH3KpO;3f)MU?nSAYJQ#5vO! z5j8w{$*K{PfSCS?-+O~XDG1qE--@>!SHPU-xq7f1(Ud1RV~6_3y%kC3g}ern%cg+N zNf#t?P+&8VlwD0+G7TYkP#VOXYoQhE-xOLa=9mU-BTsMTc#0{Qu5cTIYtUb2gLx*9!xvXIR5c5B+m?Vq7g@l!r zb{#tgV&e8x+51S{q~&0ooL(F^1T@NsON}-w2EMBjRfY=CW1pkrHu>T+JSm=(nQ3wu zLIQ81*rry5JZA?}sz9^YUk6i3)GPWG7TdsF)Zz_Gj1En3x-Ncj?okrb z;yk8IwM?Ie;Dp6Bv)|}Fn%jK!MBielxs@VHtll>E`L^iabD7`Cwy`{yf`L*q25xI( z2gX&4g)yG{xrPdA0TAs@-L25EgLWldM~IfMWx*qw4Am>wK=@7_RpTzz zK>y|eSch95A!g2z3l+4y>?BV8mX#K>vnO|Rgv8B+)77i)&wfj-^5p*D<5%pvQ`>Xq z*bsF&QU3d@Fy_p4+*ey9B2!{w0JZXKtQCNbh12&RfbYdbRb63+rRbKss_Lu8eM(6? zCRxdXXPoWjzt83ql?DrYdK~NLUnVu^m6zFpd2Z_#(I_7s8rKj-N_aFTKjJK81NAL^sqJqq@hE7AE$#HgDqY>uzN$ZH}STnAw{w%D=Q8t8Cq<9~od`KPP zWEe9;ajutmgiNMn{et_OylLJYqg0=}30@RLt$DZcD>nKnR;(rGSS8<%Ps6?(@r>I- zhBoI!@x_Mn580f8<-k@=%CZ>loIw#0D_>9bk9`}S!}t4EVqggTbMi(Srjse0GTqn2 z<~XnMbaZ*AU)A)SOLI1Hn5wDyK;zZOO$g(Bu_{?)qb}lSEI@}1K0!_>0&I{AaTsSg z&B$>*75U0%m%1Z>dPLREYyWR{e_Y4H@##f zk7d-#a_FD=Nn7P|N!f=<&uf60Km2|w$UAZ)hroZas`S+yTh(bX_<7v?J1lN~rF7cN z`p#h9CYz@OH)6yGm8A5(P(7-MkJ7pM2$kC|(lk374)Vns0xiWNDR;c6!{L9+sXl>bP#( zK;+Tn;>bGG5Wy}9U#Nlvq9Iy-Wc<1?5hE3Im>W#9FJ9|PDspNhRESE{yO?ZT;|86Y z>iLYhdzoPHd6`}Fr1I9`WdTj^!1HW%R0kRx;lNB{-xc{xGG*vJC<;xfC1T>Jkfx{){XiQ)aLrM z4Gpymc0mLZ-A>^6G%C0#NF6lRJixLSBE@cR(40%3YMkmx)IIs*w$kUvUIivU9(Z`y zN)SaNMHU)1*wl;1CIwf2-Td`X5nr}+)Cdt=1YX6EIoj%TNe(HIS{J)~BgJ)r2mu~XQW zmG@&&iXkyS)e}O6;iYHK`16gblt|QIUHUk5S7q!aT>H`1-NPEZs=hOlC_nG`+mL9c zCrMymRN(>=Vl`ing6M#EbWv1&_R;=#b63ZYiy6iY|)9@^~ z)?W!Fq=Sh)J{vsg$BN|wkmM~E-DOeQ1fPRKiJ2HXKyc<*kQMSNia>*$l)3kfgMy(E z7h{%T{3cE@6Q2NREi9xPt$;GgLmT46uFK;SvufZ)10nc*Suj2OaB@MYzBB?vqQ;Ue z;s6efM{+g&d9!P(Nx~hebV^sX#!;*|%ii`qmSy7VyfLQZS(vIjaXI!Q@7=2;sHG?i zhr=W2NLIllDVjFDRAYZ}3`+$BR|tX=s;2q^Mh@1u{3 zUQ9`{j`}={BmGREbXW-{(D%6S)eM+OKPU}b(A-o(T{|^?g18L#g*+Qrvp(}P31vQz zt@<6&<(B@SD#uJX1(4KI)xBAOi6dTG;3b-)8f8LMX_+h3!Apy`IGozE>T{|cNeGY3 z52sZ!&mvMi6~+R{EsN3hDFP}9P1i!tIz1-zENyIH#;fo3&<5rn@H=l)p=U+|y7mdL z|DXdL)*eHIXKkQ*Kc{{m*4C*_EL!HvTOl3dbz$Vgv2yT`7HAbyy4$!^(gN5aeX^mU zr}%PEjnPG2sOl7+etPSgNs8!V^5;N(xPKWm?6F->fTr zWETdU1X~ok3~3cc3=x+TGnc>XS?6J#Mua_~mJsiAS<$Q?08j^s8QxQkeSH~v8E z5>Fob==IRC6r3X=D)~^(b{4`)0Xo4V$1*3s0p?)={=jovLS9nJ6g*#d_)-r-Ev{-=OG__9@ffgzue||+MD;Z%v2nyGVWVgmm=SqdT zgSddmfg<=ZCL4MWg^<}ufl~}~-gEu(U5~P<0I^tFn=8evenLpUNa}KX`}U*k@VkSx zQ0Za1c88!}w7hE-F}22@9>!$7Eo5WPb(FB^Go_rXLKP3*c4d^PZbBM$U|}xt>{M0Z zYF4kmI@4CvCl2MP8^EjD&~{hkb+^0K8h~w}&-i z3|FFQvna*INdVMBh1Quv)%rp|m|IN6LK0?xA~tt>1-h)t3N zEy>QUkX`ZbvmQvUR+e+(i^q|nlY>hdQ<}V>1CK;@F#!~v#KER!%JgBoYZLSUm$1GN zY7|z5>xo1-nAU8Q!oOm76D5Ps+@lqW*23q0y3c~pw-WE{c(YvVb`hrkSJe=@FH-cS z0F<)V<~r>|YF`DnM~Jo)S9|>^(UBD!<7?1|QVS~(pLp}?eB(7Kw>8L$ zD(_M^GCZ^#e>O-qg&UDbrGl!)HWjMkS<)t zBP`RVvVTqj(NZ^_08L!x2;FA^pP9AK1Mw&Dayg_maTv2c^O1jeVH9ZL3^Z3jxmvGv zdHJdF@LK2FG7u3C?{Y=)HD;7pkSZ5JS<+TsM}K*r z1wpOq(`zKw3h*ksX-cp0uoLxDtdm7RC-p{tFWZS*iS|vM`w=U`Nuy2BQhmPx5sV=a z<#l4CrrNS*!=76c+=+9RrB~}()pD1wCQ{NEv)+$E1f=$S4o{J^n?~rR^#R>gmt#u zrwK+VVit_Jf3mcTnDu1RvF#v$-j72tKcxe9CD^N7PT-mWB3qi!noRJozNXCO+wR9q zng+k*_&nu|mDu4)qG04a@yWAVrSkWng78;wv}Z-s%~KIYHn`t=nj zGe`k*V$~+Nq?WcSN;=w3RT~eM$GgsiWD+XiIPX%42w@z>K}0H9lqV4|Z5A9@%J#s! zd%zzXJa{Tsu9;AWra;v0<28x=WgE!`;be+R@O277h(_~|HwV9MndR8@f(`KUZdaXQ z`Ai~;1BfU%SaJPZ+|54KokH#^XRjscfE+Bi9%Yq46j`6LE0hg-$OanZ&=GqZ9QIUS6jD^YR$RqGr+Z^P2 z8u6^%%6OA_8U5PAjw~u211n(SxmMv+ilFZ9-ym&Nv{5Y4+%G_&Wg;$dMJ`nbrPLo&%Lhu)@-c0Z`FG$NbTW8>I(V z6)2a(Lg)vKiHYq2XvFA;;8%s|DXXt43xEL=w00u6p&Z#80j42Ws=T06Vwb4tZcGw} zoOlqDHnhgki(U?I#c3k+s~Q%-g*c?QXjb%S^04QLpom&%N&vY4{5$Ee1$nv3oZ2(^ zpgD0AABl!&a(*Dy*YI~F!K56&sdv0zLnANUUJqDDf3QF4EKACwj!yvoiYD89N*lOG ztM4M%Me306L>^nV5o*^b<`U!&!W;^sw`#e5-O!rP_tRsYLa30Dhu!DgmC7!klV2KJ zOnMEaSwBzV0{~xytwT>*T?z>BY>21W0W__k^4q+tRXG$Cm@6%bO@mU@>Q7p;fBHVd zNR!iIn~VvpC)5a4$PI>&21J7O4FPn2&Y`6?sfMamM`9XiI7w9h7~`htt9*Ur@CofI zXdp?hsgZTW`v;u+z5{EKb^YykLR|~?pv0}tzjCID-29*ne5BG&l`0V|^}bf7mXsroh>nj^EF zzL0pnzOuijridmzvH^=UTw<7!K7@e&k-+Qa!38p@^=>*efK`+g9MUgq)QxA6|C{5P z<8c>m_!L(x*J#V61V`DZ!rs>ZVd+d9nST8E|K2@wZOnbmTr)?rB!o8R%(1!2F=s-b z(1oO7m@{`s%zfsHQmN)XE2PrxswfqbRH~ov@Avx~-pA|pdcI!I=c5fcYZjmEa4}{h z-Nv!Ztdp)Jjz`Mgm(Y3htb>F>(*uu(s3(5EumSlPn7D|k%n_Gf*TlK>BxX7e_XuR` zj_(ikxMTBoBimRnd|WzvTa{m^r%lX-LZb(t7w#MKf5;8l3bZ9j$C3O{oyR!^%OCX& zQc@r{RkwSI+1fTAYei4J(&^PFs*whVfYlt5Q9p`J?;_q`aUk%o;i37y-Nn{2!N zQTz$)_I4tIHy&d4QY=;NP_#j@IJ(vEF_OrxX?);p(v=qFFh_$e1T}=l2c-s?p||9ZUI%4}jQLXZi=hEb>toF>EIFr10@^ z0hsF3fE$fni>Q~Ui1~W_0=_;nJt*BQ!Y}Hd=>4wgYAZdzusn|J0}u28Yp{+5!|ERL zm6WtLfs_V`bT8QmqUPkDyQSIYFw7&$0}_IrVngosd=~3E)G2V=Gl(d?3hOYo&@4?; zGPAl+{|w*YFGQKlcOwLE7EfG698Ba7EE7H*?}iNF(8n%sy#2FY|FvRM9`W`%;6={mY7u0DM3x=(4K63qs0u!u2R?-^}J20AWT2A^MiHYzYC zcDyCguQ9z0Vht549zSLukgbjX(5l=$|Bk}4^*qYfKj5uXr)r(Dy@N?FQjE+TOXu&n zr!xw#O1z3i@rXPhF+bIUn$;g$^)1&n_xKd$m!^`e3onSx=NmM<+<`4|H5 zO2Dp^hDlx>c9h<`nm@uN<=3P`XvGr`#z-Yj% z#(rUX+bJ&-rsOR{%>(uyjV}qPKGQ3arTVAeMWCSw=n#p4R$dxiE&t3jXuwaKfD+UZ2BCj_hsyNeFnp06kV+RX3U{{ZvD2R7OYK)nx zfumi@2F!{5qvWPsqD^J-ol^9DfT%e%LuP^z)fsm>9f)Q{tHf;!``=yXTX zpPMJsPK0=XRcoztbcEET!Ykc97=5qW9uHlNxWDjicaJBATUz*t4g#~lZPh6uHFz!w zkN1;5enub_xG7@DYiZ~srtQq<7$4uhgxV&qho31G$3J~VCCv^+^M4Pvi)erSBz#k( zDJ+MxzSio802>G}rVbA2hRCWKNb9ge+bs(>+Oa-MuB~Kx=7%+vtL+|lRKd`t%ql{c zl_x=1(LTHb^ckZw2VFn#LLf76824^8Pzs>y+Up(P|KfI1N0=wVeM@pmfo0$}LUC2F z@Ii1j-T5SQNBL^x`L)i4v&d!hvOB$CDoTmAQL3t1sXM<`6>qr#8gE?lHe?pa$fU?t>Q9AZzCW z|6B3!zgCy*H5|pt#N=|_LSLj`CN^Jwh#Fco2Qnb)u!HaF#XR-~$H)84a2utX_c1Rf z@k@!9^hSzvPUd;dC($OSUo8?!!X;6%{qy}4g&Kn{vDz8Deqg(F}U-MoaL5ex<}G=Du%cB z17{Rq%FW=&3yIuk3q!g}(5;+A{juE3g&cbA^Z8mlxiRA%bzI3YY}CwKSC}UwZq`AG zuUu@p=c?XIcm*G-9Z)d0b0z`A+htysf@fBntMHkkIEs>`^@2V%)>-Q_Y&s0J0jlwJ zv(~8OtHX*+#-T;VGc<3;5kH!~S@(GlNR=+dxU+pDk0r!qeHPk!8$r*AVi72~su0K_ zbUc45>pSr~JL^G&QP=br`Gb`%Cgr`i;d{TV=IUG%;I2Sj#(>O6KjfR1wNP{-928{5 zRnG44*63AK4wuZ`LNdPHlY~kGEI4b!KQQb9>`vyPC-W?e?EKP;!8V)QT0Q;?^h6Jr z?dC$y=0cZ=;ae}K+5X|7%Fu>#jLqG7x^UIo*d;QJ!A;J-hUDO|H;&HQYzI3ANu`Rs zW~x~A&5n>vdagLkWWmtsLK-fT=#arE1Nj~^zv3fm?}fXIL{mB`4)-Y+vHWq2(;!-s z5z+Ko$KnI#o1>nR&N?=gZuy$XsUKHrvW_!XmO!?(L{X|blfbjQccK2N=QmU+%T)&N zof5tsKsE6TXlgF$N}~63b|JO_aa^RS=7r6RBI1$}8f7J4ImX->Qz_3;!^s65az}-E z3}>I$+tS2_kV7$EzvCp;=n0u;KJ_C<_h)9cG|G7yQT_MaXIqqGHs^RPy{@sEiT>B) z0(oy5gh#KFkDnBJ`uqf0+BTyi+^94`1!bz0WqYQNU3}cuLm73$rIi>cN+RHZO4}rK zsR19hQ)*bXD*6FEWO@?Kc`B`!Dxp-Tt5qCGT@?t7xi573WWVf^;Z!_6=irrsJu5@^ zsFGT{y{#lySW&_kp4e$|e5Ri$aOs#3@>+Yr0lJQ!=DdUpJEnLUF67`eM zv_PlnkKx^sXXxj91mY*+R8@3Av)tXp)Nv;=kO#?y@wH`RvV#bh8HoE_0PQV{oHD!J6QTY@JS+agFHFGMR zIif1R+aP!PjCB(10E3fewPuso4T~)B-l%O=d+GjZ|JL_tK#)*ANldN6N=qZ++Fs{i?UFn^=CdYQCwIBycV77JPR5?dsY>$KTL|QRsX9mqts2G z{ewu?T^_ak_ z4-RS^y#4sgdZ|@*Yaz50_z$dCyVeA4dgt9-7&dHS)*yn9tZ%!ug61COSowk%o4B;9 zf|Fn%WW9+Jtz_vsSpx8j+E+D0Xn|VPubEOk=Ry}XXRduu@4XqAqY>hs;>gv=4t?$Q ziJ)l8>j){|e?r=3zV39dPq=Jj$J4Zw*A7zkel!Fia{zTU+wrNp&#cu;5}WHu^O?Eb zC~%#%oS&0Uv*@!r`!!o&2zR|u(aOb}YWZ=fCj2VXcVH+^66FM(?3jjOhG&>1kx5)C zjnEL2gPKWoVovBrTfa~*6>BP->sX5pyXqN{?@y3kc`ws{JE+%ME(B+EXaxu6_yc(U zw|l+sPC+@z`_bIe>Zn_i3H#3j%tbaZ1 zDuBVJPDNCKqePDiHmK84^_qDBT>`59v7sx=J4NrF@?|7A>clCPVpuq85K?xKJM~nOS3J>1O9T=L*|S0*9{S3s>)Z3~;1;3RI|P0y{*>T`+dSTp{u4^) z3t28mkR#9#sTYb)@U;>r4B4hkhxF4g)VSRoO=nB8(+V7n?+HlgrsLK8Bb!v1nPU?) z*JoinatMj(*j@)$HAU+@MF8r;gA9oGIbmUIQ8&`>NWdoHdfW#~c?mF#h%rZLhOZU) zorwJ}$?ma=Jrsdfoj#)kM&jrR-WOd zB=Ktjs9cP)8C#lHiI?Nl6Z|$<@cXfHMDmW`<&qq`M$x&*KXQ`brrVGbl={@d$ktIA zN*e~G(e2RLfpH_~EUyG>O4BWI>&%=aJDXDW^|Rq>O4r%XJ~Jx{y$?=+T-{XFp#ZgL zpCq)c=#8qQ;+*or8LCRh)oS|>)a1ATD$GUVCAcL0cpPA!2gl0E+Ah4!X+TU@9nO8f z;5?7MlbmPVz9(CK-A3Hr74XQ`PNJGpX|{)C`V0B0z+$2EFDs5<5NKrzrqmo>-3NG<3}XW+Z7c*?L(9zju}`v|46%L;aH4$7biyGJ2N`& zS%fqUxkUYX>E0m3=$GuVjvteD1_|-|OQFun0-R4K#50oP;Co-1h1Pa74*H_O?{Nqr zXmtw)&-M>Ibx*OiAR(kv6DwPY*e`lcPeVG(mErOh2F<4Zky`OtTvxluW4&P{_$gWn z7t&#S7Ww?rD-r9vsx#!yKp2aak#k-rzt~+;1H8*vARhk3iSc{(p4=(r{d^)l_jF5| zqK$#<>Z8J;+gPq^dtPPjdafa5FQ?};BCbDDGFgPB=I&zJ^AS**3uY-x2Aq2^Lzmso z;y`J41{2*z>%cfL7otxlygA#T>|Fiy#{(Yba+8Mbe5;z6HTE_++3>W%xsi>hA{IyX zGF=3*bY#IU#5lNaM z$)b;kijd$YZ3gkw=eR&|uq;@73R@TT6H$IV51F(!ejedBdD|UakYJrwlh&Yp);yq7 z1{eelFwGD#pG66DP@1joVp#!SUISGS^z@qb2s>N(RMs%*VVsy|-5XZ?2-!>#O#S%r>1cr|kFf_1tDs$k z!b%*bFBi?Ic?*{e_8g>&>CiXCXrj%`+@i}#?qtX==r#4Y$itHd9%C28Wo@F#+pWAs zC05BRXozhLf|N$Xgfuh;hT{dSe=%sBVLo z!yA6Ei=@t9_49&JI999|z=Lx0>9c`JqBCB#$eT#YOhi|qnFNNB%yMf1M$tFP7 za&^%2kpU}HYX0iH8=p7NoD`4E@#?wmPqJh9+3jhej~I^PC-)qcFU~ALOR3g{qae&Z zR-NiZ$9RZ-UjiwzMDo1RAGvt1K>Gk2G5C0{KYx!8|LbJqmBI@)G53?zXD%XdZ43KQ zYe`O;{31uF|1qE41TS~38Q&fq9P2;}5i6{WS45MB#^DVV2ysUQc?H#+l0G@hZuhXw#W%RfMj}dV=n4_3mh5v?{Zb{mX-TA3!Ta2h0&_A%#MVA-%j*n` zR4JYSTa&7&nh_NRS*W@PfuMwu1Gtt;K#JD_7$KCCqzv)Y)flJs7JZa6RdF@_XVTu&+#jF1YyLd$h@&XgsWZ?F8ELj^ zKSfHx$X!y#W87sLz(ILu7O)+)qkxhtyoQDvyN=bZ!!n*saK`8X-hCi+kHxj3#kK|A z;z8#boDn#c#wOKjevdY?&zx`0!6)Q;F-}U;k`}LdwX#jxPmyi?@)YqFmLA}&QLuloz{mJ7)L2%!scG~Th!tXB$u(B~3`1HysI=lXL&H+lqhQc_vcca| zUtpS?zbvmBZLWW?W?|r5+Jovz^6M?EEU-tFT8Q~AJ-PZ=B5v~5O^*x~T;-PPy7oSK zoljL_9MgveT{|9=8t#IG3gYC`Npwd&eS!gALZ`FDPW?)6 zDi_Ch)`fXnWRJfsKYgX(@0ZTK_xYs|!VJ5m`u16oW=*VP#qi;Y`HZ%%(7wFpl*Uxh zd(Pe^OSpsH%s8K72Pc;&E)@emUms<`YmoZPrwP|lIi5kE_crVrs2ZP?s+=!QDHjD- z+AUXj!Hvi=J^H(?_?jP|fzw`Buhi*F{7lWPuTYzLp~43@T@+xqEm0m4tDKh;nmZ)+ z&Sezh+`}Y7`g4haB5X`9I*pKRd*%5+N(UIKgrfId@==&JRaIb44$dD;{M`sP)l9BB zUWQ2w2CLXVyO~^Ey?<@1H$GYG{m!Ou@l*8-xVeA>-yUng|4Tc7Kj^L>^YDuM%NLy3 z&3apj^ZtG&qNF>N*{)S5vhQh zV*{H&c{mBYFk37}+^WFL4zqFWCGUtkL8!}gzA|1zL_>jB=rA3!8XXOP5+N(+`rNN$ z4Trv#jnzE%*nQNzLp@4tHFMj>P_6N@w8L$l%q8zdWp*uW_UME8{ggL zoSKM#C31wreiR_ZNse8s{HE%iVLl~CY=AJSYe4-MA&jmITUgNHeO&O&JN*j+ay1UepiqKW$D~C^&qqO0!4)4M~T$kDFjp^n0m0GsGcS{nxSOg*4T- zaP!x{nyZETOQZoZYL@2+_(HRu(8&VlB~r5dzkgPL8v4{PwXk}79%}lFd=s356mJ;h zmc5x;FhADFZFn6`=(Z;Q>H}%g4#Tc!b%@*%SB@OSanjv9HSu8`|_|ZjisU#px5}{V` z?UVt<@z8&+Zi!lrauem+PZx|2RLrYD{4*2r2^h44)QcM_ydJ#nQ|}1xFShb7#gsxz zn0|(+7#)SV5(CaLEvQC>i2s*J&4egPqv5BlM-vfYI+E<4VY!#n-T5OgX1{%_^t)MY zOL!{sMs-YK3PuIh-!t!J4zDjgmG^M$fveEaySX$z)cg&w3OPFDoH=G6dU8Zf!P#a| zHJ>nJfRRT{Ew`lo*RODVgr?1HIbz>FNHLms=7l{s#7}~@qq15F;?~`8PS5;ryI64B z#t_NL*|ofpUfDn6oz~xCy(MsFnTk_<$UK0pw_PfE=_U8*tHkVr`op5T6rZ`+h1f#b za;}1z)>|R-L||3)BErMQBJ=OB^1NVq)D_dCKqK=m?S<%*_ciFjG#rPPnRBgBUCa8TS+?a5aJEr32c#3|6#Me3ZVOiK(p9as@fCAS+`)WvvS&VWi8R%B zYCm>GNOPXdOl0e`YJW6Jiq|-L>jyE3TQq?o-6{P>-73Y24s)Q{28tu4KEn#|xrtPO zoWx`W0dcWf;4xcYhUFxxoP59*GhmWI$R+2nSpHh~z=UDb(#%Mnr1glud4lnphKohS zr_Q@KFs#H^!4Z06nNrDGa@`mU9Vq)EDwzqOP?cUki#H;MQs$)THFA>&n!_ z{81x&hX=j^pAz_KEdzVHVEgT|jZJ>U3CoK~!fpF*gyZ^Ps*7s(k$v)Fg=sHSMW?Qc zINi~F^X!=l1Im6vIa{0I#;V<1J8P}#7vI_S3gn>!1)cK%l)?f;TI z>N56MKKvcKxT)aUXR0Kb&G?#IfC2yMCt*F?oN~C*dV5(#b0EhUA2i%y~1388XuWNoxI*m?zul=Xz_Q zL9Vll(mtbFh=zI|T9E|fv_aWAuCVP0`2o_aB+A|CY)hR5H)3^3@A>WfbVr$z*R~72hxpebMoTzGjJ%dfm`RhuJ%h$9 zXIhLS%E!62jQ+)J0S_z-w&kL9E}1WkvReGK7D|p6Q0z7MBFn|E*#nRr14|)va7Gus zg&i2Z;9BHw3%7 z5z|=^%hf?^XL{4BOmz4ylo&1%8XN-rPxz zU>y9`y$8pO_}Y5^0kWA)I^UQ_y;{im_fMgt0`{A-KBW$TYLyixR}Hc;gX2SM6oAS= zLwv^_T^PE~1^p$9m*kRvgP^{5#?+D5ZWwr8Iw9$R;G2s4Fe60GeMa!$1YZpn3xlAM zv+n-F)SBx`+zD9mEl%~Sv`KE=Ls$<^ARo|W<4BIk5PO)Sia?_4+5AfRCBx)gt100V zm3`N2-{&|!w^tcmY#?4C0S?!*0?v-C8VXp}=oc1y<{_oVB|Gy$3*>Iqc|SJss^I4Z zpRhVC3N;jt+N>~|!O-l|2K_s)QqsE}qeNL`lW$$9)) zK}7VJe)Hv*37#pU2}E>Cab*MM_#Yq9d%=GS%1dj+;wTG3@Tc(uCSQG}<)_f1@Tc#B z&-9<+^jBy88l8-l7=&64Io%O0Z+ojG?*mOtW(goA`^I%f{{e#g;l~?99b@W9hiApz{|YaIg^bQ2OmtfsWQ#d|I7ooE~f33vHfdCB8XaQE(73l zhlDNn^#3cy)ytuQw;>35w_9-2+u_N#-t} z(>-(w!I(HXyY5qtl!Kb~Xr%;aACI7LNMg4*S))dg$g?&}9~N@;`hL(5Tj zCU4Yz%O|E7Ba=Z0Jk!4;KU^xM_`YUCX4y< zFsLVXzHJxkqHf<<4zlLVSlL9L$i;~ryr5Qiey$YY#vQQACj=U^_>04M#j*bALo`6_ zTt6%WJbPjW){dLnG3KAaDWsX0TI6L?`wIIMlbvS&>zPWv%@7Mr*#1vkHny{}fc|UL zm~Wn=T9J&HjwVQ~7(AJckIY=kUy|YkCZY``M-r-9R8OEK>9NL9F(BioJuWP5r9xJv z=quNCY%@49TmP%oVH=cOBJX!vjkUan^}<{WG#GHF>0_Kx8yu#f7a)>xT~)?ILzb&S7?< z>OsB8*g61&8jx;FUU{Tqe(*@2mkTSYtM^HU#){>W^Xs{hnIw#L#szl!F?al5l%yXh z7&K3hR$5Zk4Z5n=t>EM$ z#4c_p0{o8PiFZ$tLYFr#jccnE=m*0exhqB77yk!TF}G}ZaugDT!geQr$N zz1MT=)(64c%U=aU+J!+%aXAm@Xv-W`G7K|UHm)BLb8q5@L{7H>g(#rt>cXKMg|ut< z88?GI>+{>|u_%k45J~iw_2v07ybWx6AMzMr#KuTE&# zB@OH>mulUskL&XBQb!Hpn9d_Pfk4yBveeBd$I*>7F{wgetW91~O_5p2QGGteK)Hqx zU__4-c-1W2HMhVZ^Nzd4AoGlI!I9uwo}QKi0X_eKjlM^xY%X40-$>k-Lkqnr{}qEx zy25OJO$R5ccs`O4?aX0TEB2obGps~O=n|>Nj^%0e{5jYto0br;@NSlMkM#LTh)nZP zEO_i`9R_`%K)C>Bo!c^ZDU@kMd~O6{y8`3fp!W%;l)W;ycvG&&Vc+XTT{I84@i^ka zod`ZCAxo5XnCj(XUHHC_eMs-+Rm}|j>Oc%)YQ58@XH3m zixzM2#i<#Mzit0S8mKA%P4pez-~pThd)qxyq2na>vY@*2E7+X%PEgWW)yC!H)*4n> z)+N7}DxT|F3@HZN(w7b%Wqd4+iGf|91Ezue87cAvRav`qVG?hUQ9!2)OJf|4RQccr{Mlx*BGqBa#`9EXJK> zwMImrV^(d1FzW~PknCQb?4AB@v*i1j>5m} zmr~-3{*-w#wE}5lXXQ49tgE16N`MQ~Wx4dEBqmr#eAY)irMa?-kF~F3*$8_6C_rCQ z!U?B`S{?DA_v!qJQUH2>4ehqet@nenxWel@O1yqP1nvrF2H2Sps_U@r@rSSgBpQEm zH0#m~!XaqAtjtidBnhTybMV30PVeR-fV5;UaDZR1N@33Un32J~)6$?RAjZ|8Q~2FG zPc6otxX&$gN|K|Y@cOXsHyN@h;mE_;g1hy|KaPjrulK(X_5V%&{Z>#wI_cQ`fUvj; zqzFYoCx-`Vlp0g8$_-q&(jI9mi`mZQVOgVGsGy|1O7 zeOK^AB-7M;$K1;?^X!KUCSNQS`S$_W%!e8-d{eW%^f&wdJ;d6ToIZ+M|ayJf(Ah8Y>|8caZ5lKnXd_i1qC^Cb0a1lj)LTo{0`kDzy<| z>g4l_bwJV=2w;-*w8#jZxi3g@<&;9vI(=`S^8%Rkfhwm>xz2+=e~*x2|9x=z>A|^3 z%S6BaI->ogL*?5fV)JP?jyZfl-K_rv9b^R!LV^m-GLN*UCqC`afJjDa`~yBc`Y{QX zgP_V6pD?~43p597Mn4_C`E&g})q{X*5ELBw%6@-^Eg+|o_I1!*u~zrn;o$hVzXJDt zRHjdxB|jCD^>YEutOSJogi$m+cwSD95vH$Ox~3L>h-zs`E9Mn+QG9p5vulp4vTc+G zC8U$;9v}q8)}5Zh@$Va$6pHxz{a1bG!USCdg`!7OAUO|{Qt)>-%;ER%8<~v$>7j3= z^Gne8R;R@7qEYGJ6Whrb*3s8t5Q@1et7%Ew^GnVYEX13`1F<$byk~I}eEv#`?lCb5 zu0?&IU-rI{da0LQwS*JwmLJ%FeM1Y6xDcC~dh^1+^J8iK%O_>0bJNsMZ4#ak;zGp0 zq$IPsV(s-jtM`|aAwo|8G0MGA+4Rx1bH&F|)kZ2MJ}yc>;g9TB{{i6Hcss>!HZU?l);1q` zv*zQkY_xfgUdqiReFN#yRB$Lly%HkMDNzUkCoDLR?w<~JbzrKBmYnp?19r(Yy-n1qbNU zP-(BQ!Z+V$RT$`ewo}nrM;z?>?{5z~tti}c^w!yLTZD(*^EhrYX2wVk^5&qLJ#r+1ZRMl&TWl(j#1i%{G8>8X^T z03Z~BIsHvS!yB?^CK}$UUMnhuJrMI#ykpUSwRT72 zq7Z+nD37D-dP`&}XYvrRi7wCzv%_6;h=0Joh4N=PFz$0oolWCoM}61CL4VMzXUywL>y^SMa*CLGsy!^bG!;)O z$pV~3-Ajq+dw(t{@&NWdxZM#~o0X()#y99Wm9(9-6~Fr=Ph622eocU)G=2@I;pX+A zEnOWVF0LV^;o(X1>Y3ln;Uxz)>l>O%+d9{X6#q7A`je~xM8&GFEj-@O(sk9UIB`j|p`I^MlKzbmpwbQd;c8)ofOAw~)GjK$gQ$Pt{?jErP0(V* zqF0l*+ZyZ?uiWrj1m-s) zBaZt^yrV)~C|qROL;vYY=^#e`3#dpEouoU6J5>)X-?wxaeYgE0HZaUkDEr_u1@*3fEX>XWgr8 ztb*!zO!o(V>5++kpMy74%$!t`4S(o^2|8xFo78pgL$#M`ESowU0P8q6C`?I9>EPN* z3LplRiI)3p4{P5m-vQBK6F&wjcps+eLu>q)s;cV0kACj|cCKkHuFA)mK+rVmI#4?X zR-X;g3i6R3x&+|J=?PL&n3&6ILDCZh=%IosFhO6=${UTS);dS4s^TiaUOw74 z2h>l!VXWMqxA?y^ip%wT06#HEtEovJWpoaEKfAg5ZHk3`%ogHy^$kOPDRP6@$JQr% zvl$R$u%)o}54aCLlAmKK7R#?SKY2%o2qQDj3_I`T62P0)e9V}(pY(~9)Wn~b=`?|a z9}_=Q9rC8GbCj&4t&2nv0Pz;z5Pv8Wf?GETMkZbAp{sS*OB(xk)U3|A>`^UJY7^Xj zXj*3$asD6*FzKyj z5^Ytm+A%+r$1O8<6@rQxA|EmVA-&#_M9hxx>bYf8_P!28KBw@*uY&3PRA#awLGZQ0rqpwhjO?1zEHF#6+zQPxS*I~_Le@&fm za1|ucd4FHJ>*}_tlel{3KIz}&_Ak#Ulri)TRvnG2=~$V5ZghI~&+h93j2_XjZ*)~ZN13u(*GFe&iB)n1ZuwxP1HCq!1?Q%V!J~~dxMG<7y7xG01(!hH3L-4G}Lu5y}daUzE2?gihJ5mO>L71 zCdC&KEMb?soLvVjF45q(dPVwA`)qv?mye2WGMGn@5Bm)+cPf?@%}*8t0SU88CP{H( zDZglFAr|hOK5*wM8`V__!9!p@I*ygB+z_7I`$f9p$Kc5_UVeT zV4-pTk@9~4{B6X4r}w;4dgD#=_T-tH8M!G!=w7bWm2C!m>t{6q9dc z8&&9ekN}h|3_E^!L%OQ>%+#igu5Dj-~%p=9CMcTR(*_+}Y67@NAUT^`1+H)*zSA$I6f4 zGH~06yf^MfvJQACC-ZUJy+E0L75!@VfNA^8|o-zdSv@Bv5y~ha6_F>N0vOttc3D2f1S7i zs9sJ~v^h+|EX)JJ0Ri zv%IR`J?wMJ;Z=Lc5$&>QNfo70(Sxd{M5qaFjKj`O~E%29*^&H1vmQc2bwqeBD;TaCt&5rk=@bXzLtuqUEh^ngkIl` zR6Y((F;zhk^!1|NVuE6yOAaMaes$^G7?IA2R?9)3cmrdMT@a#wv03>VCo+GxeQ3LQ zcO8kt$;Av9MD>aO*&mjcds5j442y2BrKzsBzg7JjPMlA-%A!kF zgf8Tw>!9q7oe~hSD7b8yx?M`($)KKatJpgS7IA%Tlx2+UCGxDp(j>bX(8`@p|4h(> z(5DdANIrnA=DXMa>^OX1r)13lkABo#MIIAM+Y5-9zZ<0FgU4t+1^xVcvk$xQW%|^w zfEaBBv;s4O=LVxU!J5FEjUA^WkX~%ilz4aa)0I`QJEL-=Fm?h<|6Y@7mvZev!&1l8u`i4kjx$ zG6^P`vanuCLzzBBl~6;_d0#d`f8U9>Bhcs+s&ErrU-u=C@-4=q_^wz<(W!F}lPe-- z%pXeZ6YOXrls^(mv;D{#90lL%`;^y+IaV`D+T^%4#O=>p&=Ys>IAUET4+_fo2W-N8 z+scq3@8nNKn+a=mZx7qqS>*4TknXwsnHlIQ$j3*66D*F>K0>l$532pJ7YklBgWR$8Si zj0?XVa?K(kO=h0?Qg(`Jkas>}kgCY0F7!QNJ#Ic;Vtp$7^t_HD0r_taFk(E}R$0IO z4>+v$S9xBrd3r1~UrV4_S1>Oxuis%s7D$y`BsMhR45CLJos4o5b{Ny zoKcDyrRqYNYakIj;(kijg!~mFRF5U8?*yH}pxW2wiW|<2u&4MrnSO*h(}czNL*Oyh zC;cJyUc_Fv&mj%5%{+>oIU0L9(82%5y6qEghRtwIu3eKK@*z)Q;olyHQoM=OC>j3b z#|zn-?{+K-3OrO5eCo?^IqDw(nQ&BFEVOb?thCLT-^C~sZ$PBZDG;hsKl*LaEdR?U zI%k{BT{G6dF=RVD2kq)Gtvws`{!p0yX!|8kz6h4ss>h3j6J9q1DZsV&7N0;FW;L|_ zX#0GA9gUpn;w>Qf5f*&*MANMuagMe$1e=h{x`2;7;94TY9~^Rvc79PT;3!$?*Yf4v zfP9_i3-plGeB0XN6TM(asNs&Pr`2%81s5ok*mUoS%XnK_R;o*p9N_qYFL|B}>W+vB zmyktqfdp_IGDouZjA@M|yBwK^2mCXjI9D*n=u1;2pTxHJFf77p5sdp4%X#NeKn(rcVd?U-0R0w2BiS1@*{f4a;QkOJAZ6nJm@R zRc>h>mRIj;zEC?#fs(8F9(l_b}2?@9GuJ3F`g4=`VP!_{Qk{YSW}y8qpUbrqEqI?Rvd3TA1zhVOgsy%-2} z12e+E^R$}*M#M38YJD0YCKgr+EP0hEXPNvEN*J6^xEe)60p{q0hcc>1!g;gF}M8dZ+p^gqq2qN z76q?>gM->^?Zcrqa`BS#CF?g|XLj-Zx+qdy0JT%q&y@R>whvZObmZa= znE!x+D(#`kr_alyamUNNer$ItA5f5?sR}<><{L$h2K?-yTGqgnjGLGyXA@nm!XyKH z14C+@2T}o^7{r3299}g>EAU>JeoAAK@1L2x`zsurYXeT2yMEEP-c%|0Q%w^^q7lmm>B#LR@%`@mZxV1X96F;YhD<>K#K zn69`qH&PT&sn>JRFCwo}ZOl^8+X7E25JtV-O4pX8SK+54RAQAcvvT8TAEA{L(ZFpv z=5?a$*Zi=Hmk%93f?;a*HK!bf8ab#$M{KJ2R0q{I^ohU@A1sH$2T2+~EPmlI@24(zx)xL6cs`%(VP7}X5Iif;!udO0h z9B$L;aJ>RkDK^6FZ44cW$E79~30!=l4W(Wsf zegRP^zx}Q&8KP|Q5O-0r z)aL$P#xXu4Ldw0xQLGSfHo^PgOQd<)0~Eb}Qp=8G+565Px7KDG4IvXcr-wsrZmtp( zqj{iRta{QpbD=ggkvy0fgkfo?H-HLp3#0!k>o%QPlUnAA6@}i{lvz;SsJeg}TlSuT zA9t^$$OJx51;JPDL(rxW*}?_ISl&b1j`>XLhoVf>lTL4?LRka~wBG%Zg_CKuu*%Jb zgu4RLo^pPZi8=k52Noma^<5h}ehknvjAQ~1S;*$9Kx>2CYDjYJjy^R}Y^ZH8za9qd zcxs`P|LvyrhnbV9-%Ggfpa~h<7ZJV=W$H&4u&3v*d$CW8RuOojwF$0TDuZEjgG3j? z+ep^_1mC#)Q|wY$Uq>AWA}C->3iTO49;Y5NRS(70VNI{+fwlj`VIoiKu5yaTNxA^2tsEOsKPOqK}eh&L|-|{b@d{;-dEt)v5BR0SeTZWYcm0TD9zNouRBtGPsC&~cL znF(Et-Wi5NQDym692}~BTj<~Dk^bLD9Kd;ye_jDb2Cu(q^GJA^umP1uGY-(ChMoxcMG3?M1VXq zN@NA<7}(dj#Z>6Yr-FUIwL{9$ro~6yPy#goM^Rh`oS)f{U=e$ptX!d;974Tilu3Uz-ZIep#O7j9m172vm5^>&T zFYq6|Jb11PKWlaKvGsK@2Y#&XU_R1n^`O6%>a#UO$iicTgy)>XSz|c_JL64NO^|Bm z*#wK_n3A~*$L=2A6KSjb%cHuJ^@1%W2)hOCJ}S}%LpQ~XkFf9(?!utjOxW zn_7h!48#T)n}=FY+glZ@NcYohicSi-z%ucP>~A;!b|d?_DL5w>&LiKOCR*tczY;=U zpr8UU>S$D&Txp4VewoJ52z-A-xl?48!Q=$5-R^s{ zhqdLghJ=_pzpDh2p9qlX%;yQ;j4KA=8>i;unBa#_Ac!p(hpq5uJ+HhZ%J=v(+kUpR z$$Om%vIZp!+V3R)7%wcUeDK}%&pNm71l)BSrL4}y8#g}c=NQa#!7{=#O8-TvDmH&Q@O-f>P#AL;!$PmdFu5rj^8Hn@S zx{>svzW|MJC_-=V%GQlC2;LT|+|e5FIg(kcJEOJpEnXl}u*_64wlV|tlrDTN^1rl5 z%|LNu+bVti{(V2|iz z-lwtVL^j1|Mq)gr)^ONu3s3eAxnGl->rmFmKFHrMpCG7hgxL1|@_x+s#vJ8f(_LYW zyW%fw`0GSUjP7UYp8T#l)hR%7QpVvbZpzR=5eJymU^x)oTKRS!n|b6->7S{M$MR6@ z35ubdqsC|*eB`&!9(X?%-LB?QIsEX1zYucjFK|?X;}vy+LA8*G6DiXu%iBZbg+RRd zA_QUlncq_>U7x26%GE(x;DXk(+J~=i)CgCVUYSl*;AMRR^+J%ZS;j+RcKx4eZC} zpTbMXI(xh>dPOPGM+rkALl}3isEagqr=3r_8pL8&9XhnXeo@+@V^bobnVSwbbgHpP zr=Nq^VK@SGyW*u#=LQze85v)HKPIph%~y}Mr%_kmOREyLz-crJc&4!zG7ph}Nc~>+ zzNdLDtQEV%_kJSMZtLKf8jVVI@lgP|=RU(JTUTK{0JLZ2d~rMaj8t&fKLbk_%T_o` z)HVL{sw?;ml%ZRSid?%cMwsT*Ca}oT=FOOI7x#J;i?Z@ow*W8fW@5KPN-gver5T(r zvSH44JwoDZgN%*Gw)2u!kYtdtTmKpmRByO1iV^uI-VVI@wc+=VC4iYnUSGJ>4iBh2 z17=IaB&Bq1?ft`oQ60n6WP0En`jge%O48+YYi*V}Qb2b&25B{>c)Y6ifRB|*n$DtZ z2XUt|Gtk&d>f>LaoO`JSsNr$^?U}}$6>P5gHfePzGElQaUG<;R z{#UE+7ed)C|05S6!r?Nl(*MM=OFj|sBi)i9f2zITuF339!4P~d%8t?lJ8==C*i!fF z_Yok(CuTFXR%(^z(Vk)fZ|0VW9W=lSAc!B{0$-Fr&HZo^?i-94tlnPQvvi;qJdTJ9 zPEP>?XW;i*ZvS%M-;^Sp7eUEa5QePKTyZ0CllAJRhl~tqbR4ae8$EPP3?|pWC+)np z+{BujPPX^T6dd@YvLdhiYV#?}3z+)`WEDj*;+<@fsO zl(Dx12V2DIZBMb+lt|5G_iBHdcH3_#e&q5$NPu~rC$j}v{P%r9 z0#Yw{^41DN;s@gqNAiIihp^EVJ?Wwn;;s9*-{+KZyedA%0#|#5`G9GzPOGv6RKyj$l`0Fnece*2QinQ{7#H@Cu`@v1~NP80;$G^)+2n*uX zUu&k+T$-IeEaFj*(J}*0xP~h-GyR$c@K1pb)Cq{Ty9Ao2ftk%(2m~db4?ZYMU*wTQGdH2gxPnP?PqRQZKt^n)6nvaNQnNI?WZg5JFK%c0Kz#BYsLiAl>aCjJE?#M@jn zMh=effmLWJ%3@5hESx*aZrQ*T}n|-ta76@tugW`^UYDC1P;y6c&erkn5qnLXMBOQPx0Wi~*cd zUGsU-4yp~k#;(ZRD(2JU9`WRC9!def5p%n4d8j1KL;V~AnIXalq}jX4K^48yW$qw= z50ncEqNmAf&zRb&M8lF+q|aiA#FvdWkj0saxP~w5{g@)gWYFud#?;C!1Ra3|OS|&F zKIq|o6%2oF#p6$4f@w!=LdNQW;^F@eEg1X-@^u?$!S$tm>3+9$mF8ebd{&$JZbW#i zV6xYRt!Yc(z0CnQ!=XV31x@ylloou@7vd7EE~$BDwy~+h%Zt;v{IU1d4l4YW>P>>Z zG?d{QXn719titZm(=oeyl}r%to1e10{W_Lf{Sxd5wy3zDClEkU7xHB~H%}JCo55>r zcaJwk8<5U}j!YrCh?>t6w5m&;15CA%z5y3ZC;KhTO(DK)$pUMeDyU>({%@zrFj!$;bN6rGN8D zNF*ztjM~Qa`~3wDd;)%y@v{IRp?D~DtXZP1zERD4w?*y2y(AK*zl7ge0TY~dF=nyw zisVTF35m-t+2IKkNn{3L2_`}c6I`rr-MZp4LaE@)@TzdJ^TMEME`P|@Aa;&io?U`M z0@hIhjkrS&n=11;vsyoThPK^A_X+Q(ojLBN#0r;~JLwmWImD*?zpL36tL^L2+!;4y z2{GU5+qOToZvVC)_ACqR!jc#l1}&N&lV}|r?u5Pu?HCwJIGEp1?HN1cm4WGg^j4}? zKeKCQakdC}@=|&|Ypn^UV(VAW4m!gppu2dREahgiw zlnH$8sIfMagH7hsScI%s?D^0Mn0;waI0YV1lp;a7nrZj&Wcn`(dXAz#LG`Mamn<2+ zHbs5`&9TnkT*^~lnmDk_|CyQ8H$^0`ap?dZDPlfq<7q&7q&X~t1UtUO<4<(nieLur zN|esgb6oObuFbcv=J1Dp5=&0>D<}_tY^$O`pR=mfdjIW?_SD&!xQ@iiJ8fQ9VQaKF zo)|j<@xEF(re~Xb&%+It+M5;v?Phk;hTSLO z5GQj5x$3iU;FnRhBUxxttXbKzm>cRTsFF6~T$L#ld|MJc@b9ngt$Nks6sLD>`S$M% zqI3g%RAF*j?E|l~JP?1Lm?@tR8vh1qB?AP|;_iR4UyoijoVb~a$;zKv8N6=k?4vm@ z@V|}%{509HmqY`uA7_9bRXJ?V%F^MLfo6$i{cETO7a9Z7HyqEpv>hJP_D#tOXLRRj znYjWw?u+Ad0ChM`F{MP)^;lVfxeucLQDwk2>{%<23IAAiv!}1<+~{=Y?#-fL3X{&n zrtsv^3Thqha9>HItFjLB^HXcuaRnfdrU3sF)y(@Me+J*R5fXYIl{X&r2o+aHP&wWg z?t95Kcs=1p8H|#0Sl7m5J;uJ*vO2NPN?8nMW08H_iZ9u!5b{zs$yvCd7Qy#!J>SR4 zWd@r;rKIUGOvep(_*YM^4e4=Y1SDy-s3)92<`UT529-ro7l+!}R|3;rS%vWmlhl9b zF0IfNV3t{oJE-3bjqJo}(z9ROQqVKyVopxC7%2)V!Q5u>z0`GngY-~=!1zUOM#&32 zsLX_n#iR^nZs#gK6J==lU4XNh9|P5quOS71gjZijk0=X5sfWJKuxkZPrKBNvyy$NC zVN(RA2nvM;-G$Ih=Q9oKI{WIv03^BU+|#==4xxx@I-(1@T=)v1Q08M&)>-~@i`JXF z(mxww+j>hTD1uJ_ECj;P(Q<0|b@#KvffVHdoVC1)%~~xda}RsVUy!e^>`h?a>VnrP z5j$#gUfN*nn?zBKDF-}$ULZchBt&S`iFRkWzCJbSMhenlfv&z07IJ2xqh%uZcvF-& zV`9T=AxIiGFU1=!01!#?kqEc^qn67PhbgoF71}BErDwVXf-DZ$$@{(FTXCCIr#4aK zWCHbfV%(YR%5Dlnr>MmJP?az&=K|8cX1=+`y{2lW+$FtiI#2LUpxW@g00T|p8vdjO zOo$D50bWhtMzoG#e8uP`E@Y=zU#uav|6f6=HiJhZo;ZKjrJDwElPey3yG~fv^uIYr zvrXCz$|Vch+rPp`^=`0cQBpfJm|9+9Dm? zKo3hyg)Mn0R1nXtzeKvwczS9g`gGY~D&8x^e~%{k6o4brV;dZcig^7d=rh}UR?OBR zJN<|$JQXUVUs5EfL({1%Kql;TT)bc%%=Ta0!ct)L@0%I3mp|iJ5d;M3gu3+eCC}zR z!?;=JY*%ppS`sS#d}!~li(R#E;tO6ok)S>ch=f}|bi{L{(jJX+T zmOaW$v2u(S6MxgD>qYjjj3K1gmE5W%`}j|p%h z^pnr##eX=xlv(zLQWXro2b%uyq#!i*$ZBnj$9-wqqk(=xeRE;J5W1IPINer4?qh%K z9#8%YEMVlimo+wCoX7goo%LOQ8s!ayND4KQzATtswZ#N#ORC25@Zxh%8&?JeyFas^ zJ7GoRUHd$<{pIi;&ugCn|8Iym43JU%2REfPLn8M)tm6^IE&_eL#%;c9Q)y&R5i1ml z$y84boLMonECNUm?5`gboG&!jC>*%0xUoc(0J9w)G*4l~7cS*2+LAF)D@UYW$mi-- z!2Q?_Q`MTE{i-}|fK%tjb^^m#DyCC}WevpGaP)jSrPIfLAL>MT~E8m4^`en7z zSv_DgpwW{*v|A_Omi~)fPve`JL4F37isiR{TxwAr0bE#=z~BudhwW`qG(J!DQ3~XE zJ4R;Qb#!ARp8F(I!4PIh98S!*3}`826xSR!aI9bV%MV#!R8Z0;E8$T`QxFW&fAoV- zrp{*F8^{+GR8?$m`#i2fA0EQ{o(ld?Cbmc`eQvu*`I)G~WVT=M1T4MO;W%`z$zLaj zZ{1hgQphjYy+k%W#0u>smw3K(zkS<>m%}?8C2~B`Uk?VF%@b$lPfINex_BKfF5hOC zu1cJBt5__SIZhI8gmrcdEuL0OkM>-jZzV7ly;dUY1)Ij+kNuf6*mK+D(i#>TLS(1=M zN~2xP`;c_~;}&>Z()Lv+!s)L!4YH@V-F7TpMlZ)JJ*eO=$Sx%x^aOuxAup&=D0NZe zAN{|)?rsfm%IHU1D>-ijmJBK};cKX90ZhhBrb^0zcDYJE5wTvb+m-{}?zw#2<;;CO zeqk#>E8Sgmb~@p83I&4xJt;hMQhdTRCO`PXqAueJ2>hE27ta!@LV2 z9MNnn5sD_Logx*aHFqZ%0NMBo8ADI{HrIaFE>TQOd$Ni>-u=TzM0ae{zHP%=Nr^4} zGA4aA2$kP|C}{>MBAr1bSU}u6Ph+kt6H4%@q zGRNW*JuAf(-Gr}Y2?wO9gMfOvl%UVcrJ>`oOPW}sltxIxtvOunnU?8N<9U}CW<}X% zX_Lz*WS1`{oQ@ycPG1(ofR1{$=d-@H4DDK72Iu?+zY?_>-6nF#A{vDeE_n~KtTm5> zb9%SK4{@jV&z};==J0Ip zFXhYdVT&InZ#BmA-Ej&q!DH3EYBRT+et)a5n&*Sj%MKT->*>_Tc7_+|jL+xdaFraI z*PlPQplj2-hWca++!!r|EHRYSl+(cI)4YcvAz@`lHQ=&@N*$pa-_Qm)aysB?2y82= zIWLZ(Q2pxumhw?yF;Cgw)aOgU*F&7?mia23|IEJFV_IfJ$jVH}$*$k#6P(J9|J8H2 z_7wzxGZ|`R?lJ_d#u|dsBxo27_nPl1+byHL{tk=RXb8aekiY7XUotF1O zYrdUAUpHfCtDwHYRZns!(^VDtzTB`jRJCnUwrXxdVsmagavWA&M0u+VU$x$z#``I( zS9{n6>V<`%H{6i8NYO3B0kX(_v|Pf`1)lk*;&oZBf!6)tSVog)uJ)=jo0s>V5SAZ) zb*NUjP6ptAxhZ?A?N*rlB}r~V02oIke=f^Lfo-bLxBo$F1e0LTn^#0zq{CEMh|AYJ z(Lk$c3LG*&f&89=#7f&Hso~|mRU6^Tui0Bz2;WZpO|X(3u@%C(X61Kn#qo zc?;Ac*(T`w{%K=Bc6wY$MpF1wQn+e&uw%ogpI-rM9F>?#eQ{a;uw80VEgiONh6hFS Ln95$ozpwretr%;R literal 0 HcmV?d00001 From 6341b5d5ed7be84b5d74929da9b0deced907d587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 30 Dec 2016 14:52:10 +0100 Subject: [PATCH 62/72] rename named parameter and update concept after adding edge sampling --- .../Concepts/PMPDistanceTraits.h | 4 ++++ .../NamedParameters.txt | 4 ++-- .../hausdorff_distance_remeshing_example.cpp | 2 +- .../CGAL/Polygon_mesh_processing/distance.h | 24 +++++++++---------- .../internal/named_function_params.h | 14 +++++------ .../test_pmp_distance.cpp | 21 ++++++++++++---- 6 files changed, 42 insertions(+), 27 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h index 252b5505121..b51c828f046 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h @@ -38,6 +38,9 @@ public: /// It provides `FT operator()(const Point_3&, const Point_3&, const Point_3&) const` /// and `FT operator()(const Triangle_3&) const` and has `FT` as result_type. typedef unspecified_type Compute_squared_area_3; + /// Functor for computing squared length of a segment. + /// and `FT operator()(const Segment_3&) const` and has `FT` as result_type. + typedef unspecified_type Compute_squared_area_3; /// Functor for constructing translated points. /// It provides `Point_3 operator()(const Point_3 &, const Vector_3 &)` typedef unspecified_type Construct_translated_point_3; @@ -52,6 +55,7 @@ public: /// @name Functions /// @{ Compute_squared_area_3 compute_squared_area_3_object(); + Compute_squared_area_3 compute_squared_length_3_object(); Construct_translated_point_3 construct_translated_point_3_object(); Construct_vector_3 construct_vector_3_object(); Construct_scaled_vector_3 construct_scaled_vector_3_object(); diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt index 09e67fda22b..d8aef11f859 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/NamedParameters.txt @@ -300,9 +300,9 @@ the grid sampling method. \b Default value is `0` \cgalNPEnd -\cgalNPBegin{number_of_points_per_squared_area_unit} \anchor PMP_number_of_points_per_squared_area_unit +\cgalNPBegin{number_of_points_per_area_unit} \anchor PMP_number_of_points_per_area_unit Parameter used in `sample_triangle_mesh()` to set the number of points per -squared area unit to be picked up in faces for the random uniform sampling and +area unit to be picked up in faces for the random uniform sampling and Monte-Carlo methods. \n \b Type : `double` \n diff --git a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp index 8560df1035d..bbd7b92d3f0 100644 --- a/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp +++ b/Polygon_mesh_processing/examples/Polygon_mesh_processing/hausdorff_distance_remeshing_example.cpp @@ -29,6 +29,6 @@ int main() std::cout << "Approximated Hausdorff distance: " << CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance - (tm1, tm2, PMP::parameters::number_of_points_per_squared_area_unit(4000)) + (tm1, tm2, PMP::parameters::number_of_points_per_area_unit(4000)) << std::endl; } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index ad5032d35c6..b8675d76511 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -206,7 +206,7 @@ struct Distance_computation{ * and uniform way on the surface of `tm`, and/or on edges of `tm`. * For faces, the number of sample points is the value passed to the named * parameter `number_of_points_on_faces()`. If not set, - * the value passed to the named parameter `number_of_points_per_squared_area_unit()` + * the value passed to the named parameter `number_of_points_per_area_unit()` * is multiplied by the area of `tm` to get the number of sample points. * If none of these parameters is set, the number of points sampled is `num_vertices(tm)`. * For edges, the number of the number of sample points is the value passed to the named @@ -228,12 +228,12 @@ struct Distance_computation{ * on each edge. * For faces, the number of points per triangle is the value passed to the named * parameter `number_of_points_per_face()`. If not set, the value passed - * to the named parameter `number_of_points_per_squared_area_unit()` is + * to the named parameter `number_of_points_per_area_unit()` is * used to pick a number of points per face proportional to the triangle * area with a minimum of one point per face. If none of these parameters * is set, 2 divided by the square of the length of the smallest non-null * edge of `tm` is used as if it was passed to - * `number_of_points_per_squared_area_unit()`. + * `number_of_points_per_area_unit()`. * For edges, the number of points per edge is the value passed to the named * parameter `number_of_points_per_edge()`. If not set, the value passed * to the named parameter `number_of_points_per_distance_unit()` is @@ -271,7 +271,7 @@ struct Distance_computation{ * used by the Monte-Carlo sampling method as the number of points per face * to pick. * \cgalParamEnd - * \cgalParamBegin{number_of_points_per_squared_area_unit} a double value + * \cgalParamBegin{number_of_points_per_area_unit} a double value * used for the random sampling and the Monte Carlo sampling methods to * repectively determine the total number of points inside faces * and the number of points per face. @@ -319,7 +319,7 @@ sample_triangle_mesh(const TriangleMesh& tm, bool smpl_dgs = choose_param(get_param(np, do_sample_edges), true); bool smpl_fcs = choose_param(get_param(np, do_sample_faces), true); - double nb_pts_sq_a_u = choose_param(get_param(np, nb_points_per_sq_area_unit), 0.); + double nb_pts_a_u = choose_param(get_param(np, nb_points_per_area_unit), 0.); double nb_pts_l_u = choose_param(get_param(np, nb_points_per_distance_unit), 0.); // sample vertices @@ -366,7 +366,7 @@ sample_triangle_mesh(const TriangleMesh& tm, std::size_t nb_points_per_edge = choose_param(get_param(np, number_of_points_per_edge), 0); - if ((nb_points_per_face == 0 && nb_pts_sq_a_u ==0.) || + if ((nb_points_per_face == 0 && nb_pts_a_u ==0.) || (nb_points_per_edge == 0 && nb_pts_l_u ==0.) ) { typedef typename boost::graph_traits @@ -385,8 +385,8 @@ sample_triangle_mesh(const TriangleMesh& tm, if (smpl_fcs) { // set default value - if (nb_points_per_face == 0 && nb_pts_sq_a_u ==0.) - nb_pts_sq_a_u = 2. / CGAL::square(min_edge_length); + if (nb_points_per_face == 0 && nb_pts_a_u ==0.) + nb_pts_a_u = 2. / CGAL::square(min_edge_length); BOOST_FOREACH(face_descriptor f, faces(tm)) { @@ -396,7 +396,7 @@ sample_triangle_mesh(const TriangleMesh& tm, nb_points = (std::max)( static_cast( std::ceil(to_double( - face_area(f,tm,parameters::geom_traits(geomtraits)))*nb_pts_sq_a_u)) + face_area(f,tm,parameters::geom_traits(geomtraits)))*nb_pts_a_u)) ,std::size_t(1)); } // extract triangle face points @@ -447,11 +447,11 @@ sample_triangle_mesh(const TriangleMesh& tm, Random_points_in_triangle_mesh_3 g(tm, pmap); if (nb_points == 0) { - if (nb_pts_sq_a_u == 0.) + if (nb_pts_a_u == 0.) nb_points = num_vertices(tm); else nb_points = static_cast( - std::ceil(g.mesh_area()*nb_pts_sq_a_u) ); + std::ceil(g.mesh_area()*nb_pts_a_u) ); } out = CGAL::cpp11::copy_n(g, nb_points, out); } @@ -467,7 +467,7 @@ sample_triangle_mesh(const TriangleMesh& tm, nb_points = num_vertices(tm); else nb_points = static_cast( - std::ceil( g.mesh_length()*nb_pts_sq_a_u) ); + std::ceil( g.mesh_length()*nb_pts_a_u) ); } out = CGAL::cpp11::copy_n(g, nb_points, out); } diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h index c8046efc95c..eca263026c9 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h @@ -48,7 +48,7 @@ namespace CGAL{ enum number_of_points_on_faces_t { number_of_points_on_faces }; enum number_of_points_per_face_t { number_of_points_per_face }; enum grid_spacing_t { grid_spacing }; - enum nb_points_per_sq_area_unit_t { nb_points_per_sq_area_unit }; + enum nb_points_per_area_unit_t { nb_points_per_area_unit }; enum number_of_points_per_edge_t { number_of_points_per_edge }; enum number_of_points_on_edges_t { number_of_points_on_edges }; enum nb_points_per_distance_unit_t{ nb_points_per_distance_unit }; @@ -312,10 +312,10 @@ namespace CGAL{ //overload template - pmp_bgl_named_params - number_of_points_per_squared_area_unit(const NT& n) const + pmp_bgl_named_params + number_of_points_per_area_unit(const NT& n) const { - typedef pmp_bgl_named_params Params; + typedef pmp_bgl_named_params Params; return Params(n, *this); } @@ -591,10 +591,10 @@ namespace parameters{ //overload template - pmp_bgl_named_params - number_of_points_per_squared_area_unit(const NT& n) + pmp_bgl_named_params + number_of_points_per_area_unit(const NT& n) { - typedef pmp_bgl_named_params Params; + typedef pmp_bgl_named_params Params; return Params(n); } diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp index dae217cc8aa..ef295895dc9 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp @@ -44,12 +44,17 @@ struct Custom_traits_Hausdorff struct Triangle_3 { - Triangle_3(){} Triangle_3(const Point_3&, const Point_3&, const Point_3&){} Point_3 operator[](int)const{return Point_3();} CGAL::Bbox_3 bbox(){return CGAL::Bbox_3();} }; + struct Segment_3 + { + Segment_3(const Point_3&, const Point_3&){} + Point_3 operator[](int)const{return Point_3();} + }; + struct Compute_squared_area_3 { typedef FT result_type; @@ -57,6 +62,12 @@ struct Custom_traits_Hausdorff FT operator()(Triangle_3)const{return FT();} }; + struct Compute_squared_length_3 + { + typedef FT result_type; + FT operator()(Segment_3)const{return FT();} + }; + struct Construct_translated_point_3 { Point_3 operator() (const Point_3 &, const Vector_3 &){return Point_3();} @@ -237,8 +248,8 @@ void general_tests(const TriangleMesh& m1, std::cout << "Symmetric distance between meshes (sequential) " << PMP::approximate_symmetric_Hausdorff_distance( m1,m2, - PMP::parameters::number_of_points_per_squared_area_unit(4000), - PMP::parameters::number_of_points_per_squared_area_unit(4000)) + PMP::parameters::number_of_points_per_area_unit(4000), + PMP::parameters::number_of_points_per_area_unit(4000)) << "\n"; std::cout << "Max distance to point set " @@ -274,7 +285,7 @@ int main(int, char** argv) time.start(); std::cout << "Distance between meshes (parallel) " << PMP::approximate_Hausdorff_distance( - m1,m2,PMP::parameters::number_of_points_per_squared_area_unit(4000)) + m1,m2,PMP::parameters::number_of_points_per_area_unit(4000)) << "\n"; time.stop(); std::cout << "done in " << time.time() << "s.\n"; @@ -283,7 +294,7 @@ int main(int, char** argv) time.start(); std::cout << "Distance between meshes (sequential) " << PMP::approximate_Hausdorff_distance( - m1,m2,PMP::parameters::number_of_points_per_squared_area_unit(4000)) + m1,m2,PMP::parameters::number_of_points_per_area_unit(4000)) << "\n"; time.stop(); std::cout << "done in " << time.time() << "s.\n"; From 88d391ab51004b560f153e99cd1e67976ba9c30b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 30 Dec 2016 15:46:13 +0100 Subject: [PATCH 63/72] improve phrasing --- .../include/CGAL/Polygon_mesh_processing/distance.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index b8675d76511..f13198239a4 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -671,15 +671,15 @@ double max_distance_to_triangle_mesh(const PointRange& points, /*! *\ingroup PMP_distance_grp - * returns an approximation of the distance to `points` of the triangle from `tm` - * that is the furthest from `points`. - * + * returns an approximation of the distance between `points` and the point lying on `tm` that is the farthest from `points` * @tparam PointRange a range of `Point_3`, model of `Range`. * @tparam TriangleMesh a model of the concept `FaceListGraph` * @tparam NamedParameters a sequence of \ref namedparameters - * @param tm the triangle mesh to compute the distance from - * @param points the range of points of interest. - * @param precision the precision of the approximate value you want. + * @param tm a triangle mesh + * @param points a range of points + * @param precision for each triangle of `tm`, the distance of its farthest point from `points` is bounded. + * A triangle is subdivided into sub-triangles so that the difference of its distance bounds + * is smaller than `precision`. `precision` must be strictly positive to avoid infinite loops. * @param np an optional sequence of \ref namedparameters among the ones listed below * * \cgalNamedParamsBegin From 0607f2511e56a8ed7fa9f2ba81627252f1a2ca39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 30 Dec 2016 16:12:37 +0100 Subject: [PATCH 64/72] improve user manual --- .../Polygon_mesh_processing/Polygon_mesh_processing.txt | 8 ++++---- 1 file changed, 4 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 e5e39436549..0ed5a16889b 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 @@ -486,7 +486,7 @@ the propagation of a connected component index to cross it. This package provides methods to compute (approximate) distances between meshes and point sets. The function \link CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance() `approximate_Hausdorff_distance()`\endlink -computes an approximation of Hausdorff distance from a mesh `tm1` to a mesh `tm2`. Given a +computes an approximation of the Hausdorff distance from a mesh `tm1` to a mesh `tm2`. Given a a sampling of `tm1`, it computes the distance to `tm2` of the farthest sample point to `tm2` \cgalCite{cignoni1998metro}. The symmetric version (\link CGAL::Polygon_mesh_processing::approximate_symmetric_Hausdorff_distance() `approximate_symmetric_Hausdorff_distance()`\endlink) is the maximum of the two non-symmetric distances. Internally, points are sampled using @@ -497,9 +497,9 @@ The quality of the approximation depends on the quality of the sampling and the Three sampling methods with different parameters are provided (see \cgalFigureRef{sampling_bunny}). \cgalFigureBegin{sampling_bunny, pmp_sampling_bunny.jpg} -Sampling of a triangle mesh using different sampling methods. From left to right: Grid sampling, Monte-Carlo sampling with fixed number of points per face and per edge, -Monte-Carlo sampling with a number of points proportional to the area/length, and Uniform random sampling. -The four pictures represent the sampling on the same portion of a mesh, parameters were ajusted so that the total number of points sampled in faces (blue points) and on +Sampling of a triangle mesh using different sampling methods. From left to right: (a) Grid sampling, (b) Monte-Carlo sampling with fixed number of points per face and per edge, +(c) Monte-Carlo sampling with a number of points proportional to the area/length, and (d) Uniform random sampling. +The four pictures represent the sampling on the same portion of a mesh, parameters were adjusted so that the total number of points sampled in faces (blue points) and on edges (red points) are roughly the same. Note that when using the random uniform sampling some faces/edges may not contain any point, but this method is the only one that allows to exactly match a given number of points. \cgalFigureEnd From 527828ff52093c570f593c045050b8781163911e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 30 Dec 2016 16:16:46 +0100 Subject: [PATCH 65/72] copy-paste error --- .../doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h index b51c828f046..0009489dcec 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Concepts/PMPDistanceTraits.h @@ -40,7 +40,7 @@ public: typedef unspecified_type Compute_squared_area_3; /// Functor for computing squared length of a segment. /// and `FT operator()(const Segment_3&) const` and has `FT` as result_type. - typedef unspecified_type Compute_squared_area_3; + typedef unspecified_type Compute_squared_length_3; /// Functor for constructing translated points. /// It provides `Point_3 operator()(const Point_3 &, const Vector_3 &)` typedef unspecified_type Construct_translated_point_3; @@ -55,7 +55,7 @@ public: /// @name Functions /// @{ Compute_squared_area_3 compute_squared_area_3_object(); - Compute_squared_area_3 compute_squared_length_3_object(); + Compute_squared_length_3 compute_squared_length_3_object(); Construct_translated_point_3 construct_translated_point_3_object(); Construct_vector_3 construct_vector_3_object(); Construct_scaled_vector_3 construct_scaled_vector_3_object(); From 33979fc8cd6c7ffa95e4278dcfc2c87737bfcbee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Thu, 5 Jan 2017 15:14:12 +0100 Subject: [PATCH 66/72] remove wrong comments --- .../internal/named_function_params.h | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h index eca263026c9..75c1f592143 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/named_function_params.h @@ -229,7 +229,6 @@ namespace CGAL{ return Params(p, *this); } - //overload template pmp_bgl_named_params use_random_uniform_sampling(const Boolean& p) const @@ -238,7 +237,6 @@ namespace CGAL{ return Params(p, *this); } - //overload template pmp_bgl_named_params use_grid_sampling(const Boolean& p) const @@ -247,7 +245,6 @@ namespace CGAL{ return Params(p, *this); } - //overload template pmp_bgl_named_params use_monte_carlo_sampling(const Boolean& p) const @@ -256,7 +253,6 @@ namespace CGAL{ return Params(p, *this); } - //overload template pmp_bgl_named_params sample_edges(const Boolean& p) const @@ -265,7 +261,6 @@ namespace CGAL{ return Params(p, *this); } - //overload template pmp_bgl_named_params sample_vertices(const Boolean& p) const @@ -274,7 +269,6 @@ namespace CGAL{ return Params(p, *this); } - //overload template pmp_bgl_named_params sample_faces(const Boolean& p) const @@ -283,7 +277,6 @@ namespace CGAL{ return Params(p, *this); } - //overload template pmp_bgl_named_params number_of_points_on_faces(const NT& n) const @@ -292,7 +285,6 @@ namespace CGAL{ return Params(n, *this); } - //overload template pmp_bgl_named_params number_of_points_per_face(const NT& n) const @@ -301,7 +293,6 @@ namespace CGAL{ return Params(n, *this); } - //overload template pmp_bgl_named_params grid_spacing(const NT& n) const @@ -310,7 +301,6 @@ namespace CGAL{ return Params(n, *this); } - //overload template pmp_bgl_named_params number_of_points_per_area_unit(const NT& n) const @@ -319,7 +309,6 @@ namespace CGAL{ return Params(n, *this); } - //overload template pmp_bgl_named_params number_of_points_per_edge(const NT& n) const @@ -328,7 +317,6 @@ namespace CGAL{ return Params(n, *this); } - //overload template pmp_bgl_named_params number_of_points_on_edges(const NT& n) const @@ -337,7 +325,6 @@ namespace CGAL{ return Params(n, *this); } - //overload template pmp_bgl_named_params number_of_points_per_distance_unit(const NT& n) const From 64c2dd925c94cb22b080e17c0bb417c68fce4525 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Thu, 5 Jan 2017 16:45:09 +0100 Subject: [PATCH 67/72] Fix : - Fix demo and add dialog to select the number of points per triangle --- .../Plugins/PMP/Distance_plugin.cpp | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp index 9c75210878d..34807ecba94 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp @@ -66,13 +66,14 @@ class Scene_distance_polyhedron_item: public Scene_item { Q_OBJECT public: - Scene_distance_polyhedron_item(Polyhedron* poly, Polyhedron* polyB, QString other_name) + Scene_distance_polyhedron_item(Polyhedron* poly, Polyhedron* polyB, QString other_name, int sampling_pts) :Scene_item(NbOfVbos,NbOfVaos), poly(poly), poly_B(polyB), are_buffers_filled(false), other_poly(other_name) { + nb_pts_per_face = sampling_pts; this->setRenderingMode(FlatPlusEdges); thermal_ramp.build_thermal(); } @@ -124,7 +125,6 @@ public: bbox.xmax(),bbox.ymax(),bbox.zmax()); } private: - Polyhedron* poly; Polyhedron* poly_B; mutable bool are_buffers_filled; @@ -134,6 +134,7 @@ private: mutable std::vector normals; mutable std::vector colors; Color_ramp thermal_ramp; + int nb_pts_per_face; enum VAOs { Facets=0, @@ -227,7 +228,11 @@ private: //compute distance with other polyhedron //sample facet std::vector sampled_points; - CGAL::Polygon_mesh_processing::sample_face(f, *poly, 400, std::back_inserter(sampled_points),CGAL::Polygon_mesh_processing::parameters::all_default()); + std::size_t nb_points = (std::max)((int)std::ceil(nb_pts_per_face * PMP::face_area(f,*poly,PMP::parameters::geom_traits(Kernel()))), + 1); + CGAL::Random_points_in_triangle_3 g(f->halfedge()->vertex()->point(), f->halfedge()->next()->vertex()->point(), + f->halfedge()->next()->next()->vertex()->point()); + CGAL::cpp11::copy_n(g, nb_points, std::back_inserter(sampled_points)); sampled_points.push_back(f->halfedge()->vertex()->point()); sampled_points.push_back(f->halfedge()->next()->vertex()->point()); sampled_points.push_back(f->halfedge()->next()->next()->vertex()->point()); @@ -424,6 +429,12 @@ public: public Q_SLOTS: void createDistanceItems() { + bool ok = false; + nb_pts_per_face = QInputDialog::getInt(mw, tr("Sampling"), + tr("Number of points per face:"),400, 1,2147483647,1, &ok); + if (!ok) + return; + //check the initial conditions Scene_polyhedron_item* itemA = qobject_cast(scene->item(scene->selectionIndices().first())); Scene_polyhedron_item* itemB = qobject_cast(scene->item(scene->selectionIndices().last())); @@ -433,8 +444,8 @@ public Q_SLOTS: return; } QApplication::setOverrideCursor(Qt::WaitCursor); - Scene_distance_polyhedron_item* new_itemA = new Scene_distance_polyhedron_item(itemA->polyhedron(),itemB->polyhedron(), itemB->name() ); - Scene_distance_polyhedron_item* new_itemB = new Scene_distance_polyhedron_item(itemB->polyhedron(),itemA->polyhedron(), itemA->name()); + Scene_distance_polyhedron_item* new_itemA = new Scene_distance_polyhedron_item(itemA->polyhedron(),itemB->polyhedron(), itemB->name(), nb_pts_per_face); + Scene_distance_polyhedron_item* new_itemB = new Scene_distance_polyhedron_item(itemB->polyhedron(),itemA->polyhedron(), itemA->name(), nb_pts_per_face); itemA->setVisible(false); itemB->setVisible(false); new_itemA->setName(QString("%1 to %2").arg(itemA->name()).arg(itemB->name())); @@ -444,6 +455,7 @@ public Q_SLOTS: QApplication::restoreOverrideCursor(); } private: + int nb_pts_per_face; QList _actions; Messages_interface* messageInterface; //The reference to the scene From b997dd17a32ab08f97499b2a70916a32d6edabf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Fri, 6 Jan 2017 09:48:22 +0100 Subject: [PATCH 68/72] fix path to input files --- .../test/Polygon_mesh_processing/test_pmp_distance.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cmd b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cmd index b4ec79db85c..856bd25a5bf 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cmd +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cmd @@ -1 +1 @@ -../data/elephant.off ../data/blobby_3cc.off +data/elephant.off data/blobby_3cc.off From 4360cf94600b431ccf33b21ef007c7e65e539395 Mon Sep 17 00:00:00 2001 From: Maxime Gimeno Date: Fri, 6 Jan 2017 12:47:26 +0100 Subject: [PATCH 69/72] Secure TBB code with #ifdef --- Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp index 34807ecba94..e446a108d32 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp @@ -22,6 +22,7 @@ using namespace CGAL::Three; namespace PMP = CGAL::Polygon_mesh_processing; +#ifdef CGAL_LINKED_WITH_TBB template struct Distance_computation{ const AABB_tree& tree; @@ -42,7 +43,6 @@ struct Distance_computation{ , output(out) { } - void operator()(const tbb::blocked_range& range) const { @@ -61,6 +61,7 @@ struct Distance_computation{ distance->store(hdist, CGAL::cpp11::memory_order_release); } }; +#endif class Scene_distance_polyhedron_item: public Scene_item { From cafe151c03a824340eeab58b3e1a3a2b5b3f9bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 9 Jan 2017 08:28:13 +0100 Subject: [PATCH 70/72] fix an error and two warnings --- .../include/CGAL/Polygon_mesh_processing/distance.h | 2 +- Polyhedron/demo/Polyhedron/Plugins/PMP/Distance_plugin.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h index f13198239a4..c3c0217f0dc 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h @@ -481,7 +481,7 @@ OutputIterator sample_triangle_mesh(const TriangleMesh& tm, OutputIterator out) { - return sample_triangle_mesh(tm, parameters::all_default()); + return sample_triangle_mesh(tm, out, parameters::all_default()); } template Date: Mon, 9 Jan 2017 08:35:56 +0100 Subject: [PATCH 71/72] only use parallel tag if TBB is here --- .../test/Polygon_mesh_processing/test_pmp_distance.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp index ef295895dc9..9064beb635a 100644 --- a/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp +++ b/Polygon_mesh_processing/test/Polygon_mesh_processing/test_pmp_distance.cpp @@ -282,6 +282,7 @@ int main(int, char** argv) std::cout << "Second mesh has " << num_faces(m2) << " faces\n"; CGAL::Real_timer time; + #ifdef CGAL_LINKED_WITH_TBB time.start(); std::cout << "Distance between meshes (parallel) " << PMP::approximate_Hausdorff_distance( @@ -289,6 +290,7 @@ int main(int, char** argv) << "\n"; time.stop(); std::cout << "done in " << time.time() << "s.\n"; + #endif time.reset(); time.start(); From 2cac99b2d68967e1c1d744ff72906e1402614326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loriot?= Date: Mon, 9 Jan 2017 12:00:06 +0100 Subject: [PATCH 72/72] protect call to max function --- .../internal/mesh_to_point_set_hausdorff_distance.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h index 4ba1bb2c97b..9ec3c3d553c 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/internal/mesh_to_point_set_hausdorff_distance.h @@ -197,7 +197,7 @@ public: CRefiner () { m_lower_bound = 0.; - m_upper_bound = std::numeric_limits::max (); + m_upper_bound = (std::numeric_limits::max) (); } bool empty () @@ -208,7 +208,7 @@ public: { m_queue = std::priority_queue (); m_lower_bound = lower_bound; - m_upper_bound = std::numeric_limits::max (); + m_upper_bound = (std::numeric_limits::max) (); } inline FT uncertainty () const