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; +}