// Copyright (c) 2023 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) : Jane Tournois // //****************************************************************************** // File Description : Defines a sizing field adapted to a triangulation //****************************************************************************** #ifndef CGAL_TETRAHEDRAL_REMESHING_ADAPTIVE_SIZING_FIELD_H #define CGAL_TETRAHEDRAL_REMESHING_ADAPTIVE_SIZING_FIELD_H #include #include #include #include #include #include #include #include #include #include #include namespace CGAL { /** * @ingroup PkgTetrahedralRemeshingSizing * * @class Adaptive_remeshing_sizing_field * @tparam Tr underlying triangulation type * * A sizing field for tetrahedral remeshing, * that keeps the same mesh density throughout the remeshing process. * * \cgalModels{RemeshingSizingField_3} */ template class Adaptive_remeshing_sizing_field : public Tetrahedral_remeshing_sizing_field { // Types public: typedef typename Tr::Geom_traits GT; typedef typename GT::FT FT; typedef typename Tr::Geom_traits::Point_3 Point_3; //Bare_point typedef typename Tr::Vertex::Index Index; private: typedef typename Tr::Point Tr_point; typedef typename Tr::Vertex_handle Vertex_handle; typedef typename Tr::Cell_handle Cell_handle; struct Point_with_info { Point_3 p; FT size; int dimension; }; private: struct Point_property_map { using Self = Point_property_map; using value_type = Point_3; using reference = value_type; //TODO : why can't that be value_type& ? using key_type = Point_with_info; using category = boost::readable_property_map_tag; const value_type operator[](const key_type& pwi) const { return pwi.p; } friend const value_type get(const Self&, const key_type& pwi) { return pwi.p; } }; private: using Kd_traits = CGAL::Search_traits_adapter >; using Neighbor_search = CGAL::Orthogonal_k_neighbor_search; using Kd_tree = typename Neighbor_search::Tree; using Distance = typename Neighbor_search::Distance; using Splitter = typename Neighbor_search::Splitter; #ifndef DOXYGEN_RUNNING public: template Adaptive_remeshing_sizing_field(const Tr& tr, const ECMap& ecmap, const FCMap& fcmap, const CellSelector& cell_selector) : m_kd_tree(points_with_info(tr, ecmap, fcmap, cell_selector), Splitter(), Kd_traits(Point_property_map())) { m_kd_tree.build(); } #endif private: template std::vector points_with_info(const Tr& tr, const ECMap& ecmap, const FCMap& fcmap, const CellSelector& cell_selector) const { auto cp = tr.geom_traits().construct_point_3_object(); std::vector points; for (const Vertex_handle v : tr.finite_vertex_handles()) { points.push_back( Point_with_info{ cp(tr.point(v)), average_edge_length_around(v, tr, ecmap, fcmap, cell_selector), v->in_dimension() }); } return points; } public: /** * Returns size at point `p`, assumed to be included in the input * subcomplex with dimension `dim` and index `index`. */ #ifdef DOXYGEN_RUNNING FT operator()(const Point_3& p, const int& dim, const Index& index) const #else FT operator()(const Point_3& p, const int& dim, const Index& ) const #endif { const int nb_neighbors = (dim == 3) ? 20 : 6; // Find nearest vertex and local size before remeshing Point_property_map pp_map; Distance dist(pp_map); Neighbor_search search(m_kd_tree, p, //query point nb_neighbors, //nb nearest neighbors 0, //epsilon true, //search nearest dist); FT sum = 0; for (const auto& neighbor : search) { [[maybe_unused]] const auto& [pi, size, dimension] = neighbor.first; // todo : should we discard points when dim != dimension? sum += size; } CGAL_assertion(sum > 0); return sum / static_cast(nb_neighbors); } private: /** * Returns size at point `p`, by interpolation into tetrahedron. * TODO : interpolate */ FT interpolate_on_four_vertices( const Point_3& p, const std::array& vertices) const; template FT average_edge_length_around(const Vertex_handle v, const Tr& tr, const ECMap& ecmap, const FCMap& fcmap, const CellSelector& cell_selector) const; private: Kd_tree m_kd_tree; };//end of class Adaptive_remeshing_sizing_field /*! * @ingroup PkgTetrahedralRemeshingSizing * * @brief Create an adaptive sizing field for tetrahedral remeshing * * \relates Adaptive_remeshing_sizing_field * * This method is a named constructor. It constructs a * sizing field of type `Adaptive_remeshing_sizing_field`, * designed to keep the density unchanged throughout the remeshing * process. * * @returns an `Adaptive_remeshing_sizing_field` * * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" * \param tr the input triangulation * \param np an optional sequence of \ref bgl_namedparameters "Named Parameters" * among the ones listed below. All of them must be the same as the ones * given to `CGAL::tetrahedral_isotropic_remeshing()`. * *\cgalNamedParamsBegin * \cgalParamNBegin{ edge_is_constrained_map } * \cgalParamDescription{a property map containing the constrained-or-not * status of each edge of `tr`.} * \cgalParamDefault{a default property map where no edge is constrained} * \cgalParamNEnd * \cgalParamNBegin{ facet_is_constrained_map } * \cgalParamDescription{a property map containing the constrained-or-not * status of each facet of `tr`.} * \cgalParamDefault{a default property map where no facet is constrained} * \cgalParamNEnd * \cgalParamNBegin{ cell_is_selected_map } * \cgalParamDescription{a property map containing the selected * - or - not status for each cell of `tr` for remeshing.} * \cgalParamDefault{a default property map where all cells of the domain are selected} * \cgalParamNEnd *\cgalNamedParamsEnd */ template Adaptive_remeshing_sizing_field create_adaptive_remeshing_sizing_field(const Tr& tr, const CGAL_NP_CLASS& np = parameters::default_values()) { using parameters::choose_parameter; using parameters::get_parameter; using V = typename Tr::Vertex_handle; using Edge_vv = std::pair; using Facet = typename Tr::Facet; auto ecmap = choose_parameter(get_parameter(np, internal_np::edge_is_constrained), CGAL::Constant_property_map(false)); auto fcmap = choose_parameter(get_parameter(np, internal_np::facet_is_constrained), CGAL::Constant_property_map(false)); auto cell_selector = choose_parameter(get_parameter(np, internal_np::cell_selector), CGAL::Tetrahedral_remeshing::internal::All_cells_selected()); return Adaptive_remeshing_sizing_field(tr, ecmap, fcmap, cell_selector); } template typename Adaptive_remeshing_sizing_field::FT Adaptive_remeshing_sizing_field:: interpolate_on_four_vertices( const Point_3& p, const std::array& vertices) const { // Interpolate value using values at vertices const FT& va = vertices[0].size; const FT& vb = vertices[1].size; const FT& vc = vertices[2].size; const FT& vd = vertices[3].size; const Point_3& a = vertices[0].p; const Point_3& b = vertices[1].p; const Point_3& c = vertices[2].p; const Point_3& d = vertices[3].p; const auto sqd = FT().compute_squared_distance_3_object(); const FT wa = 1. / sqd(a, p); const FT wb = 1. / sqd(b, p); const FT wc = 1. / sqd(c, p); const FT wd = 1. / sqd(d, p); // If den is 0, then compute the average value if (is_zero(wa + wb + wc + wd)) return (va + vb + vc + vd) / 4.; else return (wa * va + wb * vb + wc * vc + wd * vd) / (wa + wb + wc + wd); } template template typename Adaptive_remeshing_sizing_field::FT Adaptive_remeshing_sizing_field:: average_edge_length_around(const Vertex_handle v, const Tr& tr, const ECMap& ecmap, const FCMap& fcmap, const CellSelector& cell_selector) const { using Edge = typename Tr::Edge; using Facet = typename Tr::Facet; using namespace CGAL::Tetrahedral_remeshing; std::vector tmp_edges; tr.incident_edges(v, std::back_inserter(tmp_edges)); std::vector edges; switch (v->in_dimension()) { case 3: std::copy_if(tmp_edges.begin(), tmp_edges.end(), std::back_inserter(edges), [&tr, &cell_selector](const Edge& e) { return is_selected(e, tr, cell_selector); }); break; case 2: std::copy_if(tmp_edges.begin(), tmp_edges.end(), std::back_inserter(edges), [&fcmap, &cell_selector, &tr](const Edge& e) { auto fcirc = tr.incident_facets(e); const auto fend = fcirc; do { const Facet& f = *fcirc; if ( get(fcmap, f) || get(cell_selector, f.first) != get(cell_selector, f.first->neighbor(f.second))) return true; } while (++fcirc != fend); return false; }); break; case 1: case 0: std::copy_if(tmp_edges.begin(), tmp_edges.end(), std::back_inserter(edges), [&ecmap](const Edge& e) { const auto evv = make_vertex_pair(e); return get(ecmap, evv); }); break; default: // could be -1 because of far points break; } CGAL_assertion(!edges.empty()); if (edges.empty()) return 0; auto cp = tr.geom_traits().construct_point_3_object(); FT sum = 0.; for (const Edge& e : edges) { sum += CGAL::approximate_sqrt( CGAL::squared_distance(cp(e.first->vertex(e.second)->point()), cp(e.first->vertex(e.third)->point())));; } return sum / static_cast(edges.size()); } } //namespace CGAL #endif // CGAL_TETRAHEDRAL_REMESHING_ADAPTIVE_SIZING_FIELD_H