// Copyright (c) 2015 GeometryFactory (France). // All rights reserved. // // This file is part of CGAL (www.cgal.org). // // $URL$ // $Id$ // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial // // // Author(s) : Maxime Gimeno, Sebastien Loriot, Martin Skrodzki, Dmitry Anisimov #ifndef CGAL_POLYGON_MESH_PROCESSING_DISTANCE_H #define CGAL_POLYGON_MESH_PROCESSING_DISTANCE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CGAL_LINKED_WITH_TBB #include #include #include #include // METIS related #include #endif // CGAL_LINKED_WITH_TBB #include #include #include #include #include namespace CGAL { namespace Polygon_mesh_processing { namespace internal { template PointOutputIterator triangle_grid_sampling(const typename Kernel::Point_3& p0, const typename Kernel::Point_3& p1, const typename Kernel::Point_3& p2, double distance, PointOutputIterator out) { 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 )); for(double i=1; i struct Distance_computation{ typedef typename PointRange::const_iterator::value_type Point_3; const AABB_tree& tree; const PointRange& sample_points; Point_3 initial_hint; double distance; //constructor Distance_computation( const AABB_tree& tree, const Point_3& p, const PointRange& sample_points) : tree(tree) , sample_points(sample_points) , initial_hint(p) , distance(-1) {} //split constructor Distance_computation(Distance_computation& s, tbb::split ) : tree(s.tree) , sample_points(s.sample_points) , initial_hint(s.initial_hint) , distance(-1) {} void operator()(const tbb::blocked_range& range) { 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.begin() + i), hint); typename Kernel_traits::Kernel::Compute_squared_distance_3 squared_distance; double d = to_double(CGAL::approximate_sqrt( squared_distance(hint,*(sample_points.begin() + i)) )); if(d > hdist) hdist=d; } if(hdist > distance) distance = hdist; } void join( Distance_computation& rhs ) {distance = (std::max)(rhs.distance, distance); } }; #endif template double approximate_Hausdorff_distance_impl( const PointRange& sample_points, const AABBTree& tree, typename Kernel::Point_3 hint) { #if !defined(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) { std::atomic distance; distance=0; Distance_computation f(tree, hint, sample_points); tbb::parallel_reduce(tbb::blocked_range(0, sample_points.size()), f); return f.distance; } else #endif { double hdist = 0; for(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) hdist=d; } return hdist; } } template struct Triangle_structure_sampler_base { const NamedParameters np; GeomTraits gt; PointOutputIterator& out; Triangle_structure_sampler_base(PointOutputIterator& out, const NamedParameters& np) : np(np), out(out) {} void sample_points(); double get_minimum_edge_length(); template double get_tr_area(const Tr&); template std::array get_tr_points(const Tr& tr); void ms_edges_sample(const std::size_t& nb_points_per_edge, const std::size_t& nb_pts_l_u); void ru_edges_sample(); void internal_sample_triangles(double, bool, bool); Randomizer get_randomizer(); std::pair get_range(); std::size_t get_points_size(); void procede() { using parameters::choose_parameter; using parameters::get_parameter; using parameters::is_default_parameter; gt = choose_parameter(get_parameter(np, internal_np::geom_traits)); bool use_rs = choose_parameter(get_parameter(np, internal_np::random_uniform_sampling), true); bool use_gs = choose_parameter(get_parameter(np, internal_np::grid_sampling), false); bool use_ms = choose_parameter(get_parameter(np, internal_np::monte_carlo_sampling), false); if(use_gs || use_ms) if(is_default_parameter(get_parameter(np, internal_np::random_uniform_sampling))) use_rs = false; bool smpl_vrtcs = choose_parameter(get_parameter(np, internal_np::do_sample_vertices), true); bool smpl_dgs = choose_parameter(get_parameter(np, internal_np::do_sample_edges), true); bool smpl_fcs = choose_parameter(get_parameter(np, internal_np::do_sample_faces), true); double nb_pts_a_u = choose_parameter(get_parameter(np, internal_np::nb_points_per_area_unit), 0.); double nb_pts_l_u = choose_parameter(get_parameter(np, internal_np::nb_points_per_distance_unit), 0.); // sample vertices if(smpl_vrtcs) static_cast(this)->sample_points(); // grid sampling if(use_gs) { double grid_spacing_ = choose_parameter(get_parameter(np, internal_np::grid_spacing), 0.); if(grid_spacing_ == 0.) { // set grid spacing to the shortest edge length grid_spacing_ = static_cast(this)->get_minimum_edge_length(); } static_cast(this)->internal_sample_triangles(grid_spacing_, smpl_fcs, smpl_dgs); } // monte carlo sampling if(use_ms) { double min_sq_edge_length = (std::numeric_limits::max)(); std::size_t nb_points_per_face = choose_parameter(get_parameter(np, internal_np::number_of_points_per_face), 0); std::size_t nb_points_per_edge = choose_parameter(get_parameter(np, internal_np::number_of_points_per_edge), 0); if((nb_points_per_face == 0 && nb_pts_a_u == 0.) || (nb_points_per_edge == 0 && nb_pts_l_u == 0.)) { min_sq_edge_length = static_cast(this)->get_minimum_edge_length(); } // sample faces if(smpl_fcs) { // set default value if(nb_points_per_face == 0 && nb_pts_a_u == 0.) nb_pts_a_u = 2. / min_sq_edge_length; for(const auto& tr : make_range(static_cast(this)->get_range())) { std::size_t nb_points = nb_points_per_face; if(nb_points == 0) { nb_points = (std::max)( static_cast( std::ceil(static_cast(this)->get_tr_area(tr)) *nb_pts_a_u), std::size_t(1)); } // extract triangle face points std::arraypoints = static_cast(this)->get_tr_points(tr); Random_points_in_triangle_3 g(points[0], points[1], points[2]); out = std::copy_n(g, nb_points, out); } } // sample edges if(smpl_dgs) static_cast(this)->ms_edges_sample(nb_points_per_edge, nb_pts_l_u); } // random uniform sampling if(use_rs) { // sample faces if(smpl_fcs) { std::size_t nb_points = choose_parameter(get_parameter(np, internal_np::number_of_points_on_faces), 0); typename Derived::Randomizer g = static_cast(this)->get_randomizer(); if(nb_points == 0) { if(nb_pts_a_u == 0.) nb_points = static_cast(this)->get_points_size(); else nb_points = static_cast(std::ceil(g.sum_of_weights()*nb_pts_a_u)); } out = std::copy_n(g, nb_points, out); } // sample edges if(smpl_dgs) static_cast(this)->ru_edges_sample(nb_pts_l_u,nb_pts_a_u); } } }; } // namespace internal template PointOutputIterator sample_triangles(const FaceRange& triangles, const TriangleMesh& tm, VertexPointMap vpm, double distance, PointOutputIterator 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; typedef boost::graph_traits GT; typedef typename GT::face_descriptor face_descriptor; typedef typename GT::halfedge_descriptor halfedge_descriptor; boost::unordered_set sampled_edges; boost::unordered_set endpoints; for(face_descriptor fd : triangles) { // sample edges but skip endpoints halfedge_descriptor hd = halfedge(fd, tm); for(int i=0;i<3; ++i) { 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)); 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)); for(double i=1; i(p0, p1, p2, distance, out); } } return out; } namespace internal { template struct Triangle_structure_sampler_for_triangle_mesh : Triangle_structure_sampler_base::face_iterator, Random_points_in_triangle_mesh_3, Creator, Triangle_structure_sampler_for_triangle_mesh > { typedef Triangle_structure_sampler_for_triangle_mesh Self; typedef Triangle_structure_sampler_base::face_iterator, Random_points_in_triangle_mesh_3, Creator, Self> Base; typedef boost::graph_traits GT; typedef typename GT::halfedge_descriptor halfedge_descriptor; typedef typename GT::edge_descriptor edge_descriptor; typedef typename GT::face_descriptor face_descriptor; typedef Random_points_in_triangle_mesh_3 Randomizer; typedef typename boost::graph_traits::face_iterator TriangleIterator; Vpm pmap; double min_sq_edge_length; const Mesh& tm; Triangle_structure_sampler_for_triangle_mesh(const Mesh& m, PointOutputIterator& out, const NamedParameters& np) : Base(out, np), tm(m) { using parameters::choose_parameter; using parameters::get_parameter; pmap = choose_parameter(get_parameter(np, internal_np::vertex_point), get_const_property_map(vertex_point, tm)); min_sq_edge_length = (std::numeric_limits::max)(); } std::pair get_range() { return std::make_pair(faces(tm).begin(), faces(tm).end()); } void sample_points() { Property_map_to_unary_function unary(pmap); this->out = std::copy(boost::make_transform_iterator(boost::begin(vertices(tm)), unary), boost::make_transform_iterator(boost::end(vertices(tm)), unary), this->out); } double get_minimum_edge_length() { typedef typename boost::graph_traits::edge_descriptor edge_descriptor; if(min_sq_edge_length != (std::numeric_limits::max)()) return min_sq_edge_length; for(edge_descriptor ed : edges(tm)) { const double sq_el = CGAL::to_double( typename GeomTraits::Compute_squared_distance_3()(get(pmap, source(ed, tm)), get(pmap, target(ed, tm)))); if(sq_el > 0. && sq_el < min_sq_edge_length) min_sq_edge_length = sq_el; } return min_sq_edge_length; } double get_tr_area(const typename boost::graph_traits::face_descriptor& tr) { return to_double(face_area(tr,tm,parameters::geom_traits(this->gt))); } template//tr = face_descriptor here std::array get_tr_points(const Tr& tr) { std::array points; halfedge_descriptor hd(halfedge(tr,tm)); for(int i=0; i<3; ++i) { points[i] = get(pmap, target(hd, tm)); hd = next(hd, tm); } return points; } void ms_edges_sample(std::size_t nb_points_per_edge, double nb_pts_l_u) { typename GeomTraits::Compute_squared_distance_3 squared_distance = this->gt.compute_squared_distance_3_object(); if(nb_points_per_edge == 0 && nb_pts_l_u == 0.) nb_pts_l_u = 1. / CGAL::sqrt(min_sq_edge_length); for(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))); this->out = std::copy_n(g, nb_points, this->out); } } void ru_edges_sample(double nb_pts_l_u, double nb_pts_a_u) { using parameters::choose_parameter; using parameters::get_parameter; std::size_t nb_points = choose_parameter(get_parameter(this->np, internal_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_a_u)); } this->out = std::copy_n(g, nb_points, this->out); } Randomizer get_randomizer() { return Randomizer(tm, pmap); } void internal_sample_triangles(double grid_spacing_, bool smpl_fcs, bool smpl_dgs) { this->out = sample_triangles(faces(tm), tm, pmap, grid_spacing_, this->out, smpl_fcs, smpl_dgs, false); } std::size_t get_points_size() { return num_vertices(tm); } }; template struct Triangle_structure_sampler_for_triangle_soup : Triangle_structure_sampler_base, Creator, Triangle_structure_sampler_for_triangle_soup > { typedef typename TriangleRange::value_type TriangleType; typedef Triangle_structure_sampler_for_triangle_soup Self; typedef Triangle_structure_sampler_base, Creator, Self> Base; typedef typename GeomTraits::Point_3 Point_3; typedef Random_points_in_triangle_soup Randomizer; typedef typename TriangleRange::const_iterator TriangleIterator; double min_sq_edge_length; const PointRange& points; const TriangleRange& triangles; Triangle_structure_sampler_for_triangle_soup(const PointRange& pts, const TriangleRange& trs, PointOutputIterator& out, const NamedParameters& np) : Base(out, np), points(pts), triangles(trs) { min_sq_edge_length = (std::numeric_limits::max)(); } std::pair get_range() { return std::make_pair(triangles.begin(), triangles.end()); } void sample_points() { this->out = std::copy(points.begin(), points.end(), this->out); } double get_minimum_edge_length() { if(min_sq_edge_length != (std::numeric_limits::max)()) return min_sq_edge_length; for(const auto& tr : triangles) { for(std::size_t i = 0; i< 3; ++i) { const Point_3& a = points[tr[i]]; const Point_3& b = points[tr[(i+1)%3]]; const double sq_el = CGAL::to_double(typename GeomTraits::Compute_squared_distance_3()(a, b)); if(sq_el > 0. && sq_el < min_sq_edge_length) min_sq_edge_length = sq_el; } } return min_sq_edge_length; } template double get_tr_area(const Tr& tr) { return to_double(approximate_sqrt( this->gt.compute_squared_area_3_object()( points[tr[0]], points[tr[1]], points[tr[2]]))); } template std::array get_tr_points(const Tr& tr) { std::array points; for(int i=0; i<3; ++i) { points[i] = this->points[tr[i]]; } return points; } void ms_edges_sample(std::size_t, double) { // don't sample edges in soup. } void ru_edges_sample(double, double) { // don't sample edges in soup. } Randomizer get_randomizer() { return Randomizer(triangles, points); } void internal_sample_triangles(double distance, bool, bool) { for(const auto& tr : triangles) { const Point_3& p0 = points[tr[0]]; const Point_3& p1 = points[tr[1]]; const Point_3& p2 = points[tr[2]]; this->out = internal::triangle_grid_sampling(p0, p1, p2, distance, this->out); } } std::size_t get_points_size() { return points.size(); } }; } // namespace internal /** \ingroup PMP_distance_grp * * generates points on `tm` and outputs them to `out`; the sampling method * is selected using named parameters. * * @tparam TriangleMesh a model of the concepts `EdgeListGraph` and `FaceListGraph` * @tparam PointOutputIterator a model of `OutputIterator` * holding objects of the same point type as * the value type of the point type associated to the mesh `tm`, i.e. the value type of the vertex * point map property map, if provided, or the value type of the internal point property map otherwise * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * * @param tm the triangle mesh to be sampled * @param out output iterator to be filled with sample points * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin * \cgalParamNBegin{vertex_point_map} * \cgalParamDescription{a property map associating points to the vertices of `tm`} * \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%vertex_descriptor` * as key type and `%Point_3` as value type} * \cgalParamDefault{`boost::get(CGAL::vertex_point, tm)`} * \cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` * must be available in `TriangleMesh`.} * \cgalParamNEnd * * \cgalParamNBegin{geom_traits} * \cgalParamDescription{an instance of a geometric traits class} * \cgalParamType{a class model of `PMPDistanceTraits`} * \cgalParamDefault{a \cgal Kernel deduced from the point type, using `CGAL::Kernel_traits`} * \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.} * \cgalParamNEnd * * \cgalParamNBegin{use_random_uniform_sampling} * \cgalParamDescription{If `true` is passed, points are generated in a random and uniform way * on the surface of `tm`, and/or on edges of `tm`.} * \cgalParamType{Boolean} * \cgalParamType{`true`} * \cgalParamExtra{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_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 `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)`.} * \cgalParamNEnd * * \cgalParamNBegin{use_grid_sampling} * \cgalParamDescription{If `true` is passed, points are generated on a grid in each triangle, * with a minimum of one point per triangle.} * \cgalParamType{Boolean} * \cgalParamDefault{`false`} * \cgalParamExtra{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 * `grid_spacing`. Edges are also split using the same distance, if requested.} * \cgalParamNEnd * * \cgalParamNBegin{use_monte_carlo_sampling} * \cgalParamDescription{if `true` is passed, points are generated randomly in each triangle and/or on each edge.} * \cgalParamType{Boolean} * \cgalParamDefault{`false`} * \cgalParamExtra{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_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_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 * 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 `number_of_points_per_distance_unit`.} * \cgalParamNEnd * * \cgalParamNBegin{sample_vertices} * \cgalParamDescription{If `true` is passed, the vertices of `tm` are part of the sample.} * \cgalParamType{Boolean} * \cgalParamDefault{`true`} * \cgalParamNEnd * * \cgalParamNBegin{sample_edges} * \cgalParamDescription{If `true` is passed, edges of `tm` are sampled.} * \cgalParamType{Boolean} * \cgalParamDefault{`true`} * \cgalParamNEnd * * \cgalParamNBegin{sample_faces} * \cgalParamDescription{If `true` is passed, faces of `tm` are sampled.} * \cgalParamType{Boolean} * \cgalParamDefault{`true`} * \cgalParamNEnd * * \cgalParamNBegin{grid_spacing} * \cgalParamDescription{a value used as the grid spacing for the grid sampling method} * \cgalParamType{double} * \cgalParamDefault{the length of the shortest, non-degenerate edge of `tm`} * \cgalParamNEnd * * \cgalParamNBegin{number_of_points_on_edges} * \cgalParamDescription{a value used for the random sampling method as the number of points to pick exclusively on edges} * \cgalParamType{unsigned int} * \cgalParamDefault{`num_vertices(tm)` or a value based on `nb_points_per_distance_unit`, if it is defined} * \cgalParamNEnd * * \cgalParamNBegin{number_of_points_on_faces} * \cgalParamDescription{a value used for the random sampling method as the number of points to pick on the surface} * \cgalParamType{unsigned int} * \cgalParamDefault{`num_vertices(tm)` or a value based on `nb_points_per_area_unit`, if it is defined} * \cgalParamNEnd * * \cgalParamNBegin{number_of_points_per_distance_unit} * \cgalParamDescription{a value used for the random sampling and the Monte Carlo sampling methods to * respectively determine the total number of points on edges and the number of points per edge} * \cgalParamType{double} * \cgalParamDefault{`1` divided by the length of the shortest, non-degenerate edge of `tm`} * \cgalParamNEnd * * \cgalParamNBegin{number_of_points_per_edge} * \cgalParamDescription{a value used by the Monte-Carlo sampling method as the number of points per edge to pick} * \cgalParamType{unsigned int} * \cgalParamDefault{`0`} * \cgalParamNEnd * * \cgalParamNBegin{number_of_points_per_area_unit} * \cgalParamDescription{a value used for the random sampling and the Monte Carlo sampling methods to * respectively determine the total number of points inside faces and the number of points per face} * \cgalParamType{double} * \cgalParamDefault{`2` divided by the squared length of the shortest, non-degenerate edge of `tm`} * \cgalParamNEnd * * \cgalParamNBegin{number_of_points_per_face} * \cgalParamDescription{a value used by the Monte-Carlo sampling method as the number of points per face to pick} * \cgalParamType{unsigned int} * \cgalParamDefault{`0`} * \cgalParamNEnd * \cgalNamedParamsEnd * * @see `CGAL::Polygon_mesh_processing::sample_triangle_soup()` */ template PointOutputIterator sample_triangle_mesh(const TriangleMesh& tm, PointOutputIterator out, const NamedParameters& np) { typedef typename GetGeomTraits::type GeomTraits; typedef typename GetVertexPointMap::const_type Vpm; internal::Triangle_structure_sampler_for_triangle_mesh, Vpm, NamedParameters> performer(tm, out, np); performer.procede(); return performer.out; } /** \ingroup PMP_distance_grp * * generates points on a triangle soup and puts them to `out`; the sampling method * is selected using named parameters. * * @tparam PointRange a model of the concept `RandomAccessContainer` whose value type is the point type. * @tparam TriangleRange a model of the concept `RandomAccessContainer` * whose `value_type` is itself a model of the concept `RandomAccessContainer` * whose `value_type` is an unsigned integral value. * @tparam PointOutputIterator a model of `OutputIterator` holding objects of the same type as `PointRange`'s value type * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * * @param points the points of the soup * @param triangles a `TriangleRange` containing the triangles of the soup to be sampled * @param out output iterator to be filled with sample points * @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin * \cgalParamNBegin{geom_traits} * \cgalParamDescription{an instance of a geometric traits class} * \cgalParamType{a class model of `PMPDistanceTraits`} * \cgalParamDefault{a \cgal Kernel deduced from the point type, using `CGAL::Kernel_traits`} * \cgalParamExtra{The geometric traits class must be compatible with the point range's point type.} * \cgalParamNEnd * * \cgalParamNBegin{use_random_uniform_sampling} * \cgalParamDescription{If `true` is passed, points are generated in a random and uniform way * over the triangles of the soup.} * \cgalParamType{Boolean} * \cgalParamType{`true`} * \cgalParamExtra{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_area_unit` * is multiplied by the area of the soup to get the number of sample points. * If none of these parameters is set, the number of points sampled is `points.size()`.} * \cgalParamNEnd * * \cgalParamNBegin{use_grid_sampling} * \cgalParamDescription{If `true` is passed, points are generated on a grid in each triangle, * with a minimum of one point per triangle.} * \cgalParamType{Boolean} * \cgalParamDefault{`false`} * \cgalParamExtra{The distance between two consecutive points in the grid is that of the length * of the smallest non-null edge of the soup or the value passed to the named parameter * `grid_spacing`.} * \cgalParamNEnd * * \cgalParamNBegin{use_monte_carlo_sampling} * \cgalParamDescription{if `true` is passed, points are generated randomly in each triangle.} * \cgalParamType{Boolean} * \cgalParamDefault{`false`} * \cgalParamExtra{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_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, the number of points per area unit is set to 2 divided * by the square of the length of the smallest non-null edge of the soup.} * \cgalParamNEnd * * \cgalParamNBegin{sample_vertices} * \cgalParamDescription{If `true` is passed, the points of `points` are part of the sample.} * \cgalParamType{Boolean} * \cgalParamDefault{`true`} * \cgalParamNEnd * * \cgalParamNBegin{sample_faces} * \cgalParamDescription{If `true` is passed, faces of the soup are sampled.} * \cgalParamType{Boolean} * \cgalParamDefault{`true`} * \cgalParamNEnd * * \cgalParamNBegin{grid_spacing} * \cgalParamDescription{a value used as the grid spacing for the grid sampling method} * \cgalParamType{double} * \cgalParamDefault{the length of the shortest, non-degenerate edge of the soup} * \cgalParamNEnd * * \cgalParamNBegin{number_of_points_on_faces} * \cgalParamDescription{a value used for the random sampling method as the number of points to pick on the surface} * \cgalParamType{unsigned int} * \cgalParamDefault{`points.size()` or a value based on `nb_points_per_area_unit`, if it is defined} * \cgalParamNEnd * * \cgalParamNBegin{number_of_points_per_face} * \cgalParamDescription{a value used by the Monte-Carlo sampling method as the number of points per face to pick} * \cgalParamType{unsigned int} * \cgalParamDefault{`0`} * \cgalParamNEnd * * \cgalParamNBegin{number_of_points_per_area_unit} * \cgalParamDescription{a value used for the random sampling and the Monte Carlo sampling methods to * respectively determine the total number of points inside faces and the number of points per face} * \cgalParamType{double} * \cgalParamDefault{`2` divided by the squared length of the shortest, non-degenerate edge of the soup} * \cgalParamNEnd * \cgalNamedParamsEnd * * \attention Contrary to `sample_triangle_mesh()`, this method does not allow to sample edges. * * @see `CGAL::Polygon_mesh_processing::sample_triangle_mesh()` */ template PointOutputIterator sample_triangle_soup(const PointRange& points, const TriangleRange& triangles, PointOutputIterator out, const NamedParameters& np) { typedef typename PointRange::value_type Point_3; typedef typename Kernel_traits::Kernel GeomTraits; static_assert(std::is_same::value, "Wrong point type."); internal::Triangle_structure_sampler_for_triangle_soup, NamedParameters> performer(points, triangles, out, np); performer.procede(); return performer.out; } template PointOutputIterator sample_triangle_mesh(const TriangleMesh& tm, PointOutputIterator out) { return sample_triangle_mesh(tm, out, parameters::all_default()); } template PointOutputIterator sample_triangle_soup(const PointRange& points, const TriangleRange& triangles, PointOutputIterator out) { return sample_triangle_soup(points, triangles, out, parameters::all_default()); } template double approximate_Hausdorff_distance( const PointRange& original_sample_points, const TriangleMesh& tm, VertexPointMap vpm) { 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 std::cout << "Nb sample points " << sample_points.size() << "\n"; #endif 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_tree< AABB_traits > Tree; Tree tree( faces(tm).first, faces(tm).second, tm); tree.build(); Point_3 hint = get(vpm, *vertices(tm).first); return internal::approximate_Hausdorff_distance_impl (original_sample_points, tree, hint); } template double approximate_Hausdorff_distance( const TriangleMesh& tm1, const TriangleMesh& tm2, const NamedParameters& np, VertexPointMap vpm_2) { std::vector sample_points; sample_triangle_mesh(tm1, std::back_inserter(sample_points), np); return approximate_Hausdorff_distance(sample_points, tm2, vpm_2); } // documented functions /** * \ingroup PMP_distance_grp * computes the approximate Hausdorff distance from `tm1` to `tm2` by returning * 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. * * 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`, `Parallel_tag`, and `Parallel_if_available_tag`. * @tparam TriangleMesh a model of the concepts `EdgeListGraph` and `FaceListGraph` * @tparam NamedParameters1 a sequence of \ref bgl_namedparameters "Named Parameters" for `tm1` * @tparam NamedParameters2 a sequence of \ref bgl_namedparameters "Named Parameters" for `tm2` * * @param tm1 the triangle mesh that will be sampled * @param tm2 the triangle mesh to compute the distance to * @param np1 an optional sequence of \ref bgl_namedparameters "Named Parameters" forwarded to `sample_triangle_mesh()` * * @param np2 an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin * \cgalParamNBegin{vertex_point_map} * \cgalParamDescription{a property map associating points to the vertices of `tm2`} * \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%vertex_descriptor` * as key type and `%Point_3` as value type} * \cgalParamDefault{`boost::get(CGAL::vertex_point, tm2)`} * \cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` * must be available in `TriangleMesh`.} * \cgalParamNEnd * \cgalNamedParamsEnd * * The function `CGAL::parameters::all_default()` can be used to indicate to use the default values * for `np1` and specify custom values for `np2`. */ template< class Concurrency_tag, class TriangleMesh, class NamedParameters1, class NamedParameters2> double approximate_Hausdorff_distance( const TriangleMesh& tm1, const TriangleMesh& tm2, const NamedParameters1& np1, const NamedParameters2& np2) { typedef typename GetGeomTraits::type GeomTraits; return approximate_Hausdorff_distance( tm1, tm2, np1, parameters::choose_parameter(parameters::get_parameter(np2, internal_np::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, np1, np2)` * and `approximate_Hausdorff_distance(tm2, tm1, np2, np1)`. */ template< class Concurrency_tag, class TriangleMesh, class NamedParameters1, class NamedParameters2> double approximate_symmetric_Hausdorff_distance( const TriangleMesh& tm1, const TriangleMesh& tm2, const NamedParameters1& np1, const NamedParameters2& np2) { return (std::max)( approximate_Hausdorff_distance(tm1,tm2,np1,np2), approximate_Hausdorff_distance(tm2,tm1,np2,np1) ); } /** * \ingroup PMP_distance_grp * 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`. Its iterator type is `RandomAccessIterator`. * @tparam TriangleMesh a model of the concepts `EdgeListGraph` and `FaceListGraph` * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * * @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 bgl_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin * \cgalParamNBegin{vertex_point_map} * \cgalParamDescription{a property map associating points to the vertices of `tm`} * \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%vertex_descriptor` * as key type and `%Point_3` as value type} * \cgalParamDefault{`boost::get(CGAL::vertex_point, tm)`} * \cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` * must be available in `TriangleMesh`.} * \cgalParamNEnd * * \cgalParamNBegin{geom_traits} * \cgalParamDescription{an instance of a geometric traits class} * \cgalParamType{a class model of `PMPDistanceTraits`} * \cgalParamDefault{a \cgal Kernel deduced from the point type, using `CGAL::Kernel_traits`} * \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.} * \cgalParamNEnd * \cgalNamedParamsEnd */ template< class Concurrency_tag, class TriangleMesh, class PointRange, class NamedParameters> double max_distance_to_triangle_mesh(const PointRange& points, const TriangleMesh& tm, const NamedParameters& np) { typedef typename GetGeomTraits::type GeomTraits; return approximate_Hausdorff_distance (points,tm,parameters::choose_parameter(parameters::get_parameter(np, internal_np::vertex_point), get_const_property_map(vertex_point, tm))); } /*! *\ingroup PMP_distance_grp * 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 bgl_namedparameters "Named Parameters" * * @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 bgl_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin * \cgalParamNBegin{vertex_point_map} * \cgalParamDescription{a property map associating points to the vertices of `tm`} * \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%vertex_descriptor` * as key type and `%Point_3` as value type} * \cgalParamDefault{`boost::get(CGAL::vertex_point, tm)`} * \cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` * must be available in `TriangleMesh`.} * \cgalParamNEnd * * \cgalParamNBegin{geom_traits} * \cgalParamDescription{an instance of a geometric traits class} * \cgalParamType{a class model of `PMPDistanceTraits`} * \cgalParamDefault{a \cgal Kernel deduced from the point type, using `CGAL::Kernel_traits`} * \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.} * \cgalParamNEnd * \cgalNamedParamsEnd */ template< 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) { typedef typename GetGeomTraits::type GeomTraits; typedef boost::graph_traits GT; typedef Orthogonal_k_neighbor_search > Knn; typedef typename Knn::Tree Tree; Tree tree(points.begin(), points.end()); CRefiner ref; for(typename GT::face_descriptor f : faces(tm)) { typename GeomTraits::Point_3 points[3]; typename GT::halfedge_descriptor hd(halfedge(f,tm)); for(int i=0; i<3; ++i) { points[i] = get(parameters::choose_parameter(parameters::get_parameter(np, internal_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); } return to_double(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 approximate_max_distance_to_point_set(const TriangleMesh& tm, const PointRange& points, const double precision) { 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, const NamedParameters& np) { return approximate_Hausdorff_distance( tm1, tm2, np, parameters::all_default()); } template< class Concurrency_tag, class TriangleMesh> double approximate_Hausdorff_distance(const TriangleMesh& tm1, const TriangleMesh& tm2) { return approximate_Hausdorff_distance( tm1, tm2, parameters::all_default(), parameters::all_default()); } template< class Concurrency_tag, class TriangleMesh, class NamedParameters> double approximate_symmetric_Hausdorff_distance(const TriangleMesh& tm1, const TriangleMesh& tm2, const NamedParameters& np) { return approximate_symmetric_Hausdorff_distance( tm1, tm2, np, parameters::all_default()); } template< class Concurrency_tag, class TriangleMesh> double approximate_symmetric_Hausdorff_distance(const TriangleMesh& tm1, const TriangleMesh& tm2) { return approximate_symmetric_Hausdorff_distance( tm1, tm2, parameters::all_default(), parameters::all_default()); } //////////////////////////////////////////////////////////////////////// namespace internal { template< class Kernel, class TriangleMesh1, class TriangleMesh2, class VPM1, class VPM2, class TM1Tree, class TM2Tree, class FaceHandle1, class FaceHandle2> std::pair preprocess_bounded_error_Hausdorff_impl( const TriangleMesh1& tm1, const TriangleMesh2& tm2, const bool compare_meshes, const VPM1& vpm1, const VPM2& vpm2, const bool is_one_sided_distance, TM1Tree& tm1_tree, TM2Tree& tm2_tree, std::vector& tm1_only, std::vector& tm2_only) { using FT = typename Kernel::FT; using Point_3 = typename Kernel::Point_3; using Timer = CGAL::Real_timer; Timer timer; timer.start(); // std::cout << "* preprocessing begin ...." << std::endl; std::cout.precision(20); // Compute the max value that is used as infinity value for the given meshes. // In our case, it is twice the length of the diagonal of the bbox of two input meshes. const auto bbox1 = bbox(tm1); const auto bbox2 = bbox(tm2); const auto bb = bbox1 + bbox2; const FT sq_dist = CGAL::squared_distance( Point_3(bb.xmin(), bb.ymin(), bb.zmin()), Point_3(bb.xmax(), bb.ymax(), bb.zmax())); FT infinity_value = CGAL::approximate_sqrt(sq_dist) * FT(2); // Compare meshes and build trees. tm1_only.clear(); tm2_only.clear(); std::vector< std::pair > common; const auto faces1 = faces(tm1); const auto faces2 = faces(tm2); CGAL_precondition(faces1.size() > 0); CGAL_precondition(faces2.size() > 0); // Compare meshes. bool rebuild = false; if (compare_meshes) { // exact check match_faces(tm1, tm2, std::back_inserter(common), std::back_inserter(tm1_only), std::back_inserter(tm2_only)); // std::cout << "- common: " << common.size() << std::endl; // std::cout << "- tm1 only: " << tm1_only.size() << std::endl; // std::cout << "- tm2 only: " << tm2_only.size() << std::endl; if (is_one_sided_distance) { // one-sided distance if (tm1_only.size() > 0) { // create TM1 and and full TM2 tm1_tree.insert(tm1_only.begin(), tm1_only.end(), tm1, vpm1); tm2_tree.insert(faces2.begin(), faces2.end(), tm2, vpm2); } else { // do not create trees CGAL_assertion(tm1_only.size() == 0); infinity_value = -FT(1); } } else { // symmetric distance if (tm1_only.size() == 0 && tm2_only.size() == 0) { // do not create trees infinity_value = -FT(1); } else if (common.size() == 0) { // create full TM1 and TM2 tm1_tree.insert(faces1.begin(), faces1.end(), tm1, vpm1); tm2_tree.insert(faces2.begin(), faces2.end(), tm2, vpm2); } else if (tm1_only.size() == 0) { // create TM2 and full TM1 CGAL_assertion(tm2_only.size() > 0); CGAL_assertion(tm2_only.size() < faces2.size()); tm1_tree.insert(faces1.begin(), faces1.end(), tm1, vpm1); tm2_tree.insert(tm2_only.begin(), tm2_only.end(), tm2, vpm2); } else if (tm2_only.size() == 0) { // create TM1 and full TM2 CGAL_assertion(tm1_only.size() > 0); CGAL_assertion(tm1_only.size() < faces1.size()); tm1_tree.insert(tm1_only.begin(), tm1_only.end(), tm1, vpm1); tm2_tree.insert(faces2.begin(), faces2.end(), tm2, vpm2); } else { // create TM1 and full TM2 and set tag to rebuild them later CGAL_assertion(tm1_only.size() > 0); CGAL_assertion(tm1_only.size() < faces1.size()); tm1_tree.insert(tm1_only.begin(), tm1_only.end(), tm1, vpm1); tm2_tree.insert(faces2.begin(), faces2.end(), tm2, vpm2); rebuild = true; } } } else { // create full TM1 and TM2 tm1_tree.insert(faces1.begin(), faces1.end(), tm1, vpm1); tm2_tree.insert(faces2.begin(), faces2.end(), tm2, vpm2); } // tm1_tree.build(); // tm2_tree.build(); timer.stop(); // std::cout << "* .... end preprocessing" << std::endl; // std::cout << "* preprocessing time (sec.): " << timer.time() << std::endl; return std::make_pair(infinity_value, rebuild); } template< class Kernel, class TriangleMesh1, class TriangleMesh2, class VPM1, class VPM2, class TM1Tree, class TM2Tree> double bounded_error_Hausdorff_impl( const TriangleMesh1& tm1, const TriangleMesh2& tm2, const typename Kernel::FT error_bound, const VPM1& vpm1, const VPM2& vpm2, const typename Kernel::FT infinity_value, const typename Kernel::FT initial_lower_bound, const TM1Tree& tm1_tree, const TM2Tree& tm2_tree) { using FT = typename Kernel::FT; using Point_3 = typename Kernel::Point_3; using Triangle_3 = typename Kernel::Triangle_3; using TM1_tree = TM1Tree; using TM2_tree = TM2Tree; using TM1_traits = typename TM1_tree::AABB_traits; using TM2_traits = typename TM2_tree::AABB_traits; using TM1_hd_traits = Hausdorff_primitive_traits_tm1; using TM2_hd_traits = Hausdorff_primitive_traits_tm2; using Face_handle_1 = typename boost::graph_traits::face_descriptor; using Face_handle_2 = typename boost::graph_traits::face_descriptor; using Candidate = Candidate_triangle; using Timer = CGAL::Real_timer; std::cout.precision(20); CGAL_precondition(error_bound >= FT(0)); CGAL_precondition(tm1_tree.size() > 0); CGAL_precondition(tm2_tree.size() > 0); // First, we apply culling. std::cout << "- applying culling" << std::endl; Timer timer; timer.start(); // Build traversal traits for tm1_tree. TM1_hd_traits traversal_traits_tm1( tm1_tree.traits(), tm2_tree, tm1, tm2, vpm1, vpm2, error_bound, infinity_value, initial_lower_bound); // Find candidate triangles in TM1, which might realise the Hausdorff bound. // We build a sorted structure while collecting the candidates. const Point_3 stub(0, 0, 0); // dummy point given as query since it is not needed tm1_tree.traversal_with_priority(stub, traversal_traits_tm1); auto& candidate_triangles = traversal_traits_tm1.get_candidate_triangles(); auto global_bounds = traversal_traits_tm1.get_global_bounds(); // std::cout << "- number of candidate triangles: " << candidate_triangles.size() << std::endl; // const FT culling_rate = FT(100) - (FT(candidate_triangles.size()) / FT(tm1_tree.size()) * FT(100)); // std::cout << "- culling rate: " << culling_rate << "%" << std::endl; timer.stop(); // std::cout << "* culling (sec.): " << timer.time() << std::endl; // Second, we apply subdivision. std::cout << "- applying subdivision" << std::endl; timer.reset(); timer.start(); // See Section 5.1 in the paper. CGAL_assertion(global_bounds.lower >= FT(0)); CGAL_assertion(global_bounds.upper >= FT(0)); const FT squared_error_bound = error_bound * error_bound; while ( (global_bounds.upper - global_bounds.lower > error_bound) && !candidate_triangles.empty()) { // Get the first triangle and its Hausdorff bounds from the candidate set. const Candidate triangle_and_bound = candidate_triangles.top(); // Remove it from the candidate set as it will be processed now. candidate_triangles.pop(); // Only process the triangle if it can contribute to the Hausdorff distance, // i.e. if its upper bound is higher than the currently known best lower bound // and the difference between the bounds to be obtained is larger than the // user given error. const auto& triangle_bounds = triangle_and_bound.bounds; CGAL_assertion(triangle_bounds.lower >= FT(0)); CGAL_assertion(triangle_bounds.upper >= FT(0)); if ( (triangle_bounds.upper > global_bounds.lower) && (triangle_bounds.upper - triangle_bounds.lower > error_bound)) { // Get the triangle that is to be subdivided and read its vertices. const Triangle_3& triangle_for_subdivision = triangle_and_bound.triangle; const Point_3 v0 = triangle_for_subdivision.vertex(0); const Point_3 v1 = triangle_for_subdivision.vertex(1); const Point_3 v2 = triangle_for_subdivision.vertex(2); // Check second stopping condition: All three vertices of the triangle // are projected onto the same triangle in TM2. const auto closest_triangle_v0 = tm2_tree.closest_point_and_primitive(v0); const auto closest_triangle_v1 = tm2_tree.closest_point_and_primitive(v1); const auto closest_triangle_v2 = tm2_tree.closest_point_and_primitive(v2); CGAL_assertion(closest_triangle_v0.second != boost::graph_traits::null_face()); CGAL_assertion(closest_triangle_v1.second != boost::graph_traits::null_face()); CGAL_assertion(closest_triangle_v2.second != boost::graph_traits::null_face()); if ( (closest_triangle_v0.second == closest_triangle_v1.second) && (closest_triangle_v1.second == closest_triangle_v2.second)) { // The upper bound of this triangle is the actual Hausdorff distance of // the triangle to the second mesh. Use it as new global lower bound. // TODO: update the reference to the realizing triangle here as this is the best current guess. global_bounds.lower = triangle_bounds.upper; global_bounds.lpair.second = triangle_bounds.tm2_uface; continue; } // Check third stopping condition: All edge lengths of the triangle are // smaller than the given error bound, we cannot get results beyond this bound. if ( CGAL::squared_distance(v0, v1) < squared_error_bound && CGAL::squared_distance(v0, v2) < squared_error_bound && CGAL::squared_distance(v1, v2) < squared_error_bound) { // The upper bound of this triangle is within error tolerance of // the actual upper bound, use it. global_bounds.lower = triangle_bounds.upper; global_bounds.lpair.second = triangle_bounds.tm2_uface; continue; } // Subdivide the triangle into four smaller triangles. const Point_3 v01 = CGAL::midpoint(v0, v1); const Point_3 v02 = CGAL::midpoint(v0, v2); const Point_3 v12 = CGAL::midpoint(v1, v2); const std::array sub_triangles = { Triangle_3(v0, v01, v02), Triangle_3(v1 , v01, v12), Triangle_3(v2, v02, v12), Triangle_3(v01, v02, v12) }; // Send each of the four triangles to Culling on B with the bounds of the parent triangle. for (std::size_t i = 0; i < 4; ++i) { // Call Culling on B with the single triangle found. TM2_hd_traits traversal_traits_tm2( tm2_tree.traits(), tm2, vpm2, triangle_bounds, infinity_value, infinity_value, infinity_value); tm2_tree.traversal_with_priority(sub_triangles[i], traversal_traits_tm2); // Update global lower Hausdorff bound according to the obtained local bounds. const auto local_bounds = traversal_traits_tm2.get_local_bounds(); CGAL_assertion(local_bounds.lower >= FT(0)); CGAL_assertion(local_bounds.upper >= FT(0)); CGAL_precondition(local_bounds.lpair == local_bounds.default_face_pair()); CGAL_precondition(local_bounds.upair == local_bounds.default_face_pair()); if (local_bounds.lower > global_bounds.lower) { global_bounds.lower = local_bounds.lower; global_bounds.lpair.second = local_bounds.tm2_lface; } // Add the subtriangle to the candidate list. candidate_triangles.push( Candidate(sub_triangles[i], local_bounds, triangle_and_bound.tm1_face)); } // Update global upper Hausdorff bound after subdivision. const FT current_max = candidate_triangles.top().bounds.upper; CGAL_assertion(current_max >= FT(0)); if (current_max > global_bounds.lower) { global_bounds.upper = current_max; global_bounds.upair.second = candidate_triangles.top().bounds.tm2_uface; } else { global_bounds.upper = global_bounds.lower; global_bounds.upair.second = global_bounds.lpair.second; } } } timer.stop(); // std::cout << "* subdivision (sec.): " << timer.time() << std::endl; // Compute linear interpolation between the found lower and upper bounds. CGAL_assertion(global_bounds.lower >= FT(0)); CGAL_assertion(global_bounds.upper >= FT(0)); const double hdist = CGAL::to_double((global_bounds.lower + global_bounds.upper) / FT(2)); // Get realizing triangles. // std::cout << "- found lface 1:" << static_cast(global_bounds.lpair.first) << std::endl; // std::cout << "- found lface 2:" << static_cast(global_bounds.lpair.second) << std::endl; // std::cout << "- found uface 1:" << static_cast(global_bounds.upair.first) << std::endl; // std::cout << "- found uface 2:" << static_cast(global_bounds.upair.second) << std::endl; CGAL_precondition(global_bounds.lpair.first != boost::graph_traits::null_face()); CGAL_precondition(global_bounds.lpair.second != boost::graph_traits::null_face()); CGAL_precondition(global_bounds.upair.first != boost::graph_traits::null_face()); CGAL_precondition(global_bounds.upair.second != boost::graph_traits::null_face()); // Off for the moment. // const auto realizing_triangles = global_bounds.upair; // return std::make_pair(hdist, realizing_triangles); return hdist; } #if defined(CGAL_LINKED_WITH_TBB) // TODO: && METIS! template< class TriangleMesh1, class TriangleMesh2, class VPM1, class VPM2, class TM1Tree, class TM2Tree, class Kernel> struct Bounded_error_preprocessing { using FT = typename Kernel::FT; using Timer = CGAL::Real_timer; using Face_handle_1 = typename boost::graph_traits::face_descriptor; using Face_handle_2 = typename boost::graph_traits::face_descriptor; const std::vector& tm1_parts; const TriangleMesh2& tm2; const bool compare_meshes; const VPM1& vpm1; const VPM2& vpm2; const bool is_one_sided_distance; std::vector& tm1_trees; FT infinity_value; // Constructor. Bounded_error_preprocessing( const std::vector& tm1_parts, const TriangleMesh2& tm2, const bool compare_meshes, const VPM1& vpm1, const VPM2& vpm2, const bool is_one_sided_distance, std::vector& tm1_trees) : tm1_parts(tm1_parts), tm2(tm2), compare_meshes(compare_meshes), vpm1(vpm1), vpm2(vpm2), is_one_sided_distance(is_one_sided_distance), tm1_trees(tm1_trees), infinity_value(-FT(1)) { } // Split constructor. Bounded_error_preprocessing( Bounded_error_preprocessing& s, tbb::split) : tm1_parts(s.tm1_parts), tm2(s.tm2), compare_meshes(s.compare_meshes), vpm1(s.vpm1), vpm2(s.vpm2), is_one_sided_distance(s.is_one_sided_distance), tm1_trees(s.tm1_trees), infinity_value(s.infinity_value) { } void operator()(const tbb::blocked_range& range) { TM2Tree tm2_tree; std::vector tm1_only; std::vector tm2_only; CGAL_assertion(tm1_parts.size() == tm1_trees.size()); Timer timer; timer.reset(); timer.start(); FT max_inf_value = -FT(1); for (std::size_t i = range.begin(); i != range.end(); ++i) { CGAL_assertion(i < tm1_parts.size()); CGAL_assertion(i < tm1_trees.size()); tm1_only.clear(); tm2_only.clear(); tm2_tree.clear(); const FT inf_value = preprocess_bounded_error_Hausdorff_impl( tm1_parts[i], tm2, compare_meshes, vpm1, vpm2, is_one_sided_distance, tm1_trees[i], tm2_tree, tm1_only, tm2_only).first; if (inf_value > max_inf_value) max_inf_value = inf_value; } if (max_inf_value > infinity_value) infinity_value = max_inf_value; timer.stop(); // std::cout << "* time operator() preprocessing (sec.): " << timer.time() << std::endl; } void join(Bounded_error_preprocessing& rhs) { infinity_value = (CGAL::max)(rhs.infinity_value, infinity_value); } }; template< class TriangleMesh1, class TriangleMesh2, class VPM1, class VPM2, class TM1Tree, class TM2Tree, class Kernel> struct Bounded_error_distance_computation { using FT = typename Kernel::FT; using Timer = CGAL::Real_timer; const std::vector& tm1_parts; const TriangleMesh2& tm2; const FT error_bound; const VPM1& vpm1; const VPM2& vpm2; const FT infinity_value; const FT initial_lower_bound; const std::vector& tm1_trees; const TM2Tree& tm2_tree; double distance; // Constructor. Bounded_error_distance_computation( const std::vector& tm1_parts, const TriangleMesh2& tm2, const FT error_bound, const VPM1& vpm1, const VPM2& vpm2, const FT infinity_value, const FT initial_lower_bound, const std::vector& tm1_trees, const TM2Tree& tm2_tree) : tm1_parts(tm1_parts), tm2(tm2), error_bound(error_bound), vpm1(vpm1), vpm2(vpm2), infinity_value(infinity_value), initial_lower_bound(initial_lower_bound), tm1_trees(tm1_trees), tm2_tree(tm2_tree), distance(-1.0) { } // Split constructor. Bounded_error_distance_computation( Bounded_error_distance_computation& s, tbb::split) : tm1_parts(s.tm1_parts), tm2(s.tm2), error_bound(s.error_bound), vpm1(s.vpm1), vpm2(s.vpm2), infinity_value(s.infinity_value), initial_lower_bound(s.initial_lower_bound), tm1_trees(s.tm1_trees), tm2_tree(s.tm2_tree), distance(-1.0) { } void operator()(const tbb::blocked_range& range) { CGAL_assertion(tm1_parts.size() == tm1_trees.size()); Timer timer; timer.reset(); timer.start(); double hdist = -1.0; // std::cout << "* range size: " << range.size() << std::endl; for (std::size_t i = range.begin(); i != range.end(); ++i) { CGAL_assertion(i < tm1_parts.size()); CGAL_assertion(i < tm1_trees.size()); // std::cout << "* part " << i << " size: " << tm1_parts[i].number_of_faces() << std::endl; const double dist = bounded_error_Hausdorff_impl( tm1_parts[i], tm2, error_bound, vpm1, vpm2, infinity_value, initial_lower_bound, tm1_trees[i], tm2_tree); if (dist > hdist) hdist = dist; } if (hdist > distance) distance = hdist; timer.stop(); // std::cout << "* time operator() computation (sec.): " << timer.time() << std::endl; } void join(Bounded_error_distance_computation& rhs) { distance = (CGAL::max)(rhs.distance, distance); } }; #endif // defined(CGAL_LINKED_WITH_TBB) && METIS template< class Concurrency_tag, class Kernel, class TriangleMesh1, class TriangleMesh2, class VPM1, class VPM2> double bounded_error_one_sided_Hausdorff_impl( const TriangleMesh1& tm1, const TriangleMesh2& tm2, const typename Kernel::FT error_bound, const bool compare_meshes, const VPM1& vpm1, const VPM2& vpm2) { using FT = typename Kernel::FT; using TM1_primitive = AABB_face_graph_triangle_primitive; using TM2_primitive = AABB_face_graph_triangle_primitive; using TM1_traits = AABB_traits; using TM2_traits = AABB_traits; using TM1_tree = AABB_tree; using TM2_tree = AABB_tree; using Face_handle_1 = typename boost::graph_traits::face_descriptor; using Face_handle_2 = typename boost::graph_traits::face_descriptor; using Timer = CGAL::Real_timer; Timer timer; std::cout.precision(20); const int nb_cores = 4; // TODO: add to NP! TM2_tree tm2_tree; std::vector tm1_trees; std::vector tm1_parts; std::atomic infinity_value; bool rebuild = false; std::vector tm1_only; std::vector tm2_only; #if !defined(CGAL_LINKED_WITH_TBB) // TODO: && METIS! CGAL_static_assertion_msg( !(boost::is_convertible::value), "Parallel_tag is enabled but TBB is unavailable."); infinity_value = -FT(1); #else if (boost::is_convertible::value && nb_cores > 1) { // (1) -- Create partition of tm1. timer.reset(); timer.start(); using Face_property_tag = CGAL::dynamic_face_property_t; auto face_pid_map = get(Face_property_tag(), tm1); CGAL::METIS::partition_graph( tm1, nb_cores, CGAL::parameters:: face_partition_id_map(face_pid_map)); timer.stop(); std::cout << "* computing partition time (sec.): " << timer.time() << std::endl; // (2) -- Create a filtered face graph for each part. timer.reset(); timer.start(); using Filtered_graph = CGAL::Face_filtered_graph; tm1_parts.resize(nb_cores); for (int i = 0; i < nb_cores; ++i) { Filtered_graph tm1_part(tm1, i, face_pid_map); CGAL_assertion(tm1_part.is_selection_valid()); CGAL::copy_face_graph(tm1_part, tm1_parts[i]); // TODO: do not copy in the future! std::cout << "* part " << i << " size: " << tm1_parts[i].number_of_faces() << std::endl; } timer.stop(); std::cout << "* creating graphs time (sec.): " << timer.time() << std::endl; // (3) -- Preprocess all input data. timer.reset(); timer.start(); // std::cout << "* preprocessing parallel version " << std::endl; tm1_trees.resize(tm1_parts.size()); FT inf_value = -FT(1); std::tie(inf_value, rebuild) = preprocess_bounded_error_Hausdorff_impl( tm1_parts[0], tm2, compare_meshes, vpm1, vpm2, true, tm1_trees[0], tm2_tree, tm1_only, tm2_only); CGAL_assertion(!rebuild); Bounded_error_preprocessing bep( tm1_parts, tm2, compare_meshes, vpm1, vpm2, true, tm1_trees); tbb::parallel_reduce(tbb::blocked_range(1, tm1_parts.size()), bep); infinity_value = (CGAL::max)(inf_value, bep.infinity_value); tm2_tree.build(); for (auto& tm1_tree : tm1_trees) tm1_tree.build(); timer.stop(); std::cout << "* preprocessing parallel time (sec.) " << timer.time() << std::endl; } else // sequential version #endif // defined(CGAL_LINKED_WITH_TBB) && METIS { // std::cout << "* preprocessing sequential version " << std::endl; timer.reset(); timer.start(); tm1_trees.resize(1); std::tie(infinity_value, rebuild) = preprocess_bounded_error_Hausdorff_impl( tm1, tm2, compare_meshes, vpm1, vpm2, true, tm1_trees[0], tm2_tree, tm1_only, tm2_only); CGAL_assertion(!rebuild); tm2_tree.build(); tm1_trees[0].build(); timer.stop(); std::cout << "* preprocessing sequential time (sec.) " << timer.time() << std::endl; } // std::cout << "* infinity_value: " << infinity_value << std::endl; if (infinity_value < FT(0)) { // std::cout << "* culling rate: 100%" << std::endl; return 0.0; // TM1 is part of TM2 so the distance is zero } CGAL_assertion(error_bound >= FT(0)); CGAL_assertion(infinity_value > FT(0)); const FT initial_lower_bound = error_bound; std::atomic hdist; timer.reset(); timer.start(); #if !defined(CGAL_LINKED_WITH_TBB) // TODO: && METIS! CGAL_static_assertion_msg( !(boost::is_convertible::value), "Parallel_tag is enabled but TBB is unavailable."); hdist = -1.0; #else if (boost::is_convertible::value && nb_cores > 1) { std::cout << "* executing parallel version " << std::endl; Bounded_error_distance_computation bedc( tm1_parts, tm2, error_bound, vpm1, vpm2, infinity_value, initial_lower_bound, tm1_trees, tm2_tree); tbb::parallel_reduce(tbb::blocked_range(0, tm1_parts.size()), bedc); hdist = bedc.distance; } else #endif // defined(CGAL_LINKED_WITH_TBB) && METIS { std::cout << "* executing sequential version " << std::endl; hdist = bounded_error_Hausdorff_impl( tm1, tm2, error_bound, vpm1, vpm2, infinity_value, initial_lower_bound, tm1_trees[0], tm2_tree); } timer.stop(); std::cout << "* computation time (sec.) " << timer.time() << std::endl; CGAL_assertion(hdist >= 0.0); return hdist; } template< class Concurrency_tag, class Kernel, class TriangleMesh1, class TriangleMesh2, class VPM1, class VPM2> double bounded_error_symmetric_Hausdorff_impl( const TriangleMesh1& tm1, const TriangleMesh2& tm2, const typename Kernel::FT error_bound, const bool compare_meshes, const VPM1& vpm1, const VPM2& vpm2) { // Naive version. // const double hdist1 = bounded_error_one_sided_Hausdorff_impl( // tm1, tm2, error_bound, compare_meshes, vpm1, vpm2); // const double hdist2 = bounded_error_one_sided_Hausdorff_impl( // tm2, tm1, error_bound, compare_meshes, vpm2, vpm1); // return (CGAL::max)(hdist1, hdist2); // Optimized version. // -- We compare meshes only if it is required. // -- We first build trees and rebuild them only if it is required. // -- We provide better initial lower bound in the second call to the Hausdorff distance. using FT = typename Kernel::FT; using TM1_primitive = AABB_face_graph_triangle_primitive; using TM2_primitive = AABB_face_graph_triangle_primitive; using TM1_traits = AABB_traits; using TM2_traits = AABB_traits; using TM1_tree = AABB_tree; using TM2_tree = AABB_tree; using Face_handle_1 = typename boost::graph_traits::face_descriptor; using Face_handle_2 = typename boost::graph_traits::face_descriptor; std::cout.precision(20); std::vector tm1_only; std::vector tm2_only; TM1_tree tm1_tree; TM2_tree tm2_tree; FT infinity_value = -FT(1); bool rebuild = false; std::tie(infinity_value, rebuild) = preprocess_bounded_error_Hausdorff_impl( tm1, tm2, compare_meshes, vpm1, vpm2, false, tm1_tree, tm2_tree, tm1_only, tm2_only); if (infinity_value < FT(0)) { // std::cout << "* culling rate: 100%" << std::endl; return 0.0; // TM1 and TM2 are equal so the distance is zero } CGAL_assertion(infinity_value > FT(0)); // Compute the first one-sided distance. FT initial_lower_bound = error_bound; double dista = CGAL::to_double(error_bound); tm1_tree.build(); tm2_tree.build(); if (!compare_meshes || (compare_meshes && tm1_only.size() > 0)) { dista = bounded_error_Hausdorff_impl( tm1, tm2, error_bound, vpm1, vpm2, infinity_value, initial_lower_bound, tm1_tree, tm2_tree); } // In case this is true, we need to rebuild trees in order to accelerate // computations for the second call. if (rebuild) { CGAL_assertion(compare_meshes); tm1_tree.clear(); tm2_tree.clear(); CGAL_assertion(tm2_only.size() > 0); CGAL_assertion(tm2_only.size() < faces(tm2).size()); tm1_tree.insert(faces(tm1).begin(), faces(tm1).end(), tm1, vpm1); tm2_tree.insert(tm2_only.begin(), tm2_only.end(), tm2, vpm2); tm1_tree.build(); tm2_tree.build(); } // Compute the second one-sided distance. initial_lower_bound = static_cast(dista); // TODO: we should better test this optimization! double distb = CGAL::to_double(error_bound); if (!compare_meshes || (compare_meshes && tm2_only.size() > 0)) { distb = bounded_error_Hausdorff_impl( tm2, tm1, error_bound, vpm2, vpm1, infinity_value, initial_lower_bound, tm2_tree, tm1_tree); } // Return the maximum. return (CGAL::max)(dista, distb); } template typename Kernel::FT recursive_hausdorff_subdivision( const Point_3& v0, const Point_3& v1, const Point_3& v2, const TM2_tree& tm2_tree, const typename Kernel::FT& squared_error_bound) { // If all edge lengths of the triangle are below the error_bound, // return maximum of the distances of the three points to TM2 (via TM2_tree). const auto max_squared_edge_length = std::max( std::max( squared_distance( v0, v1 ), squared_distance( v0, v2 )), squared_distance( v1, v2 ) ); if ( max_squared_edge_length < squared_error_bound ) { return std::max( std::max( squared_distance( v0, tm2_tree.closest_point(v0) ), squared_distance( v1, tm2_tree.closest_point(v1) ) ), squared_distance( v2, tm2_tree.closest_point(v2) ) ); } // Else subdivide the triangle and proceed recursively const Point_3 v01 = midpoint( v0, v1 ); const Point_3 v02 = midpoint( v0, v2 ); const Point_3 v12 = midpoint( v1, v2 ); return std::max ( std::max( recursive_hausdorff_subdivision( v0,v01,v02,tm2_tree,squared_error_bound ), recursive_hausdorff_subdivision( v1,v01,v12,tm2_tree,squared_error_bound ) ), std::max( recursive_hausdorff_subdivision( v2,v02,v12,tm2_tree,squared_error_bound ), recursive_hausdorff_subdivision( v01,v02,v12,tm2_tree,squared_error_bound ) ) ); } template double bounded_error_Hausdorff_naive_impl( const TriangleMesh1& tm1, const TriangleMesh2& tm2, const typename Kernel::FT& error_bound, VPM1 vpm1, VPM2 vpm2) { CGAL_assertion_code( const bool is_triangle = is_triangle_mesh(tm1) && is_triangle_mesh(tm2) ); CGAL_assertion_msg (is_triangle, "One of the meshes is not triangulated. Distance computing impossible."); typedef AABB_face_graph_triangle_primitive TM2_primitive; typedef AABB_tree< AABB_traits > TM2_tree; typedef typename boost::graph_traits::face_descriptor face_descriptor_1; typedef typename Kernel::FT FT; typedef typename Kernel::Point_3 Point_3; typedef typename Kernel::Triangle_3 Triangle_3; // Initially, no lower bound is known FT squared_lower_bound = FT(0); // Work with squares in the following, only draw sqrt at the very end const FT squared_error_bound = error_bound * error_bound; // Build an AABB tree on tm2 TM2_tree tm2_tree( faces(tm2).begin(), faces(tm2).end(), tm2, vpm2 ); tm2_tree.build(); tm2_tree.accelerate_distance_queries(); // Build a map to obtain actual triangles from the face descriptors of tm1. const Triangle_from_face_descriptor_map face_to_triangle_map( &tm1, vpm1 ); // Iterate over the triangles of TM1. for(face_descriptor_1 fd : faces(tm1)) { // Get the vertices of the face and pass them on to a recursive method. const Triangle_3 triangle = get(face_to_triangle_map, fd); const Point_3 v0 = triangle.vertex(0); const Point_3 v1 = triangle.vertex(1); const Point_3 v2 = triangle.vertex(2); // Recursively process the current triangle to obtain a lower bound on // its Hausdorff distance. const FT triangle_bound = recursive_hausdorff_subdivision( v0, v1, v2, tm2_tree, squared_error_bound ); // Store the largest lower bound. if( triangle_bound > squared_lower_bound ) { squared_lower_bound = triangle_bound; } } // Return linear interpolation between found upper and lower bound return CGAL::sqrt(CGAL::to_double( squared_lower_bound )); } } // end of namespace internal /** * \ingroup PMP_distance_grp * returns an estimate on the Hausdorff distance between `tm1` and `tm2` that * is at most `error_bound` away from the actual Hausdorff distance between * the two given meshes. * * @tparam Concurrency_tag enables sequential versus parallel algorithm. * Possible values are `Sequential_tag` and `Parallel_tag`. * Currently, parallel computation is not implemented, though. * * @tparam TriangleMesh1 a model of the concept `FaceListGraph` * @tparam TriangleMesh2 a model of the concept `FaceListGraph` * * @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * * @param tm1 a triangle mesh * @param tm2 another triangle mesh * * @param error_bound a maximum bound by which the Hausdorff distance estimate is * allowed to deviate from the actual Hausdorff distance. * * @param np1 an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * @param np2 an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below * * \cgalNamedParamsBegin * \cgalParamNBegin{vertex_point_map} * \cgalParamDescription{a property map associating points to the vertices of `tm1` and `tm2` (`np1` and `np2`, respectively)} * \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%vertex_descriptor` * as key type and `%Point_3` as value type} * \cgalParamDefault{`boost::get(CGAL::vertex_point, tm)`} * \cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t` * must be available in `TriangleMeshX`.} * \cgalParamNEnd * \cgalParamNBegin{match_faces} * \cgalParamDescription{a boolean tag that turns on the preprocessing step that filters out all faces, * which belong to both meshes and hence do not contribute to the final distance} * \cgalParamType{Boolean} * \cgalParamDefault{true} * \cgalParamExtra{Both `np1` and `np2` must have this tag true in order to activate this preprocessing.} * \cgalParamNEnd * \cgalNamedParamsEnd * * @return the one-sided Hausdorff distance */ template< class Concurrency_tag, class TriangleMesh1, class TriangleMesh2, class NamedParameters1, class NamedParameters2 > double bounded_error_Hausdorff_distance( const TriangleMesh1& tm1, const TriangleMesh2& tm2, const double error_bound, const NamedParameters1& np1, const NamedParameters2& np2) { CGAL_assertion_code( const bool is_triangle = is_triangle_mesh(tm1) && is_triangle_mesh(tm2)); CGAL_assertion_msg(is_triangle, "Both meshes must be triangulated to compute this distance!"); using Traits = typename GetGeomTraits::type; using FT = typename Traits::FT; const auto vpm1 = parameters::choose_parameter( parameters::get_parameter(np1, internal_np::vertex_point), get_const_property_map(vertex_point, tm1)); const auto vpm2 = parameters::choose_parameter( parameters::get_parameter(np2, internal_np::vertex_point), get_const_property_map(vertex_point, tm2)); const bool match_faces1 = parameters::choose_parameter( parameters::get_parameter(np1, internal_np::match_faces), true); const bool match_faces2 = parameters::choose_parameter( parameters::get_parameter(np2, internal_np::match_faces), true); const bool match_faces = match_faces1 && match_faces2; CGAL_precondition(error_bound >= 0.0); const FT error_threshold = static_cast(error_bound); return internal::bounded_error_one_sided_Hausdorff_impl( tm1, tm2, error_threshold, match_faces, vpm1, vpm2); } template< class Concurrency_tag, class TriangleMesh1, class TriangleMesh2, class NamedParameters1 > double bounded_error_Hausdorff_distance( const TriangleMesh1& tm1, const TriangleMesh2& tm2, const double error_bound, const NamedParameters1& np1) { return bounded_error_Hausdorff_distance( tm1, tm2, error_bound, np1, parameters::all_default()); } template< class Concurrency_tag, class TriangleMesh1, class TriangleMesh2 > double bounded_error_Hausdorff_distance( const TriangleMesh1& tm1, const TriangleMesh2& tm2, const double error_bound) { return bounded_error_Hausdorff_distance( tm1, tm2, error_bound, parameters::all_default()); } /** * \ingroup PMP_distance_grp * returns the maximum of `bounded_error_Hausdorff_distance(tm1, tm2, error_bound, np1, np2)` * and `bounded_error_Hausdorff_distance(tm2, tm1, error_bound, np2, np1)`. * * This function optimizes all internal calls to shared data structures in order to * speed the computation. * * @return the symmetric Hausdorff distance */ template< class Concurrency_tag, class TriangleMesh1, class TriangleMesh2, class NamedParameters1, class NamedParameters2 > double bounded_error_symmetric_Hausdorff_distance( const TriangleMesh1& tm1, const TriangleMesh2& tm2, const double error_bound, const NamedParameters1& np1, const NamedParameters2& np2) { CGAL_assertion_code( const bool is_triangle = is_triangle_mesh(tm1) && is_triangle_mesh(tm2)); CGAL_assertion_msg(is_triangle, "Both meshes must be triangulated to compute this distance!"); using Traits = typename GetGeomTraits::type; using FT = typename Traits::FT; const auto vpm1 = parameters::choose_parameter( parameters::get_parameter(np1, internal_np::vertex_point), get_const_property_map(vertex_point, tm1)); const auto vpm2 = parameters::choose_parameter( parameters::get_parameter(np2, internal_np::vertex_point), get_const_property_map(vertex_point, tm2)); const bool match_faces1 = parameters::choose_parameter( parameters::get_parameter(np1, internal_np::match_faces), true); const bool match_faces2 = parameters::choose_parameter( parameters::get_parameter(np2, internal_np::match_faces), true); const bool match_faces = match_faces1 && match_faces2; CGAL_precondition(error_bound >= 0.0); const FT error_threshold = static_cast(error_bound); return internal::bounded_error_symmetric_Hausdorff_impl( tm1, tm2, error_threshold, match_faces, vpm1, vpm2); } template< class Concurrency_tag, class TriangleMesh1, class TriangleMesh2, class NamedParameters1 > double bounded_error_symmetric_Hausdorff_distance( const TriangleMesh1& tm1, const TriangleMesh2& tm2, const double error_bound, const NamedParameters1& np1) { return bounded_error_symmetric_Hausdorff_distance( tm1, tm2, error_bound, np1, parameters::all_default()); } template< class Concurrency_tag, class TriangleMesh1, class TriangleMesh2> double bounded_error_symmetric_Hausdorff_distance( const TriangleMesh1& tm1, const TriangleMesh2& tm2, const double error_bound) { return bounded_error_symmetric_Hausdorff_distance( tm1, tm2, error_bound, parameters::all_default()); } /* * Implementation of naive Bounded Hausdorff distance computation. */ template< class Concurrency_tag, class TriangleMesh1, class TriangleMesh2, class NamedParameters1, class NamedParameters2> double bounded_error_Hausdorff_distance_naive( const TriangleMesh1& tm1, const TriangleMesh2& tm2, double error_bound, const NamedParameters1& np1, const NamedParameters2& np2) { typedef typename GetGeomTraits::type Geom_traits; using FT = typename Geom_traits::FT; typedef typename GetVertexPointMap::const_type Vpm1; typedef typename GetVertexPointMap::const_type Vpm2; using parameters::choose_parameter; using parameters::get_parameter; Vpm1 vpm1 = choose_parameter(get_parameter(np1, internal_np::vertex_point), get_const_property_map(vertex_point, tm1)); Vpm2 vpm2 = choose_parameter(get_parameter(np2, internal_np::vertex_point), get_const_property_map(vertex_point, tm2)); CGAL_precondition(error_bound >= 0.0); const FT error_threshold = static_cast(error_bound); return internal::bounded_error_Hausdorff_naive_impl( tm1, tm2, error_threshold, vpm1, vpm2); } template< class Concurrency_tag, class TriangleMesh1, class TriangleMesh2, class NamedParameters1> double bounded_error_Hausdorff_distance_naive( const TriangleMesh1& tm1, const TriangleMesh2& tm2, double error_bound, const NamedParameters1& np1) { return bounded_error_Hausdorff_distance_naive(tm1, tm2, error_bound, np1, parameters::all_default()); } template< class Concurrency_tag, class TriangleMesh1, class TriangleMesh2> double bounded_error_Hausdorff_distance_naive( const TriangleMesh1& tm1, const TriangleMesh2& tm2, double error_bound) { return bounded_error_Hausdorff_distance_naive(tm1, tm2, error_bound, parameters::all_default() ); } } } // end of namespace CGAL::Polygon_mesh_processing #endif //CGAL_POLYGON_MESH_PROCESSING_DISTANCE_H