replace face_patch_map with plc_face_id

the patches actually correspond to PLC faces,
which must be planar, and do not match the "face patches"
of PMP::isotropic_remeshing for example

+ add a np to CGAL::IO::write_MEDIT(ccdt) to save the corresponding PLC ids,
or just "1" everywhere as patch id
This commit is contained in:
Jane Tournois 2025-05-30 16:52:00 +02:00
parent bbc534f0b3
commit 1f3e98abac
6 changed files with 67 additions and 36 deletions

View File

@ -181,20 +181,18 @@ following example demonstrates how to build such a triangulation.
\cgalExample{Constrained_triangulation_3/conforming_constrained_Delaunay_triangulation_3_from_soup.cpp } \cgalExample{Constrained_triangulation_3/conforming_constrained_Delaunay_triangulation_3_from_soup.cpp }
\subsection CT_3_example_ccdt_fpmap Build a Conforming Constrained Delaunay Triangulation with Known Face Patches \subsection CT_3_example_ccdt_fpmap Build a Conforming Constrained Delaunay Triangulation with Known Polygon Identifiers
If the user already knows the set of patch identifiers to associate with each face, this information can be If the user already knows the set of polygon identifiers to associate with each face, this information can be
provided and preserved throughout the construction of the conforming constrained Delaunay provided and preserved throughout the construction of the conforming constrained Delaunay
triangulation. triangulation.
The following example demonstrates how to detect surface patches separated by sharp edges and use The following example demonstrates how to detect surface patches separated by sharp edges and use
this segmentation during the tetrahedralization process. this segmentation during the tetrahedralization process.
When the named parameter `face_patch_map` is specified, When the named parameter `plc_face_id` is specified, each constrained face in the 3D triangulation
the face constraint indices in the 3D triangulation (accessible is assigned to the corresponding input PLC face, identified in the provided property map.
via `CGAL::Conforming_constrained_Delaunay_triangulation_cell_data_3::face_constraint_index()`) are If this parameter is not specified, each input polygon, or PLC face, is given a unique face index.
set to the corresponding patch identifiers from the given property map. If this parameter is not provided, each
input polygon is assigned a unique face index.
\cgalExample{Constrained_triangulation_3/conforming_constrained_Delaunay_triangulation_3_fpmap.cpp} \cgalExample{Constrained_triangulation_3/conforming_constrained_Delaunay_triangulation_3_fpmap.cpp}

View File

@ -37,7 +37,7 @@ int main(int argc, char* argv[])
std::cout << "Wrote segmented mesh to " << filename << "\n"; std::cout << "Wrote segmented mesh to " << filename << "\n";
auto ccdt = CGAL::make_conforming_constrained_Delaunay_triangulation_3(mesh, auto ccdt = CGAL::make_conforming_constrained_Delaunay_triangulation_3(mesh,
CGAL::parameters::face_patch_map(face_patch_map)); CGAL::parameters::plc_face_id(face_patch_map));
std::cout << "Number of vertices in the CDT: " std::cout << "Number of vertices in the CDT: "
<< ccdt.triangulation().number_of_vertices() << '\n' << ccdt.triangulation().number_of_vertices() << '\n'
@ -47,7 +47,7 @@ int main(int argc, char* argv[])
filename = argc > 3 ? argv[3] : "out.mesh"; filename = argc > 3 ? argv[3] : "out.mesh";
std::ofstream out(filename); std::ofstream out(filename);
out.precision(17); out.precision(17);
CGAL::IO::write_MEDIT(out, ccdt); CGAL::IO::write_MEDIT(out, ccdt, CGAL::parameters::with_plc_face_id(true));
std::cout << "Wrote CDT to " << filename << "\n"; std::cout << "Wrote CDT to " << filename << "\n";
return EXIT_SUCCESS; return EXIT_SUCCESS;

View File

@ -658,14 +658,14 @@ public:
auto v_selected_map = get(CGAL::dynamic_vertex_property_t<bool>{}, mesh); auto v_selected_map = get(CGAL::dynamic_vertex_property_t<bool>{}, mesh);
int number_of_patches = 0; int number_of_patches = 0;
constexpr bool has_face_patch_map = !parameters::is_default_parameter<CGAL_NP_CLASS, internal_np::face_patch_t>::value; constexpr bool has_plc_face_id = !parameters::is_default_parameter<CGAL_NP_CLASS, internal_np::plc_face_id_t>::value;
if constexpr (has_face_patch_map) { if constexpr (has_plc_face_id) {
auto mesh_face_patch_map = parameters::get_parameter(np, internal_np::face_patch); auto mesh_plc_face_id = parameters::get_parameter(np, internal_np::plc_face_id);
using Patch_id_type = CGAL::cpp20::remove_cvref_t<decltype(get(mesh_face_patch_map, *faces(mesh).first))>; using Patch_id_type = CGAL::cpp20::remove_cvref_t<decltype(get(mesh_plc_face_id, *faces(mesh).first))>;
Patch_id_type max_patch_id{0}; Patch_id_type max_patch_id{0};
for(auto f : faces(mesh)) { for(auto f : faces(mesh)) {
max_patch_id = (std::max)(max_patch_id, get(mesh_face_patch_map, f)); max_patch_id = (std::max)(max_patch_id, get(mesh_plc_face_id, f));
} }
number_of_patches = static_cast<int>(max_patch_id + 1); number_of_patches = static_cast<int>(max_patch_id + 1);
patch_edges.resize(number_of_patches); patch_edges.resize(number_of_patches);
@ -673,9 +673,9 @@ public:
if(is_border(h, mesh)) if(is_border(h, mesh))
continue; continue;
auto f = face(h, mesh); auto f = face(h, mesh);
auto patch_id = get(mesh_face_patch_map, f); auto patch_id = get(mesh_plc_face_id, f);
auto opp = opposite(h, mesh); auto opp = opposite(h, mesh);
if(is_border(opp, mesh) || patch_id != get(mesh_face_patch_map, face(opp, mesh))) { if(is_border(opp, mesh) || patch_id != get(mesh_plc_face_id, face(opp, mesh))) {
auto va = source(h, mesh); auto va = source(h, mesh);
auto vb = target(h, mesh); auto vb = target(h, mesh);
patch_edges[patch_id].emplace_back(va, vb); patch_edges[patch_id].emplace_back(va, vb);
@ -692,7 +692,7 @@ public:
auto tr_vertex_map = get(CGAL::dynamic_vertex_property_t<Vertex_handle>(), mesh); auto tr_vertex_map = get(CGAL::dynamic_vertex_property_t<Vertex_handle>(), mesh);
Cell_handle hint_ch{}; Cell_handle hint_ch{};
for(auto v : vertices(mesh)) { for(auto v : vertices(mesh)) {
if constexpr(has_face_patch_map) { if constexpr(has_plc_face_id) {
if(false == get(v_selected_map, v)) continue; if(false == get(v_selected_map, v)) continue;
} }
auto p = get(mesh_vp_map, v); auto p = get(mesh_vp_map, v);
@ -709,7 +709,7 @@ public:
auto operator()(vertex_descriptor v) const { return get(*vertex_map, v); } auto operator()(vertex_descriptor v) const { return get(*vertex_map, v); }
} tr_vertex_fct{&tr_vertex_map}; } tr_vertex_fct{&tr_vertex_map};
if constexpr(has_face_patch_map) { if constexpr(has_plc_face_id) {
for(int i = 0; i < number_of_patches; ++i) { for(int i = 0; i < number_of_patches; ++i) {
auto& edges = patch_edges[i]; auto& edges = patch_edges[i];
if(edges.empty()) if(edges.empty())
@ -792,12 +792,12 @@ public:
auto point_map = choose_parameter(get_parameter(np, internal_np::point_map), auto point_map = choose_parameter(get_parameter(np, internal_np::point_map),
CGAL::Identity_property_map<PointRange_value_type>{}); CGAL::Identity_property_map<PointRange_value_type>{});
constexpr bool has_face_patch_map = !parameters::is_default_parameter<NamedParams, internal_np::face_patch_t>::value; constexpr bool has_plc_face_id = !parameters::is_default_parameter<NamedParams, internal_np::plc_face_id_t>::value;
using std::cbegin; using std::cbegin;
using std::cend; using std::cend;
if constexpr (false == has_face_patch_map) { if constexpr (false == has_plc_face_id) {
using Vertex_handle = typename Triangulation::Vertex_handle; using Vertex_handle = typename Triangulation::Vertex_handle;
using Cell_handle = typename Triangulation::Cell_handle; using Cell_handle = typename Triangulation::Cell_handle;
using Vec_vertex_handle = std::vector<Vertex_handle>; using Vec_vertex_handle = std::vector<Vertex_handle>;
@ -836,7 +836,7 @@ public:
using PID = CGAL::cpp20::remove_cvref_t<decltype(get(polygon_patch_map, 0u))>; using PID = CGAL::cpp20::remove_cvref_t<decltype(get(polygon_patch_map, 0u))>;
constexpr auto invalid_patch_id = (std::numeric_limits<PID>::max)(); constexpr auto invalid_patch_id = (std::numeric_limits<PID>::max)();
Surface_mesh surface_mesh; Surface_mesh surface_mesh;
auto face_patch_pmap = auto plc_face_id_pmap =
surface_mesh.template add_property_map<face_descriptor, PID>("fpm", invalid_patch_id).first; surface_mesh.template add_property_map<face_descriptor, PID>("fpm", invalid_patch_id).first;
auto to_point = [point_map](const PointRange_value_type& v) { return get(point_map, v); }; auto to_point = [point_map](const PointRange_value_type& v) { return get(point_map, v); };
@ -849,14 +849,14 @@ public:
PMP::orient_polygon_soup(rw_points, rw_polygons); PMP::orient_polygon_soup(rw_points, rw_polygons);
auto polygon_to_face_output_iterator = boost::make_function_output_iterator( auto polygon_to_face_output_iterator = boost::make_function_output_iterator(
[face_patch_pmap, polygon_patch_map](std::pair<std::size_t, face_descriptor> pid_f) { [plc_face_id_pmap, polygon_patch_map](std::pair<std::size_t, face_descriptor> pid_f) {
put(face_patch_pmap, pid_f.second, get(polygon_patch_map, pid_f.first)); put(plc_face_id_pmap, pid_f.second, get(polygon_patch_map, pid_f.first));
}); });
PMP::polygon_soup_to_polygon_mesh(std::move(rw_points), std::move(rw_polygons), surface_mesh, PMP::polygon_soup_to_polygon_mesh(std::move(rw_points), std::move(rw_polygons), surface_mesh,
np.polygon_to_face_output_iterator(polygon_to_face_output_iterator)); np.polygon_to_face_output_iterator(polygon_to_face_output_iterator));
Conforming_constrained_Delaunay_triangulation_3 ccdt{std::move(surface_mesh), Conforming_constrained_Delaunay_triangulation_3 ccdt{std::move(surface_mesh),
CGAL::parameters::face_patch_map(face_patch_pmap) CGAL::parameters::plc_face_id(plc_face_id_pmap)
.do_self_intersection_tests(false) .do_self_intersection_tests(false)
.geom_traits(cdt_impl.geom_traits())}; .geom_traits(cdt_impl.geom_traits())};
*this = std::move(ccdt); *this = std::move(ccdt);

View File

@ -12,6 +12,10 @@
#include "CGAL/type_traits.h" #include "CGAL/type_traits.h"
#include "CGAL/unordered_flat_map.h" #include "CGAL/unordered_flat_map.h"
#include <CGAL/Conforming_constrained_Delaunay_triangulation_3.h> #include <CGAL/Conforming_constrained_Delaunay_triangulation_3.h>
#include <CGAL/Named_function_parameters.h>
#include <CGAL/boost/graph/named_params_helper.h>
#include <CGAL/IO/File_medit.h> #include <CGAL/IO/File_medit.h>
#include <ostream> #include <ostream>
@ -27,12 +31,24 @@ namespace IO
* file format. * file format.
* @param os the output stream * @param os the output stream
* @param ccdt the conforming constrained Delaunay triangulation to be written * @param ccdt the conforming constrained Delaunay triangulation to be written
* \param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below
* *
* \cgalNamedParamsBegin
* \cgalParamNBegin{with_plc_face_id}
* \cgalParamDescription{a Boolean activating the numbering of PLC face identifiers in the output}
* \cgalParamType{Boolean}
* \cgalParamDefault{`false`}
* \cgalParamNEnd
* \cgalNamedParamsEnd
* \see \ref IOStreamMedit * \see \ref IOStreamMedit
*/ */
template <typename Traits, typename Tr> template <typename Traits,
typename Tr,
typename NamedParameters = parameters::Default_named_parameters>
void write_MEDIT(std::ostream& os, void write_MEDIT(std::ostream& os,
const Conforming_constrained_Delaunay_triangulation_3<Traits, Tr>& ccdt) const Conforming_constrained_Delaunay_triangulation_3<Traits, Tr>& ccdt,
const NamedParameters& np = parameters::default_values())
{ {
const auto& tr = ccdt.triangulation(); const auto& tr = ccdt.triangulation();
@ -62,15 +78,21 @@ void write_MEDIT(std::ostream& os,
} }
} }
using parameters::choose_parameter;
using parameters::get_parameter;
const bool has_plc_face_id
= choose_parameter(get_parameter(np, internal_np::with_plc_face_id), false);
auto plc_patch_map = boost::make_function_property_map<Facet>([&](const Facet& f)
{ return has_plc_face_id ? f.first->ccdt_3_data().face_constraint_index(f.second) + 1 : 1; });
return SMDS_3::output_to_medit(os, return SMDS_3::output_to_medit(os,
tr, tr,
tr.finite_vertex_handles(), tr.finite_vertex_handles(),
ccdt.constrained_facets(), ccdt.constrained_facets(),
tr.finite_cell_handles(), tr.finite_cell_handles(),
boost::make_function_property_map<Vertex_handle>([](Vertex_handle) { return 0; }), boost::make_function_property_map<Vertex_handle>([](Vertex_handle) { return 0; }),
boost::make_function_property_map<Facet>([](const Facet& f) { plc_patch_map,
return f.first->ccdt_3_data().face_constraint_index(f.second) + 1;
}),
boost::make_function_property_map<Cell_handle>([&](Cell_handle ch) { boost::make_function_property_map<Cell_handle>([&](Cell_handle ch) {
return cells_in_domain[ch] ? 1 : 0; return cells_in_domain[ch] ? 1 : 0;
})); }));

View File

@ -20,6 +20,9 @@
#include <CGAL/Triangulation_3.h> #include <CGAL/Triangulation_3.h>
#include <CGAL/Triangulation_data_structure_3.h> #include <CGAL/Triangulation_data_structure_3.h>
#include <CGAL/Named_function_parameters.h>
#include <CGAL/boost/graph/named_params_helper.h>
namespace CGAL { namespace CGAL {
/*! /*!
@ -39,14 +42,14 @@ namespace CGAL {
during the triangulation process. during the triangulation process.
By default, each face of the input is considered a polygonal constraint for the triangulation. The By default, each face of the input is considered a polygonal constraint for the triangulation. The
named parameter `face_patch_map` can be used to describe larger polygonal constraints, possibly with holes. If named parameter `plc_face_id` can be used to describe larger polygonal constraints, possibly with holes. If
used, this parameter must be a property map that associates each face of the input with a patch used, this parameter must be a property map that associates each face of the input with a patch
identifier. Faces with the same patch identifier are considered part of the same surface patch. Each of these identifier. Faces with the same patch identifier are considered part of the same surface patch. Each of these
surface patches (defined as the union of the input faces with a given patch identifier) is expected to be a polygon or surface patches (defined as the union of the input faces with a given patch identifier) is expected to be a polygon or
a polygon with holes, with coplanar vertices (or nearly coplanar up to the precision of the number type used). a polygon with holes, with coplanar vertices (or nearly coplanar up to the precision of the number type used).
The generated triangulation will conform to the faces of the input, or to the surface patches The generated triangulation will conform to the faces of the input, or to the surface patches
described by the `face_patch_map` property map if provided. described by the `plc_face_id` property map if provided.
\pre The input data must not be coplanar. \pre The input data must not be coplanar.
@ -108,11 +111,13 @@ namespace CGAL {
* must be available in `PolygonMesh`.} * must be available in `PolygonMesh`.}
* \cgalParamNEnd * \cgalParamNEnd
* *
* \cgalParamNBegin{face_patch_map} * \cgalParamNBegin{plc_face_id}
* \cgalParamDescription{a property map associating a patch identifier to each face of `mesh`} * \cgalParamDescription{a property map associating a patch identifier to each face of `mesh`.
* Each identifier corresponds to a planar surface patch. Each surface
* patch can be composed of several faces of `mesh`, forming a planar polygon.}
* \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits<PolygonMesh>::%face_descriptor` * \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits<PolygonMesh>::%face_descriptor`
* as key type and with any value type that is a *regular* type (model of `Regular`)} * as key type and with any value type that is a *regular* type (model of `Regular`)}
* \cgalParamExtra{If this parameter is omitted, each face of the mesh is considered a separate patch. * \cgalParamExtra{If this parameter is omitted, each face of `mesh` is considered a separate patch.
* Faces with the same patch identifier are considered part of the same surface patch.} * Faces with the same patch identifier are considered part of the same surface patch.}
* \cgalParamNEnd * \cgalParamNEnd
* *
@ -182,8 +187,10 @@ auto make_conforming_constrained_Delaunay_triangulation_3(const PolygonMesh &mes
* \cgalParamDefault{`CGAL::Identity_property_map`} * \cgalParamDefault{`CGAL::Identity_property_map`}
* \cgalParamNEnd * \cgalParamNEnd
* *
* \cgalParamNBegin{face_patch_map} * \cgalParamNBegin{plc_face_id}
* \cgalParamDescription{a property map associating a patch identifier to each face of `soup`} * \cgalParamDescription{a property map associating a patch identifier to each face of `soup`.
* Each identifier corresponds to a planar surface patch. Each surface
* patch can be composed of several faces of `soup`, forming a planar polygon.}
* \cgalParamType{a class model of `ReadWritePropertyMap` with `std::size_t` * \cgalParamType{a class model of `ReadWritePropertyMap` with `std::size_t`
* as key type and with any value type that is a *regular* type (model of `Regular`)} * as key type and with any value type that is a *regular* type (model of `Regular`)}
* \cgalParamExtra{If this parameter is omitted, each face of the polygon soup is considered a separate patch.} * \cgalParamExtra{If this parameter is omitted, each face of the polygon soup is considered a separate patch.}

View File

@ -404,3 +404,7 @@ CGAL_add_named_parameter(do_not_modify_geometry_t, do_not_modify_geometry, do_no
//List of named parameters used in Straight_skeleton_2 //List of named parameters used in Straight_skeleton_2
CGAL_add_named_parameter_with_compatibility_ref_only(angles_param_t, angles_param, angles) CGAL_add_named_parameter_with_compatibility_ref_only(angles_param_t, angles_param, angles)
CGAL_add_named_parameter(maximum_height_t, maximum_height, maximum_height) CGAL_add_named_parameter(maximum_height_t, maximum_height, maximum_height)
// List of named parameters used in the package 'Constrained_triangulation_3'
CGAL_add_named_parameter(plc_face_id_t, plc_face_id, plc_face_id)
CGAL_add_named_parameter(with_plc_face_id_t, with_plc_face_id, with_plc_face_id)