// 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 #ifdef CGAL_TETRAHEDRAL_REMESHING_DEBUG #include #include #include #endif 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_3(points_with_info(tr, 3, ecmap, fcmap, cell_selector), Splitter(), Kd_traits(Point_property_map())) , m_kd_tree_2(points_with_info(tr, 2, ecmap, fcmap, cell_selector), Splitter(), Kd_traits(Point_property_map())) { m_kd_tree_3.build(); m_kd_tree_2.build(); #ifdef CGAL_TETRAHEDRAL_REMESHING_DEBUG dump_kd_trees(); #endif } #endif private: template std::vector points_with_info(const Tr& tr, const int dim, const ECMap& ecmap, const FCMap& fcmap, const CellSelector& cell_selector) const { namespace Tet_remeshing = CGAL::Tetrahedral_remeshing; using Facet = typename Tr::Facet; using Edge = typename Tr::Edge; auto cp = tr.geom_traits().construct_point_3_object(); auto centroid = tr.geom_traits().construct_centroid_3_object(); auto midpt = tr.geom_traits().construct_midpoint_3_object(); std::vector points; for (const Vertex_handle v : tr.finite_vertex_handles()) { if( (dim == 3 && dim == v->in_dimension())//inside volume || (dim < 3 && v->in_dimension() < 3)) //on surface { const FT size = average_edge_length_around(v, tr, ecmap, fcmap, cell_selector); if (CGAL::is_zero(size)) continue; points.push_back(Point_with_info{ cp(tr.point(v)), size, v->in_dimension() }); } } // add internal points for (const Cell_handle c : tr.finite_cell_handles()) { if (!get(cell_selector, c)) continue; // inside cells if(dim == 3) { const FT size = average_edge_length_3(c, tr); if (CGAL::is_zero(size)) continue; points.push_back(Point_with_info{ centroid(tr.tetrahedron(c)), size, 3 }); } else { // on surface facets for (int i = 0; i < 4; ++i) { const Cell_handle cn = c->neighbor(i); if( get(cell_selector, c) != get(cell_selector, cn) || get(fcmap, Facet(c, i)) || c->is_facet_on_surface(i) ) { const FT size = average_edge_length_2(c, i, tr); if (CGAL::is_zero(size)) continue; points.push_back(Point_with_info{ centroid(tr.triangle(c, i)), size, 2 }); } } // on complex edges for (const Edge& e : Tet_remeshing::cell_edges(c, tr)) { if(get(ecmap, Tet_remeshing::make_vertex_pair(e))) { const FT size = Tet_remeshing::approximate_edge_length(e, tr); if (CGAL::is_zero(size)) continue; points.push_back(Point_with_info{midpt(tr.segment(e)), size, 1 }); } } } } return points; } const Kd_tree& kd_tree(const int dim) const { if (dim == 3) return m_kd_tree_3; else return m_kd_tree_2; } int nb_neighbors(const int dim) const { if (dim == 3) return 30; else return 6; } 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 { Point_property_map pp_map; Distance dist(pp_map); // Find nearest vertex and local size before remeshing Neighbor_search search(kd_tree(dim), p, //query point nb_neighbors(dim), //nb nearest neighbors 0, //epsilon true, //search nearest dist); FT max_size = 0.; std::vector neighbors; for (const auto& neighbor : search) { [[maybe_unused]] const auto& [pi, size, dimension] = neighbor.first; max_size = (std::max)(max_size, size); if( (dim == 3 && dimension == dim) //volume point || (dim < 3 && dimension < 3) ) //surface point { neighbors.push_back(neighbor.first); } } if (neighbors.empty()) return max_size; return interpolate_on_n_vertices(p, neighbors); } private: /** * Returns size at point `p`, by interpolation among neighbors */ FT interpolate_on_n_vertices( const Point_3& p, const std::vector& 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; FT average_edge_length_3(const Cell_handle c, const Tr& tr) const; FT average_edge_length_2(const Cell_handle c, const int i, const Tr& tr) const; private: Kd_tree m_kd_tree_3; //volumes Kd_tree m_kd_tree_2; //surfaces #ifdef CGAL_TETRAHEDRAL_REMESHING_DEBUG private: void dump_kd_trees() const { std::ofstream ofs("kd_trees.ply"); using Point_Size = std::pair; std::vector points; for (auto it = m_kd_tree_3.begin(); it != m_kd_tree_3.end(); ++it) points.push_back(std::make_pair(it->p, it->size)); for (auto it = m_kd_tree_2.begin(); it != m_kd_tree_2.end(); ++it) points.push_back(std::make_pair(it->p, it->size)); using Point_map = CGAL::First_of_pair_property_map; using Size_map = CGAL::Second_of_pair_property_map; CGAL::IO::write_PLY_with_properties(ofs, points, CGAL::make_ply_point_writer(Point_map()), std::make_pair(Size_map(), CGAL::IO::PLY_property("intensity"))); } #endif };//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_n_vertices( const Point_3& p, const std::vector& points_with_info) const { // Interpolate value using values at vertices const auto sqd = GT().compute_squared_distance_3_object(); FT sum_weights = 0.; FT sum_sizes = 0.; for (const auto pwi : points_with_info) { const FT size = pwi.size; const FT sqdist = sqd(pwi.p, p); if (is_zero(sqdist)) return size; const FT weight = 1. / CGAL::approximate_sqrt(sqdist); sum_weights += weight; sum_sizes += weight * size; } CGAL_assertion(sum_weights > 0); CGAL_assertion(sum_sizes > 0); return sum_sizes / sum_weights; } template typename Adaptive_remeshing_sizing_field::FT Adaptive_remeshing_sizing_field:: average_edge_length_3(const typename Tr::Cell_handle c, const Tr& tr) const { using namespace CGAL::Tetrahedral_remeshing; FT sum = 0.; for (const typename Tr::Edge& e : cell_edges(c, tr)) { sum += approximate_edge_length(e, tr); } return sum / FT(6); } template typename Adaptive_remeshing_sizing_field::FT Adaptive_remeshing_sizing_field:: average_edge_length_2(const typename Tr::Cell_handle c, const int i, const Tr& tr) const { // facet(c,i) namespace Tet_Remeshing = CGAL::Tetrahedral_remeshing; FT sum = 0.; short nb_surface_edges = 0; for (const typename Tr::Edge& e : Tet_Remeshing::facet_edges(c, i, tr)) { CGAL_assertion_code(const auto vs = tr.vertices(e)); CGAL_assertion(vs[0]->in_dimension() < 3); CGAL_assertion(vs[1]->in_dimension() < 3); sum += Tet_Remeshing::approximate_edge_length(e, tr); ++nb_surface_edges; } return sum / FT(nb_surface_edges); } 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; } if (edges.empty()) return 0; FT sum = 0.; for (const Edge& e : edges) { sum += approximate_edge_length(e, tr); } return sum / static_cast(edges.size()); } } //namespace CGAL #endif // CGAL_TETRAHEDRAL_REMESHING_ADAPTIVE_SIZING_FIELD_H