mirror of https://github.com/CGAL/cgal
515 lines
16 KiB
C++
515 lines
16 KiB
C++
// 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 <CGAL/license/Tetrahedral_remeshing.h>
|
|
|
|
#include <CGAL/Tetrahedral_remeshing_sizing_field.h>
|
|
|
|
#include <CGAL/Search_traits_3.h>
|
|
#include <CGAL/Search_traits_adapter.h>
|
|
#include <CGAL/Orthogonal_k_neighbor_search.h>
|
|
|
|
#include <CGAL/boost/graph/named_params_helper.h>
|
|
|
|
#include <CGAL/property_map.h>
|
|
|
|
#include <CGAL/Tetrahedral_remeshing/internal/tetrahedral_remeshing_helpers.h>
|
|
#include <CGAL/Tetrahedral_remeshing/internal/property_maps.h>
|
|
|
|
#include <vector>
|
|
#include <array>
|
|
|
|
#ifdef CGAL_TETRAHEDRAL_REMESHING_DEBUG
|
|
#include <CGAL/property_map.h>
|
|
#include <CGAL/IO/write_ply_points.h>
|
|
#include <boost/property_map/property_map.hpp>
|
|
#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 <typename Tr>
|
|
class Adaptive_remeshing_sizing_field
|
|
: public Tetrahedral_remeshing_sizing_field<typename Tr::Geom_traits>
|
|
{
|
|
// 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<Point_with_info,
|
|
Point_property_map,
|
|
CGAL::Search_traits_3<GT> >;
|
|
using Neighbor_search = CGAL::Orthogonal_k_neighbor_search<Kd_traits>;
|
|
using Kd_tree = typename Neighbor_search::Tree;
|
|
using Distance = typename Neighbor_search::Distance;
|
|
using Splitter = typename Neighbor_search::Splitter;
|
|
|
|
#ifndef DOXYGEN_RUNNING
|
|
public:
|
|
template<typename ECMap, typename FCMap, typename CellSelector>
|
|
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<typename ECMap, typename FCMap, typename CellSelector>
|
|
std::vector<Point_with_info> 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<Point_with_info> 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<Point_with_info> 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<Point_with_info>& vertices) const;
|
|
|
|
template<typename ECMap, typename FCMap, typename CellSelector>
|
|
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<Point_3, float>;
|
|
std::vector<Point_Size> 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<Point_Size>;
|
|
using Size_map = CGAL::Second_of_pair_property_map<Point_Size>;
|
|
|
|
CGAL::IO::write_PLY_with_properties(ofs,
|
|
points,
|
|
CGAL::make_ply_point_writer(Point_map()),
|
|
std::make_pair(Size_map(), CGAL::IO::PLY_property<double>("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 <em>named constructor</em>. It constructs a
|
|
* sizing field of type `Adaptive_remeshing_sizing_field<Tr>`,
|
|
* designed to keep the density unchanged throughout the remeshing
|
|
* process.
|
|
*
|
|
* @returns an `Adaptive_remeshing_sizing_field<Tr>`
|
|
*
|
|
* \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<typename Tr, typename CGAL_NP_TEMPLATE_PARAMETERS>
|
|
Adaptive_remeshing_sizing_field<Tr>
|
|
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<V, V>;
|
|
using Facet = typename Tr::Facet;
|
|
|
|
auto ecmap = choose_parameter(get_parameter(np, internal_np::edge_is_constrained),
|
|
CGAL::Constant_property_map<Edge_vv, bool>(false));
|
|
auto fcmap = choose_parameter(get_parameter(np, internal_np::facet_is_constrained),
|
|
CGAL::Constant_property_map<Facet, bool>(false));
|
|
auto cell_selector = choose_parameter(get_parameter(np, internal_np::cell_selector),
|
|
CGAL::Tetrahedral_remeshing::internal::All_cells_selected<Tr>());
|
|
|
|
return Adaptive_remeshing_sizing_field<Tr>(tr, ecmap, fcmap, cell_selector);
|
|
}
|
|
|
|
|
|
template <typename Tr>
|
|
typename Adaptive_remeshing_sizing_field<Tr>::FT
|
|
Adaptive_remeshing_sizing_field<Tr>::
|
|
interpolate_on_n_vertices(
|
|
const Point_3& p,
|
|
const std::vector<Point_with_info>& 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 Tr>
|
|
typename Adaptive_remeshing_sizing_field<Tr>::FT
|
|
Adaptive_remeshing_sizing_field<Tr>::
|
|
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 Tr>
|
|
typename Adaptive_remeshing_sizing_field<Tr>::FT
|
|
Adaptive_remeshing_sizing_field<Tr>::
|
|
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 <typename Tr>
|
|
template <typename ECMap, typename FCMap, typename CellSelector>
|
|
typename Adaptive_remeshing_sizing_field<Tr>::FT
|
|
Adaptive_remeshing_sizing_field<Tr>::
|
|
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<Edge> tmp_edges;
|
|
tr.incident_edges(v, std::back_inserter(tmp_edges));
|
|
|
|
std::vector<Edge> 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<FT>(edges.size());
|
|
}
|
|
|
|
} //namespace CGAL
|
|
|
|
#endif // CGAL_TETRAHEDRAL_REMESHING_ADAPTIVE_SIZING_FIELD_H
|