diff --git a/Surface_mesh_simplification/doc/Surface_mesh_simplification/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/Bounded_distance_placement.h b/Surface_mesh_simplification/doc/Surface_mesh_simplification/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/Bounded_distance_placement.h
new file mode 100644
index 00000000000..5fa7e3c3395
--- /dev/null
+++ b/Surface_mesh_simplification/doc/Surface_mesh_simplification/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/Bounded_distance_placement.h
@@ -0,0 +1,55 @@
+
+namespace CGAL {
+namespace Surface_mesh_simplification {
+
+/*
+\ingroup PkgSurfaceMeshSimplificationRef
+
+The class `Bounded_distance_placement` is a model for the concept `GetPlacement`.
+
+This placement class is a wrapper around another (so-called base) placement class.
+The position of a vertex resulting from the contraction of an edge is obtained by first querying
+the base placement class, and checking whether this tentative position is not too far
+(according to a user-provided distance bound) from the input mesh.
+If it is too far, the position is rejected and no position is returned; otherwise,
+the position is returned.
+
+\tparam BasePlacement must be a model of `GetPlacement`.
+\tparam GeomTraits must be a model of `Kernel` and be identical to the traits specified
+ in the named parameters of the function `edge_collapse()` (if specified).
+
+The distance check is performed using an AABB tree and this class thus depends on the package \ref PkgAABBTree.
+
+\cgalModels `GetPlacement`
+
+*/
+template
+class Bounded_distance_placement
+ : public BasePlacement
+{
+public:
+ //
+ typedef typename GeomTraits::FT FT;
+
+ // \name Creation
+ //
+ // @{
+
+ // The distance bound `d` is used to control that during simplification,
+ // no vertex has a distance to the input that would be greater than `d`.
+ Bounded_distance_placement(const FT d, const BasePlacement& base_placement = BasePlacement());
+
+ // @}
+
+ // \name Operations
+ // @{
+
+ // Returns the placement computed by `base_placement`, provided the distance between the input
+ // and this placement is smaller than `d`. Otherwise, nothing is returned.
+ boost::optional operator()(const Edge_profile& profile) const;
+
+ // @}
+};
+
+} // namespace Surface_Mesh_Simplification
+} // namespace CGAL
diff --git a/Surface_mesh_simplification/doc/Surface_mesh_simplification/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/GarlandHeckbert_policies.h b/Surface_mesh_simplification/doc/Surface_mesh_simplification/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/GarlandHeckbert_policies.h
index 07fc7ed0a93..5b4de9dcaf5 100644
--- a/Surface_mesh_simplification/doc/Surface_mesh_simplification/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/GarlandHeckbert_policies.h
+++ b/Surface_mesh_simplification/doc/Surface_mesh_simplification/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/GarlandHeckbert_policies.h
@@ -12,13 +12,13 @@ associating quadrics to vertices.
Note however, that they may still be wrapped with slight behavior modifying classes
such as `Constrained_placement` or `Bounded_normal_change_placement`.
-Note that these policies depend on the third party \ref thirdpartyEigen library.
-
\tparam TriangleMesh is the type of surface mesh being simplified, and must be a model
of the `MutableFaceGraph` and `HalfedgeListGraph` concepts.
\tparam GeomTraits must be a model of `Kernel`. If you have passed a traits class in the optional
named parameters in the call to `edge_collapse()`, the types must be identical.
+These policies depend on the third party \ref thirdpartyEigen library.
+
*/
template
class GarlandHeckbert_policies
diff --git a/Surface_mesh_simplification/doc/Surface_mesh_simplification/Concepts/GetPlacement.h b/Surface_mesh_simplification/doc/Surface_mesh_simplification/Concepts/GetPlacement.h
index f7f0b927f6d..da415685d46 100644
--- a/Surface_mesh_simplification/doc/Surface_mesh_simplification/Concepts/GetPlacement.h
+++ b/Surface_mesh_simplification/doc/Surface_mesh_simplification/Concepts/GetPlacement.h
@@ -20,8 +20,9 @@ or can be intentionally returned to prevent the edge from being collapsed.
\cgalHasModel `CGAL::Surface_mesh_simplification::GarlandHeckbert_policies`
\cgalHasModel `CGAL::Surface_mesh_simplification::Bounded_normal_change_placement`
\cgalHasModel `CGAL::Surface_mesh_simplification::Constrained_placement`
-
*/
+
+
class GetPlacement
{
public:
diff --git a/Surface_mesh_simplification/include/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/Bounded_distance_placement.h b/Surface_mesh_simplification/include/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/Bounded_distance_placement.h
new file mode 100644
index 00000000000..dad4317af92
--- /dev/null
+++ b/Surface_mesh_simplification/include/CGAL/Surface_mesh_simplification/Policies/Edge_collapse/Bounded_distance_placement.h
@@ -0,0 +1,192 @@
+// Copyright (c) 2019 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,
+// Mael Rouxel-Labbé
+//
+#ifndef CGAL_SURFACE_MESH_SIMPLIFICATION_POLICIES_EDGE_COLLAPSE_BOUNDED_DISTANCE_PLACEMENT_H
+#define CGAL_SURFACE_MESH_SIMPLIFICATION_POLICIES_EDGE_COLLAPSE_BOUNDED_DISTANCE_PLACEMENT_H
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+
+namespace CGAL {
+namespace Surface_mesh_simplification {
+
+// An AABB tree can also be passed to the placement instead, see undocumented specialization below
+template
+class Bounded_distance_placement
+{
+ typedef GeomTraits Geom_traits;
+ typedef typename Geom_traits::FT FT;
+
+ typedef typename GeomTraits::Triangle_3 Triangle;
+ typedef std::vector Triangle_container;
+ typedef typename Triangle_container::iterator TC_iterator;
+
+ typedef CGAL::AABB_triangle_primitive Primitive;
+ typedef CGAL::AABB_traits Traits;
+ typedef CGAL::AABB_tree AABB_tree;
+
+private:
+ template
+ void initialize_tree(const Profile& profile) const
+ {
+ CGAL_static_assertion((std::is_same::value));
+
+ typedef typename Profile::Triangle_mesh Triangle_mesh;
+ typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor;
+ typedef typename boost::graph_traits::face_descriptor face_descriptor;
+
+ CGAL_precondition(m_tree_ptr == nullptr);
+
+ const Triangle_mesh& tm = profile.surface_mesh();
+ const Geom_traits& gt = profile.geom_traits();
+ std::vector input_triangles;
+
+ for(face_descriptor f : faces(profile.surface_mesh()))
+ {
+ halfedge_descriptor h = halfedge(f, tm);
+ CGAL_assertion(!is_border(h, tm));
+
+ input_triangles.push_back(gt.construct_triangle_3_object()(
+ get(profile.vertex_point_map(), source(h, tm)),
+ get(profile.vertex_point_map(), target(h, tm)),
+ get(profile.vertex_point_map(), target(next(h, tm), tm))));
+ }
+
+ m_tree_ptr = new AABB_tree(input_triangles.begin(), input_triangles.end());
+ const_cast(m_tree_ptr)->build();
+ }
+
+public:
+ Bounded_distance_placement(const FT dist,
+ const BasePlacement& placement = BasePlacement())
+ :
+ m_sq_threshold_dist(CGAL::square(dist)),
+ m_tree_ptr(nullptr),
+ m_base_placement(placement)
+ { }
+
+ ~Bounded_distance_placement()
+ {
+ if(m_tree_ptr != nullptr)
+ delete m_tree_ptr;
+ }
+
+ template
+ boost::optional
+ operator()(const Profile& profile) const
+ {
+ typedef typename Profile::Point Point;
+
+ boost::optional op = m_base_placement(profile);
+ if(op)
+ {
+ if(m_tree_ptr == nullptr)
+ initialize_tree(profile);
+
+ CGAL_assertion(m_tree_ptr != nullptr);
+ CGAL_assertion(!m_tree_ptr->empty());
+
+ const Point& p = *op;
+
+ m_tree_ptr->accelerate_distance_queries();
+ const Point& cp = m_tree_ptr->best_hint(p).first; // requires accelerate distance query to be called.
+
+ // We could do better by having access to the internal kd-tree
+ // and call search_any_point with a fuzzy_sphere.
+
+ // if no input vertex is closer than the threshold, then
+ // any face closer than the threshold is intersected by
+ // the sphere (avoid the inclusion of the mesh into the threshold sphere)
+ if(CGAL::compare_squared_distance(p, cp, m_sq_threshold_dist) != LARGER ||
+ m_tree_ptr->do_intersect(CGAL::Sphere_3(p, m_sq_threshold_dist)))
+ return op;
+
+ return boost::optional();
+ }
+
+ return op;
+ }
+
+private:
+ const FT m_sq_threshold_dist;
+ mutable const AABB_tree* m_tree_ptr;
+
+ const BasePlacement m_base_placement;
+};
+
+// Undocumented specizalization where an _already built_ AABB tree is passed
+template
+class Bounded_distance_placement >
+{
+ typedef CGAL::AABB_tree AABB_tree;
+ typedef typename AABB_tree::AABB_traits::FT FT;
+
+public:
+ Bounded_distance_placement(const FT dist,
+ const AABB_tree& tree,
+ const BasePlacement& placement = BasePlacement())
+ :
+ m_sq_threshold_dist(CGAL::square(dist)),
+ m_tree_ptr(&tree),
+ m_base_placement(placement)
+ { }
+
+ template
+ boost::optional
+ operator()(const Profile& profile) const
+ {
+ typedef typename Profile::Geom_traits Geom_traits;
+ typedef typename Profile::Point Point;
+
+ boost::optional op = m_base_placement(profile);
+ if(op)
+ {
+ CGAL_assertion(m_tree_ptr != nullptr);
+ CGAL_assertion(!m_tree_ptr->empty());
+
+ const Point& p = *op;
+
+ m_tree_ptr->accelerate_distance_queries();
+ const Point& cp = m_tree_ptr->best_hint(p).first; // requires accelerate distance query to be called.
+
+ if(CGAL::compare_squared_distance(p, cp, m_sq_threshold_dist) != LARGER ||
+ m_tree_ptr->do_intersect(CGAL::Sphere_3(p, m_sq_threshold_dist)))
+ return op;
+
+ return boost::optional();
+ }
+
+ return op;
+ }
+
+private:
+ const FT m_sq_threshold_dist;
+ mutable const AABB_tree* m_tree_ptr;
+
+ const BasePlacement m_base_placement;
+};
+
+} // namespace Surface_mesh_simplification
+} // namespace CGAL
+
+#endif // CGAL_SURFACE_MESH_SIMPLIFICATION_POLICIES_EDGE_COLLAPSE_BOUNDED_DISTANCE_PLACEMENT_H
diff --git a/Surface_mesh_simplification/package_info/Surface_mesh_simplification/dependencies b/Surface_mesh_simplification/package_info/Surface_mesh_simplification/dependencies
index f8f2b1e180e..27dd31f34e6 100644
--- a/Surface_mesh_simplification/package_info/Surface_mesh_simplification/dependencies
+++ b/Surface_mesh_simplification/package_info/Surface_mesh_simplification/dependencies
@@ -1,9 +1,13 @@
+AABB_tree
Algebraic_foundations
BGL
+Cartesian_kernel
Circulator
Distance_2
Distance_3
Installation
+Intersections_2
+Intersections_3
Interval_support
Kernel_23
Modular_arithmetic
@@ -12,5 +16,6 @@ Profiling_tools
Property_map
Random_numbers
STL_Extension
+Spatial_searching
Stream_support
Surface_mesh_simplification
diff --git a/Surface_mesh_simplification/test/Surface_mesh_simplification/test_edge_collapse_bounded_distance.cpp b/Surface_mesh_simplification/test/Surface_mesh_simplification/test_edge_collapse_bounded_distance.cpp
new file mode 100644
index 00000000000..8aaa51a4651
--- /dev/null
+++ b/Surface_mesh_simplification/test/Surface_mesh_simplification/test_edge_collapse_bounded_distance.cpp
@@ -0,0 +1,76 @@
+#include
+#include
+
+// Simplification function
+#include
+#include
+#include
+#include
+#include
+
+//AABB_tree
+#include
+#include
+#include
+
+//bbox
+#include
+
+#include
+#include
+
+namespace SMS = CGAL::Surface_mesh_simplification;
+
+typedef CGAL::Simple_cartesian Kernel;
+
+typedef Kernel::Point_3 Point_3;
+typedef CGAL::Surface_mesh Surface;
+
+typedef SMS::LindstromTurk_cost Cost;
+typedef SMS::LindstromTurk_placement Placement;
+
+typedef CGAL::AABB_face_graph_triangle_primitive Primitive;
+typedef CGAL::AABB_traits Traits;
+typedef CGAL::AABB_tree Tree;
+
+typedef SMS::Bounded_distance_placement Filtered_placement;
+typedef SMS::Bounded_distance_placement Filtered_placement_with_tree;
+
+int main(int argc, char** argv)
+{
+ Surface ref_mesh;
+ std::ifstream is(argc > 1 ? argv[1] : "data/helmet.off");
+ is >> ref_mesh;
+
+ SMS::Count_stop_predicate stop(num_halfedges(ref_mesh)/10);
+
+ std::cout << "input has " << num_vertices(ref_mesh) << " vertices." << std::endl;
+ CGAL::Iso_cuboid_3 bbox(CGAL::Polygon_mesh_processing::bbox(ref_mesh));
+
+ Point_3 cmin = (bbox.min)();
+ Point_3 cmax = (bbox.max)();
+ const double diag = CGAL::approximate_sqrt(CGAL::squared_distance(cmin, cmax));
+
+ Surface mesh_cpy = ref_mesh; // need a copy to keep the AABB tree valid
+ Surface small_mesh = ref_mesh;
+ Surface big_mesh = ref_mesh;
+ Tree tree(faces(mesh_cpy).first, faces(mesh_cpy).second, mesh_cpy);
+
+ Placement placement_ref;
+ SMS::edge_collapse(ref_mesh, stop, CGAL::parameters::get_cost(Cost()).get_placement(placement_ref));
+
+ Filtered_placement_with_tree placement_small(0.00005*diag, tree, placement_ref);
+ SMS::edge_collapse(small_mesh, stop, CGAL::parameters::get_cost(Cost()).get_placement(placement_small));
+
+ Filtered_placement placement_big(50*diag, placement_ref); // lazily builds the AABB tree
+ SMS::edge_collapse(big_mesh, stop, CGAL::parameters::get_cost(Cost()).get_placement(placement_big));
+
+ std::cout << "no filtering: " << vertices(ref_mesh).size() << " vertices left" << std::endl;
+ std::cout << "large filtering distance: " << vertices(big_mesh).size() << " vertices left" << std::endl;
+ std::cout << "small filtering distance: " << vertices(small_mesh).size() << " vertices left" << std::endl;
+
+ assert(vertices(small_mesh).size() != vertices(ref_mesh).size());
+ assert(vertices(big_mesh).size() == vertices(ref_mesh).size());
+
+ return EXIT_SUCCESS;
+}